From 39ac9b39e080e72b4367371dd52c1ab4c15187f6 Mon Sep 17 00:00:00 2001 From: sethkfman Date: Fri, 17 Jun 2022 15:02:23 -0600 Subject: [PATCH 1/6] added logic to update testnet ticker symbol --- src/constants.ts | 6 +++++- src/network/NetworkController.test.ts | 9 +++++++++ src/network/NetworkController.ts | 12 ++++++++++-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index a173b7b9a5..dab8916a8f 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -30,8 +30,12 @@ export const ASSET_TYPES = { UNKNOWN: 'UNKNOWN', }; +type tickerType = { + [key: string]: string; +}; + // TICKER SYMBOLS -export const TESTNET_TICKER_SYMBOLS = { +export const TESTNET_TICKER_SYMBOLS: tickerType = { RINKEBY: 'RinkebyETH', GOERLI: 'GoerliETH', ROPSTEN: 'RopstenETH', diff --git a/src/network/NetworkController.test.ts b/src/network/NetworkController.test.ts index 5a36db6184..442a4c32d0 100644 --- a/src/network/NetworkController.test.ts +++ b/src/network/NetworkController.test.ts @@ -147,6 +147,15 @@ describe('NetworkController', () => { expect(controller.state.isCustomNetwork).toBe(false); }); + it('should set new testnet provider type', () => { + const controller = new NetworkController(); + controller.config.infuraProjectId = '0x0000'; + controller.setProviderType('rinkeby' as NetworkType); + expect(controller.state.provider.type).toBe('rinkeby'); + expect(controller.state.provider.ticker).toBe('RinkebyETH'); + expect(controller.state.isCustomNetwork).toBe(false); + }); + it('should throw when setting an unrecognized provider type', () => { const controller = new NetworkController(); expect(() => controller.setProviderType('junk' as NetworkType)).toThrow( diff --git a/src/network/NetworkController.ts b/src/network/NetworkController.ts index 82053826c0..41942e55fa 100644 --- a/src/network/NetworkController.ts +++ b/src/network/NetworkController.ts @@ -4,7 +4,7 @@ import createInfuraProvider from 'eth-json-rpc-infura/src/createProvider'; import createMetamaskProvider from 'web3-provider-engine/zero'; import { Mutex } from 'async-mutex'; import { BaseController, BaseConfig, BaseState } from '../BaseController'; -import { MAINNET, RPC } from '../constants'; +import { MAINNET, RPC, TESTNET_TICKER_SYMBOLS } from '../constants'; /** * Human-readable network name @@ -282,10 +282,18 @@ export class NetworkController extends BaseController< setProviderType(type: NetworkType) { const { rpcTarget, chainId, nickname, ...providerState } = this.state.provider; + + // If testnet the ticker symbol should use a testnet prefix + const testNetTicker = TESTNET_TICKER_SYMBOLS[type.toUpperCase()]; + this.update({ provider: { ...providerState, - ...{ type, ticker: 'ETH', chainId: NetworksChainId[type] }, + ...{ + type, + ticker: testNetTicker || 'ETH', + chainId: NetworksChainId[type], + }, }, }); this.refreshNetwork(); From cf78cf073c7535d86e200c4d79bb0b84eb232c12 Mon Sep 17 00:00:00 2001 From: sethkfman Date: Fri, 17 Jun 2022 15:10:07 -0600 Subject: [PATCH 2/6] added mainet ticker test --- src/network/NetworkController.test.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/network/NetworkController.test.ts b/src/network/NetworkController.test.ts index 442a4c32d0..fe376988ee 100644 --- a/src/network/NetworkController.test.ts +++ b/src/network/NetworkController.test.ts @@ -156,6 +156,15 @@ describe('NetworkController', () => { expect(controller.state.isCustomNetwork).toBe(false); }); + it('should set mainnet provider type', () => { + const controller = new NetworkController(); + controller.config.infuraProjectId = '0x0000'; + controller.setProviderType('mainnet' as NetworkType); + expect(controller.state.provider.type).toBe('mainnet'); + expect(controller.state.provider.ticker).toBe('ETH'); + expect(controller.state.isCustomNetwork).toBe(false); + }); + it('should throw when setting an unrecognized provider type', () => { const controller = new NetworkController(); expect(() => controller.setProviderType('junk' as NetworkType)).toThrow( From c27c02557480010c5a9e12ee3579e7a2a8ed3c37 Mon Sep 17 00:00:00 2001 From: sethkfman Date: Tue, 21 Jun 2022 14:59:07 -0600 Subject: [PATCH 3/6] added dist folder --- .gitignore | 1 - dist/BaseController.d.ts | 109 ++ dist/BaseController.js | 142 ++ dist/BaseController.js.map | 1 + dist/BaseControllerV2.d.ts | 134 ++ dist/BaseControllerV2.js | 116 ++ dist/BaseControllerV2.js.map | 1 + dist/ComposableController.d.ts | 42 + dist/ComposableController.js | 62 + dist/ComposableController.js.map | 1 + dist/ControllerMessenger.d.ts | 352 +++++ dist/ControllerMessenger.js | 363 +++++ dist/ControllerMessenger.js.map | 1 + dist/announcement/AnnouncementController.d.ts | 63 + dist/announcement/AnnouncementController.js | 54 + .../AnnouncementController.js.map | 1 + dist/apis/crypto-compare.d.ts | 12 + dist/apis/crypto-compare.js | 67 + dist/apis/crypto-compare.js.map | 1 + dist/apis/token-service.d.ts | 29 + dist/apis/token-service.js | 133 ++ dist/apis/token-service.js.map | 1 + dist/approval/ApprovalController.d.ts | 278 ++++ dist/approval/ApprovalController.js | 381 +++++ dist/approval/ApprovalController.js.map | 1 + dist/assets/AccountTrackerController.d.ts | 88 ++ dist/assets/AccountTrackerController.js | 138 ++ dist/assets/AccountTrackerController.js.map | 1 + dist/assets/AssetsContractController.d.ts | 176 +++ dist/assets/AssetsContractController.js | 323 +++++ dist/assets/AssetsContractController.js.map | 1 + .../CollectibleDetectionController.d.ts | 178 +++ dist/assets/CollectibleDetectionController.js | 205 +++ .../CollectibleDetectionController.js.map | 1 + dist/assets/CollectiblesController.d.ts | 377 +++++ dist/assets/CollectiblesController.js | 759 ++++++++++ dist/assets/CollectiblesController.js.map | 1 + dist/assets/CurrencyRateController.d.ts | 99 ++ dist/assets/CurrencyRateController.js | 194 +++ dist/assets/CurrencyRateController.js.map | 1 + .../ERC1155/ERC1155Standard.d.ts | 77 ++ .../ERC1155/ERC1155Standard.js | 173 +++ .../ERC1155/ERC1155Standard.js.map | 1 + .../ERC721/ERC721Standard.d.ts | 88 ++ .../ERC721/ERC721Standard.js | 226 +++ .../ERC721/ERC721Standard.js.map | 1 + dist/assets/Standards/ERC20Standard.d.ts | 42 + dist/assets/Standards/ERC20Standard.js | 134 ++ dist/assets/Standards/ERC20Standard.js.map | 1 + dist/assets/Standards/standards-types.d.ts | 14 + dist/assets/Standards/standards-types.js | 3 + dist/assets/Standards/standards-types.js.map | 1 + dist/assets/TokenBalancesController.d.ts | 69 + dist/assets/TokenBalancesController.js | 94 ++ dist/assets/TokenBalancesController.js.map | 1 + dist/assets/TokenDetectionController.d.ts | 84 ++ dist/assets/TokenDetectionController.js | 184 +++ dist/assets/TokenDetectionController.js.map | 1 + dist/assets/TokenListController.d.ts | 101 ++ dist/assets/TokenListController.js | 207 +++ dist/assets/TokenListController.js.map | 1 + dist/assets/TokenRatesController.d.ts | 167 +++ dist/assets/TokenRatesController.js | 285 ++++ dist/assets/TokenRatesController.js.map | 1 + dist/assets/TokensController.d.ts | 236 ++++ dist/assets/TokensController.js | 532 +++++++ dist/assets/TokensController.js.map | 1 + dist/assets/assetsUtil.d.ts | 30 + dist/assets/assetsUtil.js | 91 ++ dist/assets/assetsUtil.js.map | 1 + dist/constants.d.ts | 29 + dist/constants.js | 41 + dist/constants.js.map | 1 + dist/gas/GasFeeController.d.ts | 230 ++++ dist/gas/GasFeeController.js | 243 ++++ dist/gas/GasFeeController.js.map | 1 + dist/gas/determineGasFeeCalculations.d.ts | 40 + dist/gas/determineGasFeeCalculations.js | 87 ++ dist/gas/determineGasFeeCalculations.js.map | 1 + dist/gas/fetchBlockFeeHistory.d.ts | 115 ++ dist/gas/fetchBlockFeeHistory.js | 202 +++ dist/gas/fetchBlockFeeHistory.js.map | 1 + .../fetchGasEstimatesViaEthFeeHistory.d.ts | 21 + dist/gas/fetchGasEstimatesViaEthFeeHistory.js | 53 + .../fetchGasEstimatesViaEthFeeHistory.js.map | 1 + ...ulateGasFeeEstimatesForPriorityLevels.d.ts | 16 + ...lculateGasFeeEstimatesForPriorityLevels.js | 89 ++ ...ateGasFeeEstimatesForPriorityLevels.js.map | 1 + .../fetchLatestBlock.d.ts | 10 + .../fetchLatestBlock.js | 32 + .../fetchLatestBlock.js.map | 1 + .../medianOf.d.ts | 10 + .../medianOf.js | 17 + .../medianOf.js.map | 1 + .../types.d.ts | 10 + .../types.js | 3 + .../types.js.map | 1 + dist/gas/gas-util.d.ts | 41 + dist/gas/gas-util.js | 140 ++ dist/gas/gas-util.js.map | 1 + dist/index.d.ts | 33 + dist/index.js | 66 + dist/index.js.map | 1 + dist/keyring/KeyringController.d.ts | 308 +++++ dist/keyring/KeyringController.js | 636 +++++++++ dist/keyring/KeyringController.js.map | 1 + .../AbstractMessageManager.d.ts | 172 +++ .../message-manager/AbstractMessageManager.js | 167 +++ .../AbstractMessageManager.js.map | 1 + dist/message-manager/MessageManager.d.ts | 77 ++ dist/message-manager/MessageManager.js | 83 ++ dist/message-manager/MessageManager.js.map | 1 + .../PersonalMessageManager.d.ts | 77 ++ .../message-manager/PersonalMessageManager.js | 83 ++ .../PersonalMessageManager.js.map | 1 + dist/message-manager/TypedMessageManager.d.ts | 100 ++ dist/message-manager/TypedMessageManager.js | 109 ++ .../TypedMessageManager.js.map | 1 + dist/network/NetworkController.d.ts | 126 ++ dist/network/NetworkController.js | 251 ++++ dist/network/NetworkController.js.map | 1 + dist/notification/NotificationController.d.ts | 84 ++ dist/notification/NotificationController.js | 85 ++ .../NotificationController.js.map | 1 + dist/permissions/Caveat.d.ts | 171 +++ dist/permissions/Caveat.js | 42 + dist/permissions/Caveat.js.map | 1 + dist/permissions/Permission.d.ts | 426 ++++++ dist/permissions/Permission.js | 66 + dist/permissions/Permission.js.map | 1 + dist/permissions/PermissionController.d.ts | 870 ++++++++++++ dist/permissions/PermissionController.js | 1220 +++++++++++++++++ dist/permissions/PermissionController.js.map | 1 + dist/permissions/errors.d.ts | 150 ++ dist/permissions/errors.js | 189 +++ dist/permissions/errors.js.map | 1 + dist/permissions/index.d.ts | 5 + dist/permissions/index.js | 35 + dist/permissions/index.js.map | 1 + dist/permissions/permission-middleware.d.ts | 32 + dist/permissions/permission-middleware.js | 64 + dist/permissions/permission-middleware.js.map | 1 + .../rpc-methods/getPermissions.d.ts | 7 + .../permissions/rpc-methods/getPermissions.js | 38 + .../rpc-methods/getPermissions.js.map | 1 + dist/permissions/rpc-methods/index.d.ts | 4 + dist/permissions/rpc-methods/index.js | 7 + dist/permissions/rpc-methods/index.js.map | 1 + .../rpc-methods/requestPermissions.d.ts | 16 + .../rpc-methods/requestPermissions.js | 55 + .../rpc-methods/requestPermissions.js.map | 1 + dist/permissions/utils.d.ts | 15 + dist/permissions/utils.js | 9 + dist/permissions/utils.js.map | 1 + dist/ratelimit/RateLimitController.d.ts | 82 ++ dist/ratelimit/RateLimitController.js | 110 ++ dist/ratelimit/RateLimitController.js.map | 1 + .../SubjectMetadataController.d.ts | 85 ++ .../SubjectMetadataController.js | 117 ++ .../SubjectMetadataController.js.map | 1 + dist/subject-metadata/index.d.ts | 1 + dist/subject-metadata/index.js | 18 + dist/subject-metadata/index.js.map | 1 + dist/third-party/EnsController.d.ts | 76 + dist/third-party/EnsController.js | 108 ++ dist/third-party/EnsController.js.map | 1 + dist/third-party/PhishingController.d.ts | 118 ++ dist/third-party/PhishingController.js | 165 +++ dist/third-party/PhishingController.js.map | 1 + dist/transaction/TransactionController.d.ts | 460 +++++++ dist/transaction/TransactionController.js | 915 +++++++++++++ dist/transaction/TransactionController.js.map | 1 + dist/transaction/mocks/txsMock.d.ts | 63 + dist/transaction/mocks/txsMock.js | 515 +++++++ dist/transaction/mocks/txsMock.js.map | 1 + dist/user/AddressBookController.d.ts | 83 ++ dist/user/AddressBookController.js | 87 ++ dist/user/AddressBookController.js.map | 1 + dist/user/PreferencesController.d.ts | 157 +++ dist/user/PreferencesController.js | 243 ++++ dist/user/PreferencesController.js.map | 1 + dist/util.d.ts | 357 +++++ dist/util.js | 871 ++++++++++++ dist/util.js.map | 1 + 184 files changed, 19710 insertions(+), 1 deletion(-) create mode 100644 dist/BaseController.d.ts create mode 100644 dist/BaseController.js create mode 100644 dist/BaseController.js.map create mode 100644 dist/BaseControllerV2.d.ts create mode 100644 dist/BaseControllerV2.js create mode 100644 dist/BaseControllerV2.js.map create mode 100644 dist/ComposableController.d.ts create mode 100644 dist/ComposableController.js create mode 100644 dist/ComposableController.js.map create mode 100644 dist/ControllerMessenger.d.ts create mode 100644 dist/ControllerMessenger.js create mode 100644 dist/ControllerMessenger.js.map create mode 100644 dist/announcement/AnnouncementController.d.ts create mode 100644 dist/announcement/AnnouncementController.js create mode 100644 dist/announcement/AnnouncementController.js.map create mode 100644 dist/apis/crypto-compare.d.ts create mode 100644 dist/apis/crypto-compare.js create mode 100644 dist/apis/crypto-compare.js.map create mode 100644 dist/apis/token-service.d.ts create mode 100644 dist/apis/token-service.js create mode 100644 dist/apis/token-service.js.map create mode 100644 dist/approval/ApprovalController.d.ts create mode 100644 dist/approval/ApprovalController.js create mode 100644 dist/approval/ApprovalController.js.map create mode 100644 dist/assets/AccountTrackerController.d.ts create mode 100644 dist/assets/AccountTrackerController.js create mode 100644 dist/assets/AccountTrackerController.js.map create mode 100644 dist/assets/AssetsContractController.d.ts create mode 100644 dist/assets/AssetsContractController.js create mode 100644 dist/assets/AssetsContractController.js.map create mode 100644 dist/assets/CollectibleDetectionController.d.ts create mode 100644 dist/assets/CollectibleDetectionController.js create mode 100644 dist/assets/CollectibleDetectionController.js.map create mode 100644 dist/assets/CollectiblesController.d.ts create mode 100644 dist/assets/CollectiblesController.js create mode 100644 dist/assets/CollectiblesController.js.map create mode 100644 dist/assets/CurrencyRateController.d.ts create mode 100644 dist/assets/CurrencyRateController.js create mode 100644 dist/assets/CurrencyRateController.js.map create mode 100644 dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.d.ts create mode 100644 dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.js create mode 100644 dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.js.map create mode 100644 dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.d.ts create mode 100644 dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.js create mode 100644 dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.js.map create mode 100644 dist/assets/Standards/ERC20Standard.d.ts create mode 100644 dist/assets/Standards/ERC20Standard.js create mode 100644 dist/assets/Standards/ERC20Standard.js.map create mode 100644 dist/assets/Standards/standards-types.d.ts create mode 100644 dist/assets/Standards/standards-types.js create mode 100644 dist/assets/Standards/standards-types.js.map create mode 100644 dist/assets/TokenBalancesController.d.ts create mode 100644 dist/assets/TokenBalancesController.js create mode 100644 dist/assets/TokenBalancesController.js.map create mode 100644 dist/assets/TokenDetectionController.d.ts create mode 100644 dist/assets/TokenDetectionController.js create mode 100644 dist/assets/TokenDetectionController.js.map create mode 100644 dist/assets/TokenListController.d.ts create mode 100644 dist/assets/TokenListController.js create mode 100644 dist/assets/TokenListController.js.map create mode 100644 dist/assets/TokenRatesController.d.ts create mode 100644 dist/assets/TokenRatesController.js create mode 100644 dist/assets/TokenRatesController.js.map create mode 100644 dist/assets/TokensController.d.ts create mode 100644 dist/assets/TokensController.js create mode 100644 dist/assets/TokensController.js.map create mode 100644 dist/assets/assetsUtil.d.ts create mode 100644 dist/assets/assetsUtil.js create mode 100644 dist/assets/assetsUtil.js.map create mode 100644 dist/constants.d.ts create mode 100644 dist/constants.js create mode 100644 dist/constants.js.map create mode 100644 dist/gas/GasFeeController.d.ts create mode 100644 dist/gas/GasFeeController.js create mode 100644 dist/gas/GasFeeController.js.map create mode 100644 dist/gas/determineGasFeeCalculations.d.ts create mode 100644 dist/gas/determineGasFeeCalculations.js create mode 100644 dist/gas/determineGasFeeCalculations.js.map create mode 100644 dist/gas/fetchBlockFeeHistory.d.ts create mode 100644 dist/gas/fetchBlockFeeHistory.js create mode 100644 dist/gas/fetchBlockFeeHistory.js.map create mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory.d.ts create mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory.js create mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory.js.map create mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.d.ts create mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.js create mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.js.map create mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.d.ts create mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.js create mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.js.map create mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.d.ts create mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.js create mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.js.map create mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/types.d.ts create mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/types.js create mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/types.js.map create mode 100644 dist/gas/gas-util.d.ts create mode 100644 dist/gas/gas-util.js create mode 100644 dist/gas/gas-util.js.map create mode 100644 dist/index.d.ts create mode 100644 dist/index.js create mode 100644 dist/index.js.map create mode 100644 dist/keyring/KeyringController.d.ts create mode 100644 dist/keyring/KeyringController.js create mode 100644 dist/keyring/KeyringController.js.map create mode 100644 dist/message-manager/AbstractMessageManager.d.ts create mode 100644 dist/message-manager/AbstractMessageManager.js create mode 100644 dist/message-manager/AbstractMessageManager.js.map create mode 100644 dist/message-manager/MessageManager.d.ts create mode 100644 dist/message-manager/MessageManager.js create mode 100644 dist/message-manager/MessageManager.js.map create mode 100644 dist/message-manager/PersonalMessageManager.d.ts create mode 100644 dist/message-manager/PersonalMessageManager.js create mode 100644 dist/message-manager/PersonalMessageManager.js.map create mode 100644 dist/message-manager/TypedMessageManager.d.ts create mode 100644 dist/message-manager/TypedMessageManager.js create mode 100644 dist/message-manager/TypedMessageManager.js.map create mode 100644 dist/network/NetworkController.d.ts create mode 100644 dist/network/NetworkController.js create mode 100644 dist/network/NetworkController.js.map create mode 100644 dist/notification/NotificationController.d.ts create mode 100644 dist/notification/NotificationController.js create mode 100644 dist/notification/NotificationController.js.map create mode 100644 dist/permissions/Caveat.d.ts create mode 100644 dist/permissions/Caveat.js create mode 100644 dist/permissions/Caveat.js.map create mode 100644 dist/permissions/Permission.d.ts create mode 100644 dist/permissions/Permission.js create mode 100644 dist/permissions/Permission.js.map create mode 100644 dist/permissions/PermissionController.d.ts create mode 100644 dist/permissions/PermissionController.js create mode 100644 dist/permissions/PermissionController.js.map create mode 100644 dist/permissions/errors.d.ts create mode 100644 dist/permissions/errors.js create mode 100644 dist/permissions/errors.js.map create mode 100644 dist/permissions/index.d.ts create mode 100644 dist/permissions/index.js create mode 100644 dist/permissions/index.js.map create mode 100644 dist/permissions/permission-middleware.d.ts create mode 100644 dist/permissions/permission-middleware.js create mode 100644 dist/permissions/permission-middleware.js.map create mode 100644 dist/permissions/rpc-methods/getPermissions.d.ts create mode 100644 dist/permissions/rpc-methods/getPermissions.js create mode 100644 dist/permissions/rpc-methods/getPermissions.js.map create mode 100644 dist/permissions/rpc-methods/index.d.ts create mode 100644 dist/permissions/rpc-methods/index.js create mode 100644 dist/permissions/rpc-methods/index.js.map create mode 100644 dist/permissions/rpc-methods/requestPermissions.d.ts create mode 100644 dist/permissions/rpc-methods/requestPermissions.js create mode 100644 dist/permissions/rpc-methods/requestPermissions.js.map create mode 100644 dist/permissions/utils.d.ts create mode 100644 dist/permissions/utils.js create mode 100644 dist/permissions/utils.js.map create mode 100644 dist/ratelimit/RateLimitController.d.ts create mode 100644 dist/ratelimit/RateLimitController.js create mode 100644 dist/ratelimit/RateLimitController.js.map create mode 100644 dist/subject-metadata/SubjectMetadataController.d.ts create mode 100644 dist/subject-metadata/SubjectMetadataController.js create mode 100644 dist/subject-metadata/SubjectMetadataController.js.map create mode 100644 dist/subject-metadata/index.d.ts create mode 100644 dist/subject-metadata/index.js create mode 100644 dist/subject-metadata/index.js.map create mode 100644 dist/third-party/EnsController.d.ts create mode 100644 dist/third-party/EnsController.js create mode 100644 dist/third-party/EnsController.js.map create mode 100644 dist/third-party/PhishingController.d.ts create mode 100644 dist/third-party/PhishingController.js create mode 100644 dist/third-party/PhishingController.js.map create mode 100644 dist/transaction/TransactionController.d.ts create mode 100644 dist/transaction/TransactionController.js create mode 100644 dist/transaction/TransactionController.js.map create mode 100644 dist/transaction/mocks/txsMock.d.ts create mode 100644 dist/transaction/mocks/txsMock.js create mode 100644 dist/transaction/mocks/txsMock.js.map create mode 100644 dist/user/AddressBookController.d.ts create mode 100644 dist/user/AddressBookController.js create mode 100644 dist/user/AddressBookController.js.map create mode 100644 dist/user/PreferencesController.d.ts create mode 100644 dist/user/PreferencesController.js create mode 100644 dist/user/PreferencesController.js.map create mode 100644 dist/util.d.ts create mode 100644 dist/util.js create mode 100644 dist/util.js.map diff --git a/.gitignore b/.gitignore index 8c57c00a02..a5852b2da3 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,4 @@ package-lock.json .DS_STORE coverage -dist docs diff --git a/dist/BaseController.d.ts b/dist/BaseController.d.ts new file mode 100644 index 0000000000..31625f7066 --- /dev/null +++ b/dist/BaseController.d.ts @@ -0,0 +1,109 @@ +/** + * State change callbacks + */ +export declare type Listener = (state: T) => void; +/** + * @type BaseConfig + * + * Base controller configuration + * @property disabled - Determines if this controller is enabled + */ +export interface BaseConfig { + disabled?: boolean; +} +/** + * @type BaseState + * + * Base state representation + * @property name - Unique name for this controller + */ +export interface BaseState { + name?: string; +} +/** + * Controller class that provides configuration, state management, and subscriptions + */ +export declare class BaseController { + /** + * Default options used to configure this controller + */ + defaultConfig: C; + /** + * Default state set on this controller + */ + defaultState: S; + /** + * Determines if listeners are notified of state changes + */ + disabled: boolean; + /** + * Name of this controller used during composition + */ + name: string; + private readonly initialConfig; + private readonly initialState; + private internalConfig; + private internalState; + private internalListeners; + /** + * Creates a BaseController instance. Both initial state and initial + * configuration options are merged with defaults upon initialization. + * + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor(config?: Partial, state?: Partial); + /** + * Enables the controller. This sets each config option as a member + * variable on this instance and triggers any defined setters. This + * also sets initial state and triggers any listeners. + * + * @returns This controller instance. + */ + protected initialize(): this; + /** + * Retrieves current controller configuration options. + * + * @returns The current configuration. + */ + get config(): C; + /** + * Retrieves current controller state. + * + * @returns The current state. + */ + get state(): S; + /** + * Updates controller configuration. + * + * @param config - New configuration options. + * @param overwrite - Overwrite config instead of merging. + * @param fullUpdate - Boolean that defines if the update is partial or not. + */ + configure(config: Partial, overwrite?: boolean, fullUpdate?: boolean): void; + /** + * Notifies all subscribed listeners of current state. + */ + notify(): void; + /** + * Adds new listener to be notified of state changes. + * + * @param listener - The callback triggered when state changes. + */ + subscribe(listener: Listener): void; + /** + * Removes existing listener from receiving state changes. + * + * @param listener - The callback to remove. + * @returns `true` if a listener is found and unsubscribed. + */ + unsubscribe(listener: Listener): boolean; + /** + * Updates controller state. + * + * @param state - The new state. + * @param overwrite - Overwrite state instead of merging. + */ + update(state: Partial, overwrite?: boolean): void; +} +export default BaseController; diff --git a/dist/BaseController.js b/dist/BaseController.js new file mode 100644 index 0000000000..bb2b20ad0d --- /dev/null +++ b/dist/BaseController.js @@ -0,0 +1,142 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.BaseController = void 0; +/** + * Controller class that provides configuration, state management, and subscriptions + */ +class BaseController { + /** + * Creates a BaseController instance. Both initial state and initial + * configuration options are merged with defaults upon initialization. + * + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor(config = {}, state = {}) { + /** + * Default options used to configure this controller + */ + this.defaultConfig = {}; + /** + * Default state set on this controller + */ + this.defaultState = {}; + /** + * Determines if listeners are notified of state changes + */ + this.disabled = false; + /** + * Name of this controller used during composition + */ + this.name = 'BaseController'; + this.internalConfig = this.defaultConfig; + this.internalState = this.defaultState; + this.internalListeners = []; + // Use assign since generics can't be spread: https://git.io/vpRhY + this.initialState = state; + this.initialConfig = config; + } + /** + * Enables the controller. This sets each config option as a member + * variable on this instance and triggers any defined setters. This + * also sets initial state and triggers any listeners. + * + * @returns This controller instance. + */ + initialize() { + this.internalState = this.defaultState; + this.internalConfig = this.defaultConfig; + this.configure(this.initialConfig); + this.update(this.initialState); + return this; + } + /** + * Retrieves current controller configuration options. + * + * @returns The current configuration. + */ + get config() { + return this.internalConfig; + } + /** + * Retrieves current controller state. + * + * @returns The current state. + */ + get state() { + return this.internalState; + } + /** + * Updates controller configuration. + * + * @param config - New configuration options. + * @param overwrite - Overwrite config instead of merging. + * @param fullUpdate - Boolean that defines if the update is partial or not. + */ + configure(config, overwrite = false, fullUpdate = true) { + if (fullUpdate) { + this.internalConfig = overwrite + ? config + : Object.assign(this.internalConfig, config); + for (const key in this.internalConfig) { + if (typeof this.internalConfig[key] !== 'undefined') { + this[key] = this.internalConfig[key]; + } + } + } + else { + for (const key in config) { + /* istanbul ignore else */ + if (typeof this.internalConfig[key] !== 'undefined') { + this.internalConfig[key] = config[key]; + this[key] = config[key]; + } + } + } + } + /** + * Notifies all subscribed listeners of current state. + */ + notify() { + if (this.disabled) { + return; + } + this.internalListeners.forEach((listener) => { + listener(this.internalState); + }); + } + /** + * Adds new listener to be notified of state changes. + * + * @param listener - The callback triggered when state changes. + */ + subscribe(listener) { + this.internalListeners.push(listener); + } + /** + * Removes existing listener from receiving state changes. + * + * @param listener - The callback to remove. + * @returns `true` if a listener is found and unsubscribed. + */ + unsubscribe(listener) { + const index = this.internalListeners.findIndex((cb) => listener === cb); + index > -1 && this.internalListeners.splice(index, 1); + return index > -1; + } + /** + * Updates controller state. + * + * @param state - The new state. + * @param overwrite - Overwrite state instead of merging. + */ + update(state, overwrite = false) { + this.internalState = overwrite + ? Object.assign({}, state) + : Object.assign({}, this.internalState, state); + this.notify(); + } +} +exports.BaseController = BaseController; +exports.default = BaseController; +//# sourceMappingURL=BaseController.js.map \ No newline at end of file diff --git a/dist/BaseController.js.map b/dist/BaseController.js.map new file mode 100644 index 0000000000..f628a8266a --- /dev/null +++ b/dist/BaseController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"BaseController.js","sourceRoot":"","sources":["../src/BaseController.ts"],"names":[],"mappings":";;;AAyBA;;GAEG;AACH,MAAa,cAAc;IA+BzB;;;;;;OAMG;IACH,YAAY,SAAqB,EAAO,EAAE,QAAoB,EAAO;QArCrE;;WAEG;QACH,kBAAa,GAAM,EAAO,CAAC;QAE3B;;WAEG;QACH,iBAAY,GAAM,EAAO,CAAC;QAE1B;;WAEG;QACH,aAAQ,GAAG,KAAK,CAAC;QAEjB;;WAEG;QACH,SAAI,GAAG,gBAAgB,CAAC;QAMhB,mBAAc,GAAM,IAAI,CAAC,aAAa,CAAC;QAEvC,kBAAa,GAAM,IAAI,CAAC,YAAY,CAAC;QAErC,sBAAiB,GAAkB,EAAE,CAAC;QAU5C,kEAAkE;QAClE,IAAI,CAAC,YAAY,GAAG,KAAU,CAAC;QAC/B,IAAI,CAAC,aAAa,GAAG,MAAW,CAAC;IACnC,CAAC;IAED;;;;;;OAMG;IACO,UAAU;QAClB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,YAAY,CAAC;QACvC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,aAAa,CAAC;QACzC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACnC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;OAIG;IACH,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,cAAc,CAAC;IAC7B,CAAC;IAED;;;;OAIG;IACH,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED;;;;;;OAMG;IACH,SAAS,CAAC,MAAkB,EAAE,SAAS,GAAG,KAAK,EAAE,UAAU,GAAG,IAAI;QAChE,IAAI,UAAU,EAAE;YACd,IAAI,CAAC,cAAc,GAAG,SAAS;gBAC7B,CAAC,CAAE,MAAY;gBACf,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;YAE/C,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,cAAc,EAAE;gBACrC,IAAI,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,WAAW,EAAE;oBAClD,IAAY,CAAC,GAAa,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;iBACzD;aACF;SACF;aAAM;YACL,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE;gBACxB,0BAA0B;gBAC1B,IAAI,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,WAAW,EAAE;oBACnD,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,CAAQ,CAAC;oBAC7C,IAAY,CAAC,GAAa,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;iBAC5C;aACF;SACF;IACH,CAAC;IAED;;OAEG;IACH,MAAM;QACJ,IAAI,IAAI,CAAC,QAAQ,EAAE;YACjB,OAAO;SACR;QAED,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;YAC1C,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,SAAS,CAAC,QAAqB;QAC7B,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC;IAED;;;;;OAKG;IACH,WAAW,CAAC,QAAqB;QAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,KAAK,EAAE,CAAC,CAAC;QACxE,KAAK,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QACtD,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC;IACpB,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,KAAiB,EAAE,SAAS,GAAG,KAAK;QACzC,IAAI,CAAC,aAAa,GAAG,SAAS;YAC5B,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,KAAU,CAAC;YAC/B,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;QACjD,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;CACF;AAxJD,wCAwJC;AAED,kBAAe,cAAc,CAAC","sourcesContent":["/**\n * State change callbacks\n */\nexport type Listener = (state: T) => void;\n\n/**\n * @type BaseConfig\n *\n * Base controller configuration\n * @property disabled - Determines if this controller is enabled\n */\nexport interface BaseConfig {\n disabled?: boolean;\n}\n\n/**\n * @type BaseState\n *\n * Base state representation\n * @property name - Unique name for this controller\n */\nexport interface BaseState {\n name?: string;\n}\n\n/**\n * Controller class that provides configuration, state management, and subscriptions\n */\nexport class BaseController {\n /**\n * Default options used to configure this controller\n */\n defaultConfig: C = {} as C;\n\n /**\n * Default state set on this controller\n */\n defaultState: S = {} as S;\n\n /**\n * Determines if listeners are notified of state changes\n */\n disabled = false;\n\n /**\n * Name of this controller used during composition\n */\n name = 'BaseController';\n\n private readonly initialConfig: C;\n\n private readonly initialState: S;\n\n private internalConfig: C = this.defaultConfig;\n\n private internalState: S = this.defaultState;\n\n private internalListeners: Listener[] = [];\n\n /**\n * Creates a BaseController instance. Both initial state and initial\n * configuration options are merged with defaults upon initialization.\n *\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(config: Partial = {} as C, state: Partial = {} as S) {\n // Use assign since generics can't be spread: https://git.io/vpRhY\n this.initialState = state as S;\n this.initialConfig = config as C;\n }\n\n /**\n * Enables the controller. This sets each config option as a member\n * variable on this instance and triggers any defined setters. This\n * also sets initial state and triggers any listeners.\n *\n * @returns This controller instance.\n */\n protected initialize() {\n this.internalState = this.defaultState;\n this.internalConfig = this.defaultConfig;\n this.configure(this.initialConfig);\n this.update(this.initialState);\n return this;\n }\n\n /**\n * Retrieves current controller configuration options.\n *\n * @returns The current configuration.\n */\n get config() {\n return this.internalConfig;\n }\n\n /**\n * Retrieves current controller state.\n *\n * @returns The current state.\n */\n get state() {\n return this.internalState;\n }\n\n /**\n * Updates controller configuration.\n *\n * @param config - New configuration options.\n * @param overwrite - Overwrite config instead of merging.\n * @param fullUpdate - Boolean that defines if the update is partial or not.\n */\n configure(config: Partial, overwrite = false, fullUpdate = true) {\n if (fullUpdate) {\n this.internalConfig = overwrite\n ? (config as C)\n : Object.assign(this.internalConfig, config);\n\n for (const key in this.internalConfig) {\n if (typeof this.internalConfig[key] !== 'undefined') {\n (this as any)[key as string] = this.internalConfig[key];\n }\n }\n } else {\n for (const key in config) {\n /* istanbul ignore else */\n if (typeof this.internalConfig[key] !== 'undefined') {\n this.internalConfig[key] = config[key] as any;\n (this as any)[key as string] = config[key];\n }\n }\n }\n }\n\n /**\n * Notifies all subscribed listeners of current state.\n */\n notify() {\n if (this.disabled) {\n return;\n }\n\n this.internalListeners.forEach((listener) => {\n listener(this.internalState);\n });\n }\n\n /**\n * Adds new listener to be notified of state changes.\n *\n * @param listener - The callback triggered when state changes.\n */\n subscribe(listener: Listener) {\n this.internalListeners.push(listener);\n }\n\n /**\n * Removes existing listener from receiving state changes.\n *\n * @param listener - The callback to remove.\n * @returns `true` if a listener is found and unsubscribed.\n */\n unsubscribe(listener: Listener) {\n const index = this.internalListeners.findIndex((cb) => listener === cb);\n index > -1 && this.internalListeners.splice(index, 1);\n return index > -1;\n }\n\n /**\n * Updates controller state.\n *\n * @param state - The new state.\n * @param overwrite - Overwrite state instead of merging.\n */\n update(state: Partial, overwrite = false) {\n this.internalState = overwrite\n ? Object.assign({}, state as S)\n : Object.assign({}, this.internalState, state);\n this.notify();\n }\n}\n\nexport default BaseController;\n"]} \ No newline at end of file diff --git a/dist/BaseControllerV2.d.ts b/dist/BaseControllerV2.d.ts new file mode 100644 index 0000000000..803593c02d --- /dev/null +++ b/dist/BaseControllerV2.d.ts @@ -0,0 +1,134 @@ +import type { Draft, Patch } from 'immer'; +import type { RestrictedControllerMessenger } from './ControllerMessenger'; +/** + * A state change listener. + * + * This function will get called for each state change, and is given a copy of + * the new state along with a set of patches describing the changes since the + * last update. + * + * @param state - The new controller state. + * @param patches - A list of patches describing any changes (see here for more + * information: https://immerjs.github.io/immer/docs/patches) + */ +export declare type Listener = (state: T, patches: Patch[]) => void; +/** + * An function to derive state. + * + * This function will accept one piece of the controller state (one property), + * and will return some derivation of that state. + * + * @param value - A piece of controller state. + * @returns Something derived from controller state. + */ +export declare type StateDeriver = (value: T) => Json; +/** + * State metadata. + * + * This metadata describes which parts of state should be persisted, and how to + * get an anonymized representation of the state. + */ +export declare type StateMetadata> = { + [P in keyof T]: StatePropertyMetadata; +}; +/** + * Metadata for a single state property + * + * @property persist - Indicates whether this property should be persisted + * (`true` for persistent, `false` for transient), or is set to a function + * that derives the persistent state from the state. + * @property anonymous - Indicates whether this property is already anonymous, + * (`true` for anonymous, `false` if it has potential to be personally + * identifiable), or is set to a function that returns an anonymized + * representation of this state. + */ +export interface StatePropertyMetadata { + persist: boolean | StateDeriver; + anonymous: boolean | StateDeriver; +} +export declare type Json = null | boolean | number | string | Json[] | { + [prop: string]: Json; +}; +/** + * Controller class that provides state management, subscriptions, and state metadata + */ +export declare class BaseController, messenger extends RestrictedControllerMessenger> { + private internalState; + protected messagingSystem: messenger; + /** + * The name of the controller. + * + * This is used by the ComposableController to construct a composed application state. + */ + readonly name: N; + readonly metadata: StateMetadata; + /** + * The existence of the `subscribe` property is how the ComposableController detects whether a + * controller extends the old BaseController or the new one. We set it to `undefined` here to + * ensure the ComposableController never mistakes them for an older style controller. + */ + readonly subscribe: undefined; + /** + * Creates a BaseController instance. + * + * @param options - Controller options. + * @param options.messenger - Controller messaging system. + * @param options.metadata - State metadata, describing how to "anonymize" the state, and which + * parts should be persisted. + * @param options.name - The name of the controller, used as a namespace for events and actions. + * @param options.state - Initial controller state. + */ + constructor({ messenger, metadata, name, state, }: { + messenger: messenger; + metadata: StateMetadata; + name: N; + state: S; + }); + /** + * Retrieves current controller state. + * + * @returns The current state. + */ + get state(): S; + set state(_: S); + /** + * Updates controller state. Accepts a callback that is passed a draft copy + * of the controller state. If a value is returned, it is set as the new + * state. Otherwise, any changes made within that callback to the draft are + * applied to the controller state. + * + * @param callback - Callback for updating state, passed a draft state + * object. Return a new state object or mutate the draft to update state. + */ + protected update(callback: (state: Draft) => void | S): void; + /** + * Prepares the controller for garbage collection. This should be extended + * by any subclasses to clean up any additional connections or events. + * + * The only cleanup performed here is to remove listeners. While technically + * this is not required to ensure this instance is garbage collected, it at + * least ensures this instance won't be responsible for preventing the + * listeners from being garbage collected. + */ + protected destroy(): void; +} +/** + * Returns an anonymized representation of the controller state. + * + * By "anonymized" we mean that it should not contain any information that could be personally + * identifiable. + * + * @param state - The controller state. + * @param metadata - The controller state metadata, which describes how to derive the + * anonymized state. + * @returns The anonymized controller state. + */ +export declare function getAnonymizedState>(state: S, metadata: StateMetadata): Record; +/** + * Returns the subset of state that should be persisted. + * + * @param state - The controller state. + * @param metadata - The controller state metadata, which describes which pieces of state should be persisted. + * @returns The subset of controller state that should be persisted. + */ +export declare function getPersistentState>(state: S, metadata: StateMetadata): Record; diff --git a/dist/BaseControllerV2.js b/dist/BaseControllerV2.js new file mode 100644 index 0000000000..980e2a4cd7 --- /dev/null +++ b/dist/BaseControllerV2.js @@ -0,0 +1,116 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getPersistentState = exports.getAnonymizedState = exports.BaseController = void 0; +const immer_1 = require("immer"); +(0, immer_1.enablePatches)(); +/** + * Controller class that provides state management, subscriptions, and state metadata + */ +class BaseController { + /** + * Creates a BaseController instance. + * + * @param options - Controller options. + * @param options.messenger - Controller messaging system. + * @param options.metadata - State metadata, describing how to "anonymize" the state, and which + * parts should be persisted. + * @param options.name - The name of the controller, used as a namespace for events and actions. + * @param options.state - Initial controller state. + */ + constructor({ messenger, metadata, name, state, }) { + this.messagingSystem = messenger; + this.name = name; + this.internalState = state; + this.metadata = metadata; + this.messagingSystem.registerActionHandler(`${name}:getState`, () => this.state); + } + /** + * Retrieves current controller state. + * + * @returns The current state. + */ + get state() { + return this.internalState; + } + set state(_) { + throw new Error(`Controller state cannot be directly mutated; use 'update' method instead.`); + } + /** + * Updates controller state. Accepts a callback that is passed a draft copy + * of the controller state. If a value is returned, it is set as the new + * state. Otherwise, any changes made within that callback to the draft are + * applied to the controller state. + * + * @param callback - Callback for updating state, passed a draft state + * object. Return a new state object or mutate the draft to update state. + */ + update(callback) { + // We run into ts2589, "infinite type depth", if we don't cast + // produceWithPatches here. + // The final, omitted member of the returned tuple are the inverse patches. + const [nextState, patches] = immer_1.produceWithPatches(this.internalState, callback); + this.internalState = nextState; + this.messagingSystem.publish(`${this.name}:stateChange`, nextState, patches); + } + /** + * Prepares the controller for garbage collection. This should be extended + * by any subclasses to clean up any additional connections or events. + * + * The only cleanup performed here is to remove listeners. While technically + * this is not required to ensure this instance is garbage collected, it at + * least ensures this instance won't be responsible for preventing the + * listeners from being garbage collected. + */ + destroy() { + this.messagingSystem.clearEventSubscriptions(`${this.name}:stateChange`); + } +} +exports.BaseController = BaseController; +/** + * Returns an anonymized representation of the controller state. + * + * By "anonymized" we mean that it should not contain any information that could be personally + * identifiable. + * + * @param state - The controller state. + * @param metadata - The controller state metadata, which describes how to derive the + * anonymized state. + * @returns The anonymized controller state. + */ +function getAnonymizedState(state, metadata) { + return deriveStateFromMetadata(state, metadata, 'anonymous'); +} +exports.getAnonymizedState = getAnonymizedState; +/** + * Returns the subset of state that should be persisted. + * + * @param state - The controller state. + * @param metadata - The controller state metadata, which describes which pieces of state should be persisted. + * @returns The subset of controller state that should be persisted. + */ +function getPersistentState(state, metadata) { + return deriveStateFromMetadata(state, metadata, 'persist'); +} +exports.getPersistentState = getPersistentState; +/** + * Use the metadata to derive state according to the given metadata property. + * + * @param state - The full controller state. + * @param metadata - The controller metadata. + * @param metadataProperty - The metadata property to use to derive state. + * @returns The metadata-derived controller state. + */ +function deriveStateFromMetadata(state, metadata, metadataProperty) { + return Object.keys(state).reduce((persistedState, key) => { + const propertyMetadata = metadata[key][metadataProperty]; + const stateProperty = state[key]; + if (typeof propertyMetadata === 'function') { + persistedState[key] = propertyMetadata(stateProperty); + } + else if (propertyMetadata) { + persistedState[key] = stateProperty; + } + return persistedState; + }, {}); +} +//# sourceMappingURL=BaseControllerV2.js.map \ No newline at end of file diff --git a/dist/BaseControllerV2.js.map b/dist/BaseControllerV2.js.map new file mode 100644 index 0000000000..d05f055da4 --- /dev/null +++ b/dist/BaseControllerV2.js.map @@ -0,0 +1 @@ +{"version":3,"file":"BaseControllerV2.js","sourceRoot":"","sources":["../src/BaseControllerV2.ts"],"names":[],"mappings":";;;AAAA,iCAA0D;AAW1D,IAAA,qBAAa,GAAE,CAAC;AA4DhB;;GAEG;AACH,MAAa,cAAc;IAyBzB;;;;;;;;;OASG;IACH,YAAY,EACV,SAAS,EACT,QAAQ,EACR,IAAI,EACJ,KAAK,GAMN;QACC,IAAI,CAAC,eAAe,GAAG,SAAS,CAAC;QACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;QAC3B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAEzB,IAAI,CAAC,eAAe,CAAC,qBAAqB,CACxC,GAAG,IAAI,WAAW,EAClB,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CACjB,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED,IAAI,KAAK,CAAC,CAAC;QACT,MAAM,IAAI,KAAK,CACb,2EAA2E,CAC5E,CAAC;IACJ,CAAC;IAED;;;;;;;;OAQG;IACO,MAAM,CAAC,QAAuC;QACtD,8DAA8D;QAC9D,2BAA2B;QAC3B,2EAA2E;QAC3E,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,GACxB,0BAID,CAAC,IAAI,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;QAEhC,IAAI,CAAC,aAAa,GAAG,SAAS,CAAC;QAC/B,IAAI,CAAC,eAAe,CAAC,OAAO,CAC1B,GAAG,IAAI,CAAC,IAAI,cAAoC,EAChD,SAAS,EACT,OAAO,CACR,CAAC;IACJ,CAAC;IAED;;;;;;;;OAQG;IACO,OAAO;QACf,IAAI,CAAC,eAAe,CAAC,uBAAuB,CAC1C,GAAG,IAAI,CAAC,IAAI,cAAoC,CACjD,CAAC;IACJ,CAAC;CACF;AAlHD,wCAkHC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,kBAAkB,CAChC,KAAQ,EACR,QAA0B;IAE1B,OAAO,uBAAuB,CAAC,KAAK,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;AAC/D,CAAC;AALD,gDAKC;AAED;;;;;;GAMG;AACH,SAAgB,kBAAkB,CAChC,KAAQ,EACR,QAA0B;IAE1B,OAAO,uBAAuB,CAAC,KAAK,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;AAC7D,CAAC;AALD,gDAKC;AAED;;;;;;;GAOG;AACH,SAAS,uBAAuB,CAC9B,KAAQ,EACR,QAA0B,EAC1B,gBAAyC;IAEzC,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,cAAc,EAAE,GAAG,EAAE,EAAE;QACvD,MAAM,gBAAgB,GAAG,QAAQ,CAAC,GAAc,CAAC,CAAC,gBAAgB,CAAC,CAAC;QACpE,MAAM,aAAa,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,OAAO,gBAAgB,KAAK,UAAU,EAAE;YAC1C,cAAc,CAAC,GAAa,CAAC,GAAG,gBAAgB,CAC9C,aAA2B,CAC5B,CAAC;SACH;aAAM,IAAI,gBAAgB,EAAE;YAC3B,cAAc,CAAC,GAAa,CAAC,GAAG,aAAa,CAAC;SAC/C;QACD,OAAO,cAAc,CAAC;IACxB,CAAC,EAAE,EAA0B,CAAC,CAAC;AACjC,CAAC","sourcesContent":["import { enablePatches, produceWithPatches } from 'immer';\n\n// Imported separately because only the type is used\n// eslint-disable-next-line no-duplicate-imports\nimport type { Draft, Patch } from 'immer';\n\nimport type {\n RestrictedControllerMessenger,\n Namespaced,\n} from './ControllerMessenger';\n\nenablePatches();\n\n/**\n * A state change listener.\n *\n * This function will get called for each state change, and is given a copy of\n * the new state along with a set of patches describing the changes since the\n * last update.\n *\n * @param state - The new controller state.\n * @param patches - A list of patches describing any changes (see here for more\n * information: https://immerjs.github.io/immer/docs/patches)\n */\nexport type Listener = (state: T, patches: Patch[]) => void;\n\n/**\n * An function to derive state.\n *\n * This function will accept one piece of the controller state (one property),\n * and will return some derivation of that state.\n *\n * @param value - A piece of controller state.\n * @returns Something derived from controller state.\n */\nexport type StateDeriver = (value: T) => Json;\n\n/**\n * State metadata.\n *\n * This metadata describes which parts of state should be persisted, and how to\n * get an anonymized representation of the state.\n */\nexport type StateMetadata> = {\n [P in keyof T]: StatePropertyMetadata;\n};\n\n/**\n * Metadata for a single state property\n *\n * @property persist - Indicates whether this property should be persisted\n * (`true` for persistent, `false` for transient), or is set to a function\n * that derives the persistent state from the state.\n * @property anonymous - Indicates whether this property is already anonymous,\n * (`true` for anonymous, `false` if it has potential to be personally\n * identifiable), or is set to a function that returns an anonymized\n * representation of this state.\n */\nexport interface StatePropertyMetadata {\n persist: boolean | StateDeriver;\n anonymous: boolean | StateDeriver;\n}\n\nexport type Json =\n | null\n | boolean\n | number\n | string\n | Json[]\n | { [prop: string]: Json };\n\n/**\n * Controller class that provides state management, subscriptions, and state metadata\n */\nexport class BaseController<\n N extends string,\n S extends Record,\n messenger extends RestrictedControllerMessenger,\n> {\n private internalState: S;\n\n protected messagingSystem: messenger;\n\n /**\n * The name of the controller.\n *\n * This is used by the ComposableController to construct a composed application state.\n */\n public readonly name: N;\n\n public readonly metadata: StateMetadata;\n\n /**\n * The existence of the `subscribe` property is how the ComposableController detects whether a\n * controller extends the old BaseController or the new one. We set it to `undefined` here to\n * ensure the ComposableController never mistakes them for an older style controller.\n */\n public readonly subscribe: undefined;\n\n /**\n * Creates a BaseController instance.\n *\n * @param options - Controller options.\n * @param options.messenger - Controller messaging system.\n * @param options.metadata - State metadata, describing how to \"anonymize\" the state, and which\n * parts should be persisted.\n * @param options.name - The name of the controller, used as a namespace for events and actions.\n * @param options.state - Initial controller state.\n */\n constructor({\n messenger,\n metadata,\n name,\n state,\n }: {\n messenger: messenger;\n metadata: StateMetadata;\n name: N;\n state: S;\n }) {\n this.messagingSystem = messenger;\n this.name = name;\n this.internalState = state;\n this.metadata = metadata;\n\n this.messagingSystem.registerActionHandler(\n `${name}:getState`,\n () => this.state,\n );\n }\n\n /**\n * Retrieves current controller state.\n *\n * @returns The current state.\n */\n get state() {\n return this.internalState;\n }\n\n set state(_) {\n throw new Error(\n `Controller state cannot be directly mutated; use 'update' method instead.`,\n );\n }\n\n /**\n * Updates controller state. Accepts a callback that is passed a draft copy\n * of the controller state. If a value is returned, it is set as the new\n * state. Otherwise, any changes made within that callback to the draft are\n * applied to the controller state.\n *\n * @param callback - Callback for updating state, passed a draft state\n * object. Return a new state object or mutate the draft to update state.\n */\n protected update(callback: (state: Draft) => void | S) {\n // We run into ts2589, \"infinite type depth\", if we don't cast\n // produceWithPatches here.\n // The final, omitted member of the returned tuple are the inverse patches.\n const [nextState, patches] = (\n produceWithPatches as unknown as (\n state: S,\n cb: typeof callback,\n ) => [S, Patch[], Patch[]]\n )(this.internalState, callback);\n\n this.internalState = nextState;\n this.messagingSystem.publish(\n `${this.name}:stateChange` as Namespaced,\n nextState,\n patches,\n );\n }\n\n /**\n * Prepares the controller for garbage collection. This should be extended\n * by any subclasses to clean up any additional connections or events.\n *\n * The only cleanup performed here is to remove listeners. While technically\n * this is not required to ensure this instance is garbage collected, it at\n * least ensures this instance won't be responsible for preventing the\n * listeners from being garbage collected.\n */\n protected destroy() {\n this.messagingSystem.clearEventSubscriptions(\n `${this.name}:stateChange` as Namespaced,\n );\n }\n}\n\n/**\n * Returns an anonymized representation of the controller state.\n *\n * By \"anonymized\" we mean that it should not contain any information that could be personally\n * identifiable.\n *\n * @param state - The controller state.\n * @param metadata - The controller state metadata, which describes how to derive the\n * anonymized state.\n * @returns The anonymized controller state.\n */\nexport function getAnonymizedState>(\n state: S,\n metadata: StateMetadata,\n): Record {\n return deriveStateFromMetadata(state, metadata, 'anonymous');\n}\n\n/**\n * Returns the subset of state that should be persisted.\n *\n * @param state - The controller state.\n * @param metadata - The controller state metadata, which describes which pieces of state should be persisted.\n * @returns The subset of controller state that should be persisted.\n */\nexport function getPersistentState>(\n state: S,\n metadata: StateMetadata,\n): Record {\n return deriveStateFromMetadata(state, metadata, 'persist');\n}\n\n/**\n * Use the metadata to derive state according to the given metadata property.\n *\n * @param state - The full controller state.\n * @param metadata - The controller metadata.\n * @param metadataProperty - The metadata property to use to derive state.\n * @returns The metadata-derived controller state.\n */\nfunction deriveStateFromMetadata>(\n state: S,\n metadata: StateMetadata,\n metadataProperty: 'anonymous' | 'persist',\n): Record {\n return Object.keys(state).reduce((persistedState, key) => {\n const propertyMetadata = metadata[key as keyof S][metadataProperty];\n const stateProperty = state[key];\n if (typeof propertyMetadata === 'function') {\n persistedState[key as string] = propertyMetadata(\n stateProperty as S[keyof S],\n );\n } else if (propertyMetadata) {\n persistedState[key as string] = stateProperty;\n }\n return persistedState;\n }, {} as Record);\n}\n"]} \ No newline at end of file diff --git a/dist/ComposableController.d.ts b/dist/ComposableController.d.ts new file mode 100644 index 0000000000..797da3307e --- /dev/null +++ b/dist/ComposableController.d.ts @@ -0,0 +1,42 @@ +import { BaseController } from './BaseController'; +import { RestrictedControllerMessenger } from './ControllerMessenger'; +/** + * List of child controller instances + * + * This type encompasses controllers based up either BaseController or + * BaseControllerV2. The BaseControllerV2 type can't be included directly + * because the generic parameters it expects require knowing the exact state + * shape, so instead we look for an object with the BaseControllerV2 properties + * that we use in the ComposableController (name and state). + */ +export declare type ControllerList = (BaseController | { + name: string; + state: Record; +})[]; +/** + * Controller that can be used to compose multiple controllers together + */ +export declare class ComposableController extends BaseController { + private controllers; + private messagingSystem?; + /** + * Name of this controller used during composition + */ + name: string; + /** + * Creates a ComposableController instance. + * + * @param controllers - Map of names to controller instances. + * @param messenger - The controller messaging system, used for communicating with BaseControllerV2 controllers. + */ + constructor(controllers: ControllerList, messenger?: RestrictedControllerMessenger<'ComposableController', never, any, never, any>); + /** + * Flat state representation, one that isn't keyed + * of controller name. Instead, all child controller state is merged + * together into a single, flat object. + * + * @returns Merged state representation of all child controllers. + */ + get flatState(): {}; +} +export default ComposableController; diff --git a/dist/ComposableController.js b/dist/ComposableController.js new file mode 100644 index 0000000000..edd63c5b32 --- /dev/null +++ b/dist/ComposableController.js @@ -0,0 +1,62 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ComposableController = void 0; +const BaseController_1 = require("./BaseController"); +/** + * Controller that can be used to compose multiple controllers together + */ +class ComposableController extends BaseController_1.BaseController { + /** + * Creates a ComposableController instance. + * + * @param controllers - Map of names to controller instances. + * @param messenger - The controller messaging system, used for communicating with BaseControllerV2 controllers. + */ + constructor(controllers, messenger) { + super(undefined, controllers.reduce((state, controller) => { + state[controller.name] = controller.state; + return state; + }, {})); + this.controllers = []; + /** + * Name of this controller used during composition + */ + this.name = 'ComposableController'; + this.initialize(); + this.controllers = controllers; + this.messagingSystem = messenger; + this.controllers.forEach((controller) => { + const { name } = controller; + if (controller.subscribe !== undefined) { + controller.subscribe((state) => { + this.update({ [name]: state }); + }); + } + else if (this.messagingSystem) { + this.messagingSystem.subscribe(`${name}:stateChange`, (state) => { + this.update({ [name]: state }); + }); + } + else { + throw new Error(`Messaging system required if any BaseControllerV2 controllers are used`); + } + }); + } + /** + * Flat state representation, one that isn't keyed + * of controller name. Instead, all child controller state is merged + * together into a single, flat object. + * + * @returns Merged state representation of all child controllers. + */ + get flatState() { + let flatState = {}; + for (const controller of this.controllers) { + flatState = Object.assign(Object.assign({}, flatState), controller.state); + } + return flatState; + } +} +exports.ComposableController = ComposableController; +exports.default = ComposableController; +//# sourceMappingURL=ComposableController.js.map \ No newline at end of file diff --git a/dist/ComposableController.js.map b/dist/ComposableController.js.map new file mode 100644 index 0000000000..f3877358be --- /dev/null +++ b/dist/ComposableController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ComposableController.js","sourceRoot":"","sources":["../src/ComposableController.ts"],"names":[],"mappings":";;;AAAA,qDAAkD;AAiBlD;;GAEG;AACH,MAAa,oBAAqB,SAAQ,+BAA0B;IAgBlE;;;;;OAKG;IACH,YACE,WAA2B,EAC3B,SAMC;QAED,KAAK,CACH,SAAS,EACT,WAAW,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE;YACvC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC;YAC1C,OAAO,KAAK,CAAC;QACf,CAAC,EAAE,EAAS,CAAC,CACd,CAAC;QArCI,gBAAW,GAAmB,EAAE,CAAC;QAUzC;;WAEG;QACM,SAAI,GAAG,sBAAsB,CAAC;QAyBrC,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,eAAe,GAAG,SAAS,CAAC;QACjC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,UAAU,EAAE,EAAE;YACtC,MAAM,EAAE,IAAI,EAAE,GAAG,UAAU,CAAC;YAC5B,IAAK,UAAuC,CAAC,SAAS,KAAK,SAAS,EAAE;gBACnE,UAAuC,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE;oBAC3D,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;gBACjC,CAAC,CAAC,CAAC;aACJ;iBAAM,IAAI,IAAI,CAAC,eAAe,EAAE;gBAC9B,IAAI,CAAC,eAAe,CAAC,SAAiB,CACrC,GAAG,IAAI,cAAc,EACrB,CAAC,KAAU,EAAE,EAAE;oBACb,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;gBACjC,CAAC,CACF,CAAC;aACH;iBAAM;gBACL,MAAM,IAAI,KAAK,CACb,wEAAwE,CACzE,CAAC;aACH;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;OAMG;IACH,IAAI,SAAS;QACX,IAAI,SAAS,GAAG,EAAE,CAAC;QACnB,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,WAAW,EAAE;YACzC,SAAS,mCAAQ,SAAS,GAAK,UAAU,CAAC,KAAK,CAAE,CAAC;SACnD;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;CACF;AA7ED,oDA6EC;AAED,kBAAe,oBAAoB,CAAC","sourcesContent":["import { BaseController } from './BaseController';\nimport { RestrictedControllerMessenger } from './ControllerMessenger';\n\n/**\n * List of child controller instances\n *\n * This type encompasses controllers based up either BaseController or\n * BaseControllerV2. The BaseControllerV2 type can't be included directly\n * because the generic parameters it expects require knowing the exact state\n * shape, so instead we look for an object with the BaseControllerV2 properties\n * that we use in the ComposableController (name and state).\n */\nexport type ControllerList = (\n | BaseController\n | { name: string; state: Record }\n)[];\n\n/**\n * Controller that can be used to compose multiple controllers together\n */\nexport class ComposableController extends BaseController {\n private controllers: ControllerList = [];\n\n private messagingSystem?: RestrictedControllerMessenger<\n 'ComposableController',\n never,\n any,\n never,\n any\n >;\n\n /**\n * Name of this controller used during composition\n */\n override name = 'ComposableController';\n\n /**\n * Creates a ComposableController instance.\n *\n * @param controllers - Map of names to controller instances.\n * @param messenger - The controller messaging system, used for communicating with BaseControllerV2 controllers.\n */\n constructor(\n controllers: ControllerList,\n messenger?: RestrictedControllerMessenger<\n 'ComposableController',\n never,\n any,\n never,\n any\n >,\n ) {\n super(\n undefined,\n controllers.reduce((state, controller) => {\n state[controller.name] = controller.state;\n return state;\n }, {} as any),\n );\n this.initialize();\n this.controllers = controllers;\n this.messagingSystem = messenger;\n this.controllers.forEach((controller) => {\n const { name } = controller;\n if ((controller as BaseController).subscribe !== undefined) {\n (controller as BaseController).subscribe((state) => {\n this.update({ [name]: state });\n });\n } else if (this.messagingSystem) {\n (this.messagingSystem.subscribe as any)(\n `${name}:stateChange`,\n (state: any) => {\n this.update({ [name]: state });\n },\n );\n } else {\n throw new Error(\n `Messaging system required if any BaseControllerV2 controllers are used`,\n );\n }\n });\n }\n\n /**\n * Flat state representation, one that isn't keyed\n * of controller name. Instead, all child controller state is merged\n * together into a single, flat object.\n *\n * @returns Merged state representation of all child controllers.\n */\n get flatState() {\n let flatState = {};\n for (const controller of this.controllers) {\n flatState = { ...flatState, ...controller.state };\n }\n return flatState;\n }\n}\n\nexport default ComposableController;\n"]} \ No newline at end of file diff --git a/dist/ControllerMessenger.d.ts b/dist/ControllerMessenger.d.ts new file mode 100644 index 0000000000..32a121cc6c --- /dev/null +++ b/dist/ControllerMessenger.d.ts @@ -0,0 +1,352 @@ +declare type ActionHandler = (...args: ExtractActionParameters) => ExtractActionResponse; +declare type ExtractActionParameters = Action extends { + type: T; + handler: (...args: infer H) => any; +} ? H : never; +declare type ExtractActionResponse = Action extends { + type: T; + handler: (...args: any) => infer H; +} ? H : never; +declare type ExtractEventHandler = Event extends { + type: T; + payload: infer P; +} ? P extends unknown[] ? (...payload: P) => void : never : never; +declare type ExtractEventPayload = Event extends { + type: T; + payload: infer P; +} ? P : never; +declare type SelectorFunction = (...args: Args) => ReturnValue; +declare type SelectorEventHandler = (newValue: SelectorReturnValue, previousValue: SelectorReturnValue | undefined) => void; +export declare type ActionConstraint = { + type: string; + handler: (...args: any) => unknown; +}; +export declare type EventConstraint = { + type: string; + payload: unknown[]; +}; +/** + * A namespaced string + * + * This type verifies that the string T is prefixed by the string Name followed by a colon. + * + * @template Name - The namespace we're checking for. + * @template T - The full string, including the namespace. + */ +export declare type Namespaced = T extends `${Name}:${string}` ? T : never; +declare type NarrowToNamespace = T extends { + type: `${Namespace}:${string}`; +} ? T : never; +declare type NarrowToAllowed = T extends { + type: Allowed; +} ? T : never; +/** + * A restricted controller messenger. + * + * This acts as a wrapper around the controller messenger instance that restricts access to actions + * and events. + * + * @template N - The namespace for this messenger. Typically this is the name of the controller or + * module that this messenger has been created for. The authority to publish events and register + * actions under this namespace is granted to this restricted messenger instance. + * @template Action - A type union of all Action types. + * @template Event - A type union of all Event types. + * @template AllowedAction - A type union of the 'type' string for any allowed actions. + * @template AllowedEvent - A type union of the 'type' string for any allowed events. + */ +export declare class RestrictedControllerMessenger { + private controllerMessenger; + private controllerName; + private allowedActions; + private allowedEvents; + /** + * Constructs a restricted controller messenger + * + * The provided allowlists grant the ability to call the listed actions and subscribe to the + * listed events. The "name" provided grants ownership of any actions and events under that + * namespace. Ownership allows registering actions and publishing events, as well as + * unregistering actions and clearing event subscriptions. + * + * @param options - The controller options. + * @param options.controllerMessenger - The controller messenger instance that is being wrapped. + * @param options.name - The name of the thing this messenger will be handed to (e.g. the + * controller name). This grants "ownership" of actions and events under this namespace to the + * restricted controller messenger returned. + * @param options.allowedActions - The list of actions that this restricted controller messenger + * should be alowed to call. + * @param options.allowedEvents - The list of events that this restricted controller messenger + * should be allowed to subscribe to. + */ + constructor({ controllerMessenger, name, allowedActions, allowedEvents, }: { + controllerMessenger: ControllerMessenger; + name: N; + allowedActions?: AllowedAction[]; + allowedEvents?: AllowedEvent[]; + }); + /** + * Register an action handler. + * + * This will make the registered function available to call via the `call` method. + * + * The action type this handler is registered under *must* be in the current namespace. + * + * @param action - The action type. This is a unqiue identifier for this action. + * @param handler - The action handler. This function gets called when the `call` method is + * invoked with the given action type. + * @throws Will throw when a handler has been registered for this action type already. + * @template T - A type union of Action type strings that are namespaced by N. + */ + registerActionHandler>(action: T, handler: ActionHandler): void; + /** + * Unregister an action handler. + * + * This will prevent this action from being called. + * + * The action type being unregistered *must* be in the current namespace. + * + * @param action - The action type. This is a unqiue identifier for this action. + * @template T - A type union of Action type strings that are namespaced by N. + */ + unregisterActionHandler>(action: T): void; + /** + * Call an action. + * + * This function will call the action handler corresponding to the given action type, passing + * along any parameters given. + * + * The action type being called must be on the action allowlist. + * + * @param action - The action type. This is a unqiue identifier for this action. + * @param params - The action parameters. These must match the type of the parameters of the + * registered action handler. + * @throws Will throw when no handler has been registered for the given type. + * @template T - A type union of allowed Action type strings. + * @returns The action return value. + */ + call(action: T, ...params: ExtractActionParameters): ExtractActionResponse; + /** + * Publish an event. + * + * Publishes the given payload to all subscribers of the given event type. + * + * The event type being published *must* be in the current namespace. + * + * @param event - The event type. This is a unique identifier for this event. + * @param payload - The event payload. The type of the parameters for each event handler must + * match the type of this payload. + * @template E - A type union of Event type strings that are namespaced by N. + */ + publish>(event: E, ...payload: ExtractEventPayload): void; + /** + * Subscribe to an event. + * + * Registers the given function as an event handler for the given event type. + * + * The event type being subscribed to must be on the event allowlist. + * + * @param eventType - The event type. This is a unique identifier for this event. + * @param handler - The event handler. The type of the parameters for this event handler must + * match the type of the payload for this event type. + * @template E - A type union of Event type strings. + */ + subscribe(eventType: E, handler: ExtractEventHandler): void; + /** + * Subscribe to an event, with a selector. + * + * Registers the given handler function as an event handler for the given + * event type. When an event is published, its payload is first passed to the + * selector. The event handler is only called if the selector's return value + * differs from its last known return value. + * + * The event type being subscribed to must be on the event allowlist. + * + * @param eventType - The event type. This is a unique identifier for this event. + * @param handler - The event handler. The type of the parameters for this event + * handler must match the return type of the selector. + * @param selector - The selector function used to select relevant data from + * the event payload. The type of the parameters for this selector must match + * the type of the payload for this event type. + * @template E - A type union of Event type strings. + * @template V - The selector return value. + */ + subscribe(eventType: E, handler: SelectorEventHandler, selector: SelectorFunction, V>): void; + /** + * Unsubscribe from an event. + * + * Unregisters the given function as an event handler for the given event. + * + * The event type being unsubscribed to must be on the event allowlist. + * + * @param event - The event type. This is a unique identifier for this event. + * @param handler - The event handler to unregister. + * @throws Will throw when the given event handler is not registered for this event. + * @template T - A type union of allowed Event type strings. + */ + unsubscribe(event: E, handler: ExtractEventHandler): void; + /** + * Clear subscriptions for a specific event. + * + * This will remove all subscribed handlers for this event. + * + * The event type being cleared *must* be in the current namespace. + * + * @param event - The event type. This is a unique identifier for this event. + * @template E - A type union of Event type strings that are namespaced by N. + */ + clearEventSubscriptions>(event: E): void; +} +/** + * A messaging system for controllers. + * + * The controller messenger allows registering functions as 'actions' that can be called elsewhere, + * and it allows publishing and subscribing to events. Both actions and events are identified by + * unique strings. + * + * @template Action - A type union of all Action types. + * @template Event - A type union of all Event types. + */ +export declare class ControllerMessenger { + private actions; + private events; + /** + * A cache of selector return values for their respective handlers. + */ + private eventPayloadCache; + /** + * Register an action handler. + * + * This will make the registered function available to call via the `call` method. + * + * @param actionType - The action type. This is a unqiue identifier for this action. + * @param handler - The action handler. This function gets called when the `call` method is + * invoked with the given action type. + * @throws Will throw when a handler has been registered for this action type already. + * @template T - A type union of Action type strings. + */ + registerActionHandler(actionType: T, handler: ActionHandler): void; + /** + * Unregister an action handler. + * + * This will prevent this action from being called. + * + * @param actionType - The action type. This is a unqiue identifier for this action. + * @template T - A type union of Action type strings. + */ + unregisterActionHandler(actionType: T): void; + /** + * Unregister all action handlers. + * + * This prevents all actions from being called. + */ + clearActions(): void; + /** + * Call an action. + * + * This function will call the action handler corresponding to the given action type, passing + * along any parameters given. + * + * @param actionType - The action type. This is a unqiue identifier for this action. + * @param params - The action parameters. These must match the type of the parameters of the + * registered action handler. + * @throws Will throw when no handler has been registered for the given type. + * @template T - A type union of Action type strings. + * @returns The action return value. + */ + call(actionType: T, ...params: ExtractActionParameters): ExtractActionResponse; + /** + * Publish an event. + * + * Publishes the given payload to all subscribers of the given event type. + * + * @param eventType - The event type. This is a unique identifier for this event. + * @param payload - The event payload. The type of the parameters for each event handler must + * match the type of this payload. + * @template E - A type union of Event type strings. + */ + publish(eventType: E, ...payload: ExtractEventPayload): void; + /** + * Subscribe to an event. + * + * Registers the given function as an event handler for the given event type. + * + * @param eventType - The event type. This is a unique identifier for this event. + * @param handler - The event handler. The type of the parameters for this event handler must + * match the type of the payload for this event type. + * @template E - A type union of Event type strings. + */ + subscribe(eventType: E, handler: ExtractEventHandler): void; + /** + * Subscribe to an event, with a selector. + * + * Registers the given handler function as an event handler for the given + * event type. When an event is published, its payload is first passed to the + * selector. The event handler is only called if the selector's return value + * differs from its last known return value. + * + * @param eventType - The event type. This is a unique identifier for this event. + * @param handler - The event handler. The type of the parameters for this event + * handler must match the return type of the selector. + * @param selector - The selector function used to select relevant data from + * the event payload. The type of the parameters for this selector must match + * the type of the payload for this event type. + * @template E - A type union of Event type strings. + * @template V - The selector return value. + */ + subscribe(eventType: E, handler: SelectorEventHandler, selector: SelectorFunction, V>): void; + /** + * Unsubscribe from an event. + * + * Unregisters the given function as an event handler for the given event. + * + * @param eventType - The event type. This is a unique identifier for this event. + * @param handler - The event handler to unregister. + * @throws Will throw when the given event handler is not registered for this event. + * @template E - A type union of Event type strings. + */ + unsubscribe(eventType: E, handler: ExtractEventHandler): void; + /** + * Clear subscriptions for a specific event. + * + * This will remove all subscribed handlers for this event. + * + * @param eventType - The event type. This is a unique identifier for this event. + * @template E - A type union of Event type strings. + */ + clearEventSubscriptions(eventType: E): void; + /** + * Clear all subscriptions. + * + * This will remove all subscribed handlers for all events. + */ + clearSubscriptions(): void; + /** + * Get a restricted controller messenger + * + * Returns a wrapper around the controller messenger instance that restricts access to actions + * and events. The provided allowlists grant the ability to call the listed actions and subscribe + * to the listed events. The "name" provided grants ownership of any actions and events under + * that namespace. Ownership allows registering actions and publishing events, as well as + * unregistering actions and clearing event subscriptions. + * + * @param options - Controller messenger options. + * @param options.name - The name of the thing this messenger will be handed to (e.g. the + * controller name). This grants "ownership" of actions and events under this namespace to the + * restricted controller messenger returned. + * @param options.allowedActions - The list of actions that this restricted controller messenger + * should be alowed to call. + * @param options.allowedEvents - The list of events that this restricted controller messenger + * should be allowed to subscribe to. + * @template N - The namespace for this messenger. Typically this is the name of the controller or + * module that this messenger has been created for. The authority to publish events and register + * actions under this namespace is granted to this restricted messenger instance. + * @template AllowedAction - A type union of the 'type' string for any allowed actions. + * @template AllowedEvent - A type union of the 'type' string for any allowed events. + * @returns The restricted controller messenger. + */ + getRestricted({ name, allowedActions, allowedEvents, }: { + name: N; + allowedActions?: Extract[]; + allowedEvents?: Extract[]; + }): RestrictedControllerMessenger | NarrowToAllowed, NarrowToNamespace | NarrowToAllowed, AllowedAction, AllowedEvent>; +} +export {}; diff --git a/dist/ControllerMessenger.js b/dist/ControllerMessenger.js new file mode 100644 index 0000000000..a94aad46d4 --- /dev/null +++ b/dist/ControllerMessenger.js @@ -0,0 +1,363 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ControllerMessenger = exports.RestrictedControllerMessenger = void 0; +/** + * A restricted controller messenger. + * + * This acts as a wrapper around the controller messenger instance that restricts access to actions + * and events. + * + * @template N - The namespace for this messenger. Typically this is the name of the controller or + * module that this messenger has been created for. The authority to publish events and register + * actions under this namespace is granted to this restricted messenger instance. + * @template Action - A type union of all Action types. + * @template Event - A type union of all Event types. + * @template AllowedAction - A type union of the 'type' string for any allowed actions. + * @template AllowedEvent - A type union of the 'type' string for any allowed events. + */ +class RestrictedControllerMessenger { + /** + * Constructs a restricted controller messenger + * + * The provided allowlists grant the ability to call the listed actions and subscribe to the + * listed events. The "name" provided grants ownership of any actions and events under that + * namespace. Ownership allows registering actions and publishing events, as well as + * unregistering actions and clearing event subscriptions. + * + * @param options - The controller options. + * @param options.controllerMessenger - The controller messenger instance that is being wrapped. + * @param options.name - The name of the thing this messenger will be handed to (e.g. the + * controller name). This grants "ownership" of actions and events under this namespace to the + * restricted controller messenger returned. + * @param options.allowedActions - The list of actions that this restricted controller messenger + * should be alowed to call. + * @param options.allowedEvents - The list of events that this restricted controller messenger + * should be allowed to subscribe to. + */ + constructor({ controllerMessenger, name, allowedActions, allowedEvents, }) { + this.controllerMessenger = controllerMessenger; + this.controllerName = name; + this.allowedActions = allowedActions || null; + this.allowedEvents = allowedEvents || null; + } + /** + * Register an action handler. + * + * This will make the registered function available to call via the `call` method. + * + * The action type this handler is registered under *must* be in the current namespace. + * + * @param action - The action type. This is a unqiue identifier for this action. + * @param handler - The action handler. This function gets called when the `call` method is + * invoked with the given action type. + * @throws Will throw when a handler has been registered for this action type already. + * @template T - A type union of Action type strings that are namespaced by N. + */ + registerActionHandler(action, handler) { + /* istanbul ignore if */ // Branch unreachable with valid types + if (!action.startsWith(`${this.controllerName}:`)) { + throw new Error(`Only allowed registering action handlers prefixed by '${this.controllerName}:'`); + } + this.controllerMessenger.registerActionHandler(action, handler); + } + /** + * Unregister an action handler. + * + * This will prevent this action from being called. + * + * The action type being unregistered *must* be in the current namespace. + * + * @param action - The action type. This is a unqiue identifier for this action. + * @template T - A type union of Action type strings that are namespaced by N. + */ + unregisterActionHandler(action) { + /* istanbul ignore if */ // Branch unreachable with valid types + if (!action.startsWith(`${this.controllerName}:`)) { + throw new Error(`Only allowed unregistering action handlers prefixed by '${this.controllerName}:'`); + } + this.controllerMessenger.unregisterActionHandler(action); + } + /** + * Call an action. + * + * This function will call the action handler corresponding to the given action type, passing + * along any parameters given. + * + * The action type being called must be on the action allowlist. + * + * @param action - The action type. This is a unqiue identifier for this action. + * @param params - The action parameters. These must match the type of the parameters of the + * registered action handler. + * @throws Will throw when no handler has been registered for the given type. + * @template T - A type union of allowed Action type strings. + * @returns The action return value. + */ + call(action, ...params) { + /* istanbul ignore next */ // Branches unreachable with valid types + if (this.allowedActions === null) { + throw new Error('No actions allowed'); + } + else if (!this.allowedActions.includes(action)) { + throw new Error(`Action missing from allow list: ${action}`); + } + return this.controllerMessenger.call(action, ...params); + } + /** + * Publish an event. + * + * Publishes the given payload to all subscribers of the given event type. + * + * The event type being published *must* be in the current namespace. + * + * @param event - The event type. This is a unique identifier for this event. + * @param payload - The event payload. The type of the parameters for each event handler must + * match the type of this payload. + * @template E - A type union of Event type strings that are namespaced by N. + */ + publish(event, ...payload) { + /* istanbul ignore if */ // Branch unreachable with valid types + if (!event.startsWith(`${this.controllerName}:`)) { + throw new Error(`Only allowed publishing events prefixed by '${this.controllerName}:'`); + } + this.controllerMessenger.publish(event, ...payload); + } + subscribe(event, handler, selector) { + /* istanbul ignore next */ // Branches unreachable with valid types + if (this.allowedEvents === null) { + throw new Error('No events allowed'); + } + else if (!this.allowedEvents.includes(event)) { + throw new Error(`Event missing from allow list: ${event}`); + } + if (selector) { + return this.controllerMessenger.subscribe(event, handler, selector); + } + return this.controllerMessenger.subscribe(event, handler); + } + /** + * Unsubscribe from an event. + * + * Unregisters the given function as an event handler for the given event. + * + * The event type being unsubscribed to must be on the event allowlist. + * + * @param event - The event type. This is a unique identifier for this event. + * @param handler - The event handler to unregister. + * @throws Will throw when the given event handler is not registered for this event. + * @template T - A type union of allowed Event type strings. + */ + unsubscribe(event, handler) { + /* istanbul ignore next */ // Branches unreachable with valid types + if (this.allowedEvents === null) { + throw new Error('No events allowed'); + } + else if (!this.allowedEvents.includes(event)) { + throw new Error(`Event missing from allow list: ${event}`); + } + this.controllerMessenger.unsubscribe(event, handler); + } + /** + * Clear subscriptions for a specific event. + * + * This will remove all subscribed handlers for this event. + * + * The event type being cleared *must* be in the current namespace. + * + * @param event - The event type. This is a unique identifier for this event. + * @template E - A type union of Event type strings that are namespaced by N. + */ + clearEventSubscriptions(event) { + /* istanbul ignore if */ // Branch unreachable with valid types + if (!event.startsWith(`${this.controllerName}:`)) { + throw new Error(`Only allowed clearing events prefixed by '${this.controllerName}:'`); + } + this.controllerMessenger.clearEventSubscriptions(event); + } +} +exports.RestrictedControllerMessenger = RestrictedControllerMessenger; +/** + * A messaging system for controllers. + * + * The controller messenger allows registering functions as 'actions' that can be called elsewhere, + * and it allows publishing and subscribing to events. Both actions and events are identified by + * unique strings. + * + * @template Action - A type union of all Action types. + * @template Event - A type union of all Event types. + */ +class ControllerMessenger { + constructor() { + this.actions = new Map(); + this.events = new Map(); + /** + * A cache of selector return values for their respective handlers. + */ + this.eventPayloadCache = new Map(); + } + /** + * Register an action handler. + * + * This will make the registered function available to call via the `call` method. + * + * @param actionType - The action type. This is a unqiue identifier for this action. + * @param handler - The action handler. This function gets called when the `call` method is + * invoked with the given action type. + * @throws Will throw when a handler has been registered for this action type already. + * @template T - A type union of Action type strings. + */ + registerActionHandler(actionType, handler) { + if (this.actions.has(actionType)) { + throw new Error(`A handler for ${actionType} has already been registered`); + } + this.actions.set(actionType, handler); + } + /** + * Unregister an action handler. + * + * This will prevent this action from being called. + * + * @param actionType - The action type. This is a unqiue identifier for this action. + * @template T - A type union of Action type strings. + */ + unregisterActionHandler(actionType) { + this.actions.delete(actionType); + } + /** + * Unregister all action handlers. + * + * This prevents all actions from being called. + */ + clearActions() { + this.actions.clear(); + } + /** + * Call an action. + * + * This function will call the action handler corresponding to the given action type, passing + * along any parameters given. + * + * @param actionType - The action type. This is a unqiue identifier for this action. + * @param params - The action parameters. These must match the type of the parameters of the + * registered action handler. + * @throws Will throw when no handler has been registered for the given type. + * @template T - A type union of Action type strings. + * @returns The action return value. + */ + call(actionType, ...params) { + const handler = this.actions.get(actionType); + if (!handler) { + throw new Error(`A handler for ${actionType} has not been registered`); + } + return handler(...params); + } + /** + * Publish an event. + * + * Publishes the given payload to all subscribers of the given event type. + * + * @param eventType - The event type. This is a unique identifier for this event. + * @param payload - The event payload. The type of the parameters for each event handler must + * match the type of this payload. + * @template E - A type union of Event type strings. + */ + publish(eventType, ...payload) { + const subscribers = this.events.get(eventType); + if (subscribers) { + for (const [handler, selector] of subscribers.entries()) { + if (selector) { + const previousValue = this.eventPayloadCache.get(handler); + const newValue = selector(...payload); + if (newValue !== previousValue) { + this.eventPayloadCache.set(handler, newValue); + handler(newValue, previousValue); + } + } + else { + handler(...payload); + } + } + } + } + subscribe(eventType, handler, selector) { + let subscribers = this.events.get(eventType); + if (!subscribers) { + subscribers = new Map(); + this.events.set(eventType, subscribers); + } + subscribers.set(handler, selector); + } + /** + * Unsubscribe from an event. + * + * Unregisters the given function as an event handler for the given event. + * + * @param eventType - The event type. This is a unique identifier for this event. + * @param handler - The event handler to unregister. + * @throws Will throw when the given event handler is not registered for this event. + * @template E - A type union of Event type strings. + */ + unsubscribe(eventType, handler) { + const subscribers = this.events.get(eventType); + if (!subscribers || !subscribers.has(handler)) { + throw new Error(`Subscription not found for event: ${eventType}`); + } + const selector = subscribers.get(handler); + if (selector) { + this.eventPayloadCache.delete(handler); + } + subscribers.delete(handler); + } + /** + * Clear subscriptions for a specific event. + * + * This will remove all subscribed handlers for this event. + * + * @param eventType - The event type. This is a unique identifier for this event. + * @template E - A type union of Event type strings. + */ + clearEventSubscriptions(eventType) { + this.events.delete(eventType); + } + /** + * Clear all subscriptions. + * + * This will remove all subscribed handlers for all events. + */ + clearSubscriptions() { + this.events.clear(); + } + /** + * Get a restricted controller messenger + * + * Returns a wrapper around the controller messenger instance that restricts access to actions + * and events. The provided allowlists grant the ability to call the listed actions and subscribe + * to the listed events. The "name" provided grants ownership of any actions and events under + * that namespace. Ownership allows registering actions and publishing events, as well as + * unregistering actions and clearing event subscriptions. + * + * @param options - Controller messenger options. + * @param options.name - The name of the thing this messenger will be handed to (e.g. the + * controller name). This grants "ownership" of actions and events under this namespace to the + * restricted controller messenger returned. + * @param options.allowedActions - The list of actions that this restricted controller messenger + * should be alowed to call. + * @param options.allowedEvents - The list of events that this restricted controller messenger + * should be allowed to subscribe to. + * @template N - The namespace for this messenger. Typically this is the name of the controller or + * module that this messenger has been created for. The authority to publish events and register + * actions under this namespace is granted to this restricted messenger instance. + * @template AllowedAction - A type union of the 'type' string for any allowed actions. + * @template AllowedEvent - A type union of the 'type' string for any allowed events. + * @returns The restricted controller messenger. + */ + getRestricted({ name, allowedActions, allowedEvents, }) { + return new RestrictedControllerMessenger({ + controllerMessenger: this, + name, + allowedActions, + allowedEvents, + }); + } +} +exports.ControllerMessenger = ControllerMessenger; +//# sourceMappingURL=ControllerMessenger.js.map \ No newline at end of file diff --git a/dist/ControllerMessenger.js.map b/dist/ControllerMessenger.js.map new file mode 100644 index 0000000000..523aa8280d --- /dev/null +++ b/dist/ControllerMessenger.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ControllerMessenger.js","sourceRoot":"","sources":["../src/ControllerMessenger.ts"],"names":[],"mappings":"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","sourcesContent":["type ActionHandler = (\n ...args: ExtractActionParameters\n) => ExtractActionResponse;\ntype ExtractActionParameters = Action extends {\n type: T;\n handler: (...args: infer H) => any;\n}\n ? H\n : never;\ntype ExtractActionResponse = Action extends {\n type: T;\n handler: (...args: any) => infer H;\n}\n ? H\n : never;\n\ntype ExtractEventHandler = Event extends { type: T; payload: infer P }\n ? P extends unknown[]\n ? (...payload: P) => void\n : never\n : never;\ntype ExtractEventPayload = Event extends { type: T; payload: infer P }\n ? P\n : never;\n\ntype GenericEventHandler = (...args: unknown[]) => void;\n\ntype SelectorFunction = (\n ...args: Args\n) => ReturnValue;\ntype SelectorEventHandler = (\n newValue: SelectorReturnValue,\n previousValue: SelectorReturnValue | undefined,\n) => void;\n\nexport type ActionConstraint = {\n type: string;\n handler: (...args: any) => unknown;\n};\nexport type EventConstraint = { type: string; payload: unknown[] };\n\ntype EventSubscriptionMap = Map<\n GenericEventHandler | SelectorEventHandler,\n SelectorFunction | undefined\n>;\n\n/**\n * A namespaced string\n *\n * This type verifies that the string T is prefixed by the string Name followed by a colon.\n *\n * @template Name - The namespace we're checking for.\n * @template T - The full string, including the namespace.\n */\nexport type Namespaced = T extends `${Name}:${string}`\n ? T\n : never;\n\ntype NarrowToNamespace = T extends {\n type: `${Namespace}:${string}`;\n}\n ? T\n : never;\n\ntype NarrowToAllowed = T extends {\n type: Allowed;\n}\n ? T\n : never;\n\n/**\n * A restricted controller messenger.\n *\n * This acts as a wrapper around the controller messenger instance that restricts access to actions\n * and events.\n *\n * @template N - The namespace for this messenger. Typically this is the name of the controller or\n * module that this messenger has been created for. The authority to publish events and register\n * actions under this namespace is granted to this restricted messenger instance.\n * @template Action - A type union of all Action types.\n * @template Event - A type union of all Event types.\n * @template AllowedAction - A type union of the 'type' string for any allowed actions.\n * @template AllowedEvent - A type union of the 'type' string for any allowed events.\n */\nexport class RestrictedControllerMessenger<\n N extends string,\n Action extends ActionConstraint,\n Event extends EventConstraint,\n AllowedAction extends string,\n AllowedEvent extends string,\n> {\n private controllerMessenger: ControllerMessenger<\n ActionConstraint,\n EventConstraint\n >;\n\n private controllerName: N;\n\n private allowedActions: AllowedAction[] | null;\n\n private allowedEvents: AllowedEvent[] | null;\n\n /**\n * Constructs a restricted controller messenger\n *\n * The provided allowlists grant the ability to call the listed actions and subscribe to the\n * listed events. The \"name\" provided grants ownership of any actions and events under that\n * namespace. Ownership allows registering actions and publishing events, as well as\n * unregistering actions and clearing event subscriptions.\n *\n * @param options - The controller options.\n * @param options.controllerMessenger - The controller messenger instance that is being wrapped.\n * @param options.name - The name of the thing this messenger will be handed to (e.g. the\n * controller name). This grants \"ownership\" of actions and events under this namespace to the\n * restricted controller messenger returned.\n * @param options.allowedActions - The list of actions that this restricted controller messenger\n * should be alowed to call.\n * @param options.allowedEvents - The list of events that this restricted controller messenger\n * should be allowed to subscribe to.\n */\n constructor({\n controllerMessenger,\n name,\n allowedActions,\n allowedEvents,\n }: {\n controllerMessenger: ControllerMessenger;\n name: N;\n allowedActions?: AllowedAction[];\n allowedEvents?: AllowedEvent[];\n }) {\n this.controllerMessenger = controllerMessenger;\n this.controllerName = name;\n this.allowedActions = allowedActions || null;\n this.allowedEvents = allowedEvents || null;\n }\n\n /**\n * Register an action handler.\n *\n * This will make the registered function available to call via the `call` method.\n *\n * The action type this handler is registered under *must* be in the current namespace.\n *\n * @param action - The action type. This is a unqiue identifier for this action.\n * @param handler - The action handler. This function gets called when the `call` method is\n * invoked with the given action type.\n * @throws Will throw when a handler has been registered for this action type already.\n * @template T - A type union of Action type strings that are namespaced by N.\n */\n registerActionHandler>(\n action: T,\n handler: ActionHandler,\n ) {\n /* istanbul ignore if */ // Branch unreachable with valid types\n if (!action.startsWith(`${this.controllerName}:`)) {\n throw new Error(\n `Only allowed registering action handlers prefixed by '${this.controllerName}:'`,\n );\n }\n this.controllerMessenger.registerActionHandler(action, handler);\n }\n\n /**\n * Unregister an action handler.\n *\n * This will prevent this action from being called.\n *\n * The action type being unregistered *must* be in the current namespace.\n *\n * @param action - The action type. This is a unqiue identifier for this action.\n * @template T - A type union of Action type strings that are namespaced by N.\n */\n unregisterActionHandler>(action: T) {\n /* istanbul ignore if */ // Branch unreachable with valid types\n if (!action.startsWith(`${this.controllerName}:`)) {\n throw new Error(\n `Only allowed unregistering action handlers prefixed by '${this.controllerName}:'`,\n );\n }\n this.controllerMessenger.unregisterActionHandler(action);\n }\n\n /**\n * Call an action.\n *\n * This function will call the action handler corresponding to the given action type, passing\n * along any parameters given.\n *\n * The action type being called must be on the action allowlist.\n *\n * @param action - The action type. This is a unqiue identifier for this action.\n * @param params - The action parameters. These must match the type of the parameters of the\n * registered action handler.\n * @throws Will throw when no handler has been registered for the given type.\n * @template T - A type union of allowed Action type strings.\n * @returns The action return value.\n */\n call(\n action: T,\n ...params: ExtractActionParameters\n ): ExtractActionResponse {\n /* istanbul ignore next */ // Branches unreachable with valid types\n if (this.allowedActions === null) {\n throw new Error('No actions allowed');\n } else if (!this.allowedActions.includes(action)) {\n throw new Error(`Action missing from allow list: ${action}`);\n }\n return this.controllerMessenger.call(action, ...params);\n }\n\n /**\n * Publish an event.\n *\n * Publishes the given payload to all subscribers of the given event type.\n *\n * The event type being published *must* be in the current namespace.\n *\n * @param event - The event type. This is a unique identifier for this event.\n * @param payload - The event payload. The type of the parameters for each event handler must\n * match the type of this payload.\n * @template E - A type union of Event type strings that are namespaced by N.\n */\n publish>(\n event: E,\n ...payload: ExtractEventPayload\n ) {\n /* istanbul ignore if */ // Branch unreachable with valid types\n if (!event.startsWith(`${this.controllerName}:`)) {\n throw new Error(\n `Only allowed publishing events prefixed by '${this.controllerName}:'`,\n );\n }\n this.controllerMessenger.publish(event, ...payload);\n }\n\n /**\n * Subscribe to an event.\n *\n * Registers the given function as an event handler for the given event type.\n *\n * The event type being subscribed to must be on the event allowlist.\n *\n * @param eventType - The event type. This is a unique identifier for this event.\n * @param handler - The event handler. The type of the parameters for this event handler must\n * match the type of the payload for this event type.\n * @template E - A type union of Event type strings.\n */\n subscribe(\n eventType: E,\n handler: ExtractEventHandler,\n ): void;\n\n /**\n * Subscribe to an event, with a selector.\n *\n * Registers the given handler function as an event handler for the given\n * event type. When an event is published, its payload is first passed to the\n * selector. The event handler is only called if the selector's return value\n * differs from its last known return value.\n *\n * The event type being subscribed to must be on the event allowlist.\n *\n * @param eventType - The event type. This is a unique identifier for this event.\n * @param handler - The event handler. The type of the parameters for this event\n * handler must match the return type of the selector.\n * @param selector - The selector function used to select relevant data from\n * the event payload. The type of the parameters for this selector must match\n * the type of the payload for this event type.\n * @template E - A type union of Event type strings.\n * @template V - The selector return value.\n */\n subscribe(\n eventType: E,\n handler: SelectorEventHandler,\n selector: SelectorFunction, V>,\n ): void;\n\n subscribe(\n event: E,\n handler: ExtractEventHandler,\n selector?: SelectorFunction, V>,\n ) {\n /* istanbul ignore next */ // Branches unreachable with valid types\n if (this.allowedEvents === null) {\n throw new Error('No events allowed');\n } else if (!this.allowedEvents.includes(event)) {\n throw new Error(`Event missing from allow list: ${event}`);\n }\n\n if (selector) {\n return this.controllerMessenger.subscribe(event, handler, selector);\n }\n return this.controllerMessenger.subscribe(event, handler);\n }\n\n /**\n * Unsubscribe from an event.\n *\n * Unregisters the given function as an event handler for the given event.\n *\n * The event type being unsubscribed to must be on the event allowlist.\n *\n * @param event - The event type. This is a unique identifier for this event.\n * @param handler - The event handler to unregister.\n * @throws Will throw when the given event handler is not registered for this event.\n * @template T - A type union of allowed Event type strings.\n */\n unsubscribe(\n event: E,\n handler: ExtractEventHandler,\n ) {\n /* istanbul ignore next */ // Branches unreachable with valid types\n if (this.allowedEvents === null) {\n throw new Error('No events allowed');\n } else if (!this.allowedEvents.includes(event)) {\n throw new Error(`Event missing from allow list: ${event}`);\n }\n this.controllerMessenger.unsubscribe(event, handler);\n }\n\n /**\n * Clear subscriptions for a specific event.\n *\n * This will remove all subscribed handlers for this event.\n *\n * The event type being cleared *must* be in the current namespace.\n *\n * @param event - The event type. This is a unique identifier for this event.\n * @template E - A type union of Event type strings that are namespaced by N.\n */\n clearEventSubscriptions>(event: E) {\n /* istanbul ignore if */ // Branch unreachable with valid types\n if (!event.startsWith(`${this.controllerName}:`)) {\n throw new Error(\n `Only allowed clearing events prefixed by '${this.controllerName}:'`,\n );\n }\n this.controllerMessenger.clearEventSubscriptions(event);\n }\n}\n\n/**\n * A messaging system for controllers.\n *\n * The controller messenger allows registering functions as 'actions' that can be called elsewhere,\n * and it allows publishing and subscribing to events. Both actions and events are identified by\n * unique strings.\n *\n * @template Action - A type union of all Action types.\n * @template Event - A type union of all Event types.\n */\nexport class ControllerMessenger<\n Action extends ActionConstraint,\n Event extends EventConstraint,\n> {\n private actions = new Map();\n\n private events = new Map();\n\n /**\n * A cache of selector return values for their respective handlers.\n */\n private eventPayloadCache = new Map<\n GenericEventHandler,\n unknown | undefined\n >();\n\n /**\n * Register an action handler.\n *\n * This will make the registered function available to call via the `call` method.\n *\n * @param actionType - The action type. This is a unqiue identifier for this action.\n * @param handler - The action handler. This function gets called when the `call` method is\n * invoked with the given action type.\n * @throws Will throw when a handler has been registered for this action type already.\n * @template T - A type union of Action type strings.\n */\n registerActionHandler(\n actionType: T,\n handler: ActionHandler,\n ) {\n if (this.actions.has(actionType)) {\n throw new Error(\n `A handler for ${actionType} has already been registered`,\n );\n }\n this.actions.set(actionType, handler);\n }\n\n /**\n * Unregister an action handler.\n *\n * This will prevent this action from being called.\n *\n * @param actionType - The action type. This is a unqiue identifier for this action.\n * @template T - A type union of Action type strings.\n */\n unregisterActionHandler(actionType: T) {\n this.actions.delete(actionType);\n }\n\n /**\n * Unregister all action handlers.\n *\n * This prevents all actions from being called.\n */\n clearActions() {\n this.actions.clear();\n }\n\n /**\n * Call an action.\n *\n * This function will call the action handler corresponding to the given action type, passing\n * along any parameters given.\n *\n * @param actionType - The action type. This is a unqiue identifier for this action.\n * @param params - The action parameters. These must match the type of the parameters of the\n * registered action handler.\n * @throws Will throw when no handler has been registered for the given type.\n * @template T - A type union of Action type strings.\n * @returns The action return value.\n */\n call(\n actionType: T,\n ...params: ExtractActionParameters\n ): ExtractActionResponse {\n const handler = this.actions.get(actionType) as ActionHandler;\n if (!handler) {\n throw new Error(`A handler for ${actionType} has not been registered`);\n }\n return handler(...params);\n }\n\n /**\n * Publish an event.\n *\n * Publishes the given payload to all subscribers of the given event type.\n *\n * @param eventType - The event type. This is a unique identifier for this event.\n * @param payload - The event payload. The type of the parameters for each event handler must\n * match the type of this payload.\n * @template E - A type union of Event type strings.\n */\n publish(\n eventType: E,\n ...payload: ExtractEventPayload\n ) {\n const subscribers = this.events.get(eventType);\n\n if (subscribers) {\n for (const [handler, selector] of subscribers.entries()) {\n if (selector) {\n const previousValue = this.eventPayloadCache.get(handler);\n const newValue = selector(...payload);\n\n if (newValue !== previousValue) {\n this.eventPayloadCache.set(handler, newValue);\n handler(newValue, previousValue);\n }\n } else {\n (handler as GenericEventHandler)(...payload);\n }\n }\n }\n }\n\n /**\n * Subscribe to an event.\n *\n * Registers the given function as an event handler for the given event type.\n *\n * @param eventType - The event type. This is a unique identifier for this event.\n * @param handler - The event handler. The type of the parameters for this event handler must\n * match the type of the payload for this event type.\n * @template E - A type union of Event type strings.\n */\n subscribe(\n eventType: E,\n handler: ExtractEventHandler,\n ): void;\n\n /**\n * Subscribe to an event, with a selector.\n *\n * Registers the given handler function as an event handler for the given\n * event type. When an event is published, its payload is first passed to the\n * selector. The event handler is only called if the selector's return value\n * differs from its last known return value.\n *\n * @param eventType - The event type. This is a unique identifier for this event.\n * @param handler - The event handler. The type of the parameters for this event\n * handler must match the return type of the selector.\n * @param selector - The selector function used to select relevant data from\n * the event payload. The type of the parameters for this selector must match\n * the type of the payload for this event type.\n * @template E - A type union of Event type strings.\n * @template V - The selector return value.\n */\n subscribe(\n eventType: E,\n handler: SelectorEventHandler,\n selector: SelectorFunction, V>,\n ): void;\n\n subscribe(\n eventType: E,\n handler: ExtractEventHandler,\n selector?: SelectorFunction, V>,\n ): void {\n let subscribers = this.events.get(eventType);\n if (!subscribers) {\n subscribers = new Map();\n this.events.set(eventType, subscribers);\n }\n\n subscribers.set(handler, selector);\n }\n\n /**\n * Unsubscribe from an event.\n *\n * Unregisters the given function as an event handler for the given event.\n *\n * @param eventType - The event type. This is a unique identifier for this event.\n * @param handler - The event handler to unregister.\n * @throws Will throw when the given event handler is not registered for this event.\n * @template E - A type union of Event type strings.\n */\n unsubscribe(\n eventType: E,\n handler: ExtractEventHandler,\n ) {\n const subscribers = this.events.get(eventType);\n\n if (!subscribers || !subscribers.has(handler)) {\n throw new Error(`Subscription not found for event: ${eventType}`);\n }\n\n const selector = subscribers.get(handler);\n if (selector) {\n this.eventPayloadCache.delete(handler);\n }\n\n subscribers.delete(handler);\n }\n\n /**\n * Clear subscriptions for a specific event.\n *\n * This will remove all subscribed handlers for this event.\n *\n * @param eventType - The event type. This is a unique identifier for this event.\n * @template E - A type union of Event type strings.\n */\n clearEventSubscriptions(eventType: E) {\n this.events.delete(eventType);\n }\n\n /**\n * Clear all subscriptions.\n *\n * This will remove all subscribed handlers for all events.\n */\n clearSubscriptions() {\n this.events.clear();\n }\n\n /**\n * Get a restricted controller messenger\n *\n * Returns a wrapper around the controller messenger instance that restricts access to actions\n * and events. The provided allowlists grant the ability to call the listed actions and subscribe\n * to the listed events. The \"name\" provided grants ownership of any actions and events under\n * that namespace. Ownership allows registering actions and publishing events, as well as\n * unregistering actions and clearing event subscriptions.\n *\n * @param options - Controller messenger options.\n * @param options.name - The name of the thing this messenger will be handed to (e.g. the\n * controller name). This grants \"ownership\" of actions and events under this namespace to the\n * restricted controller messenger returned.\n * @param options.allowedActions - The list of actions that this restricted controller messenger\n * should be alowed to call.\n * @param options.allowedEvents - The list of events that this restricted controller messenger\n * should be allowed to subscribe to.\n * @template N - The namespace for this messenger. Typically this is the name of the controller or\n * module that this messenger has been created for. The authority to publish events and register\n * actions under this namespace is granted to this restricted messenger instance.\n * @template AllowedAction - A type union of the 'type' string for any allowed actions.\n * @template AllowedEvent - A type union of the 'type' string for any allowed events.\n * @returns The restricted controller messenger.\n */\n getRestricted<\n N extends string,\n AllowedAction extends string,\n AllowedEvent extends string,\n >({\n name,\n allowedActions,\n allowedEvents,\n }: {\n name: N;\n allowedActions?: Extract[];\n allowedEvents?: Extract[];\n }): RestrictedControllerMessenger<\n N,\n NarrowToNamespace | NarrowToAllowed,\n NarrowToNamespace | NarrowToAllowed,\n AllowedAction,\n AllowedEvent\n > {\n return new RestrictedControllerMessenger<\n N,\n NarrowToNamespace | NarrowToAllowed,\n NarrowToNamespace | NarrowToAllowed,\n AllowedAction,\n AllowedEvent\n >({\n controllerMessenger: this,\n name,\n allowedActions,\n allowedEvents,\n });\n }\n}\n"]} \ No newline at end of file diff --git a/dist/announcement/AnnouncementController.d.ts b/dist/announcement/AnnouncementController.d.ts new file mode 100644 index 0000000000..5a5ab63f0b --- /dev/null +++ b/dist/announcement/AnnouncementController.d.ts @@ -0,0 +1,63 @@ +import { BaseController, BaseConfig, BaseState } from '../BaseController'; +interface ViewedAnnouncement { + [id: number]: boolean; +} +interface Announcement { + id: number; + date: string; +} +interface StateAnnouncement extends Announcement { + isShown: boolean; +} +/** + * A map of announcement ids to Announcement objects + */ +interface AnnouncementMap { + [id: number]: Announcement; +} +/** + * A map of announcement ids to StateAnnouncement objects + */ +export interface StateAnnouncementMap { + [id: number]: StateAnnouncement; +} +/** + * AnnouncementConfig will hold the active announcements + */ +export interface AnnouncementConfig extends BaseConfig { + allAnnouncements: AnnouncementMap; +} +/** + * Announcement state will hold all the seen and unseen announcements + * that are still active + */ +export interface AnnouncementState extends BaseState { + announcements: StateAnnouncementMap; +} +/** + * Controller for managing in-app announcements. + */ +export declare class AnnouncementController extends BaseController { + /** + * Creates a AnnouncementController instance. + * + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor(config: AnnouncementConfig, state?: AnnouncementState); + /** + * Compares the announcements in state with the announcements from file + * to check if there are any new announcements + * if yes, the new announcement will be added to the state with a flag indicating + * that the announcement is not seen by the user. + */ + private _addAnnouncements; + /** + * Updates the status of the status of the specified announcements + * once it is read by the user. + * + * @param viewedIds - The announcement IDs to mark as viewed. + */ + updateViewed(viewedIds: ViewedAnnouncement): void; +} +export {}; diff --git a/dist/announcement/AnnouncementController.js b/dist/announcement/AnnouncementController.js new file mode 100644 index 0000000000..f432f7830d --- /dev/null +++ b/dist/announcement/AnnouncementController.js @@ -0,0 +1,54 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AnnouncementController = void 0; +const BaseController_1 = require("../BaseController"); +const defaultState = { + announcements: {}, +}; +/** + * Controller for managing in-app announcements. + */ +class AnnouncementController extends BaseController_1.BaseController { + /** + * Creates a AnnouncementController instance. + * + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor(config, state) { + super(config, state || defaultState); + this.initialize(); + this._addAnnouncements(); + } + /** + * Compares the announcements in state with the announcements from file + * to check if there are any new announcements + * if yes, the new announcement will be added to the state with a flag indicating + * that the announcement is not seen by the user. + */ + _addAnnouncements() { + const newAnnouncements = {}; + const { allAnnouncements } = this.config; + Object.values(allAnnouncements).forEach((announcement) => { + newAnnouncements[announcement.id] = this.state.announcements[announcement.id] + ? this.state.announcements[announcement.id] + : Object.assign(Object.assign({}, announcement), { isShown: false }); + }); + this.update({ announcements: newAnnouncements }); + } + /** + * Updates the status of the status of the specified announcements + * once it is read by the user. + * + * @param viewedIds - The announcement IDs to mark as viewed. + */ + updateViewed(viewedIds) { + const stateAnnouncements = this.state.announcements; + for (const id of Object.keys(viewedIds).map(Number)) { + stateAnnouncements[id].isShown = viewedIds[id]; + } + this.update({ announcements: stateAnnouncements }, true); + } +} +exports.AnnouncementController = AnnouncementController; +//# sourceMappingURL=AnnouncementController.js.map \ No newline at end of file diff --git a/dist/announcement/AnnouncementController.js.map b/dist/announcement/AnnouncementController.js.map new file mode 100644 index 0000000000..fb037eb97a --- /dev/null +++ b/dist/announcement/AnnouncementController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"AnnouncementController.js","sourceRoot":"","sources":["../../src/announcement/AnnouncementController.ts"],"names":[],"mappings":";;;AAAA,sDAA0E;AA4C1E,MAAM,YAAY,GAAG;IACnB,aAAa,EAAE,EAAE;CAClB,CAAC;AAEF;;GAEG;AACH,MAAa,sBAAuB,SAAQ,+BAG3C;IACC;;;;;OAKG;IACH,YAAY,MAA0B,EAAE,KAAyB;QAC/D,KAAK,CAAC,MAAM,EAAE,KAAK,IAAI,YAAY,CAAC,CAAC;QACrC,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,CAAC,iBAAiB,EAAE,CAAC;IAC3B,CAAC;IAED;;;;;OAKG;IACK,iBAAiB;QACvB,MAAM,gBAAgB,GAAyB,EAAE,CAAC;QAClD,MAAM,EAAE,gBAAgB,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC;QACzC,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,OAAO,CACrC,CAAC,YAA+B,EAAE,EAAE;YAClC,gBAAgB,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAC1D,YAAY,CAAC,EAAE,CAChB;gBACC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC3C,CAAC,iCACM,YAAY,KACf,OAAO,EAAE,KAAK,GACf,CAAC;QACR,CAAC,CACF,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,gBAAgB,EAAE,CAAC,CAAC;IACnD,CAAC;IAED;;;;;OAKG;IACH,YAAY,CAAC,SAA6B;QACxC,MAAM,kBAAkB,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC;QAEpD,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;YACnD,kBAAkB,CAAC,EAAE,CAAC,CAAC,OAAO,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;SAChD;QACD,IAAI,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,kBAAkB,EAAE,EAAE,IAAI,CAAC,CAAC;IAC3D,CAAC;CACF;AAtDD,wDAsDC","sourcesContent":["import { BaseController, BaseConfig, BaseState } from '../BaseController';\n\ninterface ViewedAnnouncement {\n [id: number]: boolean;\n}\n\ninterface Announcement {\n id: number;\n date: string;\n}\n\ninterface StateAnnouncement extends Announcement {\n isShown: boolean;\n}\n\n/**\n * A map of announcement ids to Announcement objects\n */\ninterface AnnouncementMap {\n [id: number]: Announcement;\n}\n\n/**\n * A map of announcement ids to StateAnnouncement objects\n */\nexport interface StateAnnouncementMap {\n [id: number]: StateAnnouncement;\n}\n\n/**\n * AnnouncementConfig will hold the active announcements\n */\nexport interface AnnouncementConfig extends BaseConfig {\n allAnnouncements: AnnouncementMap;\n}\n\n/**\n * Announcement state will hold all the seen and unseen announcements\n * that are still active\n */\nexport interface AnnouncementState extends BaseState {\n announcements: StateAnnouncementMap;\n}\n\nconst defaultState = {\n announcements: {},\n};\n\n/**\n * Controller for managing in-app announcements.\n */\nexport class AnnouncementController extends BaseController<\n AnnouncementConfig,\n AnnouncementState\n> {\n /**\n * Creates a AnnouncementController instance.\n *\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(config: AnnouncementConfig, state?: AnnouncementState) {\n super(config, state || defaultState);\n this.initialize();\n this._addAnnouncements();\n }\n\n /**\n * Compares the announcements in state with the announcements from file\n * to check if there are any new announcements\n * if yes, the new announcement will be added to the state with a flag indicating\n * that the announcement is not seen by the user.\n */\n private _addAnnouncements(): void {\n const newAnnouncements: StateAnnouncementMap = {};\n const { allAnnouncements } = this.config;\n Object.values(allAnnouncements).forEach(\n (announcement: StateAnnouncement) => {\n newAnnouncements[announcement.id] = this.state.announcements[\n announcement.id\n ]\n ? this.state.announcements[announcement.id]\n : {\n ...announcement,\n isShown: false,\n };\n },\n );\n this.update({ announcements: newAnnouncements });\n }\n\n /**\n * Updates the status of the status of the specified announcements\n * once it is read by the user.\n *\n * @param viewedIds - The announcement IDs to mark as viewed.\n */\n updateViewed(viewedIds: ViewedAnnouncement): void {\n const stateAnnouncements = this.state.announcements;\n\n for (const id of Object.keys(viewedIds).map(Number)) {\n stateAnnouncements[id].isShown = viewedIds[id];\n }\n this.update({ announcements: stateAnnouncements }, true);\n }\n}\n"]} \ No newline at end of file diff --git a/dist/apis/crypto-compare.d.ts b/dist/apis/crypto-compare.d.ts new file mode 100644 index 0000000000..a62342e4fe --- /dev/null +++ b/dist/apis/crypto-compare.d.ts @@ -0,0 +1,12 @@ +/** + * Fetches the exchange rate for a given currency. + * + * @param currency - ISO 4217 currency code. + * @param nativeCurrency - Symbol for base asset. + * @param includeUSDRate - Whether to add the USD rate to the fetch. + * @returns Promise resolving to exchange rate for given currency. + */ +export declare function fetchExchangeRate(currency: string, nativeCurrency: string, includeUSDRate?: boolean): Promise<{ + conversionRate: number; + usdConversionRate: number; +}>; diff --git a/dist/apis/crypto-compare.js b/dist/apis/crypto-compare.js new file mode 100644 index 0000000000..2dd7fcae94 --- /dev/null +++ b/dist/apis/crypto-compare.js @@ -0,0 +1,67 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchExchangeRate = void 0; +const util_1 = require("../util"); +/** + * Get the CryptoCompare API URL for getting the conversion rate from the given native currency to + * the given currency. Optionally, the conversion rate from the native currency to USD can also be + * included in the response. + * + * @param currentCurrency - The currency to get a conversion rate for. + * @param nativeCurrency - The native currency to convert from. + * @param includeUSDRate - Whether or not the native currency to USD conversion rate should be + * included in the response as well. + * @returns The API URL for getting the conversion rate. + */ +function getPricingURL(currentCurrency, nativeCurrency, includeUSDRate) { + return (`https://min-api.cryptocompare.com/data/price?fsym=` + + `${nativeCurrency.toUpperCase()}&tsyms=${currentCurrency.toUpperCase()}` + + `${includeUSDRate && currentCurrency.toUpperCase() !== 'USD' ? ',USD' : ''}`); +} +/** + * Fetches the exchange rate for a given currency. + * + * @param currency - ISO 4217 currency code. + * @param nativeCurrency - Symbol for base asset. + * @param includeUSDRate - Whether to add the USD rate to the fetch. + * @returns Promise resolving to exchange rate for given currency. + */ +function fetchExchangeRate(currency, nativeCurrency, includeUSDRate) { + return __awaiter(this, void 0, void 0, function* () { + const json = yield (0, util_1.handleFetch)(getPricingURL(currency, nativeCurrency, includeUSDRate)); + /* + Example expected error response (if pair is not found) + { + Response: "Error", + Message: "cccagg_or_exchange market does not exist for this coin pair (ETH-)", + HasWarning: false, + } + */ + if (json.Response === 'Error') { + throw new Error(json.Message); + } + const conversionRate = Number(json[currency.toUpperCase()]); + const usdConversionRate = Number(json.USD); + if (!Number.isFinite(conversionRate)) { + throw new Error(`Invalid response for ${currency.toUpperCase()}: ${json[currency.toUpperCase()]}`); + } + if (includeUSDRate && !Number.isFinite(usdConversionRate)) { + throw new Error(`Invalid response for usdConversionRate: ${json.USD}`); + } + return { + conversionRate, + usdConversionRate, + }; + }); +} +exports.fetchExchangeRate = fetchExchangeRate; +//# sourceMappingURL=crypto-compare.js.map \ No newline at end of file diff --git a/dist/apis/crypto-compare.js.map b/dist/apis/crypto-compare.js.map new file mode 100644 index 0000000000..c6b80eb789 --- /dev/null +++ b/dist/apis/crypto-compare.js.map @@ -0,0 +1 @@ +{"version":3,"file":"crypto-compare.js","sourceRoot":"","sources":["../../src/apis/crypto-compare.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,kCAAsC;AAEtC;;;;;;;;;;GAUG;AACH,SAAS,aAAa,CACpB,eAAuB,EACvB,cAAsB,EACtB,cAAwB;IAExB,OAAO,CACL,oDAAoD;QACpD,GAAG,cAAc,CAAC,WAAW,EAAE,UAAU,eAAe,CAAC,WAAW,EAAE,EAAE;QACxE,GAAG,cAAc,IAAI,eAAe,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAC7E,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,SAAsB,iBAAiB,CACrC,QAAgB,EAChB,cAAsB,EACtB,cAAwB;;QAKxB,MAAM,IAAI,GAAG,MAAM,IAAA,kBAAW,EAC5B,aAAa,CAAC,QAAQ,EAAE,cAAc,EAAE,cAAc,CAAC,CACxD,CAAC;QAEF;;;;;;;UAOE;QACF,IAAI,IAAI,CAAC,QAAQ,KAAK,OAAO,EAAE;YAC7B,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;SAC/B;QAED,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QAE5D,MAAM,iBAAiB,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE;YACpC,MAAM,IAAI,KAAK,CACb,wBAAwB,QAAQ,CAAC,WAAW,EAAE,KAC5C,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAC7B,EAAE,CACH,CAAC;SACH;QAED,IAAI,cAAc,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE;YACzD,MAAM,IAAI,KAAK,CAAC,2CAA2C,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;SACxE;QAED,OAAO;YACL,cAAc;YACd,iBAAiB;SAClB,CAAC;IACJ,CAAC;CAAA;AA3CD,8CA2CC","sourcesContent":["import { handleFetch } from '../util';\n\n/**\n * Get the CryptoCompare API URL for getting the conversion rate from the given native currency to\n * the given currency. Optionally, the conversion rate from the native currency to USD can also be\n * included in the response.\n *\n * @param currentCurrency - The currency to get a conversion rate for.\n * @param nativeCurrency - The native currency to convert from.\n * @param includeUSDRate - Whether or not the native currency to USD conversion rate should be\n * included in the response as well.\n * @returns The API URL for getting the conversion rate.\n */\nfunction getPricingURL(\n currentCurrency: string,\n nativeCurrency: string,\n includeUSDRate?: boolean,\n) {\n return (\n `https://min-api.cryptocompare.com/data/price?fsym=` +\n `${nativeCurrency.toUpperCase()}&tsyms=${currentCurrency.toUpperCase()}` +\n `${includeUSDRate && currentCurrency.toUpperCase() !== 'USD' ? ',USD' : ''}`\n );\n}\n\n/**\n * Fetches the exchange rate for a given currency.\n *\n * @param currency - ISO 4217 currency code.\n * @param nativeCurrency - Symbol for base asset.\n * @param includeUSDRate - Whether to add the USD rate to the fetch.\n * @returns Promise resolving to exchange rate for given currency.\n */\nexport async function fetchExchangeRate(\n currency: string,\n nativeCurrency: string,\n includeUSDRate?: boolean,\n): Promise<{\n conversionRate: number;\n usdConversionRate: number;\n}> {\n const json = await handleFetch(\n getPricingURL(currency, nativeCurrency, includeUSDRate),\n );\n\n /*\n Example expected error response (if pair is not found)\n {\n Response: \"Error\",\n Message: \"cccagg_or_exchange market does not exist for this coin pair (ETH-)\",\n HasWarning: false,\n }\n */\n if (json.Response === 'Error') {\n throw new Error(json.Message);\n }\n\n const conversionRate = Number(json[currency.toUpperCase()]);\n\n const usdConversionRate = Number(json.USD);\n if (!Number.isFinite(conversionRate)) {\n throw new Error(\n `Invalid response for ${currency.toUpperCase()}: ${\n json[currency.toUpperCase()]\n }`,\n );\n }\n\n if (includeUSDRate && !Number.isFinite(usdConversionRate)) {\n throw new Error(`Invalid response for usdConversionRate: ${json.USD}`);\n }\n\n return {\n conversionRate,\n usdConversionRate,\n };\n}\n"]} \ No newline at end of file diff --git a/dist/apis/token-service.d.ts b/dist/apis/token-service.d.ts new file mode 100644 index 0000000000..25542efd6f --- /dev/null +++ b/dist/apis/token-service.d.ts @@ -0,0 +1,29 @@ +export declare const TOKEN_END_POINT_API = "https://token-api.metaswap.codefi.network"; +export declare const TOKEN_METADATA_NO_SUPPORT_ERROR = "TokenService Error: Network does not support fetchTokenMetadata"; +/** + * Fetch the list of token metadata for a given network. This request is cancellable using the + * abort signal passed in. + * + * @param chainId - The chain ID of the network the requested tokens are on. + * @param abortSignal - The abort signal used to cancel the request if necessary. + * @param options - Additional fetch options. + * @param options.timeout - The fetch timeout. + * @returns The token list, or `undefined` if the request was cancelled. + */ +export declare function fetchTokenList(chainId: string, abortSignal: AbortSignal, { timeout }?: { + timeout?: number | undefined; +}): Promise; +/** + * Fetch metadata for the token address provided for a given network. This request is cancellable + * using the abort signal passed in. + * + * @param chainId - The chain ID of the network the token is on. + * @param tokenAddress - The address of the token to fetch metadata for. + * @param abortSignal - The abort signal used to cancel the request if necessary. + * @param options - Additional fetch options. + * @param options.timeout - The fetch timeout. + * @returns The token metadata, or `undefined` if the request was either aborted or failed. + */ +export declare function fetchTokenMetadata(chainId: string, tokenAddress: string, abortSignal: AbortSignal, { timeout }?: { + timeout?: number | undefined; +}): Promise; diff --git a/dist/apis/token-service.js b/dist/apis/token-service.js new file mode 100644 index 0000000000..760f93070e --- /dev/null +++ b/dist/apis/token-service.js @@ -0,0 +1,133 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.fetchTokenMetadata = exports.fetchTokenList = exports.TOKEN_METADATA_NO_SUPPORT_ERROR = exports.TOKEN_END_POINT_API = void 0; +const util_1 = require("../util"); +exports.TOKEN_END_POINT_API = 'https://token-api.metaswap.codefi.network'; +exports.TOKEN_METADATA_NO_SUPPORT_ERROR = 'TokenService Error: Network does not support fetchTokenMetadata'; +/** + * Get the tokens URL for a specific network. + * + * @param chainId - The chain ID of the network the tokens requested are on. + * @returns The tokens URL. + */ +function getTokensURL(chainId) { + return `${exports.TOKEN_END_POINT_API}/tokens/${chainId}`; +} +/** + * Get the token metadata URL for the given network and token. + * + * @param chainId - The chain ID of the network the token is on. + * @param tokenAddress - The token address. + * @returns The token metadata URL. + */ +function getTokenMetadataURL(chainId, tokenAddress) { + return `${exports.TOKEN_END_POINT_API}/token/${chainId}?address=${tokenAddress}`; +} +const tenSecondsInMilliseconds = 10000; +// Token list averages 1.6 MB in size +// timeoutFetch by default has a 500ms timeout, which will almost always timeout given the response size. +const defaultTimeout = tenSecondsInMilliseconds; +/** + * Fetch the list of token metadata for a given network. This request is cancellable using the + * abort signal passed in. + * + * @param chainId - The chain ID of the network the requested tokens are on. + * @param abortSignal - The abort signal used to cancel the request if necessary. + * @param options - Additional fetch options. + * @param options.timeout - The fetch timeout. + * @returns The token list, or `undefined` if the request was cancelled. + */ +function fetchTokenList(chainId, abortSignal, { timeout = defaultTimeout } = {}) { + return __awaiter(this, void 0, void 0, function* () { + const tokenURL = getTokensURL(chainId); + const response = yield queryApi(tokenURL, abortSignal, timeout); + if (response) { + return parseJsonResponse(response); + } + return undefined; + }); +} +exports.fetchTokenList = fetchTokenList; +/** + * Fetch metadata for the token address provided for a given network. This request is cancellable + * using the abort signal passed in. + * + * @param chainId - The chain ID of the network the token is on. + * @param tokenAddress - The address of the token to fetch metadata for. + * @param abortSignal - The abort signal used to cancel the request if necessary. + * @param options - Additional fetch options. + * @param options.timeout - The fetch timeout. + * @returns The token metadata, or `undefined` if the request was either aborted or failed. + */ +function fetchTokenMetadata(chainId, tokenAddress, abortSignal, { timeout = defaultTimeout } = {}) { + return __awaiter(this, void 0, void 0, function* () { + if (!(0, util_1.isTokenDetectionSupportedForNetwork)(chainId)) { + throw new Error(exports.TOKEN_METADATA_NO_SUPPORT_ERROR); + } + const tokenMetadataURL = getTokenMetadataURL(chainId, tokenAddress); + const response = yield queryApi(tokenMetadataURL, abortSignal, timeout); + if (response) { + return parseJsonResponse(response); + } + return undefined; + }); +} +exports.fetchTokenMetadata = fetchTokenMetadata; +/** + * Perform fetch request against the api. + * + * @param apiURL - The URL of the API to fetch. + * @param abortSignal - The abort signal used to cancel the request if necessary. + * @param timeout - The fetch timeout. + * @returns Promise resolving request response. + */ +function queryApi(apiURL, abortSignal, timeout) { + return __awaiter(this, void 0, void 0, function* () { + const fetchOptions = { + referrer: apiURL, + referrerPolicy: 'no-referrer-when-downgrade', + method: 'GET', + mode: 'cors', + signal: abortSignal, + cache: 'default', + }; + fetchOptions.headers = new window.Headers(); + fetchOptions.headers.set('Content-Type', 'application/json'); + try { + return yield (0, util_1.timeoutFetch)(apiURL, fetchOptions, timeout); + } + catch (error) { + if (error instanceof Error && error.name === 'AbortError') { + console.log('Request is aborted'); + } + } + return undefined; + }); +} +/** + * Parse an API response and return the response JSON data. + * + * @param apiResponse - The API response to parse. + * @returns The response JSON data. + * @throws Will throw if the response includes an error. + */ +function parseJsonResponse(apiResponse) { + return __awaiter(this, void 0, void 0, function* () { + const responseObj = yield apiResponse.json(); + // api may return errors as json without setting an error http status code + if (responseObj === null || responseObj === void 0 ? void 0 : responseObj.error) { + throw new Error(`TokenService Error: ${responseObj.error}`); + } + return responseObj; + }); +} +//# sourceMappingURL=token-service.js.map \ No newline at end of file diff --git a/dist/apis/token-service.js.map b/dist/apis/token-service.js.map new file mode 100644 index 0000000000..ce474a034b --- /dev/null +++ b/dist/apis/token-service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"token-service.js","sourceRoot":"","sources":["../../src/apis/token-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { isTokenDetectionSupportedForNetwork, timeoutFetch } from '../util';\n\nexport const TOKEN_END_POINT_API = 'https://token-api.metaswap.codefi.network';\nexport const TOKEN_METADATA_NO_SUPPORT_ERROR =\n 'TokenService Error: Network does not support fetchTokenMetadata';\n\n/**\n * Get the tokens URL for a specific network.\n *\n * @param chainId - The chain ID of the network the tokens requested are on.\n * @returns The tokens URL.\n */\nfunction getTokensURL(chainId: string) {\n return `${TOKEN_END_POINT_API}/tokens/${chainId}`;\n}\n\n/**\n * Get the token metadata URL for the given network and token.\n *\n * @param chainId - The chain ID of the network the token is on.\n * @param tokenAddress - The token address.\n * @returns The token metadata URL.\n */\nfunction getTokenMetadataURL(chainId: string, tokenAddress: string) {\n return `${TOKEN_END_POINT_API}/token/${chainId}?address=${tokenAddress}`;\n}\n\nconst tenSecondsInMilliseconds = 10_000;\n\n// Token list averages 1.6 MB in size\n// timeoutFetch by default has a 500ms timeout, which will almost always timeout given the response size.\nconst defaultTimeout = tenSecondsInMilliseconds;\n\n/**\n * Fetch the list of token metadata for a given network. This request is cancellable using the\n * abort signal passed in.\n *\n * @param chainId - The chain ID of the network the requested tokens are on.\n * @param abortSignal - The abort signal used to cancel the request if necessary.\n * @param options - Additional fetch options.\n * @param options.timeout - The fetch timeout.\n * @returns The token list, or `undefined` if the request was cancelled.\n */\nexport async function fetchTokenList(\n chainId: string,\n abortSignal: AbortSignal,\n { timeout = defaultTimeout } = {},\n): Promise {\n const tokenURL = getTokensURL(chainId);\n const response = await queryApi(tokenURL, abortSignal, timeout);\n if (response) {\n return parseJsonResponse(response);\n }\n return undefined;\n}\n\n/**\n * Fetch metadata for the token address provided for a given network. This request is cancellable\n * using the abort signal passed in.\n *\n * @param chainId - The chain ID of the network the token is on.\n * @param tokenAddress - The address of the token to fetch metadata for.\n * @param abortSignal - The abort signal used to cancel the request if necessary.\n * @param options - Additional fetch options.\n * @param options.timeout - The fetch timeout.\n * @returns The token metadata, or `undefined` if the request was either aborted or failed.\n */\nexport async function fetchTokenMetadata(\n chainId: string,\n tokenAddress: string,\n abortSignal: AbortSignal,\n { timeout = defaultTimeout } = {},\n): Promise {\n if (!isTokenDetectionSupportedForNetwork(chainId)) {\n throw new Error(TOKEN_METADATA_NO_SUPPORT_ERROR);\n }\n const tokenMetadataURL = getTokenMetadataURL(chainId, tokenAddress);\n const response = await queryApi(tokenMetadataURL, abortSignal, timeout);\n if (response) {\n return parseJsonResponse(response) as Promise;\n }\n return undefined;\n}\n\n/**\n * Perform fetch request against the api.\n *\n * @param apiURL - The URL of the API to fetch.\n * @param abortSignal - The abort signal used to cancel the request if necessary.\n * @param timeout - The fetch timeout.\n * @returns Promise resolving request response.\n */\nasync function queryApi(\n apiURL: string,\n abortSignal: AbortSignal,\n timeout: number,\n): Promise {\n const fetchOptions: RequestInit = {\n referrer: apiURL,\n referrerPolicy: 'no-referrer-when-downgrade',\n method: 'GET',\n mode: 'cors',\n signal: abortSignal,\n cache: 'default',\n };\n fetchOptions.headers = new window.Headers();\n fetchOptions.headers.set('Content-Type', 'application/json');\n try {\n return await timeoutFetch(apiURL, fetchOptions, timeout);\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n console.log('Request is aborted');\n }\n }\n return undefined;\n}\n\n/**\n * Parse an API response and return the response JSON data.\n *\n * @param apiResponse - The API response to parse.\n * @returns The response JSON data.\n * @throws Will throw if the response includes an error.\n */\nasync function parseJsonResponse(apiResponse: Response): Promise {\n const responseObj = await apiResponse.json();\n // api may return errors as json without setting an error http status code\n if (responseObj?.error) {\n throw new Error(`TokenService Error: ${responseObj.error}`);\n }\n return responseObj;\n}\n"]} \ No newline at end of file diff --git a/dist/approval/ApprovalController.d.ts b/dist/approval/ApprovalController.d.ts new file mode 100644 index 0000000000..02fb45ec3b --- /dev/null +++ b/dist/approval/ApprovalController.d.ts @@ -0,0 +1,278 @@ +import type { Patch } from 'immer'; +import { EthereumRpcError } from 'eth-rpc-errors'; +import { BaseController, Json } from '../BaseControllerV2'; +import type { RestrictedControllerMessenger } from '../ControllerMessenger'; +declare const controllerName = "ApprovalController"; +declare type ApprovalRequestData = Record | null; +export declare type ApprovalRequest = { + /** + * The ID of the approval request. + */ + id: string; + /** + * The origin of the approval request. + */ + origin: string; + /** + * The time that the request was received, per Date.now(). + */ + time: number; + /** + * The type of the approval request. + */ + type: string; + /** + * Additional data associated with the request. + * TODO:TS4.4 make optional + */ + requestData: RequestData; +}; +declare type ShowApprovalRequest = () => void | Promise; +export declare type ApprovalControllerState = { + pendingApprovals: Record>>; + pendingApprovalCount: number; +}; +export declare type GetApprovalsState = { + type: `${typeof controllerName}:getState`; + handler: () => ApprovalControllerState; +}; +export declare type ClearApprovalRequests = { + type: `${typeof controllerName}:clearRequests`; + handler: (error: EthereumRpcError) => void; +}; +declare type AddApprovalOptions = { + id?: string; + origin: string; + type: string; + requestData?: Record; +}; +export declare type AddApprovalRequest = { + type: `${typeof controllerName}:addRequest`; + handler: (opts: AddApprovalOptions, shouldShowRequest: boolean) => ReturnType; +}; +export declare type HasApprovalRequest = { + type: `${typeof controllerName}:hasRequest`; + handler: ApprovalController['has']; +}; +export declare type AcceptRequest = { + type: `${typeof controllerName}:acceptRequest`; + handler: ApprovalController['accept']; +}; +export declare type RejectRequest = { + type: `${typeof controllerName}:rejectRequest`; + handler: ApprovalController['reject']; +}; +export declare type ApprovalControllerActions = GetApprovalsState | ClearApprovalRequests | AddApprovalRequest | HasApprovalRequest | AcceptRequest | RejectRequest; +export declare type ApprovalStateChange = { + type: `${typeof controllerName}:stateChange`; + payload: [ApprovalControllerState, Patch[]]; +}; +export declare type ApprovalControllerEvents = ApprovalStateChange; +export declare type ApprovalControllerMessenger = RestrictedControllerMessenger; +declare type ApprovalControllerOptions = { + messenger: ApprovalControllerMessenger; + showApprovalRequest: ShowApprovalRequest; + state?: Partial; +}; +/** + * Controller for managing requests that require user approval. + * + * Enables limiting the number of pending requests by origin and type, counting + * pending requests, and more. + * + * Adding a request returns a promise that resolves or rejects when the request + * is approved or denied, respectively. + */ +export declare class ApprovalController extends BaseController { + private _approvals; + private _origins; + private _showApprovalRequest; + /** + * Construct an Approval controller. + * + * @param options - The controller options. + * @param options.showApprovalRequest - Function for opening the UI such that + * the request can be displayed to the user. + * @param options.messenger - The restricted controller messenger for the Approval controller. + * @param options.state - The initial controller state. + */ + constructor({ messenger, showApprovalRequest, state, }: ApprovalControllerOptions); + /** + * Constructor helper for registering this controller's messaging system + * actions. + */ + private registerMessageHandlers; + /** + * Adds an approval request per the given arguments, calls the show approval + * request function, and returns the associated approval promise. + * + * There can only be one approval per origin and type. An error is thrown if + * attempting to add an invalid or duplicate request. + * + * @param opts - Options bag. + * @param opts.id - The id of the approval request. A random id will be + * generated if none is provided. + * @param opts.origin - The origin of the approval request. + * @param opts.type - The type associated with the approval request. + * @param opts.requestData - Additional data associated with the request, + * if any. + * @returns The approval promise. + */ + addAndShowApprovalRequest(opts: AddApprovalOptions): Promise; + /** + * Adds an approval request per the given arguments and returns the approval + * promise. + * + * There can only be one approval per origin and type. An error is thrown if + * attempting to add an invalid or duplicate request. + * + * @param opts - Options bag. + * @param opts.id - The id of the approval request. A random id will be + * generated if none is provided. + * @param opts.origin - The origin of the approval request. + * @param opts.type - The type associated with the approval request. + * @param opts.requestData - Additional data associated with the request, + * if any. + * @returns The approval promise. + */ + add(opts: AddApprovalOptions): Promise; + /** + * Gets the info for the approval request with the given id. + * + * @param id - The id of the approval request. + * @returns The approval request data associated with the id. + */ + get(id: string): ApprovalRequest | undefined; + /** + * Gets the number of pending approvals, by origin and/or type. + * + * If only `origin` is specified, all approvals for that origin will be + * counted, regardless of type. + * If only `type` is specified, all approvals for that type will be counted, + * regardless of origin. + * If both `origin` and `type` are specified, 0 or 1 will be returned. + * + * @param opts - The approval count options. + * @param opts.origin - An approval origin. + * @param opts.type - The type of the approval request. + * @returns The current approval request count for the given origin and/or + * type. + */ + getApprovalCount(opts?: { + origin?: string; + type?: string; + }): number; + /** + * Get the total count of all pending approval requests for all origins. + * + * @returns The total pending approval request count. + */ + getTotalApprovalCount(): number; + /** + * Checks if there's a pending approval request per the given parameters. + * At least one parameter must be specified. An error will be thrown if the + * parameters are invalid. + * + * If `id` is specified, all other parameters will be ignored. + * If `id` is not specified, the method will check for requests that match + * all of the specified parameters. + * + * @param opts - Options bag. + * @param opts.id - The ID to check for. + * @param opts.origin - The origin to check for. + * @param opts.type - The type to check for. + * @returns `true` if a matching approval is found, and `false` otherwise. + */ + has(opts?: { + id?: string; + origin?: string; + type?: string; + }): boolean; + /** + * Resolves the promise of the approval with the given id, and deletes the + * approval. Throws an error if no such approval exists. + * + * @param id - The id of the approval request. + * @param value - The value to resolve the approval promise with. + */ + accept(id: string, value?: unknown): void; + /** + * Rejects the promise of the approval with the given id, and deletes the + * approval. Throws an error if no such approval exists. + * + * @param id - The id of the approval request. + * @param error - The error to reject the approval promise with. + */ + reject(id: string, error: unknown): void; + /** + * Rejects and deletes all approval requests. + * + * @param rejectionError - The EthereumRpcError to reject the approval + * requests with. + */ + clear(rejectionError: EthereumRpcError): void; + /** + * Implementation of add operation. + * + * @param origin - The origin of the approval request. + * @param type - The type associated with the approval request. + * @param id - The id of the approval request. + * @param requestData - The request data associated with the approval request. + * @returns The approval promise. + */ + private _add; + /** + * Validates parameters to the add method. + * + * @param id - The id of the approval request. + * @param origin - The origin of the approval request. + * @param type - The type associated with the approval request. + * @param requestData - The request data associated with the approval request. + */ + private _validateAddParams; + /** + * Adds an entry to _origins. + * Performs no validation. + * + * @param origin - The origin of the approval request. + * @param type - The type associated with the approval request. + */ + private _addPendingApprovalOrigin; + /** + * Adds an entry to the store. + * Performs no validation. + * + * @param id - The id of the approval request. + * @param origin - The origin of the approval request. + * @param type - The type associated with the approval request. + * @param requestData - The request data associated with the approval request. + */ + private _addToStore; + /** + * Deletes the approval with the given id. The approval promise must be + * resolved or reject before this method is called. + * Deletion is an internal operation because approval state is solely + * managed by this controller. + * + * @param id - The id of the approval request to be deleted. + */ + private _delete; + /** + * Gets the approval callbacks for the given id, deletes the entry, and then + * returns the callbacks for promise resolution. + * Throws an error if no approval is found for the given id. + * + * @param id - The id of the approval request. + * @returns The promise callbacks associated with the approval request. + */ + private _deleteApprovalAndGetCallbacks; + /** + * Checks whether there are any approvals associated with the given + * origin. + * + * @param origin - The origin to check. + * @returns True if the origin has no approvals, false otherwise. + */ + private _isEmptyOrigin; +} +export default ApprovalController; diff --git a/dist/approval/ApprovalController.js b/dist/approval/ApprovalController.js new file mode 100644 index 0000000000..98d1ff2b9d --- /dev/null +++ b/dist/approval/ApprovalController.js @@ -0,0 +1,381 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ApprovalController = void 0; +const eth_rpc_errors_1 = require("eth-rpc-errors"); +const nanoid_1 = require("nanoid"); +const BaseControllerV2_1 = require("../BaseControllerV2"); +const controllerName = 'ApprovalController'; +const stateMetadata = { + pendingApprovals: { persist: false, anonymous: true }, + pendingApprovalCount: { persist: false, anonymous: false }, +}; +const getAlreadyPendingMessage = (origin, type) => `Request of type '${type}' already pending for origin ${origin}. Please wait.`; +const getDefaultState = () => { + return { + pendingApprovals: {}, + pendingApprovalCount: 0, + }; +}; +/** + * Controller for managing requests that require user approval. + * + * Enables limiting the number of pending requests by origin and type, counting + * pending requests, and more. + * + * Adding a request returns a promise that resolves or rejects when the request + * is approved or denied, respectively. + */ +class ApprovalController extends BaseControllerV2_1.BaseController { + /** + * Construct an Approval controller. + * + * @param options - The controller options. + * @param options.showApprovalRequest - Function for opening the UI such that + * the request can be displayed to the user. + * @param options.messenger - The restricted controller messenger for the Approval controller. + * @param options.state - The initial controller state. + */ + constructor({ messenger, showApprovalRequest, state = {}, }) { + super({ + name: controllerName, + metadata: stateMetadata, + messenger, + state: Object.assign(Object.assign({}, getDefaultState()), state), + }); + this._approvals = new Map(); + this._origins = new Map(); + this._showApprovalRequest = showApprovalRequest; + this.registerMessageHandlers(); + } + /** + * Constructor helper for registering this controller's messaging system + * actions. + */ + registerMessageHandlers() { + this.messagingSystem.registerActionHandler(`${controllerName}:clearRequests`, this.clear.bind(this)); + this.messagingSystem.registerActionHandler(`${controllerName}:addRequest`, (opts, shouldShowRequest) => { + if (shouldShowRequest) { + return this.addAndShowApprovalRequest(opts); + } + return this.add(opts); + }); + this.messagingSystem.registerActionHandler(`${controllerName}:hasRequest`, this.has.bind(this)); + this.messagingSystem.registerActionHandler(`${controllerName}:acceptRequest`, this.accept.bind(this)); + this.messagingSystem.registerActionHandler(`${controllerName}:rejectRequest`, this.reject.bind(this)); + } + /** + * Adds an approval request per the given arguments, calls the show approval + * request function, and returns the associated approval promise. + * + * There can only be one approval per origin and type. An error is thrown if + * attempting to add an invalid or duplicate request. + * + * @param opts - Options bag. + * @param opts.id - The id of the approval request. A random id will be + * generated if none is provided. + * @param opts.origin - The origin of the approval request. + * @param opts.type - The type associated with the approval request. + * @param opts.requestData - Additional data associated with the request, + * if any. + * @returns The approval promise. + */ + addAndShowApprovalRequest(opts) { + const promise = this._add(opts.origin, opts.type, opts.id, opts.requestData); + this._showApprovalRequest(); + return promise; + } + /** + * Adds an approval request per the given arguments and returns the approval + * promise. + * + * There can only be one approval per origin and type. An error is thrown if + * attempting to add an invalid or duplicate request. + * + * @param opts - Options bag. + * @param opts.id - The id of the approval request. A random id will be + * generated if none is provided. + * @param opts.origin - The origin of the approval request. + * @param opts.type - The type associated with the approval request. + * @param opts.requestData - Additional data associated with the request, + * if any. + * @returns The approval promise. + */ + add(opts) { + return this._add(opts.origin, opts.type, opts.id, opts.requestData); + } + /** + * Gets the info for the approval request with the given id. + * + * @param id - The id of the approval request. + * @returns The approval request data associated with the id. + */ + get(id) { + return this.state.pendingApprovals[id]; + } + /** + * Gets the number of pending approvals, by origin and/or type. + * + * If only `origin` is specified, all approvals for that origin will be + * counted, regardless of type. + * If only `type` is specified, all approvals for that type will be counted, + * regardless of origin. + * If both `origin` and `type` are specified, 0 or 1 will be returned. + * + * @param opts - The approval count options. + * @param opts.origin - An approval origin. + * @param opts.type - The type of the approval request. + * @returns The current approval request count for the given origin and/or + * type. + */ + getApprovalCount(opts = {}) { + var _a, _b; + if (!opts.origin && !opts.type) { + throw new Error('Must specify origin, type, or both.'); + } + const { origin, type: _type } = opts; + if (origin && _type) { + return Number(Boolean((_a = this._origins.get(origin)) === null || _a === void 0 ? void 0 : _a.has(_type))); + } + if (origin) { + return ((_b = this._origins.get(origin)) === null || _b === void 0 ? void 0 : _b.size) || 0; + } + // Only "type" was specified + let count = 0; + for (const approval of Object.values(this.state.pendingApprovals)) { + if (approval.type === _type) { + count += 1; + } + } + return count; + } + /** + * Get the total count of all pending approval requests for all origins. + * + * @returns The total pending approval request count. + */ + getTotalApprovalCount() { + return this.state.pendingApprovalCount; + } + /** + * Checks if there's a pending approval request per the given parameters. + * At least one parameter must be specified. An error will be thrown if the + * parameters are invalid. + * + * If `id` is specified, all other parameters will be ignored. + * If `id` is not specified, the method will check for requests that match + * all of the specified parameters. + * + * @param opts - Options bag. + * @param opts.id - The ID to check for. + * @param opts.origin - The origin to check for. + * @param opts.type - The type to check for. + * @returns `true` if a matching approval is found, and `false` otherwise. + */ + has(opts = {}) { + var _a; + const { id, origin, type: _type } = opts; + if (id) { + if (typeof id !== 'string') { + throw new Error('May not specify non-string id.'); + } + return this._approvals.has(id); + } + if (_type && typeof _type !== 'string') { + throw new Error('May not specify non-string type.'); + } + if (origin) { + if (typeof origin !== 'string') { + throw new Error('May not specify non-string origin.'); + } + // Check origin and type pair if type also specified + if (_type) { + return Boolean((_a = this._origins.get(origin)) === null || _a === void 0 ? void 0 : _a.has(_type)); + } + return this._origins.has(origin); + } + if (_type) { + for (const approval of Object.values(this.state.pendingApprovals)) { + if (approval.type === _type) { + return true; + } + } + return false; + } + throw new Error('Must specify a valid combination of id, origin, and type.'); + } + /** + * Resolves the promise of the approval with the given id, and deletes the + * approval. Throws an error if no such approval exists. + * + * @param id - The id of the approval request. + * @param value - The value to resolve the approval promise with. + */ + accept(id, value) { + this._deleteApprovalAndGetCallbacks(id).resolve(value); + } + /** + * Rejects the promise of the approval with the given id, and deletes the + * approval. Throws an error if no such approval exists. + * + * @param id - The id of the approval request. + * @param error - The error to reject the approval promise with. + */ + reject(id, error) { + this._deleteApprovalAndGetCallbacks(id).reject(error); + } + /** + * Rejects and deletes all approval requests. + * + * @param rejectionError - The EthereumRpcError to reject the approval + * requests with. + */ + clear(rejectionError) { + for (const id of this._approvals.keys()) { + this.reject(id, rejectionError); + } + this._origins.clear(); + this.update(() => getDefaultState()); + } + /** + * Implementation of add operation. + * + * @param origin - The origin of the approval request. + * @param type - The type associated with the approval request. + * @param id - The id of the approval request. + * @param requestData - The request data associated with the approval request. + * @returns The approval promise. + */ + _add(origin, type, id = (0, nanoid_1.nanoid)(), requestData) { + var _a; + this._validateAddParams(id, origin, type, requestData); + if ((_a = this._origins.get(origin)) === null || _a === void 0 ? void 0 : _a.has(type)) { + throw eth_rpc_errors_1.ethErrors.rpc.resourceUnavailable(getAlreadyPendingMessage(origin, type)); + } + // add pending approval + return new Promise((resolve, reject) => { + this._approvals.set(id, { resolve, reject }); + this._addPendingApprovalOrigin(origin, type); + this._addToStore(id, origin, type, requestData); + }); + } + /** + * Validates parameters to the add method. + * + * @param id - The id of the approval request. + * @param origin - The origin of the approval request. + * @param type - The type associated with the approval request. + * @param requestData - The request data associated with the approval request. + */ + _validateAddParams(id, origin, type, requestData) { + let errorMessage = null; + if (!id || typeof id !== 'string') { + errorMessage = 'Must specify non-empty string id.'; + } + else if (this._approvals.has(id)) { + errorMessage = `Approval request with id '${id}' already exists.`; + } + else if (!origin || typeof origin !== 'string') { + errorMessage = 'Must specify non-empty string origin.'; + } + else if (!type || typeof type !== 'string') { + errorMessage = 'Must specify non-empty string type.'; + } + else if (requestData && + (typeof requestData !== 'object' || Array.isArray(requestData))) { + errorMessage = 'Request data must be a plain object if specified.'; + } + if (errorMessage) { + throw eth_rpc_errors_1.ethErrors.rpc.internal(errorMessage); + } + } + /** + * Adds an entry to _origins. + * Performs no validation. + * + * @param origin - The origin of the approval request. + * @param type - The type associated with the approval request. + */ + _addPendingApprovalOrigin(origin, type) { + const originSet = this._origins.get(origin) || new Set(); + originSet.add(type); + if (!this._origins.has(origin)) { + this._origins.set(origin, originSet); + } + } + /** + * Adds an entry to the store. + * Performs no validation. + * + * @param id - The id of the approval request. + * @param origin - The origin of the approval request. + * @param type - The type associated with the approval request. + * @param requestData - The request data associated with the approval request. + */ + _addToStore(id, origin, type, requestData) { + const approval = { + id, + origin, + type, + time: Date.now(), + requestData: requestData || null, + }; + this.update((draftState) => { + // Typecast: ts(2589) + draftState.pendingApprovals[id] = approval; + draftState.pendingApprovalCount = Object.keys(draftState.pendingApprovals).length; + }); + } + /** + * Deletes the approval with the given id. The approval promise must be + * resolved or reject before this method is called. + * Deletion is an internal operation because approval state is solely + * managed by this controller. + * + * @param id - The id of the approval request to be deleted. + */ + _delete(id) { + this._approvals.delete(id); + // This method is only called after verifying that the approval with the + // specified id exists. + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const { origin, type } = this.state.pendingApprovals[id]; + this._origins.get(origin).delete(type); + if (this._isEmptyOrigin(origin)) { + this._origins.delete(origin); + } + this.update((draftState) => { + delete draftState.pendingApprovals[id]; + draftState.pendingApprovalCount = Object.keys(draftState.pendingApprovals).length; + }); + } + /** + * Gets the approval callbacks for the given id, deletes the entry, and then + * returns the callbacks for promise resolution. + * Throws an error if no approval is found for the given id. + * + * @param id - The id of the approval request. + * @returns The promise callbacks associated with the approval request. + */ + _deleteApprovalAndGetCallbacks(id) { + const callbacks = this._approvals.get(id); + if (!callbacks) { + throw new Error(`Approval request with id '${id}' not found.`); + } + this._delete(id); + return callbacks; + } + /** + * Checks whether there are any approvals associated with the given + * origin. + * + * @param origin - The origin to check. + * @returns True if the origin has no approvals, false otherwise. + */ + _isEmptyOrigin(origin) { + var _a; + return !((_a = this._origins.get(origin)) === null || _a === void 0 ? void 0 : _a.size); + } +} +exports.ApprovalController = ApprovalController; +exports.default = ApprovalController; +//# sourceMappingURL=ApprovalController.js.map \ No newline at end of file diff --git a/dist/approval/ApprovalController.js.map b/dist/approval/ApprovalController.js.map new file mode 100644 index 0000000000..7f2093399f --- /dev/null +++ b/dist/approval/ApprovalController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ApprovalController.js","sourceRoot":"","sources":["../../src/approval/ApprovalController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Patch } from 'immer';\nimport { EthereumRpcError, ethErrors } from 'eth-rpc-errors';\nimport { nanoid } from 'nanoid';\n\nimport { BaseController, Json } from '../BaseControllerV2';\nimport type { RestrictedControllerMessenger } from '../ControllerMessenger';\n\nconst controllerName = 'ApprovalController';\n\ntype ApprovalPromiseResolve = (value?: unknown) => void;\ntype ApprovalPromiseReject = (error?: unknown) => void;\n\ntype ApprovalRequestData = Record | null;\n\ntype ApprovalCallbacks = {\n resolve: ApprovalPromiseResolve;\n reject: ApprovalPromiseReject;\n};\n\nexport type ApprovalRequest = {\n /**\n * The ID of the approval request.\n */\n id: string;\n\n /**\n * The origin of the approval request.\n */\n origin: string;\n\n /**\n * The time that the request was received, per Date.now().\n */\n time: number;\n\n /**\n * The type of the approval request.\n */\n type: string;\n\n /**\n * Additional data associated with the request.\n * TODO:TS4.4 make optional\n */\n requestData: RequestData;\n};\n\ntype ShowApprovalRequest = () => void | Promise;\n\nexport type ApprovalControllerState = {\n pendingApprovals: Record>>;\n pendingApprovalCount: number;\n};\n\nconst stateMetadata = {\n pendingApprovals: { persist: false, anonymous: true },\n pendingApprovalCount: { persist: false, anonymous: false },\n};\n\nconst getAlreadyPendingMessage = (origin: string, type: string) =>\n `Request of type '${type}' already pending for origin ${origin}. Please wait.`;\n\nconst getDefaultState = (): ApprovalControllerState => {\n return {\n pendingApprovals: {},\n pendingApprovalCount: 0,\n };\n};\n\nexport type GetApprovalsState = {\n type: `${typeof controllerName}:getState`;\n handler: () => ApprovalControllerState;\n};\n\nexport type ClearApprovalRequests = {\n type: `${typeof controllerName}:clearRequests`;\n handler: (error: EthereumRpcError) => void;\n};\n\ntype AddApprovalOptions = {\n id?: string;\n origin: string;\n type: string;\n requestData?: Record;\n};\n\nexport type AddApprovalRequest = {\n type: `${typeof controllerName}:addRequest`;\n handler: (\n opts: AddApprovalOptions,\n shouldShowRequest: boolean,\n ) => ReturnType;\n};\n\nexport type HasApprovalRequest = {\n type: `${typeof controllerName}:hasRequest`;\n handler: ApprovalController['has'];\n};\n\nexport type AcceptRequest = {\n type: `${typeof controllerName}:acceptRequest`;\n handler: ApprovalController['accept'];\n};\n\nexport type RejectRequest = {\n type: `${typeof controllerName}:rejectRequest`;\n handler: ApprovalController['reject'];\n};\n\nexport type ApprovalControllerActions =\n | GetApprovalsState\n | ClearApprovalRequests\n | AddApprovalRequest\n | HasApprovalRequest\n | AcceptRequest\n | RejectRequest;\n\nexport type ApprovalStateChange = {\n type: `${typeof controllerName}:stateChange`;\n payload: [ApprovalControllerState, Patch[]];\n};\n\nexport type ApprovalControllerEvents = ApprovalStateChange;\n\nexport type ApprovalControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n ApprovalControllerActions,\n ApprovalControllerEvents,\n never,\n never\n>;\n\ntype ApprovalControllerOptions = {\n messenger: ApprovalControllerMessenger;\n showApprovalRequest: ShowApprovalRequest;\n state?: Partial;\n};\n\n/**\n * Controller for managing requests that require user approval.\n *\n * Enables limiting the number of pending requests by origin and type, counting\n * pending requests, and more.\n *\n * Adding a request returns a promise that resolves or rejects when the request\n * is approved or denied, respectively.\n */\nexport class ApprovalController extends BaseController<\n typeof controllerName,\n ApprovalControllerState,\n ApprovalControllerMessenger\n> {\n private _approvals: Map;\n\n private _origins: Map>;\n\n private _showApprovalRequest: () => void;\n\n /**\n * Construct an Approval controller.\n *\n * @param options - The controller options.\n * @param options.showApprovalRequest - Function for opening the UI such that\n * the request can be displayed to the user.\n * @param options.messenger - The restricted controller messenger for the Approval controller.\n * @param options.state - The initial controller state.\n */\n constructor({\n messenger,\n showApprovalRequest,\n state = {},\n }: ApprovalControllerOptions) {\n super({\n name: controllerName,\n metadata: stateMetadata,\n messenger,\n state: { ...getDefaultState(), ...state },\n });\n\n this._approvals = new Map();\n this._origins = new Map();\n this._showApprovalRequest = showApprovalRequest;\n this.registerMessageHandlers();\n }\n\n /**\n * Constructor helper for registering this controller's messaging system\n * actions.\n */\n private registerMessageHandlers(): void {\n this.messagingSystem.registerActionHandler(\n `${controllerName}:clearRequests` as const,\n this.clear.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:addRequest` as const,\n (opts: AddApprovalOptions, shouldShowRequest: boolean) => {\n if (shouldShowRequest) {\n return this.addAndShowApprovalRequest(opts);\n }\n return this.add(opts);\n },\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:hasRequest` as const,\n this.has.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:acceptRequest` as const,\n this.accept.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:rejectRequest` as const,\n this.reject.bind(this),\n );\n }\n\n /**\n * Adds an approval request per the given arguments, calls the show approval\n * request function, and returns the associated approval promise.\n *\n * There can only be one approval per origin and type. An error is thrown if\n * attempting to add an invalid or duplicate request.\n *\n * @param opts - Options bag.\n * @param opts.id - The id of the approval request. A random id will be\n * generated if none is provided.\n * @param opts.origin - The origin of the approval request.\n * @param opts.type - The type associated with the approval request.\n * @param opts.requestData - Additional data associated with the request,\n * if any.\n * @returns The approval promise.\n */\n addAndShowApprovalRequest(opts: AddApprovalOptions): Promise {\n const promise = this._add(\n opts.origin,\n opts.type,\n opts.id,\n opts.requestData,\n );\n this._showApprovalRequest();\n return promise;\n }\n\n /**\n * Adds an approval request per the given arguments and returns the approval\n * promise.\n *\n * There can only be one approval per origin and type. An error is thrown if\n * attempting to add an invalid or duplicate request.\n *\n * @param opts - Options bag.\n * @param opts.id - The id of the approval request. A random id will be\n * generated if none is provided.\n * @param opts.origin - The origin of the approval request.\n * @param opts.type - The type associated with the approval request.\n * @param opts.requestData - Additional data associated with the request,\n * if any.\n * @returns The approval promise.\n */\n add(opts: AddApprovalOptions): Promise {\n return this._add(opts.origin, opts.type, opts.id, opts.requestData);\n }\n\n /**\n * Gets the info for the approval request with the given id.\n *\n * @param id - The id of the approval request.\n * @returns The approval request data associated with the id.\n */\n get(id: string): ApprovalRequest | undefined {\n return this.state.pendingApprovals[id];\n }\n\n /**\n * Gets the number of pending approvals, by origin and/or type.\n *\n * If only `origin` is specified, all approvals for that origin will be\n * counted, regardless of type.\n * If only `type` is specified, all approvals for that type will be counted,\n * regardless of origin.\n * If both `origin` and `type` are specified, 0 or 1 will be returned.\n *\n * @param opts - The approval count options.\n * @param opts.origin - An approval origin.\n * @param opts.type - The type of the approval request.\n * @returns The current approval request count for the given origin and/or\n * type.\n */\n getApprovalCount(opts: { origin?: string; type?: string } = {}): number {\n if (!opts.origin && !opts.type) {\n throw new Error('Must specify origin, type, or both.');\n }\n const { origin, type: _type } = opts;\n\n if (origin && _type) {\n return Number(Boolean(this._origins.get(origin)?.has(_type)));\n }\n\n if (origin) {\n return this._origins.get(origin)?.size || 0;\n }\n\n // Only \"type\" was specified\n let count = 0;\n for (const approval of Object.values(this.state.pendingApprovals)) {\n if (approval.type === _type) {\n count += 1;\n }\n }\n return count;\n }\n\n /**\n * Get the total count of all pending approval requests for all origins.\n *\n * @returns The total pending approval request count.\n */\n getTotalApprovalCount(): number {\n return this.state.pendingApprovalCount;\n }\n\n /**\n * Checks if there's a pending approval request per the given parameters.\n * At least one parameter must be specified. An error will be thrown if the\n * parameters are invalid.\n *\n * If `id` is specified, all other parameters will be ignored.\n * If `id` is not specified, the method will check for requests that match\n * all of the specified parameters.\n *\n * @param opts - Options bag.\n * @param opts.id - The ID to check for.\n * @param opts.origin - The origin to check for.\n * @param opts.type - The type to check for.\n * @returns `true` if a matching approval is found, and `false` otherwise.\n */\n has(opts: { id?: string; origin?: string; type?: string } = {}): boolean {\n const { id, origin, type: _type } = opts;\n\n if (id) {\n if (typeof id !== 'string') {\n throw new Error('May not specify non-string id.');\n }\n return this._approvals.has(id);\n }\n\n if (_type && typeof _type !== 'string') {\n throw new Error('May not specify non-string type.');\n }\n\n if (origin) {\n if (typeof origin !== 'string') {\n throw new Error('May not specify non-string origin.');\n }\n\n // Check origin and type pair if type also specified\n if (_type) {\n return Boolean(this._origins.get(origin)?.has(_type));\n }\n return this._origins.has(origin);\n }\n\n if (_type) {\n for (const approval of Object.values(this.state.pendingApprovals)) {\n if (approval.type === _type) {\n return true;\n }\n }\n return false;\n }\n throw new Error(\n 'Must specify a valid combination of id, origin, and type.',\n );\n }\n\n /**\n * Resolves the promise of the approval with the given id, and deletes the\n * approval. Throws an error if no such approval exists.\n *\n * @param id - The id of the approval request.\n * @param value - The value to resolve the approval promise with.\n */\n accept(id: string, value?: unknown): void {\n this._deleteApprovalAndGetCallbacks(id).resolve(value);\n }\n\n /**\n * Rejects the promise of the approval with the given id, and deletes the\n * approval. Throws an error if no such approval exists.\n *\n * @param id - The id of the approval request.\n * @param error - The error to reject the approval promise with.\n */\n reject(id: string, error: unknown): void {\n this._deleteApprovalAndGetCallbacks(id).reject(error);\n }\n\n /**\n * Rejects and deletes all approval requests.\n *\n * @param rejectionError - The EthereumRpcError to reject the approval\n * requests with.\n */\n clear(rejectionError: EthereumRpcError): void {\n for (const id of this._approvals.keys()) {\n this.reject(id, rejectionError);\n }\n this._origins.clear();\n this.update(() => getDefaultState());\n }\n\n /**\n * Implementation of add operation.\n *\n * @param origin - The origin of the approval request.\n * @param type - The type associated with the approval request.\n * @param id - The id of the approval request.\n * @param requestData - The request data associated with the approval request.\n * @returns The approval promise.\n */\n private _add(\n origin: string,\n type: string,\n id: string = nanoid(),\n requestData?: Record,\n ): Promise {\n this._validateAddParams(id, origin, type, requestData);\n\n if (this._origins.get(origin)?.has(type)) {\n throw ethErrors.rpc.resourceUnavailable(\n getAlreadyPendingMessage(origin, type),\n );\n }\n\n // add pending approval\n return new Promise((resolve, reject) => {\n this._approvals.set(id, { resolve, reject });\n this._addPendingApprovalOrigin(origin, type);\n this._addToStore(id, origin, type, requestData);\n });\n }\n\n /**\n * Validates parameters to the add method.\n *\n * @param id - The id of the approval request.\n * @param origin - The origin of the approval request.\n * @param type - The type associated with the approval request.\n * @param requestData - The request data associated with the approval request.\n */\n private _validateAddParams(\n id: string,\n origin: string,\n type: string,\n requestData?: Record,\n ): void {\n let errorMessage = null;\n if (!id || typeof id !== 'string') {\n errorMessage = 'Must specify non-empty string id.';\n } else if (this._approvals.has(id)) {\n errorMessage = `Approval request with id '${id}' already exists.`;\n } else if (!origin || typeof origin !== 'string') {\n errorMessage = 'Must specify non-empty string origin.';\n } else if (!type || typeof type !== 'string') {\n errorMessage = 'Must specify non-empty string type.';\n } else if (\n requestData &&\n (typeof requestData !== 'object' || Array.isArray(requestData))\n ) {\n errorMessage = 'Request data must be a plain object if specified.';\n }\n\n if (errorMessage) {\n throw ethErrors.rpc.internal(errorMessage);\n }\n }\n\n /**\n * Adds an entry to _origins.\n * Performs no validation.\n *\n * @param origin - The origin of the approval request.\n * @param type - The type associated with the approval request.\n */\n private _addPendingApprovalOrigin(origin: string, type: string): void {\n const originSet = this._origins.get(origin) || new Set();\n originSet.add(type);\n\n if (!this._origins.has(origin)) {\n this._origins.set(origin, originSet);\n }\n }\n\n /**\n * Adds an entry to the store.\n * Performs no validation.\n *\n * @param id - The id of the approval request.\n * @param origin - The origin of the approval request.\n * @param type - The type associated with the approval request.\n * @param requestData - The request data associated with the approval request.\n */\n private _addToStore(\n id: string,\n origin: string,\n type: string,\n requestData?: Record,\n ): void {\n const approval: ApprovalRequest | null> = {\n id,\n origin,\n type,\n time: Date.now(),\n requestData: requestData || null,\n };\n\n this.update((draftState) => {\n // Typecast: ts(2589)\n draftState.pendingApprovals[id] = approval as any;\n draftState.pendingApprovalCount = Object.keys(\n draftState.pendingApprovals,\n ).length;\n });\n }\n\n /**\n * Deletes the approval with the given id. The approval promise must be\n * resolved or reject before this method is called.\n * Deletion is an internal operation because approval state is solely\n * managed by this controller.\n *\n * @param id - The id of the approval request to be deleted.\n */\n private _delete(id: string): void {\n this._approvals.delete(id);\n\n // This method is only called after verifying that the approval with the\n // specified id exists.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const { origin, type } = this.state.pendingApprovals[id]!;\n\n (this._origins.get(origin) as Set).delete(type);\n if (this._isEmptyOrigin(origin)) {\n this._origins.delete(origin);\n }\n\n this.update((draftState) => {\n delete draftState.pendingApprovals[id];\n draftState.pendingApprovalCount = Object.keys(\n draftState.pendingApprovals,\n ).length;\n });\n }\n\n /**\n * Gets the approval callbacks for the given id, deletes the entry, and then\n * returns the callbacks for promise resolution.\n * Throws an error if no approval is found for the given id.\n *\n * @param id - The id of the approval request.\n * @returns The promise callbacks associated with the approval request.\n */\n private _deleteApprovalAndGetCallbacks(id: string): ApprovalCallbacks {\n const callbacks = this._approvals.get(id);\n if (!callbacks) {\n throw new Error(`Approval request with id '${id}' not found.`);\n }\n\n this._delete(id);\n return callbacks;\n }\n\n /**\n * Checks whether there are any approvals associated with the given\n * origin.\n *\n * @param origin - The origin to check.\n * @returns True if the origin has no approvals, false otherwise.\n */\n private _isEmptyOrigin(origin: string): boolean {\n return !this._origins.get(origin)?.size;\n }\n}\nexport default ApprovalController;\n"]} \ No newline at end of file diff --git a/dist/assets/AccountTrackerController.d.ts b/dist/assets/AccountTrackerController.d.ts new file mode 100644 index 0000000000..0e0d74cffb --- /dev/null +++ b/dist/assets/AccountTrackerController.d.ts @@ -0,0 +1,88 @@ +import { BaseConfig, BaseController, BaseState } from '../BaseController'; +import { PreferencesState } from '../user/PreferencesController'; +/** + * @type AccountInformation + * + * Account information object + * @property balance - Hex string of an account balancec in wei + */ +export interface AccountInformation { + balance: string; +} +/** + * @type AccountTrackerConfig + * + * Account tracker controller configuration + * @property provider - Provider used to create a new underlying EthQuery instance + */ +export interface AccountTrackerConfig extends BaseConfig { + interval: number; + provider?: any; +} +/** + * @type AccountTrackerState + * + * Account tracker controller state + * @property accounts - Map of addresses to account information + */ +export interface AccountTrackerState extends BaseState { + accounts: { + [address: string]: AccountInformation; + }; +} +/** + * Controller that tracks information for all accounts in the current keychain + */ +export declare class AccountTrackerController extends BaseController { + private ethQuery; + private mutex; + private handle?; + private syncAccounts; + /** + * Name of this controller used during composition + */ + name: string; + private getIdentities; + /** + * Creates an AccountTracker instance. + * + * @param options - The controller options. + * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes. + * @param options.getIdentities - Gets the identities from the Preferences store. + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor({ onPreferencesStateChange, getIdentities, }: { + onPreferencesStateChange: (listener: (preferencesState: PreferencesState) => void) => void; + getIdentities: () => PreferencesState['identities']; + }, config?: Partial, state?: Partial); + /** + * Sets a new provider. + * + * TODO: Replace this wth a method. + * + * @param provider - Provider used to create a new underlying EthQuery instance. + */ + set provider(provider: any); + get provider(): any; + /** + * Starts a new polling interval. + * + * @param interval - Polling interval trigger a 'refresh'. + */ + poll(interval?: number): Promise; + /** + * Refreshes all accounts in the current keychain. + */ + refresh: () => Promise; + /** + * Sync accounts balances with some additional addresses. + * + * @param addresses - the additional addresses, may be hardware wallet addresses. + * @returns accounts - addresses with synced balance + */ + syncBalanceWithAddresses(addresses: string[]): Promise>; +} +export default AccountTrackerController; diff --git a/dist/assets/AccountTrackerController.js b/dist/assets/AccountTrackerController.js new file mode 100644 index 0000000000..061e82665a --- /dev/null +++ b/dist/assets/AccountTrackerController.js @@ -0,0 +1,138 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AccountTrackerController = void 0; +const eth_query_1 = __importDefault(require("eth-query")); +const async_mutex_1 = require("async-mutex"); +const BaseController_1 = require("../BaseController"); +const util_1 = require("../util"); +/** + * Controller that tracks information for all accounts in the current keychain + */ +class AccountTrackerController extends BaseController_1.BaseController { + /** + * Creates an AccountTracker instance. + * + * @param options - The controller options. + * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes. + * @param options.getIdentities - Gets the identities from the Preferences store. + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor({ onPreferencesStateChange, getIdentities, }, config, state) { + super(config, state); + this.mutex = new async_mutex_1.Mutex(); + /** + * Name of this controller used during composition + */ + this.name = 'AccountTrackerController'; + /** + * Refreshes all accounts in the current keychain. + */ + this.refresh = () => __awaiter(this, void 0, void 0, function* () { + this.syncAccounts(); + const accounts = Object.assign({}, this.state.accounts); + for (const address in accounts) { + yield (0, util_1.safelyExecuteWithTimeout)(() => __awaiter(this, void 0, void 0, function* () { + const balance = yield (0, util_1.query)(this.ethQuery, 'getBalance', [address]); + accounts[address] = { balance: (0, util_1.BNToHex)(balance) }; + })); + } + this.update({ accounts }); + }); + this.defaultConfig = { + interval: 10000, + }; + this.defaultState = { accounts: {} }; + this.initialize(); + this.getIdentities = getIdentities; + onPreferencesStateChange(() => { + this.refresh(); + }); + this.poll(); + } + syncAccounts() { + const { accounts } = this.state; + const addresses = Object.keys(this.getIdentities()); + const existing = Object.keys(accounts); + const newAddresses = addresses.filter((address) => existing.indexOf(address) === -1); + const oldAddresses = existing.filter((address) => addresses.indexOf(address) === -1); + newAddresses.forEach((address) => { + accounts[address] = { balance: '0x0' }; + }); + oldAddresses.forEach((address) => { + delete accounts[address]; + }); + this.update({ accounts: Object.assign({}, accounts) }); + } + /** + * Sets a new provider. + * + * TODO: Replace this wth a method. + * + * @param provider - Provider used to create a new underlying EthQuery instance. + */ + set provider(provider) { + this.ethQuery = new eth_query_1.default(provider); + } + get provider() { + throw new Error('Property only used for setting'); + } + /** + * Starts a new polling interval. + * + * @param interval - Polling interval trigger a 'refresh'. + */ + poll(interval) { + return __awaiter(this, void 0, void 0, function* () { + const releaseLock = yield this.mutex.acquire(); + interval && this.configure({ interval }, false, false); + this.handle && clearTimeout(this.handle); + yield this.refresh(); + this.handle = setTimeout(() => { + releaseLock(); + this.poll(this.config.interval); + }, this.config.interval); + }); + } + /** + * Sync accounts balances with some additional addresses. + * + * @param addresses - the additional addresses, may be hardware wallet addresses. + * @returns accounts - addresses with synced balance + */ + syncBalanceWithAddresses(addresses) { + return __awaiter(this, void 0, void 0, function* () { + return yield Promise.all(addresses.map((address) => { + return (0, util_1.safelyExecuteWithTimeout)(() => __awaiter(this, void 0, void 0, function* () { + const balance = yield (0, util_1.query)(this.ethQuery, 'getBalance', [address]); + return [address, balance]; + })); + })).then((value) => { + return value.reduce((obj, item) => { + if (!item) { + return obj; + } + const [address, balance] = item; + return Object.assign(Object.assign({}, obj), { [address]: { + balance, + } }); + }, {}); + }); + }); + } +} +exports.AccountTrackerController = AccountTrackerController; +exports.default = AccountTrackerController; +//# sourceMappingURL=AccountTrackerController.js.map \ No newline at end of file diff --git a/dist/assets/AccountTrackerController.js.map b/dist/assets/AccountTrackerController.js.map new file mode 100644 index 0000000000..c720ca59e3 --- /dev/null +++ b/dist/assets/AccountTrackerController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"AccountTrackerController.js","sourceRoot":"","sources":["../../src/assets/AccountTrackerController.ts"],"names":[],"mappings":"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","sourcesContent":["import EthQuery from 'eth-query';\nimport { Mutex } from 'async-mutex';\nimport { BaseConfig, BaseController, BaseState } from '../BaseController';\nimport { PreferencesState } from '../user/PreferencesController';\nimport { BNToHex, query, safelyExecuteWithTimeout } from '../util';\n\n/**\n * @type AccountInformation\n *\n * Account information object\n * @property balance - Hex string of an account balancec in wei\n */\nexport interface AccountInformation {\n balance: string;\n}\n\n/**\n * @type AccountTrackerConfig\n *\n * Account tracker controller configuration\n * @property provider - Provider used to create a new underlying EthQuery instance\n */\nexport interface AccountTrackerConfig extends BaseConfig {\n interval: number;\n provider?: any;\n}\n\n/**\n * @type AccountTrackerState\n *\n * Account tracker controller state\n * @property accounts - Map of addresses to account information\n */\nexport interface AccountTrackerState extends BaseState {\n accounts: { [address: string]: AccountInformation };\n}\n\n/**\n * Controller that tracks information for all accounts in the current keychain\n */\nexport class AccountTrackerController extends BaseController<\n AccountTrackerConfig,\n AccountTrackerState\n> {\n private ethQuery: any;\n\n private mutex = new Mutex();\n\n private handle?: NodeJS.Timer;\n\n private syncAccounts() {\n const { accounts } = this.state;\n const addresses = Object.keys(this.getIdentities());\n const existing = Object.keys(accounts);\n const newAddresses = addresses.filter(\n (address) => existing.indexOf(address) === -1,\n );\n const oldAddresses = existing.filter(\n (address) => addresses.indexOf(address) === -1,\n );\n newAddresses.forEach((address) => {\n accounts[address] = { balance: '0x0' };\n });\n\n oldAddresses.forEach((address) => {\n delete accounts[address];\n });\n this.update({ accounts: { ...accounts } });\n }\n\n /**\n * Name of this controller used during composition\n */\n override name = 'AccountTrackerController';\n\n private getIdentities: () => PreferencesState['identities'];\n\n /**\n * Creates an AccountTracker instance.\n *\n * @param options - The controller options.\n * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes.\n * @param options.getIdentities - Gets the identities from the Preferences store.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n onPreferencesStateChange,\n getIdentities,\n }: {\n onPreferencesStateChange: (\n listener: (preferencesState: PreferencesState) => void,\n ) => void;\n getIdentities: () => PreferencesState['identities'];\n },\n config?: Partial,\n state?: Partial,\n ) {\n super(config, state);\n this.defaultConfig = {\n interval: 10000,\n };\n this.defaultState = { accounts: {} };\n this.initialize();\n this.getIdentities = getIdentities;\n onPreferencesStateChange(() => {\n this.refresh();\n });\n this.poll();\n }\n\n /**\n * Sets a new provider.\n *\n * TODO: Replace this wth a method.\n *\n * @param provider - Provider used to create a new underlying EthQuery instance.\n */\n set provider(provider: any) {\n this.ethQuery = new EthQuery(provider);\n }\n\n get provider() {\n throw new Error('Property only used for setting');\n }\n\n /**\n * Starts a new polling interval.\n *\n * @param interval - Polling interval trigger a 'refresh'.\n */\n async poll(interval?: number): Promise {\n const releaseLock = await this.mutex.acquire();\n interval && this.configure({ interval }, false, false);\n this.handle && clearTimeout(this.handle);\n await this.refresh();\n this.handle = setTimeout(() => {\n releaseLock();\n this.poll(this.config.interval);\n }, this.config.interval);\n }\n\n /**\n * Refreshes all accounts in the current keychain.\n */\n refresh = async () => {\n this.syncAccounts();\n const accounts = { ...this.state.accounts };\n for (const address in accounts) {\n await safelyExecuteWithTimeout(async () => {\n const balance = await query(this.ethQuery, 'getBalance', [address]);\n accounts[address] = { balance: BNToHex(balance) };\n });\n }\n this.update({ accounts });\n };\n\n /**\n * Sync accounts balances with some additional addresses.\n *\n * @param addresses - the additional addresses, may be hardware wallet addresses.\n * @returns accounts - addresses with synced balance\n */\n async syncBalanceWithAddresses(\n addresses: string[],\n ): Promise> {\n return await Promise.all(\n addresses.map((address): Promise<[string, string] | undefined> => {\n return safelyExecuteWithTimeout(async () => {\n const balance = await query(this.ethQuery, 'getBalance', [address]);\n return [address, balance];\n });\n }),\n ).then((value) => {\n return value.reduce((obj, item) => {\n if (!item) {\n return obj;\n }\n\n const [address, balance] = item;\n return {\n ...obj,\n [address]: {\n balance,\n },\n };\n }, {});\n });\n }\n}\n\nexport default AccountTrackerController;\n"]} \ No newline at end of file diff --git a/dist/assets/AssetsContractController.d.ts b/dist/assets/AssetsContractController.d.ts new file mode 100644 index 0000000000..b4b6336683 --- /dev/null +++ b/dist/assets/AssetsContractController.d.ts @@ -0,0 +1,176 @@ +/// +import { BN } from 'ethereumjs-util'; +import { BaseController, BaseConfig, BaseState } from '../BaseController'; +import type { PreferencesState } from '../user/PreferencesController'; +import { NetworkState } from '../network/NetworkController'; +/** + * Check if token detection is enabled for certain networks + * + * @param chainId - ChainID of network + * @returns Whether the current network supports token detection + */ +export declare const SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID: Record; +export declare const MISSING_PROVIDER_ERROR = "AssetsContractController failed to set the provider correctly. A provider must be set for this method to be available"; +/** + * @type AssetsContractConfig + * + * Assets Contract controller configuration + * @property provider - Provider used to create a new web3 instance + */ +export interface AssetsContractConfig extends BaseConfig { + provider: any; + ipfsGateway: string; + chainId: string; +} +/** + * @type BalanceMap + * + * Key value object containing the balance for each tokenAddress + * @property [tokenAddress] - Address of the token + */ +export interface BalanceMap { + [tokenAddress: string]: BN; +} +/** + * Controller that interacts with contracts on mainnet through web3 + */ +export declare class AssetsContractController extends BaseController { + private web3; + private erc721Standard?; + private erc1155Standard?; + private erc20Standard?; + /** + * Name of this controller used during composition + */ + name: string; + /** + * Creates a AssetsContractController instance. + * + * @param options - The controller options. + * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes. + * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor({ onPreferencesStateChange, onNetworkStateChange, }: { + onPreferencesStateChange: (listener: (preferencesState: PreferencesState) => void) => void; + onNetworkStateChange: (listener: (networkState: NetworkState) => void) => void; + }, config?: Partial, state?: Partial); + /** + * Sets a new provider. + * + * TODO: Replace this wth a method. + * + * @property provider - Provider used to create a new underlying Web3 instance + */ + set provider(provider: any); + get provider(): any; + /** + * Get balance or count for current account on specific asset contract. + * + * @param address - Asset ERC20 contract address. + * @param selectedAddress - Current account public address. + * @returns Promise resolving to BN object containing balance for current account on specific asset contract. + */ + getERC20BalanceOf(address: string, selectedAddress: string): Promise; + /** + * Query for the decimals for a given ERC20 asset. + * + * @param address - ERC20 asset contract address. + * @returns Promise resolving to the 'decimals'. + */ + getERC20TokenDecimals(address: string): Promise; + /** + * Enumerate assets assigned to an owner. + * + * @param address - ERC721 asset contract address. + * @param selectedAddress - Current account public address. + * @param index - A collectible counter less than `balanceOf(selectedAddress)`. + * @returns Promise resolving to token identifier for the 'index'th asset assigned to 'selectedAddress'. + */ + getERC721CollectibleTokenId(address: string, selectedAddress: string, index: number): Promise; + /** + * Enumerate assets assigned to an owner. + * + * @param tokenAddress - ERC721 asset contract address. + * @param userAddress - Current account public address. + * @param tokenId - ERC721 asset identifier. + * @returns Promise resolving to an object containing the token standard and a set of details which depend on which standard the token supports. + */ + getTokenStandardAndDetails(tokenAddress: string, userAddress?: string, tokenId?: string): Promise<{ + standard: string; + tokenURI?: string | undefined; + symbol?: string | undefined; + name?: string | undefined; + decimals?: string | undefined; + balance?: BN | undefined; + }>; + /** + * Query for tokenURI for a given ERC721 asset. + * + * @param address - ERC721 asset contract address. + * @param tokenId - ERC721 asset identifier. + * @returns Promise resolving to the 'tokenURI'. + */ + getERC721TokenURI(address: string, tokenId: string): Promise; + /** + * Query for name for a given asset. + * + * @param address - ERC721 or ERC20 asset contract address. + * @returns Promise resolving to the 'name'. + */ + getERC721AssetName(address: string): Promise; + /** + * Query for symbol for a given asset. + * + * @param address - ERC721 or ERC20 asset contract address. + * @returns Promise resolving to the 'symbol'. + */ + getERC721AssetSymbol(address: string): Promise; + /** + * Query for owner for a given ERC721 asset. + * + * @param address - ERC721 asset contract address. + * @param tokenId - ERC721 asset identifier. + * @returns Promise resolving to the owner address. + */ + getERC721OwnerOf(address: string, tokenId: string): Promise; + /** + * Query for tokenURI for a given asset. + * + * @param address - ERC1155 asset contract address. + * @param tokenId - ERC1155 asset identifier. + * @returns Promise resolving to the 'tokenURI'. + */ + getERC1155TokenURI(address: string, tokenId: string): Promise; + /** + * Query for balance of a given ERC 1155 token. + * + * @param userAddress - Wallet public address. + * @param collectibleAddress - ERC1155 asset contract address. + * @param collectibleId - ERC1155 asset identifier. + * @returns Promise resolving to the 'balanceOf'. + */ + getERC1155BalanceOf(userAddress: string, collectibleAddress: string, collectibleId: string): Promise; + /** + * Transfer single ERC1155 token. + * + * @param collectibleAddress - ERC1155 token address. + * @param senderAddress - ERC1155 token sender. + * @param recipientAddress - ERC1155 token recipient. + * @param collectibleId - ERC1155 token id. + * @param qty - Quantity of tokens to be sent. + * @returns Promise resolving to the 'transferSingle' ERC1155 token. + */ + transferSingleERC1155(collectibleAddress: string, senderAddress: string, recipientAddress: string, collectibleId: string, qty: string): Promise; + /** + * Get the token balance for a list of token addresses in a single call. Only non-zero balances + * are returned. + * + * @param selectedAddress - The address to check token balances for. + * @param tokensToDetect - The token addresses to detect balances for. + * @returns The list of non-zero token balances. + */ + getBalancesInSingleCall(selectedAddress: string, tokensToDetect: string[]): Promise; +} +export default AssetsContractController; diff --git a/dist/assets/AssetsContractController.js b/dist/assets/AssetsContractController.js new file mode 100644 index 0000000000..fbc36f61ea --- /dev/null +++ b/dist/assets/AssetsContractController.js @@ -0,0 +1,323 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AssetsContractController = exports.MISSING_PROVIDER_ERROR = exports.SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID = void 0; +const web3_1 = __importDefault(require("web3")); +const single_call_balance_checker_abi_1 = __importDefault(require("single-call-balance-checker-abi")); +const BaseController_1 = require("../BaseController"); +const constants_1 = require("../constants"); +const util_1 = require("../util"); +const ERC721Standard_1 = require("./Standards/CollectibleStandards/ERC721/ERC721Standard"); +const ERC1155Standard_1 = require("./Standards/CollectibleStandards/ERC1155/ERC1155Standard"); +const ERC20Standard_1 = require("./Standards/ERC20Standard"); +/** + * Check if token detection is enabled for certain networks + * + * @param chainId - ChainID of network + * @returns Whether the current network supports token detection + */ +exports.SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID = { + [util_1.SupportedTokenDetectionNetworks.mainnet]: '0xb1f8e55c7f64d203c1400b9d8555d050f94adf39', + [util_1.SupportedTokenDetectionNetworks.bsc]: '0x2352c63A83f9Fd126af8676146721Fa00924d7e4', + [util_1.SupportedTokenDetectionNetworks.polygon]: '0x2352c63A83f9Fd126af8676146721Fa00924d7e4', + [util_1.SupportedTokenDetectionNetworks.avax]: '0xD023D153a0DFa485130ECFdE2FAA7e612EF94818', +}; +exports.MISSING_PROVIDER_ERROR = 'AssetsContractController failed to set the provider correctly. A provider must be set for this method to be available'; +/** + * Controller that interacts with contracts on mainnet through web3 + */ +class AssetsContractController extends BaseController_1.BaseController { + /** + * Creates a AssetsContractController instance. + * + * @param options - The controller options. + * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes. + * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor({ onPreferencesStateChange, onNetworkStateChange, }, config, state) { + super(config, state); + /** + * Name of this controller used during composition + */ + this.name = 'AssetsContractController'; + this.defaultConfig = { + provider: undefined, + ipfsGateway: constants_1.IPFS_DEFAULT_GATEWAY_URL, + chainId: util_1.SupportedTokenDetectionNetworks.mainnet, + }; + this.initialize(); + onPreferencesStateChange(({ ipfsGateway }) => { + this.configure({ ipfsGateway }); + }); + onNetworkStateChange((networkState) => { + if (this.config.chainId !== networkState.provider.chainId) { + this.configure({ + chainId: networkState.provider.chainId, + }); + } + }); + } + /** + * Sets a new provider. + * + * TODO: Replace this wth a method. + * + * @property provider - Provider used to create a new underlying Web3 instance + */ + set provider(provider) { + this.web3 = new web3_1.default(provider); + this.erc721Standard = new ERC721Standard_1.ERC721Standard(this.web3); + this.erc1155Standard = new ERC1155Standard_1.ERC1155Standard(this.web3); + this.erc20Standard = new ERC20Standard_1.ERC20Standard(this.web3); + } + get provider() { + throw new Error('Property only used for setting'); + } + /** + * Get balance or count for current account on specific asset contract. + * + * @param address - Asset ERC20 contract address. + * @param selectedAddress - Current account public address. + * @returns Promise resolving to BN object containing balance for current account on specific asset contract. + */ + getERC20BalanceOf(address, selectedAddress) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.erc20Standard) { + throw new Error(exports.MISSING_PROVIDER_ERROR); + } + return this.erc20Standard.getBalanceOf(address, selectedAddress); + }); + } + /** + * Query for the decimals for a given ERC20 asset. + * + * @param address - ERC20 asset contract address. + * @returns Promise resolving to the 'decimals'. + */ + getERC20TokenDecimals(address) { + return __awaiter(this, void 0, void 0, function* () { + if (this.erc20Standard === undefined) { + throw new Error(exports.MISSING_PROVIDER_ERROR); + } + return yield this.erc20Standard.getTokenDecimals(address); + }); + } + /** + * Enumerate assets assigned to an owner. + * + * @param address - ERC721 asset contract address. + * @param selectedAddress - Current account public address. + * @param index - A collectible counter less than `balanceOf(selectedAddress)`. + * @returns Promise resolving to token identifier for the 'index'th asset assigned to 'selectedAddress'. + */ + getERC721CollectibleTokenId(address, selectedAddress, index) { + if (this.erc721Standard === undefined) { + throw new Error(exports.MISSING_PROVIDER_ERROR); + } + return this.erc721Standard.getCollectibleTokenId(address, selectedAddress, index); + } + /** + * Enumerate assets assigned to an owner. + * + * @param tokenAddress - ERC721 asset contract address. + * @param userAddress - Current account public address. + * @param tokenId - ERC721 asset identifier. + * @returns Promise resolving to an object containing the token standard and a set of details which depend on which standard the token supports. + */ + getTokenStandardAndDetails(tokenAddress, userAddress, tokenId) { + return __awaiter(this, void 0, void 0, function* () { + if (this.erc721Standard === undefined || + this.erc1155Standard === undefined || + this.erc20Standard === undefined) { + throw new Error(exports.MISSING_PROVIDER_ERROR); + } + const { ipfsGateway } = this.config; + // ERC721 + try { + return Object.assign({}, (yield this.erc721Standard.getDetails(tokenAddress, ipfsGateway, tokenId))); + } + catch (_a) { + // Ignore + } + // ERC1155 + try { + return Object.assign({}, (yield this.erc1155Standard.getDetails(tokenAddress, ipfsGateway, tokenId))); + } + catch (_b) { + // Ignore + } + // ERC20 + try { + return Object.assign({}, (yield this.erc20Standard.getDetails(tokenAddress, userAddress))); + } + catch (_c) { + // Ignore + } + throw new Error('Unable to determine contract standard'); + }); + } + /** + * Query for tokenURI for a given ERC721 asset. + * + * @param address - ERC721 asset contract address. + * @param tokenId - ERC721 asset identifier. + * @returns Promise resolving to the 'tokenURI'. + */ + getERC721TokenURI(address, tokenId) { + return __awaiter(this, void 0, void 0, function* () { + if (this.erc721Standard === undefined) { + throw new Error(exports.MISSING_PROVIDER_ERROR); + } + return this.erc721Standard.getTokenURI(address, tokenId); + }); + } + /** + * Query for name for a given asset. + * + * @param address - ERC721 or ERC20 asset contract address. + * @returns Promise resolving to the 'name'. + */ + getERC721AssetName(address) { + return __awaiter(this, void 0, void 0, function* () { + if (this.erc721Standard === undefined) { + throw new Error(exports.MISSING_PROVIDER_ERROR); + } + return this.erc721Standard.getAssetName(address); + }); + } + /** + * Query for symbol for a given asset. + * + * @param address - ERC721 or ERC20 asset contract address. + * @returns Promise resolving to the 'symbol'. + */ + getERC721AssetSymbol(address) { + return __awaiter(this, void 0, void 0, function* () { + if (this.erc721Standard === undefined) { + throw new Error(exports.MISSING_PROVIDER_ERROR); + } + return this.erc721Standard.getAssetSymbol(address); + }); + } + /** + * Query for owner for a given ERC721 asset. + * + * @param address - ERC721 asset contract address. + * @param tokenId - ERC721 asset identifier. + * @returns Promise resolving to the owner address. + */ + getERC721OwnerOf(address, tokenId) { + return __awaiter(this, void 0, void 0, function* () { + if (this.erc721Standard === undefined) { + throw new Error(exports.MISSING_PROVIDER_ERROR); + } + return this.erc721Standard.getOwnerOf(address, tokenId); + }); + } + /** + * Query for tokenURI for a given asset. + * + * @param address - ERC1155 asset contract address. + * @param tokenId - ERC1155 asset identifier. + * @returns Promise resolving to the 'tokenURI'. + */ + getERC1155TokenURI(address, tokenId) { + return __awaiter(this, void 0, void 0, function* () { + if (this.erc1155Standard === undefined) { + throw new Error(exports.MISSING_PROVIDER_ERROR); + } + return this.erc1155Standard.getTokenURI(address, tokenId); + }); + } + /** + * Query for balance of a given ERC 1155 token. + * + * @param userAddress - Wallet public address. + * @param collectibleAddress - ERC1155 asset contract address. + * @param collectibleId - ERC1155 asset identifier. + * @returns Promise resolving to the 'balanceOf'. + */ + getERC1155BalanceOf(userAddress, collectibleAddress, collectibleId) { + return __awaiter(this, void 0, void 0, function* () { + if (this.erc1155Standard === undefined) { + throw new Error(exports.MISSING_PROVIDER_ERROR); + } + return yield this.erc1155Standard.getBalanceOf(collectibleAddress, userAddress, collectibleId); + }); + } + /** + * Transfer single ERC1155 token. + * + * @param collectibleAddress - ERC1155 token address. + * @param senderAddress - ERC1155 token sender. + * @param recipientAddress - ERC1155 token recipient. + * @param collectibleId - ERC1155 token id. + * @param qty - Quantity of tokens to be sent. + * @returns Promise resolving to the 'transferSingle' ERC1155 token. + */ + transferSingleERC1155(collectibleAddress, senderAddress, recipientAddress, collectibleId, qty) { + return __awaiter(this, void 0, void 0, function* () { + if (this.erc1155Standard === undefined) { + throw new Error(exports.MISSING_PROVIDER_ERROR); + } + return yield this.erc1155Standard.transferSingle(collectibleAddress, senderAddress, recipientAddress, collectibleId, qty); + }); + } + /** + * Get the token balance for a list of token addresses in a single call. Only non-zero balances + * are returned. + * + * @param selectedAddress - The address to check token balances for. + * @param tokensToDetect - The token addresses to detect balances for. + * @returns The list of non-zero token balances. + */ + getBalancesInSingleCall(selectedAddress, tokensToDetect) { + return __awaiter(this, void 0, void 0, function* () { + if (!(this.config.chainId in exports.SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID)) { + // Only fetch balance if contract address exists + return {}; + } + const contractAddress = exports.SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID[this.config.chainId]; + const contract = this.web3.eth + .contract(single_call_balance_checker_abi_1.default) + .at(contractAddress); + return new Promise((resolve, reject) => { + contract.balances([selectedAddress], tokensToDetect, (error, result) => { + /* istanbul ignore if */ + if (error) { + reject(error); + return; + } + const nonZeroBalances = {}; + /* istanbul ignore else */ + if (result.length > 0) { + tokensToDetect.forEach((tokenAddress, index) => { + const balance = result[index]; + /* istanbul ignore else */ + if (String(balance) !== '0') { + nonZeroBalances[tokenAddress] = balance; + } + }); + } + resolve(nonZeroBalances); + }); + }); + }); + } +} +exports.AssetsContractController = AssetsContractController; +exports.default = AssetsContractController; +//# sourceMappingURL=AssetsContractController.js.map \ No newline at end of file diff --git a/dist/assets/AssetsContractController.js.map b/dist/assets/AssetsContractController.js.map new file mode 100644 index 0000000000..b9108fe04d --- /dev/null +++ b/dist/assets/AssetsContractController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"AssetsContractController.js","sourceRoot":"","sources":["../../src/assets/AssetsContractController.ts"],"names":[],"mappings":"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","sourcesContent":["import { BN } from 'ethereumjs-util';\nimport Web3 from 'web3';\nimport abiSingleCallBalancesContract from 'single-call-balance-checker-abi';\nimport { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport type { PreferencesState } from '../user/PreferencesController';\nimport { IPFS_DEFAULT_GATEWAY_URL } from '../constants';\nimport { SupportedTokenDetectionNetworks } from '../util';\nimport { NetworkState } from '../network/NetworkController';\nimport { ERC721Standard } from './Standards/CollectibleStandards/ERC721/ERC721Standard';\nimport { ERC1155Standard } from './Standards/CollectibleStandards/ERC1155/ERC1155Standard';\nimport { ERC20Standard } from './Standards/ERC20Standard';\n\n/**\n * Check if token detection is enabled for certain networks\n *\n * @param chainId - ChainID of network\n * @returns Whether the current network supports token detection\n */\nexport const SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID: Record = {\n [SupportedTokenDetectionNetworks.mainnet]:\n '0xb1f8e55c7f64d203c1400b9d8555d050f94adf39',\n [SupportedTokenDetectionNetworks.bsc]:\n '0x2352c63A83f9Fd126af8676146721Fa00924d7e4',\n [SupportedTokenDetectionNetworks.polygon]:\n '0x2352c63A83f9Fd126af8676146721Fa00924d7e4',\n [SupportedTokenDetectionNetworks.avax]:\n '0xD023D153a0DFa485130ECFdE2FAA7e612EF94818',\n};\n\nexport const MISSING_PROVIDER_ERROR =\n 'AssetsContractController failed to set the provider correctly. A provider must be set for this method to be available';\n\n/**\n * @type AssetsContractConfig\n *\n * Assets Contract controller configuration\n * @property provider - Provider used to create a new web3 instance\n */\nexport interface AssetsContractConfig extends BaseConfig {\n provider: any;\n ipfsGateway: string;\n chainId: string;\n}\n\n/**\n * @type BalanceMap\n *\n * Key value object containing the balance for each tokenAddress\n * @property [tokenAddress] - Address of the token\n */\nexport interface BalanceMap {\n [tokenAddress: string]: BN;\n}\n\n/**\n * Controller that interacts with contracts on mainnet through web3\n */\nexport class AssetsContractController extends BaseController<\n AssetsContractConfig,\n BaseState\n> {\n private web3: any;\n\n private erc721Standard?: ERC721Standard;\n\n private erc1155Standard?: ERC1155Standard;\n\n private erc20Standard?: ERC20Standard;\n\n /**\n * Name of this controller used during composition\n */\n override name = 'AssetsContractController';\n\n /**\n * Creates a AssetsContractController instance.\n *\n * @param options - The controller options.\n * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes.\n * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n onPreferencesStateChange,\n onNetworkStateChange,\n }: {\n onPreferencesStateChange: (\n listener: (preferencesState: PreferencesState) => void,\n ) => void;\n onNetworkStateChange: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n },\n config?: Partial,\n state?: Partial,\n ) {\n super(config, state);\n this.defaultConfig = {\n provider: undefined,\n ipfsGateway: IPFS_DEFAULT_GATEWAY_URL,\n chainId: SupportedTokenDetectionNetworks.mainnet,\n };\n this.initialize();\n\n onPreferencesStateChange(({ ipfsGateway }) => {\n this.configure({ ipfsGateway });\n });\n\n onNetworkStateChange((networkState) => {\n if (this.config.chainId !== networkState.provider.chainId) {\n this.configure({\n chainId: networkState.provider.chainId,\n });\n }\n });\n }\n\n /**\n * Sets a new provider.\n *\n * TODO: Replace this wth a method.\n *\n * @property provider - Provider used to create a new underlying Web3 instance\n */\n set provider(provider: any) {\n this.web3 = new Web3(provider);\n this.erc721Standard = new ERC721Standard(this.web3);\n this.erc1155Standard = new ERC1155Standard(this.web3);\n this.erc20Standard = new ERC20Standard(this.web3);\n }\n\n get provider() {\n throw new Error('Property only used for setting');\n }\n\n /**\n * Get balance or count for current account on specific asset contract.\n *\n * @param address - Asset ERC20 contract address.\n * @param selectedAddress - Current account public address.\n * @returns Promise resolving to BN object containing balance for current account on specific asset contract.\n */\n async getERC20BalanceOf(\n address: string,\n selectedAddress: string,\n ): Promise {\n if (!this.erc20Standard) {\n throw new Error(MISSING_PROVIDER_ERROR);\n }\n return this.erc20Standard.getBalanceOf(address, selectedAddress);\n }\n\n /**\n * Query for the decimals for a given ERC20 asset.\n *\n * @param address - ERC20 asset contract address.\n * @returns Promise resolving to the 'decimals'.\n */\n async getERC20TokenDecimals(address: string): Promise {\n if (this.erc20Standard === undefined) {\n throw new Error(MISSING_PROVIDER_ERROR);\n }\n return await this.erc20Standard.getTokenDecimals(address);\n }\n\n /**\n * Enumerate assets assigned to an owner.\n *\n * @param address - ERC721 asset contract address.\n * @param selectedAddress - Current account public address.\n * @param index - A collectible counter less than `balanceOf(selectedAddress)`.\n * @returns Promise resolving to token identifier for the 'index'th asset assigned to 'selectedAddress'.\n */\n getERC721CollectibleTokenId(\n address: string,\n selectedAddress: string,\n index: number,\n ): Promise {\n if (this.erc721Standard === undefined) {\n throw new Error(MISSING_PROVIDER_ERROR);\n }\n return this.erc721Standard.getCollectibleTokenId(\n address,\n selectedAddress,\n index,\n );\n }\n\n /**\n * Enumerate assets assigned to an owner.\n *\n * @param tokenAddress - ERC721 asset contract address.\n * @param userAddress - Current account public address.\n * @param tokenId - ERC721 asset identifier.\n * @returns Promise resolving to an object containing the token standard and a set of details which depend on which standard the token supports.\n */\n async getTokenStandardAndDetails(\n tokenAddress: string,\n userAddress?: string,\n tokenId?: string,\n ): Promise<{\n standard: string;\n tokenURI?: string | undefined;\n symbol?: string | undefined;\n name?: string | undefined;\n decimals?: string | undefined;\n balance?: BN | undefined;\n }> {\n if (\n this.erc721Standard === undefined ||\n this.erc1155Standard === undefined ||\n this.erc20Standard === undefined\n ) {\n throw new Error(MISSING_PROVIDER_ERROR);\n }\n\n const { ipfsGateway } = this.config;\n\n // ERC721\n try {\n return {\n ...(await this.erc721Standard.getDetails(\n tokenAddress,\n ipfsGateway,\n tokenId,\n )),\n };\n } catch {\n // Ignore\n }\n\n // ERC1155\n try {\n return {\n ...(await this.erc1155Standard.getDetails(\n tokenAddress,\n ipfsGateway,\n tokenId,\n )),\n };\n } catch {\n // Ignore\n }\n\n // ERC20\n try {\n return {\n ...(await this.erc20Standard.getDetails(tokenAddress, userAddress)),\n };\n } catch {\n // Ignore\n }\n\n throw new Error('Unable to determine contract standard');\n }\n\n /**\n * Query for tokenURI for a given ERC721 asset.\n *\n * @param address - ERC721 asset contract address.\n * @param tokenId - ERC721 asset identifier.\n * @returns Promise resolving to the 'tokenURI'.\n */\n async getERC721TokenURI(address: string, tokenId: string): Promise {\n if (this.erc721Standard === undefined) {\n throw new Error(MISSING_PROVIDER_ERROR);\n }\n return this.erc721Standard.getTokenURI(address, tokenId);\n }\n\n /**\n * Query for name for a given asset.\n *\n * @param address - ERC721 or ERC20 asset contract address.\n * @returns Promise resolving to the 'name'.\n */\n async getERC721AssetName(address: string): Promise {\n if (this.erc721Standard === undefined) {\n throw new Error(MISSING_PROVIDER_ERROR);\n }\n return this.erc721Standard.getAssetName(address);\n }\n\n /**\n * Query for symbol for a given asset.\n *\n * @param address - ERC721 or ERC20 asset contract address.\n * @returns Promise resolving to the 'symbol'.\n */\n async getERC721AssetSymbol(address: string): Promise {\n if (this.erc721Standard === undefined) {\n throw new Error(MISSING_PROVIDER_ERROR);\n }\n return this.erc721Standard.getAssetSymbol(address);\n }\n\n /**\n * Query for owner for a given ERC721 asset.\n *\n * @param address - ERC721 asset contract address.\n * @param tokenId - ERC721 asset identifier.\n * @returns Promise resolving to the owner address.\n */\n async getERC721OwnerOf(address: string, tokenId: string): Promise {\n if (this.erc721Standard === undefined) {\n throw new Error(MISSING_PROVIDER_ERROR);\n }\n return this.erc721Standard.getOwnerOf(address, tokenId);\n }\n\n /**\n * Query for tokenURI for a given asset.\n *\n * @param address - ERC1155 asset contract address.\n * @param tokenId - ERC1155 asset identifier.\n * @returns Promise resolving to the 'tokenURI'.\n */\n async getERC1155TokenURI(address: string, tokenId: string): Promise {\n if (this.erc1155Standard === undefined) {\n throw new Error(MISSING_PROVIDER_ERROR);\n }\n return this.erc1155Standard.getTokenURI(address, tokenId);\n }\n\n /**\n * Query for balance of a given ERC 1155 token.\n *\n * @param userAddress - Wallet public address.\n * @param collectibleAddress - ERC1155 asset contract address.\n * @param collectibleId - ERC1155 asset identifier.\n * @returns Promise resolving to the 'balanceOf'.\n */\n async getERC1155BalanceOf(\n userAddress: string,\n collectibleAddress: string,\n collectibleId: string,\n ): Promise {\n if (this.erc1155Standard === undefined) {\n throw new Error(MISSING_PROVIDER_ERROR);\n }\n return await this.erc1155Standard.getBalanceOf(\n collectibleAddress,\n userAddress,\n collectibleId,\n );\n }\n\n /**\n * Transfer single ERC1155 token.\n *\n * @param collectibleAddress - ERC1155 token address.\n * @param senderAddress - ERC1155 token sender.\n * @param recipientAddress - ERC1155 token recipient.\n * @param collectibleId - ERC1155 token id.\n * @param qty - Quantity of tokens to be sent.\n * @returns Promise resolving to the 'transferSingle' ERC1155 token.\n */\n async transferSingleERC1155(\n collectibleAddress: string,\n senderAddress: string,\n recipientAddress: string,\n collectibleId: string,\n qty: string,\n ): Promise {\n if (this.erc1155Standard === undefined) {\n throw new Error(MISSING_PROVIDER_ERROR);\n }\n return await this.erc1155Standard.transferSingle(\n collectibleAddress,\n senderAddress,\n recipientAddress,\n collectibleId,\n qty,\n );\n }\n\n /**\n * Get the token balance for a list of token addresses in a single call. Only non-zero balances\n * are returned.\n *\n * @param selectedAddress - The address to check token balances for.\n * @param tokensToDetect - The token addresses to detect balances for.\n * @returns The list of non-zero token balances.\n */\n async getBalancesInSingleCall(\n selectedAddress: string,\n tokensToDetect: string[],\n ) {\n if (!(this.config.chainId in SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID)) {\n // Only fetch balance if contract address exists\n return {};\n }\n const contractAddress =\n SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID[this.config.chainId];\n\n const contract = this.web3.eth\n .contract(abiSingleCallBalancesContract)\n .at(contractAddress);\n return new Promise((resolve, reject) => {\n contract.balances(\n [selectedAddress],\n tokensToDetect,\n (error: Error, result: BN[]) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n const nonZeroBalances: BalanceMap = {};\n /* istanbul ignore else */\n if (result.length > 0) {\n tokensToDetect.forEach((tokenAddress, index) => {\n const balance: BN = result[index];\n /* istanbul ignore else */\n if (String(balance) !== '0') {\n nonZeroBalances[tokenAddress] = balance;\n }\n });\n }\n resolve(nonZeroBalances);\n },\n );\n });\n }\n}\n\nexport default AssetsContractController;\n"]} \ No newline at end of file diff --git a/dist/assets/CollectibleDetectionController.d.ts b/dist/assets/CollectibleDetectionController.d.ts new file mode 100644 index 0000000000..8dfded142e --- /dev/null +++ b/dist/assets/CollectibleDetectionController.d.ts @@ -0,0 +1,178 @@ +import { BaseController, BaseConfig, BaseState } from '../BaseController'; +import type { NetworkState, NetworkType } from '../network/NetworkController'; +import type { PreferencesState } from '../user/PreferencesController'; +import type { CollectiblesController, CollectiblesState } from './CollectiblesController'; +/** + * @type ApiCollectible + * + * Collectible object coming from OpenSea api + * @property token_id - The collectible identifier + * @property num_sales - Number of sales + * @property background_color - The background color to be displayed with the item + * @property image_url - URI of an image associated with this collectible + * @property image_preview_url - URI of a smaller image associated with this collectible + * @property image_thumbnail_url - URI of a thumbnail image associated with this collectible + * @property image_original_url - URI of the original image associated with this collectible + * @property animation_url - URI of a animation associated with this collectible + * @property animation_original_url - URI of the original animation associated with this collectible + * @property name - The collectible name + * @property description - The collectible description + * @property external_link - External link containing additional information + * @property assetContract - The collectible contract information object + * @property creator - The collectible owner information object + * @property lastSale - When this item was last sold + */ +export interface ApiCollectible { + token_id: string; + num_sales: number | null; + background_color: string | null; + image_url: string | null; + image_preview_url: string | null; + image_thumbnail_url: string | null; + image_original_url: string | null; + animation_url: string | null; + animation_original_url: string | null; + name: string | null; + description: string | null; + external_link: string | null; + asset_contract: ApiCollectibleContract; + creator: ApiCollectibleCreator; + last_sale: ApiCollectibleLastSale | null; +} +/** + * @type ApiCollectibleContract + * + * Collectible contract object coming from OpenSea api + * @property address - Address of the collectible contract + * @property asset_contract_type - The collectible type, it could be `semi-fungible` or `non-fungible` + * @property created_date - Creation date + * @property collection - Object containing the contract name and URI of an image associated + * @property schema_name - The schema followed by the contract, it could be `ERC721` or `ERC1155` + * @property symbol - The collectible contract symbol + * @property total_supply - Total supply of collectibles + * @property description - The collectible contract description + * @property external_link - External link containing additional information + */ +export interface ApiCollectibleContract { + address: string; + asset_contract_type: string | null; + created_date: string | null; + schema_name: string | null; + symbol: string | null; + total_supply: string | null; + description: string | null; + external_link: string | null; + collection: { + name: string | null; + image_url?: string | null; + }; +} +/** + * @type ApiCollectibleLastSale + * + * Collectible sale object coming from OpenSea api + * @property event_timestamp - Object containing a `username` + * @property total_price - URI of collectible image associated with this owner + * @property transaction - Object containing transaction_hash and block_hash + */ +export interface ApiCollectibleLastSale { + event_timestamp: string; + total_price: string; + transaction: { + transaction_hash: string; + block_hash: string; + }; +} +/** + * @type ApiCollectibleCreator + * + * Collectible creator object coming from OpenSea api + * @property user - Object containing a `username` + * @property profile_img_url - URI of collectible image associated with this owner + * @property address - The owner address + */ +export interface ApiCollectibleCreator { + user: { + username: string; + }; + profile_img_url: string; + address: string; +} +/** + * @type CollectibleDetectionConfig + * + * CollectibleDetection configuration + * @property interval - Polling interval used to fetch new token rates + * @property networkType - Network type ID as per net_version + * @property selectedAddress - Vault selected address + * @property tokens - List of tokens associated with the active vault + */ +export interface CollectibleDetectionConfig extends BaseConfig { + interval: number; + networkType: NetworkType; + chainId: `0x${string}` | `${number}` | number; + selectedAddress: string; +} +/** + * Controller that passively polls on a set interval for Collectibles auto detection + */ +export declare class CollectibleDetectionController extends BaseController { + private intervalId?; + private getOwnerCollectiblesApi; + private getOwnerCollectibles; + /** + * Name of this controller used during composition + */ + name: string; + private getOpenSeaApiKey; + private addCollectible; + private getCollectiblesState; + /** + * Creates a CollectibleDetectionController instance. + * + * @param options - The controller options. + * @param options.onCollectiblesStateChange - Allows subscribing to assets controller state changes. + * @param options.onPreferencesStateChange - Allows subscribing to preferences controller state changes. + * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. + * @param options.getOpenSeaApiKey - Gets the OpenSea API key, if one is set. + * @param options.addCollectible - Add a collectible. + * @param options.getCollectiblesState - Gets the current state of the Assets controller. + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor({ onPreferencesStateChange, onNetworkStateChange, getOpenSeaApiKey, addCollectible, getCollectiblesState, }: { + onCollectiblesStateChange: (listener: (collectiblesState: CollectiblesState) => void) => void; + onPreferencesStateChange: (listener: (preferencesState: PreferencesState) => void) => void; + onNetworkStateChange: (listener: (networkState: NetworkState) => void) => void; + getOpenSeaApiKey: () => string | undefined; + addCollectible: CollectiblesController['addCollectible']; + getCollectiblesState: () => CollectiblesState; + }, config?: Partial, state?: Partial); + /** + * Start polling for the currency rate. + */ + start(): Promise; + /** + * Stop polling for the currency rate. + */ + stop(): void; + private stopPolling; + /** + * Starts a new polling interval. + * + * @param interval - An interval on which to poll. + */ + private startPolling; + /** + * Checks whether network is mainnet or not. + * + * @returns Whether current network is mainnet. + */ + isMainnet: () => boolean; + /** + * Triggers asset ERC721 token auto detection on mainnet. Any newly detected collectibles are + * added. + */ + detectCollectibles(): Promise; +} +export default CollectibleDetectionController; diff --git a/dist/assets/CollectibleDetectionController.js b/dist/assets/CollectibleDetectionController.js new file mode 100644 index 0000000000..85b76fd0a2 --- /dev/null +++ b/dist/assets/CollectibleDetectionController.js @@ -0,0 +1,205 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CollectibleDetectionController = void 0; +const BaseController_1 = require("../BaseController"); +const util_1 = require("../util"); +const constants_1 = require("../constants"); +const DEFAULT_INTERVAL = 180000; +/** + * Controller that passively polls on a set interval for Collectibles auto detection + */ +class CollectibleDetectionController extends BaseController_1.BaseController { + /** + * Creates a CollectibleDetectionController instance. + * + * @param options - The controller options. + * @param options.onCollectiblesStateChange - Allows subscribing to assets controller state changes. + * @param options.onPreferencesStateChange - Allows subscribing to preferences controller state changes. + * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. + * @param options.getOpenSeaApiKey - Gets the OpenSea API key, if one is set. + * @param options.addCollectible - Add a collectible. + * @param options.getCollectiblesState - Gets the current state of the Assets controller. + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor({ onPreferencesStateChange, onNetworkStateChange, getOpenSeaApiKey, addCollectible, getCollectiblesState, }, config, state) { + super(config, state); + /** + * Name of this controller used during composition + */ + this.name = 'CollectibleDetectionController'; + /** + * Checks whether network is mainnet or not. + * + * @returns Whether current network is mainnet. + */ + this.isMainnet = () => this.config.networkType === constants_1.MAINNET; + this.defaultConfig = { + interval: DEFAULT_INTERVAL, + networkType: constants_1.MAINNET, + chainId: '1', + selectedAddress: '', + disabled: true, + }; + this.initialize(); + this.getCollectiblesState = getCollectiblesState; + onPreferencesStateChange(({ selectedAddress, useCollectibleDetection }) => { + const { selectedAddress: previouslySelectedAddress, disabled } = this.config; + if (selectedAddress !== previouslySelectedAddress || + !useCollectibleDetection !== disabled) { + this.configure({ selectedAddress, disabled: !useCollectibleDetection }); + } + if (useCollectibleDetection !== undefined) { + if (useCollectibleDetection) { + this.start(); + } + else { + this.stop(); + } + } + }); + onNetworkStateChange(({ provider }) => { + this.configure({ + networkType: provider.type, + chainId: provider.chainId, + }); + }); + this.getOpenSeaApiKey = getOpenSeaApiKey; + this.addCollectible = addCollectible; + } + getOwnerCollectiblesApi({ address, offset, useProxy, }) { + return useProxy + ? `${constants_1.OPENSEA_PROXY_URL}/assets?owner=${address}&offset=${offset}&limit=50` + : `${constants_1.OPENSEA_API_URL}/assets?owner=${address}&offset=${offset}&limit=50`; + } + getOwnerCollectibles(address) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + let collectiblesApiResponse; + let collectibles = []; + const openSeaApiKey = this.getOpenSeaApiKey(); + let offset = 0; + let pagingFinish = false; + /* istanbul ignore if */ + do { + collectiblesApiResponse = yield (0, util_1.fetchWithErrorHandling)({ + url: this.getOwnerCollectiblesApi({ address, offset, useProxy: true }), + timeout: 15000, + }); + if (openSeaApiKey && !collectiblesApiResponse) { + collectiblesApiResponse = yield (0, util_1.fetchWithErrorHandling)({ + url: this.getOwnerCollectiblesApi({ + address, + offset, + useProxy: false, + }), + options: { headers: { 'X-API-KEY': openSeaApiKey } }, + timeout: 15000, + // catch 403 errors (in case API key is down we don't want to blow up) + errorCodesToCatch: [403], + }); + } + if (!collectiblesApiResponse) { + return collectibles; + } + ((_a = collectiblesApiResponse === null || collectiblesApiResponse === void 0 ? void 0 : collectiblesApiResponse.assets) === null || _a === void 0 ? void 0 : _a.length) !== 0 + ? (collectibles = [...collectibles, ...collectiblesApiResponse.assets]) + : (pagingFinish = true); + offset += 50; + } while (!pagingFinish); + return collectibles; + }); + } + /** + * Start polling for the currency rate. + */ + start() { + return __awaiter(this, void 0, void 0, function* () { + if (!this.isMainnet() || this.disabled) { + return; + } + yield this.startPolling(); + }); + } + /** + * Stop polling for the currency rate. + */ + stop() { + this.stopPolling(); + } + stopPolling() { + if (this.intervalId) { + clearInterval(this.intervalId); + } + } + /** + * Starts a new polling interval. + * + * @param interval - An interval on which to poll. + */ + startPolling(interval) { + return __awaiter(this, void 0, void 0, function* () { + interval && this.configure({ interval }, false, false); + this.stopPolling(); + yield this.detectCollectibles(); + this.intervalId = setInterval(() => __awaiter(this, void 0, void 0, function* () { + yield this.detectCollectibles(); + }), this.config.interval); + }); + } + /** + * Triggers asset ERC721 token auto detection on mainnet. Any newly detected collectibles are + * added. + */ + detectCollectibles() { + return __awaiter(this, void 0, void 0, function* () { + /* istanbul ignore if */ + if (!this.isMainnet() || this.disabled) { + return; + } + const { selectedAddress, chainId } = this.config; + /* istanbul ignore else */ + if (!selectedAddress) { + return; + } + const apiCollectibles = yield this.getOwnerCollectibles(selectedAddress); + const addCollectiblesPromises = apiCollectibles.map((collectible) => __awaiter(this, void 0, void 0, function* () { + const { token_id, num_sales, background_color, image_url, image_preview_url, image_thumbnail_url, image_original_url, animation_url, animation_original_url, name, description, external_link, creator, asset_contract: { address, schema_name }, last_sale, } = collectible; + let ignored; + /* istanbul ignore else */ + const { ignoredCollectibles } = this.getCollectiblesState(); + if (ignoredCollectibles.length) { + ignored = ignoredCollectibles.find((c) => { + /* istanbul ignore next */ + return (c.address === (0, util_1.toChecksumHexAddress)(address) && + c.tokenId === token_id); + }); + } + /* istanbul ignore else */ + if (!ignored) { + /* istanbul ignore next */ + const collectibleMetadata = Object.assign({}, { name }, creator && { creator }, description && { description }, image_url && { image: image_url }, num_sales && { numberOfSales: num_sales }, background_color && { backgroundColor: background_color }, image_preview_url && { imagePreview: image_preview_url }, image_thumbnail_url && { imageThumbnail: image_thumbnail_url }, image_original_url && { imageOriginal: image_original_url }, animation_url && { animation: animation_url }, animation_original_url && { + animationOriginal: animation_original_url, + }, schema_name && { standard: schema_name }, external_link && { externalLink: external_link }, last_sale && { lastSale: last_sale }); + yield this.addCollectible(address, token_id, collectibleMetadata, { + userAddress: selectedAddress, + chainId: chainId, + }); + } + })); + yield Promise.all(addCollectiblesPromises); + }); + } +} +exports.CollectibleDetectionController = CollectibleDetectionController; +exports.default = CollectibleDetectionController; +//# sourceMappingURL=CollectibleDetectionController.js.map \ No newline at end of file diff --git a/dist/assets/CollectibleDetectionController.js.map b/dist/assets/CollectibleDetectionController.js.map new file mode 100644 index 0000000000..e3f6a79057 --- /dev/null +++ b/dist/assets/CollectibleDetectionController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CollectibleDetectionController.js","sourceRoot":"","sources":["../../src/assets/CollectibleDetectionController.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,sDAA0E;AAG1E,kCAAuE;AACvE,4CAA2E;AAQ3E,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAiHhC;;GAEG;AACH,MAAa,8BAA+B,SAAQ,+BAGnD;IAoEC;;;;;;;;;;;;OAYG;IACH,YACE,EACE,wBAAwB,EACxB,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,oBAAoB,GAcrB,EACD,MAA4C,EAC5C,KAA0B;QAE1B,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAhDvB;;WAEG;QACM,SAAI,GAAG,gCAAgC,CAAC;QA2HjD;;;;WAIG;QACH,cAAS,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,KAAK,mBAAO,CAAC;QAlF7D,IAAI,CAAC,aAAa,GAAG;YACnB,QAAQ,EAAE,gBAAgB;YAC1B,WAAW,EAAE,mBAAO;YACpB,OAAO,EAAE,GAAG;YACZ,eAAe,EAAE,EAAE;YACnB,QAAQ,EAAE,IAAI;SACf,CAAC;QACF,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,CAAC,oBAAoB,GAAG,oBAAoB,CAAC;QACjD,wBAAwB,CAAC,CAAC,EAAE,eAAe,EAAE,uBAAuB,EAAE,EAAE,EAAE;YACxE,MAAM,EAAE,eAAe,EAAE,yBAAyB,EAAE,QAAQ,EAAE,GAC5D,IAAI,CAAC,MAAM,CAAC;YAEd,IACE,eAAe,KAAK,yBAAyB;gBAC7C,CAAC,uBAAuB,KAAK,QAAQ,EACrC;gBACA,IAAI,CAAC,SAAS,CAAC,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC,uBAAuB,EAAE,CAAC,CAAC;aACzE;YAED,IAAI,uBAAuB,KAAK,SAAS,EAAE;gBACzC,IAAI,uBAAuB,EAAE;oBAC3B,IAAI,CAAC,KAAK,EAAE,CAAC;iBACd;qBAAM;oBACL,IAAI,CAAC,IAAI,EAAE,CAAC;iBACb;aACF;QACH,CAAC,CAAC,CAAC;QAEH,oBAAoB,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE;YACpC,IAAI,CAAC,SAAS,CAAC;gBACb,WAAW,EAAE,QAAQ,CAAC,IAAI;gBAC1B,OAAO,EAAE,QAAQ,CAAC,OAAgD;aACnE,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;QACzC,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;IACvC,CAAC;IA5IO,uBAAuB,CAAC,EAC9B,OAAO,EACP,MAAM,EACN,QAAQ,GAKT;QACC,OAAO,QAAQ;YACb,CAAC,CAAC,GAAG,6BAAiB,iBAAiB,OAAO,WAAW,MAAM,WAAW;YAC1E,CAAC,CAAC,GAAG,2BAAe,iBAAiB,OAAO,WAAW,MAAM,WAAW,CAAC;IAC7E,CAAC;IAEa,oBAAoB,CAAC,OAAe;;;YAChD,IAAI,uBAAqD,CAAC;YAC1D,IAAI,YAAY,GAAqB,EAAE,CAAC;YACxC,MAAM,aAAa,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC9C,IAAI,MAAM,GAAG,CAAC,CAAC;YACf,IAAI,YAAY,GAAG,KAAK,CAAC;YACzB,wBAAwB;YACxB,GAAG;gBACD,uBAAuB,GAAG,MAAM,IAAA,6BAAsB,EAAC;oBACrD,GAAG,EAAE,IAAI,CAAC,uBAAuB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;oBACtE,OAAO,EAAE,KAAK;iBACf,CAAC,CAAC;gBAEH,IAAI,aAAa,IAAI,CAAC,uBAAuB,EAAE;oBAC7C,uBAAuB,GAAG,MAAM,IAAA,6BAAsB,EAAC;wBACrD,GAAG,EAAE,IAAI,CAAC,uBAAuB,CAAC;4BAChC,OAAO;4BACP,MAAM;4BACN,QAAQ,EAAE,KAAK;yBAChB,CAAC;wBACF,OAAO,EAAE,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,aAAa,EAAE,EAAE;wBACpD,OAAO,EAAE,KAAK;wBACd,sEAAsE;wBACtE,iBAAiB,EAAE,CAAC,GAAG,CAAC;qBACzB,CAAC,CAAC;iBACJ;gBAED,IAAI,CAAC,uBAAuB,EAAE;oBAC5B,OAAO,YAAY,CAAC;iBACrB;gBAED,CAAA,MAAA,uBAAuB,aAAvB,uBAAuB,uBAAvB,uBAAuB,CAAE,MAAM,0CAAE,MAAM,MAAK,CAAC;oBAC3C,CAAC,CAAC,CAAC,YAAY,GAAG,CAAC,GAAG,YAAY,EAAE,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC;oBACvE,CAAC,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;gBAC1B,MAAM,IAAI,EAAE,CAAC;aACd,QAAQ,CAAC,YAAY,EAAE;YAExB,OAAO,YAAY,CAAC;;KACrB;IA0FD;;OAEG;IACG,KAAK;;YACT,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE;gBACtC,OAAO;aACR;YAED,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;QAC5B,CAAC;KAAA;IAED;;OAEG;IACH,IAAI;QACF,IAAI,CAAC,WAAW,EAAE,CAAC;IACrB,CAAC;IAEO,WAAW;QACjB,IAAI,IAAI,CAAC,UAAU,EAAE;YACnB,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;SAChC;IACH,CAAC;IAED;;;;OAIG;IACW,YAAY,CAAC,QAAiB;;YAC1C,QAAQ,IAAI,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;YACvD,IAAI,CAAC,WAAW,EAAE,CAAC;YACnB,MAAM,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAChC,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC,GAAS,EAAE;gBACvC,MAAM,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAClC,CAAC,CAAA,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC3B,CAAC;KAAA;IASD;;;OAGG;IACG,kBAAkB;;YACtB,wBAAwB;YACxB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE;gBACtC,OAAO;aACR;YACD,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC;YAEjD,0BAA0B;YAC1B,IAAI,CAAC,eAAe,EAAE;gBACpB,OAAO;aACR;YAED,MAAM,eAAe,GAAG,MAAM,IAAI,CAAC,oBAAoB,CAAC,eAAe,CAAC,CAAC;YACzE,MAAM,uBAAuB,GAAG,eAAe,CAAC,GAAG,CACjD,CAAO,WAA2B,EAAE,EAAE;gBACpC,MAAM,EACJ,QAAQ,EACR,SAAS,EACT,gBAAgB,EAChB,SAAS,EACT,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,aAAa,EACb,sBAAsB,EACtB,IAAI,EACJ,WAAW,EACX,aAAa,EACb,OAAO,EACP,cAAc,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,EACxC,SAAS,GACV,GAAG,WAAW,CAAC;gBAEhB,IAAI,OAAO,CAAC;gBACZ,0BAA0B;gBAC1B,MAAM,EAAE,mBAAmB,EAAE,GAAG,IAAI,CAAC,oBAAoB,EAAE,CAAC;gBAC5D,IAAI,mBAAmB,CAAC,MAAM,EAAE;oBAC9B,OAAO,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;wBACvC,0BAA0B;wBAC1B,OAAO,CACL,CAAC,CAAC,OAAO,KAAK,IAAA,2BAAoB,EAAC,OAAO,CAAC;4BAC3C,CAAC,CAAC,OAAO,KAAK,QAAQ,CACvB,CAAC;oBACJ,CAAC,CAAC,CAAC;iBACJ;gBAED,0BAA0B;gBAC1B,IAAI,CAAC,OAAO,EAAE;oBACZ,0BAA0B;oBAC1B,MAAM,mBAAmB,GAAwB,MAAM,CAAC,MAAM,CAC5D,EAAE,EACF,EAAE,IAAI,EAAE,EACR,OAAO,IAAI,EAAE,OAAO,EAAE,EACtB,WAAW,IAAI,EAAE,WAAW,EAAE,EAC9B,SAAS,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,EACjC,SAAS,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,EACzC,gBAAgB,IAAI,EAAE,eAAe,EAAE,gBAAgB,EAAE,EACzD,iBAAiB,IAAI,EAAE,YAAY,EAAE,iBAAiB,EAAE,EACxD,mBAAmB,IAAI,EAAE,cAAc,EAAE,mBAAmB,EAAE,EAC9D,kBAAkB,IAAI,EAAE,aAAa,EAAE,kBAAkB,EAAE,EAC3D,aAAa,IAAI,EAAE,SAAS,EAAE,aAAa,EAAE,EAC7C,sBAAsB,IAAI;wBACxB,iBAAiB,EAAE,sBAAsB;qBAC1C,EACD,WAAW,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,EACxC,aAAa,IAAI,EAAE,YAAY,EAAE,aAAa,EAAE,EAChD,SAAS,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,CACrC,CAAC;oBAEF,MAAM,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE;wBAChE,WAAW,EAAE,eAAe;wBAC5B,OAAO,EAAE,OAAiB;qBAC3B,CAAC,CAAC;iBACJ;YACH,CAAC,CAAA,CACF,CAAC;YACF,MAAM,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;QAC7C,CAAC;KAAA;CACF;AAnRD,wEAmRC;AAED,kBAAe,8BAA8B,CAAC","sourcesContent":["import { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport type { NetworkState, NetworkType } from '../network/NetworkController';\nimport type { PreferencesState } from '../user/PreferencesController';\nimport { fetchWithErrorHandling, toChecksumHexAddress } from '../util';\nimport { MAINNET, OPENSEA_PROXY_URL, OPENSEA_API_URL } from '../constants';\n\nimport type {\n CollectiblesController,\n CollectiblesState,\n CollectibleMetadata,\n} from './CollectiblesController';\n\nconst DEFAULT_INTERVAL = 180000;\n\n/**\n * @type ApiCollectible\n *\n * Collectible object coming from OpenSea api\n * @property token_id - The collectible identifier\n * @property num_sales - Number of sales\n * @property background_color - The background color to be displayed with the item\n * @property image_url - URI of an image associated with this collectible\n * @property image_preview_url - URI of a smaller image associated with this collectible\n * @property image_thumbnail_url - URI of a thumbnail image associated with this collectible\n * @property image_original_url - URI of the original image associated with this collectible\n * @property animation_url - URI of a animation associated with this collectible\n * @property animation_original_url - URI of the original animation associated with this collectible\n * @property name - The collectible name\n * @property description - The collectible description\n * @property external_link - External link containing additional information\n * @property assetContract - The collectible contract information object\n * @property creator - The collectible owner information object\n * @property lastSale - When this item was last sold\n */\nexport interface ApiCollectible {\n token_id: string;\n num_sales: number | null;\n background_color: string | null;\n image_url: string | null;\n image_preview_url: string | null;\n image_thumbnail_url: string | null;\n image_original_url: string | null;\n animation_url: string | null;\n animation_original_url: string | null;\n name: string | null;\n description: string | null;\n external_link: string | null;\n asset_contract: ApiCollectibleContract;\n creator: ApiCollectibleCreator;\n last_sale: ApiCollectibleLastSale | null;\n}\n\n/**\n * @type ApiCollectibleContract\n *\n * Collectible contract object coming from OpenSea api\n * @property address - Address of the collectible contract\n * @property asset_contract_type - The collectible type, it could be `semi-fungible` or `non-fungible`\n * @property created_date - Creation date\n * @property collection - Object containing the contract name and URI of an image associated\n * @property schema_name - The schema followed by the contract, it could be `ERC721` or `ERC1155`\n * @property symbol - The collectible contract symbol\n * @property total_supply - Total supply of collectibles\n * @property description - The collectible contract description\n * @property external_link - External link containing additional information\n */\nexport interface ApiCollectibleContract {\n address: string;\n asset_contract_type: string | null;\n created_date: string | null;\n schema_name: string | null;\n symbol: string | null;\n total_supply: string | null;\n description: string | null;\n external_link: string | null;\n collection: {\n name: string | null;\n image_url?: string | null;\n };\n}\n\n/**\n * @type ApiCollectibleLastSale\n *\n * Collectible sale object coming from OpenSea api\n * @property event_timestamp - Object containing a `username`\n * @property total_price - URI of collectible image associated with this owner\n * @property transaction - Object containing transaction_hash and block_hash\n */\nexport interface ApiCollectibleLastSale {\n event_timestamp: string;\n total_price: string;\n transaction: { transaction_hash: string; block_hash: string };\n}\n\n/**\n * @type ApiCollectibleCreator\n *\n * Collectible creator object coming from OpenSea api\n * @property user - Object containing a `username`\n * @property profile_img_url - URI of collectible image associated with this owner\n * @property address - The owner address\n */\nexport interface ApiCollectibleCreator {\n user: { username: string };\n profile_img_url: string;\n address: string;\n}\n\n/**\n * @type CollectibleDetectionConfig\n *\n * CollectibleDetection configuration\n * @property interval - Polling interval used to fetch new token rates\n * @property networkType - Network type ID as per net_version\n * @property selectedAddress - Vault selected address\n * @property tokens - List of tokens associated with the active vault\n */\nexport interface CollectibleDetectionConfig extends BaseConfig {\n interval: number;\n networkType: NetworkType;\n chainId: `0x${string}` | `${number}` | number;\n selectedAddress: string;\n}\n\n/**\n * Controller that passively polls on a set interval for Collectibles auto detection\n */\nexport class CollectibleDetectionController extends BaseController<\n CollectibleDetectionConfig,\n BaseState\n> {\n private intervalId?: NodeJS.Timeout;\n\n private getOwnerCollectiblesApi({\n address,\n offset,\n useProxy,\n }: {\n address: string;\n offset: number;\n useProxy: boolean;\n }) {\n return useProxy\n ? `${OPENSEA_PROXY_URL}/assets?owner=${address}&offset=${offset}&limit=50`\n : `${OPENSEA_API_URL}/assets?owner=${address}&offset=${offset}&limit=50`;\n }\n\n private async getOwnerCollectibles(address: string) {\n let collectiblesApiResponse: { assets: ApiCollectible[] };\n let collectibles: ApiCollectible[] = [];\n const openSeaApiKey = this.getOpenSeaApiKey();\n let offset = 0;\n let pagingFinish = false;\n /* istanbul ignore if */\n do {\n collectiblesApiResponse = await fetchWithErrorHandling({\n url: this.getOwnerCollectiblesApi({ address, offset, useProxy: true }),\n timeout: 15000,\n });\n\n if (openSeaApiKey && !collectiblesApiResponse) {\n collectiblesApiResponse = await fetchWithErrorHandling({\n url: this.getOwnerCollectiblesApi({\n address,\n offset,\n useProxy: false,\n }),\n options: { headers: { 'X-API-KEY': openSeaApiKey } },\n timeout: 15000,\n // catch 403 errors (in case API key is down we don't want to blow up)\n errorCodesToCatch: [403],\n });\n }\n\n if (!collectiblesApiResponse) {\n return collectibles;\n }\n\n collectiblesApiResponse?.assets?.length !== 0\n ? (collectibles = [...collectibles, ...collectiblesApiResponse.assets])\n : (pagingFinish = true);\n offset += 50;\n } while (!pagingFinish);\n\n return collectibles;\n }\n\n /**\n * Name of this controller used during composition\n */\n override name = 'CollectibleDetectionController';\n\n private getOpenSeaApiKey: () => string | undefined;\n\n private addCollectible: CollectiblesController['addCollectible'];\n\n private getCollectiblesState: () => CollectiblesState;\n\n /**\n * Creates a CollectibleDetectionController instance.\n *\n * @param options - The controller options.\n * @param options.onCollectiblesStateChange - Allows subscribing to assets controller state changes.\n * @param options.onPreferencesStateChange - Allows subscribing to preferences controller state changes.\n * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.\n * @param options.getOpenSeaApiKey - Gets the OpenSea API key, if one is set.\n * @param options.addCollectible - Add a collectible.\n * @param options.getCollectiblesState - Gets the current state of the Assets controller.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n onPreferencesStateChange,\n onNetworkStateChange,\n getOpenSeaApiKey,\n addCollectible,\n getCollectiblesState,\n }: {\n onCollectiblesStateChange: (\n listener: (collectiblesState: CollectiblesState) => void,\n ) => void;\n onPreferencesStateChange: (\n listener: (preferencesState: PreferencesState) => void,\n ) => void;\n onNetworkStateChange: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n getOpenSeaApiKey: () => string | undefined;\n addCollectible: CollectiblesController['addCollectible'];\n getCollectiblesState: () => CollectiblesState;\n },\n config?: Partial,\n state?: Partial,\n ) {\n super(config, state);\n this.defaultConfig = {\n interval: DEFAULT_INTERVAL,\n networkType: MAINNET,\n chainId: '1',\n selectedAddress: '',\n disabled: true,\n };\n this.initialize();\n this.getCollectiblesState = getCollectiblesState;\n onPreferencesStateChange(({ selectedAddress, useCollectibleDetection }) => {\n const { selectedAddress: previouslySelectedAddress, disabled } =\n this.config;\n\n if (\n selectedAddress !== previouslySelectedAddress ||\n !useCollectibleDetection !== disabled\n ) {\n this.configure({ selectedAddress, disabled: !useCollectibleDetection });\n }\n\n if (useCollectibleDetection !== undefined) {\n if (useCollectibleDetection) {\n this.start();\n } else {\n this.stop();\n }\n }\n });\n\n onNetworkStateChange(({ provider }) => {\n this.configure({\n networkType: provider.type,\n chainId: provider.chainId as CollectibleDetectionConfig['chainId'],\n });\n });\n this.getOpenSeaApiKey = getOpenSeaApiKey;\n this.addCollectible = addCollectible;\n }\n\n /**\n * Start polling for the currency rate.\n */\n async start() {\n if (!this.isMainnet() || this.disabled) {\n return;\n }\n\n await this.startPolling();\n }\n\n /**\n * Stop polling for the currency rate.\n */\n stop() {\n this.stopPolling();\n }\n\n private stopPolling() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n }\n\n /**\n * Starts a new polling interval.\n *\n * @param interval - An interval on which to poll.\n */\n private async startPolling(interval?: number): Promise {\n interval && this.configure({ interval }, false, false);\n this.stopPolling();\n await this.detectCollectibles();\n this.intervalId = setInterval(async () => {\n await this.detectCollectibles();\n }, this.config.interval);\n }\n\n /**\n * Checks whether network is mainnet or not.\n *\n * @returns Whether current network is mainnet.\n */\n isMainnet = (): boolean => this.config.networkType === MAINNET;\n\n /**\n * Triggers asset ERC721 token auto detection on mainnet. Any newly detected collectibles are\n * added.\n */\n async detectCollectibles() {\n /* istanbul ignore if */\n if (!this.isMainnet() || this.disabled) {\n return;\n }\n const { selectedAddress, chainId } = this.config;\n\n /* istanbul ignore else */\n if (!selectedAddress) {\n return;\n }\n\n const apiCollectibles = await this.getOwnerCollectibles(selectedAddress);\n const addCollectiblesPromises = apiCollectibles.map(\n async (collectible: ApiCollectible) => {\n const {\n token_id,\n num_sales,\n background_color,\n image_url,\n image_preview_url,\n image_thumbnail_url,\n image_original_url,\n animation_url,\n animation_original_url,\n name,\n description,\n external_link,\n creator,\n asset_contract: { address, schema_name },\n last_sale,\n } = collectible;\n\n let ignored;\n /* istanbul ignore else */\n const { ignoredCollectibles } = this.getCollectiblesState();\n if (ignoredCollectibles.length) {\n ignored = ignoredCollectibles.find((c) => {\n /* istanbul ignore next */\n return (\n c.address === toChecksumHexAddress(address) &&\n c.tokenId === token_id\n );\n });\n }\n\n /* istanbul ignore else */\n if (!ignored) {\n /* istanbul ignore next */\n const collectibleMetadata: CollectibleMetadata = Object.assign(\n {},\n { name },\n creator && { creator },\n description && { description },\n image_url && { image: image_url },\n num_sales && { numberOfSales: num_sales },\n background_color && { backgroundColor: background_color },\n image_preview_url && { imagePreview: image_preview_url },\n image_thumbnail_url && { imageThumbnail: image_thumbnail_url },\n image_original_url && { imageOriginal: image_original_url },\n animation_url && { animation: animation_url },\n animation_original_url && {\n animationOriginal: animation_original_url,\n },\n schema_name && { standard: schema_name },\n external_link && { externalLink: external_link },\n last_sale && { lastSale: last_sale },\n );\n\n await this.addCollectible(address, token_id, collectibleMetadata, {\n userAddress: selectedAddress,\n chainId: chainId as string,\n });\n }\n },\n );\n await Promise.all(addCollectiblesPromises);\n }\n}\n\nexport default CollectibleDetectionController;\n"]} \ No newline at end of file diff --git a/dist/assets/CollectiblesController.d.ts b/dist/assets/CollectiblesController.d.ts new file mode 100644 index 0000000000..d0d57a51a4 --- /dev/null +++ b/dist/assets/CollectiblesController.d.ts @@ -0,0 +1,377 @@ +/// +import { EventEmitter } from 'events'; +import { BaseController, BaseConfig, BaseState } from '../BaseController'; +import type { PreferencesState } from '../user/PreferencesController'; +import type { NetworkState, NetworkType } from '../network/NetworkController'; +import type { ApiCollectibleCreator, ApiCollectibleLastSale } from './CollectibleDetectionController'; +import type { AssetsContractController } from './AssetsContractController'; +/** + * @type Collectible + * + * Collectible representation + * @property address - Hex address of a ERC721 contract + * @property description - The collectible description + * @property image - URI of custom collectible image associated with this tokenId + * @property name - Name associated with this tokenId and contract address + * @property tokenId - The collectible identifier + * @property numberOfSales - Number of sales + * @property backgroundColor - The background color to be displayed with the item + * @property imagePreview - URI of a smaller image associated with this collectible + * @property imageThumbnail - URI of a thumbnail image associated with this collectible + * @property imageOriginal - URI of the original image associated with this collectible + * @property animation - URI of a animation associated with this collectible + * @property animationOriginal - URI of the original animation associated with this collectible + * @property externalLink - External link containing additional information + * @property creator - The collectible owner information object + * @property isCurrentlyOwned - Boolean indicating whether the address/chainId combination where it's currently stored currently owns this collectible + */ +export interface Collectible extends CollectibleMetadata { + tokenId: string; + address: string; + isCurrentlyOwned?: boolean; +} +/** + * @type CollectibleContract + * + * Collectible contract information representation + * @property name - Contract name + * @property logo - Contract logo + * @property address - Contract address + * @property symbol - Contract symbol + * @property description - Contract description + * @property totalSupply - Total supply of collectibles + * @property assetContractType - The collectible type, it could be `semi-fungible` or `non-fungible` + * @property createdDate - Creation date + * @property schemaName - The schema followed by the contract, it could be `ERC721` or `ERC1155` + * @property externalLink - External link containing additional information + */ +export interface CollectibleContract { + name?: string; + logo?: string; + address: string; + symbol?: string; + description?: string; + totalSupply?: string; + assetContractType?: string; + createdDate?: string; + schemaName?: string; + externalLink?: string; +} +/** + * @type CollectibleMetadata + * + * Collectible custom information + * @property name - Collectible custom name + * @property description - The collectible description + * @property numberOfSales - Number of sales + * @property backgroundColor - The background color to be displayed with the item + * @property image - Image custom image URI + * @property imagePreview - URI of a smaller image associated with this collectible + * @property imageThumbnail - URI of a thumbnail image associated with this collectible + * @property imageOriginal - URI of the original image associated with this collectible + * @property animation - URI of a animation associated with this collectible + * @property animationOriginal - URI of the original animation associated with this collectible + * @property externalLink - External link containing additional information + * @property creator - The collectible owner information object + * @property standard - NFT standard name for the collectible, e.g., ERC-721 or ERC-1155 + */ +export interface CollectibleMetadata { + name: string | null; + description: string | null; + image: string | null; + standard: string | null; + favorite?: boolean; + numberOfSales?: number; + backgroundColor?: string; + imagePreview?: string; + imageThumbnail?: string; + imageOriginal?: string; + animation?: string; + animationOriginal?: string; + externalLink?: string; + creator?: ApiCollectibleCreator; + lastSale?: ApiCollectibleLastSale; +} +interface AccountParams { + userAddress: string; + chainId: string; +} +/** + * @type CollectiblesConfig + * + * Collectibles controller configuration + * @property networkType - Network ID as per net_version + * @property selectedAddress - Vault selected address + */ +export interface CollectiblesConfig extends BaseConfig { + networkType: NetworkType; + selectedAddress: string; + chainId: string; + ipfsGateway: string; + openSeaEnabled: boolean; + useIPFSSubdomains: boolean; +} +/** + * @type CollectiblesState + * + * Assets controller state + * @property allCollectibleContracts - Object containing collectibles contract information + * @property allCollectibles - Object containing collectibles per account and network + * @property collectibleContracts - List of collectibles contracts associated with the active vault + * @property collectibles - List of collectibles associated with the active vault + * @property ignoredCollectibles - List of collectibles that should be ignored + */ +export interface CollectiblesState extends BaseState { + allCollectibleContracts: { + [key: string]: { + [key: string]: CollectibleContract[]; + }; + }; + allCollectibles: { + [key: string]: { + [key: string]: Collectible[]; + }; + }; + ignoredCollectibles: Collectible[]; +} +/** + * Controller that stores assets and exposes convenience methods + */ +export declare class CollectiblesController extends BaseController { + private mutex; + private getCollectibleApi; + private getCollectibleContractInformationApi; + /** + * Helper method to update nested state for allCollectibles and allCollectibleContracts. + * + * @param newCollection - the modified piece of state to update in the controller's store + * @param baseStateKey - The root key in the store to update. + * @param passedConfig - An object containing the selectedAddress and chainId that are passed through the auto-detection flow. + * @param passedConfig.userAddress - the address passed through the collectible detection flow to ensure detected assets are stored to the correct account + * @param passedConfig.chainId - the chainId passed through the collectible detection flow to ensure detected assets are stored to the correct account + */ + private updateNestedCollectibleState; + /** + * Request individual collectible information from OpenSea API. + * + * @param contractAddress - Hex address of the collectible contract. + * @param tokenId - The collectible identifier. + * @returns Promise resolving to the current collectible name and image. + */ + private getCollectibleInformationFromApi; + /** + * Request individual collectible information from contracts that follows Metadata Interface. + * + * @param contractAddress - Hex address of the collectible contract. + * @param tokenId - The collectible identifier. + * @returns Promise resolving to the current collectible name and image. + */ + private getCollectibleInformationFromTokenURI; + /** + * Retrieve collectible uri with metadata. TODO Update method to use IPFS. + * + * @param contractAddress - Collectible contract address. + * @param tokenId - Collectible token id. + * @returns Promise resolving collectible uri and token standard. + */ + private getCollectibleURIAndStandard; + /** + * Request individual collectible information (name, image url and description). + * + * @param contractAddress - Hex address of the collectible contract. + * @param tokenId - The collectible identifier. + * @returns Promise resolving to the current collectible name and image. + */ + private getCollectibleInformation; + /** + * Request collectible contract information from OpenSea API. + * + * @param contractAddress - Hex address of the collectible contract. + * @returns Promise resolving to the current collectible name and image. + */ + private getCollectibleContractInformationFromApi; + /** + * Request collectible contract information from the contract itself. + * + * @param contractAddress - Hex address of the collectible contract. + * @returns Promise resolving to the current collectible name and image. + */ + private getCollectibleContractInformationFromContract; + /** + * Request collectible contract information from OpenSea API. + * + * @param contractAddress - Hex address of the collectible contract. + * @returns Promise resolving to the collectible contract name, image and description. + */ + private getCollectibleContractInformation; + /** + * Adds an individual collectible to the stored collectible list. + * + * @param address - Hex address of the collectible contract. + * @param tokenId - The collectible identifier. + * @param collectibleMetadata - Collectible optional information (name, image and description). + * @param collectibleContract - An object containing contract data of the collectible being added. + * @param detection - The chain ID and address of the currently selected network and account at the moment the collectible was detected. + * @returns Promise resolving to the current collectible list. + */ + private addIndividualCollectible; + /** + * Adds a collectible contract to the stored collectible contracts list. + * + * @param address - Hex address of the collectible contract. + * @param detection - The chain ID and address of the currently selected network and account at the moment the collectible was detected. + * @returns Promise resolving to the current collectible contracts list. + */ + private addCollectibleContract; + /** + * Removes an individual collectible from the stored token list and saves it in ignored collectibles list. + * + * @param address - Hex address of the collectible contract. + * @param tokenId - Token identifier of the collectible. + */ + private removeAndIgnoreIndividualCollectible; + /** + * Removes an individual collectible from the stored token list. + * + * @param address - Hex address of the collectible contract. + * @param tokenId - Token identifier of the collectible. + */ + private removeIndividualCollectible; + /** + * Removes a collectible contract to the stored collectible contracts list. + * + * @param address - Hex address of the collectible contract. + * @returns Promise resolving to the current collectible contracts list. + */ + private removeCollectibleContract; + /** + * EventEmitter instance used to listen to specific EIP747 events + */ + hub: EventEmitter; + /** + * Optional API key to use with opensea + */ + openSeaApiKey?: string; + /** + * Name of this controller used during composition + */ + name: string; + private getERC721AssetName; + private getERC721AssetSymbol; + private getERC721TokenURI; + private getERC721OwnerOf; + private getERC1155BalanceOf; + private getERC1155TokenURI; + private onCollectibleAdded?; + /** + * Creates a CollectiblesController instance. + * + * @param options - The controller options. + * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes. + * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. + * @param options.getERC721AssetName - Gets the name of the asset at the given address. + * @param options.getERC721AssetSymbol - Gets the symbol of the asset at the given address. + * @param options.getERC721TokenURI - Gets the URI of the ERC721 token at the given address, with the given ID. + * @param options.getERC721OwnerOf - Get the owner of a ERC-721 collectible. + * @param options.getERC1155BalanceOf - Gets balance of a ERC-1155 collectible. + * @param options.getERC1155TokenURI - Gets the URI of the ERC1155 token at the given address, with the given ID. + * @param options.onCollectibleAdded - Callback that is called when a collectible is added. Currently used pass data + * for tracking the collectible added event. + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor({ onPreferencesStateChange, onNetworkStateChange, getERC721AssetName, getERC721AssetSymbol, getERC721TokenURI, getERC721OwnerOf, getERC1155BalanceOf, getERC1155TokenURI, onCollectibleAdded, }: { + onPreferencesStateChange: (listener: (preferencesState: PreferencesState) => void) => void; + onNetworkStateChange: (listener: (networkState: NetworkState) => void) => void; + getERC721AssetName: AssetsContractController['getERC721AssetName']; + getERC721AssetSymbol: AssetsContractController['getERC721AssetSymbol']; + getERC721TokenURI: AssetsContractController['getERC721TokenURI']; + getERC721OwnerOf: AssetsContractController['getERC721OwnerOf']; + getERC1155BalanceOf: AssetsContractController['getERC1155BalanceOf']; + getERC1155TokenURI: AssetsContractController['getERC1155TokenURI']; + onCollectibleAdded?: (data: { + address: string; + symbol: string | undefined; + tokenId: string; + standard: string | null; + source: string; + }) => void; + }, config?: Partial, state?: Partial); + /** + * Sets an OpenSea API key to retrieve collectible information. + * + * @param openSeaApiKey - OpenSea API key. + */ + setApiKey(openSeaApiKey: string): void; + /** + * Checks the ownership of a ERC-721 or ERC-1155 collectible for a given address. + * + * @param ownerAddress - User public address. + * @param collectibleAddress - Collectible contract address. + * @param collectibleId - Collectible token ID. + * @returns Promise resolving the collectible ownership. + */ + isCollectibleOwner(ownerAddress: string, collectibleAddress: string, collectibleId: string): Promise; + /** + * Verifies currently selected address owns entered collectible address/tokenId combo and + * adds the collectible and respective collectible contract to the stored collectible and collectible contracts lists. + * + * @param address - Hex address of the collectible contract. + * @param tokenId - The collectible identifier. + */ + addCollectibleVerifyOwnership(address: string, tokenId: string): Promise; + /** + * Adds a collectible and respective collectible contract to the stored collectible and collectible contracts lists. + * + * @param address - Hex address of the collectible contract. + * @param tokenId - The collectible identifier. + * @param collectibleMetadata - Collectible optional metadata. + * @param detection - The chain ID and address of the currently selected network and account at the moment the collectible was detected. + * @returns Promise resolving to the current collectible list. + */ + addCollectible(address: string, tokenId: string, collectibleMetadata?: CollectibleMetadata, detection?: AccountParams): Promise; + /** + * Removes a collectible from the stored token list. + * + * @param address - Hex address of the collectible contract. + * @param tokenId - Token identifier of the collectible. + */ + removeCollectible(address: string, tokenId: string): void; + /** + * Removes a collectible from the stored token list and saves it in ignored collectibles list. + * + * @param address - Hex address of the collectible contract. + * @param tokenId - Token identifier of the collectible. + */ + removeAndIgnoreCollectible(address: string, tokenId: string): void; + /** + * Removes all collectibles from the ignored list. + */ + clearIgnoredCollectibles(): void; + /** + * Checks whether input collectible is still owned by the user + * And updates the isCurrentlyOwned value on the collectible object accordingly. + * + * @param collectible - The collectible object to check and update. + * @param batch - A boolean indicating whether this method is being called as part of a batch or single update. + * @param accountParams - The userAddress and chainId to check ownership against + * @param accountParams.userAddress - the address passed through the confirmed transaction flow to ensure detected assets are stored to the correct account + * @param accountParams.chainId - the chainId passed through the confirmed transaction flow to ensure detected assets are stored to the correct account + * @returns the collectible with the updated isCurrentlyOwned value + */ + checkAndUpdateSingleCollectibleOwnershipStatus(collectible: Collectible, batch: boolean, { userAddress, chainId }?: AccountParams | undefined): Promise; + /** + * Checks whether Collectibles associated with current selectedAddress/chainId combination are still owned by the user + * And updates the isCurrentlyOwned value on each accordingly. + */ + checkAndUpdateAllCollectiblesOwnershipStatus(): Promise; + /** + * Update collectible favorite status. + * + * @param address - Hex address of the collectible contract. + * @param tokenId - Hex address of the collectible contract. + * @param favorite - Collectible new favorite status. + */ + updateCollectibleFavoriteStatus(address: string, tokenId: string, favorite: boolean): void; +} +export default CollectiblesController; diff --git a/dist/assets/CollectiblesController.js b/dist/assets/CollectiblesController.js new file mode 100644 index 0000000000..a687b2fa39 --- /dev/null +++ b/dist/assets/CollectiblesController.js @@ -0,0 +1,759 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CollectiblesController = void 0; +const events_1 = require("events"); +const ethereumjs_util_1 = require("ethereumjs-util"); +const async_mutex_1 = require("async-mutex"); +const BaseController_1 = require("../BaseController"); +const util_1 = require("../util"); +const constants_1 = require("../constants"); +const assetsUtil_1 = require("./assetsUtil"); +const ALL_COLLECTIBLES_STATE_KEY = 'allCollectibles'; +const ALL_COLLECTIBLES_CONTRACTS_STATE_KEY = 'allCollectibleContracts'; +/** + * Controller that stores assets and exposes convenience methods + */ +class CollectiblesController extends BaseController_1.BaseController { + /** + * Creates a CollectiblesController instance. + * + * @param options - The controller options. + * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes. + * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. + * @param options.getERC721AssetName - Gets the name of the asset at the given address. + * @param options.getERC721AssetSymbol - Gets the symbol of the asset at the given address. + * @param options.getERC721TokenURI - Gets the URI of the ERC721 token at the given address, with the given ID. + * @param options.getERC721OwnerOf - Get the owner of a ERC-721 collectible. + * @param options.getERC1155BalanceOf - Gets balance of a ERC-1155 collectible. + * @param options.getERC1155TokenURI - Gets the URI of the ERC1155 token at the given address, with the given ID. + * @param options.onCollectibleAdded - Callback that is called when a collectible is added. Currently used pass data + * for tracking the collectible added event. + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor({ onPreferencesStateChange, onNetworkStateChange, getERC721AssetName, getERC721AssetSymbol, getERC721TokenURI, getERC721OwnerOf, getERC1155BalanceOf, getERC1155TokenURI, onCollectibleAdded, }, config, state) { + super(config, state); + this.mutex = new async_mutex_1.Mutex(); + /** + * EventEmitter instance used to listen to specific EIP747 events + */ + this.hub = new events_1.EventEmitter(); + /** + * Name of this controller used during composition + */ + this.name = 'CollectiblesController'; + this.defaultConfig = { + networkType: constants_1.MAINNET, + selectedAddress: '', + chainId: '', + ipfsGateway: constants_1.IPFS_DEFAULT_GATEWAY_URL, + openSeaEnabled: false, + useIPFSSubdomains: true, + }; + this.defaultState = { + allCollectibleContracts: {}, + allCollectibles: {}, + ignoredCollectibles: [], + }; + this.initialize(); + this.getERC721AssetName = getERC721AssetName; + this.getERC721AssetSymbol = getERC721AssetSymbol; + this.getERC721TokenURI = getERC721TokenURI; + this.getERC721OwnerOf = getERC721OwnerOf; + this.getERC1155BalanceOf = getERC1155BalanceOf; + this.getERC1155TokenURI = getERC1155TokenURI; + this.onCollectibleAdded = onCollectibleAdded; + onPreferencesStateChange(({ selectedAddress, ipfsGateway, openSeaEnabled }) => { + this.configure({ selectedAddress, ipfsGateway, openSeaEnabled }); + }); + onNetworkStateChange(({ provider }) => { + const { chainId } = provider; + this.configure({ chainId }); + }); + } + getCollectibleApi({ contractAddress, tokenId, useProxy, }) { + const { chainId } = this.config; + if (chainId === constants_1.RINKEBY_CHAIN_ID) { + return `${constants_1.OPENSEA_TEST_API_URL}/asset/${contractAddress}/${tokenId}`; + } + return useProxy + ? `${constants_1.OPENSEA_PROXY_URL}/asset/${contractAddress}/${tokenId}` + : `${constants_1.OPENSEA_API_URL}/asset/${contractAddress}/${tokenId}`; + } + getCollectibleContractInformationApi({ contractAddress, useProxy, }) { + const { chainId } = this.config; + if (chainId === constants_1.RINKEBY_CHAIN_ID) { + return `${constants_1.OPENSEA_TEST_API_URL}/asset_contract/${contractAddress}`; + } + return useProxy + ? `${constants_1.OPENSEA_PROXY_URL}/asset_contract/${contractAddress}` + : `${constants_1.OPENSEA_API_URL}/asset_contract/${contractAddress}`; + } + /** + * Helper method to update nested state for allCollectibles and allCollectibleContracts. + * + * @param newCollection - the modified piece of state to update in the controller's store + * @param baseStateKey - The root key in the store to update. + * @param passedConfig - An object containing the selectedAddress and chainId that are passed through the auto-detection flow. + * @param passedConfig.userAddress - the address passed through the collectible detection flow to ensure detected assets are stored to the correct account + * @param passedConfig.chainId - the chainId passed through the collectible detection flow to ensure detected assets are stored to the correct account + */ + updateNestedCollectibleState(newCollection, baseStateKey, { userAddress, chainId } = { + userAddress: this.config.selectedAddress, + chainId: this.config.chainId, + }) { + const { [baseStateKey]: oldState } = this.state; + const addressState = oldState[userAddress]; + const newAddressState = Object.assign(Object.assign({}, addressState), { [chainId]: newCollection }); + const newState = Object.assign(Object.assign({}, oldState), { [userAddress]: newAddressState }); + this.update({ + [baseStateKey]: newState, + }); + } + /** + * Request individual collectible information from OpenSea API. + * + * @param contractAddress - Hex address of the collectible contract. + * @param tokenId - The collectible identifier. + * @returns Promise resolving to the current collectible name and image. + */ + getCollectibleInformationFromApi(contractAddress, tokenId) { + return __awaiter(this, void 0, void 0, function* () { + // Attempt to fetch the data with the proxy + let collectibleInformation = yield (0, util_1.fetchWithErrorHandling)({ + url: this.getCollectibleApi({ + contractAddress, + tokenId, + useProxy: true, + }), + }); + // if an openSeaApiKey is set we should attempt to refetch calling directly to OpenSea + if (!collectibleInformation && this.openSeaApiKey) { + collectibleInformation = yield (0, util_1.fetchWithErrorHandling)({ + url: this.getCollectibleApi({ + contractAddress, + tokenId, + useProxy: false, + }), + options: { + headers: { 'X-API-KEY': this.openSeaApiKey }, + }, + // catch 403 errors (in case API key is down we don't want to blow up) + errorCodesToCatch: [403], + }); + } + // if we were still unable to fetch the data we return out the default/null of `CollectibleMetadata` + if (!collectibleInformation) { + return { + name: null, + description: null, + image: null, + standard: null, + }; + } + // if we've reached this point, we have successfully fetched some data for collectibleInformation + // now we reconfigure the data to conform to the `CollectibleMetadata` type for storage. + const { num_sales, background_color, image_url, image_preview_url, image_thumbnail_url, image_original_url, animation_url, animation_original_url, name, description, external_link, creator, last_sale, asset_contract: { schema_name }, } = collectibleInformation; + /* istanbul ignore next */ + const collectibleMetadata = Object.assign({}, { name: name || null }, { description: description || null }, { image: image_url || null }, creator && { creator }, num_sales && { numberOfSales: num_sales }, background_color && { backgroundColor: background_color }, image_preview_url && { imagePreview: image_preview_url }, image_thumbnail_url && { imageThumbnail: image_thumbnail_url }, image_original_url && { imageOriginal: image_original_url }, animation_url && { animation: animation_url }, animation_original_url && { + animationOriginal: animation_original_url, + }, external_link && { externalLink: external_link }, last_sale && { lastSale: last_sale }, schema_name && { standard: schema_name }); + return collectibleMetadata; + }); + } + /** + * Request individual collectible information from contracts that follows Metadata Interface. + * + * @param contractAddress - Hex address of the collectible contract. + * @param tokenId - The collectible identifier. + * @returns Promise resolving to the current collectible name and image. + */ + getCollectibleInformationFromTokenURI(contractAddress, tokenId) { + return __awaiter(this, void 0, void 0, function* () { + const { ipfsGateway, useIPFSSubdomains } = this.config; + const result = yield this.getCollectibleURIAndStandard(contractAddress, tokenId); + let tokenURI = result[0]; + const standard = result[1]; + if (tokenURI.startsWith('ipfs://')) { + tokenURI = (0, util_1.getFormattedIpfsUrl)(ipfsGateway, tokenURI, useIPFSSubdomains); + } + try { + const object = yield (0, util_1.handleFetch)(tokenURI); + // TODO: Check image_url existence. This is not part of EIP721 nor EIP1155 + const image = Object.prototype.hasOwnProperty.call(object, 'image') + ? 'image' + : /* istanbul ignore next */ 'image_url'; + return { + image: object[image], + name: object.name, + description: object.description, + standard, + favorite: false, + }; + } + catch (_a) { + return { + image: null, + name: null, + description: null, + standard: standard || null, + favorite: false, + }; + } + }); + } + /** + * Retrieve collectible uri with metadata. TODO Update method to use IPFS. + * + * @param contractAddress - Collectible contract address. + * @param tokenId - Collectible token id. + * @returns Promise resolving collectible uri and token standard. + */ + getCollectibleURIAndStandard(contractAddress, tokenId) { + return __awaiter(this, void 0, void 0, function* () { + // try ERC721 uri + try { + const uri = yield this.getERC721TokenURI(contractAddress, tokenId); + return [uri, constants_1.ERC721]; + } + catch (_a) { + // Ignore error + } + // try ERC1155 uri + try { + const tokenURI = yield this.getERC1155TokenURI(contractAddress, tokenId); + /** + * According to EIP1155 the URI value allows for ID substitution + * in case the string `{id}` exists. + * https://eips.ethereum.org/EIPS/eip-1155#metadata + */ + if (!tokenURI.includes('{id}')) { + return [tokenURI, constants_1.ERC1155]; + } + const hexTokenId = (0, ethereumjs_util_1.stripHexPrefix)((0, util_1.BNToHex)(new ethereumjs_util_1.BN(tokenId))) + .padStart(64, '0') + .toLowerCase(); + return [tokenURI.replace('{id}', hexTokenId), constants_1.ERC1155]; + } + catch (_b) { + // Ignore error + } + return ['', '']; + }); + } + /** + * Request individual collectible information (name, image url and description). + * + * @param contractAddress - Hex address of the collectible contract. + * @param tokenId - The collectible identifier. + * @returns Promise resolving to the current collectible name and image. + */ + getCollectibleInformation(contractAddress, tokenId) { + var _a, _b, _c, _d, _e, _f, _g, _h; + return __awaiter(this, void 0, void 0, function* () { + const blockchainMetadata = yield (0, util_1.safelyExecute)(() => __awaiter(this, void 0, void 0, function* () { + return yield this.getCollectibleInformationFromTokenURI(contractAddress, tokenId); + })); + let openSeaMetadata; + if (this.config.openSeaEnabled) { + openSeaMetadata = yield (0, util_1.safelyExecute)(() => __awaiter(this, void 0, void 0, function* () { + return yield this.getCollectibleInformationFromApi(contractAddress, tokenId); + })); + } + return Object.assign(Object.assign({}, openSeaMetadata), { name: (_b = (_a = blockchainMetadata.name) !== null && _a !== void 0 ? _a : openSeaMetadata === null || openSeaMetadata === void 0 ? void 0 : openSeaMetadata.name) !== null && _b !== void 0 ? _b : null, description: (_d = (_c = blockchainMetadata.description) !== null && _c !== void 0 ? _c : openSeaMetadata === null || openSeaMetadata === void 0 ? void 0 : openSeaMetadata.description) !== null && _d !== void 0 ? _d : null, image: (_f = (_e = blockchainMetadata.image) !== null && _e !== void 0 ? _e : openSeaMetadata === null || openSeaMetadata === void 0 ? void 0 : openSeaMetadata.image) !== null && _f !== void 0 ? _f : null, standard: (_h = (_g = blockchainMetadata.standard) !== null && _g !== void 0 ? _g : openSeaMetadata === null || openSeaMetadata === void 0 ? void 0 : openSeaMetadata.standard) !== null && _h !== void 0 ? _h : null }); + }); + } + /** + * Request collectible contract information from OpenSea API. + * + * @param contractAddress - Hex address of the collectible contract. + * @returns Promise resolving to the current collectible name and image. + */ + getCollectibleContractInformationFromApi(contractAddress) { + return __awaiter(this, void 0, void 0, function* () { + /* istanbul ignore if */ + let apiCollectibleContractObject = yield (0, util_1.fetchWithErrorHandling)({ + url: this.getCollectibleContractInformationApi({ + contractAddress, + useProxy: true, + }), + }); + // if we successfully fetched return the fetched data immediately + if (apiCollectibleContractObject) { + return apiCollectibleContractObject; + } + // if we were unsuccessful in fetching from the API and an OpenSea API key is present + // attempt to refetch directly against the OpenSea API and if successful return the data immediately + if (this.openSeaApiKey) { + apiCollectibleContractObject = yield (0, util_1.fetchWithErrorHandling)({ + url: this.getCollectibleContractInformationApi({ + contractAddress, + useProxy: false, + }), + options: { + headers: { 'X-API-KEY': this.openSeaApiKey }, + }, + // catch 403 errors (in case API key is down we don't want to blow up) + errorCodesToCatch: [403], + }); + if (apiCollectibleContractObject) { + return apiCollectibleContractObject; + } + } + // If we've reached this point we were unable to fetch data from either the proxy or opensea so we return + // the default/null of ApiCollectibleContract + return { + address: contractAddress, + asset_contract_type: null, + created_date: null, + schema_name: null, + symbol: null, + total_supply: null, + description: null, + external_link: null, + collection: { + name: null, + image_url: null, + }, + }; + }); + } + /** + * Request collectible contract information from the contract itself. + * + * @param contractAddress - Hex address of the collectible contract. + * @returns Promise resolving to the current collectible name and image. + */ + getCollectibleContractInformationFromContract(contractAddress) { + return __awaiter(this, void 0, void 0, function* () { + const name = yield this.getERC721AssetName(contractAddress); + const symbol = yield this.getERC721AssetSymbol(contractAddress); + return { + collection: { name }, + symbol, + address: contractAddress, + }; + }); + } + /** + * Request collectible contract information from OpenSea API. + * + * @param contractAddress - Hex address of the collectible contract. + * @returns Promise resolving to the collectible contract name, image and description. + */ + getCollectibleContractInformation(contractAddress) { + return __awaiter(this, void 0, void 0, function* () { + const blockchainContractData = yield (0, util_1.safelyExecute)(() => __awaiter(this, void 0, void 0, function* () { + return yield this.getCollectibleContractInformationFromContract(contractAddress); + })); + let openSeaContractData; + if (this.config.openSeaEnabled) { + openSeaContractData = yield (0, util_1.safelyExecute)(() => __awaiter(this, void 0, void 0, function* () { + return yield this.getCollectibleContractInformationFromApi(contractAddress); + })); + } + if (blockchainContractData || openSeaContractData) { + return Object.assign(Object.assign(Object.assign({}, openSeaContractData), blockchainContractData), { collection: Object.assign(Object.assign({ image_url: null }, openSeaContractData === null || openSeaContractData === void 0 ? void 0 : openSeaContractData.collection), blockchainContractData === null || blockchainContractData === void 0 ? void 0 : blockchainContractData.collection) }); + } + /* istanbul ignore next */ + return { + address: contractAddress, + asset_contract_type: null, + created_date: null, + schema_name: null, + symbol: null, + total_supply: null, + description: null, + external_link: null, + collection: { name: null, image_url: null }, + }; + }); + } + /** + * Adds an individual collectible to the stored collectible list. + * + * @param address - Hex address of the collectible contract. + * @param tokenId - The collectible identifier. + * @param collectibleMetadata - Collectible optional information (name, image and description). + * @param collectibleContract - An object containing contract data of the collectible being added. + * @param detection - The chain ID and address of the currently selected network and account at the moment the collectible was detected. + * @returns Promise resolving to the current collectible list. + */ + addIndividualCollectible(address, tokenId, collectibleMetadata, collectibleContract, detection) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + // TODO: Remove unused return + const releaseLock = yield this.mutex.acquire(); + try { + address = (0, util_1.toChecksumHexAddress)(address); + const { allCollectibles } = this.state; + let chainId, selectedAddress; + if (detection) { + chainId = detection.chainId; + selectedAddress = detection.userAddress; + } + else { + chainId = this.config.chainId; + selectedAddress = this.config.selectedAddress; + } + const collectibles = ((_a = allCollectibles[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; + const existingEntry = collectibles.find((collectible) => collectible.address.toLowerCase() === address.toLowerCase() && + collectible.tokenId === tokenId); + if (existingEntry) { + const differentMetadata = (0, assetsUtil_1.compareCollectiblesMetadata)(collectibleMetadata, existingEntry); + if (differentMetadata) { + // TODO: Switch to indexToUpdate + const indexToRemove = collectibles.findIndex((collectible) => collectible.address.toLowerCase() === address.toLowerCase() && + collectible.tokenId === tokenId); + /* istanbul ignore next */ + if (indexToRemove !== -1) { + collectibles.splice(indexToRemove, 1); + } + } + else { + return collectibles; + } + } + const newEntry = Object.assign({ address, + tokenId, favorite: (existingEntry === null || existingEntry === void 0 ? void 0 : existingEntry.favorite) || false, isCurrentlyOwned: true }, collectibleMetadata); + const newCollectibles = [...collectibles, newEntry]; + this.updateNestedCollectibleState(newCollectibles, ALL_COLLECTIBLES_STATE_KEY, { chainId, userAddress: selectedAddress }); + if (this.onCollectibleAdded) { + this.onCollectibleAdded({ + address, + symbol: collectibleContract.symbol, + tokenId: tokenId.toString(), + standard: collectibleMetadata.standard, + source: detection ? 'detected' : 'custom', + }); + } + return newCollectibles; + } + finally { + releaseLock(); + } + }); + } + /** + * Adds a collectible contract to the stored collectible contracts list. + * + * @param address - Hex address of the collectible contract. + * @param detection - The chain ID and address of the currently selected network and account at the moment the collectible was detected. + * @returns Promise resolving to the current collectible contracts list. + */ + addCollectibleContract(address, detection) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const releaseLock = yield this.mutex.acquire(); + try { + address = (0, util_1.toChecksumHexAddress)(address); + const { allCollectibleContracts } = this.state; + let chainId, selectedAddress; + if (detection) { + chainId = detection.chainId; + selectedAddress = detection.userAddress; + } + else { + chainId = this.config.chainId; + selectedAddress = this.config.selectedAddress; + } + const collectibleContracts = ((_a = allCollectibleContracts[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; + const existingEntry = collectibleContracts.find((collectibleContract) => collectibleContract.address.toLowerCase() === address.toLowerCase()); + if (existingEntry) { + return collectibleContracts; + } + const contractInformation = yield this.getCollectibleContractInformation(address); + const { asset_contract_type, created_date, schema_name, symbol, total_supply, description, external_link, collection: { name, image_url }, } = contractInformation; + // If being auto-detected opensea information is expected + // Otherwise at least name from the contract is needed + if ((detection && !name) || + Object.keys(contractInformation).length === 0) { + return collectibleContracts; + } + /* istanbul ignore next */ + const newEntry = Object.assign({}, { address }, description && { description }, name && { name }, image_url && { logo: image_url }, symbol && { symbol }, total_supply !== null && + typeof total_supply !== 'undefined' && { totalSupply: total_supply }, asset_contract_type && { assetContractType: asset_contract_type }, created_date && { createdDate: created_date }, schema_name && { schemaName: schema_name }, external_link && { externalLink: external_link }); + const newCollectibleContracts = [...collectibleContracts, newEntry]; + this.updateNestedCollectibleState(newCollectibleContracts, ALL_COLLECTIBLES_CONTRACTS_STATE_KEY, { chainId, userAddress: selectedAddress }); + return newCollectibleContracts; + } + finally { + releaseLock(); + } + }); + } + /** + * Removes an individual collectible from the stored token list and saves it in ignored collectibles list. + * + * @param address - Hex address of the collectible contract. + * @param tokenId - Token identifier of the collectible. + */ + removeAndIgnoreIndividualCollectible(address, tokenId) { + var _a; + address = (0, util_1.toChecksumHexAddress)(address); + const { allCollectibles, ignoredCollectibles } = this.state; + const { chainId, selectedAddress } = this.config; + const newIgnoredCollectibles = [...ignoredCollectibles]; + const collectibles = ((_a = allCollectibles[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; + const newCollectibles = collectibles.filter((collectible) => { + if (collectible.address.toLowerCase() === address.toLowerCase() && + collectible.tokenId === tokenId) { + const alreadyIgnored = newIgnoredCollectibles.find((c) => c.address === address && c.tokenId === tokenId); + !alreadyIgnored && newIgnoredCollectibles.push(collectible); + return false; + } + return true; + }); + this.updateNestedCollectibleState(newCollectibles, ALL_COLLECTIBLES_STATE_KEY); + this.update({ + ignoredCollectibles: newIgnoredCollectibles, + }); + } + /** + * Removes an individual collectible from the stored token list. + * + * @param address - Hex address of the collectible contract. + * @param tokenId - Token identifier of the collectible. + */ + removeIndividualCollectible(address, tokenId) { + var _a; + address = (0, util_1.toChecksumHexAddress)(address); + const { allCollectibles } = this.state; + const { chainId, selectedAddress } = this.config; + const collectibles = ((_a = allCollectibles[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; + const newCollectibles = collectibles.filter((collectible) => !(collectible.address.toLowerCase() === address.toLowerCase() && + collectible.tokenId === tokenId)); + this.updateNestedCollectibleState(newCollectibles, ALL_COLLECTIBLES_STATE_KEY); + } + /** + * Removes a collectible contract to the stored collectible contracts list. + * + * @param address - Hex address of the collectible contract. + * @returns Promise resolving to the current collectible contracts list. + */ + removeCollectibleContract(address) { + var _a; + address = (0, util_1.toChecksumHexAddress)(address); + const { allCollectibleContracts } = this.state; + const { chainId, selectedAddress } = this.config; + const collectibleContracts = ((_a = allCollectibleContracts[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; + const newCollectibleContracts = collectibleContracts.filter((collectibleContract) => !(collectibleContract.address.toLowerCase() === address.toLowerCase())); + this.updateNestedCollectibleState(newCollectibleContracts, ALL_COLLECTIBLES_CONTRACTS_STATE_KEY); + return newCollectibleContracts; + } + /** + * Sets an OpenSea API key to retrieve collectible information. + * + * @param openSeaApiKey - OpenSea API key. + */ + setApiKey(openSeaApiKey) { + this.openSeaApiKey = openSeaApiKey; + } + /** + * Checks the ownership of a ERC-721 or ERC-1155 collectible for a given address. + * + * @param ownerAddress - User public address. + * @param collectibleAddress - Collectible contract address. + * @param collectibleId - Collectible token ID. + * @returns Promise resolving the collectible ownership. + */ + isCollectibleOwner(ownerAddress, collectibleAddress, collectibleId) { + return __awaiter(this, void 0, void 0, function* () { + // Checks the ownership for ERC-721. + try { + const owner = yield this.getERC721OwnerOf(collectibleAddress, collectibleId); + return ownerAddress.toLowerCase() === owner.toLowerCase(); + // eslint-disable-next-line no-empty + } + catch (_a) { + // Ignore ERC-721 contract error + } + // Checks the ownership for ERC-1155. + try { + const balance = yield this.getERC1155BalanceOf(ownerAddress, collectibleAddress, collectibleId); + return balance > 0; + // eslint-disable-next-line no-empty + } + catch (_b) { + // Ignore ERC-1155 contract error + } + throw new Error('Unable to verify ownership. Probably because the standard is not supported or the chain is incorrect.'); + }); + } + /** + * Verifies currently selected address owns entered collectible address/tokenId combo and + * adds the collectible and respective collectible contract to the stored collectible and collectible contracts lists. + * + * @param address - Hex address of the collectible contract. + * @param tokenId - The collectible identifier. + */ + addCollectibleVerifyOwnership(address, tokenId) { + return __awaiter(this, void 0, void 0, function* () { + const { selectedAddress } = this.config; + if (!(yield this.isCollectibleOwner(selectedAddress, address, tokenId))) { + throw new Error('This collectible is not owned by the user'); + } + yield this.addCollectible(address, tokenId); + }); + } + /** + * Adds a collectible and respective collectible contract to the stored collectible and collectible contracts lists. + * + * @param address - Hex address of the collectible contract. + * @param tokenId - The collectible identifier. + * @param collectibleMetadata - Collectible optional metadata. + * @param detection - The chain ID and address of the currently selected network and account at the moment the collectible was detected. + * @returns Promise resolving to the current collectible list. + */ + addCollectible(address, tokenId, collectibleMetadata, detection) { + return __awaiter(this, void 0, void 0, function* () { + address = (0, util_1.toChecksumHexAddress)(address); + const newCollectibleContracts = yield this.addCollectibleContract(address, detection); + collectibleMetadata = + collectibleMetadata || + (yield this.getCollectibleInformation(address, tokenId)); + // If collectible contract was not added, do not add individual collectible + const collectibleContract = newCollectibleContracts.find((contract) => contract.address.toLowerCase() === address.toLowerCase()); + // If collectible contract information, add individual collectible + if (collectibleContract) { + yield this.addIndividualCollectible(address, tokenId, collectibleMetadata, collectibleContract, detection); + } + }); + } + /** + * Removes a collectible from the stored token list. + * + * @param address - Hex address of the collectible contract. + * @param tokenId - Token identifier of the collectible. + */ + removeCollectible(address, tokenId) { + var _a; + address = (0, util_1.toChecksumHexAddress)(address); + this.removeIndividualCollectible(address, tokenId); + const { allCollectibles } = this.state; + const { chainId, selectedAddress } = this.config; + const collectibles = ((_a = allCollectibles[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; + const remainingCollectible = collectibles.find((collectible) => collectible.address.toLowerCase() === address.toLowerCase()); + if (!remainingCollectible) { + this.removeCollectibleContract(address); + } + } + /** + * Removes a collectible from the stored token list and saves it in ignored collectibles list. + * + * @param address - Hex address of the collectible contract. + * @param tokenId - Token identifier of the collectible. + */ + removeAndIgnoreCollectible(address, tokenId) { + var _a; + address = (0, util_1.toChecksumHexAddress)(address); + this.removeAndIgnoreIndividualCollectible(address, tokenId); + const { allCollectibles } = this.state; + const { chainId, selectedAddress } = this.config; + const collectibles = ((_a = allCollectibles[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; + const remainingCollectible = collectibles.find((collectible) => collectible.address.toLowerCase() === address.toLowerCase()); + if (!remainingCollectible) { + this.removeCollectibleContract(address); + } + } + /** + * Removes all collectibles from the ignored list. + */ + clearIgnoredCollectibles() { + this.update({ ignoredCollectibles: [] }); + } + /** + * Checks whether input collectible is still owned by the user + * And updates the isCurrentlyOwned value on the collectible object accordingly. + * + * @param collectible - The collectible object to check and update. + * @param batch - A boolean indicating whether this method is being called as part of a batch or single update. + * @param accountParams - The userAddress and chainId to check ownership against + * @param accountParams.userAddress - the address passed through the confirmed transaction flow to ensure detected assets are stored to the correct account + * @param accountParams.chainId - the chainId passed through the confirmed transaction flow to ensure detected assets are stored to the correct account + * @returns the collectible with the updated isCurrentlyOwned value + */ + checkAndUpdateSingleCollectibleOwnershipStatus(collectible, batch, { userAddress, chainId } = { + userAddress: this.config.selectedAddress, + chainId: this.config.chainId, + }) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const { address, tokenId } = collectible; + let isOwned = collectible.isCurrentlyOwned; + try { + isOwned = yield this.isCollectibleOwner(userAddress, address, tokenId); + } + catch (error) { + if (!(error instanceof Error && + error.message.includes('Unable to verify ownership'))) { + throw error; + } + } + collectible.isCurrentlyOwned = isOwned; + if (batch === true) { + return collectible; + } + // if this is not part of a batched update we update this one collectible in state + const { allCollectibles } = this.state; + const collectibles = ((_a = allCollectibles[userAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; + const collectibleToUpdate = collectibles.find((item) => item.tokenId === tokenId && + item.address.toLowerCase() === address.toLowerCase()); + if (collectibleToUpdate) { + collectibleToUpdate.isCurrentlyOwned = isOwned; + this.updateNestedCollectibleState(collectibles, ALL_COLLECTIBLES_STATE_KEY, { userAddress, chainId }); + } + return collectible; + }); + } + /** + * Checks whether Collectibles associated with current selectedAddress/chainId combination are still owned by the user + * And updates the isCurrentlyOwned value on each accordingly. + */ + checkAndUpdateAllCollectiblesOwnershipStatus() { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const { allCollectibles } = this.state; + const { chainId, selectedAddress } = this.config; + const collectibles = ((_a = allCollectibles[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; + const updatedCollectibles = yield Promise.all(collectibles.map((collectible) => __awaiter(this, void 0, void 0, function* () { + var _b; + return ((_b = (yield this.checkAndUpdateSingleCollectibleOwnershipStatus(collectible, true))) !== null && _b !== void 0 ? _b : collectible); + }))); + this.updateNestedCollectibleState(updatedCollectibles, ALL_COLLECTIBLES_STATE_KEY); + }); + } + /** + * Update collectible favorite status. + * + * @param address - Hex address of the collectible contract. + * @param tokenId - Hex address of the collectible contract. + * @param favorite - Collectible new favorite status. + */ + updateCollectibleFavoriteStatus(address, tokenId, favorite) { + var _a; + const { allCollectibles } = this.state; + const { chainId, selectedAddress } = this.config; + const collectibles = ((_a = allCollectibles[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; + const index = collectibles.findIndex((collectible) => collectible.address === address && collectible.tokenId === tokenId); + if (index === -1) { + return; + } + const updatedCollectible = Object.assign(Object.assign({}, collectibles[index]), { favorite }); + // Update Collectibles array + collectibles[index] = updatedCollectible; + this.updateNestedCollectibleState(collectibles, ALL_COLLECTIBLES_STATE_KEY); + } +} +exports.CollectiblesController = CollectiblesController; +exports.default = CollectiblesController; +//# sourceMappingURL=CollectiblesController.js.map \ No newline at end of file diff --git a/dist/assets/CollectiblesController.js.map b/dist/assets/CollectiblesController.js.map new file mode 100644 index 0000000000..e91f3ffce3 --- /dev/null +++ b/dist/assets/CollectiblesController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CollectiblesController.js","sourceRoot":"","sources":["../../src/assets/CollectiblesController.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from 'events';\nimport { BN, stripHexPrefix } from 'ethereumjs-util';\nimport { Mutex } from 'async-mutex';\n\nimport { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport type { PreferencesState } from '../user/PreferencesController';\nimport type { NetworkState, NetworkType } from '../network/NetworkController';\nimport {\n safelyExecute,\n handleFetch,\n toChecksumHexAddress,\n BNToHex,\n getFormattedIpfsUrl,\n fetchWithErrorHandling,\n} from '../util';\nimport {\n MAINNET,\n RINKEBY_CHAIN_ID,\n IPFS_DEFAULT_GATEWAY_URL,\n ERC721,\n ERC1155,\n OPENSEA_API_URL,\n OPENSEA_PROXY_URL,\n OPENSEA_TEST_API_URL,\n} from '../constants';\n\nimport type {\n ApiCollectible,\n ApiCollectibleCreator,\n ApiCollectibleContract,\n ApiCollectibleLastSale,\n} from './CollectibleDetectionController';\nimport type { AssetsContractController } from './AssetsContractController';\nimport { compareCollectiblesMetadata } from './assetsUtil';\n\n/**\n * @type Collectible\n *\n * Collectible representation\n * @property address - Hex address of a ERC721 contract\n * @property description - The collectible description\n * @property image - URI of custom collectible image associated with this tokenId\n * @property name - Name associated with this tokenId and contract address\n * @property tokenId - The collectible identifier\n * @property numberOfSales - Number of sales\n * @property backgroundColor - The background color to be displayed with the item\n * @property imagePreview - URI of a smaller image associated with this collectible\n * @property imageThumbnail - URI of a thumbnail image associated with this collectible\n * @property imageOriginal - URI of the original image associated with this collectible\n * @property animation - URI of a animation associated with this collectible\n * @property animationOriginal - URI of the original animation associated with this collectible\n * @property externalLink - External link containing additional information\n * @property creator - The collectible owner information object\n * @property isCurrentlyOwned - Boolean indicating whether the address/chainId combination where it's currently stored currently owns this collectible\n */\nexport interface Collectible extends CollectibleMetadata {\n tokenId: string;\n address: string;\n isCurrentlyOwned?: boolean;\n}\n\n/**\n * @type CollectibleContract\n *\n * Collectible contract information representation\n * @property name - Contract name\n * @property logo - Contract logo\n * @property address - Contract address\n * @property symbol - Contract symbol\n * @property description - Contract description\n * @property totalSupply - Total supply of collectibles\n * @property assetContractType - The collectible type, it could be `semi-fungible` or `non-fungible`\n * @property createdDate - Creation date\n * @property schemaName - The schema followed by the contract, it could be `ERC721` or `ERC1155`\n * @property externalLink - External link containing additional information\n */\nexport interface CollectibleContract {\n name?: string;\n logo?: string;\n address: string;\n symbol?: string;\n description?: string;\n totalSupply?: string;\n assetContractType?: string;\n createdDate?: string;\n schemaName?: string;\n externalLink?: string;\n}\n\n/**\n * @type CollectibleMetadata\n *\n * Collectible custom information\n * @property name - Collectible custom name\n * @property description - The collectible description\n * @property numberOfSales - Number of sales\n * @property backgroundColor - The background color to be displayed with the item\n * @property image - Image custom image URI\n * @property imagePreview - URI of a smaller image associated with this collectible\n * @property imageThumbnail - URI of a thumbnail image associated with this collectible\n * @property imageOriginal - URI of the original image associated with this collectible\n * @property animation - URI of a animation associated with this collectible\n * @property animationOriginal - URI of the original animation associated with this collectible\n * @property externalLink - External link containing additional information\n * @property creator - The collectible owner information object\n * @property standard - NFT standard name for the collectible, e.g., ERC-721 or ERC-1155\n */\nexport interface CollectibleMetadata {\n name: string | null;\n description: string | null;\n image: string | null;\n standard: string | null;\n favorite?: boolean;\n numberOfSales?: number;\n backgroundColor?: string;\n imagePreview?: string;\n imageThumbnail?: string;\n imageOriginal?: string;\n animation?: string;\n animationOriginal?: string;\n externalLink?: string;\n creator?: ApiCollectibleCreator;\n lastSale?: ApiCollectibleLastSale;\n}\n\ninterface AccountParams {\n userAddress: string;\n chainId: string;\n}\n\n/**\n * @type CollectiblesConfig\n *\n * Collectibles controller configuration\n * @property networkType - Network ID as per net_version\n * @property selectedAddress - Vault selected address\n */\nexport interface CollectiblesConfig extends BaseConfig {\n networkType: NetworkType;\n selectedAddress: string;\n chainId: string;\n ipfsGateway: string;\n openSeaEnabled: boolean;\n useIPFSSubdomains: boolean;\n}\n\n/**\n * @type CollectiblesState\n *\n * Assets controller state\n * @property allCollectibleContracts - Object containing collectibles contract information\n * @property allCollectibles - Object containing collectibles per account and network\n * @property collectibleContracts - List of collectibles contracts associated with the active vault\n * @property collectibles - List of collectibles associated with the active vault\n * @property ignoredCollectibles - List of collectibles that should be ignored\n */\nexport interface CollectiblesState extends BaseState {\n allCollectibleContracts: {\n [key: string]: { [key: string]: CollectibleContract[] };\n };\n allCollectibles: { [key: string]: { [key: string]: Collectible[] } };\n ignoredCollectibles: Collectible[];\n}\n\nconst ALL_COLLECTIBLES_STATE_KEY = 'allCollectibles';\nconst ALL_COLLECTIBLES_CONTRACTS_STATE_KEY = 'allCollectibleContracts';\n\n/**\n * Controller that stores assets and exposes convenience methods\n */\nexport class CollectiblesController extends BaseController<\n CollectiblesConfig,\n CollectiblesState\n> {\n private mutex = new Mutex();\n\n private getCollectibleApi({\n contractAddress,\n tokenId,\n useProxy,\n }: {\n contractAddress: string;\n tokenId: string;\n useProxy: boolean;\n }) {\n const { chainId } = this.config;\n\n if (chainId === RINKEBY_CHAIN_ID) {\n return `${OPENSEA_TEST_API_URL}/asset/${contractAddress}/${tokenId}`;\n }\n return useProxy\n ? `${OPENSEA_PROXY_URL}/asset/${contractAddress}/${tokenId}`\n : `${OPENSEA_API_URL}/asset/${contractAddress}/${tokenId}`;\n }\n\n private getCollectibleContractInformationApi({\n contractAddress,\n useProxy,\n }: {\n contractAddress: string;\n useProxy: boolean;\n }) {\n const { chainId } = this.config;\n\n if (chainId === RINKEBY_CHAIN_ID) {\n return `${OPENSEA_TEST_API_URL}/asset_contract/${contractAddress}`;\n }\n\n return useProxy\n ? `${OPENSEA_PROXY_URL}/asset_contract/${contractAddress}`\n : `${OPENSEA_API_URL}/asset_contract/${contractAddress}`;\n }\n\n /**\n * Helper method to update nested state for allCollectibles and allCollectibleContracts.\n *\n * @param newCollection - the modified piece of state to update in the controller's store\n * @param baseStateKey - The root key in the store to update.\n * @param passedConfig - An object containing the selectedAddress and chainId that are passed through the auto-detection flow.\n * @param passedConfig.userAddress - the address passed through the collectible detection flow to ensure detected assets are stored to the correct account\n * @param passedConfig.chainId - the chainId passed through the collectible detection flow to ensure detected assets are stored to the correct account\n */\n private updateNestedCollectibleState(\n newCollection: Collectible[] | CollectibleContract[],\n baseStateKey: 'allCollectibles' | 'allCollectibleContracts',\n { userAddress, chainId }: AccountParams | undefined = {\n userAddress: this.config.selectedAddress,\n chainId: this.config.chainId,\n },\n ) {\n const { [baseStateKey]: oldState } = this.state;\n\n const addressState = oldState[userAddress];\n const newAddressState = {\n ...addressState,\n ...{ [chainId]: newCollection },\n };\n const newState = {\n ...oldState,\n ...{ [userAddress]: newAddressState },\n };\n\n this.update({\n [baseStateKey]: newState,\n });\n }\n\n /**\n * Request individual collectible information from OpenSea API.\n *\n * @param contractAddress - Hex address of the collectible contract.\n * @param tokenId - The collectible identifier.\n * @returns Promise resolving to the current collectible name and image.\n */\n private async getCollectibleInformationFromApi(\n contractAddress: string,\n tokenId: string,\n ): Promise {\n // Attempt to fetch the data with the proxy\n let collectibleInformation: ApiCollectible | undefined =\n await fetchWithErrorHandling({\n url: this.getCollectibleApi({\n contractAddress,\n tokenId,\n useProxy: true,\n }),\n });\n\n // if an openSeaApiKey is set we should attempt to refetch calling directly to OpenSea\n if (!collectibleInformation && this.openSeaApiKey) {\n collectibleInformation = await fetchWithErrorHandling({\n url: this.getCollectibleApi({\n contractAddress,\n tokenId,\n useProxy: false,\n }),\n options: {\n headers: { 'X-API-KEY': this.openSeaApiKey },\n },\n // catch 403 errors (in case API key is down we don't want to blow up)\n errorCodesToCatch: [403],\n });\n }\n\n // if we were still unable to fetch the data we return out the default/null of `CollectibleMetadata`\n if (!collectibleInformation) {\n return {\n name: null,\n description: null,\n image: null,\n standard: null,\n };\n }\n\n // if we've reached this point, we have successfully fetched some data for collectibleInformation\n // now we reconfigure the data to conform to the `CollectibleMetadata` type for storage.\n const {\n num_sales,\n background_color,\n image_url,\n image_preview_url,\n image_thumbnail_url,\n image_original_url,\n animation_url,\n animation_original_url,\n name,\n description,\n external_link,\n creator,\n last_sale,\n asset_contract: { schema_name },\n } = collectibleInformation;\n\n /* istanbul ignore next */\n const collectibleMetadata: CollectibleMetadata = Object.assign(\n {},\n { name: name || null },\n { description: description || null },\n { image: image_url || null },\n creator && { creator },\n num_sales && { numberOfSales: num_sales },\n background_color && { backgroundColor: background_color },\n image_preview_url && { imagePreview: image_preview_url },\n image_thumbnail_url && { imageThumbnail: image_thumbnail_url },\n image_original_url && { imageOriginal: image_original_url },\n animation_url && { animation: animation_url },\n animation_original_url && {\n animationOriginal: animation_original_url,\n },\n external_link && { externalLink: external_link },\n last_sale && { lastSale: last_sale },\n schema_name && { standard: schema_name },\n );\n\n return collectibleMetadata;\n }\n\n /**\n * Request individual collectible information from contracts that follows Metadata Interface.\n *\n * @param contractAddress - Hex address of the collectible contract.\n * @param tokenId - The collectible identifier.\n * @returns Promise resolving to the current collectible name and image.\n */\n private async getCollectibleInformationFromTokenURI(\n contractAddress: string,\n tokenId: string,\n ): Promise {\n const { ipfsGateway, useIPFSSubdomains } = this.config;\n const result = await this.getCollectibleURIAndStandard(\n contractAddress,\n tokenId,\n );\n let tokenURI = result[0];\n const standard = result[1];\n\n if (tokenURI.startsWith('ipfs://')) {\n tokenURI = getFormattedIpfsUrl(ipfsGateway, tokenURI, useIPFSSubdomains);\n }\n\n try {\n const object = await handleFetch(tokenURI);\n // TODO: Check image_url existence. This is not part of EIP721 nor EIP1155\n const image = Object.prototype.hasOwnProperty.call(object, 'image')\n ? 'image'\n : /* istanbul ignore next */ 'image_url';\n\n return {\n image: object[image],\n name: object.name,\n description: object.description,\n standard,\n favorite: false,\n };\n } catch {\n return {\n image: null,\n name: null,\n description: null,\n standard: standard || null,\n favorite: false,\n };\n }\n }\n\n /**\n * Retrieve collectible uri with metadata. TODO Update method to use IPFS.\n *\n * @param contractAddress - Collectible contract address.\n * @param tokenId - Collectible token id.\n * @returns Promise resolving collectible uri and token standard.\n */\n private async getCollectibleURIAndStandard(\n contractAddress: string,\n tokenId: string,\n ): Promise<[string, string]> {\n // try ERC721 uri\n try {\n const uri = await this.getERC721TokenURI(contractAddress, tokenId);\n return [uri, ERC721];\n } catch {\n // Ignore error\n }\n\n // try ERC1155 uri\n try {\n const tokenURI = await this.getERC1155TokenURI(contractAddress, tokenId);\n\n /**\n * According to EIP1155 the URI value allows for ID substitution\n * in case the string `{id}` exists.\n * https://eips.ethereum.org/EIPS/eip-1155#metadata\n */\n\n if (!tokenURI.includes('{id}')) {\n return [tokenURI, ERC1155];\n }\n\n const hexTokenId = stripHexPrefix(BNToHex(new BN(tokenId)))\n .padStart(64, '0')\n .toLowerCase();\n return [tokenURI.replace('{id}', hexTokenId), ERC1155];\n } catch {\n // Ignore error\n }\n\n return ['', ''];\n }\n\n /**\n * Request individual collectible information (name, image url and description).\n *\n * @param contractAddress - Hex address of the collectible contract.\n * @param tokenId - The collectible identifier.\n * @returns Promise resolving to the current collectible name and image.\n */\n private async getCollectibleInformation(\n contractAddress: string,\n tokenId: string,\n ): Promise {\n const blockchainMetadata = await safelyExecute(async () => {\n return await this.getCollectibleInformationFromTokenURI(\n contractAddress,\n tokenId,\n );\n });\n\n let openSeaMetadata;\n if (this.config.openSeaEnabled) {\n openSeaMetadata = await safelyExecute(async () => {\n return await this.getCollectibleInformationFromApi(\n contractAddress,\n tokenId,\n );\n });\n }\n return {\n ...openSeaMetadata,\n name: blockchainMetadata.name ?? openSeaMetadata?.name ?? null,\n description:\n blockchainMetadata.description ?? openSeaMetadata?.description ?? null,\n image: blockchainMetadata.image ?? openSeaMetadata?.image ?? null,\n standard:\n blockchainMetadata.standard ?? openSeaMetadata?.standard ?? null,\n };\n }\n\n /**\n * Request collectible contract information from OpenSea API.\n *\n * @param contractAddress - Hex address of the collectible contract.\n * @returns Promise resolving to the current collectible name and image.\n */\n private async getCollectibleContractInformationFromApi(\n contractAddress: string,\n ): Promise {\n /* istanbul ignore if */\n let apiCollectibleContractObject: ApiCollectibleContract | undefined =\n await fetchWithErrorHandling({\n url: this.getCollectibleContractInformationApi({\n contractAddress,\n useProxy: true,\n }),\n });\n\n // if we successfully fetched return the fetched data immediately\n if (apiCollectibleContractObject) {\n return apiCollectibleContractObject;\n }\n\n // if we were unsuccessful in fetching from the API and an OpenSea API key is present\n // attempt to refetch directly against the OpenSea API and if successful return the data immediately\n if (this.openSeaApiKey) {\n apiCollectibleContractObject = await fetchWithErrorHandling({\n url: this.getCollectibleContractInformationApi({\n contractAddress,\n useProxy: false,\n }),\n options: {\n headers: { 'X-API-KEY': this.openSeaApiKey },\n },\n // catch 403 errors (in case API key is down we don't want to blow up)\n errorCodesToCatch: [403],\n });\n\n if (apiCollectibleContractObject) {\n return apiCollectibleContractObject;\n }\n }\n\n // If we've reached this point we were unable to fetch data from either the proxy or opensea so we return\n // the default/null of ApiCollectibleContract\n return {\n address: contractAddress,\n asset_contract_type: null,\n created_date: null,\n schema_name: null,\n symbol: null,\n total_supply: null,\n description: null,\n external_link: null,\n collection: {\n name: null,\n image_url: null,\n },\n };\n }\n\n /**\n * Request collectible contract information from the contract itself.\n *\n * @param contractAddress - Hex address of the collectible contract.\n * @returns Promise resolving to the current collectible name and image.\n */\n private async getCollectibleContractInformationFromContract(\n contractAddress: string,\n ): Promise<\n Partial &\n Pick &\n Pick\n > {\n const name = await this.getERC721AssetName(contractAddress);\n const symbol = await this.getERC721AssetSymbol(contractAddress);\n return {\n collection: { name },\n symbol,\n address: contractAddress,\n };\n }\n\n /**\n * Request collectible contract information from OpenSea API.\n *\n * @param contractAddress - Hex address of the collectible contract.\n * @returns Promise resolving to the collectible contract name, image and description.\n */\n private async getCollectibleContractInformation(\n contractAddress: string,\n ): Promise<\n Partial &\n Pick &\n Pick\n > {\n const blockchainContractData: Partial &\n Pick &\n Pick = await safelyExecute(\n async () => {\n return await this.getCollectibleContractInformationFromContract(\n contractAddress,\n );\n },\n );\n\n let openSeaContractData: Partial | undefined;\n if (this.config.openSeaEnabled) {\n openSeaContractData = await safelyExecute(async () => {\n return await this.getCollectibleContractInformationFromApi(\n contractAddress,\n );\n });\n }\n\n if (blockchainContractData || openSeaContractData) {\n return {\n ...openSeaContractData,\n ...blockchainContractData,\n collection: {\n image_url: null,\n ...openSeaContractData?.collection,\n ...blockchainContractData?.collection,\n },\n };\n }\n\n /* istanbul ignore next */\n return {\n address: contractAddress,\n asset_contract_type: null,\n created_date: null,\n schema_name: null,\n symbol: null,\n total_supply: null,\n description: null,\n external_link: null,\n collection: { name: null, image_url: null },\n };\n }\n\n /**\n * Adds an individual collectible to the stored collectible list.\n *\n * @param address - Hex address of the collectible contract.\n * @param tokenId - The collectible identifier.\n * @param collectibleMetadata - Collectible optional information (name, image and description).\n * @param collectibleContract - An object containing contract data of the collectible being added.\n * @param detection - The chain ID and address of the currently selected network and account at the moment the collectible was detected.\n * @returns Promise resolving to the current collectible list.\n */\n private async addIndividualCollectible(\n address: string,\n tokenId: string,\n collectibleMetadata: CollectibleMetadata,\n collectibleContract: CollectibleContract,\n detection?: AccountParams,\n ): Promise {\n // TODO: Remove unused return\n const releaseLock = await this.mutex.acquire();\n try {\n address = toChecksumHexAddress(address);\n const { allCollectibles } = this.state;\n let chainId, selectedAddress;\n\n if (detection) {\n chainId = detection.chainId;\n selectedAddress = detection.userAddress;\n } else {\n chainId = this.config.chainId;\n selectedAddress = this.config.selectedAddress;\n }\n\n const collectibles = allCollectibles[selectedAddress]?.[chainId] || [];\n\n const existingEntry: Collectible | undefined = collectibles.find(\n (collectible) =>\n collectible.address.toLowerCase() === address.toLowerCase() &&\n collectible.tokenId === tokenId,\n );\n\n if (existingEntry) {\n const differentMetadata = compareCollectiblesMetadata(\n collectibleMetadata,\n existingEntry,\n );\n if (differentMetadata) {\n // TODO: Switch to indexToUpdate\n const indexToRemove = collectibles.findIndex(\n (collectible) =>\n collectible.address.toLowerCase() === address.toLowerCase() &&\n collectible.tokenId === tokenId,\n );\n /* istanbul ignore next */\n if (indexToRemove !== -1) {\n collectibles.splice(indexToRemove, 1);\n }\n } else {\n return collectibles;\n }\n }\n\n const newEntry: Collectible = {\n address,\n tokenId,\n favorite: existingEntry?.favorite || false,\n isCurrentlyOwned: true,\n ...collectibleMetadata,\n };\n\n const newCollectibles = [...collectibles, newEntry];\n this.updateNestedCollectibleState(\n newCollectibles,\n ALL_COLLECTIBLES_STATE_KEY,\n { chainId, userAddress: selectedAddress },\n );\n\n if (this.onCollectibleAdded) {\n this.onCollectibleAdded({\n address,\n symbol: collectibleContract.symbol,\n tokenId: tokenId.toString(),\n standard: collectibleMetadata.standard,\n source: detection ? 'detected' : 'custom',\n });\n }\n\n return newCollectibles;\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Adds a collectible contract to the stored collectible contracts list.\n *\n * @param address - Hex address of the collectible contract.\n * @param detection - The chain ID and address of the currently selected network and account at the moment the collectible was detected.\n * @returns Promise resolving to the current collectible contracts list.\n */\n private async addCollectibleContract(\n address: string,\n detection?: AccountParams,\n ): Promise {\n const releaseLock = await this.mutex.acquire();\n try {\n address = toChecksumHexAddress(address);\n const { allCollectibleContracts } = this.state;\n\n let chainId, selectedAddress;\n if (detection) {\n chainId = detection.chainId;\n selectedAddress = detection.userAddress;\n } else {\n chainId = this.config.chainId;\n selectedAddress = this.config.selectedAddress;\n }\n\n const collectibleContracts =\n allCollectibleContracts[selectedAddress]?.[chainId] || [];\n\n const existingEntry = collectibleContracts.find(\n (collectibleContract) =>\n collectibleContract.address.toLowerCase() === address.toLowerCase(),\n );\n if (existingEntry) {\n return collectibleContracts;\n }\n const contractInformation = await this.getCollectibleContractInformation(\n address,\n );\n\n const {\n asset_contract_type,\n created_date,\n schema_name,\n symbol,\n total_supply,\n description,\n external_link,\n collection: { name, image_url },\n } = contractInformation;\n // If being auto-detected opensea information is expected\n // Otherwise at least name from the contract is needed\n if (\n (detection && !name) ||\n Object.keys(contractInformation).length === 0\n ) {\n return collectibleContracts;\n }\n\n /* istanbul ignore next */\n const newEntry: CollectibleContract = Object.assign(\n {},\n { address },\n description && { description },\n name && { name },\n image_url && { logo: image_url },\n symbol && { symbol },\n total_supply !== null &&\n typeof total_supply !== 'undefined' && { totalSupply: total_supply },\n asset_contract_type && { assetContractType: asset_contract_type },\n created_date && { createdDate: created_date },\n schema_name && { schemaName: schema_name },\n external_link && { externalLink: external_link },\n );\n const newCollectibleContracts = [...collectibleContracts, newEntry];\n this.updateNestedCollectibleState(\n newCollectibleContracts,\n ALL_COLLECTIBLES_CONTRACTS_STATE_KEY,\n { chainId, userAddress: selectedAddress },\n );\n\n return newCollectibleContracts;\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Removes an individual collectible from the stored token list and saves it in ignored collectibles list.\n *\n * @param address - Hex address of the collectible contract.\n * @param tokenId - Token identifier of the collectible.\n */\n private removeAndIgnoreIndividualCollectible(\n address: string,\n tokenId: string,\n ) {\n address = toChecksumHexAddress(address);\n const { allCollectibles, ignoredCollectibles } = this.state;\n const { chainId, selectedAddress } = this.config;\n const newIgnoredCollectibles = [...ignoredCollectibles];\n const collectibles = allCollectibles[selectedAddress]?.[chainId] || [];\n const newCollectibles = collectibles.filter((collectible) => {\n if (\n collectible.address.toLowerCase() === address.toLowerCase() &&\n collectible.tokenId === tokenId\n ) {\n const alreadyIgnored = newIgnoredCollectibles.find(\n (c) => c.address === address && c.tokenId === tokenId,\n );\n !alreadyIgnored && newIgnoredCollectibles.push(collectible);\n return false;\n }\n return true;\n });\n\n this.updateNestedCollectibleState(\n newCollectibles,\n ALL_COLLECTIBLES_STATE_KEY,\n );\n\n this.update({\n ignoredCollectibles: newIgnoredCollectibles,\n });\n }\n\n /**\n * Removes an individual collectible from the stored token list.\n *\n * @param address - Hex address of the collectible contract.\n * @param tokenId - Token identifier of the collectible.\n */\n private removeIndividualCollectible(address: string, tokenId: string) {\n address = toChecksumHexAddress(address);\n const { allCollectibles } = this.state;\n const { chainId, selectedAddress } = this.config;\n const collectibles = allCollectibles[selectedAddress]?.[chainId] || [];\n const newCollectibles = collectibles.filter(\n (collectible) =>\n !(\n collectible.address.toLowerCase() === address.toLowerCase() &&\n collectible.tokenId === tokenId\n ),\n );\n this.updateNestedCollectibleState(\n newCollectibles,\n ALL_COLLECTIBLES_STATE_KEY,\n );\n }\n\n /**\n * Removes a collectible contract to the stored collectible contracts list.\n *\n * @param address - Hex address of the collectible contract.\n * @returns Promise resolving to the current collectible contracts list.\n */\n private removeCollectibleContract(address: string): CollectibleContract[] {\n address = toChecksumHexAddress(address);\n const { allCollectibleContracts } = this.state;\n const { chainId, selectedAddress } = this.config;\n const collectibleContracts =\n allCollectibleContracts[selectedAddress]?.[chainId] || [];\n\n const newCollectibleContracts = collectibleContracts.filter(\n (collectibleContract) =>\n !(collectibleContract.address.toLowerCase() === address.toLowerCase()),\n );\n this.updateNestedCollectibleState(\n newCollectibleContracts,\n ALL_COLLECTIBLES_CONTRACTS_STATE_KEY,\n );\n\n return newCollectibleContracts;\n }\n\n /**\n * EventEmitter instance used to listen to specific EIP747 events\n */\n hub = new EventEmitter();\n\n /**\n * Optional API key to use with opensea\n */\n openSeaApiKey?: string;\n\n /**\n * Name of this controller used during composition\n */\n override name = 'CollectiblesController';\n\n private getERC721AssetName: AssetsContractController['getERC721AssetName'];\n\n private getERC721AssetSymbol: AssetsContractController['getERC721AssetSymbol'];\n\n private getERC721TokenURI: AssetsContractController['getERC721TokenURI'];\n\n private getERC721OwnerOf: AssetsContractController['getERC721OwnerOf'];\n\n private getERC1155BalanceOf: AssetsContractController['getERC1155BalanceOf'];\n\n private getERC1155TokenURI: AssetsContractController['getERC1155TokenURI'];\n\n private onCollectibleAdded?: (data: {\n address: string;\n symbol: string | undefined;\n tokenId: string;\n standard: string | null;\n source: string;\n }) => void;\n\n /**\n * Creates a CollectiblesController instance.\n *\n * @param options - The controller options.\n * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes.\n * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.\n * @param options.getERC721AssetName - Gets the name of the asset at the given address.\n * @param options.getERC721AssetSymbol - Gets the symbol of the asset at the given address.\n * @param options.getERC721TokenURI - Gets the URI of the ERC721 token at the given address, with the given ID.\n * @param options.getERC721OwnerOf - Get the owner of a ERC-721 collectible.\n * @param options.getERC1155BalanceOf - Gets balance of a ERC-1155 collectible.\n * @param options.getERC1155TokenURI - Gets the URI of the ERC1155 token at the given address, with the given ID.\n * @param options.onCollectibleAdded - Callback that is called when a collectible is added. Currently used pass data\n * for tracking the collectible added event.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n onPreferencesStateChange,\n onNetworkStateChange,\n getERC721AssetName,\n getERC721AssetSymbol,\n getERC721TokenURI,\n getERC721OwnerOf,\n getERC1155BalanceOf,\n getERC1155TokenURI,\n onCollectibleAdded,\n }: {\n onPreferencesStateChange: (\n listener: (preferencesState: PreferencesState) => void,\n ) => void;\n onNetworkStateChange: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n getERC721AssetName: AssetsContractController['getERC721AssetName'];\n getERC721AssetSymbol: AssetsContractController['getERC721AssetSymbol'];\n getERC721TokenURI: AssetsContractController['getERC721TokenURI'];\n getERC721OwnerOf: AssetsContractController['getERC721OwnerOf'];\n getERC1155BalanceOf: AssetsContractController['getERC1155BalanceOf'];\n getERC1155TokenURI: AssetsContractController['getERC1155TokenURI'];\n onCollectibleAdded?: (data: {\n address: string;\n symbol: string | undefined;\n tokenId: string;\n standard: string | null;\n source: string;\n }) => void;\n },\n config?: Partial,\n state?: Partial,\n ) {\n super(config, state);\n this.defaultConfig = {\n networkType: MAINNET,\n selectedAddress: '',\n chainId: '',\n ipfsGateway: IPFS_DEFAULT_GATEWAY_URL,\n openSeaEnabled: false,\n useIPFSSubdomains: true,\n };\n\n this.defaultState = {\n allCollectibleContracts: {},\n allCollectibles: {},\n ignoredCollectibles: [],\n };\n this.initialize();\n this.getERC721AssetName = getERC721AssetName;\n this.getERC721AssetSymbol = getERC721AssetSymbol;\n this.getERC721TokenURI = getERC721TokenURI;\n this.getERC721OwnerOf = getERC721OwnerOf;\n this.getERC1155BalanceOf = getERC1155BalanceOf;\n this.getERC1155TokenURI = getERC1155TokenURI;\n this.onCollectibleAdded = onCollectibleAdded;\n\n onPreferencesStateChange(\n ({ selectedAddress, ipfsGateway, openSeaEnabled }) => {\n this.configure({ selectedAddress, ipfsGateway, openSeaEnabled });\n },\n );\n\n onNetworkStateChange(({ provider }) => {\n const { chainId } = provider;\n this.configure({ chainId });\n });\n }\n\n /**\n * Sets an OpenSea API key to retrieve collectible information.\n *\n * @param openSeaApiKey - OpenSea API key.\n */\n setApiKey(openSeaApiKey: string) {\n this.openSeaApiKey = openSeaApiKey;\n }\n\n /**\n * Checks the ownership of a ERC-721 or ERC-1155 collectible for a given address.\n *\n * @param ownerAddress - User public address.\n * @param collectibleAddress - Collectible contract address.\n * @param collectibleId - Collectible token ID.\n * @returns Promise resolving the collectible ownership.\n */\n async isCollectibleOwner(\n ownerAddress: string,\n collectibleAddress: string,\n collectibleId: string,\n ): Promise {\n // Checks the ownership for ERC-721.\n try {\n const owner = await this.getERC721OwnerOf(\n collectibleAddress,\n collectibleId,\n );\n return ownerAddress.toLowerCase() === owner.toLowerCase();\n // eslint-disable-next-line no-empty\n } catch {\n // Ignore ERC-721 contract error\n }\n\n // Checks the ownership for ERC-1155.\n try {\n const balance = await this.getERC1155BalanceOf(\n ownerAddress,\n collectibleAddress,\n collectibleId,\n );\n return balance > 0;\n // eslint-disable-next-line no-empty\n } catch {\n // Ignore ERC-1155 contract error\n }\n\n throw new Error(\n 'Unable to verify ownership. Probably because the standard is not supported or the chain is incorrect.',\n );\n }\n\n /**\n * Verifies currently selected address owns entered collectible address/tokenId combo and\n * adds the collectible and respective collectible contract to the stored collectible and collectible contracts lists.\n *\n * @param address - Hex address of the collectible contract.\n * @param tokenId - The collectible identifier.\n */\n async addCollectibleVerifyOwnership(address: string, tokenId: string) {\n const { selectedAddress } = this.config;\n if (!(await this.isCollectibleOwner(selectedAddress, address, tokenId))) {\n throw new Error('This collectible is not owned by the user');\n }\n await this.addCollectible(address, tokenId);\n }\n\n /**\n * Adds a collectible and respective collectible contract to the stored collectible and collectible contracts lists.\n *\n * @param address - Hex address of the collectible contract.\n * @param tokenId - The collectible identifier.\n * @param collectibleMetadata - Collectible optional metadata.\n * @param detection - The chain ID and address of the currently selected network and account at the moment the collectible was detected.\n * @returns Promise resolving to the current collectible list.\n */\n async addCollectible(\n address: string,\n tokenId: string,\n collectibleMetadata?: CollectibleMetadata,\n detection?: AccountParams,\n ) {\n address = toChecksumHexAddress(address);\n const newCollectibleContracts = await this.addCollectibleContract(\n address,\n detection,\n );\n collectibleMetadata =\n collectibleMetadata ||\n (await this.getCollectibleInformation(address, tokenId));\n\n // If collectible contract was not added, do not add individual collectible\n const collectibleContract = newCollectibleContracts.find(\n (contract) => contract.address.toLowerCase() === address.toLowerCase(),\n );\n\n // If collectible contract information, add individual collectible\n if (collectibleContract) {\n await this.addIndividualCollectible(\n address,\n tokenId,\n collectibleMetadata,\n collectibleContract,\n detection,\n );\n }\n }\n\n /**\n * Removes a collectible from the stored token list.\n *\n * @param address - Hex address of the collectible contract.\n * @param tokenId - Token identifier of the collectible.\n */\n removeCollectible(address: string, tokenId: string) {\n address = toChecksumHexAddress(address);\n this.removeIndividualCollectible(address, tokenId);\n const { allCollectibles } = this.state;\n const { chainId, selectedAddress } = this.config;\n const collectibles = allCollectibles[selectedAddress]?.[chainId] || [];\n const remainingCollectible = collectibles.find(\n (collectible) =>\n collectible.address.toLowerCase() === address.toLowerCase(),\n );\n if (!remainingCollectible) {\n this.removeCollectibleContract(address);\n }\n }\n\n /**\n * Removes a collectible from the stored token list and saves it in ignored collectibles list.\n *\n * @param address - Hex address of the collectible contract.\n * @param tokenId - Token identifier of the collectible.\n */\n removeAndIgnoreCollectible(address: string, tokenId: string) {\n address = toChecksumHexAddress(address);\n this.removeAndIgnoreIndividualCollectible(address, tokenId);\n const { allCollectibles } = this.state;\n const { chainId, selectedAddress } = this.config;\n const collectibles = allCollectibles[selectedAddress]?.[chainId] || [];\n const remainingCollectible = collectibles.find(\n (collectible) =>\n collectible.address.toLowerCase() === address.toLowerCase(),\n );\n if (!remainingCollectible) {\n this.removeCollectibleContract(address);\n }\n }\n\n /**\n * Removes all collectibles from the ignored list.\n */\n clearIgnoredCollectibles() {\n this.update({ ignoredCollectibles: [] });\n }\n\n /**\n * Checks whether input collectible is still owned by the user\n * And updates the isCurrentlyOwned value on the collectible object accordingly.\n *\n * @param collectible - The collectible object to check and update.\n * @param batch - A boolean indicating whether this method is being called as part of a batch or single update.\n * @param accountParams - The userAddress and chainId to check ownership against\n * @param accountParams.userAddress - the address passed through the confirmed transaction flow to ensure detected assets are stored to the correct account\n * @param accountParams.chainId - the chainId passed through the confirmed transaction flow to ensure detected assets are stored to the correct account\n * @returns the collectible with the updated isCurrentlyOwned value\n */\n async checkAndUpdateSingleCollectibleOwnershipStatus(\n collectible: Collectible,\n batch: boolean,\n { userAddress, chainId }: AccountParams | undefined = {\n userAddress: this.config.selectedAddress,\n chainId: this.config.chainId,\n },\n ) {\n const { address, tokenId } = collectible;\n let isOwned = collectible.isCurrentlyOwned;\n try {\n isOwned = await this.isCollectibleOwner(userAddress, address, tokenId);\n } catch (error) {\n if (\n !(\n error instanceof Error &&\n error.message.includes('Unable to verify ownership')\n )\n ) {\n throw error;\n }\n }\n\n collectible.isCurrentlyOwned = isOwned;\n\n if (batch === true) {\n return collectible;\n }\n\n // if this is not part of a batched update we update this one collectible in state\n const { allCollectibles } = this.state;\n const collectibles = allCollectibles[userAddress]?.[chainId] || [];\n const collectibleToUpdate = collectibles.find(\n (item) =>\n item.tokenId === tokenId &&\n item.address.toLowerCase() === address.toLowerCase(),\n );\n if (collectibleToUpdate) {\n collectibleToUpdate.isCurrentlyOwned = isOwned;\n this.updateNestedCollectibleState(\n collectibles,\n ALL_COLLECTIBLES_STATE_KEY,\n { userAddress, chainId },\n );\n }\n return collectible;\n }\n\n /**\n * Checks whether Collectibles associated with current selectedAddress/chainId combination are still owned by the user\n * And updates the isCurrentlyOwned value on each accordingly.\n */\n async checkAndUpdateAllCollectiblesOwnershipStatus() {\n const { allCollectibles } = this.state;\n const { chainId, selectedAddress } = this.config;\n const collectibles = allCollectibles[selectedAddress]?.[chainId] || [];\n const updatedCollectibles = await Promise.all(\n collectibles.map(async (collectible) => {\n return (\n (await this.checkAndUpdateSingleCollectibleOwnershipStatus(\n collectible,\n true,\n )) ?? collectible\n );\n }),\n );\n\n this.updateNestedCollectibleState(\n updatedCollectibles,\n ALL_COLLECTIBLES_STATE_KEY,\n );\n }\n\n /**\n * Update collectible favorite status.\n *\n * @param address - Hex address of the collectible contract.\n * @param tokenId - Hex address of the collectible contract.\n * @param favorite - Collectible new favorite status.\n */\n updateCollectibleFavoriteStatus(\n address: string,\n tokenId: string,\n favorite: boolean,\n ) {\n const { allCollectibles } = this.state;\n const { chainId, selectedAddress } = this.config;\n const collectibles = allCollectibles[selectedAddress]?.[chainId] || [];\n const index: number = collectibles.findIndex(\n (collectible) =>\n collectible.address === address && collectible.tokenId === tokenId,\n );\n\n if (index === -1) {\n return;\n }\n\n const updatedCollectible: Collectible = {\n ...collectibles[index],\n favorite,\n };\n\n // Update Collectibles array\n collectibles[index] = updatedCollectible;\n\n this.updateNestedCollectibleState(collectibles, ALL_COLLECTIBLES_STATE_KEY);\n }\n}\n\nexport default CollectiblesController;\n"]} \ No newline at end of file diff --git a/dist/assets/CurrencyRateController.d.ts b/dist/assets/CurrencyRateController.d.ts new file mode 100644 index 0000000000..3395bc7276 --- /dev/null +++ b/dist/assets/CurrencyRateController.d.ts @@ -0,0 +1,99 @@ +import type { Patch } from 'immer'; +import { BaseController } from '../BaseControllerV2'; +import { fetchExchangeRate as defaultFetchExchangeRate } from '../apis/crypto-compare'; +import type { RestrictedControllerMessenger } from '../ControllerMessenger'; +/** + * @type CurrencyRateState + * @property conversionDate - Timestamp of conversion rate expressed in ms since UNIX epoch + * @property conversionRate - Conversion rate from current base asset to the current currency + * @property currentCurrency - Currently-active ISO 4217 currency code + * @property nativeCurrency - Symbol for the base asset used for conversion + * @property pendingCurrentCurrency - The currency being switched to + * @property pendingNativeCurrency - The base asset currency being switched to + * @property usdConversionRate - Conversion rate from usd to the current currency + */ +export declare type CurrencyRateState = { + conversionDate: number | null; + conversionRate: number | null; + currentCurrency: string; + nativeCurrency: string; + pendingCurrentCurrency: string | null; + pendingNativeCurrency: string | null; + usdConversionRate: number | null; +}; +declare const name = "CurrencyRateController"; +export declare type CurrencyRateStateChange = { + type: `${typeof name}:stateChange`; + payload: [CurrencyRateState, Patch[]]; +}; +export declare type GetCurrencyRateState = { + type: `${typeof name}:getState`; + handler: () => CurrencyRateState; +}; +declare type CurrencyRateMessenger = RestrictedControllerMessenger; +/** + * Controller that passively polls on a set interval for an exchange rate from the current base + * asset to the current currency + */ +export declare class CurrencyRateController extends BaseController { + private mutex; + private intervalId?; + private intervalDelay; + private fetchExchangeRate; + private includeUsdRate; + /** + * Creates a CurrencyRateController instance. + * + * @param options - Constructor options. + * @param options.includeUsdRate - Keep track of the USD rate in addition to the current currency rate. + * @param options.interval - The polling interval, in milliseconds. + * @param options.messenger - A reference to the messaging system. + * @param options.state - Initial state to set on this controller. + * @param options.fetchExchangeRate - Fetches the exchange rate from an external API. This option is primarily meant for use in unit tests. + */ + constructor({ includeUsdRate, interval, messenger, state, fetchExchangeRate, }: { + includeUsdRate?: boolean; + interval?: number; + messenger: CurrencyRateMessenger; + state?: Partial; + fetchExchangeRate?: typeof defaultFetchExchangeRate; + }); + /** + * Start polling for the currency rate. + */ + start(): Promise; + /** + * Stop polling for the currency rate. + */ + stop(): void; + /** + * Prepare to discard this controller. + * + * This stops any active polling. + */ + destroy(): void; + /** + * Sets a currency to track. + * + * @param currentCurrency - ISO 4217 currency code. + */ + setCurrentCurrency(currentCurrency: string): Promise; + /** + * Sets a new native currency. + * + * @param symbol - Symbol for the base asset. + */ + setNativeCurrency(symbol: string): Promise; + private stopPolling; + /** + * Starts a new polling interval. + */ + private startPolling; + /** + * Updates exchange rate for the current currency. + * + * @returns The controller state. + */ + updateExchangeRate(): Promise; +} +export default CurrencyRateController; diff --git a/dist/assets/CurrencyRateController.js b/dist/assets/CurrencyRateController.js new file mode 100644 index 0000000000..c7a50ee88b --- /dev/null +++ b/dist/assets/CurrencyRateController.js @@ -0,0 +1,194 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CurrencyRateController = void 0; +const async_mutex_1 = require("async-mutex"); +const BaseControllerV2_1 = require("../BaseControllerV2"); +const util_1 = require("../util"); +const crypto_compare_1 = require("../apis/crypto-compare"); +const constants_1 = require("../constants"); +const name = 'CurrencyRateController'; +const metadata = { + conversionDate: { persist: true, anonymous: true }, + conversionRate: { persist: true, anonymous: true }, + currentCurrency: { persist: true, anonymous: true }, + nativeCurrency: { persist: true, anonymous: true }, + pendingCurrentCurrency: { persist: false, anonymous: true }, + pendingNativeCurrency: { persist: false, anonymous: true }, + usdConversionRate: { persist: true, anonymous: true }, +}; +const defaultState = { + conversionDate: 0, + conversionRate: 0, + currentCurrency: 'usd', + nativeCurrency: 'ETH', + pendingCurrentCurrency: null, + pendingNativeCurrency: null, + usdConversionRate: null, +}; +/** + * Controller that passively polls on a set interval for an exchange rate from the current base + * asset to the current currency + */ +class CurrencyRateController extends BaseControllerV2_1.BaseController { + /** + * Creates a CurrencyRateController instance. + * + * @param options - Constructor options. + * @param options.includeUsdRate - Keep track of the USD rate in addition to the current currency rate. + * @param options.interval - The polling interval, in milliseconds. + * @param options.messenger - A reference to the messaging system. + * @param options.state - Initial state to set on this controller. + * @param options.fetchExchangeRate - Fetches the exchange rate from an external API. This option is primarily meant for use in unit tests. + */ + constructor({ includeUsdRate = false, interval = 180000, messenger, state, fetchExchangeRate = crypto_compare_1.fetchExchangeRate, }) { + super({ + name, + metadata, + messenger, + state: Object.assign(Object.assign({}, defaultState), state), + }); + this.mutex = new async_mutex_1.Mutex(); + this.includeUsdRate = includeUsdRate; + this.intervalDelay = interval; + this.fetchExchangeRate = fetchExchangeRate; + } + /** + * Start polling for the currency rate. + */ + start() { + return __awaiter(this, void 0, void 0, function* () { + yield this.startPolling(); + }); + } + /** + * Stop polling for the currency rate. + */ + stop() { + this.stopPolling(); + } + /** + * Prepare to discard this controller. + * + * This stops any active polling. + */ + destroy() { + super.destroy(); + this.stopPolling(); + } + /** + * Sets a currency to track. + * + * @param currentCurrency - ISO 4217 currency code. + */ + setCurrentCurrency(currentCurrency) { + return __awaiter(this, void 0, void 0, function* () { + this.update((state) => { + state.pendingCurrentCurrency = currentCurrency; + }); + yield this.updateExchangeRate(); + }); + } + /** + * Sets a new native currency. + * + * @param symbol - Symbol for the base asset. + */ + setNativeCurrency(symbol) { + return __awaiter(this, void 0, void 0, function* () { + this.update((state) => { + state.pendingNativeCurrency = symbol; + }); + yield this.updateExchangeRate(); + }); + } + stopPolling() { + if (this.intervalId) { + clearInterval(this.intervalId); + } + } + /** + * Starts a new polling interval. + */ + startPolling() { + return __awaiter(this, void 0, void 0, function* () { + this.stopPolling(); + // TODO: Expose polling currency rate update errors + yield (0, util_1.safelyExecute)(() => this.updateExchangeRate()); + this.intervalId = setInterval(() => __awaiter(this, void 0, void 0, function* () { + yield (0, util_1.safelyExecute)(() => this.updateExchangeRate()); + }), this.intervalDelay); + }); + } + /** + * Updates exchange rate for the current currency. + * + * @returns The controller state. + */ + updateExchangeRate() { + return __awaiter(this, void 0, void 0, function* () { + const releaseLock = yield this.mutex.acquire(); + const { currentCurrency: stateCurrentCurrency, nativeCurrency: stateNativeCurrency, pendingCurrentCurrency, pendingNativeCurrency, } = this.state; + let conversionDate = null; + let conversionRate = null; + let usdConversionRate = null; + const currentCurrency = pendingCurrentCurrency !== null && pendingCurrentCurrency !== void 0 ? pendingCurrentCurrency : stateCurrentCurrency; + const nativeCurrency = pendingNativeCurrency !== null && pendingNativeCurrency !== void 0 ? pendingNativeCurrency : stateNativeCurrency; + // For preloaded testnets (Rinkeby, Ropsten, Goerli, Kovan) we want to fetch exchange rate for real ETH. + const nativeCurrencyForExchangeRate = Object.values(constants_1.TESTNET_TICKER_SYMBOLS).includes(nativeCurrency) + ? constants_1.FALL_BACK_VS_CURRENCY // ETH + : nativeCurrency; + try { + if (currentCurrency && + nativeCurrency && + // if either currency is an empty string we can skip the comparison + // because it will result in an error from the api and ultimately + // a null conversionRate either way. + currentCurrency !== '' && + nativeCurrency !== '') { + ({ conversionRate, usdConversionRate } = yield this.fetchExchangeRate(currentCurrency, nativeCurrencyForExchangeRate, this.includeUsdRate)); + conversionDate = Date.now() / 1000; + } + } + catch (error) { + if (!(error instanceof Error && + error.message.includes('market does not exist for this coin pair'))) { + throw error; + } + } + finally { + try { + this.update(() => { + return { + conversionDate, + conversionRate, + // we currently allow and handle an empty string as a valid nativeCurrency + // in cases where a user has not entered a native ticker symbol for a custom network + // currentCurrency is not from user input but this protects us from unexpected changes. + nativeCurrency, + currentCurrency, + pendingCurrentCurrency: null, + pendingNativeCurrency: null, + usdConversionRate, + }; + }); + } + finally { + releaseLock(); + } + } + return this.state; + }); + } +} +exports.CurrencyRateController = CurrencyRateController; +exports.default = CurrencyRateController; +//# sourceMappingURL=CurrencyRateController.js.map \ No newline at end of file diff --git a/dist/assets/CurrencyRateController.js.map b/dist/assets/CurrencyRateController.js.map new file mode 100644 index 0000000000..e6aded6d8e --- /dev/null +++ b/dist/assets/CurrencyRateController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CurrencyRateController.js","sourceRoot":"","sources":["../../src/assets/CurrencyRateController.ts"],"names":[],"mappings":"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","sourcesContent":["import { Mutex } from 'async-mutex';\nimport type { Patch } from 'immer';\n\nimport { BaseController } from '../BaseControllerV2';\nimport { safelyExecute } from '../util';\nimport { fetchExchangeRate as defaultFetchExchangeRate } from '../apis/crypto-compare';\n\nimport type { RestrictedControllerMessenger } from '../ControllerMessenger';\nimport { TESTNET_TICKER_SYMBOLS, FALL_BACK_VS_CURRENCY } from '../constants';\n\n/**\n * @type CurrencyRateState\n * @property conversionDate - Timestamp of conversion rate expressed in ms since UNIX epoch\n * @property conversionRate - Conversion rate from current base asset to the current currency\n * @property currentCurrency - Currently-active ISO 4217 currency code\n * @property nativeCurrency - Symbol for the base asset used for conversion\n * @property pendingCurrentCurrency - The currency being switched to\n * @property pendingNativeCurrency - The base asset currency being switched to\n * @property usdConversionRate - Conversion rate from usd to the current currency\n */\nexport type CurrencyRateState = {\n conversionDate: number | null;\n conversionRate: number | null;\n currentCurrency: string;\n nativeCurrency: string;\n pendingCurrentCurrency: string | null;\n pendingNativeCurrency: string | null;\n usdConversionRate: number | null;\n};\n\nconst name = 'CurrencyRateController';\n\nexport type CurrencyRateStateChange = {\n type: `${typeof name}:stateChange`;\n payload: [CurrencyRateState, Patch[]];\n};\n\nexport type GetCurrencyRateState = {\n type: `${typeof name}:getState`;\n handler: () => CurrencyRateState;\n};\n\ntype CurrencyRateMessenger = RestrictedControllerMessenger<\n typeof name,\n GetCurrencyRateState,\n CurrencyRateStateChange,\n never,\n never\n>;\n\nconst metadata = {\n conversionDate: { persist: true, anonymous: true },\n conversionRate: { persist: true, anonymous: true },\n currentCurrency: { persist: true, anonymous: true },\n nativeCurrency: { persist: true, anonymous: true },\n pendingCurrentCurrency: { persist: false, anonymous: true },\n pendingNativeCurrency: { persist: false, anonymous: true },\n usdConversionRate: { persist: true, anonymous: true },\n};\n\nconst defaultState = {\n conversionDate: 0,\n conversionRate: 0,\n currentCurrency: 'usd',\n nativeCurrency: 'ETH',\n pendingCurrentCurrency: null,\n pendingNativeCurrency: null,\n usdConversionRate: null,\n};\n\n/**\n * Controller that passively polls on a set interval for an exchange rate from the current base\n * asset to the current currency\n */\nexport class CurrencyRateController extends BaseController<\n typeof name,\n CurrencyRateState,\n CurrencyRateMessenger\n> {\n private mutex = new Mutex();\n\n private intervalId?: NodeJS.Timeout;\n\n private intervalDelay;\n\n private fetchExchangeRate;\n\n private includeUsdRate;\n\n /**\n * Creates a CurrencyRateController instance.\n *\n * @param options - Constructor options.\n * @param options.includeUsdRate - Keep track of the USD rate in addition to the current currency rate.\n * @param options.interval - The polling interval, in milliseconds.\n * @param options.messenger - A reference to the messaging system.\n * @param options.state - Initial state to set on this controller.\n * @param options.fetchExchangeRate - Fetches the exchange rate from an external API. This option is primarily meant for use in unit tests.\n */\n constructor({\n includeUsdRate = false,\n interval = 180000,\n messenger,\n state,\n fetchExchangeRate = defaultFetchExchangeRate,\n }: {\n includeUsdRate?: boolean;\n interval?: number;\n messenger: CurrencyRateMessenger;\n state?: Partial;\n fetchExchangeRate?: typeof defaultFetchExchangeRate;\n }) {\n super({\n name,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n this.includeUsdRate = includeUsdRate;\n this.intervalDelay = interval;\n this.fetchExchangeRate = fetchExchangeRate;\n }\n\n /**\n * Start polling for the currency rate.\n */\n async start() {\n await this.startPolling();\n }\n\n /**\n * Stop polling for the currency rate.\n */\n stop() {\n this.stopPolling();\n }\n\n /**\n * Prepare to discard this controller.\n *\n * This stops any active polling.\n */\n override destroy() {\n super.destroy();\n this.stopPolling();\n }\n\n /**\n * Sets a currency to track.\n *\n * @param currentCurrency - ISO 4217 currency code.\n */\n async setCurrentCurrency(currentCurrency: string) {\n this.update((state) => {\n state.pendingCurrentCurrency = currentCurrency;\n });\n await this.updateExchangeRate();\n }\n\n /**\n * Sets a new native currency.\n *\n * @param symbol - Symbol for the base asset.\n */\n async setNativeCurrency(symbol: string) {\n this.update((state) => {\n state.pendingNativeCurrency = symbol;\n });\n await this.updateExchangeRate();\n }\n\n private stopPolling() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n }\n\n /**\n * Starts a new polling interval.\n */\n private async startPolling(): Promise {\n this.stopPolling();\n // TODO: Expose polling currency rate update errors\n await safelyExecute(() => this.updateExchangeRate());\n this.intervalId = setInterval(async () => {\n await safelyExecute(() => this.updateExchangeRate());\n }, this.intervalDelay);\n }\n\n /**\n * Updates exchange rate for the current currency.\n *\n * @returns The controller state.\n */\n async updateExchangeRate(): Promise {\n const releaseLock = await this.mutex.acquire();\n const {\n currentCurrency: stateCurrentCurrency,\n nativeCurrency: stateNativeCurrency,\n pendingCurrentCurrency,\n pendingNativeCurrency,\n } = this.state;\n\n let conversionDate: number | null = null;\n let conversionRate: number | null = null;\n let usdConversionRate: number | null = null;\n const currentCurrency = pendingCurrentCurrency ?? stateCurrentCurrency;\n const nativeCurrency = pendingNativeCurrency ?? stateNativeCurrency;\n\n // For preloaded testnets (Rinkeby, Ropsten, Goerli, Kovan) we want to fetch exchange rate for real ETH.\n const nativeCurrencyForExchangeRate = Object.values(\n TESTNET_TICKER_SYMBOLS,\n ).includes(nativeCurrency)\n ? FALL_BACK_VS_CURRENCY // ETH\n : nativeCurrency;\n\n try {\n if (\n currentCurrency &&\n nativeCurrency &&\n // if either currency is an empty string we can skip the comparison\n // because it will result in an error from the api and ultimately\n // a null conversionRate either way.\n currentCurrency !== '' &&\n nativeCurrency !== ''\n ) {\n ({ conversionRate, usdConversionRate } = await this.fetchExchangeRate(\n currentCurrency,\n nativeCurrencyForExchangeRate,\n this.includeUsdRate,\n ));\n conversionDate = Date.now() / 1000;\n }\n } catch (error) {\n if (\n !(\n error instanceof Error &&\n error.message.includes('market does not exist for this coin pair')\n )\n ) {\n throw error;\n }\n } finally {\n try {\n this.update(() => {\n return {\n conversionDate,\n conversionRate,\n // we currently allow and handle an empty string as a valid nativeCurrency\n // in cases where a user has not entered a native ticker symbol for a custom network\n // currentCurrency is not from user input but this protects us from unexpected changes.\n nativeCurrency,\n currentCurrency,\n pendingCurrentCurrency: null,\n pendingNativeCurrency: null,\n usdConversionRate,\n };\n });\n } finally {\n releaseLock();\n }\n }\n return this.state;\n }\n}\n\nexport default CurrencyRateController;\n"]} \ No newline at end of file diff --git a/dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.d.ts b/dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.d.ts new file mode 100644 index 0000000000..06e5050ce2 --- /dev/null +++ b/dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.d.ts @@ -0,0 +1,77 @@ +import { Web3 } from '../../standards-types'; +export declare class ERC1155Standard { + private web3; + constructor(web3: Web3); + /** + * Query if contract implements ERC1155 URI Metadata interface. + * + * @param address - ERC1155 asset contract address. + * @returns Promise resolving to whether the contract implements ERC1155 URI Metadata interface. + */ + contractSupportsURIMetadataInterface: (address: string) => Promise; + /** + * Query if contract implements ERC1155 Token Receiver interface. + * + * @param address - ERC1155 asset contract address. + * @returns Promise resolving to whether the contract implements ERC1155 Token Receiver interface. + */ + contractSupportsTokenReceiverInterface: (address: string) => Promise; + /** + * Query if contract implements ERC1155 interface. + * + * @param address - ERC1155 asset contract address. + * @returns Promise resolving to whether the contract implements the base ERC1155 interface. + */ + contractSupportsBase1155Interface: (address: string) => Promise; + /** + * Query for tokenURI for a given asset. + * + * @param address - ERC1155 asset contract address. + * @param tokenId - ERC1155 asset identifier. + * @returns Promise resolving to the 'tokenURI'. + */ + getTokenURI: (address: string, tokenId: string) => Promise; + /** + * Query for balance of a given ERC1155 token. + * + * @param contractAddress - ERC1155 asset contract address. + * @param address - Wallet public address. + * @param tokenId - ERC1155 asset identifier. + * @returns Promise resolving to the 'balanceOf'. + */ + getBalanceOf: (contractAddress: string, address: string, tokenId: string) => Promise; + /** + * Transfer single ERC1155 token. + * When minting/creating tokens, the from arg MUST be set to 0x0 (i.e. zero address). + * When burning/destroying tokens, the to arg MUST be set to 0x0 (i.e. zero address). + * + * @param operator - ERC1155 token address. + * @param from - ERC1155 token holder. + * @param to - ERC1155 token recipient. + * @param id - ERC1155 token id. + * @param value - Number of tokens to be sent. + * @returns Promise resolving to the 'transferSingle'. + */ + transferSingle: (operator: string, from: string, to: string, id: string, value: string) => Promise; + /** + * Query if a contract implements an interface. + * + * @param address - ERC1155 asset contract address. + * @param interfaceId - Interface identifier. + * @returns Promise resolving to whether the contract implements `interfaceID`. + */ + private contractSupportsInterface; + /** + * Query if a contract implements an interface. + * + * @param address - Asset contract address. + * @param ipfsGateway - The user's preferred IPFS gateway. + * @param tokenId - tokenId of a given token in the contract. + * @returns Promise resolving an object containing the standard, tokenURI, symbol and name of the given contract/tokenId pair. + */ + getDetails: (address: string, ipfsGateway: string, tokenId?: string | undefined) => Promise<{ + standard: string; + tokenURI: string | undefined; + image: string | undefined; + }>; +} diff --git a/dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.js b/dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.js new file mode 100644 index 0000000000..034c2feb49 --- /dev/null +++ b/dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.js @@ -0,0 +1,173 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ERC1155Standard = void 0; +const metamask_eth_abis_1 = require("@metamask/metamask-eth-abis"); +const constants_1 = require("../../../../constants"); +const util_1 = require("../../../../util"); +class ERC1155Standard { + constructor(web3) { + /** + * Query if contract implements ERC1155 URI Metadata interface. + * + * @param address - ERC1155 asset contract address. + * @returns Promise resolving to whether the contract implements ERC1155 URI Metadata interface. + */ + this.contractSupportsURIMetadataInterface = (address) => __awaiter(this, void 0, void 0, function* () { + return this.contractSupportsInterface(address, constants_1.ERC1155_METADATA_URI_INTERFACE_ID); + }); + /** + * Query if contract implements ERC1155 Token Receiver interface. + * + * @param address - ERC1155 asset contract address. + * @returns Promise resolving to whether the contract implements ERC1155 Token Receiver interface. + */ + this.contractSupportsTokenReceiverInterface = (address) => __awaiter(this, void 0, void 0, function* () { + return this.contractSupportsInterface(address, constants_1.ERC1155_TOKEN_RECEIVER_INTERFACE_ID); + }); + /** + * Query if contract implements ERC1155 interface. + * + * @param address - ERC1155 asset contract address. + * @returns Promise resolving to whether the contract implements the base ERC1155 interface. + */ + this.contractSupportsBase1155Interface = (address) => __awaiter(this, void 0, void 0, function* () { + return this.contractSupportsInterface(address, constants_1.ERC1155_INTERFACE_ID); + }); + /** + * Query for tokenURI for a given asset. + * + * @param address - ERC1155 asset contract address. + * @param tokenId - ERC1155 asset identifier. + * @returns Promise resolving to the 'tokenURI'. + */ + this.getTokenURI = (address, tokenId) => __awaiter(this, void 0, void 0, function* () { + const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC1155).at(address); + return new Promise((resolve, reject) => { + contract.uri(tokenId, (error, result) => { + /* istanbul ignore if */ + if (error) { + reject(error); + return; + } + resolve(result); + }); + }); + }); + /** + * Query for balance of a given ERC1155 token. + * + * @param contractAddress - ERC1155 asset contract address. + * @param address - Wallet public address. + * @param tokenId - ERC1155 asset identifier. + * @returns Promise resolving to the 'balanceOf'. + */ + this.getBalanceOf = (contractAddress, address, tokenId) => __awaiter(this, void 0, void 0, function* () { + const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC1155).at(contractAddress); + return new Promise((resolve, reject) => { + contract.balanceOf(address, tokenId, (error, result) => { + /* istanbul ignore if */ + if (error) { + reject(error); + return; + } + resolve(result); + }); + }); + }); + /** + * Transfer single ERC1155 token. + * When minting/creating tokens, the from arg MUST be set to 0x0 (i.e. zero address). + * When burning/destroying tokens, the to arg MUST be set to 0x0 (i.e. zero address). + * + * @param operator - ERC1155 token address. + * @param from - ERC1155 token holder. + * @param to - ERC1155 token recipient. + * @param id - ERC1155 token id. + * @param value - Number of tokens to be sent. + * @returns Promise resolving to the 'transferSingle'. + */ + this.transferSingle = (operator, from, to, id, value) => __awaiter(this, void 0, void 0, function* () { + const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC1155).at(operator); + return new Promise((resolve, reject) => { + contract.transferSingle(operator, from, to, id, value, (error, result) => { + /* istanbul ignore if */ + if (error) { + reject(error); + return; + } + resolve(result); + }); + }); + }); + /** + * Query if a contract implements an interface. + * + * @param address - ERC1155 asset contract address. + * @param interfaceId - Interface identifier. + * @returns Promise resolving to whether the contract implements `interfaceID`. + */ + this.contractSupportsInterface = (address, interfaceId) => __awaiter(this, void 0, void 0, function* () { + const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC1155).at(address); + return new Promise((resolve, reject) => { + contract.supportsInterface(interfaceId, (error, result) => { + /* istanbul ignore if */ + if (error) { + reject(error); + return; + } + resolve(result); + }); + }); + }); + /** + * Query if a contract implements an interface. + * + * @param address - Asset contract address. + * @param ipfsGateway - The user's preferred IPFS gateway. + * @param tokenId - tokenId of a given token in the contract. + * @returns Promise resolving an object containing the standard, tokenURI, symbol and name of the given contract/tokenId pair. + */ + this.getDetails = (address, ipfsGateway, tokenId) => __awaiter(this, void 0, void 0, function* () { + const isERC1155 = yield this.contractSupportsBase1155Interface(address); + if (!isERC1155) { + throw new Error("This isn't a valid ERC1155 contract"); + } + let tokenURI, image; + if (tokenId) { + tokenURI = yield this.getTokenURI(address, tokenId); + if (tokenURI.startsWith('ipfs://')) { + tokenURI = (0, util_1.getFormattedIpfsUrl)(ipfsGateway, tokenURI, true); + } + try { + const response = yield (0, util_1.timeoutFetch)(tokenURI); + const object = yield response.json(); + image = object === null || object === void 0 ? void 0 : object.image; + if (image === null || image === void 0 ? void 0 : image.startsWith('ipfs://')) { + image = (0, util_1.getFormattedIpfsUrl)(ipfsGateway, image, true); + } + } + catch (_a) { + // ignore + } + } + // TODO consider querying to the metadata to get name. + return { + standard: constants_1.ERC1155, + tokenURI, + image, + }; + }); + this.web3 = web3; + } +} +exports.ERC1155Standard = ERC1155Standard; +//# sourceMappingURL=ERC1155Standard.js.map \ No newline at end of file diff --git a/dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.js.map b/dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.js.map new file mode 100644 index 0000000000..753a1a528b --- /dev/null +++ b/dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ERC1155Standard.js","sourceRoot":"","sources":["../../../../../src/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.ts"],"names":[],"mappings":"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","sourcesContent":["import { abiERC1155 } from '@metamask/metamask-eth-abis';\nimport {\n ERC1155,\n ERC1155_INTERFACE_ID,\n ERC1155_METADATA_URI_INTERFACE_ID,\n ERC1155_TOKEN_RECEIVER_INTERFACE_ID,\n} from '../../../../constants';\nimport { getFormattedIpfsUrl, timeoutFetch } from '../../../../util';\nimport { Web3 } from '../../standards-types';\n\nexport class ERC1155Standard {\n private web3: Web3;\n\n constructor(web3: Web3) {\n this.web3 = web3;\n }\n\n /**\n * Query if contract implements ERC1155 URI Metadata interface.\n *\n * @param address - ERC1155 asset contract address.\n * @returns Promise resolving to whether the contract implements ERC1155 URI Metadata interface.\n */\n contractSupportsURIMetadataInterface = async (\n address: string,\n ): Promise => {\n return this.contractSupportsInterface(\n address,\n ERC1155_METADATA_URI_INTERFACE_ID,\n );\n };\n\n /**\n * Query if contract implements ERC1155 Token Receiver interface.\n *\n * @param address - ERC1155 asset contract address.\n * @returns Promise resolving to whether the contract implements ERC1155 Token Receiver interface.\n */\n contractSupportsTokenReceiverInterface = async (\n address: string,\n ): Promise => {\n return this.contractSupportsInterface(\n address,\n ERC1155_TOKEN_RECEIVER_INTERFACE_ID,\n );\n };\n\n /**\n * Query if contract implements ERC1155 interface.\n *\n * @param address - ERC1155 asset contract address.\n * @returns Promise resolving to whether the contract implements the base ERC1155 interface.\n */\n contractSupportsBase1155Interface = async (\n address: string,\n ): Promise => {\n return this.contractSupportsInterface(address, ERC1155_INTERFACE_ID);\n };\n\n /**\n * Query for tokenURI for a given asset.\n *\n * @param address - ERC1155 asset contract address.\n * @param tokenId - ERC1155 asset identifier.\n * @returns Promise resolving to the 'tokenURI'.\n */\n getTokenURI = async (address: string, tokenId: string): Promise => {\n const contract = this.web3.eth.contract(abiERC1155).at(address);\n return new Promise((resolve, reject) => {\n contract.uri(tokenId, (error: Error, result: string) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n });\n });\n };\n\n /**\n * Query for balance of a given ERC1155 token.\n *\n * @param contractAddress - ERC1155 asset contract address.\n * @param address - Wallet public address.\n * @param tokenId - ERC1155 asset identifier.\n * @returns Promise resolving to the 'balanceOf'.\n */\n getBalanceOf = async (\n contractAddress: string,\n address: string,\n tokenId: string,\n ): Promise => {\n const contract = this.web3.eth.contract(abiERC1155).at(contractAddress);\n return new Promise((resolve, reject) => {\n contract.balanceOf(address, tokenId, (error: Error, result: number) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n });\n });\n };\n\n /**\n * Transfer single ERC1155 token.\n * When minting/creating tokens, the from arg MUST be set to 0x0 (i.e. zero address).\n * When burning/destroying tokens, the to arg MUST be set to 0x0 (i.e. zero address).\n *\n * @param operator - ERC1155 token address.\n * @param from - ERC1155 token holder.\n * @param to - ERC1155 token recipient.\n * @param id - ERC1155 token id.\n * @param value - Number of tokens to be sent.\n * @returns Promise resolving to the 'transferSingle'.\n */\n transferSingle = async (\n operator: string,\n from: string,\n to: string,\n id: string,\n value: string,\n ): Promise => {\n const contract = this.web3.eth.contract(abiERC1155).at(operator);\n return new Promise((resolve, reject) => {\n contract.transferSingle(\n operator,\n from,\n to,\n id,\n value,\n (error: Error, result: void) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n },\n );\n });\n };\n\n /**\n * Query if a contract implements an interface.\n *\n * @param address - ERC1155 asset contract address.\n * @param interfaceId - Interface identifier.\n * @returns Promise resolving to whether the contract implements `interfaceID`.\n */\n private contractSupportsInterface = async (\n address: string,\n interfaceId: string,\n ): Promise => {\n const contract = this.web3.eth.contract(abiERC1155).at(address);\n return new Promise((resolve, reject) => {\n contract.supportsInterface(\n interfaceId,\n (error: Error, result: boolean) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n },\n );\n });\n };\n\n /**\n * Query if a contract implements an interface.\n *\n * @param address - Asset contract address.\n * @param ipfsGateway - The user's preferred IPFS gateway.\n * @param tokenId - tokenId of a given token in the contract.\n * @returns Promise resolving an object containing the standard, tokenURI, symbol and name of the given contract/tokenId pair.\n */\n getDetails = async (\n address: string,\n ipfsGateway: string,\n tokenId?: string,\n ): Promise<{\n standard: string;\n tokenURI: string | undefined;\n image: string | undefined;\n }> => {\n const isERC1155 = await this.contractSupportsBase1155Interface(address);\n\n if (!isERC1155) {\n throw new Error(\"This isn't a valid ERC1155 contract\");\n }\n let tokenURI, image;\n\n if (tokenId) {\n tokenURI = await this.getTokenURI(address, tokenId);\n if (tokenURI.startsWith('ipfs://')) {\n tokenURI = getFormattedIpfsUrl(ipfsGateway, tokenURI, true);\n }\n\n try {\n const response = await timeoutFetch(tokenURI);\n const object = await response.json();\n image = object?.image;\n if (image?.startsWith('ipfs://')) {\n image = getFormattedIpfsUrl(ipfsGateway, image, true);\n }\n } catch {\n // ignore\n }\n }\n\n // TODO consider querying to the metadata to get name.\n return {\n standard: ERC1155,\n tokenURI,\n image,\n };\n };\n}\n"]} \ No newline at end of file diff --git a/dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.d.ts b/dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.d.ts new file mode 100644 index 0000000000..06f56d75d5 --- /dev/null +++ b/dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.d.ts @@ -0,0 +1,88 @@ +import { Web3 } from '../../standards-types'; +export declare class ERC721Standard { + private web3; + constructor(web3: Web3); + /** + * Query if contract implements ERC721Metadata interface. + * + * @param address - ERC721 asset contract address. + * @returns Promise resolving to whether the contract implements ERC721Metadata interface. + */ + contractSupportsMetadataInterface: (address: string) => Promise; + /** + * Query if contract implements ERC721Enumerable interface. + * + * @param address - ERC721 asset contract address. + * @returns Promise resolving to whether the contract implements ERC721Enumerable interface. + */ + contractSupportsEnumerableInterface: (address: string) => Promise; + /** + * Query if contract implements ERC721 interface. + * + * @param address - ERC721 asset contract address. + * @returns Promise resolving to whether the contract implements ERC721 interface. + */ + contractSupportsBase721Interface: (address: string) => Promise; + /** + * Enumerate assets assigned to an owner. + * + * @param address - ERC721 asset contract address. + * @param selectedAddress - Current account public address. + * @param index - A collectible counter less than `balanceOf(selectedAddress)`. + * @returns Promise resolving to token identifier for the 'index'th asset assigned to 'selectedAddress'. + */ + getCollectibleTokenId: (address: string, selectedAddress: string, index: number) => Promise; + /** + * Query for tokenURI for a given asset. + * + * @param address - ERC721 asset contract address. + * @param tokenId - ERC721 asset identifier. + * @returns Promise resolving to the 'tokenURI'. + */ + getTokenURI: (address: string, tokenId: string) => Promise; + /** + * Query for name for a given asset. + * + * @param address - ERC721 asset contract address. + * @returns Promise resolving to the 'name'. + */ + getAssetName: (address: string) => Promise; + /** + * Query for symbol for a given asset. + * + * @param address - ERC721 asset contract address. + * @returns Promise resolving to the 'symbol'. + */ + getAssetSymbol: (address: string) => Promise; + /** + * Query for owner for a given ERC721 asset. + * + * @param address - ERC721 asset contract address. + * @param tokenId - ERC721 asset identifier. + * @returns Promise resolving to the owner address. + */ + getOwnerOf(address: string, tokenId: string): Promise; + /** + * Query if a contract implements an interface. + * + * @param address - Asset contract address. + * @param interfaceId - Interface identifier. + * @returns Promise resolving to whether the contract implements `interfaceID`. + */ + private contractSupportsInterface; + /** + * Query if a contract implements an interface. + * + * @param address - Asset contract address. + * @param ipfsGateway - The user's preferred IPFS gateway. + * @param tokenId - tokenId of a given token in the contract. + * @returns Promise resolving an object containing the standard, tokenURI, symbol and name of the given contract/tokenId pair. + */ + getDetails: (address: string, ipfsGateway: string, tokenId?: string | undefined) => Promise<{ + standard: string; + tokenURI: string | undefined; + symbol: string | undefined; + name: string | undefined; + image: string | undefined; + }>; +} diff --git a/dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.js b/dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.js new file mode 100644 index 0000000000..b840109e29 --- /dev/null +++ b/dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.js @@ -0,0 +1,226 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ERC721Standard = void 0; +const metamask_eth_abis_1 = require("@metamask/metamask-eth-abis"); +const util_1 = require("../../../../util"); +const constants_1 = require("../../../../constants"); +class ERC721Standard { + constructor(web3) { + /** + * Query if contract implements ERC721Metadata interface. + * + * @param address - ERC721 asset contract address. + * @returns Promise resolving to whether the contract implements ERC721Metadata interface. + */ + this.contractSupportsMetadataInterface = (address) => __awaiter(this, void 0, void 0, function* () { + return this.contractSupportsInterface(address, constants_1.ERC721_METADATA_INTERFACE_ID); + }); + /** + * Query if contract implements ERC721Enumerable interface. + * + * @param address - ERC721 asset contract address. + * @returns Promise resolving to whether the contract implements ERC721Enumerable interface. + */ + this.contractSupportsEnumerableInterface = (address) => __awaiter(this, void 0, void 0, function* () { + return this.contractSupportsInterface(address, constants_1.ERC721_ENUMERABLE_INTERFACE_ID); + }); + /** + * Query if contract implements ERC721 interface. + * + * @param address - ERC721 asset contract address. + * @returns Promise resolving to whether the contract implements ERC721 interface. + */ + this.contractSupportsBase721Interface = (address) => __awaiter(this, void 0, void 0, function* () { + return this.contractSupportsInterface(address, constants_1.ERC721_INTERFACE_ID); + }); + /** + * Enumerate assets assigned to an owner. + * + * @param address - ERC721 asset contract address. + * @param selectedAddress - Current account public address. + * @param index - A collectible counter less than `balanceOf(selectedAddress)`. + * @returns Promise resolving to token identifier for the 'index'th asset assigned to 'selectedAddress'. + */ + this.getCollectibleTokenId = (address, selectedAddress, index) => __awaiter(this, void 0, void 0, function* () { + const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC721).at(address); + return new Promise((resolve, reject) => { + contract.tokenOfOwnerByIndex(selectedAddress, index, (error, result) => { + /* istanbul ignore if */ + if (error) { + reject(error); + return; + } + resolve(result); + }); + }); + }); + /** + * Query for tokenURI for a given asset. + * + * @param address - ERC721 asset contract address. + * @param tokenId - ERC721 asset identifier. + * @returns Promise resolving to the 'tokenURI'. + */ + this.getTokenURI = (address, tokenId) => __awaiter(this, void 0, void 0, function* () { + const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC721).at(address); + const supportsMetadata = yield this.contractSupportsMetadataInterface(address); + if (!supportsMetadata) { + throw new Error('Contract does not support ERC721 metadata interface.'); + } + return new Promise((resolve, reject) => { + contract.tokenURI(tokenId, (error, result) => { + /* istanbul ignore if */ + if (error) { + reject(error); + return; + } + resolve(result); + }); + }); + }); + /** + * Query for name for a given asset. + * + * @param address - ERC721 asset contract address. + * @returns Promise resolving to the 'name'. + */ + this.getAssetName = (address) => __awaiter(this, void 0, void 0, function* () { + const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC721).at(address); + return new Promise((resolve, reject) => { + contract.name((error, result) => { + /* istanbul ignore if */ + if (error) { + reject(error); + return; + } + resolve(result); + }); + }); + }); + /** + * Query for symbol for a given asset. + * + * @param address - ERC721 asset contract address. + * @returns Promise resolving to the 'symbol'. + */ + this.getAssetSymbol = (address) => __awaiter(this, void 0, void 0, function* () { + const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC721).at(address); + return new Promise((resolve, reject) => { + contract.symbol((error, result) => { + /* istanbul ignore if */ + if (error) { + reject(error); + return; + } + resolve(result); + }); + }); + }); + /** + * Query if a contract implements an interface. + * + * @param address - Asset contract address. + * @param interfaceId - Interface identifier. + * @returns Promise resolving to whether the contract implements `interfaceID`. + */ + this.contractSupportsInterface = (address, interfaceId) => __awaiter(this, void 0, void 0, function* () { + const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC721).at(address); + return new Promise((resolve, reject) => { + contract.supportsInterface(interfaceId, (error, result) => { + /* istanbul ignore if */ + if (error) { + reject(error); + return; + } + resolve(result); + }); + }); + }); + /** + * Query if a contract implements an interface. + * + * @param address - Asset contract address. + * @param ipfsGateway - The user's preferred IPFS gateway. + * @param tokenId - tokenId of a given token in the contract. + * @returns Promise resolving an object containing the standard, tokenURI, symbol and name of the given contract/tokenId pair. + */ + this.getDetails = (address, ipfsGateway, tokenId) => __awaiter(this, void 0, void 0, function* () { + const isERC721 = yield this.contractSupportsBase721Interface(address); + if (!isERC721) { + throw new Error("This isn't a valid ERC721 contract"); + } + let tokenURI, image, symbol, name; + // TODO upgrade to use Promise.allSettled for name/symbol when we can refactor to use es2020 in tsconfig + try { + symbol = yield this.getAssetSymbol(address); + } + catch (_a) { + // ignore + } + try { + name = yield this.getAssetName(address); + } + catch (_b) { + // ignore + } + if (tokenId) { + try { + tokenURI = yield this.getTokenURI(address, tokenId); + if (tokenURI.startsWith('ipfs://')) { + tokenURI = (0, util_1.getFormattedIpfsUrl)(ipfsGateway, tokenURI, true); + } + const response = yield (0, util_1.timeoutFetch)(tokenURI); + const object = yield response.json(); + image = object === null || object === void 0 ? void 0 : object.image; + if (image === null || image === void 0 ? void 0 : image.startsWith('ipfs://')) { + image = (0, util_1.getFormattedIpfsUrl)(ipfsGateway, image, true); + } + } + catch (_c) { + // ignore + } + } + return { + standard: constants_1.ERC721, + tokenURI, + symbol, + name, + image, + }; + }); + this.web3 = web3; + } + /** + * Query for owner for a given ERC721 asset. + * + * @param address - ERC721 asset contract address. + * @param tokenId - ERC721 asset identifier. + * @returns Promise resolving to the owner address. + */ + getOwnerOf(address, tokenId) { + return __awaiter(this, void 0, void 0, function* () { + const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC721).at(address); + return new Promise((resolve, reject) => { + contract.ownerOf(tokenId, (error, result) => { + /* istanbul ignore if */ + if (error) { + reject(error); + return; + } + resolve(result); + }); + }); + }); + } +} +exports.ERC721Standard = ERC721Standard; +//# sourceMappingURL=ERC721Standard.js.map \ No newline at end of file diff --git a/dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.js.map b/dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.js.map new file mode 100644 index 0000000000..a11354b5da --- /dev/null +++ b/dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ERC721Standard.js","sourceRoot":"","sources":["../../../../../src/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.ts"],"names":[],"mappings":"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","sourcesContent":["import { abiERC721 } from '@metamask/metamask-eth-abis';\nimport { Web3 } from '../../standards-types';\nimport { getFormattedIpfsUrl, timeoutFetch } from '../../../../util';\nimport {\n ERC721_INTERFACE_ID,\n ERC721_METADATA_INTERFACE_ID,\n ERC721_ENUMERABLE_INTERFACE_ID,\n ERC721,\n} from '../../../../constants';\n\nexport class ERC721Standard {\n private web3: Web3;\n\n constructor(web3: Web3) {\n this.web3 = web3;\n }\n\n /**\n * Query if contract implements ERC721Metadata interface.\n *\n * @param address - ERC721 asset contract address.\n * @returns Promise resolving to whether the contract implements ERC721Metadata interface.\n */\n contractSupportsMetadataInterface = async (\n address: string,\n ): Promise => {\n return this.contractSupportsInterface(\n address,\n ERC721_METADATA_INTERFACE_ID,\n );\n };\n\n /**\n * Query if contract implements ERC721Enumerable interface.\n *\n * @param address - ERC721 asset contract address.\n * @returns Promise resolving to whether the contract implements ERC721Enumerable interface.\n */\n contractSupportsEnumerableInterface = async (\n address: string,\n ): Promise => {\n return this.contractSupportsInterface(\n address,\n ERC721_ENUMERABLE_INTERFACE_ID,\n );\n };\n\n /**\n * Query if contract implements ERC721 interface.\n *\n * @param address - ERC721 asset contract address.\n * @returns Promise resolving to whether the contract implements ERC721 interface.\n */\n contractSupportsBase721Interface = async (\n address: string,\n ): Promise => {\n return this.contractSupportsInterface(address, ERC721_INTERFACE_ID);\n };\n\n /**\n * Enumerate assets assigned to an owner.\n *\n * @param address - ERC721 asset contract address.\n * @param selectedAddress - Current account public address.\n * @param index - A collectible counter less than `balanceOf(selectedAddress)`.\n * @returns Promise resolving to token identifier for the 'index'th asset assigned to 'selectedAddress'.\n */\n getCollectibleTokenId = async (\n address: string,\n selectedAddress: string,\n index: number,\n ): Promise => {\n const contract = this.web3.eth.contract(abiERC721).at(address);\n return new Promise((resolve, reject) => {\n contract.tokenOfOwnerByIndex(\n selectedAddress,\n index,\n (error: Error, result: string) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n },\n );\n });\n };\n\n /**\n * Query for tokenURI for a given asset.\n *\n * @param address - ERC721 asset contract address.\n * @param tokenId - ERC721 asset identifier.\n * @returns Promise resolving to the 'tokenURI'.\n */\n getTokenURI = async (address: string, tokenId: string): Promise => {\n const contract = this.web3.eth.contract(abiERC721).at(address);\n const supportsMetadata = await this.contractSupportsMetadataInterface(\n address,\n );\n if (!supportsMetadata) {\n throw new Error('Contract does not support ERC721 metadata interface.');\n }\n return new Promise((resolve, reject) => {\n contract.tokenURI(tokenId, (error: Error, result: string) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n });\n });\n };\n\n /**\n * Query for name for a given asset.\n *\n * @param address - ERC721 asset contract address.\n * @returns Promise resolving to the 'name'.\n */\n getAssetName = async (address: string): Promise => {\n const contract = this.web3.eth.contract(abiERC721).at(address);\n return new Promise((resolve, reject) => {\n contract.name((error: Error, result: string) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n });\n });\n };\n\n /**\n * Query for symbol for a given asset.\n *\n * @param address - ERC721 asset contract address.\n * @returns Promise resolving to the 'symbol'.\n */\n getAssetSymbol = async (address: string): Promise => {\n const contract = this.web3.eth.contract(abiERC721).at(address);\n return new Promise((resolve, reject) => {\n contract.symbol((error: Error, result: string) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n });\n });\n };\n\n /**\n * Query for owner for a given ERC721 asset.\n *\n * @param address - ERC721 asset contract address.\n * @param tokenId - ERC721 asset identifier.\n * @returns Promise resolving to the owner address.\n */\n async getOwnerOf(address: string, tokenId: string): Promise {\n const contract = this.web3.eth.contract(abiERC721).at(address);\n return new Promise((resolve, reject) => {\n contract.ownerOf(tokenId, (error: Error, result: string) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n });\n });\n }\n\n /**\n * Query if a contract implements an interface.\n *\n * @param address - Asset contract address.\n * @param interfaceId - Interface identifier.\n * @returns Promise resolving to whether the contract implements `interfaceID`.\n */\n private contractSupportsInterface = async (\n address: string,\n interfaceId: string,\n ): Promise => {\n const contract = this.web3.eth.contract(abiERC721).at(address);\n return new Promise((resolve, reject) => {\n contract.supportsInterface(\n interfaceId,\n (error: Error, result: boolean) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n },\n );\n });\n };\n\n /**\n * Query if a contract implements an interface.\n *\n * @param address - Asset contract address.\n * @param ipfsGateway - The user's preferred IPFS gateway.\n * @param tokenId - tokenId of a given token in the contract.\n * @returns Promise resolving an object containing the standard, tokenURI, symbol and name of the given contract/tokenId pair.\n */\n getDetails = async (\n address: string,\n ipfsGateway: string,\n tokenId?: string,\n ): Promise<{\n standard: string;\n tokenURI: string | undefined;\n symbol: string | undefined;\n name: string | undefined;\n image: string | undefined;\n }> => {\n const isERC721 = await this.contractSupportsBase721Interface(address);\n if (!isERC721) {\n throw new Error(\"This isn't a valid ERC721 contract\");\n }\n\n let tokenURI, image, symbol, name;\n\n // TODO upgrade to use Promise.allSettled for name/symbol when we can refactor to use es2020 in tsconfig\n try {\n symbol = await this.getAssetSymbol(address);\n } catch {\n // ignore\n }\n\n try {\n name = await this.getAssetName(address);\n } catch {\n // ignore\n }\n\n if (tokenId) {\n try {\n tokenURI = await this.getTokenURI(address, tokenId);\n if (tokenURI.startsWith('ipfs://')) {\n tokenURI = getFormattedIpfsUrl(ipfsGateway, tokenURI, true);\n }\n\n const response = await timeoutFetch(tokenURI);\n const object = await response.json();\n image = object?.image;\n if (image?.startsWith('ipfs://')) {\n image = getFormattedIpfsUrl(ipfsGateway, image, true);\n }\n } catch {\n // ignore\n }\n }\n\n return {\n standard: ERC721,\n tokenURI,\n symbol,\n name,\n image,\n };\n };\n}\n"]} \ No newline at end of file diff --git a/dist/assets/Standards/ERC20Standard.d.ts b/dist/assets/Standards/ERC20Standard.d.ts new file mode 100644 index 0000000000..3ef53b51a5 --- /dev/null +++ b/dist/assets/Standards/ERC20Standard.d.ts @@ -0,0 +1,42 @@ +/// +import { BN } from 'ethereumjs-util'; +import { Web3 } from './standards-types'; +export declare class ERC20Standard { + private web3; + constructor(web3: Web3); + /** + * Get balance or count for current account on specific asset contract. + * + * @param address - Asset ERC20 contract address. + * @param selectedAddress - Current account public address. + * @returns Promise resolving to BN object containing balance for current account on specific asset contract. + */ + getBalanceOf(address: string, selectedAddress: string): Promise; + /** + * Query for the decimals for a given ERC20 asset. + * + * @param address - ERC20 asset contract string. + * @returns Promise resolving to the 'decimals'. + */ + getTokenDecimals(address: string): Promise; + /** + * Query for symbol for a given ERC20 asset. + * + * @param address - ERC20 asset contract address. + * @returns Promise resolving to the 'symbol'. + */ + getTokenSymbol(address: string): Promise; + /** + * Query if a contract implements an interface. + * + * @param address - Asset contract address. + * @param userAddress - The public address for the currently active user's account. + * @returns Promise resolving an object containing the standard, decimals, symbol and balance of the given contract/userAddress pair. + */ + getDetails(address: string, userAddress?: string): Promise<{ + standard: string; + symbol: string | undefined; + decimals: string | undefined; + balance: BN | undefined; + }>; +} diff --git a/dist/assets/Standards/ERC20Standard.js b/dist/assets/Standards/ERC20Standard.js new file mode 100644 index 0000000000..ad0d74d39d --- /dev/null +++ b/dist/assets/Standards/ERC20Standard.js @@ -0,0 +1,134 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ERC20Standard = void 0; +const metamask_eth_abis_1 = require("@metamask/metamask-eth-abis"); +const ethereumjs_util_1 = require("ethereumjs-util"); +const utils_1 = require("ethers/lib/utils"); +const constants_1 = require("../../constants"); +class ERC20Standard { + constructor(web3) { + this.web3 = web3; + } + /** + * Get balance or count for current account on specific asset contract. + * + * @param address - Asset ERC20 contract address. + * @param selectedAddress - Current account public address. + * @returns Promise resolving to BN object containing balance for current account on specific asset contract. + */ + getBalanceOf(address, selectedAddress) { + return __awaiter(this, void 0, void 0, function* () { + const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC20).at(address); + return new Promise((resolve, reject) => { + contract.balanceOf(selectedAddress, (error, result) => { + /* istanbul ignore if */ + if (error) { + reject(error); + return; + } + resolve(result); + }); + }); + }); + } + /** + * Query for the decimals for a given ERC20 asset. + * + * @param address - ERC20 asset contract string. + * @returns Promise resolving to the 'decimals'. + */ + getTokenDecimals(address) { + return __awaiter(this, void 0, void 0, function* () { + const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC20).at(address); + return new Promise((resolve, reject) => { + contract.decimals((error, result) => { + /* istanbul ignore if */ + if (error) { + reject(error); + return; + } + resolve(result.toString()); + }); + }); + }); + } + /** + * Query for symbol for a given ERC20 asset. + * + * @param address - ERC20 asset contract address. + * @returns Promise resolving to the 'symbol'. + */ + getTokenSymbol(address) { + return __awaiter(this, void 0, void 0, function* () { + // Signature for calling `symbol()` + const payload = { to: address, data: '0x95d89b41' }; + return new Promise((resolve, reject) => { + this.web3.eth.call(payload, undefined, (error, result) => { + /* istanbul ignore if */ + if (error) { + reject(error); + return; + } + const abiCoder = new utils_1.AbiCoder(); + // Parse as string + try { + const decoded = abiCoder.decode(['string'], result)[0]; + if (decoded) { + resolve(decoded); + return; + } + } + catch (_a) { + // Ignore error + } + // Parse as bytes + try { + const utf8 = (0, ethereumjs_util_1.toUtf8)(result); + resolve(utf8); + return; + } + catch (_b) { + // Ignore error + } + reject(new Error('Failed to parse token symbol')); + }); + }); + }); + } + /** + * Query if a contract implements an interface. + * + * @param address - Asset contract address. + * @param userAddress - The public address for the currently active user's account. + * @returns Promise resolving an object containing the standard, decimals, symbol and balance of the given contract/userAddress pair. + */ + getDetails(address, userAddress) { + return __awaiter(this, void 0, void 0, function* () { + const [decimals, symbol] = yield Promise.all([ + this.getTokenDecimals(address), + this.getTokenSymbol(address), + ]); + let balance; + if (userAddress) { + balance = yield this.getBalanceOf(address, userAddress); + } + return { + decimals, + symbol, + balance, + standard: constants_1.ERC20, + }; + }); + } +} +exports.ERC20Standard = ERC20Standard; +//# sourceMappingURL=ERC20Standard.js.map \ No newline at end of file diff --git a/dist/assets/Standards/ERC20Standard.js.map b/dist/assets/Standards/ERC20Standard.js.map new file mode 100644 index 0000000000..0fdcc0a406 --- /dev/null +++ b/dist/assets/Standards/ERC20Standard.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ERC20Standard.js","sourceRoot":"","sources":["../../../src/assets/Standards/ERC20Standard.ts"],"names":[],"mappings":"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","sourcesContent":["import { abiERC20 } from '@metamask/metamask-eth-abis';\nimport { BN, toUtf8 } from 'ethereumjs-util';\nimport { AbiCoder } from 'ethers/lib/utils';\nimport { ERC20 } from '../../constants';\nimport { Web3 } from './standards-types';\n\nexport class ERC20Standard {\n private web3: Web3;\n\n constructor(web3: Web3) {\n this.web3 = web3;\n }\n\n /**\n * Get balance or count for current account on specific asset contract.\n *\n * @param address - Asset ERC20 contract address.\n * @param selectedAddress - Current account public address.\n * @returns Promise resolving to BN object containing balance for current account on specific asset contract.\n */\n async getBalanceOf(address: string, selectedAddress: string): Promise {\n const contract = this.web3.eth.contract(abiERC20).at(address);\n return new Promise((resolve, reject) => {\n contract.balanceOf(selectedAddress, (error: Error, result: BN) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n });\n });\n }\n\n /**\n * Query for the decimals for a given ERC20 asset.\n *\n * @param address - ERC20 asset contract string.\n * @returns Promise resolving to the 'decimals'.\n */\n async getTokenDecimals(address: string): Promise {\n const contract = this.web3.eth.contract(abiERC20).at(address);\n return new Promise((resolve, reject) => {\n contract.decimals((error: Error, result: BN | string) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result.toString());\n });\n });\n }\n\n /**\n * Query for symbol for a given ERC20 asset.\n *\n * @param address - ERC20 asset contract address.\n * @returns Promise resolving to the 'symbol'.\n */\n async getTokenSymbol(address: string): Promise {\n // Signature for calling `symbol()`\n const payload = { to: address, data: '0x95d89b41' };\n return new Promise((resolve, reject) => {\n this.web3.eth.call(payload, undefined, (error: Error, result: string) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n\n const abiCoder = new AbiCoder();\n\n // Parse as string\n try {\n const decoded = abiCoder.decode(['string'], result)[0];\n if (decoded) {\n resolve(decoded);\n return;\n }\n } catch {\n // Ignore error\n }\n\n // Parse as bytes\n try {\n const utf8 = toUtf8(result);\n resolve(utf8);\n return;\n } catch {\n // Ignore error\n }\n\n reject(new Error('Failed to parse token symbol'));\n });\n });\n }\n\n /**\n * Query if a contract implements an interface.\n *\n * @param address - Asset contract address.\n * @param userAddress - The public address for the currently active user's account.\n * @returns Promise resolving an object containing the standard, decimals, symbol and balance of the given contract/userAddress pair.\n */\n async getDetails(\n address: string,\n userAddress?: string,\n ): Promise<{\n standard: string;\n symbol: string | undefined;\n decimals: string | undefined;\n balance: BN | undefined;\n }> {\n const [decimals, symbol] = await Promise.all([\n this.getTokenDecimals(address),\n this.getTokenSymbol(address),\n ]);\n let balance;\n if (userAddress) {\n balance = await this.getBalanceOf(address, userAddress);\n }\n return {\n decimals,\n symbol,\n balance,\n standard: ERC20,\n };\n }\n}\n"]} \ No newline at end of file diff --git a/dist/assets/Standards/standards-types.d.ts b/dist/assets/Standards/standards-types.d.ts new file mode 100644 index 0000000000..8efcd998d0 --- /dev/null +++ b/dist/assets/Standards/standards-types.d.ts @@ -0,0 +1,14 @@ +import { abiERC20, abiERC1155, abiERC721 } from '@metamask/metamask-eth-abis'; +declare type Contract = { + at(address: string): any; +}; +export declare type Web3 = { + eth: { + call(payload: { + to: string; + data: string; + }, block: undefined, callback: (error: Error, result: string) => void): void; + contract(abi: typeof abiERC20 | typeof abiERC721 | typeof abiERC1155): Contract; + }; +}; +export {}; diff --git a/dist/assets/Standards/standards-types.js b/dist/assets/Standards/standards-types.js new file mode 100644 index 0000000000..a9479e91d8 --- /dev/null +++ b/dist/assets/Standards/standards-types.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=standards-types.js.map \ No newline at end of file diff --git a/dist/assets/Standards/standards-types.js.map b/dist/assets/Standards/standards-types.js.map new file mode 100644 index 0000000000..dbe4038382 --- /dev/null +++ b/dist/assets/Standards/standards-types.js.map @@ -0,0 +1 @@ +{"version":3,"file":"standards-types.js","sourceRoot":"","sources":["../../../src/assets/Standards/standards-types.ts"],"names":[],"mappings":"","sourcesContent":["import { abiERC20, abiERC1155, abiERC721 } from '@metamask/metamask-eth-abis';\n\ntype Contract = {\n at(address: string): any;\n};\n\nexport type Web3 = {\n eth: {\n call(\n payload: { to: string; data: string },\n block: undefined,\n callback: (error: Error, result: string) => void,\n ): void;\n contract(\n abi: typeof abiERC20 | typeof abiERC721 | typeof abiERC1155,\n ): Contract;\n };\n};\n"]} \ No newline at end of file diff --git a/dist/assets/TokenBalancesController.d.ts b/dist/assets/TokenBalancesController.d.ts new file mode 100644 index 0000000000..9cac44f43d --- /dev/null +++ b/dist/assets/TokenBalancesController.d.ts @@ -0,0 +1,69 @@ +/// +import { BN } from 'ethereumjs-util'; +import { BaseController, BaseConfig, BaseState } from '../BaseController'; +import type { PreferencesState } from '../user/PreferencesController'; +import { Token } from './TokenRatesController'; +import { TokensState } from './TokensController'; +import type { AssetsContractController } from './AssetsContractController'; +export { BN }; +/** + * @type TokenBalancesConfig + * + * Token balances controller configuration + * @property interval - Polling interval used to fetch new token balances + * @property tokens - List of tokens to track balances for + */ +export interface TokenBalancesConfig extends BaseConfig { + interval: number; + tokens: Token[]; +} +/** + * @type TokenBalancesState + * + * Token balances controller state + * @property contractBalances - Hash of token contract addresses to balances + */ +export interface TokenBalancesState extends BaseState { + contractBalances: { + [address: string]: BN; + }; +} +/** + * Controller that passively polls on a set interval token balances + * for tokens stored in the TokensController + */ +export declare class TokenBalancesController extends BaseController { + private handle?; + /** + * Name of this controller used during composition + */ + name: string; + private getSelectedAddress; + private getERC20BalanceOf; + /** + * Creates a TokenBalancesController instance. + * + * @param options - The controller options. + * @param options.onTokensStateChange - Allows subscribing to assets controller state changes. + * @param options.getSelectedAddress - Gets the current selected address. + * @param options.getERC20BalanceOf - Gets the balance of the given account at the given contract address. + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor({ onTokensStateChange, getSelectedAddress, getERC20BalanceOf, }: { + onTokensStateChange: (listener: (tokenState: TokensState) => void) => void; + getSelectedAddress: () => PreferencesState['selectedAddress']; + getERC20BalanceOf: AssetsContractController['getERC20BalanceOf']; + }, config?: Partial, state?: Partial); + /** + * Starts a new polling interval. + * + * @param interval - Polling interval used to fetch new token balances. + */ + poll(interval?: number): Promise; + /** + * Updates balances for all tokens. + */ + updateBalances(): Promise; +} +export default TokenBalancesController; diff --git a/dist/assets/TokenBalancesController.js b/dist/assets/TokenBalancesController.js new file mode 100644 index 0000000000..dd08cee0dd --- /dev/null +++ b/dist/assets/TokenBalancesController.js @@ -0,0 +1,94 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TokenBalancesController = exports.BN = void 0; +const ethereumjs_util_1 = require("ethereumjs-util"); +Object.defineProperty(exports, "BN", { enumerable: true, get: function () { return ethereumjs_util_1.BN; } }); +const BaseController_1 = require("../BaseController"); +const util_1 = require("../util"); +/** + * Controller that passively polls on a set interval token balances + * for tokens stored in the TokensController + */ +class TokenBalancesController extends BaseController_1.BaseController { + /** + * Creates a TokenBalancesController instance. + * + * @param options - The controller options. + * @param options.onTokensStateChange - Allows subscribing to assets controller state changes. + * @param options.getSelectedAddress - Gets the current selected address. + * @param options.getERC20BalanceOf - Gets the balance of the given account at the given contract address. + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor({ onTokensStateChange, getSelectedAddress, getERC20BalanceOf, }, config, state) { + super(config, state); + /** + * Name of this controller used during composition + */ + this.name = 'TokenBalancesController'; + this.defaultConfig = { + interval: 180000, + tokens: [], + }; + this.defaultState = { contractBalances: {} }; + this.initialize(); + onTokensStateChange(({ tokens, detectedTokens }) => { + this.configure({ tokens: [...tokens, ...detectedTokens] }); + this.updateBalances(); + }); + this.getSelectedAddress = getSelectedAddress; + this.getERC20BalanceOf = getERC20BalanceOf; + this.poll(); + } + /** + * Starts a new polling interval. + * + * @param interval - Polling interval used to fetch new token balances. + */ + poll(interval) { + return __awaiter(this, void 0, void 0, function* () { + interval && this.configure({ interval }, false, false); + this.handle && clearTimeout(this.handle); + yield (0, util_1.safelyExecute)(() => this.updateBalances()); + this.handle = setTimeout(() => { + this.poll(this.config.interval); + }, this.config.interval); + }); + } + /** + * Updates balances for all tokens. + */ + updateBalances() { + return __awaiter(this, void 0, void 0, function* () { + if (this.disabled) { + return; + } + const { tokens } = this.config; + const newContractBalances = {}; + for (const i in tokens) { + const { address } = tokens[i]; + try { + newContractBalances[address] = yield this.getERC20BalanceOf(address, this.getSelectedAddress()); + tokens[i].balanceError = null; + } + catch (error) { + newContractBalances[address] = new ethereumjs_util_1.BN(0); + tokens[i].balanceError = error; + } + } + this.update({ contractBalances: newContractBalances }); + }); + } +} +exports.TokenBalancesController = TokenBalancesController; +exports.default = TokenBalancesController; +//# sourceMappingURL=TokenBalancesController.js.map \ No newline at end of file diff --git a/dist/assets/TokenBalancesController.js.map b/dist/assets/TokenBalancesController.js.map new file mode 100644 index 0000000000..1c339b8317 --- /dev/null +++ b/dist/assets/TokenBalancesController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"TokenBalancesController.js","sourceRoot":"","sources":["../../src/assets/TokenBalancesController.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAqC;AAS5B,mFATA,oBAAE,OASA;AARX,sDAA0E;AAC1E,kCAAwC;AA+BxC;;;GAGG;AACH,MAAa,uBAAwB,SAAQ,+BAG5C;IAYC;;;;;;;;;OASG;IACH,YACE,EACE,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GAOlB,EACD,MAAqC,EACrC,KAAmC;QAEnC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAlCvB;;WAEG;QACM,SAAI,GAAG,yBAAyB,CAAC;QAgCxC,IAAI,CAAC,aAAa,GAAG;YACnB,QAAQ,EAAE,MAAM;YAChB,MAAM,EAAE,EAAE;SACX,CAAC;QACF,IAAI,CAAC,YAAY,GAAG,EAAE,gBAAgB,EAAE,EAAE,EAAE,CAAC;QAC7C,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,EAAE,EAAE;YACjD,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,GAAG,cAAc,CAAC,EAAE,CAAC,CAAC;YAC3D,IAAI,CAAC,cAAc,EAAE,CAAC;QACxB,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,kBAAkB,GAAG,kBAAkB,CAAC;QAC7C,IAAI,CAAC,iBAAiB,GAAG,iBAAiB,CAAC;QAC3C,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAED;;;;OAIG;IACG,IAAI,CAAC,QAAiB;;YAC1B,QAAQ,IAAI,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;YACvD,IAAI,CAAC,MAAM,IAAI,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACzC,MAAM,IAAA,oBAAa,EAAC,GAAG,EAAE,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC;YACjD,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAClC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC3B,CAAC;KAAA;IAED;;OAEG;IACG,cAAc;;YAClB,IAAI,IAAI,CAAC,QAAQ,EAAE;gBACjB,OAAO;aACR;YACD,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC;YAC/B,MAAM,mBAAmB,GAA8B,EAAE,CAAC;YAC1D,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE;gBACtB,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;gBAC9B,IAAI;oBACF,mBAAmB,CAAC,OAAO,CAAC,GAAG,MAAM,IAAI,CAAC,iBAAiB,CACzD,OAAO,EACP,IAAI,CAAC,kBAAkB,EAAE,CAC1B,CAAC;oBACF,MAAM,CAAC,CAAC,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC;iBAC/B;gBAAC,OAAO,KAAK,EAAE;oBACd,mBAAmB,CAAC,OAAO,CAAC,GAAG,IAAI,oBAAE,CAAC,CAAC,CAAC,CAAC;oBACzC,MAAM,CAAC,CAAC,CAAC,CAAC,YAAY,GAAG,KAAK,CAAC;iBAChC;aACF;YACD,IAAI,CAAC,MAAM,CAAC,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,CAAC,CAAC;QACzD,CAAC;KAAA;CACF;AA9FD,0DA8FC;AAED,kBAAe,uBAAuB,CAAC","sourcesContent":["import { BN } from 'ethereumjs-util';\nimport { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport { safelyExecute } from '../util';\nimport type { PreferencesState } from '../user/PreferencesController';\nimport { Token } from './TokenRatesController';\nimport { TokensState } from './TokensController';\nimport type { AssetsContractController } from './AssetsContractController';\n\n// TODO: Remove this export in the next major release\nexport { BN };\n\n/**\n * @type TokenBalancesConfig\n *\n * Token balances controller configuration\n * @property interval - Polling interval used to fetch new token balances\n * @property tokens - List of tokens to track balances for\n */\nexport interface TokenBalancesConfig extends BaseConfig {\n interval: number;\n tokens: Token[];\n}\n\n/**\n * @type TokenBalancesState\n *\n * Token balances controller state\n * @property contractBalances - Hash of token contract addresses to balances\n */\nexport interface TokenBalancesState extends BaseState {\n contractBalances: { [address: string]: BN };\n}\n\n/**\n * Controller that passively polls on a set interval token balances\n * for tokens stored in the TokensController\n */\nexport class TokenBalancesController extends BaseController<\n TokenBalancesConfig,\n TokenBalancesState\n> {\n private handle?: NodeJS.Timer;\n\n /**\n * Name of this controller used during composition\n */\n override name = 'TokenBalancesController';\n\n private getSelectedAddress: () => PreferencesState['selectedAddress'];\n\n private getERC20BalanceOf: AssetsContractController['getERC20BalanceOf'];\n\n /**\n * Creates a TokenBalancesController instance.\n *\n * @param options - The controller options.\n * @param options.onTokensStateChange - Allows subscribing to assets controller state changes.\n * @param options.getSelectedAddress - Gets the current selected address.\n * @param options.getERC20BalanceOf - Gets the balance of the given account at the given contract address.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n onTokensStateChange,\n getSelectedAddress,\n getERC20BalanceOf,\n }: {\n onTokensStateChange: (\n listener: (tokenState: TokensState) => void,\n ) => void;\n getSelectedAddress: () => PreferencesState['selectedAddress'];\n getERC20BalanceOf: AssetsContractController['getERC20BalanceOf'];\n },\n config?: Partial,\n state?: Partial,\n ) {\n super(config, state);\n this.defaultConfig = {\n interval: 180000,\n tokens: [],\n };\n this.defaultState = { contractBalances: {} };\n this.initialize();\n onTokensStateChange(({ tokens, detectedTokens }) => {\n this.configure({ tokens: [...tokens, ...detectedTokens] });\n this.updateBalances();\n });\n this.getSelectedAddress = getSelectedAddress;\n this.getERC20BalanceOf = getERC20BalanceOf;\n this.poll();\n }\n\n /**\n * Starts a new polling interval.\n *\n * @param interval - Polling interval used to fetch new token balances.\n */\n async poll(interval?: number): Promise {\n interval && this.configure({ interval }, false, false);\n this.handle && clearTimeout(this.handle);\n await safelyExecute(() => this.updateBalances());\n this.handle = setTimeout(() => {\n this.poll(this.config.interval);\n }, this.config.interval);\n }\n\n /**\n * Updates balances for all tokens.\n */\n async updateBalances() {\n if (this.disabled) {\n return;\n }\n const { tokens } = this.config;\n const newContractBalances: { [address: string]: BN } = {};\n for (const i in tokens) {\n const { address } = tokens[i];\n try {\n newContractBalances[address] = await this.getERC20BalanceOf(\n address,\n this.getSelectedAddress(),\n );\n tokens[i].balanceError = null;\n } catch (error) {\n newContractBalances[address] = new BN(0);\n tokens[i].balanceError = error;\n }\n }\n this.update({ contractBalances: newContractBalances });\n }\n}\n\nexport default TokenBalancesController;\n"]} \ No newline at end of file diff --git a/dist/assets/TokenDetectionController.d.ts b/dist/assets/TokenDetectionController.d.ts new file mode 100644 index 0000000000..298c2b1291 --- /dev/null +++ b/dist/assets/TokenDetectionController.d.ts @@ -0,0 +1,84 @@ +import { BaseController, BaseConfig, BaseState } from '../BaseController'; +import type { NetworkState } from '../network/NetworkController'; +import type { PreferencesState } from '../user/PreferencesController'; +import type { TokensController, TokensState } from './TokensController'; +import type { AssetsContractController } from './AssetsContractController'; +import { TokenListState } from './TokenListController'; +/** + * @type TokenDetectionConfig + * + * TokenDetection configuration + * @property interval - Polling interval used to fetch new token rates + * @property selectedAddress - Vault selected address + * @property chainId - The chain ID of the current network + * @property isDetectionEnabledFromPreferences - Boolean to track if detection is enabled from PreferencesController + * @property isDetectionEnabledForNetwork - Boolean to track if detected is enabled for current network + */ +export interface TokenDetectionConfig extends BaseConfig { + interval: number; + selectedAddress: string; + chainId: string; + isDetectionEnabledFromPreferences: boolean; + isDetectionEnabledForNetwork: boolean; +} +/** + * Controller that passively polls on a set interval for Tokens auto detection + */ +export declare class TokenDetectionController extends BaseController { + private intervalId?; + /** + * Name of this controller used during composition + */ + name: string; + private getBalancesInSingleCall; + private addDetectedTokens; + private getTokensState; + private getTokenListState; + /** + * Creates a TokenDetectionController instance. + * + * @param options - The controller options. + * @param options.onPreferencesStateChange - Allows subscribing to preferences controller state changes. + * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. + * @param options.onTokenListStateChange - Allows subscribing to token list controller state changes. + * @param options.getBalancesInSingleCall - Gets the balances of a list of tokens for the given address. + * @param options.addDetectedTokens - Add a list of detected tokens. + * @param options.getTokenListState - Gets the current state of the TokenList controller. + * @param options.getTokensState - Gets the current state of the Tokens controller. + * @param options.getNetworkState - Gets the state of the network controller. + * @param options.getPreferencesState - Gets the state of the preferences controller. + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor({ onPreferencesStateChange, onNetworkStateChange, onTokenListStateChange, getBalancesInSingleCall, addDetectedTokens, getTokenListState, getTokensState, getNetworkState, getPreferencesState, }: { + onPreferencesStateChange: (listener: (preferencesState: PreferencesState) => void) => void; + onNetworkStateChange: (listener: (networkState: NetworkState) => void) => void; + onTokenListStateChange: (listener: (tokenListState: TokenListState) => void) => void; + getBalancesInSingleCall: AssetsContractController['getBalancesInSingleCall']; + addDetectedTokens: TokensController['addDetectedTokens']; + getTokenListState: () => TokenListState; + getTokensState: () => TokensState; + getNetworkState: () => NetworkState; + getPreferencesState: () => PreferencesState; + }, config?: Partial, state?: Partial); + /** + * Start polling for detected tokens. + */ + start(): Promise; + /** + * Stop polling for detected tokens. + */ + stop(): void; + private stopPolling; + /** + * Starts a new polling interval. + * + * @param interval - An interval on which to poll. + */ + private startPolling; + /** + * Triggers asset ERC20 token auto detection for each contract address in contract metadata on mainnet. + */ + detectTokens(): Promise; +} +export default TokenDetectionController; diff --git a/dist/assets/TokenDetectionController.js b/dist/assets/TokenDetectionController.js new file mode 100644 index 0000000000..d873dc0778 --- /dev/null +++ b/dist/assets/TokenDetectionController.js @@ -0,0 +1,184 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TokenDetectionController = void 0; +const BaseController_1 = require("../BaseController"); +const util_1 = require("../util"); +const DEFAULT_INTERVAL = 180000; +/** + * Controller that passively polls on a set interval for Tokens auto detection + */ +class TokenDetectionController extends BaseController_1.BaseController { + /** + * Creates a TokenDetectionController instance. + * + * @param options - The controller options. + * @param options.onPreferencesStateChange - Allows subscribing to preferences controller state changes. + * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. + * @param options.onTokenListStateChange - Allows subscribing to token list controller state changes. + * @param options.getBalancesInSingleCall - Gets the balances of a list of tokens for the given address. + * @param options.addDetectedTokens - Add a list of detected tokens. + * @param options.getTokenListState - Gets the current state of the TokenList controller. + * @param options.getTokensState - Gets the current state of the Tokens controller. + * @param options.getNetworkState - Gets the state of the network controller. + * @param options.getPreferencesState - Gets the state of the preferences controller. + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor({ onPreferencesStateChange, onNetworkStateChange, onTokenListStateChange, getBalancesInSingleCall, addDetectedTokens, getTokenListState, getTokensState, getNetworkState, getPreferencesState, }, config, state) { + const { provider: { chainId: defaultChainId }, } = getNetworkState(); + const { useTokenDetection: defaultUseTokenDetection } = getPreferencesState(); + super(config, state); + /** + * Name of this controller used during composition + */ + this.name = 'TokenDetectionController'; + this.defaultConfig = Object.assign({ interval: DEFAULT_INTERVAL, selectedAddress: '', disabled: true, chainId: defaultChainId, isDetectionEnabledFromPreferences: defaultUseTokenDetection, isDetectionEnabledForNetwork: (0, util_1.isTokenDetectionSupportedForNetwork)(defaultChainId) }, config); + this.initialize(); + this.getTokensState = getTokensState; + this.getTokenListState = getTokenListState; + this.addDetectedTokens = addDetectedTokens; + this.getBalancesInSingleCall = getBalancesInSingleCall; + onTokenListStateChange(({ tokenList }) => { + const hasTokens = Object.keys(tokenList).length; + if (hasTokens) { + this.detectTokens(); + } + }); + onPreferencesStateChange(({ selectedAddress, useTokenDetection }) => { + const { selectedAddress: currentSelectedAddress, isDetectionEnabledFromPreferences, } = this.config; + const isSelectedAddressChanged = selectedAddress !== currentSelectedAddress; + const isDetectionChangedFromPreferences = isDetectionEnabledFromPreferences !== useTokenDetection; + this.configure({ + isDetectionEnabledFromPreferences: useTokenDetection, + selectedAddress, + }); + if (useTokenDetection && + (isSelectedAddressChanged || isDetectionChangedFromPreferences)) { + this.detectTokens(); + } + }); + onNetworkStateChange(({ provider: { chainId } }) => { + const { chainId: currentChainId } = this.config; + const isDetectionEnabledForNetwork = (0, util_1.isTokenDetectionSupportedForNetwork)(chainId); + const isChainIdChanged = currentChainId !== chainId; + this.configure({ + chainId, + isDetectionEnabledForNetwork, + }); + if (isDetectionEnabledForNetwork && isChainIdChanged) { + this.detectTokens(); + } + }); + } + /** + * Start polling for detected tokens. + */ + start() { + return __awaiter(this, void 0, void 0, function* () { + this.configure({ disabled: false }); + yield this.startPolling(); + }); + } + /** + * Stop polling for detected tokens. + */ + stop() { + this.configure({ disabled: true }); + this.stopPolling(); + } + stopPolling() { + if (this.intervalId) { + clearInterval(this.intervalId); + } + } + /** + * Starts a new polling interval. + * + * @param interval - An interval on which to poll. + */ + startPolling(interval) { + return __awaiter(this, void 0, void 0, function* () { + interval && this.configure({ interval }, false, false); + this.stopPolling(); + yield this.detectTokens(); + this.intervalId = setInterval(() => __awaiter(this, void 0, void 0, function* () { + yield this.detectTokens(); + }), this.config.interval); + }); + } + /** + * Triggers asset ERC20 token auto detection for each contract address in contract metadata on mainnet. + */ + detectTokens() { + return __awaiter(this, void 0, void 0, function* () { + const { disabled, isDetectionEnabledForNetwork, isDetectionEnabledFromPreferences, } = this.config; + if (disabled || + !isDetectionEnabledForNetwork || + !isDetectionEnabledFromPreferences) { + return; + } + const { tokens } = this.getTokensState(); + const { selectedAddress } = this.config; + const tokensAddresses = tokens.map( + /* istanbul ignore next*/ (token) => token.address.toLowerCase()); + const { tokenList } = this.getTokenListState(); + const tokensToDetect = []; + for (const address in tokenList) { + if (!tokensAddresses.includes(address)) { + tokensToDetect.push(address); + } + } + const sliceOfTokensToDetect = []; + sliceOfTokensToDetect[0] = tokensToDetect.slice(0, 1000); + sliceOfTokensToDetect[1] = tokensToDetect.slice(1000, tokensToDetect.length - 1); + /* istanbul ignore else */ + if (!selectedAddress) { + return; + } + for (const tokensSlice of sliceOfTokensToDetect) { + if (tokensSlice.length === 0) { + break; + } + yield (0, util_1.safelyExecute)(() => __awaiter(this, void 0, void 0, function* () { + const balances = yield this.getBalancesInSingleCall(selectedAddress, tokensSlice); + const tokensToAdd = []; + for (const tokenAddress in balances) { + let ignored; + /* istanbul ignore else */ + const { ignoredTokens } = this.getTokensState(); + if (ignoredTokens.length) { + ignored = ignoredTokens.find((ignoredTokenAddress) => ignoredTokenAddress === (0, util_1.toChecksumHexAddress)(tokenAddress)); + } + const caseInsensitiveTokenKey = Object.keys(tokenList).find((i) => i.toLowerCase() === tokenAddress.toLowerCase()) || ''; + if (ignored === undefined) { + const { decimals, symbol, aggregators, iconUrl } = tokenList[caseInsensitiveTokenKey]; + tokensToAdd.push({ + address: tokenAddress, + decimals, + symbol, + aggregators, + image: iconUrl, + isERC721: false, + }); + } + } + if (tokensToAdd.length) { + yield this.addDetectedTokens(tokensToAdd); + } + })); + } + }); + } +} +exports.TokenDetectionController = TokenDetectionController; +exports.default = TokenDetectionController; +//# sourceMappingURL=TokenDetectionController.js.map \ No newline at end of file diff --git a/dist/assets/TokenDetectionController.js.map b/dist/assets/TokenDetectionController.js.map new file mode 100644 index 0000000000..da45459977 --- /dev/null +++ b/dist/assets/TokenDetectionController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"TokenDetectionController.js","sourceRoot":"","sources":["../../src/assets/TokenDetectionController.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport type { NetworkState } from '../network/NetworkController';\nimport type { PreferencesState } from '../user/PreferencesController';\nimport {\n safelyExecute,\n toChecksumHexAddress,\n isTokenDetectionSupportedForNetwork,\n} from '../util';\nimport type { TokensController, TokensState } from './TokensController';\nimport type { AssetsContractController } from './AssetsContractController';\nimport { Token } from './TokenRatesController';\nimport { TokenListState } from './TokenListController';\n\nconst DEFAULT_INTERVAL = 180000;\n\n/**\n * @type TokenDetectionConfig\n *\n * TokenDetection configuration\n * @property interval - Polling interval used to fetch new token rates\n * @property selectedAddress - Vault selected address\n * @property chainId - The chain ID of the current network\n * @property isDetectionEnabledFromPreferences - Boolean to track if detection is enabled from PreferencesController\n * @property isDetectionEnabledForNetwork - Boolean to track if detected is enabled for current network\n */\nexport interface TokenDetectionConfig extends BaseConfig {\n interval: number;\n selectedAddress: string;\n chainId: string;\n isDetectionEnabledFromPreferences: boolean;\n isDetectionEnabledForNetwork: boolean;\n}\n\n/**\n * Controller that passively polls on a set interval for Tokens auto detection\n */\nexport class TokenDetectionController extends BaseController<\n TokenDetectionConfig,\n BaseState\n> {\n private intervalId?: NodeJS.Timeout;\n\n /**\n * Name of this controller used during composition\n */\n override name = 'TokenDetectionController';\n\n private getBalancesInSingleCall: AssetsContractController['getBalancesInSingleCall'];\n\n private addDetectedTokens: TokensController['addDetectedTokens'];\n\n private getTokensState: () => TokensState;\n\n private getTokenListState: () => TokenListState;\n\n /**\n * Creates a TokenDetectionController instance.\n *\n * @param options - The controller options.\n * @param options.onPreferencesStateChange - Allows subscribing to preferences controller state changes.\n * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.\n * @param options.onTokenListStateChange - Allows subscribing to token list controller state changes.\n * @param options.getBalancesInSingleCall - Gets the balances of a list of tokens for the given address.\n * @param options.addDetectedTokens - Add a list of detected tokens.\n * @param options.getTokenListState - Gets the current state of the TokenList controller.\n * @param options.getTokensState - Gets the current state of the Tokens controller.\n * @param options.getNetworkState - Gets the state of the network controller.\n * @param options.getPreferencesState - Gets the state of the preferences controller.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n onPreferencesStateChange,\n onNetworkStateChange,\n onTokenListStateChange,\n getBalancesInSingleCall,\n addDetectedTokens,\n getTokenListState,\n getTokensState,\n getNetworkState,\n getPreferencesState,\n }: {\n onPreferencesStateChange: (\n listener: (preferencesState: PreferencesState) => void,\n ) => void;\n onNetworkStateChange: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n onTokenListStateChange: (\n listener: (tokenListState: TokenListState) => void,\n ) => void;\n getBalancesInSingleCall: AssetsContractController['getBalancesInSingleCall'];\n addDetectedTokens: TokensController['addDetectedTokens'];\n getTokenListState: () => TokenListState;\n getTokensState: () => TokensState;\n getNetworkState: () => NetworkState;\n getPreferencesState: () => PreferencesState;\n },\n config?: Partial,\n state?: Partial,\n ) {\n const {\n provider: { chainId: defaultChainId },\n } = getNetworkState();\n const { useTokenDetection: defaultUseTokenDetection } =\n getPreferencesState();\n\n super(config, state);\n this.defaultConfig = {\n interval: DEFAULT_INTERVAL,\n selectedAddress: '',\n disabled: true,\n chainId: defaultChainId,\n isDetectionEnabledFromPreferences: defaultUseTokenDetection,\n isDetectionEnabledForNetwork:\n isTokenDetectionSupportedForNetwork(defaultChainId),\n ...config,\n };\n\n this.initialize();\n this.getTokensState = getTokensState;\n this.getTokenListState = getTokenListState;\n this.addDetectedTokens = addDetectedTokens;\n this.getBalancesInSingleCall = getBalancesInSingleCall;\n\n onTokenListStateChange(({ tokenList }) => {\n const hasTokens = Object.keys(tokenList).length;\n\n if (hasTokens) {\n this.detectTokens();\n }\n });\n\n onPreferencesStateChange(({ selectedAddress, useTokenDetection }) => {\n const {\n selectedAddress: currentSelectedAddress,\n isDetectionEnabledFromPreferences,\n } = this.config;\n const isSelectedAddressChanged =\n selectedAddress !== currentSelectedAddress;\n const isDetectionChangedFromPreferences =\n isDetectionEnabledFromPreferences !== useTokenDetection;\n\n this.configure({\n isDetectionEnabledFromPreferences: useTokenDetection,\n selectedAddress,\n });\n\n if (\n useTokenDetection &&\n (isSelectedAddressChanged || isDetectionChangedFromPreferences)\n ) {\n this.detectTokens();\n }\n });\n\n onNetworkStateChange(({ provider: { chainId } }) => {\n const { chainId: currentChainId } = this.config;\n const isDetectionEnabledForNetwork =\n isTokenDetectionSupportedForNetwork(chainId);\n const isChainIdChanged = currentChainId !== chainId;\n\n this.configure({\n chainId,\n isDetectionEnabledForNetwork,\n });\n\n if (isDetectionEnabledForNetwork && isChainIdChanged) {\n this.detectTokens();\n }\n });\n }\n\n /**\n * Start polling for detected tokens.\n */\n async start() {\n this.configure({ disabled: false });\n await this.startPolling();\n }\n\n /**\n * Stop polling for detected tokens.\n */\n stop() {\n this.configure({ disabled: true });\n this.stopPolling();\n }\n\n private stopPolling() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n }\n\n /**\n * Starts a new polling interval.\n *\n * @param interval - An interval on which to poll.\n */\n private async startPolling(interval?: number): Promise {\n interval && this.configure({ interval }, false, false);\n this.stopPolling();\n await this.detectTokens();\n this.intervalId = setInterval(async () => {\n await this.detectTokens();\n }, this.config.interval);\n }\n\n /**\n * Triggers asset ERC20 token auto detection for each contract address in contract metadata on mainnet.\n */\n async detectTokens() {\n const {\n disabled,\n isDetectionEnabledForNetwork,\n isDetectionEnabledFromPreferences,\n } = this.config;\n if (\n disabled ||\n !isDetectionEnabledForNetwork ||\n !isDetectionEnabledFromPreferences\n ) {\n return;\n }\n const { tokens } = this.getTokensState();\n const { selectedAddress } = this.config;\n\n const tokensAddresses = tokens.map(\n /* istanbul ignore next*/ (token) => token.address.toLowerCase(),\n );\n const { tokenList } = this.getTokenListState();\n const tokensToDetect: string[] = [];\n for (const address in tokenList) {\n if (!tokensAddresses.includes(address)) {\n tokensToDetect.push(address);\n }\n }\n const sliceOfTokensToDetect = [];\n sliceOfTokensToDetect[0] = tokensToDetect.slice(0, 1000);\n sliceOfTokensToDetect[1] = tokensToDetect.slice(\n 1000,\n tokensToDetect.length - 1,\n );\n\n /* istanbul ignore else */\n if (!selectedAddress) {\n return;\n }\n\n for (const tokensSlice of sliceOfTokensToDetect) {\n if (tokensSlice.length === 0) {\n break;\n }\n\n await safelyExecute(async () => {\n const balances = await this.getBalancesInSingleCall(\n selectedAddress,\n tokensSlice,\n );\n const tokensToAdd: Token[] = [];\n for (const tokenAddress in balances) {\n let ignored;\n /* istanbul ignore else */\n const { ignoredTokens } = this.getTokensState();\n if (ignoredTokens.length) {\n ignored = ignoredTokens.find(\n (ignoredTokenAddress) =>\n ignoredTokenAddress === toChecksumHexAddress(tokenAddress),\n );\n }\n const caseInsensitiveTokenKey =\n Object.keys(tokenList).find(\n (i) => i.toLowerCase() === tokenAddress.toLowerCase(),\n ) || '';\n\n if (ignored === undefined) {\n const { decimals, symbol, aggregators, iconUrl } =\n tokenList[caseInsensitiveTokenKey];\n tokensToAdd.push({\n address: tokenAddress,\n decimals,\n symbol,\n aggregators,\n image: iconUrl,\n isERC721: false,\n });\n }\n }\n\n if (tokensToAdd.length) {\n await this.addDetectedTokens(tokensToAdd);\n }\n });\n }\n }\n}\n\nexport default TokenDetectionController;\n"]} \ No newline at end of file diff --git a/dist/assets/TokenListController.d.ts b/dist/assets/TokenListController.d.ts new file mode 100644 index 0000000000..c874988238 --- /dev/null +++ b/dist/assets/TokenListController.d.ts @@ -0,0 +1,101 @@ +import type { Patch } from 'immer'; +import { BaseController } from '../BaseControllerV2'; +import type { RestrictedControllerMessenger } from '../ControllerMessenger'; +import { NetworkState } from '../network/NetworkController'; +declare const name = "TokenListController"; +export declare type TokenListToken = { + name: string; + symbol: string; + decimals: number; + address: string; + occurrences: number; + aggregators: string[]; + iconUrl: string; +}; +export declare type TokenListMap = Record; +declare type DataCache = { + timestamp: number; + data: TokenListMap; +}; +declare type TokensChainsCache = { + [chainSlug: string]: DataCache; +}; +export declare type TokenListState = { + tokenList: TokenListMap; + tokensChainsCache: TokensChainsCache; +}; +export declare type TokenListStateChange = { + type: `${typeof name}:stateChange`; + payload: [TokenListState, Patch[]]; +}; +export declare type GetTokenListState = { + type: `${typeof name}:getState`; + handler: () => TokenListState; +}; +declare type TokenListMessenger = RestrictedControllerMessenger; +/** + * Controller that passively polls on a set interval for the list of tokens from metaswaps api + */ +export declare class TokenListController extends BaseController { + private mutex; + private intervalId?; + private intervalDelay; + private cacheRefreshThreshold; + private chainId; + private abortController; + /** + * Creates a TokenListController instance. + * + * @param options - The controller options. + * @param options.chainId - The chain ID of the current network. + * @param options.onNetworkStateChange - A function for registering an event handler for network state changes. + * @param options.interval - The polling interval, in milliseconds. + * @param options.cacheRefreshThreshold - The token cache expiry time, in milliseconds. + * @param options.messenger - A restricted controller messenger. + * @param options.state - Initial state to set on this controller. + */ + constructor({ chainId, onNetworkStateChange, interval, cacheRefreshThreshold, messenger, state, }: { + chainId: string; + onNetworkStateChange: (listener: (networkState: NetworkState) => void) => void; + interval?: number; + cacheRefreshThreshold?: number; + messenger: TokenListMessenger; + state?: Partial; + }); + /** + * Start polling for the token list. + */ + start(): Promise; + /** + * Restart polling for the token list. + */ + restart(): Promise; + /** + * Stop polling for the token list. + */ + stop(): void; + /** + * Prepare to discard this controller. + * + * This stops any active polling. + */ + destroy(): void; + private stopPolling; + /** + * Starts a new polling interval. + */ + private startPolling; + /** + * Fetching token list from the Token Service API. + */ + fetchTokenList(): Promise; + /** + * Checks if the Cache timestamp is valid, + * if yes data in cache will be returned + * otherwise null will be returned. + * + * @returns The cached data, or `null` if the cache was expired. + */ + fetchFromCache(): Promise; +} +export default TokenListController; diff --git a/dist/assets/TokenListController.js b/dist/assets/TokenListController.js new file mode 100644 index 0000000000..48b5c7115f --- /dev/null +++ b/dist/assets/TokenListController.js @@ -0,0 +1,207 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TokenListController = void 0; +const async_mutex_1 = require("async-mutex"); +const abort_controller_1 = require("abort-controller"); +const BaseControllerV2_1 = require("../BaseControllerV2"); +const util_1 = require("../util"); +const token_service_1 = require("../apis/token-service"); +const assetsUtil_1 = require("./assetsUtil"); +const DEFAULT_INTERVAL = 24 * 60 * 60 * 1000; +const DEFAULT_THRESHOLD = 24 * 60 * 60 * 1000; +const name = 'TokenListController'; +const metadata = { + tokenList: { persist: true, anonymous: true }, + tokensChainsCache: { persist: true, anonymous: true }, +}; +const defaultState = { + tokenList: {}, + tokensChainsCache: {}, +}; +/** + * Controller that passively polls on a set interval for the list of tokens from metaswaps api + */ +class TokenListController extends BaseControllerV2_1.BaseController { + /** + * Creates a TokenListController instance. + * + * @param options - The controller options. + * @param options.chainId - The chain ID of the current network. + * @param options.onNetworkStateChange - A function for registering an event handler for network state changes. + * @param options.interval - The polling interval, in milliseconds. + * @param options.cacheRefreshThreshold - The token cache expiry time, in milliseconds. + * @param options.messenger - A restricted controller messenger. + * @param options.state - Initial state to set on this controller. + */ + constructor({ chainId, onNetworkStateChange, interval = DEFAULT_INTERVAL, cacheRefreshThreshold = DEFAULT_THRESHOLD, messenger, state, }) { + super({ + name, + metadata, + messenger, + state: Object.assign(Object.assign({}, defaultState), state), + }); + this.mutex = new async_mutex_1.Mutex(); + this.intervalDelay = interval; + this.cacheRefreshThreshold = cacheRefreshThreshold; + this.chainId = chainId; + this.abortController = new abort_controller_1.AbortController(); + onNetworkStateChange((networkState) => __awaiter(this, void 0, void 0, function* () { + if (this.chainId !== networkState.provider.chainId) { + this.abortController.abort(); + this.abortController = new abort_controller_1.AbortController(); + this.chainId = networkState.provider.chainId; + // Ensure tokenList is referencing data from correct network + this.update(() => { + var _a; + return Object.assign(Object.assign({}, this.state), { tokenList: ((_a = this.state.tokensChainsCache[this.chainId]) === null || _a === void 0 ? void 0 : _a.data) || {} }); + }); + yield this.restart(); + } + })); + } + /** + * Start polling for the token list. + */ + start() { + return __awaiter(this, void 0, void 0, function* () { + if (!(0, util_1.isTokenDetectionSupportedForNetwork)(this.chainId)) { + return; + } + yield this.startPolling(); + }); + } + /** + * Restart polling for the token list. + */ + restart() { + return __awaiter(this, void 0, void 0, function* () { + this.stopPolling(); + yield this.startPolling(); + }); + } + /** + * Stop polling for the token list. + */ + stop() { + this.stopPolling(); + } + /** + * Prepare to discard this controller. + * + * This stops any active polling. + */ + destroy() { + super.destroy(); + this.stopPolling(); + } + stopPolling() { + if (this.intervalId) { + clearInterval(this.intervalId); + } + } + /** + * Starts a new polling interval. + */ + startPolling() { + return __awaiter(this, void 0, void 0, function* () { + yield (0, util_1.safelyExecute)(() => this.fetchTokenList()); + this.intervalId = setInterval(() => __awaiter(this, void 0, void 0, function* () { + yield (0, util_1.safelyExecute)(() => this.fetchTokenList()); + }), this.intervalDelay); + }); + } + /** + * Fetching token list from the Token Service API. + */ + fetchTokenList() { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const releaseLock = yield this.mutex.acquire(); + try { + const { tokensChainsCache } = this.state; + let tokenList = {}; + const cachedTokens = yield (0, util_1.safelyExecute)(() => this.fetchFromCache()); + if (cachedTokens) { + // Use non-expired cached tokens + tokenList = Object.assign({}, cachedTokens); + } + else { + // Fetch fresh token list + const tokensFromAPI = yield (0, util_1.safelyExecute)(() => (0, token_service_1.fetchTokenList)(this.chainId, this.abortController.signal)); + if (!tokensFromAPI) { + // Fallback to expired cached tokens + tokenList = Object.assign({}, (((_a = tokensChainsCache[this.chainId]) === null || _a === void 0 ? void 0 : _a.data) || {})); + this.update(() => { + return { + tokenList, + tokensChainsCache, + }; + }); + return; + } + // Filtering out tokens with less than 3 occurrences and native tokens + const filteredTokenList = tokensFromAPI.filter((token) => token.occurrences && + token.occurrences >= 3 && + token.address !== '0x0000000000000000000000000000000000000000'); + // Removing the tokens with symbol conflicts + const symbolsList = filteredTokenList.map((token) => token.symbol); + const duplicateSymbols = [ + ...new Set(symbolsList.filter((symbol, index) => symbolsList.indexOf(symbol) !== index)), + ]; + const uniqueTokenList = filteredTokenList.filter((token) => !duplicateSymbols.includes(token.symbol)); + for (const token of uniqueTokenList) { + const formattedToken = Object.assign(Object.assign({}, token), { aggregators: (0, assetsUtil_1.formatAggregatorNames)(token.aggregators), iconUrl: (0, assetsUtil_1.formatIconUrlWithProxy)({ + chainId: this.chainId, + tokenAddress: token.address, + }) }); + tokenList[token.address] = formattedToken; + } + } + const updatedTokensChainsCache = Object.assign(Object.assign({}, tokensChainsCache), { [this.chainId]: { + timestamp: Date.now(), + data: tokenList, + } }); + this.update(() => { + return { + tokenList, + tokensChainsCache: updatedTokensChainsCache, + }; + }); + } + finally { + releaseLock(); + } + }); + } + /** + * Checks if the Cache timestamp is valid, + * if yes data in cache will be returned + * otherwise null will be returned. + * + * @returns The cached data, or `null` if the cache was expired. + */ + fetchFromCache() { + return __awaiter(this, void 0, void 0, function* () { + const { tokensChainsCache } = this.state; + const dataCache = tokensChainsCache[this.chainId]; + const now = Date.now(); + if ((dataCache === null || dataCache === void 0 ? void 0 : dataCache.data) && + now - (dataCache === null || dataCache === void 0 ? void 0 : dataCache.timestamp) < this.cacheRefreshThreshold) { + return dataCache.data; + } + return null; + }); + } +} +exports.TokenListController = TokenListController; +exports.default = TokenListController; +//# sourceMappingURL=TokenListController.js.map \ No newline at end of file diff --git a/dist/assets/TokenListController.js.map b/dist/assets/TokenListController.js.map new file mode 100644 index 0000000000..e6fdec76e5 --- /dev/null +++ b/dist/assets/TokenListController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"TokenListController.js","sourceRoot":"","sources":["../../src/assets/TokenListController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Patch } from 'immer';\nimport { Mutex } from 'async-mutex';\nimport { AbortController } from 'abort-controller';\nimport { BaseController } from '../BaseControllerV2';\nimport type { RestrictedControllerMessenger } from '../ControllerMessenger';\nimport { safelyExecute, isTokenDetectionSupportedForNetwork } from '../util';\nimport { fetchTokenList } from '../apis/token-service';\nimport { NetworkState } from '../network/NetworkController';\nimport { formatAggregatorNames, formatIconUrlWithProxy } from './assetsUtil';\n\nconst DEFAULT_INTERVAL = 24 * 60 * 60 * 1000;\nconst DEFAULT_THRESHOLD = 24 * 60 * 60 * 1000;\n\nconst name = 'TokenListController';\n\nexport type TokenListToken = {\n name: string;\n symbol: string;\n decimals: number;\n address: string;\n occurrences: number;\n aggregators: string[];\n iconUrl: string;\n};\n\nexport type TokenListMap = Record;\n\ntype DataCache = {\n timestamp: number;\n data: TokenListMap;\n};\ntype TokensChainsCache = {\n [chainSlug: string]: DataCache;\n};\n\nexport type TokenListState = {\n tokenList: TokenListMap;\n tokensChainsCache: TokensChainsCache;\n};\n\nexport type TokenListStateChange = {\n type: `${typeof name}:stateChange`;\n payload: [TokenListState, Patch[]];\n};\n\nexport type GetTokenListState = {\n type: `${typeof name}:getState`;\n handler: () => TokenListState;\n};\n\ntype TokenListMessenger = RestrictedControllerMessenger<\n typeof name,\n GetTokenListState,\n TokenListStateChange,\n never,\n TokenListStateChange['type']\n>;\n\nconst metadata = {\n tokenList: { persist: true, anonymous: true },\n tokensChainsCache: { persist: true, anonymous: true },\n};\n\nconst defaultState: TokenListState = {\n tokenList: {},\n tokensChainsCache: {},\n};\n\n/**\n * Controller that passively polls on a set interval for the list of tokens from metaswaps api\n */\nexport class TokenListController extends BaseController<\n typeof name,\n TokenListState,\n TokenListMessenger\n> {\n private mutex = new Mutex();\n\n private intervalId?: NodeJS.Timeout;\n\n private intervalDelay: number;\n\n private cacheRefreshThreshold: number;\n\n private chainId: string;\n\n private abortController: AbortController;\n\n /**\n * Creates a TokenListController instance.\n *\n * @param options - The controller options.\n * @param options.chainId - The chain ID of the current network.\n * @param options.onNetworkStateChange - A function for registering an event handler for network state changes.\n * @param options.interval - The polling interval, in milliseconds.\n * @param options.cacheRefreshThreshold - The token cache expiry time, in milliseconds.\n * @param options.messenger - A restricted controller messenger.\n * @param options.state - Initial state to set on this controller.\n */\n constructor({\n chainId,\n onNetworkStateChange,\n interval = DEFAULT_INTERVAL,\n cacheRefreshThreshold = DEFAULT_THRESHOLD,\n messenger,\n state,\n }: {\n chainId: string;\n onNetworkStateChange: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n interval?: number;\n cacheRefreshThreshold?: number;\n messenger: TokenListMessenger;\n state?: Partial;\n }) {\n super({\n name,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n this.intervalDelay = interval;\n this.cacheRefreshThreshold = cacheRefreshThreshold;\n this.chainId = chainId;\n this.abortController = new AbortController();\n onNetworkStateChange(async (networkState) => {\n if (this.chainId !== networkState.provider.chainId) {\n this.abortController.abort();\n this.abortController = new AbortController();\n this.chainId = networkState.provider.chainId;\n // Ensure tokenList is referencing data from correct network\n this.update(() => {\n return {\n ...this.state,\n tokenList: this.state.tokensChainsCache[this.chainId]?.data || {},\n };\n });\n await this.restart();\n }\n });\n }\n\n /**\n * Start polling for the token list.\n */\n async start() {\n if (!isTokenDetectionSupportedForNetwork(this.chainId)) {\n return;\n }\n await this.startPolling();\n }\n\n /**\n * Restart polling for the token list.\n */\n async restart() {\n this.stopPolling();\n await this.startPolling();\n }\n\n /**\n * Stop polling for the token list.\n */\n stop() {\n this.stopPolling();\n }\n\n /**\n * Prepare to discard this controller.\n *\n * This stops any active polling.\n */\n override destroy() {\n super.destroy();\n this.stopPolling();\n }\n\n private stopPolling() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n }\n\n /**\n * Starts a new polling interval.\n */\n private async startPolling(): Promise {\n await safelyExecute(() => this.fetchTokenList());\n this.intervalId = setInterval(async () => {\n await safelyExecute(() => this.fetchTokenList());\n }, this.intervalDelay);\n }\n\n /**\n * Fetching token list from the Token Service API.\n */\n async fetchTokenList(): Promise {\n const releaseLock = await this.mutex.acquire();\n try {\n const { tokensChainsCache } = this.state;\n let tokenList: TokenListMap = {};\n const cachedTokens: TokenListMap = await safelyExecute(() =>\n this.fetchFromCache(),\n );\n if (cachedTokens) {\n // Use non-expired cached tokens\n tokenList = { ...cachedTokens };\n } else {\n // Fetch fresh token list\n const tokensFromAPI: TokenListToken[] = await safelyExecute(() =>\n fetchTokenList(this.chainId, this.abortController.signal),\n );\n\n if (!tokensFromAPI) {\n // Fallback to expired cached tokens\n tokenList = { ...(tokensChainsCache[this.chainId]?.data || {}) };\n\n this.update(() => {\n return {\n tokenList,\n tokensChainsCache,\n };\n });\n return;\n }\n // Filtering out tokens with less than 3 occurrences and native tokens\n const filteredTokenList = tokensFromAPI.filter(\n (token) =>\n token.occurrences &&\n token.occurrences >= 3 &&\n token.address !== '0x0000000000000000000000000000000000000000',\n );\n // Removing the tokens with symbol conflicts\n const symbolsList = filteredTokenList.map((token) => token.symbol);\n const duplicateSymbols = [\n ...new Set(\n symbolsList.filter(\n (symbol, index) => symbolsList.indexOf(symbol) !== index,\n ),\n ),\n ];\n const uniqueTokenList = filteredTokenList.filter(\n (token) => !duplicateSymbols.includes(token.symbol),\n );\n for (const token of uniqueTokenList) {\n const formattedToken: TokenListToken = {\n ...token,\n aggregators: formatAggregatorNames(token.aggregators),\n iconUrl: formatIconUrlWithProxy({\n chainId: this.chainId,\n tokenAddress: token.address,\n }),\n };\n tokenList[token.address] = formattedToken;\n }\n }\n const updatedTokensChainsCache: TokensChainsCache = {\n ...tokensChainsCache,\n [this.chainId]: {\n timestamp: Date.now(),\n data: tokenList,\n },\n };\n this.update(() => {\n return {\n tokenList,\n tokensChainsCache: updatedTokensChainsCache,\n };\n });\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Checks if the Cache timestamp is valid,\n * if yes data in cache will be returned\n * otherwise null will be returned.\n *\n * @returns The cached data, or `null` if the cache was expired.\n */\n async fetchFromCache(): Promise {\n const { tokensChainsCache }: TokenListState = this.state;\n const dataCache = tokensChainsCache[this.chainId];\n const now = Date.now();\n if (\n dataCache?.data &&\n now - dataCache?.timestamp < this.cacheRefreshThreshold\n ) {\n return dataCache.data;\n }\n return null;\n }\n}\n\nexport default TokenListController;\n"]} \ No newline at end of file diff --git a/dist/assets/TokenRatesController.d.ts b/dist/assets/TokenRatesController.d.ts new file mode 100644 index 0000000000..40e8e609ac --- /dev/null +++ b/dist/assets/TokenRatesController.d.ts @@ -0,0 +1,167 @@ +import { BaseController, BaseConfig, BaseState } from '../BaseController'; +import type { NetworkState } from '../network/NetworkController'; +import type { TokensState } from './TokensController'; +import type { CurrencyRateState } from './CurrencyRateController'; +/** + * @type CoinGeckoResponse + * + * CoinGecko API response representation + */ +export interface CoinGeckoResponse { + [address: string]: { + [currency: string]: number; + }; +} +/** + * @type CoinGeckoPlatform + * + * CoinGecko supported platform API representation + */ +export interface CoinGeckoPlatform { + id: string; + chain_identifier: null | number; + name: string; + shortname: string; +} +/** + * @type Token + * + * Token representation + * @property address - Hex address of the token contract + * @property decimals - Number of decimals the token uses + * @property symbol - Symbol of the token + * @property image - Image of the token, url or bit32 image + */ +export interface Token { + address: string; + decimals: number; + symbol: string; + aggregators?: string[]; + image?: string; + balanceError?: unknown; + isERC721?: boolean; +} +/** + * @type TokenRatesConfig + * + * Token rates controller configuration + * @property interval - Polling interval used to fetch new token rates + * @property nativeCurrency - Current native currency selected to use base of rates + * @property chainId - Current network chainId + * @property tokens - List of tokens to track exchange rates for + * @property threshold - Threshold to invalidate the supportedChains + */ +export interface TokenRatesConfig extends BaseConfig { + interval: number; + nativeCurrency: string; + chainId: string; + tokens: Token[]; + threshold: number; +} +interface ContractExchangeRates { + [address: string]: number | undefined; +} +/** + * @type TokenRatesState + * + * Token rates controller state + * @property contractExchangeRates - Hash of token contract addresses to exchange rates + * @property supportedChains - Cached chain data + */ +export interface TokenRatesState extends BaseState { + contractExchangeRates: ContractExchangeRates; +} +/** + * Controller that passively polls on a set interval for token-to-fiat exchange rates + * for tokens stored in the TokensController + */ +export declare class TokenRatesController extends BaseController { + private handle?; + private tokenList; + private supportedChains; + private supportedVsCurrencies; + /** + * Name of this controller used during composition + */ + name: string; + /** + * Creates a TokenRatesController instance. + * + * @param options - The controller options. + * @param options.onTokensStateChange - Allows subscribing to token controller state changes. + * @param options.onCurrencyRateStateChange - Allows subscribing to currency rate controller state changes. + * @param options.onNetworkStateChange - Allows subscribing to network state changes. + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor({ onTokensStateChange, onCurrencyRateStateChange, onNetworkStateChange, }: { + onTokensStateChange: (listener: (tokensState: TokensState) => void) => void; + onCurrencyRateStateChange: (listener: (currencyRateState: CurrencyRateState) => void) => void; + onNetworkStateChange: (listener: (networkState: NetworkState) => void) => void; + }, config?: Partial, state?: Partial); + /** + * Sets a new polling interval. + * + * @param interval - Polling interval used to fetch new token rates. + */ + poll(interval?: number): Promise; + /** + * Sets a new chainId. + * + * TODO: Replace this with a method. + * + * @param _chainId - The current chain ID. + */ + set chainId(_chainId: string); + get chainId(): string; + /** + * Sets a new token list to track prices. + * + * TODO: Replace this with a method. + * + * @param tokens - List of tokens to track exchange rates for. + */ + set tokens(tokens: Token[]); + get tokens(): Token[]; + /** + * Fetches a pairs of token address and native currency. + * + * @param chainSlug - Chain string identifier. + * @param vsCurrency - Query according to tokens in tokenList and native currency. + * @returns The exchange rates for the given pairs. + */ + fetchExchangeRate(chainSlug: string, vsCurrency: string): Promise; + /** + * Checks if the current native currency is a supported vs currency to use + * to query for token exchange rates. + * + * @param nativeCurrency - The native currency of the currently active network. + * @returns A boolean indicating whether it's a supported vsCurrency. + */ + private checkIsSupportedVsCurrency; + /** + * Gets current chain ID slug from cached supported platforms CoinGecko API response. + * If cached supported platforms response is stale, fetches and updates it. + * + * @returns The CoinGecko slug for the current chain ID. + */ + getChainSlug(): Promise; + /** + * Updates exchange rates for all tokens. + */ + updateExchangeRates(): Promise; + /** + * Checks if the active network's native currency is supported by the coingecko API. + * If supported, it fetches and maps contractExchange rates to a format to be consumed by the UI. + * If not supported, it fetches contractExchange rates and maps them from token/fallback-currency + * to token/nativeCurrency. + * + * @param nativeCurrency - The native currency of the currently active network. + * @param slug - The unique slug used to id the chain by the coingecko api + * should be used to query token exchange rates. + * @returns An object with conversion rates for each token + * related to the network's native currency. + */ + fetchAndMapExchangeRates(nativeCurrency: string, slug: string): Promise; +} +export default TokenRatesController; diff --git a/dist/assets/TokenRatesController.js b/dist/assets/TokenRatesController.js new file mode 100644 index 0000000000..b170a3dcfa --- /dev/null +++ b/dist/assets/TokenRatesController.js @@ -0,0 +1,285 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TokenRatesController = void 0; +const BaseController_1 = require("../BaseController"); +const util_1 = require("../util"); +const constants_1 = require("../constants"); +const crypto_compare_1 = require("../apis/crypto-compare"); +const CoinGeckoApi = { + BASE_URL: 'https://api.coingecko.com/api/v3', + getTokenPriceURL(chainSlug, query) { + return `${this.BASE_URL}/simple/token_price/${chainSlug}?${query}`; + }, + getPlatformsURL() { + return `${this.BASE_URL}/asset_platforms`; + }, + getSupportedVsCurrencies() { + return `${this.BASE_URL}/simple/supported_vs_currencies`; + }, +}; +/** + * Finds the chain slug in the data array given a chainId. + * + * @param chainId - The current chain ID. + * @param data - A list platforms supported by the CoinGecko API. + * @returns The CoinGecko slug for the given chain ID, or `null` if the slug was not found. + */ +function findChainSlug(chainId, data) { + var _a; + if (!data) { + return null; + } + const chain = (_a = data.find(({ chain_identifier }) => chain_identifier !== null && String(chain_identifier) === chainId)) !== null && _a !== void 0 ? _a : null; + return (chain === null || chain === void 0 ? void 0 : chain.id) || null; +} +/** + * Controller that passively polls on a set interval for token-to-fiat exchange rates + * for tokens stored in the TokensController + */ +class TokenRatesController extends BaseController_1.BaseController { + /** + * Creates a TokenRatesController instance. + * + * @param options - The controller options. + * @param options.onTokensStateChange - Allows subscribing to token controller state changes. + * @param options.onCurrencyRateStateChange - Allows subscribing to currency rate controller state changes. + * @param options.onNetworkStateChange - Allows subscribing to network state changes. + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor({ onTokensStateChange, onCurrencyRateStateChange, onNetworkStateChange, }, config, state) { + super(config, state); + this.tokenList = []; + this.supportedChains = { + timestamp: 0, + data: null, + }; + this.supportedVsCurrencies = { + timestamp: 0, + data: [], + }; + /** + * Name of this controller used during composition + */ + this.name = 'TokenRatesController'; + this.defaultConfig = { + disabled: true, + interval: 3 * 60 * 1000, + nativeCurrency: 'eth', + chainId: '', + tokens: [], + threshold: 6 * 60 * 60 * 1000, + }; + this.defaultState = { + contractExchangeRates: {}, + }; + this.initialize(); + this.configure({ disabled: false }, false, false); + onTokensStateChange(({ tokens, detectedTokens }) => { + this.configure({ tokens: [...tokens, ...detectedTokens] }); + }); + onCurrencyRateStateChange((currencyRateState) => { + this.configure({ nativeCurrency: currencyRateState.nativeCurrency }); + }); + onNetworkStateChange(({ provider }) => { + const { chainId } = provider; + this.update({ contractExchangeRates: {} }); + this.configure({ chainId }); + }); + this.poll(); + } + /** + * Sets a new polling interval. + * + * @param interval - Polling interval used to fetch new token rates. + */ + poll(interval) { + return __awaiter(this, void 0, void 0, function* () { + interval && this.configure({ interval }, false, false); + this.handle && clearTimeout(this.handle); + yield (0, util_1.safelyExecute)(() => this.updateExchangeRates()); + this.handle = setTimeout(() => { + this.poll(this.config.interval); + }, this.config.interval); + }); + } + /** + * Sets a new chainId. + * + * TODO: Replace this with a method. + * + * @param _chainId - The current chain ID. + */ + set chainId(_chainId) { + !this.disabled && (0, util_1.safelyExecute)(() => this.updateExchangeRates()); + } + get chainId() { + throw new Error('Property only used for setting'); + } + /** + * Sets a new token list to track prices. + * + * TODO: Replace this with a method. + * + * @param tokens - List of tokens to track exchange rates for. + */ + set tokens(tokens) { + this.tokenList = tokens; + !this.disabled && (0, util_1.safelyExecute)(() => this.updateExchangeRates()); + } + get tokens() { + throw new Error('Property only used for setting'); + } + /** + * Fetches a pairs of token address and native currency. + * + * @param chainSlug - Chain string identifier. + * @param vsCurrency - Query according to tokens in tokenList and native currency. + * @returns The exchange rates for the given pairs. + */ + fetchExchangeRate(chainSlug, vsCurrency) { + return __awaiter(this, void 0, void 0, function* () { + const tokenPairs = this.tokenList.map((token) => token.address).join(','); + const query = `contract_addresses=${tokenPairs}&vs_currencies=${vsCurrency.toLowerCase()}`; + return (0, util_1.handleFetch)(CoinGeckoApi.getTokenPriceURL(chainSlug, query)); + }); + } + /** + * Checks if the current native currency is a supported vs currency to use + * to query for token exchange rates. + * + * @param nativeCurrency - The native currency of the currently active network. + * @returns A boolean indicating whether it's a supported vsCurrency. + */ + checkIsSupportedVsCurrency(nativeCurrency) { + return __awaiter(this, void 0, void 0, function* () { + const { threshold } = this.config; + const { timestamp, data } = this.supportedVsCurrencies; + const now = Date.now(); + if (now - timestamp > threshold) { + const currencies = yield (0, util_1.handleFetch)(CoinGeckoApi.getSupportedVsCurrencies()); + this.supportedVsCurrencies = { + data: currencies, + timestamp: Date.now(), + }; + return currencies.includes(nativeCurrency.toLowerCase()); + } + return data.includes(nativeCurrency.toLowerCase()); + }); + } + /** + * Gets current chain ID slug from cached supported platforms CoinGecko API response. + * If cached supported platforms response is stale, fetches and updates it. + * + * @returns The CoinGecko slug for the current chain ID. + */ + getChainSlug() { + return __awaiter(this, void 0, void 0, function* () { + const { threshold, chainId } = this.config; + const { data, timestamp } = this.supportedChains; + const now = Date.now(); + if (now - timestamp > threshold) { + const platforms = yield (0, util_1.handleFetch)(CoinGeckoApi.getPlatformsURL()); + this.supportedChains = { + data: platforms, + timestamp: Date.now(), + }; + return findChainSlug(chainId, platforms); + } + return findChainSlug(chainId, data); + }); + } + /** + * Updates exchange rates for all tokens. + */ + updateExchangeRates() { + return __awaiter(this, void 0, void 0, function* () { + if (this.tokenList.length === 0 || this.disabled) { + return; + } + const slug = yield this.getChainSlug(); + let newContractExchangeRates = {}; + if (!slug) { + this.tokenList.forEach((token) => { + const address = (0, util_1.toChecksumHexAddress)(token.address); + newContractExchangeRates[address] = undefined; + }); + } + else { + const { nativeCurrency } = this.config; + newContractExchangeRates = yield this.fetchAndMapExchangeRates(nativeCurrency, slug); + } + this.update({ contractExchangeRates: newContractExchangeRates }); + }); + } + /** + * Checks if the active network's native currency is supported by the coingecko API. + * If supported, it fetches and maps contractExchange rates to a format to be consumed by the UI. + * If not supported, it fetches contractExchange rates and maps them from token/fallback-currency + * to token/nativeCurrency. + * + * @param nativeCurrency - The native currency of the currently active network. + * @param slug - The unique slug used to id the chain by the coingecko api + * should be used to query token exchange rates. + * @returns An object with conversion rates for each token + * related to the network's native currency. + */ + fetchAndMapExchangeRates(nativeCurrency, slug) { + return __awaiter(this, void 0, void 0, function* () { + const contractExchangeRates = {}; + // check if native currency is supported as a vs_currency by the API + const nativeCurrencySupported = yield this.checkIsSupportedVsCurrency(nativeCurrency); + if (nativeCurrencySupported) { + // If it is we can do a simple fetch against the CoinGecko API + const prices = yield this.fetchExchangeRate(slug, nativeCurrency); + this.tokenList.forEach((token) => { + const price = prices[token.address.toLowerCase()]; + contractExchangeRates[(0, util_1.toChecksumHexAddress)(token.address)] = price + ? price[nativeCurrency.toLowerCase()] + : 0; + }); + } + else { + // if native currency is not supported we need to use a fallback vsCurrency, get the exchange rates + // in token/fallback-currency format and convert them to expected token/nativeCurrency format. + let tokenExchangeRates; + let vsCurrencyToNativeCurrencyConversionRate = 0; + try { + [ + tokenExchangeRates, + { conversionRate: vsCurrencyToNativeCurrencyConversionRate }, + ] = yield Promise.all([ + this.fetchExchangeRate(slug, constants_1.FALL_BACK_VS_CURRENCY), + (0, crypto_compare_1.fetchExchangeRate)(nativeCurrency, constants_1.FALL_BACK_VS_CURRENCY, false), + ]); + } + catch (error) { + if (error instanceof Error && + error.message.includes('market does not exist for this coin pair')) { + return {}; + } + throw error; + } + for (const [tokenAddress, conversion] of Object.entries(tokenExchangeRates)) { + const tokenToVsCurrencyConversionRate = conversion[constants_1.FALL_BACK_VS_CURRENCY.toLowerCase()]; + contractExchangeRates[(0, util_1.toChecksumHexAddress)(tokenAddress)] = + tokenToVsCurrencyConversionRate * + vsCurrencyToNativeCurrencyConversionRate; + } + } + return contractExchangeRates; + }); + } +} +exports.TokenRatesController = TokenRatesController; +exports.default = TokenRatesController; +//# sourceMappingURL=TokenRatesController.js.map \ No newline at end of file diff --git a/dist/assets/TokenRatesController.js.map b/dist/assets/TokenRatesController.js.map new file mode 100644 index 0000000000..b50ffe6df9 --- /dev/null +++ b/dist/assets/TokenRatesController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"TokenRatesController.js","sourceRoot":"","sources":["../../src/assets/TokenRatesController.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport { safelyExecute, handleFetch, toChecksumHexAddress } from '../util';\n\nimport type { NetworkState } from '../network/NetworkController';\nimport { FALL_BACK_VS_CURRENCY } from '../constants';\nimport { fetchExchangeRate as fetchNativeExchangeRate } from '../apis/crypto-compare';\nimport type { TokensState } from './TokensController';\nimport type { CurrencyRateState } from './CurrencyRateController';\n\n/**\n * @type CoinGeckoResponse\n *\n * CoinGecko API response representation\n */\nexport interface CoinGeckoResponse {\n [address: string]: {\n [currency: string]: number;\n };\n}\n/**\n * @type CoinGeckoPlatform\n *\n * CoinGecko supported platform API representation\n */\nexport interface CoinGeckoPlatform {\n id: string;\n chain_identifier: null | number;\n name: string;\n shortname: string;\n}\n\n/**\n * @type Token\n *\n * Token representation\n * @property address - Hex address of the token contract\n * @property decimals - Number of decimals the token uses\n * @property symbol - Symbol of the token\n * @property image - Image of the token, url or bit32 image\n */\nexport interface Token {\n address: string;\n decimals: number;\n symbol: string;\n aggregators?: string[];\n image?: string;\n balanceError?: unknown;\n isERC721?: boolean;\n}\n\n/**\n * @type TokenRatesConfig\n *\n * Token rates controller configuration\n * @property interval - Polling interval used to fetch new token rates\n * @property nativeCurrency - Current native currency selected to use base of rates\n * @property chainId - Current network chainId\n * @property tokens - List of tokens to track exchange rates for\n * @property threshold - Threshold to invalidate the supportedChains\n */\nexport interface TokenRatesConfig extends BaseConfig {\n interval: number;\n nativeCurrency: string;\n chainId: string;\n tokens: Token[];\n threshold: number;\n}\n\ninterface ContractExchangeRates {\n [address: string]: number | undefined;\n}\n\ninterface SupportedChainsCache {\n timestamp: number;\n data: CoinGeckoPlatform[] | null;\n}\n\ninterface SupportedVsCurrenciesCache {\n timestamp: number;\n data: string[];\n}\n\n/**\n * @type TokenRatesState\n *\n * Token rates controller state\n * @property contractExchangeRates - Hash of token contract addresses to exchange rates\n * @property supportedChains - Cached chain data\n */\nexport interface TokenRatesState extends BaseState {\n contractExchangeRates: ContractExchangeRates;\n}\n\nconst CoinGeckoApi = {\n BASE_URL: 'https://api.coingecko.com/api/v3',\n getTokenPriceURL(chainSlug: string, query: string) {\n return `${this.BASE_URL}/simple/token_price/${chainSlug}?${query}`;\n },\n getPlatformsURL() {\n return `${this.BASE_URL}/asset_platforms`;\n },\n getSupportedVsCurrencies() {\n return `${this.BASE_URL}/simple/supported_vs_currencies`;\n },\n};\n\n/**\n * Finds the chain slug in the data array given a chainId.\n *\n * @param chainId - The current chain ID.\n * @param data - A list platforms supported by the CoinGecko API.\n * @returns The CoinGecko slug for the given chain ID, or `null` if the slug was not found.\n */\nfunction findChainSlug(\n chainId: string,\n data: CoinGeckoPlatform[] | null,\n): string | null {\n if (!data) {\n return null;\n }\n const chain =\n data.find(\n ({ chain_identifier }) =>\n chain_identifier !== null && String(chain_identifier) === chainId,\n ) ?? null;\n return chain?.id || null;\n}\n\n/**\n * Controller that passively polls on a set interval for token-to-fiat exchange rates\n * for tokens stored in the TokensController\n */\nexport class TokenRatesController extends BaseController<\n TokenRatesConfig,\n TokenRatesState\n> {\n private handle?: NodeJS.Timer;\n\n private tokenList: Token[] = [];\n\n private supportedChains: SupportedChainsCache = {\n timestamp: 0,\n data: null,\n };\n\n private supportedVsCurrencies: SupportedVsCurrenciesCache = {\n timestamp: 0,\n data: [],\n };\n\n /**\n * Name of this controller used during composition\n */\n override name = 'TokenRatesController';\n\n /**\n * Creates a TokenRatesController instance.\n *\n * @param options - The controller options.\n * @param options.onTokensStateChange - Allows subscribing to token controller state changes.\n * @param options.onCurrencyRateStateChange - Allows subscribing to currency rate controller state changes.\n * @param options.onNetworkStateChange - Allows subscribing to network state changes.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n onTokensStateChange,\n onCurrencyRateStateChange,\n onNetworkStateChange,\n }: {\n onTokensStateChange: (\n listener: (tokensState: TokensState) => void,\n ) => void;\n onCurrencyRateStateChange: (\n listener: (currencyRateState: CurrencyRateState) => void,\n ) => void;\n onNetworkStateChange: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n },\n config?: Partial,\n state?: Partial,\n ) {\n super(config, state);\n this.defaultConfig = {\n disabled: true,\n interval: 3 * 60 * 1000,\n nativeCurrency: 'eth',\n chainId: '',\n tokens: [],\n threshold: 6 * 60 * 60 * 1000,\n };\n\n this.defaultState = {\n contractExchangeRates: {},\n };\n this.initialize();\n this.configure({ disabled: false }, false, false);\n onTokensStateChange(({ tokens, detectedTokens }) => {\n this.configure({ tokens: [...tokens, ...detectedTokens] });\n });\n\n onCurrencyRateStateChange((currencyRateState) => {\n this.configure({ nativeCurrency: currencyRateState.nativeCurrency });\n });\n\n onNetworkStateChange(({ provider }) => {\n const { chainId } = provider;\n this.update({ contractExchangeRates: {} });\n this.configure({ chainId });\n });\n this.poll();\n }\n\n /**\n * Sets a new polling interval.\n *\n * @param interval - Polling interval used to fetch new token rates.\n */\n async poll(interval?: number): Promise {\n interval && this.configure({ interval }, false, false);\n this.handle && clearTimeout(this.handle);\n await safelyExecute(() => this.updateExchangeRates());\n this.handle = setTimeout(() => {\n this.poll(this.config.interval);\n }, this.config.interval);\n }\n\n /**\n * Sets a new chainId.\n *\n * TODO: Replace this with a method.\n *\n * @param _chainId - The current chain ID.\n */\n set chainId(_chainId: string) {\n !this.disabled && safelyExecute(() => this.updateExchangeRates());\n }\n\n get chainId() {\n throw new Error('Property only used for setting');\n }\n\n /**\n * Sets a new token list to track prices.\n *\n * TODO: Replace this with a method.\n *\n * @param tokens - List of tokens to track exchange rates for.\n */\n set tokens(tokens: Token[]) {\n this.tokenList = tokens;\n !this.disabled && safelyExecute(() => this.updateExchangeRates());\n }\n\n get tokens() {\n throw new Error('Property only used for setting');\n }\n\n /**\n * Fetches a pairs of token address and native currency.\n *\n * @param chainSlug - Chain string identifier.\n * @param vsCurrency - Query according to tokens in tokenList and native currency.\n * @returns The exchange rates for the given pairs.\n */\n async fetchExchangeRate(\n chainSlug: string,\n vsCurrency: string,\n ): Promise {\n const tokenPairs = this.tokenList.map((token) => token.address).join(',');\n const query = `contract_addresses=${tokenPairs}&vs_currencies=${vsCurrency.toLowerCase()}`;\n return handleFetch(CoinGeckoApi.getTokenPriceURL(chainSlug, query));\n }\n\n /**\n * Checks if the current native currency is a supported vs currency to use\n * to query for token exchange rates.\n *\n * @param nativeCurrency - The native currency of the currently active network.\n * @returns A boolean indicating whether it's a supported vsCurrency.\n */\n private async checkIsSupportedVsCurrency(nativeCurrency: string) {\n const { threshold } = this.config;\n const { timestamp, data } = this.supportedVsCurrencies;\n\n const now = Date.now();\n\n if (now - timestamp > threshold) {\n const currencies = await handleFetch(\n CoinGeckoApi.getSupportedVsCurrencies(),\n );\n this.supportedVsCurrencies = {\n data: currencies,\n timestamp: Date.now(),\n };\n return currencies.includes(nativeCurrency.toLowerCase());\n }\n\n return data.includes(nativeCurrency.toLowerCase());\n }\n\n /**\n * Gets current chain ID slug from cached supported platforms CoinGecko API response.\n * If cached supported platforms response is stale, fetches and updates it.\n *\n * @returns The CoinGecko slug for the current chain ID.\n */\n async getChainSlug(): Promise {\n const { threshold, chainId } = this.config;\n const { data, timestamp } = this.supportedChains;\n\n const now = Date.now();\n\n if (now - timestamp > threshold) {\n const platforms = await handleFetch(CoinGeckoApi.getPlatformsURL());\n this.supportedChains = {\n data: platforms,\n timestamp: Date.now(),\n };\n return findChainSlug(chainId, platforms);\n }\n\n return findChainSlug(chainId, data);\n }\n\n /**\n * Updates exchange rates for all tokens.\n */\n async updateExchangeRates() {\n if (this.tokenList.length === 0 || this.disabled) {\n return;\n }\n const slug = await this.getChainSlug();\n\n let newContractExchangeRates: ContractExchangeRates = {};\n if (!slug) {\n this.tokenList.forEach((token) => {\n const address = toChecksumHexAddress(token.address);\n newContractExchangeRates[address] = undefined;\n });\n } else {\n const { nativeCurrency } = this.config;\n newContractExchangeRates = await this.fetchAndMapExchangeRates(\n nativeCurrency,\n slug,\n );\n }\n this.update({ contractExchangeRates: newContractExchangeRates });\n }\n\n /**\n * Checks if the active network's native currency is supported by the coingecko API.\n * If supported, it fetches and maps contractExchange rates to a format to be consumed by the UI.\n * If not supported, it fetches contractExchange rates and maps them from token/fallback-currency\n * to token/nativeCurrency.\n *\n * @param nativeCurrency - The native currency of the currently active network.\n * @param slug - The unique slug used to id the chain by the coingecko api\n * should be used to query token exchange rates.\n * @returns An object with conversion rates for each token\n * related to the network's native currency.\n */\n async fetchAndMapExchangeRates(\n nativeCurrency: string,\n slug: string,\n ): Promise {\n const contractExchangeRates: ContractExchangeRates = {};\n\n // check if native currency is supported as a vs_currency by the API\n const nativeCurrencySupported = await this.checkIsSupportedVsCurrency(\n nativeCurrency,\n );\n\n if (nativeCurrencySupported) {\n // If it is we can do a simple fetch against the CoinGecko API\n const prices = await this.fetchExchangeRate(slug, nativeCurrency);\n this.tokenList.forEach((token) => {\n const price = prices[token.address.toLowerCase()];\n contractExchangeRates[toChecksumHexAddress(token.address)] = price\n ? price[nativeCurrency.toLowerCase()]\n : 0;\n });\n } else {\n // if native currency is not supported we need to use a fallback vsCurrency, get the exchange rates\n // in token/fallback-currency format and convert them to expected token/nativeCurrency format.\n let tokenExchangeRates;\n let vsCurrencyToNativeCurrencyConversionRate = 0;\n try {\n [\n tokenExchangeRates,\n { conversionRate: vsCurrencyToNativeCurrencyConversionRate },\n ] = await Promise.all([\n this.fetchExchangeRate(slug, FALL_BACK_VS_CURRENCY),\n fetchNativeExchangeRate(nativeCurrency, FALL_BACK_VS_CURRENCY, false),\n ]);\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes('market does not exist for this coin pair')\n ) {\n return {};\n }\n throw error;\n }\n\n for (const [tokenAddress, conversion] of Object.entries(\n tokenExchangeRates,\n )) {\n const tokenToVsCurrencyConversionRate =\n conversion[FALL_BACK_VS_CURRENCY.toLowerCase()];\n contractExchangeRates[toChecksumHexAddress(tokenAddress)] =\n tokenToVsCurrencyConversionRate *\n vsCurrencyToNativeCurrencyConversionRate;\n }\n }\n\n return contractExchangeRates;\n }\n}\n\nexport default TokenRatesController;\n"]} \ No newline at end of file diff --git a/dist/assets/TokensController.d.ts b/dist/assets/TokensController.d.ts new file mode 100644 index 0000000000..3223cff838 --- /dev/null +++ b/dist/assets/TokensController.d.ts @@ -0,0 +1,236 @@ +/// +import { EventEmitter } from 'events'; +import { BaseController, BaseConfig, BaseState } from '../BaseController'; +import type { PreferencesState } from '../user/PreferencesController'; +import type { NetworkState, NetworkType } from '../network/NetworkController'; +import type { Token } from './TokenRatesController'; +/** + * @type TokensConfig + * + * Tokens controller configuration + * @property networkType - Network ID as per net_version + * @property selectedAddress - Vault selected address + */ +export interface TokensConfig extends BaseConfig { + networkType: NetworkType; + selectedAddress: string; + chainId: string; + provider: any; +} +/** + * @type AssetSuggestionResult + * @property result - Promise resolving to a new suggested asset address + * @property suggestedAssetMeta - Meta information about this new suggested asset + */ +interface AssetSuggestionResult { + result: Promise; + suggestedAssetMeta: SuggestedAssetMeta; +} +declare enum SuggestedAssetStatus { + accepted = "accepted", + failed = "failed", + pending = "pending", + rejected = "rejected" +} +export declare type SuggestedAssetMetaBase = { + id: string; + time: number; + type: string; + asset: Token; +}; +/** + * @type SuggestedAssetMeta + * + * Suggested asset by EIP747 meta data + * @property error - Synthesized error information for failed asset suggestions + * @property id - Generated UUID associated with this suggested asset + * @property status - String status of this this suggested asset + * @property time - Timestamp associated with this this suggested asset + * @property type - Type type this suggested asset + * @property asset - Asset suggested object + */ +export declare type SuggestedAssetMeta = (SuggestedAssetMetaBase & { + status: SuggestedAssetStatus.failed; + error: Error; +}) | (SuggestedAssetMetaBase & { + status: SuggestedAssetStatus.accepted | SuggestedAssetStatus.rejected | SuggestedAssetStatus.pending; +}); +/** + * @type TokensState + * + * Assets controller state + * @property tokens - List of tokens associated with the active network and address pair + * @property ignoredTokens - List of ignoredTokens associated with the active network and address pair + * @property detectedTokens - List of detected tokens associated with the active network and address pair + * @property allTokens - Object containing tokens by network and account + * @property allIgnoredTokens - Object containing hidden/ignored tokens by network and account + * @property allDetectedTokens - Object containing tokens detected with non-zero balances + * @property suggestedAssets - List of pending suggested assets to be added or canceled + */ +export interface TokensState extends BaseState { + tokens: Token[]; + ignoredTokens: string[]; + detectedTokens: Token[]; + allTokens: { + [key: string]: { + [key: string]: Token[]; + }; + }; + allIgnoredTokens: { + [key: string]: { + [key: string]: string[]; + }; + }; + allDetectedTokens: { + [key: string]: { + [key: string]: Token[]; + }; + }; + suggestedAssets: SuggestedAssetMeta[]; +} +/** + * Controller that stores assets and exposes convenience methods + */ +export declare class TokensController extends BaseController { + private mutex; + private ethersProvider; + private abortController; + private failSuggestedAsset; + /** + * Fetch metadata for a token. + * + * @param tokenAddress - The address of the token. + * @returns The token metadata. + */ + private fetchTokenMetadata; + /** + * EventEmitter instance used to listen to specific EIP747 events + */ + hub: EventEmitter; + /** + * Name of this controller used during composition + */ + name: string; + /** + * Creates a TokensController instance. + * + * @param options - The controller options. + * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes. + * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. + * @param options.config - Initial options used to configure this controller. + * @param options.state - Initial state to set on this controller. + */ + constructor({ onPreferencesStateChange, onNetworkStateChange, config, state, }: { + onPreferencesStateChange: (listener: (preferencesState: PreferencesState) => void) => void; + onNetworkStateChange: (listener: (networkState: NetworkState) => void) => void; + config?: Partial; + state?: Partial; + }); + _instantiateNewEthersProvider(): any; + /** + * Adds a token to the stored token list. + * + * @param address - Hex address of the token contract. + * @param symbol - Symbol of the token. + * @param decimals - Number of decimals the token uses. + * @param image - Image of the token. + * @returns Current token list. + */ + addToken(address: string, symbol: string, decimals: number, image?: string): Promise; + /** + * Add a batch of tokens. + * + * @param tokensToImport - Array of tokens to import. + */ + addTokens(tokensToImport: Token[]): Promise; + /** + * Ignore a batch of tokens. + * + * @param tokenAddressesToIgnore - Array of token addresses to ignore. + */ + ignoreTokens(tokenAddressesToIgnore: string[]): void; + /** + * Adds a batch of detected tokens to the stored token list. + * + * @param incomingDetectedTokens - Array of detected tokens to be added or updated. + */ + addDetectedTokens(incomingDetectedTokens: Token[]): Promise; + /** + * Adds isERC721 field to token object. This is called when a user attempts to add tokens that + * were previously added which do not yet had isERC721 field. + * + * @param tokenAddress - The contract address of the token requiring the isERC721 field added. + * @returns The new token object with the added isERC721 field. + */ + updateTokenType(tokenAddress: string): Promise; + /** + * Detects whether or not a token is ERC-721 compatible. + * + * @param tokenAddress - The token contract address. + * @returns A boolean indicating whether the token address passed in supports the EIP-721 + * interface. + */ + _detectIsERC721(tokenAddress: string): Promise; + _createEthersContract(tokenAddress: string, abi: string, ethersProvider: any): Promise; + _generateRandomId(): string; + /** + * Adds a new suggestedAsset to state. Parameters will be validated according to + * asset type being watched. A `:pending` hub event will be emitted once added. + * + * @param asset - The asset to be watched. For now only ERC20 tokens are accepted. + * @param type - The asset type. + * @returns Object containing a Promise resolving to the suggestedAsset address if accepted. + */ + watchAsset(asset: Token, type: string): Promise; + /** + * Accepts to watch an asset and updates it's status and deletes the suggestedAsset from state, + * adding the asset to corresponding asset state. In this case ERC20 tokens. + * A `:finished` hub event is fired after accepted or failure. + * + * @param suggestedAssetID - The ID of the suggestedAsset to accept. + */ + acceptWatchAsset(suggestedAssetID: string): Promise; + /** + * Rejects a watchAsset request based on its ID by setting its status to "rejected" + * and emitting a `:finished` hub event. + * + * @param suggestedAssetID - The ID of the suggestedAsset to accept. + */ + rejectWatchAsset(suggestedAssetID: string): void; + /** + * Takes a new tokens and ignoredTokens array for the current network/account combination + * and returns new allTokens and allIgnoredTokens state to update to. + * + * @param params - Object that holds token params. + * @param params.newTokens - The new tokens to set for the current network and selected account. + * @param params.newIgnoredTokens - The new ignored tokens to set for the current network and selected account. + * @param params.newDetectedTokens - The new detected tokens to set for the current network and selected account. + * @returns The updated `allTokens` and `allIgnoredTokens` state. + */ + _getNewAllTokensState(params: { + newTokens?: Token[]; + newIgnoredTokens?: string[]; + newDetectedTokens?: Token[]; + }): { + newAllTokens: { + [key: string]: { + [key: string]: Token[]; + }; + }; + newAllIgnoredTokens: { + [key: string]: { + [key: string]: string[]; + }; + }; + newAllDetectedTokens: { + [key: string]: { + [key: string]: Token[]; + }; + }; + }; + /** + * Removes all tokens from the ignored list. + */ + clearIgnoredTokens(): void; +} +export default TokensController; diff --git a/dist/assets/TokensController.js b/dist/assets/TokensController.js new file mode 100644 index 0000000000..519c020655 --- /dev/null +++ b/dist/assets/TokensController.js @@ -0,0 +1,532 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TokensController = void 0; +const events_1 = require("events"); +const contract_metadata_1 = __importDefault(require("@metamask/contract-metadata")); +const metamask_eth_abis_1 = require("@metamask/metamask-eth-abis"); +const uuid_1 = require("uuid"); +const async_mutex_1 = require("async-mutex"); +const ethers_1 = require("ethers"); +const abort_controller_1 = require("abort-controller"); +const BaseController_1 = require("../BaseController"); +const util_1 = require("../util"); +const constants_1 = require("../constants"); +const token_service_1 = require("../apis/token-service"); +const assetsUtil_1 = require("./assetsUtil"); +var SuggestedAssetStatus; +(function (SuggestedAssetStatus) { + SuggestedAssetStatus["accepted"] = "accepted"; + SuggestedAssetStatus["failed"] = "failed"; + SuggestedAssetStatus["pending"] = "pending"; + SuggestedAssetStatus["rejected"] = "rejected"; +})(SuggestedAssetStatus || (SuggestedAssetStatus = {})); +/** + * Controller that stores assets and exposes convenience methods + */ +class TokensController extends BaseController_1.BaseController { + /** + * Creates a TokensController instance. + * + * @param options - The controller options. + * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes. + * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. + * @param options.config - Initial options used to configure this controller. + * @param options.state - Initial state to set on this controller. + */ + constructor({ onPreferencesStateChange, onNetworkStateChange, config, state, }) { + super(config, state); + this.mutex = new async_mutex_1.Mutex(); + /** + * EventEmitter instance used to listen to specific EIP747 events + */ + this.hub = new events_1.EventEmitter(); + /** + * Name of this controller used during composition + */ + this.name = 'TokensController'; + this.defaultConfig = Object.assign({ networkType: constants_1.MAINNET, selectedAddress: '', chainId: '', provider: undefined }, config); + this.defaultState = Object.assign({ tokens: [], ignoredTokens: [], detectedTokens: [], allTokens: {}, allIgnoredTokens: {}, allDetectedTokens: {}, suggestedAssets: [] }, state); + this.initialize(); + this.abortController = new abort_controller_1.AbortController(); + onPreferencesStateChange(({ selectedAddress }) => { + var _a, _b, _c; + const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state; + const { chainId } = this.config; + this.configure({ selectedAddress }); + this.update({ + tokens: ((_a = allTokens[chainId]) === null || _a === void 0 ? void 0 : _a[selectedAddress]) || [], + ignoredTokens: ((_b = allIgnoredTokens[chainId]) === null || _b === void 0 ? void 0 : _b[selectedAddress]) || [], + detectedTokens: ((_c = allDetectedTokens[chainId]) === null || _c === void 0 ? void 0 : _c[selectedAddress]) || [], + }); + }); + onNetworkStateChange(({ provider }) => { + var _a, _b, _c; + const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state; + const { selectedAddress } = this.config; + const { chainId } = provider; + this.abortController.abort(); + this.abortController = new abort_controller_1.AbortController(); + this.configure({ chainId }); + this.ethersProvider = this._instantiateNewEthersProvider(); + this.update({ + tokens: ((_a = allTokens[chainId]) === null || _a === void 0 ? void 0 : _a[selectedAddress]) || [], + ignoredTokens: ((_b = allIgnoredTokens[chainId]) === null || _b === void 0 ? void 0 : _b[selectedAddress]) || [], + detectedTokens: ((_c = allDetectedTokens[chainId]) === null || _c === void 0 ? void 0 : _c[selectedAddress]) || [], + }); + }); + } + failSuggestedAsset(suggestedAssetMeta, error) { + const failedSuggestedAssetMeta = Object.assign(Object.assign({}, suggestedAssetMeta), { status: SuggestedAssetStatus.failed, error }); + this.hub.emit(`${suggestedAssetMeta.id}:finished`, failedSuggestedAssetMeta); + } + /** + * Fetch metadata for a token. + * + * @param tokenAddress - The address of the token. + * @returns The token metadata. + */ + fetchTokenMetadata(tokenAddress) { + return __awaiter(this, void 0, void 0, function* () { + try { + const token = yield (0, token_service_1.fetchTokenMetadata)(this.config.chainId, tokenAddress, this.abortController.signal); + return token; + } + catch (error) { + if (error instanceof Error && + error.message.includes(token_service_1.TOKEN_METADATA_NO_SUPPORT_ERROR)) { + return undefined; + } + throw error; + } + }); + } + _instantiateNewEthersProvider() { + var _a; + return new ethers_1.ethers.providers.Web3Provider((_a = this.config) === null || _a === void 0 ? void 0 : _a.provider); + } + /** + * Adds a token to the stored token list. + * + * @param address - Hex address of the token contract. + * @param symbol - Symbol of the token. + * @param decimals - Number of decimals the token uses. + * @param image - Image of the token. + * @returns Current token list. + */ + addToken(address, symbol, decimals, image) { + return __awaiter(this, void 0, void 0, function* () { + const currentChainId = this.config.chainId; + const releaseLock = yield this.mutex.acquire(); + try { + address = (0, util_1.toChecksumHexAddress)(address); + const { tokens, ignoredTokens, detectedTokens } = this.state; + const newTokens = [...tokens]; + const [isERC721, tokenMetadata] = yield Promise.all([ + this._detectIsERC721(address), + this.fetchTokenMetadata(address), + ]); + if (currentChainId !== this.config.chainId) { + throw new Error('TokensController Error: Switched networks while adding token'); + } + const newEntry = { + address, + symbol, + decimals, + image: image || + (0, assetsUtil_1.formatIconUrlWithProxy)({ + chainId: this.config.chainId, + tokenAddress: address, + }), + isERC721, + aggregators: (0, assetsUtil_1.formatAggregatorNames)((tokenMetadata === null || tokenMetadata === void 0 ? void 0 : tokenMetadata.aggregators) || []), + }; + const previousEntry = newTokens.find((token) => token.address.toLowerCase() === address.toLowerCase()); + if (previousEntry) { + const previousIndex = newTokens.indexOf(previousEntry); + newTokens[previousIndex] = newEntry; + } + else { + newTokens.push(newEntry); + } + const newIgnoredTokens = ignoredTokens.filter((tokenAddress) => tokenAddress.toLowerCase() !== address.toLowerCase()); + const newDetectedTokens = detectedTokens.filter((token) => token.address.toLowerCase() !== address.toLowerCase()); + const { newAllTokens, newAllIgnoredTokens, newAllDetectedTokens } = this._getNewAllTokensState({ + newTokens, + newIgnoredTokens, + newDetectedTokens, + }); + this.update({ + tokens: newTokens, + ignoredTokens: newIgnoredTokens, + detectedTokens: newDetectedTokens, + allTokens: newAllTokens, + allIgnoredTokens: newAllIgnoredTokens, + allDetectedTokens: newAllDetectedTokens, + }); + return newTokens; + } + finally { + releaseLock(); + } + }); + } + /** + * Add a batch of tokens. + * + * @param tokensToImport - Array of tokens to import. + */ + addTokens(tokensToImport) { + return __awaiter(this, void 0, void 0, function* () { + const releaseLock = yield this.mutex.acquire(); + const { tokens, detectedTokens, ignoredTokens } = this.state; + const importedTokensMap = {}; + // Used later to dedupe imported tokens + const newTokensMap = tokens.reduce((output, current) => { + output[current.address] = current; + return output; + }, {}); + try { + tokensToImport.forEach((tokenToAdd) => { + const { address, symbol, decimals, image, aggregators } = tokenToAdd; + const checksumAddress = (0, util_1.toChecksumHexAddress)(address); + const formattedToken = { + address: checksumAddress, + symbol, + decimals, + image, + aggregators, + }; + newTokensMap[address] = formattedToken; + importedTokensMap[address.toLowerCase()] = true; + return formattedToken; + }); + const newTokens = Object.values(newTokensMap); + const newDetectedTokens = detectedTokens.filter((token) => !importedTokensMap[token.address.toLowerCase()]); + const newIgnoredTokens = ignoredTokens.filter((tokenAddress) => !newTokensMap[tokenAddress.toLowerCase()]); + const { newAllTokens, newAllDetectedTokens, newAllIgnoredTokens } = this._getNewAllTokensState({ + newTokens, + newDetectedTokens, + newIgnoredTokens, + }); + this.update({ + tokens: newTokens, + allTokens: newAllTokens, + detectedTokens: newDetectedTokens, + allDetectedTokens: newAllDetectedTokens, + ignoredTokens: newIgnoredTokens, + allIgnoredTokens: newAllIgnoredTokens, + }); + } + finally { + releaseLock(); + } + }); + } + /** + * Ignore a batch of tokens. + * + * @param tokenAddressesToIgnore - Array of token addresses to ignore. + */ + ignoreTokens(tokenAddressesToIgnore) { + const { ignoredTokens, detectedTokens, tokens } = this.state; + const ignoredTokensMap = {}; + let newIgnoredTokens = [...ignoredTokens]; + const checksummedTokenAddresses = tokenAddressesToIgnore.map((address) => { + const checksumAddress = (0, util_1.toChecksumHexAddress)(address); + ignoredTokensMap[address.toLowerCase()] = true; + return checksumAddress; + }); + newIgnoredTokens = [...ignoredTokens, ...checksummedTokenAddresses]; + const newDetectedTokens = detectedTokens.filter((token) => !ignoredTokensMap[token.address.toLowerCase()]); + const newTokens = tokens.filter((token) => !ignoredTokensMap[token.address.toLowerCase()]); + const { newAllIgnoredTokens, newAllDetectedTokens, newAllTokens } = this._getNewAllTokensState({ + newIgnoredTokens, + newDetectedTokens, + newTokens, + }); + this.update({ + ignoredTokens: newIgnoredTokens, + tokens: newTokens, + detectedTokens: newDetectedTokens, + allIgnoredTokens: newAllIgnoredTokens, + allDetectedTokens: newAllDetectedTokens, + allTokens: newAllTokens, + }); + } + /** + * Adds a batch of detected tokens to the stored token list. + * + * @param incomingDetectedTokens - Array of detected tokens to be added or updated. + */ + addDetectedTokens(incomingDetectedTokens) { + return __awaiter(this, void 0, void 0, function* () { + const releaseLock = yield this.mutex.acquire(); + const { tokens, detectedTokens, ignoredTokens } = this.state; + const newTokens = [...tokens]; + const newDetectedTokens = [...detectedTokens]; + try { + incomingDetectedTokens.forEach((tokenToAdd) => { + const { address, symbol, decimals, image, aggregators, isERC721 } = tokenToAdd; + const checksumAddress = (0, util_1.toChecksumHexAddress)(address); + const newEntry = { + address: checksumAddress, + symbol, + decimals, + image, + isERC721, + aggregators, + }; + const previousImportedEntry = newTokens.find((token) => token.address.toLowerCase() === checksumAddress.toLowerCase()); + if (previousImportedEntry) { + // Update existing data of imported token + const previousImportedIndex = newTokens.indexOf(previousImportedEntry); + newTokens[previousImportedIndex] = newEntry; + } + else { + const ignoredTokenIndex = ignoredTokens.indexOf(address); + if (ignoredTokenIndex === -1) { + // Add detected token + const previousDetectedEntry = newDetectedTokens.find((token) => token.address.toLowerCase() === checksumAddress.toLowerCase()); + if (previousDetectedEntry) { + const previousDetectedIndex = newDetectedTokens.indexOf(previousDetectedEntry); + newDetectedTokens[previousDetectedIndex] = newEntry; + } + else { + newDetectedTokens.push(newEntry); + } + } + } + }); + const { newAllTokens, newAllDetectedTokens } = this._getNewAllTokensState({ + newTokens, + newDetectedTokens, + }); + this.update({ + tokens: newTokens, + allTokens: newAllTokens, + detectedTokens: newDetectedTokens, + allDetectedTokens: newAllDetectedTokens, + }); + } + finally { + releaseLock(); + } + }); + } + /** + * Adds isERC721 field to token object. This is called when a user attempts to add tokens that + * were previously added which do not yet had isERC721 field. + * + * @param tokenAddress - The contract address of the token requiring the isERC721 field added. + * @returns The new token object with the added isERC721 field. + */ + updateTokenType(tokenAddress) { + return __awaiter(this, void 0, void 0, function* () { + const isERC721 = yield this._detectIsERC721(tokenAddress); + const { tokens } = this.state; + const tokenIndex = tokens.findIndex((token) => { + return token.address.toLowerCase() === tokenAddress.toLowerCase(); + }); + tokens[tokenIndex].isERC721 = isERC721; + this.update({ tokens }); + return tokens[tokenIndex]; + }); + } + /** + * Detects whether or not a token is ERC-721 compatible. + * + * @param tokenAddress - The token contract address. + * @returns A boolean indicating whether the token address passed in supports the EIP-721 + * interface. + */ + _detectIsERC721(tokenAddress) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + const checksumAddress = (0, util_1.toChecksumHexAddress)(tokenAddress); + // if this token is already in our contract metadata map we don't need + // to check against the contract + if (((_a = contract_metadata_1.default[checksumAddress]) === null || _a === void 0 ? void 0 : _a.erc721) === true) { + return Promise.resolve(true); + } + else if (((_b = contract_metadata_1.default[checksumAddress]) === null || _b === void 0 ? void 0 : _b.erc20) === true) { + return Promise.resolve(false); + } + const tokenContract = yield this._createEthersContract(tokenAddress, metamask_eth_abis_1.abiERC721, this.ethersProvider); + try { + return yield tokenContract.supportsInterface(constants_1.ERC721_INTERFACE_ID); + } + catch (error) { + // currently we see a variety of errors across different networks when + // token contracts are not ERC721 compatible. We need to figure out a better + // way of differentiating token interface types but for now if we get an error + // we have to assume the token is not ERC721 compatible. + return false; + } + }); + } + _createEthersContract(tokenAddress, abi, ethersProvider) { + return __awaiter(this, void 0, void 0, function* () { + const tokenContract = yield new ethers_1.ethers.Contract(tokenAddress, abi, ethersProvider); + return tokenContract; + }); + } + _generateRandomId() { + return (0, uuid_1.v1)(); + } + /** + * Adds a new suggestedAsset to state. Parameters will be validated according to + * asset type being watched. A `:pending` hub event will be emitted once added. + * + * @param asset - The asset to be watched. For now only ERC20 tokens are accepted. + * @param type - The asset type. + * @returns Object containing a Promise resolving to the suggestedAsset address if accepted. + */ + watchAsset(asset, type) { + return __awaiter(this, void 0, void 0, function* () { + const suggestedAssetMeta = { + asset, + id: this._generateRandomId(), + status: SuggestedAssetStatus.pending, + time: Date.now(), + type, + }; + try { + switch (type) { + case 'ERC20': + (0, util_1.validateTokenToWatch)(asset); + break; + default: + throw new Error(`Asset of type ${type} not supported`); + } + } + catch (error) { + this.failSuggestedAsset(suggestedAssetMeta, error); + return Promise.reject(error); + } + const result = new Promise((resolve, reject) => { + this.hub.once(`${suggestedAssetMeta.id}:finished`, (meta) => { + switch (meta.status) { + case SuggestedAssetStatus.accepted: + return resolve(meta.asset.address); + case SuggestedAssetStatus.rejected: + return reject(new Error('User rejected to watch the asset.')); + case SuggestedAssetStatus.failed: + return reject(new Error(meta.error.message)); + /* istanbul ignore next */ + default: + return reject(new Error(`Unknown status: ${meta.status}`)); + } + }); + }); + const { suggestedAssets } = this.state; + suggestedAssets.push(suggestedAssetMeta); + this.update({ suggestedAssets: [...suggestedAssets] }); + this.hub.emit('pendingSuggestedAsset', suggestedAssetMeta); + return { result, suggestedAssetMeta }; + }); + } + /** + * Accepts to watch an asset and updates it's status and deletes the suggestedAsset from state, + * adding the asset to corresponding asset state. In this case ERC20 tokens. + * A `:finished` hub event is fired after accepted or failure. + * + * @param suggestedAssetID - The ID of the suggestedAsset to accept. + */ + acceptWatchAsset(suggestedAssetID) { + return __awaiter(this, void 0, void 0, function* () { + const { suggestedAssets } = this.state; + const index = suggestedAssets.findIndex(({ id }) => suggestedAssetID === id); + const suggestedAssetMeta = suggestedAssets[index]; + try { + switch (suggestedAssetMeta.type) { + case 'ERC20': + const { address, symbol, decimals, image } = suggestedAssetMeta.asset; + yield this.addToken(address, symbol, decimals, image); + suggestedAssetMeta.status = SuggestedAssetStatus.accepted; + this.hub.emit(`${suggestedAssetMeta.id}:finished`, suggestedAssetMeta); + break; + default: + throw new Error(`Asset of type ${suggestedAssetMeta.type} not supported`); + } + } + catch (error) { + this.failSuggestedAsset(suggestedAssetMeta, error); + } + const newSuggestedAssets = suggestedAssets.filter(({ id }) => id !== suggestedAssetID); + this.update({ suggestedAssets: [...newSuggestedAssets] }); + }); + } + /** + * Rejects a watchAsset request based on its ID by setting its status to "rejected" + * and emitting a `:finished` hub event. + * + * @param suggestedAssetID - The ID of the suggestedAsset to accept. + */ + rejectWatchAsset(suggestedAssetID) { + const { suggestedAssets } = this.state; + const index = suggestedAssets.findIndex(({ id }) => suggestedAssetID === id); + const suggestedAssetMeta = suggestedAssets[index]; + if (!suggestedAssetMeta) { + return; + } + suggestedAssetMeta.status = SuggestedAssetStatus.rejected; + this.hub.emit(`${suggestedAssetMeta.id}:finished`, suggestedAssetMeta); + const newSuggestedAssets = suggestedAssets.filter(({ id }) => id !== suggestedAssetID); + this.update({ suggestedAssets: [...newSuggestedAssets] }); + } + /** + * Takes a new tokens and ignoredTokens array for the current network/account combination + * and returns new allTokens and allIgnoredTokens state to update to. + * + * @param params - Object that holds token params. + * @param params.newTokens - The new tokens to set for the current network and selected account. + * @param params.newIgnoredTokens - The new ignored tokens to set for the current network and selected account. + * @param params.newDetectedTokens - The new detected tokens to set for the current network and selected account. + * @returns The updated `allTokens` and `allIgnoredTokens` state. + */ + _getNewAllTokensState(params) { + const { newTokens, newIgnoredTokens, newDetectedTokens } = params; + const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state; + const { chainId, selectedAddress } = this.config; + let newAllTokens = allTokens; + if (newTokens) { + const networkTokens = allTokens[chainId]; + const newNetworkTokens = Object.assign(Object.assign({}, networkTokens), { [selectedAddress]: newTokens }); + newAllTokens = Object.assign(Object.assign({}, allTokens), { [chainId]: newNetworkTokens }); + } + let newAllIgnoredTokens = allIgnoredTokens; + if (newIgnoredTokens) { + const networkIgnoredTokens = allIgnoredTokens[chainId]; + const newIgnoredNetworkTokens = Object.assign(Object.assign({}, networkIgnoredTokens), { [selectedAddress]: newIgnoredTokens }); + newAllIgnoredTokens = Object.assign(Object.assign({}, allIgnoredTokens), { [chainId]: newIgnoredNetworkTokens }); + } + let newAllDetectedTokens = allDetectedTokens; + if (newDetectedTokens) { + const networkDetectedTokens = allDetectedTokens[chainId]; + const newDetectedNetworkTokens = Object.assign(Object.assign({}, networkDetectedTokens), { [selectedAddress]: newDetectedTokens }); + newAllDetectedTokens = Object.assign(Object.assign({}, allDetectedTokens), { [chainId]: newDetectedNetworkTokens }); + } + return { newAllTokens, newAllIgnoredTokens, newAllDetectedTokens }; + } + /** + * Removes all tokens from the ignored list. + */ + clearIgnoredTokens() { + this.update({ ignoredTokens: [], allIgnoredTokens: {} }); + } +} +exports.TokensController = TokensController; +exports.default = TokensController; +//# sourceMappingURL=TokensController.js.map \ No newline at end of file diff --git a/dist/assets/TokensController.js.map b/dist/assets/TokensController.js.map new file mode 100644 index 0000000000..b85eefa511 --- /dev/null +++ b/dist/assets/TokensController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"TokensController.js","sourceRoot":"","sources":["../../src/assets/TokensController.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from 'events';\nimport contractsMap from '@metamask/contract-metadata';\nimport { abiERC721 } from '@metamask/metamask-eth-abis';\nimport { v1 as random } from 'uuid';\nimport { Mutex } from 'async-mutex';\nimport { ethers } from 'ethers';\nimport { AbortController } from 'abort-controller';\nimport { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport type { PreferencesState } from '../user/PreferencesController';\nimport type { NetworkState, NetworkType } from '../network/NetworkController';\nimport { validateTokenToWatch, toChecksumHexAddress } from '../util';\nimport { MAINNET, ERC721_INTERFACE_ID } from '../constants';\nimport {\n fetchTokenMetadata,\n TOKEN_METADATA_NO_SUPPORT_ERROR,\n} from '../apis/token-service';\nimport type { Token } from './TokenRatesController';\nimport { TokenListToken } from './TokenListController';\nimport { formatAggregatorNames, formatIconUrlWithProxy } from './assetsUtil';\n\n/**\n * @type TokensConfig\n *\n * Tokens controller configuration\n * @property networkType - Network ID as per net_version\n * @property selectedAddress - Vault selected address\n */\nexport interface TokensConfig extends BaseConfig {\n networkType: NetworkType;\n selectedAddress: string;\n chainId: string;\n provider: any;\n}\n\n/**\n * @type AssetSuggestionResult\n * @property result - Promise resolving to a new suggested asset address\n * @property suggestedAssetMeta - Meta information about this new suggested asset\n */\ninterface AssetSuggestionResult {\n result: Promise;\n suggestedAssetMeta: SuggestedAssetMeta;\n}\n\nenum SuggestedAssetStatus {\n accepted = 'accepted',\n failed = 'failed',\n pending = 'pending',\n rejected = 'rejected',\n}\n\nexport type SuggestedAssetMetaBase = {\n id: string;\n time: number;\n type: string;\n asset: Token;\n};\n\n/**\n * @type SuggestedAssetMeta\n *\n * Suggested asset by EIP747 meta data\n * @property error - Synthesized error information for failed asset suggestions\n * @property id - Generated UUID associated with this suggested asset\n * @property status - String status of this this suggested asset\n * @property time - Timestamp associated with this this suggested asset\n * @property type - Type type this suggested asset\n * @property asset - Asset suggested object\n */\nexport type SuggestedAssetMeta =\n | (SuggestedAssetMetaBase & {\n status: SuggestedAssetStatus.failed;\n error: Error;\n })\n | (SuggestedAssetMetaBase & {\n status:\n | SuggestedAssetStatus.accepted\n | SuggestedAssetStatus.rejected\n | SuggestedAssetStatus.pending;\n });\n\n/**\n * @type TokensState\n *\n * Assets controller state\n * @property tokens - List of tokens associated with the active network and address pair\n * @property ignoredTokens - List of ignoredTokens associated with the active network and address pair\n * @property detectedTokens - List of detected tokens associated with the active network and address pair\n * @property allTokens - Object containing tokens by network and account\n * @property allIgnoredTokens - Object containing hidden/ignored tokens by network and account\n * @property allDetectedTokens - Object containing tokens detected with non-zero balances\n * @property suggestedAssets - List of pending suggested assets to be added or canceled\n */\nexport interface TokensState extends BaseState {\n tokens: Token[];\n ignoredTokens: string[];\n detectedTokens: Token[];\n allTokens: { [key: string]: { [key: string]: Token[] } };\n allIgnoredTokens: { [key: string]: { [key: string]: string[] } };\n allDetectedTokens: { [key: string]: { [key: string]: Token[] } };\n suggestedAssets: SuggestedAssetMeta[];\n}\n\n/**\n * Controller that stores assets and exposes convenience methods\n */\nexport class TokensController extends BaseController<\n TokensConfig,\n TokensState\n> {\n private mutex = new Mutex();\n\n private ethersProvider: any;\n\n private abortController: AbortController;\n\n private failSuggestedAsset(\n suggestedAssetMeta: SuggestedAssetMeta,\n error: unknown,\n ) {\n const failedSuggestedAssetMeta = {\n ...suggestedAssetMeta,\n status: SuggestedAssetStatus.failed,\n error,\n };\n this.hub.emit(\n `${suggestedAssetMeta.id}:finished`,\n failedSuggestedAssetMeta,\n );\n }\n\n /**\n * Fetch metadata for a token.\n *\n * @param tokenAddress - The address of the token.\n * @returns The token metadata.\n */\n private async fetchTokenMetadata(\n tokenAddress: string,\n ): Promise {\n try {\n const token = await fetchTokenMetadata(\n this.config.chainId,\n tokenAddress,\n this.abortController.signal,\n );\n return token;\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes(TOKEN_METADATA_NO_SUPPORT_ERROR)\n ) {\n return undefined;\n }\n throw error;\n }\n }\n\n /**\n * EventEmitter instance used to listen to specific EIP747 events\n */\n hub = new EventEmitter();\n\n /**\n * Name of this controller used during composition\n */\n override name = 'TokensController';\n\n /**\n * Creates a TokensController instance.\n *\n * @param options - The controller options.\n * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes.\n * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.\n * @param options.config - Initial options used to configure this controller.\n * @param options.state - Initial state to set on this controller.\n */\n constructor({\n onPreferencesStateChange,\n onNetworkStateChange,\n config,\n state,\n }: {\n onPreferencesStateChange: (\n listener: (preferencesState: PreferencesState) => void,\n ) => void;\n onNetworkStateChange: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n config?: Partial;\n state?: Partial;\n }) {\n super(config, state);\n\n this.defaultConfig = {\n networkType: MAINNET,\n selectedAddress: '',\n chainId: '',\n provider: undefined,\n ...config,\n };\n\n this.defaultState = {\n tokens: [],\n ignoredTokens: [],\n detectedTokens: [],\n allTokens: {},\n allIgnoredTokens: {},\n allDetectedTokens: {},\n suggestedAssets: [],\n ...state,\n };\n\n this.initialize();\n this.abortController = new AbortController();\n\n onPreferencesStateChange(({ selectedAddress }) => {\n const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;\n const { chainId } = this.config;\n this.configure({ selectedAddress });\n this.update({\n tokens: allTokens[chainId]?.[selectedAddress] || [],\n ignoredTokens: allIgnoredTokens[chainId]?.[selectedAddress] || [],\n detectedTokens: allDetectedTokens[chainId]?.[selectedAddress] || [],\n });\n });\n\n onNetworkStateChange(({ provider }) => {\n const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;\n const { selectedAddress } = this.config;\n const { chainId } = provider;\n this.abortController.abort();\n this.abortController = new AbortController();\n this.configure({ chainId });\n this.ethersProvider = this._instantiateNewEthersProvider();\n this.update({\n tokens: allTokens[chainId]?.[selectedAddress] || [],\n ignoredTokens: allIgnoredTokens[chainId]?.[selectedAddress] || [],\n detectedTokens: allDetectedTokens[chainId]?.[selectedAddress] || [],\n });\n });\n }\n\n _instantiateNewEthersProvider(): any {\n return new ethers.providers.Web3Provider(this.config?.provider);\n }\n\n /**\n * Adds a token to the stored token list.\n *\n * @param address - Hex address of the token contract.\n * @param symbol - Symbol of the token.\n * @param decimals - Number of decimals the token uses.\n * @param image - Image of the token.\n * @returns Current token list.\n */\n async addToken(\n address: string,\n symbol: string,\n decimals: number,\n image?: string,\n ): Promise {\n const currentChainId = this.config.chainId;\n const releaseLock = await this.mutex.acquire();\n try {\n address = toChecksumHexAddress(address);\n const { tokens, ignoredTokens, detectedTokens } = this.state;\n const newTokens: Token[] = [...tokens];\n const [isERC721, tokenMetadata] = await Promise.all([\n this._detectIsERC721(address),\n this.fetchTokenMetadata(address),\n ]);\n if (currentChainId !== this.config.chainId) {\n throw new Error(\n 'TokensController Error: Switched networks while adding token',\n );\n }\n const newEntry: Token = {\n address,\n symbol,\n decimals,\n image:\n image ||\n formatIconUrlWithProxy({\n chainId: this.config.chainId,\n tokenAddress: address,\n }),\n isERC721,\n aggregators: formatAggregatorNames(tokenMetadata?.aggregators || []),\n };\n const previousEntry = newTokens.find(\n (token) => token.address.toLowerCase() === address.toLowerCase(),\n );\n if (previousEntry) {\n const previousIndex = newTokens.indexOf(previousEntry);\n newTokens[previousIndex] = newEntry;\n } else {\n newTokens.push(newEntry);\n }\n\n const newIgnoredTokens = ignoredTokens.filter(\n (tokenAddress) => tokenAddress.toLowerCase() !== address.toLowerCase(),\n );\n const newDetectedTokens = detectedTokens.filter(\n (token) => token.address.toLowerCase() !== address.toLowerCase(),\n );\n const { newAllTokens, newAllIgnoredTokens, newAllDetectedTokens } =\n this._getNewAllTokensState({\n newTokens,\n newIgnoredTokens,\n newDetectedTokens,\n });\n\n this.update({\n tokens: newTokens,\n ignoredTokens: newIgnoredTokens,\n detectedTokens: newDetectedTokens,\n allTokens: newAllTokens,\n allIgnoredTokens: newAllIgnoredTokens,\n allDetectedTokens: newAllDetectedTokens,\n });\n return newTokens;\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Add a batch of tokens.\n *\n * @param tokensToImport - Array of tokens to import.\n */\n async addTokens(tokensToImport: Token[]) {\n const releaseLock = await this.mutex.acquire();\n const { tokens, detectedTokens, ignoredTokens } = this.state;\n const importedTokensMap: { [key: string]: true } = {};\n // Used later to dedupe imported tokens\n const newTokensMap = tokens.reduce((output, current) => {\n output[current.address] = current;\n return output;\n }, {} as { [address: string]: Token });\n\n try {\n tokensToImport.forEach((tokenToAdd) => {\n const { address, symbol, decimals, image, aggregators } = tokenToAdd;\n const checksumAddress = toChecksumHexAddress(address);\n const formattedToken: Token = {\n address: checksumAddress,\n symbol,\n decimals,\n image,\n aggregators,\n };\n newTokensMap[address] = formattedToken;\n importedTokensMap[address.toLowerCase()] = true;\n return formattedToken;\n });\n const newTokens = Object.values(newTokensMap);\n\n const newDetectedTokens = detectedTokens.filter(\n (token) => !importedTokensMap[token.address.toLowerCase()],\n );\n const newIgnoredTokens = ignoredTokens.filter(\n (tokenAddress) => !newTokensMap[tokenAddress.toLowerCase()],\n );\n\n const { newAllTokens, newAllDetectedTokens, newAllIgnoredTokens } =\n this._getNewAllTokensState({\n newTokens,\n newDetectedTokens,\n newIgnoredTokens,\n });\n\n this.update({\n tokens: newTokens,\n allTokens: newAllTokens,\n detectedTokens: newDetectedTokens,\n allDetectedTokens: newAllDetectedTokens,\n ignoredTokens: newIgnoredTokens,\n allIgnoredTokens: newAllIgnoredTokens,\n });\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Ignore a batch of tokens.\n *\n * @param tokenAddressesToIgnore - Array of token addresses to ignore.\n */\n ignoreTokens(tokenAddressesToIgnore: string[]) {\n const { ignoredTokens, detectedTokens, tokens } = this.state;\n const ignoredTokensMap: { [key: string]: true } = {};\n let newIgnoredTokens: string[] = [...ignoredTokens];\n\n const checksummedTokenAddresses = tokenAddressesToIgnore.map((address) => {\n const checksumAddress = toChecksumHexAddress(address);\n ignoredTokensMap[address.toLowerCase()] = true;\n return checksumAddress;\n });\n newIgnoredTokens = [...ignoredTokens, ...checksummedTokenAddresses];\n const newDetectedTokens = detectedTokens.filter(\n (token) => !ignoredTokensMap[token.address.toLowerCase()],\n );\n const newTokens = tokens.filter(\n (token) => !ignoredTokensMap[token.address.toLowerCase()],\n );\n\n const { newAllIgnoredTokens, newAllDetectedTokens, newAllTokens } =\n this._getNewAllTokensState({\n newIgnoredTokens,\n newDetectedTokens,\n newTokens,\n });\n\n this.update({\n ignoredTokens: newIgnoredTokens,\n tokens: newTokens,\n detectedTokens: newDetectedTokens,\n allIgnoredTokens: newAllIgnoredTokens,\n allDetectedTokens: newAllDetectedTokens,\n allTokens: newAllTokens,\n });\n }\n\n /**\n * Adds a batch of detected tokens to the stored token list.\n *\n * @param incomingDetectedTokens - Array of detected tokens to be added or updated.\n */\n async addDetectedTokens(incomingDetectedTokens: Token[]) {\n const releaseLock = await this.mutex.acquire();\n const { tokens, detectedTokens, ignoredTokens } = this.state;\n const newTokens: Token[] = [...tokens];\n const newDetectedTokens: Token[] = [...detectedTokens];\n\n try {\n incomingDetectedTokens.forEach((tokenToAdd) => {\n const { address, symbol, decimals, image, aggregators, isERC721 } =\n tokenToAdd;\n const checksumAddress = toChecksumHexAddress(address);\n const newEntry: Token = {\n address: checksumAddress,\n symbol,\n decimals,\n image,\n isERC721,\n aggregators,\n };\n const previousImportedEntry = newTokens.find(\n (token) =>\n token.address.toLowerCase() === checksumAddress.toLowerCase(),\n );\n if (previousImportedEntry) {\n // Update existing data of imported token\n const previousImportedIndex = newTokens.indexOf(\n previousImportedEntry,\n );\n newTokens[previousImportedIndex] = newEntry;\n } else {\n const ignoredTokenIndex = ignoredTokens.indexOf(address);\n if (ignoredTokenIndex === -1) {\n // Add detected token\n const previousDetectedEntry = newDetectedTokens.find(\n (token) =>\n token.address.toLowerCase() === checksumAddress.toLowerCase(),\n );\n if (previousDetectedEntry) {\n const previousDetectedIndex = newDetectedTokens.indexOf(\n previousDetectedEntry,\n );\n newDetectedTokens[previousDetectedIndex] = newEntry;\n } else {\n newDetectedTokens.push(newEntry);\n }\n }\n }\n });\n\n const { newAllTokens, newAllDetectedTokens } = this._getNewAllTokensState(\n {\n newTokens,\n newDetectedTokens,\n },\n );\n\n this.update({\n tokens: newTokens,\n allTokens: newAllTokens,\n detectedTokens: newDetectedTokens,\n allDetectedTokens: newAllDetectedTokens,\n });\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Adds isERC721 field to token object. This is called when a user attempts to add tokens that\n * were previously added which do not yet had isERC721 field.\n *\n * @param tokenAddress - The contract address of the token requiring the isERC721 field added.\n * @returns The new token object with the added isERC721 field.\n */\n async updateTokenType(tokenAddress: string) {\n const isERC721 = await this._detectIsERC721(tokenAddress);\n const { tokens } = this.state;\n const tokenIndex = tokens.findIndex((token) => {\n return token.address.toLowerCase() === tokenAddress.toLowerCase();\n });\n tokens[tokenIndex].isERC721 = isERC721;\n this.update({ tokens });\n return tokens[tokenIndex];\n }\n\n /**\n * Detects whether or not a token is ERC-721 compatible.\n *\n * @param tokenAddress - The token contract address.\n * @returns A boolean indicating whether the token address passed in supports the EIP-721\n * interface.\n */\n async _detectIsERC721(tokenAddress: string) {\n const checksumAddress = toChecksumHexAddress(tokenAddress);\n // if this token is already in our contract metadata map we don't need\n // to check against the contract\n if (contractsMap[checksumAddress]?.erc721 === true) {\n return Promise.resolve(true);\n } else if (contractsMap[checksumAddress]?.erc20 === true) {\n return Promise.resolve(false);\n }\n\n const tokenContract = await this._createEthersContract(\n tokenAddress,\n abiERC721,\n this.ethersProvider,\n );\n try {\n return await tokenContract.supportsInterface(ERC721_INTERFACE_ID);\n } catch (error: any) {\n // currently we see a variety of errors across different networks when\n // token contracts are not ERC721 compatible. We need to figure out a better\n // way of differentiating token interface types but for now if we get an error\n // we have to assume the token is not ERC721 compatible.\n return false;\n }\n }\n\n async _createEthersContract(\n tokenAddress: string,\n abi: string,\n ethersProvider: any,\n ): Promise {\n const tokenContract = await new ethers.Contract(\n tokenAddress,\n abi,\n ethersProvider,\n );\n return tokenContract;\n }\n\n _generateRandomId(): string {\n return random();\n }\n\n /**\n * Adds a new suggestedAsset to state. Parameters will be validated according to\n * asset type being watched. A `:pending` hub event will be emitted once added.\n *\n * @param asset - The asset to be watched. For now only ERC20 tokens are accepted.\n * @param type - The asset type.\n * @returns Object containing a Promise resolving to the suggestedAsset address if accepted.\n */\n async watchAsset(asset: Token, type: string): Promise {\n const suggestedAssetMeta = {\n asset,\n id: this._generateRandomId(),\n status: SuggestedAssetStatus.pending as SuggestedAssetStatus.pending,\n time: Date.now(),\n type,\n };\n try {\n switch (type) {\n case 'ERC20':\n validateTokenToWatch(asset);\n break;\n default:\n throw new Error(`Asset of type ${type} not supported`);\n }\n } catch (error) {\n this.failSuggestedAsset(suggestedAssetMeta, error);\n return Promise.reject(error);\n }\n\n const result: Promise = new Promise((resolve, reject) => {\n this.hub.once(\n `${suggestedAssetMeta.id}:finished`,\n (meta: SuggestedAssetMeta) => {\n switch (meta.status) {\n case SuggestedAssetStatus.accepted:\n return resolve(meta.asset.address);\n case SuggestedAssetStatus.rejected:\n return reject(new Error('User rejected to watch the asset.'));\n case SuggestedAssetStatus.failed:\n return reject(new Error(meta.error.message));\n /* istanbul ignore next */\n default:\n return reject(new Error(`Unknown status: ${meta.status}`));\n }\n },\n );\n });\n\n const { suggestedAssets } = this.state;\n suggestedAssets.push(suggestedAssetMeta);\n this.update({ suggestedAssets: [...suggestedAssets] });\n this.hub.emit('pendingSuggestedAsset', suggestedAssetMeta);\n return { result, suggestedAssetMeta };\n }\n\n /**\n * Accepts to watch an asset and updates it's status and deletes the suggestedAsset from state,\n * adding the asset to corresponding asset state. In this case ERC20 tokens.\n * A `:finished` hub event is fired after accepted or failure.\n *\n * @param suggestedAssetID - The ID of the suggestedAsset to accept.\n */\n async acceptWatchAsset(suggestedAssetID: string): Promise {\n const { suggestedAssets } = this.state;\n const index = suggestedAssets.findIndex(\n ({ id }) => suggestedAssetID === id,\n );\n const suggestedAssetMeta = suggestedAssets[index];\n try {\n switch (suggestedAssetMeta.type) {\n case 'ERC20':\n const { address, symbol, decimals, image } = suggestedAssetMeta.asset;\n await this.addToken(address, symbol, decimals, image);\n suggestedAssetMeta.status = SuggestedAssetStatus.accepted;\n this.hub.emit(\n `${suggestedAssetMeta.id}:finished`,\n suggestedAssetMeta,\n );\n break;\n default:\n throw new Error(\n `Asset of type ${suggestedAssetMeta.type} not supported`,\n );\n }\n } catch (error) {\n this.failSuggestedAsset(suggestedAssetMeta, error);\n }\n const newSuggestedAssets = suggestedAssets.filter(\n ({ id }) => id !== suggestedAssetID,\n );\n this.update({ suggestedAssets: [...newSuggestedAssets] });\n }\n\n /**\n * Rejects a watchAsset request based on its ID by setting its status to \"rejected\"\n * and emitting a `:finished` hub event.\n *\n * @param suggestedAssetID - The ID of the suggestedAsset to accept.\n */\n rejectWatchAsset(suggestedAssetID: string) {\n const { suggestedAssets } = this.state;\n const index = suggestedAssets.findIndex(\n ({ id }) => suggestedAssetID === id,\n );\n const suggestedAssetMeta = suggestedAssets[index];\n if (!suggestedAssetMeta) {\n return;\n }\n suggestedAssetMeta.status = SuggestedAssetStatus.rejected;\n this.hub.emit(`${suggestedAssetMeta.id}:finished`, suggestedAssetMeta);\n const newSuggestedAssets = suggestedAssets.filter(\n ({ id }) => id !== suggestedAssetID,\n );\n this.update({ suggestedAssets: [...newSuggestedAssets] });\n }\n\n /**\n * Takes a new tokens and ignoredTokens array for the current network/account combination\n * and returns new allTokens and allIgnoredTokens state to update to.\n *\n * @param params - Object that holds token params.\n * @param params.newTokens - The new tokens to set for the current network and selected account.\n * @param params.newIgnoredTokens - The new ignored tokens to set for the current network and selected account.\n * @param params.newDetectedTokens - The new detected tokens to set for the current network and selected account.\n * @returns The updated `allTokens` and `allIgnoredTokens` state.\n */\n _getNewAllTokensState(params: {\n newTokens?: Token[];\n newIgnoredTokens?: string[];\n newDetectedTokens?: Token[];\n }) {\n const { newTokens, newIgnoredTokens, newDetectedTokens } = params;\n const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;\n const { chainId, selectedAddress } = this.config;\n\n let newAllTokens = allTokens;\n if (newTokens) {\n const networkTokens = allTokens[chainId];\n const newNetworkTokens = {\n ...networkTokens,\n ...{ [selectedAddress]: newTokens },\n };\n newAllTokens = {\n ...allTokens,\n ...{ [chainId]: newNetworkTokens },\n };\n }\n\n let newAllIgnoredTokens = allIgnoredTokens;\n if (newIgnoredTokens) {\n const networkIgnoredTokens = allIgnoredTokens[chainId];\n const newIgnoredNetworkTokens = {\n ...networkIgnoredTokens,\n ...{ [selectedAddress]: newIgnoredTokens },\n };\n newAllIgnoredTokens = {\n ...allIgnoredTokens,\n ...{ [chainId]: newIgnoredNetworkTokens },\n };\n }\n\n let newAllDetectedTokens = allDetectedTokens;\n if (newDetectedTokens) {\n const networkDetectedTokens = allDetectedTokens[chainId];\n const newDetectedNetworkTokens = {\n ...networkDetectedTokens,\n ...{ [selectedAddress]: newDetectedTokens },\n };\n newAllDetectedTokens = {\n ...allDetectedTokens,\n ...{ [chainId]: newDetectedNetworkTokens },\n };\n }\n return { newAllTokens, newAllIgnoredTokens, newAllDetectedTokens };\n }\n\n /**\n * Removes all tokens from the ignored list.\n */\n clearIgnoredTokens() {\n this.update({ ignoredTokens: [], allIgnoredTokens: {} });\n }\n}\n\nexport default TokensController;\n"]} \ No newline at end of file diff --git a/dist/assets/assetsUtil.d.ts b/dist/assets/assetsUtil.d.ts new file mode 100644 index 0000000000..011688ad1d --- /dev/null +++ b/dist/assets/assetsUtil.d.ts @@ -0,0 +1,30 @@ +import { Collectible, CollectibleMetadata } from './CollectiblesController'; +/** + * Compares collectible metadata entries to any collectible entry. + * We need this method when comparing a new fetched collectible metadata, in case a entry changed to a defined value, + * there's a need to update the collectible in state. + * + * @param newCollectibleMetadata - Collectible metadata object. + * @param collectible - Collectible object to compare with. + * @returns Whether there are differences. + */ +export declare function compareCollectiblesMetadata(newCollectibleMetadata: CollectibleMetadata, collectible: Collectible): boolean; +/** + * Formats aggregator names to presentable format. + * + * @param aggregators - List of token list names in camelcase. + * @returns Formatted aggregator names. + */ +export declare const formatAggregatorNames: (aggregators: string[]) => string[]; +/** + * Format token list assets to use image proxy from Codefi. + * + * @param params - Object that contains chainID and tokenAddress. + * @param params.chainId - ChainID of network in decimal or hexadecimal format. + * @param params.tokenAddress - Address of token in mixed or lowercase. + * @returns Formatted image url + */ +export declare const formatIconUrlWithProxy: ({ chainId, tokenAddress, }: { + chainId: string; + tokenAddress: string; +}) => string; diff --git a/dist/assets/assetsUtil.js b/dist/assets/assetsUtil.js new file mode 100644 index 0000000000..9875770cac --- /dev/null +++ b/dist/assets/assetsUtil.js @@ -0,0 +1,91 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.formatIconUrlWithProxy = exports.formatAggregatorNames = exports.compareCollectiblesMetadata = void 0; +const ethereumjs_util_1 = require("ethereumjs-util"); +const util_1 = require("../util"); +/** + * Compares collectible metadata entries to any collectible entry. + * We need this method when comparing a new fetched collectible metadata, in case a entry changed to a defined value, + * there's a need to update the collectible in state. + * + * @param newCollectibleMetadata - Collectible metadata object. + * @param collectible - Collectible object to compare with. + * @returns Whether there are differences. + */ +function compareCollectiblesMetadata(newCollectibleMetadata, collectible) { + const keys = [ + 'image', + 'backgroundColor', + 'imagePreview', + 'imageThumbnail', + 'imageOriginal', + 'animation', + 'animationOriginal', + 'externalLink', + ]; + const differentValues = keys.reduce((value, key) => { + if (newCollectibleMetadata[key] && + newCollectibleMetadata[key] !== collectible[key]) { + return value + 1; + } + return value; + }, 0); + return differentValues > 0; +} +exports.compareCollectiblesMetadata = compareCollectiblesMetadata; +const aggregatorNameByKey = { + aave: 'Aave', + bancor: 'Bancor', + cmc: 'CMC', + cryptocom: 'Crypto.com', + coinGecko: 'CoinGecko', + oneInch: '1inch', + paraswap: 'Paraswap', + pmm: 'PMM', + zapper: 'Zapper', + zerion: 'Zerion', + zeroEx: '0x', + synthetix: 'Synthetix', + yearn: 'Yearn', + apeswap: 'ApeSwap', + binanceDex: 'BinanceDex', + pancakeTop100: 'PancakeTop100', + pancakeExtended: 'PancakeExtended', + balancer: 'Balancer', + quickswap: 'QuickSwap', + matcha: 'Matcha', + pangolinDex: 'PangolinDex', + pangolinDexStableCoin: 'PangolinDexStableCoin', + pangolinDexAvaxBridge: 'PangolinDexAvaxBridge', + traderJoe: 'TraderJoe', + airswapLight: 'AirswapLight', + kleros: 'Kleros', +}; +/** + * Formats aggregator names to presentable format. + * + * @param aggregators - List of token list names in camelcase. + * @returns Formatted aggregator names. + */ +const formatAggregatorNames = (aggregators) => { + return aggregators.map((key) => aggregatorNameByKey[key] || + `${key[0].toUpperCase()}${key.substring(1, key.length)}`); +}; +exports.formatAggregatorNames = formatAggregatorNames; +/** + * Format token list assets to use image proxy from Codefi. + * + * @param params - Object that contains chainID and tokenAddress. + * @param params.chainId - ChainID of network in decimal or hexadecimal format. + * @param params.tokenAddress - Address of token in mixed or lowercase. + * @returns Formatted image url + */ +const formatIconUrlWithProxy = ({ chainId, tokenAddress, }) => { + let chainIdDec = chainId; + if ((0, ethereumjs_util_1.isHexString)(chainId)) { + chainIdDec = (0, util_1.convertHexToDecimal)(chainId).toString(); + } + return `https://static.metaswap.codefi.network/api/v1/tokenIcons/${chainIdDec}/${tokenAddress.toLowerCase()}.png`; +}; +exports.formatIconUrlWithProxy = formatIconUrlWithProxy; +//# sourceMappingURL=assetsUtil.js.map \ No newline at end of file diff --git a/dist/assets/assetsUtil.js.map b/dist/assets/assetsUtil.js.map new file mode 100644 index 0000000000..5be8ed27b3 --- /dev/null +++ b/dist/assets/assetsUtil.js.map @@ -0,0 +1 @@ +{"version":3,"file":"assetsUtil.js","sourceRoot":"","sources":["../../src/assets/assetsUtil.ts"],"names":[],"mappings":";;;AAAA,qDAA8C;AAC9C,kCAA8C;AAG9C;;;;;;;;GAQG;AACH,SAAgB,2BAA2B,CACzC,sBAA2C,EAC3C,WAAwB;IAExB,MAAM,IAAI,GAAkC;QAC1C,OAAO;QACP,iBAAiB;QACjB,cAAc;QACd,gBAAgB;QAChB,eAAe;QACf,WAAW;QACX,mBAAmB;QACnB,cAAc;KACf,CAAC;IACF,MAAM,eAAe,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACjD,IACE,sBAAsB,CAAC,GAAG,CAAC;YAC3B,sBAAsB,CAAC,GAAG,CAAC,KAAK,WAAW,CAAC,GAAG,CAAC,EAChD;YACA,OAAO,KAAK,GAAG,CAAC,CAAC;SAClB;QACD,OAAO,KAAK,CAAC;IACf,CAAC,EAAE,CAAC,CAAC,CAAC;IACN,OAAO,eAAe,GAAG,CAAC,CAAC;AAC7B,CAAC;AAxBD,kEAwBC;AAED,MAAM,mBAAmB,GAA2B;IAClD,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,QAAQ;IAChB,GAAG,EAAE,KAAK;IACV,SAAS,EAAE,YAAY;IACvB,SAAS,EAAE,WAAW;IACtB,OAAO,EAAE,OAAO;IAChB,QAAQ,EAAE,UAAU;IACpB,GAAG,EAAE,KAAK;IACV,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,IAAI;IACZ,SAAS,EAAE,WAAW;IACtB,KAAK,EAAE,OAAO;IACd,OAAO,EAAE,SAAS;IAClB,UAAU,EAAE,YAAY;IACxB,aAAa,EAAE,eAAe;IAC9B,eAAe,EAAE,iBAAiB;IAClC,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,WAAW;IACtB,MAAM,EAAE,QAAQ;IAChB,WAAW,EAAE,aAAa;IAC1B,qBAAqB,EAAE,uBAAuB;IAC9C,qBAAqB,EAAE,uBAAuB;IAC9C,SAAS,EAAE,WAAW;IACtB,YAAY,EAAE,cAAc;IAC5B,MAAM,EAAE,QAAQ;CACjB,CAAC;AAEF;;;;;GAKG;AACI,MAAM,qBAAqB,GAAG,CAAC,WAAqB,EAAE,EAAE;IAC7D,OAAO,WAAW,CAAC,GAAG,CACpB,CAAC,GAAG,EAAE,EAAE,CACN,mBAAmB,CAAC,GAAG,CAAC;QACxB,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAC3D,CAAC;AACJ,CAAC,CAAC;AANW,QAAA,qBAAqB,yBAMhC;AAEF;;;;;;;GAOG;AACI,MAAM,sBAAsB,GAAG,CAAC,EACrC,OAAO,EACP,YAAY,GAIb,EAAE,EAAE;IACH,IAAI,UAAU,GAAG,OAAO,CAAC;IACzB,IAAI,IAAA,6BAAW,EAAC,OAAO,CAAC,EAAE;QACxB,UAAU,GAAG,IAAA,0BAAmB,EAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;KACtD;IACD,OAAO,4DAA4D,UAAU,IAAI,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC;AACpH,CAAC,CAAC;AAZW,QAAA,sBAAsB,0BAYjC","sourcesContent":["import { isHexString } from 'ethereumjs-util';\nimport { convertHexToDecimal } from '../util';\nimport { Collectible, CollectibleMetadata } from './CollectiblesController';\n\n/**\n * Compares collectible metadata entries to any collectible entry.\n * We need this method when comparing a new fetched collectible metadata, in case a entry changed to a defined value,\n * there's a need to update the collectible in state.\n *\n * @param newCollectibleMetadata - Collectible metadata object.\n * @param collectible - Collectible object to compare with.\n * @returns Whether there are differences.\n */\nexport function compareCollectiblesMetadata(\n newCollectibleMetadata: CollectibleMetadata,\n collectible: Collectible,\n) {\n const keys: (keyof CollectibleMetadata)[] = [\n 'image',\n 'backgroundColor',\n 'imagePreview',\n 'imageThumbnail',\n 'imageOriginal',\n 'animation',\n 'animationOriginal',\n 'externalLink',\n ];\n const differentValues = keys.reduce((value, key) => {\n if (\n newCollectibleMetadata[key] &&\n newCollectibleMetadata[key] !== collectible[key]\n ) {\n return value + 1;\n }\n return value;\n }, 0);\n return differentValues > 0;\n}\n\nconst aggregatorNameByKey: Record = {\n aave: 'Aave',\n bancor: 'Bancor',\n cmc: 'CMC',\n cryptocom: 'Crypto.com',\n coinGecko: 'CoinGecko',\n oneInch: '1inch',\n paraswap: 'Paraswap',\n pmm: 'PMM',\n zapper: 'Zapper',\n zerion: 'Zerion',\n zeroEx: '0x',\n synthetix: 'Synthetix',\n yearn: 'Yearn',\n apeswap: 'ApeSwap',\n binanceDex: 'BinanceDex',\n pancakeTop100: 'PancakeTop100',\n pancakeExtended: 'PancakeExtended',\n balancer: 'Balancer',\n quickswap: 'QuickSwap',\n matcha: 'Matcha',\n pangolinDex: 'PangolinDex',\n pangolinDexStableCoin: 'PangolinDexStableCoin',\n pangolinDexAvaxBridge: 'PangolinDexAvaxBridge',\n traderJoe: 'TraderJoe',\n airswapLight: 'AirswapLight',\n kleros: 'Kleros',\n};\n\n/**\n * Formats aggregator names to presentable format.\n *\n * @param aggregators - List of token list names in camelcase.\n * @returns Formatted aggregator names.\n */\nexport const formatAggregatorNames = (aggregators: string[]) => {\n return aggregators.map(\n (key) =>\n aggregatorNameByKey[key] ||\n `${key[0].toUpperCase()}${key.substring(1, key.length)}`,\n );\n};\n\n/**\n * Format token list assets to use image proxy from Codefi.\n *\n * @param params - Object that contains chainID and tokenAddress.\n * @param params.chainId - ChainID of network in decimal or hexadecimal format.\n * @param params.tokenAddress - Address of token in mixed or lowercase.\n * @returns Formatted image url\n */\nexport const formatIconUrlWithProxy = ({\n chainId,\n tokenAddress,\n}: {\n chainId: string;\n tokenAddress: string;\n}) => {\n let chainIdDec = chainId;\n if (isHexString(chainId)) {\n chainIdDec = convertHexToDecimal(chainId).toString();\n }\n return `https://static.metaswap.codefi.network/api/v1/tokenIcons/${chainIdDec}/${tokenAddress.toLowerCase()}.png`;\n};\n"]} \ No newline at end of file diff --git a/dist/constants.d.ts b/dist/constants.d.ts new file mode 100644 index 0000000000..d4cb157aa8 --- /dev/null +++ b/dist/constants.d.ts @@ -0,0 +1,29 @@ +export declare const MAINNET = "mainnet"; +export declare const RPC = "rpc"; +export declare const FALL_BACK_VS_CURRENCY = "ETH"; +export declare const IPFS_DEFAULT_GATEWAY_URL = "https://cloudflare-ipfs.com/ipfs/"; +export declare const RINKEBY_CHAIN_ID = "4"; +export declare const ERC721 = "ERC721"; +export declare const ERC1155 = "ERC1155"; +export declare const ERC20 = "ERC20"; +export declare const ERC721_INTERFACE_ID = "0x80ac58cd"; +export declare const ERC721_METADATA_INTERFACE_ID = "0x5b5e139f"; +export declare const ERC721_ENUMERABLE_INTERFACE_ID = "0x780e9d63"; +export declare const ERC1155_INTERFACE_ID = "0xd9b67a26"; +export declare const ERC1155_METADATA_URI_INTERFACE_ID = "0x0e89341c"; +export declare const ERC1155_TOKEN_RECEIVER_INTERFACE_ID = "0x4e2312e0"; +export declare const GWEI = "gwei"; +export declare const ASSET_TYPES: { + NATIVE: string; + TOKEN: string; + COLLECTIBLE: string; + UNKNOWN: string; +}; +declare type tickerType = { + [key: string]: string; +}; +export declare const TESTNET_TICKER_SYMBOLS: tickerType; +export declare const OPENSEA_PROXY_URL = "https://proxy.metaswap.codefi.network/opensea/v1/api/v1"; +export declare const OPENSEA_API_URL = "https://api.opensea.io/api/v1"; +export declare const OPENSEA_TEST_API_URL = "https://testnets-api.opensea.io/api/v1"; +export {}; diff --git a/dist/constants.js b/dist/constants.js new file mode 100644 index 0000000000..0adf66e427 --- /dev/null +++ b/dist/constants.js @@ -0,0 +1,41 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.OPENSEA_TEST_API_URL = exports.OPENSEA_API_URL = exports.OPENSEA_PROXY_URL = exports.TESTNET_TICKER_SYMBOLS = exports.ASSET_TYPES = exports.GWEI = exports.ERC1155_TOKEN_RECEIVER_INTERFACE_ID = exports.ERC1155_METADATA_URI_INTERFACE_ID = exports.ERC1155_INTERFACE_ID = exports.ERC721_ENUMERABLE_INTERFACE_ID = exports.ERC721_METADATA_INTERFACE_ID = exports.ERC721_INTERFACE_ID = exports.ERC20 = exports.ERC1155 = exports.ERC721 = exports.RINKEBY_CHAIN_ID = exports.IPFS_DEFAULT_GATEWAY_URL = exports.FALL_BACK_VS_CURRENCY = exports.RPC = exports.MAINNET = void 0; +exports.MAINNET = 'mainnet'; +exports.RPC = 'rpc'; +exports.FALL_BACK_VS_CURRENCY = 'ETH'; +exports.IPFS_DEFAULT_GATEWAY_URL = 'https://cloudflare-ipfs.com/ipfs/'; +// NETWORKS ID +exports.RINKEBY_CHAIN_ID = '4'; +// TOKEN STANDARDS +exports.ERC721 = 'ERC721'; +exports.ERC1155 = 'ERC1155'; +exports.ERC20 = 'ERC20'; +// TOKEN INTERFACE IDS +exports.ERC721_INTERFACE_ID = '0x80ac58cd'; +exports.ERC721_METADATA_INTERFACE_ID = '0x5b5e139f'; +exports.ERC721_ENUMERABLE_INTERFACE_ID = '0x780e9d63'; +exports.ERC1155_INTERFACE_ID = '0xd9b67a26'; +exports.ERC1155_METADATA_URI_INTERFACE_ID = '0x0e89341c'; +exports.ERC1155_TOKEN_RECEIVER_INTERFACE_ID = '0x4e2312e0'; +// UNITS +exports.GWEI = 'gwei'; +// ASSET TYPES +exports.ASSET_TYPES = { + NATIVE: 'NATIVE', + TOKEN: 'TOKEN', + COLLECTIBLE: 'COLLECTIBLE', + UNKNOWN: 'UNKNOWN', +}; +// TICKER SYMBOLS +exports.TESTNET_TICKER_SYMBOLS = { + RINKEBY: 'RinkebyETH', + GOERLI: 'GoerliETH', + ROPSTEN: 'RopstenETH', + KOVAN: 'KovanETH', +}; +// APIs +exports.OPENSEA_PROXY_URL = 'https://proxy.metaswap.codefi.network/opensea/v1/api/v1'; +exports.OPENSEA_API_URL = 'https://api.opensea.io/api/v1'; +exports.OPENSEA_TEST_API_URL = 'https://testnets-api.opensea.io/api/v1'; +//# sourceMappingURL=constants.js.map \ No newline at end of file diff --git a/dist/constants.js.map b/dist/constants.js.map new file mode 100644 index 0000000000..8e136440b7 --- /dev/null +++ b/dist/constants.js.map @@ -0,0 +1 @@ +{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":";;;AAAa,QAAA,OAAO,GAAG,SAAS,CAAC;AACpB,QAAA,GAAG,GAAG,KAAK,CAAC;AACZ,QAAA,qBAAqB,GAAG,KAAK,CAAC;AAC9B,QAAA,wBAAwB,GAAG,mCAAmC,CAAC;AAE5E,cAAc;AACD,QAAA,gBAAgB,GAAG,GAAG,CAAC;AAEpC,kBAAkB;AACL,QAAA,MAAM,GAAG,QAAQ,CAAC;AAClB,QAAA,OAAO,GAAG,SAAS,CAAC;AACpB,QAAA,KAAK,GAAG,OAAO,CAAC;AAE7B,sBAAsB;AACT,QAAA,mBAAmB,GAAG,YAAY,CAAC;AACnC,QAAA,4BAA4B,GAAG,YAAY,CAAC;AAC5C,QAAA,8BAA8B,GAAG,YAAY,CAAC;AAC9C,QAAA,oBAAoB,GAAG,YAAY,CAAC;AACpC,QAAA,iCAAiC,GAAG,YAAY,CAAC;AACjD,QAAA,mCAAmC,GAAG,YAAY,CAAC;AAEhE,QAAQ;AACK,QAAA,IAAI,GAAG,MAAM,CAAC;AAE3B,cAAc;AACD,QAAA,WAAW,GAAG;IACzB,MAAM,EAAE,QAAQ;IAChB,KAAK,EAAE,OAAO;IACd,WAAW,EAAE,aAAa;IAC1B,OAAO,EAAE,SAAS;CACnB,CAAC;AAMF,iBAAiB;AACJ,QAAA,sBAAsB,GAAe;IAChD,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,YAAY;IACrB,KAAK,EAAE,UAAU;CAClB,CAAC;AAEF,OAAO;AACM,QAAA,iBAAiB,GAC5B,yDAAyD,CAAC;AAC/C,QAAA,eAAe,GAAG,+BAA+B,CAAC;AAClD,QAAA,oBAAoB,GAAG,wCAAwC,CAAC","sourcesContent":["export const MAINNET = 'mainnet';\nexport const RPC = 'rpc';\nexport const FALL_BACK_VS_CURRENCY = 'ETH';\nexport const IPFS_DEFAULT_GATEWAY_URL = 'https://cloudflare-ipfs.com/ipfs/';\n\n// NETWORKS ID\nexport const RINKEBY_CHAIN_ID = '4';\n\n// TOKEN STANDARDS\nexport const ERC721 = 'ERC721';\nexport const ERC1155 = 'ERC1155';\nexport const ERC20 = 'ERC20';\n\n// TOKEN INTERFACE IDS\nexport const ERC721_INTERFACE_ID = '0x80ac58cd';\nexport const ERC721_METADATA_INTERFACE_ID = '0x5b5e139f';\nexport const ERC721_ENUMERABLE_INTERFACE_ID = '0x780e9d63';\nexport const ERC1155_INTERFACE_ID = '0xd9b67a26';\nexport const ERC1155_METADATA_URI_INTERFACE_ID = '0x0e89341c';\nexport const ERC1155_TOKEN_RECEIVER_INTERFACE_ID = '0x4e2312e0';\n\n// UNITS\nexport const GWEI = 'gwei';\n\n// ASSET TYPES\nexport const ASSET_TYPES = {\n NATIVE: 'NATIVE',\n TOKEN: 'TOKEN',\n COLLECTIBLE: 'COLLECTIBLE',\n UNKNOWN: 'UNKNOWN',\n};\n\ntype tickerType = {\n [key: string]: string;\n};\n\n// TICKER SYMBOLS\nexport const TESTNET_TICKER_SYMBOLS: tickerType = {\n RINKEBY: 'RinkebyETH',\n GOERLI: 'GoerliETH',\n ROPSTEN: 'RopstenETH',\n KOVAN: 'KovanETH',\n};\n\n// APIs\nexport const OPENSEA_PROXY_URL =\n 'https://proxy.metaswap.codefi.network/opensea/v1/api/v1';\nexport const OPENSEA_API_URL = 'https://api.opensea.io/api/v1';\nexport const OPENSEA_TEST_API_URL = 'https://testnets-api.opensea.io/api/v1';\n"]} \ No newline at end of file diff --git a/dist/gas/GasFeeController.d.ts b/dist/gas/GasFeeController.d.ts new file mode 100644 index 0000000000..368e527849 --- /dev/null +++ b/dist/gas/GasFeeController.d.ts @@ -0,0 +1,230 @@ +import type { Patch } from 'immer'; +import { BaseController } from '../BaseControllerV2'; +import type { RestrictedControllerMessenger } from '../ControllerMessenger'; +import type { NetworkController, NetworkState } from '../network/NetworkController'; +export declare const LEGACY_GAS_PRICES_API_URL = "https://api.metaswap.codefi.network/gasPrices"; +export declare type unknownString = 'unknown'; +export declare type FeeMarketEstimateType = 'fee-market'; +export declare type LegacyEstimateType = 'legacy'; +export declare type EthGasPriceEstimateType = 'eth_gasPrice'; +export declare type NoEstimateType = 'none'; +/** + * Indicates which type of gasEstimate the controller is currently returning. + * This is useful as a way of asserting that the shape of gasEstimates matches + * expectations. NONE is a special case indicating that no previous gasEstimate + * has been fetched. + */ +export declare const GAS_ESTIMATE_TYPES: { + FEE_MARKET: "fee-market"; + LEGACY: "legacy"; + ETH_GASPRICE: "eth_gasPrice"; + NONE: "none"; +}; +export declare type GasEstimateType = FeeMarketEstimateType | EthGasPriceEstimateType | LegacyEstimateType | NoEstimateType; +export declare type EstimatedGasFeeTimeBounds = { + lowerTimeBound: number | null; + upperTimeBound: number | unknownString; +}; +/** + * @type EthGasPriceEstimate + * + * A single gas price estimate for networks and accounts that don't support EIP-1559 + * This estimate comes from eth_gasPrice but is converted to dec gwei to match other + * return values + * @property gasPrice - A GWEI dec string + */ +export declare type EthGasPriceEstimate = { + gasPrice: string; +}; +/** + * @type LegacyGasPriceEstimate + * + * A set of gas price estimates for networks and accounts that don't support EIP-1559 + * These estimates include low, medium and high all as strings representing gwei in + * decimal format. + * @property high - gasPrice, in decimal gwei string format, suggested for fast inclusion + * @property medium - gasPrice, in decimal gwei string format, suggested for avg inclusion + * @property low - gasPrice, in decimal gwei string format, suggested for slow inclusion + */ +export declare type LegacyGasPriceEstimate = { + high: string; + medium: string; + low: string; +}; +/** + * @type Eip1559GasFee + * + * Data necessary to provide an estimate of a gas fee with a specific tip + * @property minWaitTimeEstimate - The fastest the transaction will take, in milliseconds + * @property maxWaitTimeEstimate - The slowest the transaction will take, in milliseconds + * @property suggestedMaxPriorityFeePerGas - A suggested "tip", a GWEI hex number + * @property suggestedMaxFeePerGas - A suggested max fee, the most a user will pay. a GWEI hex number + */ +export declare type Eip1559GasFee = { + minWaitTimeEstimate: number; + maxWaitTimeEstimate: number; + suggestedMaxPriorityFeePerGas: string; + suggestedMaxFeePerGas: string; +}; +/** + * @type GasFeeEstimates + * + * Data necessary to provide multiple GasFee estimates, and supporting information, to the user + * @property low - A GasFee for a minimum necessary combination of tip and maxFee + * @property medium - A GasFee for a recommended combination of tip and maxFee + * @property high - A GasFee for a high combination of tip and maxFee + * @property estimatedBaseFee - An estimate of what the base fee will be for the pending/next block. A GWEI dec number + * @property networkCongestion - A normalized number that can be used to gauge the congestion + * level of the network, with 0 meaning not congested and 1 meaning extremely congested + */ +export declare type GasFeeEstimates = SourcedGasFeeEstimates | FallbackGasFeeEstimates; +declare type SourcedGasFeeEstimates = { + low: Eip1559GasFee; + medium: Eip1559GasFee; + high: Eip1559GasFee; + estimatedBaseFee: string; + historicalBaseFeeRange: [string, string]; + baseFeeTrend: 'up' | 'down' | 'level'; + latestPriorityFeeRange: [string, string]; + historicalPriorityFeeRange: [string, string]; + priorityFeeTrend: 'up' | 'down' | 'level'; + networkCongestion: number; +}; +declare type FallbackGasFeeEstimates = { + low: Eip1559GasFee; + medium: Eip1559GasFee; + high: Eip1559GasFee; + estimatedBaseFee: string; + historicalBaseFeeRange: null; + baseFeeTrend: null; + latestPriorityFeeRange: null; + historicalPriorityFeeRange: null; + priorityFeeTrend: null; + networkCongestion: null; +}; +export declare type GasFeeStateEthGasPrice = { + gasFeeEstimates: EthGasPriceEstimate; + estimatedGasFeeTimeBounds: Record; + gasEstimateType: EthGasPriceEstimateType; +}; +export declare type GasFeeStateFeeMarket = { + gasFeeEstimates: GasFeeEstimates; + estimatedGasFeeTimeBounds: EstimatedGasFeeTimeBounds | Record; + gasEstimateType: FeeMarketEstimateType; +}; +export declare type GasFeeStateLegacy = { + gasFeeEstimates: LegacyGasPriceEstimate; + estimatedGasFeeTimeBounds: Record; + gasEstimateType: LegacyEstimateType; +}; +export declare type GasFeeStateNoEstimates = { + gasFeeEstimates: Record; + estimatedGasFeeTimeBounds: Record; + gasEstimateType: NoEstimateType; +}; +export declare type FetchGasFeeEstimateOptions = { + shouldUpdateState?: boolean; +}; +/** + * @type GasFeeState + * + * Gas Fee controller state + * @property gasFeeEstimates - Gas fee estimate data based on new EIP-1559 properties + * @property estimatedGasFeeTimeBounds - Estimates representing the minimum and maximum + */ +export declare type GasFeeState = GasFeeStateEthGasPrice | GasFeeStateFeeMarket | GasFeeStateLegacy | GasFeeStateNoEstimates; +declare const name = "GasFeeController"; +export declare type GasFeeStateChange = { + type: `${typeof name}:stateChange`; + payload: [GasFeeState, Patch[]]; +}; +export declare type GetGasFeeState = { + type: `${typeof name}:getState`; + handler: () => GasFeeState; +}; +declare type GasFeeMessenger = RestrictedControllerMessenger; +/** + * Controller that retrieves gas fee estimate data and polls for updated data on a set interval + */ +export declare class GasFeeController extends BaseController { + private intervalId?; + private intervalDelay; + private pollTokens; + private legacyAPIEndpoint; + private EIP1559APIEndpoint; + private getCurrentNetworkEIP1559Compatibility; + private getCurrentNetworkLegacyGasAPICompatibility; + private getCurrentAccountEIP1559Compatibility; + private getChainId; + private currentChainId; + private ethQuery; + private clientId?; + /** + * Creates a GasFeeController instance. + * + * @param options - The controller options. + * @param options.interval - The time in milliseconds to wait between polls. + * @param options.messenger - The controller messenger. + * @param options.state - The initial state. + * @param options.getCurrentNetworkEIP1559Compatibility - Determines whether or not the current + * network is EIP-1559 compatible. + * @param options.getCurrentNetworkLegacyGasAPICompatibility - Determines whether or not the + * current network is compatible with the legacy gas price API. + * @param options.getCurrentAccountEIP1559Compatibility - Determines whether or not the current + * account is EIP-1559 compatible. + * @param options.getChainId - Returns the current chain ID. + * @param options.getProvider - Returns a network provider for the current network. + * @param options.onNetworkStateChange - A function for registering an event handler for the + * network state change event. + * @param options.legacyAPIEndpoint - The legacy gas price API URL. This option is primarily for + * testing purposes. + * @param options.EIP1559APIEndpoint - The EIP-1559 gas price API URL. This option is primarily + * for testing purposes. + * @param options.clientId - The client ID used to identify to the gas estimation API who is + * asking for estimates. + */ + constructor({ interval, messenger, state, getCurrentNetworkEIP1559Compatibility, getCurrentAccountEIP1559Compatibility, getChainId, getCurrentNetworkLegacyGasAPICompatibility, getProvider, onNetworkStateChange, legacyAPIEndpoint, EIP1559APIEndpoint, clientId, }: { + interval?: number; + messenger: GasFeeMessenger; + state?: GasFeeState; + getCurrentNetworkEIP1559Compatibility: () => Promise; + getCurrentNetworkLegacyGasAPICompatibility: () => boolean; + getCurrentAccountEIP1559Compatibility?: () => boolean; + getChainId: () => `0x${string}` | `${number}` | number; + getProvider: () => NetworkController['provider']; + onNetworkStateChange: (listener: (state: NetworkState) => void) => void; + legacyAPIEndpoint?: string; + EIP1559APIEndpoint?: string; + clientId?: string; + }); + resetPolling(): Promise; + fetchGasFeeEstimates(options?: FetchGasFeeEstimateOptions): Promise; + getGasFeeEstimatesAndStartPolling(pollToken: string | undefined): Promise; + /** + * Gets and sets gasFeeEstimates in state. + * + * @param options - The gas fee estimate options. + * @param options.shouldUpdateState - Determines whether the state should be updated with the + * updated gas estimates. + * @returns The gas fee estimates. + */ + _fetchGasFeeEstimateData(options?: FetchGasFeeEstimateOptions): Promise; + /** + * Remove the poll token, and stop polling if the set of poll tokens is empty. + * + * @param pollToken - The poll token to disconnect. + */ + disconnectPoller(pollToken: string): void; + stopPolling(): void; + /** + * Prepare to discard this controller. + * + * This stops any active polling. + */ + destroy(): void; + private _poll; + private resetState; + private getEIP1559Compatibility; + getTimeEstimate(maxPriorityFeePerGas: string, maxFeePerGas: string): EstimatedGasFeeTimeBounds | Record; +} +export default GasFeeController; diff --git a/dist/gas/GasFeeController.js b/dist/gas/GasFeeController.js new file mode 100644 index 0000000000..9666218ba0 --- /dev/null +++ b/dist/gas/GasFeeController.js @@ -0,0 +1,243 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.GasFeeController = exports.GAS_ESTIMATE_TYPES = exports.LEGACY_GAS_PRICES_API_URL = void 0; +const eth_query_1 = __importDefault(require("eth-query")); +const uuid_1 = require("uuid"); +const ethereumjs_util_1 = require("ethereumjs-util"); +const BaseControllerV2_1 = require("../BaseControllerV2"); +const util_1 = require("../util"); +const gas_util_1 = require("./gas-util"); +const determineGasFeeCalculations_1 = __importDefault(require("./determineGasFeeCalculations")); +const fetchGasEstimatesViaEthFeeHistory_1 = __importDefault(require("./fetchGasEstimatesViaEthFeeHistory")); +const GAS_FEE_API = 'https://mock-gas-server.herokuapp.com/'; +exports.LEGACY_GAS_PRICES_API_URL = `https://api.metaswap.codefi.network/gasPrices`; +/** + * Indicates which type of gasEstimate the controller is currently returning. + * This is useful as a way of asserting that the shape of gasEstimates matches + * expectations. NONE is a special case indicating that no previous gasEstimate + * has been fetched. + */ +exports.GAS_ESTIMATE_TYPES = { + FEE_MARKET: 'fee-market', + LEGACY: 'legacy', + ETH_GASPRICE: 'eth_gasPrice', + NONE: 'none', +}; +const metadata = { + gasFeeEstimates: { persist: true, anonymous: false }, + estimatedGasFeeTimeBounds: { persist: true, anonymous: false }, + gasEstimateType: { persist: true, anonymous: false }, +}; +const name = 'GasFeeController'; +const defaultState = { + gasFeeEstimates: {}, + estimatedGasFeeTimeBounds: {}, + gasEstimateType: exports.GAS_ESTIMATE_TYPES.NONE, +}; +/** + * Controller that retrieves gas fee estimate data and polls for updated data on a set interval + */ +class GasFeeController extends BaseControllerV2_1.BaseController { + /** + * Creates a GasFeeController instance. + * + * @param options - The controller options. + * @param options.interval - The time in milliseconds to wait between polls. + * @param options.messenger - The controller messenger. + * @param options.state - The initial state. + * @param options.getCurrentNetworkEIP1559Compatibility - Determines whether or not the current + * network is EIP-1559 compatible. + * @param options.getCurrentNetworkLegacyGasAPICompatibility - Determines whether or not the + * current network is compatible with the legacy gas price API. + * @param options.getCurrentAccountEIP1559Compatibility - Determines whether or not the current + * account is EIP-1559 compatible. + * @param options.getChainId - Returns the current chain ID. + * @param options.getProvider - Returns a network provider for the current network. + * @param options.onNetworkStateChange - A function for registering an event handler for the + * network state change event. + * @param options.legacyAPIEndpoint - The legacy gas price API URL. This option is primarily for + * testing purposes. + * @param options.EIP1559APIEndpoint - The EIP-1559 gas price API URL. This option is primarily + * for testing purposes. + * @param options.clientId - The client ID used to identify to the gas estimation API who is + * asking for estimates. + */ + constructor({ interval = 15000, messenger, state, getCurrentNetworkEIP1559Compatibility, getCurrentAccountEIP1559Compatibility, getChainId, getCurrentNetworkLegacyGasAPICompatibility, getProvider, onNetworkStateChange, legacyAPIEndpoint = exports.LEGACY_GAS_PRICES_API_URL, EIP1559APIEndpoint = GAS_FEE_API, clientId, }) { + super({ + name, + metadata, + messenger, + state: Object.assign(Object.assign({}, defaultState), state), + }); + this.intervalDelay = interval; + this.pollTokens = new Set(); + this.getCurrentNetworkEIP1559Compatibility = + getCurrentNetworkEIP1559Compatibility; + this.getCurrentNetworkLegacyGasAPICompatibility = + getCurrentNetworkLegacyGasAPICompatibility; + this.getCurrentAccountEIP1559Compatibility = + getCurrentAccountEIP1559Compatibility; + this.EIP1559APIEndpoint = EIP1559APIEndpoint; + this.legacyAPIEndpoint = legacyAPIEndpoint; + this.getChainId = getChainId; + this.currentChainId = this.getChainId(); + const provider = getProvider(); + this.ethQuery = new eth_query_1.default(provider); + this.clientId = clientId; + onNetworkStateChange(() => __awaiter(this, void 0, void 0, function* () { + const newProvider = getProvider(); + const newChainId = this.getChainId(); + this.ethQuery = new eth_query_1.default(newProvider); + if (this.currentChainId !== newChainId) { + this.currentChainId = newChainId; + yield this.resetPolling(); + } + })); + } + resetPolling() { + return __awaiter(this, void 0, void 0, function* () { + if (this.pollTokens.size !== 0) { + const tokens = Array.from(this.pollTokens); + this.stopPolling(); + yield this.getGasFeeEstimatesAndStartPolling(tokens[0]); + tokens.slice(1).forEach((token) => { + this.pollTokens.add(token); + }); + } + }); + } + fetchGasFeeEstimates(options) { + return __awaiter(this, void 0, void 0, function* () { + return yield this._fetchGasFeeEstimateData(options); + }); + } + getGasFeeEstimatesAndStartPolling(pollToken) { + return __awaiter(this, void 0, void 0, function* () { + const _pollToken = pollToken || (0, uuid_1.v1)(); + this.pollTokens.add(_pollToken); + if (this.pollTokens.size === 1) { + yield this._fetchGasFeeEstimateData(); + this._poll(); + } + return _pollToken; + }); + } + /** + * Gets and sets gasFeeEstimates in state. + * + * @param options - The gas fee estimate options. + * @param options.shouldUpdateState - Determines whether the state should be updated with the + * updated gas estimates. + * @returns The gas fee estimates. + */ + _fetchGasFeeEstimateData(options = {}) { + return __awaiter(this, void 0, void 0, function* () { + const { shouldUpdateState = true } = options; + let isEIP1559Compatible; + const isLegacyGasAPICompatible = this.getCurrentNetworkLegacyGasAPICompatibility(); + let chainId = this.getChainId(); + if (typeof chainId === 'string' && (0, ethereumjs_util_1.isHexString)(chainId)) { + chainId = parseInt(chainId, 16); + } + try { + isEIP1559Compatible = yield this.getEIP1559Compatibility(); + } + catch (e) { + console.error(e); + isEIP1559Compatible = false; + } + const gasFeeCalculations = yield (0, determineGasFeeCalculations_1.default)({ + isEIP1559Compatible, + isLegacyGasAPICompatible, + fetchGasEstimates: gas_util_1.fetchGasEstimates, + fetchGasEstimatesUrl: this.EIP1559APIEndpoint.replace('', `${chainId}`), + fetchGasEstimatesViaEthFeeHistory: fetchGasEstimatesViaEthFeeHistory_1.default, + fetchLegacyGasPriceEstimates: gas_util_1.fetchLegacyGasPriceEstimates, + fetchLegacyGasPriceEstimatesUrl: this.legacyAPIEndpoint.replace('', `${chainId}`), + fetchEthGasPriceEstimate: gas_util_1.fetchEthGasPriceEstimate, + calculateTimeEstimate: gas_util_1.calculateTimeEstimate, + clientId: this.clientId, + ethQuery: this.ethQuery, + }); + if (shouldUpdateState) { + this.update((state) => { + state.gasFeeEstimates = gasFeeCalculations.gasFeeEstimates; + state.estimatedGasFeeTimeBounds = + gasFeeCalculations.estimatedGasFeeTimeBounds; + state.gasEstimateType = gasFeeCalculations.gasEstimateType; + }); + } + return gasFeeCalculations; + }); + } + /** + * Remove the poll token, and stop polling if the set of poll tokens is empty. + * + * @param pollToken - The poll token to disconnect. + */ + disconnectPoller(pollToken) { + this.pollTokens.delete(pollToken); + if (this.pollTokens.size === 0) { + this.stopPolling(); + } + } + stopPolling() { + if (this.intervalId) { + clearInterval(this.intervalId); + } + this.pollTokens.clear(); + this.resetState(); + } + /** + * Prepare to discard this controller. + * + * This stops any active polling. + */ + destroy() { + super.destroy(); + this.stopPolling(); + } + _poll() { + if (this.intervalId) { + clearInterval(this.intervalId); + } + this.intervalId = setInterval(() => __awaiter(this, void 0, void 0, function* () { + yield (0, util_1.safelyExecute)(() => this._fetchGasFeeEstimateData()); + }), this.intervalDelay); + } + resetState() { + this.update(() => { + return defaultState; + }); + } + getEIP1559Compatibility() { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + const currentNetworkIsEIP1559Compatible = yield this.getCurrentNetworkEIP1559Compatibility(); + const currentAccountIsEIP1559Compatible = (_b = (_a = this.getCurrentAccountEIP1559Compatibility) === null || _a === void 0 ? void 0 : _a.call(this)) !== null && _b !== void 0 ? _b : true; + return (currentNetworkIsEIP1559Compatible && currentAccountIsEIP1559Compatible); + }); + } + getTimeEstimate(maxPriorityFeePerGas, maxFeePerGas) { + if (!this.state.gasFeeEstimates || + this.state.gasEstimateType !== exports.GAS_ESTIMATE_TYPES.FEE_MARKET) { + return {}; + } + return (0, gas_util_1.calculateTimeEstimate)(maxPriorityFeePerGas, maxFeePerGas, this.state.gasFeeEstimates); + } +} +exports.GasFeeController = GasFeeController; +exports.default = GasFeeController; +//# sourceMappingURL=GasFeeController.js.map \ No newline at end of file diff --git a/dist/gas/GasFeeController.js.map b/dist/gas/GasFeeController.js.map new file mode 100644 index 0000000000..c98e0342b9 --- /dev/null +++ b/dist/gas/GasFeeController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"GasFeeController.js","sourceRoot":"","sources":["../../src/gas/GasFeeController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Patch } from 'immer';\n\nimport EthQuery from 'eth-query';\nimport { v1 as random } from 'uuid';\nimport { isHexString } from 'ethereumjs-util';\nimport { BaseController } from '../BaseControllerV2';\nimport { safelyExecute } from '../util';\nimport type { RestrictedControllerMessenger } from '../ControllerMessenger';\nimport type {\n NetworkController,\n NetworkState,\n} from '../network/NetworkController';\nimport {\n fetchGasEstimates,\n fetchLegacyGasPriceEstimates,\n fetchEthGasPriceEstimate,\n calculateTimeEstimate,\n} from './gas-util';\nimport determineGasFeeCalculations from './determineGasFeeCalculations';\nimport fetchGasEstimatesViaEthFeeHistory from './fetchGasEstimatesViaEthFeeHistory';\n\nconst GAS_FEE_API = 'https://mock-gas-server.herokuapp.com/';\nexport const LEGACY_GAS_PRICES_API_URL = `https://api.metaswap.codefi.network/gasPrices`;\n\nexport type unknownString = 'unknown';\n\n// Fee Market describes the way gas is set after the london hardfork, and was\n// defined by EIP-1559.\nexport type FeeMarketEstimateType = 'fee-market';\n// Legacy describes gasPrice estimates from before london hardfork, when the\n// user is connected to mainnet and are presented with fast/average/slow\n// estimate levels to choose from.\nexport type LegacyEstimateType = 'legacy';\n// EthGasPrice describes a gasPrice estimate received from eth_gasPrice. Post\n// london this value should only be used for legacy type transactions when on\n// networks that support EIP-1559. This type of estimate is the most accurate\n// to display on custom networks that don't support EIP-1559.\nexport type EthGasPriceEstimateType = 'eth_gasPrice';\n// NoEstimate describes the state of the controller before receiving its first\n// estimate.\nexport type NoEstimateType = 'none';\n\n/**\n * Indicates which type of gasEstimate the controller is currently returning.\n * This is useful as a way of asserting that the shape of gasEstimates matches\n * expectations. NONE is a special case indicating that no previous gasEstimate\n * has been fetched.\n */\nexport const GAS_ESTIMATE_TYPES = {\n FEE_MARKET: 'fee-market' as FeeMarketEstimateType,\n LEGACY: 'legacy' as LegacyEstimateType,\n ETH_GASPRICE: 'eth_gasPrice' as EthGasPriceEstimateType,\n NONE: 'none' as NoEstimateType,\n};\n\nexport type GasEstimateType =\n | FeeMarketEstimateType\n | EthGasPriceEstimateType\n | LegacyEstimateType\n | NoEstimateType;\n\nexport type EstimatedGasFeeTimeBounds = {\n lowerTimeBound: number | null;\n upperTimeBound: number | unknownString;\n};\n\n/**\n * @type EthGasPriceEstimate\n *\n * A single gas price estimate for networks and accounts that don't support EIP-1559\n * This estimate comes from eth_gasPrice but is converted to dec gwei to match other\n * return values\n * @property gasPrice - A GWEI dec string\n */\n\nexport type EthGasPriceEstimate = {\n gasPrice: string;\n};\n\n/**\n * @type LegacyGasPriceEstimate\n *\n * A set of gas price estimates for networks and accounts that don't support EIP-1559\n * These estimates include low, medium and high all as strings representing gwei in\n * decimal format.\n * @property high - gasPrice, in decimal gwei string format, suggested for fast inclusion\n * @property medium - gasPrice, in decimal gwei string format, suggested for avg inclusion\n * @property low - gasPrice, in decimal gwei string format, suggested for slow inclusion\n */\nexport type LegacyGasPriceEstimate = {\n high: string;\n medium: string;\n low: string;\n};\n\n/**\n * @type Eip1559GasFee\n *\n * Data necessary to provide an estimate of a gas fee with a specific tip\n * @property minWaitTimeEstimate - The fastest the transaction will take, in milliseconds\n * @property maxWaitTimeEstimate - The slowest the transaction will take, in milliseconds\n * @property suggestedMaxPriorityFeePerGas - A suggested \"tip\", a GWEI hex number\n * @property suggestedMaxFeePerGas - A suggested max fee, the most a user will pay. a GWEI hex number\n */\n\nexport type Eip1559GasFee = {\n minWaitTimeEstimate: number; // a time duration in milliseconds\n maxWaitTimeEstimate: number; // a time duration in milliseconds\n suggestedMaxPriorityFeePerGas: string; // a GWEI decimal number\n suggestedMaxFeePerGas: string; // a GWEI decimal number\n};\n\n/**\n * @type GasFeeEstimates\n *\n * Data necessary to provide multiple GasFee estimates, and supporting information, to the user\n * @property low - A GasFee for a minimum necessary combination of tip and maxFee\n * @property medium - A GasFee for a recommended combination of tip and maxFee\n * @property high - A GasFee for a high combination of tip and maxFee\n * @property estimatedBaseFee - An estimate of what the base fee will be for the pending/next block. A GWEI dec number\n * @property networkCongestion - A normalized number that can be used to gauge the congestion\n * level of the network, with 0 meaning not congested and 1 meaning extremely congested\n */\n\nexport type GasFeeEstimates = SourcedGasFeeEstimates | FallbackGasFeeEstimates;\n\ntype SourcedGasFeeEstimates = {\n low: Eip1559GasFee;\n medium: Eip1559GasFee;\n high: Eip1559GasFee;\n estimatedBaseFee: string;\n historicalBaseFeeRange: [string, string];\n baseFeeTrend: 'up' | 'down' | 'level';\n latestPriorityFeeRange: [string, string];\n historicalPriorityFeeRange: [string, string];\n priorityFeeTrend: 'up' | 'down' | 'level';\n networkCongestion: number;\n};\n\ntype FallbackGasFeeEstimates = {\n low: Eip1559GasFee;\n medium: Eip1559GasFee;\n high: Eip1559GasFee;\n estimatedBaseFee: string;\n historicalBaseFeeRange: null;\n baseFeeTrend: null;\n latestPriorityFeeRange: null;\n historicalPriorityFeeRange: null;\n priorityFeeTrend: null;\n networkCongestion: null;\n};\n\nconst metadata = {\n gasFeeEstimates: { persist: true, anonymous: false },\n estimatedGasFeeTimeBounds: { persist: true, anonymous: false },\n gasEstimateType: { persist: true, anonymous: false },\n};\n\nexport type GasFeeStateEthGasPrice = {\n gasFeeEstimates: EthGasPriceEstimate;\n estimatedGasFeeTimeBounds: Record;\n gasEstimateType: EthGasPriceEstimateType;\n};\n\nexport type GasFeeStateFeeMarket = {\n gasFeeEstimates: GasFeeEstimates;\n estimatedGasFeeTimeBounds: EstimatedGasFeeTimeBounds | Record;\n gasEstimateType: FeeMarketEstimateType;\n};\n\nexport type GasFeeStateLegacy = {\n gasFeeEstimates: LegacyGasPriceEstimate;\n estimatedGasFeeTimeBounds: Record;\n gasEstimateType: LegacyEstimateType;\n};\n\nexport type GasFeeStateNoEstimates = {\n gasFeeEstimates: Record;\n estimatedGasFeeTimeBounds: Record;\n gasEstimateType: NoEstimateType;\n};\n\nexport type FetchGasFeeEstimateOptions = {\n shouldUpdateState?: boolean;\n};\n\n/**\n * @type GasFeeState\n *\n * Gas Fee controller state\n * @property gasFeeEstimates - Gas fee estimate data based on new EIP-1559 properties\n * @property estimatedGasFeeTimeBounds - Estimates representing the minimum and maximum\n */\nexport type GasFeeState =\n | GasFeeStateEthGasPrice\n | GasFeeStateFeeMarket\n | GasFeeStateLegacy\n | GasFeeStateNoEstimates;\n\nconst name = 'GasFeeController';\n\nexport type GasFeeStateChange = {\n type: `${typeof name}:stateChange`;\n payload: [GasFeeState, Patch[]];\n};\n\nexport type GetGasFeeState = {\n type: `${typeof name}:getState`;\n handler: () => GasFeeState;\n};\n\ntype GasFeeMessenger = RestrictedControllerMessenger<\n typeof name,\n GetGasFeeState,\n GasFeeStateChange,\n never,\n never\n>;\n\nconst defaultState: GasFeeState = {\n gasFeeEstimates: {},\n estimatedGasFeeTimeBounds: {},\n gasEstimateType: GAS_ESTIMATE_TYPES.NONE,\n};\n\n/**\n * Controller that retrieves gas fee estimate data and polls for updated data on a set interval\n */\nexport class GasFeeController extends BaseController<\n typeof name,\n GasFeeState,\n GasFeeMessenger\n> {\n private intervalId?: NodeJS.Timeout;\n\n private intervalDelay;\n\n private pollTokens: Set;\n\n private legacyAPIEndpoint: string;\n\n private EIP1559APIEndpoint: string;\n\n private getCurrentNetworkEIP1559Compatibility;\n\n private getCurrentNetworkLegacyGasAPICompatibility;\n\n private getCurrentAccountEIP1559Compatibility;\n\n private getChainId;\n\n private currentChainId;\n\n private ethQuery: any;\n\n private clientId?: string;\n\n /**\n * Creates a GasFeeController instance.\n *\n * @param options - The controller options.\n * @param options.interval - The time in milliseconds to wait between polls.\n * @param options.messenger - The controller messenger.\n * @param options.state - The initial state.\n * @param options.getCurrentNetworkEIP1559Compatibility - Determines whether or not the current\n * network is EIP-1559 compatible.\n * @param options.getCurrentNetworkLegacyGasAPICompatibility - Determines whether or not the\n * current network is compatible with the legacy gas price API.\n * @param options.getCurrentAccountEIP1559Compatibility - Determines whether or not the current\n * account is EIP-1559 compatible.\n * @param options.getChainId - Returns the current chain ID.\n * @param options.getProvider - Returns a network provider for the current network.\n * @param options.onNetworkStateChange - A function for registering an event handler for the\n * network state change event.\n * @param options.legacyAPIEndpoint - The legacy gas price API URL. This option is primarily for\n * testing purposes.\n * @param options.EIP1559APIEndpoint - The EIP-1559 gas price API URL. This option is primarily\n * for testing purposes.\n * @param options.clientId - The client ID used to identify to the gas estimation API who is\n * asking for estimates.\n */\n constructor({\n interval = 15000,\n messenger,\n state,\n getCurrentNetworkEIP1559Compatibility,\n getCurrentAccountEIP1559Compatibility,\n getChainId,\n getCurrentNetworkLegacyGasAPICompatibility,\n getProvider,\n onNetworkStateChange,\n legacyAPIEndpoint = LEGACY_GAS_PRICES_API_URL,\n EIP1559APIEndpoint = GAS_FEE_API,\n clientId,\n }: {\n interval?: number;\n messenger: GasFeeMessenger;\n state?: GasFeeState;\n getCurrentNetworkEIP1559Compatibility: () => Promise;\n getCurrentNetworkLegacyGasAPICompatibility: () => boolean;\n getCurrentAccountEIP1559Compatibility?: () => boolean;\n getChainId: () => `0x${string}` | `${number}` | number;\n getProvider: () => NetworkController['provider'];\n onNetworkStateChange: (listener: (state: NetworkState) => void) => void;\n legacyAPIEndpoint?: string;\n EIP1559APIEndpoint?: string;\n clientId?: string;\n }) {\n super({\n name,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n this.intervalDelay = interval;\n this.pollTokens = new Set();\n this.getCurrentNetworkEIP1559Compatibility =\n getCurrentNetworkEIP1559Compatibility;\n\n this.getCurrentNetworkLegacyGasAPICompatibility =\n getCurrentNetworkLegacyGasAPICompatibility;\n\n this.getCurrentAccountEIP1559Compatibility =\n getCurrentAccountEIP1559Compatibility;\n this.EIP1559APIEndpoint = EIP1559APIEndpoint;\n this.legacyAPIEndpoint = legacyAPIEndpoint;\n this.getChainId = getChainId;\n this.currentChainId = this.getChainId();\n const provider = getProvider();\n this.ethQuery = new EthQuery(provider);\n this.clientId = clientId;\n onNetworkStateChange(async () => {\n const newProvider = getProvider();\n const newChainId = this.getChainId();\n this.ethQuery = new EthQuery(newProvider);\n if (this.currentChainId !== newChainId) {\n this.currentChainId = newChainId;\n await this.resetPolling();\n }\n });\n }\n\n async resetPolling() {\n if (this.pollTokens.size !== 0) {\n const tokens = Array.from(this.pollTokens);\n this.stopPolling();\n await this.getGasFeeEstimatesAndStartPolling(tokens[0]);\n tokens.slice(1).forEach((token) => {\n this.pollTokens.add(token);\n });\n }\n }\n\n async fetchGasFeeEstimates(options?: FetchGasFeeEstimateOptions) {\n return await this._fetchGasFeeEstimateData(options);\n }\n\n async getGasFeeEstimatesAndStartPolling(\n pollToken: string | undefined,\n ): Promise {\n const _pollToken = pollToken || random();\n\n this.pollTokens.add(_pollToken);\n\n if (this.pollTokens.size === 1) {\n await this._fetchGasFeeEstimateData();\n this._poll();\n }\n\n return _pollToken;\n }\n\n /**\n * Gets and sets gasFeeEstimates in state.\n *\n * @param options - The gas fee estimate options.\n * @param options.shouldUpdateState - Determines whether the state should be updated with the\n * updated gas estimates.\n * @returns The gas fee estimates.\n */\n async _fetchGasFeeEstimateData(\n options: FetchGasFeeEstimateOptions = {},\n ): Promise {\n const { shouldUpdateState = true } = options;\n let isEIP1559Compatible;\n const isLegacyGasAPICompatible =\n this.getCurrentNetworkLegacyGasAPICompatibility();\n\n let chainId = this.getChainId();\n if (typeof chainId === 'string' && isHexString(chainId)) {\n chainId = parseInt(chainId, 16);\n }\n\n try {\n isEIP1559Compatible = await this.getEIP1559Compatibility();\n } catch (e) {\n console.error(e);\n isEIP1559Compatible = false;\n }\n\n const gasFeeCalculations = await determineGasFeeCalculations({\n isEIP1559Compatible,\n isLegacyGasAPICompatible,\n fetchGasEstimates,\n fetchGasEstimatesUrl: this.EIP1559APIEndpoint.replace(\n '',\n `${chainId}`,\n ),\n fetchGasEstimatesViaEthFeeHistory,\n fetchLegacyGasPriceEstimates,\n fetchLegacyGasPriceEstimatesUrl: this.legacyAPIEndpoint.replace(\n '',\n `${chainId}`,\n ),\n fetchEthGasPriceEstimate,\n calculateTimeEstimate,\n clientId: this.clientId,\n ethQuery: this.ethQuery,\n });\n\n if (shouldUpdateState) {\n this.update((state) => {\n state.gasFeeEstimates = gasFeeCalculations.gasFeeEstimates;\n state.estimatedGasFeeTimeBounds =\n gasFeeCalculations.estimatedGasFeeTimeBounds;\n state.gasEstimateType = gasFeeCalculations.gasEstimateType;\n });\n }\n\n return gasFeeCalculations;\n }\n\n /**\n * Remove the poll token, and stop polling if the set of poll tokens is empty.\n *\n * @param pollToken - The poll token to disconnect.\n */\n disconnectPoller(pollToken: string) {\n this.pollTokens.delete(pollToken);\n if (this.pollTokens.size === 0) {\n this.stopPolling();\n }\n }\n\n stopPolling() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n this.pollTokens.clear();\n this.resetState();\n }\n\n /**\n * Prepare to discard this controller.\n *\n * This stops any active polling.\n */\n override destroy() {\n super.destroy();\n this.stopPolling();\n }\n\n private _poll() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n\n this.intervalId = setInterval(async () => {\n await safelyExecute(() => this._fetchGasFeeEstimateData());\n }, this.intervalDelay);\n }\n\n private resetState() {\n this.update(() => {\n return defaultState;\n });\n }\n\n private async getEIP1559Compatibility() {\n const currentNetworkIsEIP1559Compatible =\n await this.getCurrentNetworkEIP1559Compatibility();\n const currentAccountIsEIP1559Compatible =\n this.getCurrentAccountEIP1559Compatibility?.() ?? true;\n\n return (\n currentNetworkIsEIP1559Compatible && currentAccountIsEIP1559Compatible\n );\n }\n\n getTimeEstimate(\n maxPriorityFeePerGas: string,\n maxFeePerGas: string,\n ): EstimatedGasFeeTimeBounds | Record {\n if (\n !this.state.gasFeeEstimates ||\n this.state.gasEstimateType !== GAS_ESTIMATE_TYPES.FEE_MARKET\n ) {\n return {};\n }\n return calculateTimeEstimate(\n maxPriorityFeePerGas,\n maxFeePerGas,\n this.state.gasFeeEstimates,\n );\n }\n}\n\nexport default GasFeeController;\n"]} \ No newline at end of file diff --git a/dist/gas/determineGasFeeCalculations.d.ts b/dist/gas/determineGasFeeCalculations.d.ts new file mode 100644 index 0000000000..dfc404ffb6 --- /dev/null +++ b/dist/gas/determineGasFeeCalculations.d.ts @@ -0,0 +1,40 @@ +import { EstimatedGasFeeTimeBounds, EthGasPriceEstimate, GasFeeEstimates, GasFeeState as GasFeeCalculations, LegacyGasPriceEstimate } from './GasFeeController'; +/** + * Obtains a set of max base and priority fee estimates along with time estimates so that we + * can present them to users when they are sending transactions or making swaps. + * + * @param args - The arguments. + * @param args.isEIP1559Compatible - Governs whether or not we can use an EIP-1559-only method to + * produce estimates. + * @param args.isLegacyGasAPICompatible - Governs whether or not we can use a non-EIP-1559 method to + * produce estimates (for instance, testnets do not support estimates altogether). + * @param args.fetchGasEstimates - A function that fetches gas estimates using an EIP-1559-specific + * API. + * @param args.fetchGasEstimatesUrl - The URL for the API we can use to obtain EIP-1559-specific + * estimates. + * @param args.fetchGasEstimatesViaEthFeeHistory - A function that fetches gas estimates using + * `eth_feeHistory` (an EIP-1559 feature). + * @param args.fetchLegacyGasPriceEstimates - A function that fetches gas estimates using an + * non-EIP-1559-specific API. + * @param args.fetchLegacyGasPriceEstimatesUrl - The URL for the API we can use to obtain + * non-EIP-1559-specific estimates. + * @param args.fetchEthGasPriceEstimate - A function that fetches gas estimates using + * `eth_gasPrice`. + * @param args.calculateTimeEstimate - A function that determine time estimate bounds. + * @param args.clientId - An identifier that an API can use to know who is asking for estimates. + * @param args.ethQuery - An EthQuery instance we can use to talk to Ethereum directly. + * @returns The gas fee calculations. + */ +export default function determineGasFeeCalculations({ isEIP1559Compatible, isLegacyGasAPICompatible, fetchGasEstimates, fetchGasEstimatesUrl, fetchGasEstimatesViaEthFeeHistory, fetchLegacyGasPriceEstimates, fetchLegacyGasPriceEstimatesUrl, fetchEthGasPriceEstimate, calculateTimeEstimate, clientId, ethQuery, }: { + isEIP1559Compatible: boolean; + isLegacyGasAPICompatible: boolean; + fetchGasEstimates: (url: string, clientId?: string) => Promise; + fetchGasEstimatesUrl: string; + fetchGasEstimatesViaEthFeeHistory: (ethQuery: any) => Promise; + fetchLegacyGasPriceEstimates: (url: string, clientId?: string) => Promise; + fetchLegacyGasPriceEstimatesUrl: string; + fetchEthGasPriceEstimate: (ethQuery: any) => Promise; + calculateTimeEstimate: (maxPriorityFeePerGas: string, maxFeePerGas: string, gasFeeEstimates: GasFeeEstimates) => EstimatedGasFeeTimeBounds; + clientId: string | undefined; + ethQuery: any; +}): Promise; diff --git a/dist/gas/determineGasFeeCalculations.js b/dist/gas/determineGasFeeCalculations.js new file mode 100644 index 0000000000..481f03056b --- /dev/null +++ b/dist/gas/determineGasFeeCalculations.js @@ -0,0 +1,87 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const GasFeeController_1 = require("./GasFeeController"); +/** + * Obtains a set of max base and priority fee estimates along with time estimates so that we + * can present them to users when they are sending transactions or making swaps. + * + * @param args - The arguments. + * @param args.isEIP1559Compatible - Governs whether or not we can use an EIP-1559-only method to + * produce estimates. + * @param args.isLegacyGasAPICompatible - Governs whether or not we can use a non-EIP-1559 method to + * produce estimates (for instance, testnets do not support estimates altogether). + * @param args.fetchGasEstimates - A function that fetches gas estimates using an EIP-1559-specific + * API. + * @param args.fetchGasEstimatesUrl - The URL for the API we can use to obtain EIP-1559-specific + * estimates. + * @param args.fetchGasEstimatesViaEthFeeHistory - A function that fetches gas estimates using + * `eth_feeHistory` (an EIP-1559 feature). + * @param args.fetchLegacyGasPriceEstimates - A function that fetches gas estimates using an + * non-EIP-1559-specific API. + * @param args.fetchLegacyGasPriceEstimatesUrl - The URL for the API we can use to obtain + * non-EIP-1559-specific estimates. + * @param args.fetchEthGasPriceEstimate - A function that fetches gas estimates using + * `eth_gasPrice`. + * @param args.calculateTimeEstimate - A function that determine time estimate bounds. + * @param args.clientId - An identifier that an API can use to know who is asking for estimates. + * @param args.ethQuery - An EthQuery instance we can use to talk to Ethereum directly. + * @returns The gas fee calculations. + */ +function determineGasFeeCalculations({ isEIP1559Compatible, isLegacyGasAPICompatible, fetchGasEstimates, fetchGasEstimatesUrl, fetchGasEstimatesViaEthFeeHistory, fetchLegacyGasPriceEstimates, fetchLegacyGasPriceEstimatesUrl, fetchEthGasPriceEstimate, calculateTimeEstimate, clientId, ethQuery, }) { + return __awaiter(this, void 0, void 0, function* () { + try { + if (isEIP1559Compatible) { + let estimates; + try { + estimates = yield fetchGasEstimates(fetchGasEstimatesUrl, clientId); + } + catch (_a) { + estimates = yield fetchGasEstimatesViaEthFeeHistory(ethQuery); + } + const { suggestedMaxPriorityFeePerGas, suggestedMaxFeePerGas } = estimates.medium; + const estimatedGasFeeTimeBounds = calculateTimeEstimate(suggestedMaxPriorityFeePerGas, suggestedMaxFeePerGas, estimates); + return { + gasFeeEstimates: estimates, + estimatedGasFeeTimeBounds, + gasEstimateType: GasFeeController_1.GAS_ESTIMATE_TYPES.FEE_MARKET, + }; + } + else if (isLegacyGasAPICompatible) { + const estimates = yield fetchLegacyGasPriceEstimates(fetchLegacyGasPriceEstimatesUrl, clientId); + return { + gasFeeEstimates: estimates, + estimatedGasFeeTimeBounds: {}, + gasEstimateType: GasFeeController_1.GAS_ESTIMATE_TYPES.LEGACY, + }; + } + throw new Error('Main gas fee/price estimation failed. Use fallback'); + } + catch (_b) { + try { + const estimates = yield fetchEthGasPriceEstimate(ethQuery); + return { + gasFeeEstimates: estimates, + estimatedGasFeeTimeBounds: {}, + gasEstimateType: GasFeeController_1.GAS_ESTIMATE_TYPES.ETH_GASPRICE, + }; + } + catch (error) { + if (error instanceof Error) { + throw new Error(`Gas fee/price estimation failed. Message: ${error.message}`); + } + throw error; + } + } + }); +} +exports.default = determineGasFeeCalculations; +//# sourceMappingURL=determineGasFeeCalculations.js.map \ No newline at end of file diff --git a/dist/gas/determineGasFeeCalculations.js.map b/dist/gas/determineGasFeeCalculations.js.map new file mode 100644 index 0000000000..83d3d56665 --- /dev/null +++ b/dist/gas/determineGasFeeCalculations.js.map @@ -0,0 +1 @@ +{"version":3,"file":"determineGasFeeCalculations.js","sourceRoot":"","sources":["../../src/gas/determineGasFeeCalculations.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,yDAO4B;AAE5B;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,SAA8B,2BAA2B,CAAC,EACxD,mBAAmB,EACnB,wBAAwB,EACxB,iBAAiB,EACjB,oBAAoB,EACpB,iCAAiC,EACjC,4BAA4B,EAC5B,+BAA+B,EAC/B,wBAAwB,EACxB,qBAAqB,EACrB,QAAQ,EACR,QAAQ,GAyBT;;QACC,IAAI;YACF,IAAI,mBAAmB,EAAE;gBACvB,IAAI,SAA0B,CAAC;gBAC/B,IAAI;oBACF,SAAS,GAAG,MAAM,iBAAiB,CAAC,oBAAoB,EAAE,QAAQ,CAAC,CAAC;iBACrE;gBAAC,WAAM;oBACN,SAAS,GAAG,MAAM,iCAAiC,CAAC,QAAQ,CAAC,CAAC;iBAC/D;gBACD,MAAM,EAAE,6BAA6B,EAAE,qBAAqB,EAAE,GAC5D,SAAS,CAAC,MAAM,CAAC;gBACnB,MAAM,yBAAyB,GAAG,qBAAqB,CACrD,6BAA6B,EAC7B,qBAAqB,EACrB,SAAS,CACV,CAAC;gBACF,OAAO;oBACL,eAAe,EAAE,SAAS;oBAC1B,yBAAyB;oBACzB,eAAe,EAAE,qCAAkB,CAAC,UAAU;iBAC/C,CAAC;aACH;iBAAM,IAAI,wBAAwB,EAAE;gBACnC,MAAM,SAAS,GAAG,MAAM,4BAA4B,CAClD,+BAA+B,EAC/B,QAAQ,CACT,CAAC;gBACF,OAAO;oBACL,eAAe,EAAE,SAAS;oBAC1B,yBAAyB,EAAE,EAAE;oBAC7B,eAAe,EAAE,qCAAkB,CAAC,MAAM;iBAC3C,CAAC;aACH;YACD,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;SACvE;QAAC,WAAM;YACN,IAAI;gBACF,MAAM,SAAS,GAAG,MAAM,wBAAwB,CAAC,QAAQ,CAAC,CAAC;gBAC3D,OAAO;oBACL,eAAe,EAAE,SAAS;oBAC1B,yBAAyB,EAAE,EAAE;oBAC7B,eAAe,EAAE,qCAAkB,CAAC,YAAY;iBACjD,CAAC;aACH;YAAC,OAAO,KAAK,EAAE;gBACd,IAAI,KAAK,YAAY,KAAK,EAAE;oBAC1B,MAAM,IAAI,KAAK,CACb,6CAA6C,KAAK,CAAC,OAAO,EAAE,CAC7D,CAAC;iBACH;gBACD,MAAM,KAAK,CAAC;aACb;SACF;IACH,CAAC;CAAA;AAtFD,8CAsFC","sourcesContent":["import {\n GAS_ESTIMATE_TYPES,\n EstimatedGasFeeTimeBounds,\n EthGasPriceEstimate,\n GasFeeEstimates,\n GasFeeState as GasFeeCalculations,\n LegacyGasPriceEstimate,\n} from './GasFeeController';\n\n/**\n * Obtains a set of max base and priority fee estimates along with time estimates so that we\n * can present them to users when they are sending transactions or making swaps.\n *\n * @param args - The arguments.\n * @param args.isEIP1559Compatible - Governs whether or not we can use an EIP-1559-only method to\n * produce estimates.\n * @param args.isLegacyGasAPICompatible - Governs whether or not we can use a non-EIP-1559 method to\n * produce estimates (for instance, testnets do not support estimates altogether).\n * @param args.fetchGasEstimates - A function that fetches gas estimates using an EIP-1559-specific\n * API.\n * @param args.fetchGasEstimatesUrl - The URL for the API we can use to obtain EIP-1559-specific\n * estimates.\n * @param args.fetchGasEstimatesViaEthFeeHistory - A function that fetches gas estimates using\n * `eth_feeHistory` (an EIP-1559 feature).\n * @param args.fetchLegacyGasPriceEstimates - A function that fetches gas estimates using an\n * non-EIP-1559-specific API.\n * @param args.fetchLegacyGasPriceEstimatesUrl - The URL for the API we can use to obtain\n * non-EIP-1559-specific estimates.\n * @param args.fetchEthGasPriceEstimate - A function that fetches gas estimates using\n * `eth_gasPrice`.\n * @param args.calculateTimeEstimate - A function that determine time estimate bounds.\n * @param args.clientId - An identifier that an API can use to know who is asking for estimates.\n * @param args.ethQuery - An EthQuery instance we can use to talk to Ethereum directly.\n * @returns The gas fee calculations.\n */\nexport default async function determineGasFeeCalculations({\n isEIP1559Compatible,\n isLegacyGasAPICompatible,\n fetchGasEstimates,\n fetchGasEstimatesUrl,\n fetchGasEstimatesViaEthFeeHistory,\n fetchLegacyGasPriceEstimates,\n fetchLegacyGasPriceEstimatesUrl,\n fetchEthGasPriceEstimate,\n calculateTimeEstimate,\n clientId,\n ethQuery,\n}: {\n isEIP1559Compatible: boolean;\n isLegacyGasAPICompatible: boolean;\n fetchGasEstimates: (\n url: string,\n clientId?: string,\n ) => Promise;\n fetchGasEstimatesUrl: string;\n fetchGasEstimatesViaEthFeeHistory: (\n ethQuery: any,\n ) => Promise;\n fetchLegacyGasPriceEstimates: (\n url: string,\n clientId?: string,\n ) => Promise;\n fetchLegacyGasPriceEstimatesUrl: string;\n fetchEthGasPriceEstimate: (ethQuery: any) => Promise;\n calculateTimeEstimate: (\n maxPriorityFeePerGas: string,\n maxFeePerGas: string,\n gasFeeEstimates: GasFeeEstimates,\n ) => EstimatedGasFeeTimeBounds;\n clientId: string | undefined;\n ethQuery: any;\n}): Promise {\n try {\n if (isEIP1559Compatible) {\n let estimates: GasFeeEstimates;\n try {\n estimates = await fetchGasEstimates(fetchGasEstimatesUrl, clientId);\n } catch {\n estimates = await fetchGasEstimatesViaEthFeeHistory(ethQuery);\n }\n const { suggestedMaxPriorityFeePerGas, suggestedMaxFeePerGas } =\n estimates.medium;\n const estimatedGasFeeTimeBounds = calculateTimeEstimate(\n suggestedMaxPriorityFeePerGas,\n suggestedMaxFeePerGas,\n estimates,\n );\n return {\n gasFeeEstimates: estimates,\n estimatedGasFeeTimeBounds,\n gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,\n };\n } else if (isLegacyGasAPICompatible) {\n const estimates = await fetchLegacyGasPriceEstimates(\n fetchLegacyGasPriceEstimatesUrl,\n clientId,\n );\n return {\n gasFeeEstimates: estimates,\n estimatedGasFeeTimeBounds: {},\n gasEstimateType: GAS_ESTIMATE_TYPES.LEGACY,\n };\n }\n throw new Error('Main gas fee/price estimation failed. Use fallback');\n } catch {\n try {\n const estimates = await fetchEthGasPriceEstimate(ethQuery);\n return {\n gasFeeEstimates: estimates,\n estimatedGasFeeTimeBounds: {},\n gasEstimateType: GAS_ESTIMATE_TYPES.ETH_GASPRICE,\n };\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(\n `Gas fee/price estimation failed. Message: ${error.message}`,\n );\n }\n throw error;\n }\n }\n}\n"]} \ No newline at end of file diff --git a/dist/gas/fetchBlockFeeHistory.d.ts b/dist/gas/fetchBlockFeeHistory.d.ts new file mode 100644 index 0000000000..7fdab8e603 --- /dev/null +++ b/dist/gas/fetchBlockFeeHistory.d.ts @@ -0,0 +1,115 @@ +/// +import { BN } from 'ethereumjs-util'; +declare type EthQuery = any; +/** + * @type EthFeeHistoryResponse + * + * Response data for `eth_feeHistory`. + * @property oldestBlock - The id of the oldest block (in hex format) in the range of blocks + * requested. + * @property baseFeePerGas - Base fee per gas for each block in the range of blocks requested. + * For go-ethereum based chains baseFeePerGas will not returned in case of empty results + * + * @property gasUsedRatio - A number between 0 and 1 that represents the gas used vs. gas limit for + * each block in the range of blocks requested. + * @property reward - The priority fee at the percentiles requested for each block in the range of + * blocks requested. + */ +export declare type EthFeeHistoryResponse = { + oldestBlock: string; + baseFeePerGas?: string[]; + gasUsedRatio: number[]; + reward?: string[][]; +}; +/** + * @type ExistingFeeHistoryBlock + * + * Historical data for a particular block that exists on the blockchain. + * @property number - The number of the block, as a BN. + * @property baseFeePerGas - The base fee per gas for the block in WEI, as a BN. + * @property gasUsedRatio - A number between 0 and 1 that represents the ratio between the gas paid + * for the block and its set gas limit. + * @property priorityFeesByPercentile - The priority fees paid for the transactions in the block + * that occurred at particular levels at which those transactions contributed to the overall gas + * used for the block, indexed by those percentiles. (See docs for {@link fetchBlockFeeHistory} for more + * on how this works.) + */ +declare type ExistingFeeHistoryBlock = { + number: BN; + baseFeePerGas: BN; + gasUsedRatio: number; + priorityFeesByPercentile: Record; +}; +/** + * @type NextFeeHistoryBlock + * + * Historical data for a theoretical block that could exist in the future. + * @property number - The number of the block, as a BN. + * @property baseFeePerGas - The estimated base fee per gas for the block in WEI, as a BN. + */ +declare type NextFeeHistoryBlock = { + number: BN; + baseFeePerGas: BN; +}; +/** + * @type FeeHistoryBlock + * + * Historical data for a particular block. + * @property number - The number of the block, as a BN. + * @property baseFeePerGas - The base fee per gas for the block in WEI, as a BN. + * @property gasUsedRatio - A number between 0 and 1 that represents the ratio between the gas paid + * for the block and its set gas limit. + * @property priorityFeesByPercentile - The priority fees paid for the transactions in the block + * that occurred at particular levels at which those transactions contributed to the overall gas + * used for the block, indexed by those percentiles. (See docs for {@link fetchBlockFeeHistory} for more + * on how this works.) + */ +export declare type FeeHistoryBlock = ExistingFeeHistoryBlock | NextFeeHistoryBlock; +/** + * @type ExtractPercentileFrom + * + * Extracts the percentiles that the type assigned to an array of FeeHistoryBlock has been created + * with. This makes use of the `infer` keyword to read the type argument. + */ +export declare type ExtractPercentileFrom = T extends FeeHistoryBlock[] ? P : never; +/** + * Uses `eth_feeHistory` (an EIP-1559 feature) to obtain information about gas fees from a range of + * blocks that have occurred recently on a network. + * + * To learn more, see these resources: + * + * - + * - + * - + * - + * - + * + * @param args - The arguments to this function. + * @param args.ethQuery - An EthQuery instance that wraps a provider for the network in question. + * @param args.endBlock - The desired end of the requested block range. Can be "latest" if you want + * to start from the latest successful block or the number of a known past block. + * @param args.numberOfBlocks - How many total blocks to fetch. Note that if this is more than 1024, + * multiple calls to `eth_feeHistory` will be made. + * @param args.percentiles - A set of numbers between 1 and 100 which will dictate how + * `priorityFeesByPercentile` in each returned block will be formed. When Ethereum runs the + * `eth_feeHistory` method, for each block it is considering, it will first sort all transactions by + * the priority fee. It will then go through each transaction and add the total amount of gas paid + * for that transaction to a bucket which maxes out at the total gas used for the whole block. As + * the bucket fills, it will cross percentages which correspond to the percentiles specified here, + * and the priority fees of the first transactions which cause it to reach those percentages will be + * recorded. Hence, `priorityFeesByPercentile` represents the priority fees of transactions at key + * gas used contribution levels, where earlier levels have smaller contributions and later levels + * have higher contributions. + * @param args.includeNextBlock - Whether to include an extra block that represents the next + * block after the latest one. Only the `baseFeePerGas` will be filled in for this block (which is + * estimated). + * @returns The list of blocks and their fee data, sorted from oldest to newest. + */ +export default function fetchBlockFeeHistory({ ethQuery, numberOfBlocks: totalNumberOfBlocks, endBlock: givenEndBlock, percentiles: givenPercentiles, includeNextBlock, }: { + ethQuery: EthQuery; + numberOfBlocks: number; + endBlock?: 'latest' | BN; + percentiles?: readonly Percentile[]; + includeNextBlock?: boolean; +}): Promise[]>; +export {}; diff --git a/dist/gas/fetchBlockFeeHistory.js b/dist/gas/fetchBlockFeeHistory.js new file mode 100644 index 0000000000..c5011d3a18 --- /dev/null +++ b/dist/gas/fetchBlockFeeHistory.js @@ -0,0 +1,202 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const ethereumjs_util_1 = require("ethereumjs-util"); +const util_1 = require("../util"); +const MAX_NUMBER_OF_BLOCKS_PER_ETH_FEE_HISTORY_CALL = 1024; +/** + * Uses `eth_feeHistory` (an EIP-1559 feature) to obtain information about gas fees from a range of + * blocks that have occurred recently on a network. + * + * To learn more, see these resources: + * + * - + * - + * - + * - + * - + * + * @param args - The arguments to this function. + * @param args.ethQuery - An EthQuery instance that wraps a provider for the network in question. + * @param args.endBlock - The desired end of the requested block range. Can be "latest" if you want + * to start from the latest successful block or the number of a known past block. + * @param args.numberOfBlocks - How many total blocks to fetch. Note that if this is more than 1024, + * multiple calls to `eth_feeHistory` will be made. + * @param args.percentiles - A set of numbers between 1 and 100 which will dictate how + * `priorityFeesByPercentile` in each returned block will be formed. When Ethereum runs the + * `eth_feeHistory` method, for each block it is considering, it will first sort all transactions by + * the priority fee. It will then go through each transaction and add the total amount of gas paid + * for that transaction to a bucket which maxes out at the total gas used for the whole block. As + * the bucket fills, it will cross percentages which correspond to the percentiles specified here, + * and the priority fees of the first transactions which cause it to reach those percentages will be + * recorded. Hence, `priorityFeesByPercentile` represents the priority fees of transactions at key + * gas used contribution levels, where earlier levels have smaller contributions and later levels + * have higher contributions. + * @param args.includeNextBlock - Whether to include an extra block that represents the next + * block after the latest one. Only the `baseFeePerGas` will be filled in for this block (which is + * estimated). + * @returns The list of blocks and their fee data, sorted from oldest to newest. + */ +function fetchBlockFeeHistory({ ethQuery, numberOfBlocks: totalNumberOfBlocks, endBlock: givenEndBlock = 'latest', percentiles: givenPercentiles = [], includeNextBlock = false, }) { + return __awaiter(this, void 0, void 0, function* () { + const percentiles = givenPercentiles.length > 0 + ? Array.from(new Set(givenPercentiles)).sort((a, b) => a - b) + : []; + const finalEndBlockNumber = givenEndBlock === 'latest' + ? (0, util_1.fromHex)(yield (0, util_1.query)(ethQuery, 'blockNumber')) + : givenEndBlock; + const requestChunkSpecifiers = determineRequestChunkSpecifiers(finalEndBlockNumber, totalNumberOfBlocks); + const blockChunks = yield Promise.all(requestChunkSpecifiers.map(({ numberOfBlocks, endBlockNumber }, i) => { + return i === requestChunkSpecifiers.length - 1 + ? makeRequestForChunk({ + ethQuery, + numberOfBlocks, + endBlockNumber, + percentiles, + includeNextBlock, + }) + : makeRequestForChunk({ + ethQuery, + numberOfBlocks, + endBlockNumber, + percentiles, + includeNextBlock: false, + }); + })); + return blockChunks.reduce((array, blocks) => [...array, ...blocks], []); + }); +} +exports.default = fetchBlockFeeHistory; +/** + * Builds an ExistingFeeHistoryBlock. + * + * @param args - The args to this function. + * @param args.number - The number of the block. + * @param args.baseFeePerGas - The base fee per gas of the block. + * @param args.blockIndex - The index of the block in the source chunk. + * @param args.gasUsedRatios - The gas used ratios for the block. + * @param args.priorityFeePercentileGroups - The priority fee percentile groups for the block. + * @param args.percentiles - The percentiles used to fetch the source chunk. + * @returns The ExistingFeeHistoryBlock. + */ +function buildExistingFeeHistoryBlock({ baseFeePerGas, number, blockIndex, gasUsedRatios, priorityFeePercentileGroups, percentiles, }) { + const gasUsedRatio = gasUsedRatios[blockIndex]; + const priorityFeesForEachPercentile = priorityFeePercentileGroups[blockIndex]; + const priorityFeesByPercentile = percentiles.reduce((obj, percentile, percentileIndex) => { + const priorityFee = priorityFeesForEachPercentile[percentileIndex]; + return Object.assign(Object.assign({}, obj), { [percentile]: (0, util_1.fromHex)(priorityFee) }); + }, {}); + return { + number, + baseFeePerGas, + gasUsedRatio, + priorityFeesByPercentile, + }; +} +/** + * Builds a NextFeeHistoryBlock. + * + * @param args - The args to this function. + * @param args.baseFeePerGas - The base fee per gas of the block. + * @param args.number - The number of the block. + * @returns The NextFeeHistoryBlock. + */ +function buildNextFeeHistoryBlock({ baseFeePerGas, number, }) { + return { + number, + baseFeePerGas, + gasUsedRatio: null, + priorityFeesByPercentile: null, + }; +} +/** + * Uses eth_feeHistory to request historical data about a group of blocks (max size 1024). + * + * @param args - The arguments + * @param args.ethQuery - An EthQuery instance. + * @param args.numberOfBlocks - The number of blocks in the chunk. Must be at most 1024, as this is + * the maximum that `eth_feeHistory` can return in one call. + * @param args.endBlockNumber - The end of the requested block range. + * @param args.percentiles - A set of numbers between 1 and 100 that will be used to pull priority + * fees for each block. + * @param args.includeNextBlock - Whether to include an extra block that represents the next + * block after the latest one. Only the `baseFeePerGas` will be filled in for this block (which is + * estimated). + * @returns A list of block data. + */ +function makeRequestForChunk({ ethQuery, numberOfBlocks, endBlockNumber, percentiles, includeNextBlock, }) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const response = yield (0, util_1.query)(ethQuery, 'eth_feeHistory', [(0, util_1.toHex)(numberOfBlocks), (0, util_1.toHex)(endBlockNumber), percentiles]); + const startBlockNumber = (0, util_1.fromHex)(response.oldestBlock); + if (response.baseFeePerGas !== undefined && + response.baseFeePerGas.length > 0 && + response.gasUsedRatio.length > 0 && + (response.reward === undefined || response.reward.length > 0)) { + // Per + // , + // baseFeePerGas will always include an extra item which is the calculated base fee for the + // next (future) block. We may or may not care about this; if we don't, chop it off. + const baseFeesPerGasAsHex = includeNextBlock + ? response.baseFeePerGas + : response.baseFeePerGas.slice(0, numberOfBlocks); + const gasUsedRatios = response.gasUsedRatio; + const priorityFeePercentileGroups = (_a = response.reward) !== null && _a !== void 0 ? _a : []; + // Chain is allowed to return fewer number of block results + const numberOfExistingResults = gasUsedRatios.length; + return baseFeesPerGasAsHex.map((baseFeePerGasAsHex, blockIndex) => { + const baseFeePerGas = (0, util_1.fromHex)(baseFeePerGasAsHex); + const number = startBlockNumber.addn(blockIndex); + return blockIndex >= numberOfExistingResults + ? buildNextFeeHistoryBlock({ baseFeePerGas, number }) + : buildExistingFeeHistoryBlock({ + baseFeePerGas, + number, + blockIndex, + gasUsedRatios, + priorityFeePercentileGroups, + percentiles, + }); + }); + } + return []; + }); +} +/** + * Divides a block range (specified by a range size and the end of the range) into chunks based on + * the maximum number of blocks that `eth_feeHistory` can return in a single call. + * + * If the requested totalNumberOfBlocks exceed endBlockNumber, totalNumberOfBlocks is + * truncated to avoid requesting chunks with negative endBlockNumber. + * + * @param endBlockNumber - The final block in the complete desired block range after all + * `eth_feeHistory` requests have been made. + * @param totalNumberOfBlocks - The total number of desired blocks after all `eth_feeHistory` + * requests have been made. + * @returns A set of arguments that can be used to make requests to `eth_feeHistory` in order to + * retrieve all of the requested blocks, sorted from oldest block to newest block. + */ +function determineRequestChunkSpecifiers(endBlockNumber, totalNumberOfBlocks) { + if (endBlockNumber.lt(new ethereumjs_util_1.BN(totalNumberOfBlocks))) { + totalNumberOfBlocks = endBlockNumber.toNumber(); + } + const specifiers = []; + for (let chunkStartBlockNumber = endBlockNumber.subn(totalNumberOfBlocks); chunkStartBlockNumber.lt(endBlockNumber); chunkStartBlockNumber = chunkStartBlockNumber.addn(MAX_NUMBER_OF_BLOCKS_PER_ETH_FEE_HISTORY_CALL)) { + const distanceToEnd = endBlockNumber.sub(chunkStartBlockNumber).toNumber(); + const numberOfBlocks = distanceToEnd < MAX_NUMBER_OF_BLOCKS_PER_ETH_FEE_HISTORY_CALL + ? distanceToEnd + : MAX_NUMBER_OF_BLOCKS_PER_ETH_FEE_HISTORY_CALL; + const chunkEndBlockNumber = chunkStartBlockNumber.addn(numberOfBlocks); + specifiers.push({ numberOfBlocks, endBlockNumber: chunkEndBlockNumber }); + } + return specifiers; +} +//# sourceMappingURL=fetchBlockFeeHistory.js.map \ No newline at end of file diff --git a/dist/gas/fetchBlockFeeHistory.js.map b/dist/gas/fetchBlockFeeHistory.js.map new file mode 100644 index 0000000000..973e76f88d --- /dev/null +++ b/dist/gas/fetchBlockFeeHistory.js.map @@ -0,0 +1 @@ +{"version":3,"file":"fetchBlockFeeHistory.js","sourceRoot":"","sources":["../../src/gas/fetchBlockFeeHistory.ts"],"names":[],"mappings":"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","sourcesContent":["import { BN } from 'ethereumjs-util';\nimport { query, fromHex, toHex } from '../util';\n\ntype EthQuery = any;\n\n/**\n * @type RequestChunkSpecifier\n *\n * Arguments to `eth_feeHistory` that can be used to fetch a set of historical data.\n * @property blockCount - The number of blocks requested.\n * @property endBlockNumber - The number of the block at the end of the requested range.\n */\ntype RequestChunkSpecifier = {\n numberOfBlocks: number;\n endBlockNumber: BN;\n};\n\n/**\n * @type EthFeeHistoryResponse\n *\n * Response data for `eth_feeHistory`.\n * @property oldestBlock - The id of the oldest block (in hex format) in the range of blocks\n * requested.\n * @property baseFeePerGas - Base fee per gas for each block in the range of blocks requested.\n * For go-ethereum based chains baseFeePerGas will not returned in case of empty results\n * \n * @property gasUsedRatio - A number between 0 and 1 that represents the gas used vs. gas limit for\n * each block in the range of blocks requested.\n * @property reward - The priority fee at the percentiles requested for each block in the range of\n * blocks requested.\n */\n\nexport type EthFeeHistoryResponse = {\n oldestBlock: string;\n baseFeePerGas?: string[];\n gasUsedRatio: number[];\n reward?: string[][];\n};\n\n/**\n * @type ExistingFeeHistoryBlock\n *\n * Historical data for a particular block that exists on the blockchain.\n * @property number - The number of the block, as a BN.\n * @property baseFeePerGas - The base fee per gas for the block in WEI, as a BN.\n * @property gasUsedRatio - A number between 0 and 1 that represents the ratio between the gas paid\n * for the block and its set gas limit.\n * @property priorityFeesByPercentile - The priority fees paid for the transactions in the block\n * that occurred at particular levels at which those transactions contributed to the overall gas\n * used for the block, indexed by those percentiles. (See docs for {@link fetchBlockFeeHistory} for more\n * on how this works.)\n */\ntype ExistingFeeHistoryBlock = {\n number: BN;\n baseFeePerGas: BN;\n gasUsedRatio: number;\n priorityFeesByPercentile: Record;\n};\n\n/**\n * @type NextFeeHistoryBlock\n *\n * Historical data for a theoretical block that could exist in the future.\n * @property number - The number of the block, as a BN.\n * @property baseFeePerGas - The estimated base fee per gas for the block in WEI, as a BN.\n */\ntype NextFeeHistoryBlock = {\n number: BN;\n baseFeePerGas: BN;\n};\n\n/**\n * @type FeeHistoryBlock\n *\n * Historical data for a particular block.\n * @property number - The number of the block, as a BN.\n * @property baseFeePerGas - The base fee per gas for the block in WEI, as a BN.\n * @property gasUsedRatio - A number between 0 and 1 that represents the ratio between the gas paid\n * for the block and its set gas limit.\n * @property priorityFeesByPercentile - The priority fees paid for the transactions in the block\n * that occurred at particular levels at which those transactions contributed to the overall gas\n * used for the block, indexed by those percentiles. (See docs for {@link fetchBlockFeeHistory} for more\n * on how this works.)\n */\nexport type FeeHistoryBlock =\n | ExistingFeeHistoryBlock\n | NextFeeHistoryBlock;\n\n/**\n * @type ExtractPercentileFrom\n *\n * Extracts the percentiles that the type assigned to an array of FeeHistoryBlock has been created\n * with. This makes use of the `infer` keyword to read the type argument.\n */\nexport type ExtractPercentileFrom = T extends FeeHistoryBlock[]\n ? P\n : never;\n\nconst MAX_NUMBER_OF_BLOCKS_PER_ETH_FEE_HISTORY_CALL = 1024;\n\n/**\n * Uses `eth_feeHistory` (an EIP-1559 feature) to obtain information about gas fees from a range of\n * blocks that have occurred recently on a network.\n *\n * To learn more, see these resources:\n *\n * - \n * - \n * - \n * - \n * - \n *\n * @param args - The arguments to this function.\n * @param args.ethQuery - An EthQuery instance that wraps a provider for the network in question.\n * @param args.endBlock - The desired end of the requested block range. Can be \"latest\" if you want\n * to start from the latest successful block or the number of a known past block.\n * @param args.numberOfBlocks - How many total blocks to fetch. Note that if this is more than 1024,\n * multiple calls to `eth_feeHistory` will be made.\n * @param args.percentiles - A set of numbers between 1 and 100 which will dictate how\n * `priorityFeesByPercentile` in each returned block will be formed. When Ethereum runs the\n * `eth_feeHistory` method, for each block it is considering, it will first sort all transactions by\n * the priority fee. It will then go through each transaction and add the total amount of gas paid\n * for that transaction to a bucket which maxes out at the total gas used for the whole block. As\n * the bucket fills, it will cross percentages which correspond to the percentiles specified here,\n * and the priority fees of the first transactions which cause it to reach those percentages will be\n * recorded. Hence, `priorityFeesByPercentile` represents the priority fees of transactions at key\n * gas used contribution levels, where earlier levels have smaller contributions and later levels\n * have higher contributions.\n * @param args.includeNextBlock - Whether to include an extra block that represents the next\n * block after the latest one. Only the `baseFeePerGas` will be filled in for this block (which is\n * estimated).\n * @returns The list of blocks and their fee data, sorted from oldest to newest.\n */\nexport default async function fetchBlockFeeHistory({\n ethQuery,\n numberOfBlocks: totalNumberOfBlocks,\n endBlock: givenEndBlock = 'latest',\n percentiles: givenPercentiles = [],\n includeNextBlock = false,\n}: {\n ethQuery: EthQuery;\n numberOfBlocks: number;\n endBlock?: 'latest' | BN;\n percentiles?: readonly Percentile[];\n includeNextBlock?: boolean;\n}): Promise[]> {\n const percentiles =\n givenPercentiles.length > 0\n ? Array.from(new Set(givenPercentiles)).sort((a, b) => a - b)\n : [];\n\n const finalEndBlockNumber =\n givenEndBlock === 'latest'\n ? fromHex(await query(ethQuery, 'blockNumber'))\n : givenEndBlock;\n\n const requestChunkSpecifiers = determineRequestChunkSpecifiers(\n finalEndBlockNumber,\n totalNumberOfBlocks,\n );\n\n const blockChunks = await Promise.all(\n requestChunkSpecifiers.map(({ numberOfBlocks, endBlockNumber }, i) => {\n return i === requestChunkSpecifiers.length - 1\n ? makeRequestForChunk({\n ethQuery,\n numberOfBlocks,\n endBlockNumber,\n percentiles,\n includeNextBlock,\n })\n : makeRequestForChunk({\n ethQuery,\n numberOfBlocks,\n endBlockNumber,\n percentiles,\n includeNextBlock: false,\n });\n }),\n );\n\n return blockChunks.reduce(\n (array, blocks) => [...array, ...blocks],\n [] as FeeHistoryBlock[],\n );\n}\n\n/**\n * Builds an ExistingFeeHistoryBlock.\n *\n * @param args - The args to this function.\n * @param args.number - The number of the block.\n * @param args.baseFeePerGas - The base fee per gas of the block.\n * @param args.blockIndex - The index of the block in the source chunk.\n * @param args.gasUsedRatios - The gas used ratios for the block.\n * @param args.priorityFeePercentileGroups - The priority fee percentile groups for the block.\n * @param args.percentiles - The percentiles used to fetch the source chunk.\n * @returns The ExistingFeeHistoryBlock.\n */\nfunction buildExistingFeeHistoryBlock({\n baseFeePerGas,\n number,\n blockIndex,\n gasUsedRatios,\n priorityFeePercentileGroups,\n percentiles,\n}: {\n baseFeePerGas: BN;\n number: BN;\n blockIndex: number;\n gasUsedRatios: number[];\n priorityFeePercentileGroups: string[][];\n percentiles: readonly Percentile[];\n}): ExistingFeeHistoryBlock {\n const gasUsedRatio = gasUsedRatios[blockIndex];\n const priorityFeesForEachPercentile = priorityFeePercentileGroups[blockIndex];\n const priorityFeesByPercentile = percentiles.reduce(\n (obj, percentile, percentileIndex) => {\n const priorityFee = priorityFeesForEachPercentile[percentileIndex];\n return { ...obj, [percentile]: fromHex(priorityFee) };\n },\n {} as Record,\n );\n\n return {\n number,\n baseFeePerGas,\n gasUsedRatio,\n priorityFeesByPercentile,\n };\n}\n\n/**\n * Builds a NextFeeHistoryBlock.\n *\n * @param args - The args to this function.\n * @param args.baseFeePerGas - The base fee per gas of the block.\n * @param args.number - The number of the block.\n * @returns The NextFeeHistoryBlock.\n */\nfunction buildNextFeeHistoryBlock({\n baseFeePerGas,\n number,\n}: {\n baseFeePerGas: BN;\n number: BN;\n}) {\n return {\n number,\n baseFeePerGas,\n gasUsedRatio: null,\n priorityFeesByPercentile: null,\n };\n}\n\n/**\n * Uses eth_feeHistory to request historical data about a group of blocks (max size 1024).\n *\n * @param args - The arguments\n * @param args.ethQuery - An EthQuery instance.\n * @param args.numberOfBlocks - The number of blocks in the chunk. Must be at most 1024, as this is\n * the maximum that `eth_feeHistory` can return in one call.\n * @param args.endBlockNumber - The end of the requested block range.\n * @param args.percentiles - A set of numbers between 1 and 100 that will be used to pull priority\n * fees for each block.\n * @param args.includeNextBlock - Whether to include an extra block that represents the next\n * block after the latest one. Only the `baseFeePerGas` will be filled in for this block (which is\n * estimated).\n * @returns A list of block data.\n */\nasync function makeRequestForChunk({\n ethQuery,\n numberOfBlocks,\n endBlockNumber,\n percentiles,\n includeNextBlock,\n}: {\n ethQuery: EthQuery;\n numberOfBlocks: number;\n endBlockNumber: BN;\n percentiles: readonly Percentile[];\n includeNextBlock: boolean;\n}): Promise[]> {\n const response: EthFeeHistoryResponse = await query(\n ethQuery,\n 'eth_feeHistory',\n [toHex(numberOfBlocks), toHex(endBlockNumber), percentiles],\n );\n\n const startBlockNumber = fromHex(response.oldestBlock);\n\n if (\n response.baseFeePerGas !== undefined &&\n response.baseFeePerGas.length > 0 &&\n response.gasUsedRatio.length > 0 &&\n (response.reward === undefined || response.reward.length > 0)\n ) {\n // Per\n // ,\n // baseFeePerGas will always include an extra item which is the calculated base fee for the\n // next (future) block. We may or may not care about this; if we don't, chop it off.\n const baseFeesPerGasAsHex = includeNextBlock\n ? response.baseFeePerGas\n : response.baseFeePerGas.slice(0, numberOfBlocks);\n const gasUsedRatios = response.gasUsedRatio;\n const priorityFeePercentileGroups = response.reward ?? [];\n // Chain is allowed to return fewer number of block results\n const numberOfExistingResults = gasUsedRatios.length;\n\n return baseFeesPerGasAsHex.map((baseFeePerGasAsHex, blockIndex) => {\n const baseFeePerGas = fromHex(baseFeePerGasAsHex);\n const number = startBlockNumber.addn(blockIndex);\n\n return blockIndex >= numberOfExistingResults\n ? buildNextFeeHistoryBlock({ baseFeePerGas, number })\n : buildExistingFeeHistoryBlock({\n baseFeePerGas,\n number,\n blockIndex,\n gasUsedRatios,\n priorityFeePercentileGroups,\n percentiles,\n });\n });\n }\n\n return [];\n}\n\n/**\n * Divides a block range (specified by a range size and the end of the range) into chunks based on\n * the maximum number of blocks that `eth_feeHistory` can return in a single call.\n *\n * If the requested totalNumberOfBlocks exceed endBlockNumber, totalNumberOfBlocks is\n * truncated to avoid requesting chunks with negative endBlockNumber.\n *\n * @param endBlockNumber - The final block in the complete desired block range after all\n * `eth_feeHistory` requests have been made.\n * @param totalNumberOfBlocks - The total number of desired blocks after all `eth_feeHistory`\n * requests have been made.\n * @returns A set of arguments that can be used to make requests to `eth_feeHistory` in order to\n * retrieve all of the requested blocks, sorted from oldest block to newest block.\n */\nfunction determineRequestChunkSpecifiers(\n endBlockNumber: BN,\n totalNumberOfBlocks: number,\n): RequestChunkSpecifier[] {\n if (endBlockNumber.lt(new BN(totalNumberOfBlocks))) {\n totalNumberOfBlocks = endBlockNumber.toNumber();\n }\n\n const specifiers = [];\n for (\n let chunkStartBlockNumber = endBlockNumber.subn(totalNumberOfBlocks);\n chunkStartBlockNumber.lt(endBlockNumber);\n chunkStartBlockNumber = chunkStartBlockNumber.addn(\n MAX_NUMBER_OF_BLOCKS_PER_ETH_FEE_HISTORY_CALL,\n )\n ) {\n const distanceToEnd = endBlockNumber.sub(chunkStartBlockNumber).toNumber();\n const numberOfBlocks =\n distanceToEnd < MAX_NUMBER_OF_BLOCKS_PER_ETH_FEE_HISTORY_CALL\n ? distanceToEnd\n : MAX_NUMBER_OF_BLOCKS_PER_ETH_FEE_HISTORY_CALL;\n const chunkEndBlockNumber = chunkStartBlockNumber.addn(numberOfBlocks);\n specifiers.push({ numberOfBlocks, endBlockNumber: chunkEndBlockNumber });\n }\n return specifiers;\n}\n"]} \ No newline at end of file diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory.d.ts b/dist/gas/fetchGasEstimatesViaEthFeeHistory.d.ts new file mode 100644 index 0000000000..8b4ca02c5d --- /dev/null +++ b/dist/gas/fetchGasEstimatesViaEthFeeHistory.d.ts @@ -0,0 +1,21 @@ +import { GasFeeEstimates } from './GasFeeController'; +import { EthQuery } from './fetchGasEstimatesViaEthFeeHistory/types'; +/** + * Generates gas fee estimates based on gas fees that have been used in the recent past so that + * those estimates can be displayed to users. + * + * To produce the estimates, the last 5 blocks are read from the network, and for each block, the + * priority fees for transactions at the 10th, 20th, and 30th percentiles are also read (here + * "percentile" signifies the level at which those transactions contribute to the overall gas used + * for the block, where higher percentiles correspond to higher fees). This information is used to + * calculate reasonable max priority and max fees for three different priority levels (higher + * priority = higher fee). + * + * Note that properties are returned for other data that are normally obtained via the API; however, + * to prevent extra requests to Infura, these properties are empty. + * + * @param ethQuery - An EthQuery instance. + * @returns Base and priority fee estimates, categorized by priority level, as well as an estimate + * for the next block's base fee. + */ +export default function fetchGasEstimatesViaEthFeeHistory(ethQuery: EthQuery): Promise; diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory.js b/dist/gas/fetchGasEstimatesViaEthFeeHistory.js new file mode 100644 index 0000000000..9669c69093 --- /dev/null +++ b/dist/gas/fetchGasEstimatesViaEthFeeHistory.js @@ -0,0 +1,53 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const ethjs_unit_1 = require("ethjs-unit"); +const constants_1 = require("../constants"); +const fetchBlockFeeHistory_1 = __importDefault(require("./fetchBlockFeeHistory")); +const fetchLatestBlock_1 = __importDefault(require("./fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock")); +const calculateGasFeeEstimatesForPriorityLevels_1 = __importDefault(require("./fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels")); +/** + * Generates gas fee estimates based on gas fees that have been used in the recent past so that + * those estimates can be displayed to users. + * + * To produce the estimates, the last 5 blocks are read from the network, and for each block, the + * priority fees for transactions at the 10th, 20th, and 30th percentiles are also read (here + * "percentile" signifies the level at which those transactions contribute to the overall gas used + * for the block, where higher percentiles correspond to higher fees). This information is used to + * calculate reasonable max priority and max fees for three different priority levels (higher + * priority = higher fee). + * + * Note that properties are returned for other data that are normally obtained via the API; however, + * to prevent extra requests to Infura, these properties are empty. + * + * @param ethQuery - An EthQuery instance. + * @returns Base and priority fee estimates, categorized by priority level, as well as an estimate + * for the next block's base fee. + */ +function fetchGasEstimatesViaEthFeeHistory(ethQuery) { + return __awaiter(this, void 0, void 0, function* () { + const latestBlock = yield (0, fetchLatestBlock_1.default)(ethQuery); + const blocks = yield (0, fetchBlockFeeHistory_1.default)({ + ethQuery, + endBlock: latestBlock.number, + numberOfBlocks: 5, + percentiles: [10, 20, 30], + }); + const estimatedBaseFee = (0, ethjs_unit_1.fromWei)(latestBlock.baseFeePerGas, constants_1.GWEI); + const levelSpecificEstimates = (0, calculateGasFeeEstimatesForPriorityLevels_1.default)(blocks); + return Object.assign(Object.assign({}, levelSpecificEstimates), { estimatedBaseFee, historicalBaseFeeRange: null, baseFeeTrend: null, latestPriorityFeeRange: null, historicalPriorityFeeRange: null, priorityFeeTrend: null, networkCongestion: null }); + }); +} +exports.default = fetchGasEstimatesViaEthFeeHistory; +//# sourceMappingURL=fetchGasEstimatesViaEthFeeHistory.js.map \ No newline at end of file diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory.js.map b/dist/gas/fetchGasEstimatesViaEthFeeHistory.js.map new file mode 100644 index 0000000000..c7fbbbb554 --- /dev/null +++ b/dist/gas/fetchGasEstimatesViaEthFeeHistory.js.map @@ -0,0 +1 @@ +{"version":3,"file":"fetchGasEstimatesViaEthFeeHistory.js","sourceRoot":"","sources":["../../src/gas/fetchGasEstimatesViaEthFeeHistory.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,2CAAqC;AACrC,4CAAoC;AAGpC,kFAA0D;AAC1D,4GAAoF;AACpF,8JAAsI;AAEtI;;;;;;;;;;;;;;;;;GAiBG;AACH,SAA8B,iCAAiC,CAC7D,QAAkB;;QAElB,MAAM,WAAW,GAAG,MAAM,IAAA,0BAAgB,EAAC,QAAQ,CAAC,CAAC;QACrD,MAAM,MAAM,GAAG,MAAM,IAAA,8BAAoB,EAAC;YACxC,QAAQ;YACR,QAAQ,EAAE,WAAW,CAAC,MAAM;YAC5B,cAAc,EAAE,CAAC;YACjB,WAAW,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;SAC1B,CAAC,CAAC;QACH,MAAM,gBAAgB,GAAG,IAAA,oBAAO,EAAC,WAAW,CAAC,aAAa,EAAE,gBAAI,CAAC,CAAC;QAElE,MAAM,sBAAsB,GAC1B,IAAA,mDAAyC,EAAC,MAAM,CAAC,CAAC;QAEpD,uCACK,sBAAsB,KACzB,gBAAgB,EAChB,sBAAsB,EAAE,IAAI,EAC5B,YAAY,EAAE,IAAI,EAClB,sBAAsB,EAAE,IAAI,EAC5B,0BAA0B,EAAE,IAAI,EAChC,gBAAgB,EAAE,IAAI,EACtB,iBAAiB,EAAE,IAAI,IACvB;IACJ,CAAC;CAAA;AAzBD,oDAyBC","sourcesContent":["import { fromWei } from 'ethjs-unit';\nimport { GWEI } from '../constants';\nimport { GasFeeEstimates } from './GasFeeController';\nimport { EthQuery } from './fetchGasEstimatesViaEthFeeHistory/types';\nimport fetchBlockFeeHistory from './fetchBlockFeeHistory';\nimport fetchLatestBlock from './fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock';\nimport calculateGasFeeEstimatesForPriorityLevels from './fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels';\n\n/**\n * Generates gas fee estimates based on gas fees that have been used in the recent past so that\n * those estimates can be displayed to users.\n *\n * To produce the estimates, the last 5 blocks are read from the network, and for each block, the\n * priority fees for transactions at the 10th, 20th, and 30th percentiles are also read (here\n * \"percentile\" signifies the level at which those transactions contribute to the overall gas used\n * for the block, where higher percentiles correspond to higher fees). This information is used to\n * calculate reasonable max priority and max fees for three different priority levels (higher\n * priority = higher fee).\n *\n * Note that properties are returned for other data that are normally obtained via the API; however,\n * to prevent extra requests to Infura, these properties are empty.\n *\n * @param ethQuery - An EthQuery instance.\n * @returns Base and priority fee estimates, categorized by priority level, as well as an estimate\n * for the next block's base fee.\n */\nexport default async function fetchGasEstimatesViaEthFeeHistory(\n ethQuery: EthQuery,\n): Promise {\n const latestBlock = await fetchLatestBlock(ethQuery);\n const blocks = await fetchBlockFeeHistory({\n ethQuery,\n endBlock: latestBlock.number,\n numberOfBlocks: 5,\n percentiles: [10, 20, 30],\n });\n const estimatedBaseFee = fromWei(latestBlock.baseFeePerGas, GWEI);\n\n const levelSpecificEstimates =\n calculateGasFeeEstimatesForPriorityLevels(blocks);\n\n return {\n ...levelSpecificEstimates,\n estimatedBaseFee,\n historicalBaseFeeRange: null,\n baseFeeTrend: null,\n latestPriorityFeeRange: null,\n historicalPriorityFeeRange: null,\n priorityFeeTrend: null,\n networkCongestion: null,\n };\n}\n"]} \ No newline at end of file diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.d.ts b/dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.d.ts new file mode 100644 index 0000000000..df00bb5ccf --- /dev/null +++ b/dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.d.ts @@ -0,0 +1,16 @@ +import { GasFeeEstimates } from '../GasFeeController'; +import { FeeHistoryBlock } from '../fetchBlockFeeHistory'; +export declare type PriorityLevel = typeof PRIORITY_LEVELS[number]; +export declare type Percentile = typeof PRIORITY_LEVEL_PERCENTILES[number]; +declare const PRIORITY_LEVELS: readonly ["low", "medium", "high"]; +declare const PRIORITY_LEVEL_PERCENTILES: readonly [10, 20, 30]; +/** + * Calculates a set of estimates suitable for different priority levels based on the data returned + * by `eth_feeHistory`. + * + * @param blocks - A set of blocks populated with data for priority fee percentiles 10, 20, and 30, + * obtained via {@link BlockFeeHistoryDatasetFetcher}. + * @returns The estimates. + */ +export default function calculateGasFeeEstimatesForPriorityLevels(blocks: FeeHistoryBlock[]): Pick; +export {}; diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.js b/dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.js new file mode 100644 index 0000000000..c306543241 --- /dev/null +++ b/dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.js @@ -0,0 +1,89 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const ethereumjs_util_1 = require("ethereumjs-util"); +const ethjs_unit_1 = require("ethjs-unit"); +const constants_1 = require("../../constants"); +const medianOf_1 = __importDefault(require("./medianOf")); +const PRIORITY_LEVELS = ['low', 'medium', 'high']; +const PRIORITY_LEVEL_PERCENTILES = [10, 20, 30]; +const SETTINGS_BY_PRIORITY_LEVEL = { + low: { + percentile: 10, + baseFeePercentageMultiplier: new ethereumjs_util_1.BN(110), + priorityFeePercentageMultiplier: new ethereumjs_util_1.BN(94), + minSuggestedMaxPriorityFeePerGas: new ethereumjs_util_1.BN(1000000000), + estimatedWaitTimes: { + minWaitTimeEstimate: 15000, + maxWaitTimeEstimate: 30000, + }, + }, + medium: { + percentile: 20, + baseFeePercentageMultiplier: new ethereumjs_util_1.BN(120), + priorityFeePercentageMultiplier: new ethereumjs_util_1.BN(97), + minSuggestedMaxPriorityFeePerGas: new ethereumjs_util_1.BN(1500000000), + estimatedWaitTimes: { + minWaitTimeEstimate: 15000, + maxWaitTimeEstimate: 45000, + }, + }, + high: { + percentile: 30, + baseFeePercentageMultiplier: new ethereumjs_util_1.BN(125), + priorityFeePercentageMultiplier: new ethereumjs_util_1.BN(98), + minSuggestedMaxPriorityFeePerGas: new ethereumjs_util_1.BN(2000000000), + estimatedWaitTimes: { + minWaitTimeEstimate: 15000, + maxWaitTimeEstimate: 60000, + }, + }, +}; +/** + * Calculates a set of estimates assigned to a particular priority level based on the data returned + * by `eth_feeHistory`. + * + * @param priorityLevel - The level of fees that dictates how soon a transaction may go through + * ("low", "medium", or "high"). + * @param blocks - A set of blocks as obtained from {@link fetchBlockFeeHistory}. + * @returns The estimates. + */ +function calculateEstimatesForPriorityLevel(priorityLevel, blocks) { + const settings = SETTINGS_BY_PRIORITY_LEVEL[priorityLevel]; + const latestBaseFeePerGas = blocks[blocks.length - 1].baseFeePerGas; + const adjustedBaseFee = latestBaseFeePerGas + .mul(settings.baseFeePercentageMultiplier) + .divn(100); + const priorityFees = blocks + .map((block) => { + return 'priorityFeesByPercentile' in block + ? block.priorityFeesByPercentile[settings.percentile] + : null; + }) + .filter(ethereumjs_util_1.BN.isBN); + const medianPriorityFee = (0, medianOf_1.default)(priorityFees); + const adjustedPriorityFee = medianPriorityFee + .mul(settings.priorityFeePercentageMultiplier) + .divn(100); + const suggestedMaxPriorityFeePerGas = ethereumjs_util_1.BN.max(adjustedPriorityFee, settings.minSuggestedMaxPriorityFeePerGas); + const suggestedMaxFeePerGas = adjustedBaseFee.add(suggestedMaxPriorityFeePerGas); + return Object.assign(Object.assign({}, settings.estimatedWaitTimes), { suggestedMaxPriorityFeePerGas: (0, ethjs_unit_1.fromWei)(suggestedMaxPriorityFeePerGas, constants_1.GWEI), suggestedMaxFeePerGas: (0, ethjs_unit_1.fromWei)(suggestedMaxFeePerGas, constants_1.GWEI) }); +} +/** + * Calculates a set of estimates suitable for different priority levels based on the data returned + * by `eth_feeHistory`. + * + * @param blocks - A set of blocks populated with data for priority fee percentiles 10, 20, and 30, + * obtained via {@link BlockFeeHistoryDatasetFetcher}. + * @returns The estimates. + */ +function calculateGasFeeEstimatesForPriorityLevels(blocks) { + return PRIORITY_LEVELS.reduce((obj, priorityLevel) => { + const gasEstimatesForPriorityLevel = calculateEstimatesForPriorityLevel(priorityLevel, blocks); + return Object.assign(Object.assign({}, obj), { [priorityLevel]: gasEstimatesForPriorityLevel }); + }, {}); +} +exports.default = calculateGasFeeEstimatesForPriorityLevels; +//# sourceMappingURL=calculateGasFeeEstimatesForPriorityLevels.js.map \ No newline at end of file diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.js.map b/dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.js.map new file mode 100644 index 0000000000..1803a010fa --- /dev/null +++ b/dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.js.map @@ -0,0 +1 @@ +{"version":3,"file":"calculateGasFeeEstimatesForPriorityLevels.js","sourceRoot":"","sources":["../../../src/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.ts"],"names":[],"mappings":"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","sourcesContent":["import { BN } from 'ethereumjs-util';\nimport { fromWei } from 'ethjs-unit';\nimport { Eip1559GasFee, GasFeeEstimates } from '../GasFeeController';\nimport { FeeHistoryBlock } from '../fetchBlockFeeHistory';\nimport { GWEI } from '../../constants';\nimport medianOf from './medianOf';\n\nexport type PriorityLevel = typeof PRIORITY_LEVELS[number];\nexport type Percentile = typeof PRIORITY_LEVEL_PERCENTILES[number];\n\nconst PRIORITY_LEVELS = ['low', 'medium', 'high'] as const;\nconst PRIORITY_LEVEL_PERCENTILES = [10, 20, 30] as const;\nconst SETTINGS_BY_PRIORITY_LEVEL = {\n low: {\n percentile: 10 as Percentile,\n baseFeePercentageMultiplier: new BN(110),\n priorityFeePercentageMultiplier: new BN(94),\n minSuggestedMaxPriorityFeePerGas: new BN(1_000_000_000),\n estimatedWaitTimes: {\n minWaitTimeEstimate: 15_000,\n maxWaitTimeEstimate: 30_000,\n },\n },\n medium: {\n percentile: 20 as Percentile,\n baseFeePercentageMultiplier: new BN(120),\n priorityFeePercentageMultiplier: new BN(97),\n minSuggestedMaxPriorityFeePerGas: new BN(1_500_000_000),\n estimatedWaitTimes: {\n minWaitTimeEstimate: 15_000,\n maxWaitTimeEstimate: 45_000,\n },\n },\n high: {\n percentile: 30 as Percentile,\n baseFeePercentageMultiplier: new BN(125),\n priorityFeePercentageMultiplier: new BN(98),\n minSuggestedMaxPriorityFeePerGas: new BN(2_000_000_000),\n estimatedWaitTimes: {\n minWaitTimeEstimate: 15_000,\n maxWaitTimeEstimate: 60_000,\n },\n },\n};\n\n/**\n * Calculates a set of estimates assigned to a particular priority level based on the data returned\n * by `eth_feeHistory`.\n *\n * @param priorityLevel - The level of fees that dictates how soon a transaction may go through\n * (\"low\", \"medium\", or \"high\").\n * @param blocks - A set of blocks as obtained from {@link fetchBlockFeeHistory}.\n * @returns The estimates.\n */\nfunction calculateEstimatesForPriorityLevel(\n priorityLevel: PriorityLevel,\n blocks: FeeHistoryBlock[],\n): Eip1559GasFee {\n const settings = SETTINGS_BY_PRIORITY_LEVEL[priorityLevel];\n\n const latestBaseFeePerGas = blocks[blocks.length - 1].baseFeePerGas;\n\n const adjustedBaseFee = latestBaseFeePerGas\n .mul(settings.baseFeePercentageMultiplier)\n .divn(100);\n const priorityFees = blocks\n .map((block) => {\n return 'priorityFeesByPercentile' in block\n ? block.priorityFeesByPercentile[settings.percentile]\n : null;\n })\n .filter(BN.isBN);\n const medianPriorityFee = medianOf(priorityFees);\n const adjustedPriorityFee = medianPriorityFee\n .mul(settings.priorityFeePercentageMultiplier)\n .divn(100);\n\n const suggestedMaxPriorityFeePerGas = BN.max(\n adjustedPriorityFee,\n settings.minSuggestedMaxPriorityFeePerGas,\n );\n const suggestedMaxFeePerGas = adjustedBaseFee.add(\n suggestedMaxPriorityFeePerGas,\n );\n\n return {\n ...settings.estimatedWaitTimes,\n suggestedMaxPriorityFeePerGas: fromWei(suggestedMaxPriorityFeePerGas, GWEI),\n suggestedMaxFeePerGas: fromWei(suggestedMaxFeePerGas, GWEI),\n };\n}\n\n/**\n * Calculates a set of estimates suitable for different priority levels based on the data returned\n * by `eth_feeHistory`.\n *\n * @param blocks - A set of blocks populated with data for priority fee percentiles 10, 20, and 30,\n * obtained via {@link BlockFeeHistoryDatasetFetcher}.\n * @returns The estimates.\n */\nexport default function calculateGasFeeEstimatesForPriorityLevels(\n blocks: FeeHistoryBlock[],\n): Pick {\n return PRIORITY_LEVELS.reduce((obj, priorityLevel) => {\n const gasEstimatesForPriorityLevel = calculateEstimatesForPriorityLevel(\n priorityLevel,\n blocks,\n );\n return { ...obj, [priorityLevel]: gasEstimatesForPriorityLevel };\n }, {} as Pick);\n}\n"]} \ No newline at end of file diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.d.ts b/dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.d.ts new file mode 100644 index 0000000000..a1cf6f5773 --- /dev/null +++ b/dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.d.ts @@ -0,0 +1,10 @@ +import { EthBlock, EthQuery } from './types'; +/** + * Returns information about the latest completed block. + * + * @param ethQuery - An EthQuery instance + * @param includeFullTransactionData - Whether or not to include all data for transactions as + * opposed to merely hashes. False by default. + * @returns The block. + */ +export default function fetchLatestBlock(ethQuery: EthQuery, includeFullTransactionData?: boolean): Promise; diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.js b/dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.js new file mode 100644 index 0000000000..99b0fb7475 --- /dev/null +++ b/dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.js @@ -0,0 +1,32 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const util_1 = require("../../util"); +/** + * Returns information about the latest completed block. + * + * @param ethQuery - An EthQuery instance + * @param includeFullTransactionData - Whether or not to include all data for transactions as + * opposed to merely hashes. False by default. + * @returns The block. + */ +function fetchLatestBlock(ethQuery, includeFullTransactionData = false) { + return __awaiter(this, void 0, void 0, function* () { + const blockNumber = yield (0, util_1.query)(ethQuery, 'blockNumber'); + const block = yield (0, util_1.query)(ethQuery, 'getBlockByNumber', [ + blockNumber, + includeFullTransactionData, + ]); + return Object.assign(Object.assign({}, block), { number: (0, util_1.fromHex)(block.number), baseFeePerGas: (0, util_1.fromHex)(block.baseFeePerGas) }); + }); +} +exports.default = fetchLatestBlock; +//# sourceMappingURL=fetchLatestBlock.js.map \ No newline at end of file diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.js.map b/dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.js.map new file mode 100644 index 0000000000..554499bdec --- /dev/null +++ b/dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.js.map @@ -0,0 +1 @@ +{"version":3,"file":"fetchLatestBlock.js","sourceRoot":"","sources":["../../../src/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,qCAA4C;AAG5C;;;;;;;GAOG;AACH,SAA8B,gBAAgB,CAC5C,QAAkB,EAClB,0BAA0B,GAAG,KAAK;;QAElC,MAAM,WAAW,GAAG,MAAM,IAAA,YAAK,EAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;QACzD,MAAM,KAAK,GAAG,MAAM,IAAA,YAAK,EAAC,QAAQ,EAAE,kBAAkB,EAAE;YACtD,WAAW;YACX,0BAA0B;SAC3B,CAAC,CAAC;QACH,uCACK,KAAK,KACR,MAAM,EAAE,IAAA,cAAO,EAAC,KAAK,CAAC,MAAM,CAAC,EAC7B,aAAa,EAAE,IAAA,cAAO,EAAC,KAAK,CAAC,aAAa,CAAC,IAC3C;IACJ,CAAC;CAAA;AAdD,mCAcC","sourcesContent":["import { query, fromHex } from '../../util';\nimport { EthBlock, EthQuery } from './types';\n\n/**\n * Returns information about the latest completed block.\n *\n * @param ethQuery - An EthQuery instance\n * @param includeFullTransactionData - Whether or not to include all data for transactions as\n * opposed to merely hashes. False by default.\n * @returns The block.\n */\nexport default async function fetchLatestBlock(\n ethQuery: EthQuery,\n includeFullTransactionData = false,\n): Promise {\n const blockNumber = await query(ethQuery, 'blockNumber');\n const block = await query(ethQuery, 'getBlockByNumber', [\n blockNumber,\n includeFullTransactionData,\n ]);\n return {\n ...block,\n number: fromHex(block.number),\n baseFeePerGas: fromHex(block.baseFeePerGas),\n };\n}\n"]} \ No newline at end of file diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.d.ts b/dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.d.ts new file mode 100644 index 0000000000..2079cec1e9 --- /dev/null +++ b/dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.d.ts @@ -0,0 +1,10 @@ +/// +import { BN } from 'ethereumjs-util'; +/** + * Finds the median among a list of numbers. Note that this is different from the implementation + * in the MetaSwap API, as we want to hold to using BN as much as possible. + * + * @param numbers - A list of numbers, as BNs. Will be sorted automatically if unsorted. + * @returns The median number. + */ +export default function medianOf(numbers: BN[]): BN; diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.js b/dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.js new file mode 100644 index 0000000000..31a1a71423 --- /dev/null +++ b/dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.js @@ -0,0 +1,17 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * Finds the median among a list of numbers. Note that this is different from the implementation + * in the MetaSwap API, as we want to hold to using BN as much as possible. + * + * @param numbers - A list of numbers, as BNs. Will be sorted automatically if unsorted. + * @returns The median number. + */ +function medianOf(numbers) { + const sortedNumbers = numbers.slice().sort((a, b) => a.cmp(b)); + const len = sortedNumbers.length; + const index = Math.floor((len - 1) / 2); + return sortedNumbers[index]; +} +exports.default = medianOf; +//# sourceMappingURL=medianOf.js.map \ No newline at end of file diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.js.map b/dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.js.map new file mode 100644 index 0000000000..2230e3972c --- /dev/null +++ b/dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.js.map @@ -0,0 +1 @@ +{"version":3,"file":"medianOf.js","sourceRoot":"","sources":["../../../src/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.ts"],"names":[],"mappings":";;AAEA;;;;;;GAMG;AACH,SAAwB,QAAQ,CAAC,OAAa;IAC5C,MAAM,aAAa,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/D,MAAM,GAAG,GAAG,aAAa,CAAC,MAAM,CAAC;IACjC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACxC,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC;AALD,2BAKC","sourcesContent":["import { BN } from 'ethereumjs-util';\n\n/**\n * Finds the median among a list of numbers. Note that this is different from the implementation\n * in the MetaSwap API, as we want to hold to using BN as much as possible.\n *\n * @param numbers - A list of numbers, as BNs. Will be sorted automatically if unsorted.\n * @returns The median number.\n */\nexport default function medianOf(numbers: BN[]): BN {\n const sortedNumbers = numbers.slice().sort((a, b) => a.cmp(b));\n const len = sortedNumbers.length;\n const index = Math.floor((len - 1) / 2);\n return sortedNumbers[index];\n}\n"]} \ No newline at end of file diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/types.d.ts b/dist/gas/fetchGasEstimatesViaEthFeeHistory/types.d.ts new file mode 100644 index 0000000000..ddf9ad3a22 --- /dev/null +++ b/dist/gas/fetchGasEstimatesViaEthFeeHistory/types.d.ts @@ -0,0 +1,10 @@ +/// +import { BN } from 'ethereumjs-util'; +export declare type EthBlock = { + number: BN; + baseFeePerGas: BN; +}; +export declare type EthQuery = { + getBlockByNumber: (blockNumber: BN | 'latest' | 'earliest' | 'pending') => Promise; +}; +export declare type FeeRange = [string, string]; diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/types.js b/dist/gas/fetchGasEstimatesViaEthFeeHistory/types.js new file mode 100644 index 0000000000..11e638d1ee --- /dev/null +++ b/dist/gas/fetchGasEstimatesViaEthFeeHistory/types.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/types.js.map b/dist/gas/fetchGasEstimatesViaEthFeeHistory/types.js.map new file mode 100644 index 0000000000..5081b9250f --- /dev/null +++ b/dist/gas/fetchGasEstimatesViaEthFeeHistory/types.js.map @@ -0,0 +1 @@ +{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/gas/fetchGasEstimatesViaEthFeeHistory/types.ts"],"names":[],"mappings":"","sourcesContent":["import { BN } from 'ethereumjs-util';\n\nexport type EthBlock = {\n number: BN;\n baseFeePerGas: BN;\n};\n\nexport type EthQuery = {\n getBlockByNumber: (\n blockNumber: BN | 'latest' | 'earliest' | 'pending',\n ) => Promise;\n};\n\nexport type FeeRange = [string, string];\n"]} \ No newline at end of file diff --git a/dist/gas/gas-util.d.ts b/dist/gas/gas-util.d.ts new file mode 100644 index 0000000000..05ac93f4f8 --- /dev/null +++ b/dist/gas/gas-util.d.ts @@ -0,0 +1,41 @@ +import { GasFeeEstimates, EthGasPriceEstimate, EstimatedGasFeeTimeBounds, LegacyGasPriceEstimate } from './GasFeeController'; +/** + * Convert a decimal GWEI value to a decimal string rounded to the nearest WEI. + * + * @param n - The input GWEI amount, as a decimal string or a number. + * @returns The decimal string GWEI amount. + */ +export declare function normalizeGWEIDecimalNumbers(n: string | number): any; +/** + * Fetch gas estimates from the given URL. + * + * @param url - The gas estimate URL. + * @param clientId - The client ID used to identify to the API who is asking for estimates. + * @returns The gas estimates. + */ +export declare function fetchGasEstimates(url: string, clientId?: string): Promise; +/** + * Hit the legacy MetaSwaps gasPrices estimate api and return the low, medium + * high values from that API. + * + * @param url - The URL to fetch gas price estimates from. + * @param clientId - The client ID used to identify to the API who is asking for estimates. + * @returns The gas price estimates. + */ +export declare function fetchLegacyGasPriceEstimates(url: string, clientId?: string): Promise; +/** + * Get a gas price estimate from the network using the `eth_gasPrice` method. + * + * @param ethQuery - The EthQuery instance to call the network with. + * @returns A gas price estimate. + */ +export declare function fetchEthGasPriceEstimate(ethQuery: any): Promise; +/** + * Estimate the time it will take for a transaction to be confirmed. + * + * @param maxPriorityFeePerGas - The max priority fee per gas. + * @param maxFeePerGas - The max fee per gas. + * @param gasFeeEstimates - The gas fee estimates. + * @returns The estimated lower and upper bounds for when this transaction will be confirmed. + */ +export declare function calculateTimeEstimate(maxPriorityFeePerGas: string, maxFeePerGas: string, gasFeeEstimates: GasFeeEstimates): EstimatedGasFeeTimeBounds; diff --git a/dist/gas/gas-util.js b/dist/gas/gas-util.js new file mode 100644 index 0000000000..143a3e9ad3 --- /dev/null +++ b/dist/gas/gas-util.js @@ -0,0 +1,140 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.calculateTimeEstimate = exports.fetchEthGasPriceEstimate = exports.fetchLegacyGasPriceEstimates = exports.fetchGasEstimates = exports.normalizeGWEIDecimalNumbers = void 0; +const ethereumjs_util_1 = require("ethereumjs-util"); +const util_1 = require("../util"); +const makeClientIdHeader = (clientId) => ({ 'X-Client-Id': clientId }); +/** + * Convert a decimal GWEI value to a decimal string rounded to the nearest WEI. + * + * @param n - The input GWEI amount, as a decimal string or a number. + * @returns The decimal string GWEI amount. + */ +function normalizeGWEIDecimalNumbers(n) { + const numberAsWEIHex = (0, util_1.gweiDecToWEIBN)(n).toString(16); + const numberAsGWEI = (0, util_1.weiHexToGweiDec)(numberAsWEIHex).toString(10); + return numberAsGWEI; +} +exports.normalizeGWEIDecimalNumbers = normalizeGWEIDecimalNumbers; +/** + * Fetch gas estimates from the given URL. + * + * @param url - The gas estimate URL. + * @param clientId - The client ID used to identify to the API who is asking for estimates. + * @returns The gas estimates. + */ +function fetchGasEstimates(url, clientId) { + return __awaiter(this, void 0, void 0, function* () { + const estimates = yield (0, util_1.handleFetch)(url, clientId ? { headers: makeClientIdHeader(clientId) } : undefined); + return { + low: Object.assign(Object.assign({}, estimates.low), { suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(estimates.low.suggestedMaxPriorityFeePerGas), suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(estimates.low.suggestedMaxFeePerGas) }), + medium: Object.assign(Object.assign({}, estimates.medium), { suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(estimates.medium.suggestedMaxPriorityFeePerGas), suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(estimates.medium.suggestedMaxFeePerGas) }), + high: Object.assign(Object.assign({}, estimates.high), { suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(estimates.high.suggestedMaxPriorityFeePerGas), suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(estimates.high.suggestedMaxFeePerGas) }), + estimatedBaseFee: normalizeGWEIDecimalNumbers(estimates.estimatedBaseFee), + historicalBaseFeeRange: estimates.historicalBaseFeeRange, + baseFeeTrend: estimates.baseFeeTrend, + latestPriorityFeeRange: estimates.latestPriorityFeeRange, + historicalPriorityFeeRange: estimates.historicalPriorityFeeRange, + priorityFeeTrend: estimates.priorityFeeTrend, + networkCongestion: estimates.networkCongestion, + }; + }); +} +exports.fetchGasEstimates = fetchGasEstimates; +/** + * Hit the legacy MetaSwaps gasPrices estimate api and return the low, medium + * high values from that API. + * + * @param url - The URL to fetch gas price estimates from. + * @param clientId - The client ID used to identify to the API who is asking for estimates. + * @returns The gas price estimates. + */ +function fetchLegacyGasPriceEstimates(url, clientId) { + return __awaiter(this, void 0, void 0, function* () { + const result = yield (0, util_1.handleFetch)(url, { + referrer: url, + referrerPolicy: 'no-referrer-when-downgrade', + method: 'GET', + mode: 'cors', + headers: Object.assign({ 'Content-Type': 'application/json' }, (clientId && makeClientIdHeader(clientId))), + }); + return { + low: result.SafeGasPrice, + medium: result.ProposeGasPrice, + high: result.FastGasPrice, + }; + }); +} +exports.fetchLegacyGasPriceEstimates = fetchLegacyGasPriceEstimates; +/** + * Get a gas price estimate from the network using the `eth_gasPrice` method. + * + * @param ethQuery - The EthQuery instance to call the network with. + * @returns A gas price estimate. + */ +function fetchEthGasPriceEstimate(ethQuery) { + return __awaiter(this, void 0, void 0, function* () { + const gasPrice = yield (0, util_1.query)(ethQuery, 'gasPrice'); + return { + gasPrice: (0, util_1.weiHexToGweiDec)(gasPrice).toString(), + }; + }); +} +exports.fetchEthGasPriceEstimate = fetchEthGasPriceEstimate; +/** + * Estimate the time it will take for a transaction to be confirmed. + * + * @param maxPriorityFeePerGas - The max priority fee per gas. + * @param maxFeePerGas - The max fee per gas. + * @param gasFeeEstimates - The gas fee estimates. + * @returns The estimated lower and upper bounds for when this transaction will be confirmed. + */ +function calculateTimeEstimate(maxPriorityFeePerGas, maxFeePerGas, gasFeeEstimates) { + const { low, medium, high, estimatedBaseFee } = gasFeeEstimates; + const maxPriorityFeePerGasInWEI = (0, util_1.gweiDecToWEIBN)(maxPriorityFeePerGas); + const maxFeePerGasInWEI = (0, util_1.gweiDecToWEIBN)(maxFeePerGas); + const estimatedBaseFeeInWEI = (0, util_1.gweiDecToWEIBN)(estimatedBaseFee); + const effectiveMaxPriorityFee = ethereumjs_util_1.BN.min(maxPriorityFeePerGasInWEI, maxFeePerGasInWEI.sub(estimatedBaseFeeInWEI)); + const lowMaxPriorityFeeInWEI = (0, util_1.gweiDecToWEIBN)(low.suggestedMaxPriorityFeePerGas); + const mediumMaxPriorityFeeInWEI = (0, util_1.gweiDecToWEIBN)(medium.suggestedMaxPriorityFeePerGas); + const highMaxPriorityFeeInWEI = (0, util_1.gweiDecToWEIBN)(high.suggestedMaxPriorityFeePerGas); + let lowerTimeBound; + let upperTimeBound; + if (effectiveMaxPriorityFee.lt(lowMaxPriorityFeeInWEI)) { + lowerTimeBound = null; + upperTimeBound = 'unknown'; + } + else if (effectiveMaxPriorityFee.gte(lowMaxPriorityFeeInWEI) && + effectiveMaxPriorityFee.lt(mediumMaxPriorityFeeInWEI)) { + lowerTimeBound = low.minWaitTimeEstimate; + upperTimeBound = low.maxWaitTimeEstimate; + } + else if (effectiveMaxPriorityFee.gte(mediumMaxPriorityFeeInWEI) && + effectiveMaxPriorityFee.lt(highMaxPriorityFeeInWEI)) { + lowerTimeBound = medium.minWaitTimeEstimate; + upperTimeBound = medium.maxWaitTimeEstimate; + } + else if (effectiveMaxPriorityFee.eq(highMaxPriorityFeeInWEI)) { + lowerTimeBound = high.minWaitTimeEstimate; + upperTimeBound = high.maxWaitTimeEstimate; + } + else { + lowerTimeBound = 0; + upperTimeBound = high.maxWaitTimeEstimate; + } + return { + lowerTimeBound, + upperTimeBound, + }; +} +exports.calculateTimeEstimate = calculateTimeEstimate; +//# sourceMappingURL=gas-util.js.map \ No newline at end of file diff --git a/dist/gas/gas-util.js.map b/dist/gas/gas-util.js.map new file mode 100644 index 0000000000..582f092840 --- /dev/null +++ b/dist/gas/gas-util.js.map @@ -0,0 +1 @@ +{"version":3,"file":"gas-util.js","sourceRoot":"","sources":["../../src/gas/gas-util.ts"],"names":[],"mappings":"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","sourcesContent":["import { BN } from 'ethereumjs-util';\nimport { query, handleFetch, gweiDecToWEIBN, weiHexToGweiDec } from '../util';\nimport {\n GasFeeEstimates,\n EthGasPriceEstimate,\n EstimatedGasFeeTimeBounds,\n unknownString,\n LegacyGasPriceEstimate,\n} from './GasFeeController';\n\nconst makeClientIdHeader = (clientId: string) => ({ 'X-Client-Id': clientId });\n\n/**\n * Convert a decimal GWEI value to a decimal string rounded to the nearest WEI.\n *\n * @param n - The input GWEI amount, as a decimal string or a number.\n * @returns The decimal string GWEI amount.\n */\nexport function normalizeGWEIDecimalNumbers(n: string | number) {\n const numberAsWEIHex = gweiDecToWEIBN(n).toString(16);\n const numberAsGWEI = weiHexToGweiDec(numberAsWEIHex).toString(10);\n return numberAsGWEI;\n}\n\n/**\n * Fetch gas estimates from the given URL.\n *\n * @param url - The gas estimate URL.\n * @param clientId - The client ID used to identify to the API who is asking for estimates.\n * @returns The gas estimates.\n */\nexport async function fetchGasEstimates(\n url: string,\n clientId?: string,\n): Promise {\n const estimates = await handleFetch(\n url,\n clientId ? { headers: makeClientIdHeader(clientId) } : undefined,\n );\n return {\n low: {\n ...estimates.low,\n suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.low.suggestedMaxPriorityFeePerGas,\n ),\n suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.low.suggestedMaxFeePerGas,\n ),\n },\n medium: {\n ...estimates.medium,\n suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.medium.suggestedMaxPriorityFeePerGas,\n ),\n suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.medium.suggestedMaxFeePerGas,\n ),\n },\n high: {\n ...estimates.high,\n suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.high.suggestedMaxPriorityFeePerGas,\n ),\n suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.high.suggestedMaxFeePerGas,\n ),\n },\n estimatedBaseFee: normalizeGWEIDecimalNumbers(estimates.estimatedBaseFee),\n historicalBaseFeeRange: estimates.historicalBaseFeeRange,\n baseFeeTrend: estimates.baseFeeTrend,\n latestPriorityFeeRange: estimates.latestPriorityFeeRange,\n historicalPriorityFeeRange: estimates.historicalPriorityFeeRange,\n priorityFeeTrend: estimates.priorityFeeTrend,\n networkCongestion: estimates.networkCongestion,\n };\n}\n\n/**\n * Hit the legacy MetaSwaps gasPrices estimate api and return the low, medium\n * high values from that API.\n *\n * @param url - The URL to fetch gas price estimates from.\n * @param clientId - The client ID used to identify to the API who is asking for estimates.\n * @returns The gas price estimates.\n */\nexport async function fetchLegacyGasPriceEstimates(\n url: string,\n clientId?: string,\n): Promise {\n const result = await handleFetch(url, {\n referrer: url,\n referrerPolicy: 'no-referrer-when-downgrade',\n method: 'GET',\n mode: 'cors',\n headers: {\n 'Content-Type': 'application/json',\n ...(clientId && makeClientIdHeader(clientId)),\n },\n });\n return {\n low: result.SafeGasPrice,\n medium: result.ProposeGasPrice,\n high: result.FastGasPrice,\n };\n}\n\n/**\n * Get a gas price estimate from the network using the `eth_gasPrice` method.\n *\n * @param ethQuery - The EthQuery instance to call the network with.\n * @returns A gas price estimate.\n */\nexport async function fetchEthGasPriceEstimate(\n ethQuery: any,\n): Promise {\n const gasPrice = await query(ethQuery, 'gasPrice');\n return {\n gasPrice: weiHexToGweiDec(gasPrice).toString(),\n };\n}\n\n/**\n * Estimate the time it will take for a transaction to be confirmed.\n *\n * @param maxPriorityFeePerGas - The max priority fee per gas.\n * @param maxFeePerGas - The max fee per gas.\n * @param gasFeeEstimates - The gas fee estimates.\n * @returns The estimated lower and upper bounds for when this transaction will be confirmed.\n */\nexport function calculateTimeEstimate(\n maxPriorityFeePerGas: string,\n maxFeePerGas: string,\n gasFeeEstimates: GasFeeEstimates,\n): EstimatedGasFeeTimeBounds {\n const { low, medium, high, estimatedBaseFee } = gasFeeEstimates;\n\n const maxPriorityFeePerGasInWEI = gweiDecToWEIBN(maxPriorityFeePerGas);\n const maxFeePerGasInWEI = gweiDecToWEIBN(maxFeePerGas);\n const estimatedBaseFeeInWEI = gweiDecToWEIBN(estimatedBaseFee);\n\n const effectiveMaxPriorityFee = BN.min(\n maxPriorityFeePerGasInWEI,\n maxFeePerGasInWEI.sub(estimatedBaseFeeInWEI),\n );\n\n const lowMaxPriorityFeeInWEI = gweiDecToWEIBN(\n low.suggestedMaxPriorityFeePerGas,\n );\n const mediumMaxPriorityFeeInWEI = gweiDecToWEIBN(\n medium.suggestedMaxPriorityFeePerGas,\n );\n const highMaxPriorityFeeInWEI = gweiDecToWEIBN(\n high.suggestedMaxPriorityFeePerGas,\n );\n\n let lowerTimeBound;\n let upperTimeBound;\n\n if (effectiveMaxPriorityFee.lt(lowMaxPriorityFeeInWEI)) {\n lowerTimeBound = null;\n upperTimeBound = 'unknown' as unknownString;\n } else if (\n effectiveMaxPriorityFee.gte(lowMaxPriorityFeeInWEI) &&\n effectiveMaxPriorityFee.lt(mediumMaxPriorityFeeInWEI)\n ) {\n lowerTimeBound = low.minWaitTimeEstimate;\n upperTimeBound = low.maxWaitTimeEstimate;\n } else if (\n effectiveMaxPriorityFee.gte(mediumMaxPriorityFeeInWEI) &&\n effectiveMaxPriorityFee.lt(highMaxPriorityFeeInWEI)\n ) {\n lowerTimeBound = medium.minWaitTimeEstimate;\n upperTimeBound = medium.maxWaitTimeEstimate;\n } else if (effectiveMaxPriorityFee.eq(highMaxPriorityFeeInWEI)) {\n lowerTimeBound = high.minWaitTimeEstimate;\n upperTimeBound = high.maxWaitTimeEstimate;\n } else {\n lowerTimeBound = 0;\n upperTimeBound = high.maxWaitTimeEstimate;\n }\n\n return {\n lowerTimeBound,\n upperTimeBound,\n };\n}\n"]} \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000000..7ac466bc27 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,33 @@ +import 'isomorphic-fetch'; +import * as util from './util'; +export * from './assets/AccountTrackerController'; +export * from './user/AddressBookController'; +export * from './approval/ApprovalController'; +export * from './assets/AssetsContractController'; +export * from './BaseController'; +export { BaseController as BaseControllerV2, getPersistentState, getAnonymizedState, Json, StateDeriver, StateMetadata, StatePropertyMetadata, } from './BaseControllerV2'; +export * from './ComposableController'; +export * from './ControllerMessenger'; +export * from './assets/CurrencyRateController'; +export * from './keyring/KeyringController'; +export * from './message-manager/MessageManager'; +export * from './network/NetworkController'; +export * from './third-party/PhishingController'; +export * from './user/PreferencesController'; +export * from './assets/TokenBalancesController'; +export * from './assets/TokenRatesController'; +export * from './transaction/TransactionController'; +export * from './message-manager/PersonalMessageManager'; +export * from './message-manager/TypedMessageManager'; +export * from './announcement/AnnouncementController'; +export * from './assets/TokenListController'; +export * from './gas/GasFeeController'; +export * from './assets/TokensController'; +export * from './assets/CollectiblesController'; +export * from './assets/TokenDetectionController'; +export * from './assets/CollectibleDetectionController'; +export * from './permissions'; +export * from './subject-metadata'; +export * from './ratelimit/RateLimitController'; +export * from './notification/NotificationController'; +export { util }; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000000..04e1d0cd4b --- /dev/null +++ b/dist/index.js @@ -0,0 +1,66 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.util = exports.getAnonymizedState = exports.getPersistentState = exports.BaseControllerV2 = void 0; +require("isomorphic-fetch"); +const util = __importStar(require("./util")); +exports.util = util; +__exportStar(require("./assets/AccountTrackerController"), exports); +__exportStar(require("./user/AddressBookController"), exports); +__exportStar(require("./approval/ApprovalController"), exports); +__exportStar(require("./assets/AssetsContractController"), exports); +__exportStar(require("./BaseController"), exports); +var BaseControllerV2_1 = require("./BaseControllerV2"); +Object.defineProperty(exports, "BaseControllerV2", { enumerable: true, get: function () { return BaseControllerV2_1.BaseController; } }); +Object.defineProperty(exports, "getPersistentState", { enumerable: true, get: function () { return BaseControllerV2_1.getPersistentState; } }); +Object.defineProperty(exports, "getAnonymizedState", { enumerable: true, get: function () { return BaseControllerV2_1.getAnonymizedState; } }); +__exportStar(require("./ComposableController"), exports); +__exportStar(require("./ControllerMessenger"), exports); +__exportStar(require("./assets/CurrencyRateController"), exports); +__exportStar(require("./keyring/KeyringController"), exports); +__exportStar(require("./message-manager/MessageManager"), exports); +__exportStar(require("./network/NetworkController"), exports); +__exportStar(require("./third-party/PhishingController"), exports); +__exportStar(require("./user/PreferencesController"), exports); +__exportStar(require("./assets/TokenBalancesController"), exports); +__exportStar(require("./assets/TokenRatesController"), exports); +__exportStar(require("./transaction/TransactionController"), exports); +__exportStar(require("./message-manager/PersonalMessageManager"), exports); +__exportStar(require("./message-manager/TypedMessageManager"), exports); +__exportStar(require("./announcement/AnnouncementController"), exports); +__exportStar(require("./assets/TokenListController"), exports); +__exportStar(require("./gas/GasFeeController"), exports); +__exportStar(require("./assets/TokensController"), exports); +__exportStar(require("./assets/CollectiblesController"), exports); +__exportStar(require("./assets/TokenDetectionController"), exports); +__exportStar(require("./assets/CollectibleDetectionController"), exports); +__exportStar(require("./permissions"), exports); +__exportStar(require("./subject-metadata"), exports); +__exportStar(require("./ratelimit/RateLimitController"), exports); +__exportStar(require("./notification/NotificationController"), exports); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map new file mode 100644 index 0000000000..06147eff6f --- /dev/null +++ b/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,4BAA0B;AAC1B,6CAA+B;AAwCtB,oBAAI;AAtCb,oEAAkD;AAClD,+DAA6C;AAC7C,gEAA8C;AAC9C,oEAAkD;AAClD,mDAAiC;AACjC,uDAQ4B;AAP1B,oHAAA,cAAc,OAAoB;AAClC,sHAAA,kBAAkB,OAAA;AAClB,sHAAA,kBAAkB,OAAA;AAMpB,yDAAuC;AACvC,wDAAsC;AACtC,kEAAgD;AAChD,8DAA4C;AAC5C,mEAAiD;AACjD,8DAA4C;AAC5C,mEAAiD;AACjD,+DAA6C;AAC7C,mEAAiD;AACjD,gEAA8C;AAC9C,sEAAoD;AACpD,2EAAyD;AACzD,wEAAsD;AACtD,wEAAsD;AACtD,+DAA6C;AAC7C,yDAAuC;AACvC,4DAA0C;AAC1C,kEAAgD;AAChD,oEAAkD;AAClD,0EAAwD;AACxD,gDAA8B;AAC9B,qDAAmC;AACnC,kEAAgD;AAChD,wEAAsD","sourcesContent":["import 'isomorphic-fetch';\nimport * as util from './util';\n\nexport * from './assets/AccountTrackerController';\nexport * from './user/AddressBookController';\nexport * from './approval/ApprovalController';\nexport * from './assets/AssetsContractController';\nexport * from './BaseController';\nexport {\n BaseController as BaseControllerV2,\n getPersistentState,\n getAnonymizedState,\n Json,\n StateDeriver,\n StateMetadata,\n StatePropertyMetadata,\n} from './BaseControllerV2';\nexport * from './ComposableController';\nexport * from './ControllerMessenger';\nexport * from './assets/CurrencyRateController';\nexport * from './keyring/KeyringController';\nexport * from './message-manager/MessageManager';\nexport * from './network/NetworkController';\nexport * from './third-party/PhishingController';\nexport * from './user/PreferencesController';\nexport * from './assets/TokenBalancesController';\nexport * from './assets/TokenRatesController';\nexport * from './transaction/TransactionController';\nexport * from './message-manager/PersonalMessageManager';\nexport * from './message-manager/TypedMessageManager';\nexport * from './announcement/AnnouncementController';\nexport * from './assets/TokenListController';\nexport * from './gas/GasFeeController';\nexport * from './assets/TokensController';\nexport * from './assets/CollectiblesController';\nexport * from './assets/TokenDetectionController';\nexport * from './assets/CollectibleDetectionController';\nexport * from './permissions';\nexport * from './subject-metadata';\nexport * from './ratelimit/RateLimitController';\nexport * from './notification/NotificationController';\nexport { util };\n"]} \ No newline at end of file diff --git a/dist/keyring/KeyringController.d.ts b/dist/keyring/KeyringController.d.ts new file mode 100644 index 0000000000..23ee5e3da0 --- /dev/null +++ b/dist/keyring/KeyringController.d.ts @@ -0,0 +1,308 @@ +import { MetaMaskKeyring as QRKeyring, IKeyringState as IQRKeyringState } from '@keystonehq/metamask-airgapped-keyring'; +import { BaseController, BaseConfig, BaseState, Listener } from '../BaseController'; +import { PreferencesController } from '../user/PreferencesController'; +import { PersonalMessageParams } from '../message-manager/PersonalMessageManager'; +import { TypedMessageParams } from '../message-manager/TypedMessageManager'; +/** + * Available keyring types + */ +export declare enum KeyringTypes { + simple = "Simple Key Pair", + hd = "HD Key Tree", + qr = "QR Hardware Wallet Device" +} +/** + * @type KeyringObject + * + * Keyring object + * @property type - Keyring type + * @property accounts - Associated accounts + * @function getAccounts - Get associated accounts + */ +export interface KeyringObject { + type: string; + accounts: string[]; + getAccounts(): string[]; +} +/** + * @type KeyringState + * + * Keyring controller state + * @property vault - Encrypted string representing keyring data + * @property keyrings - Group of accounts + */ +export interface KeyringState extends BaseState { + vault?: string; + keyrings: Keyring[]; +} +/** + * @type KeyringMemState + * + * Keyring mem controller state + * @property isUnlocked - Whether vault is unlocked + * @property keyringTypes - Account types + * @property keyrings - Group of accounts + */ +export interface KeyringMemState extends BaseState { + isUnlocked: boolean; + keyringTypes: string[]; + keyrings: Keyring[]; +} +/** + * @type KeyringConfig + * + * Keyring controller configuration + * @property encryptor - Keyring encryptor + */ +export interface KeyringConfig extends BaseConfig { + encryptor?: any; + keyringTypes?: any[]; +} +/** + * @type Keyring + * + * Keyring object to return in fullUpdate + * @property type - Keyring type + * @property accounts - Associated accounts + * @property index - Associated index + */ +export interface Keyring { + accounts: string[]; + type: string; + index?: number; +} +/** + * A strategy for importing an account + */ +export declare enum AccountImportStrategy { + privateKey = "privateKey", + json = "json" +} +/** + * The `signTypedMessage` version + * + * @see https://docs.metamask.io/guide/signing-data.html + */ +export declare enum SignTypedDataVersion { + V1 = "V1", + V3 = "V3", + V4 = "V4" +} +/** + * Controller responsible for establishing and managing user identity + */ +export declare class KeyringController extends BaseController { + #private; + private mutex; + /** + * Name of this controller used during composition + */ + name: string; + private removeIdentity; + private syncIdentities; + private updateIdentities; + private setSelectedAddress; + private setAccountLabel?; + /** + * Creates a KeyringController instance. + * + * @param options - The controller options. + * @param options.removeIdentity - Remove the identity with the given address. + * @param options.syncIdentities - Sync identities with the given list of addresses. + * @param options.updateIdentities - Generate an identity for each address given that doesn't already have an identity. + * @param options.setSelectedAddress - Set the selected address. + * @param options.setAccountLabel - Set a new name for account. + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor({ removeIdentity, syncIdentities, updateIdentities, setSelectedAddress, setAccountLabel, }: { + removeIdentity: PreferencesController['removeIdentity']; + syncIdentities: PreferencesController['syncIdentities']; + updateIdentities: PreferencesController['updateIdentities']; + setSelectedAddress: PreferencesController['setSelectedAddress']; + setAccountLabel?: PreferencesController['setAccountLabel']; + }, config?: Partial, state?: Partial); + /** + * Adds a new account to the default (first) HD seed phrase keyring. + * + * @returns Promise resolving to current state when the account is added. + */ + addNewAccount(): Promise; + /** + * Adds a new account to the default (first) HD seed phrase keyring without updating identities in preferences. + * + * @returns Promise resolving to current state when the account is added. + */ + addNewAccountWithoutUpdate(): Promise; + /** + * Effectively the same as creating a new keychain then populating it + * using the given seed phrase. + * + * @param password - Password to unlock keychain. + * @param seed - A BIP39-compliant seed phrase, + * either as a string or an array of UTF-8 bytes that represent the string. + * @returns Promise resolving to the restored keychain object. + */ + createNewVaultAndRestore(password: string, seed: string | number[]): Promise; + /** + * Create a new primary keychain and wipe any previous keychains. + * + * @param password - Password to unlock the new vault. + * @returns Newly-created keychain object. + */ + createNewVaultAndKeychain(password: string): Promise; + /** + * Returns the status of the vault. + * + * @returns Boolean returning true if the vault is unlocked. + */ + isUnlocked(): boolean; + /** + * Gets the seed phrase of the HD keyring. + * + * @param password - Password of the keyring. + * @returns Promise resolving to the seed phrase. + */ + exportSeedPhrase(password: string): any; + /** + * Gets the private key from the keyring controlling an address. + * + * @param password - Password of the keyring. + * @param address - Address to export. + * @returns Promise resolving to the private key for an address. + */ + exportAccount(password: string, address: string): Promise; + /** + * Returns the public addresses of all accounts for the current keyring. + * + * @returns A promise resolving to an array of addresses. + */ + getAccounts(): Promise; + /** + * Imports an account with the specified import strategy. + * + * @param strategy - Import strategy name. + * @param args - Array of arguments to pass to the underlying stategy. + * @throws Will throw when passed an unrecognized strategy. + * @returns Promise resolving to current state when the import is complete. + */ + importAccountWithStrategy(strategy: AccountImportStrategy, args: any[]): Promise; + /** + * Removes an account from keyring state. + * + * @param address - Address of the account to remove. + * @returns Promise resolving current state when this account removal completes. + */ + removeAccount(address: string): Promise; + /** + * Deallocates all secrets and locks the wallet. + * + * @returns Promise resolving to current state. + */ + setLocked(): Promise; + /** + * Signs message by calling down into a specific keyring. + * + * @param messageParams - PersonalMessageParams object to sign. + * @returns Promise resolving to a signed message string. + */ + signMessage(messageParams: PersonalMessageParams): any; + /** + * Signs personal message by calling down into a specific keyring. + * + * @param messageParams - PersonalMessageParams object to sign. + * @returns Promise resolving to a signed message string. + */ + signPersonalMessage(messageParams: PersonalMessageParams): any; + /** + * Signs typed message by calling down into a specific keyring. + * + * @param messageParams - TypedMessageParams object to sign. + * @param version - Compatibility version EIP712. + * @throws Will throw when passed an unrecognized version. + * @returns Promise resolving to a signed message string or an error if any. + */ + signTypedMessage(messageParams: TypedMessageParams, version: SignTypedDataVersion): Promise; + /** + * Signs a transaction by calling down into a specific keyring. + * + * @param transaction - Transaction object to sign. Must be a `ethereumjs-tx` transaction instance. + * @param from - Address to sign from, should be in keychain. + * @returns Promise resolving to a signed transaction string. + */ + signTransaction(transaction: unknown, from: string): any; + /** + * Attempts to decrypt the current vault and load its keyrings. + * + * @param password - Password to unlock the keychain. + * @returns Promise resolving to the current state. + */ + submitPassword(password: string): Promise; + /** + * Adds new listener to be notified of state changes. + * + * @param listener - Callback triggered when state changes. + */ + subscribe(listener: Listener): void; + /** + * Removes existing listener from receiving state changes. + * + * @param listener - Callback to remove. + * @returns True if a listener is found and unsubscribed. + */ + unsubscribe(listener: Listener): any; + /** + * Adds new listener to be notified when the wallet is locked. + * + * @param listener - Callback triggered when wallet is locked. + * @returns EventEmitter if listener added. + */ + onLock(listener: () => void): any; + /** + * Adds new listener to be notified when the wallet is unlocked. + * + * @param listener - Callback triggered when wallet is unlocked. + * @returns EventEmitter if listener added. + */ + onUnlock(listener: () => void): any; + /** + * Verifies the that the seed phrase restores the current keychain's accounts. + * + * @returns Whether the verification succeeds. + */ + verifySeedPhrase(): Promise; + /** + * Update keyrings in state and calls KeyringController fullUpdate method returning current state. + * + * @returns The current state. + */ + fullUpdate(): Promise; + /** + * Add qr hardware keyring. + * + * @returns The added keyring + */ + private addQRKeyring; + /** + * Get qr hardware keyring. + * + * @returns The added keyring + */ + getOrAddQRKeyring(): Promise; + restoreQRKeyring(serialized: any): Promise; + resetQRKeyringState(): Promise; + getQRKeyringState(): Promise; + submitQRCryptoHDKey(cryptoHDKey: string): Promise; + submitQRCryptoAccount(cryptoAccount: string): Promise; + submitQRSignature(requestId: string, ethSignature: string): Promise; + cancelQRSignRequest(): Promise; + connectQRHardware(page: number): Promise<{ + balance: string; + address: string; + index: number; + }[]>; + unlockQRHardwareWalletAccount(index: number): Promise; + getAccountKeyringType(account: string): Promise; + forgetQRDevice(): Promise; +} +export default KeyringController; diff --git a/dist/keyring/KeyringController.js b/dist/keyring/KeyringController.js new file mode 100644 index 0000000000..8d63c54469 --- /dev/null +++ b/dist/keyring/KeyringController.js @@ -0,0 +1,636 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { + if (kind === "m") throw new TypeError("Private method is not writable"); + if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); + if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); + return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; +}; +var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { + if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); + if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); + return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +var _KeyringController_keyring; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.KeyringController = exports.SignTypedDataVersion = exports.AccountImportStrategy = exports.KeyringTypes = void 0; +const ethereumjs_util_1 = require("ethereumjs-util"); +const eth_sig_util_1 = require("eth-sig-util"); +const ethereumjs_wallet_1 = __importStar(require("ethereumjs-wallet")); +const eth_keyring_controller_1 = __importDefault(require("eth-keyring-controller")); +const async_mutex_1 = require("async-mutex"); +const BaseController_1 = require("../BaseController"); +const util_1 = require("../util"); +/** + * Available keyring types + */ +var KeyringTypes; +(function (KeyringTypes) { + KeyringTypes["simple"] = "Simple Key Pair"; + KeyringTypes["hd"] = "HD Key Tree"; + KeyringTypes["qr"] = "QR Hardware Wallet Device"; +})(KeyringTypes = exports.KeyringTypes || (exports.KeyringTypes = {})); +/** + * A strategy for importing an account + */ +var AccountImportStrategy; +(function (AccountImportStrategy) { + AccountImportStrategy["privateKey"] = "privateKey"; + AccountImportStrategy["json"] = "json"; +})(AccountImportStrategy = exports.AccountImportStrategy || (exports.AccountImportStrategy = {})); +/** + * The `signTypedMessage` version + * + * @see https://docs.metamask.io/guide/signing-data.html + */ +var SignTypedDataVersion; +(function (SignTypedDataVersion) { + SignTypedDataVersion["V1"] = "V1"; + SignTypedDataVersion["V3"] = "V3"; + SignTypedDataVersion["V4"] = "V4"; +})(SignTypedDataVersion = exports.SignTypedDataVersion || (exports.SignTypedDataVersion = {})); +/** + * Controller responsible for establishing and managing user identity + */ +class KeyringController extends BaseController_1.BaseController { + /** + * Creates a KeyringController instance. + * + * @param options - The controller options. + * @param options.removeIdentity - Remove the identity with the given address. + * @param options.syncIdentities - Sync identities with the given list of addresses. + * @param options.updateIdentities - Generate an identity for each address given that doesn't already have an identity. + * @param options.setSelectedAddress - Set the selected address. + * @param options.setAccountLabel - Set a new name for account. + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor({ removeIdentity, syncIdentities, updateIdentities, setSelectedAddress, setAccountLabel, }, config, state) { + super(config, state); + this.mutex = new async_mutex_1.Mutex(); + /** + * Name of this controller used during composition + */ + this.name = 'KeyringController'; + _KeyringController_keyring.set(this, void 0); + __classPrivateFieldSet(this, _KeyringController_keyring, new eth_keyring_controller_1.default(Object.assign({ initState: state }, config)), "f"); + this.defaultState = Object.assign(Object.assign({}, __classPrivateFieldGet(this, _KeyringController_keyring, "f").store.getState()), { keyrings: [] }); + this.removeIdentity = removeIdentity; + this.syncIdentities = syncIdentities; + this.updateIdentities = updateIdentities; + this.setSelectedAddress = setSelectedAddress; + this.setAccountLabel = setAccountLabel; + this.initialize(); + this.fullUpdate(); + } + /** + * Adds a new account to the default (first) HD seed phrase keyring. + * + * @returns Promise resolving to current state when the account is added. + */ + addNewAccount() { + return __awaiter(this, void 0, void 0, function* () { + const primaryKeyring = __classPrivateFieldGet(this, _KeyringController_keyring, "f").getKeyringsByType('HD Key Tree')[0]; + /* istanbul ignore if */ + if (!primaryKeyring) { + throw new Error('No HD keyring found'); + } + const oldAccounts = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts(); + yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").addNewAccount(primaryKeyring); + const newAccounts = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts(); + yield this.verifySeedPhrase(); + this.updateIdentities(newAccounts); + newAccounts.forEach((selectedAddress) => { + if (!oldAccounts.includes(selectedAddress)) { + this.setSelectedAddress(selectedAddress); + } + }); + return this.fullUpdate(); + }); + } + /** + * Adds a new account to the default (first) HD seed phrase keyring without updating identities in preferences. + * + * @returns Promise resolving to current state when the account is added. + */ + addNewAccountWithoutUpdate() { + return __awaiter(this, void 0, void 0, function* () { + const primaryKeyring = __classPrivateFieldGet(this, _KeyringController_keyring, "f").getKeyringsByType('HD Key Tree')[0]; + /* istanbul ignore if */ + if (!primaryKeyring) { + throw new Error('No HD keyring found'); + } + yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").addNewAccount(primaryKeyring); + yield this.verifySeedPhrase(); + return this.fullUpdate(); + }); + } + /** + * Effectively the same as creating a new keychain then populating it + * using the given seed phrase. + * + * @param password - Password to unlock keychain. + * @param seed - A BIP39-compliant seed phrase, + * either as a string or an array of UTF-8 bytes that represent the string. + * @returns Promise resolving to the restored keychain object. + */ + createNewVaultAndRestore(password, seed) { + return __awaiter(this, void 0, void 0, function* () { + const releaseLock = yield this.mutex.acquire(); + if (!password || !password.length) { + throw new Error('Invalid password'); + } + try { + this.updateIdentities([]); + const vault = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").createNewVaultAndRestore(password, seed); + this.updateIdentities(yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts()); + this.fullUpdate(); + return vault; + } + finally { + releaseLock(); + } + }); + } + /** + * Create a new primary keychain and wipe any previous keychains. + * + * @param password - Password to unlock the new vault. + * @returns Newly-created keychain object. + */ + createNewVaultAndKeychain(password) { + return __awaiter(this, void 0, void 0, function* () { + const releaseLock = yield this.mutex.acquire(); + try { + const vault = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").createNewVaultAndKeychain(password); + this.updateIdentities(yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts()); + this.fullUpdate(); + return vault; + } + finally { + releaseLock(); + } + }); + } + /** + * Returns the status of the vault. + * + * @returns Boolean returning true if the vault is unlocked. + */ + isUnlocked() { + return __classPrivateFieldGet(this, _KeyringController_keyring, "f").memStore.getState().isUnlocked; + } + /** + * Gets the seed phrase of the HD keyring. + * + * @param password - Password of the keyring. + * @returns Promise resolving to the seed phrase. + */ + exportSeedPhrase(password) { + if (__classPrivateFieldGet(this, _KeyringController_keyring, "f").password === password) { + return __classPrivateFieldGet(this, _KeyringController_keyring, "f").keyrings[0].mnemonic; + } + throw new Error('Invalid password'); + } + /** + * Gets the private key from the keyring controlling an address. + * + * @param password - Password of the keyring. + * @param address - Address to export. + * @returns Promise resolving to the private key for an address. + */ + exportAccount(password, address) { + if (__classPrivateFieldGet(this, _KeyringController_keyring, "f").password === password) { + return __classPrivateFieldGet(this, _KeyringController_keyring, "f").exportAccount(address); + } + throw new Error('Invalid password'); + } + /** + * Returns the public addresses of all accounts for the current keyring. + * + * @returns A promise resolving to an array of addresses. + */ + getAccounts() { + return __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts(); + } + /** + * Imports an account with the specified import strategy. + * + * @param strategy - Import strategy name. + * @param args - Array of arguments to pass to the underlying stategy. + * @throws Will throw when passed an unrecognized strategy. + * @returns Promise resolving to current state when the import is complete. + */ + importAccountWithStrategy(strategy, args) { + return __awaiter(this, void 0, void 0, function* () { + let privateKey; + switch (strategy) { + case 'privateKey': + const [importedKey] = args; + if (!importedKey) { + throw new Error('Cannot import an empty key.'); + } + const prefixed = (0, ethereumjs_util_1.addHexPrefix)(importedKey); + let bufferedPrivateKey; + try { + bufferedPrivateKey = (0, ethereumjs_util_1.toBuffer)(prefixed); + } + catch (_a) { + throw new Error('Cannot import invalid private key.'); + } + /* istanbul ignore if */ + if (!(0, ethereumjs_util_1.isValidPrivate)(bufferedPrivateKey)) { + throw new Error('Cannot import invalid private key.'); + } + privateKey = (0, ethereumjs_util_1.stripHexPrefix)(prefixed); + break; + case 'json': + let wallet; + const [input, password] = args; + try { + wallet = ethereumjs_wallet_1.thirdparty.fromEtherWallet(input, password); + } + catch (e) { + wallet = wallet || (yield ethereumjs_wallet_1.default.fromV3(input, password, true)); + } + privateKey = (0, ethereumjs_util_1.bufferToHex)(wallet.getPrivateKey()); + break; + default: + throw new Error(`Unexpected import strategy: '${strategy}'`); + } + const newKeyring = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").addNewKeyring(KeyringTypes.simple, [ + privateKey, + ]); + const accounts = yield newKeyring.getAccounts(); + const allAccounts = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts(); + this.updateIdentities(allAccounts); + this.setSelectedAddress(accounts[0]); + return this.fullUpdate(); + }); + } + /** + * Removes an account from keyring state. + * + * @param address - Address of the account to remove. + * @returns Promise resolving current state when this account removal completes. + */ + removeAccount(address) { + return __awaiter(this, void 0, void 0, function* () { + this.removeIdentity(address); + yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").removeAccount(address); + return this.fullUpdate(); + }); + } + /** + * Deallocates all secrets and locks the wallet. + * + * @returns Promise resolving to current state. + */ + setLocked() { + return __classPrivateFieldGet(this, _KeyringController_keyring, "f").setLocked(); + } + /** + * Signs message by calling down into a specific keyring. + * + * @param messageParams - PersonalMessageParams object to sign. + * @returns Promise resolving to a signed message string. + */ + signMessage(messageParams) { + return __classPrivateFieldGet(this, _KeyringController_keyring, "f").signMessage(messageParams); + } + /** + * Signs personal message by calling down into a specific keyring. + * + * @param messageParams - PersonalMessageParams object to sign. + * @returns Promise resolving to a signed message string. + */ + signPersonalMessage(messageParams) { + return __classPrivateFieldGet(this, _KeyringController_keyring, "f").signPersonalMessage(messageParams); + } + /** + * Signs typed message by calling down into a specific keyring. + * + * @param messageParams - TypedMessageParams object to sign. + * @param version - Compatibility version EIP712. + * @throws Will throw when passed an unrecognized version. + * @returns Promise resolving to a signed message string or an error if any. + */ + signTypedMessage(messageParams, version) { + return __awaiter(this, void 0, void 0, function* () { + try { + const address = (0, eth_sig_util_1.normalize)(messageParams.from); + const qrKeyring = yield this.getOrAddQRKeyring(); + const qrAccounts = yield qrKeyring.getAccounts(); + if (qrAccounts.findIndex((qrAddress) => qrAddress.toLowerCase() === address.toLowerCase()) !== -1) { + const messageParamsClone = Object.assign({}, messageParams); + if (version !== SignTypedDataVersion.V1 && + typeof messageParamsClone.data === 'string') { + messageParamsClone.data = JSON.parse(messageParamsClone.data); + } + return __classPrivateFieldGet(this, _KeyringController_keyring, "f").signTypedMessage(messageParamsClone, { version }); + } + const { password } = __classPrivateFieldGet(this, _KeyringController_keyring, "f"); + const privateKey = yield this.exportAccount(password, address); + const privateKeyBuffer = (0, ethereumjs_util_1.toBuffer)((0, ethereumjs_util_1.addHexPrefix)(privateKey)); + switch (version) { + case SignTypedDataVersion.V1: + // signTypedDataLegacy will throw if the data is invalid. + return (0, eth_sig_util_1.signTypedDataLegacy)(privateKeyBuffer, { + data: messageParams.data, + }); + case SignTypedDataVersion.V3: + return (0, eth_sig_util_1.signTypedData)(privateKeyBuffer, { + data: JSON.parse(messageParams.data), + }); + case SignTypedDataVersion.V4: + return (0, eth_sig_util_1.signTypedData_v4)(privateKeyBuffer, { + data: JSON.parse(messageParams.data), + }); + default: + throw new Error(`Unexpected signTypedMessage version: '${version}'`); + } + } + catch (error) { + throw new Error(`Keyring Controller signTypedMessage: ${error}`); + } + }); + } + /** + * Signs a transaction by calling down into a specific keyring. + * + * @param transaction - Transaction object to sign. Must be a `ethereumjs-tx` transaction instance. + * @param from - Address to sign from, should be in keychain. + * @returns Promise resolving to a signed transaction string. + */ + signTransaction(transaction, from) { + return __classPrivateFieldGet(this, _KeyringController_keyring, "f").signTransaction(transaction, from); + } + /** + * Attempts to decrypt the current vault and load its keyrings. + * + * @param password - Password to unlock the keychain. + * @returns Promise resolving to the current state. + */ + submitPassword(password) { + return __awaiter(this, void 0, void 0, function* () { + yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").submitPassword(password); + const accounts = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts(); + yield this.syncIdentities(accounts); + return this.fullUpdate(); + }); + } + /** + * Adds new listener to be notified of state changes. + * + * @param listener - Callback triggered when state changes. + */ + subscribe(listener) { + __classPrivateFieldGet(this, _KeyringController_keyring, "f").store.subscribe(listener); + } + /** + * Removes existing listener from receiving state changes. + * + * @param listener - Callback to remove. + * @returns True if a listener is found and unsubscribed. + */ + unsubscribe(listener) { + return __classPrivateFieldGet(this, _KeyringController_keyring, "f").store.unsubscribe(listener); + } + /** + * Adds new listener to be notified when the wallet is locked. + * + * @param listener - Callback triggered when wallet is locked. + * @returns EventEmitter if listener added. + */ + onLock(listener) { + return __classPrivateFieldGet(this, _KeyringController_keyring, "f").on('lock', listener); + } + /** + * Adds new listener to be notified when the wallet is unlocked. + * + * @param listener - Callback triggered when wallet is unlocked. + * @returns EventEmitter if listener added. + */ + onUnlock(listener) { + return __classPrivateFieldGet(this, _KeyringController_keyring, "f").on('unlock', listener); + } + /** + * Verifies the that the seed phrase restores the current keychain's accounts. + * + * @returns Whether the verification succeeds. + */ + verifySeedPhrase() { + return __awaiter(this, void 0, void 0, function* () { + const primaryKeyring = __classPrivateFieldGet(this, _KeyringController_keyring, "f").getKeyringsByType(KeyringTypes.hd)[0]; + /* istanbul ignore if */ + if (!primaryKeyring) { + throw new Error('No HD keyring found.'); + } + const seedWords = (yield primaryKeyring.serialize()).mnemonic; + const accounts = yield primaryKeyring.getAccounts(); + /* istanbul ignore if */ + if (accounts.length === 0) { + throw new Error('Cannot verify an empty keyring.'); + } + const TestKeyringClass = __classPrivateFieldGet(this, _KeyringController_keyring, "f").getKeyringClassForType(KeyringTypes.hd); + const testKeyring = new TestKeyringClass({ + mnemonic: seedWords, + numberOfAccounts: accounts.length, + }); + const testAccounts = yield testKeyring.getAccounts(); + /* istanbul ignore if */ + if (testAccounts.length !== accounts.length) { + throw new Error('Seed phrase imported incorrect number of accounts.'); + } + testAccounts.forEach((account, i) => { + /* istanbul ignore if */ + if (account.toLowerCase() !== accounts[i].toLowerCase()) { + throw new Error('Seed phrase imported different accounts.'); + } + }); + return seedWords; + }); + } + /** + * Update keyrings in state and calls KeyringController fullUpdate method returning current state. + * + * @returns The current state. + */ + fullUpdate() { + return __awaiter(this, void 0, void 0, function* () { + const keyrings = yield Promise.all(__classPrivateFieldGet(this, _KeyringController_keyring, "f").keyrings.map((keyring, index) => __awaiter(this, void 0, void 0, function* () { + const keyringAccounts = yield keyring.getAccounts(); + const accounts = Array.isArray(keyringAccounts) + ? keyringAccounts.map((address) => (0, util_1.toChecksumHexAddress)(address)) + : /* istanbul ignore next */ []; + return { + accounts, + index, + type: keyring.type, + }; + }))); + this.update({ keyrings: [...keyrings] }); + return __classPrivateFieldGet(this, _KeyringController_keyring, "f").fullUpdate(); + }); + } + // QR Hardware related methods + /** + * Add qr hardware keyring. + * + * @returns The added keyring + */ + addQRKeyring() { + return __awaiter(this, void 0, void 0, function* () { + const keyring = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").addNewKeyring(KeyringTypes.qr); + yield this.fullUpdate(); + return keyring; + }); + } + /** + * Get qr hardware keyring. + * + * @returns The added keyring + */ + getOrAddQRKeyring() { + return __awaiter(this, void 0, void 0, function* () { + const keyring = __classPrivateFieldGet(this, _KeyringController_keyring, "f").getKeyringsByType(KeyringTypes.qr)[0]; + return keyring || (yield this.addQRKeyring()); + }); + } + restoreQRKeyring(serialized) { + return __awaiter(this, void 0, void 0, function* () { + (yield this.getOrAddQRKeyring()).deserialize(serialized); + this.updateIdentities(yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts()); + yield this.fullUpdate(); + }); + } + resetQRKeyringState() { + return __awaiter(this, void 0, void 0, function* () { + (yield this.getOrAddQRKeyring()).resetStore(); + }); + } + getQRKeyringState() { + return __awaiter(this, void 0, void 0, function* () { + return (yield this.getOrAddQRKeyring()).getMemStore(); + }); + } + submitQRCryptoHDKey(cryptoHDKey) { + return __awaiter(this, void 0, void 0, function* () { + (yield this.getOrAddQRKeyring()).submitCryptoHDKey(cryptoHDKey); + }); + } + submitQRCryptoAccount(cryptoAccount) { + return __awaiter(this, void 0, void 0, function* () { + (yield this.getOrAddQRKeyring()).submitCryptoAccount(cryptoAccount); + }); + } + submitQRSignature(requestId, ethSignature) { + return __awaiter(this, void 0, void 0, function* () { + (yield this.getOrAddQRKeyring()).submitSignature(requestId, ethSignature); + }); + } + cancelQRSignRequest() { + return __awaiter(this, void 0, void 0, function* () { + (yield this.getOrAddQRKeyring()).cancelSignRequest(); + }); + } + connectQRHardware(page) { + return __awaiter(this, void 0, void 0, function* () { + try { + const keyring = yield this.getOrAddQRKeyring(); + let accounts; + switch (page) { + case -1: + accounts = yield keyring.getPreviousPage(); + break; + case 1: + accounts = yield keyring.getNextPage(); + break; + default: + accounts = yield keyring.getFirstPage(); + } + return accounts.map((account) => { + return Object.assign(Object.assign({}, account), { balance: '0x0' }); + }); + } + catch (e) { + throw new Error(`Unspecified error when connect QR Hardware, ${e}`); + } + }); + } + unlockQRHardwareWalletAccount(index) { + return __awaiter(this, void 0, void 0, function* () { + const keyring = yield this.getOrAddQRKeyring(); + keyring.setAccountToUnlock(index); + const oldAccounts = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts(); + yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").addNewAccount(keyring); + const newAccounts = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts(); + this.updateIdentities(newAccounts); + newAccounts.forEach((address) => { + if (!oldAccounts.includes(address)) { + if (this.setAccountLabel) { + this.setAccountLabel(address, `${keyring.getName()} ${index}`); + } + this.setSelectedAddress(address); + } + }); + yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").persistAllKeyrings(); + yield this.fullUpdate(); + }); + } + getAccountKeyringType(account) { + return __awaiter(this, void 0, void 0, function* () { + return (yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getKeyringForAccount(account)).type; + }); + } + forgetQRDevice() { + return __awaiter(this, void 0, void 0, function* () { + const keyring = yield this.getOrAddQRKeyring(); + keyring.forgetDevice(); + const accounts = (yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts()); + accounts.forEach((account) => { + this.setSelectedAddress(account); + }); + yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").persistAllKeyrings(); + yield this.fullUpdate(); + }); + } +} +exports.KeyringController = KeyringController; +_KeyringController_keyring = new WeakMap(); +exports.default = KeyringController; +//# sourceMappingURL=KeyringController.js.map \ No newline at end of file diff --git a/dist/keyring/KeyringController.js.map b/dist/keyring/KeyringController.js.map new file mode 100644 index 0000000000..3d4787d7dd --- /dev/null +++ b/dist/keyring/KeyringController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"KeyringController.js","sourceRoot":"","sources":["../../src/keyring/KeyringController.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n addHexPrefix,\n bufferToHex,\n isValidPrivate,\n toBuffer,\n stripHexPrefix,\n} from 'ethereumjs-util';\nimport {\n normalize as normalizeAddress,\n signTypedData,\n signTypedData_v4,\n signTypedDataLegacy,\n} from 'eth-sig-util';\nimport Wallet, { thirdparty as importers } from 'ethereumjs-wallet';\nimport Keyring from 'eth-keyring-controller';\nimport { Mutex } from 'async-mutex';\nimport {\n MetaMaskKeyring as QRKeyring,\n IKeyringState as IQRKeyringState,\n} from '@keystonehq/metamask-airgapped-keyring';\nimport {\n BaseController,\n BaseConfig,\n BaseState,\n Listener,\n} from '../BaseController';\nimport { PreferencesController } from '../user/PreferencesController';\nimport { PersonalMessageParams } from '../message-manager/PersonalMessageManager';\nimport { TypedMessageParams } from '../message-manager/TypedMessageManager';\nimport { toChecksumHexAddress } from '../util';\n\n/**\n * Available keyring types\n */\nexport enum KeyringTypes {\n simple = 'Simple Key Pair',\n hd = 'HD Key Tree',\n qr = 'QR Hardware Wallet Device',\n}\n\n/**\n * @type KeyringObject\n *\n * Keyring object\n * @property type - Keyring type\n * @property accounts - Associated accounts\n * @function getAccounts - Get associated accounts\n */\nexport interface KeyringObject {\n type: string;\n accounts: string[];\n getAccounts(): string[];\n}\n\n/**\n * @type KeyringState\n *\n * Keyring controller state\n * @property vault - Encrypted string representing keyring data\n * @property keyrings - Group of accounts\n */\nexport interface KeyringState extends BaseState {\n vault?: string;\n keyrings: Keyring[];\n}\n\n/**\n * @type KeyringMemState\n *\n * Keyring mem controller state\n * @property isUnlocked - Whether vault is unlocked\n * @property keyringTypes - Account types\n * @property keyrings - Group of accounts\n */\nexport interface KeyringMemState extends BaseState {\n isUnlocked: boolean;\n keyringTypes: string[];\n keyrings: Keyring[];\n}\n\n/**\n * @type KeyringConfig\n *\n * Keyring controller configuration\n * @property encryptor - Keyring encryptor\n */\nexport interface KeyringConfig extends BaseConfig {\n encryptor?: any;\n keyringTypes?: any[];\n}\n\n/**\n * @type Keyring\n *\n * Keyring object to return in fullUpdate\n * @property type - Keyring type\n * @property accounts - Associated accounts\n * @property index - Associated index\n */\nexport interface Keyring {\n accounts: string[];\n type: string;\n index?: number;\n}\n\n/**\n * A strategy for importing an account\n */\nexport enum AccountImportStrategy {\n privateKey = 'privateKey',\n json = 'json',\n}\n\n/**\n * The `signTypedMessage` version\n *\n * @see https://docs.metamask.io/guide/signing-data.html\n */\nexport enum SignTypedDataVersion {\n V1 = 'V1',\n V3 = 'V3',\n V4 = 'V4',\n}\n\n/**\n * Controller responsible for establishing and managing user identity\n */\nexport class KeyringController extends BaseController<\n KeyringConfig,\n KeyringState\n> {\n private mutex = new Mutex();\n\n /**\n * Name of this controller used during composition\n */\n override name = 'KeyringController';\n\n private removeIdentity: PreferencesController['removeIdentity'];\n\n private syncIdentities: PreferencesController['syncIdentities'];\n\n private updateIdentities: PreferencesController['updateIdentities'];\n\n private setSelectedAddress: PreferencesController['setSelectedAddress'];\n\n private setAccountLabel?: PreferencesController['setAccountLabel'];\n\n #keyring: typeof Keyring;\n\n /**\n * Creates a KeyringController instance.\n *\n * @param options - The controller options.\n * @param options.removeIdentity - Remove the identity with the given address.\n * @param options.syncIdentities - Sync identities with the given list of addresses.\n * @param options.updateIdentities - Generate an identity for each address given that doesn't already have an identity.\n * @param options.setSelectedAddress - Set the selected address.\n * @param options.setAccountLabel - Set a new name for account.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n removeIdentity,\n syncIdentities,\n updateIdentities,\n setSelectedAddress,\n setAccountLabel,\n }: {\n removeIdentity: PreferencesController['removeIdentity'];\n syncIdentities: PreferencesController['syncIdentities'];\n updateIdentities: PreferencesController['updateIdentities'];\n setSelectedAddress: PreferencesController['setSelectedAddress'];\n setAccountLabel?: PreferencesController['setAccountLabel'];\n },\n config?: Partial,\n state?: Partial,\n ) {\n super(config, state);\n this.#keyring = new Keyring(Object.assign({ initState: state }, config));\n\n this.defaultState = {\n ...this.#keyring.store.getState(),\n keyrings: [],\n };\n this.removeIdentity = removeIdentity;\n this.syncIdentities = syncIdentities;\n this.updateIdentities = updateIdentities;\n this.setSelectedAddress = setSelectedAddress;\n this.setAccountLabel = setAccountLabel;\n this.initialize();\n this.fullUpdate();\n }\n\n /**\n * Adds a new account to the default (first) HD seed phrase keyring.\n *\n * @returns Promise resolving to current state when the account is added.\n */\n async addNewAccount(): Promise {\n const primaryKeyring = this.#keyring.getKeyringsByType('HD Key Tree')[0];\n /* istanbul ignore if */\n if (!primaryKeyring) {\n throw new Error('No HD keyring found');\n }\n const oldAccounts = await this.#keyring.getAccounts();\n await this.#keyring.addNewAccount(primaryKeyring);\n const newAccounts = await this.#keyring.getAccounts();\n\n await this.verifySeedPhrase();\n\n this.updateIdentities(newAccounts);\n newAccounts.forEach((selectedAddress: string) => {\n if (!oldAccounts.includes(selectedAddress)) {\n this.setSelectedAddress(selectedAddress);\n }\n });\n return this.fullUpdate();\n }\n\n /**\n * Adds a new account to the default (first) HD seed phrase keyring without updating identities in preferences.\n *\n * @returns Promise resolving to current state when the account is added.\n */\n async addNewAccountWithoutUpdate(): Promise {\n const primaryKeyring = this.#keyring.getKeyringsByType('HD Key Tree')[0];\n /* istanbul ignore if */\n if (!primaryKeyring) {\n throw new Error('No HD keyring found');\n }\n await this.#keyring.addNewAccount(primaryKeyring);\n await this.verifySeedPhrase();\n return this.fullUpdate();\n }\n\n /**\n * Effectively the same as creating a new keychain then populating it\n * using the given seed phrase.\n *\n * @param password - Password to unlock keychain.\n * @param seed - A BIP39-compliant seed phrase,\n * either as a string or an array of UTF-8 bytes that represent the string.\n * @returns Promise resolving to the restored keychain object.\n */\n async createNewVaultAndRestore(password: string, seed: string | number[]) {\n const releaseLock = await this.mutex.acquire();\n if (!password || !password.length) {\n throw new Error('Invalid password');\n }\n\n try {\n this.updateIdentities([]);\n const vault = await this.#keyring.createNewVaultAndRestore(\n password,\n seed,\n );\n this.updateIdentities(await this.#keyring.getAccounts());\n this.fullUpdate();\n return vault;\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Create a new primary keychain and wipe any previous keychains.\n *\n * @param password - Password to unlock the new vault.\n * @returns Newly-created keychain object.\n */\n async createNewVaultAndKeychain(password: string) {\n const releaseLock = await this.mutex.acquire();\n try {\n const vault = await this.#keyring.createNewVaultAndKeychain(password);\n this.updateIdentities(await this.#keyring.getAccounts());\n this.fullUpdate();\n return vault;\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Returns the status of the vault.\n *\n * @returns Boolean returning true if the vault is unlocked.\n */\n isUnlocked(): boolean {\n return this.#keyring.memStore.getState().isUnlocked;\n }\n\n /**\n * Gets the seed phrase of the HD keyring.\n *\n * @param password - Password of the keyring.\n * @returns Promise resolving to the seed phrase.\n */\n exportSeedPhrase(password: string) {\n if (this.#keyring.password === password) {\n return this.#keyring.keyrings[0].mnemonic;\n }\n throw new Error('Invalid password');\n }\n\n /**\n * Gets the private key from the keyring controlling an address.\n *\n * @param password - Password of the keyring.\n * @param address - Address to export.\n * @returns Promise resolving to the private key for an address.\n */\n exportAccount(password: string, address: string): Promise {\n if (this.#keyring.password === password) {\n return this.#keyring.exportAccount(address);\n }\n throw new Error('Invalid password');\n }\n\n /**\n * Returns the public addresses of all accounts for the current keyring.\n *\n * @returns A promise resolving to an array of addresses.\n */\n getAccounts(): Promise {\n return this.#keyring.getAccounts();\n }\n\n /**\n * Imports an account with the specified import strategy.\n *\n * @param strategy - Import strategy name.\n * @param args - Array of arguments to pass to the underlying stategy.\n * @throws Will throw when passed an unrecognized strategy.\n * @returns Promise resolving to current state when the import is complete.\n */\n async importAccountWithStrategy(\n strategy: AccountImportStrategy,\n args: any[],\n ): Promise {\n let privateKey;\n switch (strategy) {\n case 'privateKey':\n const [importedKey] = args;\n if (!importedKey) {\n throw new Error('Cannot import an empty key.');\n }\n const prefixed = addHexPrefix(importedKey);\n\n let bufferedPrivateKey;\n try {\n bufferedPrivateKey = toBuffer(prefixed);\n } catch {\n throw new Error('Cannot import invalid private key.');\n }\n\n /* istanbul ignore if */\n if (!isValidPrivate(bufferedPrivateKey)) {\n throw new Error('Cannot import invalid private key.');\n }\n\n privateKey = stripHexPrefix(prefixed);\n break;\n case 'json':\n let wallet;\n const [input, password] = args;\n try {\n wallet = importers.fromEtherWallet(input, password);\n } catch (e) {\n wallet = wallet || (await Wallet.fromV3(input, password, true));\n }\n privateKey = bufferToHex(wallet.getPrivateKey());\n break;\n default:\n throw new Error(`Unexpected import strategy: '${strategy}'`);\n }\n const newKeyring = await this.#keyring.addNewKeyring(KeyringTypes.simple, [\n privateKey,\n ]);\n const accounts = await newKeyring.getAccounts();\n const allAccounts = await this.#keyring.getAccounts();\n this.updateIdentities(allAccounts);\n this.setSelectedAddress(accounts[0]);\n return this.fullUpdate();\n }\n\n /**\n * Removes an account from keyring state.\n *\n * @param address - Address of the account to remove.\n * @returns Promise resolving current state when this account removal completes.\n */\n async removeAccount(address: string): Promise {\n this.removeIdentity(address);\n await this.#keyring.removeAccount(address);\n return this.fullUpdate();\n }\n\n /**\n * Deallocates all secrets and locks the wallet.\n *\n * @returns Promise resolving to current state.\n */\n setLocked(): Promise {\n return this.#keyring.setLocked();\n }\n\n /**\n * Signs message by calling down into a specific keyring.\n *\n * @param messageParams - PersonalMessageParams object to sign.\n * @returns Promise resolving to a signed message string.\n */\n signMessage(messageParams: PersonalMessageParams) {\n return this.#keyring.signMessage(messageParams);\n }\n\n /**\n * Signs personal message by calling down into a specific keyring.\n *\n * @param messageParams - PersonalMessageParams object to sign.\n * @returns Promise resolving to a signed message string.\n */\n signPersonalMessage(messageParams: PersonalMessageParams) {\n return this.#keyring.signPersonalMessage(messageParams);\n }\n\n /**\n * Signs typed message by calling down into a specific keyring.\n *\n * @param messageParams - TypedMessageParams object to sign.\n * @param version - Compatibility version EIP712.\n * @throws Will throw when passed an unrecognized version.\n * @returns Promise resolving to a signed message string or an error if any.\n */\n async signTypedMessage(\n messageParams: TypedMessageParams,\n version: SignTypedDataVersion,\n ) {\n try {\n const address = normalizeAddress(messageParams.from);\n const qrKeyring = await this.getOrAddQRKeyring();\n const qrAccounts = await qrKeyring.getAccounts();\n if (\n qrAccounts.findIndex(\n (qrAddress: string) =>\n qrAddress.toLowerCase() === address.toLowerCase(),\n ) !== -1\n ) {\n const messageParamsClone = { ...messageParams };\n if (\n version !== SignTypedDataVersion.V1 &&\n typeof messageParamsClone.data === 'string'\n ) {\n messageParamsClone.data = JSON.parse(messageParamsClone.data);\n }\n return this.#keyring.signTypedMessage(messageParamsClone, { version });\n }\n\n const { password } = this.#keyring;\n const privateKey = await this.exportAccount(password, address);\n const privateKeyBuffer = toBuffer(addHexPrefix(privateKey));\n switch (version) {\n case SignTypedDataVersion.V1:\n // signTypedDataLegacy will throw if the data is invalid.\n return signTypedDataLegacy(privateKeyBuffer, {\n data: messageParams.data as any,\n });\n case SignTypedDataVersion.V3:\n return signTypedData(privateKeyBuffer, {\n data: JSON.parse(messageParams.data as string),\n });\n case SignTypedDataVersion.V4:\n return signTypedData_v4(privateKeyBuffer, {\n data: JSON.parse(messageParams.data as string),\n });\n default:\n throw new Error(`Unexpected signTypedMessage version: '${version}'`);\n }\n } catch (error) {\n throw new Error(`Keyring Controller signTypedMessage: ${error}`);\n }\n }\n\n /**\n * Signs a transaction by calling down into a specific keyring.\n *\n * @param transaction - Transaction object to sign. Must be a `ethereumjs-tx` transaction instance.\n * @param from - Address to sign from, should be in keychain.\n * @returns Promise resolving to a signed transaction string.\n */\n signTransaction(transaction: unknown, from: string) {\n return this.#keyring.signTransaction(transaction, from);\n }\n\n /**\n * Attempts to decrypt the current vault and load its keyrings.\n *\n * @param password - Password to unlock the keychain.\n * @returns Promise resolving to the current state.\n */\n async submitPassword(password: string): Promise {\n await this.#keyring.submitPassword(password);\n const accounts = await this.#keyring.getAccounts();\n await this.syncIdentities(accounts);\n return this.fullUpdate();\n }\n\n /**\n * Adds new listener to be notified of state changes.\n *\n * @param listener - Callback triggered when state changes.\n */\n override subscribe(listener: Listener) {\n this.#keyring.store.subscribe(listener);\n }\n\n /**\n * Removes existing listener from receiving state changes.\n *\n * @param listener - Callback to remove.\n * @returns True if a listener is found and unsubscribed.\n */\n override unsubscribe(listener: Listener) {\n return this.#keyring.store.unsubscribe(listener);\n }\n\n /**\n * Adds new listener to be notified when the wallet is locked.\n *\n * @param listener - Callback triggered when wallet is locked.\n * @returns EventEmitter if listener added.\n */\n onLock(listener: () => void) {\n return this.#keyring.on('lock', listener);\n }\n\n /**\n * Adds new listener to be notified when the wallet is unlocked.\n *\n * @param listener - Callback triggered when wallet is unlocked.\n * @returns EventEmitter if listener added.\n */\n onUnlock(listener: () => void) {\n return this.#keyring.on('unlock', listener);\n }\n\n /**\n * Verifies the that the seed phrase restores the current keychain's accounts.\n *\n * @returns Whether the verification succeeds.\n */\n async verifySeedPhrase(): Promise {\n const primaryKeyring = this.#keyring.getKeyringsByType(KeyringTypes.hd)[0];\n /* istanbul ignore if */\n if (!primaryKeyring) {\n throw new Error('No HD keyring found.');\n }\n\n const seedWords = (await primaryKeyring.serialize()).mnemonic;\n const accounts = await primaryKeyring.getAccounts();\n /* istanbul ignore if */\n if (accounts.length === 0) {\n throw new Error('Cannot verify an empty keyring.');\n }\n\n const TestKeyringClass = this.#keyring.getKeyringClassForType(\n KeyringTypes.hd,\n );\n const testKeyring = new TestKeyringClass({\n mnemonic: seedWords,\n numberOfAccounts: accounts.length,\n });\n const testAccounts = await testKeyring.getAccounts();\n /* istanbul ignore if */\n if (testAccounts.length !== accounts.length) {\n throw new Error('Seed phrase imported incorrect number of accounts.');\n }\n\n testAccounts.forEach((account: string, i: number) => {\n /* istanbul ignore if */\n if (account.toLowerCase() !== accounts[i].toLowerCase()) {\n throw new Error('Seed phrase imported different accounts.');\n }\n });\n\n return seedWords;\n }\n\n /**\n * Update keyrings in state and calls KeyringController fullUpdate method returning current state.\n *\n * @returns The current state.\n */\n async fullUpdate(): Promise {\n const keyrings: Keyring[] = await Promise.all(\n this.#keyring.keyrings.map(\n async (keyring: KeyringObject, index: number): Promise => {\n const keyringAccounts = await keyring.getAccounts();\n const accounts = Array.isArray(keyringAccounts)\n ? keyringAccounts.map((address) => toChecksumHexAddress(address))\n : /* istanbul ignore next */ [];\n return {\n accounts,\n index,\n type: keyring.type,\n };\n },\n ),\n );\n this.update({ keyrings: [...keyrings] });\n return this.#keyring.fullUpdate();\n }\n\n // QR Hardware related methods\n\n /**\n * Add qr hardware keyring.\n *\n * @returns The added keyring\n */\n private async addQRKeyring(): Promise {\n const keyring = await this.#keyring.addNewKeyring(KeyringTypes.qr);\n await this.fullUpdate();\n return keyring;\n }\n\n /**\n * Get qr hardware keyring.\n *\n * @returns The added keyring\n */\n async getOrAddQRKeyring(): Promise {\n const keyring = this.#keyring.getKeyringsByType(KeyringTypes.qr)[0];\n return keyring || (await this.addQRKeyring());\n }\n\n async restoreQRKeyring(serialized: any): Promise {\n (await this.getOrAddQRKeyring()).deserialize(serialized);\n this.updateIdentities(await this.#keyring.getAccounts());\n await this.fullUpdate();\n }\n\n async resetQRKeyringState(): Promise {\n (await this.getOrAddQRKeyring()).resetStore();\n }\n\n async getQRKeyringState(): Promise {\n return (await this.getOrAddQRKeyring()).getMemStore();\n }\n\n async submitQRCryptoHDKey(cryptoHDKey: string): Promise {\n (await this.getOrAddQRKeyring()).submitCryptoHDKey(cryptoHDKey);\n }\n\n async submitQRCryptoAccount(cryptoAccount: string): Promise {\n (await this.getOrAddQRKeyring()).submitCryptoAccount(cryptoAccount);\n }\n\n async submitQRSignature(\n requestId: string,\n ethSignature: string,\n ): Promise {\n (await this.getOrAddQRKeyring()).submitSignature(requestId, ethSignature);\n }\n\n async cancelQRSignRequest(): Promise {\n (await this.getOrAddQRKeyring()).cancelSignRequest();\n }\n\n async connectQRHardware(\n page: number,\n ): Promise<{ balance: string; address: string; index: number }[]> {\n try {\n const keyring = await this.getOrAddQRKeyring();\n let accounts;\n switch (page) {\n case -1:\n accounts = await keyring.getPreviousPage();\n break;\n case 1:\n accounts = await keyring.getNextPage();\n break;\n default:\n accounts = await keyring.getFirstPage();\n }\n return accounts.map((account: any) => {\n return {\n ...account,\n balance: '0x0',\n };\n });\n } catch (e) {\n throw new Error(`Unspecified error when connect QR Hardware, ${e}`);\n }\n }\n\n async unlockQRHardwareWalletAccount(index: number): Promise {\n const keyring = await this.getOrAddQRKeyring();\n\n keyring.setAccountToUnlock(index);\n const oldAccounts = await this.#keyring.getAccounts();\n await this.#keyring.addNewAccount(keyring);\n const newAccounts = await this.#keyring.getAccounts();\n this.updateIdentities(newAccounts);\n newAccounts.forEach((address: string) => {\n if (!oldAccounts.includes(address)) {\n if (this.setAccountLabel) {\n this.setAccountLabel(address, `${keyring.getName()} ${index}`);\n }\n this.setSelectedAddress(address);\n }\n });\n await this.#keyring.persistAllKeyrings();\n await this.fullUpdate();\n }\n\n async getAccountKeyringType(account: string): Promise {\n return (await this.#keyring.getKeyringForAccount(account)).type;\n }\n\n async forgetQRDevice(): Promise {\n const keyring = await this.getOrAddQRKeyring();\n keyring.forgetDevice();\n const accounts = (await this.#keyring.getAccounts()) as string[];\n accounts.forEach((account) => {\n this.setSelectedAddress(account);\n });\n await this.#keyring.persistAllKeyrings();\n await this.fullUpdate();\n }\n}\n\nexport default KeyringController;\n"]} \ No newline at end of file diff --git a/dist/message-manager/AbstractMessageManager.d.ts b/dist/message-manager/AbstractMessageManager.d.ts new file mode 100644 index 0000000000..6226fc8bf2 --- /dev/null +++ b/dist/message-manager/AbstractMessageManager.d.ts @@ -0,0 +1,172 @@ +/// +import { EventEmitter } from 'events'; +import { BaseController, BaseConfig, BaseState } from '../BaseController'; +/** + * @type OriginalRequest + * + * Represents the original request object for adding a message. + * @property origin? - Is it is specified, represents the origin + */ +export interface OriginalRequest { + origin?: string; +} +/** + * @type Message + * + * Represents and contains data about a signing type signature request. + * @property id - An id to track and identify the message object + * @property type - The json-prc signing method for which a signature request has been made. + * A 'Message' which always has a signing type + * @property rawSig - Raw data of the signature request + */ +export interface AbstractMessage { + id: string; + time: number; + status: string; + type: string; + rawSig?: string; +} +/** + * @type MessageParams + * + * Represents the parameters to pass to the signing method once the signature request is approved. + * @property from - Address to sign this message from + * @property origin? - Added for request origin identification + */ +export interface AbstractMessageParams { + from: string; + origin?: string; +} +/** + * @type MessageParamsMetamask + * + * Represents the parameters to pass to the signing method once the signature request is approved + * plus data added by MetaMask. + * @property metamaskId - Added for tracking and identification within MetaMask + * @property from - Address to sign this message from + * @property origin? - Added for request origin identification + */ +export interface AbstractMessageParamsMetamask extends AbstractMessageParams { + metamaskId?: string; +} +/** + * @type MessageManagerState + * + * Message Manager state + * @property unapprovedMessages - A collection of all Messages in the 'unapproved' state + * @property unapprovedMessagesCount - The count of all Messages in this.unapprovedMessages + */ +export interface MessageManagerState extends BaseState { + unapprovedMessages: { + [key: string]: M; + }; + unapprovedMessagesCount: number; +} +/** + * Controller in charge of managing - storing, adding, removing, updating - Messages. + */ +export declare abstract class AbstractMessageManager extends BaseController> { + protected messages: M[]; + /** + * Saves the unapproved messages, and their count to state. + * + */ + protected saveMessageList(): void; + /** + * Updates the status of a Message in this.messages. + * + * @param messageId - The id of the Message to update. + * @param status - The new status of the Message. + */ + protected setMessageStatus(messageId: string, status: string): void; + /** + * Sets a Message in this.messages to the passed Message if the ids are equal. + * Then saves the unapprovedMessage list to storage. + * + * @param message - A Message that will replace an existing Message (with the id) in this.messages. + */ + protected updateMessage(message: M): void; + /** + * EventEmitter instance used to listen to specific message events + */ + hub: EventEmitter; + /** + * Name of this controller used during composition + */ + name: string; + /** + * Creates an AbstractMessageManager instance. + * + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor(config?: Partial, state?: Partial>); + /** + * A getter for the number of 'unapproved' Messages in this.messages. + * + * @returns The number of 'unapproved' Messages in this.messages. + */ + getUnapprovedMessagesCount(): number; + /** + * A getter for the 'unapproved' Messages in state messages. + * + * @returns An index of Message ids to Messages, for all 'unapproved' Messages in this.messages. + */ + getUnapprovedMessages(): { + [key: string]: M; + }; + /** + * Adds a passed Message to this.messages, and calls this.saveMessageList() to save + * the unapproved Messages from that list to this.messages. + * + * @param message - The Message to add to this.messages. + */ + addMessage(message: M): void; + /** + * Returns a specified Message. + * + * @param messageId - The id of the Message to get. + * @returns The Message with the id that matches the passed messageId, or undefined + * if no Message has that id. + */ + getMessage(messageId: string): M | undefined; + /** + * Approves a Message. Sets the message status via a call to this.setMessageStatusApproved, + * and returns a promise with any the message params modified for proper signing. + * + * @param messageParams - The messageParams to be used when signing method is called, + * plus data added by MetaMask. + * @returns Promise resolving to the messageParams with the metamaskId property removed. + */ + approveMessage(messageParams: PM): Promise

; + /** + * Sets a Message status to 'approved' via a call to this.setMessageStatus. + * + * @param messageId - The id of the Message to approve. + */ + setMessageStatusApproved(messageId: string): void; + /** + * Sets a Message status to 'signed' via a call to this.setMessageStatus and updates + * that Message in this.messages by adding the raw signature data of the signature + * request to the Message. + * + * @param messageId - The id of the Message to sign. + * @param rawSig - The raw data of the signature request. + */ + setMessageStatusSigned(messageId: string, rawSig: string): void; + /** + * Removes the metamaskId property from passed messageParams and returns a promise which + * resolves the updated messageParams + * + * @param messageParams - The messageParams to modify + * @returns Promise resolving to the messageParams with the metamaskId property removed + */ + abstract prepMessageForSigning(messageParams: PM): Promise

; + /** + * Sets a Message status to 'rejected' via a call to this.setMessageStatus. + * + * @param messageId - The id of the Message to reject. + */ + rejectMessage(messageId: string): void; +} +export default AbstractMessageManager; diff --git a/dist/message-manager/AbstractMessageManager.js b/dist/message-manager/AbstractMessageManager.js new file mode 100644 index 0000000000..faec6ab542 --- /dev/null +++ b/dist/message-manager/AbstractMessageManager.js @@ -0,0 +1,167 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AbstractMessageManager = void 0; +const events_1 = require("events"); +const BaseController_1 = require("../BaseController"); +/** + * Controller in charge of managing - storing, adding, removing, updating - Messages. + */ +class AbstractMessageManager extends BaseController_1.BaseController { + /** + * Creates an AbstractMessageManager instance. + * + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor(config, state) { + super(config, state); + /** + * EventEmitter instance used to listen to specific message events + */ + this.hub = new events_1.EventEmitter(); + /** + * Name of this controller used during composition + */ + this.name = 'AbstractMessageManager'; + this.defaultState = { + unapprovedMessages: {}, + unapprovedMessagesCount: 0, + }; + this.messages = []; + this.initialize(); + } + /** + * Saves the unapproved messages, and their count to state. + * + */ + saveMessageList() { + const unapprovedMessages = this.getUnapprovedMessages(); + const unapprovedMessagesCount = this.getUnapprovedMessagesCount(); + this.update({ unapprovedMessages, unapprovedMessagesCount }); + this.hub.emit('updateBadge'); + } + /** + * Updates the status of a Message in this.messages. + * + * @param messageId - The id of the Message to update. + * @param status - The new status of the Message. + */ + setMessageStatus(messageId, status) { + const message = this.getMessage(messageId); + if (!message) { + throw new Error(`${this.name}: Message not found for id: ${messageId}.`); + } + message.status = status; + this.updateMessage(message); + this.hub.emit(`${messageId}:${status}`, message); + if (status === 'rejected' || status === 'signed' || status === 'errored') { + this.hub.emit(`${messageId}:finished`, message); + } + } + /** + * Sets a Message in this.messages to the passed Message if the ids are equal. + * Then saves the unapprovedMessage list to storage. + * + * @param message - A Message that will replace an existing Message (with the id) in this.messages. + */ + updateMessage(message) { + const index = this.messages.findIndex((msg) => message.id === msg.id); + /* istanbul ignore next */ + if (index !== -1) { + this.messages[index] = message; + } + this.saveMessageList(); + } + /** + * A getter for the number of 'unapproved' Messages in this.messages. + * + * @returns The number of 'unapproved' Messages in this.messages. + */ + getUnapprovedMessagesCount() { + return Object.keys(this.getUnapprovedMessages()).length; + } + /** + * A getter for the 'unapproved' Messages in state messages. + * + * @returns An index of Message ids to Messages, for all 'unapproved' Messages in this.messages. + */ + getUnapprovedMessages() { + return this.messages + .filter((message) => message.status === 'unapproved') + .reduce((result, message) => { + result[message.id] = message; + return result; + }, {}); + } + /** + * Adds a passed Message to this.messages, and calls this.saveMessageList() to save + * the unapproved Messages from that list to this.messages. + * + * @param message - The Message to add to this.messages. + */ + addMessage(message) { + this.messages.push(message); + this.saveMessageList(); + } + /** + * Returns a specified Message. + * + * @param messageId - The id of the Message to get. + * @returns The Message with the id that matches the passed messageId, or undefined + * if no Message has that id. + */ + getMessage(messageId) { + return this.messages.find((message) => message.id === messageId); + } + /** + * Approves a Message. Sets the message status via a call to this.setMessageStatusApproved, + * and returns a promise with any the message params modified for proper signing. + * + * @param messageParams - The messageParams to be used when signing method is called, + * plus data added by MetaMask. + * @returns Promise resolving to the messageParams with the metamaskId property removed. + */ + approveMessage(messageParams) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + this.setMessageStatusApproved(messageParams.metamaskId); + return this.prepMessageForSigning(messageParams); + } + /** + * Sets a Message status to 'approved' via a call to this.setMessageStatus. + * + * @param messageId - The id of the Message to approve. + */ + setMessageStatusApproved(messageId) { + this.setMessageStatus(messageId, 'approved'); + } + /** + * Sets a Message status to 'signed' via a call to this.setMessageStatus and updates + * that Message in this.messages by adding the raw signature data of the signature + * request to the Message. + * + * @param messageId - The id of the Message to sign. + * @param rawSig - The raw data of the signature request. + */ + setMessageStatusSigned(messageId, rawSig) { + const message = this.getMessage(messageId); + /* istanbul ignore if */ + if (!message) { + return; + } + message.rawSig = rawSig; + this.updateMessage(message); + this.setMessageStatus(messageId, 'signed'); + } + /** + * Sets a Message status to 'rejected' via a call to this.setMessageStatus. + * + * @param messageId - The id of the Message to reject. + */ + rejectMessage(messageId) { + this.setMessageStatus(messageId, 'rejected'); + } +} +exports.AbstractMessageManager = AbstractMessageManager; +exports.default = AbstractMessageManager; +//# sourceMappingURL=AbstractMessageManager.js.map \ No newline at end of file diff --git a/dist/message-manager/AbstractMessageManager.js.map b/dist/message-manager/AbstractMessageManager.js.map new file mode 100644 index 0000000000..210ce2fc23 --- /dev/null +++ b/dist/message-manager/AbstractMessageManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"AbstractMessageManager.js","sourceRoot":"","sources":["../../src/message-manager/AbstractMessageManager.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from 'events';\nimport { BaseController, BaseConfig, BaseState } from '../BaseController';\n\n/**\n * @type OriginalRequest\n *\n * Represents the original request object for adding a message.\n * @property origin? - Is it is specified, represents the origin\n */\nexport interface OriginalRequest {\n origin?: string;\n}\n\n/**\n * @type Message\n *\n * Represents and contains data about a signing type signature request.\n * @property id - An id to track and identify the message object\n * @property type - The json-prc signing method for which a signature request has been made.\n * A 'Message' which always has a signing type\n * @property rawSig - Raw data of the signature request\n */\nexport interface AbstractMessage {\n id: string;\n time: number;\n status: string;\n type: string;\n rawSig?: string;\n}\n\n/**\n * @type MessageParams\n *\n * Represents the parameters to pass to the signing method once the signature request is approved.\n * @property from - Address to sign this message from\n * @property origin? - Added for request origin identification\n */\nexport interface AbstractMessageParams {\n from: string;\n origin?: string;\n}\n\n/**\n * @type MessageParamsMetamask\n *\n * Represents the parameters to pass to the signing method once the signature request is approved\n * plus data added by MetaMask.\n * @property metamaskId - Added for tracking and identification within MetaMask\n * @property from - Address to sign this message from\n * @property origin? - Added for request origin identification\n */\nexport interface AbstractMessageParamsMetamask extends AbstractMessageParams {\n metamaskId?: string;\n}\n\n/**\n * @type MessageManagerState\n *\n * Message Manager state\n * @property unapprovedMessages - A collection of all Messages in the 'unapproved' state\n * @property unapprovedMessagesCount - The count of all Messages in this.unapprovedMessages\n */\nexport interface MessageManagerState\n extends BaseState {\n unapprovedMessages: { [key: string]: M };\n unapprovedMessagesCount: number;\n}\n\n/**\n * Controller in charge of managing - storing, adding, removing, updating - Messages.\n */\nexport abstract class AbstractMessageManager<\n M extends AbstractMessage,\n P extends AbstractMessageParams,\n PM extends AbstractMessageParamsMetamask,\n> extends BaseController> {\n protected messages: M[];\n\n /**\n * Saves the unapproved messages, and their count to state.\n *\n */\n protected saveMessageList() {\n const unapprovedMessages = this.getUnapprovedMessages();\n const unapprovedMessagesCount = this.getUnapprovedMessagesCount();\n this.update({ unapprovedMessages, unapprovedMessagesCount });\n this.hub.emit('updateBadge');\n }\n\n /**\n * Updates the status of a Message in this.messages.\n *\n * @param messageId - The id of the Message to update.\n * @param status - The new status of the Message.\n */\n protected setMessageStatus(messageId: string, status: string) {\n const message = this.getMessage(messageId);\n if (!message) {\n throw new Error(`${this.name}: Message not found for id: ${messageId}.`);\n }\n message.status = status;\n this.updateMessage(message);\n this.hub.emit(`${messageId}:${status}`, message);\n if (status === 'rejected' || status === 'signed' || status === 'errored') {\n this.hub.emit(`${messageId}:finished`, message);\n }\n }\n\n /**\n * Sets a Message in this.messages to the passed Message if the ids are equal.\n * Then saves the unapprovedMessage list to storage.\n *\n * @param message - A Message that will replace an existing Message (with the id) in this.messages.\n */\n protected updateMessage(message: M) {\n const index = this.messages.findIndex((msg) => message.id === msg.id);\n /* istanbul ignore next */\n if (index !== -1) {\n this.messages[index] = message;\n }\n this.saveMessageList();\n }\n\n /**\n * EventEmitter instance used to listen to specific message events\n */\n hub = new EventEmitter();\n\n /**\n * Name of this controller used during composition\n */\n override name = 'AbstractMessageManager';\n\n /**\n * Creates an AbstractMessageManager instance.\n *\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n config?: Partial,\n state?: Partial>,\n ) {\n super(config, state);\n this.defaultState = {\n unapprovedMessages: {},\n unapprovedMessagesCount: 0,\n };\n this.messages = [];\n this.initialize();\n }\n\n /**\n * A getter for the number of 'unapproved' Messages in this.messages.\n *\n * @returns The number of 'unapproved' Messages in this.messages.\n */\n getUnapprovedMessagesCount() {\n return Object.keys(this.getUnapprovedMessages()).length;\n }\n\n /**\n * A getter for the 'unapproved' Messages in state messages.\n *\n * @returns An index of Message ids to Messages, for all 'unapproved' Messages in this.messages.\n */\n getUnapprovedMessages() {\n return this.messages\n .filter((message) => message.status === 'unapproved')\n .reduce((result: { [key: string]: M }, message: M) => {\n result[message.id] = message;\n return result;\n }, {}) as { [key: string]: M };\n }\n\n /**\n * Adds a passed Message to this.messages, and calls this.saveMessageList() to save\n * the unapproved Messages from that list to this.messages.\n *\n * @param message - The Message to add to this.messages.\n */\n addMessage(message: M) {\n this.messages.push(message);\n this.saveMessageList();\n }\n\n /**\n * Returns a specified Message.\n *\n * @param messageId - The id of the Message to get.\n * @returns The Message with the id that matches the passed messageId, or undefined\n * if no Message has that id.\n */\n getMessage(messageId: string) {\n return this.messages.find((message) => message.id === messageId);\n }\n\n /**\n * Approves a Message. Sets the message status via a call to this.setMessageStatusApproved,\n * and returns a promise with any the message params modified for proper signing.\n *\n * @param messageParams - The messageParams to be used when signing method is called,\n * plus data added by MetaMask.\n * @returns Promise resolving to the messageParams with the metamaskId property removed.\n */\n approveMessage(messageParams: PM): Promise

{\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n this.setMessageStatusApproved(messageParams.metamaskId);\n return this.prepMessageForSigning(messageParams);\n }\n\n /**\n * Sets a Message status to 'approved' via a call to this.setMessageStatus.\n *\n * @param messageId - The id of the Message to approve.\n */\n setMessageStatusApproved(messageId: string) {\n this.setMessageStatus(messageId, 'approved');\n }\n\n /**\n * Sets a Message status to 'signed' via a call to this.setMessageStatus and updates\n * that Message in this.messages by adding the raw signature data of the signature\n * request to the Message.\n *\n * @param messageId - The id of the Message to sign.\n * @param rawSig - The raw data of the signature request.\n */\n setMessageStatusSigned(messageId: string, rawSig: string) {\n const message = this.getMessage(messageId);\n /* istanbul ignore if */\n if (!message) {\n return;\n }\n message.rawSig = rawSig;\n this.updateMessage(message);\n this.setMessageStatus(messageId, 'signed');\n }\n\n /**\n * Removes the metamaskId property from passed messageParams and returns a promise which\n * resolves the updated messageParams\n *\n * @param messageParams - The messageParams to modify\n * @returns Promise resolving to the messageParams with the metamaskId property removed\n */\n abstract prepMessageForSigning(messageParams: PM): Promise

;\n\n /**\n * Sets a Message status to 'rejected' via a call to this.setMessageStatus.\n *\n * @param messageId - The id of the Message to reject.\n */\n rejectMessage(messageId: string) {\n this.setMessageStatus(messageId, 'rejected');\n }\n}\n\nexport default AbstractMessageManager;\n"]} \ No newline at end of file diff --git a/dist/message-manager/MessageManager.d.ts b/dist/message-manager/MessageManager.d.ts new file mode 100644 index 0000000000..c8fb2fbe01 --- /dev/null +++ b/dist/message-manager/MessageManager.d.ts @@ -0,0 +1,77 @@ +import { AbstractMessageManager, AbstractMessage, AbstractMessageParams, AbstractMessageParamsMetamask, OriginalRequest } from './AbstractMessageManager'; +/** + * @type Message + * + * Represents and contains data about a 'eth_sign' type signature request. + * These are created when a signature for an eth_sign call is requested. + * @property id - An id to track and identify the message object + * @property messageParams - The parameters to pass to the eth_sign method once the signature request is approved + * @property type - The json-prc signing method for which a signature request has been made. + * A 'Message' which always has a 'eth_sign' type + * @property rawSig - Raw data of the signature request + */ +export interface Message extends AbstractMessage { + messageParams: MessageParams; +} +/** + * @type PersonalMessageParams + * + * Represents the parameters to pass to the eth_sign method once the signature request is approved. + * @property data - A hex string conversion of the raw buffer data of the signature request + * @property from - Address to sign this message from + * @property origin? - Added for request origin identification + */ +export interface MessageParams extends AbstractMessageParams { + data: string; +} +/** + * @type MessageParamsMetamask + * + * Represents the parameters to pass to the eth_sign method once the signature request is approved + * plus data added by MetaMask. + * @property metamaskId - Added for tracking and identification within MetaMask + * @property data - A hex string conversion of the raw buffer data of the signature request + * @property from - Address to sign this message from + * @property origin? - Added for request origin identification + */ +export interface MessageParamsMetamask extends AbstractMessageParamsMetamask { + data: string; +} +/** + * Controller in charge of managing - storing, adding, removing, updating - Messages. + */ +export declare class MessageManager extends AbstractMessageManager { + /** + * Name of this controller used during composition + */ + name: string; + /** + * Creates a new Message with an 'unapproved' status using the passed messageParams. + * this.addMessage is called to add the new Message to this.messages, and to save the unapproved Messages. + * + * @param messageParams - The params for the eth_sign call to be made after the message is approved. + * @param req - The original request object possibly containing the origin. + * @returns Promise resolving to the raw data of the signature request. + */ + addUnapprovedMessageAsync(messageParams: MessageParams, req?: OriginalRequest): Promise; + /** + * Creates a new Message with an 'unapproved' status using the passed messageParams. + * this.addMessage is called to add the new Message to this.messages, and to save the + * unapproved Messages. + * + * @param messageParams - The params for the eth_sign call to be made after the message + * is approved. + * @param req - The original request object possibly containing the origin. + * @returns The id of the newly created message. + */ + addUnapprovedMessage(messageParams: MessageParams, req?: OriginalRequest): string; + /** + * Removes the metamaskId property from passed messageParams and returns a promise which + * resolves the updated messageParams. + * + * @param messageParams - The messageParams to modify. + * @returns Promise resolving to the messageParams with the metamaskId property removed. + */ + prepMessageForSigning(messageParams: MessageParamsMetamask): Promise; +} +export default MessageManager; diff --git a/dist/message-manager/MessageManager.js b/dist/message-manager/MessageManager.js new file mode 100644 index 0000000000..a89a95b46d --- /dev/null +++ b/dist/message-manager/MessageManager.js @@ -0,0 +1,83 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MessageManager = void 0; +const uuid_1 = require("uuid"); +const util_1 = require("../util"); +const AbstractMessageManager_1 = require("./AbstractMessageManager"); +/** + * Controller in charge of managing - storing, adding, removing, updating - Messages. + */ +class MessageManager extends AbstractMessageManager_1.AbstractMessageManager { + constructor() { + super(...arguments); + /** + * Name of this controller used during composition + */ + this.name = 'MessageManager'; + } + /** + * Creates a new Message with an 'unapproved' status using the passed messageParams. + * this.addMessage is called to add the new Message to this.messages, and to save the unapproved Messages. + * + * @param messageParams - The params for the eth_sign call to be made after the message is approved. + * @param req - The original request object possibly containing the origin. + * @returns Promise resolving to the raw data of the signature request. + */ + addUnapprovedMessageAsync(messageParams, req) { + return new Promise((resolve, reject) => { + (0, util_1.validateSignMessageData)(messageParams); + const messageId = this.addUnapprovedMessage(messageParams, req); + this.hub.once(`${messageId}:finished`, (data) => { + switch (data.status) { + case 'signed': + return resolve(data.rawSig); + case 'rejected': + return reject(new Error('MetaMask Message Signature: User denied message signature.')); + default: + return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(messageParams)}`)); + } + }); + }); + } + /** + * Creates a new Message with an 'unapproved' status using the passed messageParams. + * this.addMessage is called to add the new Message to this.messages, and to save the + * unapproved Messages. + * + * @param messageParams - The params for the eth_sign call to be made after the message + * is approved. + * @param req - The original request object possibly containing the origin. + * @returns The id of the newly created message. + */ + addUnapprovedMessage(messageParams, req) { + if (req) { + messageParams.origin = req.origin; + } + messageParams.data = (0, util_1.normalizeMessageData)(messageParams.data); + const messageId = (0, uuid_1.v1)(); + const messageData = { + id: messageId, + messageParams, + status: 'unapproved', + time: Date.now(), + type: 'eth_sign', + }; + this.addMessage(messageData); + this.hub.emit(`unapprovedMessage`, Object.assign(Object.assign({}, messageParams), { metamaskId: messageId })); + return messageId; + } + /** + * Removes the metamaskId property from passed messageParams and returns a promise which + * resolves the updated messageParams. + * + * @param messageParams - The messageParams to modify. + * @returns Promise resolving to the messageParams with the metamaskId property removed. + */ + prepMessageForSigning(messageParams) { + delete messageParams.metamaskId; + return Promise.resolve(messageParams); + } +} +exports.MessageManager = MessageManager; +exports.default = MessageManager; +//# sourceMappingURL=MessageManager.js.map \ No newline at end of file diff --git a/dist/message-manager/MessageManager.js.map b/dist/message-manager/MessageManager.js.map new file mode 100644 index 0000000000..56fb1bc15c --- /dev/null +++ b/dist/message-manager/MessageManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"MessageManager.js","sourceRoot":"","sources":["../../src/message-manager/MessageManager.ts"],"names":[],"mappings":";;;AAAA,+BAAoC;AACpC,kCAAwE;AACxE,qEAMkC;AA2ClC;;GAEG;AACH,MAAa,cAAe,SAAQ,+CAInC;IAJD;;QAKE;;WAEG;QACM,SAAI,GAAG,gBAAgB,CAAC;IAoFnC,CAAC;IAlFC;;;;;;;OAOG;IACH,yBAAyB,CACvB,aAA4B,EAC5B,GAAqB;QAErB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAA,8BAAuB,EAAC,aAAa,CAAC,CAAC;YACvC,MAAM,SAAS,GAAG,IAAI,CAAC,oBAAoB,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;YAChE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,SAAS,WAAW,EAAE,CAAC,IAAa,EAAE,EAAE;gBACvD,QAAQ,IAAI,CAAC,MAAM,EAAE;oBACnB,KAAK,QAAQ;wBACX,OAAO,OAAO,CAAC,IAAI,CAAC,MAAgB,CAAC,CAAC;oBACxC,KAAK,UAAU;wBACb,OAAO,MAAM,CACX,IAAI,KAAK,CACP,4DAA4D,CAC7D,CACF,CAAC;oBACJ;wBACE,OAAO,MAAM,CACX,IAAI,KAAK,CACP,gDAAgD,IAAI,CAAC,SAAS,CAC5D,aAAa,CACd,EAAE,CACJ,CACF,CAAC;iBACL;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;OASG;IACH,oBAAoB,CAAC,aAA4B,EAAE,GAAqB;QACtE,IAAI,GAAG,EAAE;YACP,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;SACnC;QACD,aAAa,CAAC,IAAI,GAAG,IAAA,2BAAoB,EAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QAC9D,MAAM,SAAS,GAAG,IAAA,SAAM,GAAE,CAAC;QAC3B,MAAM,WAAW,GAAY;YAC3B,EAAE,EAAE,SAAS;YACb,aAAa;YACb,MAAM,EAAE,YAAY;YACpB,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE;YAChB,IAAI,EAAE,UAAU;SACjB,CAAC;QACF,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;QAC7B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,mBAAmB,kCAC5B,aAAa,GACb,EAAE,UAAU,EAAE,SAAS,EAAE,EAC5B,CAAC;QACH,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;;;;;OAMG;IACH,qBAAqB,CACnB,aAAoC;QAEpC,OAAO,aAAa,CAAC,UAAU,CAAC;QAChC,OAAO,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IACxC,CAAC;CACF;AA5FD,wCA4FC;AAED,kBAAe,cAAc,CAAC","sourcesContent":["import { v1 as random } from 'uuid';\nimport { validateSignMessageData, normalizeMessageData } from '../util';\nimport {\n AbstractMessageManager,\n AbstractMessage,\n AbstractMessageParams,\n AbstractMessageParamsMetamask,\n OriginalRequest,\n} from './AbstractMessageManager';\n\n/**\n * @type Message\n *\n * Represents and contains data about a 'eth_sign' type signature request.\n * These are created when a signature for an eth_sign call is requested.\n * @property id - An id to track and identify the message object\n * @property messageParams - The parameters to pass to the eth_sign method once the signature request is approved\n * @property type - The json-prc signing method for which a signature request has been made.\n * A 'Message' which always has a 'eth_sign' type\n * @property rawSig - Raw data of the signature request\n */\nexport interface Message extends AbstractMessage {\n messageParams: MessageParams;\n}\n\n/**\n * @type PersonalMessageParams\n *\n * Represents the parameters to pass to the eth_sign method once the signature request is approved.\n * @property data - A hex string conversion of the raw buffer data of the signature request\n * @property from - Address to sign this message from\n * @property origin? - Added for request origin identification\n */\nexport interface MessageParams extends AbstractMessageParams {\n data: string;\n}\n\n/**\n * @type MessageParamsMetamask\n *\n * Represents the parameters to pass to the eth_sign method once the signature request is approved\n * plus data added by MetaMask.\n * @property metamaskId - Added for tracking and identification within MetaMask\n * @property data - A hex string conversion of the raw buffer data of the signature request\n * @property from - Address to sign this message from\n * @property origin? - Added for request origin identification\n */\nexport interface MessageParamsMetamask extends AbstractMessageParamsMetamask {\n data: string;\n}\n\n/**\n * Controller in charge of managing - storing, adding, removing, updating - Messages.\n */\nexport class MessageManager extends AbstractMessageManager<\n Message,\n MessageParams,\n MessageParamsMetamask\n> {\n /**\n * Name of this controller used during composition\n */\n override name = 'MessageManager';\n\n /**\n * Creates a new Message with an 'unapproved' status using the passed messageParams.\n * this.addMessage is called to add the new Message to this.messages, and to save the unapproved Messages.\n *\n * @param messageParams - The params for the eth_sign call to be made after the message is approved.\n * @param req - The original request object possibly containing the origin.\n * @returns Promise resolving to the raw data of the signature request.\n */\n addUnapprovedMessageAsync(\n messageParams: MessageParams,\n req?: OriginalRequest,\n ): Promise {\n return new Promise((resolve, reject) => {\n validateSignMessageData(messageParams);\n const messageId = this.addUnapprovedMessage(messageParams, req);\n this.hub.once(`${messageId}:finished`, (data: Message) => {\n switch (data.status) {\n case 'signed':\n return resolve(data.rawSig as string);\n case 'rejected':\n return reject(\n new Error(\n 'MetaMask Message Signature: User denied message signature.',\n ),\n );\n default:\n return reject(\n new Error(\n `MetaMask Message Signature: Unknown problem: ${JSON.stringify(\n messageParams,\n )}`,\n ),\n );\n }\n });\n });\n }\n\n /**\n * Creates a new Message with an 'unapproved' status using the passed messageParams.\n * this.addMessage is called to add the new Message to this.messages, and to save the\n * unapproved Messages.\n *\n * @param messageParams - The params for the eth_sign call to be made after the message\n * is approved.\n * @param req - The original request object possibly containing the origin.\n * @returns The id of the newly created message.\n */\n addUnapprovedMessage(messageParams: MessageParams, req?: OriginalRequest) {\n if (req) {\n messageParams.origin = req.origin;\n }\n messageParams.data = normalizeMessageData(messageParams.data);\n const messageId = random();\n const messageData: Message = {\n id: messageId,\n messageParams,\n status: 'unapproved',\n time: Date.now(),\n type: 'eth_sign',\n };\n this.addMessage(messageData);\n this.hub.emit(`unapprovedMessage`, {\n ...messageParams,\n ...{ metamaskId: messageId },\n });\n return messageId;\n }\n\n /**\n * Removes the metamaskId property from passed messageParams and returns a promise which\n * resolves the updated messageParams.\n *\n * @param messageParams - The messageParams to modify.\n * @returns Promise resolving to the messageParams with the metamaskId property removed.\n */\n prepMessageForSigning(\n messageParams: MessageParamsMetamask,\n ): Promise {\n delete messageParams.metamaskId;\n return Promise.resolve(messageParams);\n }\n}\n\nexport default MessageManager;\n"]} \ No newline at end of file diff --git a/dist/message-manager/PersonalMessageManager.d.ts b/dist/message-manager/PersonalMessageManager.d.ts new file mode 100644 index 0000000000..999eeb3614 --- /dev/null +++ b/dist/message-manager/PersonalMessageManager.d.ts @@ -0,0 +1,77 @@ +import { AbstractMessageManager, AbstractMessage, AbstractMessageParams, AbstractMessageParamsMetamask, OriginalRequest } from './AbstractMessageManager'; +/** + * @type Message + * + * Represents and contains data about a 'personal_sign' type signature request. + * These are created when a signature for a personal_sign call is requested. + * @property id - An id to track and identify the message object + * @property messageParams - The parameters to pass to the personal_sign method once the signature request is approved + * @property type - The json-prc signing method for which a signature request has been made. + * A 'Message' which always has a 'personal_sign' type + * @property rawSig - Raw data of the signature request + */ +export interface PersonalMessage extends AbstractMessage { + messageParams: PersonalMessageParams; +} +/** + * @type PersonalMessageParams + * + * Represents the parameters to pass to the personal_sign method once the signature request is approved. + * @property data - A hex string conversion of the raw buffer data of the signature request + * @property from - Address to sign this message from + * @property origin? - Added for request origin identification + */ +export interface PersonalMessageParams extends AbstractMessageParams { + data: string; +} +/** + * @type MessageParamsMetamask + * + * Represents the parameters to pass to the personal_sign method once the signature request is approved + * plus data added by MetaMask. + * @property metamaskId - Added for tracking and identification within MetaMask + * @property data - A hex string conversion of the raw buffer data of the signature request + * @property from - Address to sign this message from + * @property origin? - Added for request origin identification + */ +export interface PersonalMessageParamsMetamask extends AbstractMessageParamsMetamask { + data: string; +} +/** + * Controller in charge of managing - storing, adding, removing, updating - Messages. + */ +export declare class PersonalMessageManager extends AbstractMessageManager { + /** + * Name of this controller used during composition + */ + name: string; + /** + * Creates a new Message with an 'unapproved' status using the passed messageParams. + * this.addMessage is called to add the new Message to this.messages, and to save the unapproved Messages. + * + * @param messageParams - The params for the personal_sign call to be made after the message is approved. + * @param req - The original request object possibly containing the origin. + * @returns Promise resolving to the raw data of the signature request. + */ + addUnapprovedMessageAsync(messageParams: PersonalMessageParams, req?: OriginalRequest): Promise; + /** + * Creates a new Message with an 'unapproved' status using the passed messageParams. + * this.addMessage is called to add the new Message to this.messages, and to save the + * unapproved Messages. + * + * @param messageParams - The params for the personal_sign call to be made after the message + * is approved. + * @param req - The original request object possibly containing the origin. + * @returns The id of the newly created message. + */ + addUnapprovedMessage(messageParams: PersonalMessageParams, req?: OriginalRequest): string; + /** + * Removes the metamaskId property from passed messageParams and returns a promise which + * resolves the updated messageParams. + * + * @param messageParams - The messageParams to modify. + * @returns Promise resolving to the messageParams with the metamaskId property removed. + */ + prepMessageForSigning(messageParams: PersonalMessageParamsMetamask): Promise; +} +export default PersonalMessageManager; diff --git a/dist/message-manager/PersonalMessageManager.js b/dist/message-manager/PersonalMessageManager.js new file mode 100644 index 0000000000..12f7215c3b --- /dev/null +++ b/dist/message-manager/PersonalMessageManager.js @@ -0,0 +1,83 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PersonalMessageManager = void 0; +const uuid_1 = require("uuid"); +const util_1 = require("../util"); +const AbstractMessageManager_1 = require("./AbstractMessageManager"); +/** + * Controller in charge of managing - storing, adding, removing, updating - Messages. + */ +class PersonalMessageManager extends AbstractMessageManager_1.AbstractMessageManager { + constructor() { + super(...arguments); + /** + * Name of this controller used during composition + */ + this.name = 'PersonalMessageManager'; + } + /** + * Creates a new Message with an 'unapproved' status using the passed messageParams. + * this.addMessage is called to add the new Message to this.messages, and to save the unapproved Messages. + * + * @param messageParams - The params for the personal_sign call to be made after the message is approved. + * @param req - The original request object possibly containing the origin. + * @returns Promise resolving to the raw data of the signature request. + */ + addUnapprovedMessageAsync(messageParams, req) { + return new Promise((resolve, reject) => { + (0, util_1.validateSignMessageData)(messageParams); + const messageId = this.addUnapprovedMessage(messageParams, req); + this.hub.once(`${messageId}:finished`, (data) => { + switch (data.status) { + case 'signed': + return resolve(data.rawSig); + case 'rejected': + return reject(new Error('MetaMask Personal Message Signature: User denied message signature.')); + default: + return reject(new Error(`MetaMask Personal Message Signature: Unknown problem: ${JSON.stringify(messageParams)}`)); + } + }); + }); + } + /** + * Creates a new Message with an 'unapproved' status using the passed messageParams. + * this.addMessage is called to add the new Message to this.messages, and to save the + * unapproved Messages. + * + * @param messageParams - The params for the personal_sign call to be made after the message + * is approved. + * @param req - The original request object possibly containing the origin. + * @returns The id of the newly created message. + */ + addUnapprovedMessage(messageParams, req) { + if (req) { + messageParams.origin = req.origin; + } + messageParams.data = (0, util_1.normalizeMessageData)(messageParams.data); + const messageId = (0, uuid_1.v1)(); + const messageData = { + id: messageId, + messageParams, + status: 'unapproved', + time: Date.now(), + type: 'personal_sign', + }; + this.addMessage(messageData); + this.hub.emit(`unapprovedMessage`, Object.assign(Object.assign({}, messageParams), { metamaskId: messageId })); + return messageId; + } + /** + * Removes the metamaskId property from passed messageParams and returns a promise which + * resolves the updated messageParams. + * + * @param messageParams - The messageParams to modify. + * @returns Promise resolving to the messageParams with the metamaskId property removed. + */ + prepMessageForSigning(messageParams) { + delete messageParams.metamaskId; + return Promise.resolve(messageParams); + } +} +exports.PersonalMessageManager = PersonalMessageManager; +exports.default = PersonalMessageManager; +//# sourceMappingURL=PersonalMessageManager.js.map \ No newline at end of file diff --git a/dist/message-manager/PersonalMessageManager.js.map b/dist/message-manager/PersonalMessageManager.js.map new file mode 100644 index 0000000000..ef1f8134c2 --- /dev/null +++ b/dist/message-manager/PersonalMessageManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"PersonalMessageManager.js","sourceRoot":"","sources":["../../src/message-manager/PersonalMessageManager.ts"],"names":[],"mappings":";;;AAAA,+BAAoC;AACpC,kCAAwE;AACxE,qEAMkC;AA4ClC;;GAEG;AACH,MAAa,sBAAuB,SAAQ,+CAI3C;IAJD;;QAKE;;WAEG;QACM,SAAI,GAAG,wBAAwB,CAAC;IAuF3C,CAAC;IArFC;;;;;;;OAOG;IACH,yBAAyB,CACvB,aAAoC,EACpC,GAAqB;QAErB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAA,8BAAuB,EAAC,aAAa,CAAC,CAAC;YACvC,MAAM,SAAS,GAAG,IAAI,CAAC,oBAAoB,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;YAChE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,SAAS,WAAW,EAAE,CAAC,IAAqB,EAAE,EAAE;gBAC/D,QAAQ,IAAI,CAAC,MAAM,EAAE;oBACnB,KAAK,QAAQ;wBACX,OAAO,OAAO,CAAC,IAAI,CAAC,MAAgB,CAAC,CAAC;oBACxC,KAAK,UAAU;wBACb,OAAO,MAAM,CACX,IAAI,KAAK,CACP,qEAAqE,CACtE,CACF,CAAC;oBACJ;wBACE,OAAO,MAAM,CACX,IAAI,KAAK,CACP,yDAAyD,IAAI,CAAC,SAAS,CACrE,aAAa,CACd,EAAE,CACJ,CACF,CAAC;iBACL;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;OASG;IACH,oBAAoB,CAClB,aAAoC,EACpC,GAAqB;QAErB,IAAI,GAAG,EAAE;YACP,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;SACnC;QACD,aAAa,CAAC,IAAI,GAAG,IAAA,2BAAoB,EAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QAC9D,MAAM,SAAS,GAAG,IAAA,SAAM,GAAE,CAAC;QAC3B,MAAM,WAAW,GAAoB;YACnC,EAAE,EAAE,SAAS;YACb,aAAa;YACb,MAAM,EAAE,YAAY;YACpB,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE;YAChB,IAAI,EAAE,eAAe;SACtB,CAAC;QACF,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;QAC7B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,mBAAmB,kCAC5B,aAAa,GACb,EAAE,UAAU,EAAE,SAAS,EAAE,EAC5B,CAAC;QACH,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;;;;;OAMG;IACH,qBAAqB,CACnB,aAA4C;QAE5C,OAAO,aAAa,CAAC,UAAU,CAAC;QAChC,OAAO,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IACxC,CAAC;CACF;AA/FD,wDA+FC;AAED,kBAAe,sBAAsB,CAAC","sourcesContent":["import { v1 as random } from 'uuid';\nimport { validateSignMessageData, normalizeMessageData } from '../util';\nimport {\n AbstractMessageManager,\n AbstractMessage,\n AbstractMessageParams,\n AbstractMessageParamsMetamask,\n OriginalRequest,\n} from './AbstractMessageManager';\n\n/**\n * @type Message\n *\n * Represents and contains data about a 'personal_sign' type signature request.\n * These are created when a signature for a personal_sign call is requested.\n * @property id - An id to track and identify the message object\n * @property messageParams - The parameters to pass to the personal_sign method once the signature request is approved\n * @property type - The json-prc signing method for which a signature request has been made.\n * A 'Message' which always has a 'personal_sign' type\n * @property rawSig - Raw data of the signature request\n */\nexport interface PersonalMessage extends AbstractMessage {\n messageParams: PersonalMessageParams;\n}\n\n/**\n * @type PersonalMessageParams\n *\n * Represents the parameters to pass to the personal_sign method once the signature request is approved.\n * @property data - A hex string conversion of the raw buffer data of the signature request\n * @property from - Address to sign this message from\n * @property origin? - Added for request origin identification\n */\nexport interface PersonalMessageParams extends AbstractMessageParams {\n data: string;\n}\n\n/**\n * @type MessageParamsMetamask\n *\n * Represents the parameters to pass to the personal_sign method once the signature request is approved\n * plus data added by MetaMask.\n * @property metamaskId - Added for tracking and identification within MetaMask\n * @property data - A hex string conversion of the raw buffer data of the signature request\n * @property from - Address to sign this message from\n * @property origin? - Added for request origin identification\n */\nexport interface PersonalMessageParamsMetamask\n extends AbstractMessageParamsMetamask {\n data: string;\n}\n\n/**\n * Controller in charge of managing - storing, adding, removing, updating - Messages.\n */\nexport class PersonalMessageManager extends AbstractMessageManager<\n PersonalMessage,\n PersonalMessageParams,\n PersonalMessageParamsMetamask\n> {\n /**\n * Name of this controller used during composition\n */\n override name = 'PersonalMessageManager';\n\n /**\n * Creates a new Message with an 'unapproved' status using the passed messageParams.\n * this.addMessage is called to add the new Message to this.messages, and to save the unapproved Messages.\n *\n * @param messageParams - The params for the personal_sign call to be made after the message is approved.\n * @param req - The original request object possibly containing the origin.\n * @returns Promise resolving to the raw data of the signature request.\n */\n addUnapprovedMessageAsync(\n messageParams: PersonalMessageParams,\n req?: OriginalRequest,\n ): Promise {\n return new Promise((resolve, reject) => {\n validateSignMessageData(messageParams);\n const messageId = this.addUnapprovedMessage(messageParams, req);\n this.hub.once(`${messageId}:finished`, (data: PersonalMessage) => {\n switch (data.status) {\n case 'signed':\n return resolve(data.rawSig as string);\n case 'rejected':\n return reject(\n new Error(\n 'MetaMask Personal Message Signature: User denied message signature.',\n ),\n );\n default:\n return reject(\n new Error(\n `MetaMask Personal Message Signature: Unknown problem: ${JSON.stringify(\n messageParams,\n )}`,\n ),\n );\n }\n });\n });\n }\n\n /**\n * Creates a new Message with an 'unapproved' status using the passed messageParams.\n * this.addMessage is called to add the new Message to this.messages, and to save the\n * unapproved Messages.\n *\n * @param messageParams - The params for the personal_sign call to be made after the message\n * is approved.\n * @param req - The original request object possibly containing the origin.\n * @returns The id of the newly created message.\n */\n addUnapprovedMessage(\n messageParams: PersonalMessageParams,\n req?: OriginalRequest,\n ) {\n if (req) {\n messageParams.origin = req.origin;\n }\n messageParams.data = normalizeMessageData(messageParams.data);\n const messageId = random();\n const messageData: PersonalMessage = {\n id: messageId,\n messageParams,\n status: 'unapproved',\n time: Date.now(),\n type: 'personal_sign',\n };\n this.addMessage(messageData);\n this.hub.emit(`unapprovedMessage`, {\n ...messageParams,\n ...{ metamaskId: messageId },\n });\n return messageId;\n }\n\n /**\n * Removes the metamaskId property from passed messageParams and returns a promise which\n * resolves the updated messageParams.\n *\n * @param messageParams - The messageParams to modify.\n * @returns Promise resolving to the messageParams with the metamaskId property removed.\n */\n prepMessageForSigning(\n messageParams: PersonalMessageParamsMetamask,\n ): Promise {\n delete messageParams.metamaskId;\n return Promise.resolve(messageParams);\n }\n}\n\nexport default PersonalMessageManager;\n"]} \ No newline at end of file diff --git a/dist/message-manager/TypedMessageManager.d.ts b/dist/message-manager/TypedMessageManager.d.ts new file mode 100644 index 0000000000..a47573c075 --- /dev/null +++ b/dist/message-manager/TypedMessageManager.d.ts @@ -0,0 +1,100 @@ +import { AbstractMessageManager, AbstractMessage, AbstractMessageParams, AbstractMessageParamsMetamask, OriginalRequest } from './AbstractMessageManager'; +/** + * @type TypedMessage + * + * Represents and contains data about an 'eth_signTypedData' type signature request. + * These are created when a signature for an eth_signTypedData call is requested. + * @property id - An id to track and identify the message object + * @property error - Error corresponding to eth_signTypedData error in failure case + * @property messageParams - The parameters to pass to the eth_signTypedData method once + * the signature request is approved + * @property type - The json-prc signing method for which a signature request has been made. + * A 'TypedMessage' which always has a 'eth_signTypedData' type + * @property rawSig - Raw data of the signature request + */ +export interface TypedMessage extends AbstractMessage { + error?: string; + messageParams: TypedMessageParams; + time: number; + status: string; + type: string; + rawSig?: string; +} +/** + * @type TypedMessageParams + * + * Represents the parameters to pass to the eth_signTypedData method once the signature request is approved. + * @property data - A hex string conversion of the raw buffer or an object containing data of the signature + * request depending on version + * @property from - Address to sign this message from + * @property origin? - Added for request origin identification + */ +export interface TypedMessageParams extends AbstractMessageParams { + data: Record[] | string; +} +/** + * @type TypedMessageParamsMetamask + * + * Represents the parameters to pass to the eth_signTypedData method once the signature request is approved + * plus data added by MetaMask. + * @property metamaskId - Added for tracking and identification within MetaMask + * @property data - A hex string conversion of the raw buffer or an object containing data of the signature + * request depending on version + * @property error? - Added for message errored + * @property from - Address to sign this message from + * @property origin? - Added for request origin identification + * @property version - Compatibility version EIP712 + */ +export interface TypedMessageParamsMetamask extends AbstractMessageParamsMetamask { + data: Record[] | string; + metamaskId?: string; + error?: string; + version?: string; +} +/** + * Controller in charge of managing - storing, adding, removing, updating - TypedMessages. + */ +export declare class TypedMessageManager extends AbstractMessageManager { + /** + * Name of this controller used during composition + */ + name: string; + /** + * Creates a new TypedMessage with an 'unapproved' status using the passed messageParams. + * this.addMessage is called to add the new TypedMessage to this.messages, and to save the unapproved TypedMessages. + * + * @param messageParams - The params for the eth_signTypedData call to be made after the message is approved. + * @param version - Compatibility version EIP712. + * @param req - The original request object possibly containing the origin. + * @returns Promise resolving to the raw data of the signature request. + */ + addUnapprovedMessageAsync(messageParams: TypedMessageParams, version: string, req?: OriginalRequest): Promise; + /** + * Creates a new TypedMessage with an 'unapproved' status using the passed messageParams. + * this.addMessage is called to add the new TypedMessage to this.messages, and to save the + * unapproved TypedMessages. + * + * @param messageParams - The params for the 'eth_signTypedData' call to be made after the message + * is approved. + * @param version - Compatibility version EIP712. + * @param req - The original request object possibly containing the origin. + * @returns The id of the newly created TypedMessage. + */ + addUnapprovedMessage(messageParams: TypedMessageParams, version: string, req?: OriginalRequest): string; + /** + * Sets a TypedMessage status to 'errored' via a call to this.setMessageStatus. + * + * @param messageId - The id of the TypedMessage to error. + * @param error - The error to be included in TypedMessage. + */ + setMessageStatusErrored(messageId: string, error: string): void; + /** + * Removes the metamaskId and version properties from passed messageParams and returns a promise which + * resolves the updated messageParams. + * + * @param messageParams - The messageParams to modify. + * @returns Promise resolving to the messageParams with the metamaskId and version properties removed. + */ + prepMessageForSigning(messageParams: TypedMessageParamsMetamask): Promise; +} +export default TypedMessageManager; diff --git a/dist/message-manager/TypedMessageManager.js b/dist/message-manager/TypedMessageManager.js new file mode 100644 index 0000000000..f22d79778d --- /dev/null +++ b/dist/message-manager/TypedMessageManager.js @@ -0,0 +1,109 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TypedMessageManager = void 0; +const uuid_1 = require("uuid"); +const util_1 = require("../util"); +const AbstractMessageManager_1 = require("./AbstractMessageManager"); +/** + * Controller in charge of managing - storing, adding, removing, updating - TypedMessages. + */ +class TypedMessageManager extends AbstractMessageManager_1.AbstractMessageManager { + constructor() { + super(...arguments); + /** + * Name of this controller used during composition + */ + this.name = 'TypedMessageManager'; + } + /** + * Creates a new TypedMessage with an 'unapproved' status using the passed messageParams. + * this.addMessage is called to add the new TypedMessage to this.messages, and to save the unapproved TypedMessages. + * + * @param messageParams - The params for the eth_signTypedData call to be made after the message is approved. + * @param version - Compatibility version EIP712. + * @param req - The original request object possibly containing the origin. + * @returns Promise resolving to the raw data of the signature request. + */ + addUnapprovedMessageAsync(messageParams, version, req) { + return new Promise((resolve, reject) => { + if (version === 'V1') { + (0, util_1.validateTypedSignMessageDataV1)(messageParams); + } + if (version === 'V3') { + (0, util_1.validateTypedSignMessageDataV3)(messageParams); + } + const messageId = this.addUnapprovedMessage(messageParams, version, req); + this.hub.once(`${messageId}:finished`, (data) => { + switch (data.status) { + case 'signed': + return resolve(data.rawSig); + case 'rejected': + return reject(new Error('MetaMask Typed Message Signature: User denied message signature.')); + case 'errored': + return reject(new Error(`MetaMask Typed Message Signature: ${data.error}`)); + default: + return reject(new Error(`MetaMask Typed Message Signature: Unknown problem: ${JSON.stringify(messageParams)}`)); + } + }); + }); + } + /** + * Creates a new TypedMessage with an 'unapproved' status using the passed messageParams. + * this.addMessage is called to add the new TypedMessage to this.messages, and to save the + * unapproved TypedMessages. + * + * @param messageParams - The params for the 'eth_signTypedData' call to be made after the message + * is approved. + * @param version - Compatibility version EIP712. + * @param req - The original request object possibly containing the origin. + * @returns The id of the newly created TypedMessage. + */ + addUnapprovedMessage(messageParams, version, req) { + const messageId = (0, uuid_1.v1)(); + const messageParamsMetamask = Object.assign(Object.assign({}, messageParams), { metamaskId: messageId, version }); + if (req) { + messageParams.origin = req.origin; + } + const messageData = { + id: messageId, + messageParams, + status: 'unapproved', + time: Date.now(), + type: 'eth_signTypedData', + }; + this.addMessage(messageData); + this.hub.emit(`unapprovedMessage`, messageParamsMetamask); + return messageId; + } + /** + * Sets a TypedMessage status to 'errored' via a call to this.setMessageStatus. + * + * @param messageId - The id of the TypedMessage to error. + * @param error - The error to be included in TypedMessage. + */ + setMessageStatusErrored(messageId, error) { + const message = this.getMessage(messageId); + /* istanbul ignore if */ + if (!message) { + return; + } + message.error = error; + this.updateMessage(message); + this.setMessageStatus(messageId, 'errored'); + } + /** + * Removes the metamaskId and version properties from passed messageParams and returns a promise which + * resolves the updated messageParams. + * + * @param messageParams - The messageParams to modify. + * @returns Promise resolving to the messageParams with the metamaskId and version properties removed. + */ + prepMessageForSigning(messageParams) { + delete messageParams.metamaskId; + delete messageParams.version; + return Promise.resolve(messageParams); + } +} +exports.TypedMessageManager = TypedMessageManager; +exports.default = TypedMessageManager; +//# sourceMappingURL=TypedMessageManager.js.map \ No newline at end of file diff --git a/dist/message-manager/TypedMessageManager.js.map b/dist/message-manager/TypedMessageManager.js.map new file mode 100644 index 0000000000..710b94731e --- /dev/null +++ b/dist/message-manager/TypedMessageManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"TypedMessageManager.js","sourceRoot":"","sources":["../../src/message-manager/TypedMessageManager.ts"],"names":[],"mappings":"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","sourcesContent":["import { v1 as random } from 'uuid';\nimport {\n validateTypedSignMessageDataV3,\n validateTypedSignMessageDataV1,\n} from '../util';\nimport {\n AbstractMessageManager,\n AbstractMessage,\n AbstractMessageParams,\n AbstractMessageParamsMetamask,\n OriginalRequest,\n} from './AbstractMessageManager';\n\n/**\n * @type TypedMessage\n *\n * Represents and contains data about an 'eth_signTypedData' type signature request.\n * These are created when a signature for an eth_signTypedData call is requested.\n * @property id - An id to track and identify the message object\n * @property error - Error corresponding to eth_signTypedData error in failure case\n * @property messageParams - The parameters to pass to the eth_signTypedData method once\n * the signature request is approved\n * @property type - The json-prc signing method for which a signature request has been made.\n * A 'TypedMessage' which always has a 'eth_signTypedData' type\n * @property rawSig - Raw data of the signature request\n */\nexport interface TypedMessage extends AbstractMessage {\n error?: string;\n messageParams: TypedMessageParams;\n time: number;\n status: string;\n type: string;\n rawSig?: string;\n}\n\n/**\n * @type TypedMessageParams\n *\n * Represents the parameters to pass to the eth_signTypedData method once the signature request is approved.\n * @property data - A hex string conversion of the raw buffer or an object containing data of the signature\n * request depending on version\n * @property from - Address to sign this message from\n * @property origin? - Added for request origin identification\n */\nexport interface TypedMessageParams extends AbstractMessageParams {\n data: Record[] | string;\n}\n\n/**\n * @type TypedMessageParamsMetamask\n *\n * Represents the parameters to pass to the eth_signTypedData method once the signature request is approved\n * plus data added by MetaMask.\n * @property metamaskId - Added for tracking and identification within MetaMask\n * @property data - A hex string conversion of the raw buffer or an object containing data of the signature\n * request depending on version\n * @property error? - Added for message errored\n * @property from - Address to sign this message from\n * @property origin? - Added for request origin identification\n * @property version - Compatibility version EIP712\n */\nexport interface TypedMessageParamsMetamask\n extends AbstractMessageParamsMetamask {\n data: Record[] | string;\n metamaskId?: string;\n error?: string;\n version?: string;\n}\n\n/**\n * Controller in charge of managing - storing, adding, removing, updating - TypedMessages.\n */\nexport class TypedMessageManager extends AbstractMessageManager<\n TypedMessage,\n TypedMessageParams,\n TypedMessageParamsMetamask\n> {\n /**\n * Name of this controller used during composition\n */\n override name = 'TypedMessageManager';\n\n /**\n * Creates a new TypedMessage with an 'unapproved' status using the passed messageParams.\n * this.addMessage is called to add the new TypedMessage to this.messages, and to save the unapproved TypedMessages.\n *\n * @param messageParams - The params for the eth_signTypedData call to be made after the message is approved.\n * @param version - Compatibility version EIP712.\n * @param req - The original request object possibly containing the origin.\n * @returns Promise resolving to the raw data of the signature request.\n */\n addUnapprovedMessageAsync(\n messageParams: TypedMessageParams,\n version: string,\n req?: OriginalRequest,\n ): Promise {\n return new Promise((resolve, reject) => {\n if (version === 'V1') {\n validateTypedSignMessageDataV1(messageParams);\n }\n\n if (version === 'V3') {\n validateTypedSignMessageDataV3(messageParams);\n }\n const messageId = this.addUnapprovedMessage(messageParams, version, req);\n this.hub.once(`${messageId}:finished`, (data: TypedMessage) => {\n switch (data.status) {\n case 'signed':\n return resolve(data.rawSig as string);\n case 'rejected':\n return reject(\n new Error(\n 'MetaMask Typed Message Signature: User denied message signature.',\n ),\n );\n case 'errored':\n return reject(\n new Error(`MetaMask Typed Message Signature: ${data.error}`),\n );\n default:\n return reject(\n new Error(\n `MetaMask Typed Message Signature: Unknown problem: ${JSON.stringify(\n messageParams,\n )}`,\n ),\n );\n }\n });\n });\n }\n\n /**\n * Creates a new TypedMessage with an 'unapproved' status using the passed messageParams.\n * this.addMessage is called to add the new TypedMessage to this.messages, and to save the\n * unapproved TypedMessages.\n *\n * @param messageParams - The params for the 'eth_signTypedData' call to be made after the message\n * is approved.\n * @param version - Compatibility version EIP712.\n * @param req - The original request object possibly containing the origin.\n * @returns The id of the newly created TypedMessage.\n */\n addUnapprovedMessage(\n messageParams: TypedMessageParams,\n version: string,\n req?: OriginalRequest,\n ) {\n const messageId = random();\n const messageParamsMetamask = {\n ...messageParams,\n metamaskId: messageId,\n version,\n };\n if (req) {\n messageParams.origin = req.origin;\n }\n const messageData: TypedMessage = {\n id: messageId,\n messageParams,\n status: 'unapproved',\n time: Date.now(),\n type: 'eth_signTypedData',\n };\n this.addMessage(messageData);\n this.hub.emit(`unapprovedMessage`, messageParamsMetamask);\n return messageId;\n }\n\n /**\n * Sets a TypedMessage status to 'errored' via a call to this.setMessageStatus.\n *\n * @param messageId - The id of the TypedMessage to error.\n * @param error - The error to be included in TypedMessage.\n */\n setMessageStatusErrored(messageId: string, error: string) {\n const message = this.getMessage(messageId);\n /* istanbul ignore if */\n if (!message) {\n return;\n }\n message.error = error;\n this.updateMessage(message);\n this.setMessageStatus(messageId, 'errored');\n }\n\n /**\n * Removes the metamaskId and version properties from passed messageParams and returns a promise which\n * resolves the updated messageParams.\n *\n * @param messageParams - The messageParams to modify.\n * @returns Promise resolving to the messageParams with the metamaskId and version properties removed.\n */\n prepMessageForSigning(\n messageParams: TypedMessageParamsMetamask,\n ): Promise {\n delete messageParams.metamaskId;\n delete messageParams.version;\n return Promise.resolve(messageParams);\n }\n}\n\nexport default TypedMessageManager;\n"]} \ No newline at end of file diff --git a/dist/network/NetworkController.d.ts b/dist/network/NetworkController.d.ts new file mode 100644 index 0000000000..8c55e27a56 --- /dev/null +++ b/dist/network/NetworkController.d.ts @@ -0,0 +1,126 @@ +import { BaseController, BaseConfig, BaseState } from '../BaseController'; +/** + * Human-readable network name + */ +export declare type NetworkType = 'kovan' | 'localhost' | 'mainnet' | 'rinkeby' | 'goerli' | 'ropsten' | 'rpc' | 'optimism' | 'optimismTest'; +export declare enum NetworksChainId { + mainnet = "1", + kovan = "42", + rinkeby = "4", + goerli = "5", + ropsten = "3", + localhost = "", + rpc = "", + optimism = "10", + optimismTest = "69" +} +/** + * @type ProviderConfig + * + * Configuration passed to web3-provider-engine + * @property rpcTarget - RPC target URL. + * @property type - Human-readable network name. + * @property chainId - Network ID as per EIP-155. + * @property ticker - Currency ticker. + * @property nickname - Personalized network name. + */ +export interface ProviderConfig { + rpcTarget?: string; + type: NetworkType; + chainId: string; + ticker?: string; + nickname?: string; +} +export interface Block { + baseFeePerGas?: string; +} +export interface NetworkProperties { + isEIP1559Compatible?: boolean; +} +/** + * @type NetworkConfig + * + * Network controller configuration + * @property infuraProjectId - an Infura project ID + * @property providerConfig - web3-provider-engine configuration + */ +export interface NetworkConfig extends BaseConfig { + infuraProjectId?: string; + providerConfig: ProviderConfig; +} +/** + * @type NetworkState + * + * Network controller state + * @property network - Network ID as per net_version + * @property isCustomNetwork - Identifies if the network is a custom network + * @property provider - RPC URL and network name provider settings + */ +export interface NetworkState extends BaseState { + network: string; + isCustomNetwork: boolean; + provider: ProviderConfig; + properties: NetworkProperties; +} +/** + * Controller that creates and manages an Ethereum network provider + */ +export declare class NetworkController extends BaseController { + private ethQuery; + private internalProviderConfig; + private mutex; + private initializeProvider; + private refreshNetwork; + private registerProvider; + private setupInfuraProvider; + private getIsCustomNetwork; + private setupStandardProvider; + private updateProvider; + private safelyStopProvider; + private verifyNetwork; + /** + * Name of this controller used during composition + */ + name: string; + /** + * Ethereum provider object for the current network + */ + provider: any; + /** + * Creates a NetworkController instance. + * + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor(config?: Partial, state?: Partial); + /** + * Sets a new configuration for web3-provider-engine. + * + * TODO: Replace this wth a method. + * + * @param providerConfig - The web3-provider-engine configuration. + */ + set providerConfig(providerConfig: ProviderConfig); + get providerConfig(): ProviderConfig; + /** + * Refreshes the current network code. + */ + lookupNetwork(): Promise; + /** + * Convenience method to update provider network type settings. + * + * @param type - Human readable network name. + */ + setProviderType(type: NetworkType): void; + /** + * Convenience method to update provider RPC settings. + * + * @param rpcTarget - The RPC endpoint URL. + * @param chainId - The chain ID as per EIP-155. + * @param ticker - The currency ticker. + * @param nickname - Personalized network name. + */ + setRpcTarget(rpcTarget: string, chainId: string, ticker?: string, nickname?: string): void; + getEIP1559Compatibility(): Promise; +} +export default NetworkController; diff --git a/dist/network/NetworkController.js b/dist/network/NetworkController.js new file mode 100644 index 0000000000..ce1ada7078 --- /dev/null +++ b/dist/network/NetworkController.js @@ -0,0 +1,251 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.NetworkController = exports.NetworksChainId = void 0; +const eth_query_1 = __importDefault(require("eth-query")); +const provider_1 = __importDefault(require("web3-provider-engine/subproviders/provider")); +const createProvider_1 = __importDefault(require("eth-json-rpc-infura/src/createProvider")); +const zero_1 = __importDefault(require("web3-provider-engine/zero")); +const async_mutex_1 = require("async-mutex"); +const BaseController_1 = require("../BaseController"); +const constants_1 = require("../constants"); +var NetworksChainId; +(function (NetworksChainId) { + NetworksChainId["mainnet"] = "1"; + NetworksChainId["kovan"] = "42"; + NetworksChainId["rinkeby"] = "4"; + NetworksChainId["goerli"] = "5"; + NetworksChainId["ropsten"] = "3"; + NetworksChainId["localhost"] = ""; + NetworksChainId["rpc"] = ""; + NetworksChainId["optimism"] = "10"; + NetworksChainId["optimismTest"] = "69"; +})(NetworksChainId = exports.NetworksChainId || (exports.NetworksChainId = {})); +const LOCALHOST_RPC_URL = 'http://localhost:8545'; +/** + * Controller that creates and manages an Ethereum network provider + */ +class NetworkController extends BaseController_1.BaseController { + /** + * Creates a NetworkController instance. + * + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor(config, state) { + super(config, state); + this.internalProviderConfig = {}; + this.mutex = new async_mutex_1.Mutex(); + /** + * Name of this controller used during composition + */ + this.name = 'NetworkController'; + this.defaultState = { + network: 'loading', + isCustomNetwork: false, + provider: { type: constants_1.MAINNET, chainId: NetworksChainId.mainnet }, + properties: { isEIP1559Compatible: false }, + }; + this.initialize(); + this.getEIP1559Compatibility(); + } + initializeProvider(type, rpcTarget, chainId, ticker, nickname) { + this.update({ isCustomNetwork: this.getIsCustomNetwork(chainId) }); + switch (type) { + case 'kovan': + case constants_1.MAINNET: + case 'rinkeby': + case 'goerli': + case 'optimism': + case 'optimismTest': + case 'ropsten': + this.setupInfuraProvider(type); + break; + case 'localhost': + this.setupStandardProvider(LOCALHOST_RPC_URL); + break; + case constants_1.RPC: + rpcTarget && + this.setupStandardProvider(rpcTarget, chainId, ticker, nickname); + break; + default: + throw new Error(`Unrecognized network type: '${type}'`); + } + } + refreshNetwork() { + this.update({ network: 'loading', properties: {} }); + const { rpcTarget, type, chainId, ticker } = this.state.provider; + this.initializeProvider(type, rpcTarget, chainId, ticker); + this.lookupNetwork(); + } + registerProvider() { + this.provider.on('error', this.verifyNetwork.bind(this)); + this.ethQuery = new eth_query_1.default(this.provider); + } + setupInfuraProvider(type) { + const infuraProvider = (0, createProvider_1.default)({ + network: type, + projectId: this.config.infuraProjectId, + }); + const infuraSubprovider = new provider_1.default(infuraProvider); + const config = Object.assign(Object.assign({}, this.internalProviderConfig), { + dataSubprovider: infuraSubprovider, + engineParams: { + blockTrackerProvider: infuraProvider, + pollingInterval: 12000, + }, + }); + this.updateProvider((0, zero_1.default)(config)); + } + getIsCustomNetwork(chainId) { + return (chainId !== NetworksChainId.mainnet && + chainId !== NetworksChainId.kovan && + chainId !== NetworksChainId.rinkeby && + chainId !== NetworksChainId.goerli && + chainId !== NetworksChainId.ropsten && + chainId !== NetworksChainId.localhost); + } + setupStandardProvider(rpcTarget, chainId, ticker, nickname) { + const config = Object.assign(Object.assign({}, this.internalProviderConfig), { + chainId, + engineParams: { pollingInterval: 12000 }, + nickname, + rpcUrl: rpcTarget, + ticker, + }); + this.updateProvider((0, zero_1.default)(config)); + } + updateProvider(provider) { + this.safelyStopProvider(this.provider); + this.provider = provider; + this.registerProvider(); + } + safelyStopProvider(provider) { + setTimeout(() => { + provider === null || provider === void 0 ? void 0 : provider.stop(); + }, 500); + } + verifyNetwork() { + this.state.network === 'loading' && this.lookupNetwork(); + } + /** + * Sets a new configuration for web3-provider-engine. + * + * TODO: Replace this wth a method. + * + * @param providerConfig - The web3-provider-engine configuration. + */ + set providerConfig(providerConfig) { + this.internalProviderConfig = providerConfig; + const { type, rpcTarget, chainId, ticker, nickname } = this.state.provider; + this.initializeProvider(type, rpcTarget, chainId, ticker, nickname); + this.registerProvider(); + this.lookupNetwork(); + } + get providerConfig() { + throw new Error('Property only used for setting'); + } + /** + * Refreshes the current network code. + */ + lookupNetwork() { + return __awaiter(this, void 0, void 0, function* () { + /* istanbul ignore if */ + if (!this.ethQuery || !this.ethQuery.sendAsync) { + return; + } + const releaseLock = yield this.mutex.acquire(); + this.ethQuery.sendAsync({ method: 'net_version' }, (error, network) => { + this.update({ + network: error ? /* istanbul ignore next*/ 'loading' : network, + }); + releaseLock(); + }); + }); + } + /** + * Convenience method to update provider network type settings. + * + * @param type - Human readable network name. + */ + setProviderType(type) { + const _a = this.state.provider, { rpcTarget, chainId, nickname } = _a, providerState = __rest(_a, ["rpcTarget", "chainId", "nickname"]); + // If testnet the ticker symbol should use a testnet prefix + const testNetTicker = constants_1.TESTNET_TICKER_SYMBOLS[type.toUpperCase()]; + this.update({ + provider: Object.assign(Object.assign({}, providerState), { + type, + ticker: testNetTicker || 'ETH', + chainId: NetworksChainId[type], + }), + }); + this.refreshNetwork(); + } + /** + * Convenience method to update provider RPC settings. + * + * @param rpcTarget - The RPC endpoint URL. + * @param chainId - The chain ID as per EIP-155. + * @param ticker - The currency ticker. + * @param nickname - Personalized network name. + */ + setRpcTarget(rpcTarget, chainId, ticker, nickname) { + this.update({ + provider: Object.assign(Object.assign({}, this.state.provider), { type: constants_1.RPC, ticker, rpcTarget, chainId, nickname }), + }); + this.refreshNetwork(); + } + getEIP1559Compatibility() { + var _a; + const { properties = {} } = this.state; + if (!properties.isEIP1559Compatible) { + if (typeof ((_a = this.ethQuery) === null || _a === void 0 ? void 0 : _a.sendAsync) !== 'function') { + return Promise.resolve(true); + } + return new Promise((resolve, reject) => { + this.ethQuery.sendAsync({ method: 'eth_getBlockByNumber', params: ['latest', false] }, (error, block) => { + if (error) { + reject(error); + } + else { + const isEIP1559Compatible = typeof block.baseFeePerGas !== 'undefined'; + if (properties.isEIP1559Compatible !== isEIP1559Compatible) { + this.update({ + properties: { + isEIP1559Compatible, + }, + }); + } + resolve(isEIP1559Compatible); + } + }); + }); + } + return Promise.resolve(true); + } +} +exports.NetworkController = NetworkController; +exports.default = NetworkController; +//# sourceMappingURL=NetworkController.js.map \ No newline at end of file diff --git a/dist/network/NetworkController.js.map b/dist/network/NetworkController.js.map new file mode 100644 index 0000000000..b9215a4189 --- /dev/null +++ b/dist/network/NetworkController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"NetworkController.js","sourceRoot":"","sources":["../../src/network/NetworkController.ts"],"names":[],"mappings":"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","sourcesContent":["import EthQuery from 'eth-query';\nimport Subprovider from 'web3-provider-engine/subproviders/provider';\nimport createInfuraProvider from 'eth-json-rpc-infura/src/createProvider';\nimport createMetamaskProvider from 'web3-provider-engine/zero';\nimport { Mutex } from 'async-mutex';\nimport { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport { MAINNET, RPC, TESTNET_TICKER_SYMBOLS } from '../constants';\n\n/**\n * Human-readable network name\n */\nexport type NetworkType =\n | 'kovan'\n | 'localhost'\n | 'mainnet'\n | 'rinkeby'\n | 'goerli'\n | 'ropsten'\n | 'rpc'\n | 'optimism'\n | 'optimismTest';\n\nexport enum NetworksChainId {\n mainnet = '1',\n kovan = '42',\n rinkeby = '4',\n goerli = '5',\n ropsten = '3',\n localhost = '',\n rpc = '',\n optimism = '10',\n optimismTest = '69',\n}\n\n/**\n * @type ProviderConfig\n *\n * Configuration passed to web3-provider-engine\n * @property rpcTarget - RPC target URL.\n * @property type - Human-readable network name.\n * @property chainId - Network ID as per EIP-155.\n * @property ticker - Currency ticker.\n * @property nickname - Personalized network name.\n */\nexport interface ProviderConfig {\n rpcTarget?: string;\n type: NetworkType;\n chainId: string;\n ticker?: string;\n nickname?: string;\n}\n\nexport interface Block {\n baseFeePerGas?: string;\n}\n\nexport interface NetworkProperties {\n isEIP1559Compatible?: boolean;\n}\n\n/**\n * @type NetworkConfig\n *\n * Network controller configuration\n * @property infuraProjectId - an Infura project ID\n * @property providerConfig - web3-provider-engine configuration\n */\nexport interface NetworkConfig extends BaseConfig {\n infuraProjectId?: string;\n providerConfig: ProviderConfig;\n}\n\n/**\n * @type NetworkState\n *\n * Network controller state\n * @property network - Network ID as per net_version\n * @property isCustomNetwork - Identifies if the network is a custom network\n * @property provider - RPC URL and network name provider settings\n */\nexport interface NetworkState extends BaseState {\n network: string;\n isCustomNetwork: boolean;\n provider: ProviderConfig;\n properties: NetworkProperties;\n}\n\nconst LOCALHOST_RPC_URL = 'http://localhost:8545';\n\n/**\n * Controller that creates and manages an Ethereum network provider\n */\nexport class NetworkController extends BaseController<\n NetworkConfig,\n NetworkState\n> {\n private ethQuery: any;\n\n private internalProviderConfig: ProviderConfig = {} as ProviderConfig;\n\n private mutex = new Mutex();\n\n private initializeProvider(\n type: NetworkType,\n rpcTarget?: string,\n chainId?: string,\n ticker?: string,\n nickname?: string,\n ) {\n this.update({ isCustomNetwork: this.getIsCustomNetwork(chainId) });\n switch (type) {\n case 'kovan':\n case MAINNET:\n case 'rinkeby':\n case 'goerli':\n case 'optimism':\n case 'optimismTest':\n case 'ropsten':\n this.setupInfuraProvider(type);\n break;\n case 'localhost':\n this.setupStandardProvider(LOCALHOST_RPC_URL);\n break;\n case RPC:\n rpcTarget &&\n this.setupStandardProvider(rpcTarget, chainId, ticker, nickname);\n break;\n default:\n throw new Error(`Unrecognized network type: '${type}'`);\n }\n }\n\n private refreshNetwork() {\n this.update({ network: 'loading', properties: {} });\n const { rpcTarget, type, chainId, ticker } = this.state.provider;\n this.initializeProvider(type, rpcTarget, chainId, ticker);\n this.lookupNetwork();\n }\n\n private registerProvider() {\n this.provider.on('error', this.verifyNetwork.bind(this));\n this.ethQuery = new EthQuery(this.provider);\n }\n\n private setupInfuraProvider(type: NetworkType) {\n const infuraProvider = createInfuraProvider({\n network: type,\n projectId: this.config.infuraProjectId,\n });\n const infuraSubprovider = new Subprovider(infuraProvider);\n const config = {\n ...this.internalProviderConfig,\n ...{\n dataSubprovider: infuraSubprovider,\n engineParams: {\n blockTrackerProvider: infuraProvider,\n pollingInterval: 12000,\n },\n },\n };\n this.updateProvider(createMetamaskProvider(config));\n }\n\n private getIsCustomNetwork(chainId?: string) {\n return (\n chainId !== NetworksChainId.mainnet &&\n chainId !== NetworksChainId.kovan &&\n chainId !== NetworksChainId.rinkeby &&\n chainId !== NetworksChainId.goerli &&\n chainId !== NetworksChainId.ropsten &&\n chainId !== NetworksChainId.localhost\n );\n }\n\n private setupStandardProvider(\n rpcTarget: string,\n chainId?: string,\n ticker?: string,\n nickname?: string,\n ) {\n const config = {\n ...this.internalProviderConfig,\n ...{\n chainId,\n engineParams: { pollingInterval: 12000 },\n nickname,\n rpcUrl: rpcTarget,\n ticker,\n },\n };\n this.updateProvider(createMetamaskProvider(config));\n }\n\n private updateProvider(provider: any) {\n this.safelyStopProvider(this.provider);\n this.provider = provider;\n this.registerProvider();\n }\n\n private safelyStopProvider(provider: any) {\n setTimeout(() => {\n provider?.stop();\n }, 500);\n }\n\n private verifyNetwork() {\n this.state.network === 'loading' && this.lookupNetwork();\n }\n\n /**\n * Name of this controller used during composition\n */\n override name = 'NetworkController';\n\n /**\n * Ethereum provider object for the current network\n */\n provider: any;\n\n /**\n * Creates a NetworkController instance.\n *\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(config?: Partial, state?: Partial) {\n super(config, state);\n this.defaultState = {\n network: 'loading',\n isCustomNetwork: false,\n provider: { type: MAINNET, chainId: NetworksChainId.mainnet },\n properties: { isEIP1559Compatible: false },\n };\n this.initialize();\n this.getEIP1559Compatibility();\n }\n\n /**\n * Sets a new configuration for web3-provider-engine.\n *\n * TODO: Replace this wth a method.\n *\n * @param providerConfig - The web3-provider-engine configuration.\n */\n set providerConfig(providerConfig: ProviderConfig) {\n this.internalProviderConfig = providerConfig;\n const { type, rpcTarget, chainId, ticker, nickname } = this.state.provider;\n this.initializeProvider(type, rpcTarget, chainId, ticker, nickname);\n this.registerProvider();\n this.lookupNetwork();\n }\n\n get providerConfig() {\n throw new Error('Property only used for setting');\n }\n\n /**\n * Refreshes the current network code.\n */\n async lookupNetwork() {\n /* istanbul ignore if */\n if (!this.ethQuery || !this.ethQuery.sendAsync) {\n return;\n }\n const releaseLock = await this.mutex.acquire();\n this.ethQuery.sendAsync(\n { method: 'net_version' },\n (error: Error, network: string) => {\n this.update({\n network: error ? /* istanbul ignore next*/ 'loading' : network,\n });\n releaseLock();\n },\n );\n }\n\n /**\n * Convenience method to update provider network type settings.\n *\n * @param type - Human readable network name.\n */\n setProviderType(type: NetworkType) {\n const { rpcTarget, chainId, nickname, ...providerState } =\n this.state.provider;\n\n // If testnet the ticker symbol should use a testnet prefix\n const testNetTicker = TESTNET_TICKER_SYMBOLS[type.toUpperCase()];\n\n this.update({\n provider: {\n ...providerState,\n ...{\n type,\n ticker: testNetTicker || 'ETH',\n chainId: NetworksChainId[type],\n },\n },\n });\n this.refreshNetwork();\n }\n\n /**\n * Convenience method to update provider RPC settings.\n *\n * @param rpcTarget - The RPC endpoint URL.\n * @param chainId - The chain ID as per EIP-155.\n * @param ticker - The currency ticker.\n * @param nickname - Personalized network name.\n */\n setRpcTarget(\n rpcTarget: string,\n chainId: string,\n ticker?: string,\n nickname?: string,\n ) {\n this.update({\n provider: {\n ...this.state.provider,\n ...{ type: RPC, ticker, rpcTarget, chainId, nickname },\n },\n });\n this.refreshNetwork();\n }\n\n getEIP1559Compatibility() {\n const { properties = {} } = this.state;\n\n if (!properties.isEIP1559Compatible) {\n if (typeof this.ethQuery?.sendAsync !== 'function') {\n return Promise.resolve(true);\n }\n return new Promise((resolve, reject) => {\n this.ethQuery.sendAsync(\n { method: 'eth_getBlockByNumber', params: ['latest', false] },\n (error: Error, block: Block) => {\n if (error) {\n reject(error);\n } else {\n const isEIP1559Compatible =\n typeof block.baseFeePerGas !== 'undefined';\n if (properties.isEIP1559Compatible !== isEIP1559Compatible) {\n this.update({\n properties: {\n isEIP1559Compatible,\n },\n });\n }\n resolve(isEIP1559Compatible);\n }\n },\n );\n });\n }\n return Promise.resolve(true);\n }\n}\n\nexport default NetworkController;\n"]} \ No newline at end of file diff --git a/dist/notification/NotificationController.d.ts b/dist/notification/NotificationController.d.ts new file mode 100644 index 0000000000..c0460f9c4b --- /dev/null +++ b/dist/notification/NotificationController.d.ts @@ -0,0 +1,84 @@ +import type { Patch } from 'immer'; +import { BaseController } from '../BaseControllerV2'; +import type { RestrictedControllerMessenger } from '../ControllerMessenger'; +/** + * @typedef NotificationControllerState + * @property notifications - Stores existing notifications to be shown in the UI + */ +export declare type NotificationControllerState = { + notifications: Record; +}; +/** + * @typedef Notification - Stores information about in-app notifications, to be shown in the UI + * @property id - A UUID that identifies the notification + * @property origin - The origin that requested the notification + * @property createdDate - The notification creation date in milliseconds elapsed since the UNIX epoch + * @property readDate - The notification read date in milliseconds elapsed since the UNIX epoch or null if unread + * @property message - The notification message + */ +export declare type Notification = { + id: string; + origin: string; + createdDate: number; + readDate: number | null; + message: string; +}; +declare const name = "NotificationController"; +export declare type NotificationControllerStateChange = { + type: `${typeof name}:stateChange`; + payload: [NotificationControllerState, Patch[]]; +}; +export declare type GetNotificationControllerState = { + type: `${typeof name}:getState`; + handler: () => NotificationControllerState; +}; +export declare type ShowNotification = { + type: `${typeof name}:show`; + handler: NotificationController['show']; +}; +export declare type DismissNotification = { + type: `${typeof name}:dismiss`; + handler: NotificationController['dismiss']; +}; +export declare type MarkNotificationRead = { + type: `${typeof name}:markRead`; + handler: NotificationController['markRead']; +}; +export declare type NotificationControllerActions = GetNotificationControllerState | ShowNotification | DismissNotification | MarkNotificationRead; +export declare type NotificationControllerMessenger = RestrictedControllerMessenger; +/** + * Controller that handles storing notifications and showing them to the user + */ +export declare class NotificationController extends BaseController { + /** + * Creates a NotificationController instance. + * + * @param options - Constructor options. + * @param options.messenger - A reference to the messaging system. + * @param options.state - Initial state to set on this controller. + */ + constructor({ messenger, state, }: { + messenger: NotificationControllerMessenger; + state?: Partial; + }); + /** + * Shows a notification. + * + * @param origin - The origin trying to send a notification + * @param message - A message to show on the notification + */ + show(origin: string, message: string): void; + /** + * Dimisses a list of notifications. + * + * @param ids - A list of notification IDs + */ + dismiss(ids: string[]): void; + /** + * Marks a list of notifications as read. + * + * @param ids - A list of notification IDs + */ + markRead(ids: string[]): void; +} +export {}; diff --git a/dist/notification/NotificationController.js b/dist/notification/NotificationController.js new file mode 100644 index 0000000000..7d8a4b7cbe --- /dev/null +++ b/dist/notification/NotificationController.js @@ -0,0 +1,85 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.NotificationController = void 0; +const nanoid_1 = require("nanoid"); +const util_1 = require("../util"); +const BaseControllerV2_1 = require("../BaseControllerV2"); +const name = 'NotificationController'; +const metadata = { + notifications: { persist: true, anonymous: false }, +}; +const defaultState = { + notifications: {}, +}; +/** + * Controller that handles storing notifications and showing them to the user + */ +class NotificationController extends BaseControllerV2_1.BaseController { + /** + * Creates a NotificationController instance. + * + * @param options - Constructor options. + * @param options.messenger - A reference to the messaging system. + * @param options.state - Initial state to set on this controller. + */ + constructor({ messenger, state, }) { + super({ + name, + metadata, + messenger, + state: Object.assign(Object.assign({}, defaultState), state), + }); + this.messagingSystem.registerActionHandler(`${name}:show`, (origin, message) => this.show(origin, message)); + this.messagingSystem.registerActionHandler(`${name}:dismiss`, (ids) => this.dismiss(ids)); + this.messagingSystem.registerActionHandler(`${name}:markRead`, (ids) => this.markRead(ids)); + } + /** + * Shows a notification. + * + * @param origin - The origin trying to send a notification + * @param message - A message to show on the notification + */ + show(origin, message) { + const id = (0, nanoid_1.nanoid)(); + const notification = { + id, + origin, + createdDate: Date.now(), + readDate: null, + message, + }; + this.update((state) => { + state.notifications[id] = notification; + }); + } + /** + * Dimisses a list of notifications. + * + * @param ids - A list of notification IDs + */ + dismiss(ids) { + this.update((state) => { + for (const id of ids) { + if ((0, util_1.hasProperty)(state.notifications, id)) { + delete state.notifications[id]; + } + } + }); + } + /** + * Marks a list of notifications as read. + * + * @param ids - A list of notification IDs + */ + markRead(ids) { + this.update((state) => { + for (const id of ids) { + if ((0, util_1.hasProperty)(state.notifications, id)) { + state.notifications[id].readDate = Date.now(); + } + } + }); + } +} +exports.NotificationController = NotificationController; +//# sourceMappingURL=NotificationController.js.map \ No newline at end of file diff --git a/dist/notification/NotificationController.js.map b/dist/notification/NotificationController.js.map new file mode 100644 index 0000000000..c2d3c0ea34 --- /dev/null +++ b/dist/notification/NotificationController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"NotificationController.js","sourceRoot":"","sources":["../../src/notification/NotificationController.ts"],"names":[],"mappings":";;;AACA,mCAAgC;AAEhC,kCAAsC;AACtC,0DAAqD;AA4BrD,MAAM,IAAI,GAAG,wBAAwB,CAAC;AAyCtC,MAAM,QAAQ,GAAG;IACf,aAAa,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE;CACnD,CAAC;AAEF,MAAM,YAAY,GAAG;IACnB,aAAa,EAAE,EAAE;CAClB,CAAC;AAEF;;GAEG;AACH,MAAa,sBAAuB,SAAQ,iCAI3C;IACC;;;;;;OAMG;IACH,YAAY,EACV,SAAS,EACT,KAAK,GAIN;QACC,KAAK,CAAC;YACJ,IAAI;YACJ,QAAQ;YACR,SAAS;YACT,KAAK,kCAAO,YAAY,GAAK,KAAK,CAAE;SACrC,CAAC,CAAC;QAEH,IAAI,CAAC,eAAe,CAAC,qBAAqB,CACxC,GAAG,IAAI,OAAgB,EACvB,CAAC,MAAc,EAAE,OAAe,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAChE,CAAC;QAEF,IAAI,CAAC,eAAe,CAAC,qBAAqB,CACxC,GAAG,IAAI,UAAmB,EAC1B,CAAC,GAAa,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CACrC,CAAC;QAEF,IAAI,CAAC,eAAe,CAAC,qBAAqB,CACxC,GAAG,IAAI,WAAoB,EAC3B,CAAC,GAAa,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CACtC,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACH,IAAI,CAAC,MAAc,EAAE,OAAe;QAClC,MAAM,EAAE,GAAG,IAAA,eAAM,GAAE,CAAC;QACpB,MAAM,YAAY,GAAG;YACnB,EAAE;YACF,MAAM;YACN,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE;YACvB,QAAQ,EAAE,IAAI;YACd,OAAO;SACR,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACpB,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC;QACzC,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,OAAO,CAAC,GAAa;QACnB,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACpB,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE;gBACpB,IAAI,IAAA,kBAAW,EAAC,KAAK,CAAC,aAAa,EAAE,EAAE,CAAC,EAAE;oBACxC,OAAO,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;iBAChC;aACF;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,QAAQ,CAAC,GAAa;QACpB,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACpB,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE;gBACpB,IAAI,IAAA,kBAAW,EAAC,KAAK,CAAC,aAAa,EAAE,EAAE,CAAC,EAAE;oBACxC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;iBAC/C;aACF;QACH,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AA3FD,wDA2FC","sourcesContent":["import type { Patch } from 'immer';\nimport { nanoid } from 'nanoid';\n\nimport { hasProperty } from '../util';\nimport { BaseController } from '../BaseControllerV2';\n\nimport type { RestrictedControllerMessenger } from '../ControllerMessenger';\n\n/**\n * @typedef NotificationControllerState\n * @property notifications - Stores existing notifications to be shown in the UI\n */\nexport type NotificationControllerState = {\n notifications: Record;\n};\n\n/**\n * @typedef Notification - Stores information about in-app notifications, to be shown in the UI\n * @property id - A UUID that identifies the notification\n * @property origin - The origin that requested the notification\n * @property createdDate - The notification creation date in milliseconds elapsed since the UNIX epoch\n * @property readDate - The notification read date in milliseconds elapsed since the UNIX epoch or null if unread\n * @property message - The notification message\n */\nexport type Notification = {\n id: string;\n origin: string;\n createdDate: number;\n readDate: number | null;\n message: string;\n};\n\nconst name = 'NotificationController';\n\nexport type NotificationControllerStateChange = {\n type: `${typeof name}:stateChange`;\n payload: [NotificationControllerState, Patch[]];\n};\n\nexport type GetNotificationControllerState = {\n type: `${typeof name}:getState`;\n handler: () => NotificationControllerState;\n};\n\nexport type ShowNotification = {\n type: `${typeof name}:show`;\n handler: NotificationController['show'];\n};\n\nexport type DismissNotification = {\n type: `${typeof name}:dismiss`;\n handler: NotificationController['dismiss'];\n};\n\nexport type MarkNotificationRead = {\n type: `${typeof name}:markRead`;\n handler: NotificationController['markRead'];\n};\n\nexport type NotificationControllerActions =\n | GetNotificationControllerState\n | ShowNotification\n | DismissNotification\n | MarkNotificationRead;\n\nexport type NotificationControllerMessenger = RestrictedControllerMessenger<\n typeof name,\n NotificationControllerActions,\n NotificationControllerStateChange,\n never,\n never\n>;\n\nconst metadata = {\n notifications: { persist: true, anonymous: false },\n};\n\nconst defaultState = {\n notifications: {},\n};\n\n/**\n * Controller that handles storing notifications and showing them to the user\n */\nexport class NotificationController extends BaseController<\n typeof name,\n NotificationControllerState,\n NotificationControllerMessenger\n> {\n /**\n * Creates a NotificationController instance.\n *\n * @param options - Constructor options.\n * @param options.messenger - A reference to the messaging system.\n * @param options.state - Initial state to set on this controller.\n */\n constructor({\n messenger,\n state,\n }: {\n messenger: NotificationControllerMessenger;\n state?: Partial;\n }) {\n super({\n name,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n\n this.messagingSystem.registerActionHandler(\n `${name}:show` as const,\n (origin: string, message: string) => this.show(origin, message),\n );\n\n this.messagingSystem.registerActionHandler(\n `${name}:dismiss` as const,\n (ids: string[]) => this.dismiss(ids),\n );\n\n this.messagingSystem.registerActionHandler(\n `${name}:markRead` as const,\n (ids: string[]) => this.markRead(ids),\n );\n }\n\n /**\n * Shows a notification.\n *\n * @param origin - The origin trying to send a notification\n * @param message - A message to show on the notification\n */\n show(origin: string, message: string) {\n const id = nanoid();\n const notification = {\n id,\n origin,\n createdDate: Date.now(),\n readDate: null,\n message,\n };\n this.update((state) => {\n state.notifications[id] = notification;\n });\n }\n\n /**\n * Dimisses a list of notifications.\n *\n * @param ids - A list of notification IDs\n */\n dismiss(ids: string[]) {\n this.update((state) => {\n for (const id of ids) {\n if (hasProperty(state.notifications, id)) {\n delete state.notifications[id];\n }\n }\n });\n }\n\n /**\n * Marks a list of notifications as read.\n *\n * @param ids - A list of notification IDs\n */\n markRead(ids: string[]) {\n this.update((state) => {\n for (const id of ids) {\n if (hasProperty(state.notifications, id)) {\n state.notifications[id].readDate = Date.now();\n }\n }\n });\n }\n}\n"]} \ No newline at end of file diff --git a/dist/permissions/Caveat.d.ts b/dist/permissions/Caveat.d.ts new file mode 100644 index 0000000000..4a0cf17300 --- /dev/null +++ b/dist/permissions/Caveat.d.ts @@ -0,0 +1,171 @@ +import { Json } from '@metamask/types'; +import { AsyncRestrictedMethod, RestrictedMethod, PermissionConstraint, RestrictedMethodParameters } from './Permission'; +export declare type CaveatConstraint = { + /** + * The type of the caveat. The type is presumed to be meaningful in the + * context of the capability it is associated with. + * + * In MetaMask, every permission can only have one caveat of each type. + */ + readonly type: string; + /** + * Any additional data necessary to enforce the caveat. + */ + readonly value: Json; +}; +/** + * A `ZCAP-LD`-like caveat object. A caveat is associated with a particular + * permission, and stored in its `caveats` array. Conceptually, a caveat is + * an arbitrary attenuation of the authority granted by its associated + * permission. It is the responsibility of the host to interpret and apply + * the restriction represented by a caveat. + * + * @template Type - The type of the caveat. + * @template Value - The value associated with the caveat. + */ +export declare type Caveat = { + /** + * The type of the caveat. The type is presumed to be meaningful in the + * context of the capability it is associated with. + * + * In MetaMask, every permission can only have one caveat of each type. + */ + readonly type: Type; + /** + * Any additional data necessary to enforce the caveat. + */ + readonly value: Value; +}; +/** + * A function for applying caveats to a restricted method request. + * + * @template ParentCaveat - The caveat type associated with this decorator. + * @param decorated - The restricted method implementation to be decorated. + * The method may have already been decorated with other caveats. + * @param caveat - The caveat object. + * @returns The decorated restricted method implementation. + */ +export declare type CaveatDecorator = (decorated: AsyncRestrictedMethod, caveat: ParentCaveat) => AsyncRestrictedMethod; +/** + * Extracts a caveat value type from a caveat decorator. + * + * @template Decorator - The {@link CaveatDecorator} to extract a caveat value + * type from. + */ +declare type ExtractCaveatValueFromDecorator> = Decorator extends (decorated: any, caveat: infer ParentCaveat) => AsyncRestrictedMethod ? ParentCaveat extends CaveatConstraint ? ParentCaveat['value'] : never : never; +/** + * A function for validating caveats of a particular type. + * + * @template ParentCaveat - The caveat type associated with this validator. + * @param caveat - The caveat object to validate. + * @param origin - The origin associated with the parent permission. + * @param target - The target of the parent permission. + */ +export declare type CaveatValidator = (caveat: { + type: ParentCaveat['type']; + value: unknown; +}, origin?: string, target?: string) => void; +/** + * The constraint for caveat specification objects. Every {@link Caveat} + * supported by a {@link PermissionController} must have an associated + * specification, which is the source of truth for all caveat-related types. + * In addition, a caveat specification includes the decorator function used + * to apply the caveat's attenuation to a restricted method, and any validator + * function specified by the consumer. + * + * See the README for more details. + */ +export declare type CaveatSpecificationConstraint = { + /** + * The string type of the caveat. + */ + type: string; + /** + * The decorator function used to apply the caveat to restricted method + * requests. + */ + decorator: CaveatDecorator; + /** + * The validator function used to validate caveats of the associated type + * whenever they are instantiated. Caveat are instantiated whenever they are + * created or mutated. + * + * The validator should throw an appropriate JSON-RPC error if validation fails. + * + * If no validator is specified, no validation of caveat values will be + * performed. Although caveats can also be validated by permission validators, + * validating caveat values separately is strongly recommended. + */ + validator?: CaveatValidator; +}; +/** + * Options for {@link CaveatSpecificationBuilder} functions. + */ +declare type CaveatSpecificationBuilderOptions, ValidatorHooks extends Record> = { + type?: string; + decoratorHooks?: DecoratorHooks; + validatorHooks?: ValidatorHooks; +}; +/** + * A function that builds caveat specifications. Modules that specify caveats + * for external consumption should make this their primary / default export so + * that host applications can use them to generate concrete specifications + * tailored to their requirements. + */ +export declare type CaveatSpecificationBuilder, Specification extends CaveatSpecificationConstraint> = (options: Options) => Specification; +/** + * A caveat specification export object, containing the + * {@link CaveatSpecificationBuilder} function and "hook name" objects. + */ +export declare type CaveatSpecificationBuilderExportConstraint = { + specificationBuilder: CaveatSpecificationBuilder, CaveatSpecificationConstraint>; + decoratorHookNames?: Record; + validatorHookNames?: Record; +}; +/** + * The specifications for all caveats supported by a particular + * {@link PermissionController}. + * + * @template Specifications - The union of all {@link CaveatSpecificationConstraint} types. + */ +export declare type CaveatSpecificationMap = Record; +/** + * Extracts the union of all caveat types specified by the given + * {@link CaveatSpecificationConstraint} type. + * + * @template CaveatSpecification - The {@link CaveatSpecificationConstraint} to extract a + * caveat type union from. + */ +export declare type ExtractCaveats = CaveatSpecification extends any ? Caveat> : never; +/** + * Extracts the type of a specific {@link Caveat} from a union of caveat + * specifications. + * + * @template CaveatSpecifications - The union of all caveat specifications. + * @template CaveatType - The type of the caveat to extract. + */ +export declare type ExtractCaveat = Extract, { + type: CaveatType; +}>; +/** + * Extracts the value type of a specific {@link Caveat} from a union of caveat + * specifications. + * + * @template CaveatSpecifications - The union of all caveat specifications. + * @template CaveatType - The type of the caveat whose value to extract. + */ +export declare type ExtractCaveatValue = ExtractCaveat['value']; +/** + * Decorate a restricted method implementation with its caveats. + * + * Note that all caveat functions (i.e. the argument and return value of the + * decorator) must be awaited. + * + * @param methodImplementation - The restricted method implementation + * @param permission - The origin's potential permission + * @param caveatSpecifications - All caveat implementations + * @returns The decorated method implementation + */ +export declare function decorateWithCaveats(methodImplementation: RestrictedMethod, permission: Readonly, // bound to the requesting origin +caveatSpecifications: CaveatSpecificationMap): RestrictedMethod; +export {}; diff --git a/dist/permissions/Caveat.js b/dist/permissions/Caveat.js new file mode 100644 index 0000000000..af74c17872 --- /dev/null +++ b/dist/permissions/Caveat.js @@ -0,0 +1,42 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.decorateWithCaveats = void 0; +const errors_1 = require("./errors"); +/** + * Decorate a restricted method implementation with its caveats. + * + * Note that all caveat functions (i.e. the argument and return value of the + * decorator) must be awaited. + * + * @param methodImplementation - The restricted method implementation + * @param permission - The origin's potential permission + * @param caveatSpecifications - All caveat implementations + * @returns The decorated method implementation + */ +function decorateWithCaveats(methodImplementation, permission, // bound to the requesting origin +caveatSpecifications) { + const { caveats } = permission; + if (!caveats) { + return methodImplementation; + } + let decorated = (args) => __awaiter(this, void 0, void 0, function* () { return methodImplementation(args); }); + for (const caveat of caveats) { + const specification = caveatSpecifications[caveat.type]; + if (!specification) { + throw new errors_1.UnrecognizedCaveatTypeError(caveat.type); + } + decorated = specification.decorator(decorated, caveat); + } + return decorated; +} +exports.decorateWithCaveats = decorateWithCaveats; +//# sourceMappingURL=Caveat.js.map \ No newline at end of file diff --git a/dist/permissions/Caveat.js.map b/dist/permissions/Caveat.js.map new file mode 100644 index 0000000000..76d665f32e --- /dev/null +++ b/dist/permissions/Caveat.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Caveat.js","sourceRoot":"","sources":["../../src/permissions/Caveat.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,qCAAuD;AA+NvD;;;;;;;;;;GAUG;AACH,SAAgB,mBAAmB,CAGjC,oBAAwE,EACxE,UAA0C,EAAE,iCAAiC;AAC7E,oBAAkE;IAElE,MAAM,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC;IAC/B,IAAI,CAAC,OAAO,EAAE;QACZ,OAAO,oBAAoB,CAAC;KAC7B;IAED,IAAI,SAAS,GAAG,CACd,IAAuE,EACvE,EAAE,gDAAC,OAAA,oBAAoB,CAAC,IAAI,CAAC,CAAA,GAAA,CAAC;IAEhC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE;QAC5B,MAAM,aAAa,GACjB,oBAAoB,CAAC,MAAM,CAAC,IAAoC,CAAC,CAAC;QACpE,IAAI,CAAC,aAAa,EAAE;YAClB,MAAM,IAAI,oCAA2B,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;SACpD;QAED,SAAS,GAAG,aAAa,CAAC,SAAS,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;KACxD;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AA3BD,kDA2BC","sourcesContent":["import { Json } from '@metamask/types';\nimport { UnrecognizedCaveatTypeError } from './errors';\nimport {\n AsyncRestrictedMethod,\n RestrictedMethod,\n PermissionConstraint,\n RestrictedMethodParameters,\n} from './Permission';\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nimport type { PermissionController } from './PermissionController';\n\nexport type CaveatConstraint = {\n /**\n * The type of the caveat. The type is presumed to be meaningful in the\n * context of the capability it is associated with.\n *\n * In MetaMask, every permission can only have one caveat of each type.\n */\n readonly type: string;\n\n // TODO:TS4.4 Make optional\n /**\n * Any additional data necessary to enforce the caveat.\n */\n readonly value: Json;\n};\n\n/**\n * A `ZCAP-LD`-like caveat object. A caveat is associated with a particular\n * permission, and stored in its `caveats` array. Conceptually, a caveat is\n * an arbitrary attenuation of the authority granted by its associated\n * permission. It is the responsibility of the host to interpret and apply\n * the restriction represented by a caveat.\n *\n * @template Type - The type of the caveat.\n * @template Value - The value associated with the caveat.\n */\nexport type Caveat = {\n /**\n * The type of the caveat. The type is presumed to be meaningful in the\n * context of the capability it is associated with.\n *\n * In MetaMask, every permission can only have one caveat of each type.\n */\n readonly type: Type;\n\n // TODO:TS4.4 Make optional\n /**\n * Any additional data necessary to enforce the caveat.\n */\n readonly value: Value;\n};\n\n// Next, we define types used for specifying caveats at the consumer layer,\n// and a function for applying caveats to a restricted method request. This is\n// Accomplished by decorating the restricted method implementation with the\n// the corresponding caveat functions.\n\n/**\n * A function for applying caveats to a restricted method request.\n *\n * @template ParentCaveat - The caveat type associated with this decorator.\n * @param decorated - The restricted method implementation to be decorated.\n * The method may have already been decorated with other caveats.\n * @param caveat - The caveat object.\n * @returns The decorated restricted method implementation.\n */\nexport type CaveatDecorator = (\n decorated: AsyncRestrictedMethod,\n caveat: ParentCaveat,\n) => AsyncRestrictedMethod;\n\n/**\n * Extracts a caveat value type from a caveat decorator.\n *\n * @template Decorator - The {@link CaveatDecorator} to extract a caveat value\n * type from.\n */\ntype ExtractCaveatValueFromDecorator> =\n Decorator extends (\n decorated: any,\n caveat: infer ParentCaveat,\n ) => AsyncRestrictedMethod\n ? ParentCaveat extends CaveatConstraint\n ? ParentCaveat['value']\n : never\n : never;\n\n/**\n * A function for validating caveats of a particular type.\n *\n * @template ParentCaveat - The caveat type associated with this validator.\n * @param caveat - The caveat object to validate.\n * @param origin - The origin associated with the parent permission.\n * @param target - The target of the parent permission.\n */\nexport type CaveatValidator = (\n caveat: { type: ParentCaveat['type']; value: unknown },\n origin?: string,\n target?: string,\n) => void;\n\n/**\n * The constraint for caveat specification objects. Every {@link Caveat}\n * supported by a {@link PermissionController} must have an associated\n * specification, which is the source of truth for all caveat-related types.\n * In addition, a caveat specification includes the decorator function used\n * to apply the caveat's attenuation to a restricted method, and any validator\n * function specified by the consumer.\n *\n * See the README for more details.\n */\nexport type CaveatSpecificationConstraint = {\n /**\n * The string type of the caveat.\n */\n type: string;\n\n /**\n * The decorator function used to apply the caveat to restricted method\n * requests.\n */\n decorator: CaveatDecorator;\n\n /**\n * The validator function used to validate caveats of the associated type\n * whenever they are instantiated. Caveat are instantiated whenever they are\n * created or mutated.\n *\n * The validator should throw an appropriate JSON-RPC error if validation fails.\n *\n * If no validator is specified, no validation of caveat values will be\n * performed. Although caveats can also be validated by permission validators,\n * validating caveat values separately is strongly recommended.\n */\n validator?: CaveatValidator;\n};\n\n/**\n * Options for {@link CaveatSpecificationBuilder} functions.\n */\ntype CaveatSpecificationBuilderOptions<\n DecoratorHooks extends Record,\n ValidatorHooks extends Record,\n> = {\n type?: string;\n decoratorHooks?: DecoratorHooks;\n validatorHooks?: ValidatorHooks;\n};\n\n/**\n * A function that builds caveat specifications. Modules that specify caveats\n * for external consumption should make this their primary / default export so\n * that host applications can use them to generate concrete specifications\n * tailored to their requirements.\n */\nexport type CaveatSpecificationBuilder<\n Options extends CaveatSpecificationBuilderOptions,\n Specification extends CaveatSpecificationConstraint,\n> = (options: Options) => Specification;\n\n/**\n * A caveat specification export object, containing the\n * {@link CaveatSpecificationBuilder} function and \"hook name\" objects.\n */\nexport type CaveatSpecificationBuilderExportConstraint = {\n specificationBuilder: CaveatSpecificationBuilder<\n CaveatSpecificationBuilderOptions,\n CaveatSpecificationConstraint\n >;\n decoratorHookNames?: Record;\n validatorHookNames?: Record;\n};\n\n/**\n * The specifications for all caveats supported by a particular\n * {@link PermissionController}.\n *\n * @template Specifications - The union of all {@link CaveatSpecificationConstraint} types.\n */\nexport type CaveatSpecificationMap<\n CaveatSpecification extends CaveatSpecificationConstraint,\n> = Record;\n\n/**\n * Extracts the union of all caveat types specified by the given\n * {@link CaveatSpecificationConstraint} type.\n *\n * @template CaveatSpecification - The {@link CaveatSpecificationConstraint} to extract a\n * caveat type union from.\n */\nexport type ExtractCaveats<\n CaveatSpecification extends CaveatSpecificationConstraint,\n> = CaveatSpecification extends any\n ? Caveat<\n CaveatSpecification['type'],\n ExtractCaveatValueFromDecorator\n >\n : never;\n\n/**\n * Extracts the type of a specific {@link Caveat} from a union of caveat\n * specifications.\n *\n * @template CaveatSpecifications - The union of all caveat specifications.\n * @template CaveatType - The type of the caveat to extract.\n */\nexport type ExtractCaveat<\n CaveatSpecifications extends CaveatSpecificationConstraint,\n CaveatType extends string,\n> = Extract, { type: CaveatType }>;\n\n/**\n * Extracts the value type of a specific {@link Caveat} from a union of caveat\n * specifications.\n *\n * @template CaveatSpecifications - The union of all caveat specifications.\n * @template CaveatType - The type of the caveat whose value to extract.\n */\nexport type ExtractCaveatValue<\n CaveatSpecifications extends CaveatSpecificationConstraint,\n CaveatType extends string,\n> = ExtractCaveat['value'];\n\n/**\n * Decorate a restricted method implementation with its caveats.\n *\n * Note that all caveat functions (i.e. the argument and return value of the\n * decorator) must be awaited.\n *\n * @param methodImplementation - The restricted method implementation\n * @param permission - The origin's potential permission\n * @param caveatSpecifications - All caveat implementations\n * @returns The decorated method implementation\n */\nexport function decorateWithCaveats<\n CaveatSpecifications extends CaveatSpecificationConstraint,\n>(\n methodImplementation: RestrictedMethod,\n permission: Readonly, // bound to the requesting origin\n caveatSpecifications: CaveatSpecificationMap, // all caveat implementations\n): RestrictedMethod {\n const { caveats } = permission;\n if (!caveats) {\n return methodImplementation;\n }\n\n let decorated = async (\n args: Parameters>[0],\n ) => methodImplementation(args);\n\n for (const caveat of caveats) {\n const specification =\n caveatSpecifications[caveat.type as CaveatSpecifications['type']];\n if (!specification) {\n throw new UnrecognizedCaveatTypeError(caveat.type);\n }\n\n decorated = specification.decorator(decorated, caveat);\n }\n\n return decorated;\n}\n"]} \ No newline at end of file diff --git a/dist/permissions/Permission.d.ts b/dist/permissions/Permission.d.ts new file mode 100644 index 0000000000..7ba47e4208 --- /dev/null +++ b/dist/permissions/Permission.d.ts @@ -0,0 +1,426 @@ +import { Json } from '@metamask/types'; +import { NonEmptyArray } from '../util'; +import { CaveatConstraint } from './Caveat'; +/** + * The origin of a subject. + * Effectively the GUID of an entity that can have permissions. + */ +export declare type OriginString = string; +/** + * The name of a permission target. + */ +declare type TargetName = string; +/** + * A `ZCAP-LD`-like permission object. A permission is associated with a + * particular `invoker`, which is the holder of the permission. Possessing the + * permission grants access to a particular restricted resource, identified by + * the `parentCapability`. The use of the restricted resource may be further + * restricted by any `caveats` associated with the permission. + * + * See the README for details. + */ +export declare type PermissionConstraint = { + /** + * The context(s) in which this capability is meaningful. + * + * It is required by the standard, but we make it optional since there is only + * one context in our usage (i.e. the user's MetaMask instance). + */ + readonly '@context'?: NonEmptyArray; + /** + * The caveats of the permission. + * + * @see {@link Caveat} For more information. + */ + readonly caveats: null | NonEmptyArray; + /** + * The creation date of the permission, in UNIX epoch time. + */ + readonly date: number; + /** + * The GUID of the permission object. + */ + readonly id: string; + /** + * The origin string of the subject that has the permission. + */ + readonly invoker: OriginString; + /** + * A pointer to the resource that possession of the capability grants + * access to, for example a JSON-RPC method or endowment. + */ + readonly parentCapability: string; +}; +/** + * A `ZCAP-LD`-like permission object. A permission is associated with a + * particular `invoker`, which is the holder of the permission. Possessing the + * permission grants access to a particular restricted resource, identified by + * the `parentCapability`. The use of the restricted resource may be further + * restricted by any `caveats` associated with the permission. + * + * See the README for details. + * + * @template TargetKey - They key of the permission target that the permission + * corresponds to. + * @template AllowedCaveat - A union of the allowed {@link Caveat} types + * for the permission. + */ +export declare type ValidPermission = PermissionConstraint & { + /** + * The caveats of the permission. + * + * @see {@link Caveat} For more information. + */ + readonly caveats: AllowedCaveat extends never ? null : NonEmptyArray | null; + /** + * A pointer to the resource that possession of the capability grants + * access to, for example a JSON-RPC method or endowment. + */ + readonly parentCapability: ExtractPermissionTargetNames; +}; +/** + * A utility type for ensuring that the given permission target name conforms to + * our naming conventions. + * + * See the README for the distinction between target names and keys. + */ +declare type ValidTargetName = Name extends `${string}*` ? never : Name extends `${string}_` ? never : Name; +/** + * A utility type for extracting permission target names from a union of target + * keys. + * + * See the README for the distinction between target names and keys. + * + * @template Key - The target key type to extract target names from. + */ +export declare type ExtractPermissionTargetNames = ValidTargetName; +/** + * Extracts the permission key of a particular name from a union of keys. + * An internal utility type used in {@link ExtractPermissionTargetKey}. + * + * @template Key - The target key type to extract from. + * @template Name - The name whose key to extract. + */ +declare type KeyOfTargetName = Name extends ExtractPermissionTargetNames ? Key : never; +/** + * A utility type for finding the permission target key corresponding to a + * target name. In a way, the inverse of {@link ExtractPermissionTargetNames}. + * + * See the README for the distinction between target names and keys. + * + * @template Key - The target key type to extract from. + * @template Name - The name whose key to extract. + */ +export declare type ExtractPermissionTargetKey = Key extends Name ? Key : Extract>; +/** + * Internal utility for extracting the members types of an array. The type + * evalutes to `never` if the specified type is the empty tuple or neither + * an array nor a tuple. + * + * @template ArrayType - The array type whose members to extract. + */ +declare type ExtractArrayMembers = ArrayType extends [] ? never : ArrayType extends any[] | readonly any[] ? ArrayType[number] : never; +/** + * A utility type for extracting the allowed caveat types for a particular + * permission from a permission specification type. + * + * @template PermissionSpecification - The permission specification type to + * extract valid caveat types from. + */ +export declare type ExtractAllowedCaveatTypes = ExtractArrayMembers; +/** + * The options object of {@link constructPermission}. + * + * @template TargetPermission - The {@link Permission} that will be constructed. + */ +export declare type PermissionOptions = { + target: TargetPermission['parentCapability']; + /** + * The origin string of the subject that has the permission. + */ + invoker: OriginString; + /** + * The caveats of the permission. + * See {@link Caveat}. + */ + caveats?: NonEmptyArray; +}; +/** + * The default permission factory function. Naively constructs a permission from + * the inputs. Sets a default, random `id` if none is provided. + * + * @see {@link Permission} For more details. + * @template TargetPermission- - The {@link Permission} that will be constructed. + * @param options - The options for the permission. + * @returns The new permission object. + */ +export declare function constructPermission(options: PermissionOptions): TargetPermission; +/** + * Gets the caveat of the specified type belonging to the specified permission. + * + * @param permission - The permission whose caveat to retrieve. + * @param caveatType - The type of the caveat to retrieve. + * @returns The caveat, or undefined if no such caveat exists. + */ +export declare function findCaveat(permission: PermissionConstraint, caveatType: string): CaveatConstraint | undefined; +/** + * A requested permission object. Just an object with any of the properties + * of a {@link PermissionConstraint} object. + */ +declare type RequestedPermission = Partial; +/** + * A record of target names and their {@link RequestedPermission} objects. + */ +export declare type RequestedPermissions = Record; +/** + * The restricted method context object. Essentially a way to pass internal + * arguments to restricted methods and caveat functions, most importantly the + * requesting origin. + */ +declare type RestrictedMethodContext = Readonly<{ + origin: OriginString; + [key: string]: any; +}>; +export declare type RestrictedMethodParameters = Json[] | Record | void; +/** + * The arguments passed to a restricted method implementation. + * + * @template Params - The JSON-RPC parameters of the restricted method. + */ +export declare type RestrictedMethodOptions = { + method: TargetName; + params?: Params; + context: RestrictedMethodContext; +}; +/** + * A synchronous restricted method implementation. + * + * @template Params - The JSON-RPC parameters of the restricted method. + * @template Result - The JSON-RPC result of the restricted method. + */ +export declare type SyncRestrictedMethod = (args: RestrictedMethodOptions) => Result; +/** + * An asynchronous restricted method implementation. + * + * @template Params - The JSON-RPC parameters of the restricted method. + * @template Result - The JSON-RPC result of the restricted method. + */ +export declare type AsyncRestrictedMethod = (args: RestrictedMethodOptions) => Promise; +/** + * A synchronous or asynchronous restricted method implementation. + * + * @template Params - The JSON-RPC parameters of the restricted method. + * @template Result - The JSON-RPC result of the restricted method. + */ +export declare type RestrictedMethod = SyncRestrictedMethod | AsyncRestrictedMethod; +export declare type ValidRestrictedMethod> = MethodImplementation extends (args: infer Options) => Json | Promise ? Options extends RestrictedMethodOptions ? MethodImplementation : never : never; +/** + * {@link EndowmentGetter} parameter object. + */ +export declare type EndowmentGetterParams = { + /** + * The origin of the requesting subject. + */ + origin: string; + /** + * Any additional data associated with the request. + */ + requestData?: unknown; + [key: string]: unknown; +}; +/** + * A synchronous or asynchronous function that gets the endowments for a + * particular endowment permission. The getter receives the origin of the + * requesting subject and, optionally, additional request metadata. + */ +export declare type EndowmentGetter = (options: EndowmentGetterParams) => Endowments | Promise; +export declare type PermissionFactory> = (options: PermissionOptions, requestData?: RequestData) => TargetPermission; +export declare type PermissionValidatorConstraint = (permission: PermissionConstraint, origin?: OriginString, target?: string) => void; +/** + * A utility type for ensuring that the given permission target key conforms to + * our naming conventions. + * + * See the README for the distinction between target names and keys. + * + * @template Key - The target key string to apply the constraint to. + */ +declare type ValidTargetKey = Key extends `${string}_*` ? Key : Key extends `${string}_` ? never : Key extends `${string}*` ? never : Key; +/** + * The different possible types of permissions. + */ +export declare enum PermissionType { + /** + * A restricted JSON-RPC method. A subject must have the requisite permission + * to call a restricted JSON-RPC method. + */ + RestrictedMethod = "RestrictedMethod", + /** + * An "endowment" granted to subjects that possess the requisite permission, + * such as a global environment variable exposing a restricted API, etc. + */ + Endowment = "Endowment" +} +/** + * The base constraint for permission specification objects. Every + * {@link Permission} supported by a {@link PermissionController} must have an + * associated specification, which is the source of truth for all permission- + * related types. A permission specification includes the list of permitted + * caveats, and any factory and validation functions specified by the consumer. + * A concrete permission specification may specify further fields as necessary. + * + * See the README for more details. + */ +declare type PermissionSpecificationBase = { + /** + * The type of the specified permission. + */ + permissionType: Type; + /** + * The target resource of the permission. The shape of this string depends on + * the permission type. For example, a restricted method target key will + * consist of either a complete method name or the prefix of a namespaced + * method, e.g. `wallet_snap_*`. + */ + targetKey: string; + /** + * An array of the caveat types that may be added to instances of this + * permission. + */ + allowedCaveats: Readonly> | null; + /** + * The factory function used to get permission objects. Permissions returned + * by this function are presumed to valid, and they will not be passed to the + * validator function associated with this specification (if any). In other + * words, the factory function should validate the permissions it creates. + * + * If no factory is specified, the {@link Permission} constructor will be + * used, and the validator function (if specified) will be called on newly + * constructed permissions. + */ + factory?: PermissionFactory>; + /** + * The validator function used to validate permissions of the associated type + * whenever they are mutated. The only way a permission can be legally mutated + * is when its caveats are modified by the permission controller. + * + * The validator should throw an appropriate JSON-RPC error if validation fails. + */ + validator?: PermissionValidatorConstraint; +}; +/** + * The constraint for restricted method permission specification objects. + * Permissions that correspond to JSON-RPC methods are specified using objects + * that conform to this type. + * + * See the README for more details. + */ +export declare type RestrictedMethodSpecificationConstraint = PermissionSpecificationBase & { + /** + * The implementation of the restricted method that the permission + * corresponds to. + */ + methodImplementation: RestrictedMethod; +}; +/** + * The constraint for endowment permission specification objects. Permissions + * that endow callers with some restricted resource are specified using objects + * that conform to this type. + * + * See the README for more details. + */ +export declare type EndowmentSpecificationConstraint = PermissionSpecificationBase & { + /** + * Endowment permissions do not support caveats. + */ + allowedCaveats: null; + /** + * The {@link EndowmentGetter} function for the permission. This function + * will be called by the {@link PermissionController} whenever the + * permission is invoked, after which the host can apply the endowments to + * the requesting subject in the intended manner. + */ + endowmentGetter: EndowmentGetter; +}; +/** + * The constraint for permission specification objects. Every {@link Permission} + * supported by a {@link PermissionController} must have an associated + * specification, which is the source of truth for all permission-related types. + * All specifications must adhere to the {@link PermissionSpecificationBase} + * interface, but specifications may have different fields depending on the + * {@link PermissionType}. + * + * See the README for more details. + */ +export declare type PermissionSpecificationConstraint = EndowmentSpecificationConstraint | RestrictedMethodSpecificationConstraint; +/** + * Options for {@link PermissionSpecificationBuilder} functions. + */ +declare type PermissionSpecificationBuilderOptions, MethodHooks extends Record, ValidatorHooks extends Record> = { + targetKey?: string; + allowedCaveats?: Readonly> | null; + factoryHooks?: FactoryHooks; + methodHooks?: MethodHooks; + validatorHooks?: ValidatorHooks; +}; +/** + * A function that builds a permission specification. Modules that specify + * permissions for external consumption should make this their primary / + * default export so that host applications can use them to generate concrete + * specifications tailored to their requirements. + */ +export declare type PermissionSpecificationBuilder, Specification extends PermissionSpecificationConstraint & { + permissionType: Type; +}> = (options: Options) => Specification; +/** + * A restricted method permission export object, containing the + * {@link PermissionSpecificationBuilder} function and "hook name" objects. + */ +export declare type PermissionSpecificationBuilderExportConstraint = { + targetKey: string; + specificationBuilder: PermissionSpecificationBuilder, PermissionSpecificationConstraint>; + factoryHookNames?: Record; + methodHookNames?: Record; + validatorHookNames?: Record; +}; +declare type ValidRestrictedMethodSpecification = Specification['methodImplementation'] extends ValidRestrictedMethod ? Specification : never; +/** + * Constraint for {@link PermissionSpecificationConstraint} objects that + * evaluates to `never` if the specification contains any invalid fields. + * + * @template Specification - The permission specification to validate. + */ +export declare type ValidPermissionSpecification = Specification['targetKey'] extends ValidTargetKey ? Specification['permissionType'] extends PermissionType.Endowment ? Specification : Specification['permissionType'] extends PermissionType.RestrictedMethod ? ValidRestrictedMethodSpecification> : never : never; +/** + * Checks that the specification has the expected permission type. + * + * @param specification - The specification to check. + * @param expectedType - The expected permission type. + * @template Specification - The specification to check. + * @template Type - The expected permission type. + * @returns Whether or not the specification is of the expected type. + */ +export declare function hasSpecificationType(specification: Specification, expectedType: Type): specification is Specification & { + permissionType: Type; +}; +/** + * The specifications for all permissions supported by a particular + * {@link PermissionController}. + * + * @template Specifications - The union of all {@link PermissionSpecificationConstraint} types. + */ +export declare type PermissionSpecificationMap = { + [TargetKey in Specification['targetKey']]: Specification extends { + targetKey: TargetKey; + } ? Specification : never; +}; +/** + * Extracts a specific {@link PermissionSpecificationConstraint} from a union of + * permission specifications. + * + * @template Specification - The specification union type to extract from. + * @template TargetKey - The `targetKey` of the specification to extract. + */ +export declare type ExtractPermissionSpecification = Specification extends { + targetKey: TargetKey; +} ? Specification : never; +export {}; diff --git a/dist/permissions/Permission.js b/dist/permissions/Permission.js new file mode 100644 index 0000000000..b1f7f2f589 --- /dev/null +++ b/dist/permissions/Permission.js @@ -0,0 +1,66 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.hasSpecificationType = exports.PermissionType = exports.findCaveat = exports.constructPermission = void 0; +const nanoid_1 = require("nanoid"); +/** + * The default permission factory function. Naively constructs a permission from + * the inputs. Sets a default, random `id` if none is provided. + * + * @see {@link Permission} For more details. + * @template TargetPermission- - The {@link Permission} that will be constructed. + * @param options - The options for the permission. + * @returns The new permission object. + */ +function constructPermission(options) { + const { caveats = null, invoker, target } = options; + return { + id: (0, nanoid_1.nanoid)(), + parentCapability: target, + invoker, + caveats, + date: new Date().getTime(), + }; +} +exports.constructPermission = constructPermission; +/** + * Gets the caveat of the specified type belonging to the specified permission. + * + * @param permission - The permission whose caveat to retrieve. + * @param caveatType - The type of the caveat to retrieve. + * @returns The caveat, or undefined if no such caveat exists. + */ +function findCaveat(permission, caveatType) { + var _a; + return (_a = permission.caveats) === null || _a === void 0 ? void 0 : _a.find((caveat) => caveat.type === caveatType); +} +exports.findCaveat = findCaveat; +/** + * The different possible types of permissions. + */ +var PermissionType; +(function (PermissionType) { + /** + * A restricted JSON-RPC method. A subject must have the requisite permission + * to call a restricted JSON-RPC method. + */ + PermissionType["RestrictedMethod"] = "RestrictedMethod"; + /** + * An "endowment" granted to subjects that possess the requisite permission, + * such as a global environment variable exposing a restricted API, etc. + */ + PermissionType["Endowment"] = "Endowment"; +})(PermissionType = exports.PermissionType || (exports.PermissionType = {})); +/** + * Checks that the specification has the expected permission type. + * + * @param specification - The specification to check. + * @param expectedType - The expected permission type. + * @template Specification - The specification to check. + * @template Type - The expected permission type. + * @returns Whether or not the specification is of the expected type. + */ +function hasSpecificationType(specification, expectedType) { + return specification.permissionType === expectedType; +} +exports.hasSpecificationType = hasSpecificationType; +//# sourceMappingURL=Permission.js.map \ No newline at end of file diff --git a/dist/permissions/Permission.js.map b/dist/permissions/Permission.js.map new file mode 100644 index 0000000000..df7ad30fa3 --- /dev/null +++ b/dist/permissions/Permission.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Permission.js","sourceRoot":"","sources":["../../src/permissions/Permission.ts"],"names":[],"mappings":";;;AACA,mCAAgC;AAmMhC;;;;;;;;GAQG;AACH,SAAgB,mBAAmB,CAEjC,OAA4C;IAC5C,MAAM,EAAE,OAAO,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAEpD,OAAO;QACL,EAAE,EAAE,IAAA,eAAM,GAAE;QACZ,gBAAgB,EAAE,MAAM;QACxB,OAAO;QACP,OAAO;QACP,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE;KACP,CAAC;AACxB,CAAC;AAZD,kDAYC;AAED;;;;;;GAMG;AACH,SAAgB,UAAU,CACxB,UAAgC,EAChC,UAAkB;;IAElB,OAAO,MAAA,UAAU,CAAC,OAAO,0CAAE,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;AAC1E,CAAC;AALD,gCAKC;AAwID;;GAEG;AACH,IAAY,cAYX;AAZD,WAAY,cAAc;IACxB;;;OAGG;IACH,uDAAqC,CAAA;IAErC;;;OAGG;IACH,yCAAuB,CAAA;AACzB,CAAC,EAZW,cAAc,GAAd,sBAAc,KAAd,sBAAc,QAYzB;AAoLD;;;;;;;;GAQG;AACH,SAAgB,oBAAoB,CAIlC,aAA4B,EAC5B,YAAkB;IAIlB,OAAO,aAAa,CAAC,cAAc,KAAK,YAAY,CAAC;AACvD,CAAC;AAVD,oDAUC","sourcesContent":["import { Json } from '@metamask/types';\nimport { nanoid } from 'nanoid';\nimport { NonEmptyArray } from '../util';\nimport { CaveatConstraint } from './Caveat';\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nimport type { PermissionController } from './PermissionController';\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nimport type { Caveat } from './Caveat';\n\n/**\n * The origin of a subject.\n * Effectively the GUID of an entity that can have permissions.\n */\nexport type OriginString = string;\n\n/**\n * The name of a permission target.\n */\ntype TargetName = string;\n\n/**\n * A `ZCAP-LD`-like permission object. A permission is associated with a\n * particular `invoker`, which is the holder of the permission. Possessing the\n * permission grants access to a particular restricted resource, identified by\n * the `parentCapability`. The use of the restricted resource may be further\n * restricted by any `caveats` associated with the permission.\n *\n * See the README for details.\n */\nexport type PermissionConstraint = {\n /**\n * The context(s) in which this capability is meaningful.\n *\n * It is required by the standard, but we make it optional since there is only\n * one context in our usage (i.e. the user's MetaMask instance).\n */\n readonly '@context'?: NonEmptyArray;\n\n // TODO:TS4.4 Make optional\n /**\n * The caveats of the permission.\n *\n * @see {@link Caveat} For more information.\n */\n readonly caveats: null | NonEmptyArray;\n\n /**\n * The creation date of the permission, in UNIX epoch time.\n */\n readonly date: number;\n\n /**\n * The GUID of the permission object.\n */\n readonly id: string;\n\n /**\n * The origin string of the subject that has the permission.\n */\n readonly invoker: OriginString;\n\n /**\n * A pointer to the resource that possession of the capability grants\n * access to, for example a JSON-RPC method or endowment.\n */\n readonly parentCapability: string;\n};\n\n/**\n * A `ZCAP-LD`-like permission object. A permission is associated with a\n * particular `invoker`, which is the holder of the permission. Possessing the\n * permission grants access to a particular restricted resource, identified by\n * the `parentCapability`. The use of the restricted resource may be further\n * restricted by any `caveats` associated with the permission.\n *\n * See the README for details.\n *\n * @template TargetKey - They key of the permission target that the permission\n * corresponds to.\n * @template AllowedCaveat - A union of the allowed {@link Caveat} types\n * for the permission.\n */\nexport type ValidPermission<\n TargetKey extends TargetName,\n AllowedCaveat extends CaveatConstraint,\n> = PermissionConstraint & {\n // TODO:TS4.4 Make optional\n /**\n * The caveats of the permission.\n *\n * @see {@link Caveat} For more information.\n */\n readonly caveats: AllowedCaveat extends never\n ? null\n : NonEmptyArray | null;\n\n /**\n * A pointer to the resource that possession of the capability grants\n * access to, for example a JSON-RPC method or endowment.\n */\n readonly parentCapability: ExtractPermissionTargetNames;\n};\n\n/**\n * A utility type for ensuring that the given permission target name conforms to\n * our naming conventions.\n *\n * See the README for the distinction between target names and keys.\n */\ntype ValidTargetName = Name extends `${string}*`\n ? never\n : Name extends `${string}_`\n ? never\n : Name;\n\n/**\n * A utility type for extracting permission target names from a union of target\n * keys.\n *\n * See the README for the distinction between target names and keys.\n *\n * @template Key - The target key type to extract target names from.\n */\nexport type ExtractPermissionTargetNames = ValidTargetName<\n Key extends `${infer Base}_*` ? `${Base}_${string}` : Key\n>;\n\n/**\n * Extracts the permission key of a particular name from a union of keys.\n * An internal utility type used in {@link ExtractPermissionTargetKey}.\n *\n * @template Key - The target key type to extract from.\n * @template Name - The name whose key to extract.\n */\ntype KeyOfTargetName<\n Key extends string,\n Name extends string,\n> = Name extends ExtractPermissionTargetNames ? Key : never;\n\n/**\n * A utility type for finding the permission target key corresponding to a\n * target name. In a way, the inverse of {@link ExtractPermissionTargetNames}.\n *\n * See the README for the distinction between target names and keys.\n *\n * @template Key - The target key type to extract from.\n * @template Name - The name whose key to extract.\n */\nexport type ExtractPermissionTargetKey<\n Key extends string,\n Name extends string,\n> = Key extends Name ? Key : Extract>;\n\n/**\n * Internal utility for extracting the members types of an array. The type\n * evalutes to `never` if the specified type is the empty tuple or neither\n * an array nor a tuple.\n *\n * @template ArrayType - The array type whose members to extract.\n */\ntype ExtractArrayMembers = ArrayType extends []\n ? never\n : ArrayType extends any[] | readonly any[]\n ? ArrayType[number]\n : never;\n\n/**\n * A utility type for extracting the allowed caveat types for a particular\n * permission from a permission specification type.\n *\n * @template PermissionSpecification - The permission specification type to\n * extract valid caveat types from.\n */\nexport type ExtractAllowedCaveatTypes<\n PermissionSpecification extends PermissionSpecificationConstraint,\n> = ExtractArrayMembers;\n\n/**\n * The options object of {@link constructPermission}.\n *\n * @template TargetPermission - The {@link Permission} that will be constructed.\n */\nexport type PermissionOptions = {\n target: TargetPermission['parentCapability'];\n /**\n * The origin string of the subject that has the permission.\n */\n invoker: OriginString;\n\n /**\n * The caveats of the permission.\n * See {@link Caveat}.\n */\n caveats?: NonEmptyArray;\n};\n\n/**\n * The default permission factory function. Naively constructs a permission from\n * the inputs. Sets a default, random `id` if none is provided.\n *\n * @see {@link Permission} For more details.\n * @template TargetPermission- - The {@link Permission} that will be constructed.\n * @param options - The options for the permission.\n * @returns The new permission object.\n */\nexport function constructPermission<\n TargetPermission extends PermissionConstraint,\n>(options: PermissionOptions): TargetPermission {\n const { caveats = null, invoker, target } = options;\n\n return {\n id: nanoid(),\n parentCapability: target,\n invoker,\n caveats,\n date: new Date().getTime(),\n } as TargetPermission;\n}\n\n/**\n * Gets the caveat of the specified type belonging to the specified permission.\n *\n * @param permission - The permission whose caveat to retrieve.\n * @param caveatType - The type of the caveat to retrieve.\n * @returns The caveat, or undefined if no such caveat exists.\n */\nexport function findCaveat(\n permission: PermissionConstraint,\n caveatType: string,\n): CaveatConstraint | undefined {\n return permission.caveats?.find((caveat) => caveat.type === caveatType);\n}\n\n/**\n * A requested permission object. Just an object with any of the properties\n * of a {@link PermissionConstraint} object.\n */\ntype RequestedPermission = Partial;\n\n/**\n * A record of target names and their {@link RequestedPermission} objects.\n */\nexport type RequestedPermissions = Record;\n\n/**\n * The restricted method context object. Essentially a way to pass internal\n * arguments to restricted methods and caveat functions, most importantly the\n * requesting origin.\n */\ntype RestrictedMethodContext = Readonly<{\n origin: OriginString;\n [key: string]: any;\n}>;\n\nexport type RestrictedMethodParameters = Json[] | Record | void;\n\n/**\n * The arguments passed to a restricted method implementation.\n *\n * @template Params - The JSON-RPC parameters of the restricted method.\n */\nexport type RestrictedMethodOptions =\n {\n method: TargetName;\n params?: Params;\n context: RestrictedMethodContext;\n };\n\n/**\n * A synchronous restricted method implementation.\n *\n * @template Params - The JSON-RPC parameters of the restricted method.\n * @template Result - The JSON-RPC result of the restricted method.\n */\nexport type SyncRestrictedMethod<\n Params extends RestrictedMethodParameters,\n Result extends Json,\n> = (args: RestrictedMethodOptions) => Result;\n\n/**\n * An asynchronous restricted method implementation.\n *\n * @template Params - The JSON-RPC parameters of the restricted method.\n * @template Result - The JSON-RPC result of the restricted method.\n */\nexport type AsyncRestrictedMethod<\n Params extends RestrictedMethodParameters,\n Result extends Json,\n> = (args: RestrictedMethodOptions) => Promise;\n\n/**\n * A synchronous or asynchronous restricted method implementation.\n *\n * @template Params - The JSON-RPC parameters of the restricted method.\n * @template Result - The JSON-RPC result of the restricted method.\n */\nexport type RestrictedMethod<\n Params extends RestrictedMethodParameters,\n Result extends Json,\n> =\n | SyncRestrictedMethod\n | AsyncRestrictedMethod;\n\nexport type ValidRestrictedMethod<\n MethodImplementation extends RestrictedMethod,\n> = MethodImplementation extends (args: infer Options) => Json | Promise\n ? Options extends RestrictedMethodOptions\n ? MethodImplementation\n : never\n : never;\n\n/**\n * {@link EndowmentGetter} parameter object.\n */\nexport type EndowmentGetterParams = {\n /**\n * The origin of the requesting subject.\n */\n origin: string;\n\n /**\n * Any additional data associated with the request.\n */\n requestData?: unknown;\n\n [key: string]: unknown;\n};\n\n/**\n * A synchronous or asynchronous function that gets the endowments for a\n * particular endowment permission. The getter receives the origin of the\n * requesting subject and, optionally, additional request metadata.\n */\nexport type EndowmentGetter = (\n options: EndowmentGetterParams,\n) => Endowments | Promise;\n\nexport type PermissionFactory<\n TargetPermission extends PermissionConstraint,\n RequestData extends Record,\n> = (\n options: PermissionOptions,\n requestData?: RequestData,\n) => TargetPermission;\n\nexport type PermissionValidatorConstraint = (\n permission: PermissionConstraint,\n origin?: OriginString,\n target?: string,\n) => void;\n\n/**\n * A utility type for ensuring that the given permission target key conforms to\n * our naming conventions.\n *\n * See the README for the distinction between target names and keys.\n *\n * @template Key - The target key string to apply the constraint to.\n */\ntype ValidTargetKey = Key extends `${string}_*`\n ? Key\n : Key extends `${string}_`\n ? never\n : Key extends `${string}*`\n ? never\n : Key;\n\n/**\n * The different possible types of permissions.\n */\nexport enum PermissionType {\n /**\n * A restricted JSON-RPC method. A subject must have the requisite permission\n * to call a restricted JSON-RPC method.\n */\n RestrictedMethod = 'RestrictedMethod',\n\n /**\n * An \"endowment\" granted to subjects that possess the requisite permission,\n * such as a global environment variable exposing a restricted API, etc.\n */\n Endowment = 'Endowment',\n}\n\n/**\n * The base constraint for permission specification objects. Every\n * {@link Permission} supported by a {@link PermissionController} must have an\n * associated specification, which is the source of truth for all permission-\n * related types. A permission specification includes the list of permitted\n * caveats, and any factory and validation functions specified by the consumer.\n * A concrete permission specification may specify further fields as necessary.\n *\n * See the README for more details.\n */\ntype PermissionSpecificationBase = {\n /**\n * The type of the specified permission.\n */\n permissionType: Type;\n\n /**\n * The target resource of the permission. The shape of this string depends on\n * the permission type. For example, a restricted method target key will\n * consist of either a complete method name or the prefix of a namespaced\n * method, e.g. `wallet_snap_*`.\n */\n targetKey: string;\n\n /**\n * An array of the caveat types that may be added to instances of this\n * permission.\n */\n allowedCaveats: Readonly> | null;\n\n /**\n * The factory function used to get permission objects. Permissions returned\n * by this function are presumed to valid, and they will not be passed to the\n * validator function associated with this specification (if any). In other\n * words, the factory function should validate the permissions it creates.\n *\n * If no factory is specified, the {@link Permission} constructor will be\n * used, and the validator function (if specified) will be called on newly\n * constructed permissions.\n */\n factory?: PermissionFactory>;\n\n /**\n * The validator function used to validate permissions of the associated type\n * whenever they are mutated. The only way a permission can be legally mutated\n * is when its caveats are modified by the permission controller.\n *\n * The validator should throw an appropriate JSON-RPC error if validation fails.\n */\n validator?: PermissionValidatorConstraint;\n};\n\n/**\n * The constraint for restricted method permission specification objects.\n * Permissions that correspond to JSON-RPC methods are specified using objects\n * that conform to this type.\n *\n * See the README for more details.\n */\nexport type RestrictedMethodSpecificationConstraint =\n PermissionSpecificationBase & {\n /**\n * The implementation of the restricted method that the permission\n * corresponds to.\n */\n methodImplementation: RestrictedMethod;\n };\n\n/**\n * The constraint for endowment permission specification objects. Permissions\n * that endow callers with some restricted resource are specified using objects\n * that conform to this type.\n *\n * See the README for more details.\n */\nexport type EndowmentSpecificationConstraint =\n PermissionSpecificationBase & {\n /**\n * Endowment permissions do not support caveats.\n */\n allowedCaveats: null;\n\n /**\n * The {@link EndowmentGetter} function for the permission. This function\n * will be called by the {@link PermissionController} whenever the\n * permission is invoked, after which the host can apply the endowments to\n * the requesting subject in the intended manner.\n */\n endowmentGetter: EndowmentGetter;\n };\n\n/**\n * The constraint for permission specification objects. Every {@link Permission}\n * supported by a {@link PermissionController} must have an associated\n * specification, which is the source of truth for all permission-related types.\n * All specifications must adhere to the {@link PermissionSpecificationBase}\n * interface, but specifications may have different fields depending on the\n * {@link PermissionType}.\n *\n * See the README for more details.\n */\nexport type PermissionSpecificationConstraint =\n | EndowmentSpecificationConstraint\n | RestrictedMethodSpecificationConstraint;\n\n/**\n * Options for {@link PermissionSpecificationBuilder} functions.\n */\ntype PermissionSpecificationBuilderOptions<\n FactoryHooks extends Record,\n MethodHooks extends Record,\n ValidatorHooks extends Record,\n> = {\n targetKey?: string;\n allowedCaveats?: Readonly> | null;\n factoryHooks?: FactoryHooks;\n methodHooks?: MethodHooks;\n validatorHooks?: ValidatorHooks;\n};\n\n/**\n * A function that builds a permission specification. Modules that specify\n * permissions for external consumption should make this their primary /\n * default export so that host applications can use them to generate concrete\n * specifications tailored to their requirements.\n */\nexport type PermissionSpecificationBuilder<\n Type extends PermissionType,\n Options extends PermissionSpecificationBuilderOptions,\n Specification extends PermissionSpecificationConstraint & {\n permissionType: Type;\n },\n> = (options: Options) => Specification;\n\n/**\n * A restricted method permission export object, containing the\n * {@link PermissionSpecificationBuilder} function and \"hook name\" objects.\n */\nexport type PermissionSpecificationBuilderExportConstraint = {\n targetKey: string;\n specificationBuilder: PermissionSpecificationBuilder<\n PermissionType,\n PermissionSpecificationBuilderOptions,\n PermissionSpecificationConstraint\n >;\n factoryHookNames?: Record;\n methodHookNames?: Record;\n validatorHookNames?: Record;\n};\n\ntype ValidRestrictedMethodSpecification<\n Specification extends RestrictedMethodSpecificationConstraint,\n> = Specification['methodImplementation'] extends ValidRestrictedMethod<\n Specification['methodImplementation']\n>\n ? Specification\n : never;\n\n/**\n * Constraint for {@link PermissionSpecificationConstraint} objects that\n * evaluates to `never` if the specification contains any invalid fields.\n *\n * @template Specification - The permission specification to validate.\n */\nexport type ValidPermissionSpecification<\n Specification extends PermissionSpecificationConstraint,\n> = Specification['targetKey'] extends ValidTargetKey<\n Specification['targetKey']\n>\n ? Specification['permissionType'] extends PermissionType.Endowment\n ? Specification\n : Specification['permissionType'] extends PermissionType.RestrictedMethod\n ? ValidRestrictedMethodSpecification<\n Extract\n >\n : never\n : never;\n\n/**\n * Checks that the specification has the expected permission type.\n *\n * @param specification - The specification to check.\n * @param expectedType - The expected permission type.\n * @template Specification - The specification to check.\n * @template Type - The expected permission type.\n * @returns Whether or not the specification is of the expected type.\n */\nexport function hasSpecificationType<\n Specification extends PermissionSpecificationConstraint,\n Type extends PermissionType,\n>(\n specification: Specification,\n expectedType: Type,\n): specification is Specification & {\n permissionType: Type;\n} {\n return specification.permissionType === expectedType;\n}\n\n/**\n * The specifications for all permissions supported by a particular\n * {@link PermissionController}.\n *\n * @template Specifications - The union of all {@link PermissionSpecificationConstraint} types.\n */\nexport type PermissionSpecificationMap<\n Specification extends PermissionSpecificationConstraint,\n> = {\n [TargetKey in Specification['targetKey']]: Specification extends {\n targetKey: TargetKey;\n }\n ? Specification\n : never;\n};\n\n/**\n * Extracts a specific {@link PermissionSpecificationConstraint} from a union of\n * permission specifications.\n *\n * @template Specification - The specification union type to extract from.\n * @template TargetKey - The `targetKey` of the specification to extract.\n */\nexport type ExtractPermissionSpecification<\n Specification extends PermissionSpecificationConstraint,\n TargetKey extends Specification['targetKey'],\n> = Specification extends {\n targetKey: TargetKey;\n}\n ? Specification\n : never;\n"]} \ No newline at end of file diff --git a/dist/permissions/PermissionController.d.ts b/dist/permissions/PermissionController.d.ts new file mode 100644 index 0000000000..6b56c9831a --- /dev/null +++ b/dist/permissions/PermissionController.d.ts @@ -0,0 +1,870 @@ +import { Patch } from 'immer'; +import { AcceptRequest as AcceptApprovalRequest, AddApprovalRequest, HasApprovalRequest, RejectRequest as RejectApprovalRequest } from '../approval/ApprovalController'; +import { BaseController, Json } from '../BaseControllerV2'; +import { RestrictedControllerMessenger } from '../ControllerMessenger'; +import { NonEmptyArray } from '../util'; +import { CaveatConstraint, CaveatSpecificationConstraint, CaveatSpecificationMap, ExtractCaveat, ExtractCaveats, ExtractCaveatValue } from './Caveat'; +import { EndowmentSpecificationConstraint, ExtractAllowedCaveatTypes, OriginString, PermissionConstraint, PermissionSpecificationConstraint, PermissionSpecificationMap, RequestedPermissions, RestrictedMethod, RestrictedMethodParameters, RestrictedMethodSpecificationConstraint, ValidPermission, ValidPermissionSpecification } from './Permission'; +import { getPermissionMiddlewareFactory } from './permission-middleware'; +/** + * Metadata associated with {@link PermissionController} subjects. + */ +export declare type PermissionSubjectMetadata = { + origin: OriginString; +}; +/** + * Metadata associated with permission requests. + */ +export declare type PermissionsRequestMetadata = PermissionSubjectMetadata & { + id: string; +}; +/** + * Used for prompting the user about a proposed new permission. + * Includes information about the grantee subject, requested permissions, and + * any additional information added by the consumer. + * + * All properties except `permissions` are passed to any factories found for + * the requested permissions. + */ +export declare type PermissionsRequest = { + metadata: PermissionsRequestMetadata; + permissions: RequestedPermissions; + [key: string]: Json; +}; +/** + * The name of the {@link PermissionController}. + */ +declare const controllerName = "PermissionController"; +/** + * Permissions associated with a {@link PermissionController} subject. + */ +export declare type SubjectPermissions = Record; +/** + * Permissions and metadata associated with a {@link PermissionController} + * subject. + */ +export declare type PermissionSubjectEntry = { + origin: SubjectPermission['invoker']; + permissions: SubjectPermissions; +}; +/** + * All subjects of a {@link PermissionController}. + * + * @template SubjectPermission - The permissions of the subject. + */ +export declare type PermissionControllerSubjects = Record>; +/** + * The state of a {@link PermissionController}. + * + * @template Permission - The controller's permission type union. + */ +export declare type PermissionControllerState = Permission extends PermissionConstraint ? { + subjects: PermissionControllerSubjects; +} : never; +/** + * Gets the state of the {@link PermissionController}. + */ +export declare type GetPermissionControllerState = { + type: `${typeof controllerName}:getState`; + handler: () => PermissionControllerState; +}; +/** + * Gets the names of all subjects from the {@link PermissionController}. + */ +export declare type GetSubjects = { + type: `${typeof controllerName}:getSubjectNames`; + handler: () => (keyof PermissionControllerSubjects)[]; +}; +/** + * Gets the permissions for specified subject + */ +export declare type GetPermissions = { + type: `${typeof controllerName}:getPermissions`; + handler: GenericPermissionController['getPermissions']; +}; +/** + * Checks whether the specified subject has any permissions. + */ +export declare type HasPermissions = { + type: `${typeof controllerName}:hasPermissions`; + handler: GenericPermissionController['hasPermissions']; +}; +/** + * Checks whether the specified subject has a specific permission. + */ +export declare type HasPermission = { + type: `${typeof controllerName}:hasPermission`; + handler: GenericPermissionController['hasPermission']; +}; +/** + * Directly grants given permissions for a specificed origin without requesting user approval + */ +export declare type GrantPermissions = { + type: `${typeof controllerName}:grantPermissions`; + handler: GenericPermissionController['grantPermissions']; +}; +/** + * Requests given permissions for a specified origin + */ +export declare type RequestPermissions = { + type: `${typeof controllerName}:requestPermissions`; + handler: GenericPermissionController['requestPermissions']; +}; +/** + * Removes the specified permissions for each origin. + */ +export declare type RevokePermissions = { + type: `${typeof controllerName}:revokePermissions`; + handler: GenericPermissionController['revokePermissions']; +}; +/** + * Removes all permissions for a given origin + */ +export declare type RevokeAllPermissions = { + type: `${typeof controllerName}:revokeAllPermissions`; + handler: GenericPermissionController['revokeAllPermissions']; +}; +/** + * Revokes all permissions corresponding to the specified target for all subjects. + * Does nothing if no subjects or no such permission exists. + */ +export declare type RevokePermissionForAllSubjects = { + type: `${typeof controllerName}:revokePermissionForAllSubjects`; + handler: GenericPermissionController['revokePermissionForAllSubjects']; +}; +/** + * Clears all permissions from the {@link PermissionController}. + */ +export declare type ClearPermissions = { + type: `${typeof controllerName}:clearPermissions`; + handler: () => void; +}; +/** + * Gets the endowments for the given subject and permission. + */ +export declare type GetEndowments = { + type: `${typeof controllerName}:getEndowments`; + handler: GenericPermissionController['getEndowments']; +}; +/** + * The {@link ControllerMessenger} actions of the {@link PermissionController}. + */ +export declare type PermissionControllerActions = ClearPermissions | GetEndowments | GetPermissionControllerState | GetSubjects | GetPermissions | HasPermission | HasPermissions | GrantPermissions | RequestPermissions | RevokeAllPermissions | RevokePermissionForAllSubjects | RevokePermissions; +/** + * The generic state change event of the {@link PermissionController}. + */ +export declare type PermissionControllerStateChange = { + type: `${typeof controllerName}:stateChange`; + payload: [PermissionControllerState, Patch[]]; +}; +/** + * The {@link ControllerMessenger} events of the {@link PermissionController}. + * + * The permission controller only emits its generic state change events. + * Consumers should use selector subscriptions to subscribe to relevant + * substate. + */ +export declare type PermissionControllerEvents = PermissionControllerStateChange; +/** + * The external {@link ControllerMessenger} actions available to the + * {@link PermissionController}. + */ +declare type AllowedActions = AddApprovalRequest | HasApprovalRequest | AcceptApprovalRequest | RejectApprovalRequest; +/** + * The messenger of the {@link PermissionController}. + */ +export declare type PermissionControllerMessenger = RestrictedControllerMessenger; +/** + * A generic {@link PermissionController}. + */ +export declare type GenericPermissionController = PermissionController; +/** + * Describes the possible results of a {@link CaveatMutator} function. + */ +export declare enum CaveatMutatorOperation { + noop = 0, + updateValue = 1, + deleteCaveat = 2, + revokePermission = 3 +} +/** + * Given a caveat value, returns a {@link CaveatMutatorOperation} and, optionally, + * a new caveat value. + * + * @see {@link PermissionController.updatePermissionsByCaveat} for more details. + * @template Caveat - The caveat type for which this mutator is intended. + * @param caveatValue - The existing value of the caveat being mutated. + * @returns A tuple of the mutation result and, optionally, the new caveat + * value. + */ +export declare type CaveatMutator = (caveatValue: TargetCaveat['value']) => CaveatMutatorResult; +declare type CaveatMutatorResult = Readonly<{ + operation: CaveatMutatorOperation.updateValue; + value: CaveatConstraint['value']; +}> | Readonly<{ + operation: Exclude; +}>; +/** + * Extracts the permission(s) specified by the given permission and caveat + * specifications. + * + * @template ControllerPermissionSpecification - The permission specification(s) + * to extract from. + * @template ControllerCaveatSpecification - The caveat specification(s) to + * extract from. Necessary because {@link Permission} has a generic parameter + * that describes the allowed caveats for the permission. + */ +export declare type ExtractPermission = ControllerPermissionSpecification extends ValidPermissionSpecification ? ValidPermission> : never; +/** + * Extracts the restricted method permission(s) specified by the given + * permission and caveat specifications. + * + * @template ControllerPermissionSpecification - The permission specification(s) + * to extract from. + * @template ControllerCaveatSpecification - The caveat specification(s) to + * extract from. Necessary because {@link Permission} has a generic parameter + * that describes the allowed caveats for the permission. + */ +export declare type ExtractRestrictedMethodPermission = ExtractPermission, ControllerCaveatSpecification>; +/** + * Extracts the endowment permission(s) specified by the given permission and + * caveat specifications. + * + * @template ControllerPermissionSpecification - The permission specification(s) + * to extract from. + * @template ControllerCaveatSpecification - The caveat specification(s) to + * extract from. Necessary because {@link Permission} has a generic parameter + * that describes the allowed caveats for the permission. + */ +export declare type ExtractEndowmentPermission = ExtractPermission, ControllerCaveatSpecification>; +/** + * Options for the {@link PermissionController} constructor. + * + * @template ControllerPermissionSpecification - A union of the types of all + * permission specifications available to the controller. Any referenced caveats + * must be included in the controller's caveat specifications. + * @template ControllerCaveatSpecification - A union of the types of all + * caveat specifications available to the controller. + */ +export declare type PermissionControllerOptions = { + messenger: PermissionControllerMessenger; + caveatSpecifications: CaveatSpecificationMap; + permissionSpecifications: PermissionSpecificationMap; + unrestrictedMethods: string[]; + state?: Partial>>; +}; +/** + * The permission controller. See the README for details. + * + * Assumes the existence of an {@link ApprovalController} reachable via the + * {@link ControllerMessenger}. + * + * @template ControllerPermissionSpecification - A union of the types of all + * permission specifications available to the controller. Any referenced caveats + * must be included in the controller's caveat specifications. + * @template ControllerCaveatSpecification - A union of the types of all + * caveat specifications available to the controller. + */ +export declare class PermissionController extends BaseController>, PermissionControllerMessenger> { + private readonly _caveatSpecifications; + private readonly _permissionSpecifications; + private readonly _unrestrictedMethods; + /** + * The names of all JSON-RPC methods that will be ignored by the controller. + * + * @returns The names of all unrestricted JSON-RPC methods + */ + get unrestrictedMethods(): ReadonlySet; + /** + * Returns a `json-rpc-engine` middleware function factory, so that the rules + * described by the state of this controller can be applied to incoming + * JSON-RPC requests. + * + * The middleware **must** be added in the correct place in the middleware + * stack in order for it to work. See the README for an example. + */ + createPermissionMiddleware: ReturnType; + /** + * Constructs the PermissionController. + * + * @param options - Permission controller options. + * @param options.caveatSpecifications - The specifications of all caveats + * available to the controller. See {@link CaveatSpecificationMap} and the + * documentation for more details. + * @param options.permissionSpecifications - The specifications of all + * permissions available to the controller. See + * {@link PermissionSpecificationMap} and the README for more details. + * @param options.unrestrictedMethods - The callable names of all JSON-RPC + * methods ignored by the new controller. + * @param options.messenger - The controller messenger. See + * {@link BaseController} for more information. + * @param options.state - Existing state to hydrate the controller with at + * initialization. + */ + constructor(options: PermissionControllerOptions); + /** + * Gets a permission specification. + * + * @param targetKey - The target key of the permission specification to get. + * @returns The permission specification with the specified target key. + */ + private getPermissionSpecification; + /** + * Gets a caveat specification. + * + * @param caveatType - The type of the caveat specification to get. + * @returns The caveat specification with the specified type. + */ + private getCaveatSpecification; + /** + * Constructor helper for validating permission specifications. This is + * intended to prevent the use of invalid target keys which, while impossible + * to add in TypeScript, could rather easily occur in plain JavaScript. + * + * Throws an error if validation fails. + * + * @param permissionSpecifications - The permission specifications passed to + * this controller's constructor. + * @param caveatSpecifications - The caveat specifications passed to this + * controller. + */ + private validatePermissionSpecifications; + /** + * Constructor helper for registering the controller's messaging system + * actions. + */ + private registerMessageHandlers; + /** + * Clears the state of the controller. + */ + clearState(): void; + /** + * Gets the permission specification corresponding to the given permission + * type and target name. Throws an error if the target name does not + * correspond to a permission, or if the specification is not of the + * given permission type. + * + * @template Type - The type of the permission specification to get. + * @param permissionType - The type of the permission specification to get. + * @param targetName - The name of the permission whose specification to get. + * @param requestingOrigin - The origin of the requesting subject, if any. + * Will be added to any thrown errors. + * @returns The specification object corresponding to the given type and + * target name. + */ + private getTypedPermissionSpecification; + /** + * Gets the implementation of the specified restricted method. + * + * A JSON-RPC error is thrown if the method does not exist. + * + * @see {@link PermissionController.executeRestrictedMethod} and + * {@link PermissionController.createPermissionMiddleware} for internal usage. + * @param method - The name of the restricted method. + * @param origin - The origin associated with the request for the restricted + * method, if any. + * @returns The restricted method implementation. + */ + getRestrictedMethod(method: string, origin?: string): RestrictedMethod; + /** + * Gets a list of all origins of subjects. + * + * @returns The origins (i.e. IDs) of all subjects. + */ + getSubjectNames(): OriginString[]; + /** + * Gets the permission for the specified target of the subject corresponding + * to the specified origin. + * + * @param origin - The origin of the subject. + * @param targetName - The method name as invoked by a third party (i.e., not + * a method key). + * @returns The permission if it exists, or undefined otherwise. + */ + getPermission>(origin: OriginString, targetName: SubjectPermission['parentCapability']): SubjectPermission | undefined; + /** + * Gets all permissions for the specified subject, if any. + * + * @param origin - The origin of the subject. + * @returns The permissions of the subject, if any. + */ + getPermissions(origin: OriginString): SubjectPermissions>> | undefined; + /** + * Checks whether the subject with the specified origin has the specified + * permission. + * + * @param origin - The origin of the subject. + * @param target - The target name of the permission. + * @returns Whether the subject has the permission. + */ + hasPermission(origin: OriginString, target: ExtractPermission['parentCapability']): boolean; + /** + * Checks whether the subject with the specified origin has any permissions. + * Use this if you want to know if a subject "exists". + * + * @param origin - The origin of the subject to check. + * @returns Whether the subject has any permissions. + */ + hasPermissions(origin: OriginString): boolean; + /** + * Revokes all permissions from the specified origin. + * + * Throws an error of the origin has no permissions. + * + * @param origin - The origin whose permissions to revoke. + */ + revokeAllPermissions(origin: OriginString): void; + /** + * Revokes the specified permission from the subject with the specified + * origin. + * + * Throws an error if the subject or the permission does not exist. + * + * @param origin - The origin of the subject whose permission to revoke. + * @param target - The target name of the permission to revoke. + */ + revokePermission(origin: OriginString, target: ExtractPermission['parentCapability']): void; + /** + * Revokes the specified permissions from the specified subjects. + * + * Throws an error if any of the subjects or permissions do not exist. + * + * @param subjectsAndPermissions - An object mapping subject origins + * to arrays of permission target names to revoke. + */ + revokePermissions(subjectsAndPermissions: Record['parentCapability']>>): void; + /** + * Revokes all permissions corresponding to the specified target for all subjects. + * Does nothing if no subjects or no such permission exists. + * + * @param target - The name of the target to revoke all permissions for. + */ + revokePermissionForAllSubjects(target: ExtractPermission['parentCapability']): void; + /** + * Deletes the permission identified by the given origin and target. If the + * permission is the single remaining permission of its subject, the subject + * is also deleted. + * + * @param subjects - The draft permission controller subjects. + * @param origin - The origin of the subject associated with the permission + * to delete. + * @param target - The target name of the permission to delete. + */ + private deletePermission; + /** + * Checks whether the permission of the subject corresponding to the given + * origin has a caveat of the specified type. + * + * Throws an error if the subject does not have a permission with the + * specified target name. + * + * @template TargetName - The permission target name. Should be inferred. + * @template CaveatType - The valid caveat types for the permission. Should + * be inferred. + * @param origin - The origin of the subject. + * @param target - The target name of the permission. + * @param caveatType - The type of the caveat to check for. + * @returns Whether the permission has the specified caveat. + */ + hasCaveat['parentCapability'], CaveatType extends ExtractAllowedCaveatTypes>(origin: OriginString, target: TargetName, caveatType: CaveatType): boolean; + /** + * Gets the caveat of the specified type, if any, for the permission of + * the subject corresponding to the given origin. + * + * Throws an error if the subject does not have a permission with the + * specified target name. + * + * @template TargetName - The permission target name. Should be inferred. + * @template CaveatType - The valid caveat types for the permission. Should + * be inferred. + * @param origin - The origin of the subject. + * @param target - The target name of the permission. + * @param caveatType - The type of the caveat to get. + * @returns The caveat, or `undefined` if no such caveat exists. + */ + getCaveat['parentCapability'], CaveatType extends ExtractAllowedCaveatTypes>(origin: OriginString, target: TargetName, caveatType: CaveatType): ExtractCaveat | undefined; + /** + * Adds a caveat of the specified type, with the specified caveat value, to + * the permission corresponding to the given subject origin and permission + * target. + * + * For modifying existing caveats, use + * {@link PermissionController.updateCaveat}. + * + * Throws an error if no such permission exists, or if the caveat already + * exists. + * + * @template TargetName - The permission target name. Should be inferred. + * @template CaveatType - The valid caveat types for the permission. Should + * be inferred. + * @param origin - The origin of the subject. + * @param target - The target name of the permission. + * @param caveatType - The type of the caveat to add. + * @param caveatValue - The value of the caveat to add. + */ + addCaveat['parentCapability'], CaveatType extends ExtractAllowedCaveatTypes>(origin: OriginString, target: TargetName, caveatType: CaveatType, caveatValue: ExtractCaveatValue): void; + /** + * Updates the value of the caveat of the specified type belonging to the + * permission corresponding to the given subject origin and permission + * target. + * + * For adding new caveats, use + * {@link PermissionController.addCaveat}. + * + * Throws an error if no such permission or caveat exists. + * + * @template TargetName - The permission target name. Should be inferred. + * @template CaveatType - The valid caveat types for the permission. Should + * be inferred. + * @param origin - The origin of the subject. + * @param target - The target name of the permission. + * @param caveatType - The type of the caveat to update. + * @param caveatValue - The new value of the caveat. + */ + updateCaveat['parentCapability'], CaveatType extends ExtractAllowedCaveatTypes, CaveatValue extends ExtractCaveatValue>(origin: OriginString, target: TargetName, caveatType: CaveatType, caveatValue: CaveatValue): void; + /** + * Sets the specified caveat on the specified permission. Overwrites existing + * caveats of the same type in-place (preserving array order), and adds the + * caveat to the end of the array otherwise. + * + * Throws an error if the permission does not exist or fails to validate after + * its caveats have been modified. + * + * @see {@link PermissionController.addCaveat} + * @see {@link PermissionController.updateCaveat} + * @template TargetName - The permission target name. Should be inferred. + * @template CaveatType - The valid caveat types for the permission. Should + * be inferred. + * @param origin - The origin of the subject. + * @param target - The target name of the permission. + * @param caveatType - The type of the caveat to set. + * @param caveatValue - The value of the caveat to set. + */ + private setCaveat; + /** + * Updates all caveats with the specified type for all subjects and + * permissions by applying the specified mutator function to them. + * + * ATTN: Permissions can be revoked entirely by the action of this method, + * read on for details. + * + * Caveat mutators are functions that receive a caveat value and return a + * tuple consisting of a {@link CaveatMutatorOperation} and, optionally, a new + * value to update the existing caveat with. + * + * For each caveat, depending on the mutator result, this method will: + * - Do nothing ({@link CaveatMutatorOperation.noop}) + * - Update the value of the caveat ({@link CaveatMutatorOperation.updateValue}). The caveat specification validator, if any, will be called after updating the value. + * - Delete the caveat ({@link CaveatMutatorOperation.deleteCaveat}). The permission specification validator, if any, will be called after deleting the caveat. + * - Revoke the parent permission ({@link CaveatMutatorOperation.revokePermission}) + * + * This method throws if the validation of any caveat or permission fails. + * + * @param targetCaveatType - The type of the caveats to update. + * @param mutator - The mutator function which will be applied to all caveat + * values. + */ + updatePermissionsByCaveat['type'], TargetCaveat extends ExtractCaveat>(targetCaveatType: CaveatType, mutator: CaveatMutator): void; + /** + * Removes the caveat of the specified type from the permission corresponding + * to the given subject origin and target name. + * + * Throws an error if no such permission or caveat exists. + * + * @template TargetName - The permission target name. Should be inferred. + * @template CaveatType - The valid caveat types for the permission. Should + * be inferred. + * @param origin - The origin of the subject. + * @param target - The target name of the permission. + * @param caveatType - The type of the caveat to remove. + */ + removeCaveat['parentCapability'], CaveatType extends ExtractAllowedCaveatTypes>(origin: OriginString, target: TargetName, caveatType: CaveatType): void; + /** + * Deletes the specified caveat from the specified permission. If no caveats + * remain after deletion, the permission's caveat property is set to `null`. + * The permission is validated after being modified. + * + * Throws an error if the permission does not have a caveat with the specified + * type. + * + * @param permission - The permission whose caveat to delete. + * @param caveatType - The type of the caveat to delete. + * @param origin - The origin the permission subject. + * @param target - The name of the permission target. + */ + private deleteCaveat; + /** + * Validates the specified modified permission. Should **always** be invoked + * on a permission after its caveats have been modified. + * + * Just like {@link PermissionController.validatePermission}, except that the + * corresponding target key and specification are retrieved first, and an + * error is thrown if the target key does not exist. + * + * @param permission - The modified permission to validate. + * @param origin - The origin associated with the permission. + * @param targetName - The target name name of the permission. + */ + private validateModifiedPermission; + /** + * Gets the key for the specified permission target. + * + * Used to support our namespaced permission target feature, which is used + * to implement namespaced restricted JSON-RPC methods. + * + * @param target - The requested permission target. + * @returns The internal key of the permission target. + */ + private getTargetKey; + /** + * Grants _approved_ permissions to the specified subject. Every permission and + * caveat is stringently validated – including by calling every specification + * validator – and an error is thrown if any validation fails. + * + * ATTN: This method does **not** prompt the user for approval. + * + * @see {@link PermissionController.requestPermissions} For initiating a + * permissions request requiring user approval. + * @param options - Options bag. + * @param options.approvedPermissions - The requested permissions approved by + * the user. + * @param options.requestData - Permission request data. Passed to permission + * factory functions. + * @param options.preserveExistingPermissions - Whether to preserve the + * subject's existing permissions. + * @param options.subject - The subject to grant permissions to. + * @returns The granted permissions. + */ + grantPermissions({ approvedPermissions, requestData, preserveExistingPermissions, subject, }: { + approvedPermissions: RequestedPermissions; + subject: PermissionSubjectMetadata; + preserveExistingPermissions?: boolean; + requestData?: Record; + }): SubjectPermissions>; + /** + * Validates the specified permission by: + * - Ensuring that its `caveats` property is either `null` or a non-empty array. + * - Ensuring that it only includes caveats allowed by its specification. + * - Ensuring that it includes no duplicate caveats (by caveat type). + * - Validating each caveat object, if `performCaveatValidation` is `true`. + * - Calling the validator of its specification, if one exists and `invokePermissionValidator` is `true`. + * + * An error is thrown if validation fails. + * + * @param specification - The specification of the permission. + * @param permission - The permission to validate. + * @param origin - The origin associated with the permission. + * @param targetName - The target name of the permission. + * @param validationOptions - Validation options. + * @param validationOptions.invokePermissionValidator - Whether to invoke the + * permission's consumer-specified validator function, if any. + * @param validationOptions.performCaveatValidation - Whether to invoke + * {@link PermissionController.validateCaveat} on each of the permission's + * caveats. + */ + private validatePermission; + /** + * Assigns the specified permissions to the subject with the given origin. + * Overwrites all existing permissions, and creates a subject entry if it + * doesn't already exist. + * + * ATTN: Assumes that the new permissions have been validated. + * + * @param origin - The origin of the grantee subject. + * @param permissions - The new permissions for the grantee subject. + */ + private setValidatedPermissions; + /** + * Validates the requested caveats for the permission of the specified + * subject origin and target name and returns the validated caveat array. + * + * Throws an error if validation fails. + * + * @param origin - The origin of the permission subject. + * @param target - The permission target name. + * @param requestedCaveats - The requested caveats to construct. + * @returns The constructed caveats. + */ + private constructCaveats; + /** + * This methods validates that the specified caveat is an object with the + * expected properties and types. It also ensures that a caveat specification + * exists for the requested caveat type, and calls the specification + * validator, if it exists, on the caveat object. + * + * Throws an error if validation fails. + * + * @param caveat - The caveat object to validate. + * @param origin - The origin associated with the subject of the parent + * permission. + * @param target - The target name associated with the parent permission. + */ + private validateCaveat; + /** + * Initiates a permission request that requires user approval. This should + * always be used to grant additional permissions to a subject, unless user + * approval has been obtained through some other means. + * + * Permissions are validated at every step of the approval process, and this + * method will reject if validation fails. + * + * @see {@link ApprovalController} For the user approval logic. + * @see {@link PermissionController.acceptPermissionsRequest} For the method + * that _accepts_ the request and resolves the user approval promise. + * @see {@link PermissionController.rejectPermissionsRequest} For the method + * that _rejects_ the request and the user approval promise. + * @param subject - The grantee subject. + * @param requestedPermissions - The requested permissions. + * @param options - Additional options. + * @param options.id - The id of the permissions request. Defaults to a unique + * id. + * @param options.preserveExistingPermissions - Whether to preserve the + * subject's existing permissions. Defaults to `true`. + * @returns The granted permissions and request metadata. + */ + requestPermissions(subject: PermissionSubjectMetadata, requestedPermissions: RequestedPermissions, options?: { + id?: string; + preserveExistingPermissions?: boolean; + }): Promise<[ + SubjectPermissions>, + { + id: string; + origin: OriginString; + } + ]>; + /** + * Validates requested permissions. Throws if validation fails. + * + * This method ensures that the requested permissions are a properly + * formatted {@link RequestedPermissions} object, and performs the same + * validation as {@link PermissionController.grantPermissions}, except that + * consumer-specified permission validator functions are not called, since + * they are only called on fully constructed, approved permissions that are + * otherwise completely valid. + * + * Unrecognzied properties on requested permissions are ignored. + * + * @param origin - The origin of the grantee subject. + * @param requestedPermissions - The requested permissions. + */ + private validateRequestedPermissions; + /** + * Adds a request to the {@link ApprovalController} using the + * {@link AddApprovalRequest} action. Also validates the resulting approved + * permissions request, and throws an error if validation fails. + * + * @param permissionsRequest - The permissions request object. + * @returns The approved permissions request object. + */ + private requestUserApproval; + /** + * Validates an approved {@link PermissionsRequest} object. The approved + * request must have the required `metadata` and `permissions` properties, + * the `id` and `origin` of the `metadata` must match the original request + * metadata, and the requested permissions must be valid per + * {@link PermissionController.validateRequestedPermissions}. Any extra + * metadata properties are ignored. + * + * An error is thrown if validation fails. + * + * @param approvedRequest - The approved permissions request object. + * @param originalMetadata - The original request metadata. + */ + private validateApprovedPermissions; + /** + * Accepts a permissions request created by + * {@link PermissionController.requestPermissions}. + * + * @param request - The permissions request. + */ + acceptPermissionsRequest(request: PermissionsRequest): Promise; + /** + * Rejects a permissions request created by + * {@link PermissionController.requestPermissions}. + * + * @param id - The id of the request to be rejected. + */ + rejectPermissionsRequest(id: string): Promise; + /** + * Checks whether the {@link ApprovalController} has a particular permissions + * request. + * + * @see {@link PermissionController.acceptPermissionsRequest} and + * {@link PermissionController.rejectPermissionsRequest} for usage. + * @param options - The {@link HasApprovalRequest} options. + * @param options.id - The id of the approval request to check for. + * @returns Whether the specified request exists. + */ + private hasApprovalRequest; + /** + * Rejects the permissions request with the specified id, with the specified + * error as the reason. This method is effectively a wrapper around a + * messenger call for the `ApprovalController:rejectRequest` action. + * + * @see {@link PermissionController.acceptPermissionsRequest} and + * {@link PermissionController.rejectPermissionsRequest} for usage. + * @param id - The id of the request to reject. + * @param error - The error associated with the rejection. + * @returns Nothing + */ + private _rejectPermissionsRequest; + /** + * Gets the subject's endowments per the specified endowment permission. + * Throws if the subject does not have the required permission or if the + * permission is not an endowment permission. + * + * @param origin - The origin of the subject whose endowments to retrieve. + * @param targetName - The name of the endowment permission. This must be a + * valid permission target name. + * @param requestData - Additional data associated with the request, if any. + * Forwarded to the endowment getter function for the permission. + * @returns The endowments, if any. + */ + getEndowments(origin: string, targetName: ExtractEndowmentPermission['parentCapability'], requestData?: unknown): Promise; + /** + * Executes a restricted method as the subject with the given origin. + * The specified params, if any, will be passed to the method implementation. + * + * ATTN: Great caution should be exercised in the use of this method. + * Methods that cause side effects or affect application state should + * be avoided. + * + * This method will first attempt to retrieve the requested restricted method + * implementation, throwing if it does not exist. The method will then be + * invoked as though the subject with the specified origin had invoked it with + * the specified parameters. This means that any existing caveats will be + * applied to the restricted method, and this method will throw if the + * restricted method or its caveat decorators throw. + * + * In addition, this method will throw if the subject does not have a + * permission for the specified restricted method. + * + * @param origin - The origin of the subject to execute the method on behalf + * of. + * @param targetName - The name of the method to execute. This must be a valid + * permission target name. + * @param params - The parameters to pass to the method implementation. + * @returns The result of the executed method. + */ + executeRestrictedMethod(origin: OriginString, targetName: ExtractRestrictedMethodPermission['parentCapability'], params?: RestrictedMethodParameters): Promise; + /** + * An internal method used in the controller's `json-rpc-engine` middleware + * and {@link PermissionController.executeRestrictedMethod}. Calls the + * specified restricted method implementation after decorating it with the + * caveats of its permission. Throws if the subject does not have the + * requisite permission. + * + * ATTN: Parameter validation is the responsibility of the caller, or + * the restricted method implementation in the case of `params`. + * + * @see {@link PermissionController.executeRestrictedMethod} and + * {@link PermissionController.createPermissionMiddleware} for usage. + * @param methodImplementation - The implementation of the method to call. + * @param subject - Metadata about the subject that made the request. + * @param method - The method name + * @param params - Params needed for executing the restricted method + * @returns The result of the restricted method implementation + */ + private _executeRestrictedMethod; +} +export {}; diff --git a/dist/permissions/PermissionController.js b/dist/permissions/PermissionController.js new file mode 100644 index 0000000000..e1661ca5a2 --- /dev/null +++ b/dist/permissions/PermissionController.js @@ -0,0 +1,1220 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PermissionController = exports.CaveatMutatorOperation = void 0; +const deep_freeze_strict_1 = __importDefault(require("deep-freeze-strict")); +const immer_1 = require("immer"); +const nanoid_1 = require("nanoid"); +const eth_rpc_errors_1 = require("eth-rpc-errors"); +const BaseControllerV2_1 = require("../BaseControllerV2"); +const util_1 = require("../util"); +const Caveat_1 = require("./Caveat"); +const errors_1 = require("./errors"); +const Permission_1 = require("./Permission"); +const permission_middleware_1 = require("./permission-middleware"); +const utils_1 = require("./utils"); +/** + * The name of the {@link PermissionController}. + */ +const controllerName = 'PermissionController'; +/** + * Get the state metadata of the {@link PermissionController}. + * + * @template Permission - The controller's permission type union. + * @returns The state metadata + */ +function getStateMetadata() { + return { subjects: { anonymous: true, persist: true } }; +} +/** + * Get the default state of the {@link PermissionController}. + * + * @template Permission - The controller's permission type union. + * @returns The default state of the controller + */ +function getDefaultState() { + return { subjects: {} }; +} +/** + * Describes the possible results of a {@link CaveatMutator} function. + */ +var CaveatMutatorOperation; +(function (CaveatMutatorOperation) { + CaveatMutatorOperation[CaveatMutatorOperation["noop"] = 0] = "noop"; + CaveatMutatorOperation[CaveatMutatorOperation["updateValue"] = 1] = "updateValue"; + CaveatMutatorOperation[CaveatMutatorOperation["deleteCaveat"] = 2] = "deleteCaveat"; + CaveatMutatorOperation[CaveatMutatorOperation["revokePermission"] = 3] = "revokePermission"; +})(CaveatMutatorOperation = exports.CaveatMutatorOperation || (exports.CaveatMutatorOperation = {})); +/** + * The permission controller. See the README for details. + * + * Assumes the existence of an {@link ApprovalController} reachable via the + * {@link ControllerMessenger}. + * + * @template ControllerPermissionSpecification - A union of the types of all + * permission specifications available to the controller. Any referenced caveats + * must be included in the controller's caveat specifications. + * @template ControllerCaveatSpecification - A union of the types of all + * caveat specifications available to the controller. + */ +class PermissionController extends BaseControllerV2_1.BaseController { + /** + * Constructs the PermissionController. + * + * @param options - Permission controller options. + * @param options.caveatSpecifications - The specifications of all caveats + * available to the controller. See {@link CaveatSpecificationMap} and the + * documentation for more details. + * @param options.permissionSpecifications - The specifications of all + * permissions available to the controller. See + * {@link PermissionSpecificationMap} and the README for more details. + * @param options.unrestrictedMethods - The callable names of all JSON-RPC + * methods ignored by the new controller. + * @param options.messenger - The controller messenger. See + * {@link BaseController} for more information. + * @param options.state - Existing state to hydrate the controller with at + * initialization. + */ + constructor(options) { + const { caveatSpecifications, permissionSpecifications, unrestrictedMethods, messenger, state = {}, } = options; + super({ + name: controllerName, + metadata: getStateMetadata(), + messenger, + state: Object.assign(Object.assign({}, getDefaultState()), state), + }); + this._unrestrictedMethods = new Set(unrestrictedMethods); + this._caveatSpecifications = (0, deep_freeze_strict_1.default)(Object.assign({}, caveatSpecifications)); + this.validatePermissionSpecifications(permissionSpecifications, this._caveatSpecifications); + this._permissionSpecifications = (0, deep_freeze_strict_1.default)(Object.assign({}, permissionSpecifications)); + this.registerMessageHandlers(); + this.createPermissionMiddleware = (0, permission_middleware_1.getPermissionMiddlewareFactory)({ + executeRestrictedMethod: this._executeRestrictedMethod.bind(this), + getRestrictedMethod: this.getRestrictedMethod.bind(this), + isUnrestrictedMethod: this.unrestrictedMethods.has.bind(this.unrestrictedMethods), + }); + } + /** + * The names of all JSON-RPC methods that will be ignored by the controller. + * + * @returns The names of all unrestricted JSON-RPC methods + */ + get unrestrictedMethods() { + return this._unrestrictedMethods; + } + /** + * Gets a permission specification. + * + * @param targetKey - The target key of the permission specification to get. + * @returns The permission specification with the specified target key. + */ + getPermissionSpecification(targetKey) { + return this._permissionSpecifications[targetKey]; + } + /** + * Gets a caveat specification. + * + * @param caveatType - The type of the caveat specification to get. + * @returns The caveat specification with the specified type. + */ + getCaveatSpecification(caveatType) { + return this._caveatSpecifications[caveatType]; + } + /** + * Constructor helper for validating permission specifications. This is + * intended to prevent the use of invalid target keys which, while impossible + * to add in TypeScript, could rather easily occur in plain JavaScript. + * + * Throws an error if validation fails. + * + * @param permissionSpecifications - The permission specifications passed to + * this controller's constructor. + * @param caveatSpecifications - The caveat specifications passed to this + * controller. + */ + validatePermissionSpecifications(permissionSpecifications, caveatSpecifications) { + Object.entries(permissionSpecifications).forEach(([targetKey, { permissionType, targetKey: innerTargetKey, allowedCaveats },]) => { + if (!permissionType || !(0, util_1.hasProperty)(Permission_1.PermissionType, permissionType)) { + throw new Error(`Invalid permission type: "${permissionType}"`); + } + // Check if the target key is the empty string, ends with "_", or ends + // with "*" but not "_*" + if (!targetKey || /_$/u.test(targetKey) || /[^_]\*$/u.test(targetKey)) { + throw new Error(`Invalid permission target key: "${targetKey}"`); + } + if (targetKey !== innerTargetKey) { + throw new Error(`Invalid permission specification: key "${targetKey}" must match specification.target value "${innerTargetKey}".`); + } + if (allowedCaveats) { + allowedCaveats.forEach((caveatType) => { + if (!(0, util_1.hasProperty)(caveatSpecifications, caveatType)) { + throw new errors_1.UnrecognizedCaveatTypeError(caveatType); + } + }); + } + }); + } + /** + * Constructor helper for registering the controller's messaging system + * actions. + */ + registerMessageHandlers() { + this.messagingSystem.registerActionHandler(`${controllerName}:clearPermissions`, () => this.clearState()); + this.messagingSystem.registerActionHandler(`${controllerName}:getEndowments`, (origin, targetName, requestData) => this.getEndowments(origin, targetName, requestData)); + this.messagingSystem.registerActionHandler(`${controllerName}:getSubjectNames`, () => this.getSubjectNames()); + this.messagingSystem.registerActionHandler(`${controllerName}:getPermissions`, (origin) => this.getPermissions(origin)); + this.messagingSystem.registerActionHandler(`${controllerName}:hasPermission`, (origin, targetName) => this.hasPermission(origin, targetName)); + this.messagingSystem.registerActionHandler(`${controllerName}:hasPermissions`, (origin) => this.hasPermissions(origin)); + this.messagingSystem.registerActionHandler(`${controllerName}:grantPermissions`, this.grantPermissions.bind(this)); + this.messagingSystem.registerActionHandler(`${controllerName}:requestPermissions`, (subject, permissions) => this.requestPermissions(subject, permissions)); + this.messagingSystem.registerActionHandler(`${controllerName}:revokeAllPermissions`, (origin) => this.revokeAllPermissions(origin)); + this.messagingSystem.registerActionHandler(`${controllerName}:revokePermissionForAllSubjects`, (target) => this.revokePermissionForAllSubjects(target)); + this.messagingSystem.registerActionHandler(`${controllerName}:revokePermissions`, this.revokePermissions.bind(this)); + } + /** + * Clears the state of the controller. + */ + clearState() { + this.update((_draftState) => { + return Object.assign({}, getDefaultState()); + }); + } + /** + * Gets the permission specification corresponding to the given permission + * type and target name. Throws an error if the target name does not + * correspond to a permission, or if the specification is not of the + * given permission type. + * + * @template Type - The type of the permission specification to get. + * @param permissionType - The type of the permission specification to get. + * @param targetName - The name of the permission whose specification to get. + * @param requestingOrigin - The origin of the requesting subject, if any. + * Will be added to any thrown errors. + * @returns The specification object corresponding to the given type and + * target name. + */ + getTypedPermissionSpecification(permissionType, targetName, requestingOrigin) { + const failureError = permissionType === Permission_1.PermissionType.RestrictedMethod + ? (0, errors_1.methodNotFound)(targetName, requestingOrigin ? { origin: requestingOrigin } : undefined) + : new errors_1.EndowmentPermissionDoesNotExistError(targetName, requestingOrigin); + const targetKey = this.getTargetKey(targetName); + if (!targetKey) { + throw failureError; + } + const specification = this.getPermissionSpecification(targetKey); + if (!(0, Permission_1.hasSpecificationType)(specification, permissionType)) { + throw failureError; + } + return specification; + } + /** + * Gets the implementation of the specified restricted method. + * + * A JSON-RPC error is thrown if the method does not exist. + * + * @see {@link PermissionController.executeRestrictedMethod} and + * {@link PermissionController.createPermissionMiddleware} for internal usage. + * @param method - The name of the restricted method. + * @param origin - The origin associated with the request for the restricted + * method, if any. + * @returns The restricted method implementation. + */ + getRestrictedMethod(method, origin) { + return this.getTypedPermissionSpecification(Permission_1.PermissionType.RestrictedMethod, method, origin).methodImplementation; + } + /** + * Gets a list of all origins of subjects. + * + * @returns The origins (i.e. IDs) of all subjects. + */ + getSubjectNames() { + return Object.keys(this.state.subjects); + } + /** + * Gets the permission for the specified target of the subject corresponding + * to the specified origin. + * + * @param origin - The origin of the subject. + * @param targetName - The method name as invoked by a third party (i.e., not + * a method key). + * @returns The permission if it exists, or undefined otherwise. + */ + getPermission(origin, targetName) { + var _a; + return (_a = this.state.subjects[origin]) === null || _a === void 0 ? void 0 : _a.permissions[targetName]; + } + /** + * Gets all permissions for the specified subject, if any. + * + * @param origin - The origin of the subject. + * @returns The permissions of the subject, if any. + */ + getPermissions(origin) { + var _a; + return (_a = this.state.subjects[origin]) === null || _a === void 0 ? void 0 : _a.permissions; + } + /** + * Checks whether the subject with the specified origin has the specified + * permission. + * + * @param origin - The origin of the subject. + * @param target - The target name of the permission. + * @returns Whether the subject has the permission. + */ + hasPermission(origin, target) { + return Boolean(this.getPermission(origin, target)); + } + /** + * Checks whether the subject with the specified origin has any permissions. + * Use this if you want to know if a subject "exists". + * + * @param origin - The origin of the subject to check. + * @returns Whether the subject has any permissions. + */ + hasPermissions(origin) { + return Boolean(this.state.subjects[origin]); + } + /** + * Revokes all permissions from the specified origin. + * + * Throws an error of the origin has no permissions. + * + * @param origin - The origin whose permissions to revoke. + */ + revokeAllPermissions(origin) { + this.update((draftState) => { + if (!draftState.subjects[origin]) { + throw new errors_1.UnrecognizedSubjectError(origin); + } + delete draftState.subjects[origin]; + }); + } + /** + * Revokes the specified permission from the subject with the specified + * origin. + * + * Throws an error if the subject or the permission does not exist. + * + * @param origin - The origin of the subject whose permission to revoke. + * @param target - The target name of the permission to revoke. + */ + revokePermission(origin, target) { + this.revokePermissions({ [origin]: [target] }); + } + /** + * Revokes the specified permissions from the specified subjects. + * + * Throws an error if any of the subjects or permissions do not exist. + * + * @param subjectsAndPermissions - An object mapping subject origins + * to arrays of permission target names to revoke. + */ + revokePermissions(subjectsAndPermissions) { + this.update((draftState) => { + Object.keys(subjectsAndPermissions).forEach((origin) => { + if (!(0, util_1.hasProperty)(draftState.subjects, origin)) { + throw new errors_1.UnrecognizedSubjectError(origin); + } + subjectsAndPermissions[origin].forEach((target) => { + const { permissions } = draftState.subjects[origin]; + if (!(0, util_1.hasProperty)(permissions, target)) { + throw new errors_1.PermissionDoesNotExistError(origin, target); + } + this.deletePermission(draftState.subjects, origin, target); + }); + }); + }); + } + /** + * Revokes all permissions corresponding to the specified target for all subjects. + * Does nothing if no subjects or no such permission exists. + * + * @param target - The name of the target to revoke all permissions for. + */ + revokePermissionForAllSubjects(target) { + if (this.getSubjectNames().length === 0) { + return; + } + this.update((draftState) => { + Object.entries(draftState.subjects).forEach(([origin, subject]) => { + const { permissions } = subject; + if ((0, util_1.hasProperty)(permissions, target)) { + this.deletePermission(draftState.subjects, origin, target); + } + }); + }); + } + /** + * Deletes the permission identified by the given origin and target. If the + * permission is the single remaining permission of its subject, the subject + * is also deleted. + * + * @param subjects - The draft permission controller subjects. + * @param origin - The origin of the subject associated with the permission + * to delete. + * @param target - The target name of the permission to delete. + */ + deletePermission(subjects, origin, target) { + const { permissions } = subjects[origin]; + if (Object.keys(permissions).length > 1) { + delete permissions[target]; + } + else { + delete subjects[origin]; + } + } + /** + * Checks whether the permission of the subject corresponding to the given + * origin has a caveat of the specified type. + * + * Throws an error if the subject does not have a permission with the + * specified target name. + * + * @template TargetName - The permission target name. Should be inferred. + * @template CaveatType - The valid caveat types for the permission. Should + * be inferred. + * @param origin - The origin of the subject. + * @param target - The target name of the permission. + * @param caveatType - The type of the caveat to check for. + * @returns Whether the permission has the specified caveat. + */ + hasCaveat(origin, target, caveatType) { + return Boolean(this.getCaveat(origin, target, caveatType)); + } + /** + * Gets the caveat of the specified type, if any, for the permission of + * the subject corresponding to the given origin. + * + * Throws an error if the subject does not have a permission with the + * specified target name. + * + * @template TargetName - The permission target name. Should be inferred. + * @template CaveatType - The valid caveat types for the permission. Should + * be inferred. + * @param origin - The origin of the subject. + * @param target - The target name of the permission. + * @param caveatType - The type of the caveat to get. + * @returns The caveat, or `undefined` if no such caveat exists. + */ + getCaveat(origin, target, caveatType) { + const permission = this.getPermission(origin, target); + if (!permission) { + throw new errors_1.PermissionDoesNotExistError(origin, target); + } + return (0, Permission_1.findCaveat)(permission, caveatType); + } + /** + * Adds a caveat of the specified type, with the specified caveat value, to + * the permission corresponding to the given subject origin and permission + * target. + * + * For modifying existing caveats, use + * {@link PermissionController.updateCaveat}. + * + * Throws an error if no such permission exists, or if the caveat already + * exists. + * + * @template TargetName - The permission target name. Should be inferred. + * @template CaveatType - The valid caveat types for the permission. Should + * be inferred. + * @param origin - The origin of the subject. + * @param target - The target name of the permission. + * @param caveatType - The type of the caveat to add. + * @param caveatValue - The value of the caveat to add. + */ + addCaveat(origin, target, caveatType, caveatValue) { + if (this.hasCaveat(origin, target, caveatType)) { + throw new errors_1.CaveatAlreadyExistsError(origin, target, caveatType); + } + this.setCaveat(origin, target, caveatType, caveatValue); + } + /** + * Updates the value of the caveat of the specified type belonging to the + * permission corresponding to the given subject origin and permission + * target. + * + * For adding new caveats, use + * {@link PermissionController.addCaveat}. + * + * Throws an error if no such permission or caveat exists. + * + * @template TargetName - The permission target name. Should be inferred. + * @template CaveatType - The valid caveat types for the permission. Should + * be inferred. + * @param origin - The origin of the subject. + * @param target - The target name of the permission. + * @param caveatType - The type of the caveat to update. + * @param caveatValue - The new value of the caveat. + */ + updateCaveat(origin, target, caveatType, caveatValue) { + if (!this.hasCaveat(origin, target, caveatType)) { + throw new errors_1.CaveatDoesNotExistError(origin, target, caveatType); + } + this.setCaveat(origin, target, caveatType, caveatValue); + } + /** + * Sets the specified caveat on the specified permission. Overwrites existing + * caveats of the same type in-place (preserving array order), and adds the + * caveat to the end of the array otherwise. + * + * Throws an error if the permission does not exist or fails to validate after + * its caveats have been modified. + * + * @see {@link PermissionController.addCaveat} + * @see {@link PermissionController.updateCaveat} + * @template TargetName - The permission target name. Should be inferred. + * @template CaveatType - The valid caveat types for the permission. Should + * be inferred. + * @param origin - The origin of the subject. + * @param target - The target name of the permission. + * @param caveatType - The type of the caveat to set. + * @param caveatValue - The value of the caveat to set. + */ + setCaveat(origin, target, caveatType, caveatValue) { + this.update((draftState) => { + const subject = draftState.subjects[origin]; + // Unreachable because `hasCaveat` is always called before this, and it + // throws if permissions are missing. TypeScript needs this, however. + /* istanbul ignore if */ + if (!subject) { + throw new errors_1.UnrecognizedSubjectError(origin); + } + const permission = subject.permissions[target]; + /* istanbul ignore if: practically impossible, but TypeScript wants it */ + if (!permission) { + throw new errors_1.PermissionDoesNotExistError(origin, target); + } + const caveat = { + type: caveatType, + value: caveatValue, + }; + this.validateCaveat(caveat, origin, target); + if (permission.caveats) { + const caveatIndex = permission.caveats.findIndex((existingCaveat) => existingCaveat.type === caveat.type); + if (caveatIndex === -1) { + permission.caveats.push(caveat); + } + else { + permission.caveats.splice(caveatIndex, 1, caveat); + } + } + else { + // Typecast: At this point, we don't know if the specific permission + // is allowed to have caveats, but it should be impossible to call + // this method for a permission that may not have any caveats. + // If all else fails, the permission validator is also called. + permission.caveats = [caveat]; + } + this.validateModifiedPermission(permission, origin, target); + }); + } + /** + * Updates all caveats with the specified type for all subjects and + * permissions by applying the specified mutator function to them. + * + * ATTN: Permissions can be revoked entirely by the action of this method, + * read on for details. + * + * Caveat mutators are functions that receive a caveat value and return a + * tuple consisting of a {@link CaveatMutatorOperation} and, optionally, a new + * value to update the existing caveat with. + * + * For each caveat, depending on the mutator result, this method will: + * - Do nothing ({@link CaveatMutatorOperation.noop}) + * - Update the value of the caveat ({@link CaveatMutatorOperation.updateValue}). The caveat specification validator, if any, will be called after updating the value. + * - Delete the caveat ({@link CaveatMutatorOperation.deleteCaveat}). The permission specification validator, if any, will be called after deleting the caveat. + * - Revoke the parent permission ({@link CaveatMutatorOperation.revokePermission}) + * + * This method throws if the validation of any caveat or permission fails. + * + * @param targetCaveatType - The type of the caveats to update. + * @param mutator - The mutator function which will be applied to all caveat + * values. + */ + updatePermissionsByCaveat(targetCaveatType, mutator) { + if (Object.keys(this.state.subjects).length === 0) { + return; + } + this.update((draftState) => { + Object.values(draftState.subjects).forEach((subject) => { + Object.values(subject.permissions).forEach((permission) => { + const { caveats } = permission; + const targetCaveat = caveats === null || caveats === void 0 ? void 0 : caveats.find(({ type }) => type === targetCaveatType); + if (!targetCaveat) { + return; + } + // The mutator may modify the caveat value in place, and must always + // return a valid mutation result. + const mutatorResult = mutator(targetCaveat.value); + switch (mutatorResult.operation) { + case CaveatMutatorOperation.noop: + break; + case CaveatMutatorOperation.updateValue: + // Typecast: `Mutable` is used here to assign to a readonly + // property. `targetConstraint` should already be mutable because + // it's part of a draft, but for some reason it's not. We can't + // use the more-correct `Draft` type here either because it + // results in an error. + targetCaveat.value = + mutatorResult.value; + this.validateCaveat(targetCaveat, subject.origin, permission.parentCapability); + break; + case CaveatMutatorOperation.deleteCaveat: + this.deleteCaveat(permission, targetCaveatType, subject.origin, permission.parentCapability); + break; + case CaveatMutatorOperation.revokePermission: + this.deletePermission(draftState.subjects, subject.origin, permission.parentCapability); + break; + default: { + // This type check ensures that the switch statement is + // exhaustive. + const _exhaustiveCheck = mutatorResult; + throw new Error(`Unrecognized mutation result: "${_exhaustiveCheck.operation}"`); + } + } + }); + }); + }); + } + /** + * Removes the caveat of the specified type from the permission corresponding + * to the given subject origin and target name. + * + * Throws an error if no such permission or caveat exists. + * + * @template TargetName - The permission target name. Should be inferred. + * @template CaveatType - The valid caveat types for the permission. Should + * be inferred. + * @param origin - The origin of the subject. + * @param target - The target name of the permission. + * @param caveatType - The type of the caveat to remove. + */ + removeCaveat(origin, target, caveatType) { + this.update((draftState) => { + var _a; + const permission = (_a = draftState.subjects[origin]) === null || _a === void 0 ? void 0 : _a.permissions[target]; + if (!permission) { + throw new errors_1.PermissionDoesNotExistError(origin, target); + } + if (!permission.caveats) { + throw new errors_1.CaveatDoesNotExistError(origin, target, caveatType); + } + this.deleteCaveat(permission, caveatType, origin, target); + }); + } + /** + * Deletes the specified caveat from the specified permission. If no caveats + * remain after deletion, the permission's caveat property is set to `null`. + * The permission is validated after being modified. + * + * Throws an error if the permission does not have a caveat with the specified + * type. + * + * @param permission - The permission whose caveat to delete. + * @param caveatType - The type of the caveat to delete. + * @param origin - The origin the permission subject. + * @param target - The name of the permission target. + */ + deleteCaveat(permission, caveatType, origin, target) { + /* istanbul ignore if: not possible in our usage */ + if (!permission.caveats) { + throw new errors_1.CaveatDoesNotExistError(origin, target, caveatType); + } + const caveatIndex = permission.caveats.findIndex((existingCaveat) => existingCaveat.type === caveatType); + if (caveatIndex === -1) { + throw new errors_1.CaveatDoesNotExistError(origin, target, caveatType); + } + if (permission.caveats.length === 1) { + permission.caveats = null; + } + else { + permission.caveats.splice(caveatIndex, 1); + } + this.validateModifiedPermission(permission, origin, target); + } + /** + * Validates the specified modified permission. Should **always** be invoked + * on a permission after its caveats have been modified. + * + * Just like {@link PermissionController.validatePermission}, except that the + * corresponding target key and specification are retrieved first, and an + * error is thrown if the target key does not exist. + * + * @param permission - The modified permission to validate. + * @param origin - The origin associated with the permission. + * @param targetName - The target name name of the permission. + */ + validateModifiedPermission(permission, origin, targetName) { + const targetKey = this.getTargetKey(permission.parentCapability); + /* istanbul ignore if: this should be impossible */ + if (!targetKey) { + throw new Error(`Fatal: Existing permission target key "${targetKey}" has no specification.`); + } + this.validatePermission(this.getPermissionSpecification(targetKey), permission, origin, targetName); + } + /** + * Gets the key for the specified permission target. + * + * Used to support our namespaced permission target feature, which is used + * to implement namespaced restricted JSON-RPC methods. + * + * @param target - The requested permission target. + * @returns The internal key of the permission target. + */ + getTargetKey(target) { + if ((0, util_1.hasProperty)(this._permissionSpecifications, target)) { + return target; + } + const namespacedTargetsWithoutWildcard = {}; + for (const targetKey of Object.keys(this._permissionSpecifications)) { + const wildCardMatch = targetKey.match(/(.+)\*$/u); + if (wildCardMatch) { + namespacedTargetsWithoutWildcard[wildCardMatch[1]] = true; + } + } + // Check for potentially nested namespaces: + // Ex: wildzone_ + // Ex: eth_plugin_ + const segments = target.split('_'); + let targetKey = ''; + while (segments.length > 0 && + !(0, util_1.hasProperty)(this._permissionSpecifications, targetKey) && + !namespacedTargetsWithoutWildcard[targetKey]) { + targetKey += `${segments.shift()}_`; + } + if (namespacedTargetsWithoutWildcard[targetKey]) { + return `${targetKey}*`; + } + return undefined; + } + /** + * Grants _approved_ permissions to the specified subject. Every permission and + * caveat is stringently validated – including by calling every specification + * validator – and an error is thrown if any validation fails. + * + * ATTN: This method does **not** prompt the user for approval. + * + * @see {@link PermissionController.requestPermissions} For initiating a + * permissions request requiring user approval. + * @param options - Options bag. + * @param options.approvedPermissions - The requested permissions approved by + * the user. + * @param options.requestData - Permission request data. Passed to permission + * factory functions. + * @param options.preserveExistingPermissions - Whether to preserve the + * subject's existing permissions. + * @param options.subject - The subject to grant permissions to. + * @returns The granted permissions. + */ + grantPermissions({ approvedPermissions, requestData, preserveExistingPermissions = true, subject, }) { + const { origin } = subject; + if (!origin || typeof origin !== 'string') { + throw new errors_1.InvalidSubjectIdentifierError(origin); + } + const permissions = (preserveExistingPermissions + ? Object.assign({}, this.getPermissions(origin)) : {}); + for (const [requestedTarget, approvedPermission] of Object.entries(approvedPermissions)) { + const targetKey = this.getTargetKey(requestedTarget); + if (!targetKey) { + throw (0, errors_1.methodNotFound)(requestedTarget); + } + if (approvedPermission.parentCapability !== undefined && + requestedTarget !== approvedPermission.parentCapability) { + throw new errors_1.InvalidApprovedPermissionError(origin, requestedTarget, approvedPermission); + } + // The requested target must be a valid target name if we found its key. + // We reassign it to change its type. + const targetName = requestedTarget; + const specification = this.getPermissionSpecification(targetKey); + // The requested caveats are validated here. + const caveats = this.constructCaveats(origin, targetName, approvedPermission.caveats); + const permissionOptions = { + caveats, + invoker: origin, + target: targetName, + }; + let permission; + if (specification.factory) { + permission = specification.factory(permissionOptions, requestData); + // Full caveat and permission validation is performed here since the + // factory function can arbitrarily modify the entire permission object, + // including its caveats. + this.validatePermission(specification, permission, origin, targetName); + } + else { + permission = (0, Permission_1.constructPermission)(permissionOptions); + // We do not need to validate caveats in this case, because the plain + // permission constructor function does not modify the caveats, which + // were already validated by `constructCaveats` above. + this.validatePermission(specification, permission, origin, targetName, { + invokePermissionValidator: true, + performCaveatValidation: false, + }); + } + permissions[targetName] = permission; + } + this.setValidatedPermissions(origin, permissions); + return permissions; + } + /** + * Validates the specified permission by: + * - Ensuring that its `caveats` property is either `null` or a non-empty array. + * - Ensuring that it only includes caveats allowed by its specification. + * - Ensuring that it includes no duplicate caveats (by caveat type). + * - Validating each caveat object, if `performCaveatValidation` is `true`. + * - Calling the validator of its specification, if one exists and `invokePermissionValidator` is `true`. + * + * An error is thrown if validation fails. + * + * @param specification - The specification of the permission. + * @param permission - The permission to validate. + * @param origin - The origin associated with the permission. + * @param targetName - The target name of the permission. + * @param validationOptions - Validation options. + * @param validationOptions.invokePermissionValidator - Whether to invoke the + * permission's consumer-specified validator function, if any. + * @param validationOptions.performCaveatValidation - Whether to invoke + * {@link PermissionController.validateCaveat} on each of the permission's + * caveats. + */ + validatePermission(specification, permission, origin, targetName, { invokePermissionValidator, performCaveatValidation } = { + invokePermissionValidator: true, + performCaveatValidation: true, + }) { + const { allowedCaveats, validator } = specification; + if ((0, util_1.hasProperty)(permission, 'caveats')) { + const { caveats } = permission; + if (caveats !== null && !(Array.isArray(caveats) && caveats.length > 0)) { + throw new errors_1.InvalidCaveatsPropertyError(origin, targetName, caveats); + } + const seenCaveatTypes = new Set(); + caveats === null || caveats === void 0 ? void 0 : caveats.forEach((caveat) => { + if (performCaveatValidation) { + this.validateCaveat(caveat, origin, targetName); + } + if (!(allowedCaveats === null || allowedCaveats === void 0 ? void 0 : allowedCaveats.includes(caveat.type))) { + throw new errors_1.ForbiddenCaveatError(caveat.type, origin, targetName); + } + if (seenCaveatTypes.has(caveat.type)) { + throw new errors_1.DuplicateCaveatError(caveat.type, origin, targetName); + } + seenCaveatTypes.add(caveat.type); + }); + } + if (invokePermissionValidator && validator) { + validator(permission, origin, targetName); + } + } + /** + * Assigns the specified permissions to the subject with the given origin. + * Overwrites all existing permissions, and creates a subject entry if it + * doesn't already exist. + * + * ATTN: Assumes that the new permissions have been validated. + * + * @param origin - The origin of the grantee subject. + * @param permissions - The new permissions for the grantee subject. + */ + setValidatedPermissions(origin, permissions) { + this.update((draftState) => { + if (!draftState.subjects[origin]) { + draftState.subjects[origin] = { origin, permissions: {} }; + } + draftState.subjects[origin].permissions = (0, immer_1.castDraft)(permissions); + }); + } + /** + * Validates the requested caveats for the permission of the specified + * subject origin and target name and returns the validated caveat array. + * + * Throws an error if validation fails. + * + * @param origin - The origin of the permission subject. + * @param target - The permission target name. + * @param requestedCaveats - The requested caveats to construct. + * @returns The constructed caveats. + */ + constructCaveats(origin, target, requestedCaveats) { + const caveatArray = requestedCaveats === null || requestedCaveats === void 0 ? void 0 : requestedCaveats.map((requestedCaveat) => { + this.validateCaveat(requestedCaveat, origin, target); + // Reassign so that we have a fresh object. + const { type, value } = requestedCaveat; + return { type, value }; + }); + return caveatArray && (0, util_1.isNonEmptyArray)(caveatArray) + ? caveatArray + : undefined; + } + /** + * This methods validates that the specified caveat is an object with the + * expected properties and types. It also ensures that a caveat specification + * exists for the requested caveat type, and calls the specification + * validator, if it exists, on the caveat object. + * + * Throws an error if validation fails. + * + * @param caveat - The caveat object to validate. + * @param origin - The origin associated with the subject of the parent + * permission. + * @param target - The target name associated with the parent permission. + */ + validateCaveat(caveat, origin, target) { + var _a; + if (!(0, util_1.isPlainObject)(caveat)) { + throw new errors_1.InvalidCaveatError(caveat, origin, target); + } + if (Object.keys(caveat).length !== 2) { + throw new errors_1.InvalidCaveatFieldsError(caveat, origin, target); + } + if (typeof caveat.type !== 'string') { + throw new errors_1.InvalidCaveatTypeError(caveat, origin, target); + } + const specification = this.getCaveatSpecification(caveat.type); + if (!specification) { + throw new errors_1.UnrecognizedCaveatTypeError(caveat.type, origin, target); + } + if (!(0, util_1.hasProperty)(caveat, 'value') || caveat.value === undefined) { + throw new errors_1.CaveatMissingValueError(caveat, origin, target); + } + if (!(0, util_1.isValidJson)(caveat.value)) { + throw new errors_1.CaveatInvalidJsonError(caveat, origin, target); + } + // Typecast: TypeScript still believes that the caveat is a PlainObject. + (_a = specification.validator) === null || _a === void 0 ? void 0 : _a.call(specification, caveat, origin, target); + } + /** + * Initiates a permission request that requires user approval. This should + * always be used to grant additional permissions to a subject, unless user + * approval has been obtained through some other means. + * + * Permissions are validated at every step of the approval process, and this + * method will reject if validation fails. + * + * @see {@link ApprovalController} For the user approval logic. + * @see {@link PermissionController.acceptPermissionsRequest} For the method + * that _accepts_ the request and resolves the user approval promise. + * @see {@link PermissionController.rejectPermissionsRequest} For the method + * that _rejects_ the request and the user approval promise. + * @param subject - The grantee subject. + * @param requestedPermissions - The requested permissions. + * @param options - Additional options. + * @param options.id - The id of the permissions request. Defaults to a unique + * id. + * @param options.preserveExistingPermissions - Whether to preserve the + * subject's existing permissions. Defaults to `true`. + * @returns The granted permissions and request metadata. + */ + requestPermissions(subject, requestedPermissions, options = {}) { + return __awaiter(this, void 0, void 0, function* () { + const { origin } = subject; + const { id = (0, nanoid_1.nanoid)(), preserveExistingPermissions = true } = options; + this.validateRequestedPermissions(origin, requestedPermissions); + const metadata = { + id, + origin, + }; + const permissionsRequest = { + metadata, + permissions: requestedPermissions, + }; + const _a = yield this.requestUserApproval(permissionsRequest), { permissions: approvedPermissions } = _a, requestData = __rest(_a, ["permissions"]); + return [ + this.grantPermissions({ + subject, + approvedPermissions, + preserveExistingPermissions, + requestData, + }), + metadata, + ]; + }); + } + /** + * Validates requested permissions. Throws if validation fails. + * + * This method ensures that the requested permissions are a properly + * formatted {@link RequestedPermissions} object, and performs the same + * validation as {@link PermissionController.grantPermissions}, except that + * consumer-specified permission validator functions are not called, since + * they are only called on fully constructed, approved permissions that are + * otherwise completely valid. + * + * Unrecognzied properties on requested permissions are ignored. + * + * @param origin - The origin of the grantee subject. + * @param requestedPermissions - The requested permissions. + */ + validateRequestedPermissions(origin, requestedPermissions) { + if (!(0, util_1.isPlainObject)(requestedPermissions)) { + throw (0, errors_1.invalidParams)({ + message: `Requested permissions for origin "${origin}" is not a plain object.`, + data: { origin, requestedPermissions }, + }); + } + if (Object.keys(requestedPermissions).length === 0) { + throw (0, errors_1.invalidParams)({ + message: `Permissions request for origin "${origin}" contains no permissions.`, + data: { requestedPermissions }, + }); + } + for (const targetName of Object.keys(requestedPermissions)) { + const permission = requestedPermissions[targetName]; + const targetKey = this.getTargetKey(targetName); + if (!targetKey) { + throw (0, errors_1.methodNotFound)(targetName, { origin, requestedPermissions }); + } + if (!(0, util_1.isPlainObject)(permission) || + (permission.parentCapability !== undefined && + targetName !== permission.parentCapability)) { + throw (0, errors_1.invalidParams)({ + message: `Permissions request for origin "${origin}" contains invalid requested permission(s).`, + data: { origin, requestedPermissions }, + }); + } + // Here we validate the permission without invoking its validator, if any. + // The validator will be invoked after the permission has been approved. + this.validatePermission(this.getPermissionSpecification(targetKey), + // Typecast: The permission is still a "PlainObject" here. + permission, origin, targetName, { invokePermissionValidator: false, performCaveatValidation: true }); + } + } + /** + * Adds a request to the {@link ApprovalController} using the + * {@link AddApprovalRequest} action. Also validates the resulting approved + * permissions request, and throws an error if validation fails. + * + * @param permissionsRequest - The permissions request object. + * @returns The approved permissions request object. + */ + requestUserApproval(permissionsRequest) { + return __awaiter(this, void 0, void 0, function* () { + const { origin, id } = permissionsRequest.metadata; + const approvedRequest = yield this.messagingSystem.call('ApprovalController:addRequest', { + id, + origin, + requestData: permissionsRequest, + type: utils_1.MethodNames.requestPermissions, + }, true); + this.validateApprovedPermissions(approvedRequest, { id, origin }); + return approvedRequest; + }); + } + /** + * Validates an approved {@link PermissionsRequest} object. The approved + * request must have the required `metadata` and `permissions` properties, + * the `id` and `origin` of the `metadata` must match the original request + * metadata, and the requested permissions must be valid per + * {@link PermissionController.validateRequestedPermissions}. Any extra + * metadata properties are ignored. + * + * An error is thrown if validation fails. + * + * @param approvedRequest - The approved permissions request object. + * @param originalMetadata - The original request metadata. + */ + validateApprovedPermissions(approvedRequest, originalMetadata) { + const { id, origin } = originalMetadata; + if (!(0, util_1.isPlainObject)(approvedRequest) || + !(0, util_1.isPlainObject)(approvedRequest.metadata)) { + throw (0, errors_1.internalError)(`Approved permissions request for subject "${origin}" is invalid.`, { data: { approvedRequest } }); + } + const { metadata: { id: newId, origin: newOrigin }, permissions, } = approvedRequest; + if (newId !== id) { + throw (0, errors_1.internalError)(`Approved permissions request for subject "${origin}" mutated its id.`, { originalId: id, mutatedId: newId }); + } + if (newOrigin !== origin) { + throw (0, errors_1.internalError)(`Approved permissions request for subject "${origin}" mutated its origin.`, { originalOrigin: origin, mutatedOrigin: newOrigin }); + } + try { + this.validateRequestedPermissions(origin, permissions); + } + catch (error) { + if (error instanceof eth_rpc_errors_1.EthereumRpcError) { + // Re-throw as an internal error; we should never receive invalid approved + // permissions. + throw (0, errors_1.internalError)(`Invalid approved permissions request: ${error.message}`, error.data); + } + throw (0, errors_1.internalError)('Unrecognized error type', { error }); + } + } + /** + * Accepts a permissions request created by + * {@link PermissionController.requestPermissions}. + * + * @param request - The permissions request. + */ + acceptPermissionsRequest(request) { + return __awaiter(this, void 0, void 0, function* () { + const { id } = request.metadata; + if (!this.hasApprovalRequest({ id })) { + throw new errors_1.PermissionsRequestNotFoundError(id); + } + if (Object.keys(request.permissions).length === 0) { + this._rejectPermissionsRequest(id, (0, errors_1.invalidParams)({ + message: 'Must request at least one permission.', + })); + return; + } + try { + this.messagingSystem.call('ApprovalController:acceptRequest', id, request); + } + catch (error) { + // If accepting unexpectedly fails, reject the request and re-throw the + // error + this._rejectPermissionsRequest(id, error); + throw error; + } + }); + } + /** + * Rejects a permissions request created by + * {@link PermissionController.requestPermissions}. + * + * @param id - The id of the request to be rejected. + */ + rejectPermissionsRequest(id) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.hasApprovalRequest({ id })) { + throw new errors_1.PermissionsRequestNotFoundError(id); + } + this._rejectPermissionsRequest(id, (0, errors_1.userRejectedRequest)()); + }); + } + /** + * Checks whether the {@link ApprovalController} has a particular permissions + * request. + * + * @see {@link PermissionController.acceptPermissionsRequest} and + * {@link PermissionController.rejectPermissionsRequest} for usage. + * @param options - The {@link HasApprovalRequest} options. + * @param options.id - The id of the approval request to check for. + * @returns Whether the specified request exists. + */ + hasApprovalRequest(options) { + return this.messagingSystem.call('ApprovalController:hasRequest', + // Typecast: For some reason, the type here expects all of the possible + // HasApprovalRequest options to be specified, when they're actually all + // optional. Passing just the id is definitely valid, so we just cast it. + options); + } + /** + * Rejects the permissions request with the specified id, with the specified + * error as the reason. This method is effectively a wrapper around a + * messenger call for the `ApprovalController:rejectRequest` action. + * + * @see {@link PermissionController.acceptPermissionsRequest} and + * {@link PermissionController.rejectPermissionsRequest} for usage. + * @param id - The id of the request to reject. + * @param error - The error associated with the rejection. + * @returns Nothing + */ + _rejectPermissionsRequest(id, error) { + return this.messagingSystem.call('ApprovalController:rejectRequest', id, error); + } + /** + * Gets the subject's endowments per the specified endowment permission. + * Throws if the subject does not have the required permission or if the + * permission is not an endowment permission. + * + * @param origin - The origin of the subject whose endowments to retrieve. + * @param targetName - The name of the endowment permission. This must be a + * valid permission target name. + * @param requestData - Additional data associated with the request, if any. + * Forwarded to the endowment getter function for the permission. + * @returns The endowments, if any. + */ + getEndowments(origin, targetName, requestData) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.hasPermission(origin, targetName)) { + throw (0, errors_1.unauthorized)({ data: { origin, targetName } }); + } + return this.getTypedPermissionSpecification(Permission_1.PermissionType.Endowment, targetName, origin).endowmentGetter({ origin, requestData }); + }); + } + /** + * Executes a restricted method as the subject with the given origin. + * The specified params, if any, will be passed to the method implementation. + * + * ATTN: Great caution should be exercised in the use of this method. + * Methods that cause side effects or affect application state should + * be avoided. + * + * This method will first attempt to retrieve the requested restricted method + * implementation, throwing if it does not exist. The method will then be + * invoked as though the subject with the specified origin had invoked it with + * the specified parameters. This means that any existing caveats will be + * applied to the restricted method, and this method will throw if the + * restricted method or its caveat decorators throw. + * + * In addition, this method will throw if the subject does not have a + * permission for the specified restricted method. + * + * @param origin - The origin of the subject to execute the method on behalf + * of. + * @param targetName - The name of the method to execute. This must be a valid + * permission target name. + * @param params - The parameters to pass to the method implementation. + * @returns The result of the executed method. + */ + executeRestrictedMethod(origin, targetName, params) { + return __awaiter(this, void 0, void 0, function* () { + // Throws if the method does not exist + const methodImplementation = this.getRestrictedMethod(targetName, origin); + const result = yield this._executeRestrictedMethod(methodImplementation, { origin }, targetName, params); + if (result === undefined) { + throw new Error(`Internal request for method "${targetName}" as origin "${origin}" returned no result.`); + } + return result; + }); + } + /** + * An internal method used in the controller's `json-rpc-engine` middleware + * and {@link PermissionController.executeRestrictedMethod}. Calls the + * specified restricted method implementation after decorating it with the + * caveats of its permission. Throws if the subject does not have the + * requisite permission. + * + * ATTN: Parameter validation is the responsibility of the caller, or + * the restricted method implementation in the case of `params`. + * + * @see {@link PermissionController.executeRestrictedMethod} and + * {@link PermissionController.createPermissionMiddleware} for usage. + * @param methodImplementation - The implementation of the method to call. + * @param subject - Metadata about the subject that made the request. + * @param method - The method name + * @param params - Params needed for executing the restricted method + * @returns The result of the restricted method implementation + */ + _executeRestrictedMethod(methodImplementation, subject, method, params = []) { + const { origin } = subject; + const permission = this.getPermission(origin, method); + if (!permission) { + throw (0, errors_1.unauthorized)({ data: { origin, method } }); + } + return (0, Caveat_1.decorateWithCaveats)(methodImplementation, permission, this._caveatSpecifications)({ method, params, context: { origin } }); + } +} +exports.PermissionController = PermissionController; +//# sourceMappingURL=PermissionController.js.map \ No newline at end of file diff --git a/dist/permissions/PermissionController.js.map b/dist/permissions/PermissionController.js.map new file mode 100644 index 0000000000..a1ff067265 --- /dev/null +++ b/dist/permissions/PermissionController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"PermissionController.js","sourceRoot":"","sources":["../../src/permissions/PermissionController.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-enable @typescript-eslint/no-unused-vars */\nimport { Mutable } from '@metamask/types';\nimport deepFreeze from 'deep-freeze-strict';\nimport { castDraft, Draft, Patch } from 'immer';\nimport { nanoid } from 'nanoid';\nimport { EthereumRpcError } from 'eth-rpc-errors';\nimport {\n AcceptRequest as AcceptApprovalRequest,\n AddApprovalRequest,\n HasApprovalRequest,\n RejectRequest as RejectApprovalRequest,\n} from '../approval/ApprovalController';\nimport { BaseController, Json, StateMetadata } from '../BaseControllerV2';\nimport { RestrictedControllerMessenger } from '../ControllerMessenger';\nimport {\n hasProperty,\n isNonEmptyArray,\n isPlainObject,\n isValidJson,\n NonEmptyArray,\n} from '../util';\nimport {\n CaveatConstraint,\n CaveatSpecificationConstraint,\n CaveatSpecificationMap,\n decorateWithCaveats,\n ExtractCaveat,\n ExtractCaveats,\n ExtractCaveatValue,\n} from './Caveat';\nimport {\n CaveatAlreadyExistsError,\n CaveatDoesNotExistError,\n CaveatInvalidJsonError,\n CaveatMissingValueError,\n DuplicateCaveatError,\n EndowmentPermissionDoesNotExistError,\n ForbiddenCaveatError,\n internalError,\n InvalidApprovedPermissionError,\n InvalidCaveatError,\n InvalidCaveatFieldsError,\n InvalidCaveatsPropertyError,\n InvalidCaveatTypeError,\n invalidParams,\n InvalidSubjectIdentifierError,\n methodNotFound,\n PermissionDoesNotExistError,\n PermissionsRequestNotFoundError,\n unauthorized,\n UnrecognizedCaveatTypeError,\n UnrecognizedSubjectError,\n userRejectedRequest,\n} from './errors';\nimport {\n constructPermission,\n EndowmentSpecificationConstraint,\n ExtractAllowedCaveatTypes,\n ExtractPermissionSpecification,\n findCaveat,\n hasSpecificationType,\n OriginString,\n PermissionConstraint,\n PermissionSpecificationConstraint,\n PermissionSpecificationMap,\n PermissionType,\n RequestedPermissions,\n RestrictedMethod,\n RestrictedMethodParameters,\n RestrictedMethodSpecificationConstraint,\n ValidPermission,\n ValidPermissionSpecification,\n} from './Permission';\nimport { getPermissionMiddlewareFactory } from './permission-middleware';\nimport { MethodNames } from './utils';\n\n/**\n * Metadata associated with {@link PermissionController} subjects.\n */\nexport type PermissionSubjectMetadata = {\n origin: OriginString;\n};\n\n/**\n * Metadata associated with permission requests.\n */\nexport type PermissionsRequestMetadata = PermissionSubjectMetadata & {\n id: string;\n};\n\n/**\n * Used for prompting the user about a proposed new permission.\n * Includes information about the grantee subject, requested permissions, and\n * any additional information added by the consumer.\n *\n * All properties except `permissions` are passed to any factories found for\n * the requested permissions.\n */\nexport type PermissionsRequest = {\n metadata: PermissionsRequestMetadata;\n permissions: RequestedPermissions;\n [key: string]: Json;\n};\n\n/**\n * The name of the {@link PermissionController}.\n */\nconst controllerName = 'PermissionController';\n\n/**\n * Permissions associated with a {@link PermissionController} subject.\n */\nexport type SubjectPermissions =\n Record;\n\n/**\n * Permissions and metadata associated with a {@link PermissionController}\n * subject.\n */\nexport type PermissionSubjectEntry<\n SubjectPermission extends PermissionConstraint,\n> = {\n origin: SubjectPermission['invoker'];\n permissions: SubjectPermissions;\n};\n\n/**\n * All subjects of a {@link PermissionController}.\n *\n * @template SubjectPermission - The permissions of the subject.\n */\nexport type PermissionControllerSubjects<\n SubjectPermission extends PermissionConstraint,\n> = Record<\n SubjectPermission['invoker'],\n PermissionSubjectEntry\n>;\n\n// TODO:TS4.4 Enable compiler flags to forbid unchecked member access\n/**\n * The state of a {@link PermissionController}.\n *\n * @template Permission - The controller's permission type union.\n */\nexport type PermissionControllerState =\n Permission extends PermissionConstraint\n ? {\n subjects: PermissionControllerSubjects;\n }\n : never;\n\n/**\n * Get the state metadata of the {@link PermissionController}.\n *\n * @template Permission - The controller's permission type union.\n * @returns The state metadata\n */\nfunction getStateMetadata() {\n return { subjects: { anonymous: true, persist: true } } as StateMetadata<\n PermissionControllerState\n >;\n}\n\n/**\n * Get the default state of the {@link PermissionController}.\n *\n * @template Permission - The controller's permission type union.\n * @returns The default state of the controller\n */\nfunction getDefaultState() {\n return { subjects: {} } as PermissionControllerState;\n}\n\n/**\n * Gets the state of the {@link PermissionController}.\n */\nexport type GetPermissionControllerState = {\n type: `${typeof controllerName}:getState`;\n handler: () => PermissionControllerState;\n};\n\n/**\n * Gets the names of all subjects from the {@link PermissionController}.\n */\nexport type GetSubjects = {\n type: `${typeof controllerName}:getSubjectNames`;\n handler: () => (keyof PermissionControllerSubjects)[];\n};\n\n/**\n * Gets the permissions for specified subject\n */\nexport type GetPermissions = {\n type: `${typeof controllerName}:getPermissions`;\n handler: GenericPermissionController['getPermissions'];\n};\n\n/**\n * Checks whether the specified subject has any permissions.\n */\nexport type HasPermissions = {\n type: `${typeof controllerName}:hasPermissions`;\n handler: GenericPermissionController['hasPermissions'];\n};\n\n/**\n * Checks whether the specified subject has a specific permission.\n */\nexport type HasPermission = {\n type: `${typeof controllerName}:hasPermission`;\n handler: GenericPermissionController['hasPermission'];\n};\n\n/**\n * Directly grants given permissions for a specificed origin without requesting user approval\n */\nexport type GrantPermissions = {\n type: `${typeof controllerName}:grantPermissions`;\n handler: GenericPermissionController['grantPermissions'];\n};\n\n/**\n * Requests given permissions for a specified origin\n */\nexport type RequestPermissions = {\n type: `${typeof controllerName}:requestPermissions`;\n handler: GenericPermissionController['requestPermissions'];\n};\n\n/**\n * Removes the specified permissions for each origin.\n */\nexport type RevokePermissions = {\n type: `${typeof controllerName}:revokePermissions`;\n handler: GenericPermissionController['revokePermissions'];\n};\n\n/**\n * Removes all permissions for a given origin\n */\nexport type RevokeAllPermissions = {\n type: `${typeof controllerName}:revokeAllPermissions`;\n handler: GenericPermissionController['revokeAllPermissions'];\n};\n\n/**\n * Revokes all permissions corresponding to the specified target for all subjects.\n * Does nothing if no subjects or no such permission exists.\n */\nexport type RevokePermissionForAllSubjects = {\n type: `${typeof controllerName}:revokePermissionForAllSubjects`;\n handler: GenericPermissionController['revokePermissionForAllSubjects'];\n};\n\n/**\n * Clears all permissions from the {@link PermissionController}.\n */\nexport type ClearPermissions = {\n type: `${typeof controllerName}:clearPermissions`;\n handler: () => void;\n};\n\n/**\n * Gets the endowments for the given subject and permission.\n */\nexport type GetEndowments = {\n type: `${typeof controllerName}:getEndowments`;\n handler: GenericPermissionController['getEndowments'];\n};\n\n/**\n * The {@link ControllerMessenger} actions of the {@link PermissionController}.\n */\nexport type PermissionControllerActions =\n | ClearPermissions\n | GetEndowments\n | GetPermissionControllerState\n | GetSubjects\n | GetPermissions\n | HasPermission\n | HasPermissions\n | GrantPermissions\n | RequestPermissions\n | RevokeAllPermissions\n | RevokePermissionForAllSubjects\n | RevokePermissions;\n\n/**\n * The generic state change event of the {@link PermissionController}.\n */\nexport type PermissionControllerStateChange = {\n type: `${typeof controllerName}:stateChange`;\n payload: [PermissionControllerState, Patch[]];\n};\n\n/**\n * The {@link ControllerMessenger} events of the {@link PermissionController}.\n *\n * The permission controller only emits its generic state change events.\n * Consumers should use selector subscriptions to subscribe to relevant\n * substate.\n */\nexport type PermissionControllerEvents = PermissionControllerStateChange;\n\n/**\n * The external {@link ControllerMessenger} actions available to the\n * {@link PermissionController}.\n */\ntype AllowedActions =\n | AddApprovalRequest\n | HasApprovalRequest\n | AcceptApprovalRequest\n | RejectApprovalRequest;\n\n/**\n * The messenger of the {@link PermissionController}.\n */\nexport type PermissionControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n PermissionControllerActions | AllowedActions,\n PermissionControllerEvents,\n AllowedActions['type'],\n never\n>;\n\n/**\n * A generic {@link PermissionController}.\n */\nexport type GenericPermissionController = PermissionController<\n PermissionSpecificationConstraint,\n CaveatSpecificationConstraint\n>;\n\n/**\n * Describes the possible results of a {@link CaveatMutator} function.\n */\nexport enum CaveatMutatorOperation {\n noop,\n updateValue,\n deleteCaveat,\n revokePermission,\n}\n\n/**\n * Given a caveat value, returns a {@link CaveatMutatorOperation} and, optionally,\n * a new caveat value.\n *\n * @see {@link PermissionController.updatePermissionsByCaveat} for more details.\n * @template Caveat - The caveat type for which this mutator is intended.\n * @param caveatValue - The existing value of the caveat being mutated.\n * @returns A tuple of the mutation result and, optionally, the new caveat\n * value.\n */\nexport type CaveatMutator = (\n caveatValue: TargetCaveat['value'],\n) => CaveatMutatorResult;\n\ntype CaveatMutatorResult =\n | Readonly<{\n operation: CaveatMutatorOperation.updateValue;\n value: CaveatConstraint['value'];\n }>\n | Readonly<{\n operation: Exclude<\n CaveatMutatorOperation,\n CaveatMutatorOperation.updateValue\n >;\n }>;\n\n/**\n * Extracts the permission(s) specified by the given permission and caveat\n * specifications.\n *\n * @template ControllerPermissionSpecification - The permission specification(s)\n * to extract from.\n * @template ControllerCaveatSpecification - The caveat specification(s) to\n * extract from. Necessary because {@link Permission} has a generic parameter\n * that describes the allowed caveats for the permission.\n */\nexport type ExtractPermission<\n ControllerPermissionSpecification extends PermissionSpecificationConstraint,\n ControllerCaveatSpecification extends CaveatSpecificationConstraint,\n> = ControllerPermissionSpecification extends ValidPermissionSpecification\n ? ValidPermission<\n ControllerPermissionSpecification['targetKey'],\n ExtractCaveats\n >\n : never;\n\n/**\n * Extracts the restricted method permission(s) specified by the given\n * permission and caveat specifications.\n *\n * @template ControllerPermissionSpecification - The permission specification(s)\n * to extract from.\n * @template ControllerCaveatSpecification - The caveat specification(s) to\n * extract from. Necessary because {@link Permission} has a generic parameter\n * that describes the allowed caveats for the permission.\n */\nexport type ExtractRestrictedMethodPermission<\n ControllerPermissionSpecification extends PermissionSpecificationConstraint,\n ControllerCaveatSpecification extends CaveatSpecificationConstraint,\n> = ExtractPermission<\n Extract<\n ControllerPermissionSpecification,\n RestrictedMethodSpecificationConstraint\n >,\n ControllerCaveatSpecification\n>;\n\n/**\n * Extracts the endowment permission(s) specified by the given permission and\n * caveat specifications.\n *\n * @template ControllerPermissionSpecification - The permission specification(s)\n * to extract from.\n * @template ControllerCaveatSpecification - The caveat specification(s) to\n * extract from. Necessary because {@link Permission} has a generic parameter\n * that describes the allowed caveats for the permission.\n */\nexport type ExtractEndowmentPermission<\n ControllerPermissionSpecification extends PermissionSpecificationConstraint,\n ControllerCaveatSpecification extends CaveatSpecificationConstraint,\n> = ExtractPermission<\n Extract,\n ControllerCaveatSpecification\n>;\n\n/**\n * Options for the {@link PermissionController} constructor.\n *\n * @template ControllerPermissionSpecification - A union of the types of all\n * permission specifications available to the controller. Any referenced caveats\n * must be included in the controller's caveat specifications.\n * @template ControllerCaveatSpecification - A union of the types of all\n * caveat specifications available to the controller.\n */\nexport type PermissionControllerOptions<\n ControllerPermissionSpecification extends PermissionSpecificationConstraint,\n ControllerCaveatSpecification extends CaveatSpecificationConstraint,\n> = {\n messenger: PermissionControllerMessenger;\n caveatSpecifications: CaveatSpecificationMap;\n permissionSpecifications: PermissionSpecificationMap;\n unrestrictedMethods: string[];\n state?: Partial<\n PermissionControllerState<\n ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >\n >\n >;\n};\n\n/**\n * The permission controller. See the README for details.\n *\n * Assumes the existence of an {@link ApprovalController} reachable via the\n * {@link ControllerMessenger}.\n *\n * @template ControllerPermissionSpecification - A union of the types of all\n * permission specifications available to the controller. Any referenced caveats\n * must be included in the controller's caveat specifications.\n * @template ControllerCaveatSpecification - A union of the types of all\n * caveat specifications available to the controller.\n */\nexport class PermissionController<\n ControllerPermissionSpecification extends PermissionSpecificationConstraint,\n ControllerCaveatSpecification extends CaveatSpecificationConstraint,\n> extends BaseController<\n typeof controllerName,\n PermissionControllerState<\n ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >\n >,\n PermissionControllerMessenger\n> {\n private readonly _caveatSpecifications: Readonly<\n CaveatSpecificationMap\n >;\n\n private readonly _permissionSpecifications: Readonly<\n PermissionSpecificationMap\n >;\n\n private readonly _unrestrictedMethods: ReadonlySet;\n\n /**\n * The names of all JSON-RPC methods that will be ignored by the controller.\n *\n * @returns The names of all unrestricted JSON-RPC methods\n */\n public get unrestrictedMethods(): ReadonlySet {\n return this._unrestrictedMethods;\n }\n\n /**\n * Returns a `json-rpc-engine` middleware function factory, so that the rules\n * described by the state of this controller can be applied to incoming\n * JSON-RPC requests.\n *\n * The middleware **must** be added in the correct place in the middleware\n * stack in order for it to work. See the README for an example.\n */\n public createPermissionMiddleware: ReturnType<\n typeof getPermissionMiddlewareFactory\n >;\n\n /**\n * Constructs the PermissionController.\n *\n * @param options - Permission controller options.\n * @param options.caveatSpecifications - The specifications of all caveats\n * available to the controller. See {@link CaveatSpecificationMap} and the\n * documentation for more details.\n * @param options.permissionSpecifications - The specifications of all\n * permissions available to the controller. See\n * {@link PermissionSpecificationMap} and the README for more details.\n * @param options.unrestrictedMethods - The callable names of all JSON-RPC\n * methods ignored by the new controller.\n * @param options.messenger - The controller messenger. See\n * {@link BaseController} for more information.\n * @param options.state - Existing state to hydrate the controller with at\n * initialization.\n */\n constructor(\n options: PermissionControllerOptions<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >,\n ) {\n const {\n caveatSpecifications,\n permissionSpecifications,\n unrestrictedMethods,\n messenger,\n state = {},\n } = options;\n\n super({\n name: controllerName,\n metadata:\n getStateMetadata<\n ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >\n >(),\n messenger,\n state: {\n ...getDefaultState<\n ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >\n >(),\n ...state,\n },\n });\n\n this._unrestrictedMethods = new Set(unrestrictedMethods);\n this._caveatSpecifications = deepFreeze({ ...caveatSpecifications });\n\n this.validatePermissionSpecifications(\n permissionSpecifications,\n this._caveatSpecifications,\n );\n\n this._permissionSpecifications = deepFreeze({\n ...permissionSpecifications,\n });\n\n this.registerMessageHandlers();\n this.createPermissionMiddleware = getPermissionMiddlewareFactory({\n executeRestrictedMethod: this._executeRestrictedMethod.bind(this),\n getRestrictedMethod: this.getRestrictedMethod.bind(this),\n isUnrestrictedMethod: this.unrestrictedMethods.has.bind(\n this.unrestrictedMethods,\n ),\n });\n }\n\n /**\n * Gets a permission specification.\n *\n * @param targetKey - The target key of the permission specification to get.\n * @returns The permission specification with the specified target key.\n */\n private getPermissionSpecification<\n TargetKey extends ControllerPermissionSpecification['targetKey'],\n >(\n targetKey: TargetKey,\n ): ExtractPermissionSpecification<\n ControllerPermissionSpecification,\n TargetKey\n > {\n return this._permissionSpecifications[targetKey];\n }\n\n /**\n * Gets a caveat specification.\n *\n * @param caveatType - The type of the caveat specification to get.\n * @returns The caveat specification with the specified type.\n */\n private getCaveatSpecification<\n CaveatType extends ControllerCaveatSpecification['type'],\n >(caveatType: CaveatType) {\n return this._caveatSpecifications[caveatType];\n }\n\n /**\n * Constructor helper for validating permission specifications. This is\n * intended to prevent the use of invalid target keys which, while impossible\n * to add in TypeScript, could rather easily occur in plain JavaScript.\n *\n * Throws an error if validation fails.\n *\n * @param permissionSpecifications - The permission specifications passed to\n * this controller's constructor.\n * @param caveatSpecifications - The caveat specifications passed to this\n * controller.\n */\n private validatePermissionSpecifications(\n permissionSpecifications: PermissionSpecificationMap,\n caveatSpecifications: CaveatSpecificationMap,\n ) {\n Object.entries(\n permissionSpecifications,\n ).forEach(\n ([\n targetKey,\n { permissionType, targetKey: innerTargetKey, allowedCaveats },\n ]) => {\n if (!permissionType || !hasProperty(PermissionType, permissionType)) {\n throw new Error(`Invalid permission type: \"${permissionType}\"`);\n }\n\n // Check if the target key is the empty string, ends with \"_\", or ends\n // with \"*\" but not \"_*\"\n if (!targetKey || /_$/u.test(targetKey) || /[^_]\\*$/u.test(targetKey)) {\n throw new Error(`Invalid permission target key: \"${targetKey}\"`);\n }\n\n if (targetKey !== innerTargetKey) {\n throw new Error(\n `Invalid permission specification: key \"${targetKey}\" must match specification.target value \"${innerTargetKey}\".`,\n );\n }\n\n if (allowedCaveats) {\n allowedCaveats.forEach((caveatType) => {\n if (!hasProperty(caveatSpecifications, caveatType)) {\n throw new UnrecognizedCaveatTypeError(caveatType);\n }\n });\n }\n },\n );\n }\n\n /**\n * Constructor helper for registering the controller's messaging system\n * actions.\n */\n private registerMessageHandlers(): void {\n this.messagingSystem.registerActionHandler(\n `${controllerName}:clearPermissions` as const,\n () => this.clearState(),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:getEndowments` as const,\n (origin: string, targetName: string, requestData?: unknown) =>\n this.getEndowments(origin, targetName, requestData),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:getSubjectNames` as const,\n () => this.getSubjectNames(),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:getPermissions` as const,\n (origin: OriginString) => this.getPermissions(origin),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:hasPermission` as const,\n (origin: OriginString, targetName: string) =>\n this.hasPermission(origin, targetName),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:hasPermissions` as const,\n (origin: OriginString) => this.hasPermissions(origin),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:grantPermissions` as const,\n this.grantPermissions.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:requestPermissions` as const,\n (subject: PermissionSubjectMetadata, permissions: RequestedPermissions) =>\n this.requestPermissions(subject, permissions),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:revokeAllPermissions` as const,\n (origin: OriginString) => this.revokeAllPermissions(origin),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:revokePermissionForAllSubjects` as const,\n (\n target: ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n ) => this.revokePermissionForAllSubjects(target),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:revokePermissions` as const,\n this.revokePermissions.bind(this),\n );\n }\n\n /**\n * Clears the state of the controller.\n */\n clearState(): void {\n this.update((_draftState) => {\n return {\n ...getDefaultState<\n ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >\n >(),\n };\n });\n }\n\n /**\n * Gets the permission specification corresponding to the given permission\n * type and target name. Throws an error if the target name does not\n * correspond to a permission, or if the specification is not of the\n * given permission type.\n *\n * @template Type - The type of the permission specification to get.\n * @param permissionType - The type of the permission specification to get.\n * @param targetName - The name of the permission whose specification to get.\n * @param requestingOrigin - The origin of the requesting subject, if any.\n * Will be added to any thrown errors.\n * @returns The specification object corresponding to the given type and\n * target name.\n */\n private getTypedPermissionSpecification(\n permissionType: Type,\n targetName: string,\n requestingOrigin?: string,\n ): ControllerPermissionSpecification & { permissionType: Type } {\n const failureError =\n permissionType === PermissionType.RestrictedMethod\n ? methodNotFound(\n targetName,\n requestingOrigin ? { origin: requestingOrigin } : undefined,\n )\n : new EndowmentPermissionDoesNotExistError(\n targetName,\n requestingOrigin,\n );\n\n const targetKey = this.getTargetKey(targetName);\n if (!targetKey) {\n throw failureError;\n }\n\n const specification = this.getPermissionSpecification(targetKey);\n if (!hasSpecificationType(specification, permissionType)) {\n throw failureError;\n }\n\n return specification;\n }\n\n /**\n * Gets the implementation of the specified restricted method.\n *\n * A JSON-RPC error is thrown if the method does not exist.\n *\n * @see {@link PermissionController.executeRestrictedMethod} and\n * {@link PermissionController.createPermissionMiddleware} for internal usage.\n * @param method - The name of the restricted method.\n * @param origin - The origin associated with the request for the restricted\n * method, if any.\n * @returns The restricted method implementation.\n */\n getRestrictedMethod(\n method: string,\n origin?: string,\n ): RestrictedMethod {\n return this.getTypedPermissionSpecification(\n PermissionType.RestrictedMethod,\n method,\n origin,\n ).methodImplementation;\n }\n\n /**\n * Gets a list of all origins of subjects.\n *\n * @returns The origins (i.e. IDs) of all subjects.\n */\n getSubjectNames(): OriginString[] {\n return Object.keys(this.state.subjects);\n }\n\n /**\n * Gets the permission for the specified target of the subject corresponding\n * to the specified origin.\n *\n * @param origin - The origin of the subject.\n * @param targetName - The method name as invoked by a third party (i.e., not\n * a method key).\n * @returns The permission if it exists, or undefined otherwise.\n */\n getPermission<\n SubjectPermission extends ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >,\n >(\n origin: OriginString,\n targetName: SubjectPermission['parentCapability'],\n ): SubjectPermission | undefined {\n return this.state.subjects[origin]?.permissions[targetName] as\n | SubjectPermission\n | undefined;\n }\n\n /**\n * Gets all permissions for the specified subject, if any.\n *\n * @param origin - The origin of the subject.\n * @returns The permissions of the subject, if any.\n */\n getPermissions(\n origin: OriginString,\n ):\n | SubjectPermissions<\n ValidPermission>\n >\n | undefined {\n return this.state.subjects[origin]?.permissions;\n }\n\n /**\n * Checks whether the subject with the specified origin has the specified\n * permission.\n *\n * @param origin - The origin of the subject.\n * @param target - The target name of the permission.\n * @returns Whether the subject has the permission.\n */\n hasPermission(\n origin: OriginString,\n target: ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n ): boolean {\n return Boolean(this.getPermission(origin, target));\n }\n\n /**\n * Checks whether the subject with the specified origin has any permissions.\n * Use this if you want to know if a subject \"exists\".\n *\n * @param origin - The origin of the subject to check.\n * @returns Whether the subject has any permissions.\n */\n hasPermissions(origin: OriginString): boolean {\n return Boolean(this.state.subjects[origin]);\n }\n\n /**\n * Revokes all permissions from the specified origin.\n *\n * Throws an error of the origin has no permissions.\n *\n * @param origin - The origin whose permissions to revoke.\n */\n revokeAllPermissions(origin: OriginString): void {\n this.update((draftState) => {\n if (!draftState.subjects[origin]) {\n throw new UnrecognizedSubjectError(origin);\n }\n delete draftState.subjects[origin];\n });\n }\n\n /**\n * Revokes the specified permission from the subject with the specified\n * origin.\n *\n * Throws an error if the subject or the permission does not exist.\n *\n * @param origin - The origin of the subject whose permission to revoke.\n * @param target - The target name of the permission to revoke.\n */\n revokePermission(\n origin: OriginString,\n target: ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n ): void {\n this.revokePermissions({ [origin]: [target] });\n }\n\n /**\n * Revokes the specified permissions from the specified subjects.\n *\n * Throws an error if any of the subjects or permissions do not exist.\n *\n * @param subjectsAndPermissions - An object mapping subject origins\n * to arrays of permission target names to revoke.\n */\n revokePermissions(\n subjectsAndPermissions: Record<\n OriginString,\n NonEmptyArray<\n ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability']\n >\n >,\n ): void {\n this.update((draftState) => {\n Object.keys(subjectsAndPermissions).forEach((origin) => {\n if (!hasProperty(draftState.subjects, origin)) {\n throw new UnrecognizedSubjectError(origin);\n }\n\n subjectsAndPermissions[origin].forEach((target) => {\n const { permissions } = draftState.subjects[origin];\n if (!hasProperty(permissions as Record, target)) {\n throw new PermissionDoesNotExistError(origin, target);\n }\n\n this.deletePermission(draftState.subjects, origin, target);\n });\n });\n });\n }\n\n /**\n * Revokes all permissions corresponding to the specified target for all subjects.\n * Does nothing if no subjects or no such permission exists.\n *\n * @param target - The name of the target to revoke all permissions for.\n */\n revokePermissionForAllSubjects(\n target: ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n ): void {\n if (this.getSubjectNames().length === 0) {\n return;\n }\n\n this.update((draftState) => {\n Object.entries(draftState.subjects).forEach(([origin, subject]) => {\n const { permissions } = subject;\n\n if (hasProperty(permissions as Record, target)) {\n this.deletePermission(draftState.subjects, origin, target);\n }\n });\n });\n }\n\n /**\n * Deletes the permission identified by the given origin and target. If the\n * permission is the single remaining permission of its subject, the subject\n * is also deleted.\n *\n * @param subjects - The draft permission controller subjects.\n * @param origin - The origin of the subject associated with the permission\n * to delete.\n * @param target - The target name of the permission to delete.\n */\n private deletePermission(\n subjects: Draft>,\n origin: OriginString,\n target: ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n ): void {\n const { permissions } = subjects[origin];\n if (Object.keys(permissions).length > 1) {\n delete permissions[target];\n } else {\n delete subjects[origin];\n }\n }\n\n /**\n * Checks whether the permission of the subject corresponding to the given\n * origin has a caveat of the specified type.\n *\n * Throws an error if the subject does not have a permission with the\n * specified target name.\n *\n * @template TargetName - The permission target name. Should be inferred.\n * @template CaveatType - The valid caveat types for the permission. Should\n * be inferred.\n * @param origin - The origin of the subject.\n * @param target - The target name of the permission.\n * @param caveatType - The type of the caveat to check for.\n * @returns Whether the permission has the specified caveat.\n */\n hasCaveat<\n TargetName extends ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n CaveatType extends ExtractAllowedCaveatTypes,\n >(origin: OriginString, target: TargetName, caveatType: CaveatType): boolean {\n return Boolean(this.getCaveat(origin, target, caveatType));\n }\n\n /**\n * Gets the caveat of the specified type, if any, for the permission of\n * the subject corresponding to the given origin.\n *\n * Throws an error if the subject does not have a permission with the\n * specified target name.\n *\n * @template TargetName - The permission target name. Should be inferred.\n * @template CaveatType - The valid caveat types for the permission. Should\n * be inferred.\n * @param origin - The origin of the subject.\n * @param target - The target name of the permission.\n * @param caveatType - The type of the caveat to get.\n * @returns The caveat, or `undefined` if no such caveat exists.\n */\n getCaveat<\n TargetName extends ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n CaveatType extends ExtractAllowedCaveatTypes,\n >(\n origin: OriginString,\n target: TargetName,\n caveatType: CaveatType,\n ): ExtractCaveat | undefined {\n const permission = this.getPermission(origin, target);\n if (!permission) {\n throw new PermissionDoesNotExistError(origin, target);\n }\n\n return findCaveat(permission, caveatType) as\n | ExtractCaveat\n | undefined;\n }\n\n /**\n * Adds a caveat of the specified type, with the specified caveat value, to\n * the permission corresponding to the given subject origin and permission\n * target.\n *\n * For modifying existing caveats, use\n * {@link PermissionController.updateCaveat}.\n *\n * Throws an error if no such permission exists, or if the caveat already\n * exists.\n *\n * @template TargetName - The permission target name. Should be inferred.\n * @template CaveatType - The valid caveat types for the permission. Should\n * be inferred.\n * @param origin - The origin of the subject.\n * @param target - The target name of the permission.\n * @param caveatType - The type of the caveat to add.\n * @param caveatValue - The value of the caveat to add.\n */\n addCaveat<\n TargetName extends ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n CaveatType extends ExtractAllowedCaveatTypes,\n >(\n origin: OriginString,\n target: TargetName,\n caveatType: CaveatType,\n caveatValue: ExtractCaveatValue,\n ): void {\n if (this.hasCaveat(origin, target, caveatType)) {\n throw new CaveatAlreadyExistsError(origin, target, caveatType);\n }\n\n this.setCaveat(origin, target, caveatType, caveatValue);\n }\n\n /**\n * Updates the value of the caveat of the specified type belonging to the\n * permission corresponding to the given subject origin and permission\n * target.\n *\n * For adding new caveats, use\n * {@link PermissionController.addCaveat}.\n *\n * Throws an error if no such permission or caveat exists.\n *\n * @template TargetName - The permission target name. Should be inferred.\n * @template CaveatType - The valid caveat types for the permission. Should\n * be inferred.\n * @param origin - The origin of the subject.\n * @param target - The target name of the permission.\n * @param caveatType - The type of the caveat to update.\n * @param caveatValue - The new value of the caveat.\n */\n updateCaveat<\n TargetName extends ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n CaveatType extends ExtractAllowedCaveatTypes,\n CaveatValue extends ExtractCaveatValue<\n ControllerCaveatSpecification,\n CaveatType\n >,\n >(\n origin: OriginString,\n target: TargetName,\n caveatType: CaveatType,\n caveatValue: CaveatValue,\n ): void {\n if (!this.hasCaveat(origin, target, caveatType)) {\n throw new CaveatDoesNotExistError(origin, target, caveatType);\n }\n\n this.setCaveat(origin, target, caveatType, caveatValue);\n }\n\n /**\n * Sets the specified caveat on the specified permission. Overwrites existing\n * caveats of the same type in-place (preserving array order), and adds the\n * caveat to the end of the array otherwise.\n *\n * Throws an error if the permission does not exist or fails to validate after\n * its caveats have been modified.\n *\n * @see {@link PermissionController.addCaveat}\n * @see {@link PermissionController.updateCaveat}\n * @template TargetName - The permission target name. Should be inferred.\n * @template CaveatType - The valid caveat types for the permission. Should\n * be inferred.\n * @param origin - The origin of the subject.\n * @param target - The target name of the permission.\n * @param caveatType - The type of the caveat to set.\n * @param caveatValue - The value of the caveat to set.\n */\n private setCaveat<\n TargetName extends ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n CaveatType extends ExtractAllowedCaveatTypes,\n >(\n origin: OriginString,\n target: TargetName,\n caveatType: CaveatType,\n caveatValue: ExtractCaveatValue,\n ): void {\n this.update((draftState) => {\n const subject = draftState.subjects[origin];\n\n // Unreachable because `hasCaveat` is always called before this, and it\n // throws if permissions are missing. TypeScript needs this, however.\n /* istanbul ignore if */\n if (!subject) {\n throw new UnrecognizedSubjectError(origin);\n }\n\n const permission = subject.permissions[target];\n\n /* istanbul ignore if: practically impossible, but TypeScript wants it */\n if (!permission) {\n throw new PermissionDoesNotExistError(origin, target);\n }\n\n const caveat = {\n type: caveatType,\n value: caveatValue,\n };\n this.validateCaveat(caveat, origin, target);\n\n if (permission.caveats) {\n const caveatIndex = permission.caveats.findIndex(\n (existingCaveat) => existingCaveat.type === caveat.type,\n );\n\n if (caveatIndex === -1) {\n permission.caveats.push(caveat);\n } else {\n permission.caveats.splice(caveatIndex, 1, caveat);\n }\n } else {\n // Typecast: At this point, we don't know if the specific permission\n // is allowed to have caveats, but it should be impossible to call\n // this method for a permission that may not have any caveats.\n // If all else fails, the permission validator is also called.\n permission.caveats = [caveat] as any;\n }\n\n this.validateModifiedPermission(permission, origin, target);\n });\n }\n\n /**\n * Updates all caveats with the specified type for all subjects and\n * permissions by applying the specified mutator function to them.\n *\n * ATTN: Permissions can be revoked entirely by the action of this method,\n * read on for details.\n *\n * Caveat mutators are functions that receive a caveat value and return a\n * tuple consisting of a {@link CaveatMutatorOperation} and, optionally, a new\n * value to update the existing caveat with.\n *\n * For each caveat, depending on the mutator result, this method will:\n * - Do nothing ({@link CaveatMutatorOperation.noop})\n * - Update the value of the caveat ({@link CaveatMutatorOperation.updateValue}). The caveat specification validator, if any, will be called after updating the value.\n * - Delete the caveat ({@link CaveatMutatorOperation.deleteCaveat}). The permission specification validator, if any, will be called after deleting the caveat.\n * - Revoke the parent permission ({@link CaveatMutatorOperation.revokePermission})\n *\n * This method throws if the validation of any caveat or permission fails.\n *\n * @param targetCaveatType - The type of the caveats to update.\n * @param mutator - The mutator function which will be applied to all caveat\n * values.\n */\n updatePermissionsByCaveat<\n CaveatType extends ExtractCaveats['type'],\n TargetCaveat extends ExtractCaveat<\n ControllerCaveatSpecification,\n CaveatType\n >,\n >(targetCaveatType: CaveatType, mutator: CaveatMutator): void {\n if (Object.keys(this.state.subjects).length === 0) {\n return;\n }\n\n this.update((draftState) => {\n Object.values(draftState.subjects).forEach((subject) => {\n Object.values(subject.permissions).forEach((permission) => {\n const { caveats } = permission;\n const targetCaveat = caveats?.find(\n ({ type }) => type === targetCaveatType,\n );\n if (!targetCaveat) {\n return;\n }\n\n // The mutator may modify the caveat value in place, and must always\n // return a valid mutation result.\n const mutatorResult = mutator(targetCaveat.value);\n switch (mutatorResult.operation) {\n case CaveatMutatorOperation.noop:\n break;\n\n case CaveatMutatorOperation.updateValue:\n // Typecast: `Mutable` is used here to assign to a readonly\n // property. `targetConstraint` should already be mutable because\n // it's part of a draft, but for some reason it's not. We can't\n // use the more-correct `Draft` type here either because it\n // results in an error.\n (targetCaveat as Mutable).value =\n mutatorResult.value;\n\n this.validateCaveat(\n targetCaveat,\n subject.origin,\n permission.parentCapability,\n );\n break;\n\n case CaveatMutatorOperation.deleteCaveat:\n this.deleteCaveat(\n permission,\n targetCaveatType,\n subject.origin,\n permission.parentCapability,\n );\n break;\n\n case CaveatMutatorOperation.revokePermission:\n this.deletePermission(\n draftState.subjects,\n subject.origin,\n permission.parentCapability,\n );\n break;\n\n default: {\n // This type check ensures that the switch statement is\n // exhaustive.\n const _exhaustiveCheck: never = mutatorResult;\n throw new Error(\n `Unrecognized mutation result: \"${\n (_exhaustiveCheck as any).operation\n }\"`,\n );\n }\n }\n });\n });\n });\n }\n\n /**\n * Removes the caveat of the specified type from the permission corresponding\n * to the given subject origin and target name.\n *\n * Throws an error if no such permission or caveat exists.\n *\n * @template TargetName - The permission target name. Should be inferred.\n * @template CaveatType - The valid caveat types for the permission. Should\n * be inferred.\n * @param origin - The origin of the subject.\n * @param target - The target name of the permission.\n * @param caveatType - The type of the caveat to remove.\n */\n removeCaveat<\n TargetName extends ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n CaveatType extends ExtractAllowedCaveatTypes,\n >(origin: OriginString, target: TargetName, caveatType: CaveatType): void {\n this.update((draftState) => {\n const permission = draftState.subjects[origin]?.permissions[target];\n if (!permission) {\n throw new PermissionDoesNotExistError(origin, target);\n }\n\n if (!permission.caveats) {\n throw new CaveatDoesNotExistError(origin, target, caveatType);\n }\n\n this.deleteCaveat(permission, caveatType, origin, target);\n });\n }\n\n /**\n * Deletes the specified caveat from the specified permission. If no caveats\n * remain after deletion, the permission's caveat property is set to `null`.\n * The permission is validated after being modified.\n *\n * Throws an error if the permission does not have a caveat with the specified\n * type.\n *\n * @param permission - The permission whose caveat to delete.\n * @param caveatType - The type of the caveat to delete.\n * @param origin - The origin the permission subject.\n * @param target - The name of the permission target.\n */\n private deleteCaveat<\n TargetName extends ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n CaveatType extends ExtractCaveats['type'],\n >(\n permission: Draft,\n caveatType: CaveatType,\n origin: OriginString,\n target: TargetName,\n ): void {\n /* istanbul ignore if: not possible in our usage */\n if (!permission.caveats) {\n throw new CaveatDoesNotExistError(origin, target, caveatType);\n }\n\n const caveatIndex = permission.caveats.findIndex(\n (existingCaveat) => existingCaveat.type === caveatType,\n );\n\n if (caveatIndex === -1) {\n throw new CaveatDoesNotExistError(origin, target, caveatType);\n }\n\n if (permission.caveats.length === 1) {\n permission.caveats = null;\n } else {\n permission.caveats.splice(caveatIndex, 1);\n }\n\n this.validateModifiedPermission(permission, origin, target);\n }\n\n /**\n * Validates the specified modified permission. Should **always** be invoked\n * on a permission after its caveats have been modified.\n *\n * Just like {@link PermissionController.validatePermission}, except that the\n * corresponding target key and specification are retrieved first, and an\n * error is thrown if the target key does not exist.\n *\n * @param permission - The modified permission to validate.\n * @param origin - The origin associated with the permission.\n * @param targetName - The target name name of the permission.\n */\n private validateModifiedPermission(\n permission: Draft,\n origin: OriginString,\n targetName: ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n ): void {\n const targetKey = this.getTargetKey(permission.parentCapability);\n /* istanbul ignore if: this should be impossible */\n if (!targetKey) {\n throw new Error(\n `Fatal: Existing permission target key \"${targetKey}\" has no specification.`,\n );\n }\n\n this.validatePermission(\n this.getPermissionSpecification(targetKey),\n permission as PermissionConstraint,\n origin,\n targetName,\n );\n }\n\n /**\n * Gets the key for the specified permission target.\n *\n * Used to support our namespaced permission target feature, which is used\n * to implement namespaced restricted JSON-RPC methods.\n *\n * @param target - The requested permission target.\n * @returns The internal key of the permission target.\n */\n private getTargetKey(\n target: string,\n ): ControllerPermissionSpecification['targetKey'] | undefined {\n if (hasProperty(this._permissionSpecifications, target)) {\n return target;\n }\n\n const namespacedTargetsWithoutWildcard: Record = {};\n for (const targetKey of Object.keys(this._permissionSpecifications)) {\n const wildCardMatch = targetKey.match(/(.+)\\*$/u);\n if (wildCardMatch) {\n namespacedTargetsWithoutWildcard[wildCardMatch[1]] = true;\n }\n }\n\n // Check for potentially nested namespaces:\n // Ex: wildzone_\n // Ex: eth_plugin_\n const segments = target.split('_');\n let targetKey = '';\n\n while (\n segments.length > 0 &&\n !hasProperty(this._permissionSpecifications, targetKey) &&\n !namespacedTargetsWithoutWildcard[targetKey]\n ) {\n targetKey += `${segments.shift()}_`;\n }\n\n if (namespacedTargetsWithoutWildcard[targetKey]) {\n return `${targetKey}*`;\n }\n\n return undefined;\n }\n\n /**\n * Grants _approved_ permissions to the specified subject. Every permission and\n * caveat is stringently validated – including by calling every specification\n * validator – and an error is thrown if any validation fails.\n *\n * ATTN: This method does **not** prompt the user for approval.\n *\n * @see {@link PermissionController.requestPermissions} For initiating a\n * permissions request requiring user approval.\n * @param options - Options bag.\n * @param options.approvedPermissions - The requested permissions approved by\n * the user.\n * @param options.requestData - Permission request data. Passed to permission\n * factory functions.\n * @param options.preserveExistingPermissions - Whether to preserve the\n * subject's existing permissions.\n * @param options.subject - The subject to grant permissions to.\n * @returns The granted permissions.\n */\n grantPermissions({\n approvedPermissions,\n requestData,\n preserveExistingPermissions = true,\n subject,\n }: {\n approvedPermissions: RequestedPermissions;\n subject: PermissionSubjectMetadata;\n preserveExistingPermissions?: boolean;\n requestData?: Record;\n }): SubjectPermissions<\n ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >\n > {\n const { origin } = subject;\n\n if (!origin || typeof origin !== 'string') {\n throw new InvalidSubjectIdentifierError(origin);\n }\n\n const permissions = (\n preserveExistingPermissions\n ? {\n ...this.getPermissions(origin),\n }\n : {}\n ) as SubjectPermissions<\n ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >\n >;\n\n for (const [requestedTarget, approvedPermission] of Object.entries(\n approvedPermissions,\n )) {\n const targetKey = this.getTargetKey(requestedTarget);\n if (!targetKey) {\n throw methodNotFound(requestedTarget);\n }\n\n if (\n approvedPermission.parentCapability !== undefined &&\n requestedTarget !== approvedPermission.parentCapability\n ) {\n throw new InvalidApprovedPermissionError(\n origin,\n requestedTarget,\n approvedPermission,\n );\n }\n\n // The requested target must be a valid target name if we found its key.\n // We reassign it to change its type.\n const targetName = requestedTarget as ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'];\n const specification = this.getPermissionSpecification(targetKey);\n\n // The requested caveats are validated here.\n const caveats = this.constructCaveats(\n origin,\n targetName,\n approvedPermission.caveats,\n );\n\n const permissionOptions = {\n caveats,\n invoker: origin,\n target: targetName,\n };\n\n let permission: ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >;\n if (specification.factory) {\n permission = specification.factory(permissionOptions, requestData);\n\n // Full caveat and permission validation is performed here since the\n // factory function can arbitrarily modify the entire permission object,\n // including its caveats.\n this.validatePermission(specification, permission, origin, targetName);\n } else {\n permission = constructPermission(permissionOptions);\n\n // We do not need to validate caveats in this case, because the plain\n // permission constructor function does not modify the caveats, which\n // were already validated by `constructCaveats` above.\n this.validatePermission(specification, permission, origin, targetName, {\n invokePermissionValidator: true,\n performCaveatValidation: false,\n });\n }\n permissions[targetName] = permission;\n }\n\n this.setValidatedPermissions(origin, permissions);\n return permissions;\n }\n\n /**\n * Validates the specified permission by:\n * - Ensuring that its `caveats` property is either `null` or a non-empty array.\n * - Ensuring that it only includes caveats allowed by its specification.\n * - Ensuring that it includes no duplicate caveats (by caveat type).\n * - Validating each caveat object, if `performCaveatValidation` is `true`.\n * - Calling the validator of its specification, if one exists and `invokePermissionValidator` is `true`.\n *\n * An error is thrown if validation fails.\n *\n * @param specification - The specification of the permission.\n * @param permission - The permission to validate.\n * @param origin - The origin associated with the permission.\n * @param targetName - The target name of the permission.\n * @param validationOptions - Validation options.\n * @param validationOptions.invokePermissionValidator - Whether to invoke the\n * permission's consumer-specified validator function, if any.\n * @param validationOptions.performCaveatValidation - Whether to invoke\n * {@link PermissionController.validateCaveat} on each of the permission's\n * caveats.\n */\n private validatePermission(\n specification: PermissionSpecificationConstraint,\n permission: PermissionConstraint,\n origin: OriginString,\n targetName: ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n { invokePermissionValidator, performCaveatValidation } = {\n invokePermissionValidator: true,\n performCaveatValidation: true,\n },\n ): void {\n const { allowedCaveats, validator } = specification;\n if (hasProperty(permission, 'caveats')) {\n const { caveats } = permission;\n\n if (caveats !== null && !(Array.isArray(caveats) && caveats.length > 0)) {\n throw new InvalidCaveatsPropertyError(origin, targetName, caveats);\n }\n\n const seenCaveatTypes = new Set();\n caveats?.forEach((caveat) => {\n if (performCaveatValidation) {\n this.validateCaveat(caveat, origin, targetName);\n }\n\n if (!allowedCaveats?.includes(caveat.type)) {\n throw new ForbiddenCaveatError(caveat.type, origin, targetName);\n }\n\n if (seenCaveatTypes.has(caveat.type)) {\n throw new DuplicateCaveatError(caveat.type, origin, targetName);\n }\n seenCaveatTypes.add(caveat.type);\n });\n }\n\n if (invokePermissionValidator && validator) {\n validator(permission, origin, targetName);\n }\n }\n\n /**\n * Assigns the specified permissions to the subject with the given origin.\n * Overwrites all existing permissions, and creates a subject entry if it\n * doesn't already exist.\n *\n * ATTN: Assumes that the new permissions have been validated.\n *\n * @param origin - The origin of the grantee subject.\n * @param permissions - The new permissions for the grantee subject.\n */\n private setValidatedPermissions(\n origin: OriginString,\n permissions: Record<\n string,\n ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >\n >,\n ): void {\n this.update((draftState) => {\n if (!draftState.subjects[origin]) {\n draftState.subjects[origin] = { origin, permissions: {} };\n }\n\n draftState.subjects[origin].permissions = castDraft(permissions);\n });\n }\n\n /**\n * Validates the requested caveats for the permission of the specified\n * subject origin and target name and returns the validated caveat array.\n *\n * Throws an error if validation fails.\n *\n * @param origin - The origin of the permission subject.\n * @param target - The permission target name.\n * @param requestedCaveats - The requested caveats to construct.\n * @returns The constructed caveats.\n */\n private constructCaveats(\n origin: OriginString,\n target: ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n requestedCaveats?: unknown[] | null,\n ): NonEmptyArray> | undefined {\n const caveatArray = requestedCaveats?.map((requestedCaveat) => {\n this.validateCaveat(requestedCaveat, origin, target);\n\n // Reassign so that we have a fresh object.\n const { type, value } = requestedCaveat as CaveatConstraint;\n return { type, value } as ExtractCaveats;\n });\n\n return caveatArray && isNonEmptyArray(caveatArray)\n ? caveatArray\n : undefined;\n }\n\n /**\n * This methods validates that the specified caveat is an object with the\n * expected properties and types. It also ensures that a caveat specification\n * exists for the requested caveat type, and calls the specification\n * validator, if it exists, on the caveat object.\n *\n * Throws an error if validation fails.\n *\n * @param caveat - The caveat object to validate.\n * @param origin - The origin associated with the subject of the parent\n * permission.\n * @param target - The target name associated with the parent permission.\n */\n private validateCaveat(\n caveat: unknown,\n origin: OriginString,\n target: string,\n ): void {\n if (!isPlainObject(caveat)) {\n throw new InvalidCaveatError(caveat, origin, target);\n }\n\n if (Object.keys(caveat).length !== 2) {\n throw new InvalidCaveatFieldsError(caveat, origin, target);\n }\n\n if (typeof caveat.type !== 'string') {\n throw new InvalidCaveatTypeError(caveat, origin, target);\n }\n\n const specification = this.getCaveatSpecification(caveat.type);\n if (!specification) {\n throw new UnrecognizedCaveatTypeError(caveat.type, origin, target);\n }\n\n if (!hasProperty(caveat, 'value') || caveat.value === undefined) {\n throw new CaveatMissingValueError(caveat, origin, target);\n }\n\n if (!isValidJson(caveat.value)) {\n throw new CaveatInvalidJsonError(caveat, origin, target);\n }\n\n // Typecast: TypeScript still believes that the caveat is a PlainObject.\n specification.validator?.(caveat as CaveatConstraint, origin, target);\n }\n\n /**\n * Initiates a permission request that requires user approval. This should\n * always be used to grant additional permissions to a subject, unless user\n * approval has been obtained through some other means.\n *\n * Permissions are validated at every step of the approval process, and this\n * method will reject if validation fails.\n *\n * @see {@link ApprovalController} For the user approval logic.\n * @see {@link PermissionController.acceptPermissionsRequest} For the method\n * that _accepts_ the request and resolves the user approval promise.\n * @see {@link PermissionController.rejectPermissionsRequest} For the method\n * that _rejects_ the request and the user approval promise.\n * @param subject - The grantee subject.\n * @param requestedPermissions - The requested permissions.\n * @param options - Additional options.\n * @param options.id - The id of the permissions request. Defaults to a unique\n * id.\n * @param options.preserveExistingPermissions - Whether to preserve the\n * subject's existing permissions. Defaults to `true`.\n * @returns The granted permissions and request metadata.\n */\n async requestPermissions(\n subject: PermissionSubjectMetadata,\n requestedPermissions: RequestedPermissions,\n options: {\n id?: string;\n preserveExistingPermissions?: boolean;\n } = {},\n ): Promise<\n [\n SubjectPermissions<\n ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >\n >,\n { id: string; origin: OriginString },\n ]\n > {\n const { origin } = subject;\n const { id = nanoid(), preserveExistingPermissions = true } = options;\n this.validateRequestedPermissions(origin, requestedPermissions);\n\n const metadata = {\n id,\n origin,\n };\n\n const permissionsRequest = {\n metadata,\n permissions: requestedPermissions,\n };\n\n const { permissions: approvedPermissions, ...requestData } =\n await this.requestUserApproval(permissionsRequest);\n\n return [\n this.grantPermissions({\n subject,\n approvedPermissions,\n preserveExistingPermissions,\n requestData,\n }),\n metadata,\n ];\n }\n\n /**\n * Validates requested permissions. Throws if validation fails.\n *\n * This method ensures that the requested permissions are a properly\n * formatted {@link RequestedPermissions} object, and performs the same\n * validation as {@link PermissionController.grantPermissions}, except that\n * consumer-specified permission validator functions are not called, since\n * they are only called on fully constructed, approved permissions that are\n * otherwise completely valid.\n *\n * Unrecognzied properties on requested permissions are ignored.\n *\n * @param origin - The origin of the grantee subject.\n * @param requestedPermissions - The requested permissions.\n */\n private validateRequestedPermissions(\n origin: OriginString,\n requestedPermissions: unknown,\n ): void {\n if (!isPlainObject(requestedPermissions)) {\n throw invalidParams({\n message: `Requested permissions for origin \"${origin}\" is not a plain object.`,\n data: { origin, requestedPermissions },\n });\n }\n\n if (Object.keys(requestedPermissions).length === 0) {\n throw invalidParams({\n message: `Permissions request for origin \"${origin}\" contains no permissions.`,\n data: { requestedPermissions },\n });\n }\n\n for (const targetName of Object.keys(requestedPermissions)) {\n const permission = requestedPermissions[targetName];\n const targetKey = this.getTargetKey(targetName);\n\n if (!targetKey) {\n throw methodNotFound(targetName, { origin, requestedPermissions });\n }\n\n if (\n !isPlainObject(permission) ||\n (permission.parentCapability !== undefined &&\n targetName !== permission.parentCapability)\n ) {\n throw invalidParams({\n message: `Permissions request for origin \"${origin}\" contains invalid requested permission(s).`,\n data: { origin, requestedPermissions },\n });\n }\n\n // Here we validate the permission without invoking its validator, if any.\n // The validator will be invoked after the permission has been approved.\n this.validatePermission(\n this.getPermissionSpecification(targetKey),\n // Typecast: The permission is still a \"PlainObject\" here.\n permission as PermissionConstraint,\n origin,\n targetName,\n { invokePermissionValidator: false, performCaveatValidation: true },\n );\n }\n }\n\n /**\n * Adds a request to the {@link ApprovalController} using the\n * {@link AddApprovalRequest} action. Also validates the resulting approved\n * permissions request, and throws an error if validation fails.\n *\n * @param permissionsRequest - The permissions request object.\n * @returns The approved permissions request object.\n */\n private async requestUserApproval(permissionsRequest: PermissionsRequest) {\n const { origin, id } = permissionsRequest.metadata;\n const approvedRequest = await this.messagingSystem.call(\n 'ApprovalController:addRequest',\n {\n id,\n origin,\n requestData: permissionsRequest,\n type: MethodNames.requestPermissions,\n },\n true,\n );\n\n this.validateApprovedPermissions(approvedRequest, { id, origin });\n return approvedRequest as PermissionsRequest;\n }\n\n /**\n * Validates an approved {@link PermissionsRequest} object. The approved\n * request must have the required `metadata` and `permissions` properties,\n * the `id` and `origin` of the `metadata` must match the original request\n * metadata, and the requested permissions must be valid per\n * {@link PermissionController.validateRequestedPermissions}. Any extra\n * metadata properties are ignored.\n *\n * An error is thrown if validation fails.\n *\n * @param approvedRequest - The approved permissions request object.\n * @param originalMetadata - The original request metadata.\n */\n private validateApprovedPermissions(\n approvedRequest: unknown,\n originalMetadata: PermissionsRequestMetadata,\n ) {\n const { id, origin } = originalMetadata;\n\n if (\n !isPlainObject(approvedRequest) ||\n !isPlainObject(approvedRequest.metadata)\n ) {\n throw internalError(\n `Approved permissions request for subject \"${origin}\" is invalid.`,\n { data: { approvedRequest } },\n );\n }\n\n const {\n metadata: { id: newId, origin: newOrigin },\n permissions,\n } = approvedRequest;\n\n if (newId !== id) {\n throw internalError(\n `Approved permissions request for subject \"${origin}\" mutated its id.`,\n { originalId: id, mutatedId: newId },\n );\n }\n\n if (newOrigin !== origin) {\n throw internalError(\n `Approved permissions request for subject \"${origin}\" mutated its origin.`,\n { originalOrigin: origin, mutatedOrigin: newOrigin },\n );\n }\n\n try {\n this.validateRequestedPermissions(origin, permissions);\n } catch (error) {\n if (error instanceof EthereumRpcError) {\n // Re-throw as an internal error; we should never receive invalid approved\n // permissions.\n throw internalError(\n `Invalid approved permissions request: ${error.message}`,\n error.data,\n );\n }\n throw internalError('Unrecognized error type', { error });\n }\n }\n\n /**\n * Accepts a permissions request created by\n * {@link PermissionController.requestPermissions}.\n *\n * @param request - The permissions request.\n */\n async acceptPermissionsRequest(request: PermissionsRequest): Promise {\n const { id } = request.metadata;\n\n if (!this.hasApprovalRequest({ id })) {\n throw new PermissionsRequestNotFoundError(id);\n }\n\n if (Object.keys(request.permissions).length === 0) {\n this._rejectPermissionsRequest(\n id,\n invalidParams({\n message: 'Must request at least one permission.',\n }),\n );\n return;\n }\n\n try {\n this.messagingSystem.call(\n 'ApprovalController:acceptRequest',\n id,\n request,\n );\n } catch (error) {\n // If accepting unexpectedly fails, reject the request and re-throw the\n // error\n this._rejectPermissionsRequest(id, error);\n throw error;\n }\n }\n\n /**\n * Rejects a permissions request created by\n * {@link PermissionController.requestPermissions}.\n *\n * @param id - The id of the request to be rejected.\n */\n async rejectPermissionsRequest(id: string): Promise {\n if (!this.hasApprovalRequest({ id })) {\n throw new PermissionsRequestNotFoundError(id);\n }\n\n this._rejectPermissionsRequest(id, userRejectedRequest());\n }\n\n /**\n * Checks whether the {@link ApprovalController} has a particular permissions\n * request.\n *\n * @see {@link PermissionController.acceptPermissionsRequest} and\n * {@link PermissionController.rejectPermissionsRequest} for usage.\n * @param options - The {@link HasApprovalRequest} options.\n * @param options.id - The id of the approval request to check for.\n * @returns Whether the specified request exists.\n */\n private hasApprovalRequest(options: { id: string }): boolean {\n return this.messagingSystem.call(\n 'ApprovalController:hasRequest',\n // Typecast: For some reason, the type here expects all of the possible\n // HasApprovalRequest options to be specified, when they're actually all\n // optional. Passing just the id is definitely valid, so we just cast it.\n options as any,\n );\n }\n\n /**\n * Rejects the permissions request with the specified id, with the specified\n * error as the reason. This method is effectively a wrapper around a\n * messenger call for the `ApprovalController:rejectRequest` action.\n *\n * @see {@link PermissionController.acceptPermissionsRequest} and\n * {@link PermissionController.rejectPermissionsRequest} for usage.\n * @param id - The id of the request to reject.\n * @param error - The error associated with the rejection.\n * @returns Nothing\n */\n private _rejectPermissionsRequest(id: string, error: unknown): void {\n return this.messagingSystem.call(\n 'ApprovalController:rejectRequest',\n id,\n error,\n );\n }\n\n /**\n * Gets the subject's endowments per the specified endowment permission.\n * Throws if the subject does not have the required permission or if the\n * permission is not an endowment permission.\n *\n * @param origin - The origin of the subject whose endowments to retrieve.\n * @param targetName - The name of the endowment permission. This must be a\n * valid permission target name.\n * @param requestData - Additional data associated with the request, if any.\n * Forwarded to the endowment getter function for the permission.\n * @returns The endowments, if any.\n */\n async getEndowments(\n origin: string,\n targetName: ExtractEndowmentPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n requestData?: unknown,\n ): Promise {\n if (!this.hasPermission(origin, targetName)) {\n throw unauthorized({ data: { origin, targetName } });\n }\n\n return this.getTypedPermissionSpecification(\n PermissionType.Endowment,\n targetName,\n origin,\n ).endowmentGetter({ origin, requestData });\n }\n\n /**\n * Executes a restricted method as the subject with the given origin.\n * The specified params, if any, will be passed to the method implementation.\n *\n * ATTN: Great caution should be exercised in the use of this method.\n * Methods that cause side effects or affect application state should\n * be avoided.\n *\n * This method will first attempt to retrieve the requested restricted method\n * implementation, throwing if it does not exist. The method will then be\n * invoked as though the subject with the specified origin had invoked it with\n * the specified parameters. This means that any existing caveats will be\n * applied to the restricted method, and this method will throw if the\n * restricted method or its caveat decorators throw.\n *\n * In addition, this method will throw if the subject does not have a\n * permission for the specified restricted method.\n *\n * @param origin - The origin of the subject to execute the method on behalf\n * of.\n * @param targetName - The name of the method to execute. This must be a valid\n * permission target name.\n * @param params - The parameters to pass to the method implementation.\n * @returns The result of the executed method.\n */\n async executeRestrictedMethod(\n origin: OriginString,\n targetName: ExtractRestrictedMethodPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n params?: RestrictedMethodParameters,\n ): Promise {\n // Throws if the method does not exist\n const methodImplementation = this.getRestrictedMethod(targetName, origin);\n\n const result = await this._executeRestrictedMethod(\n methodImplementation,\n { origin },\n targetName,\n params,\n );\n\n if (result === undefined) {\n throw new Error(\n `Internal request for method \"${targetName}\" as origin \"${origin}\" returned no result.`,\n );\n }\n\n return result;\n }\n\n /**\n * An internal method used in the controller's `json-rpc-engine` middleware\n * and {@link PermissionController.executeRestrictedMethod}. Calls the\n * specified restricted method implementation after decorating it with the\n * caveats of its permission. Throws if the subject does not have the\n * requisite permission.\n *\n * ATTN: Parameter validation is the responsibility of the caller, or\n * the restricted method implementation in the case of `params`.\n *\n * @see {@link PermissionController.executeRestrictedMethod} and\n * {@link PermissionController.createPermissionMiddleware} for usage.\n * @param methodImplementation - The implementation of the method to call.\n * @param subject - Metadata about the subject that made the request.\n * @param method - The method name\n * @param params - Params needed for executing the restricted method\n * @returns The result of the restricted method implementation\n */\n private _executeRestrictedMethod(\n methodImplementation: RestrictedMethod,\n subject: PermissionSubjectMetadata,\n method: ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n params: RestrictedMethodParameters = [],\n ): ReturnType> {\n const { origin } = subject;\n\n const permission = this.getPermission(origin, method);\n if (!permission) {\n throw unauthorized({ data: { origin, method } });\n }\n\n return decorateWithCaveats(\n methodImplementation,\n permission,\n this._caveatSpecifications,\n )({ method, params, context: { origin } });\n }\n}\n"]} \ No newline at end of file diff --git a/dist/permissions/errors.d.ts b/dist/permissions/errors.d.ts new file mode 100644 index 0000000000..2d043dd4aa --- /dev/null +++ b/dist/permissions/errors.d.ts @@ -0,0 +1,150 @@ +import { EthereumRpcError } from 'eth-rpc-errors'; +declare type UnauthorizedArg = { + data?: Record; +}; +/** + * Utility function for building an "unauthorized" error. + * + * @param opts - Optional arguments that add extra context + * @returns The built error + */ +export declare function unauthorized(opts: UnauthorizedArg): import("eth-rpc-errors").EthereumProviderError>; +/** + * Utility function for building a "method not found" error. + * + * @param method - The method in question. + * @param data - Optional data for context. + * @returns The built error + */ +export declare function methodNotFound(method: string, data?: unknown): EthereumRpcError; +declare type InvalidParamsArg = { + message?: string; + data?: unknown; +}; +/** + * Utility function for building an "invalid params" error. + * + * @param opts - Optional arguments that add extra context + * @returns The built error + */ +export declare function invalidParams(opts: InvalidParamsArg): EthereumRpcError; +/** + * Utility function for building an "user rejected request" error. + * + * @param data - Optional data to add extra context + * @returns The built error + */ +export declare function userRejectedRequest>(data?: Data): EthereumRpcError; +/** + * Utility function for building an internal error. + * + * @param message - The error message + * @param data - Optional data to add extra context + * @returns The built error + */ +export declare function internalError>(message: string, data?: Data): EthereumRpcError; +export declare class InvalidSubjectIdentifierError extends Error { + constructor(origin: unknown); +} +export declare class UnrecognizedSubjectError extends Error { + constructor(origin: string); +} +export declare class InvalidApprovedPermissionError extends Error { + data: { + origin: string; + target: string; + approvedPermission: Record; + }; + constructor(origin: string, target: string, approvedPermission: Record); +} +export declare class PermissionDoesNotExistError extends Error { + constructor(origin: string, target: string); +} +export declare class EndowmentPermissionDoesNotExistError extends Error { + data?: { + origin: string; + }; + constructor(target: string, origin?: string); +} +export declare class UnrecognizedCaveatTypeError extends Error { + data: { + caveatType: string; + origin?: string; + target?: string; + }; + constructor(caveatType: string); + constructor(caveatType: string, origin: string, target: string); +} +export declare class InvalidCaveatsPropertyError extends Error { + data: { + origin: string; + target: string; + caveatsProperty: unknown; + }; + constructor(origin: string, target: string, caveatsProperty: unknown); +} +export declare class CaveatDoesNotExistError extends Error { + constructor(origin: string, target: string, caveatType: string); +} +export declare class CaveatAlreadyExistsError extends Error { + constructor(origin: string, target: string, caveatType: string); +} +export declare class InvalidCaveatError extends EthereumRpcError { + data: { + origin: string; + target: string; + }; + constructor(receivedCaveat: unknown, origin: string, target: string); +} +export declare class InvalidCaveatTypeError extends Error { + data: { + caveat: Record; + origin: string; + target: string; + }; + constructor(caveat: Record, origin: string, target: string); +} +export declare class CaveatMissingValueError extends Error { + data: { + caveat: Record; + origin: string; + target: string; + }; + constructor(caveat: Record, origin: string, target: string); +} +export declare class CaveatInvalidJsonError extends Error { + data: { + caveat: Record; + origin: string; + target: string; + }; + constructor(caveat: Record, origin: string, target: string); +} +export declare class InvalidCaveatFieldsError extends Error { + data: { + caveat: Record; + origin: string; + target: string; + }; + constructor(caveat: Record, origin: string, target: string); +} +export declare class ForbiddenCaveatError extends Error { + data: { + caveatType: string; + origin: string; + target: string; + }; + constructor(caveatType: string, origin: string, targetName: string); +} +export declare class DuplicateCaveatError extends Error { + data: { + caveatType: string; + origin: string; + target: string; + }; + constructor(caveatType: string, origin: string, targetName: string); +} +export declare class PermissionsRequestNotFoundError extends Error { + constructor(id: string); +} +export {}; diff --git a/dist/permissions/errors.js b/dist/permissions/errors.js new file mode 100644 index 0000000000..f293fb0fe0 --- /dev/null +++ b/dist/permissions/errors.js @@ -0,0 +1,189 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PermissionsRequestNotFoundError = exports.DuplicateCaveatError = exports.ForbiddenCaveatError = exports.InvalidCaveatFieldsError = exports.CaveatInvalidJsonError = exports.CaveatMissingValueError = exports.InvalidCaveatTypeError = exports.InvalidCaveatError = exports.CaveatAlreadyExistsError = exports.CaveatDoesNotExistError = exports.InvalidCaveatsPropertyError = exports.UnrecognizedCaveatTypeError = exports.EndowmentPermissionDoesNotExistError = exports.PermissionDoesNotExistError = exports.InvalidApprovedPermissionError = exports.UnrecognizedSubjectError = exports.InvalidSubjectIdentifierError = exports.internalError = exports.userRejectedRequest = exports.invalidParams = exports.methodNotFound = exports.unauthorized = void 0; +const eth_rpc_errors_1 = require("eth-rpc-errors"); +/** + * Utility function for building an "unauthorized" error. + * + * @param opts - Optional arguments that add extra context + * @returns The built error + */ +function unauthorized(opts) { + return eth_rpc_errors_1.ethErrors.provider.unauthorized({ + message: 'Unauthorized to perform action. Try requesting the required permission(s) first. For more information, see: https://docs.metamask.io/guide/rpc-api.html#permissions', + data: opts.data, + }); +} +exports.unauthorized = unauthorized; +/** + * Utility function for building a "method not found" error. + * + * @param method - The method in question. + * @param data - Optional data for context. + * @returns The built error + */ +function methodNotFound(method, data) { + const message = `The method "${method}" does not exist / is not available.`; + const opts = { message }; + if (data !== undefined) { + opts.data = data; + } + return eth_rpc_errors_1.ethErrors.rpc.methodNotFound(opts); +} +exports.methodNotFound = methodNotFound; +/** + * Utility function for building an "invalid params" error. + * + * @param opts - Optional arguments that add extra context + * @returns The built error + */ +function invalidParams(opts) { + return eth_rpc_errors_1.ethErrors.rpc.invalidParams({ + data: opts.data, + message: opts.message, + }); +} +exports.invalidParams = invalidParams; +/** + * Utility function for building an "user rejected request" error. + * + * @param data - Optional data to add extra context + * @returns The built error + */ +function userRejectedRequest(data) { + return eth_rpc_errors_1.ethErrors.provider.userRejectedRequest({ data }); +} +exports.userRejectedRequest = userRejectedRequest; +/** + * Utility function for building an internal error. + * + * @param message - The error message + * @param data - Optional data to add extra context + * @returns The built error + */ +function internalError(message, data) { + return eth_rpc_errors_1.ethErrors.rpc.internal({ message, data }); +} +exports.internalError = internalError; +class InvalidSubjectIdentifierError extends Error { + constructor(origin) { + super(`Invalid subject identifier: "${typeof origin === 'string' ? origin : typeof origin}"`); + } +} +exports.InvalidSubjectIdentifierError = InvalidSubjectIdentifierError; +class UnrecognizedSubjectError extends Error { + constructor(origin) { + super(`Unrecognized subject: "${origin}" has no permissions.`); + } +} +exports.UnrecognizedSubjectError = UnrecognizedSubjectError; +class InvalidApprovedPermissionError extends Error { + constructor(origin, target, approvedPermission) { + super(`Invalid approved permission for origin "${origin}" and target "${target}".`); + this.data = { origin, target, approvedPermission }; + } +} +exports.InvalidApprovedPermissionError = InvalidApprovedPermissionError; +class PermissionDoesNotExistError extends Error { + constructor(origin, target) { + super(`Subject "${origin}" has no permission for "${target}".`); + } +} +exports.PermissionDoesNotExistError = PermissionDoesNotExistError; +class EndowmentPermissionDoesNotExistError extends Error { + constructor(target, origin) { + super(`Subject "${origin}" has no permission for "${target}".`); + if (origin) { + this.data = { origin }; + } + } +} +exports.EndowmentPermissionDoesNotExistError = EndowmentPermissionDoesNotExistError; +class UnrecognizedCaveatTypeError extends Error { + constructor(caveatType, origin, target) { + super(`Unrecognized caveat type: "${caveatType}"`); + this.data = { caveatType }; + if (origin !== undefined) { + this.data.origin = origin; + } + if (target !== undefined) { + this.data.target = target; + } + } +} +exports.UnrecognizedCaveatTypeError = UnrecognizedCaveatTypeError; +class InvalidCaveatsPropertyError extends Error { + constructor(origin, target, caveatsProperty) { + super(`The "caveats" property of permission for "${target}" of subject "${origin}" is invalid. It must be a non-empty array if specified.`); + this.data = { origin, target, caveatsProperty }; + } +} +exports.InvalidCaveatsPropertyError = InvalidCaveatsPropertyError; +class CaveatDoesNotExistError extends Error { + constructor(origin, target, caveatType) { + super(`Permission for "${target}" of subject "${origin}" has no caveat of type "${caveatType}".`); + } +} +exports.CaveatDoesNotExistError = CaveatDoesNotExistError; +class CaveatAlreadyExistsError extends Error { + constructor(origin, target, caveatType) { + super(`Permission for "${target}" of subject "${origin}" already has a caveat of type "${caveatType}".`); + } +} +exports.CaveatAlreadyExistsError = CaveatAlreadyExistsError; +class InvalidCaveatError extends eth_rpc_errors_1.EthereumRpcError { + constructor(receivedCaveat, origin, target) { + super(eth_rpc_errors_1.errorCodes.rpc.invalidParams, `Invalid caveat. Caveats must be plain objects.`, { receivedCaveat }); + this.data = { origin, target }; + } +} +exports.InvalidCaveatError = InvalidCaveatError; +class InvalidCaveatTypeError extends Error { + constructor(caveat, origin, target) { + super(`Caveat types must be strings. Received: "${typeof caveat.type}"`); + this.data = { caveat, origin, target }; + } +} +exports.InvalidCaveatTypeError = InvalidCaveatTypeError; +class CaveatMissingValueError extends Error { + constructor(caveat, origin, target) { + super(`Caveat is missing "value" field.`); + this.data = { caveat, origin, target }; + } +} +exports.CaveatMissingValueError = CaveatMissingValueError; +class CaveatInvalidJsonError extends Error { + constructor(caveat, origin, target) { + super(`Caveat "value" is invalid JSON.`); + this.data = { caveat, origin, target }; + } +} +exports.CaveatInvalidJsonError = CaveatInvalidJsonError; +class InvalidCaveatFieldsError extends Error { + constructor(caveat, origin, target) { + super(`Caveat has unexpected number of fields: "${Object.keys(caveat).length}"`); + this.data = { caveat, origin, target }; + } +} +exports.InvalidCaveatFieldsError = InvalidCaveatFieldsError; +class ForbiddenCaveatError extends Error { + constructor(caveatType, origin, targetName) { + super(`Permissions for target "${targetName}" may not have caveats of type "${caveatType}".`); + this.data = { caveatType, origin, target: targetName }; + } +} +exports.ForbiddenCaveatError = ForbiddenCaveatError; +class DuplicateCaveatError extends Error { + constructor(caveatType, origin, targetName) { + super(`Permissions for target "${targetName}" contains multiple caveats of type "${caveatType}".`); + this.data = { caveatType, origin, target: targetName }; + } +} +exports.DuplicateCaveatError = DuplicateCaveatError; +class PermissionsRequestNotFoundError extends Error { + constructor(id) { + super(`Permissions request with id "${id}" not found.`); + } +} +exports.PermissionsRequestNotFoundError = PermissionsRequestNotFoundError; +//# sourceMappingURL=errors.js.map \ No newline at end of file diff --git a/dist/permissions/errors.js.map b/dist/permissions/errors.js.map new file mode 100644 index 0000000000..ce45f83276 --- /dev/null +++ b/dist/permissions/errors.js.map @@ -0,0 +1 @@ +{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/permissions/errors.ts"],"names":[],"mappings":";;;AAAA,mDAAyE;AAMzE;;;;;GAKG;AACH,SAAgB,YAAY,CAAC,IAAqB;IAChD,OAAO,0BAAS,CAAC,QAAQ,CAAC,YAAY,CAAC;QACrC,OAAO,EACL,qKAAqK;QACvK,IAAI,EAAE,IAAI,CAAC,IAAI;KAChB,CAAC,CAAC;AACL,CAAC;AAND,oCAMC;AAED;;;;;;GAMG;AACH,SAAgB,cAAc,CAAC,MAAc,EAAE,IAAc;IAC3D,MAAM,OAAO,GAAG,eAAe,MAAM,sCAAsC,CAAC;IAE5E,MAAM,IAAI,GAAuD,EAAE,OAAO,EAAE,CAAC;IAC7E,IAAI,IAAI,KAAK,SAAS,EAAE;QACtB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;KAClB;IACD,OAAO,0BAAS,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;AAC5C,CAAC;AARD,wCAQC;AAOD;;;;;GAKG;AACH,SAAgB,aAAa,CAAC,IAAsB;IAClD,OAAO,0BAAS,CAAC,GAAG,CAAC,aAAa,CAAC;QACjC,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,OAAO,EAAE,IAAI,CAAC,OAAO;KACtB,CAAC,CAAC;AACL,CAAC;AALD,sCAKC;AAED;;;;;GAKG;AACH,SAAgB,mBAAmB,CACjC,IAAW;IAEX,OAAO,0BAAS,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;AAC1D,CAAC;AAJD,kDAIC;AAED;;;;;;GAMG;AACH,SAAgB,aAAa,CAC3B,OAAe,EACf,IAAW;IAEX,OAAO,0BAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;AACnD,CAAC;AALD,sCAKC;AAED,MAAa,6BAA8B,SAAQ,KAAK;IACtD,YAAY,MAAe;QACzB,KAAK,CACH,gCACE,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,MAC/C,GAAG,CACJ,CAAC;IACJ,CAAC;CACF;AARD,sEAQC;AAED,MAAa,wBAAyB,SAAQ,KAAK;IACjD,YAAY,MAAc;QACxB,KAAK,CAAC,0BAA0B,MAAM,uBAAuB,CAAC,CAAC;IACjE,CAAC;CACF;AAJD,4DAIC;AAED,MAAa,8BAA+B,SAAQ,KAAK;IAOvD,YACE,MAAc,EACd,MAAc,EACd,kBAA2C;QAE3C,KAAK,CACH,2CAA2C,MAAM,iBAAiB,MAAM,IAAI,CAC7E,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IACrD,CAAC;CACF;AAjBD,wEAiBC;AACD,MAAa,2BAA4B,SAAQ,KAAK;IACpD,YAAY,MAAc,EAAE,MAAc;QACxC,KAAK,CAAC,YAAY,MAAM,4BAA4B,MAAM,IAAI,CAAC,CAAC;IAClE,CAAC;CACF;AAJD,kEAIC;AAED,MAAa,oCAAqC,SAAQ,KAAK;IAG7D,YAAY,MAAc,EAAE,MAAe;QACzC,KAAK,CAAC,YAAY,MAAM,4BAA4B,MAAM,IAAI,CAAC,CAAC;QAChE,IAAI,MAAM,EAAE;YACV,IAAI,CAAC,IAAI,GAAG,EAAE,MAAM,EAAE,CAAC;SACxB;IACH,CAAC;CACF;AATD,oFASC;AAED,MAAa,2BAA4B,SAAQ,KAAK;IAWpD,YAAY,UAAkB,EAAE,MAAe,EAAE,MAAe;QAC9D,KAAK,CAAC,8BAA8B,UAAU,GAAG,CAAC,CAAC;QACnD,IAAI,CAAC,IAAI,GAAG,EAAE,UAAU,EAAE,CAAC;QAC3B,IAAI,MAAM,KAAK,SAAS,EAAE;YACxB,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;SAC3B;QAED,IAAI,MAAM,KAAK,SAAS,EAAE;YACxB,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;SAC3B;IACH,CAAC;CACF;AAtBD,kEAsBC;AAED,MAAa,2BAA4B,SAAQ,KAAK;IAGpD,YAAY,MAAc,EAAE,MAAc,EAAE,eAAwB;QAClE,KAAK,CACH,6CAA6C,MAAM,iBAAiB,MAAM,0DAA0D,CACrI,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IAClD,CAAC;CACF;AATD,kEASC;AAED,MAAa,uBAAwB,SAAQ,KAAK;IAChD,YAAY,MAAc,EAAE,MAAc,EAAE,UAAkB;QAC5D,KAAK,CACH,mBAAmB,MAAM,iBAAiB,MAAM,4BAA4B,UAAU,IAAI,CAC3F,CAAC;IACJ,CAAC;CACF;AAND,0DAMC;AAED,MAAa,wBAAyB,SAAQ,KAAK;IACjD,YAAY,MAAc,EAAE,MAAc,EAAE,UAAkB;QAC5D,KAAK,CACH,mBAAmB,MAAM,iBAAiB,MAAM,mCAAmC,UAAU,IAAI,CAClG,CAAC;IACJ,CAAC;CACF;AAND,4DAMC;AAED,MAAa,kBAAmB,SAAQ,iCAAyB;IAG/D,YAAY,cAAuB,EAAE,MAAc,EAAE,MAAc;QACjE,KAAK,CACH,2BAAU,CAAC,GAAG,CAAC,aAAa,EAC5B,gDAAgD,EAChD,EAAE,cAAc,EAAE,CACnB,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACjC,CAAC;CACF;AAXD,gDAWC;AAED,MAAa,sBAAuB,SAAQ,KAAK;IAO/C,YAAY,MAA+B,EAAE,MAAc,EAAE,MAAc;QACzE,KAAK,CAAC,4CAA4C,OAAO,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC;QACzE,IAAI,CAAC,IAAI,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACzC,CAAC;CACF;AAXD,wDAWC;AAED,MAAa,uBAAwB,SAAQ,KAAK;IAOhD,YAAY,MAA+B,EAAE,MAAc,EAAE,MAAc;QACzE,KAAK,CAAC,kCAAkC,CAAC,CAAC;QAC1C,IAAI,CAAC,IAAI,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACzC,CAAC;CACF;AAXD,0DAWC;AAED,MAAa,sBAAuB,SAAQ,KAAK;IAO/C,YAAY,MAA+B,EAAE,MAAc,EAAE,MAAc;QACzE,KAAK,CAAC,iCAAiC,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACzC,CAAC;CACF;AAXD,wDAWC;AAED,MAAa,wBAAyB,SAAQ,KAAK;IAOjD,YAAY,MAA+B,EAAE,MAAc,EAAE,MAAc;QACzE,KAAK,CACH,4CAA4C,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAC1E,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACzC,CAAC;CACF;AAbD,4DAaC;AAED,MAAa,oBAAqB,SAAQ,KAAK;IAO7C,YAAY,UAAkB,EAAE,MAAc,EAAE,UAAkB;QAChE,KAAK,CACH,2BAA2B,UAAU,mCAAmC,UAAU,IAAI,CACvF,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IACzD,CAAC;CACF;AAbD,oDAaC;AAED,MAAa,oBAAqB,SAAQ,KAAK;IAO7C,YAAY,UAAkB,EAAE,MAAc,EAAE,UAAkB;QAChE,KAAK,CACH,2BAA2B,UAAU,wCAAwC,UAAU,IAAI,CAC5F,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IACzD,CAAC;CACF;AAbD,oDAaC;AAED,MAAa,+BAAgC,SAAQ,KAAK;IACxD,YAAY,EAAU;QACpB,KAAK,CAAC,gCAAgC,EAAE,cAAc,CAAC,CAAC;IAC1D,CAAC;CACF;AAJD,0EAIC","sourcesContent":["import { errorCodes, ethErrors, EthereumRpcError } from 'eth-rpc-errors';\n\ntype UnauthorizedArg = {\n data?: Record;\n};\n\n/**\n * Utility function for building an \"unauthorized\" error.\n *\n * @param opts - Optional arguments that add extra context\n * @returns The built error\n */\nexport function unauthorized(opts: UnauthorizedArg) {\n return ethErrors.provider.unauthorized({\n message:\n 'Unauthorized to perform action. Try requesting the required permission(s) first. For more information, see: https://docs.metamask.io/guide/rpc-api.html#permissions',\n data: opts.data,\n });\n}\n\n/**\n * Utility function for building a \"method not found\" error.\n *\n * @param method - The method in question.\n * @param data - Optional data for context.\n * @returns The built error\n */\nexport function methodNotFound(method: string, data?: unknown) {\n const message = `The method \"${method}\" does not exist / is not available.`;\n\n const opts: Parameters[0] = { message };\n if (data !== undefined) {\n opts.data = data;\n }\n return ethErrors.rpc.methodNotFound(opts);\n}\n\ntype InvalidParamsArg = {\n message?: string;\n data?: unknown;\n};\n\n/**\n * Utility function for building an \"invalid params\" error.\n *\n * @param opts - Optional arguments that add extra context\n * @returns The built error\n */\nexport function invalidParams(opts: InvalidParamsArg) {\n return ethErrors.rpc.invalidParams({\n data: opts.data,\n message: opts.message,\n });\n}\n\n/**\n * Utility function for building an \"user rejected request\" error.\n *\n * @param data - Optional data to add extra context\n * @returns The built error\n */\nexport function userRejectedRequest>(\n data?: Data,\n): EthereumRpcError {\n return ethErrors.provider.userRejectedRequest({ data });\n}\n\n/**\n * Utility function for building an internal error.\n *\n * @param message - The error message\n * @param data - Optional data to add extra context\n * @returns The built error\n */\nexport function internalError>(\n message: string,\n data?: Data,\n): EthereumRpcError {\n return ethErrors.rpc.internal({ message, data });\n}\n\nexport class InvalidSubjectIdentifierError extends Error {\n constructor(origin: unknown) {\n super(\n `Invalid subject identifier: \"${\n typeof origin === 'string' ? origin : typeof origin\n }\"`,\n );\n }\n}\n\nexport class UnrecognizedSubjectError extends Error {\n constructor(origin: string) {\n super(`Unrecognized subject: \"${origin}\" has no permissions.`);\n }\n}\n\nexport class InvalidApprovedPermissionError extends Error {\n public data: {\n origin: string;\n target: string;\n approvedPermission: Record;\n };\n\n constructor(\n origin: string,\n target: string,\n approvedPermission: Record,\n ) {\n super(\n `Invalid approved permission for origin \"${origin}\" and target \"${target}\".`,\n );\n this.data = { origin, target, approvedPermission };\n }\n}\nexport class PermissionDoesNotExistError extends Error {\n constructor(origin: string, target: string) {\n super(`Subject \"${origin}\" has no permission for \"${target}\".`);\n }\n}\n\nexport class EndowmentPermissionDoesNotExistError extends Error {\n public data?: { origin: string };\n\n constructor(target: string, origin?: string) {\n super(`Subject \"${origin}\" has no permission for \"${target}\".`);\n if (origin) {\n this.data = { origin };\n }\n }\n}\n\nexport class UnrecognizedCaveatTypeError extends Error {\n public data: {\n caveatType: string;\n origin?: string;\n target?: string;\n };\n\n constructor(caveatType: string);\n\n constructor(caveatType: string, origin: string, target: string);\n\n constructor(caveatType: string, origin?: string, target?: string) {\n super(`Unrecognized caveat type: \"${caveatType}\"`);\n this.data = { caveatType };\n if (origin !== undefined) {\n this.data.origin = origin;\n }\n\n if (target !== undefined) {\n this.data.target = target;\n }\n }\n}\n\nexport class InvalidCaveatsPropertyError extends Error {\n public data: { origin: string; target: string; caveatsProperty: unknown };\n\n constructor(origin: string, target: string, caveatsProperty: unknown) {\n super(\n `The \"caveats\" property of permission for \"${target}\" of subject \"${origin}\" is invalid. It must be a non-empty array if specified.`,\n );\n this.data = { origin, target, caveatsProperty };\n }\n}\n\nexport class CaveatDoesNotExistError extends Error {\n constructor(origin: string, target: string, caveatType: string) {\n super(\n `Permission for \"${target}\" of subject \"${origin}\" has no caveat of type \"${caveatType}\".`,\n );\n }\n}\n\nexport class CaveatAlreadyExistsError extends Error {\n constructor(origin: string, target: string, caveatType: string) {\n super(\n `Permission for \"${target}\" of subject \"${origin}\" already has a caveat of type \"${caveatType}\".`,\n );\n }\n}\n\nexport class InvalidCaveatError extends EthereumRpcError {\n public override data: { origin: string; target: string };\n\n constructor(receivedCaveat: unknown, origin: string, target: string) {\n super(\n errorCodes.rpc.invalidParams,\n `Invalid caveat. Caveats must be plain objects.`,\n { receivedCaveat },\n );\n this.data = { origin, target };\n }\n}\n\nexport class InvalidCaveatTypeError extends Error {\n public data: {\n caveat: Record;\n origin: string;\n target: string;\n };\n\n constructor(caveat: Record, origin: string, target: string) {\n super(`Caveat types must be strings. Received: \"${typeof caveat.type}\"`);\n this.data = { caveat, origin, target };\n }\n}\n\nexport class CaveatMissingValueError extends Error {\n public data: {\n caveat: Record;\n origin: string;\n target: string;\n };\n\n constructor(caveat: Record, origin: string, target: string) {\n super(`Caveat is missing \"value\" field.`);\n this.data = { caveat, origin, target };\n }\n}\n\nexport class CaveatInvalidJsonError extends Error {\n public data: {\n caveat: Record;\n origin: string;\n target: string;\n };\n\n constructor(caveat: Record, origin: string, target: string) {\n super(`Caveat \"value\" is invalid JSON.`);\n this.data = { caveat, origin, target };\n }\n}\n\nexport class InvalidCaveatFieldsError extends Error {\n public data: {\n caveat: Record;\n origin: string;\n target: string;\n };\n\n constructor(caveat: Record, origin: string, target: string) {\n super(\n `Caveat has unexpected number of fields: \"${Object.keys(caveat).length}\"`,\n );\n this.data = { caveat, origin, target };\n }\n}\n\nexport class ForbiddenCaveatError extends Error {\n public data: {\n caveatType: string;\n origin: string;\n target: string;\n };\n\n constructor(caveatType: string, origin: string, targetName: string) {\n super(\n `Permissions for target \"${targetName}\" may not have caveats of type \"${caveatType}\".`,\n );\n this.data = { caveatType, origin, target: targetName };\n }\n}\n\nexport class DuplicateCaveatError extends Error {\n public data: {\n caveatType: string;\n origin: string;\n target: string;\n };\n\n constructor(caveatType: string, origin: string, targetName: string) {\n super(\n `Permissions for target \"${targetName}\" contains multiple caveats of type \"${caveatType}\".`,\n );\n this.data = { caveatType, origin, target: targetName };\n }\n}\n\nexport class PermissionsRequestNotFoundError extends Error {\n constructor(id: string) {\n super(`Permissions request with id \"${id}\" not found.`);\n }\n}\n"]} \ No newline at end of file diff --git a/dist/permissions/index.d.ts b/dist/permissions/index.d.ts new file mode 100644 index 0000000000..dced5a9ac3 --- /dev/null +++ b/dist/permissions/index.d.ts @@ -0,0 +1,5 @@ +export * from './Caveat'; +export * from './Permission'; +export * from './PermissionController'; +export * from './utils'; +export * as permissionRpcMethods from './rpc-methods'; diff --git a/dist/permissions/index.js b/dist/permissions/index.js new file mode 100644 index 0000000000..53edaa8197 --- /dev/null +++ b/dist/permissions/index.js @@ -0,0 +1,35 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.permissionRpcMethods = void 0; +__exportStar(require("./Caveat"), exports); +__exportStar(require("./Permission"), exports); +__exportStar(require("./PermissionController"), exports); +__exportStar(require("./utils"), exports); +exports.permissionRpcMethods = __importStar(require("./rpc-methods")); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/permissions/index.js.map b/dist/permissions/index.js.map new file mode 100644 index 0000000000..d3ce12bea1 --- /dev/null +++ b/dist/permissions/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/permissions/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,+CAA6B;AAC7B,yDAAuC;AACvC,0CAAwB;AACxB,sEAAsD","sourcesContent":["export * from './Caveat';\nexport * from './Permission';\nexport * from './PermissionController';\nexport * from './utils';\nexport * as permissionRpcMethods from './rpc-methods';\n"]} \ No newline at end of file diff --git a/dist/permissions/permission-middleware.d.ts b/dist/permissions/permission-middleware.d.ts new file mode 100644 index 0000000000..66bb51f09a --- /dev/null +++ b/dist/permissions/permission-middleware.d.ts @@ -0,0 +1,32 @@ +import type { Json } from '@metamask/types'; +import { JsonRpcMiddleware } from 'json-rpc-engine'; +import { GenericPermissionController, PermissionSubjectMetadata, RestrictedMethodParameters } from '.'; +declare type PermissionMiddlewareFactoryOptions = { + executeRestrictedMethod: GenericPermissionController['_executeRestrictedMethod']; + getRestrictedMethod: GenericPermissionController['getRestrictedMethod']; + isUnrestrictedMethod: (method: string) => boolean; +}; +/** + * Creates a permission middleware function factory. Intended for internal use + * in the {@link PermissionController}. Like any {@link JsonRpcEngine} + * middleware, each middleware will only receive requests from a particular + * subject / origin. However, each middleware also requires access to some + * `PermissionController` internals, which is why this "factory factory" exists. + * + * The middlewares returned by the factory will pass through requests for + * unrestricted methods, and attempt to execute restricted methods. If a method + * is neither restricted nor unrestricted, a "method not found" error will be + * returned. + * If a method is restricted, the middleware will first attempt to retrieve the + * subject's permission for that method. If the permission is found, the method + * will be executed. Otherwise, an "unauthorized" error will be returned. + * + * @param options - Options bag. + * @param options.executeRestrictedMethod - {@link PermissionController._executeRestrictedMethod}. + * @param options.getRestrictedMethod - {@link PermissionController.getRestrictedMethod}. + * @param options.isUnrestrictedMethod - A function that checks whether a + * particular method is unrestricted. + * @returns A permission middleware factory function. + */ +export declare function getPermissionMiddlewareFactory({ executeRestrictedMethod, getRestrictedMethod, isUnrestrictedMethod, }: PermissionMiddlewareFactoryOptions): (subject: PermissionSubjectMetadata) => JsonRpcMiddleware; +export {}; diff --git a/dist/permissions/permission-middleware.js b/dist/permissions/permission-middleware.js new file mode 100644 index 0000000000..402983e774 --- /dev/null +++ b/dist/permissions/permission-middleware.js @@ -0,0 +1,64 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getPermissionMiddlewareFactory = void 0; +const json_rpc_engine_1 = require("json-rpc-engine"); +const errors_1 = require("./errors"); +/** + * Creates a permission middleware function factory. Intended for internal use + * in the {@link PermissionController}. Like any {@link JsonRpcEngine} + * middleware, each middleware will only receive requests from a particular + * subject / origin. However, each middleware also requires access to some + * `PermissionController` internals, which is why this "factory factory" exists. + * + * The middlewares returned by the factory will pass through requests for + * unrestricted methods, and attempt to execute restricted methods. If a method + * is neither restricted nor unrestricted, a "method not found" error will be + * returned. + * If a method is restricted, the middleware will first attempt to retrieve the + * subject's permission for that method. If the permission is found, the method + * will be executed. Otherwise, an "unauthorized" error will be returned. + * + * @param options - Options bag. + * @param options.executeRestrictedMethod - {@link PermissionController._executeRestrictedMethod}. + * @param options.getRestrictedMethod - {@link PermissionController.getRestrictedMethod}. + * @param options.isUnrestrictedMethod - A function that checks whether a + * particular method is unrestricted. + * @returns A permission middleware factory function. + */ +function getPermissionMiddlewareFactory({ executeRestrictedMethod, getRestrictedMethod, isUnrestrictedMethod, }) { + return function createPermissionMiddleware(subject) { + const { origin } = subject; + if (typeof origin !== 'string' || !origin) { + throw new Error('The subject "origin" must be a non-empty string.'); + } + const permissionsMiddleware = (req, res, next) => __awaiter(this, void 0, void 0, function* () { + const { method, params } = req; + // Skip registered unrestricted methods. + if (isUnrestrictedMethod(method)) { + return next(); + } + // This will throw if no restricted method implementation is found. + const methodImplementation = getRestrictedMethod(method, origin); + // This will throw if the permission does not exist. + const result = yield executeRestrictedMethod(methodImplementation, subject, method, params); + if (result === undefined) { + res.error = (0, errors_1.internalError)(`Request for method "${req.method}" returned undefined result.`, { request: req }); + return undefined; + } + res.result = result; + return undefined; + }); + return (0, json_rpc_engine_1.createAsyncMiddleware)(permissionsMiddleware); + }; +} +exports.getPermissionMiddlewareFactory = getPermissionMiddlewareFactory; +//# sourceMappingURL=permission-middleware.js.map \ No newline at end of file diff --git a/dist/permissions/permission-middleware.js.map b/dist/permissions/permission-middleware.js.map new file mode 100644 index 0000000000..ec6364b68c --- /dev/null +++ b/dist/permissions/permission-middleware.js.map @@ -0,0 +1 @@ +{"version":3,"file":"permission-middleware.js","sourceRoot":"","sources":["../../src/permissions/permission-middleware.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,qDAMyB;AAGzB,qCAAyC;AAezC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,SAAgB,8BAA8B,CAAC,EAC7C,uBAAuB,EACvB,mBAAmB,EACnB,oBAAoB,GACe;IACnC,OAAO,SAAS,0BAA0B,CACxC,OAAkC;QAElC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAC3B,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,MAAM,EAAE;YACzC,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;SACrE;QAED,MAAM,qBAAqB,GAAG,CAC5B,GAA+C,EAC/C,GAAiC,EACjC,IAAoC,EACrB,EAAE;YACjB,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;YAE/B,wCAAwC;YACxC,IAAI,oBAAoB,CAAC,MAAM,CAAC,EAAE;gBAChC,OAAO,IAAI,EAAE,CAAC;aACf;YAED,mEAAmE;YACnE,MAAM,oBAAoB,GAAG,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAEjE,oDAAoD;YACpD,MAAM,MAAM,GAAG,MAAM,uBAAuB,CAC1C,oBAAoB,EACpB,OAAO,EACP,MAAM,EACN,MAAM,CACP,CAAC;YAEF,IAAI,MAAM,KAAK,SAAS,EAAE;gBACxB,GAAG,CAAC,KAAK,GAAG,IAAA,sBAAa,EACvB,uBAAuB,GAAG,CAAC,MAAM,8BAA8B,EAC/D,EAAE,OAAO,EAAE,GAAG,EAAE,CACjB,CAAC;gBACF,OAAO,SAAS,CAAC;aAClB;YAED,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC;YACpB,OAAO,SAAS,CAAC;QACnB,CAAC,CAAA,CAAC;QAEF,OAAO,IAAA,uCAAqB,EAAC,qBAAqB,CAAC,CAAC;IACtD,CAAC,CAAC;AACJ,CAAC;AAlDD,wEAkDC","sourcesContent":["import type { Json } from '@metamask/types';\nimport {\n JsonRpcMiddleware,\n AsyncJsonRpcEngineNextCallback,\n createAsyncMiddleware,\n PendingJsonRpcResponse,\n JsonRpcRequest,\n} from 'json-rpc-engine';\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nimport type { JsonRpcEngine } from 'json-rpc-engine';\nimport { internalError } from './errors';\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nimport type { PermissionController } from './PermissionController';\nimport {\n GenericPermissionController,\n PermissionSubjectMetadata,\n RestrictedMethodParameters,\n} from '.';\n\ntype PermissionMiddlewareFactoryOptions = {\n executeRestrictedMethod: GenericPermissionController['_executeRestrictedMethod'];\n getRestrictedMethod: GenericPermissionController['getRestrictedMethod'];\n isUnrestrictedMethod: (method: string) => boolean;\n};\n\n/**\n * Creates a permission middleware function factory. Intended for internal use\n * in the {@link PermissionController}. Like any {@link JsonRpcEngine}\n * middleware, each middleware will only receive requests from a particular\n * subject / origin. However, each middleware also requires access to some\n * `PermissionController` internals, which is why this \"factory factory\" exists.\n *\n * The middlewares returned by the factory will pass through requests for\n * unrestricted methods, and attempt to execute restricted methods. If a method\n * is neither restricted nor unrestricted, a \"method not found\" error will be\n * returned.\n * If a method is restricted, the middleware will first attempt to retrieve the\n * subject's permission for that method. If the permission is found, the method\n * will be executed. Otherwise, an \"unauthorized\" error will be returned.\n *\n * @param options - Options bag.\n * @param options.executeRestrictedMethod - {@link PermissionController._executeRestrictedMethod}.\n * @param options.getRestrictedMethod - {@link PermissionController.getRestrictedMethod}.\n * @param options.isUnrestrictedMethod - A function that checks whether a\n * particular method is unrestricted.\n * @returns A permission middleware factory function.\n */\nexport function getPermissionMiddlewareFactory({\n executeRestrictedMethod,\n getRestrictedMethod,\n isUnrestrictedMethod,\n}: PermissionMiddlewareFactoryOptions) {\n return function createPermissionMiddleware(\n subject: PermissionSubjectMetadata,\n ): JsonRpcMiddleware {\n const { origin } = subject;\n if (typeof origin !== 'string' || !origin) {\n throw new Error('The subject \"origin\" must be a non-empty string.');\n }\n\n const permissionsMiddleware = async (\n req: JsonRpcRequest,\n res: PendingJsonRpcResponse,\n next: AsyncJsonRpcEngineNextCallback,\n ): Promise => {\n const { method, params } = req;\n\n // Skip registered unrestricted methods.\n if (isUnrestrictedMethod(method)) {\n return next();\n }\n\n // This will throw if no restricted method implementation is found.\n const methodImplementation = getRestrictedMethod(method, origin);\n\n // This will throw if the permission does not exist.\n const result = await executeRestrictedMethod(\n methodImplementation,\n subject,\n method,\n params,\n );\n\n if (result === undefined) {\n res.error = internalError(\n `Request for method \"${req.method}\" returned undefined result.`,\n { request: req },\n );\n return undefined;\n }\n\n res.result = result;\n return undefined;\n };\n\n return createAsyncMiddleware(permissionsMiddleware);\n };\n}\n"]} \ No newline at end of file diff --git a/dist/permissions/rpc-methods/getPermissions.d.ts b/dist/permissions/rpc-methods/getPermissions.d.ts new file mode 100644 index 0000000000..9380ecb422 --- /dev/null +++ b/dist/permissions/rpc-methods/getPermissions.d.ts @@ -0,0 +1,7 @@ +import type { PermittedHandlerExport } from '@metamask/types'; +import type { PermissionConstraint } from '../Permission'; +import type { SubjectPermissions } from '../PermissionController'; +export declare const getPermissionsHandler: PermittedHandlerExport; +export declare type GetPermissionsHooks = { + getPermissionsForOrigin: () => SubjectPermissions; +}; diff --git a/dist/permissions/rpc-methods/getPermissions.js b/dist/permissions/rpc-methods/getPermissions.js new file mode 100644 index 0000000000..74fb13017a --- /dev/null +++ b/dist/permissions/rpc-methods/getPermissions.js @@ -0,0 +1,38 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getPermissionsHandler = void 0; +const utils_1 = require("../utils"); +exports.getPermissionsHandler = { + methodNames: [utils_1.MethodNames.getPermissions], + implementation: getPermissionsImplementation, + hookNames: { + getPermissionsForOrigin: true, + }, +}; +/** + * Get Permissions implementation to be used in JsonRpcEngine middleware. + * + * @param _req - The JsonRpcEngine request - unused + * @param res - The JsonRpcEngine result object + * @param _next - JsonRpcEngine next() callback - unused + * @param end - JsonRpcEngine end() callback + * @param options - Method hooks passed to the method implementation + * @param options.getPermissionsForOrigin - The specific method hook needed for this method implementation + * @returns A promise that resolves to nothing + */ +function getPermissionsImplementation(_req, res, _next, end, { getPermissionsForOrigin }) { + return __awaiter(this, void 0, void 0, function* () { + res.result = Object.values(getPermissionsForOrigin() || {}); + return end(); + }); +} +//# sourceMappingURL=getPermissions.js.map \ No newline at end of file diff --git a/dist/permissions/rpc-methods/getPermissions.js.map b/dist/permissions/rpc-methods/getPermissions.js.map new file mode 100644 index 0000000000..f3fde5bf04 --- /dev/null +++ b/dist/permissions/rpc-methods/getPermissions.js.map @@ -0,0 +1 @@ +{"version":3,"file":"getPermissions.js","sourceRoot":"","sources":["../../../src/permissions/rpc-methods/getPermissions.ts"],"names":[],"mappings":";;;;;;;;;;;;AAKA,oCAAuC;AAK1B,QAAA,qBAAqB,GAI9B;IACF,WAAW,EAAE,CAAC,mBAAW,CAAC,cAAc,CAAC;IACzC,cAAc,EAAE,4BAA4B;IAC5C,SAAS,EAAE;QACT,uBAAuB,EAAE,IAAI;KAC9B;CACF,CAAC;AAOF;;;;;;;;;;GAUG;AACH,SAAe,4BAA4B,CACzC,IAAa,EACb,GAAmD,EACnD,KAAc,EACd,GAA6B,EAC7B,EAAE,uBAAuB,EAAuB;;QAEhD,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,uBAAuB,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5D,OAAO,GAAG,EAAE,CAAC;IACf,CAAC;CAAA","sourcesContent":["import type {\n JsonRpcEngineEndCallback,\n PendingJsonRpcResponse,\n PermittedHandlerExport,\n} from '@metamask/types';\nimport { MethodNames } from '../utils';\n\nimport type { PermissionConstraint } from '../Permission';\nimport type { SubjectPermissions } from '../PermissionController';\n\nexport const getPermissionsHandler: PermittedHandlerExport<\n GetPermissionsHooks,\n void,\n PermissionConstraint[]\n> = {\n methodNames: [MethodNames.getPermissions],\n implementation: getPermissionsImplementation,\n hookNames: {\n getPermissionsForOrigin: true,\n },\n};\n\nexport type GetPermissionsHooks = {\n // This must be bound to the requesting origin.\n getPermissionsForOrigin: () => SubjectPermissions;\n};\n\n/**\n * Get Permissions implementation to be used in JsonRpcEngine middleware.\n *\n * @param _req - The JsonRpcEngine request - unused\n * @param res - The JsonRpcEngine result object\n * @param _next - JsonRpcEngine next() callback - unused\n * @param end - JsonRpcEngine end() callback\n * @param options - Method hooks passed to the method implementation\n * @param options.getPermissionsForOrigin - The specific method hook needed for this method implementation\n * @returns A promise that resolves to nothing\n */\nasync function getPermissionsImplementation(\n _req: unknown,\n res: PendingJsonRpcResponse,\n _next: unknown,\n end: JsonRpcEngineEndCallback,\n { getPermissionsForOrigin }: GetPermissionsHooks,\n): Promise {\n res.result = Object.values(getPermissionsForOrigin() || {});\n return end();\n}\n"]} \ No newline at end of file diff --git a/dist/permissions/rpc-methods/index.d.ts b/dist/permissions/rpc-methods/index.d.ts new file mode 100644 index 0000000000..940377921b --- /dev/null +++ b/dist/permissions/rpc-methods/index.d.ts @@ -0,0 +1,4 @@ +import { RequestPermissionsHooks } from './requestPermissions'; +import { GetPermissionsHooks } from './getPermissions'; +export declare type PermittedRpcMethodHooks = RequestPermissionsHooks & GetPermissionsHooks; +export declare const handlers: (import("@metamask/types").PermittedHandlerExport | import("@metamask/types").PermittedHandlerExport)[]; diff --git a/dist/permissions/rpc-methods/index.js b/dist/permissions/rpc-methods/index.js new file mode 100644 index 0000000000..7a1dbd3f51 --- /dev/null +++ b/dist/permissions/rpc-methods/index.js @@ -0,0 +1,7 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.handlers = void 0; +const requestPermissions_1 = require("./requestPermissions"); +const getPermissions_1 = require("./getPermissions"); +exports.handlers = [requestPermissions_1.requestPermissionsHandler, getPermissions_1.getPermissionsHandler]; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/permissions/rpc-methods/index.js.map b/dist/permissions/rpc-methods/index.js.map new file mode 100644 index 0000000000..b4620208b1 --- /dev/null +++ b/dist/permissions/rpc-methods/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/permissions/rpc-methods/index.ts"],"names":[],"mappings":";;;AAAA,6DAG8B;AAC9B,qDAA8E;AAKjE,QAAA,QAAQ,GAAG,CAAC,8CAAyB,EAAE,sCAAqB,CAAC,CAAC","sourcesContent":["import {\n requestPermissionsHandler,\n RequestPermissionsHooks,\n} from './requestPermissions';\nimport { getPermissionsHandler, GetPermissionsHooks } from './getPermissions';\n\nexport type PermittedRpcMethodHooks = RequestPermissionsHooks &\n GetPermissionsHooks;\n\nexport const handlers = [requestPermissionsHandler, getPermissionsHandler];\n"]} \ No newline at end of file diff --git a/dist/permissions/rpc-methods/requestPermissions.d.ts b/dist/permissions/rpc-methods/requestPermissions.d.ts new file mode 100644 index 0000000000..9707420e8f --- /dev/null +++ b/dist/permissions/rpc-methods/requestPermissions.d.ts @@ -0,0 +1,16 @@ +import type { PermittedHandlerExport } from '@metamask/types'; +import type { PermissionConstraint, RequestedPermissions } from '../Permission'; +export declare const requestPermissionsHandler: PermittedHandlerExport; +declare type RequestPermissions = (requestedPermissions: RequestedPermissions, id: string) => Promise<[ + Record, + { + id: string; + origin: string; + } +]>; +export declare type RequestPermissionsHooks = { + requestPermissionsForOrigin: RequestPermissions; +}; +export {}; diff --git a/dist/permissions/rpc-methods/requestPermissions.js b/dist/permissions/rpc-methods/requestPermissions.js new file mode 100644 index 0000000000..e8648fcbf6 --- /dev/null +++ b/dist/permissions/rpc-methods/requestPermissions.js @@ -0,0 +1,55 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.requestPermissionsHandler = void 0; +const eth_rpc_errors_1 = require("eth-rpc-errors"); +const utils_1 = require("../utils"); +const errors_1 = require("../errors"); +const util_1 = require("../../util"); +exports.requestPermissionsHandler = { + methodNames: [utils_1.MethodNames.requestPermissions], + implementation: requestPermissionsImplementation, + hookNames: { + requestPermissionsForOrigin: true, + }, +}; +/** + * Request Permissions implementation to be used in JsonRpcEngine middleware. + * + * @param req - The JsonRpcEngine request + * @param res - The JsonRpcEngine result object + * @param _next - JsonRpcEngine next() callback - unused + * @param end - JsonRpcEngine end() callback + * @param options - Method hooks passed to the method implementation + * @param options.requestPermissionsForOrigin - The specific method hook needed for this method implementation + * @returns A promise that resolves to nothing + */ +function requestPermissionsImplementation(req, res, _next, end, { requestPermissionsForOrigin }) { + return __awaiter(this, void 0, void 0, function* () { + const { id, params } = req; + if ((typeof id !== 'number' && typeof id !== 'string') || + (typeof id === 'string' && !id)) { + return end(eth_rpc_errors_1.ethErrors.rpc.invalidRequest({ + message: 'Invalid request: Must specify a valid id.', + data: { request: req }, + })); + } + if (!Array.isArray(params) || !(0, util_1.isPlainObject)(params[0])) { + return end((0, errors_1.invalidParams)({ data: { request: req } })); + } + const [requestedPermissions] = params; + const [grantedPermissions] = yield requestPermissionsForOrigin(requestedPermissions, String(id)); + // `wallet_requestPermission` is specified to return an array. + res.result = Object.values(grantedPermissions); + return end(); + }); +} +//# sourceMappingURL=requestPermissions.js.map \ No newline at end of file diff --git a/dist/permissions/rpc-methods/requestPermissions.js.map b/dist/permissions/rpc-methods/requestPermissions.js.map new file mode 100644 index 0000000000..97bad458d6 --- /dev/null +++ b/dist/permissions/rpc-methods/requestPermissions.js.map @@ -0,0 +1 @@ +{"version":3,"file":"requestPermissions.js","sourceRoot":"","sources":["../../../src/permissions/rpc-methods/requestPermissions.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,mDAA2C;AAO3C,oCAAuC;AAEvC,sCAA0C;AAE1C,qCAA2C;AAE9B,QAAA,yBAAyB,GAIlC;IACF,WAAW,EAAE,CAAC,mBAAW,CAAC,kBAAkB,CAAC;IAC7C,cAAc,EAAE,gCAAgC;IAChD,SAAS,EAAE;QACT,2BAA2B,EAAE,IAAI;KAClC;CACF,CAAC;AAaF;;;;;;;;;;GAUG;AACH,SAAe,gCAAgC,CAC7C,GAA2C,EAC3C,GAAmD,EACnD,KAAc,EACd,GAA6B,EAC7B,EAAE,2BAA2B,EAA2B;;QAExD,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;QAE3B,IACE,CAAC,OAAO,EAAE,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ,CAAC;YAClD,CAAC,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,EAAE,CAAC,EAC/B;YACA,OAAO,GAAG,CACR,0BAAS,CAAC,GAAG,CAAC,cAAc,CAAC;gBAC3B,OAAO,EAAE,2CAA2C;gBACpD,IAAI,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE;aACvB,CAAC,CACH,CAAC;SACH;QAED,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAA,oBAAa,EAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE;YACvD,OAAO,GAAG,CAAC,IAAA,sBAAa,EAAC,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;SACvD;QAED,MAAM,CAAC,oBAAoB,CAAC,GAAG,MAAM,CAAC;QACtC,MAAM,CAAC,kBAAkB,CAAC,GAAG,MAAM,2BAA2B,CAC5D,oBAAoB,EACpB,MAAM,CAAC,EAAE,CAAC,CACX,CAAC;QAEF,8DAA8D;QAC9D,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC;QAC/C,OAAO,GAAG,EAAE,CAAC;IACf,CAAC;CAAA","sourcesContent":["import { ethErrors } from 'eth-rpc-errors';\nimport type {\n JsonRpcEngineEndCallback,\n JsonRpcRequest,\n PendingJsonRpcResponse,\n PermittedHandlerExport,\n} from '@metamask/types';\nimport { MethodNames } from '../utils';\n\nimport { invalidParams } from '../errors';\nimport type { PermissionConstraint, RequestedPermissions } from '../Permission';\nimport { isPlainObject } from '../../util';\n\nexport const requestPermissionsHandler: PermittedHandlerExport<\n RequestPermissionsHooks,\n [RequestedPermissions],\n PermissionConstraint[]\n> = {\n methodNames: [MethodNames.requestPermissions],\n implementation: requestPermissionsImplementation,\n hookNames: {\n requestPermissionsForOrigin: true,\n },\n};\n\ntype RequestPermissions = (\n requestedPermissions: RequestedPermissions,\n id: string,\n) => Promise<\n [Record, { id: string; origin: string }]\n>;\n\nexport type RequestPermissionsHooks = {\n requestPermissionsForOrigin: RequestPermissions;\n};\n\n/**\n * Request Permissions implementation to be used in JsonRpcEngine middleware.\n *\n * @param req - The JsonRpcEngine request\n * @param res - The JsonRpcEngine result object\n * @param _next - JsonRpcEngine next() callback - unused\n * @param end - JsonRpcEngine end() callback\n * @param options - Method hooks passed to the method implementation\n * @param options.requestPermissionsForOrigin - The specific method hook needed for this method implementation\n * @returns A promise that resolves to nothing\n */\nasync function requestPermissionsImplementation(\n req: JsonRpcRequest<[RequestedPermissions]>,\n res: PendingJsonRpcResponse,\n _next: unknown,\n end: JsonRpcEngineEndCallback,\n { requestPermissionsForOrigin }: RequestPermissionsHooks,\n): Promise {\n const { id, params } = req;\n\n if (\n (typeof id !== 'number' && typeof id !== 'string') ||\n (typeof id === 'string' && !id)\n ) {\n return end(\n ethErrors.rpc.invalidRequest({\n message: 'Invalid request: Must specify a valid id.',\n data: { request: req },\n }),\n );\n }\n\n if (!Array.isArray(params) || !isPlainObject(params[0])) {\n return end(invalidParams({ data: { request: req } }));\n }\n\n const [requestedPermissions] = params;\n const [grantedPermissions] = await requestPermissionsForOrigin(\n requestedPermissions,\n String(id),\n );\n\n // `wallet_requestPermission` is specified to return an array.\n res.result = Object.values(grantedPermissions);\n return end();\n}\n"]} \ No newline at end of file diff --git a/dist/permissions/utils.d.ts b/dist/permissions/utils.d.ts new file mode 100644 index 0000000000..91f3fdf79a --- /dev/null +++ b/dist/permissions/utils.d.ts @@ -0,0 +1,15 @@ +import { CaveatSpecificationConstraint, CaveatSpecificationMap } from './Caveat'; +import { PermissionSpecificationConstraint, PermissionSpecificationMap } from './Permission'; +export declare enum MethodNames { + requestPermissions = "wallet_requestPermissions", + getPermissions = "wallet_getPermissions" +} +/** + * Utility type for extracting a union of all individual caveat or permission + * specification types from a {@link CaveatSpecificationMap} or + * {@link PermissionSpecificationMap}. + * + * @template SpecificationsMap - The caveat or permission specifications map + * whose specification type union to extract. + */ +export declare type ExtractSpecifications | PermissionSpecificationMap> = SpecificationsMap[keyof SpecificationsMap]; diff --git a/dist/permissions/utils.js b/dist/permissions/utils.js new file mode 100644 index 0000000000..4ba6e95cbe --- /dev/null +++ b/dist/permissions/utils.js @@ -0,0 +1,9 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MethodNames = void 0; +var MethodNames; +(function (MethodNames) { + MethodNames["requestPermissions"] = "wallet_requestPermissions"; + MethodNames["getPermissions"] = "wallet_getPermissions"; +})(MethodNames = exports.MethodNames || (exports.MethodNames = {})); +//# sourceMappingURL=utils.js.map \ No newline at end of file diff --git a/dist/permissions/utils.js.map b/dist/permissions/utils.js.map new file mode 100644 index 0000000000..c0e270558d --- /dev/null +++ b/dist/permissions/utils.js.map @@ -0,0 +1 @@ +{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/permissions/utils.ts"],"names":[],"mappings":";;;AASA,IAAY,WAGX;AAHD,WAAY,WAAW;IACrB,+DAAgD,CAAA;IAChD,uDAAwC,CAAA;AAC1C,CAAC,EAHW,WAAW,GAAX,mBAAW,KAAX,mBAAW,QAGtB","sourcesContent":["import {\n CaveatSpecificationConstraint,\n CaveatSpecificationMap,\n} from './Caveat';\nimport {\n PermissionSpecificationConstraint,\n PermissionSpecificationMap,\n} from './Permission';\n\nexport enum MethodNames {\n requestPermissions = 'wallet_requestPermissions',\n getPermissions = 'wallet_getPermissions',\n}\n\n/**\n * Utility type for extracting a union of all individual caveat or permission\n * specification types from a {@link CaveatSpecificationMap} or\n * {@link PermissionSpecificationMap}.\n *\n * @template SpecificationsMap - The caveat or permission specifications map\n * whose specification type union to extract.\n */\nexport type ExtractSpecifications<\n SpecificationsMap extends\n | CaveatSpecificationMap\n | PermissionSpecificationMap,\n> = SpecificationsMap[keyof SpecificationsMap];\n"]} \ No newline at end of file diff --git a/dist/ratelimit/RateLimitController.d.ts b/dist/ratelimit/RateLimitController.d.ts new file mode 100644 index 0000000000..bf07da598d --- /dev/null +++ b/dist/ratelimit/RateLimitController.d.ts @@ -0,0 +1,82 @@ +import type { Patch } from 'immer'; +import { BaseController } from '../BaseControllerV2'; +import type { RestrictedControllerMessenger } from '../ControllerMessenger'; +/** + * @type RateLimitState + * @property requests - Object containing number of requests in a given interval for each origin and api type combination + */ +export declare type RateLimitState any>> = { + requests: Record>; +}; +declare const name = "RateLimitController"; +export declare type RateLimitStateChange any>> = { + type: `${typeof name}:stateChange`; + payload: [RateLimitState, Patch[]]; +}; +export declare type GetRateLimitState any>> = { + type: `${typeof name}:getState`; + handler: () => RateLimitState; +}; +export declare type CallApi any>> = { + type: `${typeof name}:call`; + handler: RateLimitController['call']; +}; +export declare type RateLimitControllerActions any>> = GetRateLimitState | CallApi; +export declare type RateLimitMessenger any>> = RestrictedControllerMessenger, RateLimitStateChange, never, never>; +/** + * Controller with logic for rate-limiting API endpoints per requesting origin. + */ +export declare class RateLimitController any>> extends BaseController, RateLimitMessenger> { + private implementations; + private rateLimitTimeout; + private rateLimitCount; + /** + * Creates a RateLimitController instance. + * + * @param options - Constructor options. + * @param options.messenger - A reference to the messaging system. + * @param options.state - Initial state to set on this controller. + * @param options.implementations - Mapping from API type to API implementation. + * @param options.rateLimitTimeout - The time window in which the rate limit is applied (in ms). + * @param options.rateLimitCount - The amount of calls an origin can make in the rate limit time window. + */ + constructor({ rateLimitTimeout, rateLimitCount, messenger, state, implementations, }: { + rateLimitTimeout?: number; + rateLimitCount?: number; + messenger: RateLimitMessenger; + state?: Partial>; + implementations: RateLimitedApis; + }); + /** + * Calls an API if the requesting origin is not rate-limited. + * + * @param origin - The requesting origin. + * @param type - The type of API call to make. + * @param args - Arguments for the API call. + * @returns `false` if rate-limited, and `true` otherwise. + */ + call(origin: string, type: ApiType, ...args: Parameters): Promise>; + /** + * Checks whether an origin is rate limited for the a specific API. + * + * @param api - The API the origin is trying to access. + * @param origin - The origin trying to access the API. + * @returns `true` if rate-limited, and `false` otherwise. + */ + private isRateLimited; + /** + * Records that an origin has made a request to call an API, for rate-limiting purposes. + * + * @param api - The API the origin is trying to access. + * @param origin - The origin trying to access the API. + */ + private recordRequest; + /** + * Resets the request count for a given origin and API combination, for rate-limiting purposes. + * + * @param api - The API in question. + * @param origin - The origin in question. + */ + private resetRequestCount; +} +export {}; diff --git a/dist/ratelimit/RateLimitController.js b/dist/ratelimit/RateLimitController.js new file mode 100644 index 0000000000..79f874801a --- /dev/null +++ b/dist/ratelimit/RateLimitController.js @@ -0,0 +1,110 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RateLimitController = void 0; +const eth_rpc_errors_1 = require("eth-rpc-errors"); +const BaseControllerV2_1 = require("../BaseControllerV2"); +const name = 'RateLimitController'; +const metadata = { + requests: { persist: false, anonymous: false }, +}; +/** + * Controller with logic for rate-limiting API endpoints per requesting origin. + */ +class RateLimitController extends BaseControllerV2_1.BaseController { + /** + * Creates a RateLimitController instance. + * + * @param options - Constructor options. + * @param options.messenger - A reference to the messaging system. + * @param options.state - Initial state to set on this controller. + * @param options.implementations - Mapping from API type to API implementation. + * @param options.rateLimitTimeout - The time window in which the rate limit is applied (in ms). + * @param options.rateLimitCount - The amount of calls an origin can make in the rate limit time window. + */ + constructor({ rateLimitTimeout = 5000, rateLimitCount = 1, messenger, state, implementations, }) { + const defaultState = { + requests: Object.keys(implementations).reduce((acc, key) => (Object.assign(Object.assign({}, acc), { [key]: {} })), {}), + }; + super({ + name, + metadata, + messenger, + state: Object.assign(Object.assign({}, defaultState), state), + }); + this.implementations = implementations; + this.rateLimitTimeout = rateLimitTimeout; + this.rateLimitCount = rateLimitCount; + this.messagingSystem.registerActionHandler(`${name}:call`, ((origin, type, ...args) => this.call(origin, type, ...args))); + } + /** + * Calls an API if the requesting origin is not rate-limited. + * + * @param origin - The requesting origin. + * @param type - The type of API call to make. + * @param args - Arguments for the API call. + * @returns `false` if rate-limited, and `true` otherwise. + */ + call(origin, type, ...args) { + return __awaiter(this, void 0, void 0, function* () { + if (this.isRateLimited(type, origin)) { + throw eth_rpc_errors_1.ethErrors.rpc.limitExceeded({ + message: `"${type}" is currently rate-limited. Please try again later.`, + }); + } + this.recordRequest(type, origin); + const implementation = this.implementations[type]; + if (!implementation) { + throw new Error('Invalid api type'); + } + return implementation(...args); + }); + } + /** + * Checks whether an origin is rate limited for the a specific API. + * + * @param api - The API the origin is trying to access. + * @param origin - The origin trying to access the API. + * @returns `true` if rate-limited, and `false` otherwise. + */ + isRateLimited(api, origin) { + return this.state.requests[api][origin] >= this.rateLimitCount; + } + /** + * Records that an origin has made a request to call an API, for rate-limiting purposes. + * + * @param api - The API the origin is trying to access. + * @param origin - The origin trying to access the API. + */ + recordRequest(api, origin) { + this.update((state) => { + var _a; + const previous = (_a = state.requests[api][origin]) !== null && _a !== void 0 ? _a : 0; + state.requests[api][origin] = previous + 1; + if (previous === 0) { + setTimeout(() => this.resetRequestCount(api, origin), this.rateLimitTimeout); + } + }); + } + /** + * Resets the request count for a given origin and API combination, for rate-limiting purposes. + * + * @param api - The API in question. + * @param origin - The origin in question. + */ + resetRequestCount(api, origin) { + this.update((state) => { + state.requests[api][origin] = 0; + }); + } +} +exports.RateLimitController = RateLimitController; +//# sourceMappingURL=RateLimitController.js.map \ No newline at end of file diff --git a/dist/ratelimit/RateLimitController.js.map b/dist/ratelimit/RateLimitController.js.map new file mode 100644 index 0000000000..fb0a08d793 --- /dev/null +++ b/dist/ratelimit/RateLimitController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"RateLimitController.js","sourceRoot":"","sources":["../../src/ratelimit/RateLimitController.ts"],"names":[],"mappings":"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","sourcesContent":["import { ethErrors } from 'eth-rpc-errors';\nimport type { Patch } from 'immer';\n\nimport { BaseController } from '../BaseControllerV2';\n\nimport type { RestrictedControllerMessenger } from '../ControllerMessenger';\n\n/**\n * @type RateLimitState\n * @property requests - Object containing number of requests in a given interval for each origin and api type combination\n */\nexport type RateLimitState<\n RateLimitedApis extends Record any>,\n> = {\n requests: Record>;\n};\n\nconst name = 'RateLimitController';\n\nexport type RateLimitStateChange<\n RateLimitedApis extends Record any>,\n> = {\n type: `${typeof name}:stateChange`;\n payload: [RateLimitState, Patch[]];\n};\n\nexport type GetRateLimitState<\n RateLimitedApis extends Record any>,\n> = {\n type: `${typeof name}:getState`;\n handler: () => RateLimitState;\n};\n\nexport type CallApi<\n RateLimitedApis extends Record any>,\n> = {\n type: `${typeof name}:call`;\n handler: RateLimitController['call'];\n};\n\nexport type RateLimitControllerActions<\n RateLimitedApis extends Record any>,\n> = GetRateLimitState | CallApi;\n\nexport type RateLimitMessenger<\n RateLimitedApis extends Record any>,\n> = RestrictedControllerMessenger<\n typeof name,\n RateLimitControllerActions,\n RateLimitStateChange,\n never,\n never\n>;\n\nconst metadata = {\n requests: { persist: false, anonymous: false },\n};\n\n/**\n * Controller with logic for rate-limiting API endpoints per requesting origin.\n */\nexport class RateLimitController<\n RateLimitedApis extends Record any>,\n> extends BaseController<\n typeof name,\n RateLimitState,\n RateLimitMessenger\n> {\n private implementations;\n\n private rateLimitTimeout;\n\n private rateLimitCount;\n\n /**\n * Creates a RateLimitController instance.\n *\n * @param options - Constructor options.\n * @param options.messenger - A reference to the messaging system.\n * @param options.state - Initial state to set on this controller.\n * @param options.implementations - Mapping from API type to API implementation.\n * @param options.rateLimitTimeout - The time window in which the rate limit is applied (in ms).\n * @param options.rateLimitCount - The amount of calls an origin can make in the rate limit time window.\n */\n constructor({\n rateLimitTimeout = 5000,\n rateLimitCount = 1,\n messenger,\n state,\n implementations,\n }: {\n rateLimitTimeout?: number;\n rateLimitCount?: number;\n messenger: RateLimitMessenger;\n state?: Partial>;\n implementations: RateLimitedApis;\n }) {\n const defaultState = {\n requests: Object.keys(implementations).reduce(\n (acc, key) => ({ ...acc, [key]: {} }),\n {} as Record>,\n ),\n };\n super({\n name,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n this.implementations = implementations;\n this.rateLimitTimeout = rateLimitTimeout;\n this.rateLimitCount = rateLimitCount;\n\n this.messagingSystem.registerActionHandler(\n `${name}:call` as const,\n ((\n origin: string,\n type: keyof RateLimitedApis,\n ...args: Parameters\n ) => this.call(origin, type, ...args)) as any,\n );\n }\n\n /**\n * Calls an API if the requesting origin is not rate-limited.\n *\n * @param origin - The requesting origin.\n * @param type - The type of API call to make.\n * @param args - Arguments for the API call.\n * @returns `false` if rate-limited, and `true` otherwise.\n */\n async call(\n origin: string,\n type: ApiType,\n ...args: Parameters\n ): Promise> {\n if (this.isRateLimited(type, origin)) {\n throw ethErrors.rpc.limitExceeded({\n message: `\"${type}\" is currently rate-limited. Please try again later.`,\n });\n }\n this.recordRequest(type, origin);\n\n const implementation = this.implementations[type];\n\n if (!implementation) {\n throw new Error('Invalid api type');\n }\n\n return implementation(...args);\n }\n\n /**\n * Checks whether an origin is rate limited for the a specific API.\n *\n * @param api - The API the origin is trying to access.\n * @param origin - The origin trying to access the API.\n * @returns `true` if rate-limited, and `false` otherwise.\n */\n private isRateLimited(api: keyof RateLimitedApis, origin: string) {\n return this.state.requests[api][origin] >= this.rateLimitCount;\n }\n\n /**\n * Records that an origin has made a request to call an API, for rate-limiting purposes.\n *\n * @param api - The API the origin is trying to access.\n * @param origin - The origin trying to access the API.\n */\n private recordRequest(api: keyof RateLimitedApis, origin: string) {\n this.update((state) => {\n const previous = (state as any).requests[api][origin] ?? 0;\n (state as any).requests[api][origin] = previous + 1;\n\n if (previous === 0) {\n setTimeout(\n () => this.resetRequestCount(api, origin),\n this.rateLimitTimeout,\n );\n }\n });\n }\n\n /**\n * Resets the request count for a given origin and API combination, for rate-limiting purposes.\n *\n * @param api - The API in question.\n * @param origin - The origin in question.\n */\n private resetRequestCount(api: keyof RateLimitedApis, origin: string) {\n this.update((state) => {\n (state as any).requests[api][origin] = 0;\n });\n }\n}\n"]} \ No newline at end of file diff --git a/dist/subject-metadata/SubjectMetadataController.d.ts b/dist/subject-metadata/SubjectMetadataController.d.ts new file mode 100644 index 0000000000..9d17cc167c --- /dev/null +++ b/dist/subject-metadata/SubjectMetadataController.d.ts @@ -0,0 +1,85 @@ +import type { Patch } from 'immer'; +import { Json } from '@metamask/types'; +import { BaseController } from '../BaseControllerV2'; +import { RestrictedControllerMessenger } from '../ControllerMessenger'; +import type { PermissionSubjectMetadata, HasPermissions } from '../permissions'; +declare const controllerName = "SubjectMetadataController"; +declare type SubjectOrigin = string; +export declare type SubjectMetadata = PermissionSubjectMetadata & { + [key: string]: Json; + name: string | null; + extensionId: string | null; + iconUrl: string | null; +}; +declare type SubjectMetadataToAdd = PermissionSubjectMetadata & { + name?: string | null; + extensionId?: string | null; + iconUrl?: string | null; +} & Record; +export declare type SubjectMetadataControllerState = { + subjectMetadata: Record; +}; +export declare type GetSubjectMetadataState = { + type: `${typeof controllerName}:getState`; + handler: () => SubjectMetadataControllerState; +}; +export declare type SubjectMetadataControllerActions = GetSubjectMetadataState; +export declare type SubjectMetadataStateChange = { + type: `${typeof controllerName}:stateChange`; + payload: [SubjectMetadataControllerState, Patch[]]; +}; +export declare type SubjectMetadataControllerEvents = SubjectMetadataStateChange; +declare type AllowedActions = HasPermissions; +export declare type SubjectMetadataControllerMessenger = RestrictedControllerMessenger; +declare type SubjectMetadataControllerOptions = { + messenger: SubjectMetadataControllerMessenger; + subjectCacheLimit: number; + state?: Partial; +}; +/** + * A controller for storing metadata associated with permission subjects. More + * or less, a cache. + */ +export declare class SubjectMetadataController extends BaseController { + private subjectCacheLimit; + private subjectsWithoutPermissionsEcounteredSinceStartup; + private subjectHasPermissions; + constructor({ messenger, subjectCacheLimit, state, }: SubjectMetadataControllerOptions); + /** + * Clears the state of this controller. Also resets the cache of subjects + * encountered since startup, so as to not prematurely reach the cache limit. + */ + clearState(): void; + /** + * Stores domain metadata for the given origin (subject). Deletes metadata for + * subjects without permissions in a FIFO manner once more than + * {@link SubjectMetadataController.subjectCacheLimit} distinct origins have + * been added since boot. + * + * In order to prevent a degraded user experience, + * metadata is never deleted for subjects with permissions, since metadata + * cannot yet be requested on demand. + * + * @param metadata - The subject metadata to store. + */ + addSubjectMetadata(metadata: SubjectMetadataToAdd): void; + /** + * Deletes all subjects without permissions from the controller's state. + */ + trimMetadataState(): void; + /** + * Returns a new state object that only includes subjects with permissions. + * This method is static because we want to call it in the constructor, before + * the controller's state is initialized. + * + * @param state - The state object to trim. + * @param hasPermissions - A function that returns a boolean indicating + * whether a particular subject (identified by its origin) has any + * permissions. + * @returns The new state object. If the specified `state` object has no + * subject metadata, the returned object will be equivalent to the default + * state of this controller. + */ + private static getTrimmedState; +} +export {}; diff --git a/dist/subject-metadata/SubjectMetadataController.js b/dist/subject-metadata/SubjectMetadataController.js new file mode 100644 index 0000000000..a5cbc9d619 --- /dev/null +++ b/dist/subject-metadata/SubjectMetadataController.js @@ -0,0 +1,117 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SubjectMetadataController = void 0; +const BaseControllerV2_1 = require("../BaseControllerV2"); +const controllerName = 'SubjectMetadataController'; +const stateMetadata = { + subjectMetadata: { persist: true, anonymous: false }, +}; +const defaultState = { + subjectMetadata: {}, +}; +/** + * A controller for storing metadata associated with permission subjects. More + * or less, a cache. + */ +class SubjectMetadataController extends BaseControllerV2_1.BaseController { + constructor({ messenger, subjectCacheLimit, state = {}, }) { + if (!Number.isInteger(subjectCacheLimit) || subjectCacheLimit < 1) { + throw new Error(`subjectCacheLimit must be a positive integer. Received: "${subjectCacheLimit}"`); + } + const hasPermissions = (origin) => { + return messenger.call('PermissionController:hasPermissions', origin); + }; + super({ + name: controllerName, + metadata: stateMetadata, + messenger, + state: Object.assign({}, SubjectMetadataController.getTrimmedState(state, hasPermissions)), + }); + this.subjectHasPermissions = hasPermissions; + this.subjectCacheLimit = subjectCacheLimit; + this.subjectsWithoutPermissionsEcounteredSinceStartup = new Set(); + } + /** + * Clears the state of this controller. Also resets the cache of subjects + * encountered since startup, so as to not prematurely reach the cache limit. + */ + clearState() { + this.subjectsWithoutPermissionsEcounteredSinceStartup.clear(); + this.update((_draftState) => { + return Object.assign({}, defaultState); + }); + } + /** + * Stores domain metadata for the given origin (subject). Deletes metadata for + * subjects without permissions in a FIFO manner once more than + * {@link SubjectMetadataController.subjectCacheLimit} distinct origins have + * been added since boot. + * + * In order to prevent a degraded user experience, + * metadata is never deleted for subjects with permissions, since metadata + * cannot yet be requested on demand. + * + * @param metadata - The subject metadata to store. + */ + addSubjectMetadata(metadata) { + const { origin } = metadata; + const newMetadata = Object.assign(Object.assign({}, metadata), { extensionId: metadata.extensionId || null, iconUrl: metadata.iconUrl || null, name: metadata.name || null }); + let originToForget = null; + // We only delete the oldest encountered subject from the cache, again to + // ensure that the user's experience isn't degraded by missing icons etc. + if (this.subjectsWithoutPermissionsEcounteredSinceStartup.size >= + this.subjectCacheLimit) { + const cachedOrigin = this.subjectsWithoutPermissionsEcounteredSinceStartup + .values() + .next().value; + this.subjectsWithoutPermissionsEcounteredSinceStartup.delete(cachedOrigin); + if (!this.subjectHasPermissions(cachedOrigin)) { + originToForget = cachedOrigin; + } + } + this.subjectsWithoutPermissionsEcounteredSinceStartup.add(origin); + this.update((draftState) => { + // Typecast: ts(2589) + draftState.subjectMetadata[origin] = newMetadata; + if (typeof originToForget === 'string') { + delete draftState.subjectMetadata[originToForget]; + } + }); + } + /** + * Deletes all subjects without permissions from the controller's state. + */ + trimMetadataState() { + this.update((draftState) => { + return SubjectMetadataController.getTrimmedState( + // Typecast: ts(2589) + draftState, this.subjectHasPermissions); + }); + } + /** + * Returns a new state object that only includes subjects with permissions. + * This method is static because we want to call it in the constructor, before + * the controller's state is initialized. + * + * @param state - The state object to trim. + * @param hasPermissions - A function that returns a boolean indicating + * whether a particular subject (identified by its origin) has any + * permissions. + * @returns The new state object. If the specified `state` object has no + * subject metadata, the returned object will be equivalent to the default + * state of this controller. + */ + static getTrimmedState(state, hasPermissions) { + const { subjectMetadata = {} } = state; + return { + subjectMetadata: Object.keys(subjectMetadata).reduce((newSubjectMetadata, origin) => { + if (hasPermissions(origin)) { + newSubjectMetadata[origin] = subjectMetadata[origin]; + } + return newSubjectMetadata; + }, {}), + }; + } +} +exports.SubjectMetadataController = SubjectMetadataController; +//# sourceMappingURL=SubjectMetadataController.js.map \ No newline at end of file diff --git a/dist/subject-metadata/SubjectMetadataController.js.map b/dist/subject-metadata/SubjectMetadataController.js.map new file mode 100644 index 0000000000..8e3141607a --- /dev/null +++ b/dist/subject-metadata/SubjectMetadataController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"SubjectMetadataController.js","sourceRoot":"","sources":["../../src/subject-metadata/SubjectMetadataController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Patch } from 'immer';\nimport { Json } from '@metamask/types';\nimport { BaseController } from '../BaseControllerV2';\nimport { RestrictedControllerMessenger } from '../ControllerMessenger';\n\nimport type {\n GenericPermissionController,\n PermissionSubjectMetadata,\n HasPermissions,\n} from '../permissions';\n\nconst controllerName = 'SubjectMetadataController';\n\ntype SubjectOrigin = string;\n\nexport type SubjectMetadata = PermissionSubjectMetadata & {\n [key: string]: Json;\n // TODO:TS4.4 make optional\n name: string | null;\n extensionId: string | null;\n iconUrl: string | null;\n};\n\ntype SubjectMetadataToAdd = PermissionSubjectMetadata & {\n name?: string | null;\n extensionId?: string | null;\n iconUrl?: string | null;\n} & Record;\n\nexport type SubjectMetadataControllerState = {\n subjectMetadata: Record;\n};\n\nconst stateMetadata = {\n subjectMetadata: { persist: true, anonymous: false },\n};\n\nconst defaultState: SubjectMetadataControllerState = {\n subjectMetadata: {},\n};\n\nexport type GetSubjectMetadataState = {\n type: `${typeof controllerName}:getState`;\n handler: () => SubjectMetadataControllerState;\n};\n\nexport type SubjectMetadataControllerActions = GetSubjectMetadataState;\n\nexport type SubjectMetadataStateChange = {\n type: `${typeof controllerName}:stateChange`;\n payload: [SubjectMetadataControllerState, Patch[]];\n};\n\nexport type SubjectMetadataControllerEvents = SubjectMetadataStateChange;\n\ntype AllowedActions = HasPermissions;\n\nexport type SubjectMetadataControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n SubjectMetadataControllerActions | AllowedActions,\n SubjectMetadataControllerEvents,\n AllowedActions['type'],\n never\n>;\n\ntype SubjectMetadataControllerOptions = {\n messenger: SubjectMetadataControllerMessenger;\n subjectCacheLimit: number;\n state?: Partial;\n};\n\n/**\n * A controller for storing metadata associated with permission subjects. More\n * or less, a cache.\n */\nexport class SubjectMetadataController extends BaseController<\n typeof controllerName,\n SubjectMetadataControllerState,\n SubjectMetadataControllerMessenger\n> {\n private subjectCacheLimit: number;\n\n private subjectsWithoutPermissionsEcounteredSinceStartup: Set;\n\n private subjectHasPermissions: GenericPermissionController['hasPermissions'];\n\n constructor({\n messenger,\n subjectCacheLimit,\n state = {},\n }: SubjectMetadataControllerOptions) {\n if (!Number.isInteger(subjectCacheLimit) || subjectCacheLimit < 1) {\n throw new Error(\n `subjectCacheLimit must be a positive integer. Received: \"${subjectCacheLimit}\"`,\n );\n }\n\n const hasPermissions = (origin: string) => {\n return messenger.call('PermissionController:hasPermissions', origin);\n };\n\n super({\n name: controllerName,\n metadata: stateMetadata,\n messenger,\n state: {\n ...SubjectMetadataController.getTrimmedState(state, hasPermissions),\n },\n });\n\n this.subjectHasPermissions = hasPermissions;\n this.subjectCacheLimit = subjectCacheLimit;\n this.subjectsWithoutPermissionsEcounteredSinceStartup = new Set();\n }\n\n /**\n * Clears the state of this controller. Also resets the cache of subjects\n * encountered since startup, so as to not prematurely reach the cache limit.\n */\n clearState(): void {\n this.subjectsWithoutPermissionsEcounteredSinceStartup.clear();\n this.update((_draftState) => {\n return { ...defaultState };\n });\n }\n\n /**\n * Stores domain metadata for the given origin (subject). Deletes metadata for\n * subjects without permissions in a FIFO manner once more than\n * {@link SubjectMetadataController.subjectCacheLimit} distinct origins have\n * been added since boot.\n *\n * In order to prevent a degraded user experience,\n * metadata is never deleted for subjects with permissions, since metadata\n * cannot yet be requested on demand.\n *\n * @param metadata - The subject metadata to store.\n */\n addSubjectMetadata(metadata: SubjectMetadataToAdd): void {\n const { origin } = metadata;\n const newMetadata: SubjectMetadata = {\n ...metadata,\n extensionId: metadata.extensionId || null,\n iconUrl: metadata.iconUrl || null,\n name: metadata.name || null,\n };\n\n let originToForget: string | null = null;\n // We only delete the oldest encountered subject from the cache, again to\n // ensure that the user's experience isn't degraded by missing icons etc.\n if (\n this.subjectsWithoutPermissionsEcounteredSinceStartup.size >=\n this.subjectCacheLimit\n ) {\n const cachedOrigin = this.subjectsWithoutPermissionsEcounteredSinceStartup\n .values()\n .next().value;\n\n this.subjectsWithoutPermissionsEcounteredSinceStartup.delete(\n cachedOrigin,\n );\n\n if (!this.subjectHasPermissions(cachedOrigin)) {\n originToForget = cachedOrigin;\n }\n }\n\n this.subjectsWithoutPermissionsEcounteredSinceStartup.add(origin);\n\n this.update((draftState) => {\n // Typecast: ts(2589)\n draftState.subjectMetadata[origin] = newMetadata as any;\n if (typeof originToForget === 'string') {\n delete draftState.subjectMetadata[originToForget];\n }\n });\n }\n\n /**\n * Deletes all subjects without permissions from the controller's state.\n */\n trimMetadataState(): void {\n this.update((draftState) => {\n return SubjectMetadataController.getTrimmedState(\n // Typecast: ts(2589)\n draftState as any,\n this.subjectHasPermissions,\n );\n });\n }\n\n /**\n * Returns a new state object that only includes subjects with permissions.\n * This method is static because we want to call it in the constructor, before\n * the controller's state is initialized.\n *\n * @param state - The state object to trim.\n * @param hasPermissions - A function that returns a boolean indicating\n * whether a particular subject (identified by its origin) has any\n * permissions.\n * @returns The new state object. If the specified `state` object has no\n * subject metadata, the returned object will be equivalent to the default\n * state of this controller.\n */\n private static getTrimmedState(\n state: Partial,\n hasPermissions: SubjectMetadataController['subjectHasPermissions'],\n ): SubjectMetadataControllerState {\n const { subjectMetadata = {} } = state;\n\n return {\n subjectMetadata: Object.keys(subjectMetadata).reduce<\n Record\n >((newSubjectMetadata, origin) => {\n if (hasPermissions(origin)) {\n newSubjectMetadata[origin] = subjectMetadata[origin];\n }\n return newSubjectMetadata;\n }, {}),\n };\n }\n}\n"]} \ No newline at end of file diff --git a/dist/subject-metadata/index.d.ts b/dist/subject-metadata/index.d.ts new file mode 100644 index 0000000000..3b0bcaa14b --- /dev/null +++ b/dist/subject-metadata/index.d.ts @@ -0,0 +1 @@ +export * from './SubjectMetadataController'; diff --git a/dist/subject-metadata/index.js b/dist/subject-metadata/index.js new file mode 100644 index 0000000000..b02817a57b --- /dev/null +++ b/dist/subject-metadata/index.js @@ -0,0 +1,18 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +__exportStar(require("./SubjectMetadataController"), exports); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/subject-metadata/index.js.map b/dist/subject-metadata/index.js.map new file mode 100644 index 0000000000..ef4fa2f3f1 --- /dev/null +++ b/dist/subject-metadata/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/subject-metadata/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,8DAA4C","sourcesContent":["export * from './SubjectMetadataController';\n"]} \ No newline at end of file diff --git a/dist/third-party/EnsController.d.ts b/dist/third-party/EnsController.d.ts new file mode 100644 index 0000000000..e92c9cfd2c --- /dev/null +++ b/dist/third-party/EnsController.d.ts @@ -0,0 +1,76 @@ +import { BaseController, BaseConfig, BaseState } from '../BaseController'; +/** + * @type EnsEntry + * + * ENS entry representation + * @property chainId - Id of the associated chain + * @property ensName - The ENS name + * @property address - Hex address with the ENS name, or null + */ +export interface EnsEntry { + chainId: string; + ensName: string; + address: string | null; +} +/** + * @type EnsState + * + * ENS controller state + * @property ensEntries - Object of ENS entry objects + */ +export interface EnsState extends BaseState { + ensEntries: { + [chainId: string]: { + [ensName: string]: EnsEntry; + }; + }; +} +/** + * Controller that manages a list ENS names and their resolved addresses + * by chainId. A null address indicates an unresolved ENS name. + */ +export declare class EnsController extends BaseController { + /** + * Name of this controller used during composition + */ + name: string; + /** + * Creates an EnsController instance. + * + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor(config?: Partial, state?: Partial); + /** + * Remove all chain Ids and ENS entries from state. + */ + clear(): void; + /** + * Delete an ENS entry. + * + * @param chainId - Parent chain of the ENS entry to delete. + * @param ensName - Name of the ENS entry to delete. + * @returns Boolean indicating if the entry was deleted. + */ + delete(chainId: string, ensName: string): boolean; + /** + * Retrieve a DNS entry. + * + * @param chainId - Parent chain of the ENS entry to retrieve. + * @param ensName - Name of the ENS entry to retrieve. + * @returns The EnsEntry or null if it does not exist. + */ + get(chainId: string, ensName: string): EnsEntry | null; + /** + * Add or update an ENS entry by chainId and ensName. + * + * A null address indicates that the ENS name does not resolve. + * + * @param chainId - Id of the associated chain. + * @param ensName - The ENS name. + * @param address - Associated address (or null) to add or update. + * @returns Boolean indicating if the entry was set. + */ + set(chainId: string, ensName: string, address: string | null): boolean; +} +export default EnsController; diff --git a/dist/third-party/EnsController.js b/dist/third-party/EnsController.js new file mode 100644 index 0000000000..d2f68682c3 --- /dev/null +++ b/dist/third-party/EnsController.js @@ -0,0 +1,108 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.EnsController = void 0; +const BaseController_1 = require("../BaseController"); +const util_1 = require("../util"); +/** + * Controller that manages a list ENS names and their resolved addresses + * by chainId. A null address indicates an unresolved ENS name. + */ +class EnsController extends BaseController_1.BaseController { + /** + * Creates an EnsController instance. + * + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor(config, state) { + super(config, state); + /** + * Name of this controller used during composition + */ + this.name = 'EnsController'; + this.defaultState = { ensEntries: {} }; + this.initialize(); + } + /** + * Remove all chain Ids and ENS entries from state. + */ + clear() { + this.update({ ensEntries: {} }); + } + /** + * Delete an ENS entry. + * + * @param chainId - Parent chain of the ENS entry to delete. + * @param ensName - Name of the ENS entry to delete. + * @returns Boolean indicating if the entry was deleted. + */ + delete(chainId, ensName) { + const normalizedEnsName = (0, util_1.normalizeEnsName)(ensName); + if (!normalizedEnsName || + !this.state.ensEntries[chainId] || + !this.state.ensEntries[chainId][normalizedEnsName]) { + return false; + } + const ensEntries = Object.assign({}, this.state.ensEntries); + delete ensEntries[chainId][normalizedEnsName]; + if (Object.keys(ensEntries[chainId]).length === 0) { + delete ensEntries[chainId]; + } + this.update({ ensEntries }); + return true; + } + /** + * Retrieve a DNS entry. + * + * @param chainId - Parent chain of the ENS entry to retrieve. + * @param ensName - Name of the ENS entry to retrieve. + * @returns The EnsEntry or null if it does not exist. + */ + get(chainId, ensName) { + const normalizedEnsName = (0, util_1.normalizeEnsName)(ensName); + // TODO Explicitly handle the case where `normalizedEnsName` is `null` + // eslint-disable-next-line no-implicit-coercion + return !!normalizedEnsName && this.state.ensEntries[chainId] + ? this.state.ensEntries[chainId][normalizedEnsName] || null + : null; + } + /** + * Add or update an ENS entry by chainId and ensName. + * + * A null address indicates that the ENS name does not resolve. + * + * @param chainId - Id of the associated chain. + * @param ensName - The ENS name. + * @param address - Associated address (or null) to add or update. + * @returns Boolean indicating if the entry was set. + */ + set(chainId, ensName, address) { + if (!Number.isInteger(Number.parseInt(chainId, 10)) || + !ensName || + typeof ensName !== 'string' || + (address && !(0, util_1.isValidHexAddress)(address))) { + throw new Error(`Invalid ENS entry: { chainId:${chainId}, ensName:${ensName}, address:${address}}`); + } + const normalizedEnsName = (0, util_1.normalizeEnsName)(ensName); + if (!normalizedEnsName) { + throw new Error(`Invalid ENS name: ${ensName}`); + } + const normalizedAddress = address ? (0, util_1.toChecksumHexAddress)(address) : null; + const subState = this.state.ensEntries[chainId]; + if ((subState === null || subState === void 0 ? void 0 : subState[normalizedEnsName]) && + subState[normalizedEnsName].address === normalizedAddress) { + return false; + } + this.update({ + ensEntries: Object.assign(Object.assign({}, this.state.ensEntries), { [chainId]: Object.assign(Object.assign({}, this.state.ensEntries[chainId]), { [normalizedEnsName]: { + address: normalizedAddress, + chainId, + ensName: normalizedEnsName, + } }) }), + }); + return true; + } +} +exports.EnsController = EnsController; +exports.default = EnsController; +//# sourceMappingURL=EnsController.js.map \ No newline at end of file diff --git a/dist/third-party/EnsController.js.map b/dist/third-party/EnsController.js.map new file mode 100644 index 0000000000..8e01bb711b --- /dev/null +++ b/dist/third-party/EnsController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"EnsController.js","sourceRoot":"","sources":["../../src/third-party/EnsController.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport {\n normalizeEnsName,\n isValidHexAddress,\n toChecksumHexAddress,\n} from '../util';\n\n/**\n * @type EnsEntry\n *\n * ENS entry representation\n * @property chainId - Id of the associated chain\n * @property ensName - The ENS name\n * @property address - Hex address with the ENS name, or null\n */\nexport interface EnsEntry {\n chainId: string;\n ensName: string;\n address: string | null;\n}\n\n/**\n * @type EnsState\n *\n * ENS controller state\n * @property ensEntries - Object of ENS entry objects\n */\nexport interface EnsState extends BaseState {\n ensEntries: { [chainId: string]: { [ensName: string]: EnsEntry } };\n}\n\n/**\n * Controller that manages a list ENS names and their resolved addresses\n * by chainId. A null address indicates an unresolved ENS name.\n */\nexport class EnsController extends BaseController {\n /**\n * Name of this controller used during composition\n */\n override name = 'EnsController';\n\n /**\n * Creates an EnsController instance.\n *\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(config?: Partial, state?: Partial) {\n super(config, state);\n\n this.defaultState = { ensEntries: {} };\n\n this.initialize();\n }\n\n /**\n * Remove all chain Ids and ENS entries from state.\n */\n clear() {\n this.update({ ensEntries: {} });\n }\n\n /**\n * Delete an ENS entry.\n *\n * @param chainId - Parent chain of the ENS entry to delete.\n * @param ensName - Name of the ENS entry to delete.\n * @returns Boolean indicating if the entry was deleted.\n */\n delete(chainId: string, ensName: string): boolean {\n const normalizedEnsName = normalizeEnsName(ensName);\n if (\n !normalizedEnsName ||\n !this.state.ensEntries[chainId] ||\n !this.state.ensEntries[chainId][normalizedEnsName]\n ) {\n return false;\n }\n\n const ensEntries = Object.assign({}, this.state.ensEntries);\n delete ensEntries[chainId][normalizedEnsName];\n\n if (Object.keys(ensEntries[chainId]).length === 0) {\n delete ensEntries[chainId];\n }\n\n this.update({ ensEntries });\n return true;\n }\n\n /**\n * Retrieve a DNS entry.\n *\n * @param chainId - Parent chain of the ENS entry to retrieve.\n * @param ensName - Name of the ENS entry to retrieve.\n * @returns The EnsEntry or null if it does not exist.\n */\n get(chainId: string, ensName: string): EnsEntry | null {\n const normalizedEnsName = normalizeEnsName(ensName);\n\n // TODO Explicitly handle the case where `normalizedEnsName` is `null`\n // eslint-disable-next-line no-implicit-coercion\n return !!normalizedEnsName && this.state.ensEntries[chainId]\n ? this.state.ensEntries[chainId][normalizedEnsName] || null\n : null;\n }\n\n /**\n * Add or update an ENS entry by chainId and ensName.\n *\n * A null address indicates that the ENS name does not resolve.\n *\n * @param chainId - Id of the associated chain.\n * @param ensName - The ENS name.\n * @param address - Associated address (or null) to add or update.\n * @returns Boolean indicating if the entry was set.\n */\n set(chainId: string, ensName: string, address: string | null): boolean {\n if (\n !Number.isInteger(Number.parseInt(chainId, 10)) ||\n !ensName ||\n typeof ensName !== 'string' ||\n (address && !isValidHexAddress(address))\n ) {\n throw new Error(\n `Invalid ENS entry: { chainId:${chainId}, ensName:${ensName}, address:${address}}`,\n );\n }\n\n const normalizedEnsName = normalizeEnsName(ensName);\n if (!normalizedEnsName) {\n throw new Error(`Invalid ENS name: ${ensName}`);\n }\n\n const normalizedAddress = address ? toChecksumHexAddress(address) : null;\n const subState = this.state.ensEntries[chainId];\n\n if (\n subState?.[normalizedEnsName] &&\n subState[normalizedEnsName].address === normalizedAddress\n ) {\n return false;\n }\n\n this.update({\n ensEntries: {\n ...this.state.ensEntries,\n [chainId]: {\n ...this.state.ensEntries[chainId],\n [normalizedEnsName]: {\n address: normalizedAddress,\n chainId,\n ensName: normalizedEnsName,\n },\n },\n },\n });\n return true;\n }\n}\n\nexport default EnsController;\n"]} \ No newline at end of file diff --git a/dist/third-party/PhishingController.d.ts b/dist/third-party/PhishingController.d.ts new file mode 100644 index 0000000000..fc892e328d --- /dev/null +++ b/dist/third-party/PhishingController.d.ts @@ -0,0 +1,118 @@ +import { BaseController, BaseConfig, BaseState } from '../BaseController'; +/** + * @type EthPhishingResponse + * + * Configuration response from the eth-phishing-detect package + * consisting of approved and unapproved website origins + * @property blacklist - List of unapproved origins + * @property fuzzylist - List of fuzzy-matched unapproved origins + * @property tolerance - Fuzzy match tolerance level + * @property version - Version number of this configuration + * @property whitelist - List of approved origins + */ +export interface EthPhishingResponse { + blacklist: string[]; + fuzzylist: string[]; + tolerance: number; + version: number; + whitelist: string[]; +} +/** + * @type EthPhishingDetectConfig + * + * Interface defining expected input to PhishingDetector. + * @property allowlist - List of approved origins (legacy naming "whitelist") + * @property blocklist - List of unapproved origins (legacy naming "blacklist") + * @property fuzzylist - List of fuzzy-matched unapproved origins + * @property tolerance - Fuzzy match tolerance level + */ +export interface EthPhishingDetectConfig { + allowlist: string[]; + blocklist: string[]; + fuzzylist: string[]; + tolerance: number; + name: string; + version: number; +} +/** + * @type EthPhishingDetectResult + * + * Interface that describes the result of the `test` method. + * @property name - Name of the config on which a match was found. + * @property version - Version of the config on which a match was found. + * @property result - Whether a domain was detected as a phishing domain. True means an unsafe domain. + * @property match - The matching fuzzylist origin when a fuzzylist match is found. Returned as undefined for non-fuzzy true results. + * @property type - The field of the config on which a match was found. + */ +export interface EthPhishingDetectResult { + name?: string; + version?: string; + result: boolean; + match?: string; + type: 'all' | 'fuzzy' | 'blocklist' | 'allowlist'; +} +/** + * @type PhishingConfig + * + * Phishing controller configuration + * @property interval - Polling interval used to fetch new block / approve lists + */ +export interface PhishingConfig extends BaseConfig { + interval: number; +} +/** + * @type PhishingState + * + * Phishing controller state + * @property phishing - eth-phishing-detect configuration + * @property whitelist - array of temporarily-approved origins + */ +export interface PhishingState extends BaseState { + phishing: EthPhishingDetectConfig[]; + whitelist: string[]; +} +/** + * Controller that passively polls on a set interval for approved and unapproved website origins + */ +export declare class PhishingController extends BaseController { + private configUrlMetaMask; + private configUrlPhishFortHotlist; + private detector; + private handle?; + /** + * Name of this controller used during composition + */ + name: string; + /** + * Creates a PhishingController instance. + * + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor(config?: Partial, state?: Partial); + /** + * Starts a new polling interval. + * + * @param interval - Polling interval used to fetch new approval lists. + */ + poll(interval?: number): Promise; + /** + * Determines if a given origin is unapproved. + * + * @param origin - Domain origin of a website. + * @returns Whether the origin is an unapproved origin. + */ + test(origin: string): EthPhishingDetectResult; + /** + * Temporarily marks a given origin as approved. + * + * @param origin - The origin to mark as approved. + */ + bypass(origin: string): void; + /** + * Updates lists of approved and unapproved website origins. + */ + updatePhishingLists(): Promise; + private queryConfig; +} +export default PhishingController; diff --git a/dist/third-party/PhishingController.js b/dist/third-party/PhishingController.js new file mode 100644 index 0000000000..ea408a4e2a --- /dev/null +++ b/dist/third-party/PhishingController.js @@ -0,0 +1,165 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PhishingController = void 0; +const punycode_1 = require("punycode/"); +const config_json_1 = __importDefault(require("eth-phishing-detect/src/config.json")); +const detector_1 = __importDefault(require("eth-phishing-detect/src/detector")); +const BaseController_1 = require("../BaseController"); +const util_1 = require("../util"); +/** + * Controller that passively polls on a set interval for approved and unapproved website origins + */ +class PhishingController extends BaseController_1.BaseController { + /** + * Creates a PhishingController instance. + * + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor(config, state) { + super(config, state); + this.configUrlMetaMask = 'https://cdn.jsdelivr.net/gh/MetaMask/eth-phishing-detect@master/src/config.json'; + this.configUrlPhishFortHotlist = `https://cdn.jsdelivr.net/gh/phishfort/phishfort-lists@master/blacklists/hotlist.json`; + /** + * Name of this controller used during composition + */ + this.name = 'PhishingController'; + this.defaultConfig = { interval: 60 * 60 * 1000 }; + this.defaultState = { + phishing: [ + { + allowlist: config_json_1.default + .whitelist, + blocklist: config_json_1.default + .blacklist, + fuzzylist: config_json_1.default + .fuzzylist, + tolerance: config_json_1.default + .tolerance, + name: `MetaMask`, + version: config_json_1.default.version, + }, + ], + whitelist: [], + }; + this.detector = new detector_1.default(this.defaultState.phishing); + this.initialize(); + this.poll(); + } + /** + * Starts a new polling interval. + * + * @param interval - Polling interval used to fetch new approval lists. + */ + poll(interval) { + return __awaiter(this, void 0, void 0, function* () { + interval && this.configure({ interval }, false, false); + this.handle && clearTimeout(this.handle); + yield (0, util_1.safelyExecute)(() => this.updatePhishingLists()); + this.handle = setTimeout(() => { + this.poll(this.config.interval); + }, this.config.interval); + }); + } + /** + * Determines if a given origin is unapproved. + * + * @param origin - Domain origin of a website. + * @returns Whether the origin is an unapproved origin. + */ + test(origin) { + const punycodeOrigin = (0, punycode_1.toASCII)(origin); + if (this.state.whitelist.indexOf(punycodeOrigin) !== -1) { + return { result: false, type: 'all' }; // Same as whitelisted match returned by detector.check(...). + } + return this.detector.check(punycodeOrigin); + } + /** + * Temporarily marks a given origin as approved. + * + * @param origin - The origin to mark as approved. + */ + bypass(origin) { + const punycodeOrigin = (0, punycode_1.toASCII)(origin); + const { whitelist } = this.state; + if (whitelist.indexOf(punycodeOrigin) !== -1) { + return; + } + this.update({ whitelist: [...whitelist, punycodeOrigin] }); + } + /** + * Updates lists of approved and unapproved website origins. + */ + updatePhishingLists() { + return __awaiter(this, void 0, void 0, function* () { + if (this.disabled) { + return; + } + const configs = []; + const [metamaskConfigLegacy, phishfortHotlist] = yield Promise.all([ + yield this.queryConfig(this.configUrlMetaMask), + yield this.queryConfig(this.configUrlPhishFortHotlist), + ]); + // Correctly shaping MetaMask config. + const metamaskConfig = { + allowlist: metamaskConfigLegacy ? metamaskConfigLegacy.whitelist : [], + blocklist: metamaskConfigLegacy ? metamaskConfigLegacy.blacklist : [], + fuzzylist: metamaskConfigLegacy ? metamaskConfigLegacy.fuzzylist : [], + tolerance: metamaskConfigLegacy ? metamaskConfigLegacy.tolerance : 0, + name: `MetaMask`, + version: metamaskConfigLegacy ? metamaskConfigLegacy.version : 0, + }; + if (metamaskConfigLegacy) { + configs.push(metamaskConfig); + } + // Correctly shaping PhishFort config. + const phishfortConfig = { + allowlist: [], + blocklist: (phishfortHotlist || []).filter((i) => !metamaskConfig.blocklist.includes(i)), + fuzzylist: [], + tolerance: 0, + name: `PhishFort`, + version: 1, + }; + if (phishfortHotlist) { + configs.push(phishfortConfig); + } + // Do not update if all configs are unavailable. + if (!configs.length) { + return; + } + this.detector = new detector_1.default(configs); + this.update({ + phishing: configs, + }); + }); + } + queryConfig(input) { + return __awaiter(this, void 0, void 0, function* () { + const response = yield fetch(input, { cache: 'no-cache' }); + switch (response.status) { + case 200: { + return yield response.json(); + } + default: { + return null; + } + } + }); + } +} +exports.PhishingController = PhishingController; +exports.default = PhishingController; +//# sourceMappingURL=PhishingController.js.map \ No newline at end of file diff --git a/dist/third-party/PhishingController.js.map b/dist/third-party/PhishingController.js.map new file mode 100644 index 0000000000..fc34bf44d0 --- /dev/null +++ b/dist/third-party/PhishingController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"PhishingController.js","sourceRoot":"","sources":["../../src/third-party/PhishingController.ts"],"names":[],"mappings":"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","sourcesContent":["import { toASCII } from 'punycode/';\nimport DEFAULT_PHISHING_RESPONSE from 'eth-phishing-detect/src/config.json';\nimport PhishingDetector from 'eth-phishing-detect/src/detector';\nimport { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport { safelyExecute } from '../util';\n\n/**\n * @type EthPhishingResponse\n *\n * Configuration response from the eth-phishing-detect package\n * consisting of approved and unapproved website origins\n * @property blacklist - List of unapproved origins\n * @property fuzzylist - List of fuzzy-matched unapproved origins\n * @property tolerance - Fuzzy match tolerance level\n * @property version - Version number of this configuration\n * @property whitelist - List of approved origins\n */\nexport interface EthPhishingResponse {\n blacklist: string[];\n fuzzylist: string[];\n tolerance: number;\n version: number;\n whitelist: string[];\n}\n\n/**\n * @type EthPhishingDetectConfig\n *\n * Interface defining expected input to PhishingDetector.\n * @property allowlist - List of approved origins (legacy naming \"whitelist\")\n * @property blocklist - List of unapproved origins (legacy naming \"blacklist\")\n * @property fuzzylist - List of fuzzy-matched unapproved origins\n * @property tolerance - Fuzzy match tolerance level\n */\nexport interface EthPhishingDetectConfig {\n allowlist: string[];\n blocklist: string[];\n fuzzylist: string[];\n tolerance: number;\n name: string;\n version: number;\n}\n\n/**\n * @type EthPhishingDetectResult\n *\n * Interface that describes the result of the `test` method.\n * @property name - Name of the config on which a match was found.\n * @property version - Version of the config on which a match was found.\n * @property result - Whether a domain was detected as a phishing domain. True means an unsafe domain.\n * @property match - The matching fuzzylist origin when a fuzzylist match is found. Returned as undefined for non-fuzzy true results.\n * @property type - The field of the config on which a match was found.\n */\nexport interface EthPhishingDetectResult {\n name?: string;\n version?: string;\n result: boolean;\n match?: string; // Returned as undefined for non-fuzzy true results.\n type: 'all' | 'fuzzy' | 'blocklist' | 'allowlist';\n}\n\n/**\n * @type PhishingConfig\n *\n * Phishing controller configuration\n * @property interval - Polling interval used to fetch new block / approve lists\n */\nexport interface PhishingConfig extends BaseConfig {\n interval: number;\n}\n\n/**\n * @type PhishingState\n *\n * Phishing controller state\n * @property phishing - eth-phishing-detect configuration\n * @property whitelist - array of temporarily-approved origins\n */\nexport interface PhishingState extends BaseState {\n phishing: EthPhishingDetectConfig[];\n whitelist: string[];\n}\n\n/**\n * Controller that passively polls on a set interval for approved and unapproved website origins\n */\nexport class PhishingController extends BaseController<\n PhishingConfig,\n PhishingState\n> {\n private configUrlMetaMask =\n 'https://cdn.jsdelivr.net/gh/MetaMask/eth-phishing-detect@master/src/config.json';\n\n private configUrlPhishFortHotlist = `https://cdn.jsdelivr.net/gh/phishfort/phishfort-lists@master/blacklists/hotlist.json`;\n\n private detector: any;\n\n private handle?: NodeJS.Timer;\n\n /**\n * Name of this controller used during composition\n */\n override name = 'PhishingController';\n\n /**\n * Creates a PhishingController instance.\n *\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n config?: Partial,\n state?: Partial,\n ) {\n super(config, state);\n this.defaultConfig = { interval: 60 * 60 * 1000 };\n this.defaultState = {\n phishing: [\n {\n allowlist: (DEFAULT_PHISHING_RESPONSE as EthPhishingResponse)\n .whitelist,\n blocklist: (DEFAULT_PHISHING_RESPONSE as EthPhishingResponse)\n .blacklist,\n fuzzylist: (DEFAULT_PHISHING_RESPONSE as EthPhishingResponse)\n .fuzzylist,\n tolerance: (DEFAULT_PHISHING_RESPONSE as EthPhishingResponse)\n .tolerance,\n name: `MetaMask`,\n version: (DEFAULT_PHISHING_RESPONSE as EthPhishingResponse).version,\n },\n ],\n whitelist: [],\n };\n this.detector = new PhishingDetector(this.defaultState.phishing);\n this.initialize();\n this.poll();\n }\n\n /**\n * Starts a new polling interval.\n *\n * @param interval - Polling interval used to fetch new approval lists.\n */\n async poll(interval?: number): Promise {\n interval && this.configure({ interval }, false, false);\n this.handle && clearTimeout(this.handle);\n await safelyExecute(() => this.updatePhishingLists());\n this.handle = setTimeout(() => {\n this.poll(this.config.interval);\n }, this.config.interval);\n }\n\n /**\n * Determines if a given origin is unapproved.\n *\n * @param origin - Domain origin of a website.\n * @returns Whether the origin is an unapproved origin.\n */\n test(origin: string): EthPhishingDetectResult {\n const punycodeOrigin = toASCII(origin);\n if (this.state.whitelist.indexOf(punycodeOrigin) !== -1) {\n return { result: false, type: 'all' }; // Same as whitelisted match returned by detector.check(...).\n }\n return this.detector.check(punycodeOrigin);\n }\n\n /**\n * Temporarily marks a given origin as approved.\n *\n * @param origin - The origin to mark as approved.\n */\n bypass(origin: string) {\n const punycodeOrigin = toASCII(origin);\n const { whitelist } = this.state;\n if (whitelist.indexOf(punycodeOrigin) !== -1) {\n return;\n }\n this.update({ whitelist: [...whitelist, punycodeOrigin] });\n }\n\n /**\n * Updates lists of approved and unapproved website origins.\n */\n async updatePhishingLists() {\n if (this.disabled) {\n return;\n }\n\n const configs: EthPhishingDetectConfig[] = [];\n\n const [metamaskConfigLegacy, phishfortHotlist] = await Promise.all([\n await this.queryConfig(this.configUrlMetaMask),\n await this.queryConfig(this.configUrlPhishFortHotlist),\n ]);\n\n // Correctly shaping MetaMask config.\n const metamaskConfig: EthPhishingDetectConfig = {\n allowlist: metamaskConfigLegacy ? metamaskConfigLegacy.whitelist : [],\n blocklist: metamaskConfigLegacy ? metamaskConfigLegacy.blacklist : [],\n fuzzylist: metamaskConfigLegacy ? metamaskConfigLegacy.fuzzylist : [],\n tolerance: metamaskConfigLegacy ? metamaskConfigLegacy.tolerance : 0,\n name: `MetaMask`,\n version: metamaskConfigLegacy ? metamaskConfigLegacy.version : 0,\n };\n if (metamaskConfigLegacy) {\n configs.push(metamaskConfig);\n }\n\n // Correctly shaping PhishFort config.\n const phishfortConfig: EthPhishingDetectConfig = {\n allowlist: [],\n blocklist: (phishfortHotlist || []).filter(\n (i) => !metamaskConfig.blocklist.includes(i),\n ), // Removal of duplicates.\n fuzzylist: [],\n tolerance: 0,\n name: `PhishFort`,\n version: 1,\n };\n if (phishfortHotlist) {\n configs.push(phishfortConfig);\n }\n\n // Do not update if all configs are unavailable.\n if (!configs.length) {\n return;\n }\n\n this.detector = new PhishingDetector(configs);\n this.update({\n phishing: configs,\n });\n }\n\n private async queryConfig(\n input: RequestInfo,\n ): Promise {\n const response = await fetch(input, { cache: 'no-cache' });\n\n switch (response.status) {\n case 200: {\n return await response.json();\n }\n\n default: {\n return null;\n }\n }\n }\n}\n\nexport default PhishingController;\n"]} \ No newline at end of file diff --git a/dist/transaction/TransactionController.d.ts b/dist/transaction/TransactionController.d.ts new file mode 100644 index 0000000000..8152a26aec --- /dev/null +++ b/dist/transaction/TransactionController.d.ts @@ -0,0 +1,460 @@ +/// +import { EventEmitter } from 'events'; +import Common from '@ethereumjs/common'; +import { TypedTransaction } from '@ethereumjs/tx'; +import { BaseController, BaseConfig, BaseState } from '../BaseController'; +import type { NetworkState, NetworkController } from '../network/NetworkController'; +/** + * @type Result + * @property result - Promise resolving to a new transaction hash + * @property transactionMeta - Meta information about this new transaction + */ +export interface Result { + result: Promise; + transactionMeta: TransactionMeta; +} +/** + * @type Fetch All Options + * @property fromBlock - String containing a specific block decimal number + * @property etherscanApiKey - API key to be used to fetch token transactions + */ +export interface FetchAllOptions { + fromBlock?: string; + etherscanApiKey?: string; +} +/** + * @type Transaction + * + * Transaction representation + * @property chainId - Network ID as per EIP-155 + * @property data - Data to pass with this transaction + * @property from - Address to send this transaction from + * @property gas - Gas to send with this transaction + * @property gasPrice - Price of gas with this transaction + * @property gasUsed - Gas used in the transaction + * @property nonce - Unique number to prevent replay attacks + * @property to - Address to send this transaction to + * @property value - Value associated with this transaction + */ +export interface Transaction { + chainId?: number; + data?: string; + from: string; + gas?: string; + gasPrice?: string; + gasUsed?: string; + nonce?: string; + to?: string; + value?: string; + maxFeePerGas?: string; + maxPriorityFeePerGas?: string; + estimatedBaseFee?: string; +} +export interface GasPriceValue { + gasPrice: string; +} +export interface FeeMarketEIP1559Values { + maxFeePerGas: string; + maxPriorityFeePerGas: string; +} +/** + * The status of the transaction. Each status represents the state of the transaction internally + * in the wallet. Some of these correspond with the state of the transaction on the network, but + * some are wallet-specific. + */ +export declare enum TransactionStatus { + approved = "approved", + cancelled = "cancelled", + confirmed = "confirmed", + failed = "failed", + rejected = "rejected", + signed = "signed", + submitted = "submitted", + unapproved = "unapproved" +} +/** + * Options for wallet device. + */ +export declare enum WalletDevice { + MM_MOBILE = "metamask_mobile", + MM_EXTENSION = "metamask_extension", + OTHER = "other_device" +} +declare type TransactionMetaBase = { + isTransfer?: boolean; + transferInformation?: { + symbol: string; + contractAddress: string; + decimals: number; + }; + id: string; + networkID?: string; + chainId?: string; + origin?: string; + rawTransaction?: string; + time: number; + toSmartContract?: boolean; + transaction: Transaction; + transactionHash?: string; + blockNumber?: string; + deviceConfirmedOn?: WalletDevice; + verifiedOnBlockchain?: boolean; +}; +/** + * @type TransactionMeta + * + * TransactionMeta representation + * @property error - Synthesized error information for failed transactions + * @property id - Generated UUID associated with this transaction + * @property networkID - Network code as per EIP-155 for this transaction + * @property origin - Origin this transaction was sent from + * @property deviceConfirmedOn - string to indicate what device the transaction was confirmed + * @property rawTransaction - Hex representation of the underlying transaction + * @property status - String status of this transaction + * @property time - Timestamp associated with this transaction + * @property toSmartContract - Whether transaction recipient is a smart contract + * @property transaction - Underlying Transaction object + * @property transactionHash - Hash of a successful transaction + * @property blockNumber - Number of the block where the transaction has been included + */ +export declare type TransactionMeta = ({ + status: Exclude; +} & TransactionMetaBase) | ({ + status: TransactionStatus.failed; + error: Error; +} & TransactionMetaBase); +/** + * @type EtherscanTransactionMeta + * + * EtherscanTransactionMeta representation + * @property blockNumber - Number of the block where the transaction has been included + * @property timeStamp - Timestamp associated with this transaction + * @property hash - Hash of a successful transaction + * @property nonce - Nonce of the transaction + * @property blockHash - Hash of the block where the transaction has been included + * @property transactionIndex - Etherscan internal index for this transaction + * @property from - Address to send this transaction from + * @property to - Address to send this transaction to + * @property gas - Gas to send with this transaction + * @property gasPrice - Price of gas with this transaction + * @property isError - Synthesized error information for failed transactions + * @property txreceipt_status - Receipt status for this transaction + * @property input - input of the transaction + * @property contractAddress - Address of the contract + * @property cumulativeGasUsed - Amount of gas used + * @property confirmations - Number of confirmations + */ +export interface EtherscanTransactionMeta { + blockNumber: string; + timeStamp: string; + hash: string; + nonce: string; + blockHash: string; + transactionIndex: string; + from: string; + to: string; + value: string; + gas: string; + gasPrice: string; + cumulativeGasUsed: string; + gasUsed: string; + isError: string; + txreceipt_status: string; + input: string; + contractAddress: string; + confirmations: string; + tokenDecimal: string; + tokenSymbol: string; +} +/** + * @type TransactionConfig + * + * Transaction controller configuration + * @property interval - Polling interval used to fetch new currency rate + * @property provider - Provider used to create a new underlying EthQuery instance + * @property sign - Method used to sign transactions + */ +export interface TransactionConfig extends BaseConfig { + interval: number; + sign?: (transaction: Transaction, from: string) => Promise; + txHistoryLimit: number; +} +/** + * @type MethodData + * + * Method data registry object + * @property registryMethod - Registry method raw string + * @property parsedRegistryMethod - Registry method object, containing name and method arguments + */ +export interface MethodData { + registryMethod: string; + parsedRegistryMethod: Record; +} +/** + * @type TransactionState + * + * Transaction controller state + * @property transactions - A list of TransactionMeta objects + * @property methodData - Object containing all known method data information + */ +export interface TransactionState extends BaseState { + transactions: TransactionMeta[]; + methodData: { + [key: string]: MethodData; + }; +} +/** + * Multiplier used to determine a transaction's increased gas fee during cancellation + */ +export declare const CANCEL_RATE = 1.5; +/** + * Multiplier used to determine a transaction's increased gas fee during speed up + */ +export declare const SPEED_UP_RATE = 1.1; +/** + * Controller responsible for submitting and managing transactions + */ +export declare class TransactionController extends BaseController { + private ethQuery; + private registry; + private handle?; + private mutex; + private getNetworkState; + private failTransaction; + private registryLookup; + /** + * Normalizes the transaction information from etherscan + * to be compatible with the TransactionMeta interface. + * + * @param txMeta - The transaction. + * @param currentNetworkID - The current network ID. + * @param currentChainId - The current chain ID. + * @returns The normalized transaction. + */ + private normalizeTx; + private normalizeTokenTx; + /** + * EventEmitter instance used to listen to specific transactional events + */ + hub: EventEmitter; + /** + * Name of this controller used during composition + */ + name: string; + /** + * Method used to sign transactions + */ + sign?: (transaction: TypedTransaction, from: string) => Promise; + /** + * Creates a TransactionController instance. + * + * @param options - The controller options. + * @param options.getNetworkState - Gets the state of the network controller. + * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. + * @param options.getProvider - Returns a provider for the current network. + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor({ getNetworkState, onNetworkStateChange, getProvider, }: { + getNetworkState: () => NetworkState; + onNetworkStateChange: (listener: (state: NetworkState) => void) => void; + getProvider: () => NetworkController['provider']; + }, config?: Partial, state?: Partial); + /** + * Starts a new polling interval. + * + * @param interval - The polling interval used to fetch new transaction statuses. + */ + poll(interval?: number): Promise; + /** + * Handle new method data request. + * + * @param fourBytePrefix - The method prefix. + * @returns The method data object corresponding to the given signature prefix. + */ + handleMethodData(fourBytePrefix: string): Promise; + /** + * Add a new unapproved transaction to state. Parameters will be validated, a + * unique transaction id will be generated, and gas and gasPrice will be calculated + * if not provided. If A `:unapproved` hub event will be emitted once added. + * + * @param transaction - The transaction object to add. + * @param origin - The domain origin to append to the generated TransactionMeta. + * @param deviceConfirmedOn - An enum to indicate what device the transaction was confirmed to append to the generated TransactionMeta. + * @returns Object containing a promise resolving to the transaction hash if approved. + */ + addTransaction(transaction: Transaction, origin?: string, deviceConfirmedOn?: WalletDevice): Promise; + prepareUnsignedEthTx(txParams: Record): TypedTransaction; + /** + * `@ethereumjs/tx` uses `@ethereumjs/common` as a configuration tool for + * specifying which chain, network, hardfork and EIPs to support for + * a transaction. By referencing this configuration, and analyzing the fields + * specified in txParams, @ethereumjs/tx is able to determine which EIP-2718 + * transaction type to use. + * + * @returns {Common} common configuration object + */ + getCommonConfiguration(): Common; + /** + * Approves a transaction and updates it's status in state. If this is not a + * retry transaction, a nonce will be generated. The transaction is signed + * using the sign configuration property, then published to the blockchain. + * A `:finished` hub event is fired after success or failure. + * + * @param transactionID - The ID of the transaction to approve. + */ + approveTransaction(transactionID: string): Promise; + /** + * Cancels a transaction based on its ID by setting its status to "rejected" + * and emitting a `:finished` hub event. + * + * @param transactionID - The ID of the transaction to cancel. + */ + cancelTransaction(transactionID: string): void; + /** + * Attempts to cancel a transaction based on its ID by setting its status to "rejected" + * and emitting a `:finished` hub event. + * + * @param transactionID - The ID of the transaction to cancel. + * @param gasValues - The gas values to use for the cancellation transation. + */ + stopTransaction(transactionID: string, gasValues?: GasPriceValue | FeeMarketEIP1559Values): Promise; + /** + * Attempts to speed up a transaction increasing transaction gasPrice by ten percent. + * + * @param transactionID - The ID of the transaction to speed up. + * @param gasValues - The gas values to use for the speed up transation. + */ + speedUpTransaction(transactionID: string, gasValues?: GasPriceValue | FeeMarketEIP1559Values): Promise; + /** + * Estimates required gas for a given transaction. + * + * @param transaction - The transaction to estimate gas for. + * @returns The gas and gas price. + */ + estimateGas(transaction: Transaction): Promise<{ + gas: string; + gasPrice: any; + }>; + /** + * Check the status of submitted transactions on the network to determine whether they have + * been included in a block. Any that have been included in a block are marked as confirmed. + */ + queryTransactionStatuses(): Promise; + /** + * Updates an existing transaction in state. + * + * @param transactionMeta - The new transaction to store in state. + */ + updateTransaction(transactionMeta: TransactionMeta): void; + /** + * Removes all transactions from state, optionally based on the current network. + * + * @param ignoreNetwork - Determines whether to wipe all transactions, or just those on the + * current network. If `true`, all transactions are wiped. + */ + wipeTransactions(ignoreNetwork?: boolean): void; + /** + * Get transactions from Etherscan for the given address. By default all transactions are + * returned, but the `fromBlock` option can be given to filter just for transactions from a + * specific block onward. + * + * @param address - The address to fetch the transactions for. + * @param opt - Object containing optional data, fromBlock and Etherscan API key. + * @returns The block number of the latest incoming transaction. + */ + fetchAll(address: string, opt?: FetchAllOptions): Promise; + /** + * Trim the amount of transactions that are set on the state. Checks + * if the length of the tx history is longer then desired persistence + * limit and then if it is removes the oldest confirmed or rejected tx. + * Pending or unapproved transactions will not be removed by this + * operation. For safety of presenting a fully functional transaction UI + * representation, this function will not break apart transactions with the + * same nonce, created on the same day, per network. Not accounting for transactions of the same + * nonce, same day and network combo can result in confusing or broken experiences + * in the UI. The transactions are then updated using the BaseController update. + * + * @param transactions - The transactions to be applied to the state. + * @returns The trimmed list of transactions. + */ + private trimTransactionsForState; + /** + * Determines if the transaction is in a final state. + * + * @param status - The transaction status. + * @returns Whether the transaction is in a final state. + */ + private isFinalState; + /** + * Method to verify the state of a transaction using the Blockchain as a source of truth. + * + * @param meta - The local transaction to verify on the blockchain. + * @returns A tuple containing the updated transaction, and whether or not an update was required. + */ + private blockchainTransactionStateReconciler; + /** + * Method to check if a tx has failed according to their receipt + * According to the Web3 docs: + * TRUE if the transaction was successful, FALSE if the EVM reverted the transaction. + * The receipt is not available for pending transactions and returns null. + * + * @param txHash - The transaction hash. + * @returns Whether the transaction has failed. + */ + private checkTxReceiptStatusIsFailed; + /** + * Method to verify the state of transactions using Etherscan as a source of truth. + * + * @param remoteTxs - Transactions to reconcile that are from a remote source. + * @param localTxs - Transactions to reconcile that are local. + * @returns A tuple containing a boolean indicating whether or not an update was required, and the updated transaction. + */ + private etherscanTransactionStateReconciler; + /** + * Get all transactions that are in the remote transactions array + * but not in the local transactions array. + * + * @param remoteTxs - Array of transactions from remote source. + * @param localTxs - Array of transactions stored locally. + * @returns The new transactions. + */ + private getNewTransactions; + /** + * Get all the transactions that are locally outdated with respect + * to a remote source (etherscan or blockchain). The returned array + * contains the transactions with the updated data. + * + * @param remoteTxs - Array of transactions from remote source. + * @param localTxs - Array of transactions stored locally. + * @returns The updated transactions. + */ + private getUpdatedTransactions; + /** + * Verifies if a local transaction is outdated with respect to the remote transaction. + * + * @param remoteTx - The remote transaction from Etherscan. + * @param localTx - The local transaction. + * @returns Whether the transaction is outdated. + */ + private isTransactionOutdated; + /** + * Verifies if the status of a local transaction is outdated with respect to the remote transaction. + * + * @param remoteTxHash - Remote transaction hash. + * @param localTxHash - Local transaction hash. + * @param remoteTxStatus - Remote transaction status. + * @param localTxStatus - Local transaction status. + * @returns Whether the status is outdated. + */ + private isStatusOutdated; + /** + * Verifies if the gas data of a local transaction is outdated with respect to the remote transaction. + * + * @param remoteGasUsed - Remote gas used in the transaction. + * @param localGasUsed - Local gas used in the transaction. + * @returns Whether the gas data is outdated. + */ + private isGasDataOutdated; +} +export default TransactionController; diff --git a/dist/transaction/TransactionController.js b/dist/transaction/TransactionController.js new file mode 100644 index 0000000000..70ab12e0e4 --- /dev/null +++ b/dist/transaction/TransactionController.js @@ -0,0 +1,915 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TransactionController = exports.SPEED_UP_RATE = exports.CANCEL_RATE = exports.WalletDevice = exports.TransactionStatus = void 0; +const events_1 = require("events"); +const ethereumjs_util_1 = require("ethereumjs-util"); +const eth_rpc_errors_1 = require("eth-rpc-errors"); +const eth_method_registry_1 = __importDefault(require("eth-method-registry")); +const eth_query_1 = __importDefault(require("eth-query")); +const common_1 = __importDefault(require("@ethereumjs/common")); +const tx_1 = require("@ethereumjs/tx"); +const uuid_1 = require("uuid"); +const async_mutex_1 = require("async-mutex"); +const BaseController_1 = require("../BaseController"); +const util_1 = require("../util"); +const constants_1 = require("../constants"); +const HARDFORK = 'london'; +/** + * The status of the transaction. Each status represents the state of the transaction internally + * in the wallet. Some of these correspond with the state of the transaction on the network, but + * some are wallet-specific. + */ +var TransactionStatus; +(function (TransactionStatus) { + TransactionStatus["approved"] = "approved"; + TransactionStatus["cancelled"] = "cancelled"; + TransactionStatus["confirmed"] = "confirmed"; + TransactionStatus["failed"] = "failed"; + TransactionStatus["rejected"] = "rejected"; + TransactionStatus["signed"] = "signed"; + TransactionStatus["submitted"] = "submitted"; + TransactionStatus["unapproved"] = "unapproved"; +})(TransactionStatus = exports.TransactionStatus || (exports.TransactionStatus = {})); +/** + * Options for wallet device. + */ +var WalletDevice; +(function (WalletDevice) { + WalletDevice["MM_MOBILE"] = "metamask_mobile"; + WalletDevice["MM_EXTENSION"] = "metamask_extension"; + WalletDevice["OTHER"] = "other_device"; +})(WalletDevice = exports.WalletDevice || (exports.WalletDevice = {})); +/** + * Multiplier used to determine a transaction's increased gas fee during cancellation + */ +exports.CANCEL_RATE = 1.5; +/** + * Multiplier used to determine a transaction's increased gas fee during speed up + */ +exports.SPEED_UP_RATE = 1.1; +/** + * Controller responsible for submitting and managing transactions + */ +class TransactionController extends BaseController_1.BaseController { + /** + * Creates a TransactionController instance. + * + * @param options - The controller options. + * @param options.getNetworkState - Gets the state of the network controller. + * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. + * @param options.getProvider - Returns a provider for the current network. + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor({ getNetworkState, onNetworkStateChange, getProvider, }, config, state) { + super(config, state); + this.mutex = new async_mutex_1.Mutex(); + this.normalizeTokenTx = (txMeta, currentNetworkID, currentChainId) => { + const time = parseInt(txMeta.timeStamp, 10) * 1000; + const { to, from, gas, gasPrice, gasUsed, hash, contractAddress, tokenDecimal, tokenSymbol, value, } = txMeta; + return { + id: (0, uuid_1.v1)({ msecs: time }), + isTransfer: true, + networkID: currentNetworkID, + chainId: currentChainId, + status: TransactionStatus.confirmed, + time, + transaction: { + chainId: 1, + from, + gas, + gasPrice, + gasUsed, + to, + value, + }, + transactionHash: hash, + transferInformation: { + contractAddress, + decimals: Number(tokenDecimal), + symbol: tokenSymbol, + }, + verifiedOnBlockchain: false, + }; + }; + /** + * EventEmitter instance used to listen to specific transactional events + */ + this.hub = new events_1.EventEmitter(); + /** + * Name of this controller used during composition + */ + this.name = 'TransactionController'; + this.defaultConfig = { + interval: 15000, + txHistoryLimit: 40, + }; + this.defaultState = { + methodData: {}, + transactions: [], + }; + this.initialize(); + const provider = getProvider(); + this.getNetworkState = getNetworkState; + this.ethQuery = new eth_query_1.default(provider); + this.registry = new eth_method_registry_1.default({ provider }); + onNetworkStateChange(() => { + const newProvider = getProvider(); + this.ethQuery = new eth_query_1.default(newProvider); + this.registry = new eth_method_registry_1.default({ provider: newProvider }); + }); + this.poll(); + } + failTransaction(transactionMeta, error) { + const newTransactionMeta = Object.assign(Object.assign({}, transactionMeta), { error, status: TransactionStatus.failed }); + this.updateTransaction(newTransactionMeta); + this.hub.emit(`${transactionMeta.id}:finished`, newTransactionMeta); + } + registryLookup(fourBytePrefix) { + return __awaiter(this, void 0, void 0, function* () { + const registryMethod = yield this.registry.lookup(fourBytePrefix); + const parsedRegistryMethod = this.registry.parse(registryMethod); + return { registryMethod, parsedRegistryMethod }; + }); + } + /** + * Normalizes the transaction information from etherscan + * to be compatible with the TransactionMeta interface. + * + * @param txMeta - The transaction. + * @param currentNetworkID - The current network ID. + * @param currentChainId - The current chain ID. + * @returns The normalized transaction. + */ + normalizeTx(txMeta, currentNetworkID, currentChainId) { + const time = parseInt(txMeta.timeStamp, 10) * 1000; + const normalizedTransactionBase = { + blockNumber: txMeta.blockNumber, + id: (0, uuid_1.v1)({ msecs: time }), + networkID: currentNetworkID, + chainId: currentChainId, + time, + transaction: { + data: txMeta.input, + from: txMeta.from, + gas: (0, util_1.BNToHex)(new ethereumjs_util_1.BN(txMeta.gas)), + gasPrice: (0, util_1.BNToHex)(new ethereumjs_util_1.BN(txMeta.gasPrice)), + gasUsed: (0, util_1.BNToHex)(new ethereumjs_util_1.BN(txMeta.gasUsed)), + nonce: (0, util_1.BNToHex)(new ethereumjs_util_1.BN(txMeta.nonce)), + to: txMeta.to, + value: (0, util_1.BNToHex)(new ethereumjs_util_1.BN(txMeta.value)), + }, + transactionHash: txMeta.hash, + verifiedOnBlockchain: false, + }; + /* istanbul ignore else */ + if (txMeta.isError === '0') { + return Object.assign(Object.assign({}, normalizedTransactionBase), { status: TransactionStatus.confirmed }); + } + /* istanbul ignore next */ + return Object.assign(Object.assign({}, normalizedTransactionBase), { error: new Error('Transaction failed'), status: TransactionStatus.failed }); + } + /** + * Starts a new polling interval. + * + * @param interval - The polling interval used to fetch new transaction statuses. + */ + poll(interval) { + return __awaiter(this, void 0, void 0, function* () { + interval && this.configure({ interval }, false, false); + this.handle && clearTimeout(this.handle); + yield (0, util_1.safelyExecute)(() => this.queryTransactionStatuses()); + this.handle = setTimeout(() => { + this.poll(this.config.interval); + }, this.config.interval); + }); + } + /** + * Handle new method data request. + * + * @param fourBytePrefix - The method prefix. + * @returns The method data object corresponding to the given signature prefix. + */ + handleMethodData(fourBytePrefix) { + return __awaiter(this, void 0, void 0, function* () { + const releaseLock = yield this.mutex.acquire(); + try { + const { methodData } = this.state; + const knownMethod = Object.keys(methodData).find((knownFourBytePrefix) => fourBytePrefix === knownFourBytePrefix); + if (knownMethod) { + return methodData[fourBytePrefix]; + } + const registry = yield this.registryLookup(fourBytePrefix); + this.update({ + methodData: Object.assign(Object.assign({}, methodData), { [fourBytePrefix]: registry }), + }); + return registry; + } + finally { + releaseLock(); + } + }); + } + /** + * Add a new unapproved transaction to state. Parameters will be validated, a + * unique transaction id will be generated, and gas and gasPrice will be calculated + * if not provided. If A `:unapproved` hub event will be emitted once added. + * + * @param transaction - The transaction object to add. + * @param origin - The domain origin to append to the generated TransactionMeta. + * @param deviceConfirmedOn - An enum to indicate what device the transaction was confirmed to append to the generated TransactionMeta. + * @returns Object containing a promise resolving to the transaction hash if approved. + */ + addTransaction(transaction, origin, deviceConfirmedOn) { + return __awaiter(this, void 0, void 0, function* () { + const { provider, network } = this.getNetworkState(); + const { transactions } = this.state; + transaction = (0, util_1.normalizeTransaction)(transaction); + (0, util_1.validateTransaction)(transaction); + const transactionMeta = { + id: (0, uuid_1.v1)(), + networkID: network, + chainId: provider.chainId, + origin, + status: TransactionStatus.unapproved, + time: Date.now(), + transaction, + deviceConfirmedOn, + verifiedOnBlockchain: false, + }; + try { + const { gas } = yield this.estimateGas(transaction); + transaction.gas = gas; + } + catch (error) { + this.failTransaction(transactionMeta, error); + return Promise.reject(error); + } + const result = new Promise((resolve, reject) => { + this.hub.once(`${transactionMeta.id}:finished`, (meta) => { + switch (meta.status) { + case TransactionStatus.submitted: + return resolve(meta.transactionHash); + case TransactionStatus.rejected: + return reject(eth_rpc_errors_1.ethErrors.provider.userRejectedRequest('User rejected the transaction')); + case TransactionStatus.cancelled: + return reject(eth_rpc_errors_1.ethErrors.rpc.internal('User cancelled the transaction')); + case TransactionStatus.failed: + return reject(eth_rpc_errors_1.ethErrors.rpc.internal(meta.error.message)); + /* istanbul ignore next */ + default: + return reject(eth_rpc_errors_1.ethErrors.rpc.internal(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(meta)}`)); + } + }); + }); + transactions.push(transactionMeta); + this.update({ transactions: this.trimTransactionsForState(transactions) }); + this.hub.emit(`unapprovedTransaction`, transactionMeta); + return { result, transactionMeta }; + }); + } + prepareUnsignedEthTx(txParams) { + return tx_1.TransactionFactory.fromTxData(txParams, { + common: this.getCommonConfiguration(), + freeze: false, + }); + } + /** + * `@ethereumjs/tx` uses `@ethereumjs/common` as a configuration tool for + * specifying which chain, network, hardfork and EIPs to support for + * a transaction. By referencing this configuration, and analyzing the fields + * specified in txParams, @ethereumjs/tx is able to determine which EIP-2718 + * transaction type to use. + * + * @returns {Common} common configuration object + */ + getCommonConfiguration() { + const { network: networkId, provider: { type: chain, chainId, nickname: name }, } = this.getNetworkState(); + if (chain !== constants_1.RPC) { + return new common_1.default({ chain, hardfork: HARDFORK }); + } + const customChainParams = { + name, + chainId: parseInt(chainId, undefined), + networkId: parseInt(networkId, undefined), + }; + return common_1.default.forCustomChain(constants_1.MAINNET, customChainParams, HARDFORK); + } + /** + * Approves a transaction and updates it's status in state. If this is not a + * retry transaction, a nonce will be generated. The transaction is signed + * using the sign configuration property, then published to the blockchain. + * A `:finished` hub event is fired after success or failure. + * + * @param transactionID - The ID of the transaction to approve. + */ + approveTransaction(transactionID) { + return __awaiter(this, void 0, void 0, function* () { + const { transactions } = this.state; + const releaseLock = yield this.mutex.acquire(); + const { provider } = this.getNetworkState(); + const { chainId: currentChainId } = provider; + const index = transactions.findIndex(({ id }) => transactionID === id); + const transactionMeta = transactions[index]; + const { nonce } = transactionMeta.transaction; + try { + const { from } = transactionMeta.transaction; + if (!this.sign) { + releaseLock(); + this.failTransaction(transactionMeta, new Error('No sign method defined.')); + return; + } + else if (!currentChainId) { + releaseLock(); + this.failTransaction(transactionMeta, new Error('No chainId defined.')); + return; + } + const chainId = parseInt(currentChainId, undefined); + const { approved: status } = TransactionStatus; + const txNonce = nonce || + (yield (0, util_1.query)(this.ethQuery, 'getTransactionCount', [from, 'pending'])); + transactionMeta.status = status; + transactionMeta.transaction.nonce = txNonce; + transactionMeta.transaction.chainId = chainId; + const baseTxParams = Object.assign(Object.assign({}, transactionMeta.transaction), { gasLimit: transactionMeta.transaction.gas, chainId, nonce: txNonce, status }); + const isEIP1559 = (0, util_1.isEIP1559Transaction)(transactionMeta.transaction); + const txParams = isEIP1559 + ? Object.assign(Object.assign({}, baseTxParams), { maxFeePerGas: transactionMeta.transaction.maxFeePerGas, maxPriorityFeePerGas: transactionMeta.transaction.maxPriorityFeePerGas, estimatedBaseFee: transactionMeta.transaction.estimatedBaseFee, + // specify type 2 if maxFeePerGas and maxPriorityFeePerGas are set + type: 2 }) : baseTxParams; + // delete gasPrice if maxFeePerGas and maxPriorityFeePerGas are set + if (isEIP1559) { + delete txParams.gasPrice; + } + const unsignedEthTx = this.prepareUnsignedEthTx(txParams); + const signedTx = yield this.sign(unsignedEthTx, from); + transactionMeta.status = TransactionStatus.signed; + this.updateTransaction(transactionMeta); + const rawTransaction = (0, ethereumjs_util_1.bufferToHex)(signedTx.serialize()); + transactionMeta.rawTransaction = rawTransaction; + this.updateTransaction(transactionMeta); + const transactionHash = yield (0, util_1.query)(this.ethQuery, 'sendRawTransaction', [ + rawTransaction, + ]); + transactionMeta.transactionHash = transactionHash; + transactionMeta.status = TransactionStatus.submitted; + this.updateTransaction(transactionMeta); + this.hub.emit(`${transactionMeta.id}:finished`, transactionMeta); + } + catch (error) { + this.failTransaction(transactionMeta, error); + } + finally { + releaseLock(); + } + }); + } + /** + * Cancels a transaction based on its ID by setting its status to "rejected" + * and emitting a `:finished` hub event. + * + * @param transactionID - The ID of the transaction to cancel. + */ + cancelTransaction(transactionID) { + const transactionMeta = this.state.transactions.find(({ id }) => id === transactionID); + if (!transactionMeta) { + return; + } + transactionMeta.status = TransactionStatus.rejected; + this.hub.emit(`${transactionMeta.id}:finished`, transactionMeta); + const transactions = this.state.transactions.filter(({ id }) => id !== transactionID); + this.update({ transactions: this.trimTransactionsForState(transactions) }); + } + /** + * Attempts to cancel a transaction based on its ID by setting its status to "rejected" + * and emitting a `:finished` hub event. + * + * @param transactionID - The ID of the transaction to cancel. + * @param gasValues - The gas values to use for the cancellation transation. + */ + stopTransaction(transactionID, gasValues) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + if (gasValues) { + (0, util_1.validateGasValues)(gasValues); + } + const transactionMeta = this.state.transactions.find(({ id }) => id === transactionID); + if (!transactionMeta) { + return; + } + if (!this.sign) { + throw new Error('No sign method defined.'); + } + // gasPrice (legacy non EIP1559) + const minGasPrice = (0, util_1.getIncreasedPriceFromExisting)(transactionMeta.transaction.gasPrice, exports.CANCEL_RATE); + const gasPriceFromValues = (0, util_1.isGasPriceValue)(gasValues) && gasValues.gasPrice; + const newGasPrice = (gasPriceFromValues && + (0, util_1.validateMinimumIncrease)(gasPriceFromValues, minGasPrice)) || + minGasPrice; + // maxFeePerGas (EIP1559) + const existingMaxFeePerGas = (_a = transactionMeta.transaction) === null || _a === void 0 ? void 0 : _a.maxFeePerGas; + const minMaxFeePerGas = (0, util_1.getIncreasedPriceFromExisting)(existingMaxFeePerGas, exports.CANCEL_RATE); + const maxFeePerGasValues = (0, util_1.isFeeMarketEIP1559Values)(gasValues) && gasValues.maxFeePerGas; + const newMaxFeePerGas = (maxFeePerGasValues && + (0, util_1.validateMinimumIncrease)(maxFeePerGasValues, minMaxFeePerGas)) || + (existingMaxFeePerGas && minMaxFeePerGas); + // maxPriorityFeePerGas (EIP1559) + const existingMaxPriorityFeePerGas = (_b = transactionMeta.transaction) === null || _b === void 0 ? void 0 : _b.maxPriorityFeePerGas; + const minMaxPriorityFeePerGas = (0, util_1.getIncreasedPriceFromExisting)(existingMaxPriorityFeePerGas, exports.CANCEL_RATE); + const maxPriorityFeePerGasValues = (0, util_1.isFeeMarketEIP1559Values)(gasValues) && gasValues.maxPriorityFeePerGas; + const newMaxPriorityFeePerGas = (maxPriorityFeePerGasValues && + (0, util_1.validateMinimumIncrease)(maxPriorityFeePerGasValues, minMaxPriorityFeePerGas)) || + (existingMaxPriorityFeePerGas && minMaxPriorityFeePerGas); + const txParams = newMaxFeePerGas && newMaxPriorityFeePerGas + ? { + from: transactionMeta.transaction.from, + gasLimit: transactionMeta.transaction.gas, + maxFeePerGas: newMaxFeePerGas, + maxPriorityFeePerGas: newMaxPriorityFeePerGas, + type: 2, + nonce: transactionMeta.transaction.nonce, + to: transactionMeta.transaction.from, + value: '0x0', + } + : { + from: transactionMeta.transaction.from, + gasLimit: transactionMeta.transaction.gas, + gasPrice: newGasPrice, + nonce: transactionMeta.transaction.nonce, + to: transactionMeta.transaction.from, + value: '0x0', + }; + const unsignedEthTx = this.prepareUnsignedEthTx(txParams); + const signedTx = yield this.sign(unsignedEthTx, transactionMeta.transaction.from); + const rawTransaction = (0, ethereumjs_util_1.bufferToHex)(signedTx.serialize()); + yield (0, util_1.query)(this.ethQuery, 'sendRawTransaction', [rawTransaction]); + transactionMeta.status = TransactionStatus.cancelled; + this.hub.emit(`${transactionMeta.id}:finished`, transactionMeta); + }); + } + /** + * Attempts to speed up a transaction increasing transaction gasPrice by ten percent. + * + * @param transactionID - The ID of the transaction to speed up. + * @param gasValues - The gas values to use for the speed up transation. + */ + speedUpTransaction(transactionID, gasValues) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + if (gasValues) { + (0, util_1.validateGasValues)(gasValues); + } + const transactionMeta = this.state.transactions.find(({ id }) => id === transactionID); + /* istanbul ignore next */ + if (!transactionMeta) { + return; + } + /* istanbul ignore next */ + if (!this.sign) { + throw new Error('No sign method defined.'); + } + const { transactions } = this.state; + // gasPrice (legacy non EIP1559) + const minGasPrice = (0, util_1.getIncreasedPriceFromExisting)(transactionMeta.transaction.gasPrice, exports.SPEED_UP_RATE); + const gasPriceFromValues = (0, util_1.isGasPriceValue)(gasValues) && gasValues.gasPrice; + const newGasPrice = (gasPriceFromValues && + (0, util_1.validateMinimumIncrease)(gasPriceFromValues, minGasPrice)) || + minGasPrice; + // maxFeePerGas (EIP1559) + const existingMaxFeePerGas = (_a = transactionMeta.transaction) === null || _a === void 0 ? void 0 : _a.maxFeePerGas; + const minMaxFeePerGas = (0, util_1.getIncreasedPriceFromExisting)(existingMaxFeePerGas, exports.SPEED_UP_RATE); + const maxFeePerGasValues = (0, util_1.isFeeMarketEIP1559Values)(gasValues) && gasValues.maxFeePerGas; + const newMaxFeePerGas = (maxFeePerGasValues && + (0, util_1.validateMinimumIncrease)(maxFeePerGasValues, minMaxFeePerGas)) || + (existingMaxFeePerGas && minMaxFeePerGas); + // maxPriorityFeePerGas (EIP1559) + const existingMaxPriorityFeePerGas = (_b = transactionMeta.transaction) === null || _b === void 0 ? void 0 : _b.maxPriorityFeePerGas; + const minMaxPriorityFeePerGas = (0, util_1.getIncreasedPriceFromExisting)(existingMaxPriorityFeePerGas, exports.SPEED_UP_RATE); + const maxPriorityFeePerGasValues = (0, util_1.isFeeMarketEIP1559Values)(gasValues) && gasValues.maxPriorityFeePerGas; + const newMaxPriorityFeePerGas = (maxPriorityFeePerGasValues && + (0, util_1.validateMinimumIncrease)(maxPriorityFeePerGasValues, minMaxPriorityFeePerGas)) || + (existingMaxPriorityFeePerGas && minMaxPriorityFeePerGas); + const txParams = newMaxFeePerGas && newMaxPriorityFeePerGas + ? Object.assign(Object.assign({}, transactionMeta.transaction), { gasLimit: transactionMeta.transaction.gas, maxFeePerGas: newMaxFeePerGas, maxPriorityFeePerGas: newMaxPriorityFeePerGas, type: 2 }) : Object.assign(Object.assign({}, transactionMeta.transaction), { gasLimit: transactionMeta.transaction.gas, gasPrice: newGasPrice }); + const unsignedEthTx = this.prepareUnsignedEthTx(txParams); + const signedTx = yield this.sign(unsignedEthTx, transactionMeta.transaction.from); + const rawTransaction = (0, ethereumjs_util_1.bufferToHex)(signedTx.serialize()); + const transactionHash = yield (0, util_1.query)(this.ethQuery, 'sendRawTransaction', [ + rawTransaction, + ]); + const baseTransactionMeta = Object.assign(Object.assign({}, transactionMeta), { id: (0, uuid_1.v1)(), time: Date.now(), transactionHash }); + const newTransactionMeta = newMaxFeePerGas && newMaxPriorityFeePerGas + ? Object.assign(Object.assign({}, baseTransactionMeta), { transaction: Object.assign(Object.assign({}, transactionMeta.transaction), { maxFeePerGas: newMaxFeePerGas, maxPriorityFeePerGas: newMaxPriorityFeePerGas }) }) : Object.assign(Object.assign({}, baseTransactionMeta), { transaction: Object.assign(Object.assign({}, transactionMeta.transaction), { gasPrice: newGasPrice }) }); + transactions.push(newTransactionMeta); + this.update({ transactions: this.trimTransactionsForState(transactions) }); + this.hub.emit(`${transactionMeta.id}:speedup`, newTransactionMeta); + }); + } + /** + * Estimates required gas for a given transaction. + * + * @param transaction - The transaction to estimate gas for. + * @returns The gas and gas price. + */ + estimateGas(transaction) { + return __awaiter(this, void 0, void 0, function* () { + const estimatedTransaction = Object.assign({}, transaction); + const { gas, gasPrice: providedGasPrice, to, value, data, } = estimatedTransaction; + const gasPrice = typeof providedGasPrice === 'undefined' + ? yield (0, util_1.query)(this.ethQuery, 'gasPrice') + : providedGasPrice; + const { isCustomNetwork } = this.getNetworkState(); + // 1. If gas is already defined on the transaction, use it + if (typeof gas !== 'undefined') { + return { gas, gasPrice }; + } + const { gasLimit } = yield (0, util_1.query)(this.ethQuery, 'getBlockByNumber', [ + 'latest', + false, + ]); + // 2. If to is not defined or this is not a contract address, and there is no data use 0x5208 / 21000. + // If the newtwork is a custom network then bypass this check and fetch 'estimateGas'. + /* istanbul ignore next */ + const code = to ? yield (0, util_1.query)(this.ethQuery, 'getCode', [to]) : undefined; + /* istanbul ignore next */ + if (!isCustomNetwork && + (!to || (to && !data && (!code || code === '0x')))) { + return { gas: '0x5208', gasPrice }; + } + // if data, should be hex string format + estimatedTransaction.data = !data + ? data + : /* istanbul ignore next */ (0, ethereumjs_util_1.addHexPrefix)(data); + // 3. If this is a contract address, safely estimate gas using RPC + estimatedTransaction.value = + typeof value === 'undefined' ? '0x0' : /* istanbul ignore next */ value; + const gasLimitBN = (0, util_1.hexToBN)(gasLimit); + estimatedTransaction.gas = (0, util_1.BNToHex)((0, util_1.fractionBN)(gasLimitBN, 19, 20)); + const gasHex = yield (0, util_1.query)(this.ethQuery, 'estimateGas', [ + estimatedTransaction, + ]); + // 4. Pad estimated gas without exceeding the most recent block gasLimit. If the network is a + // a custom network then return the eth_estimateGas value. + const gasBN = (0, util_1.hexToBN)(gasHex); + const maxGasBN = gasLimitBN.muln(0.9); + const paddedGasBN = gasBN.muln(1.5); + /* istanbul ignore next */ + if (gasBN.gt(maxGasBN) || isCustomNetwork) { + return { gas: (0, ethereumjs_util_1.addHexPrefix)(gasHex), gasPrice }; + } + /* istanbul ignore next */ + if (paddedGasBN.lt(maxGasBN)) { + return { gas: (0, ethereumjs_util_1.addHexPrefix)((0, util_1.BNToHex)(paddedGasBN)), gasPrice }; + } + return { gas: (0, ethereumjs_util_1.addHexPrefix)((0, util_1.BNToHex)(maxGasBN)), gasPrice }; + }); + } + /** + * Check the status of submitted transactions on the network to determine whether they have + * been included in a block. Any that have been included in a block are marked as confirmed. + */ + queryTransactionStatuses() { + return __awaiter(this, void 0, void 0, function* () { + const { transactions } = this.state; + const { provider, network: currentNetworkID } = this.getNetworkState(); + const { chainId: currentChainId } = provider; + let gotUpdates = false; + yield (0, util_1.safelyExecute)(() => Promise.all(transactions.map((meta, index) => __awaiter(this, void 0, void 0, function* () { + // Using fallback to networkID only when there is no chainId present. + // Should be removed when networkID is completely removed. + const txBelongsToCurrentChain = meta.chainId === currentChainId || + (!meta.chainId && meta.networkID === currentNetworkID); + if (!meta.verifiedOnBlockchain && txBelongsToCurrentChain) { + const [reconciledTx, updateRequired] = yield this.blockchainTransactionStateReconciler(meta); + if (updateRequired) { + transactions[index] = reconciledTx; + gotUpdates = updateRequired; + } + } + })))); + /* istanbul ignore else */ + if (gotUpdates) { + this.update({ + transactions: this.trimTransactionsForState(transactions), + }); + } + }); + } + /** + * Updates an existing transaction in state. + * + * @param transactionMeta - The new transaction to store in state. + */ + updateTransaction(transactionMeta) { + const { transactions } = this.state; + transactionMeta.transaction = (0, util_1.normalizeTransaction)(transactionMeta.transaction); + (0, util_1.validateTransaction)(transactionMeta.transaction); + const index = transactions.findIndex(({ id }) => transactionMeta.id === id); + transactions[index] = transactionMeta; + this.update({ transactions: this.trimTransactionsForState(transactions) }); + } + /** + * Removes all transactions from state, optionally based on the current network. + * + * @param ignoreNetwork - Determines whether to wipe all transactions, or just those on the + * current network. If `true`, all transactions are wiped. + */ + wipeTransactions(ignoreNetwork) { + /* istanbul ignore next */ + if (ignoreNetwork) { + this.update({ transactions: [] }); + return; + } + const { provider, network: currentNetworkID } = this.getNetworkState(); + const { chainId: currentChainId } = provider; + const newTransactions = this.state.transactions.filter(({ networkID, chainId }) => { + // Using fallback to networkID only when there is no chainId present. Should be removed when networkID is completely removed. + const isCurrentNetwork = chainId === currentChainId || + (!chainId && networkID === currentNetworkID); + return !isCurrentNetwork; + }); + this.update({ + transactions: this.trimTransactionsForState(newTransactions), + }); + } + /** + * Get transactions from Etherscan for the given address. By default all transactions are + * returned, but the `fromBlock` option can be given to filter just for transactions from a + * specific block onward. + * + * @param address - The address to fetch the transactions for. + * @param opt - Object containing optional data, fromBlock and Etherscan API key. + * @returns The block number of the latest incoming transaction. + */ + fetchAll(address, opt) { + return __awaiter(this, void 0, void 0, function* () { + const { provider, network: currentNetworkID } = this.getNetworkState(); + const { chainId: currentChainId, type: networkType } = provider; + const { transactions } = this.state; + const supportedNetworkIds = ['1', '3', '4', '42']; + /* istanbul ignore next */ + if (supportedNetworkIds.indexOf(currentNetworkID) === -1) { + return undefined; + } + const [etherscanTxResponse, etherscanTokenResponse] = yield (0, util_1.handleTransactionFetch)(networkType, address, this.config.txHistoryLimit, opt); + const normalizedTxs = etherscanTxResponse.result.map((tx) => this.normalizeTx(tx, currentNetworkID, currentChainId)); + const normalizedTokenTxs = etherscanTokenResponse.result.map((tx) => this.normalizeTokenTx(tx, currentNetworkID, currentChainId)); + const [updateRequired, allTxs] = this.etherscanTransactionStateReconciler([...normalizedTxs, ...normalizedTokenTxs], transactions); + allTxs.sort((a, b) => (a.time < b.time ? -1 : 1)); + let latestIncomingTxBlockNumber; + allTxs.forEach((tx) => __awaiter(this, void 0, void 0, function* () { + /* istanbul ignore next */ + if ( + // Using fallback to networkID only when there is no chainId present. Should be removed when networkID is completely removed. + (tx.chainId === currentChainId || + (!tx.chainId && tx.networkID === currentNetworkID)) && + tx.transaction.to && + tx.transaction.to.toLowerCase() === address.toLowerCase()) { + if (tx.blockNumber && + (!latestIncomingTxBlockNumber || + parseInt(latestIncomingTxBlockNumber, 10) < + parseInt(tx.blockNumber, 10))) { + latestIncomingTxBlockNumber = tx.blockNumber; + } + } + /* istanbul ignore else */ + if (tx.toSmartContract === undefined) { + // If not `to` is a contract deploy, if not `data` is send eth + if (tx.transaction.to && + (!tx.transaction.data || tx.transaction.data !== '0x')) { + const code = yield (0, util_1.query)(this.ethQuery, 'getCode', [ + tx.transaction.to, + ]); + tx.toSmartContract = (0, util_1.isSmartContractCode)(code); + } + else { + tx.toSmartContract = false; + } + } + })); + // Update state only if new transactions were fetched or + // the status or gas data of a transaction has changed + if (updateRequired) { + this.update({ transactions: this.trimTransactionsForState(allTxs) }); + } + return latestIncomingTxBlockNumber; + }); + } + /** + * Trim the amount of transactions that are set on the state. Checks + * if the length of the tx history is longer then desired persistence + * limit and then if it is removes the oldest confirmed or rejected tx. + * Pending or unapproved transactions will not be removed by this + * operation. For safety of presenting a fully functional transaction UI + * representation, this function will not break apart transactions with the + * same nonce, created on the same day, per network. Not accounting for transactions of the same + * nonce, same day and network combo can result in confusing or broken experiences + * in the UI. The transactions are then updated using the BaseController update. + * + * @param transactions - The transactions to be applied to the state. + * @returns The trimmed list of transactions. + */ + trimTransactionsForState(transactions) { + const nonceNetworkSet = new Set(); + const txsToKeep = transactions.reverse().filter((tx) => { + const { chainId, networkID, status, transaction, time } = tx; + if (transaction) { + const key = `${transaction.nonce}-${chainId !== null && chainId !== void 0 ? chainId : networkID}-${new Date(time).toDateString()}`; + if (nonceNetworkSet.has(key)) { + return true; + } + else if (nonceNetworkSet.size < this.config.txHistoryLimit || + !this.isFinalState(status)) { + nonceNetworkSet.add(key); + return true; + } + } + return false; + }); + txsToKeep.reverse(); + return txsToKeep; + } + /** + * Determines if the transaction is in a final state. + * + * @param status - The transaction status. + * @returns Whether the transaction is in a final state. + */ + isFinalState(status) { + return (status === TransactionStatus.rejected || + status === TransactionStatus.confirmed || + status === TransactionStatus.failed || + status === TransactionStatus.cancelled); + } + /** + * Method to verify the state of a transaction using the Blockchain as a source of truth. + * + * @param meta - The local transaction to verify on the blockchain. + * @returns A tuple containing the updated transaction, and whether or not an update was required. + */ + blockchainTransactionStateReconciler(meta) { + return __awaiter(this, void 0, void 0, function* () { + const { status, transactionHash } = meta; + switch (status) { + case TransactionStatus.confirmed: + const txReceipt = yield (0, util_1.query)(this.ethQuery, 'getTransactionReceipt', [ + transactionHash, + ]); + if (!txReceipt) { + return [meta, false]; + } + meta.verifiedOnBlockchain = true; + meta.transaction.gasUsed = txReceipt.gasUsed; + // According to the Web3 docs: + // TRUE if the transaction was successful, FALSE if the EVM reverted the transaction. + if (Number(txReceipt.status) === 0) { + const error = new Error('Transaction failed. The transaction was reversed'); + this.failTransaction(meta, error); + return [meta, false]; + } + return [meta, true]; + case TransactionStatus.submitted: + const txObj = yield (0, util_1.query)(this.ethQuery, 'getTransactionByHash', [ + transactionHash, + ]); + if (!txObj) { + const receiptShowsFailedStatus = yield this.checkTxReceiptStatusIsFailed(transactionHash); + // Case the txObj is evaluated as false, a second check will + // determine if the tx failed or it is pending or confirmed + if (receiptShowsFailedStatus) { + const error = new Error('Transaction failed. The transaction was dropped or replaced by a new one'); + this.failTransaction(meta, error); + } + } + /* istanbul ignore next */ + if (txObj === null || txObj === void 0 ? void 0 : txObj.blockNumber) { + meta.status = TransactionStatus.confirmed; + this.hub.emit(`${meta.id}:confirmed`, meta); + return [meta, true]; + } + return [meta, false]; + default: + return [meta, false]; + } + }); + } + /** + * Method to check if a tx has failed according to their receipt + * According to the Web3 docs: + * TRUE if the transaction was successful, FALSE if the EVM reverted the transaction. + * The receipt is not available for pending transactions and returns null. + * + * @param txHash - The transaction hash. + * @returns Whether the transaction has failed. + */ + checkTxReceiptStatusIsFailed(txHash) { + return __awaiter(this, void 0, void 0, function* () { + const txReceipt = yield (0, util_1.query)(this.ethQuery, 'getTransactionReceipt', [ + txHash, + ]); + if (!txReceipt) { + // Transaction is pending + return false; + } + return Number(txReceipt.status) === 0; + }); + } + /** + * Method to verify the state of transactions using Etherscan as a source of truth. + * + * @param remoteTxs - Transactions to reconcile that are from a remote source. + * @param localTxs - Transactions to reconcile that are local. + * @returns A tuple containing a boolean indicating whether or not an update was required, and the updated transaction. + */ + etherscanTransactionStateReconciler(remoteTxs, localTxs) { + const updatedTxs = this.getUpdatedTransactions(remoteTxs, localTxs); + const newTxs = this.getNewTransactions(remoteTxs, localTxs); + const updatedLocalTxs = localTxs.map((tx) => { + const txIdx = updatedTxs.findIndex(({ transactionHash }) => transactionHash === tx.transactionHash); + return txIdx === -1 ? tx : updatedTxs[txIdx]; + }); + const updateRequired = newTxs.length > 0 || updatedLocalTxs.length > 0; + return [updateRequired, [...newTxs, ...updatedLocalTxs]]; + } + /** + * Get all transactions that are in the remote transactions array + * but not in the local transactions array. + * + * @param remoteTxs - Array of transactions from remote source. + * @param localTxs - Array of transactions stored locally. + * @returns The new transactions. + */ + getNewTransactions(remoteTxs, localTxs) { + return remoteTxs.filter((tx) => { + const alreadyInTransactions = localTxs.find(({ transactionHash }) => transactionHash === tx.transactionHash); + return !alreadyInTransactions; + }); + } + /** + * Get all the transactions that are locally outdated with respect + * to a remote source (etherscan or blockchain). The returned array + * contains the transactions with the updated data. + * + * @param remoteTxs - Array of transactions from remote source. + * @param localTxs - Array of transactions stored locally. + * @returns The updated transactions. + */ + getUpdatedTransactions(remoteTxs, localTxs) { + return remoteTxs.filter((remoteTx) => { + const isTxOutdated = localTxs.find((localTx) => { + return (remoteTx.transactionHash === localTx.transactionHash && + this.isTransactionOutdated(remoteTx, localTx)); + }); + return isTxOutdated; + }); + } + /** + * Verifies if a local transaction is outdated with respect to the remote transaction. + * + * @param remoteTx - The remote transaction from Etherscan. + * @param localTx - The local transaction. + * @returns Whether the transaction is outdated. + */ + isTransactionOutdated(remoteTx, localTx) { + const statusOutdated = this.isStatusOutdated(remoteTx.transactionHash, localTx.transactionHash, remoteTx.status, localTx.status); + const gasDataOutdated = this.isGasDataOutdated(remoteTx.transaction.gasUsed, localTx.transaction.gasUsed); + return statusOutdated || gasDataOutdated; + } + /** + * Verifies if the status of a local transaction is outdated with respect to the remote transaction. + * + * @param remoteTxHash - Remote transaction hash. + * @param localTxHash - Local transaction hash. + * @param remoteTxStatus - Remote transaction status. + * @param localTxStatus - Local transaction status. + * @returns Whether the status is outdated. + */ + isStatusOutdated(remoteTxHash, localTxHash, remoteTxStatus, localTxStatus) { + return remoteTxHash === localTxHash && remoteTxStatus !== localTxStatus; + } + /** + * Verifies if the gas data of a local transaction is outdated with respect to the remote transaction. + * + * @param remoteGasUsed - Remote gas used in the transaction. + * @param localGasUsed - Local gas used in the transaction. + * @returns Whether the gas data is outdated. + */ + isGasDataOutdated(remoteGasUsed, localGasUsed) { + return remoteGasUsed !== localGasUsed; + } +} +exports.TransactionController = TransactionController; +exports.default = TransactionController; +//# sourceMappingURL=TransactionController.js.map \ No newline at end of file diff --git a/dist/transaction/TransactionController.js.map b/dist/transaction/TransactionController.js.map new file mode 100644 index 0000000000..ee17936aea --- /dev/null +++ b/dist/transaction/TransactionController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"TransactionController.js","sourceRoot":"","sources":["../../src/transaction/TransactionController.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from 'events';\nimport { addHexPrefix, bufferToHex, BN } from 'ethereumjs-util';\nimport { ethErrors } from 'eth-rpc-errors';\nimport MethodRegistry from 'eth-method-registry';\nimport EthQuery from 'eth-query';\nimport Common from '@ethereumjs/common';\nimport { TransactionFactory, TypedTransaction } from '@ethereumjs/tx';\nimport { v1 as random } from 'uuid';\nimport { Mutex } from 'async-mutex';\nimport { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport type {\n NetworkState,\n NetworkController,\n} from '../network/NetworkController';\nimport {\n BNToHex,\n fractionBN,\n hexToBN,\n normalizeTransaction,\n safelyExecute,\n validateTransaction,\n isSmartContractCode,\n handleTransactionFetch,\n query,\n getIncreasedPriceFromExisting,\n isEIP1559Transaction,\n isGasPriceValue,\n isFeeMarketEIP1559Values,\n validateGasValues,\n validateMinimumIncrease,\n} from '../util';\nimport { MAINNET, RPC } from '../constants';\n\nconst HARDFORK = 'london';\n\n/**\n * @type Result\n * @property result - Promise resolving to a new transaction hash\n * @property transactionMeta - Meta information about this new transaction\n */\nexport interface Result {\n result: Promise;\n transactionMeta: TransactionMeta;\n}\n\n/**\n * @type Fetch All Options\n * @property fromBlock - String containing a specific block decimal number\n * @property etherscanApiKey - API key to be used to fetch token transactions\n */\nexport interface FetchAllOptions {\n fromBlock?: string;\n etherscanApiKey?: string;\n}\n\n/**\n * @type Transaction\n *\n * Transaction representation\n * @property chainId - Network ID as per EIP-155\n * @property data - Data to pass with this transaction\n * @property from - Address to send this transaction from\n * @property gas - Gas to send with this transaction\n * @property gasPrice - Price of gas with this transaction\n * @property gasUsed - Gas used in the transaction\n * @property nonce - Unique number to prevent replay attacks\n * @property to - Address to send this transaction to\n * @property value - Value associated with this transaction\n */\nexport interface Transaction {\n chainId?: number;\n data?: string;\n from: string;\n gas?: string;\n gasPrice?: string;\n gasUsed?: string;\n nonce?: string;\n to?: string;\n value?: string;\n maxFeePerGas?: string;\n maxPriorityFeePerGas?: string;\n estimatedBaseFee?: string;\n}\n\nexport interface GasPriceValue {\n gasPrice: string;\n}\n\nexport interface FeeMarketEIP1559Values {\n maxFeePerGas: string;\n maxPriorityFeePerGas: string;\n}\n\n/**\n * The status of the transaction. Each status represents the state of the transaction internally\n * in the wallet. Some of these correspond with the state of the transaction on the network, but\n * some are wallet-specific.\n */\nexport enum TransactionStatus {\n approved = 'approved',\n cancelled = 'cancelled',\n confirmed = 'confirmed',\n failed = 'failed',\n rejected = 'rejected',\n signed = 'signed',\n submitted = 'submitted',\n unapproved = 'unapproved',\n}\n\n/**\n * Options for wallet device.\n */\nexport enum WalletDevice {\n MM_MOBILE = 'metamask_mobile',\n MM_EXTENSION = 'metamask_extension',\n OTHER = 'other_device',\n}\n\ntype TransactionMetaBase = {\n isTransfer?: boolean;\n transferInformation?: {\n symbol: string;\n contractAddress: string;\n decimals: number;\n };\n id: string;\n networkID?: string;\n chainId?: string;\n origin?: string;\n rawTransaction?: string;\n time: number;\n toSmartContract?: boolean;\n transaction: Transaction;\n transactionHash?: string;\n blockNumber?: string;\n deviceConfirmedOn?: WalletDevice;\n verifiedOnBlockchain?: boolean;\n};\n\n/**\n * @type TransactionMeta\n *\n * TransactionMeta representation\n * @property error - Synthesized error information for failed transactions\n * @property id - Generated UUID associated with this transaction\n * @property networkID - Network code as per EIP-155 for this transaction\n * @property origin - Origin this transaction was sent from\n * @property deviceConfirmedOn - string to indicate what device the transaction was confirmed\n * @property rawTransaction - Hex representation of the underlying transaction\n * @property status - String status of this transaction\n * @property time - Timestamp associated with this transaction\n * @property toSmartContract - Whether transaction recipient is a smart contract\n * @property transaction - Underlying Transaction object\n * @property transactionHash - Hash of a successful transaction\n * @property blockNumber - Number of the block where the transaction has been included\n */\nexport type TransactionMeta =\n | ({\n status: Exclude;\n } & TransactionMetaBase)\n | ({ status: TransactionStatus.failed; error: Error } & TransactionMetaBase);\n\n/**\n * @type EtherscanTransactionMeta\n *\n * EtherscanTransactionMeta representation\n * @property blockNumber - Number of the block where the transaction has been included\n * @property timeStamp - Timestamp associated with this transaction\n * @property hash - Hash of a successful transaction\n * @property nonce - Nonce of the transaction\n * @property blockHash - Hash of the block where the transaction has been included\n * @property transactionIndex - Etherscan internal index for this transaction\n * @property from - Address to send this transaction from\n * @property to - Address to send this transaction to\n * @property gas - Gas to send with this transaction\n * @property gasPrice - Price of gas with this transaction\n * @property isError - Synthesized error information for failed transactions\n * @property txreceipt_status - Receipt status for this transaction\n * @property input - input of the transaction\n * @property contractAddress - Address of the contract\n * @property cumulativeGasUsed - Amount of gas used\n * @property confirmations - Number of confirmations\n */\nexport interface EtherscanTransactionMeta {\n blockNumber: string;\n timeStamp: string;\n hash: string;\n nonce: string;\n blockHash: string;\n transactionIndex: string;\n from: string;\n to: string;\n value: string;\n gas: string;\n gasPrice: string;\n cumulativeGasUsed: string;\n gasUsed: string;\n isError: string;\n txreceipt_status: string;\n input: string;\n contractAddress: string;\n confirmations: string;\n tokenDecimal: string;\n tokenSymbol: string;\n}\n\n/**\n * @type TransactionConfig\n *\n * Transaction controller configuration\n * @property interval - Polling interval used to fetch new currency rate\n * @property provider - Provider used to create a new underlying EthQuery instance\n * @property sign - Method used to sign transactions\n */\nexport interface TransactionConfig extends BaseConfig {\n interval: number;\n sign?: (transaction: Transaction, from: string) => Promise;\n txHistoryLimit: number;\n}\n\n/**\n * @type MethodData\n *\n * Method data registry object\n * @property registryMethod - Registry method raw string\n * @property parsedRegistryMethod - Registry method object, containing name and method arguments\n */\nexport interface MethodData {\n registryMethod: string;\n parsedRegistryMethod: Record;\n}\n\n/**\n * @type TransactionState\n *\n * Transaction controller state\n * @property transactions - A list of TransactionMeta objects\n * @property methodData - Object containing all known method data information\n */\nexport interface TransactionState extends BaseState {\n transactions: TransactionMeta[];\n methodData: { [key: string]: MethodData };\n}\n\n/**\n * Multiplier used to determine a transaction's increased gas fee during cancellation\n */\nexport const CANCEL_RATE = 1.5;\n\n/**\n * Multiplier used to determine a transaction's increased gas fee during speed up\n */\nexport const SPEED_UP_RATE = 1.1;\n\n/**\n * Controller responsible for submitting and managing transactions\n */\nexport class TransactionController extends BaseController<\n TransactionConfig,\n TransactionState\n> {\n private ethQuery: any;\n\n private registry: any;\n\n private handle?: NodeJS.Timer;\n\n private mutex = new Mutex();\n\n private getNetworkState: () => NetworkState;\n\n private failTransaction(transactionMeta: TransactionMeta, error: Error) {\n const newTransactionMeta = {\n ...transactionMeta,\n error,\n status: TransactionStatus.failed,\n };\n this.updateTransaction(newTransactionMeta);\n this.hub.emit(`${transactionMeta.id}:finished`, newTransactionMeta);\n }\n\n private async registryLookup(fourBytePrefix: string): Promise {\n const registryMethod = await this.registry.lookup(fourBytePrefix);\n const parsedRegistryMethod = this.registry.parse(registryMethod);\n return { registryMethod, parsedRegistryMethod };\n }\n\n /**\n * Normalizes the transaction information from etherscan\n * to be compatible with the TransactionMeta interface.\n *\n * @param txMeta - The transaction.\n * @param currentNetworkID - The current network ID.\n * @param currentChainId - The current chain ID.\n * @returns The normalized transaction.\n */\n private normalizeTx(\n txMeta: EtherscanTransactionMeta,\n currentNetworkID: string,\n currentChainId: string,\n ): TransactionMeta {\n const time = parseInt(txMeta.timeStamp, 10) * 1000;\n const normalizedTransactionBase = {\n blockNumber: txMeta.blockNumber,\n id: random({ msecs: time }),\n networkID: currentNetworkID,\n chainId: currentChainId,\n time,\n transaction: {\n data: txMeta.input,\n from: txMeta.from,\n gas: BNToHex(new BN(txMeta.gas)),\n gasPrice: BNToHex(new BN(txMeta.gasPrice)),\n gasUsed: BNToHex(new BN(txMeta.gasUsed)),\n nonce: BNToHex(new BN(txMeta.nonce)),\n to: txMeta.to,\n value: BNToHex(new BN(txMeta.value)),\n },\n transactionHash: txMeta.hash,\n verifiedOnBlockchain: false,\n };\n\n /* istanbul ignore else */\n if (txMeta.isError === '0') {\n return {\n ...normalizedTransactionBase,\n status: TransactionStatus.confirmed,\n };\n }\n\n /* istanbul ignore next */\n return {\n ...normalizedTransactionBase,\n error: new Error('Transaction failed'),\n status: TransactionStatus.failed,\n };\n }\n\n private normalizeTokenTx = (\n txMeta: EtherscanTransactionMeta,\n currentNetworkID: string,\n currentChainId: string,\n ): TransactionMeta => {\n const time = parseInt(txMeta.timeStamp, 10) * 1000;\n const {\n to,\n from,\n gas,\n gasPrice,\n gasUsed,\n hash,\n contractAddress,\n tokenDecimal,\n tokenSymbol,\n value,\n } = txMeta;\n return {\n id: random({ msecs: time }),\n isTransfer: true,\n networkID: currentNetworkID,\n chainId: currentChainId,\n status: TransactionStatus.confirmed,\n time,\n transaction: {\n chainId: 1,\n from,\n gas,\n gasPrice,\n gasUsed,\n to,\n value,\n },\n transactionHash: hash,\n transferInformation: {\n contractAddress,\n decimals: Number(tokenDecimal),\n symbol: tokenSymbol,\n },\n verifiedOnBlockchain: false,\n };\n };\n\n /**\n * EventEmitter instance used to listen to specific transactional events\n */\n hub = new EventEmitter();\n\n /**\n * Name of this controller used during composition\n */\n override name = 'TransactionController';\n\n /**\n * Method used to sign transactions\n */\n sign?: (\n transaction: TypedTransaction,\n from: string,\n ) => Promise;\n\n /**\n * Creates a TransactionController instance.\n *\n * @param options - The controller options.\n * @param options.getNetworkState - Gets the state of the network controller.\n * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.\n * @param options.getProvider - Returns a provider for the current network.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n getNetworkState,\n onNetworkStateChange,\n getProvider,\n }: {\n getNetworkState: () => NetworkState;\n onNetworkStateChange: (listener: (state: NetworkState) => void) => void;\n getProvider: () => NetworkController['provider'];\n },\n config?: Partial,\n state?: Partial,\n ) {\n super(config, state);\n this.defaultConfig = {\n interval: 15000,\n txHistoryLimit: 40,\n };\n\n this.defaultState = {\n methodData: {},\n transactions: [],\n };\n this.initialize();\n const provider = getProvider();\n this.getNetworkState = getNetworkState;\n this.ethQuery = new EthQuery(provider);\n this.registry = new MethodRegistry({ provider });\n onNetworkStateChange(() => {\n const newProvider = getProvider();\n this.ethQuery = new EthQuery(newProvider);\n this.registry = new MethodRegistry({ provider: newProvider });\n });\n this.poll();\n }\n\n /**\n * Starts a new polling interval.\n *\n * @param interval - The polling interval used to fetch new transaction statuses.\n */\n async poll(interval?: number): Promise {\n interval && this.configure({ interval }, false, false);\n this.handle && clearTimeout(this.handle);\n await safelyExecute(() => this.queryTransactionStatuses());\n this.handle = setTimeout(() => {\n this.poll(this.config.interval);\n }, this.config.interval);\n }\n\n /**\n * Handle new method data request.\n *\n * @param fourBytePrefix - The method prefix.\n * @returns The method data object corresponding to the given signature prefix.\n */\n async handleMethodData(fourBytePrefix: string): Promise {\n const releaseLock = await this.mutex.acquire();\n try {\n const { methodData } = this.state;\n const knownMethod = Object.keys(methodData).find(\n (knownFourBytePrefix) => fourBytePrefix === knownFourBytePrefix,\n );\n if (knownMethod) {\n return methodData[fourBytePrefix];\n }\n const registry = await this.registryLookup(fourBytePrefix);\n this.update({\n methodData: { ...methodData, ...{ [fourBytePrefix]: registry } },\n });\n return registry;\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Add a new unapproved transaction to state. Parameters will be validated, a\n * unique transaction id will be generated, and gas and gasPrice will be calculated\n * if not provided. If A `:unapproved` hub event will be emitted once added.\n *\n * @param transaction - The transaction object to add.\n * @param origin - The domain origin to append to the generated TransactionMeta.\n * @param deviceConfirmedOn - An enum to indicate what device the transaction was confirmed to append to the generated TransactionMeta.\n * @returns Object containing a promise resolving to the transaction hash if approved.\n */\n async addTransaction(\n transaction: Transaction,\n origin?: string,\n deviceConfirmedOn?: WalletDevice,\n ): Promise {\n const { provider, network } = this.getNetworkState();\n const { transactions } = this.state;\n transaction = normalizeTransaction(transaction);\n validateTransaction(transaction);\n\n const transactionMeta: TransactionMeta = {\n id: random(),\n networkID: network,\n chainId: provider.chainId,\n origin,\n status: TransactionStatus.unapproved as TransactionStatus.unapproved,\n time: Date.now(),\n transaction,\n deviceConfirmedOn,\n verifiedOnBlockchain: false,\n };\n\n try {\n const { gas } = await this.estimateGas(transaction);\n transaction.gas = gas;\n } catch (error: any) {\n this.failTransaction(transactionMeta, error);\n return Promise.reject(error);\n }\n\n const result: Promise = new Promise((resolve, reject) => {\n this.hub.once(\n `${transactionMeta.id}:finished`,\n (meta: TransactionMeta) => {\n switch (meta.status) {\n case TransactionStatus.submitted:\n return resolve(meta.transactionHash as string);\n case TransactionStatus.rejected:\n return reject(\n ethErrors.provider.userRejectedRequest(\n 'User rejected the transaction',\n ),\n );\n case TransactionStatus.cancelled:\n return reject(\n ethErrors.rpc.internal('User cancelled the transaction'),\n );\n case TransactionStatus.failed:\n return reject(ethErrors.rpc.internal(meta.error.message));\n /* istanbul ignore next */\n default:\n return reject(\n ethErrors.rpc.internal(\n `MetaMask Tx Signature: Unknown problem: ${JSON.stringify(\n meta,\n )}`,\n ),\n );\n }\n },\n );\n });\n\n transactions.push(transactionMeta);\n this.update({ transactions: this.trimTransactionsForState(transactions) });\n this.hub.emit(`unapprovedTransaction`, transactionMeta);\n return { result, transactionMeta };\n }\n\n prepareUnsignedEthTx(txParams: Record): TypedTransaction {\n return TransactionFactory.fromTxData(txParams, {\n common: this.getCommonConfiguration(),\n freeze: false,\n });\n }\n\n /**\n * `@ethereumjs/tx` uses `@ethereumjs/common` as a configuration tool for\n * specifying which chain, network, hardfork and EIPs to support for\n * a transaction. By referencing this configuration, and analyzing the fields\n * specified in txParams, @ethereumjs/tx is able to determine which EIP-2718\n * transaction type to use.\n *\n * @returns {Common} common configuration object\n */\n\n getCommonConfiguration(): Common {\n const {\n network: networkId,\n provider: { type: chain, chainId, nickname: name },\n } = this.getNetworkState();\n\n if (chain !== RPC) {\n return new Common({ chain, hardfork: HARDFORK });\n }\n\n const customChainParams = {\n name,\n chainId: parseInt(chainId, undefined),\n networkId: parseInt(networkId, undefined),\n };\n\n return Common.forCustomChain(MAINNET, customChainParams, HARDFORK);\n }\n\n /**\n * Approves a transaction and updates it's status in state. If this is not a\n * retry transaction, a nonce will be generated. The transaction is signed\n * using the sign configuration property, then published to the blockchain.\n * A `:finished` hub event is fired after success or failure.\n *\n * @param transactionID - The ID of the transaction to approve.\n */\n async approveTransaction(transactionID: string) {\n const { transactions } = this.state;\n const releaseLock = await this.mutex.acquire();\n const { provider } = this.getNetworkState();\n const { chainId: currentChainId } = provider;\n const index = transactions.findIndex(({ id }) => transactionID === id);\n const transactionMeta = transactions[index];\n const { nonce } = transactionMeta.transaction;\n\n try {\n const { from } = transactionMeta.transaction;\n if (!this.sign) {\n releaseLock();\n this.failTransaction(\n transactionMeta,\n new Error('No sign method defined.'),\n );\n return;\n } else if (!currentChainId) {\n releaseLock();\n this.failTransaction(transactionMeta, new Error('No chainId defined.'));\n return;\n }\n\n const chainId = parseInt(currentChainId, undefined);\n const { approved: status } = TransactionStatus;\n\n const txNonce =\n nonce ||\n (await query(this.ethQuery, 'getTransactionCount', [from, 'pending']));\n\n transactionMeta.status = status;\n transactionMeta.transaction.nonce = txNonce;\n transactionMeta.transaction.chainId = chainId;\n\n const baseTxParams = {\n ...transactionMeta.transaction,\n gasLimit: transactionMeta.transaction.gas,\n chainId,\n nonce: txNonce,\n status,\n };\n\n const isEIP1559 = isEIP1559Transaction(transactionMeta.transaction);\n\n const txParams = isEIP1559\n ? {\n ...baseTxParams,\n maxFeePerGas: transactionMeta.transaction.maxFeePerGas,\n maxPriorityFeePerGas:\n transactionMeta.transaction.maxPriorityFeePerGas,\n estimatedBaseFee: transactionMeta.transaction.estimatedBaseFee,\n // specify type 2 if maxFeePerGas and maxPriorityFeePerGas are set\n type: 2,\n }\n : baseTxParams;\n\n // delete gasPrice if maxFeePerGas and maxPriorityFeePerGas are set\n if (isEIP1559) {\n delete txParams.gasPrice;\n }\n\n const unsignedEthTx = this.prepareUnsignedEthTx(txParams);\n const signedTx = await this.sign(unsignedEthTx, from);\n transactionMeta.status = TransactionStatus.signed;\n this.updateTransaction(transactionMeta);\n const rawTransaction = bufferToHex(signedTx.serialize());\n\n transactionMeta.rawTransaction = rawTransaction;\n this.updateTransaction(transactionMeta);\n const transactionHash = await query(this.ethQuery, 'sendRawTransaction', [\n rawTransaction,\n ]);\n transactionMeta.transactionHash = transactionHash;\n transactionMeta.status = TransactionStatus.submitted;\n this.updateTransaction(transactionMeta);\n this.hub.emit(`${transactionMeta.id}:finished`, transactionMeta);\n } catch (error: any) {\n this.failTransaction(transactionMeta, error);\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Cancels a transaction based on its ID by setting its status to \"rejected\"\n * and emitting a `:finished` hub event.\n *\n * @param transactionID - The ID of the transaction to cancel.\n */\n cancelTransaction(transactionID: string) {\n const transactionMeta = this.state.transactions.find(\n ({ id }) => id === transactionID,\n );\n if (!transactionMeta) {\n return;\n }\n transactionMeta.status = TransactionStatus.rejected;\n this.hub.emit(`${transactionMeta.id}:finished`, transactionMeta);\n const transactions = this.state.transactions.filter(\n ({ id }) => id !== transactionID,\n );\n this.update({ transactions: this.trimTransactionsForState(transactions) });\n }\n\n /**\n * Attempts to cancel a transaction based on its ID by setting its status to \"rejected\"\n * and emitting a `:finished` hub event.\n *\n * @param transactionID - The ID of the transaction to cancel.\n * @param gasValues - The gas values to use for the cancellation transation.\n */\n async stopTransaction(\n transactionID: string,\n gasValues?: GasPriceValue | FeeMarketEIP1559Values,\n ) {\n if (gasValues) {\n validateGasValues(gasValues);\n }\n const transactionMeta = this.state.transactions.find(\n ({ id }) => id === transactionID,\n );\n if (!transactionMeta) {\n return;\n }\n\n if (!this.sign) {\n throw new Error('No sign method defined.');\n }\n\n // gasPrice (legacy non EIP1559)\n const minGasPrice = getIncreasedPriceFromExisting(\n transactionMeta.transaction.gasPrice,\n CANCEL_RATE,\n );\n\n const gasPriceFromValues = isGasPriceValue(gasValues) && gasValues.gasPrice;\n\n const newGasPrice =\n (gasPriceFromValues &&\n validateMinimumIncrease(gasPriceFromValues, minGasPrice)) ||\n minGasPrice;\n\n // maxFeePerGas (EIP1559)\n const existingMaxFeePerGas = transactionMeta.transaction?.maxFeePerGas;\n const minMaxFeePerGas = getIncreasedPriceFromExisting(\n existingMaxFeePerGas,\n CANCEL_RATE,\n );\n const maxFeePerGasValues =\n isFeeMarketEIP1559Values(gasValues) && gasValues.maxFeePerGas;\n const newMaxFeePerGas =\n (maxFeePerGasValues &&\n validateMinimumIncrease(maxFeePerGasValues, minMaxFeePerGas)) ||\n (existingMaxFeePerGas && minMaxFeePerGas);\n\n // maxPriorityFeePerGas (EIP1559)\n const existingMaxPriorityFeePerGas =\n transactionMeta.transaction?.maxPriorityFeePerGas;\n const minMaxPriorityFeePerGas = getIncreasedPriceFromExisting(\n existingMaxPriorityFeePerGas,\n CANCEL_RATE,\n );\n const maxPriorityFeePerGasValues =\n isFeeMarketEIP1559Values(gasValues) && gasValues.maxPriorityFeePerGas;\n const newMaxPriorityFeePerGas =\n (maxPriorityFeePerGasValues &&\n validateMinimumIncrease(\n maxPriorityFeePerGasValues,\n minMaxPriorityFeePerGas,\n )) ||\n (existingMaxPriorityFeePerGas && minMaxPriorityFeePerGas);\n\n const txParams =\n newMaxFeePerGas && newMaxPriorityFeePerGas\n ? {\n from: transactionMeta.transaction.from,\n gasLimit: transactionMeta.transaction.gas,\n maxFeePerGas: newMaxFeePerGas,\n maxPriorityFeePerGas: newMaxPriorityFeePerGas,\n type: 2,\n nonce: transactionMeta.transaction.nonce,\n to: transactionMeta.transaction.from,\n value: '0x0',\n }\n : {\n from: transactionMeta.transaction.from,\n gasLimit: transactionMeta.transaction.gas,\n gasPrice: newGasPrice,\n nonce: transactionMeta.transaction.nonce,\n to: transactionMeta.transaction.from,\n value: '0x0',\n };\n\n const unsignedEthTx = this.prepareUnsignedEthTx(txParams);\n\n const signedTx = await this.sign(\n unsignedEthTx,\n transactionMeta.transaction.from,\n );\n const rawTransaction = bufferToHex(signedTx.serialize());\n await query(this.ethQuery, 'sendRawTransaction', [rawTransaction]);\n transactionMeta.status = TransactionStatus.cancelled;\n this.hub.emit(`${transactionMeta.id}:finished`, transactionMeta);\n }\n\n /**\n * Attempts to speed up a transaction increasing transaction gasPrice by ten percent.\n *\n * @param transactionID - The ID of the transaction to speed up.\n * @param gasValues - The gas values to use for the speed up transation.\n */\n async speedUpTransaction(\n transactionID: string,\n gasValues?: GasPriceValue | FeeMarketEIP1559Values,\n ) {\n if (gasValues) {\n validateGasValues(gasValues);\n }\n const transactionMeta = this.state.transactions.find(\n ({ id }) => id === transactionID,\n );\n /* istanbul ignore next */\n if (!transactionMeta) {\n return;\n }\n\n /* istanbul ignore next */\n if (!this.sign) {\n throw new Error('No sign method defined.');\n }\n\n const { transactions } = this.state;\n\n // gasPrice (legacy non EIP1559)\n const minGasPrice = getIncreasedPriceFromExisting(\n transactionMeta.transaction.gasPrice,\n SPEED_UP_RATE,\n );\n\n const gasPriceFromValues = isGasPriceValue(gasValues) && gasValues.gasPrice;\n\n const newGasPrice =\n (gasPriceFromValues &&\n validateMinimumIncrease(gasPriceFromValues, minGasPrice)) ||\n minGasPrice;\n\n // maxFeePerGas (EIP1559)\n const existingMaxFeePerGas = transactionMeta.transaction?.maxFeePerGas;\n const minMaxFeePerGas = getIncreasedPriceFromExisting(\n existingMaxFeePerGas,\n SPEED_UP_RATE,\n );\n const maxFeePerGasValues =\n isFeeMarketEIP1559Values(gasValues) && gasValues.maxFeePerGas;\n const newMaxFeePerGas =\n (maxFeePerGasValues &&\n validateMinimumIncrease(maxFeePerGasValues, minMaxFeePerGas)) ||\n (existingMaxFeePerGas && minMaxFeePerGas);\n\n // maxPriorityFeePerGas (EIP1559)\n const existingMaxPriorityFeePerGas =\n transactionMeta.transaction?.maxPriorityFeePerGas;\n const minMaxPriorityFeePerGas = getIncreasedPriceFromExisting(\n existingMaxPriorityFeePerGas,\n SPEED_UP_RATE,\n );\n const maxPriorityFeePerGasValues =\n isFeeMarketEIP1559Values(gasValues) && gasValues.maxPriorityFeePerGas;\n const newMaxPriorityFeePerGas =\n (maxPriorityFeePerGasValues &&\n validateMinimumIncrease(\n maxPriorityFeePerGasValues,\n minMaxPriorityFeePerGas,\n )) ||\n (existingMaxPriorityFeePerGas && minMaxPriorityFeePerGas);\n\n const txParams =\n newMaxFeePerGas && newMaxPriorityFeePerGas\n ? {\n ...transactionMeta.transaction,\n gasLimit: transactionMeta.transaction.gas,\n maxFeePerGas: newMaxFeePerGas,\n maxPriorityFeePerGas: newMaxPriorityFeePerGas,\n type: 2,\n }\n : {\n ...transactionMeta.transaction,\n gasLimit: transactionMeta.transaction.gas,\n gasPrice: newGasPrice,\n };\n\n const unsignedEthTx = this.prepareUnsignedEthTx(txParams);\n\n const signedTx = await this.sign(\n unsignedEthTx,\n transactionMeta.transaction.from,\n );\n const rawTransaction = bufferToHex(signedTx.serialize());\n const transactionHash = await query(this.ethQuery, 'sendRawTransaction', [\n rawTransaction,\n ]);\n const baseTransactionMeta = {\n ...transactionMeta,\n id: random(),\n time: Date.now(),\n transactionHash,\n };\n const newTransactionMeta =\n newMaxFeePerGas && newMaxPriorityFeePerGas\n ? {\n ...baseTransactionMeta,\n transaction: {\n ...transactionMeta.transaction,\n maxFeePerGas: newMaxFeePerGas,\n maxPriorityFeePerGas: newMaxPriorityFeePerGas,\n },\n }\n : {\n ...baseTransactionMeta,\n transaction: {\n ...transactionMeta.transaction,\n gasPrice: newGasPrice,\n },\n };\n transactions.push(newTransactionMeta);\n this.update({ transactions: this.trimTransactionsForState(transactions) });\n this.hub.emit(`${transactionMeta.id}:speedup`, newTransactionMeta);\n }\n\n /**\n * Estimates required gas for a given transaction.\n *\n * @param transaction - The transaction to estimate gas for.\n * @returns The gas and gas price.\n */\n async estimateGas(transaction: Transaction) {\n const estimatedTransaction = { ...transaction };\n const {\n gas,\n gasPrice: providedGasPrice,\n to,\n value,\n data,\n } = estimatedTransaction;\n const gasPrice =\n typeof providedGasPrice === 'undefined'\n ? await query(this.ethQuery, 'gasPrice')\n : providedGasPrice;\n const { isCustomNetwork } = this.getNetworkState();\n // 1. If gas is already defined on the transaction, use it\n if (typeof gas !== 'undefined') {\n return { gas, gasPrice };\n }\n const { gasLimit } = await query(this.ethQuery, 'getBlockByNumber', [\n 'latest',\n false,\n ]);\n\n // 2. If to is not defined or this is not a contract address, and there is no data use 0x5208 / 21000.\n // If the newtwork is a custom network then bypass this check and fetch 'estimateGas'.\n /* istanbul ignore next */\n const code = to ? await query(this.ethQuery, 'getCode', [to]) : undefined;\n /* istanbul ignore next */\n if (\n !isCustomNetwork &&\n (!to || (to && !data && (!code || code === '0x')))\n ) {\n return { gas: '0x5208', gasPrice };\n }\n\n // if data, should be hex string format\n estimatedTransaction.data = !data\n ? data\n : /* istanbul ignore next */ addHexPrefix(data);\n\n // 3. If this is a contract address, safely estimate gas using RPC\n estimatedTransaction.value =\n typeof value === 'undefined' ? '0x0' : /* istanbul ignore next */ value;\n const gasLimitBN = hexToBN(gasLimit);\n estimatedTransaction.gas = BNToHex(fractionBN(gasLimitBN, 19, 20));\n const gasHex = await query(this.ethQuery, 'estimateGas', [\n estimatedTransaction,\n ]);\n\n // 4. Pad estimated gas without exceeding the most recent block gasLimit. If the network is a\n // a custom network then return the eth_estimateGas value.\n const gasBN = hexToBN(gasHex);\n const maxGasBN = gasLimitBN.muln(0.9);\n const paddedGasBN = gasBN.muln(1.5);\n /* istanbul ignore next */\n if (gasBN.gt(maxGasBN) || isCustomNetwork) {\n return { gas: addHexPrefix(gasHex), gasPrice };\n }\n\n /* istanbul ignore next */\n if (paddedGasBN.lt(maxGasBN)) {\n return { gas: addHexPrefix(BNToHex(paddedGasBN)), gasPrice };\n }\n return { gas: addHexPrefix(BNToHex(maxGasBN)), gasPrice };\n }\n\n /**\n * Check the status of submitted transactions on the network to determine whether they have\n * been included in a block. Any that have been included in a block are marked as confirmed.\n */\n async queryTransactionStatuses() {\n const { transactions } = this.state;\n const { provider, network: currentNetworkID } = this.getNetworkState();\n const { chainId: currentChainId } = provider;\n let gotUpdates = false;\n await safelyExecute(() =>\n Promise.all(\n transactions.map(async (meta, index) => {\n // Using fallback to networkID only when there is no chainId present.\n // Should be removed when networkID is completely removed.\n const txBelongsToCurrentChain =\n meta.chainId === currentChainId ||\n (!meta.chainId && meta.networkID === currentNetworkID);\n\n if (!meta.verifiedOnBlockchain && txBelongsToCurrentChain) {\n const [reconciledTx, updateRequired] =\n await this.blockchainTransactionStateReconciler(meta);\n if (updateRequired) {\n transactions[index] = reconciledTx;\n gotUpdates = updateRequired;\n }\n }\n }),\n ),\n );\n\n /* istanbul ignore else */\n if (gotUpdates) {\n this.update({\n transactions: this.trimTransactionsForState(transactions),\n });\n }\n }\n\n /**\n * Updates an existing transaction in state.\n *\n * @param transactionMeta - The new transaction to store in state.\n */\n updateTransaction(transactionMeta: TransactionMeta) {\n const { transactions } = this.state;\n transactionMeta.transaction = normalizeTransaction(\n transactionMeta.transaction,\n );\n validateTransaction(transactionMeta.transaction);\n const index = transactions.findIndex(({ id }) => transactionMeta.id === id);\n transactions[index] = transactionMeta;\n this.update({ transactions: this.trimTransactionsForState(transactions) });\n }\n\n /**\n * Removes all transactions from state, optionally based on the current network.\n *\n * @param ignoreNetwork - Determines whether to wipe all transactions, or just those on the\n * current network. If `true`, all transactions are wiped.\n */\n wipeTransactions(ignoreNetwork?: boolean) {\n /* istanbul ignore next */\n if (ignoreNetwork) {\n this.update({ transactions: [] });\n return;\n }\n const { provider, network: currentNetworkID } = this.getNetworkState();\n const { chainId: currentChainId } = provider;\n const newTransactions = this.state.transactions.filter(\n ({ networkID, chainId }) => {\n // Using fallback to networkID only when there is no chainId present. Should be removed when networkID is completely removed.\n const isCurrentNetwork =\n chainId === currentChainId ||\n (!chainId && networkID === currentNetworkID);\n return !isCurrentNetwork;\n },\n );\n\n this.update({\n transactions: this.trimTransactionsForState(newTransactions),\n });\n }\n\n /**\n * Get transactions from Etherscan for the given address. By default all transactions are\n * returned, but the `fromBlock` option can be given to filter just for transactions from a\n * specific block onward.\n *\n * @param address - The address to fetch the transactions for.\n * @param opt - Object containing optional data, fromBlock and Etherscan API key.\n * @returns The block number of the latest incoming transaction.\n */\n async fetchAll(\n address: string,\n opt?: FetchAllOptions,\n ): Promise {\n const { provider, network: currentNetworkID } = this.getNetworkState();\n const { chainId: currentChainId, type: networkType } = provider;\n const { transactions } = this.state;\n\n const supportedNetworkIds = ['1', '3', '4', '42'];\n /* istanbul ignore next */\n if (supportedNetworkIds.indexOf(currentNetworkID) === -1) {\n return undefined;\n }\n\n const [etherscanTxResponse, etherscanTokenResponse] =\n await handleTransactionFetch(\n networkType,\n address,\n this.config.txHistoryLimit,\n opt,\n );\n\n const normalizedTxs = etherscanTxResponse.result.map(\n (tx: EtherscanTransactionMeta) =>\n this.normalizeTx(tx, currentNetworkID, currentChainId),\n );\n const normalizedTokenTxs = etherscanTokenResponse.result.map(\n (tx: EtherscanTransactionMeta) =>\n this.normalizeTokenTx(tx, currentNetworkID, currentChainId),\n );\n\n const [updateRequired, allTxs] = this.etherscanTransactionStateReconciler(\n [...normalizedTxs, ...normalizedTokenTxs],\n transactions,\n );\n\n allTxs.sort((a, b) => (a.time < b.time ? -1 : 1));\n\n let latestIncomingTxBlockNumber: string | undefined;\n allTxs.forEach(async (tx) => {\n /* istanbul ignore next */\n if (\n // Using fallback to networkID only when there is no chainId present. Should be removed when networkID is completely removed.\n (tx.chainId === currentChainId ||\n (!tx.chainId && tx.networkID === currentNetworkID)) &&\n tx.transaction.to &&\n tx.transaction.to.toLowerCase() === address.toLowerCase()\n ) {\n if (\n tx.blockNumber &&\n (!latestIncomingTxBlockNumber ||\n parseInt(latestIncomingTxBlockNumber, 10) <\n parseInt(tx.blockNumber, 10))\n ) {\n latestIncomingTxBlockNumber = tx.blockNumber;\n }\n }\n\n /* istanbul ignore else */\n if (tx.toSmartContract === undefined) {\n // If not `to` is a contract deploy, if not `data` is send eth\n if (\n tx.transaction.to &&\n (!tx.transaction.data || tx.transaction.data !== '0x')\n ) {\n const code = await query(this.ethQuery, 'getCode', [\n tx.transaction.to,\n ]);\n tx.toSmartContract = isSmartContractCode(code);\n } else {\n tx.toSmartContract = false;\n }\n }\n });\n\n // Update state only if new transactions were fetched or\n // the status or gas data of a transaction has changed\n if (updateRequired) {\n this.update({ transactions: this.trimTransactionsForState(allTxs) });\n }\n return latestIncomingTxBlockNumber;\n }\n\n /**\n * Trim the amount of transactions that are set on the state. Checks\n * if the length of the tx history is longer then desired persistence\n * limit and then if it is removes the oldest confirmed or rejected tx.\n * Pending or unapproved transactions will not be removed by this\n * operation. For safety of presenting a fully functional transaction UI\n * representation, this function will not break apart transactions with the\n * same nonce, created on the same day, per network. Not accounting for transactions of the same\n * nonce, same day and network combo can result in confusing or broken experiences\n * in the UI. The transactions are then updated using the BaseController update.\n *\n * @param transactions - The transactions to be applied to the state.\n * @returns The trimmed list of transactions.\n */\n private trimTransactionsForState(\n transactions: TransactionMeta[],\n ): TransactionMeta[] {\n const nonceNetworkSet = new Set();\n const txsToKeep = transactions.reverse().filter((tx) => {\n const { chainId, networkID, status, transaction, time } = tx;\n if (transaction) {\n const key = `${transaction.nonce}-${chainId ?? networkID}-${new Date(\n time,\n ).toDateString()}`;\n if (nonceNetworkSet.has(key)) {\n return true;\n } else if (\n nonceNetworkSet.size < this.config.txHistoryLimit ||\n !this.isFinalState(status)\n ) {\n nonceNetworkSet.add(key);\n return true;\n }\n }\n return false;\n });\n txsToKeep.reverse();\n return txsToKeep;\n }\n\n /**\n * Determines if the transaction is in a final state.\n *\n * @param status - The transaction status.\n * @returns Whether the transaction is in a final state.\n */\n private isFinalState(status: TransactionStatus): boolean {\n return (\n status === TransactionStatus.rejected ||\n status === TransactionStatus.confirmed ||\n status === TransactionStatus.failed ||\n status === TransactionStatus.cancelled\n );\n }\n\n /**\n * Method to verify the state of a transaction using the Blockchain as a source of truth.\n *\n * @param meta - The local transaction to verify on the blockchain.\n * @returns A tuple containing the updated transaction, and whether or not an update was required.\n */\n private async blockchainTransactionStateReconciler(\n meta: TransactionMeta,\n ): Promise<[TransactionMeta, boolean]> {\n const { status, transactionHash } = meta;\n switch (status) {\n case TransactionStatus.confirmed:\n const txReceipt = await query(this.ethQuery, 'getTransactionReceipt', [\n transactionHash,\n ]);\n\n if (!txReceipt) {\n return [meta, false];\n }\n\n meta.verifiedOnBlockchain = true;\n meta.transaction.gasUsed = txReceipt.gasUsed;\n\n // According to the Web3 docs:\n // TRUE if the transaction was successful, FALSE if the EVM reverted the transaction.\n if (Number(txReceipt.status) === 0) {\n const error: Error = new Error(\n 'Transaction failed. The transaction was reversed',\n );\n this.failTransaction(meta, error);\n return [meta, false];\n }\n\n return [meta, true];\n case TransactionStatus.submitted:\n const txObj = await query(this.ethQuery, 'getTransactionByHash', [\n transactionHash,\n ]);\n\n if (!txObj) {\n const receiptShowsFailedStatus =\n await this.checkTxReceiptStatusIsFailed(transactionHash);\n\n // Case the txObj is evaluated as false, a second check will\n // determine if the tx failed or it is pending or confirmed\n if (receiptShowsFailedStatus) {\n const error: Error = new Error(\n 'Transaction failed. The transaction was dropped or replaced by a new one',\n );\n this.failTransaction(meta, error);\n }\n }\n\n /* istanbul ignore next */\n if (txObj?.blockNumber) {\n meta.status = TransactionStatus.confirmed;\n this.hub.emit(`${meta.id}:confirmed`, meta);\n return [meta, true];\n }\n\n return [meta, false];\n default:\n return [meta, false];\n }\n }\n\n /**\n * Method to check if a tx has failed according to their receipt\n * According to the Web3 docs:\n * TRUE if the transaction was successful, FALSE if the EVM reverted the transaction.\n * The receipt is not available for pending transactions and returns null.\n *\n * @param txHash - The transaction hash.\n * @returns Whether the transaction has failed.\n */\n private async checkTxReceiptStatusIsFailed(\n txHash: string | undefined,\n ): Promise {\n const txReceipt = await query(this.ethQuery, 'getTransactionReceipt', [\n txHash,\n ]);\n if (!txReceipt) {\n // Transaction is pending\n return false;\n }\n return Number(txReceipt.status) === 0;\n }\n\n /**\n * Method to verify the state of transactions using Etherscan as a source of truth.\n *\n * @param remoteTxs - Transactions to reconcile that are from a remote source.\n * @param localTxs - Transactions to reconcile that are local.\n * @returns A tuple containing a boolean indicating whether or not an update was required, and the updated transaction.\n */\n private etherscanTransactionStateReconciler(\n remoteTxs: TransactionMeta[],\n localTxs: TransactionMeta[],\n ): [boolean, TransactionMeta[]] {\n const updatedTxs: TransactionMeta[] = this.getUpdatedTransactions(\n remoteTxs,\n localTxs,\n );\n\n const newTxs: TransactionMeta[] = this.getNewTransactions(\n remoteTxs,\n localTxs,\n );\n\n const updatedLocalTxs = localTxs.map((tx: TransactionMeta) => {\n const txIdx = updatedTxs.findIndex(\n ({ transactionHash }) => transactionHash === tx.transactionHash,\n );\n return txIdx === -1 ? tx : updatedTxs[txIdx];\n });\n\n const updateRequired = newTxs.length > 0 || updatedLocalTxs.length > 0;\n\n return [updateRequired, [...newTxs, ...updatedLocalTxs]];\n }\n\n /**\n * Get all transactions that are in the remote transactions array\n * but not in the local transactions array.\n *\n * @param remoteTxs - Array of transactions from remote source.\n * @param localTxs - Array of transactions stored locally.\n * @returns The new transactions.\n */\n private getNewTransactions(\n remoteTxs: TransactionMeta[],\n localTxs: TransactionMeta[],\n ): TransactionMeta[] {\n return remoteTxs.filter((tx) => {\n const alreadyInTransactions = localTxs.find(\n ({ transactionHash }) => transactionHash === tx.transactionHash,\n );\n return !alreadyInTransactions;\n });\n }\n\n /**\n * Get all the transactions that are locally outdated with respect\n * to a remote source (etherscan or blockchain). The returned array\n * contains the transactions with the updated data.\n *\n * @param remoteTxs - Array of transactions from remote source.\n * @param localTxs - Array of transactions stored locally.\n * @returns The updated transactions.\n */\n private getUpdatedTransactions(\n remoteTxs: TransactionMeta[],\n localTxs: TransactionMeta[],\n ): TransactionMeta[] {\n return remoteTxs.filter((remoteTx) => {\n const isTxOutdated = localTxs.find((localTx) => {\n return (\n remoteTx.transactionHash === localTx.transactionHash &&\n this.isTransactionOutdated(remoteTx, localTx)\n );\n });\n return isTxOutdated;\n });\n }\n\n /**\n * Verifies if a local transaction is outdated with respect to the remote transaction.\n *\n * @param remoteTx - The remote transaction from Etherscan.\n * @param localTx - The local transaction.\n * @returns Whether the transaction is outdated.\n */\n private isTransactionOutdated(\n remoteTx: TransactionMeta,\n localTx: TransactionMeta,\n ): boolean {\n const statusOutdated = this.isStatusOutdated(\n remoteTx.transactionHash,\n localTx.transactionHash,\n remoteTx.status,\n localTx.status,\n );\n const gasDataOutdated = this.isGasDataOutdated(\n remoteTx.transaction.gasUsed,\n localTx.transaction.gasUsed,\n );\n return statusOutdated || gasDataOutdated;\n }\n\n /**\n * Verifies if the status of a local transaction is outdated with respect to the remote transaction.\n *\n * @param remoteTxHash - Remote transaction hash.\n * @param localTxHash - Local transaction hash.\n * @param remoteTxStatus - Remote transaction status.\n * @param localTxStatus - Local transaction status.\n * @returns Whether the status is outdated.\n */\n private isStatusOutdated(\n remoteTxHash: string | undefined,\n localTxHash: string | undefined,\n remoteTxStatus: TransactionStatus,\n localTxStatus: TransactionStatus,\n ): boolean {\n return remoteTxHash === localTxHash && remoteTxStatus !== localTxStatus;\n }\n\n /**\n * Verifies if the gas data of a local transaction is outdated with respect to the remote transaction.\n *\n * @param remoteGasUsed - Remote gas used in the transaction.\n * @param localGasUsed - Local gas used in the transaction.\n * @returns Whether the gas data is outdated.\n */\n private isGasDataOutdated(\n remoteGasUsed: string | undefined,\n localGasUsed: string | undefined,\n ): boolean {\n return remoteGasUsed !== localGasUsed;\n }\n}\n\nexport default TransactionController;\n"]} \ No newline at end of file diff --git a/dist/transaction/mocks/txsMock.d.ts b/dist/transaction/mocks/txsMock.d.ts new file mode 100644 index 0000000000..649d313651 --- /dev/null +++ b/dist/transaction/mocks/txsMock.d.ts @@ -0,0 +1,63 @@ +import { TransactionMeta } from '../TransactionController'; +export declare const ethTxsMock: (ethTxHash: string) => ({ + blockNumber: string; + confirmations: string; + contractAddress: string; + cumulativeGasUsed: string; + from: string; + gas: string; + gasPrice: string; + gasUsed: string; + hash: string; + input: string; + isError: string; + nonce: string; + timeStamp: string; + to: string; + transactionIndex: string; + txreceipt_status: string; + value: string; +} | { + blockNumber: string; + confirmations: string; + contractAddress: string; + cumulativeGasUsed: string; + from: string; + gas: string; + gasPrice: string; + gasUsed: string; + hash: string; + input: string; + isError: string; + nonce: string; + timeStamp: string; + transactionIndex: string; + txreceipt_status: string; + value: string; + to?: undefined; +})[]; +export declare const tokenTxsMock: (tokenTxHash: string) => { + blockNumber: string; + timeStamp: string; + hash: string; + nonce: string; + blockHash: string; + from: string; + contractAddress: string; + to: string; + value: string; + tokenName: string; + tokenSymbol: string; + tokenDecimal: string; + transactionIndex: string; + gas: string; + gasPrice: string; + gasUsed: string; + cumulativeGasUsed: string; + input: string; + confirmations: string; +}[]; +export declare const txsInStateMock: (ethTxHash: string, tokenTxHash: string) => TransactionMeta[]; +export declare const txsInStateWithOutdatedStatusMock: (ethTxHash: string, tokenTxHash: string) => TransactionMeta[]; +export declare const txsInStateWithOutdatedGasDataMock: (ethTxHash: string, tokenTxHash: string) => TransactionMeta[]; +export declare const txsInStateWithOutdatedStatusAndGasDataMock: (ethTxHash: string, tokenTxHash: string) => TransactionMeta[]; diff --git a/dist/transaction/mocks/txsMock.js b/dist/transaction/mocks/txsMock.js new file mode 100644 index 0000000000..47cf349940 --- /dev/null +++ b/dist/transaction/mocks/txsMock.js @@ -0,0 +1,515 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.txsInStateWithOutdatedStatusAndGasDataMock = exports.txsInStateWithOutdatedGasDataMock = exports.txsInStateWithOutdatedStatusMock = exports.txsInStateMock = exports.tokenTxsMock = exports.ethTxsMock = void 0; +const TransactionController_1 = require("../TransactionController"); +const ethTxsMock = (ethTxHash) => [ + { + blockNumber: '4535101', + confirmations: '10', + contractAddress: '', + cumulativeGasUsed: '120607', + from: '0xe46abaf75cfbff815c0b7ffed6f02b0760ea27f1', + gas: '335208', + gasPrice: '10000000000', + gasUsed: '21000', + hash: ethTxHash, + input: '0x', + isError: '0', + nonce: '9', + timeStamp: '1543596286', + to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + transactionIndex: '2', + txreceipt_status: '1', + value: '100000000000000000', + }, + { + blockNumber: '4535108', + confirmations: '3', + contractAddress: '', + cumulativeGasUsed: '693910', + from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + gas: '335208', + gasPrice: '20000000000', + gasUsed: '21000', + hash: '0x342e9d73e10004af41d04973339fc7219dbadcbb5629730cfe65e9f9cb15ff92', + input: '0x', + isError: '0', + nonce: '0', + timeStamp: '1543596378', + to: '0xb2d191b6fe03c5b8a1ab249cfe88c37553357a23', + transactionIndex: '12', + txreceipt_status: '1', + value: '50000000000000000', + }, + { + blockNumber: '4535105', + confirmations: '4', + contractAddress: '', + cumulativeGasUsed: '693910', + from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + gas: '335208', + gasPrice: '20000000000', + gasUsed: '21000', + hash: '0x342e9d73e10004af41d04973339fc7219dbadcbb5629730cfe65e9f9cb15ff91', + input: '0x', + isError: '0', + nonce: '1', + timeStamp: '1543596356', + transactionIndex: '13', + txreceipt_status: '1', + value: '50000000000000000', + }, + { + blockNumber: '4535106', + confirmations: '4', + contractAddress: '', + cumulativeGasUsed: '693910', + from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + gas: '335208', + gasPrice: '20000000000', + gasUsed: '21000', + hash: '0x342e9d73e10004af41d04973139fc7219dbadcbb5629730cfe65e9f9cb15ff91', + input: '0x11', + isError: '0', + nonce: '3', + timeStamp: '1543596356', + to: '0xb2d191b6fe03c5b8a1ab249cfe88c37553357a23', + transactionIndex: '13', + txreceipt_status: '1', + value: '50000000000000000', + }, +]; +exports.ethTxsMock = ethTxsMock; +const tokenTxsMock = (tokenTxHash) => [ + { + blockNumber: '8222239', + timeStamp: '1564091067', + hash: tokenTxHash, + nonce: '2329', + blockHash: '0x3c30a9be9aea7be13caad419444140c11839d72e70479ec7e9c6d8bd08c533bc', + from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071', + contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + value: '0', + tokenName: 'Sai Stablecoin v1.0', + tokenSymbol: 'SAI', + tokenDecimal: '18', + transactionIndex: '69', + gas: '624874', + gasPrice: '20000000000', + gasUsed: '21000', + cumulativeGasUsed: '3203881', + input: 'deprecated', + confirmations: '3659676', + }, + { + blockNumber: '8222250', + timeStamp: '1564091247', + hash: '0xdcd1c8bee545d3f76d80b20a23ad44276ba2e376681228eb4570cf3518491279', + nonce: '2330', + blockHash: '0x16986dd66bedb20a5b846ec2b6c0ecaa62f1c4b51fac58c1326101fd9126dd82', + from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071', + contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + value: '0', + tokenName: 'Sai Stablecoin v1.0', + tokenSymbol: 'SAI', + tokenDecimal: '18', + transactionIndex: '40', + gas: '594268', + gasPrice: '20000000000', + gasUsed: '579268', + cumulativeGasUsed: '2009011', + input: 'deprecated', + confirmations: '3659665', + }, + { + blockNumber: '8223771', + timeStamp: '1564111652', + hash: '0x070369e6f560b0deca52e050ff1a961fa7b688bbec5cea08435921c9d9b0f52e', + nonce: '2333', + blockHash: '0x0aff8b36881be99df6d176d7c64c2171672c0483684a10c112d2c90fefe30a0a', + from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071', + contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + value: '0', + tokenName: 'Sai Stablecoin v1.0', + tokenSymbol: 'SAI', + tokenDecimal: '18', + transactionIndex: '132', + gas: '583810', + gasPrice: '6000000000', + gasUsed: '568810', + cumulativeGasUsed: '6956245', + input: 'deprecated', + confirmations: '3658144', + }, + { + blockNumber: '8224850', + timeStamp: '1564126442', + hash: '0x8ef20ec9597c8c2e945bcc76d2668e5d3bb088b081fe8c5b5af2e1cbd315a20f', + nonce: '31', + blockHash: '0xb80d4d861ecb7a3cb14e591c0aaeb226842d0267772affa2acc1a590c7535647', + from: '0x6c70e3563cef0c6835703bb2664c9f59a92353e4', + contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + value: '10000000000000000000', + tokenName: 'Sai Stablecoin v1.0', + tokenSymbol: 'SAI', + tokenDecimal: '18', + transactionIndex: '169', + gas: '78447', + gasPrice: '2000000000', + gasUsed: '52298', + cumulativeGasUsed: '7047823', + input: 'deprecated', + confirmations: '3657065', + }, + { + blockNumber: '8228053', + timeStamp: '1564168901', + hash: '0xa0f2d7b558bb3cc28fa568f6feb8ed30eb28a01a674d7c0d4ae603fc691e6020', + nonce: '2368', + blockHash: '0x62c515ea049842c968ca67499f47a32a11394364d319d9c9cc0a0211652a7294', + from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071', + contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + value: '0', + tokenName: 'Sai Stablecoin v1.0', + tokenSymbol: 'SAI', + tokenDecimal: '18', + transactionIndex: '43', + gas: '567156', + gasPrice: '3000000000', + gasUsed: '552156', + cumulativeGasUsed: '3048261', + input: 'deprecated', + confirmations: '3653862', + }, + { + blockNumber: '8315335', + timeStamp: '1565339223', + hash: '0x464df60fe00b6dd04c9e8ab341d02af9b10a619d2fcd60fd2971f10edf12118f', + nonce: '206760', + blockHash: '0x98275388ef6708debe35ac7bf2e30143c9b1fd9e0e457ca03598fc1f4209e273', + from: '0x00cfbbaf7ddb3a1476767101c12a0162e241fbad', + contractAddress: '0x4dc3643dbc642b72c158e7f3d2ff232df61cb6ce', + to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + value: '100000000000000000', + tokenName: 'Amber', + tokenSymbol: 'AMB', + tokenDecimal: '18', + transactionIndex: '186', + gas: '60000', + gasPrice: '2000000000', + gasUsed: '52108', + cumulativeGasUsed: '7490707', + input: 'deprecated', + confirmations: '3566580', + }, + { + blockNumber: '8350846', + timeStamp: '1565815049', + hash: '0xc0682327ad3efd56dfa33e8206b4e09efad4e419a6191076069d217e3ee2341f', + nonce: '2506', + blockHash: '0xd0aa3c0e319fdfeb21b0192cf77b9760b8668060a5977a5f10f8413531083afa', + from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071', + contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + value: '4', + tokenName: 'Sai Stablecoin v1.0', + tokenSymbol: 'SAI', + tokenDecimal: '18', + transactionIndex: '48', + gas: '578737', + gasPrice: '3000000000', + gasUsed: '518737', + cumulativeGasUsed: '2848015', + input: 'deprecated', + confirmations: '3531069', + }, + { + blockNumber: '8350859', + timeStamp: '1565815221', + hash: '0x989ea9f3ee576fa43957f44363e839adf1a4a397c3d8392a4f7cbbf7949fd0ae', + nonce: '2', + blockHash: '0xb9cf1d29c665c052e3831b5754903e539c5b0b1d33b8bcab6cd2d450764d601f', + from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + to: '0x09cabec1ead1c0ba254b09efb3ee13841712be14', + value: '10000000000000000000', + tokenName: 'Sai Stablecoin v1.0', + tokenSymbol: 'SAI', + tokenDecimal: '18', + transactionIndex: '31', + gas: '60734', + gasPrice: '1000000000', + gasUsed: '54745', + cumulativeGasUsed: '7833857', + input: 'deprecated', + confirmations: '3531056', + }, + { + blockNumber: '8679548', + timeStamp: '1570244087', + hash: '0xc0016b89b3b525b30d73f242653b0d80ec3ebf285376dff5bb52cef3261498b2', + nonce: '3', + blockHash: '0x1ceb2f8b83087f010773e2acf63d1526633c8a884bd1980f118a1bba576be69f', + from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + to: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071', + value: '0', + tokenName: 'Sai Stablecoin v1.0', + tokenSymbol: 'SAI', + tokenDecimal: '18', + transactionIndex: '56', + gas: '993379', + gasPrice: '1440000000', + gasUsed: '647253', + cumulativeGasUsed: '3562204', + input: 'deprecated', + confirmations: '3202367', + }, + { + blockNumber: '8679548', + timeStamp: '1570244087', + hash: '0xc0016b89b3b525b30d73f242653b0d80ec3ebf285376dff5bb52cef3261498b2', + nonce: '3', + blockHash: '0x1ceb2f8b83087f010773e2acf63d1526633c8a884bd1980f118a1bba576be69f', + from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071', + contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + value: '0', + tokenName: 'Sai Stablecoin v1.0', + tokenSymbol: 'SAI', + tokenDecimal: '18', + transactionIndex: '56', + gas: '993379', + gasPrice: '1440000000', + gasUsed: '647253', + cumulativeGasUsed: '3562204', + input: 'deprecated', + confirmations: '3202367', + }, + { + blockNumber: '8694142', + timeStamp: '1570440625', + hash: '0xd8397138bb93d56e50d01e92a9eae99ebd3ae28844acdaa4663976a5501116cf', + nonce: '2837', + blockHash: '0xba45dd64e71e146066af9b6d2dd3bc5d72f4a3399148c155dced74c139fc3c51', + from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071', + contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + value: '0', + tokenName: 'Sai Stablecoin v1.0', + tokenSymbol: 'SAI', + tokenDecimal: '18', + transactionIndex: '217', + gas: '600632', + gasPrice: '9000000000', + gasUsed: '570632', + cumulativeGasUsed: '9023725', + input: 'deprecated', + confirmations: '3187773', + }, + { + blockNumber: '10877041', + timeStamp: '1600310867', + hash: '0xc8bd16b6b41b4c24849eb6869702e1489c808cb5b125b01f084e38fefcb5ea77', + nonce: '4', + blockHash: '0x7fa16a022bcf1f69c2d7adf6bd7d3f058e808eec5c66aaa910dfa8016a5333d1', + from: '0x090d4613473dee047c3f2706764f49e0821d256e', + contractAddress: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', + to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + value: '400000000000000000000', + tokenName: 'Uniswap', + tokenSymbol: 'UNI', + tokenDecimal: '18', + transactionIndex: '42', + gas: '90038', + gasPrice: '550000000000', + gasUsed: '81853', + cumulativeGasUsed: '3163540', + input: 'deprecated', + confirmations: '1004874', + }, + { + blockNumber: '10877897', + timeStamp: '1600321973', + hash: '0xa7162489faef826ee77862ed5210b01726524f09428f69842118dad394842d62', + nonce: '6', + blockHash: '0xa74eb9d16f65f307dde4ce58c813c981b28f242edf1090ee2ac42caac9dccaca', + from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + contractAddress: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', + to: '0x5e736f1f25992b2cad20ded179a52823d3d24b26', + value: '400000000000000000000', + tokenName: 'Uniswap', + tokenSymbol: 'UNI', + tokenDecimal: '18', + transactionIndex: '86', + gas: '60759', + gasPrice: '640000000000', + gasUsed: '25506', + cumulativeGasUsed: '4408393', + input: 'deprecated', + confirmations: '1004018', + }, +]; +exports.tokenTxsMock = tokenTxsMock; +const txsInStateMock = (ethTxHash, tokenTxHash) => [ + { + id: 'token-transaction-id', + chainId: '1', + status: TransactionController_1.TransactionStatus.confirmed, + time: 1615497996125, + transaction: { + from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + data: '0x', + gas: '624874', + gasPrice: '20000000000', + gasUsed: '21000', + nonce: '0x12', + to: '0x881d40237659c251811cec9c364ef91dc08d300c', + value: '0x0', + }, + transactionHash: tokenTxHash, + toSmartContract: true, + }, + { + id: 'eth-transaction-id', + chainId: '1', + status: TransactionController_1.TransactionStatus.confirmed, + time: 1615497996125, + transaction: { + from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + data: '0x', + gas: '0x51d68', + gasPrice: '0x2540be400', + gasUsed: '0x5208', + nonce: '0x12', + to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + value: '100000000000000000', + }, + transactionHash: ethTxHash, + toSmartContract: false, + }, +]; +exports.txsInStateMock = txsInStateMock; +const txsInStateWithOutdatedStatusMock = (ethTxHash, tokenTxHash) => [ + { + id: 'token-transaction-id', + chainId: '1', + status: TransactionController_1.TransactionStatus.rejected, + time: 1615497996125, + transaction: { + from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + data: '0x', + gas: '624874', + gasPrice: '20000000000', + gasUsed: '21000', + nonce: '0x12', + to: '0x881d40237659c251811cec9c364ef91dc08d300c', + value: '0x0', + }, + transactionHash: tokenTxHash, + toSmartContract: true, + }, + { + id: 'eth-transaction-id', + chainId: '1', + status: TransactionController_1.TransactionStatus.rejected, + time: 1615497996125, + transaction: { + from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + data: '0x', + gas: '0x51d68', + gasPrice: '0x2540be400', + gasUsed: '0x5208', + nonce: '0x12', + to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + value: '100000000000000000', + }, + transactionHash: ethTxHash, + toSmartContract: false, + }, +]; +exports.txsInStateWithOutdatedStatusMock = txsInStateWithOutdatedStatusMock; +const txsInStateWithOutdatedGasDataMock = (ethTxHash, tokenTxHash) => [ + { + id: 'token-transaction-id', + chainId: '1', + status: TransactionController_1.TransactionStatus.confirmed, + time: 1615497996125, + transaction: { + from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + data: '0x', + gas: '624874', + gasPrice: '20000000000', + gasUsed: undefined, + nonce: '0x12', + to: '0x881d40237659c251811cec9c364ef91dc08d300c', + value: '0x0', + }, + transactionHash: tokenTxHash, + toSmartContract: true, + }, + { + id: 'eth-transaction-id', + chainId: '1', + status: TransactionController_1.TransactionStatus.confirmed, + time: 1615497996125, + transaction: { + from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + data: '0x', + gas: '0x51d68', + gasPrice: '0x2540be400', + gasUsed: undefined, + nonce: '0x12', + to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + value: '100000000000000000', + }, + transactionHash: ethTxHash, + toSmartContract: false, + }, +]; +exports.txsInStateWithOutdatedGasDataMock = txsInStateWithOutdatedGasDataMock; +const txsInStateWithOutdatedStatusAndGasDataMock = (ethTxHash, tokenTxHash) => [ + { + id: 'token-transaction-id', + chainId: '1', + status: TransactionController_1.TransactionStatus.rejected, + time: 1615497996125, + transaction: { + from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + data: '0x', + gas: '624874', + gasPrice: '20000000000', + gasUsed: undefined, + nonce: '0x12', + to: '0x881d40237659c251811cec9c364ef91dc08d300c', + value: '0x0', + }, + transactionHash: tokenTxHash, + toSmartContract: true, + }, + { + id: 'eth-transaction-id', + chainId: '1', + status: TransactionController_1.TransactionStatus.rejected, + time: 1615497996125, + transaction: { + from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + data: '0x', + gas: '0x51d68', + gasPrice: '0x2540be400', + gasUsed: undefined, + nonce: '0x12', + to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', + value: '100000000000000000', + }, + transactionHash: ethTxHash, + toSmartContract: false, + }, +]; +exports.txsInStateWithOutdatedStatusAndGasDataMock = txsInStateWithOutdatedStatusAndGasDataMock; +//# sourceMappingURL=txsMock.js.map \ No newline at end of file diff --git a/dist/transaction/mocks/txsMock.js.map b/dist/transaction/mocks/txsMock.js.map new file mode 100644 index 0000000000..f6a5db83da --- /dev/null +++ b/dist/transaction/mocks/txsMock.js.map @@ -0,0 +1 @@ +{"version":3,"file":"txsMock.js","sourceRoot":"","sources":["../../../src/transaction/mocks/txsMock.ts"],"names":[],"mappings":";;;AAAA,oEAA8E;AAEvE,MAAM,UAAU,GAAG,CAAC,SAAiB,EAAE,EAAE,CAAC;IAC/C;QACE,WAAW,EAAE,SAAS;QACtB,aAAa,EAAE,IAAI;QACnB,eAAe,EAAE,EAAE;QACnB,iBAAiB,EAAE,QAAQ;QAC3B,IAAI,EAAE,4CAA4C;QAClD,GAAG,EAAE,QAAQ;QACb,QAAQ,EAAE,aAAa;QACvB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,IAAI;QACX,OAAO,EAAE,GAAG;QACZ,KAAK,EAAE,GAAG;QACV,SAAS,EAAE,YAAY;QACvB,EAAE,EAAE,4CAA4C;QAChD,gBAAgB,EAAE,GAAG;QACrB,gBAAgB,EAAE,GAAG;QACrB,KAAK,EAAE,oBAAoB;KAC5B;IACD;QACE,WAAW,EAAE,SAAS;QACtB,aAAa,EAAE,GAAG;QAClB,eAAe,EAAE,EAAE;QACnB,iBAAiB,EAAE,QAAQ;QAC3B,IAAI,EAAE,4CAA4C;QAClD,GAAG,EAAE,QAAQ;QACb,QAAQ,EAAE,aAAa;QACvB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,oEAAoE;QAC1E,KAAK,EAAE,IAAI;QACX,OAAO,EAAE,GAAG;QACZ,KAAK,EAAE,GAAG;QACV,SAAS,EAAE,YAAY;QACvB,EAAE,EAAE,4CAA4C;QAChD,gBAAgB,EAAE,IAAI;QACtB,gBAAgB,EAAE,GAAG;QACrB,KAAK,EAAE,mBAAmB;KAC3B;IACD;QACE,WAAW,EAAE,SAAS;QACtB,aAAa,EAAE,GAAG;QAClB,eAAe,EAAE,EAAE;QACnB,iBAAiB,EAAE,QAAQ;QAC3B,IAAI,EAAE,4CAA4C;QAClD,GAAG,EAAE,QAAQ;QACb,QAAQ,EAAE,aAAa;QACvB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,oEAAoE;QAC1E,KAAK,EAAE,IAAI;QACX,OAAO,EAAE,GAAG;QACZ,KAAK,EAAE,GAAG;QACV,SAAS,EAAE,YAAY;QACvB,gBAAgB,EAAE,IAAI;QACtB,gBAAgB,EAAE,GAAG;QACrB,KAAK,EAAE,mBAAmB;KAC3B;IACD;QACE,WAAW,EAAE,SAAS;QACtB,aAAa,EAAE,GAAG;QAClB,eAAe,EAAE,EAAE;QACnB,iBAAiB,EAAE,QAAQ;QAC3B,IAAI,EAAE,4CAA4C;QAClD,GAAG,EAAE,QAAQ;QACb,QAAQ,EAAE,aAAa;QACvB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,oEAAoE;QAC1E,KAAK,EAAE,MAAM;QACb,OAAO,EAAE,GAAG;QACZ,KAAK,EAAE,GAAG;QACV,SAAS,EAAE,YAAY;QACvB,EAAE,EAAE,4CAA4C;QAChD,gBAAgB,EAAE,IAAI;QACtB,gBAAgB,EAAE,GAAG;QACrB,KAAK,EAAE,mBAAmB;KAC3B;CACF,CAAC;AA5EW,QAAA,UAAU,cA4ErB;AAEK,MAAM,YAAY,GAAG,CAAC,WAAmB,EAAE,EAAE,CAAC;IACnD;QACE,WAAW,EAAE,SAAS;QACtB,SAAS,EAAE,YAAY;QACvB,IAAI,EAAE,WAAW;QACjB,KAAK,EAAE,MAAM;QACb,SAAS,EACP,oEAAoE;QACtE,IAAI,EAAE,4CAA4C;QAClD,eAAe,EAAE,4CAA4C;QAC7D,EAAE,EAAE,4CAA4C;QAChD,KAAK,EAAE,GAAG;QACV,SAAS,EAAE,qBAAqB;QAChC,WAAW,EAAE,KAAK;QAClB,YAAY,EAAE,IAAI;QAClB,gBAAgB,EAAE,IAAI;QACtB,GAAG,EAAE,QAAQ;QACb,QAAQ,EAAE,aAAa;QACvB,OAAO,EAAE,OAAO;QAChB,iBAAiB,EAAE,SAAS;QAC5B,KAAK,EAAE,YAAY;QACnB,aAAa,EAAE,SAAS;KACzB;IACD;QACE,WAAW,EAAE,SAAS;QACtB,SAAS,EAAE,YAAY;QACvB,IAAI,EAAE,oEAAoE;QAC1E,KAAK,EAAE,MAAM;QACb,SAAS,EACP,oEAAoE;QACtE,IAAI,EAAE,4CAA4C;QAClD,eAAe,EAAE,4CAA4C;QAC7D,EAAE,EAAE,4CAA4C;QAChD,KAAK,EAAE,GAAG;QACV,SAAS,EAAE,qBAAqB;QAChC,WAAW,EAAE,KAAK;QAClB,YAAY,EAAE,IAAI;QAClB,gBAAgB,EAAE,IAAI;QACtB,GAAG,EAAE,QAAQ;QACb,QAAQ,EAAE,aAAa;QACvB,OAAO,EAAE,QAAQ;QACjB,iBAAiB,EAAE,SAAS;QAC5B,KAAK,EAAE,YAAY;QACnB,aAAa,EAAE,SAAS;KACzB;IACD;QACE,WAAW,EAAE,SAAS;QACtB,SAAS,EAAE,YAAY;QACvB,IAAI,EAAE,oEAAoE;QAC1E,KAAK,EAAE,MAAM;QACb,SAAS,EACP,oEAAoE;QACtE,IAAI,EAAE,4CAA4C;QAClD,eAAe,EAAE,4CAA4C;QAC7D,EAAE,EAAE,4CAA4C;QAChD,KAAK,EAAE,GAAG;QACV,SAAS,EAAE,qBAAqB;QAChC,WAAW,EAAE,KAAK;QAClB,YAAY,EAAE,IAAI;QAClB,gBAAgB,EAAE,KAAK;QACvB,GAAG,EAAE,QAAQ;QACb,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,QAAQ;QACjB,iBAAiB,EAAE,SAAS;QAC5B,KAAK,EAAE,YAAY;QACnB,aAAa,EAAE,SAAS;KACzB;IACD;QACE,WAAW,EAAE,SAAS;QACtB,SAAS,EAAE,YAAY;QACvB,IAAI,EAAE,oEAAoE;QAC1E,KAAK,EAAE,IAAI;QACX,SAAS,EACP,oEAAoE;QACtE,IAAI,EAAE,4CAA4C;QAClD,eAAe,EAAE,4CAA4C;QAC7D,EAAE,EAAE,4CAA4C;QAChD,KAAK,EAAE,sBAAsB;QAC7B,SAAS,EAAE,qBAAqB;QAChC,WAAW,EAAE,KAAK;QAClB,YAAY,EAAE,IAAI;QAClB,gBAAgB,EAAE,KAAK;QACvB,GAAG,EAAE,OAAO;QACZ,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,OAAO;QAChB,iBAAiB,EAAE,SAAS;QAC5B,KAAK,EAAE,YAAY;QACnB,aAAa,EAAE,SAAS;KACzB;IACD;QACE,WAAW,EAAE,SAAS;QACtB,SAAS,EAAE,YAAY;QACvB,IAAI,EAAE,oEAAoE;QAC1E,KAAK,EAAE,MAAM;QACb,SAAS,EACP,oEAAoE;QACtE,IAAI,EAAE,4CAA4C;QAClD,eAAe,EAAE,4CAA4C;QAC7D,EAAE,EAAE,4CAA4C;QAChD,KAAK,EAAE,GAAG;QACV,SAAS,EAAE,qBAAqB;QAChC,WAAW,EAAE,KAAK;QAClB,YAAY,EAAE,IAAI;QAClB,gBAAgB,EAAE,IAAI;QACtB,GAAG,EAAE,QAAQ;QACb,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,QAAQ;QACjB,iBAAiB,EAAE,SAAS;QAC5B,KAAK,EAAE,YAAY;QACnB,aAAa,EAAE,SAAS;KACzB;IACD;QACE,WAAW,EAAE,SAAS;QACtB,SAAS,EAAE,YAAY;QACvB,IAAI,EAAE,oEAAoE;QAC1E,KAAK,EAAE,QAAQ;QACf,SAAS,EACP,oEAAoE;QACtE,IAAI,EAAE,4CAA4C;QAClD,eAAe,EAAE,4CAA4C;QAC7D,EAAE,EAAE,4CAA4C;QAChD,KAAK,EAAE,oBAAoB;QAC3B,SAAS,EAAE,OAAO;QAClB,WAAW,EAAE,KAAK;QAClB,YAAY,EAAE,IAAI;QAClB,gBAAgB,EAAE,KAAK;QACvB,GAAG,EAAE,OAAO;QACZ,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,OAAO;QAChB,iBAAiB,EAAE,SAAS;QAC5B,KAAK,EAAE,YAAY;QACnB,aAAa,EAAE,SAAS;KACzB;IACD;QACE,WAAW,EAAE,SAAS;QACtB,SAAS,EAAE,YAAY;QACvB,IAAI,EAAE,oEAAoE;QAC1E,KAAK,EAAE,MAAM;QACb,SAAS,EACP,oEAAoE;QACtE,IAAI,EAAE,4CAA4C;QAClD,eAAe,EAAE,4CAA4C;QAC7D,EAAE,EAAE,4CAA4C;QAChD,KAAK,EAAE,GAAG;QACV,SAAS,EAAE,qBAAqB;QAChC,WAAW,EAAE,KAAK;QAClB,YAAY,EAAE,IAAI;QAClB,gBAAgB,EAAE,IAAI;QACtB,GAAG,EAAE,QAAQ;QACb,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,QAAQ;QACjB,iBAAiB,EAAE,SAAS;QAC5B,KAAK,EAAE,YAAY;QACnB,aAAa,EAAE,SAAS;KACzB;IACD;QACE,WAAW,EAAE,SAAS;QACtB,SAAS,EAAE,YAAY;QACvB,IAAI,EAAE,oEAAoE;QAC1E,KAAK,EAAE,GAAG;QACV,SAAS,EACP,oEAAoE;QACtE,IAAI,EAAE,4CAA4C;QAClD,eAAe,EAAE,4CAA4C;QAC7D,EAAE,EAAE,4CAA4C;QAChD,KAAK,EAAE,sBAAsB;QAC7B,SAAS,EAAE,qBAAqB;QAChC,WAAW,EAAE,KAAK;QAClB,YAAY,EAAE,IAAI;QAClB,gBAAgB,EAAE,IAAI;QACtB,GAAG,EAAE,OAAO;QACZ,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,OAAO;QAChB,iBAAiB,EAAE,SAAS;QAC5B,KAAK,EAAE,YAAY;QACnB,aAAa,EAAE,SAAS;KACzB;IACD;QACE,WAAW,EAAE,SAAS;QACtB,SAAS,EAAE,YAAY;QACvB,IAAI,EAAE,oEAAoE;QAC1E,KAAK,EAAE,GAAG;QACV,SAAS,EACP,oEAAoE;QACtE,IAAI,EAAE,4CAA4C;QAClD,eAAe,EAAE,4CAA4C;QAC7D,EAAE,EAAE,4CAA4C;QAChD,KAAK,EAAE,GAAG;QACV,SAAS,EAAE,qBAAqB;QAChC,WAAW,EAAE,KAAK;QAClB,YAAY,EAAE,IAAI;QAClB,gBAAgB,EAAE,IAAI;QACtB,GAAG,EAAE,QAAQ;QACb,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,QAAQ;QACjB,iBAAiB,EAAE,SAAS;QAC5B,KAAK,EAAE,YAAY;QACnB,aAAa,EAAE,SAAS;KACzB;IACD;QACE,WAAW,EAAE,SAAS;QACtB,SAAS,EAAE,YAAY;QACvB,IAAI,EAAE,oEAAoE;QAC1E,KAAK,EAAE,GAAG;QACV,SAAS,EACP,oEAAoE;QACtE,IAAI,EAAE,4CAA4C;QAClD,eAAe,EAAE,4CAA4C;QAC7D,EAAE,EAAE,4CAA4C;QAChD,KAAK,EAAE,GAAG;QACV,SAAS,EAAE,qBAAqB;QAChC,WAAW,EAAE,KAAK;QAClB,YAAY,EAAE,IAAI;QAClB,gBAAgB,EAAE,IAAI;QACtB,GAAG,EAAE,QAAQ;QACb,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,QAAQ;QACjB,iBAAiB,EAAE,SAAS;QAC5B,KAAK,EAAE,YAAY;QACnB,aAAa,EAAE,SAAS;KACzB;IACD;QACE,WAAW,EAAE,SAAS;QACtB,SAAS,EAAE,YAAY;QACvB,IAAI,EAAE,oEAAoE;QAC1E,KAAK,EAAE,MAAM;QACb,SAAS,EACP,oEAAoE;QACtE,IAAI,EAAE,4CAA4C;QAClD,eAAe,EAAE,4CAA4C;QAC7D,EAAE,EAAE,4CAA4C;QAChD,KAAK,EAAE,GAAG;QACV,SAAS,EAAE,qBAAqB;QAChC,WAAW,EAAE,KAAK;QAClB,YAAY,EAAE,IAAI;QAClB,gBAAgB,EAAE,KAAK;QACvB,GAAG,EAAE,QAAQ;QACb,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,QAAQ;QACjB,iBAAiB,EAAE,SAAS;QAC5B,KAAK,EAAE,YAAY;QACnB,aAAa,EAAE,SAAS;KACzB;IACD;QACE,WAAW,EAAE,UAAU;QACvB,SAAS,EAAE,YAAY;QACvB,IAAI,EAAE,oEAAoE;QAC1E,KAAK,EAAE,GAAG;QACV,SAAS,EACP,oEAAoE;QACtE,IAAI,EAAE,4CAA4C;QAClD,eAAe,EAAE,4CAA4C;QAC7D,EAAE,EAAE,4CAA4C;QAChD,KAAK,EAAE,uBAAuB;QAC9B,SAAS,EAAE,SAAS;QACpB,WAAW,EAAE,KAAK;QAClB,YAAY,EAAE,IAAI;QAClB,gBAAgB,EAAE,IAAI;QACtB,GAAG,EAAE,OAAO;QACZ,QAAQ,EAAE,cAAc;QACxB,OAAO,EAAE,OAAO;QAChB,iBAAiB,EAAE,SAAS;QAC5B,KAAK,EAAE,YAAY;QACnB,aAAa,EAAE,SAAS;KACzB;IACD;QACE,WAAW,EAAE,UAAU;QACvB,SAAS,EAAE,YAAY;QACvB,IAAI,EAAE,oEAAoE;QAC1E,KAAK,EAAE,GAAG;QACV,SAAS,EACP,oEAAoE;QACtE,IAAI,EAAE,4CAA4C;QAClD,eAAe,EAAE,4CAA4C;QAC7D,EAAE,EAAE,4CAA4C;QAChD,KAAK,EAAE,uBAAuB;QAC9B,SAAS,EAAE,SAAS;QACpB,WAAW,EAAE,KAAK;QAClB,YAAY,EAAE,IAAI;QAClB,gBAAgB,EAAE,IAAI;QACtB,GAAG,EAAE,OAAO;QACZ,QAAQ,EAAE,cAAc;QACxB,OAAO,EAAE,OAAO;QAChB,iBAAiB,EAAE,SAAS;QAC5B,KAAK,EAAE,YAAY;QACnB,aAAa,EAAE,SAAS;KACzB;CACF,CAAC;AA/RW,QAAA,YAAY,gBA+RvB;AAEK,MAAM,cAAc,GAAG,CAC5B,SAAiB,EACjB,WAAmB,EACA,EAAE,CAAC;IACtB;QACE,EAAE,EAAE,sBAAsB;QAC1B,OAAO,EAAE,GAAG;QACZ,MAAM,EAAE,yCAAiB,CAAC,SAAS;QACnC,IAAI,EAAE,aAAa;QACnB,WAAW,EAAE;YACX,IAAI,EAAE,4CAA4C;YAClD,IAAI,EAAE,IAAI;YACV,GAAG,EAAE,QAAQ;YACb,QAAQ,EAAE,aAAa;YACvB,OAAO,EAAE,OAAO;YAChB,KAAK,EAAE,MAAM;YACb,EAAE,EAAE,4CAA4C;YAChD,KAAK,EAAE,KAAK;SACb;QACD,eAAe,EAAE,WAAW;QAC5B,eAAe,EAAE,IAAI;KACtB;IACD;QACE,EAAE,EAAE,oBAAoB;QACxB,OAAO,EAAE,GAAG;QACZ,MAAM,EAAE,yCAAiB,CAAC,SAAS;QACnC,IAAI,EAAE,aAAa;QACnB,WAAW,EAAE;YACX,IAAI,EAAE,4CAA4C;YAClD,IAAI,EAAE,IAAI;YACV,GAAG,EAAE,SAAS;YACd,QAAQ,EAAE,aAAa;YACvB,OAAO,EAAE,QAAQ;YACjB,KAAK,EAAE,MAAM;YACb,EAAE,EAAE,4CAA4C;YAChD,KAAK,EAAE,oBAAoB;SAC5B;QACD,eAAe,EAAE,SAAS;QAC1B,eAAe,EAAE,KAAK;KACvB;CACF,CAAC;AAxCW,QAAA,cAAc,kBAwCzB;AAEK,MAAM,gCAAgC,GAAG,CAC9C,SAAiB,EACjB,WAAmB,EACA,EAAE,CAAC;IACtB;QACE,EAAE,EAAE,sBAAsB;QAC1B,OAAO,EAAE,GAAG;QACZ,MAAM,EAAE,yCAAiB,CAAC,QAAQ;QAClC,IAAI,EAAE,aAAa;QACnB,WAAW,EAAE;YACX,IAAI,EAAE,4CAA4C;YAClD,IAAI,EAAE,IAAI;YACV,GAAG,EAAE,QAAQ;YACb,QAAQ,EAAE,aAAa;YACvB,OAAO,EAAE,OAAO;YAChB,KAAK,EAAE,MAAM;YACb,EAAE,EAAE,4CAA4C;YAChD,KAAK,EAAE,KAAK;SACb;QACD,eAAe,EAAE,WAAW;QAC5B,eAAe,EAAE,IAAI;KACtB;IACD;QACE,EAAE,EAAE,oBAAoB;QACxB,OAAO,EAAE,GAAG;QACZ,MAAM,EAAE,yCAAiB,CAAC,QAAQ;QAClC,IAAI,EAAE,aAAa;QACnB,WAAW,EAAE;YACX,IAAI,EAAE,4CAA4C;YAClD,IAAI,EAAE,IAAI;YACV,GAAG,EAAE,SAAS;YACd,QAAQ,EAAE,aAAa;YACvB,OAAO,EAAE,QAAQ;YACjB,KAAK,EAAE,MAAM;YACb,EAAE,EAAE,4CAA4C;YAChD,KAAK,EAAE,oBAAoB;SAC5B;QACD,eAAe,EAAE,SAAS;QAC1B,eAAe,EAAE,KAAK;KACvB;CACF,CAAC;AAxCW,QAAA,gCAAgC,oCAwC3C;AAEK,MAAM,iCAAiC,GAAG,CAC/C,SAAiB,EACjB,WAAmB,EACA,EAAE,CAAC;IACtB;QACE,EAAE,EAAE,sBAAsB;QAC1B,OAAO,EAAE,GAAG;QACZ,MAAM,EAAE,yCAAiB,CAAC,SAAS;QACnC,IAAI,EAAE,aAAa;QACnB,WAAW,EAAE;YACX,IAAI,EAAE,4CAA4C;YAClD,IAAI,EAAE,IAAI;YACV,GAAG,EAAE,QAAQ;YACb,QAAQ,EAAE,aAAa;YACvB,OAAO,EAAE,SAAS;YAClB,KAAK,EAAE,MAAM;YACb,EAAE,EAAE,4CAA4C;YAChD,KAAK,EAAE,KAAK;SACb;QACD,eAAe,EAAE,WAAW;QAC5B,eAAe,EAAE,IAAI;KACtB;IACD;QACE,EAAE,EAAE,oBAAoB;QACxB,OAAO,EAAE,GAAG;QACZ,MAAM,EAAE,yCAAiB,CAAC,SAAS;QACnC,IAAI,EAAE,aAAa;QACnB,WAAW,EAAE;YACX,IAAI,EAAE,4CAA4C;YAClD,IAAI,EAAE,IAAI;YACV,GAAG,EAAE,SAAS;YACd,QAAQ,EAAE,aAAa;YACvB,OAAO,EAAE,SAAS;YAClB,KAAK,EAAE,MAAM;YACb,EAAE,EAAE,4CAA4C;YAChD,KAAK,EAAE,oBAAoB;SAC5B;QACD,eAAe,EAAE,SAAS;QAC1B,eAAe,EAAE,KAAK;KACvB;CACF,CAAC;AAxCW,QAAA,iCAAiC,qCAwC5C;AAEK,MAAM,0CAA0C,GAAG,CACxD,SAAiB,EACjB,WAAmB,EACA,EAAE,CAAC;IACtB;QACE,EAAE,EAAE,sBAAsB;QAC1B,OAAO,EAAE,GAAG;QACZ,MAAM,EAAE,yCAAiB,CAAC,QAAQ;QAClC,IAAI,EAAE,aAAa;QACnB,WAAW,EAAE;YACX,IAAI,EAAE,4CAA4C;YAClD,IAAI,EAAE,IAAI;YACV,GAAG,EAAE,QAAQ;YACb,QAAQ,EAAE,aAAa;YACvB,OAAO,EAAE,SAAS;YAClB,KAAK,EAAE,MAAM;YACb,EAAE,EAAE,4CAA4C;YAChD,KAAK,EAAE,KAAK;SACb;QACD,eAAe,EAAE,WAAW;QAC5B,eAAe,EAAE,IAAI;KACtB;IACD;QACE,EAAE,EAAE,oBAAoB;QACxB,OAAO,EAAE,GAAG;QACZ,MAAM,EAAE,yCAAiB,CAAC,QAAQ;QAClC,IAAI,EAAE,aAAa;QACnB,WAAW,EAAE;YACX,IAAI,EAAE,4CAA4C;YAClD,IAAI,EAAE,IAAI;YACV,GAAG,EAAE,SAAS;YACd,QAAQ,EAAE,aAAa;YACvB,OAAO,EAAE,SAAS;YAClB,KAAK,EAAE,MAAM;YACb,EAAE,EAAE,4CAA4C;YAChD,KAAK,EAAE,oBAAoB;SAC5B;QACD,eAAe,EAAE,SAAS;QAC1B,eAAe,EAAE,KAAK;KACvB;CACF,CAAC;AAxCW,QAAA,0CAA0C,8CAwCrD","sourcesContent":["import { TransactionMeta, TransactionStatus } from '../TransactionController';\n\nexport const ethTxsMock = (ethTxHash: string) => [\n {\n blockNumber: '4535101',\n confirmations: '10',\n contractAddress: '',\n cumulativeGasUsed: '120607',\n from: '0xe46abaf75cfbff815c0b7ffed6f02b0760ea27f1',\n gas: '335208',\n gasPrice: '10000000000',\n gasUsed: '21000',\n hash: ethTxHash,\n input: '0x',\n isError: '0',\n nonce: '9',\n timeStamp: '1543596286',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n transactionIndex: '2',\n txreceipt_status: '1',\n value: '100000000000000000',\n },\n {\n blockNumber: '4535108',\n confirmations: '3',\n contractAddress: '',\n cumulativeGasUsed: '693910',\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n gas: '335208',\n gasPrice: '20000000000',\n gasUsed: '21000',\n hash: '0x342e9d73e10004af41d04973339fc7219dbadcbb5629730cfe65e9f9cb15ff92',\n input: '0x',\n isError: '0',\n nonce: '0',\n timeStamp: '1543596378',\n to: '0xb2d191b6fe03c5b8a1ab249cfe88c37553357a23',\n transactionIndex: '12',\n txreceipt_status: '1',\n value: '50000000000000000',\n },\n {\n blockNumber: '4535105',\n confirmations: '4',\n contractAddress: '',\n cumulativeGasUsed: '693910',\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n gas: '335208',\n gasPrice: '20000000000',\n gasUsed: '21000',\n hash: '0x342e9d73e10004af41d04973339fc7219dbadcbb5629730cfe65e9f9cb15ff91',\n input: '0x',\n isError: '0',\n nonce: '1',\n timeStamp: '1543596356',\n transactionIndex: '13',\n txreceipt_status: '1',\n value: '50000000000000000',\n },\n {\n blockNumber: '4535106',\n confirmations: '4',\n contractAddress: '',\n cumulativeGasUsed: '693910',\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n gas: '335208',\n gasPrice: '20000000000',\n gasUsed: '21000',\n hash: '0x342e9d73e10004af41d04973139fc7219dbadcbb5629730cfe65e9f9cb15ff91',\n input: '0x11',\n isError: '0',\n nonce: '3',\n timeStamp: '1543596356',\n to: '0xb2d191b6fe03c5b8a1ab249cfe88c37553357a23',\n transactionIndex: '13',\n txreceipt_status: '1',\n value: '50000000000000000',\n },\n];\n\nexport const tokenTxsMock = (tokenTxHash: string) => [\n {\n blockNumber: '8222239',\n timeStamp: '1564091067',\n hash: tokenTxHash,\n nonce: '2329',\n blockHash:\n '0x3c30a9be9aea7be13caad419444140c11839d72e70479ec7e9c6d8bd08c533bc',\n from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071',\n contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '0',\n tokenName: 'Sai Stablecoin v1.0',\n tokenSymbol: 'SAI',\n tokenDecimal: '18',\n transactionIndex: '69',\n gas: '624874',\n gasPrice: '20000000000',\n gasUsed: '21000',\n cumulativeGasUsed: '3203881',\n input: 'deprecated',\n confirmations: '3659676',\n },\n {\n blockNumber: '8222250',\n timeStamp: '1564091247',\n hash: '0xdcd1c8bee545d3f76d80b20a23ad44276ba2e376681228eb4570cf3518491279',\n nonce: '2330',\n blockHash:\n '0x16986dd66bedb20a5b846ec2b6c0ecaa62f1c4b51fac58c1326101fd9126dd82',\n from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071',\n contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '0',\n tokenName: 'Sai Stablecoin v1.0',\n tokenSymbol: 'SAI',\n tokenDecimal: '18',\n transactionIndex: '40',\n gas: '594268',\n gasPrice: '20000000000',\n gasUsed: '579268',\n cumulativeGasUsed: '2009011',\n input: 'deprecated',\n confirmations: '3659665',\n },\n {\n blockNumber: '8223771',\n timeStamp: '1564111652',\n hash: '0x070369e6f560b0deca52e050ff1a961fa7b688bbec5cea08435921c9d9b0f52e',\n nonce: '2333',\n blockHash:\n '0x0aff8b36881be99df6d176d7c64c2171672c0483684a10c112d2c90fefe30a0a',\n from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071',\n contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '0',\n tokenName: 'Sai Stablecoin v1.0',\n tokenSymbol: 'SAI',\n tokenDecimal: '18',\n transactionIndex: '132',\n gas: '583810',\n gasPrice: '6000000000',\n gasUsed: '568810',\n cumulativeGasUsed: '6956245',\n input: 'deprecated',\n confirmations: '3658144',\n },\n {\n blockNumber: '8224850',\n timeStamp: '1564126442',\n hash: '0x8ef20ec9597c8c2e945bcc76d2668e5d3bb088b081fe8c5b5af2e1cbd315a20f',\n nonce: '31',\n blockHash:\n '0xb80d4d861ecb7a3cb14e591c0aaeb226842d0267772affa2acc1a590c7535647',\n from: '0x6c70e3563cef0c6835703bb2664c9f59a92353e4',\n contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '10000000000000000000',\n tokenName: 'Sai Stablecoin v1.0',\n tokenSymbol: 'SAI',\n tokenDecimal: '18',\n transactionIndex: '169',\n gas: '78447',\n gasPrice: '2000000000',\n gasUsed: '52298',\n cumulativeGasUsed: '7047823',\n input: 'deprecated',\n confirmations: '3657065',\n },\n {\n blockNumber: '8228053',\n timeStamp: '1564168901',\n hash: '0xa0f2d7b558bb3cc28fa568f6feb8ed30eb28a01a674d7c0d4ae603fc691e6020',\n nonce: '2368',\n blockHash:\n '0x62c515ea049842c968ca67499f47a32a11394364d319d9c9cc0a0211652a7294',\n from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071',\n contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '0',\n tokenName: 'Sai Stablecoin v1.0',\n tokenSymbol: 'SAI',\n tokenDecimal: '18',\n transactionIndex: '43',\n gas: '567156',\n gasPrice: '3000000000',\n gasUsed: '552156',\n cumulativeGasUsed: '3048261',\n input: 'deprecated',\n confirmations: '3653862',\n },\n {\n blockNumber: '8315335',\n timeStamp: '1565339223',\n hash: '0x464df60fe00b6dd04c9e8ab341d02af9b10a619d2fcd60fd2971f10edf12118f',\n nonce: '206760',\n blockHash:\n '0x98275388ef6708debe35ac7bf2e30143c9b1fd9e0e457ca03598fc1f4209e273',\n from: '0x00cfbbaf7ddb3a1476767101c12a0162e241fbad',\n contractAddress: '0x4dc3643dbc642b72c158e7f3d2ff232df61cb6ce',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '100000000000000000',\n tokenName: 'Amber',\n tokenSymbol: 'AMB',\n tokenDecimal: '18',\n transactionIndex: '186',\n gas: '60000',\n gasPrice: '2000000000',\n gasUsed: '52108',\n cumulativeGasUsed: '7490707',\n input: 'deprecated',\n confirmations: '3566580',\n },\n {\n blockNumber: '8350846',\n timeStamp: '1565815049',\n hash: '0xc0682327ad3efd56dfa33e8206b4e09efad4e419a6191076069d217e3ee2341f',\n nonce: '2506',\n blockHash:\n '0xd0aa3c0e319fdfeb21b0192cf77b9760b8668060a5977a5f10f8413531083afa',\n from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071',\n contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '4',\n tokenName: 'Sai Stablecoin v1.0',\n tokenSymbol: 'SAI',\n tokenDecimal: '18',\n transactionIndex: '48',\n gas: '578737',\n gasPrice: '3000000000',\n gasUsed: '518737',\n cumulativeGasUsed: '2848015',\n input: 'deprecated',\n confirmations: '3531069',\n },\n {\n blockNumber: '8350859',\n timeStamp: '1565815221',\n hash: '0x989ea9f3ee576fa43957f44363e839adf1a4a397c3d8392a4f7cbbf7949fd0ae',\n nonce: '2',\n blockHash:\n '0xb9cf1d29c665c052e3831b5754903e539c5b0b1d33b8bcab6cd2d450764d601f',\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',\n to: '0x09cabec1ead1c0ba254b09efb3ee13841712be14',\n value: '10000000000000000000',\n tokenName: 'Sai Stablecoin v1.0',\n tokenSymbol: 'SAI',\n tokenDecimal: '18',\n transactionIndex: '31',\n gas: '60734',\n gasPrice: '1000000000',\n gasUsed: '54745',\n cumulativeGasUsed: '7833857',\n input: 'deprecated',\n confirmations: '3531056',\n },\n {\n blockNumber: '8679548',\n timeStamp: '1570244087',\n hash: '0xc0016b89b3b525b30d73f242653b0d80ec3ebf285376dff5bb52cef3261498b2',\n nonce: '3',\n blockHash:\n '0x1ceb2f8b83087f010773e2acf63d1526633c8a884bd1980f118a1bba576be69f',\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',\n to: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071',\n value: '0',\n tokenName: 'Sai Stablecoin v1.0',\n tokenSymbol: 'SAI',\n tokenDecimal: '18',\n transactionIndex: '56',\n gas: '993379',\n gasPrice: '1440000000',\n gasUsed: '647253',\n cumulativeGasUsed: '3562204',\n input: 'deprecated',\n confirmations: '3202367',\n },\n {\n blockNumber: '8679548',\n timeStamp: '1570244087',\n hash: '0xc0016b89b3b525b30d73f242653b0d80ec3ebf285376dff5bb52cef3261498b2',\n nonce: '3',\n blockHash:\n '0x1ceb2f8b83087f010773e2acf63d1526633c8a884bd1980f118a1bba576be69f',\n from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071',\n contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '0',\n tokenName: 'Sai Stablecoin v1.0',\n tokenSymbol: 'SAI',\n tokenDecimal: '18',\n transactionIndex: '56',\n gas: '993379',\n gasPrice: '1440000000',\n gasUsed: '647253',\n cumulativeGasUsed: '3562204',\n input: 'deprecated',\n confirmations: '3202367',\n },\n {\n blockNumber: '8694142',\n timeStamp: '1570440625',\n hash: '0xd8397138bb93d56e50d01e92a9eae99ebd3ae28844acdaa4663976a5501116cf',\n nonce: '2837',\n blockHash:\n '0xba45dd64e71e146066af9b6d2dd3bc5d72f4a3399148c155dced74c139fc3c51',\n from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071',\n contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '0',\n tokenName: 'Sai Stablecoin v1.0',\n tokenSymbol: 'SAI',\n tokenDecimal: '18',\n transactionIndex: '217',\n gas: '600632',\n gasPrice: '9000000000',\n gasUsed: '570632',\n cumulativeGasUsed: '9023725',\n input: 'deprecated',\n confirmations: '3187773',\n },\n {\n blockNumber: '10877041',\n timeStamp: '1600310867',\n hash: '0xc8bd16b6b41b4c24849eb6869702e1489c808cb5b125b01f084e38fefcb5ea77',\n nonce: '4',\n blockHash:\n '0x7fa16a022bcf1f69c2d7adf6bd7d3f058e808eec5c66aaa910dfa8016a5333d1',\n from: '0x090d4613473dee047c3f2706764f49e0821d256e',\n contractAddress: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '400000000000000000000',\n tokenName: 'Uniswap',\n tokenSymbol: 'UNI',\n tokenDecimal: '18',\n transactionIndex: '42',\n gas: '90038',\n gasPrice: '550000000000',\n gasUsed: '81853',\n cumulativeGasUsed: '3163540',\n input: 'deprecated',\n confirmations: '1004874',\n },\n {\n blockNumber: '10877897',\n timeStamp: '1600321973',\n hash: '0xa7162489faef826ee77862ed5210b01726524f09428f69842118dad394842d62',\n nonce: '6',\n blockHash:\n '0xa74eb9d16f65f307dde4ce58c813c981b28f242edf1090ee2ac42caac9dccaca',\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n contractAddress: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',\n to: '0x5e736f1f25992b2cad20ded179a52823d3d24b26',\n value: '400000000000000000000',\n tokenName: 'Uniswap',\n tokenSymbol: 'UNI',\n tokenDecimal: '18',\n transactionIndex: '86',\n gas: '60759',\n gasPrice: '640000000000',\n gasUsed: '25506',\n cumulativeGasUsed: '4408393',\n input: 'deprecated',\n confirmations: '1004018',\n },\n];\n\nexport const txsInStateMock = (\n ethTxHash: string,\n tokenTxHash: string,\n): TransactionMeta[] => [\n {\n id: 'token-transaction-id',\n chainId: '1',\n status: TransactionStatus.confirmed,\n time: 1615497996125,\n transaction: {\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n data: '0x',\n gas: '624874',\n gasPrice: '20000000000',\n gasUsed: '21000',\n nonce: '0x12',\n to: '0x881d40237659c251811cec9c364ef91dc08d300c',\n value: '0x0',\n },\n transactionHash: tokenTxHash,\n toSmartContract: true,\n },\n {\n id: 'eth-transaction-id',\n chainId: '1',\n status: TransactionStatus.confirmed,\n time: 1615497996125,\n transaction: {\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n data: '0x',\n gas: '0x51d68',\n gasPrice: '0x2540be400',\n gasUsed: '0x5208',\n nonce: '0x12',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '100000000000000000',\n },\n transactionHash: ethTxHash,\n toSmartContract: false,\n },\n];\n\nexport const txsInStateWithOutdatedStatusMock = (\n ethTxHash: string,\n tokenTxHash: string,\n): TransactionMeta[] => [\n {\n id: 'token-transaction-id',\n chainId: '1',\n status: TransactionStatus.rejected,\n time: 1615497996125,\n transaction: {\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n data: '0x',\n gas: '624874',\n gasPrice: '20000000000',\n gasUsed: '21000',\n nonce: '0x12',\n to: '0x881d40237659c251811cec9c364ef91dc08d300c',\n value: '0x0',\n },\n transactionHash: tokenTxHash,\n toSmartContract: true,\n },\n {\n id: 'eth-transaction-id',\n chainId: '1',\n status: TransactionStatus.rejected,\n time: 1615497996125,\n transaction: {\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n data: '0x',\n gas: '0x51d68',\n gasPrice: '0x2540be400',\n gasUsed: '0x5208',\n nonce: '0x12',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '100000000000000000',\n },\n transactionHash: ethTxHash,\n toSmartContract: false,\n },\n];\n\nexport const txsInStateWithOutdatedGasDataMock = (\n ethTxHash: string,\n tokenTxHash: string,\n): TransactionMeta[] => [\n {\n id: 'token-transaction-id',\n chainId: '1',\n status: TransactionStatus.confirmed,\n time: 1615497996125,\n transaction: {\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n data: '0x',\n gas: '624874',\n gasPrice: '20000000000',\n gasUsed: undefined,\n nonce: '0x12',\n to: '0x881d40237659c251811cec9c364ef91dc08d300c',\n value: '0x0',\n },\n transactionHash: tokenTxHash,\n toSmartContract: true,\n },\n {\n id: 'eth-transaction-id',\n chainId: '1',\n status: TransactionStatus.confirmed,\n time: 1615497996125,\n transaction: {\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n data: '0x',\n gas: '0x51d68',\n gasPrice: '0x2540be400',\n gasUsed: undefined,\n nonce: '0x12',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '100000000000000000',\n },\n transactionHash: ethTxHash,\n toSmartContract: false,\n },\n];\n\nexport const txsInStateWithOutdatedStatusAndGasDataMock = (\n ethTxHash: string,\n tokenTxHash: string,\n): TransactionMeta[] => [\n {\n id: 'token-transaction-id',\n chainId: '1',\n status: TransactionStatus.rejected,\n time: 1615497996125,\n transaction: {\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n data: '0x',\n gas: '624874',\n gasPrice: '20000000000',\n gasUsed: undefined,\n nonce: '0x12',\n to: '0x881d40237659c251811cec9c364ef91dc08d300c',\n value: '0x0',\n },\n transactionHash: tokenTxHash,\n toSmartContract: true,\n },\n {\n id: 'eth-transaction-id',\n chainId: '1',\n status: TransactionStatus.rejected,\n time: 1615497996125,\n transaction: {\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n data: '0x',\n gas: '0x51d68',\n gasPrice: '0x2540be400',\n gasUsed: undefined,\n nonce: '0x12',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '100000000000000000',\n },\n transactionHash: ethTxHash,\n toSmartContract: false,\n },\n];\n"]} \ No newline at end of file diff --git a/dist/user/AddressBookController.d.ts b/dist/user/AddressBookController.d.ts new file mode 100644 index 0000000000..8a94510a71 --- /dev/null +++ b/dist/user/AddressBookController.d.ts @@ -0,0 +1,83 @@ +import { BaseController, BaseConfig, BaseState } from '../BaseController'; +/** + * @type ContactEntry + * + * ContactEntry representation + * @property address - Hex address of a recipient account + * @property name - Nickname associated with this address + * @property importTime - Data time when an account as created/imported + */ +export interface ContactEntry { + address: string; + name: string; + importTime?: number; +} +/** + * @type AddressBookEntry + * + * AddressBookEntry representation + * @property address - Hex address of a recipient account + * @property name - Nickname associated with this address + * @property chainId - Chain id identifies the current chain + * @property memo - User's note about address + * @property isEns - is the entry an ENS name + */ +export interface AddressBookEntry { + address: string; + name: string; + chainId: string; + memo: string; + isEns: boolean; +} +/** + * @type AddressBookState + * + * Address book controller state + * @property addressBook - Array of contact entry objects + */ +export interface AddressBookState extends BaseState { + addressBook: { + [chainId: string]: { + [address: string]: AddressBookEntry; + }; + }; +} +/** + * Controller that manages a list of recipient addresses associated with nicknames + */ +export declare class AddressBookController extends BaseController { + /** + * Name of this controller used during composition + */ + name: string; + /** + * Creates an AddressBookController instance. + * + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor(config?: Partial, state?: Partial); + /** + * Remove all contract entries. + */ + clear(): void; + /** + * Remove a contract entry by address. + * + * @param chainId - Chain id identifies the current chain. + * @param address - Recipient address to delete. + * @returns Whether the entry was deleted. + */ + delete(chainId: string, address: string): boolean; + /** + * Add or update a contact entry by address. + * + * @param address - Recipient address to add or update. + * @param name - Nickname to associate with this address. + * @param chainId - Chain id identifies the current chain. + * @param memo - User's note about address. + * @returns Boolean indicating if the address was successfully set. + */ + set(address: string, name: string, chainId?: string, memo?: string): boolean; +} +export default AddressBookController; diff --git a/dist/user/AddressBookController.js b/dist/user/AddressBookController.js new file mode 100644 index 0000000000..97e088e14a --- /dev/null +++ b/dist/user/AddressBookController.js @@ -0,0 +1,87 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AddressBookController = void 0; +const util_1 = require("../util"); +const BaseController_1 = require("../BaseController"); +/** + * Controller that manages a list of recipient addresses associated with nicknames + */ +class AddressBookController extends BaseController_1.BaseController { + /** + * Creates an AddressBookController instance. + * + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor(config, state) { + super(config, state); + /** + * Name of this controller used during composition + */ + this.name = 'AddressBookController'; + this.defaultState = { addressBook: {} }; + this.initialize(); + } + /** + * Remove all contract entries. + */ + clear() { + this.update({ addressBook: {} }); + } + /** + * Remove a contract entry by address. + * + * @param chainId - Chain id identifies the current chain. + * @param address - Recipient address to delete. + * @returns Whether the entry was deleted. + */ + delete(chainId, address) { + address = (0, util_1.toChecksumHexAddress)(address); + if (!(0, util_1.isValidHexAddress)(address) || + !this.state.addressBook[chainId] || + !this.state.addressBook[chainId][address]) { + return false; + } + const addressBook = Object.assign({}, this.state.addressBook); + delete addressBook[chainId][address]; + if (Object.keys(addressBook[chainId]).length === 0) { + delete addressBook[chainId]; + } + this.update({ addressBook }); + return true; + } + /** + * Add or update a contact entry by address. + * + * @param address - Recipient address to add or update. + * @param name - Nickname to associate with this address. + * @param chainId - Chain id identifies the current chain. + * @param memo - User's note about address. + * @returns Boolean indicating if the address was successfully set. + */ + set(address, name, chainId = '1', memo = '') { + address = (0, util_1.toChecksumHexAddress)(address); + if (!(0, util_1.isValidHexAddress)(address)) { + return false; + } + const entry = { + address, + chainId, + isEns: false, + memo, + name, + }; + const ensName = (0, util_1.normalizeEnsName)(name); + if (ensName) { + entry.name = ensName; + entry.isEns = true; + } + this.update({ + addressBook: Object.assign(Object.assign({}, this.state.addressBook), { [chainId]: Object.assign(Object.assign({}, this.state.addressBook[chainId]), { [address]: entry }) }), + }); + return true; + } +} +exports.AddressBookController = AddressBookController; +exports.default = AddressBookController; +//# sourceMappingURL=AddressBookController.js.map \ No newline at end of file diff --git a/dist/user/AddressBookController.js.map b/dist/user/AddressBookController.js.map new file mode 100644 index 0000000000..7ee321edb1 --- /dev/null +++ b/dist/user/AddressBookController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"AddressBookController.js","sourceRoot":"","sources":["../../src/user/AddressBookController.ts"],"names":[],"mappings":";;;AAAA,kCAIiB;AACjB,sDAA0E;AA4C1E;;GAEG;AACH,MAAa,qBAAsB,SAAQ,+BAG1C;IAMC;;;;;OAKG;IACH,YAAY,MAA4B,EAAE,KAAiC;QACzE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAZvB;;WAEG;QACM,SAAI,GAAG,uBAAuB,CAAC;QAWtC,IAAI,CAAC,YAAY,GAAG,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;QAExC,IAAI,CAAC,UAAU,EAAE,CAAC;IACpB,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;IACnC,CAAC;IAED;;;;;;OAMG;IACH,MAAM,CAAC,OAAe,EAAE,OAAe;QACrC,OAAO,GAAG,IAAA,2BAAoB,EAAC,OAAO,CAAC,CAAC;QACxC,IACE,CAAC,IAAA,wBAAiB,EAAC,OAAO,CAAC;YAC3B,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC;YAChC,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EACzC;YACA,OAAO,KAAK,CAAC;SACd;QAED,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAC9D,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC;QAErC,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE;YAClD,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC;SAC7B;QAED,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;;;OAQG;IACH,GAAG,CAAC,OAAe,EAAE,IAAY,EAAE,OAAO,GAAG,GAAG,EAAE,IAAI,GAAG,EAAE;QACzD,OAAO,GAAG,IAAA,2BAAoB,EAAC,OAAO,CAAC,CAAC;QACxC,IAAI,CAAC,IAAA,wBAAiB,EAAC,OAAO,CAAC,EAAE;YAC/B,OAAO,KAAK,CAAC;SACd;QAED,MAAM,KAAK,GAAG;YACZ,OAAO;YACP,OAAO;YACP,KAAK,EAAE,KAAK;YACZ,IAAI;YACJ,IAAI;SACL,CAAC;QAEF,MAAM,OAAO,GAAG,IAAA,uBAAgB,EAAC,IAAI,CAAC,CAAC;QACvC,IAAI,OAAO,EAAE;YACX,KAAK,CAAC,IAAI,GAAG,OAAO,CAAC;YACrB,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC;SACpB;QAED,IAAI,CAAC,MAAM,CAAC;YACV,WAAW,kCACN,IAAI,CAAC,KAAK,CAAC,WAAW,KACzB,CAAC,OAAO,CAAC,kCACJ,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,KAClC,CAAC,OAAO,CAAC,EAAE,KAAK,MAEnB;SACF,CAAC,CAAC;QAEH,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAnGD,sDAmGC;AAED,kBAAe,qBAAqB,CAAC","sourcesContent":["import {\n normalizeEnsName,\n isValidHexAddress,\n toChecksumHexAddress,\n} from '../util';\nimport { BaseController, BaseConfig, BaseState } from '../BaseController';\n\n/**\n * @type ContactEntry\n *\n * ContactEntry representation\n * @property address - Hex address of a recipient account\n * @property name - Nickname associated with this address\n * @property importTime - Data time when an account as created/imported\n */\nexport interface ContactEntry {\n address: string;\n name: string;\n importTime?: number;\n}\n\n/**\n * @type AddressBookEntry\n *\n * AddressBookEntry representation\n * @property address - Hex address of a recipient account\n * @property name - Nickname associated with this address\n * @property chainId - Chain id identifies the current chain\n * @property memo - User's note about address\n * @property isEns - is the entry an ENS name\n */\nexport interface AddressBookEntry {\n address: string;\n name: string;\n chainId: string;\n memo: string;\n isEns: boolean;\n}\n\n/**\n * @type AddressBookState\n *\n * Address book controller state\n * @property addressBook - Array of contact entry objects\n */\nexport interface AddressBookState extends BaseState {\n addressBook: { [chainId: string]: { [address: string]: AddressBookEntry } };\n}\n\n/**\n * Controller that manages a list of recipient addresses associated with nicknames\n */\nexport class AddressBookController extends BaseController<\n BaseConfig,\n AddressBookState\n> {\n /**\n * Name of this controller used during composition\n */\n override name = 'AddressBookController';\n\n /**\n * Creates an AddressBookController instance.\n *\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(config?: Partial, state?: Partial) {\n super(config, state);\n\n this.defaultState = { addressBook: {} };\n\n this.initialize();\n }\n\n /**\n * Remove all contract entries.\n */\n clear() {\n this.update({ addressBook: {} });\n }\n\n /**\n * Remove a contract entry by address.\n *\n * @param chainId - Chain id identifies the current chain.\n * @param address - Recipient address to delete.\n * @returns Whether the entry was deleted.\n */\n delete(chainId: string, address: string) {\n address = toChecksumHexAddress(address);\n if (\n !isValidHexAddress(address) ||\n !this.state.addressBook[chainId] ||\n !this.state.addressBook[chainId][address]\n ) {\n return false;\n }\n\n const addressBook = Object.assign({}, this.state.addressBook);\n delete addressBook[chainId][address];\n\n if (Object.keys(addressBook[chainId]).length === 0) {\n delete addressBook[chainId];\n }\n\n this.update({ addressBook });\n return true;\n }\n\n /**\n * Add or update a contact entry by address.\n *\n * @param address - Recipient address to add or update.\n * @param name - Nickname to associate with this address.\n * @param chainId - Chain id identifies the current chain.\n * @param memo - User's note about address.\n * @returns Boolean indicating if the address was successfully set.\n */\n set(address: string, name: string, chainId = '1', memo = '') {\n address = toChecksumHexAddress(address);\n if (!isValidHexAddress(address)) {\n return false;\n }\n\n const entry = {\n address,\n chainId,\n isEns: false,\n memo,\n name,\n };\n\n const ensName = normalizeEnsName(name);\n if (ensName) {\n entry.name = ensName;\n entry.isEns = true;\n }\n\n this.update({\n addressBook: {\n ...this.state.addressBook,\n [chainId]: {\n ...this.state.addressBook[chainId],\n [address]: entry,\n },\n },\n });\n\n return true;\n }\n}\n\nexport default AddressBookController;\n"]} \ No newline at end of file diff --git a/dist/user/PreferencesController.d.ts b/dist/user/PreferencesController.d.ts new file mode 100644 index 0000000000..532860bc8f --- /dev/null +++ b/dist/user/PreferencesController.d.ts @@ -0,0 +1,157 @@ +import { BaseController, BaseConfig, BaseState } from '../BaseController'; +import { ContactEntry } from './AddressBookController'; +/** + * Custom RPC network information + * + * @property rpcUrl - RPC target URL. + * @property chainId - Network ID as per EIP-155 + * @property nickname - Personalized network name. + * @property ticker - Currency ticker. + * @property rpcPrefs - Personalized preferences. + */ +export interface FrequentRpc { + rpcUrl: string; + chainId?: number; + nickname?: string; + ticker?: string; + rpcPrefs?: RpcPreferences; +} +/** + * Custom RPC network preferences + * + * @param blockExplorerUrl - Block explorer URL. + */ +export interface RpcPreferences { + blockExplorerUrl: string; +} +/** + * @type PreferencesState + * + * Preferences controller state + * @property featureFlags - Map of specific features to enable or disable + * @property frequentRpcList - A list of custom RPCs to provide the user + * @property identities - Map of addresses to ContactEntry objects + * @property lostIdentities - Map of lost addresses to ContactEntry objects + * @property selectedAddress - Current coinbase account + */ +export interface PreferencesState extends BaseState { + featureFlags: { + [feature: string]: boolean; + }; + frequentRpcList: FrequentRpc[]; + ipfsGateway: string; + identities: { + [address: string]: ContactEntry; + }; + lostIdentities: { + [address: string]: ContactEntry; + }; + selectedAddress: string; + useTokenDetection: boolean; + useCollectibleDetection: boolean; + openSeaEnabled: boolean; +} +/** + * Controller that stores shared settings and exposes convenience methods + */ +export declare class PreferencesController extends BaseController { + /** + * Name of this controller used during composition + */ + name: string; + /** + * Creates a PreferencesController instance. + * + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor(config?: Partial, state?: Partial); + /** + * Adds identities to state. + * + * @param addresses - List of addresses to use to generate new identities. + */ + addIdentities(addresses: string[]): void; + /** + * Removes an identity from state. + * + * @param address - Address of the identity to remove. + */ + removeIdentity(address: string): void; + /** + * Associates a new label with an identity. + * + * @param address - Address of the identity to associate. + * @param label - New label to assign. + */ + setAccountLabel(address: string, label: string): void; + /** + * Enable or disable a specific feature flag. + * + * @param feature - Feature to toggle. + * @param activated - Value to assign. + */ + setFeatureFlag(feature: string, activated: boolean): void; + /** + * Synchronizes the current identity list with new identities. + * + * @param addresses - List of addresses corresponding to identities to sync. + * @returns Newly-selected address after syncing. + */ + syncIdentities(addresses: string[]): string; + /** + * Generates and stores a new list of stored identities based on address. If the selected address + * is unset, or if it refers to an identity that was removed, it will be set to the first + * identity. + * + * @param addresses - List of addresses to use as a basis for each identity. + */ + updateIdentities(addresses: string[]): void; + /** + * Adds custom RPC URL to state. + * + * @param url - The custom RPC URL. + * @param chainId - The chain ID of the network, as per EIP-155. + * @param ticker - Currency ticker. + * @param nickname - Personalized network name. + * @param rpcPrefs - Personalized preferences. + */ + addToFrequentRpcList(url: string, chainId?: number, ticker?: string, nickname?: string, rpcPrefs?: RpcPreferences): void; + /** + * Removes custom RPC URL from state. + * + * @param url - Custom RPC URL. + */ + removeFromFrequentRpcList(url: string): void; + /** + * Sets selected address. + * + * @param selectedAddress - Ethereum address. + */ + setSelectedAddress(selectedAddress: string): void; + /** + * Sets new IPFS gateway. + * + * @param ipfsGateway - IPFS gateway string. + */ + setIpfsGateway(ipfsGateway: string): void; + /** + * Toggle the token detection setting. + * + * @param useTokenDetection - Boolean indicating user preference on token detection. + */ + setUseTokenDetection(useTokenDetection: boolean): void; + /** + * Toggle the collectible detection setting. + * + * @param useCollectibleDetection - Boolean indicating user preference on collectible detection. + */ + setUseCollectibleDetection(useCollectibleDetection: boolean): void; + /** + * Toggle the opensea enabled setting. + * + * @param openSeaEnabled - Boolean indicating user preference on using OpenSea's API. + */ + setOpenSeaEnabled(openSeaEnabled: boolean): void; +} +export default PreferencesController; diff --git a/dist/user/PreferencesController.js b/dist/user/PreferencesController.js new file mode 100644 index 0000000000..e0d8c2354f --- /dev/null +++ b/dist/user/PreferencesController.js @@ -0,0 +1,243 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PreferencesController = void 0; +const BaseController_1 = require("../BaseController"); +const util_1 = require("../util"); +/** + * Controller that stores shared settings and exposes convenience methods + */ +class PreferencesController extends BaseController_1.BaseController { + /** + * Creates a PreferencesController instance. + * + * @param config - Initial options used to configure this controller. + * @param state - Initial state to set on this controller. + */ + constructor(config, state) { + super(config, state); + /** + * Name of this controller used during composition + */ + this.name = 'PreferencesController'; + this.defaultState = { + featureFlags: {}, + frequentRpcList: [], + identities: {}, + ipfsGateway: 'https://ipfs.io/ipfs/', + lostIdentities: {}, + selectedAddress: '', + useTokenDetection: true, + useCollectibleDetection: false, + openSeaEnabled: false, + }; + this.initialize(); + } + /** + * Adds identities to state. + * + * @param addresses - List of addresses to use to generate new identities. + */ + addIdentities(addresses) { + const { identities } = this.state; + addresses.forEach((address) => { + address = (0, util_1.toChecksumHexAddress)(address); + if (identities[address]) { + return; + } + const identityCount = Object.keys(identities).length; + identities[address] = { + name: `Account ${identityCount + 1}`, + address, + importTime: Date.now(), + }; + }); + this.update({ identities: Object.assign({}, identities) }); + } + /** + * Removes an identity from state. + * + * @param address - Address of the identity to remove. + */ + removeIdentity(address) { + address = (0, util_1.toChecksumHexAddress)(address); + const { identities } = this.state; + if (!identities[address]) { + return; + } + delete identities[address]; + this.update({ identities: Object.assign({}, identities) }); + if (address === this.state.selectedAddress) { + this.update({ selectedAddress: Object.keys(identities)[0] }); + } + } + /** + * Associates a new label with an identity. + * + * @param address - Address of the identity to associate. + * @param label - New label to assign. + */ + setAccountLabel(address, label) { + address = (0, util_1.toChecksumHexAddress)(address); + const { identities } = this.state; + identities[address] = identities[address] || {}; + identities[address].name = label; + this.update({ identities: Object.assign({}, identities) }); + } + /** + * Enable or disable a specific feature flag. + * + * @param feature - Feature to toggle. + * @param activated - Value to assign. + */ + setFeatureFlag(feature, activated) { + const oldFeatureFlags = this.state.featureFlags; + const featureFlags = Object.assign(Object.assign({}, oldFeatureFlags), { [feature]: activated }); + this.update({ featureFlags: Object.assign({}, featureFlags) }); + } + /** + * Synchronizes the current identity list with new identities. + * + * @param addresses - List of addresses corresponding to identities to sync. + * @returns Newly-selected address after syncing. + */ + syncIdentities(addresses) { + addresses = addresses.map((address) => (0, util_1.toChecksumHexAddress)(address)); + const { identities, lostIdentities } = this.state; + const newlyLost = {}; + for (const identity in identities) { + if (addresses.indexOf(identity) === -1) { + newlyLost[identity] = identities[identity]; + delete identities[identity]; + } + } + if (Object.keys(newlyLost).length > 0) { + for (const key in newlyLost) { + lostIdentities[key] = newlyLost[key]; + } + } + this.update({ + identities: Object.assign({}, identities), + lostIdentities: Object.assign({}, lostIdentities), + }); + this.addIdentities(addresses); + if (addresses.indexOf(this.state.selectedAddress) === -1) { + this.update({ selectedAddress: addresses[0] }); + } + return this.state.selectedAddress; + } + /** + * Generates and stores a new list of stored identities based on address. If the selected address + * is unset, or if it refers to an identity that was removed, it will be set to the first + * identity. + * + * @param addresses - List of addresses to use as a basis for each identity. + */ + updateIdentities(addresses) { + addresses = addresses.map((address) => (0, util_1.toChecksumHexAddress)(address)); + const oldIdentities = this.state.identities; + const identities = addresses.reduce((ids, address, index) => { + ids[address] = oldIdentities[address] || { + address, + name: `Account ${index + 1}`, + importTime: Date.now(), + }; + return ids; + }, {}); + let { selectedAddress } = this.state; + if (!Object.keys(identities).includes(selectedAddress)) { + selectedAddress = Object.keys(identities)[0]; + } + this.update({ identities: Object.assign({}, identities), selectedAddress }); + } + /** + * Adds custom RPC URL to state. + * + * @param url - The custom RPC URL. + * @param chainId - The chain ID of the network, as per EIP-155. + * @param ticker - Currency ticker. + * @param nickname - Personalized network name. + * @param rpcPrefs - Personalized preferences. + */ + addToFrequentRpcList(url, chainId, ticker, nickname, rpcPrefs) { + const { frequentRpcList } = this.state; + const index = frequentRpcList.findIndex(({ rpcUrl }) => { + return rpcUrl === url; + }); + if (index !== -1) { + frequentRpcList.splice(index, 1); + } + const newFrequestRpc = { + rpcUrl: url, + chainId, + ticker, + nickname, + rpcPrefs, + }; + frequentRpcList.push(newFrequestRpc); + this.update({ frequentRpcList: [...frequentRpcList] }); + } + /** + * Removes custom RPC URL from state. + * + * @param url - Custom RPC URL. + */ + removeFromFrequentRpcList(url) { + const { frequentRpcList } = this.state; + const index = frequentRpcList.findIndex(({ rpcUrl }) => { + return rpcUrl === url; + }); + if (index !== -1) { + frequentRpcList.splice(index, 1); + } + this.update({ frequentRpcList: [...frequentRpcList] }); + } + /** + * Sets selected address. + * + * @param selectedAddress - Ethereum address. + */ + setSelectedAddress(selectedAddress) { + this.update({ selectedAddress: (0, util_1.toChecksumHexAddress)(selectedAddress) }); + } + /** + * Sets new IPFS gateway. + * + * @param ipfsGateway - IPFS gateway string. + */ + setIpfsGateway(ipfsGateway) { + this.update({ ipfsGateway }); + } + /** + * Toggle the token detection setting. + * + * @param useTokenDetection - Boolean indicating user preference on token detection. + */ + setUseTokenDetection(useTokenDetection) { + this.update({ useTokenDetection }); + } + /** + * Toggle the collectible detection setting. + * + * @param useCollectibleDetection - Boolean indicating user preference on collectible detection. + */ + setUseCollectibleDetection(useCollectibleDetection) { + if (useCollectibleDetection && !this.state.openSeaEnabled) { + throw new Error('useCollectibleDetection cannot be enabled if openSeaEnabled is false'); + } + this.update({ useCollectibleDetection }); + } + /** + * Toggle the opensea enabled setting. + * + * @param openSeaEnabled - Boolean indicating user preference on using OpenSea's API. + */ + setOpenSeaEnabled(openSeaEnabled) { + this.update({ openSeaEnabled }); + if (!openSeaEnabled) { + this.update({ useCollectibleDetection: false }); + } + } +} +exports.PreferencesController = PreferencesController; +exports.default = PreferencesController; +//# sourceMappingURL=PreferencesController.js.map \ No newline at end of file diff --git a/dist/user/PreferencesController.js.map b/dist/user/PreferencesController.js.map new file mode 100644 index 0000000000..7e569e5a74 --- /dev/null +++ b/dist/user/PreferencesController.js.map @@ -0,0 +1 @@ +{"version":3,"file":"PreferencesController.js","sourceRoot":"","sources":["../../src/user/PreferencesController.ts"],"names":[],"mappings":";;;AAAA,sDAA0E;AAC1E,kCAA+C;AAmD/C;;GAEG;AACH,MAAa,qBAAsB,SAAQ,+BAG1C;IAMC;;;;;OAKG;IACH,YAAY,MAA4B,EAAE,KAAiC;QACzE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAZvB;;WAEG;QACM,SAAI,GAAG,uBAAuB,CAAC;QAUtC,IAAI,CAAC,YAAY,GAAG;YAClB,YAAY,EAAE,EAAE;YAChB,eAAe,EAAE,EAAE;YACnB,UAAU,EAAE,EAAE;YACd,WAAW,EAAE,uBAAuB;YACpC,cAAc,EAAE,EAAE;YAClB,eAAe,EAAE,EAAE;YACnB,iBAAiB,EAAE,IAAI;YACvB,uBAAuB,EAAE,KAAK;YAC9B,cAAc,EAAE,KAAK;SACtB,CAAC;QACF,IAAI,CAAC,UAAU,EAAE,CAAC;IACpB,CAAC;IAED;;;;OAIG;IACH,aAAa,CAAC,SAAmB;QAC/B,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;QAClC,SAAS,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC5B,OAAO,GAAG,IAAA,2BAAoB,EAAC,OAAO,CAAC,CAAC;YACxC,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE;gBACvB,OAAO;aACR;YACD,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC;YAErD,UAAU,CAAC,OAAO,CAAC,GAAG;gBACpB,IAAI,EAAE,WAAW,aAAa,GAAG,CAAC,EAAE;gBACpC,OAAO;gBACP,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE;aACvB,CAAC;QACJ,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,EAAE,UAAU,oBAAO,UAAU,CAAE,EAAE,CAAC,CAAC;IACjD,CAAC;IAED;;;;OAIG;IACH,cAAc,CAAC,OAAe;QAC5B,OAAO,GAAG,IAAA,2BAAoB,EAAC,OAAO,CAAC,CAAC;QACxC,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;QAClC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE;YACxB,OAAO;SACR;QACD,OAAO,UAAU,CAAC,OAAO,CAAC,CAAC;QAC3B,IAAI,CAAC,MAAM,CAAC,EAAE,UAAU,oBAAO,UAAU,CAAE,EAAE,CAAC,CAAC;QAC/C,IAAI,OAAO,KAAK,IAAI,CAAC,KAAK,CAAC,eAAe,EAAE;YAC1C,IAAI,CAAC,MAAM,CAAC,EAAE,eAAe,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;SAC9D;IACH,CAAC;IAED;;;;;OAKG;IACH,eAAe,CAAC,OAAe,EAAE,KAAa;QAC5C,OAAO,GAAG,IAAA,2BAAoB,EAAC,OAAO,CAAC,CAAC;QACxC,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;QAClC,UAAU,CAAC,OAAO,CAAC,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QAChD,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC;QACjC,IAAI,CAAC,MAAM,CAAC,EAAE,UAAU,oBAAO,UAAU,CAAE,EAAE,CAAC,CAAC;IACjD,CAAC;IAED;;;;;OAKG;IACH,cAAc,CAAC,OAAe,EAAE,SAAkB;QAChD,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC;QAChD,MAAM,YAAY,mCAAQ,eAAe,GAAK,EAAE,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,CAAE,CAAC;QACzE,IAAI,CAAC,MAAM,CAAC,EAAE,YAAY,oBAAO,YAAY,CAAE,EAAE,CAAC,CAAC;IACrD,CAAC;IAED;;;;;OAKG;IACH,cAAc,CAAC,SAAmB;QAChC,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,OAAe,EAAE,EAAE,CAC5C,IAAA,2BAAoB,EAAC,OAAO,CAAC,CAC9B,CAAC;QACF,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;QAClD,MAAM,SAAS,GAAwC,EAAE,CAAC;QAE1D,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE;YACjC,IAAI,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE;gBACtC,SAAS,CAAC,QAAQ,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;gBAC3C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC;aAC7B;SACF;QAED,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE;YACrC,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE;gBAC3B,cAAc,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;aACtC;SACF;QAED,IAAI,CAAC,MAAM,CAAC;YACV,UAAU,oBAAO,UAAU,CAAE;YAC7B,cAAc,oBAAO,cAAc,CAAE;SACtC,CAAC,CAAC;QACH,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;QAE9B,IAAI,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,EAAE;YACxD,IAAI,CAAC,MAAM,CAAC,EAAE,eAAe,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;SAChD;QAED,OAAO,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC;IACpC,CAAC;IAED;;;;;;OAMG;IACH,gBAAgB,CAAC,SAAmB;QAClC,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,OAAe,EAAE,EAAE,CAC5C,IAAA,2BAAoB,EAAC,OAAO,CAAC,CAC9B,CAAC;QACF,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC;QAC5C,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,CACjC,CAAC,GAAwC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YAC3D,GAAG,CAAC,OAAO,CAAC,GAAG,aAAa,CAAC,OAAO,CAAC,IAAI;gBACvC,OAAO;gBACP,IAAI,EAAE,WAAW,KAAK,GAAG,CAAC,EAAE;gBAC5B,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE;aACvB,CAAC;YACF,OAAO,GAAG,CAAC;QACb,CAAC,EACD,EAAE,CACH,CAAC;QACF,IAAI,EAAE,eAAe,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;QACrC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE;YACtD,eAAe,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;SAC9C;QACD,IAAI,CAAC,MAAM,CAAC,EAAE,UAAU,oBAAO,UAAU,CAAE,EAAE,eAAe,EAAE,CAAC,CAAC;IAClE,CAAC;IAED;;;;;;;;OAQG;IACH,oBAAoB,CAClB,GAAW,EACX,OAAgB,EAChB,MAAe,EACf,QAAiB,EACjB,QAAyB;QAEzB,MAAM,EAAE,eAAe,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;QACvC,MAAM,KAAK,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE;YACrD,OAAO,MAAM,KAAK,GAAG,CAAC;QACxB,CAAC,CAAC,CAAC;QACH,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE;YAChB,eAAe,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;SAClC;QACD,MAAM,cAAc,GAAgB;YAClC,MAAM,EAAE,GAAG;YACX,OAAO;YACP,MAAM;YACN,QAAQ;YACR,QAAQ;SACT,CAAC;QACF,eAAe,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACrC,IAAI,CAAC,MAAM,CAAC,EAAE,eAAe,EAAE,CAAC,GAAG,eAAe,CAAC,EAAE,CAAC,CAAC;IACzD,CAAC;IAED;;;;OAIG;IACH,yBAAyB,CAAC,GAAW;QACnC,MAAM,EAAE,eAAe,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;QACvC,MAAM,KAAK,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE;YACrD,OAAO,MAAM,KAAK,GAAG,CAAC;QACxB,CAAC,CAAC,CAAC;QACH,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE;YAChB,eAAe,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;SAClC;QACD,IAAI,CAAC,MAAM,CAAC,EAAE,eAAe,EAAE,CAAC,GAAG,eAAe,CAAC,EAAE,CAAC,CAAC;IACzD,CAAC;IAED;;;;OAIG;IACH,kBAAkB,CAAC,eAAuB;QACxC,IAAI,CAAC,MAAM,CAAC,EAAE,eAAe,EAAE,IAAA,2BAAoB,EAAC,eAAe,CAAC,EAAE,CAAC,CAAC;IAC1E,CAAC;IAED;;;;OAIG;IACH,cAAc,CAAC,WAAmB;QAChC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC;IAC/B,CAAC;IAED;;;;OAIG;IACH,oBAAoB,CAAC,iBAA0B;QAC7C,IAAI,CAAC,MAAM,CAAC,EAAE,iBAAiB,EAAE,CAAC,CAAC;IACrC,CAAC;IAED;;;;OAIG;IACH,0BAA0B,CAAC,uBAAgC;QACzD,IAAI,uBAAuB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE;YACzD,MAAM,IAAI,KAAK,CACb,sEAAsE,CACvE,CAAC;SACH;QACD,IAAI,CAAC,MAAM,CAAC,EAAE,uBAAuB,EAAE,CAAC,CAAC;IAC3C,CAAC;IAED;;;;OAIG;IACH,iBAAiB,CAAC,cAAuB;QACvC,IAAI,CAAC,MAAM,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC;QAChC,IAAI,CAAC,cAAc,EAAE;YACnB,IAAI,CAAC,MAAM,CAAC,EAAE,uBAAuB,EAAE,KAAK,EAAE,CAAC,CAAC;SACjD;IACH,CAAC;CACF;AA7QD,sDA6QC;AAED,kBAAe,qBAAqB,CAAC","sourcesContent":["import { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport { toChecksumHexAddress } from '../util';\nimport { ContactEntry } from './AddressBookController';\n\n/**\n * Custom RPC network information\n *\n * @property rpcUrl - RPC target URL.\n * @property chainId - Network ID as per EIP-155\n * @property nickname - Personalized network name.\n * @property ticker - Currency ticker.\n * @property rpcPrefs - Personalized preferences.\n */\nexport interface FrequentRpc {\n rpcUrl: string;\n chainId?: number;\n nickname?: string;\n ticker?: string;\n rpcPrefs?: RpcPreferences;\n}\n\n/**\n * Custom RPC network preferences\n *\n * @param blockExplorerUrl - Block explorer URL.\n */\nexport interface RpcPreferences {\n blockExplorerUrl: string;\n}\n\n/**\n * @type PreferencesState\n *\n * Preferences controller state\n * @property featureFlags - Map of specific features to enable or disable\n * @property frequentRpcList - A list of custom RPCs to provide the user\n * @property identities - Map of addresses to ContactEntry objects\n * @property lostIdentities - Map of lost addresses to ContactEntry objects\n * @property selectedAddress - Current coinbase account\n */\nexport interface PreferencesState extends BaseState {\n featureFlags: { [feature: string]: boolean };\n frequentRpcList: FrequentRpc[];\n ipfsGateway: string;\n identities: { [address: string]: ContactEntry };\n lostIdentities: { [address: string]: ContactEntry };\n selectedAddress: string;\n useTokenDetection: boolean;\n useCollectibleDetection: boolean;\n openSeaEnabled: boolean;\n}\n\n/**\n * Controller that stores shared settings and exposes convenience methods\n */\nexport class PreferencesController extends BaseController<\n BaseConfig,\n PreferencesState\n> {\n /**\n * Name of this controller used during composition\n */\n override name = 'PreferencesController';\n\n /**\n * Creates a PreferencesController instance.\n *\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(config?: Partial, state?: Partial) {\n super(config, state);\n this.defaultState = {\n featureFlags: {},\n frequentRpcList: [],\n identities: {},\n ipfsGateway: 'https://ipfs.io/ipfs/',\n lostIdentities: {},\n selectedAddress: '',\n useTokenDetection: true,\n useCollectibleDetection: false,\n openSeaEnabled: false,\n };\n this.initialize();\n }\n\n /**\n * Adds identities to state.\n *\n * @param addresses - List of addresses to use to generate new identities.\n */\n addIdentities(addresses: string[]) {\n const { identities } = this.state;\n addresses.forEach((address) => {\n address = toChecksumHexAddress(address);\n if (identities[address]) {\n return;\n }\n const identityCount = Object.keys(identities).length;\n\n identities[address] = {\n name: `Account ${identityCount + 1}`,\n address,\n importTime: Date.now(),\n };\n });\n this.update({ identities: { ...identities } });\n }\n\n /**\n * Removes an identity from state.\n *\n * @param address - Address of the identity to remove.\n */\n removeIdentity(address: string) {\n address = toChecksumHexAddress(address);\n const { identities } = this.state;\n if (!identities[address]) {\n return;\n }\n delete identities[address];\n this.update({ identities: { ...identities } });\n if (address === this.state.selectedAddress) {\n this.update({ selectedAddress: Object.keys(identities)[0] });\n }\n }\n\n /**\n * Associates a new label with an identity.\n *\n * @param address - Address of the identity to associate.\n * @param label - New label to assign.\n */\n setAccountLabel(address: string, label: string) {\n address = toChecksumHexAddress(address);\n const { identities } = this.state;\n identities[address] = identities[address] || {};\n identities[address].name = label;\n this.update({ identities: { ...identities } });\n }\n\n /**\n * Enable or disable a specific feature flag.\n *\n * @param feature - Feature to toggle.\n * @param activated - Value to assign.\n */\n setFeatureFlag(feature: string, activated: boolean) {\n const oldFeatureFlags = this.state.featureFlags;\n const featureFlags = { ...oldFeatureFlags, ...{ [feature]: activated } };\n this.update({ featureFlags: { ...featureFlags } });\n }\n\n /**\n * Synchronizes the current identity list with new identities.\n *\n * @param addresses - List of addresses corresponding to identities to sync.\n * @returns Newly-selected address after syncing.\n */\n syncIdentities(addresses: string[]) {\n addresses = addresses.map((address: string) =>\n toChecksumHexAddress(address),\n );\n const { identities, lostIdentities } = this.state;\n const newlyLost: { [address: string]: ContactEntry } = {};\n\n for (const identity in identities) {\n if (addresses.indexOf(identity) === -1) {\n newlyLost[identity] = identities[identity];\n delete identities[identity];\n }\n }\n\n if (Object.keys(newlyLost).length > 0) {\n for (const key in newlyLost) {\n lostIdentities[key] = newlyLost[key];\n }\n }\n\n this.update({\n identities: { ...identities },\n lostIdentities: { ...lostIdentities },\n });\n this.addIdentities(addresses);\n\n if (addresses.indexOf(this.state.selectedAddress) === -1) {\n this.update({ selectedAddress: addresses[0] });\n }\n\n return this.state.selectedAddress;\n }\n\n /**\n * Generates and stores a new list of stored identities based on address. If the selected address\n * is unset, or if it refers to an identity that was removed, it will be set to the first\n * identity.\n *\n * @param addresses - List of addresses to use as a basis for each identity.\n */\n updateIdentities(addresses: string[]) {\n addresses = addresses.map((address: string) =>\n toChecksumHexAddress(address),\n );\n const oldIdentities = this.state.identities;\n const identities = addresses.reduce(\n (ids: { [address: string]: ContactEntry }, address, index) => {\n ids[address] = oldIdentities[address] || {\n address,\n name: `Account ${index + 1}`,\n importTime: Date.now(),\n };\n return ids;\n },\n {},\n );\n let { selectedAddress } = this.state;\n if (!Object.keys(identities).includes(selectedAddress)) {\n selectedAddress = Object.keys(identities)[0];\n }\n this.update({ identities: { ...identities }, selectedAddress });\n }\n\n /**\n * Adds custom RPC URL to state.\n *\n * @param url - The custom RPC URL.\n * @param chainId - The chain ID of the network, as per EIP-155.\n * @param ticker - Currency ticker.\n * @param nickname - Personalized network name.\n * @param rpcPrefs - Personalized preferences.\n */\n addToFrequentRpcList(\n url: string,\n chainId?: number,\n ticker?: string,\n nickname?: string,\n rpcPrefs?: RpcPreferences,\n ) {\n const { frequentRpcList } = this.state;\n const index = frequentRpcList.findIndex(({ rpcUrl }) => {\n return rpcUrl === url;\n });\n if (index !== -1) {\n frequentRpcList.splice(index, 1);\n }\n const newFrequestRpc: FrequentRpc = {\n rpcUrl: url,\n chainId,\n ticker,\n nickname,\n rpcPrefs,\n };\n frequentRpcList.push(newFrequestRpc);\n this.update({ frequentRpcList: [...frequentRpcList] });\n }\n\n /**\n * Removes custom RPC URL from state.\n *\n * @param url - Custom RPC URL.\n */\n removeFromFrequentRpcList(url: string) {\n const { frequentRpcList } = this.state;\n const index = frequentRpcList.findIndex(({ rpcUrl }) => {\n return rpcUrl === url;\n });\n if (index !== -1) {\n frequentRpcList.splice(index, 1);\n }\n this.update({ frequentRpcList: [...frequentRpcList] });\n }\n\n /**\n * Sets selected address.\n *\n * @param selectedAddress - Ethereum address.\n */\n setSelectedAddress(selectedAddress: string) {\n this.update({ selectedAddress: toChecksumHexAddress(selectedAddress) });\n }\n\n /**\n * Sets new IPFS gateway.\n *\n * @param ipfsGateway - IPFS gateway string.\n */\n setIpfsGateway(ipfsGateway: string) {\n this.update({ ipfsGateway });\n }\n\n /**\n * Toggle the token detection setting.\n *\n * @param useTokenDetection - Boolean indicating user preference on token detection.\n */\n setUseTokenDetection(useTokenDetection: boolean) {\n this.update({ useTokenDetection });\n }\n\n /**\n * Toggle the collectible detection setting.\n *\n * @param useCollectibleDetection - Boolean indicating user preference on collectible detection.\n */\n setUseCollectibleDetection(useCollectibleDetection: boolean) {\n if (useCollectibleDetection && !this.state.openSeaEnabled) {\n throw new Error(\n 'useCollectibleDetection cannot be enabled if openSeaEnabled is false',\n );\n }\n this.update({ useCollectibleDetection });\n }\n\n /**\n * Toggle the opensea enabled setting.\n *\n * @param openSeaEnabled - Boolean indicating user preference on using OpenSea's API.\n */\n setOpenSeaEnabled(openSeaEnabled: boolean) {\n this.update({ openSeaEnabled });\n if (!openSeaEnabled) {\n this.update({ useCollectibleDetection: false });\n }\n }\n}\n\nexport default PreferencesController;\n"]} \ No newline at end of file diff --git a/dist/util.d.ts b/dist/util.d.ts new file mode 100644 index 0000000000..c2d98aa84d --- /dev/null +++ b/dist/util.d.ts @@ -0,0 +1,357 @@ +/// +import { BN } from 'ethereumjs-util'; +import { Transaction, FetchAllOptions, GasPriceValue, FeeMarketEIP1559Values } from './transaction/TransactionController'; +import { MessageParams } from './message-manager/MessageManager'; +import { PersonalMessageParams } from './message-manager/PersonalMessageManager'; +import { TypedMessageParams } from './message-manager/TypedMessageManager'; +import { Token } from './assets/TokenRatesController'; +import { Json } from './BaseControllerV2'; +/** + * Converts a BN object to a hex string with a '0x' prefix. + * + * @param inputBn - BN instance to convert to a hex string. + * @returns A '0x'-prefixed hex string. + */ +export declare function BNToHex(inputBn: any): string; +/** + * Used to multiply a BN by a fraction. + * + * @param targetBN - Number to multiply by a fraction. + * @param numerator - Numerator of the fraction multiplier. + * @param denominator - Denominator of the fraction multiplier. + * @returns Product of the multiplication. + */ +export declare function fractionBN(targetBN: any, numerator: number | string, denominator: number | string): any; +/** + * Used to convert a base-10 number from GWEI to WEI. Can handle numbers with decimal parts. + * + * @param n - The base 10 number to convert to WEI. + * @returns The number in WEI, as a BN. + */ +export declare function gweiDecToWEIBN(n: number | string): any; +/** + * Used to convert values from wei hex format to dec gwei format. + * + * @param hex - The value in hex wei. + * @returns The value in dec gwei as string. + */ +export declare function weiHexToGweiDec(hex: string): any; +/** + * Return a URL that can be used to obtain ETH for a given network. + * + * @param networkCode - Network code of desired network. + * @param address - Address to deposit obtained ETH. + * @param amount - How much ETH is desired. + * @returns URL to buy ETH based on network. + */ +export declare function getBuyURL(networkCode?: string, address?: string, amount?: number): string | undefined; +/** + * Return a URL that can be used to fetch ETH transactions. + * + * @param networkType - Network type of desired network. + * @param urlParams - The parameters used to construct the URL. + * @returns URL to fetch the access the endpoint. + */ +export declare function getEtherscanApiUrl(networkType: string, urlParams: any): string; +/** + * Handles the fetch of incoming transactions. + * + * @param networkType - Network type of desired network. + * @param address - Address to get the transactions from. + * @param txHistoryLimit - The maximum number of transactions to fetch. + * @param opt - Object that can contain fromBlock and Etherscan service API key. + * @returns Responses for both ETH and ERC20 token transactions. + */ +export declare function handleTransactionFetch(networkType: string, address: string, txHistoryLimit: number, opt?: FetchAllOptions): Promise<[{ + [result: string]: []; +}, { + [result: string]: []; +}]>; +/** + * Converts a hex string to a BN object. + * + * @param inputHex - Number represented as a hex string. + * @returns A BN instance. + */ +export declare function hexToBN(inputHex: string): BN; +/** + * A helper function that converts hex data to human readable string. + * + * @param hex - The hex string to convert to string. + * @returns A human readable string conversion. + */ +export declare function hexToText(hex: string): string; +/** + * Parses a hex string and converts it into a number that can be operated on in a bignum-safe, + * base-10 way. + * + * @param value - A base-16 number encoded as a string. + * @returns The number as a BN object in base-16 mode. + */ +export declare function fromHex(value: string | BN): BN; +/** + * Converts an integer to a hexadecimal representation. + * + * @param value - An integer, an integer encoded as a base-10 string, or a BN. + * @returns The integer encoded as a hex string. + */ +export declare function toHex(value: number | string | BN): string; +/** + * Normalizes properties on a Transaction object. + * + * @param transaction - Transaction object to normalize. + * @returns Normalized Transaction object. + */ +export declare function normalizeTransaction(transaction: Transaction): Transaction; +/** + * Execute and return an asynchronous operation without throwing errors. + * + * @param operation - Function returning a Promise. + * @param logError - Determines if the error should be logged. + * @returns Promise resolving to the result of the async operation. + */ +export declare function safelyExecute(operation: () => Promise, logError?: boolean): Promise; +/** + * Execute and return an asynchronous operation with a timeout. + * + * @param operation - Function returning a Promise. + * @param logError - Determines if the error should be logged. + * @param timeout - Timeout to fail the operation. + * @returns Promise resolving to the result of the async operation. + */ +export declare function safelyExecuteWithTimeout(operation: () => Promise, logError?: boolean, timeout?: number): Promise; +/** + * Convert an address to a checksummed hexidecimal address. + * + * @param address - The address to convert. + * @returns A 0x-prefixed hexidecimal checksummed address. + */ +export declare function toChecksumHexAddress(address: string): string; +/** + * Validates that the input is a hex address. This utility method is a thin + * wrapper around ethereumjs-util.isValidAddress, with the exception that it + * does not throw an error when provided values that are not hex strings. In + * addition, and by default, this method will return true for hex strings that + * meet the length requirement of a hex address, but are not prefixed with `0x` + * Finally, if the mixedCaseUseChecksum flag is true and a mixed case string is + * provided this method will validate it has the proper checksum formatting. + * + * @param possibleAddress - Input parameter to check against. + * @param options - The validation options. + * @param options.allowNonPrefixed - If true will first ensure '0x' is prepended to the string. + * @returns Whether or not the input is a valid hex address. + */ +export declare function isValidHexAddress(possibleAddress: string, { allowNonPrefixed }?: { + allowNonPrefixed?: boolean | undefined; +}): boolean; +/** + * Validates a Transaction object for required properties and throws in + * the event of any validation error. + * + * @param transaction - Transaction object to validate. + */ +export declare function validateTransaction(transaction: Transaction): void; +/** + * A helper function that converts rawmessageData buffer data to a hex, or just returns the data if + * it is already formatted as a hex. + * + * @param data - The buffer data to convert to a hex. + * @returns A hex string conversion of the buffer data. + */ +export declare function normalizeMessageData(data: string): string; +/** + * Validates a PersonalMessageParams and MessageParams objects for required properties and throws in + * the event of any validation error. + * + * @param messageData - PersonalMessageParams object to validate. + */ +export declare function validateSignMessageData(messageData: PersonalMessageParams | MessageParams): void; +/** + * Validates a TypedMessageParams object for required properties and throws in + * the event of any validation error for eth_signTypedMessage_V1. + * + * @param messageData - TypedMessageParams object to validate. + */ +export declare function validateTypedSignMessageDataV1(messageData: TypedMessageParams): void; +/** + * Validates a TypedMessageParams object for required properties and throws in + * the event of any validation error for eth_signTypedMessage_V3. + * + * @param messageData - TypedMessageParams object to validate. + */ +export declare function validateTypedSignMessageDataV3(messageData: TypedMessageParams): void; +/** + * Validates a ERC20 token to be added with EIP747. + * + * @param token - Token object to validate. + */ +export declare function validateTokenToWatch(token: Token): void; +/** + * Returns whether the given code corresponds to a smart contract. + * + * @param code - The potential smart contract code. + * @returns Whether the code was smart contract code or not. + */ +export declare function isSmartContractCode(code: string): boolean; +/** + * Execute fetch and verify that the response was successful. + * + * @param request - Request information. + * @param options - Fetch options. + * @returns The fetch response. + */ +export declare function successfulFetch(request: string, options?: RequestInit): Promise; +/** + * Execute fetch and return object response. + * + * @param request - The request information. + * @param options - The fetch options. + * @returns The fetch response JSON data. + */ +export declare function handleFetch(request: string, options?: RequestInit): Promise; +/** + * Execute fetch and return object response, log if known error thrown, otherwise rethrow error. + * + * @param request - the request options object + * @param request.url - The request url to query. + * @param request.options - The fetch options. + * @param request.timeout - Timeout to fail request + * @param request.errorCodesToCatch - array of error codes for errors we want to catch in a particular context + * @returns The fetch response JSON data or undefined (if error occurs). + */ +export declare function fetchWithErrorHandling({ url, options, timeout, errorCodesToCatch, }: { + url: string; + options?: RequestInit; + timeout?: number; + errorCodesToCatch?: number[]; +}): Promise; +/** + * Fetch that fails after timeout. + * + * @param url - Url to fetch. + * @param options - Options to send with the request. + * @param timeout - Timeout to fail request. + * @returns Promise resolving the request. + */ +export declare function timeoutFetch(url: string, options?: RequestInit, timeout?: number): Promise; +/** + * Normalizes the given ENS name. + * + * @param ensName - The ENS name. + * @returns The normalized ENS name string. + */ +export declare function normalizeEnsName(ensName: string): string | null; +/** + * Wrapper method to handle EthQuery requests. + * + * @param ethQuery - EthQuery object initialized with a provider. + * @param method - Method to request. + * @param args - Arguments to send. + * @returns Promise resolving the request. + */ +export declare function query(ethQuery: any, method: string, args?: any[]): Promise; +/** + * Checks if a transaction is EIP-1559 by checking for the existence of + * maxFeePerGas and maxPriorityFeePerGas within its parameters. + * + * @param transaction - Transaction object to add. + * @returns Boolean that is true if the transaction is EIP-1559 (has maxFeePerGas and maxPriorityFeePerGas), otherwise returns false. + */ +export declare const isEIP1559Transaction: (transaction: Transaction) => boolean; +export declare const convertHexToDecimal: (value: string | undefined) => number; +export declare const getIncreasedPriceHex: (value: number, rate: number) => string; +export declare const getIncreasedPriceFromExisting: (value: string | undefined, rate: number) => string; +export declare const validateGasValues: (gasValues: GasPriceValue | FeeMarketEIP1559Values) => void; +export declare const isFeeMarketEIP1559Values: (gasValues?: GasPriceValue | FeeMarketEIP1559Values | undefined) => gasValues is FeeMarketEIP1559Values; +export declare const isGasPriceValue: (gasValues?: GasPriceValue | FeeMarketEIP1559Values | undefined) => gasValues is GasPriceValue; +/** + * Validates that the proposed value is greater than or equal to the minimum value. + * + * @param proposed - The proposed value. + * @param min - The minimum value. + * @returns The proposed value. + * @throws Will throw if the proposed value is too low. + */ +export declare function validateMinimumIncrease(proposed: string, min: string): string; +/** + * Removes IPFS protocol prefix from input string. + * + * @param ipfsUrl - An IPFS url (e.g. ipfs://{content id}) + * @returns IPFS content identifier and (possibly) path in a string + * @throws Will throw if the url passed is not IPFS. + */ +export declare function removeIpfsProtocolPrefix(ipfsUrl: string): string; +/** + * Extracts content identifier and path from an input string. + * + * @param ipfsUrl - An IPFS URL minus the IPFS protocol prefix + * @returns IFPS content identifier (cid) and sub path as string. + * @throws Will throw if the url passed is not ipfs. + */ +export declare function getIpfsCIDv1AndPath(ipfsUrl: string): { + cid: string; + path?: string; +}; +/** + * Adds URL protocol prefix to input URL string if missing. + * + * @param urlString - An IPFS URL. + * @returns A URL with a https:// prepended. + */ +export declare function addUrlProtocolPrefix(urlString: string): string; +/** + * Formats URL correctly for use retrieving assets hosted on IPFS. + * + * @param ipfsGateway - The users preferred IPFS gateway (full URL or just host). + * @param ipfsUrl - The IFPS URL pointed at the asset. + * @param subdomainSupported - Boolean indicating whether the URL should be formatted with subdomains or not. + * @returns A formatted URL, with the user's preferred IPFS gateway and format (subdomain or not), pointing to an asset hosted on IPFS. + */ +export declare function getFormattedIpfsUrl(ipfsGateway: string, ipfsUrl: string, subdomainSupported: boolean): string; +declare type PlainObject = Record; +/** + * Determines whether a value is a "plain" object. + * + * @param value - A value to check + * @returns True if the passed value is a plain object + */ +export declare function isPlainObject(value: unknown): value is PlainObject; +export declare const hasProperty: (object: PlainObject, key: string | number | symbol) => boolean; +/** + * Like {@link Array}, but always non-empty. + * + * @template T - The non-empty array member type. + */ +export declare type NonEmptyArray = [T, ...T[]]; +/** + * Type guard for {@link NonEmptyArray}. + * + * @template T - The non-empty array member type. + * @param value - The value to check. + * @returns Whether the value is a non-empty array. + */ +export declare function isNonEmptyArray(value: T[]): value is NonEmptyArray; +/** + * Type guard for {@link Json}. + * + * @param value - The value to check. + * @returns Whether the value is valid JSON. + */ +export declare function isValidJson(value: unknown): value is Json; +/** + * Networks where token detection is supported - Values are in decimal format + */ +export declare enum SupportedTokenDetectionNetworks { + mainnet = "1", + bsc = "56", + polygon = "137", + avax = "43114" +} +/** + * Check if token detection is enabled for certain networks. + * + * @param chainId - ChainID of network + * @returns Whether the current network supports token detection + */ +export declare function isTokenDetectionSupportedForNetwork(chainId: string): boolean; +export {}; diff --git a/dist/util.js b/dist/util.js new file mode 100644 index 0000000000..574c57b7d1 --- /dev/null +++ b/dist/util.js @@ -0,0 +1,871 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.isTokenDetectionSupportedForNetwork = exports.SupportedTokenDetectionNetworks = exports.isValidJson = exports.isNonEmptyArray = exports.hasProperty = exports.isPlainObject = exports.getFormattedIpfsUrl = exports.addUrlProtocolPrefix = exports.getIpfsCIDv1AndPath = exports.removeIpfsProtocolPrefix = exports.validateMinimumIncrease = exports.isGasPriceValue = exports.isFeeMarketEIP1559Values = exports.validateGasValues = exports.getIncreasedPriceFromExisting = exports.getIncreasedPriceHex = exports.convertHexToDecimal = exports.isEIP1559Transaction = exports.query = exports.normalizeEnsName = exports.timeoutFetch = exports.fetchWithErrorHandling = exports.handleFetch = exports.successfulFetch = exports.isSmartContractCode = exports.validateTokenToWatch = exports.validateTypedSignMessageDataV3 = exports.validateTypedSignMessageDataV1 = exports.validateSignMessageData = exports.normalizeMessageData = exports.validateTransaction = exports.isValidHexAddress = exports.toChecksumHexAddress = exports.safelyExecuteWithTimeout = exports.safelyExecute = exports.normalizeTransaction = exports.toHex = exports.fromHex = exports.hexToText = exports.hexToBN = exports.handleTransactionFetch = exports.getEtherscanApiUrl = exports.getBuyURL = exports.weiHexToGweiDec = exports.gweiDecToWEIBN = exports.fractionBN = exports.BNToHex = void 0; +const ethereumjs_util_1 = require("ethereumjs-util"); +const ethjs_unit_1 = require("ethjs-unit"); +const eth_rpc_errors_1 = require("eth-rpc-errors"); +const eth_ens_namehash_1 = __importDefault(require("eth-ens-namehash")); +const eth_sig_util_1 = require("eth-sig-util"); +const jsonschema_1 = require("jsonschema"); +const cid_1 = require("multiformats/cid"); +const fast_deep_equal_1 = __importDefault(require("fast-deep-equal")); +const constants_1 = require("./constants"); +const TIMEOUT_ERROR = new Error('timeout'); +const hexRe = /^[0-9A-Fa-f]+$/gu; +const NORMALIZERS = { + data: (data) => (0, ethereumjs_util_1.addHexPrefix)(data), + from: (from) => (0, ethereumjs_util_1.addHexPrefix)(from).toLowerCase(), + gas: (gas) => (0, ethereumjs_util_1.addHexPrefix)(gas), + gasPrice: (gasPrice) => (0, ethereumjs_util_1.addHexPrefix)(gasPrice), + nonce: (nonce) => (0, ethereumjs_util_1.addHexPrefix)(nonce), + to: (to) => (0, ethereumjs_util_1.addHexPrefix)(to).toLowerCase(), + value: (value) => (0, ethereumjs_util_1.addHexPrefix)(value), + maxFeePerGas: (maxFeePerGas) => (0, ethereumjs_util_1.addHexPrefix)(maxFeePerGas), + maxPriorityFeePerGas: (maxPriorityFeePerGas) => (0, ethereumjs_util_1.addHexPrefix)(maxPriorityFeePerGas), + estimatedBaseFee: (maxPriorityFeePerGas) => (0, ethereumjs_util_1.addHexPrefix)(maxPriorityFeePerGas), +}; +/** + * Converts a BN object to a hex string with a '0x' prefix. + * + * @param inputBn - BN instance to convert to a hex string. + * @returns A '0x'-prefixed hex string. + */ +function BNToHex(inputBn) { + return (0, ethereumjs_util_1.addHexPrefix)(inputBn.toString(16)); +} +exports.BNToHex = BNToHex; +/** + * Used to multiply a BN by a fraction. + * + * @param targetBN - Number to multiply by a fraction. + * @param numerator - Numerator of the fraction multiplier. + * @param denominator - Denominator of the fraction multiplier. + * @returns Product of the multiplication. + */ +function fractionBN(targetBN, numerator, denominator) { + const numBN = new ethereumjs_util_1.BN(numerator); + const denomBN = new ethereumjs_util_1.BN(denominator); + return targetBN.mul(numBN).div(denomBN); +} +exports.fractionBN = fractionBN; +/** + * Used to convert a base-10 number from GWEI to WEI. Can handle numbers with decimal parts. + * + * @param n - The base 10 number to convert to WEI. + * @returns The number in WEI, as a BN. + */ +function gweiDecToWEIBN(n) { + if (Number.isNaN(n)) { + return new ethereumjs_util_1.BN(0); + } + const parts = n.toString().split('.'); + const wholePart = parts[0] || '0'; + let decimalPart = parts[1] || ''; + if (!decimalPart) { + return (0, ethjs_unit_1.toWei)(wholePart, 'gwei'); + } + if (decimalPart.length <= 9) { + return (0, ethjs_unit_1.toWei)(`${wholePart}.${decimalPart}`, 'gwei'); + } + const decimalPartToRemove = decimalPart.slice(9); + const decimalRoundingDigit = decimalPartToRemove[0]; + decimalPart = decimalPart.slice(0, 9); + let wei = (0, ethjs_unit_1.toWei)(`${wholePart}.${decimalPart}`, 'gwei'); + if (Number(decimalRoundingDigit) >= 5) { + wei = wei.add(new ethereumjs_util_1.BN(1)); + } + return wei; +} +exports.gweiDecToWEIBN = gweiDecToWEIBN; +/** + * Used to convert values from wei hex format to dec gwei format. + * + * @param hex - The value in hex wei. + * @returns The value in dec gwei as string. + */ +function weiHexToGweiDec(hex) { + const hexWei = new ethereumjs_util_1.BN((0, ethereumjs_util_1.stripHexPrefix)(hex), 16); + return (0, ethjs_unit_1.fromWei)(hexWei, 'gwei').toString(10); +} +exports.weiHexToGweiDec = weiHexToGweiDec; +/** + * Return a URL that can be used to obtain ETH for a given network. + * + * @param networkCode - Network code of desired network. + * @param address - Address to deposit obtained ETH. + * @param amount - How much ETH is desired. + * @returns URL to buy ETH based on network. + */ +function getBuyURL(networkCode = '1', address, amount = 5) { + switch (networkCode) { + case '1': + return `https://buy.coinbase.com/?code=9ec56d01-7e81-5017-930c-513daa27bb6a&amount=${amount}&address=${address}&crypto_currency=ETH`; + case '3': + return 'https://faucet.metamask.io/'; + case '4': + return 'https://www.rinkeby.io/'; + case '5': + return 'https://goerli-faucet.slock.it/'; + case '42': + return 'https://github.com/kovan-testnet/faucet'; + default: + return undefined; + } +} +exports.getBuyURL = getBuyURL; +/** + * Return a URL that can be used to fetch ETH transactions. + * + * @param networkType - Network type of desired network. + * @param urlParams - The parameters used to construct the URL. + * @returns URL to fetch the access the endpoint. + */ +function getEtherscanApiUrl(networkType, urlParams) { + let etherscanSubdomain = 'api'; + if (networkType !== constants_1.MAINNET) { + etherscanSubdomain = `api-${networkType}`; + } + const apiUrl = `https://${etherscanSubdomain}.etherscan.io`; + let url = `${apiUrl}/api?`; + for (const paramKey in urlParams) { + if (urlParams[paramKey]) { + url += `${paramKey}=${urlParams[paramKey]}&`; + } + } + url += 'tag=latest&page=1'; + return url; +} +exports.getEtherscanApiUrl = getEtherscanApiUrl; +/** + * Handles the fetch of incoming transactions. + * + * @param networkType - Network type of desired network. + * @param address - Address to get the transactions from. + * @param txHistoryLimit - The maximum number of transactions to fetch. + * @param opt - Object that can contain fromBlock and Etherscan service API key. + * @returns Responses for both ETH and ERC20 token transactions. + */ +function handleTransactionFetch(networkType, address, txHistoryLimit, opt) { + return __awaiter(this, void 0, void 0, function* () { + // transactions + const urlParams = { + module: 'account', + address, + startBlock: opt === null || opt === void 0 ? void 0 : opt.fromBlock, + apikey: opt === null || opt === void 0 ? void 0 : opt.etherscanApiKey, + offset: txHistoryLimit.toString(), + order: 'desc', + }; + const etherscanTxUrl = getEtherscanApiUrl(networkType, Object.assign(Object.assign({}, urlParams), { action: 'txlist' })); + const etherscanTxResponsePromise = handleFetch(etherscanTxUrl); + // tokens + const etherscanTokenUrl = getEtherscanApiUrl(networkType, Object.assign(Object.assign({}, urlParams), { action: 'tokentx' })); + const etherscanTokenResponsePromise = handleFetch(etherscanTokenUrl); + let [etherscanTxResponse, etherscanTokenResponse] = yield Promise.all([ + etherscanTxResponsePromise, + etherscanTokenResponsePromise, + ]); + if (etherscanTxResponse.status === '0' || + etherscanTxResponse.result.length <= 0) { + etherscanTxResponse = { status: etherscanTxResponse.status, result: [] }; + } + if (etherscanTokenResponse.status === '0' || + etherscanTokenResponse.result.length <= 0) { + etherscanTokenResponse = { + status: etherscanTokenResponse.status, + result: [], + }; + } + return [etherscanTxResponse, etherscanTokenResponse]; + }); +} +exports.handleTransactionFetch = handleTransactionFetch; +/** + * Converts a hex string to a BN object. + * + * @param inputHex - Number represented as a hex string. + * @returns A BN instance. + */ +function hexToBN(inputHex) { + return new ethereumjs_util_1.BN((0, ethereumjs_util_1.stripHexPrefix)(inputHex), 16); +} +exports.hexToBN = hexToBN; +/** + * A helper function that converts hex data to human readable string. + * + * @param hex - The hex string to convert to string. + * @returns A human readable string conversion. + */ +function hexToText(hex) { + try { + const stripped = (0, ethereumjs_util_1.stripHexPrefix)(hex); + const buff = Buffer.from(stripped, 'hex'); + return buff.toString('utf8'); + } + catch (e) { + /* istanbul ignore next */ + return hex; + } +} +exports.hexToText = hexToText; +/** + * Parses a hex string and converts it into a number that can be operated on in a bignum-safe, + * base-10 way. + * + * @param value - A base-16 number encoded as a string. + * @returns The number as a BN object in base-16 mode. + */ +function fromHex(value) { + if (ethereumjs_util_1.BN.isBN(value)) { + return value; + } + return new ethereumjs_util_1.BN(hexToBN(value).toString(10)); +} +exports.fromHex = fromHex; +/** + * Converts an integer to a hexadecimal representation. + * + * @param value - An integer, an integer encoded as a base-10 string, or a BN. + * @returns The integer encoded as a hex string. + */ +function toHex(value) { + if (typeof value === 'string' && (0, ethereumjs_util_1.isHexString)(value)) { + return value; + } + const hexString = ethereumjs_util_1.BN.isBN(value) + ? value.toString(16) + : new ethereumjs_util_1.BN(value.toString(), 10).toString(16); + return `0x${hexString}`; +} +exports.toHex = toHex; +/** + * Normalizes properties on a Transaction object. + * + * @param transaction - Transaction object to normalize. + * @returns Normalized Transaction object. + */ +function normalizeTransaction(transaction) { + const normalizedTransaction = { from: '' }; + let key; + for (key in NORMALIZERS) { + if (transaction[key]) { + normalizedTransaction[key] = NORMALIZERS[key](transaction[key]); + } + } + return normalizedTransaction; +} +exports.normalizeTransaction = normalizeTransaction; +/** + * Execute and return an asynchronous operation without throwing errors. + * + * @param operation - Function returning a Promise. + * @param logError - Determines if the error should be logged. + * @returns Promise resolving to the result of the async operation. + */ +function safelyExecute(operation, logError = false) { + return __awaiter(this, void 0, void 0, function* () { + try { + return yield operation(); + } + catch (error) { + /* istanbul ignore next */ + if (logError) { + console.error(error); + } + return undefined; + } + }); +} +exports.safelyExecute = safelyExecute; +/** + * Execute and return an asynchronous operation with a timeout. + * + * @param operation - Function returning a Promise. + * @param logError - Determines if the error should be logged. + * @param timeout - Timeout to fail the operation. + * @returns Promise resolving to the result of the async operation. + */ +function safelyExecuteWithTimeout(operation, logError = false, timeout = 500) { + return __awaiter(this, void 0, void 0, function* () { + try { + return yield Promise.race([ + operation(), + new Promise((_, reject) => setTimeout(() => { + reject(TIMEOUT_ERROR); + }, timeout)), + ]); + } + catch (error) { + /* istanbul ignore next */ + if (logError) { + console.error(error); + } + return undefined; + } + }); +} +exports.safelyExecuteWithTimeout = safelyExecuteWithTimeout; +/** + * Convert an address to a checksummed hexidecimal address. + * + * @param address - The address to convert. + * @returns A 0x-prefixed hexidecimal checksummed address. + */ +function toChecksumHexAddress(address) { + const hexPrefixed = (0, ethereumjs_util_1.addHexPrefix)(address); + if (!(0, ethereumjs_util_1.isHexString)(hexPrefixed)) { + // Version 5.1 of ethereumjs-utils would have returned '0xY' for input 'y' + // but we shouldn't waste effort trying to change case on a clearly invalid + // string. Instead just return the hex prefixed original string which most + // closely mimics the original behavior. + return hexPrefixed; + } + return (0, ethereumjs_util_1.toChecksumAddress)(hexPrefixed); +} +exports.toChecksumHexAddress = toChecksumHexAddress; +/** + * Validates that the input is a hex address. This utility method is a thin + * wrapper around ethereumjs-util.isValidAddress, with the exception that it + * does not throw an error when provided values that are not hex strings. In + * addition, and by default, this method will return true for hex strings that + * meet the length requirement of a hex address, but are not prefixed with `0x` + * Finally, if the mixedCaseUseChecksum flag is true and a mixed case string is + * provided this method will validate it has the proper checksum formatting. + * + * @param possibleAddress - Input parameter to check against. + * @param options - The validation options. + * @param options.allowNonPrefixed - If true will first ensure '0x' is prepended to the string. + * @returns Whether or not the input is a valid hex address. + */ +function isValidHexAddress(possibleAddress, { allowNonPrefixed = true } = {}) { + const addressToCheck = allowNonPrefixed + ? (0, ethereumjs_util_1.addHexPrefix)(possibleAddress) + : possibleAddress; + if (!(0, ethereumjs_util_1.isHexString)(addressToCheck)) { + return false; + } + return (0, ethereumjs_util_1.isValidAddress)(addressToCheck); +} +exports.isValidHexAddress = isValidHexAddress; +/** + * Validates a Transaction object for required properties and throws in + * the event of any validation error. + * + * @param transaction - Transaction object to validate. + */ +function validateTransaction(transaction) { + if (!transaction.from || + typeof transaction.from !== 'string' || + !isValidHexAddress(transaction.from)) { + throw new Error(`Invalid "from" address: ${transaction.from} must be a valid string.`); + } + if (transaction.to === '0x' || transaction.to === undefined) { + if (transaction.data) { + delete transaction.to; + } + else { + throw new Error(`Invalid "to" address: ${transaction.to} must be a valid string.`); + } + } + else if (transaction.to !== undefined && + !isValidHexAddress(transaction.to)) { + throw new Error(`Invalid "to" address: ${transaction.to} must be a valid string.`); + } + if (transaction.value !== undefined) { + const value = transaction.value.toString(); + if (value.includes('-')) { + throw new Error(`Invalid "value": ${value} is not a positive number.`); + } + if (value.includes('.')) { + throw new Error(`Invalid "value": ${value} number must be denominated in wei.`); + } + const intValue = parseInt(transaction.value, 10); + const isValid = Number.isFinite(intValue) && + !Number.isNaN(intValue) && + !isNaN(Number(value)) && + Number.isSafeInteger(intValue); + if (!isValid) { + throw new Error(`Invalid "value": ${value} number must be a valid number.`); + } + } +} +exports.validateTransaction = validateTransaction; +/** + * A helper function that converts rawmessageData buffer data to a hex, or just returns the data if + * it is already formatted as a hex. + * + * @param data - The buffer data to convert to a hex. + * @returns A hex string conversion of the buffer data. + */ +function normalizeMessageData(data) { + try { + const stripped = (0, ethereumjs_util_1.stripHexPrefix)(data); + if (stripped.match(hexRe)) { + return (0, ethereumjs_util_1.addHexPrefix)(stripped); + } + } + catch (e) { + /* istanbul ignore next */ + } + return (0, ethereumjs_util_1.bufferToHex)(Buffer.from(data, 'utf8')); +} +exports.normalizeMessageData = normalizeMessageData; +/** + * Validates a PersonalMessageParams and MessageParams objects for required properties and throws in + * the event of any validation error. + * + * @param messageData - PersonalMessageParams object to validate. + */ +function validateSignMessageData(messageData) { + const { from, data } = messageData; + if (!from || typeof from !== 'string' || !isValidHexAddress(from)) { + throw new Error(`Invalid "from" address: ${from} must be a valid string.`); + } + if (!data || typeof data !== 'string') { + throw new Error(`Invalid message "data": ${data} must be a valid string.`); + } +} +exports.validateSignMessageData = validateSignMessageData; +/** + * Validates a TypedMessageParams object for required properties and throws in + * the event of any validation error for eth_signTypedMessage_V1. + * + * @param messageData - TypedMessageParams object to validate. + */ +function validateTypedSignMessageDataV1(messageData) { + if (!messageData.from || + typeof messageData.from !== 'string' || + !isValidHexAddress(messageData.from)) { + throw new Error(`Invalid "from" address: ${messageData.from} must be a valid string.`); + } + if (!messageData.data || !Array.isArray(messageData.data)) { + throw new Error(`Invalid message "data": ${messageData.data} must be a valid array.`); + } + try { + // typedSignatureHash will throw if the data is invalid. + (0, eth_sig_util_1.typedSignatureHash)(messageData.data); + } + catch (e) { + throw new Error(`Expected EIP712 typed data.`); + } +} +exports.validateTypedSignMessageDataV1 = validateTypedSignMessageDataV1; +/** + * Validates a TypedMessageParams object for required properties and throws in + * the event of any validation error for eth_signTypedMessage_V3. + * + * @param messageData - TypedMessageParams object to validate. + */ +function validateTypedSignMessageDataV3(messageData) { + if (!messageData.from || + typeof messageData.from !== 'string' || + !isValidHexAddress(messageData.from)) { + throw new Error(`Invalid "from" address: ${messageData.from} must be a valid string.`); + } + if (!messageData.data || typeof messageData.data !== 'string') { + throw new Error(`Invalid message "data": ${messageData.data} must be a valid array.`); + } + let data; + try { + data = JSON.parse(messageData.data); + } + catch (e) { + throw new Error('Data must be passed as a valid JSON string.'); + } + const validation = (0, jsonschema_1.validate)(data, eth_sig_util_1.TYPED_MESSAGE_SCHEMA); + if (validation.errors.length > 0) { + throw new Error('Data must conform to EIP-712 schema. See https://git.io/fNtcx.'); + } +} +exports.validateTypedSignMessageDataV3 = validateTypedSignMessageDataV3; +/** + * Validates a ERC20 token to be added with EIP747. + * + * @param token - Token object to validate. + */ +function validateTokenToWatch(token) { + const { address, symbol, decimals } = token; + if (!address || !symbol || typeof decimals === 'undefined') { + throw eth_rpc_errors_1.ethErrors.rpc.invalidParams(`Must specify address, symbol, and decimals.`); + } + if (typeof symbol !== 'string') { + throw eth_rpc_errors_1.ethErrors.rpc.invalidParams(`Invalid symbol: not a string.`); + } + if (symbol.length > 11) { + throw eth_rpc_errors_1.ethErrors.rpc.invalidParams(`Invalid symbol "${symbol}": longer than 11 characters.`); + } + const numDecimals = parseInt(decimals, 10); + if (isNaN(numDecimals) || numDecimals > 36 || numDecimals < 0) { + throw eth_rpc_errors_1.ethErrors.rpc.invalidParams(`Invalid decimals "${decimals}": must be 0 <= 36.`); + } + if (!isValidHexAddress(address)) { + throw eth_rpc_errors_1.ethErrors.rpc.invalidParams(`Invalid address "${address}".`); + } +} +exports.validateTokenToWatch = validateTokenToWatch; +/** + * Returns whether the given code corresponds to a smart contract. + * + * @param code - The potential smart contract code. + * @returns Whether the code was smart contract code or not. + */ +function isSmartContractCode(code) { + /* istanbul ignore if */ + if (!code) { + return false; + } + // Geth will return '0x', and ganache-core v2.2.1 will return '0x0' + const smartContractCode = code !== '0x' && code !== '0x0'; + return smartContractCode; +} +exports.isSmartContractCode = isSmartContractCode; +/** + * Execute fetch and verify that the response was successful. + * + * @param request - Request information. + * @param options - Fetch options. + * @returns The fetch response. + */ +function successfulFetch(request, options) { + return __awaiter(this, void 0, void 0, function* () { + const response = yield fetch(request, options); + if (!response.ok) { + throw new Error(`Fetch failed with status '${response.status}' for request '${request}'`); + } + return response; + }); +} +exports.successfulFetch = successfulFetch; +/** + * Execute fetch and return object response. + * + * @param request - The request information. + * @param options - The fetch options. + * @returns The fetch response JSON data. + */ +function handleFetch(request, options) { + return __awaiter(this, void 0, void 0, function* () { + const response = yield successfulFetch(request, options); + const object = yield response.json(); + return object; + }); +} +exports.handleFetch = handleFetch; +/** + * Execute fetch and return object response, log if known error thrown, otherwise rethrow error. + * + * @param request - the request options object + * @param request.url - The request url to query. + * @param request.options - The fetch options. + * @param request.timeout - Timeout to fail request + * @param request.errorCodesToCatch - array of error codes for errors we want to catch in a particular context + * @returns The fetch response JSON data or undefined (if error occurs). + */ +function fetchWithErrorHandling({ url, options, timeout, errorCodesToCatch, }) { + return __awaiter(this, void 0, void 0, function* () { + let result; + try { + if (timeout) { + result = Promise.race([ + yield handleFetch(url, options), + new Promise((_, reject) => setTimeout(() => { + reject(TIMEOUT_ERROR); + }, timeout)), + ]); + } + else { + result = yield handleFetch(url, options); + } + } + catch (e) { + logOrRethrowError(e, errorCodesToCatch); + } + return result; + }); +} +exports.fetchWithErrorHandling = fetchWithErrorHandling; +/** + * Fetch that fails after timeout. + * + * @param url - Url to fetch. + * @param options - Options to send with the request. + * @param timeout - Timeout to fail request. + * @returns Promise resolving the request. + */ +function timeoutFetch(url, options, timeout = 500) { + return __awaiter(this, void 0, void 0, function* () { + return Promise.race([ + successfulFetch(url, options), + new Promise((_, reject) => setTimeout(() => { + reject(TIMEOUT_ERROR); + }, timeout)), + ]); + }); +} +exports.timeoutFetch = timeoutFetch; +/** + * Normalizes the given ENS name. + * + * @param ensName - The ENS name. + * @returns The normalized ENS name string. + */ +function normalizeEnsName(ensName) { + if (ensName && typeof ensName === 'string') { + try { + const normalized = eth_ens_namehash_1.default.normalize(ensName.trim()); + // this regex is only sufficient with the above call to ensNamehash.normalize + // TODO: change 7 in regex to 3 when shorter ENS domains are live + if (normalized.match(/^(([\w\d-]+)\.)*[\w\d-]{7,}\.(eth|test)$/u)) { + return normalized; + } + } + catch (_) { + // do nothing + } + } + return null; +} +exports.normalizeEnsName = normalizeEnsName; +/** + * Wrapper method to handle EthQuery requests. + * + * @param ethQuery - EthQuery object initialized with a provider. + * @param method - Method to request. + * @param args - Arguments to send. + * @returns Promise resolving the request. + */ +function query(ethQuery, method, args = []) { + return new Promise((resolve, reject) => { + const cb = (error, result) => { + if (error) { + reject(error); + return; + } + resolve(result); + }; + if (typeof ethQuery[method] === 'function') { + ethQuery[method](...args, cb); + } + else { + ethQuery.sendAsync({ method, params: args }, cb); + } + }); +} +exports.query = query; +/** + * Checks if a transaction is EIP-1559 by checking for the existence of + * maxFeePerGas and maxPriorityFeePerGas within its parameters. + * + * @param transaction - Transaction object to add. + * @returns Boolean that is true if the transaction is EIP-1559 (has maxFeePerGas and maxPriorityFeePerGas), otherwise returns false. + */ +const isEIP1559Transaction = (transaction) => { + const hasOwnProp = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key); + return (hasOwnProp(transaction, 'maxFeePerGas') && + hasOwnProp(transaction, 'maxPriorityFeePerGas')); +}; +exports.isEIP1559Transaction = isEIP1559Transaction; +const convertHexToDecimal = (value) => parseInt(value === undefined ? '0x0' : value, 16); +exports.convertHexToDecimal = convertHexToDecimal; +const getIncreasedPriceHex = (value, rate) => (0, ethereumjs_util_1.addHexPrefix)(`${parseInt(`${value * rate}`, 10).toString(16)}`); +exports.getIncreasedPriceHex = getIncreasedPriceHex; +const getIncreasedPriceFromExisting = (value, rate) => { + return (0, exports.getIncreasedPriceHex)((0, exports.convertHexToDecimal)(value), rate); +}; +exports.getIncreasedPriceFromExisting = getIncreasedPriceFromExisting; +const validateGasValues = (gasValues) => { + Object.keys(gasValues).forEach((key) => { + const value = gasValues[key]; + if (typeof value !== 'string' || !(0, ethereumjs_util_1.isHexString)(value)) { + throw new TypeError(`expected hex string for ${key} but received: ${value}`); + } + }); +}; +exports.validateGasValues = validateGasValues; +const isFeeMarketEIP1559Values = (gasValues) => (gasValues === null || gasValues === void 0 ? void 0 : gasValues.maxFeePerGas) !== undefined || + (gasValues === null || gasValues === void 0 ? void 0 : gasValues.maxPriorityFeePerGas) !== undefined; +exports.isFeeMarketEIP1559Values = isFeeMarketEIP1559Values; +const isGasPriceValue = (gasValues) => (gasValues === null || gasValues === void 0 ? void 0 : gasValues.gasPrice) !== undefined; +exports.isGasPriceValue = isGasPriceValue; +/** + * Validates that the proposed value is greater than or equal to the minimum value. + * + * @param proposed - The proposed value. + * @param min - The minimum value. + * @returns The proposed value. + * @throws Will throw if the proposed value is too low. + */ +function validateMinimumIncrease(proposed, min) { + const proposedDecimal = (0, exports.convertHexToDecimal)(proposed); + const minDecimal = (0, exports.convertHexToDecimal)(min); + if (proposedDecimal >= minDecimal) { + return proposed; + } + const errorMsg = `The proposed value: ${proposedDecimal} should meet or exceed the minimum value: ${minDecimal}`; + throw new Error(errorMsg); +} +exports.validateMinimumIncrease = validateMinimumIncrease; +/** + * Removes IPFS protocol prefix from input string. + * + * @param ipfsUrl - An IPFS url (e.g. ipfs://{content id}) + * @returns IPFS content identifier and (possibly) path in a string + * @throws Will throw if the url passed is not IPFS. + */ +function removeIpfsProtocolPrefix(ipfsUrl) { + if (ipfsUrl.startsWith('ipfs://ipfs/')) { + return ipfsUrl.replace('ipfs://ipfs/', ''); + } + else if (ipfsUrl.startsWith('ipfs://')) { + return ipfsUrl.replace('ipfs://', ''); + } + // this method should not be used with non-ipfs urls (i.e. startsWith('ipfs://') === true) + throw new Error('this method should not be used with non ipfs urls'); +} +exports.removeIpfsProtocolPrefix = removeIpfsProtocolPrefix; +/** + * Extracts content identifier and path from an input string. + * + * @param ipfsUrl - An IPFS URL minus the IPFS protocol prefix + * @returns IFPS content identifier (cid) and sub path as string. + * @throws Will throw if the url passed is not ipfs. + */ +function getIpfsCIDv1AndPath(ipfsUrl) { + const url = removeIpfsProtocolPrefix(ipfsUrl); + // check if there is a path + // (CID is everything preceding first forward slash, path is everything after) + const index = url.indexOf('/'); + const cid = index !== -1 ? url.substring(0, index) : url; + const path = index !== -1 ? url.substring(index) : undefined; + // We want to ensure that the CID is v1 (https://docs.ipfs.io/concepts/content-addressing/#identifier-formats) + // because most cid v0s appear to be incompatible with IPFS subdomains + return { + cid: cid_1.CID.parse(cid).toV1().toString(), + path, + }; +} +exports.getIpfsCIDv1AndPath = getIpfsCIDv1AndPath; +/** + * Adds URL protocol prefix to input URL string if missing. + * + * @param urlString - An IPFS URL. + * @returns A URL with a https:// prepended. + */ +function addUrlProtocolPrefix(urlString) { + if (!urlString.match(/(^http:\/\/)|(^https:\/\/)/u)) { + return `https://${urlString}`; + } + return urlString; +} +exports.addUrlProtocolPrefix = addUrlProtocolPrefix; +/** + * Formats URL correctly for use retrieving assets hosted on IPFS. + * + * @param ipfsGateway - The users preferred IPFS gateway (full URL or just host). + * @param ipfsUrl - The IFPS URL pointed at the asset. + * @param subdomainSupported - Boolean indicating whether the URL should be formatted with subdomains or not. + * @returns A formatted URL, with the user's preferred IPFS gateway and format (subdomain or not), pointing to an asset hosted on IPFS. + */ +function getFormattedIpfsUrl(ipfsGateway, ipfsUrl, subdomainSupported) { + const { host, protocol, origin } = new URL(addUrlProtocolPrefix(ipfsGateway)); + if (subdomainSupported) { + const { cid, path } = getIpfsCIDv1AndPath(ipfsUrl); + return `${protocol}//${cid}.ipfs.${host}${path !== null && path !== void 0 ? path : ''}`; + } + const cidAndPath = removeIpfsProtocolPrefix(ipfsUrl); + return `${origin}/ipfs/${cidAndPath}`; +} +exports.getFormattedIpfsUrl = getFormattedIpfsUrl; +/** + * Determines whether a value is a "plain" object. + * + * @param value - A value to check + * @returns True if the passed value is a plain object + */ +function isPlainObject(value) { + return Boolean(value) && typeof value === 'object' && !Array.isArray(value); +} +exports.isPlainObject = isPlainObject; +const hasProperty = (object, key) => Reflect.hasOwnProperty.call(object, key); +exports.hasProperty = hasProperty; +/** + * Type guard for {@link NonEmptyArray}. + * + * @template T - The non-empty array member type. + * @param value - The value to check. + * @returns Whether the value is a non-empty array. + */ +function isNonEmptyArray(value) { + return Array.isArray(value) && value.length > 0; +} +exports.isNonEmptyArray = isNonEmptyArray; +/** + * Type guard for {@link Json}. + * + * @param value - The value to check. + * @returns Whether the value is valid JSON. + */ +function isValidJson(value) { + try { + return (0, fast_deep_equal_1.default)(value, JSON.parse(JSON.stringify(value))); + } + catch (_) { + return false; + } +} +exports.isValidJson = isValidJson; +/** + * Networks where token detection is supported - Values are in decimal format + */ +var SupportedTokenDetectionNetworks; +(function (SupportedTokenDetectionNetworks) { + SupportedTokenDetectionNetworks["mainnet"] = "1"; + SupportedTokenDetectionNetworks["bsc"] = "56"; + SupportedTokenDetectionNetworks["polygon"] = "137"; + SupportedTokenDetectionNetworks["avax"] = "43114"; +})(SupportedTokenDetectionNetworks = exports.SupportedTokenDetectionNetworks || (exports.SupportedTokenDetectionNetworks = {})); +/** + * Check if token detection is enabled for certain networks. + * + * @param chainId - ChainID of network + * @returns Whether the current network supports token detection + */ +function isTokenDetectionSupportedForNetwork(chainId) { + return Object.values(SupportedTokenDetectionNetworks).includes(chainId); +} +exports.isTokenDetectionSupportedForNetwork = isTokenDetectionSupportedForNetwork; +/** + * Utility method to log if error is a common fetch error and otherwise rethrow it. + * + * @param error - Caught error that we should either rethrow or log to console + * @param codesToCatch - array of error codes for errors we want to catch and log in a particular context + */ +function logOrRethrowError(error, codesToCatch = []) { + var _a; + if (!error) { + return; + } + const includesErrorCodeToCatch = codesToCatch.some((code) => { var _a; return (_a = error.message) === null || _a === void 0 ? void 0 : _a.includes(`Fetch failed with status '${code}'`); }); + if (error instanceof Error && + (includesErrorCodeToCatch || + ((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('Failed to fetch')) || + error === TIMEOUT_ERROR)) { + console.error(error); + } + else { + throw error; + } +} +//# sourceMappingURL=util.js.map \ No newline at end of file diff --git a/dist/util.js.map b/dist/util.js.map new file mode 100644 index 0000000000..5a0b373692 --- /dev/null +++ b/dist/util.js.map @@ -0,0 +1 @@ +{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n addHexPrefix,\n isValidAddress,\n isHexString,\n bufferToHex,\n BN,\n toChecksumAddress,\n stripHexPrefix,\n} from 'ethereumjs-util';\nimport { fromWei, toWei } from 'ethjs-unit';\nimport { ethErrors } from 'eth-rpc-errors';\nimport ensNamehash from 'eth-ens-namehash';\nimport { TYPED_MESSAGE_SCHEMA, typedSignatureHash } from 'eth-sig-util';\nimport { validate } from 'jsonschema';\nimport { CID } from 'multiformats/cid';\nimport deepEqual from 'fast-deep-equal';\nimport {\n Transaction,\n FetchAllOptions,\n GasPriceValue,\n FeeMarketEIP1559Values,\n} from './transaction/TransactionController';\nimport { MessageParams } from './message-manager/MessageManager';\nimport { PersonalMessageParams } from './message-manager/PersonalMessageManager';\nimport { TypedMessageParams } from './message-manager/TypedMessageManager';\nimport { Token } from './assets/TokenRatesController';\nimport { MAINNET } from './constants';\nimport { Json } from './BaseControllerV2';\n\nconst TIMEOUT_ERROR = new Error('timeout');\n\nconst hexRe = /^[0-9A-Fa-f]+$/gu;\n\nconst NORMALIZERS: { [param in keyof Transaction]: any } = {\n data: (data: string) => addHexPrefix(data),\n from: (from: string) => addHexPrefix(from).toLowerCase(),\n gas: (gas: string) => addHexPrefix(gas),\n gasPrice: (gasPrice: string) => addHexPrefix(gasPrice),\n nonce: (nonce: string) => addHexPrefix(nonce),\n to: (to: string) => addHexPrefix(to).toLowerCase(),\n value: (value: string) => addHexPrefix(value),\n maxFeePerGas: (maxFeePerGas: string) => addHexPrefix(maxFeePerGas),\n maxPriorityFeePerGas: (maxPriorityFeePerGas: string) =>\n addHexPrefix(maxPriorityFeePerGas),\n estimatedBaseFee: (maxPriorityFeePerGas: string) =>\n addHexPrefix(maxPriorityFeePerGas),\n};\n\n/**\n * Converts a BN object to a hex string with a '0x' prefix.\n *\n * @param inputBn - BN instance to convert to a hex string.\n * @returns A '0x'-prefixed hex string.\n */\nexport function BNToHex(inputBn: any) {\n return addHexPrefix(inputBn.toString(16));\n}\n\n/**\n * Used to multiply a BN by a fraction.\n *\n * @param targetBN - Number to multiply by a fraction.\n * @param numerator - Numerator of the fraction multiplier.\n * @param denominator - Denominator of the fraction multiplier.\n * @returns Product of the multiplication.\n */\nexport function fractionBN(\n targetBN: any,\n numerator: number | string,\n denominator: number | string,\n) {\n const numBN = new BN(numerator);\n const denomBN = new BN(denominator);\n return targetBN.mul(numBN).div(denomBN);\n}\n\n/**\n * Used to convert a base-10 number from GWEI to WEI. Can handle numbers with decimal parts.\n *\n * @param n - The base 10 number to convert to WEI.\n * @returns The number in WEI, as a BN.\n */\nexport function gweiDecToWEIBN(n: number | string) {\n if (Number.isNaN(n)) {\n return new BN(0);\n }\n\n const parts = n.toString().split('.');\n const wholePart = parts[0] || '0';\n let decimalPart = parts[1] || '';\n\n if (!decimalPart) {\n return toWei(wholePart, 'gwei');\n }\n\n if (decimalPart.length <= 9) {\n return toWei(`${wholePart}.${decimalPart}`, 'gwei');\n }\n\n const decimalPartToRemove = decimalPart.slice(9);\n const decimalRoundingDigit = decimalPartToRemove[0];\n\n decimalPart = decimalPart.slice(0, 9);\n let wei = toWei(`${wholePart}.${decimalPart}`, 'gwei');\n\n if (Number(decimalRoundingDigit) >= 5) {\n wei = wei.add(new BN(1));\n }\n\n return wei;\n}\n\n/**\n * Used to convert values from wei hex format to dec gwei format.\n *\n * @param hex - The value in hex wei.\n * @returns The value in dec gwei as string.\n */\nexport function weiHexToGweiDec(hex: string) {\n const hexWei = new BN(stripHexPrefix(hex), 16);\n return fromWei(hexWei, 'gwei').toString(10);\n}\n\n/**\n * Return a URL that can be used to obtain ETH for a given network.\n *\n * @param networkCode - Network code of desired network.\n * @param address - Address to deposit obtained ETH.\n * @param amount - How much ETH is desired.\n * @returns URL to buy ETH based on network.\n */\nexport function getBuyURL(\n networkCode = '1',\n address?: string,\n amount = 5,\n): string | undefined {\n switch (networkCode) {\n case '1':\n return `https://buy.coinbase.com/?code=9ec56d01-7e81-5017-930c-513daa27bb6a&amount=${amount}&address=${address}&crypto_currency=ETH`;\n case '3':\n return 'https://faucet.metamask.io/';\n case '4':\n return 'https://www.rinkeby.io/';\n case '5':\n return 'https://goerli-faucet.slock.it/';\n case '42':\n return 'https://github.com/kovan-testnet/faucet';\n default:\n return undefined;\n }\n}\n\n/**\n * Return a URL that can be used to fetch ETH transactions.\n *\n * @param networkType - Network type of desired network.\n * @param urlParams - The parameters used to construct the URL.\n * @returns URL to fetch the access the endpoint.\n */\nexport function getEtherscanApiUrl(\n networkType: string,\n urlParams: any,\n): string {\n let etherscanSubdomain = 'api';\n if (networkType !== MAINNET) {\n etherscanSubdomain = `api-${networkType}`;\n }\n const apiUrl = `https://${etherscanSubdomain}.etherscan.io`;\n let url = `${apiUrl}/api?`;\n\n for (const paramKey in urlParams) {\n if (urlParams[paramKey]) {\n url += `${paramKey}=${urlParams[paramKey]}&`;\n }\n }\n url += 'tag=latest&page=1';\n return url;\n}\n\n/**\n * Handles the fetch of incoming transactions.\n *\n * @param networkType - Network type of desired network.\n * @param address - Address to get the transactions from.\n * @param txHistoryLimit - The maximum number of transactions to fetch.\n * @param opt - Object that can contain fromBlock and Etherscan service API key.\n * @returns Responses for both ETH and ERC20 token transactions.\n */\nexport async function handleTransactionFetch(\n networkType: string,\n address: string,\n txHistoryLimit: number,\n opt?: FetchAllOptions,\n): Promise<[{ [result: string]: [] }, { [result: string]: [] }]> {\n // transactions\n const urlParams = {\n module: 'account',\n address,\n startBlock: opt?.fromBlock,\n apikey: opt?.etherscanApiKey,\n offset: txHistoryLimit.toString(),\n order: 'desc',\n };\n const etherscanTxUrl = getEtherscanApiUrl(networkType, {\n ...urlParams,\n action: 'txlist',\n });\n const etherscanTxResponsePromise = handleFetch(etherscanTxUrl);\n\n // tokens\n const etherscanTokenUrl = getEtherscanApiUrl(networkType, {\n ...urlParams,\n action: 'tokentx',\n });\n const etherscanTokenResponsePromise = handleFetch(etherscanTokenUrl);\n\n let [etherscanTxResponse, etherscanTokenResponse] = await Promise.all([\n etherscanTxResponsePromise,\n etherscanTokenResponsePromise,\n ]);\n\n if (\n etherscanTxResponse.status === '0' ||\n etherscanTxResponse.result.length <= 0\n ) {\n etherscanTxResponse = { status: etherscanTxResponse.status, result: [] };\n }\n\n if (\n etherscanTokenResponse.status === '0' ||\n etherscanTokenResponse.result.length <= 0\n ) {\n etherscanTokenResponse = {\n status: etherscanTokenResponse.status,\n result: [],\n };\n }\n\n return [etherscanTxResponse, etherscanTokenResponse];\n}\n\n/**\n * Converts a hex string to a BN object.\n *\n * @param inputHex - Number represented as a hex string.\n * @returns A BN instance.\n */\nexport function hexToBN(inputHex: string) {\n return new BN(stripHexPrefix(inputHex), 16);\n}\n\n/**\n * A helper function that converts hex data to human readable string.\n *\n * @param hex - The hex string to convert to string.\n * @returns A human readable string conversion.\n */\nexport function hexToText(hex: string) {\n try {\n const stripped = stripHexPrefix(hex);\n const buff = Buffer.from(stripped, 'hex');\n return buff.toString('utf8');\n } catch (e) {\n /* istanbul ignore next */\n return hex;\n }\n}\n\n/**\n * Parses a hex string and converts it into a number that can be operated on in a bignum-safe,\n * base-10 way.\n *\n * @param value - A base-16 number encoded as a string.\n * @returns The number as a BN object in base-16 mode.\n */\nexport function fromHex(value: string | BN): BN {\n if (BN.isBN(value)) {\n return value;\n }\n return new BN(hexToBN(value).toString(10));\n}\n\n/**\n * Converts an integer to a hexadecimal representation.\n *\n * @param value - An integer, an integer encoded as a base-10 string, or a BN.\n * @returns The integer encoded as a hex string.\n */\nexport function toHex(value: number | string | BN): string {\n if (typeof value === 'string' && isHexString(value)) {\n return value;\n }\n const hexString = BN.isBN(value)\n ? value.toString(16)\n : new BN(value.toString(), 10).toString(16);\n return `0x${hexString}`;\n}\n\n/**\n * Normalizes properties on a Transaction object.\n *\n * @param transaction - Transaction object to normalize.\n * @returns Normalized Transaction object.\n */\nexport function normalizeTransaction(transaction: Transaction) {\n const normalizedTransaction: Transaction = { from: '' };\n let key: keyof Transaction;\n for (key in NORMALIZERS) {\n if (transaction[key as keyof Transaction]) {\n normalizedTransaction[key] = NORMALIZERS[key](transaction[key]) as never;\n }\n }\n return normalizedTransaction;\n}\n\n/**\n * Execute and return an asynchronous operation without throwing errors.\n *\n * @param operation - Function returning a Promise.\n * @param logError - Determines if the error should be logged.\n * @returns Promise resolving to the result of the async operation.\n */\nexport async function safelyExecute(\n operation: () => Promise,\n logError = false,\n) {\n try {\n return await operation();\n } catch (error: any) {\n /* istanbul ignore next */\n if (logError) {\n console.error(error);\n }\n return undefined;\n }\n}\n\n/**\n * Execute and return an asynchronous operation with a timeout.\n *\n * @param operation - Function returning a Promise.\n * @param logError - Determines if the error should be logged.\n * @param timeout - Timeout to fail the operation.\n * @returns Promise resolving to the result of the async operation.\n */\nexport async function safelyExecuteWithTimeout(\n operation: () => Promise,\n logError = false,\n timeout = 500,\n) {\n try {\n return await Promise.race([\n operation(),\n new Promise((_, reject) =>\n setTimeout(() => {\n reject(TIMEOUT_ERROR);\n }, timeout),\n ),\n ]);\n } catch (error) {\n /* istanbul ignore next */\n if (logError) {\n console.error(error);\n }\n return undefined;\n }\n}\n\n/**\n * Convert an address to a checksummed hexidecimal address.\n *\n * @param address - The address to convert.\n * @returns A 0x-prefixed hexidecimal checksummed address.\n */\nexport function toChecksumHexAddress(address: string) {\n const hexPrefixed = addHexPrefix(address);\n if (!isHexString(hexPrefixed)) {\n // Version 5.1 of ethereumjs-utils would have returned '0xY' for input 'y'\n // but we shouldn't waste effort trying to change case on a clearly invalid\n // string. Instead just return the hex prefixed original string which most\n // closely mimics the original behavior.\n return hexPrefixed;\n }\n return toChecksumAddress(hexPrefixed);\n}\n\n/**\n * Validates that the input is a hex address. This utility method is a thin\n * wrapper around ethereumjs-util.isValidAddress, with the exception that it\n * does not throw an error when provided values that are not hex strings. In\n * addition, and by default, this method will return true for hex strings that\n * meet the length requirement of a hex address, but are not prefixed with `0x`\n * Finally, if the mixedCaseUseChecksum flag is true and a mixed case string is\n * provided this method will validate it has the proper checksum formatting.\n *\n * @param possibleAddress - Input parameter to check against.\n * @param options - The validation options.\n * @param options.allowNonPrefixed - If true will first ensure '0x' is prepended to the string.\n * @returns Whether or not the input is a valid hex address.\n */\nexport function isValidHexAddress(\n possibleAddress: string,\n { allowNonPrefixed = true } = {},\n) {\n const addressToCheck = allowNonPrefixed\n ? addHexPrefix(possibleAddress)\n : possibleAddress;\n if (!isHexString(addressToCheck)) {\n return false;\n }\n\n return isValidAddress(addressToCheck);\n}\n\n/**\n * Validates a Transaction object for required properties and throws in\n * the event of any validation error.\n *\n * @param transaction - Transaction object to validate.\n */\nexport function validateTransaction(transaction: Transaction) {\n if (\n !transaction.from ||\n typeof transaction.from !== 'string' ||\n !isValidHexAddress(transaction.from)\n ) {\n throw new Error(\n `Invalid \"from\" address: ${transaction.from} must be a valid string.`,\n );\n }\n\n if (transaction.to === '0x' || transaction.to === undefined) {\n if (transaction.data) {\n delete transaction.to;\n } else {\n throw new Error(\n `Invalid \"to\" address: ${transaction.to} must be a valid string.`,\n );\n }\n } else if (\n transaction.to !== undefined &&\n !isValidHexAddress(transaction.to)\n ) {\n throw new Error(\n `Invalid \"to\" address: ${transaction.to} must be a valid string.`,\n );\n }\n\n if (transaction.value !== undefined) {\n const value = transaction.value.toString();\n if (value.includes('-')) {\n throw new Error(`Invalid \"value\": ${value} is not a positive number.`);\n }\n\n if (value.includes('.')) {\n throw new Error(\n `Invalid \"value\": ${value} number must be denominated in wei.`,\n );\n }\n const intValue = parseInt(transaction.value, 10);\n const isValid =\n Number.isFinite(intValue) &&\n !Number.isNaN(intValue) &&\n !isNaN(Number(value)) &&\n Number.isSafeInteger(intValue);\n if (!isValid) {\n throw new Error(\n `Invalid \"value\": ${value} number must be a valid number.`,\n );\n }\n }\n}\n\n/**\n * A helper function that converts rawmessageData buffer data to a hex, or just returns the data if\n * it is already formatted as a hex.\n *\n * @param data - The buffer data to convert to a hex.\n * @returns A hex string conversion of the buffer data.\n */\nexport function normalizeMessageData(data: string) {\n try {\n const stripped = stripHexPrefix(data);\n if (stripped.match(hexRe)) {\n return addHexPrefix(stripped);\n }\n } catch (e) {\n /* istanbul ignore next */\n }\n return bufferToHex(Buffer.from(data, 'utf8'));\n}\n\n/**\n * Validates a PersonalMessageParams and MessageParams objects for required properties and throws in\n * the event of any validation error.\n *\n * @param messageData - PersonalMessageParams object to validate.\n */\nexport function validateSignMessageData(\n messageData: PersonalMessageParams | MessageParams,\n) {\n const { from, data } = messageData;\n if (!from || typeof from !== 'string' || !isValidHexAddress(from)) {\n throw new Error(`Invalid \"from\" address: ${from} must be a valid string.`);\n }\n\n if (!data || typeof data !== 'string') {\n throw new Error(`Invalid message \"data\": ${data} must be a valid string.`);\n }\n}\n\n/**\n * Validates a TypedMessageParams object for required properties and throws in\n * the event of any validation error for eth_signTypedMessage_V1.\n *\n * @param messageData - TypedMessageParams object to validate.\n */\nexport function validateTypedSignMessageDataV1(\n messageData: TypedMessageParams,\n) {\n if (\n !messageData.from ||\n typeof messageData.from !== 'string' ||\n !isValidHexAddress(messageData.from)\n ) {\n throw new Error(\n `Invalid \"from\" address: ${messageData.from} must be a valid string.`,\n );\n }\n\n if (!messageData.data || !Array.isArray(messageData.data)) {\n throw new Error(\n `Invalid message \"data\": ${messageData.data} must be a valid array.`,\n );\n }\n\n try {\n // typedSignatureHash will throw if the data is invalid.\n typedSignatureHash(messageData.data as any);\n } catch (e) {\n throw new Error(`Expected EIP712 typed data.`);\n }\n}\n\n/**\n * Validates a TypedMessageParams object for required properties and throws in\n * the event of any validation error for eth_signTypedMessage_V3.\n *\n * @param messageData - TypedMessageParams object to validate.\n */\nexport function validateTypedSignMessageDataV3(\n messageData: TypedMessageParams,\n) {\n if (\n !messageData.from ||\n typeof messageData.from !== 'string' ||\n !isValidHexAddress(messageData.from)\n ) {\n throw new Error(\n `Invalid \"from\" address: ${messageData.from} must be a valid string.`,\n );\n }\n\n if (!messageData.data || typeof messageData.data !== 'string') {\n throw new Error(\n `Invalid message \"data\": ${messageData.data} must be a valid array.`,\n );\n }\n let data;\n try {\n data = JSON.parse(messageData.data);\n } catch (e) {\n throw new Error('Data must be passed as a valid JSON string.');\n }\n const validation = validate(data, TYPED_MESSAGE_SCHEMA);\n if (validation.errors.length > 0) {\n throw new Error(\n 'Data must conform to EIP-712 schema. See https://git.io/fNtcx.',\n );\n }\n}\n\n/**\n * Validates a ERC20 token to be added with EIP747.\n *\n * @param token - Token object to validate.\n */\nexport function validateTokenToWatch(token: Token) {\n const { address, symbol, decimals } = token;\n if (!address || !symbol || typeof decimals === 'undefined') {\n throw ethErrors.rpc.invalidParams(\n `Must specify address, symbol, and decimals.`,\n );\n }\n\n if (typeof symbol !== 'string') {\n throw ethErrors.rpc.invalidParams(`Invalid symbol: not a string.`);\n }\n\n if (symbol.length > 11) {\n throw ethErrors.rpc.invalidParams(\n `Invalid symbol \"${symbol}\": longer than 11 characters.`,\n );\n }\n const numDecimals = parseInt(decimals as unknown as string, 10);\n if (isNaN(numDecimals) || numDecimals > 36 || numDecimals < 0) {\n throw ethErrors.rpc.invalidParams(\n `Invalid decimals \"${decimals}\": must be 0 <= 36.`,\n );\n }\n\n if (!isValidHexAddress(address)) {\n throw ethErrors.rpc.invalidParams(`Invalid address \"${address}\".`);\n }\n}\n\n/**\n * Returns whether the given code corresponds to a smart contract.\n *\n * @param code - The potential smart contract code.\n * @returns Whether the code was smart contract code or not.\n */\nexport function isSmartContractCode(code: string) {\n /* istanbul ignore if */\n if (!code) {\n return false;\n }\n // Geth will return '0x', and ganache-core v2.2.1 will return '0x0'\n const smartContractCode = code !== '0x' && code !== '0x0';\n return smartContractCode;\n}\n\n/**\n * Execute fetch and verify that the response was successful.\n *\n * @param request - Request information.\n * @param options - Fetch options.\n * @returns The fetch response.\n */\nexport async function successfulFetch(request: string, options?: RequestInit) {\n const response = await fetch(request, options);\n if (!response.ok) {\n throw new Error(\n `Fetch failed with status '${response.status}' for request '${request}'`,\n );\n }\n return response;\n}\n\n/**\n * Execute fetch and return object response.\n *\n * @param request - The request information.\n * @param options - The fetch options.\n * @returns The fetch response JSON data.\n */\nexport async function handleFetch(request: string, options?: RequestInit) {\n const response = await successfulFetch(request, options);\n const object = await response.json();\n return object;\n}\n\n/**\n * Execute fetch and return object response, log if known error thrown, otherwise rethrow error.\n *\n * @param request - the request options object\n * @param request.url - The request url to query.\n * @param request.options - The fetch options.\n * @param request.timeout - Timeout to fail request\n * @param request.errorCodesToCatch - array of error codes for errors we want to catch in a particular context\n * @returns The fetch response JSON data or undefined (if error occurs).\n */\nexport async function fetchWithErrorHandling({\n url,\n options,\n timeout,\n errorCodesToCatch,\n}: {\n url: string;\n options?: RequestInit;\n timeout?: number;\n errorCodesToCatch?: number[];\n}) {\n let result;\n try {\n if (timeout) {\n result = Promise.race([\n await handleFetch(url, options),\n new Promise((_, reject) =>\n setTimeout(() => {\n reject(TIMEOUT_ERROR);\n }, timeout),\n ),\n ]);\n } else {\n result = await handleFetch(url, options);\n }\n } catch (e) {\n logOrRethrowError(e, errorCodesToCatch);\n }\n return result;\n}\n\n/**\n * Fetch that fails after timeout.\n *\n * @param url - Url to fetch.\n * @param options - Options to send with the request.\n * @param timeout - Timeout to fail request.\n * @returns Promise resolving the request.\n */\nexport async function timeoutFetch(\n url: string,\n options?: RequestInit,\n timeout = 500,\n): Promise {\n return Promise.race([\n successfulFetch(url, options),\n new Promise((_, reject) =>\n setTimeout(() => {\n reject(TIMEOUT_ERROR);\n }, timeout),\n ),\n ]);\n}\n\n/**\n * Normalizes the given ENS name.\n *\n * @param ensName - The ENS name.\n * @returns The normalized ENS name string.\n */\nexport function normalizeEnsName(ensName: string): string | null {\n if (ensName && typeof ensName === 'string') {\n try {\n const normalized = ensNamehash.normalize(ensName.trim());\n // this regex is only sufficient with the above call to ensNamehash.normalize\n // TODO: change 7 in regex to 3 when shorter ENS domains are live\n if (normalized.match(/^(([\\w\\d-]+)\\.)*[\\w\\d-]{7,}\\.(eth|test)$/u)) {\n return normalized;\n }\n } catch (_) {\n // do nothing\n }\n }\n return null;\n}\n\n/**\n * Wrapper method to handle EthQuery requests.\n *\n * @param ethQuery - EthQuery object initialized with a provider.\n * @param method - Method to request.\n * @param args - Arguments to send.\n * @returns Promise resolving the request.\n */\nexport function query(\n ethQuery: any,\n method: string,\n args: any[] = [],\n): Promise {\n return new Promise((resolve, reject) => {\n const cb = (error: Error, result: any) => {\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n };\n\n if (typeof ethQuery[method] === 'function') {\n ethQuery[method](...args, cb);\n } else {\n ethQuery.sendAsync({ method, params: args }, cb);\n }\n });\n}\n\n/**\n * Checks if a transaction is EIP-1559 by checking for the existence of\n * maxFeePerGas and maxPriorityFeePerGas within its parameters.\n *\n * @param transaction - Transaction object to add.\n * @returns Boolean that is true if the transaction is EIP-1559 (has maxFeePerGas and maxPriorityFeePerGas), otherwise returns false.\n */\nexport const isEIP1559Transaction = (transaction: Transaction): boolean => {\n const hasOwnProp = (obj: Transaction, key: string) =>\n Object.prototype.hasOwnProperty.call(obj, key);\n return (\n hasOwnProp(transaction, 'maxFeePerGas') &&\n hasOwnProp(transaction, 'maxPriorityFeePerGas')\n );\n};\n\nexport const convertHexToDecimal = (value: string | undefined): number =>\n parseInt(value === undefined ? '0x0' : value, 16);\n\nexport const getIncreasedPriceHex = (value: number, rate: number): string =>\n addHexPrefix(`${parseInt(`${value * rate}`, 10).toString(16)}`);\n\nexport const getIncreasedPriceFromExisting = (\n value: string | undefined,\n rate: number,\n): string => {\n return getIncreasedPriceHex(convertHexToDecimal(value), rate);\n};\n\nexport const validateGasValues = (\n gasValues: GasPriceValue | FeeMarketEIP1559Values,\n) => {\n Object.keys(gasValues).forEach((key) => {\n const value = (gasValues as any)[key];\n if (typeof value !== 'string' || !isHexString(value)) {\n throw new TypeError(\n `expected hex string for ${key} but received: ${value}`,\n );\n }\n });\n};\n\nexport const isFeeMarketEIP1559Values = (\n gasValues?: GasPriceValue | FeeMarketEIP1559Values,\n): gasValues is FeeMarketEIP1559Values =>\n (gasValues as FeeMarketEIP1559Values)?.maxFeePerGas !== undefined ||\n (gasValues as FeeMarketEIP1559Values)?.maxPriorityFeePerGas !== undefined;\n\nexport const isGasPriceValue = (\n gasValues?: GasPriceValue | FeeMarketEIP1559Values,\n): gasValues is GasPriceValue =>\n (gasValues as GasPriceValue)?.gasPrice !== undefined;\n\n/**\n * Validates that the proposed value is greater than or equal to the minimum value.\n *\n * @param proposed - The proposed value.\n * @param min - The minimum value.\n * @returns The proposed value.\n * @throws Will throw if the proposed value is too low.\n */\nexport function validateMinimumIncrease(proposed: string, min: string) {\n const proposedDecimal = convertHexToDecimal(proposed);\n const minDecimal = convertHexToDecimal(min);\n if (proposedDecimal >= minDecimal) {\n return proposed;\n }\n const errorMsg = `The proposed value: ${proposedDecimal} should meet or exceed the minimum value: ${minDecimal}`;\n throw new Error(errorMsg);\n}\n\n/**\n * Removes IPFS protocol prefix from input string.\n *\n * @param ipfsUrl - An IPFS url (e.g. ipfs://{content id})\n * @returns IPFS content identifier and (possibly) path in a string\n * @throws Will throw if the url passed is not IPFS.\n */\nexport function removeIpfsProtocolPrefix(ipfsUrl: string) {\n if (ipfsUrl.startsWith('ipfs://ipfs/')) {\n return ipfsUrl.replace('ipfs://ipfs/', '');\n } else if (ipfsUrl.startsWith('ipfs://')) {\n return ipfsUrl.replace('ipfs://', '');\n }\n // this method should not be used with non-ipfs urls (i.e. startsWith('ipfs://') === true)\n throw new Error('this method should not be used with non ipfs urls');\n}\n\n/**\n * Extracts content identifier and path from an input string.\n *\n * @param ipfsUrl - An IPFS URL minus the IPFS protocol prefix\n * @returns IFPS content identifier (cid) and sub path as string.\n * @throws Will throw if the url passed is not ipfs.\n */\nexport function getIpfsCIDv1AndPath(ipfsUrl: string): {\n cid: string;\n path?: string;\n} {\n const url = removeIpfsProtocolPrefix(ipfsUrl);\n\n // check if there is a path\n // (CID is everything preceding first forward slash, path is everything after)\n const index = url.indexOf('/');\n const cid = index !== -1 ? url.substring(0, index) : url;\n const path = index !== -1 ? url.substring(index) : undefined;\n\n // We want to ensure that the CID is v1 (https://docs.ipfs.io/concepts/content-addressing/#identifier-formats)\n // because most cid v0s appear to be incompatible with IPFS subdomains\n return {\n cid: CID.parse(cid).toV1().toString(),\n path,\n };\n}\n\n/**\n * Adds URL protocol prefix to input URL string if missing.\n *\n * @param urlString - An IPFS URL.\n * @returns A URL with a https:// prepended.\n */\nexport function addUrlProtocolPrefix(urlString: string): string {\n if (!urlString.match(/(^http:\\/\\/)|(^https:\\/\\/)/u)) {\n return `https://${urlString}`;\n }\n return urlString;\n}\n\n/**\n * Formats URL correctly for use retrieving assets hosted on IPFS.\n *\n * @param ipfsGateway - The users preferred IPFS gateway (full URL or just host).\n * @param ipfsUrl - The IFPS URL pointed at the asset.\n * @param subdomainSupported - Boolean indicating whether the URL should be formatted with subdomains or not.\n * @returns A formatted URL, with the user's preferred IPFS gateway and format (subdomain or not), pointing to an asset hosted on IPFS.\n */\nexport function getFormattedIpfsUrl(\n ipfsGateway: string,\n ipfsUrl: string,\n subdomainSupported: boolean,\n): string {\n const { host, protocol, origin } = new URL(addUrlProtocolPrefix(ipfsGateway));\n if (subdomainSupported) {\n const { cid, path } = getIpfsCIDv1AndPath(ipfsUrl);\n return `${protocol}//${cid}.ipfs.${host}${path ?? ''}`;\n }\n const cidAndPath = removeIpfsProtocolPrefix(ipfsUrl);\n return `${origin}/ipfs/${cidAndPath}`;\n}\n\ntype PlainObject = Record;\n\n/**\n * Determines whether a value is a \"plain\" object.\n *\n * @param value - A value to check\n * @returns True if the passed value is a plain object\n */\nexport function isPlainObject(value: unknown): value is PlainObject {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value);\n}\n\nexport const hasProperty = (\n object: PlainObject,\n key: string | number | symbol,\n) => Reflect.hasOwnProperty.call(object, key);\n\n/**\n * Like {@link Array}, but always non-empty.\n *\n * @template T - The non-empty array member type.\n */\nexport type NonEmptyArray = [T, ...T[]];\n\n/**\n * Type guard for {@link NonEmptyArray}.\n *\n * @template T - The non-empty array member type.\n * @param value - The value to check.\n * @returns Whether the value is a non-empty array.\n */\nexport function isNonEmptyArray(value: T[]): value is NonEmptyArray {\n return Array.isArray(value) && value.length > 0;\n}\n\n/**\n * Type guard for {@link Json}.\n *\n * @param value - The value to check.\n * @returns Whether the value is valid JSON.\n */\nexport function isValidJson(value: unknown): value is Json {\n try {\n return deepEqual(value, JSON.parse(JSON.stringify(value)));\n } catch (_) {\n return false;\n }\n}\n\n/**\n * Networks where token detection is supported - Values are in decimal format\n */\nexport enum SupportedTokenDetectionNetworks {\n mainnet = '1',\n bsc = '56',\n polygon = '137',\n avax = '43114',\n}\n\n/**\n * Check if token detection is enabled for certain networks.\n *\n * @param chainId - ChainID of network\n * @returns Whether the current network supports token detection\n */\nexport function isTokenDetectionSupportedForNetwork(chainId: string): boolean {\n return Object.values(SupportedTokenDetectionNetworks).includes(\n chainId,\n );\n}\n\n/**\n * Utility method to log if error is a common fetch error and otherwise rethrow it.\n *\n * @param error - Caught error that we should either rethrow or log to console\n * @param codesToCatch - array of error codes for errors we want to catch and log in a particular context\n */\nfunction logOrRethrowError(error: any, codesToCatch: number[] = []) {\n if (!error) {\n return;\n }\n\n const includesErrorCodeToCatch = codesToCatch.some((code) =>\n error.message?.includes(`Fetch failed with status '${code}'`),\n );\n\n if (\n error instanceof Error &&\n (includesErrorCodeToCatch ||\n error.message?.includes('Failed to fetch') ||\n error === TIMEOUT_ERROR)\n ) {\n console.error(error);\n } else {\n throw error;\n }\n}\n"]} \ No newline at end of file From 203b6ace9214dc1211e59e87c8a4600ea29f6856 Mon Sep 17 00:00:00 2001 From: sethkfman Date: Wed, 22 Jun 2022 15:49:57 -0600 Subject: [PATCH 4/6] PR feedback added NetworkType constant and applied to NetworkController --- src/constants.ts | 23 ++++++++++++++++++----- src/network/NetworkController.ts | 14 +++++++++++--- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 2620350cab..66568f0e4f 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,3 +1,5 @@ +import { NetworkType } from "./network/NetworkController"; + export const MAINNET = 'mainnet'; export const RPC = 'rpc'; export const FALL_BACK_VS_CURRENCY = 'ETH'; @@ -31,18 +33,29 @@ export const ASSET_TYPES = { UNKNOWN: 'UNKNOWN', }; -type tickerType = { - [key: string]: string; -}; - // TICKER SYMBOLS -export const TESTNET_TICKER_SYMBOLS: tickerType = { +export const TESTNET_TICKER_SYMBOLS = { RINKEBY: 'RinkebyETH', GOERLI: 'GoerliETH', ROPSTEN: 'RopstenETH', KOVAN: 'KovanETH', }; +// TYPED NetworkType TICKER SYMBOLS +export const TESTNET_NETWORK_TYPE_TO_TICKER_SYMBOL: { + [K in NetworkType]: string; +} = { + rinkeby: 'RinkebyETH', + goerli: 'GoerliETH', + ropsten: 'RopstenETH', + kovan: 'KovanETH', + mainnet: '', + rpc: '', + localhost: '', + optimism: '', + optimismTest: '', +}; + // APIs export const OPENSEA_PROXY_URL = 'https://proxy.metaswap.codefi.network/opensea/v1/api/v1'; diff --git a/src/network/NetworkController.ts b/src/network/NetworkController.ts index 41942e55fa..f7db81af17 100644 --- a/src/network/NetworkController.ts +++ b/src/network/NetworkController.ts @@ -4,7 +4,11 @@ import createInfuraProvider from 'eth-json-rpc-infura/src/createProvider'; import createMetamaskProvider from 'web3-provider-engine/zero'; import { Mutex } from 'async-mutex'; import { BaseController, BaseConfig, BaseState } from '../BaseController'; -import { MAINNET, RPC, TESTNET_TICKER_SYMBOLS } from '../constants'; +import { + MAINNET, + RPC, + TESTNET_NETWORK_TYPE_TO_TICKER_SYMBOL, +} from '../constants'; /** * Human-readable network name @@ -284,14 +288,18 @@ export class NetworkController extends BaseController< this.state.provider; // If testnet the ticker symbol should use a testnet prefix - const testNetTicker = TESTNET_TICKER_SYMBOLS[type.toUpperCase()]; + const ticker = + type in TESTNET_NETWORK_TYPE_TO_TICKER_SYMBOL && + TESTNET_NETWORK_TYPE_TO_TICKER_SYMBOL[type].length > 0 + ? TESTNET_NETWORK_TYPE_TO_TICKER_SYMBOL[type] + : 'ETH'; this.update({ provider: { ...providerState, ...{ type, - ticker: testNetTicker || 'ETH', + ticker, chainId: NetworksChainId[type], }, }, From 4c58025dee7efd3b603305c0cc0e8c10933e215f Mon Sep 17 00:00:00 2001 From: sethkfman Date: Fri, 1 Jul 2022 08:49:44 -0600 Subject: [PATCH 5/6] fixed lint warning --- src/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants.ts b/src/constants.ts index 66568f0e4f..74938c9e50 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,4 +1,4 @@ -import { NetworkType } from "./network/NetworkController"; +import { NetworkType } from './network/NetworkController'; export const MAINNET = 'mainnet'; export const RPC = 'rpc'; From db118e0fae9ea4191b09ec3817edbd0243767b9b Mon Sep 17 00:00:00 2001 From: sethkfman Date: Thu, 7 Jul 2022 14:42:29 -0600 Subject: [PATCH 6/6] removed dist folder and update .gitignore --- .gitignore | 1 + dist/BaseController.d.ts | 109 -- dist/BaseController.js | 142 -- dist/BaseController.js.map | 1 - dist/BaseControllerV2.d.ts | 134 -- dist/BaseControllerV2.js | 116 -- dist/BaseControllerV2.js.map | 1 - dist/ComposableController.d.ts | 42 - dist/ComposableController.js | 62 - dist/ComposableController.js.map | 1 - dist/ControllerMessenger.d.ts | 352 ----- dist/ControllerMessenger.js | 363 ----- dist/ControllerMessenger.js.map | 1 - dist/announcement/AnnouncementController.d.ts | 63 - dist/announcement/AnnouncementController.js | 54 - .../AnnouncementController.js.map | 1 - dist/apis/crypto-compare.d.ts | 12 - dist/apis/crypto-compare.js | 67 - dist/apis/crypto-compare.js.map | 1 - dist/apis/token-service.d.ts | 29 - dist/apis/token-service.js | 133 -- dist/apis/token-service.js.map | 1 - dist/approval/ApprovalController.d.ts | 278 ---- dist/approval/ApprovalController.js | 381 ----- dist/approval/ApprovalController.js.map | 1 - dist/assets/AccountTrackerController.d.ts | 88 -- dist/assets/AccountTrackerController.js | 138 -- dist/assets/AccountTrackerController.js.map | 1 - dist/assets/AssetsContractController.d.ts | 176 --- dist/assets/AssetsContractController.js | 323 ----- dist/assets/AssetsContractController.js.map | 1 - .../CollectibleDetectionController.d.ts | 178 --- dist/assets/CollectibleDetectionController.js | 205 --- .../CollectibleDetectionController.js.map | 1 - dist/assets/CollectiblesController.d.ts | 377 ----- dist/assets/CollectiblesController.js | 759 ---------- dist/assets/CollectiblesController.js.map | 1 - dist/assets/CurrencyRateController.d.ts | 99 -- dist/assets/CurrencyRateController.js | 194 --- dist/assets/CurrencyRateController.js.map | 1 - .../ERC1155/ERC1155Standard.d.ts | 77 -- .../ERC1155/ERC1155Standard.js | 173 --- .../ERC1155/ERC1155Standard.js.map | 1 - .../ERC721/ERC721Standard.d.ts | 88 -- .../ERC721/ERC721Standard.js | 226 --- .../ERC721/ERC721Standard.js.map | 1 - dist/assets/Standards/ERC20Standard.d.ts | 42 - dist/assets/Standards/ERC20Standard.js | 134 -- dist/assets/Standards/ERC20Standard.js.map | 1 - dist/assets/Standards/standards-types.d.ts | 14 - dist/assets/Standards/standards-types.js | 3 - dist/assets/Standards/standards-types.js.map | 1 - dist/assets/TokenBalancesController.d.ts | 69 - dist/assets/TokenBalancesController.js | 94 -- dist/assets/TokenBalancesController.js.map | 1 - dist/assets/TokenDetectionController.d.ts | 84 -- dist/assets/TokenDetectionController.js | 184 --- dist/assets/TokenDetectionController.js.map | 1 - dist/assets/TokenListController.d.ts | 101 -- dist/assets/TokenListController.js | 207 --- dist/assets/TokenListController.js.map | 1 - dist/assets/TokenRatesController.d.ts | 167 --- dist/assets/TokenRatesController.js | 285 ---- dist/assets/TokenRatesController.js.map | 1 - dist/assets/TokensController.d.ts | 236 ---- dist/assets/TokensController.js | 532 ------- dist/assets/TokensController.js.map | 1 - dist/assets/assetsUtil.d.ts | 30 - dist/assets/assetsUtil.js | 91 -- dist/assets/assetsUtil.js.map | 1 - dist/constants.d.ts | 29 - dist/constants.js | 41 - dist/constants.js.map | 1 - dist/gas/GasFeeController.d.ts | 230 ---- dist/gas/GasFeeController.js | 243 ---- dist/gas/GasFeeController.js.map | 1 - dist/gas/determineGasFeeCalculations.d.ts | 40 - dist/gas/determineGasFeeCalculations.js | 87 -- dist/gas/determineGasFeeCalculations.js.map | 1 - dist/gas/fetchBlockFeeHistory.d.ts | 115 -- dist/gas/fetchBlockFeeHistory.js | 202 --- dist/gas/fetchBlockFeeHistory.js.map | 1 - .../fetchGasEstimatesViaEthFeeHistory.d.ts | 21 - dist/gas/fetchGasEstimatesViaEthFeeHistory.js | 53 - .../fetchGasEstimatesViaEthFeeHistory.js.map | 1 - ...ulateGasFeeEstimatesForPriorityLevels.d.ts | 16 - ...lculateGasFeeEstimatesForPriorityLevels.js | 89 -- ...ateGasFeeEstimatesForPriorityLevels.js.map | 1 - .../fetchLatestBlock.d.ts | 10 - .../fetchLatestBlock.js | 32 - .../fetchLatestBlock.js.map | 1 - .../medianOf.d.ts | 10 - .../medianOf.js | 17 - .../medianOf.js.map | 1 - .../types.d.ts | 10 - .../types.js | 3 - .../types.js.map | 1 - dist/gas/gas-util.d.ts | 41 - dist/gas/gas-util.js | 140 -- dist/gas/gas-util.js.map | 1 - dist/index.d.ts | 33 - dist/index.js | 66 - dist/index.js.map | 1 - dist/keyring/KeyringController.d.ts | 308 ----- dist/keyring/KeyringController.js | 636 --------- dist/keyring/KeyringController.js.map | 1 - .../AbstractMessageManager.d.ts | 172 --- .../message-manager/AbstractMessageManager.js | 167 --- .../AbstractMessageManager.js.map | 1 - dist/message-manager/MessageManager.d.ts | 77 -- dist/message-manager/MessageManager.js | 83 -- dist/message-manager/MessageManager.js.map | 1 - .../PersonalMessageManager.d.ts | 77 -- .../message-manager/PersonalMessageManager.js | 83 -- .../PersonalMessageManager.js.map | 1 - dist/message-manager/TypedMessageManager.d.ts | 100 -- dist/message-manager/TypedMessageManager.js | 109 -- .../TypedMessageManager.js.map | 1 - dist/network/NetworkController.d.ts | 126 -- dist/network/NetworkController.js | 251 ---- dist/network/NetworkController.js.map | 1 - dist/notification/NotificationController.d.ts | 84 -- dist/notification/NotificationController.js | 85 -- .../NotificationController.js.map | 1 - dist/permissions/Caveat.d.ts | 171 --- dist/permissions/Caveat.js | 42 - dist/permissions/Caveat.js.map | 1 - dist/permissions/Permission.d.ts | 426 ------ dist/permissions/Permission.js | 66 - dist/permissions/Permission.js.map | 1 - dist/permissions/PermissionController.d.ts | 870 ------------ dist/permissions/PermissionController.js | 1220 ----------------- dist/permissions/PermissionController.js.map | 1 - dist/permissions/errors.d.ts | 150 -- dist/permissions/errors.js | 189 --- dist/permissions/errors.js.map | 1 - dist/permissions/index.d.ts | 5 - dist/permissions/index.js | 35 - dist/permissions/index.js.map | 1 - dist/permissions/permission-middleware.d.ts | 32 - dist/permissions/permission-middleware.js | 64 - dist/permissions/permission-middleware.js.map | 1 - .../rpc-methods/getPermissions.d.ts | 7 - .../permissions/rpc-methods/getPermissions.js | 38 - .../rpc-methods/getPermissions.js.map | 1 - dist/permissions/rpc-methods/index.d.ts | 4 - dist/permissions/rpc-methods/index.js | 7 - dist/permissions/rpc-methods/index.js.map | 1 - .../rpc-methods/requestPermissions.d.ts | 16 - .../rpc-methods/requestPermissions.js | 55 - .../rpc-methods/requestPermissions.js.map | 1 - dist/permissions/utils.d.ts | 15 - dist/permissions/utils.js | 9 - dist/permissions/utils.js.map | 1 - dist/ratelimit/RateLimitController.d.ts | 82 -- dist/ratelimit/RateLimitController.js | 110 -- dist/ratelimit/RateLimitController.js.map | 1 - .../SubjectMetadataController.d.ts | 85 -- .../SubjectMetadataController.js | 117 -- .../SubjectMetadataController.js.map | 1 - dist/subject-metadata/index.d.ts | 1 - dist/subject-metadata/index.js | 18 - dist/subject-metadata/index.js.map | 1 - dist/third-party/EnsController.d.ts | 76 - dist/third-party/EnsController.js | 108 -- dist/third-party/EnsController.js.map | 1 - dist/third-party/PhishingController.d.ts | 118 -- dist/third-party/PhishingController.js | 165 --- dist/third-party/PhishingController.js.map | 1 - dist/transaction/TransactionController.d.ts | 460 ------- dist/transaction/TransactionController.js | 915 ------------- dist/transaction/TransactionController.js.map | 1 - dist/transaction/mocks/txsMock.d.ts | 63 - dist/transaction/mocks/txsMock.js | 515 ------- dist/transaction/mocks/txsMock.js.map | 1 - dist/user/AddressBookController.d.ts | 83 -- dist/user/AddressBookController.js | 87 -- dist/user/AddressBookController.js.map | 1 - dist/user/PreferencesController.d.ts | 157 --- dist/user/PreferencesController.js | 243 ---- dist/user/PreferencesController.js.map | 1 - dist/util.d.ts | 357 ----- dist/util.js | 871 ------------ dist/util.js.map | 1 - 184 files changed, 1 insertion(+), 19710 deletions(-) delete mode 100644 dist/BaseController.d.ts delete mode 100644 dist/BaseController.js delete mode 100644 dist/BaseController.js.map delete mode 100644 dist/BaseControllerV2.d.ts delete mode 100644 dist/BaseControllerV2.js delete mode 100644 dist/BaseControllerV2.js.map delete mode 100644 dist/ComposableController.d.ts delete mode 100644 dist/ComposableController.js delete mode 100644 dist/ComposableController.js.map delete mode 100644 dist/ControllerMessenger.d.ts delete mode 100644 dist/ControllerMessenger.js delete mode 100644 dist/ControllerMessenger.js.map delete mode 100644 dist/announcement/AnnouncementController.d.ts delete mode 100644 dist/announcement/AnnouncementController.js delete mode 100644 dist/announcement/AnnouncementController.js.map delete mode 100644 dist/apis/crypto-compare.d.ts delete mode 100644 dist/apis/crypto-compare.js delete mode 100644 dist/apis/crypto-compare.js.map delete mode 100644 dist/apis/token-service.d.ts delete mode 100644 dist/apis/token-service.js delete mode 100644 dist/apis/token-service.js.map delete mode 100644 dist/approval/ApprovalController.d.ts delete mode 100644 dist/approval/ApprovalController.js delete mode 100644 dist/approval/ApprovalController.js.map delete mode 100644 dist/assets/AccountTrackerController.d.ts delete mode 100644 dist/assets/AccountTrackerController.js delete mode 100644 dist/assets/AccountTrackerController.js.map delete mode 100644 dist/assets/AssetsContractController.d.ts delete mode 100644 dist/assets/AssetsContractController.js delete mode 100644 dist/assets/AssetsContractController.js.map delete mode 100644 dist/assets/CollectibleDetectionController.d.ts delete mode 100644 dist/assets/CollectibleDetectionController.js delete mode 100644 dist/assets/CollectibleDetectionController.js.map delete mode 100644 dist/assets/CollectiblesController.d.ts delete mode 100644 dist/assets/CollectiblesController.js delete mode 100644 dist/assets/CollectiblesController.js.map delete mode 100644 dist/assets/CurrencyRateController.d.ts delete mode 100644 dist/assets/CurrencyRateController.js delete mode 100644 dist/assets/CurrencyRateController.js.map delete mode 100644 dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.d.ts delete mode 100644 dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.js delete mode 100644 dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.js.map delete mode 100644 dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.d.ts delete mode 100644 dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.js delete mode 100644 dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.js.map delete mode 100644 dist/assets/Standards/ERC20Standard.d.ts delete mode 100644 dist/assets/Standards/ERC20Standard.js delete mode 100644 dist/assets/Standards/ERC20Standard.js.map delete mode 100644 dist/assets/Standards/standards-types.d.ts delete mode 100644 dist/assets/Standards/standards-types.js delete mode 100644 dist/assets/Standards/standards-types.js.map delete mode 100644 dist/assets/TokenBalancesController.d.ts delete mode 100644 dist/assets/TokenBalancesController.js delete mode 100644 dist/assets/TokenBalancesController.js.map delete mode 100644 dist/assets/TokenDetectionController.d.ts delete mode 100644 dist/assets/TokenDetectionController.js delete mode 100644 dist/assets/TokenDetectionController.js.map delete mode 100644 dist/assets/TokenListController.d.ts delete mode 100644 dist/assets/TokenListController.js delete mode 100644 dist/assets/TokenListController.js.map delete mode 100644 dist/assets/TokenRatesController.d.ts delete mode 100644 dist/assets/TokenRatesController.js delete mode 100644 dist/assets/TokenRatesController.js.map delete mode 100644 dist/assets/TokensController.d.ts delete mode 100644 dist/assets/TokensController.js delete mode 100644 dist/assets/TokensController.js.map delete mode 100644 dist/assets/assetsUtil.d.ts delete mode 100644 dist/assets/assetsUtil.js delete mode 100644 dist/assets/assetsUtil.js.map delete mode 100644 dist/constants.d.ts delete mode 100644 dist/constants.js delete mode 100644 dist/constants.js.map delete mode 100644 dist/gas/GasFeeController.d.ts delete mode 100644 dist/gas/GasFeeController.js delete mode 100644 dist/gas/GasFeeController.js.map delete mode 100644 dist/gas/determineGasFeeCalculations.d.ts delete mode 100644 dist/gas/determineGasFeeCalculations.js delete mode 100644 dist/gas/determineGasFeeCalculations.js.map delete mode 100644 dist/gas/fetchBlockFeeHistory.d.ts delete mode 100644 dist/gas/fetchBlockFeeHistory.js delete mode 100644 dist/gas/fetchBlockFeeHistory.js.map delete mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory.d.ts delete mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory.js delete mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory.js.map delete mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.d.ts delete mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.js delete mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.js.map delete mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.d.ts delete mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.js delete mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.js.map delete mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.d.ts delete mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.js delete mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.js.map delete mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/types.d.ts delete mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/types.js delete mode 100644 dist/gas/fetchGasEstimatesViaEthFeeHistory/types.js.map delete mode 100644 dist/gas/gas-util.d.ts delete mode 100644 dist/gas/gas-util.js delete mode 100644 dist/gas/gas-util.js.map delete mode 100644 dist/index.d.ts delete mode 100644 dist/index.js delete mode 100644 dist/index.js.map delete mode 100644 dist/keyring/KeyringController.d.ts delete mode 100644 dist/keyring/KeyringController.js delete mode 100644 dist/keyring/KeyringController.js.map delete mode 100644 dist/message-manager/AbstractMessageManager.d.ts delete mode 100644 dist/message-manager/AbstractMessageManager.js delete mode 100644 dist/message-manager/AbstractMessageManager.js.map delete mode 100644 dist/message-manager/MessageManager.d.ts delete mode 100644 dist/message-manager/MessageManager.js delete mode 100644 dist/message-manager/MessageManager.js.map delete mode 100644 dist/message-manager/PersonalMessageManager.d.ts delete mode 100644 dist/message-manager/PersonalMessageManager.js delete mode 100644 dist/message-manager/PersonalMessageManager.js.map delete mode 100644 dist/message-manager/TypedMessageManager.d.ts delete mode 100644 dist/message-manager/TypedMessageManager.js delete mode 100644 dist/message-manager/TypedMessageManager.js.map delete mode 100644 dist/network/NetworkController.d.ts delete mode 100644 dist/network/NetworkController.js delete mode 100644 dist/network/NetworkController.js.map delete mode 100644 dist/notification/NotificationController.d.ts delete mode 100644 dist/notification/NotificationController.js delete mode 100644 dist/notification/NotificationController.js.map delete mode 100644 dist/permissions/Caveat.d.ts delete mode 100644 dist/permissions/Caveat.js delete mode 100644 dist/permissions/Caveat.js.map delete mode 100644 dist/permissions/Permission.d.ts delete mode 100644 dist/permissions/Permission.js delete mode 100644 dist/permissions/Permission.js.map delete mode 100644 dist/permissions/PermissionController.d.ts delete mode 100644 dist/permissions/PermissionController.js delete mode 100644 dist/permissions/PermissionController.js.map delete mode 100644 dist/permissions/errors.d.ts delete mode 100644 dist/permissions/errors.js delete mode 100644 dist/permissions/errors.js.map delete mode 100644 dist/permissions/index.d.ts delete mode 100644 dist/permissions/index.js delete mode 100644 dist/permissions/index.js.map delete mode 100644 dist/permissions/permission-middleware.d.ts delete mode 100644 dist/permissions/permission-middleware.js delete mode 100644 dist/permissions/permission-middleware.js.map delete mode 100644 dist/permissions/rpc-methods/getPermissions.d.ts delete mode 100644 dist/permissions/rpc-methods/getPermissions.js delete mode 100644 dist/permissions/rpc-methods/getPermissions.js.map delete mode 100644 dist/permissions/rpc-methods/index.d.ts delete mode 100644 dist/permissions/rpc-methods/index.js delete mode 100644 dist/permissions/rpc-methods/index.js.map delete mode 100644 dist/permissions/rpc-methods/requestPermissions.d.ts delete mode 100644 dist/permissions/rpc-methods/requestPermissions.js delete mode 100644 dist/permissions/rpc-methods/requestPermissions.js.map delete mode 100644 dist/permissions/utils.d.ts delete mode 100644 dist/permissions/utils.js delete mode 100644 dist/permissions/utils.js.map delete mode 100644 dist/ratelimit/RateLimitController.d.ts delete mode 100644 dist/ratelimit/RateLimitController.js delete mode 100644 dist/ratelimit/RateLimitController.js.map delete mode 100644 dist/subject-metadata/SubjectMetadataController.d.ts delete mode 100644 dist/subject-metadata/SubjectMetadataController.js delete mode 100644 dist/subject-metadata/SubjectMetadataController.js.map delete mode 100644 dist/subject-metadata/index.d.ts delete mode 100644 dist/subject-metadata/index.js delete mode 100644 dist/subject-metadata/index.js.map delete mode 100644 dist/third-party/EnsController.d.ts delete mode 100644 dist/third-party/EnsController.js delete mode 100644 dist/third-party/EnsController.js.map delete mode 100644 dist/third-party/PhishingController.d.ts delete mode 100644 dist/third-party/PhishingController.js delete mode 100644 dist/third-party/PhishingController.js.map delete mode 100644 dist/transaction/TransactionController.d.ts delete mode 100644 dist/transaction/TransactionController.js delete mode 100644 dist/transaction/TransactionController.js.map delete mode 100644 dist/transaction/mocks/txsMock.d.ts delete mode 100644 dist/transaction/mocks/txsMock.js delete mode 100644 dist/transaction/mocks/txsMock.js.map delete mode 100644 dist/user/AddressBookController.d.ts delete mode 100644 dist/user/AddressBookController.js delete mode 100644 dist/user/AddressBookController.js.map delete mode 100644 dist/user/PreferencesController.d.ts delete mode 100644 dist/user/PreferencesController.js delete mode 100644 dist/user/PreferencesController.js.map delete mode 100644 dist/util.d.ts delete mode 100644 dist/util.js delete mode 100644 dist/util.js.map diff --git a/.gitignore b/.gitignore index f409a8434f..654ab7b4c5 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ package-lock.json .DS_STORE coverage +dist docs # yarn v3 (w/o zero-install) diff --git a/dist/BaseController.d.ts b/dist/BaseController.d.ts deleted file mode 100644 index 31625f7066..0000000000 --- a/dist/BaseController.d.ts +++ /dev/null @@ -1,109 +0,0 @@ -/** - * State change callbacks - */ -export declare type Listener = (state: T) => void; -/** - * @type BaseConfig - * - * Base controller configuration - * @property disabled - Determines if this controller is enabled - */ -export interface BaseConfig { - disabled?: boolean; -} -/** - * @type BaseState - * - * Base state representation - * @property name - Unique name for this controller - */ -export interface BaseState { - name?: string; -} -/** - * Controller class that provides configuration, state management, and subscriptions - */ -export declare class BaseController { - /** - * Default options used to configure this controller - */ - defaultConfig: C; - /** - * Default state set on this controller - */ - defaultState: S; - /** - * Determines if listeners are notified of state changes - */ - disabled: boolean; - /** - * Name of this controller used during composition - */ - name: string; - private readonly initialConfig; - private readonly initialState; - private internalConfig; - private internalState; - private internalListeners; - /** - * Creates a BaseController instance. Both initial state and initial - * configuration options are merged with defaults upon initialization. - * - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor(config?: Partial, state?: Partial); - /** - * Enables the controller. This sets each config option as a member - * variable on this instance and triggers any defined setters. This - * also sets initial state and triggers any listeners. - * - * @returns This controller instance. - */ - protected initialize(): this; - /** - * Retrieves current controller configuration options. - * - * @returns The current configuration. - */ - get config(): C; - /** - * Retrieves current controller state. - * - * @returns The current state. - */ - get state(): S; - /** - * Updates controller configuration. - * - * @param config - New configuration options. - * @param overwrite - Overwrite config instead of merging. - * @param fullUpdate - Boolean that defines if the update is partial or not. - */ - configure(config: Partial, overwrite?: boolean, fullUpdate?: boolean): void; - /** - * Notifies all subscribed listeners of current state. - */ - notify(): void; - /** - * Adds new listener to be notified of state changes. - * - * @param listener - The callback triggered when state changes. - */ - subscribe(listener: Listener): void; - /** - * Removes existing listener from receiving state changes. - * - * @param listener - The callback to remove. - * @returns `true` if a listener is found and unsubscribed. - */ - unsubscribe(listener: Listener): boolean; - /** - * Updates controller state. - * - * @param state - The new state. - * @param overwrite - Overwrite state instead of merging. - */ - update(state: Partial, overwrite?: boolean): void; -} -export default BaseController; diff --git a/dist/BaseController.js b/dist/BaseController.js deleted file mode 100644 index bb2b20ad0d..0000000000 --- a/dist/BaseController.js +++ /dev/null @@ -1,142 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.BaseController = void 0; -/** - * Controller class that provides configuration, state management, and subscriptions - */ -class BaseController { - /** - * Creates a BaseController instance. Both initial state and initial - * configuration options are merged with defaults upon initialization. - * - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor(config = {}, state = {}) { - /** - * Default options used to configure this controller - */ - this.defaultConfig = {}; - /** - * Default state set on this controller - */ - this.defaultState = {}; - /** - * Determines if listeners are notified of state changes - */ - this.disabled = false; - /** - * Name of this controller used during composition - */ - this.name = 'BaseController'; - this.internalConfig = this.defaultConfig; - this.internalState = this.defaultState; - this.internalListeners = []; - // Use assign since generics can't be spread: https://git.io/vpRhY - this.initialState = state; - this.initialConfig = config; - } - /** - * Enables the controller. This sets each config option as a member - * variable on this instance and triggers any defined setters. This - * also sets initial state and triggers any listeners. - * - * @returns This controller instance. - */ - initialize() { - this.internalState = this.defaultState; - this.internalConfig = this.defaultConfig; - this.configure(this.initialConfig); - this.update(this.initialState); - return this; - } - /** - * Retrieves current controller configuration options. - * - * @returns The current configuration. - */ - get config() { - return this.internalConfig; - } - /** - * Retrieves current controller state. - * - * @returns The current state. - */ - get state() { - return this.internalState; - } - /** - * Updates controller configuration. - * - * @param config - New configuration options. - * @param overwrite - Overwrite config instead of merging. - * @param fullUpdate - Boolean that defines if the update is partial or not. - */ - configure(config, overwrite = false, fullUpdate = true) { - if (fullUpdate) { - this.internalConfig = overwrite - ? config - : Object.assign(this.internalConfig, config); - for (const key in this.internalConfig) { - if (typeof this.internalConfig[key] !== 'undefined') { - this[key] = this.internalConfig[key]; - } - } - } - else { - for (const key in config) { - /* istanbul ignore else */ - if (typeof this.internalConfig[key] !== 'undefined') { - this.internalConfig[key] = config[key]; - this[key] = config[key]; - } - } - } - } - /** - * Notifies all subscribed listeners of current state. - */ - notify() { - if (this.disabled) { - return; - } - this.internalListeners.forEach((listener) => { - listener(this.internalState); - }); - } - /** - * Adds new listener to be notified of state changes. - * - * @param listener - The callback triggered when state changes. - */ - subscribe(listener) { - this.internalListeners.push(listener); - } - /** - * Removes existing listener from receiving state changes. - * - * @param listener - The callback to remove. - * @returns `true` if a listener is found and unsubscribed. - */ - unsubscribe(listener) { - const index = this.internalListeners.findIndex((cb) => listener === cb); - index > -1 && this.internalListeners.splice(index, 1); - return index > -1; - } - /** - * Updates controller state. - * - * @param state - The new state. - * @param overwrite - Overwrite state instead of merging. - */ - update(state, overwrite = false) { - this.internalState = overwrite - ? Object.assign({}, state) - : Object.assign({}, this.internalState, state); - this.notify(); - } -} -exports.BaseController = BaseController; -exports.default = BaseController; -//# sourceMappingURL=BaseController.js.map \ No newline at end of file diff --git a/dist/BaseController.js.map b/dist/BaseController.js.map deleted file mode 100644 index f628a8266a..0000000000 --- a/dist/BaseController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"BaseController.js","sourceRoot":"","sources":["../src/BaseController.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * State change callbacks\n */\nexport type Listener = (state: T) => void;\n\n/**\n * @type BaseConfig\n *\n * Base controller configuration\n * @property disabled - Determines if this controller is enabled\n */\nexport interface BaseConfig {\n disabled?: boolean;\n}\n\n/**\n * @type BaseState\n *\n * Base state representation\n * @property name - Unique name for this controller\n */\nexport interface BaseState {\n name?: string;\n}\n\n/**\n * Controller class that provides configuration, state management, and subscriptions\n */\nexport class BaseController {\n /**\n * Default options used to configure this controller\n */\n defaultConfig: C = {} as C;\n\n /**\n * Default state set on this controller\n */\n defaultState: S = {} as S;\n\n /**\n * Determines if listeners are notified of state changes\n */\n disabled = false;\n\n /**\n * Name of this controller used during composition\n */\n name = 'BaseController';\n\n private readonly initialConfig: C;\n\n private readonly initialState: S;\n\n private internalConfig: C = this.defaultConfig;\n\n private internalState: S = this.defaultState;\n\n private internalListeners: Listener[] = [];\n\n /**\n * Creates a BaseController instance. Both initial state and initial\n * configuration options are merged with defaults upon initialization.\n *\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(config: Partial = {} as C, state: Partial = {} as S) {\n // Use assign since generics can't be spread: https://git.io/vpRhY\n this.initialState = state as S;\n this.initialConfig = config as C;\n }\n\n /**\n * Enables the controller. This sets each config option as a member\n * variable on this instance and triggers any defined setters. This\n * also sets initial state and triggers any listeners.\n *\n * @returns This controller instance.\n */\n protected initialize() {\n this.internalState = this.defaultState;\n this.internalConfig = this.defaultConfig;\n this.configure(this.initialConfig);\n this.update(this.initialState);\n return this;\n }\n\n /**\n * Retrieves current controller configuration options.\n *\n * @returns The current configuration.\n */\n get config() {\n return this.internalConfig;\n }\n\n /**\n * Retrieves current controller state.\n *\n * @returns The current state.\n */\n get state() {\n return this.internalState;\n }\n\n /**\n * Updates controller configuration.\n *\n * @param config - New configuration options.\n * @param overwrite - Overwrite config instead of merging.\n * @param fullUpdate - Boolean that defines if the update is partial or not.\n */\n configure(config: Partial, overwrite = false, fullUpdate = true) {\n if (fullUpdate) {\n this.internalConfig = overwrite\n ? (config as C)\n : Object.assign(this.internalConfig, config);\n\n for (const key in this.internalConfig) {\n if (typeof this.internalConfig[key] !== 'undefined') {\n (this as any)[key as string] = this.internalConfig[key];\n }\n }\n } else {\n for (const key in config) {\n /* istanbul ignore else */\n if (typeof this.internalConfig[key] !== 'undefined') {\n this.internalConfig[key] = config[key] as any;\n (this as any)[key as string] = config[key];\n }\n }\n }\n }\n\n /**\n * Notifies all subscribed listeners of current state.\n */\n notify() {\n if (this.disabled) {\n return;\n }\n\n this.internalListeners.forEach((listener) => {\n listener(this.internalState);\n });\n }\n\n /**\n * Adds new listener to be notified of state changes.\n *\n * @param listener - The callback triggered when state changes.\n */\n subscribe(listener: Listener) {\n this.internalListeners.push(listener);\n }\n\n /**\n * Removes existing listener from receiving state changes.\n *\n * @param listener - The callback to remove.\n * @returns `true` if a listener is found and unsubscribed.\n */\n unsubscribe(listener: Listener) {\n const index = this.internalListeners.findIndex((cb) => listener === cb);\n index > -1 && this.internalListeners.splice(index, 1);\n return index > -1;\n }\n\n /**\n * Updates controller state.\n *\n * @param state - The new state.\n * @param overwrite - Overwrite state instead of merging.\n */\n update(state: Partial, overwrite = false) {\n this.internalState = overwrite\n ? Object.assign({}, state as S)\n : Object.assign({}, this.internalState, state);\n this.notify();\n }\n}\n\nexport default BaseController;\n"]} \ No newline at end of file diff --git a/dist/BaseControllerV2.d.ts b/dist/BaseControllerV2.d.ts deleted file mode 100644 index 803593c02d..0000000000 --- a/dist/BaseControllerV2.d.ts +++ /dev/null @@ -1,134 +0,0 @@ -import type { Draft, Patch } from 'immer'; -import type { RestrictedControllerMessenger } from './ControllerMessenger'; -/** - * A state change listener. - * - * This function will get called for each state change, and is given a copy of - * the new state along with a set of patches describing the changes since the - * last update. - * - * @param state - The new controller state. - * @param patches - A list of patches describing any changes (see here for more - * information: https://immerjs.github.io/immer/docs/patches) - */ -export declare type Listener = (state: T, patches: Patch[]) => void; -/** - * An function to derive state. - * - * This function will accept one piece of the controller state (one property), - * and will return some derivation of that state. - * - * @param value - A piece of controller state. - * @returns Something derived from controller state. - */ -export declare type StateDeriver = (value: T) => Json; -/** - * State metadata. - * - * This metadata describes which parts of state should be persisted, and how to - * get an anonymized representation of the state. - */ -export declare type StateMetadata> = { - [P in keyof T]: StatePropertyMetadata; -}; -/** - * Metadata for a single state property - * - * @property persist - Indicates whether this property should be persisted - * (`true` for persistent, `false` for transient), or is set to a function - * that derives the persistent state from the state. - * @property anonymous - Indicates whether this property is already anonymous, - * (`true` for anonymous, `false` if it has potential to be personally - * identifiable), or is set to a function that returns an anonymized - * representation of this state. - */ -export interface StatePropertyMetadata { - persist: boolean | StateDeriver; - anonymous: boolean | StateDeriver; -} -export declare type Json = null | boolean | number | string | Json[] | { - [prop: string]: Json; -}; -/** - * Controller class that provides state management, subscriptions, and state metadata - */ -export declare class BaseController, messenger extends RestrictedControllerMessenger> { - private internalState; - protected messagingSystem: messenger; - /** - * The name of the controller. - * - * This is used by the ComposableController to construct a composed application state. - */ - readonly name: N; - readonly metadata: StateMetadata; - /** - * The existence of the `subscribe` property is how the ComposableController detects whether a - * controller extends the old BaseController or the new one. We set it to `undefined` here to - * ensure the ComposableController never mistakes them for an older style controller. - */ - readonly subscribe: undefined; - /** - * Creates a BaseController instance. - * - * @param options - Controller options. - * @param options.messenger - Controller messaging system. - * @param options.metadata - State metadata, describing how to "anonymize" the state, and which - * parts should be persisted. - * @param options.name - The name of the controller, used as a namespace for events and actions. - * @param options.state - Initial controller state. - */ - constructor({ messenger, metadata, name, state, }: { - messenger: messenger; - metadata: StateMetadata; - name: N; - state: S; - }); - /** - * Retrieves current controller state. - * - * @returns The current state. - */ - get state(): S; - set state(_: S); - /** - * Updates controller state. Accepts a callback that is passed a draft copy - * of the controller state. If a value is returned, it is set as the new - * state. Otherwise, any changes made within that callback to the draft are - * applied to the controller state. - * - * @param callback - Callback for updating state, passed a draft state - * object. Return a new state object or mutate the draft to update state. - */ - protected update(callback: (state: Draft) => void | S): void; - /** - * Prepares the controller for garbage collection. This should be extended - * by any subclasses to clean up any additional connections or events. - * - * The only cleanup performed here is to remove listeners. While technically - * this is not required to ensure this instance is garbage collected, it at - * least ensures this instance won't be responsible for preventing the - * listeners from being garbage collected. - */ - protected destroy(): void; -} -/** - * Returns an anonymized representation of the controller state. - * - * By "anonymized" we mean that it should not contain any information that could be personally - * identifiable. - * - * @param state - The controller state. - * @param metadata - The controller state metadata, which describes how to derive the - * anonymized state. - * @returns The anonymized controller state. - */ -export declare function getAnonymizedState>(state: S, metadata: StateMetadata): Record; -/** - * Returns the subset of state that should be persisted. - * - * @param state - The controller state. - * @param metadata - The controller state metadata, which describes which pieces of state should be persisted. - * @returns The subset of controller state that should be persisted. - */ -export declare function getPersistentState>(state: S, metadata: StateMetadata): Record; diff --git a/dist/BaseControllerV2.js b/dist/BaseControllerV2.js deleted file mode 100644 index 980e2a4cd7..0000000000 --- a/dist/BaseControllerV2.js +++ /dev/null @@ -1,116 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.getPersistentState = exports.getAnonymizedState = exports.BaseController = void 0; -const immer_1 = require("immer"); -(0, immer_1.enablePatches)(); -/** - * Controller class that provides state management, subscriptions, and state metadata - */ -class BaseController { - /** - * Creates a BaseController instance. - * - * @param options - Controller options. - * @param options.messenger - Controller messaging system. - * @param options.metadata - State metadata, describing how to "anonymize" the state, and which - * parts should be persisted. - * @param options.name - The name of the controller, used as a namespace for events and actions. - * @param options.state - Initial controller state. - */ - constructor({ messenger, metadata, name, state, }) { - this.messagingSystem = messenger; - this.name = name; - this.internalState = state; - this.metadata = metadata; - this.messagingSystem.registerActionHandler(`${name}:getState`, () => this.state); - } - /** - * Retrieves current controller state. - * - * @returns The current state. - */ - get state() { - return this.internalState; - } - set state(_) { - throw new Error(`Controller state cannot be directly mutated; use 'update' method instead.`); - } - /** - * Updates controller state. Accepts a callback that is passed a draft copy - * of the controller state. If a value is returned, it is set as the new - * state. Otherwise, any changes made within that callback to the draft are - * applied to the controller state. - * - * @param callback - Callback for updating state, passed a draft state - * object. Return a new state object or mutate the draft to update state. - */ - update(callback) { - // We run into ts2589, "infinite type depth", if we don't cast - // produceWithPatches here. - // The final, omitted member of the returned tuple are the inverse patches. - const [nextState, patches] = immer_1.produceWithPatches(this.internalState, callback); - this.internalState = nextState; - this.messagingSystem.publish(`${this.name}:stateChange`, nextState, patches); - } - /** - * Prepares the controller for garbage collection. This should be extended - * by any subclasses to clean up any additional connections or events. - * - * The only cleanup performed here is to remove listeners. While technically - * this is not required to ensure this instance is garbage collected, it at - * least ensures this instance won't be responsible for preventing the - * listeners from being garbage collected. - */ - destroy() { - this.messagingSystem.clearEventSubscriptions(`${this.name}:stateChange`); - } -} -exports.BaseController = BaseController; -/** - * Returns an anonymized representation of the controller state. - * - * By "anonymized" we mean that it should not contain any information that could be personally - * identifiable. - * - * @param state - The controller state. - * @param metadata - The controller state metadata, which describes how to derive the - * anonymized state. - * @returns The anonymized controller state. - */ -function getAnonymizedState(state, metadata) { - return deriveStateFromMetadata(state, metadata, 'anonymous'); -} -exports.getAnonymizedState = getAnonymizedState; -/** - * Returns the subset of state that should be persisted. - * - * @param state - The controller state. - * @param metadata - The controller state metadata, which describes which pieces of state should be persisted. - * @returns The subset of controller state that should be persisted. - */ -function getPersistentState(state, metadata) { - return deriveStateFromMetadata(state, metadata, 'persist'); -} -exports.getPersistentState = getPersistentState; -/** - * Use the metadata to derive state according to the given metadata property. - * - * @param state - The full controller state. - * @param metadata - The controller metadata. - * @param metadataProperty - The metadata property to use to derive state. - * @returns The metadata-derived controller state. - */ -function deriveStateFromMetadata(state, metadata, metadataProperty) { - return Object.keys(state).reduce((persistedState, key) => { - const propertyMetadata = metadata[key][metadataProperty]; - const stateProperty = state[key]; - if (typeof propertyMetadata === 'function') { - persistedState[key] = propertyMetadata(stateProperty); - } - else if (propertyMetadata) { - persistedState[key] = stateProperty; - } - return persistedState; - }, {}); -} -//# sourceMappingURL=BaseControllerV2.js.map \ No newline at end of file diff --git a/dist/BaseControllerV2.js.map b/dist/BaseControllerV2.js.map deleted file mode 100644 index d05f055da4..0000000000 --- a/dist/BaseControllerV2.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"BaseControllerV2.js","sourceRoot":"","sources":["../src/BaseControllerV2.ts"],"names":[],"mappings":";;;AAAA,iCAA0D;AAW1D,IAAA,qBAAa,GAAE,CAAC;AA4DhB;;GAEG;AACH,MAAa,cAAc;IAyBzB;;;;;;;;;OASG;IACH,YAAY,EACV,SAAS,EACT,QAAQ,EACR,IAAI,EACJ,KAAK,GAMN;QACC,IAAI,CAAC,eAAe,GAAG,SAAS,CAAC;QACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;QAC3B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAEzB,IAAI,CAAC,eAAe,CAAC,qBAAqB,CACxC,GAAG,IAAI,WAAW,EAClB,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CACjB,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED,IAAI,KAAK,CAAC,CAAC;QACT,MAAM,IAAI,KAAK,CACb,2EAA2E,CAC5E,CAAC;IACJ,CAAC;IAED;;;;;;;;OAQG;IACO,MAAM,CAAC,QAAuC;QACtD,8DAA8D;QAC9D,2BAA2B;QAC3B,2EAA2E;QAC3E,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,GACxB,0BAID,CAAC,IAAI,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;QAEhC,IAAI,CAAC,aAAa,GAAG,SAAS,CAAC;QAC/B,IAAI,CAAC,eAAe,CAAC,OAAO,CAC1B,GAAG,IAAI,CAAC,IAAI,cAAoC,EAChD,SAAS,EACT,OAAO,CACR,CAAC;IACJ,CAAC;IAED;;;;;;;;OAQG;IACO,OAAO;QACf,IAAI,CAAC,eAAe,CAAC,uBAAuB,CAC1C,GAAG,IAAI,CAAC,IAAI,cAAoC,CACjD,CAAC;IACJ,CAAC;CACF;AAlHD,wCAkHC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,kBAAkB,CAChC,KAAQ,EACR,QAA0B;IAE1B,OAAO,uBAAuB,CAAC,KAAK,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;AAC/D,CAAC;AALD,gDAKC;AAED;;;;;;GAMG;AACH,SAAgB,kBAAkB,CAChC,KAAQ,EACR,QAA0B;IAE1B,OAAO,uBAAuB,CAAC,KAAK,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;AAC7D,CAAC;AALD,gDAKC;AAED;;;;;;;GAOG;AACH,SAAS,uBAAuB,CAC9B,KAAQ,EACR,QAA0B,EAC1B,gBAAyC;IAEzC,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,cAAc,EAAE,GAAG,EAAE,EAAE;QACvD,MAAM,gBAAgB,GAAG,QAAQ,CAAC,GAAc,CAAC,CAAC,gBAAgB,CAAC,CAAC;QACpE,MAAM,aAAa,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,OAAO,gBAAgB,KAAK,UAAU,EAAE;YAC1C,cAAc,CAAC,GAAa,CAAC,GAAG,gBAAgB,CAC9C,aAA2B,CAC5B,CAAC;SACH;aAAM,IAAI,gBAAgB,EAAE;YAC3B,cAAc,CAAC,GAAa,CAAC,GAAG,aAAa,CAAC;SAC/C;QACD,OAAO,cAAc,CAAC;IACxB,CAAC,EAAE,EAA0B,CAAC,CAAC;AACjC,CAAC","sourcesContent":["import { enablePatches, produceWithPatches } from 'immer';\n\n// Imported separately because only the type is used\n// eslint-disable-next-line no-duplicate-imports\nimport type { Draft, Patch } from 'immer';\n\nimport type {\n RestrictedControllerMessenger,\n Namespaced,\n} from './ControllerMessenger';\n\nenablePatches();\n\n/**\n * A state change listener.\n *\n * This function will get called for each state change, and is given a copy of\n * the new state along with a set of patches describing the changes since the\n * last update.\n *\n * @param state - The new controller state.\n * @param patches - A list of patches describing any changes (see here for more\n * information: https://immerjs.github.io/immer/docs/patches)\n */\nexport type Listener = (state: T, patches: Patch[]) => void;\n\n/**\n * An function to derive state.\n *\n * This function will accept one piece of the controller state (one property),\n * and will return some derivation of that state.\n *\n * @param value - A piece of controller state.\n * @returns Something derived from controller state.\n */\nexport type StateDeriver = (value: T) => Json;\n\n/**\n * State metadata.\n *\n * This metadata describes which parts of state should be persisted, and how to\n * get an anonymized representation of the state.\n */\nexport type StateMetadata> = {\n [P in keyof T]: StatePropertyMetadata;\n};\n\n/**\n * Metadata for a single state property\n *\n * @property persist - Indicates whether this property should be persisted\n * (`true` for persistent, `false` for transient), or is set to a function\n * that derives the persistent state from the state.\n * @property anonymous - Indicates whether this property is already anonymous,\n * (`true` for anonymous, `false` if it has potential to be personally\n * identifiable), or is set to a function that returns an anonymized\n * representation of this state.\n */\nexport interface StatePropertyMetadata {\n persist: boolean | StateDeriver;\n anonymous: boolean | StateDeriver;\n}\n\nexport type Json =\n | null\n | boolean\n | number\n | string\n | Json[]\n | { [prop: string]: Json };\n\n/**\n * Controller class that provides state management, subscriptions, and state metadata\n */\nexport class BaseController<\n N extends string,\n S extends Record,\n messenger extends RestrictedControllerMessenger,\n> {\n private internalState: S;\n\n protected messagingSystem: messenger;\n\n /**\n * The name of the controller.\n *\n * This is used by the ComposableController to construct a composed application state.\n */\n public readonly name: N;\n\n public readonly metadata: StateMetadata;\n\n /**\n * The existence of the `subscribe` property is how the ComposableController detects whether a\n * controller extends the old BaseController or the new one. We set it to `undefined` here to\n * ensure the ComposableController never mistakes them for an older style controller.\n */\n public readonly subscribe: undefined;\n\n /**\n * Creates a BaseController instance.\n *\n * @param options - Controller options.\n * @param options.messenger - Controller messaging system.\n * @param options.metadata - State metadata, describing how to \"anonymize\" the state, and which\n * parts should be persisted.\n * @param options.name - The name of the controller, used as a namespace for events and actions.\n * @param options.state - Initial controller state.\n */\n constructor({\n messenger,\n metadata,\n name,\n state,\n }: {\n messenger: messenger;\n metadata: StateMetadata;\n name: N;\n state: S;\n }) {\n this.messagingSystem = messenger;\n this.name = name;\n this.internalState = state;\n this.metadata = metadata;\n\n this.messagingSystem.registerActionHandler(\n `${name}:getState`,\n () => this.state,\n );\n }\n\n /**\n * Retrieves current controller state.\n *\n * @returns The current state.\n */\n get state() {\n return this.internalState;\n }\n\n set state(_) {\n throw new Error(\n `Controller state cannot be directly mutated; use 'update' method instead.`,\n );\n }\n\n /**\n * Updates controller state. Accepts a callback that is passed a draft copy\n * of the controller state. If a value is returned, it is set as the new\n * state. Otherwise, any changes made within that callback to the draft are\n * applied to the controller state.\n *\n * @param callback - Callback for updating state, passed a draft state\n * object. Return a new state object or mutate the draft to update state.\n */\n protected update(callback: (state: Draft) => void | S) {\n // We run into ts2589, \"infinite type depth\", if we don't cast\n // produceWithPatches here.\n // The final, omitted member of the returned tuple are the inverse patches.\n const [nextState, patches] = (\n produceWithPatches as unknown as (\n state: S,\n cb: typeof callback,\n ) => [S, Patch[], Patch[]]\n )(this.internalState, callback);\n\n this.internalState = nextState;\n this.messagingSystem.publish(\n `${this.name}:stateChange` as Namespaced,\n nextState,\n patches,\n );\n }\n\n /**\n * Prepares the controller for garbage collection. This should be extended\n * by any subclasses to clean up any additional connections or events.\n *\n * The only cleanup performed here is to remove listeners. While technically\n * this is not required to ensure this instance is garbage collected, it at\n * least ensures this instance won't be responsible for preventing the\n * listeners from being garbage collected.\n */\n protected destroy() {\n this.messagingSystem.clearEventSubscriptions(\n `${this.name}:stateChange` as Namespaced,\n );\n }\n}\n\n/**\n * Returns an anonymized representation of the controller state.\n *\n * By \"anonymized\" we mean that it should not contain any information that could be personally\n * identifiable.\n *\n * @param state - The controller state.\n * @param metadata - The controller state metadata, which describes how to derive the\n * anonymized state.\n * @returns The anonymized controller state.\n */\nexport function getAnonymizedState>(\n state: S,\n metadata: StateMetadata,\n): Record {\n return deriveStateFromMetadata(state, metadata, 'anonymous');\n}\n\n/**\n * Returns the subset of state that should be persisted.\n *\n * @param state - The controller state.\n * @param metadata - The controller state metadata, which describes which pieces of state should be persisted.\n * @returns The subset of controller state that should be persisted.\n */\nexport function getPersistentState>(\n state: S,\n metadata: StateMetadata,\n): Record {\n return deriveStateFromMetadata(state, metadata, 'persist');\n}\n\n/**\n * Use the metadata to derive state according to the given metadata property.\n *\n * @param state - The full controller state.\n * @param metadata - The controller metadata.\n * @param metadataProperty - The metadata property to use to derive state.\n * @returns The metadata-derived controller state.\n */\nfunction deriveStateFromMetadata>(\n state: S,\n metadata: StateMetadata,\n metadataProperty: 'anonymous' | 'persist',\n): Record {\n return Object.keys(state).reduce((persistedState, key) => {\n const propertyMetadata = metadata[key as keyof S][metadataProperty];\n const stateProperty = state[key];\n if (typeof propertyMetadata === 'function') {\n persistedState[key as string] = propertyMetadata(\n stateProperty as S[keyof S],\n );\n } else if (propertyMetadata) {\n persistedState[key as string] = stateProperty;\n }\n return persistedState;\n }, {} as Record);\n}\n"]} \ No newline at end of file diff --git a/dist/ComposableController.d.ts b/dist/ComposableController.d.ts deleted file mode 100644 index 797da3307e..0000000000 --- a/dist/ComposableController.d.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { BaseController } from './BaseController'; -import { RestrictedControllerMessenger } from './ControllerMessenger'; -/** - * List of child controller instances - * - * This type encompasses controllers based up either BaseController or - * BaseControllerV2. The BaseControllerV2 type can't be included directly - * because the generic parameters it expects require knowing the exact state - * shape, so instead we look for an object with the BaseControllerV2 properties - * that we use in the ComposableController (name and state). - */ -export declare type ControllerList = (BaseController | { - name: string; - state: Record; -})[]; -/** - * Controller that can be used to compose multiple controllers together - */ -export declare class ComposableController extends BaseController { - private controllers; - private messagingSystem?; - /** - * Name of this controller used during composition - */ - name: string; - /** - * Creates a ComposableController instance. - * - * @param controllers - Map of names to controller instances. - * @param messenger - The controller messaging system, used for communicating with BaseControllerV2 controllers. - */ - constructor(controllers: ControllerList, messenger?: RestrictedControllerMessenger<'ComposableController', never, any, never, any>); - /** - * Flat state representation, one that isn't keyed - * of controller name. Instead, all child controller state is merged - * together into a single, flat object. - * - * @returns Merged state representation of all child controllers. - */ - get flatState(): {}; -} -export default ComposableController; diff --git a/dist/ComposableController.js b/dist/ComposableController.js deleted file mode 100644 index edd63c5b32..0000000000 --- a/dist/ComposableController.js +++ /dev/null @@ -1,62 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.ComposableController = void 0; -const BaseController_1 = require("./BaseController"); -/** - * Controller that can be used to compose multiple controllers together - */ -class ComposableController extends BaseController_1.BaseController { - /** - * Creates a ComposableController instance. - * - * @param controllers - Map of names to controller instances. - * @param messenger - The controller messaging system, used for communicating with BaseControllerV2 controllers. - */ - constructor(controllers, messenger) { - super(undefined, controllers.reduce((state, controller) => { - state[controller.name] = controller.state; - return state; - }, {})); - this.controllers = []; - /** - * Name of this controller used during composition - */ - this.name = 'ComposableController'; - this.initialize(); - this.controllers = controllers; - this.messagingSystem = messenger; - this.controllers.forEach((controller) => { - const { name } = controller; - if (controller.subscribe !== undefined) { - controller.subscribe((state) => { - this.update({ [name]: state }); - }); - } - else if (this.messagingSystem) { - this.messagingSystem.subscribe(`${name}:stateChange`, (state) => { - this.update({ [name]: state }); - }); - } - else { - throw new Error(`Messaging system required if any BaseControllerV2 controllers are used`); - } - }); - } - /** - * Flat state representation, one that isn't keyed - * of controller name. Instead, all child controller state is merged - * together into a single, flat object. - * - * @returns Merged state representation of all child controllers. - */ - get flatState() { - let flatState = {}; - for (const controller of this.controllers) { - flatState = Object.assign(Object.assign({}, flatState), controller.state); - } - return flatState; - } -} -exports.ComposableController = ComposableController; -exports.default = ComposableController; -//# sourceMappingURL=ComposableController.js.map \ No newline at end of file diff --git a/dist/ComposableController.js.map b/dist/ComposableController.js.map deleted file mode 100644 index f3877358be..0000000000 --- a/dist/ComposableController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"ComposableController.js","sourceRoot":"","sources":["../src/ComposableController.ts"],"names":[],"mappings":";;;AAAA,qDAAkD;AAiBlD;;GAEG;AACH,MAAa,oBAAqB,SAAQ,+BAA0B;IAgBlE;;;;;OAKG;IACH,YACE,WAA2B,EAC3B,SAMC;QAED,KAAK,CACH,SAAS,EACT,WAAW,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE;YACvC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC;YAC1C,OAAO,KAAK,CAAC;QACf,CAAC,EAAE,EAAS,CAAC,CACd,CAAC;QArCI,gBAAW,GAAmB,EAAE,CAAC;QAUzC;;WAEG;QACM,SAAI,GAAG,sBAAsB,CAAC;QAyBrC,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,eAAe,GAAG,SAAS,CAAC;QACjC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,UAAU,EAAE,EAAE;YACtC,MAAM,EAAE,IAAI,EAAE,GAAG,UAAU,CAAC;YAC5B,IAAK,UAAuC,CAAC,SAAS,KAAK,SAAS,EAAE;gBACnE,UAAuC,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE;oBAC3D,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;gBACjC,CAAC,CAAC,CAAC;aACJ;iBAAM,IAAI,IAAI,CAAC,eAAe,EAAE;gBAC9B,IAAI,CAAC,eAAe,CAAC,SAAiB,CACrC,GAAG,IAAI,cAAc,EACrB,CAAC,KAAU,EAAE,EAAE;oBACb,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;gBACjC,CAAC,CACF,CAAC;aACH;iBAAM;gBACL,MAAM,IAAI,KAAK,CACb,wEAAwE,CACzE,CAAC;aACH;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;OAMG;IACH,IAAI,SAAS;QACX,IAAI,SAAS,GAAG,EAAE,CAAC;QACnB,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,WAAW,EAAE;YACzC,SAAS,mCAAQ,SAAS,GAAK,UAAU,CAAC,KAAK,CAAE,CAAC;SACnD;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;CACF;AA7ED,oDA6EC;AAED,kBAAe,oBAAoB,CAAC","sourcesContent":["import { BaseController } from './BaseController';\nimport { RestrictedControllerMessenger } from './ControllerMessenger';\n\n/**\n * List of child controller instances\n *\n * This type encompasses controllers based up either BaseController or\n * BaseControllerV2. The BaseControllerV2 type can't be included directly\n * because the generic parameters it expects require knowing the exact state\n * shape, so instead we look for an object with the BaseControllerV2 properties\n * that we use in the ComposableController (name and state).\n */\nexport type ControllerList = (\n | BaseController\n | { name: string; state: Record }\n)[];\n\n/**\n * Controller that can be used to compose multiple controllers together\n */\nexport class ComposableController extends BaseController {\n private controllers: ControllerList = [];\n\n private messagingSystem?: RestrictedControllerMessenger<\n 'ComposableController',\n never,\n any,\n never,\n any\n >;\n\n /**\n * Name of this controller used during composition\n */\n override name = 'ComposableController';\n\n /**\n * Creates a ComposableController instance.\n *\n * @param controllers - Map of names to controller instances.\n * @param messenger - The controller messaging system, used for communicating with BaseControllerV2 controllers.\n */\n constructor(\n controllers: ControllerList,\n messenger?: RestrictedControllerMessenger<\n 'ComposableController',\n never,\n any,\n never,\n any\n >,\n ) {\n super(\n undefined,\n controllers.reduce((state, controller) => {\n state[controller.name] = controller.state;\n return state;\n }, {} as any),\n );\n this.initialize();\n this.controllers = controllers;\n this.messagingSystem = messenger;\n this.controllers.forEach((controller) => {\n const { name } = controller;\n if ((controller as BaseController).subscribe !== undefined) {\n (controller as BaseController).subscribe((state) => {\n this.update({ [name]: state });\n });\n } else if (this.messagingSystem) {\n (this.messagingSystem.subscribe as any)(\n `${name}:stateChange`,\n (state: any) => {\n this.update({ [name]: state });\n },\n );\n } else {\n throw new Error(\n `Messaging system required if any BaseControllerV2 controllers are used`,\n );\n }\n });\n }\n\n /**\n * Flat state representation, one that isn't keyed\n * of controller name. Instead, all child controller state is merged\n * together into a single, flat object.\n *\n * @returns Merged state representation of all child controllers.\n */\n get flatState() {\n let flatState = {};\n for (const controller of this.controllers) {\n flatState = { ...flatState, ...controller.state };\n }\n return flatState;\n }\n}\n\nexport default ComposableController;\n"]} \ No newline at end of file diff --git a/dist/ControllerMessenger.d.ts b/dist/ControllerMessenger.d.ts deleted file mode 100644 index 32a121cc6c..0000000000 --- a/dist/ControllerMessenger.d.ts +++ /dev/null @@ -1,352 +0,0 @@ -declare type ActionHandler = (...args: ExtractActionParameters) => ExtractActionResponse; -declare type ExtractActionParameters = Action extends { - type: T; - handler: (...args: infer H) => any; -} ? H : never; -declare type ExtractActionResponse = Action extends { - type: T; - handler: (...args: any) => infer H; -} ? H : never; -declare type ExtractEventHandler = Event extends { - type: T; - payload: infer P; -} ? P extends unknown[] ? (...payload: P) => void : never : never; -declare type ExtractEventPayload = Event extends { - type: T; - payload: infer P; -} ? P : never; -declare type SelectorFunction = (...args: Args) => ReturnValue; -declare type SelectorEventHandler = (newValue: SelectorReturnValue, previousValue: SelectorReturnValue | undefined) => void; -export declare type ActionConstraint = { - type: string; - handler: (...args: any) => unknown; -}; -export declare type EventConstraint = { - type: string; - payload: unknown[]; -}; -/** - * A namespaced string - * - * This type verifies that the string T is prefixed by the string Name followed by a colon. - * - * @template Name - The namespace we're checking for. - * @template T - The full string, including the namespace. - */ -export declare type Namespaced = T extends `${Name}:${string}` ? T : never; -declare type NarrowToNamespace = T extends { - type: `${Namespace}:${string}`; -} ? T : never; -declare type NarrowToAllowed = T extends { - type: Allowed; -} ? T : never; -/** - * A restricted controller messenger. - * - * This acts as a wrapper around the controller messenger instance that restricts access to actions - * and events. - * - * @template N - The namespace for this messenger. Typically this is the name of the controller or - * module that this messenger has been created for. The authority to publish events and register - * actions under this namespace is granted to this restricted messenger instance. - * @template Action - A type union of all Action types. - * @template Event - A type union of all Event types. - * @template AllowedAction - A type union of the 'type' string for any allowed actions. - * @template AllowedEvent - A type union of the 'type' string for any allowed events. - */ -export declare class RestrictedControllerMessenger { - private controllerMessenger; - private controllerName; - private allowedActions; - private allowedEvents; - /** - * Constructs a restricted controller messenger - * - * The provided allowlists grant the ability to call the listed actions and subscribe to the - * listed events. The "name" provided grants ownership of any actions and events under that - * namespace. Ownership allows registering actions and publishing events, as well as - * unregistering actions and clearing event subscriptions. - * - * @param options - The controller options. - * @param options.controllerMessenger - The controller messenger instance that is being wrapped. - * @param options.name - The name of the thing this messenger will be handed to (e.g. the - * controller name). This grants "ownership" of actions and events under this namespace to the - * restricted controller messenger returned. - * @param options.allowedActions - The list of actions that this restricted controller messenger - * should be alowed to call. - * @param options.allowedEvents - The list of events that this restricted controller messenger - * should be allowed to subscribe to. - */ - constructor({ controllerMessenger, name, allowedActions, allowedEvents, }: { - controllerMessenger: ControllerMessenger; - name: N; - allowedActions?: AllowedAction[]; - allowedEvents?: AllowedEvent[]; - }); - /** - * Register an action handler. - * - * This will make the registered function available to call via the `call` method. - * - * The action type this handler is registered under *must* be in the current namespace. - * - * @param action - The action type. This is a unqiue identifier for this action. - * @param handler - The action handler. This function gets called when the `call` method is - * invoked with the given action type. - * @throws Will throw when a handler has been registered for this action type already. - * @template T - A type union of Action type strings that are namespaced by N. - */ - registerActionHandler>(action: T, handler: ActionHandler): void; - /** - * Unregister an action handler. - * - * This will prevent this action from being called. - * - * The action type being unregistered *must* be in the current namespace. - * - * @param action - The action type. This is a unqiue identifier for this action. - * @template T - A type union of Action type strings that are namespaced by N. - */ - unregisterActionHandler>(action: T): void; - /** - * Call an action. - * - * This function will call the action handler corresponding to the given action type, passing - * along any parameters given. - * - * The action type being called must be on the action allowlist. - * - * @param action - The action type. This is a unqiue identifier for this action. - * @param params - The action parameters. These must match the type of the parameters of the - * registered action handler. - * @throws Will throw when no handler has been registered for the given type. - * @template T - A type union of allowed Action type strings. - * @returns The action return value. - */ - call(action: T, ...params: ExtractActionParameters): ExtractActionResponse; - /** - * Publish an event. - * - * Publishes the given payload to all subscribers of the given event type. - * - * The event type being published *must* be in the current namespace. - * - * @param event - The event type. This is a unique identifier for this event. - * @param payload - The event payload. The type of the parameters for each event handler must - * match the type of this payload. - * @template E - A type union of Event type strings that are namespaced by N. - */ - publish>(event: E, ...payload: ExtractEventPayload): void; - /** - * Subscribe to an event. - * - * Registers the given function as an event handler for the given event type. - * - * The event type being subscribed to must be on the event allowlist. - * - * @param eventType - The event type. This is a unique identifier for this event. - * @param handler - The event handler. The type of the parameters for this event handler must - * match the type of the payload for this event type. - * @template E - A type union of Event type strings. - */ - subscribe(eventType: E, handler: ExtractEventHandler): void; - /** - * Subscribe to an event, with a selector. - * - * Registers the given handler function as an event handler for the given - * event type. When an event is published, its payload is first passed to the - * selector. The event handler is only called if the selector's return value - * differs from its last known return value. - * - * The event type being subscribed to must be on the event allowlist. - * - * @param eventType - The event type. This is a unique identifier for this event. - * @param handler - The event handler. The type of the parameters for this event - * handler must match the return type of the selector. - * @param selector - The selector function used to select relevant data from - * the event payload. The type of the parameters for this selector must match - * the type of the payload for this event type. - * @template E - A type union of Event type strings. - * @template V - The selector return value. - */ - subscribe(eventType: E, handler: SelectorEventHandler, selector: SelectorFunction, V>): void; - /** - * Unsubscribe from an event. - * - * Unregisters the given function as an event handler for the given event. - * - * The event type being unsubscribed to must be on the event allowlist. - * - * @param event - The event type. This is a unique identifier for this event. - * @param handler - The event handler to unregister. - * @throws Will throw when the given event handler is not registered for this event. - * @template T - A type union of allowed Event type strings. - */ - unsubscribe(event: E, handler: ExtractEventHandler): void; - /** - * Clear subscriptions for a specific event. - * - * This will remove all subscribed handlers for this event. - * - * The event type being cleared *must* be in the current namespace. - * - * @param event - The event type. This is a unique identifier for this event. - * @template E - A type union of Event type strings that are namespaced by N. - */ - clearEventSubscriptions>(event: E): void; -} -/** - * A messaging system for controllers. - * - * The controller messenger allows registering functions as 'actions' that can be called elsewhere, - * and it allows publishing and subscribing to events. Both actions and events are identified by - * unique strings. - * - * @template Action - A type union of all Action types. - * @template Event - A type union of all Event types. - */ -export declare class ControllerMessenger { - private actions; - private events; - /** - * A cache of selector return values for their respective handlers. - */ - private eventPayloadCache; - /** - * Register an action handler. - * - * This will make the registered function available to call via the `call` method. - * - * @param actionType - The action type. This is a unqiue identifier for this action. - * @param handler - The action handler. This function gets called when the `call` method is - * invoked with the given action type. - * @throws Will throw when a handler has been registered for this action type already. - * @template T - A type union of Action type strings. - */ - registerActionHandler(actionType: T, handler: ActionHandler): void; - /** - * Unregister an action handler. - * - * This will prevent this action from being called. - * - * @param actionType - The action type. This is a unqiue identifier for this action. - * @template T - A type union of Action type strings. - */ - unregisterActionHandler(actionType: T): void; - /** - * Unregister all action handlers. - * - * This prevents all actions from being called. - */ - clearActions(): void; - /** - * Call an action. - * - * This function will call the action handler corresponding to the given action type, passing - * along any parameters given. - * - * @param actionType - The action type. This is a unqiue identifier for this action. - * @param params - The action parameters. These must match the type of the parameters of the - * registered action handler. - * @throws Will throw when no handler has been registered for the given type. - * @template T - A type union of Action type strings. - * @returns The action return value. - */ - call(actionType: T, ...params: ExtractActionParameters): ExtractActionResponse; - /** - * Publish an event. - * - * Publishes the given payload to all subscribers of the given event type. - * - * @param eventType - The event type. This is a unique identifier for this event. - * @param payload - The event payload. The type of the parameters for each event handler must - * match the type of this payload. - * @template E - A type union of Event type strings. - */ - publish(eventType: E, ...payload: ExtractEventPayload): void; - /** - * Subscribe to an event. - * - * Registers the given function as an event handler for the given event type. - * - * @param eventType - The event type. This is a unique identifier for this event. - * @param handler - The event handler. The type of the parameters for this event handler must - * match the type of the payload for this event type. - * @template E - A type union of Event type strings. - */ - subscribe(eventType: E, handler: ExtractEventHandler): void; - /** - * Subscribe to an event, with a selector. - * - * Registers the given handler function as an event handler for the given - * event type. When an event is published, its payload is first passed to the - * selector. The event handler is only called if the selector's return value - * differs from its last known return value. - * - * @param eventType - The event type. This is a unique identifier for this event. - * @param handler - The event handler. The type of the parameters for this event - * handler must match the return type of the selector. - * @param selector - The selector function used to select relevant data from - * the event payload. The type of the parameters for this selector must match - * the type of the payload for this event type. - * @template E - A type union of Event type strings. - * @template V - The selector return value. - */ - subscribe(eventType: E, handler: SelectorEventHandler, selector: SelectorFunction, V>): void; - /** - * Unsubscribe from an event. - * - * Unregisters the given function as an event handler for the given event. - * - * @param eventType - The event type. This is a unique identifier for this event. - * @param handler - The event handler to unregister. - * @throws Will throw when the given event handler is not registered for this event. - * @template E - A type union of Event type strings. - */ - unsubscribe(eventType: E, handler: ExtractEventHandler): void; - /** - * Clear subscriptions for a specific event. - * - * This will remove all subscribed handlers for this event. - * - * @param eventType - The event type. This is a unique identifier for this event. - * @template E - A type union of Event type strings. - */ - clearEventSubscriptions(eventType: E): void; - /** - * Clear all subscriptions. - * - * This will remove all subscribed handlers for all events. - */ - clearSubscriptions(): void; - /** - * Get a restricted controller messenger - * - * Returns a wrapper around the controller messenger instance that restricts access to actions - * and events. The provided allowlists grant the ability to call the listed actions and subscribe - * to the listed events. The "name" provided grants ownership of any actions and events under - * that namespace. Ownership allows registering actions and publishing events, as well as - * unregistering actions and clearing event subscriptions. - * - * @param options - Controller messenger options. - * @param options.name - The name of the thing this messenger will be handed to (e.g. the - * controller name). This grants "ownership" of actions and events under this namespace to the - * restricted controller messenger returned. - * @param options.allowedActions - The list of actions that this restricted controller messenger - * should be alowed to call. - * @param options.allowedEvents - The list of events that this restricted controller messenger - * should be allowed to subscribe to. - * @template N - The namespace for this messenger. Typically this is the name of the controller or - * module that this messenger has been created for. The authority to publish events and register - * actions under this namespace is granted to this restricted messenger instance. - * @template AllowedAction - A type union of the 'type' string for any allowed actions. - * @template AllowedEvent - A type union of the 'type' string for any allowed events. - * @returns The restricted controller messenger. - */ - getRestricted({ name, allowedActions, allowedEvents, }: { - name: N; - allowedActions?: Extract[]; - allowedEvents?: Extract[]; - }): RestrictedControllerMessenger | NarrowToAllowed, NarrowToNamespace | NarrowToAllowed, AllowedAction, AllowedEvent>; -} -export {}; diff --git a/dist/ControllerMessenger.js b/dist/ControllerMessenger.js deleted file mode 100644 index a94aad46d4..0000000000 --- a/dist/ControllerMessenger.js +++ /dev/null @@ -1,363 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.ControllerMessenger = exports.RestrictedControllerMessenger = void 0; -/** - * A restricted controller messenger. - * - * This acts as a wrapper around the controller messenger instance that restricts access to actions - * and events. - * - * @template N - The namespace for this messenger. Typically this is the name of the controller or - * module that this messenger has been created for. The authority to publish events and register - * actions under this namespace is granted to this restricted messenger instance. - * @template Action - A type union of all Action types. - * @template Event - A type union of all Event types. - * @template AllowedAction - A type union of the 'type' string for any allowed actions. - * @template AllowedEvent - A type union of the 'type' string for any allowed events. - */ -class RestrictedControllerMessenger { - /** - * Constructs a restricted controller messenger - * - * The provided allowlists grant the ability to call the listed actions and subscribe to the - * listed events. The "name" provided grants ownership of any actions and events under that - * namespace. Ownership allows registering actions and publishing events, as well as - * unregistering actions and clearing event subscriptions. - * - * @param options - The controller options. - * @param options.controllerMessenger - The controller messenger instance that is being wrapped. - * @param options.name - The name of the thing this messenger will be handed to (e.g. the - * controller name). This grants "ownership" of actions and events under this namespace to the - * restricted controller messenger returned. - * @param options.allowedActions - The list of actions that this restricted controller messenger - * should be alowed to call. - * @param options.allowedEvents - The list of events that this restricted controller messenger - * should be allowed to subscribe to. - */ - constructor({ controllerMessenger, name, allowedActions, allowedEvents, }) { - this.controllerMessenger = controllerMessenger; - this.controllerName = name; - this.allowedActions = allowedActions || null; - this.allowedEvents = allowedEvents || null; - } - /** - * Register an action handler. - * - * This will make the registered function available to call via the `call` method. - * - * The action type this handler is registered under *must* be in the current namespace. - * - * @param action - The action type. This is a unqiue identifier for this action. - * @param handler - The action handler. This function gets called when the `call` method is - * invoked with the given action type. - * @throws Will throw when a handler has been registered for this action type already. - * @template T - A type union of Action type strings that are namespaced by N. - */ - registerActionHandler(action, handler) { - /* istanbul ignore if */ // Branch unreachable with valid types - if (!action.startsWith(`${this.controllerName}:`)) { - throw new Error(`Only allowed registering action handlers prefixed by '${this.controllerName}:'`); - } - this.controllerMessenger.registerActionHandler(action, handler); - } - /** - * Unregister an action handler. - * - * This will prevent this action from being called. - * - * The action type being unregistered *must* be in the current namespace. - * - * @param action - The action type. This is a unqiue identifier for this action. - * @template T - A type union of Action type strings that are namespaced by N. - */ - unregisterActionHandler(action) { - /* istanbul ignore if */ // Branch unreachable with valid types - if (!action.startsWith(`${this.controllerName}:`)) { - throw new Error(`Only allowed unregistering action handlers prefixed by '${this.controllerName}:'`); - } - this.controllerMessenger.unregisterActionHandler(action); - } - /** - * Call an action. - * - * This function will call the action handler corresponding to the given action type, passing - * along any parameters given. - * - * The action type being called must be on the action allowlist. - * - * @param action - The action type. This is a unqiue identifier for this action. - * @param params - The action parameters. These must match the type of the parameters of the - * registered action handler. - * @throws Will throw when no handler has been registered for the given type. - * @template T - A type union of allowed Action type strings. - * @returns The action return value. - */ - call(action, ...params) { - /* istanbul ignore next */ // Branches unreachable with valid types - if (this.allowedActions === null) { - throw new Error('No actions allowed'); - } - else if (!this.allowedActions.includes(action)) { - throw new Error(`Action missing from allow list: ${action}`); - } - return this.controllerMessenger.call(action, ...params); - } - /** - * Publish an event. - * - * Publishes the given payload to all subscribers of the given event type. - * - * The event type being published *must* be in the current namespace. - * - * @param event - The event type. This is a unique identifier for this event. - * @param payload - The event payload. The type of the parameters for each event handler must - * match the type of this payload. - * @template E - A type union of Event type strings that are namespaced by N. - */ - publish(event, ...payload) { - /* istanbul ignore if */ // Branch unreachable with valid types - if (!event.startsWith(`${this.controllerName}:`)) { - throw new Error(`Only allowed publishing events prefixed by '${this.controllerName}:'`); - } - this.controllerMessenger.publish(event, ...payload); - } - subscribe(event, handler, selector) { - /* istanbul ignore next */ // Branches unreachable with valid types - if (this.allowedEvents === null) { - throw new Error('No events allowed'); - } - else if (!this.allowedEvents.includes(event)) { - throw new Error(`Event missing from allow list: ${event}`); - } - if (selector) { - return this.controllerMessenger.subscribe(event, handler, selector); - } - return this.controllerMessenger.subscribe(event, handler); - } - /** - * Unsubscribe from an event. - * - * Unregisters the given function as an event handler for the given event. - * - * The event type being unsubscribed to must be on the event allowlist. - * - * @param event - The event type. This is a unique identifier for this event. - * @param handler - The event handler to unregister. - * @throws Will throw when the given event handler is not registered for this event. - * @template T - A type union of allowed Event type strings. - */ - unsubscribe(event, handler) { - /* istanbul ignore next */ // Branches unreachable with valid types - if (this.allowedEvents === null) { - throw new Error('No events allowed'); - } - else if (!this.allowedEvents.includes(event)) { - throw new Error(`Event missing from allow list: ${event}`); - } - this.controllerMessenger.unsubscribe(event, handler); - } - /** - * Clear subscriptions for a specific event. - * - * This will remove all subscribed handlers for this event. - * - * The event type being cleared *must* be in the current namespace. - * - * @param event - The event type. This is a unique identifier for this event. - * @template E - A type union of Event type strings that are namespaced by N. - */ - clearEventSubscriptions(event) { - /* istanbul ignore if */ // Branch unreachable with valid types - if (!event.startsWith(`${this.controllerName}:`)) { - throw new Error(`Only allowed clearing events prefixed by '${this.controllerName}:'`); - } - this.controllerMessenger.clearEventSubscriptions(event); - } -} -exports.RestrictedControllerMessenger = RestrictedControllerMessenger; -/** - * A messaging system for controllers. - * - * The controller messenger allows registering functions as 'actions' that can be called elsewhere, - * and it allows publishing and subscribing to events. Both actions and events are identified by - * unique strings. - * - * @template Action - A type union of all Action types. - * @template Event - A type union of all Event types. - */ -class ControllerMessenger { - constructor() { - this.actions = new Map(); - this.events = new Map(); - /** - * A cache of selector return values for their respective handlers. - */ - this.eventPayloadCache = new Map(); - } - /** - * Register an action handler. - * - * This will make the registered function available to call via the `call` method. - * - * @param actionType - The action type. This is a unqiue identifier for this action. - * @param handler - The action handler. This function gets called when the `call` method is - * invoked with the given action type. - * @throws Will throw when a handler has been registered for this action type already. - * @template T - A type union of Action type strings. - */ - registerActionHandler(actionType, handler) { - if (this.actions.has(actionType)) { - throw new Error(`A handler for ${actionType} has already been registered`); - } - this.actions.set(actionType, handler); - } - /** - * Unregister an action handler. - * - * This will prevent this action from being called. - * - * @param actionType - The action type. This is a unqiue identifier for this action. - * @template T - A type union of Action type strings. - */ - unregisterActionHandler(actionType) { - this.actions.delete(actionType); - } - /** - * Unregister all action handlers. - * - * This prevents all actions from being called. - */ - clearActions() { - this.actions.clear(); - } - /** - * Call an action. - * - * This function will call the action handler corresponding to the given action type, passing - * along any parameters given. - * - * @param actionType - The action type. This is a unqiue identifier for this action. - * @param params - The action parameters. These must match the type of the parameters of the - * registered action handler. - * @throws Will throw when no handler has been registered for the given type. - * @template T - A type union of Action type strings. - * @returns The action return value. - */ - call(actionType, ...params) { - const handler = this.actions.get(actionType); - if (!handler) { - throw new Error(`A handler for ${actionType} has not been registered`); - } - return handler(...params); - } - /** - * Publish an event. - * - * Publishes the given payload to all subscribers of the given event type. - * - * @param eventType - The event type. This is a unique identifier for this event. - * @param payload - The event payload. The type of the parameters for each event handler must - * match the type of this payload. - * @template E - A type union of Event type strings. - */ - publish(eventType, ...payload) { - const subscribers = this.events.get(eventType); - if (subscribers) { - for (const [handler, selector] of subscribers.entries()) { - if (selector) { - const previousValue = this.eventPayloadCache.get(handler); - const newValue = selector(...payload); - if (newValue !== previousValue) { - this.eventPayloadCache.set(handler, newValue); - handler(newValue, previousValue); - } - } - else { - handler(...payload); - } - } - } - } - subscribe(eventType, handler, selector) { - let subscribers = this.events.get(eventType); - if (!subscribers) { - subscribers = new Map(); - this.events.set(eventType, subscribers); - } - subscribers.set(handler, selector); - } - /** - * Unsubscribe from an event. - * - * Unregisters the given function as an event handler for the given event. - * - * @param eventType - The event type. This is a unique identifier for this event. - * @param handler - The event handler to unregister. - * @throws Will throw when the given event handler is not registered for this event. - * @template E - A type union of Event type strings. - */ - unsubscribe(eventType, handler) { - const subscribers = this.events.get(eventType); - if (!subscribers || !subscribers.has(handler)) { - throw new Error(`Subscription not found for event: ${eventType}`); - } - const selector = subscribers.get(handler); - if (selector) { - this.eventPayloadCache.delete(handler); - } - subscribers.delete(handler); - } - /** - * Clear subscriptions for a specific event. - * - * This will remove all subscribed handlers for this event. - * - * @param eventType - The event type. This is a unique identifier for this event. - * @template E - A type union of Event type strings. - */ - clearEventSubscriptions(eventType) { - this.events.delete(eventType); - } - /** - * Clear all subscriptions. - * - * This will remove all subscribed handlers for all events. - */ - clearSubscriptions() { - this.events.clear(); - } - /** - * Get a restricted controller messenger - * - * Returns a wrapper around the controller messenger instance that restricts access to actions - * and events. The provided allowlists grant the ability to call the listed actions and subscribe - * to the listed events. The "name" provided grants ownership of any actions and events under - * that namespace. Ownership allows registering actions and publishing events, as well as - * unregistering actions and clearing event subscriptions. - * - * @param options - Controller messenger options. - * @param options.name - The name of the thing this messenger will be handed to (e.g. the - * controller name). This grants "ownership" of actions and events under this namespace to the - * restricted controller messenger returned. - * @param options.allowedActions - The list of actions that this restricted controller messenger - * should be alowed to call. - * @param options.allowedEvents - The list of events that this restricted controller messenger - * should be allowed to subscribe to. - * @template N - The namespace for this messenger. Typically this is the name of the controller or - * module that this messenger has been created for. The authority to publish events and register - * actions under this namespace is granted to this restricted messenger instance. - * @template AllowedAction - A type union of the 'type' string for any allowed actions. - * @template AllowedEvent - A type union of the 'type' string for any allowed events. - * @returns The restricted controller messenger. - */ - getRestricted({ name, allowedActions, allowedEvents, }) { - return new RestrictedControllerMessenger({ - controllerMessenger: this, - name, - allowedActions, - allowedEvents, - }); - } -} -exports.ControllerMessenger = ControllerMessenger; -//# sourceMappingURL=ControllerMessenger.js.map \ No newline at end of file diff --git a/dist/ControllerMessenger.js.map b/dist/ControllerMessenger.js.map deleted file mode 100644 index 523aa8280d..0000000000 --- a/dist/ControllerMessenger.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"ControllerMessenger.js","sourceRoot":"","sources":["../src/ControllerMessenger.ts"],"names":[],"mappings":"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","sourcesContent":["type ActionHandler = (\n ...args: ExtractActionParameters\n) => ExtractActionResponse;\ntype ExtractActionParameters = Action extends {\n type: T;\n handler: (...args: infer H) => any;\n}\n ? H\n : never;\ntype ExtractActionResponse = Action extends {\n type: T;\n handler: (...args: any) => infer H;\n}\n ? H\n : never;\n\ntype ExtractEventHandler = Event extends { type: T; payload: infer P }\n ? P extends unknown[]\n ? (...payload: P) => void\n : never\n : never;\ntype ExtractEventPayload = Event extends { type: T; payload: infer P }\n ? P\n : never;\n\ntype GenericEventHandler = (...args: unknown[]) => void;\n\ntype SelectorFunction = (\n ...args: Args\n) => ReturnValue;\ntype SelectorEventHandler = (\n newValue: SelectorReturnValue,\n previousValue: SelectorReturnValue | undefined,\n) => void;\n\nexport type ActionConstraint = {\n type: string;\n handler: (...args: any) => unknown;\n};\nexport type EventConstraint = { type: string; payload: unknown[] };\n\ntype EventSubscriptionMap = Map<\n GenericEventHandler | SelectorEventHandler,\n SelectorFunction | undefined\n>;\n\n/**\n * A namespaced string\n *\n * This type verifies that the string T is prefixed by the string Name followed by a colon.\n *\n * @template Name - The namespace we're checking for.\n * @template T - The full string, including the namespace.\n */\nexport type Namespaced = T extends `${Name}:${string}`\n ? T\n : never;\n\ntype NarrowToNamespace = T extends {\n type: `${Namespace}:${string}`;\n}\n ? T\n : never;\n\ntype NarrowToAllowed = T extends {\n type: Allowed;\n}\n ? T\n : never;\n\n/**\n * A restricted controller messenger.\n *\n * This acts as a wrapper around the controller messenger instance that restricts access to actions\n * and events.\n *\n * @template N - The namespace for this messenger. Typically this is the name of the controller or\n * module that this messenger has been created for. The authority to publish events and register\n * actions under this namespace is granted to this restricted messenger instance.\n * @template Action - A type union of all Action types.\n * @template Event - A type union of all Event types.\n * @template AllowedAction - A type union of the 'type' string for any allowed actions.\n * @template AllowedEvent - A type union of the 'type' string for any allowed events.\n */\nexport class RestrictedControllerMessenger<\n N extends string,\n Action extends ActionConstraint,\n Event extends EventConstraint,\n AllowedAction extends string,\n AllowedEvent extends string,\n> {\n private controllerMessenger: ControllerMessenger<\n ActionConstraint,\n EventConstraint\n >;\n\n private controllerName: N;\n\n private allowedActions: AllowedAction[] | null;\n\n private allowedEvents: AllowedEvent[] | null;\n\n /**\n * Constructs a restricted controller messenger\n *\n * The provided allowlists grant the ability to call the listed actions and subscribe to the\n * listed events. The \"name\" provided grants ownership of any actions and events under that\n * namespace. Ownership allows registering actions and publishing events, as well as\n * unregistering actions and clearing event subscriptions.\n *\n * @param options - The controller options.\n * @param options.controllerMessenger - The controller messenger instance that is being wrapped.\n * @param options.name - The name of the thing this messenger will be handed to (e.g. the\n * controller name). This grants \"ownership\" of actions and events under this namespace to the\n * restricted controller messenger returned.\n * @param options.allowedActions - The list of actions that this restricted controller messenger\n * should be alowed to call.\n * @param options.allowedEvents - The list of events that this restricted controller messenger\n * should be allowed to subscribe to.\n */\n constructor({\n controllerMessenger,\n name,\n allowedActions,\n allowedEvents,\n }: {\n controllerMessenger: ControllerMessenger;\n name: N;\n allowedActions?: AllowedAction[];\n allowedEvents?: AllowedEvent[];\n }) {\n this.controllerMessenger = controllerMessenger;\n this.controllerName = name;\n this.allowedActions = allowedActions || null;\n this.allowedEvents = allowedEvents || null;\n }\n\n /**\n * Register an action handler.\n *\n * This will make the registered function available to call via the `call` method.\n *\n * The action type this handler is registered under *must* be in the current namespace.\n *\n * @param action - The action type. This is a unqiue identifier for this action.\n * @param handler - The action handler. This function gets called when the `call` method is\n * invoked with the given action type.\n * @throws Will throw when a handler has been registered for this action type already.\n * @template T - A type union of Action type strings that are namespaced by N.\n */\n registerActionHandler>(\n action: T,\n handler: ActionHandler,\n ) {\n /* istanbul ignore if */ // Branch unreachable with valid types\n if (!action.startsWith(`${this.controllerName}:`)) {\n throw new Error(\n `Only allowed registering action handlers prefixed by '${this.controllerName}:'`,\n );\n }\n this.controllerMessenger.registerActionHandler(action, handler);\n }\n\n /**\n * Unregister an action handler.\n *\n * This will prevent this action from being called.\n *\n * The action type being unregistered *must* be in the current namespace.\n *\n * @param action - The action type. This is a unqiue identifier for this action.\n * @template T - A type union of Action type strings that are namespaced by N.\n */\n unregisterActionHandler>(action: T) {\n /* istanbul ignore if */ // Branch unreachable with valid types\n if (!action.startsWith(`${this.controllerName}:`)) {\n throw new Error(\n `Only allowed unregistering action handlers prefixed by '${this.controllerName}:'`,\n );\n }\n this.controllerMessenger.unregisterActionHandler(action);\n }\n\n /**\n * Call an action.\n *\n * This function will call the action handler corresponding to the given action type, passing\n * along any parameters given.\n *\n * The action type being called must be on the action allowlist.\n *\n * @param action - The action type. This is a unqiue identifier for this action.\n * @param params - The action parameters. These must match the type of the parameters of the\n * registered action handler.\n * @throws Will throw when no handler has been registered for the given type.\n * @template T - A type union of allowed Action type strings.\n * @returns The action return value.\n */\n call(\n action: T,\n ...params: ExtractActionParameters\n ): ExtractActionResponse {\n /* istanbul ignore next */ // Branches unreachable with valid types\n if (this.allowedActions === null) {\n throw new Error('No actions allowed');\n } else if (!this.allowedActions.includes(action)) {\n throw new Error(`Action missing from allow list: ${action}`);\n }\n return this.controllerMessenger.call(action, ...params);\n }\n\n /**\n * Publish an event.\n *\n * Publishes the given payload to all subscribers of the given event type.\n *\n * The event type being published *must* be in the current namespace.\n *\n * @param event - The event type. This is a unique identifier for this event.\n * @param payload - The event payload. The type of the parameters for each event handler must\n * match the type of this payload.\n * @template E - A type union of Event type strings that are namespaced by N.\n */\n publish>(\n event: E,\n ...payload: ExtractEventPayload\n ) {\n /* istanbul ignore if */ // Branch unreachable with valid types\n if (!event.startsWith(`${this.controllerName}:`)) {\n throw new Error(\n `Only allowed publishing events prefixed by '${this.controllerName}:'`,\n );\n }\n this.controllerMessenger.publish(event, ...payload);\n }\n\n /**\n * Subscribe to an event.\n *\n * Registers the given function as an event handler for the given event type.\n *\n * The event type being subscribed to must be on the event allowlist.\n *\n * @param eventType - The event type. This is a unique identifier for this event.\n * @param handler - The event handler. The type of the parameters for this event handler must\n * match the type of the payload for this event type.\n * @template E - A type union of Event type strings.\n */\n subscribe(\n eventType: E,\n handler: ExtractEventHandler,\n ): void;\n\n /**\n * Subscribe to an event, with a selector.\n *\n * Registers the given handler function as an event handler for the given\n * event type. When an event is published, its payload is first passed to the\n * selector. The event handler is only called if the selector's return value\n * differs from its last known return value.\n *\n * The event type being subscribed to must be on the event allowlist.\n *\n * @param eventType - The event type. This is a unique identifier for this event.\n * @param handler - The event handler. The type of the parameters for this event\n * handler must match the return type of the selector.\n * @param selector - The selector function used to select relevant data from\n * the event payload. The type of the parameters for this selector must match\n * the type of the payload for this event type.\n * @template E - A type union of Event type strings.\n * @template V - The selector return value.\n */\n subscribe(\n eventType: E,\n handler: SelectorEventHandler,\n selector: SelectorFunction, V>,\n ): void;\n\n subscribe(\n event: E,\n handler: ExtractEventHandler,\n selector?: SelectorFunction, V>,\n ) {\n /* istanbul ignore next */ // Branches unreachable with valid types\n if (this.allowedEvents === null) {\n throw new Error('No events allowed');\n } else if (!this.allowedEvents.includes(event)) {\n throw new Error(`Event missing from allow list: ${event}`);\n }\n\n if (selector) {\n return this.controllerMessenger.subscribe(event, handler, selector);\n }\n return this.controllerMessenger.subscribe(event, handler);\n }\n\n /**\n * Unsubscribe from an event.\n *\n * Unregisters the given function as an event handler for the given event.\n *\n * The event type being unsubscribed to must be on the event allowlist.\n *\n * @param event - The event type. This is a unique identifier for this event.\n * @param handler - The event handler to unregister.\n * @throws Will throw when the given event handler is not registered for this event.\n * @template T - A type union of allowed Event type strings.\n */\n unsubscribe(\n event: E,\n handler: ExtractEventHandler,\n ) {\n /* istanbul ignore next */ // Branches unreachable with valid types\n if (this.allowedEvents === null) {\n throw new Error('No events allowed');\n } else if (!this.allowedEvents.includes(event)) {\n throw new Error(`Event missing from allow list: ${event}`);\n }\n this.controllerMessenger.unsubscribe(event, handler);\n }\n\n /**\n * Clear subscriptions for a specific event.\n *\n * This will remove all subscribed handlers for this event.\n *\n * The event type being cleared *must* be in the current namespace.\n *\n * @param event - The event type. This is a unique identifier for this event.\n * @template E - A type union of Event type strings that are namespaced by N.\n */\n clearEventSubscriptions>(event: E) {\n /* istanbul ignore if */ // Branch unreachable with valid types\n if (!event.startsWith(`${this.controllerName}:`)) {\n throw new Error(\n `Only allowed clearing events prefixed by '${this.controllerName}:'`,\n );\n }\n this.controllerMessenger.clearEventSubscriptions(event);\n }\n}\n\n/**\n * A messaging system for controllers.\n *\n * The controller messenger allows registering functions as 'actions' that can be called elsewhere,\n * and it allows publishing and subscribing to events. Both actions and events are identified by\n * unique strings.\n *\n * @template Action - A type union of all Action types.\n * @template Event - A type union of all Event types.\n */\nexport class ControllerMessenger<\n Action extends ActionConstraint,\n Event extends EventConstraint,\n> {\n private actions = new Map();\n\n private events = new Map();\n\n /**\n * A cache of selector return values for their respective handlers.\n */\n private eventPayloadCache = new Map<\n GenericEventHandler,\n unknown | undefined\n >();\n\n /**\n * Register an action handler.\n *\n * This will make the registered function available to call via the `call` method.\n *\n * @param actionType - The action type. This is a unqiue identifier for this action.\n * @param handler - The action handler. This function gets called when the `call` method is\n * invoked with the given action type.\n * @throws Will throw when a handler has been registered for this action type already.\n * @template T - A type union of Action type strings.\n */\n registerActionHandler(\n actionType: T,\n handler: ActionHandler,\n ) {\n if (this.actions.has(actionType)) {\n throw new Error(\n `A handler for ${actionType} has already been registered`,\n );\n }\n this.actions.set(actionType, handler);\n }\n\n /**\n * Unregister an action handler.\n *\n * This will prevent this action from being called.\n *\n * @param actionType - The action type. This is a unqiue identifier for this action.\n * @template T - A type union of Action type strings.\n */\n unregisterActionHandler(actionType: T) {\n this.actions.delete(actionType);\n }\n\n /**\n * Unregister all action handlers.\n *\n * This prevents all actions from being called.\n */\n clearActions() {\n this.actions.clear();\n }\n\n /**\n * Call an action.\n *\n * This function will call the action handler corresponding to the given action type, passing\n * along any parameters given.\n *\n * @param actionType - The action type. This is a unqiue identifier for this action.\n * @param params - The action parameters. These must match the type of the parameters of the\n * registered action handler.\n * @throws Will throw when no handler has been registered for the given type.\n * @template T - A type union of Action type strings.\n * @returns The action return value.\n */\n call(\n actionType: T,\n ...params: ExtractActionParameters\n ): ExtractActionResponse {\n const handler = this.actions.get(actionType) as ActionHandler;\n if (!handler) {\n throw new Error(`A handler for ${actionType} has not been registered`);\n }\n return handler(...params);\n }\n\n /**\n * Publish an event.\n *\n * Publishes the given payload to all subscribers of the given event type.\n *\n * @param eventType - The event type. This is a unique identifier for this event.\n * @param payload - The event payload. The type of the parameters for each event handler must\n * match the type of this payload.\n * @template E - A type union of Event type strings.\n */\n publish(\n eventType: E,\n ...payload: ExtractEventPayload\n ) {\n const subscribers = this.events.get(eventType);\n\n if (subscribers) {\n for (const [handler, selector] of subscribers.entries()) {\n if (selector) {\n const previousValue = this.eventPayloadCache.get(handler);\n const newValue = selector(...payload);\n\n if (newValue !== previousValue) {\n this.eventPayloadCache.set(handler, newValue);\n handler(newValue, previousValue);\n }\n } else {\n (handler as GenericEventHandler)(...payload);\n }\n }\n }\n }\n\n /**\n * Subscribe to an event.\n *\n * Registers the given function as an event handler for the given event type.\n *\n * @param eventType - The event type. This is a unique identifier for this event.\n * @param handler - The event handler. The type of the parameters for this event handler must\n * match the type of the payload for this event type.\n * @template E - A type union of Event type strings.\n */\n subscribe(\n eventType: E,\n handler: ExtractEventHandler,\n ): void;\n\n /**\n * Subscribe to an event, with a selector.\n *\n * Registers the given handler function as an event handler for the given\n * event type. When an event is published, its payload is first passed to the\n * selector. The event handler is only called if the selector's return value\n * differs from its last known return value.\n *\n * @param eventType - The event type. This is a unique identifier for this event.\n * @param handler - The event handler. The type of the parameters for this event\n * handler must match the return type of the selector.\n * @param selector - The selector function used to select relevant data from\n * the event payload. The type of the parameters for this selector must match\n * the type of the payload for this event type.\n * @template E - A type union of Event type strings.\n * @template V - The selector return value.\n */\n subscribe(\n eventType: E,\n handler: SelectorEventHandler,\n selector: SelectorFunction, V>,\n ): void;\n\n subscribe(\n eventType: E,\n handler: ExtractEventHandler,\n selector?: SelectorFunction, V>,\n ): void {\n let subscribers = this.events.get(eventType);\n if (!subscribers) {\n subscribers = new Map();\n this.events.set(eventType, subscribers);\n }\n\n subscribers.set(handler, selector);\n }\n\n /**\n * Unsubscribe from an event.\n *\n * Unregisters the given function as an event handler for the given event.\n *\n * @param eventType - The event type. This is a unique identifier for this event.\n * @param handler - The event handler to unregister.\n * @throws Will throw when the given event handler is not registered for this event.\n * @template E - A type union of Event type strings.\n */\n unsubscribe(\n eventType: E,\n handler: ExtractEventHandler,\n ) {\n const subscribers = this.events.get(eventType);\n\n if (!subscribers || !subscribers.has(handler)) {\n throw new Error(`Subscription not found for event: ${eventType}`);\n }\n\n const selector = subscribers.get(handler);\n if (selector) {\n this.eventPayloadCache.delete(handler);\n }\n\n subscribers.delete(handler);\n }\n\n /**\n * Clear subscriptions for a specific event.\n *\n * This will remove all subscribed handlers for this event.\n *\n * @param eventType - The event type. This is a unique identifier for this event.\n * @template E - A type union of Event type strings.\n */\n clearEventSubscriptions(eventType: E) {\n this.events.delete(eventType);\n }\n\n /**\n * Clear all subscriptions.\n *\n * This will remove all subscribed handlers for all events.\n */\n clearSubscriptions() {\n this.events.clear();\n }\n\n /**\n * Get a restricted controller messenger\n *\n * Returns a wrapper around the controller messenger instance that restricts access to actions\n * and events. The provided allowlists grant the ability to call the listed actions and subscribe\n * to the listed events. The \"name\" provided grants ownership of any actions and events under\n * that namespace. Ownership allows registering actions and publishing events, as well as\n * unregistering actions and clearing event subscriptions.\n *\n * @param options - Controller messenger options.\n * @param options.name - The name of the thing this messenger will be handed to (e.g. the\n * controller name). This grants \"ownership\" of actions and events under this namespace to the\n * restricted controller messenger returned.\n * @param options.allowedActions - The list of actions that this restricted controller messenger\n * should be alowed to call.\n * @param options.allowedEvents - The list of events that this restricted controller messenger\n * should be allowed to subscribe to.\n * @template N - The namespace for this messenger. Typically this is the name of the controller or\n * module that this messenger has been created for. The authority to publish events and register\n * actions under this namespace is granted to this restricted messenger instance.\n * @template AllowedAction - A type union of the 'type' string for any allowed actions.\n * @template AllowedEvent - A type union of the 'type' string for any allowed events.\n * @returns The restricted controller messenger.\n */\n getRestricted<\n N extends string,\n AllowedAction extends string,\n AllowedEvent extends string,\n >({\n name,\n allowedActions,\n allowedEvents,\n }: {\n name: N;\n allowedActions?: Extract[];\n allowedEvents?: Extract[];\n }): RestrictedControllerMessenger<\n N,\n NarrowToNamespace | NarrowToAllowed,\n NarrowToNamespace | NarrowToAllowed,\n AllowedAction,\n AllowedEvent\n > {\n return new RestrictedControllerMessenger<\n N,\n NarrowToNamespace | NarrowToAllowed,\n NarrowToNamespace | NarrowToAllowed,\n AllowedAction,\n AllowedEvent\n >({\n controllerMessenger: this,\n name,\n allowedActions,\n allowedEvents,\n });\n }\n}\n"]} \ No newline at end of file diff --git a/dist/announcement/AnnouncementController.d.ts b/dist/announcement/AnnouncementController.d.ts deleted file mode 100644 index 5a5ab63f0b..0000000000 --- a/dist/announcement/AnnouncementController.d.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { BaseController, BaseConfig, BaseState } from '../BaseController'; -interface ViewedAnnouncement { - [id: number]: boolean; -} -interface Announcement { - id: number; - date: string; -} -interface StateAnnouncement extends Announcement { - isShown: boolean; -} -/** - * A map of announcement ids to Announcement objects - */ -interface AnnouncementMap { - [id: number]: Announcement; -} -/** - * A map of announcement ids to StateAnnouncement objects - */ -export interface StateAnnouncementMap { - [id: number]: StateAnnouncement; -} -/** - * AnnouncementConfig will hold the active announcements - */ -export interface AnnouncementConfig extends BaseConfig { - allAnnouncements: AnnouncementMap; -} -/** - * Announcement state will hold all the seen and unseen announcements - * that are still active - */ -export interface AnnouncementState extends BaseState { - announcements: StateAnnouncementMap; -} -/** - * Controller for managing in-app announcements. - */ -export declare class AnnouncementController extends BaseController { - /** - * Creates a AnnouncementController instance. - * - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor(config: AnnouncementConfig, state?: AnnouncementState); - /** - * Compares the announcements in state with the announcements from file - * to check if there are any new announcements - * if yes, the new announcement will be added to the state with a flag indicating - * that the announcement is not seen by the user. - */ - private _addAnnouncements; - /** - * Updates the status of the status of the specified announcements - * once it is read by the user. - * - * @param viewedIds - The announcement IDs to mark as viewed. - */ - updateViewed(viewedIds: ViewedAnnouncement): void; -} -export {}; diff --git a/dist/announcement/AnnouncementController.js b/dist/announcement/AnnouncementController.js deleted file mode 100644 index f432f7830d..0000000000 --- a/dist/announcement/AnnouncementController.js +++ /dev/null @@ -1,54 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.AnnouncementController = void 0; -const BaseController_1 = require("../BaseController"); -const defaultState = { - announcements: {}, -}; -/** - * Controller for managing in-app announcements. - */ -class AnnouncementController extends BaseController_1.BaseController { - /** - * Creates a AnnouncementController instance. - * - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor(config, state) { - super(config, state || defaultState); - this.initialize(); - this._addAnnouncements(); - } - /** - * Compares the announcements in state with the announcements from file - * to check if there are any new announcements - * if yes, the new announcement will be added to the state with a flag indicating - * that the announcement is not seen by the user. - */ - _addAnnouncements() { - const newAnnouncements = {}; - const { allAnnouncements } = this.config; - Object.values(allAnnouncements).forEach((announcement) => { - newAnnouncements[announcement.id] = this.state.announcements[announcement.id] - ? this.state.announcements[announcement.id] - : Object.assign(Object.assign({}, announcement), { isShown: false }); - }); - this.update({ announcements: newAnnouncements }); - } - /** - * Updates the status of the status of the specified announcements - * once it is read by the user. - * - * @param viewedIds - The announcement IDs to mark as viewed. - */ - updateViewed(viewedIds) { - const stateAnnouncements = this.state.announcements; - for (const id of Object.keys(viewedIds).map(Number)) { - stateAnnouncements[id].isShown = viewedIds[id]; - } - this.update({ announcements: stateAnnouncements }, true); - } -} -exports.AnnouncementController = AnnouncementController; -//# sourceMappingURL=AnnouncementController.js.map \ No newline at end of file diff --git a/dist/announcement/AnnouncementController.js.map b/dist/announcement/AnnouncementController.js.map deleted file mode 100644 index fb037eb97a..0000000000 --- a/dist/announcement/AnnouncementController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"AnnouncementController.js","sourceRoot":"","sources":["../../src/announcement/AnnouncementController.ts"],"names":[],"mappings":";;;AAAA,sDAA0E;AA4C1E,MAAM,YAAY,GAAG;IACnB,aAAa,EAAE,EAAE;CAClB,CAAC;AAEF;;GAEG;AACH,MAAa,sBAAuB,SAAQ,+BAG3C;IACC;;;;;OAKG;IACH,YAAY,MAA0B,EAAE,KAAyB;QAC/D,KAAK,CAAC,MAAM,EAAE,KAAK,IAAI,YAAY,CAAC,CAAC;QACrC,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,CAAC,iBAAiB,EAAE,CAAC;IAC3B,CAAC;IAED;;;;;OAKG;IACK,iBAAiB;QACvB,MAAM,gBAAgB,GAAyB,EAAE,CAAC;QAClD,MAAM,EAAE,gBAAgB,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC;QACzC,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,OAAO,CACrC,CAAC,YAA+B,EAAE,EAAE;YAClC,gBAAgB,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAC1D,YAAY,CAAC,EAAE,CAChB;gBACC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC3C,CAAC,iCACM,YAAY,KACf,OAAO,EAAE,KAAK,GACf,CAAC;QACR,CAAC,CACF,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,gBAAgB,EAAE,CAAC,CAAC;IACnD,CAAC;IAED;;;;;OAKG;IACH,YAAY,CAAC,SAA6B;QACxC,MAAM,kBAAkB,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC;QAEpD,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;YACnD,kBAAkB,CAAC,EAAE,CAAC,CAAC,OAAO,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;SAChD;QACD,IAAI,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,kBAAkB,EAAE,EAAE,IAAI,CAAC,CAAC;IAC3D,CAAC;CACF;AAtDD,wDAsDC","sourcesContent":["import { BaseController, BaseConfig, BaseState } from '../BaseController';\n\ninterface ViewedAnnouncement {\n [id: number]: boolean;\n}\n\ninterface Announcement {\n id: number;\n date: string;\n}\n\ninterface StateAnnouncement extends Announcement {\n isShown: boolean;\n}\n\n/**\n * A map of announcement ids to Announcement objects\n */\ninterface AnnouncementMap {\n [id: number]: Announcement;\n}\n\n/**\n * A map of announcement ids to StateAnnouncement objects\n */\nexport interface StateAnnouncementMap {\n [id: number]: StateAnnouncement;\n}\n\n/**\n * AnnouncementConfig will hold the active announcements\n */\nexport interface AnnouncementConfig extends BaseConfig {\n allAnnouncements: AnnouncementMap;\n}\n\n/**\n * Announcement state will hold all the seen and unseen announcements\n * that are still active\n */\nexport interface AnnouncementState extends BaseState {\n announcements: StateAnnouncementMap;\n}\n\nconst defaultState = {\n announcements: {},\n};\n\n/**\n * Controller for managing in-app announcements.\n */\nexport class AnnouncementController extends BaseController<\n AnnouncementConfig,\n AnnouncementState\n> {\n /**\n * Creates a AnnouncementController instance.\n *\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(config: AnnouncementConfig, state?: AnnouncementState) {\n super(config, state || defaultState);\n this.initialize();\n this._addAnnouncements();\n }\n\n /**\n * Compares the announcements in state with the announcements from file\n * to check if there are any new announcements\n * if yes, the new announcement will be added to the state with a flag indicating\n * that the announcement is not seen by the user.\n */\n private _addAnnouncements(): void {\n const newAnnouncements: StateAnnouncementMap = {};\n const { allAnnouncements } = this.config;\n Object.values(allAnnouncements).forEach(\n (announcement: StateAnnouncement) => {\n newAnnouncements[announcement.id] = this.state.announcements[\n announcement.id\n ]\n ? this.state.announcements[announcement.id]\n : {\n ...announcement,\n isShown: false,\n };\n },\n );\n this.update({ announcements: newAnnouncements });\n }\n\n /**\n * Updates the status of the status of the specified announcements\n * once it is read by the user.\n *\n * @param viewedIds - The announcement IDs to mark as viewed.\n */\n updateViewed(viewedIds: ViewedAnnouncement): void {\n const stateAnnouncements = this.state.announcements;\n\n for (const id of Object.keys(viewedIds).map(Number)) {\n stateAnnouncements[id].isShown = viewedIds[id];\n }\n this.update({ announcements: stateAnnouncements }, true);\n }\n}\n"]} \ No newline at end of file diff --git a/dist/apis/crypto-compare.d.ts b/dist/apis/crypto-compare.d.ts deleted file mode 100644 index a62342e4fe..0000000000 --- a/dist/apis/crypto-compare.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Fetches the exchange rate for a given currency. - * - * @param currency - ISO 4217 currency code. - * @param nativeCurrency - Symbol for base asset. - * @param includeUSDRate - Whether to add the USD rate to the fetch. - * @returns Promise resolving to exchange rate for given currency. - */ -export declare function fetchExchangeRate(currency: string, nativeCurrency: string, includeUSDRate?: boolean): Promise<{ - conversionRate: number; - usdConversionRate: number; -}>; diff --git a/dist/apis/crypto-compare.js b/dist/apis/crypto-compare.js deleted file mode 100644 index 2dd7fcae94..0000000000 --- a/dist/apis/crypto-compare.js +++ /dev/null @@ -1,67 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.fetchExchangeRate = void 0; -const util_1 = require("../util"); -/** - * Get the CryptoCompare API URL for getting the conversion rate from the given native currency to - * the given currency. Optionally, the conversion rate from the native currency to USD can also be - * included in the response. - * - * @param currentCurrency - The currency to get a conversion rate for. - * @param nativeCurrency - The native currency to convert from. - * @param includeUSDRate - Whether or not the native currency to USD conversion rate should be - * included in the response as well. - * @returns The API URL for getting the conversion rate. - */ -function getPricingURL(currentCurrency, nativeCurrency, includeUSDRate) { - return (`https://min-api.cryptocompare.com/data/price?fsym=` + - `${nativeCurrency.toUpperCase()}&tsyms=${currentCurrency.toUpperCase()}` + - `${includeUSDRate && currentCurrency.toUpperCase() !== 'USD' ? ',USD' : ''}`); -} -/** - * Fetches the exchange rate for a given currency. - * - * @param currency - ISO 4217 currency code. - * @param nativeCurrency - Symbol for base asset. - * @param includeUSDRate - Whether to add the USD rate to the fetch. - * @returns Promise resolving to exchange rate for given currency. - */ -function fetchExchangeRate(currency, nativeCurrency, includeUSDRate) { - return __awaiter(this, void 0, void 0, function* () { - const json = yield (0, util_1.handleFetch)(getPricingURL(currency, nativeCurrency, includeUSDRate)); - /* - Example expected error response (if pair is not found) - { - Response: "Error", - Message: "cccagg_or_exchange market does not exist for this coin pair (ETH-)", - HasWarning: false, - } - */ - if (json.Response === 'Error') { - throw new Error(json.Message); - } - const conversionRate = Number(json[currency.toUpperCase()]); - const usdConversionRate = Number(json.USD); - if (!Number.isFinite(conversionRate)) { - throw new Error(`Invalid response for ${currency.toUpperCase()}: ${json[currency.toUpperCase()]}`); - } - if (includeUSDRate && !Number.isFinite(usdConversionRate)) { - throw new Error(`Invalid response for usdConversionRate: ${json.USD}`); - } - return { - conversionRate, - usdConversionRate, - }; - }); -} -exports.fetchExchangeRate = fetchExchangeRate; -//# sourceMappingURL=crypto-compare.js.map \ No newline at end of file diff --git a/dist/apis/crypto-compare.js.map b/dist/apis/crypto-compare.js.map deleted file mode 100644 index c6b80eb789..0000000000 --- a/dist/apis/crypto-compare.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"crypto-compare.js","sourceRoot":"","sources":["../../src/apis/crypto-compare.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,kCAAsC;AAEtC;;;;;;;;;;GAUG;AACH,SAAS,aAAa,CACpB,eAAuB,EACvB,cAAsB,EACtB,cAAwB;IAExB,OAAO,CACL,oDAAoD;QACpD,GAAG,cAAc,CAAC,WAAW,EAAE,UAAU,eAAe,CAAC,WAAW,EAAE,EAAE;QACxE,GAAG,cAAc,IAAI,eAAe,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAC7E,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,SAAsB,iBAAiB,CACrC,QAAgB,EAChB,cAAsB,EACtB,cAAwB;;QAKxB,MAAM,IAAI,GAAG,MAAM,IAAA,kBAAW,EAC5B,aAAa,CAAC,QAAQ,EAAE,cAAc,EAAE,cAAc,CAAC,CACxD,CAAC;QAEF;;;;;;;UAOE;QACF,IAAI,IAAI,CAAC,QAAQ,KAAK,OAAO,EAAE;YAC7B,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;SAC/B;QAED,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QAE5D,MAAM,iBAAiB,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE;YACpC,MAAM,IAAI,KAAK,CACb,wBAAwB,QAAQ,CAAC,WAAW,EAAE,KAC5C,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAC7B,EAAE,CACH,CAAC;SACH;QAED,IAAI,cAAc,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE;YACzD,MAAM,IAAI,KAAK,CAAC,2CAA2C,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;SACxE;QAED,OAAO;YACL,cAAc;YACd,iBAAiB;SAClB,CAAC;IACJ,CAAC;CAAA;AA3CD,8CA2CC","sourcesContent":["import { handleFetch } from '../util';\n\n/**\n * Get the CryptoCompare API URL for getting the conversion rate from the given native currency to\n * the given currency. Optionally, the conversion rate from the native currency to USD can also be\n * included in the response.\n *\n * @param currentCurrency - The currency to get a conversion rate for.\n * @param nativeCurrency - The native currency to convert from.\n * @param includeUSDRate - Whether or not the native currency to USD conversion rate should be\n * included in the response as well.\n * @returns The API URL for getting the conversion rate.\n */\nfunction getPricingURL(\n currentCurrency: string,\n nativeCurrency: string,\n includeUSDRate?: boolean,\n) {\n return (\n `https://min-api.cryptocompare.com/data/price?fsym=` +\n `${nativeCurrency.toUpperCase()}&tsyms=${currentCurrency.toUpperCase()}` +\n `${includeUSDRate && currentCurrency.toUpperCase() !== 'USD' ? ',USD' : ''}`\n );\n}\n\n/**\n * Fetches the exchange rate for a given currency.\n *\n * @param currency - ISO 4217 currency code.\n * @param nativeCurrency - Symbol for base asset.\n * @param includeUSDRate - Whether to add the USD rate to the fetch.\n * @returns Promise resolving to exchange rate for given currency.\n */\nexport async function fetchExchangeRate(\n currency: string,\n nativeCurrency: string,\n includeUSDRate?: boolean,\n): Promise<{\n conversionRate: number;\n usdConversionRate: number;\n}> {\n const json = await handleFetch(\n getPricingURL(currency, nativeCurrency, includeUSDRate),\n );\n\n /*\n Example expected error response (if pair is not found)\n {\n Response: \"Error\",\n Message: \"cccagg_or_exchange market does not exist for this coin pair (ETH-)\",\n HasWarning: false,\n }\n */\n if (json.Response === 'Error') {\n throw new Error(json.Message);\n }\n\n const conversionRate = Number(json[currency.toUpperCase()]);\n\n const usdConversionRate = Number(json.USD);\n if (!Number.isFinite(conversionRate)) {\n throw new Error(\n `Invalid response for ${currency.toUpperCase()}: ${\n json[currency.toUpperCase()]\n }`,\n );\n }\n\n if (includeUSDRate && !Number.isFinite(usdConversionRate)) {\n throw new Error(`Invalid response for usdConversionRate: ${json.USD}`);\n }\n\n return {\n conversionRate,\n usdConversionRate,\n };\n}\n"]} \ No newline at end of file diff --git a/dist/apis/token-service.d.ts b/dist/apis/token-service.d.ts deleted file mode 100644 index 25542efd6f..0000000000 --- a/dist/apis/token-service.d.ts +++ /dev/null @@ -1,29 +0,0 @@ -export declare const TOKEN_END_POINT_API = "https://token-api.metaswap.codefi.network"; -export declare const TOKEN_METADATA_NO_SUPPORT_ERROR = "TokenService Error: Network does not support fetchTokenMetadata"; -/** - * Fetch the list of token metadata for a given network. This request is cancellable using the - * abort signal passed in. - * - * @param chainId - The chain ID of the network the requested tokens are on. - * @param abortSignal - The abort signal used to cancel the request if necessary. - * @param options - Additional fetch options. - * @param options.timeout - The fetch timeout. - * @returns The token list, or `undefined` if the request was cancelled. - */ -export declare function fetchTokenList(chainId: string, abortSignal: AbortSignal, { timeout }?: { - timeout?: number | undefined; -}): Promise; -/** - * Fetch metadata for the token address provided for a given network. This request is cancellable - * using the abort signal passed in. - * - * @param chainId - The chain ID of the network the token is on. - * @param tokenAddress - The address of the token to fetch metadata for. - * @param abortSignal - The abort signal used to cancel the request if necessary. - * @param options - Additional fetch options. - * @param options.timeout - The fetch timeout. - * @returns The token metadata, or `undefined` if the request was either aborted or failed. - */ -export declare function fetchTokenMetadata(chainId: string, tokenAddress: string, abortSignal: AbortSignal, { timeout }?: { - timeout?: number | undefined; -}): Promise; diff --git a/dist/apis/token-service.js b/dist/apis/token-service.js deleted file mode 100644 index 760f93070e..0000000000 --- a/dist/apis/token-service.js +++ /dev/null @@ -1,133 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.fetchTokenMetadata = exports.fetchTokenList = exports.TOKEN_METADATA_NO_SUPPORT_ERROR = exports.TOKEN_END_POINT_API = void 0; -const util_1 = require("../util"); -exports.TOKEN_END_POINT_API = 'https://token-api.metaswap.codefi.network'; -exports.TOKEN_METADATA_NO_SUPPORT_ERROR = 'TokenService Error: Network does not support fetchTokenMetadata'; -/** - * Get the tokens URL for a specific network. - * - * @param chainId - The chain ID of the network the tokens requested are on. - * @returns The tokens URL. - */ -function getTokensURL(chainId) { - return `${exports.TOKEN_END_POINT_API}/tokens/${chainId}`; -} -/** - * Get the token metadata URL for the given network and token. - * - * @param chainId - The chain ID of the network the token is on. - * @param tokenAddress - The token address. - * @returns The token metadata URL. - */ -function getTokenMetadataURL(chainId, tokenAddress) { - return `${exports.TOKEN_END_POINT_API}/token/${chainId}?address=${tokenAddress}`; -} -const tenSecondsInMilliseconds = 10000; -// Token list averages 1.6 MB in size -// timeoutFetch by default has a 500ms timeout, which will almost always timeout given the response size. -const defaultTimeout = tenSecondsInMilliseconds; -/** - * Fetch the list of token metadata for a given network. This request is cancellable using the - * abort signal passed in. - * - * @param chainId - The chain ID of the network the requested tokens are on. - * @param abortSignal - The abort signal used to cancel the request if necessary. - * @param options - Additional fetch options. - * @param options.timeout - The fetch timeout. - * @returns The token list, or `undefined` if the request was cancelled. - */ -function fetchTokenList(chainId, abortSignal, { timeout = defaultTimeout } = {}) { - return __awaiter(this, void 0, void 0, function* () { - const tokenURL = getTokensURL(chainId); - const response = yield queryApi(tokenURL, abortSignal, timeout); - if (response) { - return parseJsonResponse(response); - } - return undefined; - }); -} -exports.fetchTokenList = fetchTokenList; -/** - * Fetch metadata for the token address provided for a given network. This request is cancellable - * using the abort signal passed in. - * - * @param chainId - The chain ID of the network the token is on. - * @param tokenAddress - The address of the token to fetch metadata for. - * @param abortSignal - The abort signal used to cancel the request if necessary. - * @param options - Additional fetch options. - * @param options.timeout - The fetch timeout. - * @returns The token metadata, or `undefined` if the request was either aborted or failed. - */ -function fetchTokenMetadata(chainId, tokenAddress, abortSignal, { timeout = defaultTimeout } = {}) { - return __awaiter(this, void 0, void 0, function* () { - if (!(0, util_1.isTokenDetectionSupportedForNetwork)(chainId)) { - throw new Error(exports.TOKEN_METADATA_NO_SUPPORT_ERROR); - } - const tokenMetadataURL = getTokenMetadataURL(chainId, tokenAddress); - const response = yield queryApi(tokenMetadataURL, abortSignal, timeout); - if (response) { - return parseJsonResponse(response); - } - return undefined; - }); -} -exports.fetchTokenMetadata = fetchTokenMetadata; -/** - * Perform fetch request against the api. - * - * @param apiURL - The URL of the API to fetch. - * @param abortSignal - The abort signal used to cancel the request if necessary. - * @param timeout - The fetch timeout. - * @returns Promise resolving request response. - */ -function queryApi(apiURL, abortSignal, timeout) { - return __awaiter(this, void 0, void 0, function* () { - const fetchOptions = { - referrer: apiURL, - referrerPolicy: 'no-referrer-when-downgrade', - method: 'GET', - mode: 'cors', - signal: abortSignal, - cache: 'default', - }; - fetchOptions.headers = new window.Headers(); - fetchOptions.headers.set('Content-Type', 'application/json'); - try { - return yield (0, util_1.timeoutFetch)(apiURL, fetchOptions, timeout); - } - catch (error) { - if (error instanceof Error && error.name === 'AbortError') { - console.log('Request is aborted'); - } - } - return undefined; - }); -} -/** - * Parse an API response and return the response JSON data. - * - * @param apiResponse - The API response to parse. - * @returns The response JSON data. - * @throws Will throw if the response includes an error. - */ -function parseJsonResponse(apiResponse) { - return __awaiter(this, void 0, void 0, function* () { - const responseObj = yield apiResponse.json(); - // api may return errors as json without setting an error http status code - if (responseObj === null || responseObj === void 0 ? void 0 : responseObj.error) { - throw new Error(`TokenService Error: ${responseObj.error}`); - } - return responseObj; - }); -} -//# sourceMappingURL=token-service.js.map \ No newline at end of file diff --git a/dist/apis/token-service.js.map b/dist/apis/token-service.js.map deleted file mode 100644 index ce474a034b..0000000000 --- a/dist/apis/token-service.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"token-service.js","sourceRoot":"","sources":["../../src/apis/token-service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,kCAA4E;AAE/D,QAAA,mBAAmB,GAAG,2CAA2C,CAAC;AAClE,QAAA,+BAA+B,GAC1C,iEAAiE,CAAC;AAEpE;;;;;GAKG;AACH,SAAS,YAAY,CAAC,OAAe;IACnC,OAAO,GAAG,2BAAmB,WAAW,OAAO,EAAE,CAAC;AACpD,CAAC;AAED;;;;;;GAMG;AACH,SAAS,mBAAmB,CAAC,OAAe,EAAE,YAAoB;IAChE,OAAO,GAAG,2BAAmB,UAAU,OAAO,YAAY,YAAY,EAAE,CAAC;AAC3E,CAAC;AAED,MAAM,wBAAwB,GAAG,KAAM,CAAC;AAExC,qCAAqC;AACrC,yGAAyG;AACzG,MAAM,cAAc,GAAG,wBAAwB,CAAC;AAEhD;;;;;;;;;GASG;AACH,SAAsB,cAAc,CAClC,OAAe,EACf,WAAwB,EACxB,EAAE,OAAO,GAAG,cAAc,EAAE,GAAG,EAAE;;QAEjC,MAAM,QAAQ,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QACvC,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;QAChE,IAAI,QAAQ,EAAE;YACZ,OAAO,iBAAiB,CAAC,QAAQ,CAAC,CAAC;SACpC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;CAAA;AAXD,wCAWC;AAED;;;;;;;;;;GAUG;AACH,SAAsB,kBAAkB,CACtC,OAAe,EACf,YAAoB,EACpB,WAAwB,EACxB,EAAE,OAAO,GAAG,cAAc,EAAE,GAAG,EAAE;;QAEjC,IAAI,CAAC,IAAA,0CAAmC,EAAC,OAAO,CAAC,EAAE;YACjD,MAAM,IAAI,KAAK,CAAC,uCAA+B,CAAC,CAAC;SAClD;QACD,MAAM,gBAAgB,GAAG,mBAAmB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QACpE,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,gBAAgB,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;QACxE,IAAI,QAAQ,EAAE;YACZ,OAAO,iBAAiB,CAAC,QAAQ,CAAe,CAAC;SAClD;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;CAAA;AAfD,gDAeC;AAED;;;;;;;GAOG;AACH,SAAe,QAAQ,CACrB,MAAc,EACd,WAAwB,EACxB,OAAe;;QAEf,MAAM,YAAY,GAAgB;YAChC,QAAQ,EAAE,MAAM;YAChB,cAAc,EAAE,4BAA4B;YAC5C,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,MAAM;YACZ,MAAM,EAAE,WAAW;YACnB,KAAK,EAAE,SAAS;SACjB,CAAC;QACF,YAAY,CAAC,OAAO,GAAG,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QAC5C,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;QAC7D,IAAI;YACF,OAAO,MAAM,IAAA,mBAAY,EAAC,MAAM,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;SAC1D;QAAC,OAAO,KAAK,EAAE;YACd,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE;gBACzD,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;aACnC;SACF;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;CAAA;AAED;;;;;;GAMG;AACH,SAAe,iBAAiB,CAAC,WAAqB;;QACpD,MAAM,WAAW,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC;QAC7C,0EAA0E;QAC1E,IAAI,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,KAAK,EAAE;YACtB,MAAM,IAAI,KAAK,CAAC,uBAAuB,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC;SAC7D;QACD,OAAO,WAAW,CAAC;IACrB,CAAC;CAAA","sourcesContent":["import { isTokenDetectionSupportedForNetwork, timeoutFetch } from '../util';\n\nexport const TOKEN_END_POINT_API = 'https://token-api.metaswap.codefi.network';\nexport const TOKEN_METADATA_NO_SUPPORT_ERROR =\n 'TokenService Error: Network does not support fetchTokenMetadata';\n\n/**\n * Get the tokens URL for a specific network.\n *\n * @param chainId - The chain ID of the network the tokens requested are on.\n * @returns The tokens URL.\n */\nfunction getTokensURL(chainId: string) {\n return `${TOKEN_END_POINT_API}/tokens/${chainId}`;\n}\n\n/**\n * Get the token metadata URL for the given network and token.\n *\n * @param chainId - The chain ID of the network the token is on.\n * @param tokenAddress - The token address.\n * @returns The token metadata URL.\n */\nfunction getTokenMetadataURL(chainId: string, tokenAddress: string) {\n return `${TOKEN_END_POINT_API}/token/${chainId}?address=${tokenAddress}`;\n}\n\nconst tenSecondsInMilliseconds = 10_000;\n\n// Token list averages 1.6 MB in size\n// timeoutFetch by default has a 500ms timeout, which will almost always timeout given the response size.\nconst defaultTimeout = tenSecondsInMilliseconds;\n\n/**\n * Fetch the list of token metadata for a given network. This request is cancellable using the\n * abort signal passed in.\n *\n * @param chainId - The chain ID of the network the requested tokens are on.\n * @param abortSignal - The abort signal used to cancel the request if necessary.\n * @param options - Additional fetch options.\n * @param options.timeout - The fetch timeout.\n * @returns The token list, or `undefined` if the request was cancelled.\n */\nexport async function fetchTokenList(\n chainId: string,\n abortSignal: AbortSignal,\n { timeout = defaultTimeout } = {},\n): Promise {\n const tokenURL = getTokensURL(chainId);\n const response = await queryApi(tokenURL, abortSignal, timeout);\n if (response) {\n return parseJsonResponse(response);\n }\n return undefined;\n}\n\n/**\n * Fetch metadata for the token address provided for a given network. This request is cancellable\n * using the abort signal passed in.\n *\n * @param chainId - The chain ID of the network the token is on.\n * @param tokenAddress - The address of the token to fetch metadata for.\n * @param abortSignal - The abort signal used to cancel the request if necessary.\n * @param options - Additional fetch options.\n * @param options.timeout - The fetch timeout.\n * @returns The token metadata, or `undefined` if the request was either aborted or failed.\n */\nexport async function fetchTokenMetadata(\n chainId: string,\n tokenAddress: string,\n abortSignal: AbortSignal,\n { timeout = defaultTimeout } = {},\n): Promise {\n if (!isTokenDetectionSupportedForNetwork(chainId)) {\n throw new Error(TOKEN_METADATA_NO_SUPPORT_ERROR);\n }\n const tokenMetadataURL = getTokenMetadataURL(chainId, tokenAddress);\n const response = await queryApi(tokenMetadataURL, abortSignal, timeout);\n if (response) {\n return parseJsonResponse(response) as Promise;\n }\n return undefined;\n}\n\n/**\n * Perform fetch request against the api.\n *\n * @param apiURL - The URL of the API to fetch.\n * @param abortSignal - The abort signal used to cancel the request if necessary.\n * @param timeout - The fetch timeout.\n * @returns Promise resolving request response.\n */\nasync function queryApi(\n apiURL: string,\n abortSignal: AbortSignal,\n timeout: number,\n): Promise {\n const fetchOptions: RequestInit = {\n referrer: apiURL,\n referrerPolicy: 'no-referrer-when-downgrade',\n method: 'GET',\n mode: 'cors',\n signal: abortSignal,\n cache: 'default',\n };\n fetchOptions.headers = new window.Headers();\n fetchOptions.headers.set('Content-Type', 'application/json');\n try {\n return await timeoutFetch(apiURL, fetchOptions, timeout);\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n console.log('Request is aborted');\n }\n }\n return undefined;\n}\n\n/**\n * Parse an API response and return the response JSON data.\n *\n * @param apiResponse - The API response to parse.\n * @returns The response JSON data.\n * @throws Will throw if the response includes an error.\n */\nasync function parseJsonResponse(apiResponse: Response): Promise {\n const responseObj = await apiResponse.json();\n // api may return errors as json without setting an error http status code\n if (responseObj?.error) {\n throw new Error(`TokenService Error: ${responseObj.error}`);\n }\n return responseObj;\n}\n"]} \ No newline at end of file diff --git a/dist/approval/ApprovalController.d.ts b/dist/approval/ApprovalController.d.ts deleted file mode 100644 index 02fb45ec3b..0000000000 --- a/dist/approval/ApprovalController.d.ts +++ /dev/null @@ -1,278 +0,0 @@ -import type { Patch } from 'immer'; -import { EthereumRpcError } from 'eth-rpc-errors'; -import { BaseController, Json } from '../BaseControllerV2'; -import type { RestrictedControllerMessenger } from '../ControllerMessenger'; -declare const controllerName = "ApprovalController"; -declare type ApprovalRequestData = Record | null; -export declare type ApprovalRequest = { - /** - * The ID of the approval request. - */ - id: string; - /** - * The origin of the approval request. - */ - origin: string; - /** - * The time that the request was received, per Date.now(). - */ - time: number; - /** - * The type of the approval request. - */ - type: string; - /** - * Additional data associated with the request. - * TODO:TS4.4 make optional - */ - requestData: RequestData; -}; -declare type ShowApprovalRequest = () => void | Promise; -export declare type ApprovalControllerState = { - pendingApprovals: Record>>; - pendingApprovalCount: number; -}; -export declare type GetApprovalsState = { - type: `${typeof controllerName}:getState`; - handler: () => ApprovalControllerState; -}; -export declare type ClearApprovalRequests = { - type: `${typeof controllerName}:clearRequests`; - handler: (error: EthereumRpcError) => void; -}; -declare type AddApprovalOptions = { - id?: string; - origin: string; - type: string; - requestData?: Record; -}; -export declare type AddApprovalRequest = { - type: `${typeof controllerName}:addRequest`; - handler: (opts: AddApprovalOptions, shouldShowRequest: boolean) => ReturnType; -}; -export declare type HasApprovalRequest = { - type: `${typeof controllerName}:hasRequest`; - handler: ApprovalController['has']; -}; -export declare type AcceptRequest = { - type: `${typeof controllerName}:acceptRequest`; - handler: ApprovalController['accept']; -}; -export declare type RejectRequest = { - type: `${typeof controllerName}:rejectRequest`; - handler: ApprovalController['reject']; -}; -export declare type ApprovalControllerActions = GetApprovalsState | ClearApprovalRequests | AddApprovalRequest | HasApprovalRequest | AcceptRequest | RejectRequest; -export declare type ApprovalStateChange = { - type: `${typeof controllerName}:stateChange`; - payload: [ApprovalControllerState, Patch[]]; -}; -export declare type ApprovalControllerEvents = ApprovalStateChange; -export declare type ApprovalControllerMessenger = RestrictedControllerMessenger; -declare type ApprovalControllerOptions = { - messenger: ApprovalControllerMessenger; - showApprovalRequest: ShowApprovalRequest; - state?: Partial; -}; -/** - * Controller for managing requests that require user approval. - * - * Enables limiting the number of pending requests by origin and type, counting - * pending requests, and more. - * - * Adding a request returns a promise that resolves or rejects when the request - * is approved or denied, respectively. - */ -export declare class ApprovalController extends BaseController { - private _approvals; - private _origins; - private _showApprovalRequest; - /** - * Construct an Approval controller. - * - * @param options - The controller options. - * @param options.showApprovalRequest - Function for opening the UI such that - * the request can be displayed to the user. - * @param options.messenger - The restricted controller messenger for the Approval controller. - * @param options.state - The initial controller state. - */ - constructor({ messenger, showApprovalRequest, state, }: ApprovalControllerOptions); - /** - * Constructor helper for registering this controller's messaging system - * actions. - */ - private registerMessageHandlers; - /** - * Adds an approval request per the given arguments, calls the show approval - * request function, and returns the associated approval promise. - * - * There can only be one approval per origin and type. An error is thrown if - * attempting to add an invalid or duplicate request. - * - * @param opts - Options bag. - * @param opts.id - The id of the approval request. A random id will be - * generated if none is provided. - * @param opts.origin - The origin of the approval request. - * @param opts.type - The type associated with the approval request. - * @param opts.requestData - Additional data associated with the request, - * if any. - * @returns The approval promise. - */ - addAndShowApprovalRequest(opts: AddApprovalOptions): Promise; - /** - * Adds an approval request per the given arguments and returns the approval - * promise. - * - * There can only be one approval per origin and type. An error is thrown if - * attempting to add an invalid or duplicate request. - * - * @param opts - Options bag. - * @param opts.id - The id of the approval request. A random id will be - * generated if none is provided. - * @param opts.origin - The origin of the approval request. - * @param opts.type - The type associated with the approval request. - * @param opts.requestData - Additional data associated with the request, - * if any. - * @returns The approval promise. - */ - add(opts: AddApprovalOptions): Promise; - /** - * Gets the info for the approval request with the given id. - * - * @param id - The id of the approval request. - * @returns The approval request data associated with the id. - */ - get(id: string): ApprovalRequest | undefined; - /** - * Gets the number of pending approvals, by origin and/or type. - * - * If only `origin` is specified, all approvals for that origin will be - * counted, regardless of type. - * If only `type` is specified, all approvals for that type will be counted, - * regardless of origin. - * If both `origin` and `type` are specified, 0 or 1 will be returned. - * - * @param opts - The approval count options. - * @param opts.origin - An approval origin. - * @param opts.type - The type of the approval request. - * @returns The current approval request count for the given origin and/or - * type. - */ - getApprovalCount(opts?: { - origin?: string; - type?: string; - }): number; - /** - * Get the total count of all pending approval requests for all origins. - * - * @returns The total pending approval request count. - */ - getTotalApprovalCount(): number; - /** - * Checks if there's a pending approval request per the given parameters. - * At least one parameter must be specified. An error will be thrown if the - * parameters are invalid. - * - * If `id` is specified, all other parameters will be ignored. - * If `id` is not specified, the method will check for requests that match - * all of the specified parameters. - * - * @param opts - Options bag. - * @param opts.id - The ID to check for. - * @param opts.origin - The origin to check for. - * @param opts.type - The type to check for. - * @returns `true` if a matching approval is found, and `false` otherwise. - */ - has(opts?: { - id?: string; - origin?: string; - type?: string; - }): boolean; - /** - * Resolves the promise of the approval with the given id, and deletes the - * approval. Throws an error if no such approval exists. - * - * @param id - The id of the approval request. - * @param value - The value to resolve the approval promise with. - */ - accept(id: string, value?: unknown): void; - /** - * Rejects the promise of the approval with the given id, and deletes the - * approval. Throws an error if no such approval exists. - * - * @param id - The id of the approval request. - * @param error - The error to reject the approval promise with. - */ - reject(id: string, error: unknown): void; - /** - * Rejects and deletes all approval requests. - * - * @param rejectionError - The EthereumRpcError to reject the approval - * requests with. - */ - clear(rejectionError: EthereumRpcError): void; - /** - * Implementation of add operation. - * - * @param origin - The origin of the approval request. - * @param type - The type associated with the approval request. - * @param id - The id of the approval request. - * @param requestData - The request data associated with the approval request. - * @returns The approval promise. - */ - private _add; - /** - * Validates parameters to the add method. - * - * @param id - The id of the approval request. - * @param origin - The origin of the approval request. - * @param type - The type associated with the approval request. - * @param requestData - The request data associated with the approval request. - */ - private _validateAddParams; - /** - * Adds an entry to _origins. - * Performs no validation. - * - * @param origin - The origin of the approval request. - * @param type - The type associated with the approval request. - */ - private _addPendingApprovalOrigin; - /** - * Adds an entry to the store. - * Performs no validation. - * - * @param id - The id of the approval request. - * @param origin - The origin of the approval request. - * @param type - The type associated with the approval request. - * @param requestData - The request data associated with the approval request. - */ - private _addToStore; - /** - * Deletes the approval with the given id. The approval promise must be - * resolved or reject before this method is called. - * Deletion is an internal operation because approval state is solely - * managed by this controller. - * - * @param id - The id of the approval request to be deleted. - */ - private _delete; - /** - * Gets the approval callbacks for the given id, deletes the entry, and then - * returns the callbacks for promise resolution. - * Throws an error if no approval is found for the given id. - * - * @param id - The id of the approval request. - * @returns The promise callbacks associated with the approval request. - */ - private _deleteApprovalAndGetCallbacks; - /** - * Checks whether there are any approvals associated with the given - * origin. - * - * @param origin - The origin to check. - * @returns True if the origin has no approvals, false otherwise. - */ - private _isEmptyOrigin; -} -export default ApprovalController; diff --git a/dist/approval/ApprovalController.js b/dist/approval/ApprovalController.js deleted file mode 100644 index 98d1ff2b9d..0000000000 --- a/dist/approval/ApprovalController.js +++ /dev/null @@ -1,381 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.ApprovalController = void 0; -const eth_rpc_errors_1 = require("eth-rpc-errors"); -const nanoid_1 = require("nanoid"); -const BaseControllerV2_1 = require("../BaseControllerV2"); -const controllerName = 'ApprovalController'; -const stateMetadata = { - pendingApprovals: { persist: false, anonymous: true }, - pendingApprovalCount: { persist: false, anonymous: false }, -}; -const getAlreadyPendingMessage = (origin, type) => `Request of type '${type}' already pending for origin ${origin}. Please wait.`; -const getDefaultState = () => { - return { - pendingApprovals: {}, - pendingApprovalCount: 0, - }; -}; -/** - * Controller for managing requests that require user approval. - * - * Enables limiting the number of pending requests by origin and type, counting - * pending requests, and more. - * - * Adding a request returns a promise that resolves or rejects when the request - * is approved or denied, respectively. - */ -class ApprovalController extends BaseControllerV2_1.BaseController { - /** - * Construct an Approval controller. - * - * @param options - The controller options. - * @param options.showApprovalRequest - Function for opening the UI such that - * the request can be displayed to the user. - * @param options.messenger - The restricted controller messenger for the Approval controller. - * @param options.state - The initial controller state. - */ - constructor({ messenger, showApprovalRequest, state = {}, }) { - super({ - name: controllerName, - metadata: stateMetadata, - messenger, - state: Object.assign(Object.assign({}, getDefaultState()), state), - }); - this._approvals = new Map(); - this._origins = new Map(); - this._showApprovalRequest = showApprovalRequest; - this.registerMessageHandlers(); - } - /** - * Constructor helper for registering this controller's messaging system - * actions. - */ - registerMessageHandlers() { - this.messagingSystem.registerActionHandler(`${controllerName}:clearRequests`, this.clear.bind(this)); - this.messagingSystem.registerActionHandler(`${controllerName}:addRequest`, (opts, shouldShowRequest) => { - if (shouldShowRequest) { - return this.addAndShowApprovalRequest(opts); - } - return this.add(opts); - }); - this.messagingSystem.registerActionHandler(`${controllerName}:hasRequest`, this.has.bind(this)); - this.messagingSystem.registerActionHandler(`${controllerName}:acceptRequest`, this.accept.bind(this)); - this.messagingSystem.registerActionHandler(`${controllerName}:rejectRequest`, this.reject.bind(this)); - } - /** - * Adds an approval request per the given arguments, calls the show approval - * request function, and returns the associated approval promise. - * - * There can only be one approval per origin and type. An error is thrown if - * attempting to add an invalid or duplicate request. - * - * @param opts - Options bag. - * @param opts.id - The id of the approval request. A random id will be - * generated if none is provided. - * @param opts.origin - The origin of the approval request. - * @param opts.type - The type associated with the approval request. - * @param opts.requestData - Additional data associated with the request, - * if any. - * @returns The approval promise. - */ - addAndShowApprovalRequest(opts) { - const promise = this._add(opts.origin, opts.type, opts.id, opts.requestData); - this._showApprovalRequest(); - return promise; - } - /** - * Adds an approval request per the given arguments and returns the approval - * promise. - * - * There can only be one approval per origin and type. An error is thrown if - * attempting to add an invalid or duplicate request. - * - * @param opts - Options bag. - * @param opts.id - The id of the approval request. A random id will be - * generated if none is provided. - * @param opts.origin - The origin of the approval request. - * @param opts.type - The type associated with the approval request. - * @param opts.requestData - Additional data associated with the request, - * if any. - * @returns The approval promise. - */ - add(opts) { - return this._add(opts.origin, opts.type, opts.id, opts.requestData); - } - /** - * Gets the info for the approval request with the given id. - * - * @param id - The id of the approval request. - * @returns The approval request data associated with the id. - */ - get(id) { - return this.state.pendingApprovals[id]; - } - /** - * Gets the number of pending approvals, by origin and/or type. - * - * If only `origin` is specified, all approvals for that origin will be - * counted, regardless of type. - * If only `type` is specified, all approvals for that type will be counted, - * regardless of origin. - * If both `origin` and `type` are specified, 0 or 1 will be returned. - * - * @param opts - The approval count options. - * @param opts.origin - An approval origin. - * @param opts.type - The type of the approval request. - * @returns The current approval request count for the given origin and/or - * type. - */ - getApprovalCount(opts = {}) { - var _a, _b; - if (!opts.origin && !opts.type) { - throw new Error('Must specify origin, type, or both.'); - } - const { origin, type: _type } = opts; - if (origin && _type) { - return Number(Boolean((_a = this._origins.get(origin)) === null || _a === void 0 ? void 0 : _a.has(_type))); - } - if (origin) { - return ((_b = this._origins.get(origin)) === null || _b === void 0 ? void 0 : _b.size) || 0; - } - // Only "type" was specified - let count = 0; - for (const approval of Object.values(this.state.pendingApprovals)) { - if (approval.type === _type) { - count += 1; - } - } - return count; - } - /** - * Get the total count of all pending approval requests for all origins. - * - * @returns The total pending approval request count. - */ - getTotalApprovalCount() { - return this.state.pendingApprovalCount; - } - /** - * Checks if there's a pending approval request per the given parameters. - * At least one parameter must be specified. An error will be thrown if the - * parameters are invalid. - * - * If `id` is specified, all other parameters will be ignored. - * If `id` is not specified, the method will check for requests that match - * all of the specified parameters. - * - * @param opts - Options bag. - * @param opts.id - The ID to check for. - * @param opts.origin - The origin to check for. - * @param opts.type - The type to check for. - * @returns `true` if a matching approval is found, and `false` otherwise. - */ - has(opts = {}) { - var _a; - const { id, origin, type: _type } = opts; - if (id) { - if (typeof id !== 'string') { - throw new Error('May not specify non-string id.'); - } - return this._approvals.has(id); - } - if (_type && typeof _type !== 'string') { - throw new Error('May not specify non-string type.'); - } - if (origin) { - if (typeof origin !== 'string') { - throw new Error('May not specify non-string origin.'); - } - // Check origin and type pair if type also specified - if (_type) { - return Boolean((_a = this._origins.get(origin)) === null || _a === void 0 ? void 0 : _a.has(_type)); - } - return this._origins.has(origin); - } - if (_type) { - for (const approval of Object.values(this.state.pendingApprovals)) { - if (approval.type === _type) { - return true; - } - } - return false; - } - throw new Error('Must specify a valid combination of id, origin, and type.'); - } - /** - * Resolves the promise of the approval with the given id, and deletes the - * approval. Throws an error if no such approval exists. - * - * @param id - The id of the approval request. - * @param value - The value to resolve the approval promise with. - */ - accept(id, value) { - this._deleteApprovalAndGetCallbacks(id).resolve(value); - } - /** - * Rejects the promise of the approval with the given id, and deletes the - * approval. Throws an error if no such approval exists. - * - * @param id - The id of the approval request. - * @param error - The error to reject the approval promise with. - */ - reject(id, error) { - this._deleteApprovalAndGetCallbacks(id).reject(error); - } - /** - * Rejects and deletes all approval requests. - * - * @param rejectionError - The EthereumRpcError to reject the approval - * requests with. - */ - clear(rejectionError) { - for (const id of this._approvals.keys()) { - this.reject(id, rejectionError); - } - this._origins.clear(); - this.update(() => getDefaultState()); - } - /** - * Implementation of add operation. - * - * @param origin - The origin of the approval request. - * @param type - The type associated with the approval request. - * @param id - The id of the approval request. - * @param requestData - The request data associated with the approval request. - * @returns The approval promise. - */ - _add(origin, type, id = (0, nanoid_1.nanoid)(), requestData) { - var _a; - this._validateAddParams(id, origin, type, requestData); - if ((_a = this._origins.get(origin)) === null || _a === void 0 ? void 0 : _a.has(type)) { - throw eth_rpc_errors_1.ethErrors.rpc.resourceUnavailable(getAlreadyPendingMessage(origin, type)); - } - // add pending approval - return new Promise((resolve, reject) => { - this._approvals.set(id, { resolve, reject }); - this._addPendingApprovalOrigin(origin, type); - this._addToStore(id, origin, type, requestData); - }); - } - /** - * Validates parameters to the add method. - * - * @param id - The id of the approval request. - * @param origin - The origin of the approval request. - * @param type - The type associated with the approval request. - * @param requestData - The request data associated with the approval request. - */ - _validateAddParams(id, origin, type, requestData) { - let errorMessage = null; - if (!id || typeof id !== 'string') { - errorMessage = 'Must specify non-empty string id.'; - } - else if (this._approvals.has(id)) { - errorMessage = `Approval request with id '${id}' already exists.`; - } - else if (!origin || typeof origin !== 'string') { - errorMessage = 'Must specify non-empty string origin.'; - } - else if (!type || typeof type !== 'string') { - errorMessage = 'Must specify non-empty string type.'; - } - else if (requestData && - (typeof requestData !== 'object' || Array.isArray(requestData))) { - errorMessage = 'Request data must be a plain object if specified.'; - } - if (errorMessage) { - throw eth_rpc_errors_1.ethErrors.rpc.internal(errorMessage); - } - } - /** - * Adds an entry to _origins. - * Performs no validation. - * - * @param origin - The origin of the approval request. - * @param type - The type associated with the approval request. - */ - _addPendingApprovalOrigin(origin, type) { - const originSet = this._origins.get(origin) || new Set(); - originSet.add(type); - if (!this._origins.has(origin)) { - this._origins.set(origin, originSet); - } - } - /** - * Adds an entry to the store. - * Performs no validation. - * - * @param id - The id of the approval request. - * @param origin - The origin of the approval request. - * @param type - The type associated with the approval request. - * @param requestData - The request data associated with the approval request. - */ - _addToStore(id, origin, type, requestData) { - const approval = { - id, - origin, - type, - time: Date.now(), - requestData: requestData || null, - }; - this.update((draftState) => { - // Typecast: ts(2589) - draftState.pendingApprovals[id] = approval; - draftState.pendingApprovalCount = Object.keys(draftState.pendingApprovals).length; - }); - } - /** - * Deletes the approval with the given id. The approval promise must be - * resolved or reject before this method is called. - * Deletion is an internal operation because approval state is solely - * managed by this controller. - * - * @param id - The id of the approval request to be deleted. - */ - _delete(id) { - this._approvals.delete(id); - // This method is only called after verifying that the approval with the - // specified id exists. - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const { origin, type } = this.state.pendingApprovals[id]; - this._origins.get(origin).delete(type); - if (this._isEmptyOrigin(origin)) { - this._origins.delete(origin); - } - this.update((draftState) => { - delete draftState.pendingApprovals[id]; - draftState.pendingApprovalCount = Object.keys(draftState.pendingApprovals).length; - }); - } - /** - * Gets the approval callbacks for the given id, deletes the entry, and then - * returns the callbacks for promise resolution. - * Throws an error if no approval is found for the given id. - * - * @param id - The id of the approval request. - * @returns The promise callbacks associated with the approval request. - */ - _deleteApprovalAndGetCallbacks(id) { - const callbacks = this._approvals.get(id); - if (!callbacks) { - throw new Error(`Approval request with id '${id}' not found.`); - } - this._delete(id); - return callbacks; - } - /** - * Checks whether there are any approvals associated with the given - * origin. - * - * @param origin - The origin to check. - * @returns True if the origin has no approvals, false otherwise. - */ - _isEmptyOrigin(origin) { - var _a; - return !((_a = this._origins.get(origin)) === null || _a === void 0 ? void 0 : _a.size); - } -} -exports.ApprovalController = ApprovalController; -exports.default = ApprovalController; -//# sourceMappingURL=ApprovalController.js.map \ No newline at end of file diff --git a/dist/approval/ApprovalController.js.map b/dist/approval/ApprovalController.js.map deleted file mode 100644 index 7f2093399f..0000000000 --- a/dist/approval/ApprovalController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"ApprovalController.js","sourceRoot":"","sources":["../../src/approval/ApprovalController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Patch } from 'immer';\nimport { EthereumRpcError, ethErrors } from 'eth-rpc-errors';\nimport { nanoid } from 'nanoid';\n\nimport { BaseController, Json } from '../BaseControllerV2';\nimport type { RestrictedControllerMessenger } from '../ControllerMessenger';\n\nconst controllerName = 'ApprovalController';\n\ntype ApprovalPromiseResolve = (value?: unknown) => void;\ntype ApprovalPromiseReject = (error?: unknown) => void;\n\ntype ApprovalRequestData = Record | null;\n\ntype ApprovalCallbacks = {\n resolve: ApprovalPromiseResolve;\n reject: ApprovalPromiseReject;\n};\n\nexport type ApprovalRequest = {\n /**\n * The ID of the approval request.\n */\n id: string;\n\n /**\n * The origin of the approval request.\n */\n origin: string;\n\n /**\n * The time that the request was received, per Date.now().\n */\n time: number;\n\n /**\n * The type of the approval request.\n */\n type: string;\n\n /**\n * Additional data associated with the request.\n * TODO:TS4.4 make optional\n */\n requestData: RequestData;\n};\n\ntype ShowApprovalRequest = () => void | Promise;\n\nexport type ApprovalControllerState = {\n pendingApprovals: Record>>;\n pendingApprovalCount: number;\n};\n\nconst stateMetadata = {\n pendingApprovals: { persist: false, anonymous: true },\n pendingApprovalCount: { persist: false, anonymous: false },\n};\n\nconst getAlreadyPendingMessage = (origin: string, type: string) =>\n `Request of type '${type}' already pending for origin ${origin}. Please wait.`;\n\nconst getDefaultState = (): ApprovalControllerState => {\n return {\n pendingApprovals: {},\n pendingApprovalCount: 0,\n };\n};\n\nexport type GetApprovalsState = {\n type: `${typeof controllerName}:getState`;\n handler: () => ApprovalControllerState;\n};\n\nexport type ClearApprovalRequests = {\n type: `${typeof controllerName}:clearRequests`;\n handler: (error: EthereumRpcError) => void;\n};\n\ntype AddApprovalOptions = {\n id?: string;\n origin: string;\n type: string;\n requestData?: Record;\n};\n\nexport type AddApprovalRequest = {\n type: `${typeof controllerName}:addRequest`;\n handler: (\n opts: AddApprovalOptions,\n shouldShowRequest: boolean,\n ) => ReturnType;\n};\n\nexport type HasApprovalRequest = {\n type: `${typeof controllerName}:hasRequest`;\n handler: ApprovalController['has'];\n};\n\nexport type AcceptRequest = {\n type: `${typeof controllerName}:acceptRequest`;\n handler: ApprovalController['accept'];\n};\n\nexport type RejectRequest = {\n type: `${typeof controllerName}:rejectRequest`;\n handler: ApprovalController['reject'];\n};\n\nexport type ApprovalControllerActions =\n | GetApprovalsState\n | ClearApprovalRequests\n | AddApprovalRequest\n | HasApprovalRequest\n | AcceptRequest\n | RejectRequest;\n\nexport type ApprovalStateChange = {\n type: `${typeof controllerName}:stateChange`;\n payload: [ApprovalControllerState, Patch[]];\n};\n\nexport type ApprovalControllerEvents = ApprovalStateChange;\n\nexport type ApprovalControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n ApprovalControllerActions,\n ApprovalControllerEvents,\n never,\n never\n>;\n\ntype ApprovalControllerOptions = {\n messenger: ApprovalControllerMessenger;\n showApprovalRequest: ShowApprovalRequest;\n state?: Partial;\n};\n\n/**\n * Controller for managing requests that require user approval.\n *\n * Enables limiting the number of pending requests by origin and type, counting\n * pending requests, and more.\n *\n * Adding a request returns a promise that resolves or rejects when the request\n * is approved or denied, respectively.\n */\nexport class ApprovalController extends BaseController<\n typeof controllerName,\n ApprovalControllerState,\n ApprovalControllerMessenger\n> {\n private _approvals: Map;\n\n private _origins: Map>;\n\n private _showApprovalRequest: () => void;\n\n /**\n * Construct an Approval controller.\n *\n * @param options - The controller options.\n * @param options.showApprovalRequest - Function for opening the UI such that\n * the request can be displayed to the user.\n * @param options.messenger - The restricted controller messenger for the Approval controller.\n * @param options.state - The initial controller state.\n */\n constructor({\n messenger,\n showApprovalRequest,\n state = {},\n }: ApprovalControllerOptions) {\n super({\n name: controllerName,\n metadata: stateMetadata,\n messenger,\n state: { ...getDefaultState(), ...state },\n });\n\n this._approvals = new Map();\n this._origins = new Map();\n this._showApprovalRequest = showApprovalRequest;\n this.registerMessageHandlers();\n }\n\n /**\n * Constructor helper for registering this controller's messaging system\n * actions.\n */\n private registerMessageHandlers(): void {\n this.messagingSystem.registerActionHandler(\n `${controllerName}:clearRequests` as const,\n this.clear.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:addRequest` as const,\n (opts: AddApprovalOptions, shouldShowRequest: boolean) => {\n if (shouldShowRequest) {\n return this.addAndShowApprovalRequest(opts);\n }\n return this.add(opts);\n },\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:hasRequest` as const,\n this.has.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:acceptRequest` as const,\n this.accept.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:rejectRequest` as const,\n this.reject.bind(this),\n );\n }\n\n /**\n * Adds an approval request per the given arguments, calls the show approval\n * request function, and returns the associated approval promise.\n *\n * There can only be one approval per origin and type. An error is thrown if\n * attempting to add an invalid or duplicate request.\n *\n * @param opts - Options bag.\n * @param opts.id - The id of the approval request. A random id will be\n * generated if none is provided.\n * @param opts.origin - The origin of the approval request.\n * @param opts.type - The type associated with the approval request.\n * @param opts.requestData - Additional data associated with the request,\n * if any.\n * @returns The approval promise.\n */\n addAndShowApprovalRequest(opts: AddApprovalOptions): Promise {\n const promise = this._add(\n opts.origin,\n opts.type,\n opts.id,\n opts.requestData,\n );\n this._showApprovalRequest();\n return promise;\n }\n\n /**\n * Adds an approval request per the given arguments and returns the approval\n * promise.\n *\n * There can only be one approval per origin and type. An error is thrown if\n * attempting to add an invalid or duplicate request.\n *\n * @param opts - Options bag.\n * @param opts.id - The id of the approval request. A random id will be\n * generated if none is provided.\n * @param opts.origin - The origin of the approval request.\n * @param opts.type - The type associated with the approval request.\n * @param opts.requestData - Additional data associated with the request,\n * if any.\n * @returns The approval promise.\n */\n add(opts: AddApprovalOptions): Promise {\n return this._add(opts.origin, opts.type, opts.id, opts.requestData);\n }\n\n /**\n * Gets the info for the approval request with the given id.\n *\n * @param id - The id of the approval request.\n * @returns The approval request data associated with the id.\n */\n get(id: string): ApprovalRequest | undefined {\n return this.state.pendingApprovals[id];\n }\n\n /**\n * Gets the number of pending approvals, by origin and/or type.\n *\n * If only `origin` is specified, all approvals for that origin will be\n * counted, regardless of type.\n * If only `type` is specified, all approvals for that type will be counted,\n * regardless of origin.\n * If both `origin` and `type` are specified, 0 or 1 will be returned.\n *\n * @param opts - The approval count options.\n * @param opts.origin - An approval origin.\n * @param opts.type - The type of the approval request.\n * @returns The current approval request count for the given origin and/or\n * type.\n */\n getApprovalCount(opts: { origin?: string; type?: string } = {}): number {\n if (!opts.origin && !opts.type) {\n throw new Error('Must specify origin, type, or both.');\n }\n const { origin, type: _type } = opts;\n\n if (origin && _type) {\n return Number(Boolean(this._origins.get(origin)?.has(_type)));\n }\n\n if (origin) {\n return this._origins.get(origin)?.size || 0;\n }\n\n // Only \"type\" was specified\n let count = 0;\n for (const approval of Object.values(this.state.pendingApprovals)) {\n if (approval.type === _type) {\n count += 1;\n }\n }\n return count;\n }\n\n /**\n * Get the total count of all pending approval requests for all origins.\n *\n * @returns The total pending approval request count.\n */\n getTotalApprovalCount(): number {\n return this.state.pendingApprovalCount;\n }\n\n /**\n * Checks if there's a pending approval request per the given parameters.\n * At least one parameter must be specified. An error will be thrown if the\n * parameters are invalid.\n *\n * If `id` is specified, all other parameters will be ignored.\n * If `id` is not specified, the method will check for requests that match\n * all of the specified parameters.\n *\n * @param opts - Options bag.\n * @param opts.id - The ID to check for.\n * @param opts.origin - The origin to check for.\n * @param opts.type - The type to check for.\n * @returns `true` if a matching approval is found, and `false` otherwise.\n */\n has(opts: { id?: string; origin?: string; type?: string } = {}): boolean {\n const { id, origin, type: _type } = opts;\n\n if (id) {\n if (typeof id !== 'string') {\n throw new Error('May not specify non-string id.');\n }\n return this._approvals.has(id);\n }\n\n if (_type && typeof _type !== 'string') {\n throw new Error('May not specify non-string type.');\n }\n\n if (origin) {\n if (typeof origin !== 'string') {\n throw new Error('May not specify non-string origin.');\n }\n\n // Check origin and type pair if type also specified\n if (_type) {\n return Boolean(this._origins.get(origin)?.has(_type));\n }\n return this._origins.has(origin);\n }\n\n if (_type) {\n for (const approval of Object.values(this.state.pendingApprovals)) {\n if (approval.type === _type) {\n return true;\n }\n }\n return false;\n }\n throw new Error(\n 'Must specify a valid combination of id, origin, and type.',\n );\n }\n\n /**\n * Resolves the promise of the approval with the given id, and deletes the\n * approval. Throws an error if no such approval exists.\n *\n * @param id - The id of the approval request.\n * @param value - The value to resolve the approval promise with.\n */\n accept(id: string, value?: unknown): void {\n this._deleteApprovalAndGetCallbacks(id).resolve(value);\n }\n\n /**\n * Rejects the promise of the approval with the given id, and deletes the\n * approval. Throws an error if no such approval exists.\n *\n * @param id - The id of the approval request.\n * @param error - The error to reject the approval promise with.\n */\n reject(id: string, error: unknown): void {\n this._deleteApprovalAndGetCallbacks(id).reject(error);\n }\n\n /**\n * Rejects and deletes all approval requests.\n *\n * @param rejectionError - The EthereumRpcError to reject the approval\n * requests with.\n */\n clear(rejectionError: EthereumRpcError): void {\n for (const id of this._approvals.keys()) {\n this.reject(id, rejectionError);\n }\n this._origins.clear();\n this.update(() => getDefaultState());\n }\n\n /**\n * Implementation of add operation.\n *\n * @param origin - The origin of the approval request.\n * @param type - The type associated with the approval request.\n * @param id - The id of the approval request.\n * @param requestData - The request data associated with the approval request.\n * @returns The approval promise.\n */\n private _add(\n origin: string,\n type: string,\n id: string = nanoid(),\n requestData?: Record,\n ): Promise {\n this._validateAddParams(id, origin, type, requestData);\n\n if (this._origins.get(origin)?.has(type)) {\n throw ethErrors.rpc.resourceUnavailable(\n getAlreadyPendingMessage(origin, type),\n );\n }\n\n // add pending approval\n return new Promise((resolve, reject) => {\n this._approvals.set(id, { resolve, reject });\n this._addPendingApprovalOrigin(origin, type);\n this._addToStore(id, origin, type, requestData);\n });\n }\n\n /**\n * Validates parameters to the add method.\n *\n * @param id - The id of the approval request.\n * @param origin - The origin of the approval request.\n * @param type - The type associated with the approval request.\n * @param requestData - The request data associated with the approval request.\n */\n private _validateAddParams(\n id: string,\n origin: string,\n type: string,\n requestData?: Record,\n ): void {\n let errorMessage = null;\n if (!id || typeof id !== 'string') {\n errorMessage = 'Must specify non-empty string id.';\n } else if (this._approvals.has(id)) {\n errorMessage = `Approval request with id '${id}' already exists.`;\n } else if (!origin || typeof origin !== 'string') {\n errorMessage = 'Must specify non-empty string origin.';\n } else if (!type || typeof type !== 'string') {\n errorMessage = 'Must specify non-empty string type.';\n } else if (\n requestData &&\n (typeof requestData !== 'object' || Array.isArray(requestData))\n ) {\n errorMessage = 'Request data must be a plain object if specified.';\n }\n\n if (errorMessage) {\n throw ethErrors.rpc.internal(errorMessage);\n }\n }\n\n /**\n * Adds an entry to _origins.\n * Performs no validation.\n *\n * @param origin - The origin of the approval request.\n * @param type - The type associated with the approval request.\n */\n private _addPendingApprovalOrigin(origin: string, type: string): void {\n const originSet = this._origins.get(origin) || new Set();\n originSet.add(type);\n\n if (!this._origins.has(origin)) {\n this._origins.set(origin, originSet);\n }\n }\n\n /**\n * Adds an entry to the store.\n * Performs no validation.\n *\n * @param id - The id of the approval request.\n * @param origin - The origin of the approval request.\n * @param type - The type associated with the approval request.\n * @param requestData - The request data associated with the approval request.\n */\n private _addToStore(\n id: string,\n origin: string,\n type: string,\n requestData?: Record,\n ): void {\n const approval: ApprovalRequest | null> = {\n id,\n origin,\n type,\n time: Date.now(),\n requestData: requestData || null,\n };\n\n this.update((draftState) => {\n // Typecast: ts(2589)\n draftState.pendingApprovals[id] = approval as any;\n draftState.pendingApprovalCount = Object.keys(\n draftState.pendingApprovals,\n ).length;\n });\n }\n\n /**\n * Deletes the approval with the given id. The approval promise must be\n * resolved or reject before this method is called.\n * Deletion is an internal operation because approval state is solely\n * managed by this controller.\n *\n * @param id - The id of the approval request to be deleted.\n */\n private _delete(id: string): void {\n this._approvals.delete(id);\n\n // This method is only called after verifying that the approval with the\n // specified id exists.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const { origin, type } = this.state.pendingApprovals[id]!;\n\n (this._origins.get(origin) as Set).delete(type);\n if (this._isEmptyOrigin(origin)) {\n this._origins.delete(origin);\n }\n\n this.update((draftState) => {\n delete draftState.pendingApprovals[id];\n draftState.pendingApprovalCount = Object.keys(\n draftState.pendingApprovals,\n ).length;\n });\n }\n\n /**\n * Gets the approval callbacks for the given id, deletes the entry, and then\n * returns the callbacks for promise resolution.\n * Throws an error if no approval is found for the given id.\n *\n * @param id - The id of the approval request.\n * @returns The promise callbacks associated with the approval request.\n */\n private _deleteApprovalAndGetCallbacks(id: string): ApprovalCallbacks {\n const callbacks = this._approvals.get(id);\n if (!callbacks) {\n throw new Error(`Approval request with id '${id}' not found.`);\n }\n\n this._delete(id);\n return callbacks;\n }\n\n /**\n * Checks whether there are any approvals associated with the given\n * origin.\n *\n * @param origin - The origin to check.\n * @returns True if the origin has no approvals, false otherwise.\n */\n private _isEmptyOrigin(origin: string): boolean {\n return !this._origins.get(origin)?.size;\n }\n}\nexport default ApprovalController;\n"]} \ No newline at end of file diff --git a/dist/assets/AccountTrackerController.d.ts b/dist/assets/AccountTrackerController.d.ts deleted file mode 100644 index 0e0d74cffb..0000000000 --- a/dist/assets/AccountTrackerController.d.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { BaseConfig, BaseController, BaseState } from '../BaseController'; -import { PreferencesState } from '../user/PreferencesController'; -/** - * @type AccountInformation - * - * Account information object - * @property balance - Hex string of an account balancec in wei - */ -export interface AccountInformation { - balance: string; -} -/** - * @type AccountTrackerConfig - * - * Account tracker controller configuration - * @property provider - Provider used to create a new underlying EthQuery instance - */ -export interface AccountTrackerConfig extends BaseConfig { - interval: number; - provider?: any; -} -/** - * @type AccountTrackerState - * - * Account tracker controller state - * @property accounts - Map of addresses to account information - */ -export interface AccountTrackerState extends BaseState { - accounts: { - [address: string]: AccountInformation; - }; -} -/** - * Controller that tracks information for all accounts in the current keychain - */ -export declare class AccountTrackerController extends BaseController { - private ethQuery; - private mutex; - private handle?; - private syncAccounts; - /** - * Name of this controller used during composition - */ - name: string; - private getIdentities; - /** - * Creates an AccountTracker instance. - * - * @param options - The controller options. - * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes. - * @param options.getIdentities - Gets the identities from the Preferences store. - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor({ onPreferencesStateChange, getIdentities, }: { - onPreferencesStateChange: (listener: (preferencesState: PreferencesState) => void) => void; - getIdentities: () => PreferencesState['identities']; - }, config?: Partial, state?: Partial); - /** - * Sets a new provider. - * - * TODO: Replace this wth a method. - * - * @param provider - Provider used to create a new underlying EthQuery instance. - */ - set provider(provider: any); - get provider(): any; - /** - * Starts a new polling interval. - * - * @param interval - Polling interval trigger a 'refresh'. - */ - poll(interval?: number): Promise; - /** - * Refreshes all accounts in the current keychain. - */ - refresh: () => Promise; - /** - * Sync accounts balances with some additional addresses. - * - * @param addresses - the additional addresses, may be hardware wallet addresses. - * @returns accounts - addresses with synced balance - */ - syncBalanceWithAddresses(addresses: string[]): Promise>; -} -export default AccountTrackerController; diff --git a/dist/assets/AccountTrackerController.js b/dist/assets/AccountTrackerController.js deleted file mode 100644 index 061e82665a..0000000000 --- a/dist/assets/AccountTrackerController.js +++ /dev/null @@ -1,138 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.AccountTrackerController = void 0; -const eth_query_1 = __importDefault(require("eth-query")); -const async_mutex_1 = require("async-mutex"); -const BaseController_1 = require("../BaseController"); -const util_1 = require("../util"); -/** - * Controller that tracks information for all accounts in the current keychain - */ -class AccountTrackerController extends BaseController_1.BaseController { - /** - * Creates an AccountTracker instance. - * - * @param options - The controller options. - * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes. - * @param options.getIdentities - Gets the identities from the Preferences store. - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor({ onPreferencesStateChange, getIdentities, }, config, state) { - super(config, state); - this.mutex = new async_mutex_1.Mutex(); - /** - * Name of this controller used during composition - */ - this.name = 'AccountTrackerController'; - /** - * Refreshes all accounts in the current keychain. - */ - this.refresh = () => __awaiter(this, void 0, void 0, function* () { - this.syncAccounts(); - const accounts = Object.assign({}, this.state.accounts); - for (const address in accounts) { - yield (0, util_1.safelyExecuteWithTimeout)(() => __awaiter(this, void 0, void 0, function* () { - const balance = yield (0, util_1.query)(this.ethQuery, 'getBalance', [address]); - accounts[address] = { balance: (0, util_1.BNToHex)(balance) }; - })); - } - this.update({ accounts }); - }); - this.defaultConfig = { - interval: 10000, - }; - this.defaultState = { accounts: {} }; - this.initialize(); - this.getIdentities = getIdentities; - onPreferencesStateChange(() => { - this.refresh(); - }); - this.poll(); - } - syncAccounts() { - const { accounts } = this.state; - const addresses = Object.keys(this.getIdentities()); - const existing = Object.keys(accounts); - const newAddresses = addresses.filter((address) => existing.indexOf(address) === -1); - const oldAddresses = existing.filter((address) => addresses.indexOf(address) === -1); - newAddresses.forEach((address) => { - accounts[address] = { balance: '0x0' }; - }); - oldAddresses.forEach((address) => { - delete accounts[address]; - }); - this.update({ accounts: Object.assign({}, accounts) }); - } - /** - * Sets a new provider. - * - * TODO: Replace this wth a method. - * - * @param provider - Provider used to create a new underlying EthQuery instance. - */ - set provider(provider) { - this.ethQuery = new eth_query_1.default(provider); - } - get provider() { - throw new Error('Property only used for setting'); - } - /** - * Starts a new polling interval. - * - * @param interval - Polling interval trigger a 'refresh'. - */ - poll(interval) { - return __awaiter(this, void 0, void 0, function* () { - const releaseLock = yield this.mutex.acquire(); - interval && this.configure({ interval }, false, false); - this.handle && clearTimeout(this.handle); - yield this.refresh(); - this.handle = setTimeout(() => { - releaseLock(); - this.poll(this.config.interval); - }, this.config.interval); - }); - } - /** - * Sync accounts balances with some additional addresses. - * - * @param addresses - the additional addresses, may be hardware wallet addresses. - * @returns accounts - addresses with synced balance - */ - syncBalanceWithAddresses(addresses) { - return __awaiter(this, void 0, void 0, function* () { - return yield Promise.all(addresses.map((address) => { - return (0, util_1.safelyExecuteWithTimeout)(() => __awaiter(this, void 0, void 0, function* () { - const balance = yield (0, util_1.query)(this.ethQuery, 'getBalance', [address]); - return [address, balance]; - })); - })).then((value) => { - return value.reduce((obj, item) => { - if (!item) { - return obj; - } - const [address, balance] = item; - return Object.assign(Object.assign({}, obj), { [address]: { - balance, - } }); - }, {}); - }); - }); - } -} -exports.AccountTrackerController = AccountTrackerController; -exports.default = AccountTrackerController; -//# sourceMappingURL=AccountTrackerController.js.map \ No newline at end of file diff --git a/dist/assets/AccountTrackerController.js.map b/dist/assets/AccountTrackerController.js.map deleted file mode 100644 index c720ca59e3..0000000000 --- a/dist/assets/AccountTrackerController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"AccountTrackerController.js","sourceRoot":"","sources":["../../src/assets/AccountTrackerController.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,0DAAiC;AACjC,6CAAoC;AACpC,sDAA0E;AAE1E,kCAAmE;AAiCnE;;GAEG;AACH,MAAa,wBAAyB,SAAQ,+BAG7C;IAkCC;;;;;;;;OAQG;IACH,YACE,EACE,wBAAwB,EACxB,aAAa,GAMd,EACD,MAAsC,EACtC,KAAoC;QAEpC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QArDf,UAAK,GAAG,IAAI,mBAAK,EAAE,CAAC;QAwB5B;;WAEG;QACM,SAAI,GAAG,0BAA0B,CAAC;QAsE3C;;WAEG;QACH,YAAO,GAAG,GAAS,EAAE;YACnB,IAAI,CAAC,YAAY,EAAE,CAAC;YACpB,MAAM,QAAQ,qBAAQ,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAE,CAAC;YAC5C,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE;gBAC9B,MAAM,IAAA,+BAAwB,EAAC,GAAS,EAAE;oBACxC,MAAM,OAAO,GAAG,MAAM,IAAA,YAAK,EAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;oBACpE,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,IAAA,cAAO,EAAC,OAAO,CAAC,EAAE,CAAC;gBACpD,CAAC,CAAA,CAAC,CAAC;aACJ;YACD,IAAI,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC5B,CAAC,CAAA,CAAC;QAxDA,IAAI,CAAC,aAAa,GAAG;YACnB,QAAQ,EAAE,KAAK;SAChB,CAAC;QACF,IAAI,CAAC,YAAY,GAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;QACrC,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,wBAAwB,CAAC,GAAG,EAAE;YAC5B,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IA5DO,YAAY;QAClB,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;QAChC,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;QACpD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvC,MAAM,YAAY,GAAG,SAAS,CAAC,MAAM,CACnC,CAAC,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAC9C,CAAC;QACF,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAClC,CAAC,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAC/C,CAAC;QACF,YAAY,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC/B,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QACzC,CAAC,CAAC,CAAC;QAEH,YAAY,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC/B,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,EAAE,QAAQ,oBAAO,QAAQ,CAAE,EAAE,CAAC,CAAC;IAC7C,CAAC;IA4CD;;;;;;OAMG;IACH,IAAI,QAAQ,CAAC,QAAa;QACxB,IAAI,CAAC,QAAQ,GAAG,IAAI,mBAAQ,CAAC,QAAQ,CAAC,CAAC;IACzC,CAAC;IAED,IAAI,QAAQ;QACV,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED;;;;OAIG;IACG,IAAI,CAAC,QAAiB;;YAC1B,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;YAC/C,QAAQ,IAAI,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;YACvD,IAAI,CAAC,MAAM,IAAI,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACzC,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACrB,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,WAAW,EAAE,CAAC;gBACd,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAClC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC3B,CAAC;KAAA;IAiBD;;;;;OAKG;IACG,wBAAwB,CAC5B,SAAmB;;YAEnB,OAAO,MAAM,OAAO,CAAC,GAAG,CACtB,SAAS,CAAC,GAAG,CAAC,CAAC,OAAO,EAAyC,EAAE;gBAC/D,OAAO,IAAA,+BAAwB,EAAC,GAAS,EAAE;oBACzC,MAAM,OAAO,GAAG,MAAM,IAAA,YAAK,EAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;oBACpE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBAC5B,CAAC,CAAA,CAAC,CAAC;YACL,CAAC,CAAC,CACH,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;gBACf,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;oBAChC,IAAI,CAAC,IAAI,EAAE;wBACT,OAAO,GAAG,CAAC;qBACZ;oBAED,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;oBAChC,uCACK,GAAG,KACN,CAAC,OAAO,CAAC,EAAE;4BACT,OAAO;yBACR,IACD;gBACJ,CAAC,EAAE,EAAE,CAAC,CAAC;YACT,CAAC,CAAC,CAAC;QACL,CAAC;KAAA;CACF;AAtJD,4DAsJC;AAED,kBAAe,wBAAwB,CAAC","sourcesContent":["import EthQuery from 'eth-query';\nimport { Mutex } from 'async-mutex';\nimport { BaseConfig, BaseController, BaseState } from '../BaseController';\nimport { PreferencesState } from '../user/PreferencesController';\nimport { BNToHex, query, safelyExecuteWithTimeout } from '../util';\n\n/**\n * @type AccountInformation\n *\n * Account information object\n * @property balance - Hex string of an account balancec in wei\n */\nexport interface AccountInformation {\n balance: string;\n}\n\n/**\n * @type AccountTrackerConfig\n *\n * Account tracker controller configuration\n * @property provider - Provider used to create a new underlying EthQuery instance\n */\nexport interface AccountTrackerConfig extends BaseConfig {\n interval: number;\n provider?: any;\n}\n\n/**\n * @type AccountTrackerState\n *\n * Account tracker controller state\n * @property accounts - Map of addresses to account information\n */\nexport interface AccountTrackerState extends BaseState {\n accounts: { [address: string]: AccountInformation };\n}\n\n/**\n * Controller that tracks information for all accounts in the current keychain\n */\nexport class AccountTrackerController extends BaseController<\n AccountTrackerConfig,\n AccountTrackerState\n> {\n private ethQuery: any;\n\n private mutex = new Mutex();\n\n private handle?: NodeJS.Timer;\n\n private syncAccounts() {\n const { accounts } = this.state;\n const addresses = Object.keys(this.getIdentities());\n const existing = Object.keys(accounts);\n const newAddresses = addresses.filter(\n (address) => existing.indexOf(address) === -1,\n );\n const oldAddresses = existing.filter(\n (address) => addresses.indexOf(address) === -1,\n );\n newAddresses.forEach((address) => {\n accounts[address] = { balance: '0x0' };\n });\n\n oldAddresses.forEach((address) => {\n delete accounts[address];\n });\n this.update({ accounts: { ...accounts } });\n }\n\n /**\n * Name of this controller used during composition\n */\n override name = 'AccountTrackerController';\n\n private getIdentities: () => PreferencesState['identities'];\n\n /**\n * Creates an AccountTracker instance.\n *\n * @param options - The controller options.\n * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes.\n * @param options.getIdentities - Gets the identities from the Preferences store.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n onPreferencesStateChange,\n getIdentities,\n }: {\n onPreferencesStateChange: (\n listener: (preferencesState: PreferencesState) => void,\n ) => void;\n getIdentities: () => PreferencesState['identities'];\n },\n config?: Partial,\n state?: Partial,\n ) {\n super(config, state);\n this.defaultConfig = {\n interval: 10000,\n };\n this.defaultState = { accounts: {} };\n this.initialize();\n this.getIdentities = getIdentities;\n onPreferencesStateChange(() => {\n this.refresh();\n });\n this.poll();\n }\n\n /**\n * Sets a new provider.\n *\n * TODO: Replace this wth a method.\n *\n * @param provider - Provider used to create a new underlying EthQuery instance.\n */\n set provider(provider: any) {\n this.ethQuery = new EthQuery(provider);\n }\n\n get provider() {\n throw new Error('Property only used for setting');\n }\n\n /**\n * Starts a new polling interval.\n *\n * @param interval - Polling interval trigger a 'refresh'.\n */\n async poll(interval?: number): Promise {\n const releaseLock = await this.mutex.acquire();\n interval && this.configure({ interval }, false, false);\n this.handle && clearTimeout(this.handle);\n await this.refresh();\n this.handle = setTimeout(() => {\n releaseLock();\n this.poll(this.config.interval);\n }, this.config.interval);\n }\n\n /**\n * Refreshes all accounts in the current keychain.\n */\n refresh = async () => {\n this.syncAccounts();\n const accounts = { ...this.state.accounts };\n for (const address in accounts) {\n await safelyExecuteWithTimeout(async () => {\n const balance = await query(this.ethQuery, 'getBalance', [address]);\n accounts[address] = { balance: BNToHex(balance) };\n });\n }\n this.update({ accounts });\n };\n\n /**\n * Sync accounts balances with some additional addresses.\n *\n * @param addresses - the additional addresses, may be hardware wallet addresses.\n * @returns accounts - addresses with synced balance\n */\n async syncBalanceWithAddresses(\n addresses: string[],\n ): Promise> {\n return await Promise.all(\n addresses.map((address): Promise<[string, string] | undefined> => {\n return safelyExecuteWithTimeout(async () => {\n const balance = await query(this.ethQuery, 'getBalance', [address]);\n return [address, balance];\n });\n }),\n ).then((value) => {\n return value.reduce((obj, item) => {\n if (!item) {\n return obj;\n }\n\n const [address, balance] = item;\n return {\n ...obj,\n [address]: {\n balance,\n },\n };\n }, {});\n });\n }\n}\n\nexport default AccountTrackerController;\n"]} \ No newline at end of file diff --git a/dist/assets/AssetsContractController.d.ts b/dist/assets/AssetsContractController.d.ts deleted file mode 100644 index b4b6336683..0000000000 --- a/dist/assets/AssetsContractController.d.ts +++ /dev/null @@ -1,176 +0,0 @@ -/// -import { BN } from 'ethereumjs-util'; -import { BaseController, BaseConfig, BaseState } from '../BaseController'; -import type { PreferencesState } from '../user/PreferencesController'; -import { NetworkState } from '../network/NetworkController'; -/** - * Check if token detection is enabled for certain networks - * - * @param chainId - ChainID of network - * @returns Whether the current network supports token detection - */ -export declare const SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID: Record; -export declare const MISSING_PROVIDER_ERROR = "AssetsContractController failed to set the provider correctly. A provider must be set for this method to be available"; -/** - * @type AssetsContractConfig - * - * Assets Contract controller configuration - * @property provider - Provider used to create a new web3 instance - */ -export interface AssetsContractConfig extends BaseConfig { - provider: any; - ipfsGateway: string; - chainId: string; -} -/** - * @type BalanceMap - * - * Key value object containing the balance for each tokenAddress - * @property [tokenAddress] - Address of the token - */ -export interface BalanceMap { - [tokenAddress: string]: BN; -} -/** - * Controller that interacts with contracts on mainnet through web3 - */ -export declare class AssetsContractController extends BaseController { - private web3; - private erc721Standard?; - private erc1155Standard?; - private erc20Standard?; - /** - * Name of this controller used during composition - */ - name: string; - /** - * Creates a AssetsContractController instance. - * - * @param options - The controller options. - * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes. - * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor({ onPreferencesStateChange, onNetworkStateChange, }: { - onPreferencesStateChange: (listener: (preferencesState: PreferencesState) => void) => void; - onNetworkStateChange: (listener: (networkState: NetworkState) => void) => void; - }, config?: Partial, state?: Partial); - /** - * Sets a new provider. - * - * TODO: Replace this wth a method. - * - * @property provider - Provider used to create a new underlying Web3 instance - */ - set provider(provider: any); - get provider(): any; - /** - * Get balance or count for current account on specific asset contract. - * - * @param address - Asset ERC20 contract address. - * @param selectedAddress - Current account public address. - * @returns Promise resolving to BN object containing balance for current account on specific asset contract. - */ - getERC20BalanceOf(address: string, selectedAddress: string): Promise; - /** - * Query for the decimals for a given ERC20 asset. - * - * @param address - ERC20 asset contract address. - * @returns Promise resolving to the 'decimals'. - */ - getERC20TokenDecimals(address: string): Promise; - /** - * Enumerate assets assigned to an owner. - * - * @param address - ERC721 asset contract address. - * @param selectedAddress - Current account public address. - * @param index - A collectible counter less than `balanceOf(selectedAddress)`. - * @returns Promise resolving to token identifier for the 'index'th asset assigned to 'selectedAddress'. - */ - getERC721CollectibleTokenId(address: string, selectedAddress: string, index: number): Promise; - /** - * Enumerate assets assigned to an owner. - * - * @param tokenAddress - ERC721 asset contract address. - * @param userAddress - Current account public address. - * @param tokenId - ERC721 asset identifier. - * @returns Promise resolving to an object containing the token standard and a set of details which depend on which standard the token supports. - */ - getTokenStandardAndDetails(tokenAddress: string, userAddress?: string, tokenId?: string): Promise<{ - standard: string; - tokenURI?: string | undefined; - symbol?: string | undefined; - name?: string | undefined; - decimals?: string | undefined; - balance?: BN | undefined; - }>; - /** - * Query for tokenURI for a given ERC721 asset. - * - * @param address - ERC721 asset contract address. - * @param tokenId - ERC721 asset identifier. - * @returns Promise resolving to the 'tokenURI'. - */ - getERC721TokenURI(address: string, tokenId: string): Promise; - /** - * Query for name for a given asset. - * - * @param address - ERC721 or ERC20 asset contract address. - * @returns Promise resolving to the 'name'. - */ - getERC721AssetName(address: string): Promise; - /** - * Query for symbol for a given asset. - * - * @param address - ERC721 or ERC20 asset contract address. - * @returns Promise resolving to the 'symbol'. - */ - getERC721AssetSymbol(address: string): Promise; - /** - * Query for owner for a given ERC721 asset. - * - * @param address - ERC721 asset contract address. - * @param tokenId - ERC721 asset identifier. - * @returns Promise resolving to the owner address. - */ - getERC721OwnerOf(address: string, tokenId: string): Promise; - /** - * Query for tokenURI for a given asset. - * - * @param address - ERC1155 asset contract address. - * @param tokenId - ERC1155 asset identifier. - * @returns Promise resolving to the 'tokenURI'. - */ - getERC1155TokenURI(address: string, tokenId: string): Promise; - /** - * Query for balance of a given ERC 1155 token. - * - * @param userAddress - Wallet public address. - * @param collectibleAddress - ERC1155 asset contract address. - * @param collectibleId - ERC1155 asset identifier. - * @returns Promise resolving to the 'balanceOf'. - */ - getERC1155BalanceOf(userAddress: string, collectibleAddress: string, collectibleId: string): Promise; - /** - * Transfer single ERC1155 token. - * - * @param collectibleAddress - ERC1155 token address. - * @param senderAddress - ERC1155 token sender. - * @param recipientAddress - ERC1155 token recipient. - * @param collectibleId - ERC1155 token id. - * @param qty - Quantity of tokens to be sent. - * @returns Promise resolving to the 'transferSingle' ERC1155 token. - */ - transferSingleERC1155(collectibleAddress: string, senderAddress: string, recipientAddress: string, collectibleId: string, qty: string): Promise; - /** - * Get the token balance for a list of token addresses in a single call. Only non-zero balances - * are returned. - * - * @param selectedAddress - The address to check token balances for. - * @param tokensToDetect - The token addresses to detect balances for. - * @returns The list of non-zero token balances. - */ - getBalancesInSingleCall(selectedAddress: string, tokensToDetect: string[]): Promise; -} -export default AssetsContractController; diff --git a/dist/assets/AssetsContractController.js b/dist/assets/AssetsContractController.js deleted file mode 100644 index fbc36f61ea..0000000000 --- a/dist/assets/AssetsContractController.js +++ /dev/null @@ -1,323 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.AssetsContractController = exports.MISSING_PROVIDER_ERROR = exports.SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID = void 0; -const web3_1 = __importDefault(require("web3")); -const single_call_balance_checker_abi_1 = __importDefault(require("single-call-balance-checker-abi")); -const BaseController_1 = require("../BaseController"); -const constants_1 = require("../constants"); -const util_1 = require("../util"); -const ERC721Standard_1 = require("./Standards/CollectibleStandards/ERC721/ERC721Standard"); -const ERC1155Standard_1 = require("./Standards/CollectibleStandards/ERC1155/ERC1155Standard"); -const ERC20Standard_1 = require("./Standards/ERC20Standard"); -/** - * Check if token detection is enabled for certain networks - * - * @param chainId - ChainID of network - * @returns Whether the current network supports token detection - */ -exports.SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID = { - [util_1.SupportedTokenDetectionNetworks.mainnet]: '0xb1f8e55c7f64d203c1400b9d8555d050f94adf39', - [util_1.SupportedTokenDetectionNetworks.bsc]: '0x2352c63A83f9Fd126af8676146721Fa00924d7e4', - [util_1.SupportedTokenDetectionNetworks.polygon]: '0x2352c63A83f9Fd126af8676146721Fa00924d7e4', - [util_1.SupportedTokenDetectionNetworks.avax]: '0xD023D153a0DFa485130ECFdE2FAA7e612EF94818', -}; -exports.MISSING_PROVIDER_ERROR = 'AssetsContractController failed to set the provider correctly. A provider must be set for this method to be available'; -/** - * Controller that interacts with contracts on mainnet through web3 - */ -class AssetsContractController extends BaseController_1.BaseController { - /** - * Creates a AssetsContractController instance. - * - * @param options - The controller options. - * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes. - * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor({ onPreferencesStateChange, onNetworkStateChange, }, config, state) { - super(config, state); - /** - * Name of this controller used during composition - */ - this.name = 'AssetsContractController'; - this.defaultConfig = { - provider: undefined, - ipfsGateway: constants_1.IPFS_DEFAULT_GATEWAY_URL, - chainId: util_1.SupportedTokenDetectionNetworks.mainnet, - }; - this.initialize(); - onPreferencesStateChange(({ ipfsGateway }) => { - this.configure({ ipfsGateway }); - }); - onNetworkStateChange((networkState) => { - if (this.config.chainId !== networkState.provider.chainId) { - this.configure({ - chainId: networkState.provider.chainId, - }); - } - }); - } - /** - * Sets a new provider. - * - * TODO: Replace this wth a method. - * - * @property provider - Provider used to create a new underlying Web3 instance - */ - set provider(provider) { - this.web3 = new web3_1.default(provider); - this.erc721Standard = new ERC721Standard_1.ERC721Standard(this.web3); - this.erc1155Standard = new ERC1155Standard_1.ERC1155Standard(this.web3); - this.erc20Standard = new ERC20Standard_1.ERC20Standard(this.web3); - } - get provider() { - throw new Error('Property only used for setting'); - } - /** - * Get balance or count for current account on specific asset contract. - * - * @param address - Asset ERC20 contract address. - * @param selectedAddress - Current account public address. - * @returns Promise resolving to BN object containing balance for current account on specific asset contract. - */ - getERC20BalanceOf(address, selectedAddress) { - return __awaiter(this, void 0, void 0, function* () { - if (!this.erc20Standard) { - throw new Error(exports.MISSING_PROVIDER_ERROR); - } - return this.erc20Standard.getBalanceOf(address, selectedAddress); - }); - } - /** - * Query for the decimals for a given ERC20 asset. - * - * @param address - ERC20 asset contract address. - * @returns Promise resolving to the 'decimals'. - */ - getERC20TokenDecimals(address) { - return __awaiter(this, void 0, void 0, function* () { - if (this.erc20Standard === undefined) { - throw new Error(exports.MISSING_PROVIDER_ERROR); - } - return yield this.erc20Standard.getTokenDecimals(address); - }); - } - /** - * Enumerate assets assigned to an owner. - * - * @param address - ERC721 asset contract address. - * @param selectedAddress - Current account public address. - * @param index - A collectible counter less than `balanceOf(selectedAddress)`. - * @returns Promise resolving to token identifier for the 'index'th asset assigned to 'selectedAddress'. - */ - getERC721CollectibleTokenId(address, selectedAddress, index) { - if (this.erc721Standard === undefined) { - throw new Error(exports.MISSING_PROVIDER_ERROR); - } - return this.erc721Standard.getCollectibleTokenId(address, selectedAddress, index); - } - /** - * Enumerate assets assigned to an owner. - * - * @param tokenAddress - ERC721 asset contract address. - * @param userAddress - Current account public address. - * @param tokenId - ERC721 asset identifier. - * @returns Promise resolving to an object containing the token standard and a set of details which depend on which standard the token supports. - */ - getTokenStandardAndDetails(tokenAddress, userAddress, tokenId) { - return __awaiter(this, void 0, void 0, function* () { - if (this.erc721Standard === undefined || - this.erc1155Standard === undefined || - this.erc20Standard === undefined) { - throw new Error(exports.MISSING_PROVIDER_ERROR); - } - const { ipfsGateway } = this.config; - // ERC721 - try { - return Object.assign({}, (yield this.erc721Standard.getDetails(tokenAddress, ipfsGateway, tokenId))); - } - catch (_a) { - // Ignore - } - // ERC1155 - try { - return Object.assign({}, (yield this.erc1155Standard.getDetails(tokenAddress, ipfsGateway, tokenId))); - } - catch (_b) { - // Ignore - } - // ERC20 - try { - return Object.assign({}, (yield this.erc20Standard.getDetails(tokenAddress, userAddress))); - } - catch (_c) { - // Ignore - } - throw new Error('Unable to determine contract standard'); - }); - } - /** - * Query for tokenURI for a given ERC721 asset. - * - * @param address - ERC721 asset contract address. - * @param tokenId - ERC721 asset identifier. - * @returns Promise resolving to the 'tokenURI'. - */ - getERC721TokenURI(address, tokenId) { - return __awaiter(this, void 0, void 0, function* () { - if (this.erc721Standard === undefined) { - throw new Error(exports.MISSING_PROVIDER_ERROR); - } - return this.erc721Standard.getTokenURI(address, tokenId); - }); - } - /** - * Query for name for a given asset. - * - * @param address - ERC721 or ERC20 asset contract address. - * @returns Promise resolving to the 'name'. - */ - getERC721AssetName(address) { - return __awaiter(this, void 0, void 0, function* () { - if (this.erc721Standard === undefined) { - throw new Error(exports.MISSING_PROVIDER_ERROR); - } - return this.erc721Standard.getAssetName(address); - }); - } - /** - * Query for symbol for a given asset. - * - * @param address - ERC721 or ERC20 asset contract address. - * @returns Promise resolving to the 'symbol'. - */ - getERC721AssetSymbol(address) { - return __awaiter(this, void 0, void 0, function* () { - if (this.erc721Standard === undefined) { - throw new Error(exports.MISSING_PROVIDER_ERROR); - } - return this.erc721Standard.getAssetSymbol(address); - }); - } - /** - * Query for owner for a given ERC721 asset. - * - * @param address - ERC721 asset contract address. - * @param tokenId - ERC721 asset identifier. - * @returns Promise resolving to the owner address. - */ - getERC721OwnerOf(address, tokenId) { - return __awaiter(this, void 0, void 0, function* () { - if (this.erc721Standard === undefined) { - throw new Error(exports.MISSING_PROVIDER_ERROR); - } - return this.erc721Standard.getOwnerOf(address, tokenId); - }); - } - /** - * Query for tokenURI for a given asset. - * - * @param address - ERC1155 asset contract address. - * @param tokenId - ERC1155 asset identifier. - * @returns Promise resolving to the 'tokenURI'. - */ - getERC1155TokenURI(address, tokenId) { - return __awaiter(this, void 0, void 0, function* () { - if (this.erc1155Standard === undefined) { - throw new Error(exports.MISSING_PROVIDER_ERROR); - } - return this.erc1155Standard.getTokenURI(address, tokenId); - }); - } - /** - * Query for balance of a given ERC 1155 token. - * - * @param userAddress - Wallet public address. - * @param collectibleAddress - ERC1155 asset contract address. - * @param collectibleId - ERC1155 asset identifier. - * @returns Promise resolving to the 'balanceOf'. - */ - getERC1155BalanceOf(userAddress, collectibleAddress, collectibleId) { - return __awaiter(this, void 0, void 0, function* () { - if (this.erc1155Standard === undefined) { - throw new Error(exports.MISSING_PROVIDER_ERROR); - } - return yield this.erc1155Standard.getBalanceOf(collectibleAddress, userAddress, collectibleId); - }); - } - /** - * Transfer single ERC1155 token. - * - * @param collectibleAddress - ERC1155 token address. - * @param senderAddress - ERC1155 token sender. - * @param recipientAddress - ERC1155 token recipient. - * @param collectibleId - ERC1155 token id. - * @param qty - Quantity of tokens to be sent. - * @returns Promise resolving to the 'transferSingle' ERC1155 token. - */ - transferSingleERC1155(collectibleAddress, senderAddress, recipientAddress, collectibleId, qty) { - return __awaiter(this, void 0, void 0, function* () { - if (this.erc1155Standard === undefined) { - throw new Error(exports.MISSING_PROVIDER_ERROR); - } - return yield this.erc1155Standard.transferSingle(collectibleAddress, senderAddress, recipientAddress, collectibleId, qty); - }); - } - /** - * Get the token balance for a list of token addresses in a single call. Only non-zero balances - * are returned. - * - * @param selectedAddress - The address to check token balances for. - * @param tokensToDetect - The token addresses to detect balances for. - * @returns The list of non-zero token balances. - */ - getBalancesInSingleCall(selectedAddress, tokensToDetect) { - return __awaiter(this, void 0, void 0, function* () { - if (!(this.config.chainId in exports.SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID)) { - // Only fetch balance if contract address exists - return {}; - } - const contractAddress = exports.SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID[this.config.chainId]; - const contract = this.web3.eth - .contract(single_call_balance_checker_abi_1.default) - .at(contractAddress); - return new Promise((resolve, reject) => { - contract.balances([selectedAddress], tokensToDetect, (error, result) => { - /* istanbul ignore if */ - if (error) { - reject(error); - return; - } - const nonZeroBalances = {}; - /* istanbul ignore else */ - if (result.length > 0) { - tokensToDetect.forEach((tokenAddress, index) => { - const balance = result[index]; - /* istanbul ignore else */ - if (String(balance) !== '0') { - nonZeroBalances[tokenAddress] = balance; - } - }); - } - resolve(nonZeroBalances); - }); - }); - }); - } -} -exports.AssetsContractController = AssetsContractController; -exports.default = AssetsContractController; -//# sourceMappingURL=AssetsContractController.js.map \ No newline at end of file diff --git a/dist/assets/AssetsContractController.js.map b/dist/assets/AssetsContractController.js.map deleted file mode 100644 index b9108fe04d..0000000000 --- a/dist/assets/AssetsContractController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"AssetsContractController.js","sourceRoot":"","sources":["../../src/assets/AssetsContractController.ts"],"names":[],"mappings":"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","sourcesContent":["import { BN } from 'ethereumjs-util';\nimport Web3 from 'web3';\nimport abiSingleCallBalancesContract from 'single-call-balance-checker-abi';\nimport { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport type { PreferencesState } from '../user/PreferencesController';\nimport { IPFS_DEFAULT_GATEWAY_URL } from '../constants';\nimport { SupportedTokenDetectionNetworks } from '../util';\nimport { NetworkState } from '../network/NetworkController';\nimport { ERC721Standard } from './Standards/CollectibleStandards/ERC721/ERC721Standard';\nimport { ERC1155Standard } from './Standards/CollectibleStandards/ERC1155/ERC1155Standard';\nimport { ERC20Standard } from './Standards/ERC20Standard';\n\n/**\n * Check if token detection is enabled for certain networks\n *\n * @param chainId - ChainID of network\n * @returns Whether the current network supports token detection\n */\nexport const SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID: Record = {\n [SupportedTokenDetectionNetworks.mainnet]:\n '0xb1f8e55c7f64d203c1400b9d8555d050f94adf39',\n [SupportedTokenDetectionNetworks.bsc]:\n '0x2352c63A83f9Fd126af8676146721Fa00924d7e4',\n [SupportedTokenDetectionNetworks.polygon]:\n '0x2352c63A83f9Fd126af8676146721Fa00924d7e4',\n [SupportedTokenDetectionNetworks.avax]:\n '0xD023D153a0DFa485130ECFdE2FAA7e612EF94818',\n};\n\nexport const MISSING_PROVIDER_ERROR =\n 'AssetsContractController failed to set the provider correctly. A provider must be set for this method to be available';\n\n/**\n * @type AssetsContractConfig\n *\n * Assets Contract controller configuration\n * @property provider - Provider used to create a new web3 instance\n */\nexport interface AssetsContractConfig extends BaseConfig {\n provider: any;\n ipfsGateway: string;\n chainId: string;\n}\n\n/**\n * @type BalanceMap\n *\n * Key value object containing the balance for each tokenAddress\n * @property [tokenAddress] - Address of the token\n */\nexport interface BalanceMap {\n [tokenAddress: string]: BN;\n}\n\n/**\n * Controller that interacts with contracts on mainnet through web3\n */\nexport class AssetsContractController extends BaseController<\n AssetsContractConfig,\n BaseState\n> {\n private web3: any;\n\n private erc721Standard?: ERC721Standard;\n\n private erc1155Standard?: ERC1155Standard;\n\n private erc20Standard?: ERC20Standard;\n\n /**\n * Name of this controller used during composition\n */\n override name = 'AssetsContractController';\n\n /**\n * Creates a AssetsContractController instance.\n *\n * @param options - The controller options.\n * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes.\n * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n onPreferencesStateChange,\n onNetworkStateChange,\n }: {\n onPreferencesStateChange: (\n listener: (preferencesState: PreferencesState) => void,\n ) => void;\n onNetworkStateChange: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n },\n config?: Partial,\n state?: Partial,\n ) {\n super(config, state);\n this.defaultConfig = {\n provider: undefined,\n ipfsGateway: IPFS_DEFAULT_GATEWAY_URL,\n chainId: SupportedTokenDetectionNetworks.mainnet,\n };\n this.initialize();\n\n onPreferencesStateChange(({ ipfsGateway }) => {\n this.configure({ ipfsGateway });\n });\n\n onNetworkStateChange((networkState) => {\n if (this.config.chainId !== networkState.provider.chainId) {\n this.configure({\n chainId: networkState.provider.chainId,\n });\n }\n });\n }\n\n /**\n * Sets a new provider.\n *\n * TODO: Replace this wth a method.\n *\n * @property provider - Provider used to create a new underlying Web3 instance\n */\n set provider(provider: any) {\n this.web3 = new Web3(provider);\n this.erc721Standard = new ERC721Standard(this.web3);\n this.erc1155Standard = new ERC1155Standard(this.web3);\n this.erc20Standard = new ERC20Standard(this.web3);\n }\n\n get provider() {\n throw new Error('Property only used for setting');\n }\n\n /**\n * Get balance or count for current account on specific asset contract.\n *\n * @param address - Asset ERC20 contract address.\n * @param selectedAddress - Current account public address.\n * @returns Promise resolving to BN object containing balance for current account on specific asset contract.\n */\n async getERC20BalanceOf(\n address: string,\n selectedAddress: string,\n ): Promise {\n if (!this.erc20Standard) {\n throw new Error(MISSING_PROVIDER_ERROR);\n }\n return this.erc20Standard.getBalanceOf(address, selectedAddress);\n }\n\n /**\n * Query for the decimals for a given ERC20 asset.\n *\n * @param address - ERC20 asset contract address.\n * @returns Promise resolving to the 'decimals'.\n */\n async getERC20TokenDecimals(address: string): Promise {\n if (this.erc20Standard === undefined) {\n throw new Error(MISSING_PROVIDER_ERROR);\n }\n return await this.erc20Standard.getTokenDecimals(address);\n }\n\n /**\n * Enumerate assets assigned to an owner.\n *\n * @param address - ERC721 asset contract address.\n * @param selectedAddress - Current account public address.\n * @param index - A collectible counter less than `balanceOf(selectedAddress)`.\n * @returns Promise resolving to token identifier for the 'index'th asset assigned to 'selectedAddress'.\n */\n getERC721CollectibleTokenId(\n address: string,\n selectedAddress: string,\n index: number,\n ): Promise {\n if (this.erc721Standard === undefined) {\n throw new Error(MISSING_PROVIDER_ERROR);\n }\n return this.erc721Standard.getCollectibleTokenId(\n address,\n selectedAddress,\n index,\n );\n }\n\n /**\n * Enumerate assets assigned to an owner.\n *\n * @param tokenAddress - ERC721 asset contract address.\n * @param userAddress - Current account public address.\n * @param tokenId - ERC721 asset identifier.\n * @returns Promise resolving to an object containing the token standard and a set of details which depend on which standard the token supports.\n */\n async getTokenStandardAndDetails(\n tokenAddress: string,\n userAddress?: string,\n tokenId?: string,\n ): Promise<{\n standard: string;\n tokenURI?: string | undefined;\n symbol?: string | undefined;\n name?: string | undefined;\n decimals?: string | undefined;\n balance?: BN | undefined;\n }> {\n if (\n this.erc721Standard === undefined ||\n this.erc1155Standard === undefined ||\n this.erc20Standard === undefined\n ) {\n throw new Error(MISSING_PROVIDER_ERROR);\n }\n\n const { ipfsGateway } = this.config;\n\n // ERC721\n try {\n return {\n ...(await this.erc721Standard.getDetails(\n tokenAddress,\n ipfsGateway,\n tokenId,\n )),\n };\n } catch {\n // Ignore\n }\n\n // ERC1155\n try {\n return {\n ...(await this.erc1155Standard.getDetails(\n tokenAddress,\n ipfsGateway,\n tokenId,\n )),\n };\n } catch {\n // Ignore\n }\n\n // ERC20\n try {\n return {\n ...(await this.erc20Standard.getDetails(tokenAddress, userAddress)),\n };\n } catch {\n // Ignore\n }\n\n throw new Error('Unable to determine contract standard');\n }\n\n /**\n * Query for tokenURI for a given ERC721 asset.\n *\n * @param address - ERC721 asset contract address.\n * @param tokenId - ERC721 asset identifier.\n * @returns Promise resolving to the 'tokenURI'.\n */\n async getERC721TokenURI(address: string, tokenId: string): Promise {\n if (this.erc721Standard === undefined) {\n throw new Error(MISSING_PROVIDER_ERROR);\n }\n return this.erc721Standard.getTokenURI(address, tokenId);\n }\n\n /**\n * Query for name for a given asset.\n *\n * @param address - ERC721 or ERC20 asset contract address.\n * @returns Promise resolving to the 'name'.\n */\n async getERC721AssetName(address: string): Promise {\n if (this.erc721Standard === undefined) {\n throw new Error(MISSING_PROVIDER_ERROR);\n }\n return this.erc721Standard.getAssetName(address);\n }\n\n /**\n * Query for symbol for a given asset.\n *\n * @param address - ERC721 or ERC20 asset contract address.\n * @returns Promise resolving to the 'symbol'.\n */\n async getERC721AssetSymbol(address: string): Promise {\n if (this.erc721Standard === undefined) {\n throw new Error(MISSING_PROVIDER_ERROR);\n }\n return this.erc721Standard.getAssetSymbol(address);\n }\n\n /**\n * Query for owner for a given ERC721 asset.\n *\n * @param address - ERC721 asset contract address.\n * @param tokenId - ERC721 asset identifier.\n * @returns Promise resolving to the owner address.\n */\n async getERC721OwnerOf(address: string, tokenId: string): Promise {\n if (this.erc721Standard === undefined) {\n throw new Error(MISSING_PROVIDER_ERROR);\n }\n return this.erc721Standard.getOwnerOf(address, tokenId);\n }\n\n /**\n * Query for tokenURI for a given asset.\n *\n * @param address - ERC1155 asset contract address.\n * @param tokenId - ERC1155 asset identifier.\n * @returns Promise resolving to the 'tokenURI'.\n */\n async getERC1155TokenURI(address: string, tokenId: string): Promise {\n if (this.erc1155Standard === undefined) {\n throw new Error(MISSING_PROVIDER_ERROR);\n }\n return this.erc1155Standard.getTokenURI(address, tokenId);\n }\n\n /**\n * Query for balance of a given ERC 1155 token.\n *\n * @param userAddress - Wallet public address.\n * @param collectibleAddress - ERC1155 asset contract address.\n * @param collectibleId - ERC1155 asset identifier.\n * @returns Promise resolving to the 'balanceOf'.\n */\n async getERC1155BalanceOf(\n userAddress: string,\n collectibleAddress: string,\n collectibleId: string,\n ): Promise {\n if (this.erc1155Standard === undefined) {\n throw new Error(MISSING_PROVIDER_ERROR);\n }\n return await this.erc1155Standard.getBalanceOf(\n collectibleAddress,\n userAddress,\n collectibleId,\n );\n }\n\n /**\n * Transfer single ERC1155 token.\n *\n * @param collectibleAddress - ERC1155 token address.\n * @param senderAddress - ERC1155 token sender.\n * @param recipientAddress - ERC1155 token recipient.\n * @param collectibleId - ERC1155 token id.\n * @param qty - Quantity of tokens to be sent.\n * @returns Promise resolving to the 'transferSingle' ERC1155 token.\n */\n async transferSingleERC1155(\n collectibleAddress: string,\n senderAddress: string,\n recipientAddress: string,\n collectibleId: string,\n qty: string,\n ): Promise {\n if (this.erc1155Standard === undefined) {\n throw new Error(MISSING_PROVIDER_ERROR);\n }\n return await this.erc1155Standard.transferSingle(\n collectibleAddress,\n senderAddress,\n recipientAddress,\n collectibleId,\n qty,\n );\n }\n\n /**\n * Get the token balance for a list of token addresses in a single call. Only non-zero balances\n * are returned.\n *\n * @param selectedAddress - The address to check token balances for.\n * @param tokensToDetect - The token addresses to detect balances for.\n * @returns The list of non-zero token balances.\n */\n async getBalancesInSingleCall(\n selectedAddress: string,\n tokensToDetect: string[],\n ) {\n if (!(this.config.chainId in SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID)) {\n // Only fetch balance if contract address exists\n return {};\n }\n const contractAddress =\n SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID[this.config.chainId];\n\n const contract = this.web3.eth\n .contract(abiSingleCallBalancesContract)\n .at(contractAddress);\n return new Promise((resolve, reject) => {\n contract.balances(\n [selectedAddress],\n tokensToDetect,\n (error: Error, result: BN[]) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n const nonZeroBalances: BalanceMap = {};\n /* istanbul ignore else */\n if (result.length > 0) {\n tokensToDetect.forEach((tokenAddress, index) => {\n const balance: BN = result[index];\n /* istanbul ignore else */\n if (String(balance) !== '0') {\n nonZeroBalances[tokenAddress] = balance;\n }\n });\n }\n resolve(nonZeroBalances);\n },\n );\n });\n }\n}\n\nexport default AssetsContractController;\n"]} \ No newline at end of file diff --git a/dist/assets/CollectibleDetectionController.d.ts b/dist/assets/CollectibleDetectionController.d.ts deleted file mode 100644 index 8dfded142e..0000000000 --- a/dist/assets/CollectibleDetectionController.d.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { BaseController, BaseConfig, BaseState } from '../BaseController'; -import type { NetworkState, NetworkType } from '../network/NetworkController'; -import type { PreferencesState } from '../user/PreferencesController'; -import type { CollectiblesController, CollectiblesState } from './CollectiblesController'; -/** - * @type ApiCollectible - * - * Collectible object coming from OpenSea api - * @property token_id - The collectible identifier - * @property num_sales - Number of sales - * @property background_color - The background color to be displayed with the item - * @property image_url - URI of an image associated with this collectible - * @property image_preview_url - URI of a smaller image associated with this collectible - * @property image_thumbnail_url - URI of a thumbnail image associated with this collectible - * @property image_original_url - URI of the original image associated with this collectible - * @property animation_url - URI of a animation associated with this collectible - * @property animation_original_url - URI of the original animation associated with this collectible - * @property name - The collectible name - * @property description - The collectible description - * @property external_link - External link containing additional information - * @property assetContract - The collectible contract information object - * @property creator - The collectible owner information object - * @property lastSale - When this item was last sold - */ -export interface ApiCollectible { - token_id: string; - num_sales: number | null; - background_color: string | null; - image_url: string | null; - image_preview_url: string | null; - image_thumbnail_url: string | null; - image_original_url: string | null; - animation_url: string | null; - animation_original_url: string | null; - name: string | null; - description: string | null; - external_link: string | null; - asset_contract: ApiCollectibleContract; - creator: ApiCollectibleCreator; - last_sale: ApiCollectibleLastSale | null; -} -/** - * @type ApiCollectibleContract - * - * Collectible contract object coming from OpenSea api - * @property address - Address of the collectible contract - * @property asset_contract_type - The collectible type, it could be `semi-fungible` or `non-fungible` - * @property created_date - Creation date - * @property collection - Object containing the contract name and URI of an image associated - * @property schema_name - The schema followed by the contract, it could be `ERC721` or `ERC1155` - * @property symbol - The collectible contract symbol - * @property total_supply - Total supply of collectibles - * @property description - The collectible contract description - * @property external_link - External link containing additional information - */ -export interface ApiCollectibleContract { - address: string; - asset_contract_type: string | null; - created_date: string | null; - schema_name: string | null; - symbol: string | null; - total_supply: string | null; - description: string | null; - external_link: string | null; - collection: { - name: string | null; - image_url?: string | null; - }; -} -/** - * @type ApiCollectibleLastSale - * - * Collectible sale object coming from OpenSea api - * @property event_timestamp - Object containing a `username` - * @property total_price - URI of collectible image associated with this owner - * @property transaction - Object containing transaction_hash and block_hash - */ -export interface ApiCollectibleLastSale { - event_timestamp: string; - total_price: string; - transaction: { - transaction_hash: string; - block_hash: string; - }; -} -/** - * @type ApiCollectibleCreator - * - * Collectible creator object coming from OpenSea api - * @property user - Object containing a `username` - * @property profile_img_url - URI of collectible image associated with this owner - * @property address - The owner address - */ -export interface ApiCollectibleCreator { - user: { - username: string; - }; - profile_img_url: string; - address: string; -} -/** - * @type CollectibleDetectionConfig - * - * CollectibleDetection configuration - * @property interval - Polling interval used to fetch new token rates - * @property networkType - Network type ID as per net_version - * @property selectedAddress - Vault selected address - * @property tokens - List of tokens associated with the active vault - */ -export interface CollectibleDetectionConfig extends BaseConfig { - interval: number; - networkType: NetworkType; - chainId: `0x${string}` | `${number}` | number; - selectedAddress: string; -} -/** - * Controller that passively polls on a set interval for Collectibles auto detection - */ -export declare class CollectibleDetectionController extends BaseController { - private intervalId?; - private getOwnerCollectiblesApi; - private getOwnerCollectibles; - /** - * Name of this controller used during composition - */ - name: string; - private getOpenSeaApiKey; - private addCollectible; - private getCollectiblesState; - /** - * Creates a CollectibleDetectionController instance. - * - * @param options - The controller options. - * @param options.onCollectiblesStateChange - Allows subscribing to assets controller state changes. - * @param options.onPreferencesStateChange - Allows subscribing to preferences controller state changes. - * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. - * @param options.getOpenSeaApiKey - Gets the OpenSea API key, if one is set. - * @param options.addCollectible - Add a collectible. - * @param options.getCollectiblesState - Gets the current state of the Assets controller. - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor({ onPreferencesStateChange, onNetworkStateChange, getOpenSeaApiKey, addCollectible, getCollectiblesState, }: { - onCollectiblesStateChange: (listener: (collectiblesState: CollectiblesState) => void) => void; - onPreferencesStateChange: (listener: (preferencesState: PreferencesState) => void) => void; - onNetworkStateChange: (listener: (networkState: NetworkState) => void) => void; - getOpenSeaApiKey: () => string | undefined; - addCollectible: CollectiblesController['addCollectible']; - getCollectiblesState: () => CollectiblesState; - }, config?: Partial, state?: Partial); - /** - * Start polling for the currency rate. - */ - start(): Promise; - /** - * Stop polling for the currency rate. - */ - stop(): void; - private stopPolling; - /** - * Starts a new polling interval. - * - * @param interval - An interval on which to poll. - */ - private startPolling; - /** - * Checks whether network is mainnet or not. - * - * @returns Whether current network is mainnet. - */ - isMainnet: () => boolean; - /** - * Triggers asset ERC721 token auto detection on mainnet. Any newly detected collectibles are - * added. - */ - detectCollectibles(): Promise; -} -export default CollectibleDetectionController; diff --git a/dist/assets/CollectibleDetectionController.js b/dist/assets/CollectibleDetectionController.js deleted file mode 100644 index 85b76fd0a2..0000000000 --- a/dist/assets/CollectibleDetectionController.js +++ /dev/null @@ -1,205 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.CollectibleDetectionController = void 0; -const BaseController_1 = require("../BaseController"); -const util_1 = require("../util"); -const constants_1 = require("../constants"); -const DEFAULT_INTERVAL = 180000; -/** - * Controller that passively polls on a set interval for Collectibles auto detection - */ -class CollectibleDetectionController extends BaseController_1.BaseController { - /** - * Creates a CollectibleDetectionController instance. - * - * @param options - The controller options. - * @param options.onCollectiblesStateChange - Allows subscribing to assets controller state changes. - * @param options.onPreferencesStateChange - Allows subscribing to preferences controller state changes. - * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. - * @param options.getOpenSeaApiKey - Gets the OpenSea API key, if one is set. - * @param options.addCollectible - Add a collectible. - * @param options.getCollectiblesState - Gets the current state of the Assets controller. - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor({ onPreferencesStateChange, onNetworkStateChange, getOpenSeaApiKey, addCollectible, getCollectiblesState, }, config, state) { - super(config, state); - /** - * Name of this controller used during composition - */ - this.name = 'CollectibleDetectionController'; - /** - * Checks whether network is mainnet or not. - * - * @returns Whether current network is mainnet. - */ - this.isMainnet = () => this.config.networkType === constants_1.MAINNET; - this.defaultConfig = { - interval: DEFAULT_INTERVAL, - networkType: constants_1.MAINNET, - chainId: '1', - selectedAddress: '', - disabled: true, - }; - this.initialize(); - this.getCollectiblesState = getCollectiblesState; - onPreferencesStateChange(({ selectedAddress, useCollectibleDetection }) => { - const { selectedAddress: previouslySelectedAddress, disabled } = this.config; - if (selectedAddress !== previouslySelectedAddress || - !useCollectibleDetection !== disabled) { - this.configure({ selectedAddress, disabled: !useCollectibleDetection }); - } - if (useCollectibleDetection !== undefined) { - if (useCollectibleDetection) { - this.start(); - } - else { - this.stop(); - } - } - }); - onNetworkStateChange(({ provider }) => { - this.configure({ - networkType: provider.type, - chainId: provider.chainId, - }); - }); - this.getOpenSeaApiKey = getOpenSeaApiKey; - this.addCollectible = addCollectible; - } - getOwnerCollectiblesApi({ address, offset, useProxy, }) { - return useProxy - ? `${constants_1.OPENSEA_PROXY_URL}/assets?owner=${address}&offset=${offset}&limit=50` - : `${constants_1.OPENSEA_API_URL}/assets?owner=${address}&offset=${offset}&limit=50`; - } - getOwnerCollectibles(address) { - var _a; - return __awaiter(this, void 0, void 0, function* () { - let collectiblesApiResponse; - let collectibles = []; - const openSeaApiKey = this.getOpenSeaApiKey(); - let offset = 0; - let pagingFinish = false; - /* istanbul ignore if */ - do { - collectiblesApiResponse = yield (0, util_1.fetchWithErrorHandling)({ - url: this.getOwnerCollectiblesApi({ address, offset, useProxy: true }), - timeout: 15000, - }); - if (openSeaApiKey && !collectiblesApiResponse) { - collectiblesApiResponse = yield (0, util_1.fetchWithErrorHandling)({ - url: this.getOwnerCollectiblesApi({ - address, - offset, - useProxy: false, - }), - options: { headers: { 'X-API-KEY': openSeaApiKey } }, - timeout: 15000, - // catch 403 errors (in case API key is down we don't want to blow up) - errorCodesToCatch: [403], - }); - } - if (!collectiblesApiResponse) { - return collectibles; - } - ((_a = collectiblesApiResponse === null || collectiblesApiResponse === void 0 ? void 0 : collectiblesApiResponse.assets) === null || _a === void 0 ? void 0 : _a.length) !== 0 - ? (collectibles = [...collectibles, ...collectiblesApiResponse.assets]) - : (pagingFinish = true); - offset += 50; - } while (!pagingFinish); - return collectibles; - }); - } - /** - * Start polling for the currency rate. - */ - start() { - return __awaiter(this, void 0, void 0, function* () { - if (!this.isMainnet() || this.disabled) { - return; - } - yield this.startPolling(); - }); - } - /** - * Stop polling for the currency rate. - */ - stop() { - this.stopPolling(); - } - stopPolling() { - if (this.intervalId) { - clearInterval(this.intervalId); - } - } - /** - * Starts a new polling interval. - * - * @param interval - An interval on which to poll. - */ - startPolling(interval) { - return __awaiter(this, void 0, void 0, function* () { - interval && this.configure({ interval }, false, false); - this.stopPolling(); - yield this.detectCollectibles(); - this.intervalId = setInterval(() => __awaiter(this, void 0, void 0, function* () { - yield this.detectCollectibles(); - }), this.config.interval); - }); - } - /** - * Triggers asset ERC721 token auto detection on mainnet. Any newly detected collectibles are - * added. - */ - detectCollectibles() { - return __awaiter(this, void 0, void 0, function* () { - /* istanbul ignore if */ - if (!this.isMainnet() || this.disabled) { - return; - } - const { selectedAddress, chainId } = this.config; - /* istanbul ignore else */ - if (!selectedAddress) { - return; - } - const apiCollectibles = yield this.getOwnerCollectibles(selectedAddress); - const addCollectiblesPromises = apiCollectibles.map((collectible) => __awaiter(this, void 0, void 0, function* () { - const { token_id, num_sales, background_color, image_url, image_preview_url, image_thumbnail_url, image_original_url, animation_url, animation_original_url, name, description, external_link, creator, asset_contract: { address, schema_name }, last_sale, } = collectible; - let ignored; - /* istanbul ignore else */ - const { ignoredCollectibles } = this.getCollectiblesState(); - if (ignoredCollectibles.length) { - ignored = ignoredCollectibles.find((c) => { - /* istanbul ignore next */ - return (c.address === (0, util_1.toChecksumHexAddress)(address) && - c.tokenId === token_id); - }); - } - /* istanbul ignore else */ - if (!ignored) { - /* istanbul ignore next */ - const collectibleMetadata = Object.assign({}, { name }, creator && { creator }, description && { description }, image_url && { image: image_url }, num_sales && { numberOfSales: num_sales }, background_color && { backgroundColor: background_color }, image_preview_url && { imagePreview: image_preview_url }, image_thumbnail_url && { imageThumbnail: image_thumbnail_url }, image_original_url && { imageOriginal: image_original_url }, animation_url && { animation: animation_url }, animation_original_url && { - animationOriginal: animation_original_url, - }, schema_name && { standard: schema_name }, external_link && { externalLink: external_link }, last_sale && { lastSale: last_sale }); - yield this.addCollectible(address, token_id, collectibleMetadata, { - userAddress: selectedAddress, - chainId: chainId, - }); - } - })); - yield Promise.all(addCollectiblesPromises); - }); - } -} -exports.CollectibleDetectionController = CollectibleDetectionController; -exports.default = CollectibleDetectionController; -//# sourceMappingURL=CollectibleDetectionController.js.map \ No newline at end of file diff --git a/dist/assets/CollectibleDetectionController.js.map b/dist/assets/CollectibleDetectionController.js.map deleted file mode 100644 index e3f6a79057..0000000000 --- a/dist/assets/CollectibleDetectionController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"CollectibleDetectionController.js","sourceRoot":"","sources":["../../src/assets/CollectibleDetectionController.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport type { NetworkState, NetworkType } from '../network/NetworkController';\nimport type { PreferencesState } from '../user/PreferencesController';\nimport { fetchWithErrorHandling, toChecksumHexAddress } from '../util';\nimport { MAINNET, OPENSEA_PROXY_URL, OPENSEA_API_URL } from '../constants';\n\nimport type {\n CollectiblesController,\n CollectiblesState,\n CollectibleMetadata,\n} from './CollectiblesController';\n\nconst DEFAULT_INTERVAL = 180000;\n\n/**\n * @type ApiCollectible\n *\n * Collectible object coming from OpenSea api\n * @property token_id - The collectible identifier\n * @property num_sales - Number of sales\n * @property background_color - The background color to be displayed with the item\n * @property image_url - URI of an image associated with this collectible\n * @property image_preview_url - URI of a smaller image associated with this collectible\n * @property image_thumbnail_url - URI of a thumbnail image associated with this collectible\n * @property image_original_url - URI of the original image associated with this collectible\n * @property animation_url - URI of a animation associated with this collectible\n * @property animation_original_url - URI of the original animation associated with this collectible\n * @property name - The collectible name\n * @property description - The collectible description\n * @property external_link - External link containing additional information\n * @property assetContract - The collectible contract information object\n * @property creator - The collectible owner information object\n * @property lastSale - When this item was last sold\n */\nexport interface ApiCollectible {\n token_id: string;\n num_sales: number | null;\n background_color: string | null;\n image_url: string | null;\n image_preview_url: string | null;\n image_thumbnail_url: string | null;\n image_original_url: string | null;\n animation_url: string | null;\n animation_original_url: string | null;\n name: string | null;\n description: string | null;\n external_link: string | null;\n asset_contract: ApiCollectibleContract;\n creator: ApiCollectibleCreator;\n last_sale: ApiCollectibleLastSale | null;\n}\n\n/**\n * @type ApiCollectibleContract\n *\n * Collectible contract object coming from OpenSea api\n * @property address - Address of the collectible contract\n * @property asset_contract_type - The collectible type, it could be `semi-fungible` or `non-fungible`\n * @property created_date - Creation date\n * @property collection - Object containing the contract name and URI of an image associated\n * @property schema_name - The schema followed by the contract, it could be `ERC721` or `ERC1155`\n * @property symbol - The collectible contract symbol\n * @property total_supply - Total supply of collectibles\n * @property description - The collectible contract description\n * @property external_link - External link containing additional information\n */\nexport interface ApiCollectibleContract {\n address: string;\n asset_contract_type: string | null;\n created_date: string | null;\n schema_name: string | null;\n symbol: string | null;\n total_supply: string | null;\n description: string | null;\n external_link: string | null;\n collection: {\n name: string | null;\n image_url?: string | null;\n };\n}\n\n/**\n * @type ApiCollectibleLastSale\n *\n * Collectible sale object coming from OpenSea api\n * @property event_timestamp - Object containing a `username`\n * @property total_price - URI of collectible image associated with this owner\n * @property transaction - Object containing transaction_hash and block_hash\n */\nexport interface ApiCollectibleLastSale {\n event_timestamp: string;\n total_price: string;\n transaction: { transaction_hash: string; block_hash: string };\n}\n\n/**\n * @type ApiCollectibleCreator\n *\n * Collectible creator object coming from OpenSea api\n * @property user - Object containing a `username`\n * @property profile_img_url - URI of collectible image associated with this owner\n * @property address - The owner address\n */\nexport interface ApiCollectibleCreator {\n user: { username: string };\n profile_img_url: string;\n address: string;\n}\n\n/**\n * @type CollectibleDetectionConfig\n *\n * CollectibleDetection configuration\n * @property interval - Polling interval used to fetch new token rates\n * @property networkType - Network type ID as per net_version\n * @property selectedAddress - Vault selected address\n * @property tokens - List of tokens associated with the active vault\n */\nexport interface CollectibleDetectionConfig extends BaseConfig {\n interval: number;\n networkType: NetworkType;\n chainId: `0x${string}` | `${number}` | number;\n selectedAddress: string;\n}\n\n/**\n * Controller that passively polls on a set interval for Collectibles auto detection\n */\nexport class CollectibleDetectionController extends BaseController<\n CollectibleDetectionConfig,\n BaseState\n> {\n private intervalId?: NodeJS.Timeout;\n\n private getOwnerCollectiblesApi({\n address,\n offset,\n useProxy,\n }: {\n address: string;\n offset: number;\n useProxy: boolean;\n }) {\n return useProxy\n ? `${OPENSEA_PROXY_URL}/assets?owner=${address}&offset=${offset}&limit=50`\n : `${OPENSEA_API_URL}/assets?owner=${address}&offset=${offset}&limit=50`;\n }\n\n private async getOwnerCollectibles(address: string) {\n let collectiblesApiResponse: { assets: ApiCollectible[] };\n let collectibles: ApiCollectible[] = [];\n const openSeaApiKey = this.getOpenSeaApiKey();\n let offset = 0;\n let pagingFinish = false;\n /* istanbul ignore if */\n do {\n collectiblesApiResponse = await fetchWithErrorHandling({\n url: this.getOwnerCollectiblesApi({ address, offset, useProxy: true }),\n timeout: 15000,\n });\n\n if (openSeaApiKey && !collectiblesApiResponse) {\n collectiblesApiResponse = await fetchWithErrorHandling({\n url: this.getOwnerCollectiblesApi({\n address,\n offset,\n useProxy: false,\n }),\n options: { headers: { 'X-API-KEY': openSeaApiKey } },\n timeout: 15000,\n // catch 403 errors (in case API key is down we don't want to blow up)\n errorCodesToCatch: [403],\n });\n }\n\n if (!collectiblesApiResponse) {\n return collectibles;\n }\n\n collectiblesApiResponse?.assets?.length !== 0\n ? (collectibles = [...collectibles, ...collectiblesApiResponse.assets])\n : (pagingFinish = true);\n offset += 50;\n } while (!pagingFinish);\n\n return collectibles;\n }\n\n /**\n * Name of this controller used during composition\n */\n override name = 'CollectibleDetectionController';\n\n private getOpenSeaApiKey: () => string | undefined;\n\n private addCollectible: CollectiblesController['addCollectible'];\n\n private getCollectiblesState: () => CollectiblesState;\n\n /**\n * Creates a CollectibleDetectionController instance.\n *\n * @param options - The controller options.\n * @param options.onCollectiblesStateChange - Allows subscribing to assets controller state changes.\n * @param options.onPreferencesStateChange - Allows subscribing to preferences controller state changes.\n * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.\n * @param options.getOpenSeaApiKey - Gets the OpenSea API key, if one is set.\n * @param options.addCollectible - Add a collectible.\n * @param options.getCollectiblesState - Gets the current state of the Assets controller.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n onPreferencesStateChange,\n onNetworkStateChange,\n getOpenSeaApiKey,\n addCollectible,\n getCollectiblesState,\n }: {\n onCollectiblesStateChange: (\n listener: (collectiblesState: CollectiblesState) => void,\n ) => void;\n onPreferencesStateChange: (\n listener: (preferencesState: PreferencesState) => void,\n ) => void;\n onNetworkStateChange: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n getOpenSeaApiKey: () => string | undefined;\n addCollectible: CollectiblesController['addCollectible'];\n getCollectiblesState: () => CollectiblesState;\n },\n config?: Partial,\n state?: Partial,\n ) {\n super(config, state);\n this.defaultConfig = {\n interval: DEFAULT_INTERVAL,\n networkType: MAINNET,\n chainId: '1',\n selectedAddress: '',\n disabled: true,\n };\n this.initialize();\n this.getCollectiblesState = getCollectiblesState;\n onPreferencesStateChange(({ selectedAddress, useCollectibleDetection }) => {\n const { selectedAddress: previouslySelectedAddress, disabled } =\n this.config;\n\n if (\n selectedAddress !== previouslySelectedAddress ||\n !useCollectibleDetection !== disabled\n ) {\n this.configure({ selectedAddress, disabled: !useCollectibleDetection });\n }\n\n if (useCollectibleDetection !== undefined) {\n if (useCollectibleDetection) {\n this.start();\n } else {\n this.stop();\n }\n }\n });\n\n onNetworkStateChange(({ provider }) => {\n this.configure({\n networkType: provider.type,\n chainId: provider.chainId as CollectibleDetectionConfig['chainId'],\n });\n });\n this.getOpenSeaApiKey = getOpenSeaApiKey;\n this.addCollectible = addCollectible;\n }\n\n /**\n * Start polling for the currency rate.\n */\n async start() {\n if (!this.isMainnet() || this.disabled) {\n return;\n }\n\n await this.startPolling();\n }\n\n /**\n * Stop polling for the currency rate.\n */\n stop() {\n this.stopPolling();\n }\n\n private stopPolling() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n }\n\n /**\n * Starts a new polling interval.\n *\n * @param interval - An interval on which to poll.\n */\n private async startPolling(interval?: number): Promise {\n interval && this.configure({ interval }, false, false);\n this.stopPolling();\n await this.detectCollectibles();\n this.intervalId = setInterval(async () => {\n await this.detectCollectibles();\n }, this.config.interval);\n }\n\n /**\n * Checks whether network is mainnet or not.\n *\n * @returns Whether current network is mainnet.\n */\n isMainnet = (): boolean => this.config.networkType === MAINNET;\n\n /**\n * Triggers asset ERC721 token auto detection on mainnet. Any newly detected collectibles are\n * added.\n */\n async detectCollectibles() {\n /* istanbul ignore if */\n if (!this.isMainnet() || this.disabled) {\n return;\n }\n const { selectedAddress, chainId } = this.config;\n\n /* istanbul ignore else */\n if (!selectedAddress) {\n return;\n }\n\n const apiCollectibles = await this.getOwnerCollectibles(selectedAddress);\n const addCollectiblesPromises = apiCollectibles.map(\n async (collectible: ApiCollectible) => {\n const {\n token_id,\n num_sales,\n background_color,\n image_url,\n image_preview_url,\n image_thumbnail_url,\n image_original_url,\n animation_url,\n animation_original_url,\n name,\n description,\n external_link,\n creator,\n asset_contract: { address, schema_name },\n last_sale,\n } = collectible;\n\n let ignored;\n /* istanbul ignore else */\n const { ignoredCollectibles } = this.getCollectiblesState();\n if (ignoredCollectibles.length) {\n ignored = ignoredCollectibles.find((c) => {\n /* istanbul ignore next */\n return (\n c.address === toChecksumHexAddress(address) &&\n c.tokenId === token_id\n );\n });\n }\n\n /* istanbul ignore else */\n if (!ignored) {\n /* istanbul ignore next */\n const collectibleMetadata: CollectibleMetadata = Object.assign(\n {},\n { name },\n creator && { creator },\n description && { description },\n image_url && { image: image_url },\n num_sales && { numberOfSales: num_sales },\n background_color && { backgroundColor: background_color },\n image_preview_url && { imagePreview: image_preview_url },\n image_thumbnail_url && { imageThumbnail: image_thumbnail_url },\n image_original_url && { imageOriginal: image_original_url },\n animation_url && { animation: animation_url },\n animation_original_url && {\n animationOriginal: animation_original_url,\n },\n schema_name && { standard: schema_name },\n external_link && { externalLink: external_link },\n last_sale && { lastSale: last_sale },\n );\n\n await this.addCollectible(address, token_id, collectibleMetadata, {\n userAddress: selectedAddress,\n chainId: chainId as string,\n });\n }\n },\n );\n await Promise.all(addCollectiblesPromises);\n }\n}\n\nexport default CollectibleDetectionController;\n"]} \ No newline at end of file diff --git a/dist/assets/CollectiblesController.d.ts b/dist/assets/CollectiblesController.d.ts deleted file mode 100644 index d0d57a51a4..0000000000 --- a/dist/assets/CollectiblesController.d.ts +++ /dev/null @@ -1,377 +0,0 @@ -/// -import { EventEmitter } from 'events'; -import { BaseController, BaseConfig, BaseState } from '../BaseController'; -import type { PreferencesState } from '../user/PreferencesController'; -import type { NetworkState, NetworkType } from '../network/NetworkController'; -import type { ApiCollectibleCreator, ApiCollectibleLastSale } from './CollectibleDetectionController'; -import type { AssetsContractController } from './AssetsContractController'; -/** - * @type Collectible - * - * Collectible representation - * @property address - Hex address of a ERC721 contract - * @property description - The collectible description - * @property image - URI of custom collectible image associated with this tokenId - * @property name - Name associated with this tokenId and contract address - * @property tokenId - The collectible identifier - * @property numberOfSales - Number of sales - * @property backgroundColor - The background color to be displayed with the item - * @property imagePreview - URI of a smaller image associated with this collectible - * @property imageThumbnail - URI of a thumbnail image associated with this collectible - * @property imageOriginal - URI of the original image associated with this collectible - * @property animation - URI of a animation associated with this collectible - * @property animationOriginal - URI of the original animation associated with this collectible - * @property externalLink - External link containing additional information - * @property creator - The collectible owner information object - * @property isCurrentlyOwned - Boolean indicating whether the address/chainId combination where it's currently stored currently owns this collectible - */ -export interface Collectible extends CollectibleMetadata { - tokenId: string; - address: string; - isCurrentlyOwned?: boolean; -} -/** - * @type CollectibleContract - * - * Collectible contract information representation - * @property name - Contract name - * @property logo - Contract logo - * @property address - Contract address - * @property symbol - Contract symbol - * @property description - Contract description - * @property totalSupply - Total supply of collectibles - * @property assetContractType - The collectible type, it could be `semi-fungible` or `non-fungible` - * @property createdDate - Creation date - * @property schemaName - The schema followed by the contract, it could be `ERC721` or `ERC1155` - * @property externalLink - External link containing additional information - */ -export interface CollectibleContract { - name?: string; - logo?: string; - address: string; - symbol?: string; - description?: string; - totalSupply?: string; - assetContractType?: string; - createdDate?: string; - schemaName?: string; - externalLink?: string; -} -/** - * @type CollectibleMetadata - * - * Collectible custom information - * @property name - Collectible custom name - * @property description - The collectible description - * @property numberOfSales - Number of sales - * @property backgroundColor - The background color to be displayed with the item - * @property image - Image custom image URI - * @property imagePreview - URI of a smaller image associated with this collectible - * @property imageThumbnail - URI of a thumbnail image associated with this collectible - * @property imageOriginal - URI of the original image associated with this collectible - * @property animation - URI of a animation associated with this collectible - * @property animationOriginal - URI of the original animation associated with this collectible - * @property externalLink - External link containing additional information - * @property creator - The collectible owner information object - * @property standard - NFT standard name for the collectible, e.g., ERC-721 or ERC-1155 - */ -export interface CollectibleMetadata { - name: string | null; - description: string | null; - image: string | null; - standard: string | null; - favorite?: boolean; - numberOfSales?: number; - backgroundColor?: string; - imagePreview?: string; - imageThumbnail?: string; - imageOriginal?: string; - animation?: string; - animationOriginal?: string; - externalLink?: string; - creator?: ApiCollectibleCreator; - lastSale?: ApiCollectibleLastSale; -} -interface AccountParams { - userAddress: string; - chainId: string; -} -/** - * @type CollectiblesConfig - * - * Collectibles controller configuration - * @property networkType - Network ID as per net_version - * @property selectedAddress - Vault selected address - */ -export interface CollectiblesConfig extends BaseConfig { - networkType: NetworkType; - selectedAddress: string; - chainId: string; - ipfsGateway: string; - openSeaEnabled: boolean; - useIPFSSubdomains: boolean; -} -/** - * @type CollectiblesState - * - * Assets controller state - * @property allCollectibleContracts - Object containing collectibles contract information - * @property allCollectibles - Object containing collectibles per account and network - * @property collectibleContracts - List of collectibles contracts associated with the active vault - * @property collectibles - List of collectibles associated with the active vault - * @property ignoredCollectibles - List of collectibles that should be ignored - */ -export interface CollectiblesState extends BaseState { - allCollectibleContracts: { - [key: string]: { - [key: string]: CollectibleContract[]; - }; - }; - allCollectibles: { - [key: string]: { - [key: string]: Collectible[]; - }; - }; - ignoredCollectibles: Collectible[]; -} -/** - * Controller that stores assets and exposes convenience methods - */ -export declare class CollectiblesController extends BaseController { - private mutex; - private getCollectibleApi; - private getCollectibleContractInformationApi; - /** - * Helper method to update nested state for allCollectibles and allCollectibleContracts. - * - * @param newCollection - the modified piece of state to update in the controller's store - * @param baseStateKey - The root key in the store to update. - * @param passedConfig - An object containing the selectedAddress and chainId that are passed through the auto-detection flow. - * @param passedConfig.userAddress - the address passed through the collectible detection flow to ensure detected assets are stored to the correct account - * @param passedConfig.chainId - the chainId passed through the collectible detection flow to ensure detected assets are stored to the correct account - */ - private updateNestedCollectibleState; - /** - * Request individual collectible information from OpenSea API. - * - * @param contractAddress - Hex address of the collectible contract. - * @param tokenId - The collectible identifier. - * @returns Promise resolving to the current collectible name and image. - */ - private getCollectibleInformationFromApi; - /** - * Request individual collectible information from contracts that follows Metadata Interface. - * - * @param contractAddress - Hex address of the collectible contract. - * @param tokenId - The collectible identifier. - * @returns Promise resolving to the current collectible name and image. - */ - private getCollectibleInformationFromTokenURI; - /** - * Retrieve collectible uri with metadata. TODO Update method to use IPFS. - * - * @param contractAddress - Collectible contract address. - * @param tokenId - Collectible token id. - * @returns Promise resolving collectible uri and token standard. - */ - private getCollectibleURIAndStandard; - /** - * Request individual collectible information (name, image url and description). - * - * @param contractAddress - Hex address of the collectible contract. - * @param tokenId - The collectible identifier. - * @returns Promise resolving to the current collectible name and image. - */ - private getCollectibleInformation; - /** - * Request collectible contract information from OpenSea API. - * - * @param contractAddress - Hex address of the collectible contract. - * @returns Promise resolving to the current collectible name and image. - */ - private getCollectibleContractInformationFromApi; - /** - * Request collectible contract information from the contract itself. - * - * @param contractAddress - Hex address of the collectible contract. - * @returns Promise resolving to the current collectible name and image. - */ - private getCollectibleContractInformationFromContract; - /** - * Request collectible contract information from OpenSea API. - * - * @param contractAddress - Hex address of the collectible contract. - * @returns Promise resolving to the collectible contract name, image and description. - */ - private getCollectibleContractInformation; - /** - * Adds an individual collectible to the stored collectible list. - * - * @param address - Hex address of the collectible contract. - * @param tokenId - The collectible identifier. - * @param collectibleMetadata - Collectible optional information (name, image and description). - * @param collectibleContract - An object containing contract data of the collectible being added. - * @param detection - The chain ID and address of the currently selected network and account at the moment the collectible was detected. - * @returns Promise resolving to the current collectible list. - */ - private addIndividualCollectible; - /** - * Adds a collectible contract to the stored collectible contracts list. - * - * @param address - Hex address of the collectible contract. - * @param detection - The chain ID and address of the currently selected network and account at the moment the collectible was detected. - * @returns Promise resolving to the current collectible contracts list. - */ - private addCollectibleContract; - /** - * Removes an individual collectible from the stored token list and saves it in ignored collectibles list. - * - * @param address - Hex address of the collectible contract. - * @param tokenId - Token identifier of the collectible. - */ - private removeAndIgnoreIndividualCollectible; - /** - * Removes an individual collectible from the stored token list. - * - * @param address - Hex address of the collectible contract. - * @param tokenId - Token identifier of the collectible. - */ - private removeIndividualCollectible; - /** - * Removes a collectible contract to the stored collectible contracts list. - * - * @param address - Hex address of the collectible contract. - * @returns Promise resolving to the current collectible contracts list. - */ - private removeCollectibleContract; - /** - * EventEmitter instance used to listen to specific EIP747 events - */ - hub: EventEmitter; - /** - * Optional API key to use with opensea - */ - openSeaApiKey?: string; - /** - * Name of this controller used during composition - */ - name: string; - private getERC721AssetName; - private getERC721AssetSymbol; - private getERC721TokenURI; - private getERC721OwnerOf; - private getERC1155BalanceOf; - private getERC1155TokenURI; - private onCollectibleAdded?; - /** - * Creates a CollectiblesController instance. - * - * @param options - The controller options. - * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes. - * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. - * @param options.getERC721AssetName - Gets the name of the asset at the given address. - * @param options.getERC721AssetSymbol - Gets the symbol of the asset at the given address. - * @param options.getERC721TokenURI - Gets the URI of the ERC721 token at the given address, with the given ID. - * @param options.getERC721OwnerOf - Get the owner of a ERC-721 collectible. - * @param options.getERC1155BalanceOf - Gets balance of a ERC-1155 collectible. - * @param options.getERC1155TokenURI - Gets the URI of the ERC1155 token at the given address, with the given ID. - * @param options.onCollectibleAdded - Callback that is called when a collectible is added. Currently used pass data - * for tracking the collectible added event. - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor({ onPreferencesStateChange, onNetworkStateChange, getERC721AssetName, getERC721AssetSymbol, getERC721TokenURI, getERC721OwnerOf, getERC1155BalanceOf, getERC1155TokenURI, onCollectibleAdded, }: { - onPreferencesStateChange: (listener: (preferencesState: PreferencesState) => void) => void; - onNetworkStateChange: (listener: (networkState: NetworkState) => void) => void; - getERC721AssetName: AssetsContractController['getERC721AssetName']; - getERC721AssetSymbol: AssetsContractController['getERC721AssetSymbol']; - getERC721TokenURI: AssetsContractController['getERC721TokenURI']; - getERC721OwnerOf: AssetsContractController['getERC721OwnerOf']; - getERC1155BalanceOf: AssetsContractController['getERC1155BalanceOf']; - getERC1155TokenURI: AssetsContractController['getERC1155TokenURI']; - onCollectibleAdded?: (data: { - address: string; - symbol: string | undefined; - tokenId: string; - standard: string | null; - source: string; - }) => void; - }, config?: Partial, state?: Partial); - /** - * Sets an OpenSea API key to retrieve collectible information. - * - * @param openSeaApiKey - OpenSea API key. - */ - setApiKey(openSeaApiKey: string): void; - /** - * Checks the ownership of a ERC-721 or ERC-1155 collectible for a given address. - * - * @param ownerAddress - User public address. - * @param collectibleAddress - Collectible contract address. - * @param collectibleId - Collectible token ID. - * @returns Promise resolving the collectible ownership. - */ - isCollectibleOwner(ownerAddress: string, collectibleAddress: string, collectibleId: string): Promise; - /** - * Verifies currently selected address owns entered collectible address/tokenId combo and - * adds the collectible and respective collectible contract to the stored collectible and collectible contracts lists. - * - * @param address - Hex address of the collectible contract. - * @param tokenId - The collectible identifier. - */ - addCollectibleVerifyOwnership(address: string, tokenId: string): Promise; - /** - * Adds a collectible and respective collectible contract to the stored collectible and collectible contracts lists. - * - * @param address - Hex address of the collectible contract. - * @param tokenId - The collectible identifier. - * @param collectibleMetadata - Collectible optional metadata. - * @param detection - The chain ID and address of the currently selected network and account at the moment the collectible was detected. - * @returns Promise resolving to the current collectible list. - */ - addCollectible(address: string, tokenId: string, collectibleMetadata?: CollectibleMetadata, detection?: AccountParams): Promise; - /** - * Removes a collectible from the stored token list. - * - * @param address - Hex address of the collectible contract. - * @param tokenId - Token identifier of the collectible. - */ - removeCollectible(address: string, tokenId: string): void; - /** - * Removes a collectible from the stored token list and saves it in ignored collectibles list. - * - * @param address - Hex address of the collectible contract. - * @param tokenId - Token identifier of the collectible. - */ - removeAndIgnoreCollectible(address: string, tokenId: string): void; - /** - * Removes all collectibles from the ignored list. - */ - clearIgnoredCollectibles(): void; - /** - * Checks whether input collectible is still owned by the user - * And updates the isCurrentlyOwned value on the collectible object accordingly. - * - * @param collectible - The collectible object to check and update. - * @param batch - A boolean indicating whether this method is being called as part of a batch or single update. - * @param accountParams - The userAddress and chainId to check ownership against - * @param accountParams.userAddress - the address passed through the confirmed transaction flow to ensure detected assets are stored to the correct account - * @param accountParams.chainId - the chainId passed through the confirmed transaction flow to ensure detected assets are stored to the correct account - * @returns the collectible with the updated isCurrentlyOwned value - */ - checkAndUpdateSingleCollectibleOwnershipStatus(collectible: Collectible, batch: boolean, { userAddress, chainId }?: AccountParams | undefined): Promise; - /** - * Checks whether Collectibles associated with current selectedAddress/chainId combination are still owned by the user - * And updates the isCurrentlyOwned value on each accordingly. - */ - checkAndUpdateAllCollectiblesOwnershipStatus(): Promise; - /** - * Update collectible favorite status. - * - * @param address - Hex address of the collectible contract. - * @param tokenId - Hex address of the collectible contract. - * @param favorite - Collectible new favorite status. - */ - updateCollectibleFavoriteStatus(address: string, tokenId: string, favorite: boolean): void; -} -export default CollectiblesController; diff --git a/dist/assets/CollectiblesController.js b/dist/assets/CollectiblesController.js deleted file mode 100644 index a687b2fa39..0000000000 --- a/dist/assets/CollectiblesController.js +++ /dev/null @@ -1,759 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.CollectiblesController = void 0; -const events_1 = require("events"); -const ethereumjs_util_1 = require("ethereumjs-util"); -const async_mutex_1 = require("async-mutex"); -const BaseController_1 = require("../BaseController"); -const util_1 = require("../util"); -const constants_1 = require("../constants"); -const assetsUtil_1 = require("./assetsUtil"); -const ALL_COLLECTIBLES_STATE_KEY = 'allCollectibles'; -const ALL_COLLECTIBLES_CONTRACTS_STATE_KEY = 'allCollectibleContracts'; -/** - * Controller that stores assets and exposes convenience methods - */ -class CollectiblesController extends BaseController_1.BaseController { - /** - * Creates a CollectiblesController instance. - * - * @param options - The controller options. - * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes. - * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. - * @param options.getERC721AssetName - Gets the name of the asset at the given address. - * @param options.getERC721AssetSymbol - Gets the symbol of the asset at the given address. - * @param options.getERC721TokenURI - Gets the URI of the ERC721 token at the given address, with the given ID. - * @param options.getERC721OwnerOf - Get the owner of a ERC-721 collectible. - * @param options.getERC1155BalanceOf - Gets balance of a ERC-1155 collectible. - * @param options.getERC1155TokenURI - Gets the URI of the ERC1155 token at the given address, with the given ID. - * @param options.onCollectibleAdded - Callback that is called when a collectible is added. Currently used pass data - * for tracking the collectible added event. - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor({ onPreferencesStateChange, onNetworkStateChange, getERC721AssetName, getERC721AssetSymbol, getERC721TokenURI, getERC721OwnerOf, getERC1155BalanceOf, getERC1155TokenURI, onCollectibleAdded, }, config, state) { - super(config, state); - this.mutex = new async_mutex_1.Mutex(); - /** - * EventEmitter instance used to listen to specific EIP747 events - */ - this.hub = new events_1.EventEmitter(); - /** - * Name of this controller used during composition - */ - this.name = 'CollectiblesController'; - this.defaultConfig = { - networkType: constants_1.MAINNET, - selectedAddress: '', - chainId: '', - ipfsGateway: constants_1.IPFS_DEFAULT_GATEWAY_URL, - openSeaEnabled: false, - useIPFSSubdomains: true, - }; - this.defaultState = { - allCollectibleContracts: {}, - allCollectibles: {}, - ignoredCollectibles: [], - }; - this.initialize(); - this.getERC721AssetName = getERC721AssetName; - this.getERC721AssetSymbol = getERC721AssetSymbol; - this.getERC721TokenURI = getERC721TokenURI; - this.getERC721OwnerOf = getERC721OwnerOf; - this.getERC1155BalanceOf = getERC1155BalanceOf; - this.getERC1155TokenURI = getERC1155TokenURI; - this.onCollectibleAdded = onCollectibleAdded; - onPreferencesStateChange(({ selectedAddress, ipfsGateway, openSeaEnabled }) => { - this.configure({ selectedAddress, ipfsGateway, openSeaEnabled }); - }); - onNetworkStateChange(({ provider }) => { - const { chainId } = provider; - this.configure({ chainId }); - }); - } - getCollectibleApi({ contractAddress, tokenId, useProxy, }) { - const { chainId } = this.config; - if (chainId === constants_1.RINKEBY_CHAIN_ID) { - return `${constants_1.OPENSEA_TEST_API_URL}/asset/${contractAddress}/${tokenId}`; - } - return useProxy - ? `${constants_1.OPENSEA_PROXY_URL}/asset/${contractAddress}/${tokenId}` - : `${constants_1.OPENSEA_API_URL}/asset/${contractAddress}/${tokenId}`; - } - getCollectibleContractInformationApi({ contractAddress, useProxy, }) { - const { chainId } = this.config; - if (chainId === constants_1.RINKEBY_CHAIN_ID) { - return `${constants_1.OPENSEA_TEST_API_URL}/asset_contract/${contractAddress}`; - } - return useProxy - ? `${constants_1.OPENSEA_PROXY_URL}/asset_contract/${contractAddress}` - : `${constants_1.OPENSEA_API_URL}/asset_contract/${contractAddress}`; - } - /** - * Helper method to update nested state for allCollectibles and allCollectibleContracts. - * - * @param newCollection - the modified piece of state to update in the controller's store - * @param baseStateKey - The root key in the store to update. - * @param passedConfig - An object containing the selectedAddress and chainId that are passed through the auto-detection flow. - * @param passedConfig.userAddress - the address passed through the collectible detection flow to ensure detected assets are stored to the correct account - * @param passedConfig.chainId - the chainId passed through the collectible detection flow to ensure detected assets are stored to the correct account - */ - updateNestedCollectibleState(newCollection, baseStateKey, { userAddress, chainId } = { - userAddress: this.config.selectedAddress, - chainId: this.config.chainId, - }) { - const { [baseStateKey]: oldState } = this.state; - const addressState = oldState[userAddress]; - const newAddressState = Object.assign(Object.assign({}, addressState), { [chainId]: newCollection }); - const newState = Object.assign(Object.assign({}, oldState), { [userAddress]: newAddressState }); - this.update({ - [baseStateKey]: newState, - }); - } - /** - * Request individual collectible information from OpenSea API. - * - * @param contractAddress - Hex address of the collectible contract. - * @param tokenId - The collectible identifier. - * @returns Promise resolving to the current collectible name and image. - */ - getCollectibleInformationFromApi(contractAddress, tokenId) { - return __awaiter(this, void 0, void 0, function* () { - // Attempt to fetch the data with the proxy - let collectibleInformation = yield (0, util_1.fetchWithErrorHandling)({ - url: this.getCollectibleApi({ - contractAddress, - tokenId, - useProxy: true, - }), - }); - // if an openSeaApiKey is set we should attempt to refetch calling directly to OpenSea - if (!collectibleInformation && this.openSeaApiKey) { - collectibleInformation = yield (0, util_1.fetchWithErrorHandling)({ - url: this.getCollectibleApi({ - contractAddress, - tokenId, - useProxy: false, - }), - options: { - headers: { 'X-API-KEY': this.openSeaApiKey }, - }, - // catch 403 errors (in case API key is down we don't want to blow up) - errorCodesToCatch: [403], - }); - } - // if we were still unable to fetch the data we return out the default/null of `CollectibleMetadata` - if (!collectibleInformation) { - return { - name: null, - description: null, - image: null, - standard: null, - }; - } - // if we've reached this point, we have successfully fetched some data for collectibleInformation - // now we reconfigure the data to conform to the `CollectibleMetadata` type for storage. - const { num_sales, background_color, image_url, image_preview_url, image_thumbnail_url, image_original_url, animation_url, animation_original_url, name, description, external_link, creator, last_sale, asset_contract: { schema_name }, } = collectibleInformation; - /* istanbul ignore next */ - const collectibleMetadata = Object.assign({}, { name: name || null }, { description: description || null }, { image: image_url || null }, creator && { creator }, num_sales && { numberOfSales: num_sales }, background_color && { backgroundColor: background_color }, image_preview_url && { imagePreview: image_preview_url }, image_thumbnail_url && { imageThumbnail: image_thumbnail_url }, image_original_url && { imageOriginal: image_original_url }, animation_url && { animation: animation_url }, animation_original_url && { - animationOriginal: animation_original_url, - }, external_link && { externalLink: external_link }, last_sale && { lastSale: last_sale }, schema_name && { standard: schema_name }); - return collectibleMetadata; - }); - } - /** - * Request individual collectible information from contracts that follows Metadata Interface. - * - * @param contractAddress - Hex address of the collectible contract. - * @param tokenId - The collectible identifier. - * @returns Promise resolving to the current collectible name and image. - */ - getCollectibleInformationFromTokenURI(contractAddress, tokenId) { - return __awaiter(this, void 0, void 0, function* () { - const { ipfsGateway, useIPFSSubdomains } = this.config; - const result = yield this.getCollectibleURIAndStandard(contractAddress, tokenId); - let tokenURI = result[0]; - const standard = result[1]; - if (tokenURI.startsWith('ipfs://')) { - tokenURI = (0, util_1.getFormattedIpfsUrl)(ipfsGateway, tokenURI, useIPFSSubdomains); - } - try { - const object = yield (0, util_1.handleFetch)(tokenURI); - // TODO: Check image_url existence. This is not part of EIP721 nor EIP1155 - const image = Object.prototype.hasOwnProperty.call(object, 'image') - ? 'image' - : /* istanbul ignore next */ 'image_url'; - return { - image: object[image], - name: object.name, - description: object.description, - standard, - favorite: false, - }; - } - catch (_a) { - return { - image: null, - name: null, - description: null, - standard: standard || null, - favorite: false, - }; - } - }); - } - /** - * Retrieve collectible uri with metadata. TODO Update method to use IPFS. - * - * @param contractAddress - Collectible contract address. - * @param tokenId - Collectible token id. - * @returns Promise resolving collectible uri and token standard. - */ - getCollectibleURIAndStandard(contractAddress, tokenId) { - return __awaiter(this, void 0, void 0, function* () { - // try ERC721 uri - try { - const uri = yield this.getERC721TokenURI(contractAddress, tokenId); - return [uri, constants_1.ERC721]; - } - catch (_a) { - // Ignore error - } - // try ERC1155 uri - try { - const tokenURI = yield this.getERC1155TokenURI(contractAddress, tokenId); - /** - * According to EIP1155 the URI value allows for ID substitution - * in case the string `{id}` exists. - * https://eips.ethereum.org/EIPS/eip-1155#metadata - */ - if (!tokenURI.includes('{id}')) { - return [tokenURI, constants_1.ERC1155]; - } - const hexTokenId = (0, ethereumjs_util_1.stripHexPrefix)((0, util_1.BNToHex)(new ethereumjs_util_1.BN(tokenId))) - .padStart(64, '0') - .toLowerCase(); - return [tokenURI.replace('{id}', hexTokenId), constants_1.ERC1155]; - } - catch (_b) { - // Ignore error - } - return ['', '']; - }); - } - /** - * Request individual collectible information (name, image url and description). - * - * @param contractAddress - Hex address of the collectible contract. - * @param tokenId - The collectible identifier. - * @returns Promise resolving to the current collectible name and image. - */ - getCollectibleInformation(contractAddress, tokenId) { - var _a, _b, _c, _d, _e, _f, _g, _h; - return __awaiter(this, void 0, void 0, function* () { - const blockchainMetadata = yield (0, util_1.safelyExecute)(() => __awaiter(this, void 0, void 0, function* () { - return yield this.getCollectibleInformationFromTokenURI(contractAddress, tokenId); - })); - let openSeaMetadata; - if (this.config.openSeaEnabled) { - openSeaMetadata = yield (0, util_1.safelyExecute)(() => __awaiter(this, void 0, void 0, function* () { - return yield this.getCollectibleInformationFromApi(contractAddress, tokenId); - })); - } - return Object.assign(Object.assign({}, openSeaMetadata), { name: (_b = (_a = blockchainMetadata.name) !== null && _a !== void 0 ? _a : openSeaMetadata === null || openSeaMetadata === void 0 ? void 0 : openSeaMetadata.name) !== null && _b !== void 0 ? _b : null, description: (_d = (_c = blockchainMetadata.description) !== null && _c !== void 0 ? _c : openSeaMetadata === null || openSeaMetadata === void 0 ? void 0 : openSeaMetadata.description) !== null && _d !== void 0 ? _d : null, image: (_f = (_e = blockchainMetadata.image) !== null && _e !== void 0 ? _e : openSeaMetadata === null || openSeaMetadata === void 0 ? void 0 : openSeaMetadata.image) !== null && _f !== void 0 ? _f : null, standard: (_h = (_g = blockchainMetadata.standard) !== null && _g !== void 0 ? _g : openSeaMetadata === null || openSeaMetadata === void 0 ? void 0 : openSeaMetadata.standard) !== null && _h !== void 0 ? _h : null }); - }); - } - /** - * Request collectible contract information from OpenSea API. - * - * @param contractAddress - Hex address of the collectible contract. - * @returns Promise resolving to the current collectible name and image. - */ - getCollectibleContractInformationFromApi(contractAddress) { - return __awaiter(this, void 0, void 0, function* () { - /* istanbul ignore if */ - let apiCollectibleContractObject = yield (0, util_1.fetchWithErrorHandling)({ - url: this.getCollectibleContractInformationApi({ - contractAddress, - useProxy: true, - }), - }); - // if we successfully fetched return the fetched data immediately - if (apiCollectibleContractObject) { - return apiCollectibleContractObject; - } - // if we were unsuccessful in fetching from the API and an OpenSea API key is present - // attempt to refetch directly against the OpenSea API and if successful return the data immediately - if (this.openSeaApiKey) { - apiCollectibleContractObject = yield (0, util_1.fetchWithErrorHandling)({ - url: this.getCollectibleContractInformationApi({ - contractAddress, - useProxy: false, - }), - options: { - headers: { 'X-API-KEY': this.openSeaApiKey }, - }, - // catch 403 errors (in case API key is down we don't want to blow up) - errorCodesToCatch: [403], - }); - if (apiCollectibleContractObject) { - return apiCollectibleContractObject; - } - } - // If we've reached this point we were unable to fetch data from either the proxy or opensea so we return - // the default/null of ApiCollectibleContract - return { - address: contractAddress, - asset_contract_type: null, - created_date: null, - schema_name: null, - symbol: null, - total_supply: null, - description: null, - external_link: null, - collection: { - name: null, - image_url: null, - }, - }; - }); - } - /** - * Request collectible contract information from the contract itself. - * - * @param contractAddress - Hex address of the collectible contract. - * @returns Promise resolving to the current collectible name and image. - */ - getCollectibleContractInformationFromContract(contractAddress) { - return __awaiter(this, void 0, void 0, function* () { - const name = yield this.getERC721AssetName(contractAddress); - const symbol = yield this.getERC721AssetSymbol(contractAddress); - return { - collection: { name }, - symbol, - address: contractAddress, - }; - }); - } - /** - * Request collectible contract information from OpenSea API. - * - * @param contractAddress - Hex address of the collectible contract. - * @returns Promise resolving to the collectible contract name, image and description. - */ - getCollectibleContractInformation(contractAddress) { - return __awaiter(this, void 0, void 0, function* () { - const blockchainContractData = yield (0, util_1.safelyExecute)(() => __awaiter(this, void 0, void 0, function* () { - return yield this.getCollectibleContractInformationFromContract(contractAddress); - })); - let openSeaContractData; - if (this.config.openSeaEnabled) { - openSeaContractData = yield (0, util_1.safelyExecute)(() => __awaiter(this, void 0, void 0, function* () { - return yield this.getCollectibleContractInformationFromApi(contractAddress); - })); - } - if (blockchainContractData || openSeaContractData) { - return Object.assign(Object.assign(Object.assign({}, openSeaContractData), blockchainContractData), { collection: Object.assign(Object.assign({ image_url: null }, openSeaContractData === null || openSeaContractData === void 0 ? void 0 : openSeaContractData.collection), blockchainContractData === null || blockchainContractData === void 0 ? void 0 : blockchainContractData.collection) }); - } - /* istanbul ignore next */ - return { - address: contractAddress, - asset_contract_type: null, - created_date: null, - schema_name: null, - symbol: null, - total_supply: null, - description: null, - external_link: null, - collection: { name: null, image_url: null }, - }; - }); - } - /** - * Adds an individual collectible to the stored collectible list. - * - * @param address - Hex address of the collectible contract. - * @param tokenId - The collectible identifier. - * @param collectibleMetadata - Collectible optional information (name, image and description). - * @param collectibleContract - An object containing contract data of the collectible being added. - * @param detection - The chain ID and address of the currently selected network and account at the moment the collectible was detected. - * @returns Promise resolving to the current collectible list. - */ - addIndividualCollectible(address, tokenId, collectibleMetadata, collectibleContract, detection) { - var _a; - return __awaiter(this, void 0, void 0, function* () { - // TODO: Remove unused return - const releaseLock = yield this.mutex.acquire(); - try { - address = (0, util_1.toChecksumHexAddress)(address); - const { allCollectibles } = this.state; - let chainId, selectedAddress; - if (detection) { - chainId = detection.chainId; - selectedAddress = detection.userAddress; - } - else { - chainId = this.config.chainId; - selectedAddress = this.config.selectedAddress; - } - const collectibles = ((_a = allCollectibles[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; - const existingEntry = collectibles.find((collectible) => collectible.address.toLowerCase() === address.toLowerCase() && - collectible.tokenId === tokenId); - if (existingEntry) { - const differentMetadata = (0, assetsUtil_1.compareCollectiblesMetadata)(collectibleMetadata, existingEntry); - if (differentMetadata) { - // TODO: Switch to indexToUpdate - const indexToRemove = collectibles.findIndex((collectible) => collectible.address.toLowerCase() === address.toLowerCase() && - collectible.tokenId === tokenId); - /* istanbul ignore next */ - if (indexToRemove !== -1) { - collectibles.splice(indexToRemove, 1); - } - } - else { - return collectibles; - } - } - const newEntry = Object.assign({ address, - tokenId, favorite: (existingEntry === null || existingEntry === void 0 ? void 0 : existingEntry.favorite) || false, isCurrentlyOwned: true }, collectibleMetadata); - const newCollectibles = [...collectibles, newEntry]; - this.updateNestedCollectibleState(newCollectibles, ALL_COLLECTIBLES_STATE_KEY, { chainId, userAddress: selectedAddress }); - if (this.onCollectibleAdded) { - this.onCollectibleAdded({ - address, - symbol: collectibleContract.symbol, - tokenId: tokenId.toString(), - standard: collectibleMetadata.standard, - source: detection ? 'detected' : 'custom', - }); - } - return newCollectibles; - } - finally { - releaseLock(); - } - }); - } - /** - * Adds a collectible contract to the stored collectible contracts list. - * - * @param address - Hex address of the collectible contract. - * @param detection - The chain ID and address of the currently selected network and account at the moment the collectible was detected. - * @returns Promise resolving to the current collectible contracts list. - */ - addCollectibleContract(address, detection) { - var _a; - return __awaiter(this, void 0, void 0, function* () { - const releaseLock = yield this.mutex.acquire(); - try { - address = (0, util_1.toChecksumHexAddress)(address); - const { allCollectibleContracts } = this.state; - let chainId, selectedAddress; - if (detection) { - chainId = detection.chainId; - selectedAddress = detection.userAddress; - } - else { - chainId = this.config.chainId; - selectedAddress = this.config.selectedAddress; - } - const collectibleContracts = ((_a = allCollectibleContracts[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; - const existingEntry = collectibleContracts.find((collectibleContract) => collectibleContract.address.toLowerCase() === address.toLowerCase()); - if (existingEntry) { - return collectibleContracts; - } - const contractInformation = yield this.getCollectibleContractInformation(address); - const { asset_contract_type, created_date, schema_name, symbol, total_supply, description, external_link, collection: { name, image_url }, } = contractInformation; - // If being auto-detected opensea information is expected - // Otherwise at least name from the contract is needed - if ((detection && !name) || - Object.keys(contractInformation).length === 0) { - return collectibleContracts; - } - /* istanbul ignore next */ - const newEntry = Object.assign({}, { address }, description && { description }, name && { name }, image_url && { logo: image_url }, symbol && { symbol }, total_supply !== null && - typeof total_supply !== 'undefined' && { totalSupply: total_supply }, asset_contract_type && { assetContractType: asset_contract_type }, created_date && { createdDate: created_date }, schema_name && { schemaName: schema_name }, external_link && { externalLink: external_link }); - const newCollectibleContracts = [...collectibleContracts, newEntry]; - this.updateNestedCollectibleState(newCollectibleContracts, ALL_COLLECTIBLES_CONTRACTS_STATE_KEY, { chainId, userAddress: selectedAddress }); - return newCollectibleContracts; - } - finally { - releaseLock(); - } - }); - } - /** - * Removes an individual collectible from the stored token list and saves it in ignored collectibles list. - * - * @param address - Hex address of the collectible contract. - * @param tokenId - Token identifier of the collectible. - */ - removeAndIgnoreIndividualCollectible(address, tokenId) { - var _a; - address = (0, util_1.toChecksumHexAddress)(address); - const { allCollectibles, ignoredCollectibles } = this.state; - const { chainId, selectedAddress } = this.config; - const newIgnoredCollectibles = [...ignoredCollectibles]; - const collectibles = ((_a = allCollectibles[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; - const newCollectibles = collectibles.filter((collectible) => { - if (collectible.address.toLowerCase() === address.toLowerCase() && - collectible.tokenId === tokenId) { - const alreadyIgnored = newIgnoredCollectibles.find((c) => c.address === address && c.tokenId === tokenId); - !alreadyIgnored && newIgnoredCollectibles.push(collectible); - return false; - } - return true; - }); - this.updateNestedCollectibleState(newCollectibles, ALL_COLLECTIBLES_STATE_KEY); - this.update({ - ignoredCollectibles: newIgnoredCollectibles, - }); - } - /** - * Removes an individual collectible from the stored token list. - * - * @param address - Hex address of the collectible contract. - * @param tokenId - Token identifier of the collectible. - */ - removeIndividualCollectible(address, tokenId) { - var _a; - address = (0, util_1.toChecksumHexAddress)(address); - const { allCollectibles } = this.state; - const { chainId, selectedAddress } = this.config; - const collectibles = ((_a = allCollectibles[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; - const newCollectibles = collectibles.filter((collectible) => !(collectible.address.toLowerCase() === address.toLowerCase() && - collectible.tokenId === tokenId)); - this.updateNestedCollectibleState(newCollectibles, ALL_COLLECTIBLES_STATE_KEY); - } - /** - * Removes a collectible contract to the stored collectible contracts list. - * - * @param address - Hex address of the collectible contract. - * @returns Promise resolving to the current collectible contracts list. - */ - removeCollectibleContract(address) { - var _a; - address = (0, util_1.toChecksumHexAddress)(address); - const { allCollectibleContracts } = this.state; - const { chainId, selectedAddress } = this.config; - const collectibleContracts = ((_a = allCollectibleContracts[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; - const newCollectibleContracts = collectibleContracts.filter((collectibleContract) => !(collectibleContract.address.toLowerCase() === address.toLowerCase())); - this.updateNestedCollectibleState(newCollectibleContracts, ALL_COLLECTIBLES_CONTRACTS_STATE_KEY); - return newCollectibleContracts; - } - /** - * Sets an OpenSea API key to retrieve collectible information. - * - * @param openSeaApiKey - OpenSea API key. - */ - setApiKey(openSeaApiKey) { - this.openSeaApiKey = openSeaApiKey; - } - /** - * Checks the ownership of a ERC-721 or ERC-1155 collectible for a given address. - * - * @param ownerAddress - User public address. - * @param collectibleAddress - Collectible contract address. - * @param collectibleId - Collectible token ID. - * @returns Promise resolving the collectible ownership. - */ - isCollectibleOwner(ownerAddress, collectibleAddress, collectibleId) { - return __awaiter(this, void 0, void 0, function* () { - // Checks the ownership for ERC-721. - try { - const owner = yield this.getERC721OwnerOf(collectibleAddress, collectibleId); - return ownerAddress.toLowerCase() === owner.toLowerCase(); - // eslint-disable-next-line no-empty - } - catch (_a) { - // Ignore ERC-721 contract error - } - // Checks the ownership for ERC-1155. - try { - const balance = yield this.getERC1155BalanceOf(ownerAddress, collectibleAddress, collectibleId); - return balance > 0; - // eslint-disable-next-line no-empty - } - catch (_b) { - // Ignore ERC-1155 contract error - } - throw new Error('Unable to verify ownership. Probably because the standard is not supported or the chain is incorrect.'); - }); - } - /** - * Verifies currently selected address owns entered collectible address/tokenId combo and - * adds the collectible and respective collectible contract to the stored collectible and collectible contracts lists. - * - * @param address - Hex address of the collectible contract. - * @param tokenId - The collectible identifier. - */ - addCollectibleVerifyOwnership(address, tokenId) { - return __awaiter(this, void 0, void 0, function* () { - const { selectedAddress } = this.config; - if (!(yield this.isCollectibleOwner(selectedAddress, address, tokenId))) { - throw new Error('This collectible is not owned by the user'); - } - yield this.addCollectible(address, tokenId); - }); - } - /** - * Adds a collectible and respective collectible contract to the stored collectible and collectible contracts lists. - * - * @param address - Hex address of the collectible contract. - * @param tokenId - The collectible identifier. - * @param collectibleMetadata - Collectible optional metadata. - * @param detection - The chain ID and address of the currently selected network and account at the moment the collectible was detected. - * @returns Promise resolving to the current collectible list. - */ - addCollectible(address, tokenId, collectibleMetadata, detection) { - return __awaiter(this, void 0, void 0, function* () { - address = (0, util_1.toChecksumHexAddress)(address); - const newCollectibleContracts = yield this.addCollectibleContract(address, detection); - collectibleMetadata = - collectibleMetadata || - (yield this.getCollectibleInformation(address, tokenId)); - // If collectible contract was not added, do not add individual collectible - const collectibleContract = newCollectibleContracts.find((contract) => contract.address.toLowerCase() === address.toLowerCase()); - // If collectible contract information, add individual collectible - if (collectibleContract) { - yield this.addIndividualCollectible(address, tokenId, collectibleMetadata, collectibleContract, detection); - } - }); - } - /** - * Removes a collectible from the stored token list. - * - * @param address - Hex address of the collectible contract. - * @param tokenId - Token identifier of the collectible. - */ - removeCollectible(address, tokenId) { - var _a; - address = (0, util_1.toChecksumHexAddress)(address); - this.removeIndividualCollectible(address, tokenId); - const { allCollectibles } = this.state; - const { chainId, selectedAddress } = this.config; - const collectibles = ((_a = allCollectibles[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; - const remainingCollectible = collectibles.find((collectible) => collectible.address.toLowerCase() === address.toLowerCase()); - if (!remainingCollectible) { - this.removeCollectibleContract(address); - } - } - /** - * Removes a collectible from the stored token list and saves it in ignored collectibles list. - * - * @param address - Hex address of the collectible contract. - * @param tokenId - Token identifier of the collectible. - */ - removeAndIgnoreCollectible(address, tokenId) { - var _a; - address = (0, util_1.toChecksumHexAddress)(address); - this.removeAndIgnoreIndividualCollectible(address, tokenId); - const { allCollectibles } = this.state; - const { chainId, selectedAddress } = this.config; - const collectibles = ((_a = allCollectibles[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; - const remainingCollectible = collectibles.find((collectible) => collectible.address.toLowerCase() === address.toLowerCase()); - if (!remainingCollectible) { - this.removeCollectibleContract(address); - } - } - /** - * Removes all collectibles from the ignored list. - */ - clearIgnoredCollectibles() { - this.update({ ignoredCollectibles: [] }); - } - /** - * Checks whether input collectible is still owned by the user - * And updates the isCurrentlyOwned value on the collectible object accordingly. - * - * @param collectible - The collectible object to check and update. - * @param batch - A boolean indicating whether this method is being called as part of a batch or single update. - * @param accountParams - The userAddress and chainId to check ownership against - * @param accountParams.userAddress - the address passed through the confirmed transaction flow to ensure detected assets are stored to the correct account - * @param accountParams.chainId - the chainId passed through the confirmed transaction flow to ensure detected assets are stored to the correct account - * @returns the collectible with the updated isCurrentlyOwned value - */ - checkAndUpdateSingleCollectibleOwnershipStatus(collectible, batch, { userAddress, chainId } = { - userAddress: this.config.selectedAddress, - chainId: this.config.chainId, - }) { - var _a; - return __awaiter(this, void 0, void 0, function* () { - const { address, tokenId } = collectible; - let isOwned = collectible.isCurrentlyOwned; - try { - isOwned = yield this.isCollectibleOwner(userAddress, address, tokenId); - } - catch (error) { - if (!(error instanceof Error && - error.message.includes('Unable to verify ownership'))) { - throw error; - } - } - collectible.isCurrentlyOwned = isOwned; - if (batch === true) { - return collectible; - } - // if this is not part of a batched update we update this one collectible in state - const { allCollectibles } = this.state; - const collectibles = ((_a = allCollectibles[userAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; - const collectibleToUpdate = collectibles.find((item) => item.tokenId === tokenId && - item.address.toLowerCase() === address.toLowerCase()); - if (collectibleToUpdate) { - collectibleToUpdate.isCurrentlyOwned = isOwned; - this.updateNestedCollectibleState(collectibles, ALL_COLLECTIBLES_STATE_KEY, { userAddress, chainId }); - } - return collectible; - }); - } - /** - * Checks whether Collectibles associated with current selectedAddress/chainId combination are still owned by the user - * And updates the isCurrentlyOwned value on each accordingly. - */ - checkAndUpdateAllCollectiblesOwnershipStatus() { - var _a; - return __awaiter(this, void 0, void 0, function* () { - const { allCollectibles } = this.state; - const { chainId, selectedAddress } = this.config; - const collectibles = ((_a = allCollectibles[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; - const updatedCollectibles = yield Promise.all(collectibles.map((collectible) => __awaiter(this, void 0, void 0, function* () { - var _b; - return ((_b = (yield this.checkAndUpdateSingleCollectibleOwnershipStatus(collectible, true))) !== null && _b !== void 0 ? _b : collectible); - }))); - this.updateNestedCollectibleState(updatedCollectibles, ALL_COLLECTIBLES_STATE_KEY); - }); - } - /** - * Update collectible favorite status. - * - * @param address - Hex address of the collectible contract. - * @param tokenId - Hex address of the collectible contract. - * @param favorite - Collectible new favorite status. - */ - updateCollectibleFavoriteStatus(address, tokenId, favorite) { - var _a; - const { allCollectibles } = this.state; - const { chainId, selectedAddress } = this.config; - const collectibles = ((_a = allCollectibles[selectedAddress]) === null || _a === void 0 ? void 0 : _a[chainId]) || []; - const index = collectibles.findIndex((collectible) => collectible.address === address && collectible.tokenId === tokenId); - if (index === -1) { - return; - } - const updatedCollectible = Object.assign(Object.assign({}, collectibles[index]), { favorite }); - // Update Collectibles array - collectibles[index] = updatedCollectible; - this.updateNestedCollectibleState(collectibles, ALL_COLLECTIBLES_STATE_KEY); - } -} -exports.CollectiblesController = CollectiblesController; -exports.default = CollectiblesController; -//# sourceMappingURL=CollectiblesController.js.map \ No newline at end of file diff --git a/dist/assets/CollectiblesController.js.map b/dist/assets/CollectiblesController.js.map deleted file mode 100644 index e91f3ffce3..0000000000 --- a/dist/assets/CollectiblesController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"CollectiblesController.js","sourceRoot":"","sources":["../../src/assets/CollectiblesController.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from 'events';\nimport { BN, stripHexPrefix } from 'ethereumjs-util';\nimport { Mutex } from 'async-mutex';\n\nimport { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport type { PreferencesState } from '../user/PreferencesController';\nimport type { NetworkState, NetworkType } from '../network/NetworkController';\nimport {\n safelyExecute,\n handleFetch,\n toChecksumHexAddress,\n BNToHex,\n getFormattedIpfsUrl,\n fetchWithErrorHandling,\n} from '../util';\nimport {\n MAINNET,\n RINKEBY_CHAIN_ID,\n IPFS_DEFAULT_GATEWAY_URL,\n ERC721,\n ERC1155,\n OPENSEA_API_URL,\n OPENSEA_PROXY_URL,\n OPENSEA_TEST_API_URL,\n} from '../constants';\n\nimport type {\n ApiCollectible,\n ApiCollectibleCreator,\n ApiCollectibleContract,\n ApiCollectibleLastSale,\n} from './CollectibleDetectionController';\nimport type { AssetsContractController } from './AssetsContractController';\nimport { compareCollectiblesMetadata } from './assetsUtil';\n\n/**\n * @type Collectible\n *\n * Collectible representation\n * @property address - Hex address of a ERC721 contract\n * @property description - The collectible description\n * @property image - URI of custom collectible image associated with this tokenId\n * @property name - Name associated with this tokenId and contract address\n * @property tokenId - The collectible identifier\n * @property numberOfSales - Number of sales\n * @property backgroundColor - The background color to be displayed with the item\n * @property imagePreview - URI of a smaller image associated with this collectible\n * @property imageThumbnail - URI of a thumbnail image associated with this collectible\n * @property imageOriginal - URI of the original image associated with this collectible\n * @property animation - URI of a animation associated with this collectible\n * @property animationOriginal - URI of the original animation associated with this collectible\n * @property externalLink - External link containing additional information\n * @property creator - The collectible owner information object\n * @property isCurrentlyOwned - Boolean indicating whether the address/chainId combination where it's currently stored currently owns this collectible\n */\nexport interface Collectible extends CollectibleMetadata {\n tokenId: string;\n address: string;\n isCurrentlyOwned?: boolean;\n}\n\n/**\n * @type CollectibleContract\n *\n * Collectible contract information representation\n * @property name - Contract name\n * @property logo - Contract logo\n * @property address - Contract address\n * @property symbol - Contract symbol\n * @property description - Contract description\n * @property totalSupply - Total supply of collectibles\n * @property assetContractType - The collectible type, it could be `semi-fungible` or `non-fungible`\n * @property createdDate - Creation date\n * @property schemaName - The schema followed by the contract, it could be `ERC721` or `ERC1155`\n * @property externalLink - External link containing additional information\n */\nexport interface CollectibleContract {\n name?: string;\n logo?: string;\n address: string;\n symbol?: string;\n description?: string;\n totalSupply?: string;\n assetContractType?: string;\n createdDate?: string;\n schemaName?: string;\n externalLink?: string;\n}\n\n/**\n * @type CollectibleMetadata\n *\n * Collectible custom information\n * @property name - Collectible custom name\n * @property description - The collectible description\n * @property numberOfSales - Number of sales\n * @property backgroundColor - The background color to be displayed with the item\n * @property image - Image custom image URI\n * @property imagePreview - URI of a smaller image associated with this collectible\n * @property imageThumbnail - URI of a thumbnail image associated with this collectible\n * @property imageOriginal - URI of the original image associated with this collectible\n * @property animation - URI of a animation associated with this collectible\n * @property animationOriginal - URI of the original animation associated with this collectible\n * @property externalLink - External link containing additional information\n * @property creator - The collectible owner information object\n * @property standard - NFT standard name for the collectible, e.g., ERC-721 or ERC-1155\n */\nexport interface CollectibleMetadata {\n name: string | null;\n description: string | null;\n image: string | null;\n standard: string | null;\n favorite?: boolean;\n numberOfSales?: number;\n backgroundColor?: string;\n imagePreview?: string;\n imageThumbnail?: string;\n imageOriginal?: string;\n animation?: string;\n animationOriginal?: string;\n externalLink?: string;\n creator?: ApiCollectibleCreator;\n lastSale?: ApiCollectibleLastSale;\n}\n\ninterface AccountParams {\n userAddress: string;\n chainId: string;\n}\n\n/**\n * @type CollectiblesConfig\n *\n * Collectibles controller configuration\n * @property networkType - Network ID as per net_version\n * @property selectedAddress - Vault selected address\n */\nexport interface CollectiblesConfig extends BaseConfig {\n networkType: NetworkType;\n selectedAddress: string;\n chainId: string;\n ipfsGateway: string;\n openSeaEnabled: boolean;\n useIPFSSubdomains: boolean;\n}\n\n/**\n * @type CollectiblesState\n *\n * Assets controller state\n * @property allCollectibleContracts - Object containing collectibles contract information\n * @property allCollectibles - Object containing collectibles per account and network\n * @property collectibleContracts - List of collectibles contracts associated with the active vault\n * @property collectibles - List of collectibles associated with the active vault\n * @property ignoredCollectibles - List of collectibles that should be ignored\n */\nexport interface CollectiblesState extends BaseState {\n allCollectibleContracts: {\n [key: string]: { [key: string]: CollectibleContract[] };\n };\n allCollectibles: { [key: string]: { [key: string]: Collectible[] } };\n ignoredCollectibles: Collectible[];\n}\n\nconst ALL_COLLECTIBLES_STATE_KEY = 'allCollectibles';\nconst ALL_COLLECTIBLES_CONTRACTS_STATE_KEY = 'allCollectibleContracts';\n\n/**\n * Controller that stores assets and exposes convenience methods\n */\nexport class CollectiblesController extends BaseController<\n CollectiblesConfig,\n CollectiblesState\n> {\n private mutex = new Mutex();\n\n private getCollectibleApi({\n contractAddress,\n tokenId,\n useProxy,\n }: {\n contractAddress: string;\n tokenId: string;\n useProxy: boolean;\n }) {\n const { chainId } = this.config;\n\n if (chainId === RINKEBY_CHAIN_ID) {\n return `${OPENSEA_TEST_API_URL}/asset/${contractAddress}/${tokenId}`;\n }\n return useProxy\n ? `${OPENSEA_PROXY_URL}/asset/${contractAddress}/${tokenId}`\n : `${OPENSEA_API_URL}/asset/${contractAddress}/${tokenId}`;\n }\n\n private getCollectibleContractInformationApi({\n contractAddress,\n useProxy,\n }: {\n contractAddress: string;\n useProxy: boolean;\n }) {\n const { chainId } = this.config;\n\n if (chainId === RINKEBY_CHAIN_ID) {\n return `${OPENSEA_TEST_API_URL}/asset_contract/${contractAddress}`;\n }\n\n return useProxy\n ? `${OPENSEA_PROXY_URL}/asset_contract/${contractAddress}`\n : `${OPENSEA_API_URL}/asset_contract/${contractAddress}`;\n }\n\n /**\n * Helper method to update nested state for allCollectibles and allCollectibleContracts.\n *\n * @param newCollection - the modified piece of state to update in the controller's store\n * @param baseStateKey - The root key in the store to update.\n * @param passedConfig - An object containing the selectedAddress and chainId that are passed through the auto-detection flow.\n * @param passedConfig.userAddress - the address passed through the collectible detection flow to ensure detected assets are stored to the correct account\n * @param passedConfig.chainId - the chainId passed through the collectible detection flow to ensure detected assets are stored to the correct account\n */\n private updateNestedCollectibleState(\n newCollection: Collectible[] | CollectibleContract[],\n baseStateKey: 'allCollectibles' | 'allCollectibleContracts',\n { userAddress, chainId }: AccountParams | undefined = {\n userAddress: this.config.selectedAddress,\n chainId: this.config.chainId,\n },\n ) {\n const { [baseStateKey]: oldState } = this.state;\n\n const addressState = oldState[userAddress];\n const newAddressState = {\n ...addressState,\n ...{ [chainId]: newCollection },\n };\n const newState = {\n ...oldState,\n ...{ [userAddress]: newAddressState },\n };\n\n this.update({\n [baseStateKey]: newState,\n });\n }\n\n /**\n * Request individual collectible information from OpenSea API.\n *\n * @param contractAddress - Hex address of the collectible contract.\n * @param tokenId - The collectible identifier.\n * @returns Promise resolving to the current collectible name and image.\n */\n private async getCollectibleInformationFromApi(\n contractAddress: string,\n tokenId: string,\n ): Promise {\n // Attempt to fetch the data with the proxy\n let collectibleInformation: ApiCollectible | undefined =\n await fetchWithErrorHandling({\n url: this.getCollectibleApi({\n contractAddress,\n tokenId,\n useProxy: true,\n }),\n });\n\n // if an openSeaApiKey is set we should attempt to refetch calling directly to OpenSea\n if (!collectibleInformation && this.openSeaApiKey) {\n collectibleInformation = await fetchWithErrorHandling({\n url: this.getCollectibleApi({\n contractAddress,\n tokenId,\n useProxy: false,\n }),\n options: {\n headers: { 'X-API-KEY': this.openSeaApiKey },\n },\n // catch 403 errors (in case API key is down we don't want to blow up)\n errorCodesToCatch: [403],\n });\n }\n\n // if we were still unable to fetch the data we return out the default/null of `CollectibleMetadata`\n if (!collectibleInformation) {\n return {\n name: null,\n description: null,\n image: null,\n standard: null,\n };\n }\n\n // if we've reached this point, we have successfully fetched some data for collectibleInformation\n // now we reconfigure the data to conform to the `CollectibleMetadata` type for storage.\n const {\n num_sales,\n background_color,\n image_url,\n image_preview_url,\n image_thumbnail_url,\n image_original_url,\n animation_url,\n animation_original_url,\n name,\n description,\n external_link,\n creator,\n last_sale,\n asset_contract: { schema_name },\n } = collectibleInformation;\n\n /* istanbul ignore next */\n const collectibleMetadata: CollectibleMetadata = Object.assign(\n {},\n { name: name || null },\n { description: description || null },\n { image: image_url || null },\n creator && { creator },\n num_sales && { numberOfSales: num_sales },\n background_color && { backgroundColor: background_color },\n image_preview_url && { imagePreview: image_preview_url },\n image_thumbnail_url && { imageThumbnail: image_thumbnail_url },\n image_original_url && { imageOriginal: image_original_url },\n animation_url && { animation: animation_url },\n animation_original_url && {\n animationOriginal: animation_original_url,\n },\n external_link && { externalLink: external_link },\n last_sale && { lastSale: last_sale },\n schema_name && { standard: schema_name },\n );\n\n return collectibleMetadata;\n }\n\n /**\n * Request individual collectible information from contracts that follows Metadata Interface.\n *\n * @param contractAddress - Hex address of the collectible contract.\n * @param tokenId - The collectible identifier.\n * @returns Promise resolving to the current collectible name and image.\n */\n private async getCollectibleInformationFromTokenURI(\n contractAddress: string,\n tokenId: string,\n ): Promise {\n const { ipfsGateway, useIPFSSubdomains } = this.config;\n const result = await this.getCollectibleURIAndStandard(\n contractAddress,\n tokenId,\n );\n let tokenURI = result[0];\n const standard = result[1];\n\n if (tokenURI.startsWith('ipfs://')) {\n tokenURI = getFormattedIpfsUrl(ipfsGateway, tokenURI, useIPFSSubdomains);\n }\n\n try {\n const object = await handleFetch(tokenURI);\n // TODO: Check image_url existence. This is not part of EIP721 nor EIP1155\n const image = Object.prototype.hasOwnProperty.call(object, 'image')\n ? 'image'\n : /* istanbul ignore next */ 'image_url';\n\n return {\n image: object[image],\n name: object.name,\n description: object.description,\n standard,\n favorite: false,\n };\n } catch {\n return {\n image: null,\n name: null,\n description: null,\n standard: standard || null,\n favorite: false,\n };\n }\n }\n\n /**\n * Retrieve collectible uri with metadata. TODO Update method to use IPFS.\n *\n * @param contractAddress - Collectible contract address.\n * @param tokenId - Collectible token id.\n * @returns Promise resolving collectible uri and token standard.\n */\n private async getCollectibleURIAndStandard(\n contractAddress: string,\n tokenId: string,\n ): Promise<[string, string]> {\n // try ERC721 uri\n try {\n const uri = await this.getERC721TokenURI(contractAddress, tokenId);\n return [uri, ERC721];\n } catch {\n // Ignore error\n }\n\n // try ERC1155 uri\n try {\n const tokenURI = await this.getERC1155TokenURI(contractAddress, tokenId);\n\n /**\n * According to EIP1155 the URI value allows for ID substitution\n * in case the string `{id}` exists.\n * https://eips.ethereum.org/EIPS/eip-1155#metadata\n */\n\n if (!tokenURI.includes('{id}')) {\n return [tokenURI, ERC1155];\n }\n\n const hexTokenId = stripHexPrefix(BNToHex(new BN(tokenId)))\n .padStart(64, '0')\n .toLowerCase();\n return [tokenURI.replace('{id}', hexTokenId), ERC1155];\n } catch {\n // Ignore error\n }\n\n return ['', ''];\n }\n\n /**\n * Request individual collectible information (name, image url and description).\n *\n * @param contractAddress - Hex address of the collectible contract.\n * @param tokenId - The collectible identifier.\n * @returns Promise resolving to the current collectible name and image.\n */\n private async getCollectibleInformation(\n contractAddress: string,\n tokenId: string,\n ): Promise {\n const blockchainMetadata = await safelyExecute(async () => {\n return await this.getCollectibleInformationFromTokenURI(\n contractAddress,\n tokenId,\n );\n });\n\n let openSeaMetadata;\n if (this.config.openSeaEnabled) {\n openSeaMetadata = await safelyExecute(async () => {\n return await this.getCollectibleInformationFromApi(\n contractAddress,\n tokenId,\n );\n });\n }\n return {\n ...openSeaMetadata,\n name: blockchainMetadata.name ?? openSeaMetadata?.name ?? null,\n description:\n blockchainMetadata.description ?? openSeaMetadata?.description ?? null,\n image: blockchainMetadata.image ?? openSeaMetadata?.image ?? null,\n standard:\n blockchainMetadata.standard ?? openSeaMetadata?.standard ?? null,\n };\n }\n\n /**\n * Request collectible contract information from OpenSea API.\n *\n * @param contractAddress - Hex address of the collectible contract.\n * @returns Promise resolving to the current collectible name and image.\n */\n private async getCollectibleContractInformationFromApi(\n contractAddress: string,\n ): Promise {\n /* istanbul ignore if */\n let apiCollectibleContractObject: ApiCollectibleContract | undefined =\n await fetchWithErrorHandling({\n url: this.getCollectibleContractInformationApi({\n contractAddress,\n useProxy: true,\n }),\n });\n\n // if we successfully fetched return the fetched data immediately\n if (apiCollectibleContractObject) {\n return apiCollectibleContractObject;\n }\n\n // if we were unsuccessful in fetching from the API and an OpenSea API key is present\n // attempt to refetch directly against the OpenSea API and if successful return the data immediately\n if (this.openSeaApiKey) {\n apiCollectibleContractObject = await fetchWithErrorHandling({\n url: this.getCollectibleContractInformationApi({\n contractAddress,\n useProxy: false,\n }),\n options: {\n headers: { 'X-API-KEY': this.openSeaApiKey },\n },\n // catch 403 errors (in case API key is down we don't want to blow up)\n errorCodesToCatch: [403],\n });\n\n if (apiCollectibleContractObject) {\n return apiCollectibleContractObject;\n }\n }\n\n // If we've reached this point we were unable to fetch data from either the proxy or opensea so we return\n // the default/null of ApiCollectibleContract\n return {\n address: contractAddress,\n asset_contract_type: null,\n created_date: null,\n schema_name: null,\n symbol: null,\n total_supply: null,\n description: null,\n external_link: null,\n collection: {\n name: null,\n image_url: null,\n },\n };\n }\n\n /**\n * Request collectible contract information from the contract itself.\n *\n * @param contractAddress - Hex address of the collectible contract.\n * @returns Promise resolving to the current collectible name and image.\n */\n private async getCollectibleContractInformationFromContract(\n contractAddress: string,\n ): Promise<\n Partial &\n Pick &\n Pick\n > {\n const name = await this.getERC721AssetName(contractAddress);\n const symbol = await this.getERC721AssetSymbol(contractAddress);\n return {\n collection: { name },\n symbol,\n address: contractAddress,\n };\n }\n\n /**\n * Request collectible contract information from OpenSea API.\n *\n * @param contractAddress - Hex address of the collectible contract.\n * @returns Promise resolving to the collectible contract name, image and description.\n */\n private async getCollectibleContractInformation(\n contractAddress: string,\n ): Promise<\n Partial &\n Pick &\n Pick\n > {\n const blockchainContractData: Partial &\n Pick &\n Pick = await safelyExecute(\n async () => {\n return await this.getCollectibleContractInformationFromContract(\n contractAddress,\n );\n },\n );\n\n let openSeaContractData: Partial | undefined;\n if (this.config.openSeaEnabled) {\n openSeaContractData = await safelyExecute(async () => {\n return await this.getCollectibleContractInformationFromApi(\n contractAddress,\n );\n });\n }\n\n if (blockchainContractData || openSeaContractData) {\n return {\n ...openSeaContractData,\n ...blockchainContractData,\n collection: {\n image_url: null,\n ...openSeaContractData?.collection,\n ...blockchainContractData?.collection,\n },\n };\n }\n\n /* istanbul ignore next */\n return {\n address: contractAddress,\n asset_contract_type: null,\n created_date: null,\n schema_name: null,\n symbol: null,\n total_supply: null,\n description: null,\n external_link: null,\n collection: { name: null, image_url: null },\n };\n }\n\n /**\n * Adds an individual collectible to the stored collectible list.\n *\n * @param address - Hex address of the collectible contract.\n * @param tokenId - The collectible identifier.\n * @param collectibleMetadata - Collectible optional information (name, image and description).\n * @param collectibleContract - An object containing contract data of the collectible being added.\n * @param detection - The chain ID and address of the currently selected network and account at the moment the collectible was detected.\n * @returns Promise resolving to the current collectible list.\n */\n private async addIndividualCollectible(\n address: string,\n tokenId: string,\n collectibleMetadata: CollectibleMetadata,\n collectibleContract: CollectibleContract,\n detection?: AccountParams,\n ): Promise {\n // TODO: Remove unused return\n const releaseLock = await this.mutex.acquire();\n try {\n address = toChecksumHexAddress(address);\n const { allCollectibles } = this.state;\n let chainId, selectedAddress;\n\n if (detection) {\n chainId = detection.chainId;\n selectedAddress = detection.userAddress;\n } else {\n chainId = this.config.chainId;\n selectedAddress = this.config.selectedAddress;\n }\n\n const collectibles = allCollectibles[selectedAddress]?.[chainId] || [];\n\n const existingEntry: Collectible | undefined = collectibles.find(\n (collectible) =>\n collectible.address.toLowerCase() === address.toLowerCase() &&\n collectible.tokenId === tokenId,\n );\n\n if (existingEntry) {\n const differentMetadata = compareCollectiblesMetadata(\n collectibleMetadata,\n existingEntry,\n );\n if (differentMetadata) {\n // TODO: Switch to indexToUpdate\n const indexToRemove = collectibles.findIndex(\n (collectible) =>\n collectible.address.toLowerCase() === address.toLowerCase() &&\n collectible.tokenId === tokenId,\n );\n /* istanbul ignore next */\n if (indexToRemove !== -1) {\n collectibles.splice(indexToRemove, 1);\n }\n } else {\n return collectibles;\n }\n }\n\n const newEntry: Collectible = {\n address,\n tokenId,\n favorite: existingEntry?.favorite || false,\n isCurrentlyOwned: true,\n ...collectibleMetadata,\n };\n\n const newCollectibles = [...collectibles, newEntry];\n this.updateNestedCollectibleState(\n newCollectibles,\n ALL_COLLECTIBLES_STATE_KEY,\n { chainId, userAddress: selectedAddress },\n );\n\n if (this.onCollectibleAdded) {\n this.onCollectibleAdded({\n address,\n symbol: collectibleContract.symbol,\n tokenId: tokenId.toString(),\n standard: collectibleMetadata.standard,\n source: detection ? 'detected' : 'custom',\n });\n }\n\n return newCollectibles;\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Adds a collectible contract to the stored collectible contracts list.\n *\n * @param address - Hex address of the collectible contract.\n * @param detection - The chain ID and address of the currently selected network and account at the moment the collectible was detected.\n * @returns Promise resolving to the current collectible contracts list.\n */\n private async addCollectibleContract(\n address: string,\n detection?: AccountParams,\n ): Promise {\n const releaseLock = await this.mutex.acquire();\n try {\n address = toChecksumHexAddress(address);\n const { allCollectibleContracts } = this.state;\n\n let chainId, selectedAddress;\n if (detection) {\n chainId = detection.chainId;\n selectedAddress = detection.userAddress;\n } else {\n chainId = this.config.chainId;\n selectedAddress = this.config.selectedAddress;\n }\n\n const collectibleContracts =\n allCollectibleContracts[selectedAddress]?.[chainId] || [];\n\n const existingEntry = collectibleContracts.find(\n (collectibleContract) =>\n collectibleContract.address.toLowerCase() === address.toLowerCase(),\n );\n if (existingEntry) {\n return collectibleContracts;\n }\n const contractInformation = await this.getCollectibleContractInformation(\n address,\n );\n\n const {\n asset_contract_type,\n created_date,\n schema_name,\n symbol,\n total_supply,\n description,\n external_link,\n collection: { name, image_url },\n } = contractInformation;\n // If being auto-detected opensea information is expected\n // Otherwise at least name from the contract is needed\n if (\n (detection && !name) ||\n Object.keys(contractInformation).length === 0\n ) {\n return collectibleContracts;\n }\n\n /* istanbul ignore next */\n const newEntry: CollectibleContract = Object.assign(\n {},\n { address },\n description && { description },\n name && { name },\n image_url && { logo: image_url },\n symbol && { symbol },\n total_supply !== null &&\n typeof total_supply !== 'undefined' && { totalSupply: total_supply },\n asset_contract_type && { assetContractType: asset_contract_type },\n created_date && { createdDate: created_date },\n schema_name && { schemaName: schema_name },\n external_link && { externalLink: external_link },\n );\n const newCollectibleContracts = [...collectibleContracts, newEntry];\n this.updateNestedCollectibleState(\n newCollectibleContracts,\n ALL_COLLECTIBLES_CONTRACTS_STATE_KEY,\n { chainId, userAddress: selectedAddress },\n );\n\n return newCollectibleContracts;\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Removes an individual collectible from the stored token list and saves it in ignored collectibles list.\n *\n * @param address - Hex address of the collectible contract.\n * @param tokenId - Token identifier of the collectible.\n */\n private removeAndIgnoreIndividualCollectible(\n address: string,\n tokenId: string,\n ) {\n address = toChecksumHexAddress(address);\n const { allCollectibles, ignoredCollectibles } = this.state;\n const { chainId, selectedAddress } = this.config;\n const newIgnoredCollectibles = [...ignoredCollectibles];\n const collectibles = allCollectibles[selectedAddress]?.[chainId] || [];\n const newCollectibles = collectibles.filter((collectible) => {\n if (\n collectible.address.toLowerCase() === address.toLowerCase() &&\n collectible.tokenId === tokenId\n ) {\n const alreadyIgnored = newIgnoredCollectibles.find(\n (c) => c.address === address && c.tokenId === tokenId,\n );\n !alreadyIgnored && newIgnoredCollectibles.push(collectible);\n return false;\n }\n return true;\n });\n\n this.updateNestedCollectibleState(\n newCollectibles,\n ALL_COLLECTIBLES_STATE_KEY,\n );\n\n this.update({\n ignoredCollectibles: newIgnoredCollectibles,\n });\n }\n\n /**\n * Removes an individual collectible from the stored token list.\n *\n * @param address - Hex address of the collectible contract.\n * @param tokenId - Token identifier of the collectible.\n */\n private removeIndividualCollectible(address: string, tokenId: string) {\n address = toChecksumHexAddress(address);\n const { allCollectibles } = this.state;\n const { chainId, selectedAddress } = this.config;\n const collectibles = allCollectibles[selectedAddress]?.[chainId] || [];\n const newCollectibles = collectibles.filter(\n (collectible) =>\n !(\n collectible.address.toLowerCase() === address.toLowerCase() &&\n collectible.tokenId === tokenId\n ),\n );\n this.updateNestedCollectibleState(\n newCollectibles,\n ALL_COLLECTIBLES_STATE_KEY,\n );\n }\n\n /**\n * Removes a collectible contract to the stored collectible contracts list.\n *\n * @param address - Hex address of the collectible contract.\n * @returns Promise resolving to the current collectible contracts list.\n */\n private removeCollectibleContract(address: string): CollectibleContract[] {\n address = toChecksumHexAddress(address);\n const { allCollectibleContracts } = this.state;\n const { chainId, selectedAddress } = this.config;\n const collectibleContracts =\n allCollectibleContracts[selectedAddress]?.[chainId] || [];\n\n const newCollectibleContracts = collectibleContracts.filter(\n (collectibleContract) =>\n !(collectibleContract.address.toLowerCase() === address.toLowerCase()),\n );\n this.updateNestedCollectibleState(\n newCollectibleContracts,\n ALL_COLLECTIBLES_CONTRACTS_STATE_KEY,\n );\n\n return newCollectibleContracts;\n }\n\n /**\n * EventEmitter instance used to listen to specific EIP747 events\n */\n hub = new EventEmitter();\n\n /**\n * Optional API key to use with opensea\n */\n openSeaApiKey?: string;\n\n /**\n * Name of this controller used during composition\n */\n override name = 'CollectiblesController';\n\n private getERC721AssetName: AssetsContractController['getERC721AssetName'];\n\n private getERC721AssetSymbol: AssetsContractController['getERC721AssetSymbol'];\n\n private getERC721TokenURI: AssetsContractController['getERC721TokenURI'];\n\n private getERC721OwnerOf: AssetsContractController['getERC721OwnerOf'];\n\n private getERC1155BalanceOf: AssetsContractController['getERC1155BalanceOf'];\n\n private getERC1155TokenURI: AssetsContractController['getERC1155TokenURI'];\n\n private onCollectibleAdded?: (data: {\n address: string;\n symbol: string | undefined;\n tokenId: string;\n standard: string | null;\n source: string;\n }) => void;\n\n /**\n * Creates a CollectiblesController instance.\n *\n * @param options - The controller options.\n * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes.\n * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.\n * @param options.getERC721AssetName - Gets the name of the asset at the given address.\n * @param options.getERC721AssetSymbol - Gets the symbol of the asset at the given address.\n * @param options.getERC721TokenURI - Gets the URI of the ERC721 token at the given address, with the given ID.\n * @param options.getERC721OwnerOf - Get the owner of a ERC-721 collectible.\n * @param options.getERC1155BalanceOf - Gets balance of a ERC-1155 collectible.\n * @param options.getERC1155TokenURI - Gets the URI of the ERC1155 token at the given address, with the given ID.\n * @param options.onCollectibleAdded - Callback that is called when a collectible is added. Currently used pass data\n * for tracking the collectible added event.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n onPreferencesStateChange,\n onNetworkStateChange,\n getERC721AssetName,\n getERC721AssetSymbol,\n getERC721TokenURI,\n getERC721OwnerOf,\n getERC1155BalanceOf,\n getERC1155TokenURI,\n onCollectibleAdded,\n }: {\n onPreferencesStateChange: (\n listener: (preferencesState: PreferencesState) => void,\n ) => void;\n onNetworkStateChange: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n getERC721AssetName: AssetsContractController['getERC721AssetName'];\n getERC721AssetSymbol: AssetsContractController['getERC721AssetSymbol'];\n getERC721TokenURI: AssetsContractController['getERC721TokenURI'];\n getERC721OwnerOf: AssetsContractController['getERC721OwnerOf'];\n getERC1155BalanceOf: AssetsContractController['getERC1155BalanceOf'];\n getERC1155TokenURI: AssetsContractController['getERC1155TokenURI'];\n onCollectibleAdded?: (data: {\n address: string;\n symbol: string | undefined;\n tokenId: string;\n standard: string | null;\n source: string;\n }) => void;\n },\n config?: Partial,\n state?: Partial,\n ) {\n super(config, state);\n this.defaultConfig = {\n networkType: MAINNET,\n selectedAddress: '',\n chainId: '',\n ipfsGateway: IPFS_DEFAULT_GATEWAY_URL,\n openSeaEnabled: false,\n useIPFSSubdomains: true,\n };\n\n this.defaultState = {\n allCollectibleContracts: {},\n allCollectibles: {},\n ignoredCollectibles: [],\n };\n this.initialize();\n this.getERC721AssetName = getERC721AssetName;\n this.getERC721AssetSymbol = getERC721AssetSymbol;\n this.getERC721TokenURI = getERC721TokenURI;\n this.getERC721OwnerOf = getERC721OwnerOf;\n this.getERC1155BalanceOf = getERC1155BalanceOf;\n this.getERC1155TokenURI = getERC1155TokenURI;\n this.onCollectibleAdded = onCollectibleAdded;\n\n onPreferencesStateChange(\n ({ selectedAddress, ipfsGateway, openSeaEnabled }) => {\n this.configure({ selectedAddress, ipfsGateway, openSeaEnabled });\n },\n );\n\n onNetworkStateChange(({ provider }) => {\n const { chainId } = provider;\n this.configure({ chainId });\n });\n }\n\n /**\n * Sets an OpenSea API key to retrieve collectible information.\n *\n * @param openSeaApiKey - OpenSea API key.\n */\n setApiKey(openSeaApiKey: string) {\n this.openSeaApiKey = openSeaApiKey;\n }\n\n /**\n * Checks the ownership of a ERC-721 or ERC-1155 collectible for a given address.\n *\n * @param ownerAddress - User public address.\n * @param collectibleAddress - Collectible contract address.\n * @param collectibleId - Collectible token ID.\n * @returns Promise resolving the collectible ownership.\n */\n async isCollectibleOwner(\n ownerAddress: string,\n collectibleAddress: string,\n collectibleId: string,\n ): Promise {\n // Checks the ownership for ERC-721.\n try {\n const owner = await this.getERC721OwnerOf(\n collectibleAddress,\n collectibleId,\n );\n return ownerAddress.toLowerCase() === owner.toLowerCase();\n // eslint-disable-next-line no-empty\n } catch {\n // Ignore ERC-721 contract error\n }\n\n // Checks the ownership for ERC-1155.\n try {\n const balance = await this.getERC1155BalanceOf(\n ownerAddress,\n collectibleAddress,\n collectibleId,\n );\n return balance > 0;\n // eslint-disable-next-line no-empty\n } catch {\n // Ignore ERC-1155 contract error\n }\n\n throw new Error(\n 'Unable to verify ownership. Probably because the standard is not supported or the chain is incorrect.',\n );\n }\n\n /**\n * Verifies currently selected address owns entered collectible address/tokenId combo and\n * adds the collectible and respective collectible contract to the stored collectible and collectible contracts lists.\n *\n * @param address - Hex address of the collectible contract.\n * @param tokenId - The collectible identifier.\n */\n async addCollectibleVerifyOwnership(address: string, tokenId: string) {\n const { selectedAddress } = this.config;\n if (!(await this.isCollectibleOwner(selectedAddress, address, tokenId))) {\n throw new Error('This collectible is not owned by the user');\n }\n await this.addCollectible(address, tokenId);\n }\n\n /**\n * Adds a collectible and respective collectible contract to the stored collectible and collectible contracts lists.\n *\n * @param address - Hex address of the collectible contract.\n * @param tokenId - The collectible identifier.\n * @param collectibleMetadata - Collectible optional metadata.\n * @param detection - The chain ID and address of the currently selected network and account at the moment the collectible was detected.\n * @returns Promise resolving to the current collectible list.\n */\n async addCollectible(\n address: string,\n tokenId: string,\n collectibleMetadata?: CollectibleMetadata,\n detection?: AccountParams,\n ) {\n address = toChecksumHexAddress(address);\n const newCollectibleContracts = await this.addCollectibleContract(\n address,\n detection,\n );\n collectibleMetadata =\n collectibleMetadata ||\n (await this.getCollectibleInformation(address, tokenId));\n\n // If collectible contract was not added, do not add individual collectible\n const collectibleContract = newCollectibleContracts.find(\n (contract) => contract.address.toLowerCase() === address.toLowerCase(),\n );\n\n // If collectible contract information, add individual collectible\n if (collectibleContract) {\n await this.addIndividualCollectible(\n address,\n tokenId,\n collectibleMetadata,\n collectibleContract,\n detection,\n );\n }\n }\n\n /**\n * Removes a collectible from the stored token list.\n *\n * @param address - Hex address of the collectible contract.\n * @param tokenId - Token identifier of the collectible.\n */\n removeCollectible(address: string, tokenId: string) {\n address = toChecksumHexAddress(address);\n this.removeIndividualCollectible(address, tokenId);\n const { allCollectibles } = this.state;\n const { chainId, selectedAddress } = this.config;\n const collectibles = allCollectibles[selectedAddress]?.[chainId] || [];\n const remainingCollectible = collectibles.find(\n (collectible) =>\n collectible.address.toLowerCase() === address.toLowerCase(),\n );\n if (!remainingCollectible) {\n this.removeCollectibleContract(address);\n }\n }\n\n /**\n * Removes a collectible from the stored token list and saves it in ignored collectibles list.\n *\n * @param address - Hex address of the collectible contract.\n * @param tokenId - Token identifier of the collectible.\n */\n removeAndIgnoreCollectible(address: string, tokenId: string) {\n address = toChecksumHexAddress(address);\n this.removeAndIgnoreIndividualCollectible(address, tokenId);\n const { allCollectibles } = this.state;\n const { chainId, selectedAddress } = this.config;\n const collectibles = allCollectibles[selectedAddress]?.[chainId] || [];\n const remainingCollectible = collectibles.find(\n (collectible) =>\n collectible.address.toLowerCase() === address.toLowerCase(),\n );\n if (!remainingCollectible) {\n this.removeCollectibleContract(address);\n }\n }\n\n /**\n * Removes all collectibles from the ignored list.\n */\n clearIgnoredCollectibles() {\n this.update({ ignoredCollectibles: [] });\n }\n\n /**\n * Checks whether input collectible is still owned by the user\n * And updates the isCurrentlyOwned value on the collectible object accordingly.\n *\n * @param collectible - The collectible object to check and update.\n * @param batch - A boolean indicating whether this method is being called as part of a batch or single update.\n * @param accountParams - The userAddress and chainId to check ownership against\n * @param accountParams.userAddress - the address passed through the confirmed transaction flow to ensure detected assets are stored to the correct account\n * @param accountParams.chainId - the chainId passed through the confirmed transaction flow to ensure detected assets are stored to the correct account\n * @returns the collectible with the updated isCurrentlyOwned value\n */\n async checkAndUpdateSingleCollectibleOwnershipStatus(\n collectible: Collectible,\n batch: boolean,\n { userAddress, chainId }: AccountParams | undefined = {\n userAddress: this.config.selectedAddress,\n chainId: this.config.chainId,\n },\n ) {\n const { address, tokenId } = collectible;\n let isOwned = collectible.isCurrentlyOwned;\n try {\n isOwned = await this.isCollectibleOwner(userAddress, address, tokenId);\n } catch (error) {\n if (\n !(\n error instanceof Error &&\n error.message.includes('Unable to verify ownership')\n )\n ) {\n throw error;\n }\n }\n\n collectible.isCurrentlyOwned = isOwned;\n\n if (batch === true) {\n return collectible;\n }\n\n // if this is not part of a batched update we update this one collectible in state\n const { allCollectibles } = this.state;\n const collectibles = allCollectibles[userAddress]?.[chainId] || [];\n const collectibleToUpdate = collectibles.find(\n (item) =>\n item.tokenId === tokenId &&\n item.address.toLowerCase() === address.toLowerCase(),\n );\n if (collectibleToUpdate) {\n collectibleToUpdate.isCurrentlyOwned = isOwned;\n this.updateNestedCollectibleState(\n collectibles,\n ALL_COLLECTIBLES_STATE_KEY,\n { userAddress, chainId },\n );\n }\n return collectible;\n }\n\n /**\n * Checks whether Collectibles associated with current selectedAddress/chainId combination are still owned by the user\n * And updates the isCurrentlyOwned value on each accordingly.\n */\n async checkAndUpdateAllCollectiblesOwnershipStatus() {\n const { allCollectibles } = this.state;\n const { chainId, selectedAddress } = this.config;\n const collectibles = allCollectibles[selectedAddress]?.[chainId] || [];\n const updatedCollectibles = await Promise.all(\n collectibles.map(async (collectible) => {\n return (\n (await this.checkAndUpdateSingleCollectibleOwnershipStatus(\n collectible,\n true,\n )) ?? collectible\n );\n }),\n );\n\n this.updateNestedCollectibleState(\n updatedCollectibles,\n ALL_COLLECTIBLES_STATE_KEY,\n );\n }\n\n /**\n * Update collectible favorite status.\n *\n * @param address - Hex address of the collectible contract.\n * @param tokenId - Hex address of the collectible contract.\n * @param favorite - Collectible new favorite status.\n */\n updateCollectibleFavoriteStatus(\n address: string,\n tokenId: string,\n favorite: boolean,\n ) {\n const { allCollectibles } = this.state;\n const { chainId, selectedAddress } = this.config;\n const collectibles = allCollectibles[selectedAddress]?.[chainId] || [];\n const index: number = collectibles.findIndex(\n (collectible) =>\n collectible.address === address && collectible.tokenId === tokenId,\n );\n\n if (index === -1) {\n return;\n }\n\n const updatedCollectible: Collectible = {\n ...collectibles[index],\n favorite,\n };\n\n // Update Collectibles array\n collectibles[index] = updatedCollectible;\n\n this.updateNestedCollectibleState(collectibles, ALL_COLLECTIBLES_STATE_KEY);\n }\n}\n\nexport default CollectiblesController;\n"]} \ No newline at end of file diff --git a/dist/assets/CurrencyRateController.d.ts b/dist/assets/CurrencyRateController.d.ts deleted file mode 100644 index 3395bc7276..0000000000 --- a/dist/assets/CurrencyRateController.d.ts +++ /dev/null @@ -1,99 +0,0 @@ -import type { Patch } from 'immer'; -import { BaseController } from '../BaseControllerV2'; -import { fetchExchangeRate as defaultFetchExchangeRate } from '../apis/crypto-compare'; -import type { RestrictedControllerMessenger } from '../ControllerMessenger'; -/** - * @type CurrencyRateState - * @property conversionDate - Timestamp of conversion rate expressed in ms since UNIX epoch - * @property conversionRate - Conversion rate from current base asset to the current currency - * @property currentCurrency - Currently-active ISO 4217 currency code - * @property nativeCurrency - Symbol for the base asset used for conversion - * @property pendingCurrentCurrency - The currency being switched to - * @property pendingNativeCurrency - The base asset currency being switched to - * @property usdConversionRate - Conversion rate from usd to the current currency - */ -export declare type CurrencyRateState = { - conversionDate: number | null; - conversionRate: number | null; - currentCurrency: string; - nativeCurrency: string; - pendingCurrentCurrency: string | null; - pendingNativeCurrency: string | null; - usdConversionRate: number | null; -}; -declare const name = "CurrencyRateController"; -export declare type CurrencyRateStateChange = { - type: `${typeof name}:stateChange`; - payload: [CurrencyRateState, Patch[]]; -}; -export declare type GetCurrencyRateState = { - type: `${typeof name}:getState`; - handler: () => CurrencyRateState; -}; -declare type CurrencyRateMessenger = RestrictedControllerMessenger; -/** - * Controller that passively polls on a set interval for an exchange rate from the current base - * asset to the current currency - */ -export declare class CurrencyRateController extends BaseController { - private mutex; - private intervalId?; - private intervalDelay; - private fetchExchangeRate; - private includeUsdRate; - /** - * Creates a CurrencyRateController instance. - * - * @param options - Constructor options. - * @param options.includeUsdRate - Keep track of the USD rate in addition to the current currency rate. - * @param options.interval - The polling interval, in milliseconds. - * @param options.messenger - A reference to the messaging system. - * @param options.state - Initial state to set on this controller. - * @param options.fetchExchangeRate - Fetches the exchange rate from an external API. This option is primarily meant for use in unit tests. - */ - constructor({ includeUsdRate, interval, messenger, state, fetchExchangeRate, }: { - includeUsdRate?: boolean; - interval?: number; - messenger: CurrencyRateMessenger; - state?: Partial; - fetchExchangeRate?: typeof defaultFetchExchangeRate; - }); - /** - * Start polling for the currency rate. - */ - start(): Promise; - /** - * Stop polling for the currency rate. - */ - stop(): void; - /** - * Prepare to discard this controller. - * - * This stops any active polling. - */ - destroy(): void; - /** - * Sets a currency to track. - * - * @param currentCurrency - ISO 4217 currency code. - */ - setCurrentCurrency(currentCurrency: string): Promise; - /** - * Sets a new native currency. - * - * @param symbol - Symbol for the base asset. - */ - setNativeCurrency(symbol: string): Promise; - private stopPolling; - /** - * Starts a new polling interval. - */ - private startPolling; - /** - * Updates exchange rate for the current currency. - * - * @returns The controller state. - */ - updateExchangeRate(): Promise; -} -export default CurrencyRateController; diff --git a/dist/assets/CurrencyRateController.js b/dist/assets/CurrencyRateController.js deleted file mode 100644 index c7a50ee88b..0000000000 --- a/dist/assets/CurrencyRateController.js +++ /dev/null @@ -1,194 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.CurrencyRateController = void 0; -const async_mutex_1 = require("async-mutex"); -const BaseControllerV2_1 = require("../BaseControllerV2"); -const util_1 = require("../util"); -const crypto_compare_1 = require("../apis/crypto-compare"); -const constants_1 = require("../constants"); -const name = 'CurrencyRateController'; -const metadata = { - conversionDate: { persist: true, anonymous: true }, - conversionRate: { persist: true, anonymous: true }, - currentCurrency: { persist: true, anonymous: true }, - nativeCurrency: { persist: true, anonymous: true }, - pendingCurrentCurrency: { persist: false, anonymous: true }, - pendingNativeCurrency: { persist: false, anonymous: true }, - usdConversionRate: { persist: true, anonymous: true }, -}; -const defaultState = { - conversionDate: 0, - conversionRate: 0, - currentCurrency: 'usd', - nativeCurrency: 'ETH', - pendingCurrentCurrency: null, - pendingNativeCurrency: null, - usdConversionRate: null, -}; -/** - * Controller that passively polls on a set interval for an exchange rate from the current base - * asset to the current currency - */ -class CurrencyRateController extends BaseControllerV2_1.BaseController { - /** - * Creates a CurrencyRateController instance. - * - * @param options - Constructor options. - * @param options.includeUsdRate - Keep track of the USD rate in addition to the current currency rate. - * @param options.interval - The polling interval, in milliseconds. - * @param options.messenger - A reference to the messaging system. - * @param options.state - Initial state to set on this controller. - * @param options.fetchExchangeRate - Fetches the exchange rate from an external API. This option is primarily meant for use in unit tests. - */ - constructor({ includeUsdRate = false, interval = 180000, messenger, state, fetchExchangeRate = crypto_compare_1.fetchExchangeRate, }) { - super({ - name, - metadata, - messenger, - state: Object.assign(Object.assign({}, defaultState), state), - }); - this.mutex = new async_mutex_1.Mutex(); - this.includeUsdRate = includeUsdRate; - this.intervalDelay = interval; - this.fetchExchangeRate = fetchExchangeRate; - } - /** - * Start polling for the currency rate. - */ - start() { - return __awaiter(this, void 0, void 0, function* () { - yield this.startPolling(); - }); - } - /** - * Stop polling for the currency rate. - */ - stop() { - this.stopPolling(); - } - /** - * Prepare to discard this controller. - * - * This stops any active polling. - */ - destroy() { - super.destroy(); - this.stopPolling(); - } - /** - * Sets a currency to track. - * - * @param currentCurrency - ISO 4217 currency code. - */ - setCurrentCurrency(currentCurrency) { - return __awaiter(this, void 0, void 0, function* () { - this.update((state) => { - state.pendingCurrentCurrency = currentCurrency; - }); - yield this.updateExchangeRate(); - }); - } - /** - * Sets a new native currency. - * - * @param symbol - Symbol for the base asset. - */ - setNativeCurrency(symbol) { - return __awaiter(this, void 0, void 0, function* () { - this.update((state) => { - state.pendingNativeCurrency = symbol; - }); - yield this.updateExchangeRate(); - }); - } - stopPolling() { - if (this.intervalId) { - clearInterval(this.intervalId); - } - } - /** - * Starts a new polling interval. - */ - startPolling() { - return __awaiter(this, void 0, void 0, function* () { - this.stopPolling(); - // TODO: Expose polling currency rate update errors - yield (0, util_1.safelyExecute)(() => this.updateExchangeRate()); - this.intervalId = setInterval(() => __awaiter(this, void 0, void 0, function* () { - yield (0, util_1.safelyExecute)(() => this.updateExchangeRate()); - }), this.intervalDelay); - }); - } - /** - * Updates exchange rate for the current currency. - * - * @returns The controller state. - */ - updateExchangeRate() { - return __awaiter(this, void 0, void 0, function* () { - const releaseLock = yield this.mutex.acquire(); - const { currentCurrency: stateCurrentCurrency, nativeCurrency: stateNativeCurrency, pendingCurrentCurrency, pendingNativeCurrency, } = this.state; - let conversionDate = null; - let conversionRate = null; - let usdConversionRate = null; - const currentCurrency = pendingCurrentCurrency !== null && pendingCurrentCurrency !== void 0 ? pendingCurrentCurrency : stateCurrentCurrency; - const nativeCurrency = pendingNativeCurrency !== null && pendingNativeCurrency !== void 0 ? pendingNativeCurrency : stateNativeCurrency; - // For preloaded testnets (Rinkeby, Ropsten, Goerli, Kovan) we want to fetch exchange rate for real ETH. - const nativeCurrencyForExchangeRate = Object.values(constants_1.TESTNET_TICKER_SYMBOLS).includes(nativeCurrency) - ? constants_1.FALL_BACK_VS_CURRENCY // ETH - : nativeCurrency; - try { - if (currentCurrency && - nativeCurrency && - // if either currency is an empty string we can skip the comparison - // because it will result in an error from the api and ultimately - // a null conversionRate either way. - currentCurrency !== '' && - nativeCurrency !== '') { - ({ conversionRate, usdConversionRate } = yield this.fetchExchangeRate(currentCurrency, nativeCurrencyForExchangeRate, this.includeUsdRate)); - conversionDate = Date.now() / 1000; - } - } - catch (error) { - if (!(error instanceof Error && - error.message.includes('market does not exist for this coin pair'))) { - throw error; - } - } - finally { - try { - this.update(() => { - return { - conversionDate, - conversionRate, - // we currently allow and handle an empty string as a valid nativeCurrency - // in cases where a user has not entered a native ticker symbol for a custom network - // currentCurrency is not from user input but this protects us from unexpected changes. - nativeCurrency, - currentCurrency, - pendingCurrentCurrency: null, - pendingNativeCurrency: null, - usdConversionRate, - }; - }); - } - finally { - releaseLock(); - } - } - return this.state; - }); - } -} -exports.CurrencyRateController = CurrencyRateController; -exports.default = CurrencyRateController; -//# sourceMappingURL=CurrencyRateController.js.map \ No newline at end of file diff --git a/dist/assets/CurrencyRateController.js.map b/dist/assets/CurrencyRateController.js.map deleted file mode 100644 index e6aded6d8e..0000000000 --- a/dist/assets/CurrencyRateController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"CurrencyRateController.js","sourceRoot":"","sources":["../../src/assets/CurrencyRateController.ts"],"names":[],"mappings":"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","sourcesContent":["import { Mutex } from 'async-mutex';\nimport type { Patch } from 'immer';\n\nimport { BaseController } from '../BaseControllerV2';\nimport { safelyExecute } from '../util';\nimport { fetchExchangeRate as defaultFetchExchangeRate } from '../apis/crypto-compare';\n\nimport type { RestrictedControllerMessenger } from '../ControllerMessenger';\nimport { TESTNET_TICKER_SYMBOLS, FALL_BACK_VS_CURRENCY } from '../constants';\n\n/**\n * @type CurrencyRateState\n * @property conversionDate - Timestamp of conversion rate expressed in ms since UNIX epoch\n * @property conversionRate - Conversion rate from current base asset to the current currency\n * @property currentCurrency - Currently-active ISO 4217 currency code\n * @property nativeCurrency - Symbol for the base asset used for conversion\n * @property pendingCurrentCurrency - The currency being switched to\n * @property pendingNativeCurrency - The base asset currency being switched to\n * @property usdConversionRate - Conversion rate from usd to the current currency\n */\nexport type CurrencyRateState = {\n conversionDate: number | null;\n conversionRate: number | null;\n currentCurrency: string;\n nativeCurrency: string;\n pendingCurrentCurrency: string | null;\n pendingNativeCurrency: string | null;\n usdConversionRate: number | null;\n};\n\nconst name = 'CurrencyRateController';\n\nexport type CurrencyRateStateChange = {\n type: `${typeof name}:stateChange`;\n payload: [CurrencyRateState, Patch[]];\n};\n\nexport type GetCurrencyRateState = {\n type: `${typeof name}:getState`;\n handler: () => CurrencyRateState;\n};\n\ntype CurrencyRateMessenger = RestrictedControllerMessenger<\n typeof name,\n GetCurrencyRateState,\n CurrencyRateStateChange,\n never,\n never\n>;\n\nconst metadata = {\n conversionDate: { persist: true, anonymous: true },\n conversionRate: { persist: true, anonymous: true },\n currentCurrency: { persist: true, anonymous: true },\n nativeCurrency: { persist: true, anonymous: true },\n pendingCurrentCurrency: { persist: false, anonymous: true },\n pendingNativeCurrency: { persist: false, anonymous: true },\n usdConversionRate: { persist: true, anonymous: true },\n};\n\nconst defaultState = {\n conversionDate: 0,\n conversionRate: 0,\n currentCurrency: 'usd',\n nativeCurrency: 'ETH',\n pendingCurrentCurrency: null,\n pendingNativeCurrency: null,\n usdConversionRate: null,\n};\n\n/**\n * Controller that passively polls on a set interval for an exchange rate from the current base\n * asset to the current currency\n */\nexport class CurrencyRateController extends BaseController<\n typeof name,\n CurrencyRateState,\n CurrencyRateMessenger\n> {\n private mutex = new Mutex();\n\n private intervalId?: NodeJS.Timeout;\n\n private intervalDelay;\n\n private fetchExchangeRate;\n\n private includeUsdRate;\n\n /**\n * Creates a CurrencyRateController instance.\n *\n * @param options - Constructor options.\n * @param options.includeUsdRate - Keep track of the USD rate in addition to the current currency rate.\n * @param options.interval - The polling interval, in milliseconds.\n * @param options.messenger - A reference to the messaging system.\n * @param options.state - Initial state to set on this controller.\n * @param options.fetchExchangeRate - Fetches the exchange rate from an external API. This option is primarily meant for use in unit tests.\n */\n constructor({\n includeUsdRate = false,\n interval = 180000,\n messenger,\n state,\n fetchExchangeRate = defaultFetchExchangeRate,\n }: {\n includeUsdRate?: boolean;\n interval?: number;\n messenger: CurrencyRateMessenger;\n state?: Partial;\n fetchExchangeRate?: typeof defaultFetchExchangeRate;\n }) {\n super({\n name,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n this.includeUsdRate = includeUsdRate;\n this.intervalDelay = interval;\n this.fetchExchangeRate = fetchExchangeRate;\n }\n\n /**\n * Start polling for the currency rate.\n */\n async start() {\n await this.startPolling();\n }\n\n /**\n * Stop polling for the currency rate.\n */\n stop() {\n this.stopPolling();\n }\n\n /**\n * Prepare to discard this controller.\n *\n * This stops any active polling.\n */\n override destroy() {\n super.destroy();\n this.stopPolling();\n }\n\n /**\n * Sets a currency to track.\n *\n * @param currentCurrency - ISO 4217 currency code.\n */\n async setCurrentCurrency(currentCurrency: string) {\n this.update((state) => {\n state.pendingCurrentCurrency = currentCurrency;\n });\n await this.updateExchangeRate();\n }\n\n /**\n * Sets a new native currency.\n *\n * @param symbol - Symbol for the base asset.\n */\n async setNativeCurrency(symbol: string) {\n this.update((state) => {\n state.pendingNativeCurrency = symbol;\n });\n await this.updateExchangeRate();\n }\n\n private stopPolling() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n }\n\n /**\n * Starts a new polling interval.\n */\n private async startPolling(): Promise {\n this.stopPolling();\n // TODO: Expose polling currency rate update errors\n await safelyExecute(() => this.updateExchangeRate());\n this.intervalId = setInterval(async () => {\n await safelyExecute(() => this.updateExchangeRate());\n }, this.intervalDelay);\n }\n\n /**\n * Updates exchange rate for the current currency.\n *\n * @returns The controller state.\n */\n async updateExchangeRate(): Promise {\n const releaseLock = await this.mutex.acquire();\n const {\n currentCurrency: stateCurrentCurrency,\n nativeCurrency: stateNativeCurrency,\n pendingCurrentCurrency,\n pendingNativeCurrency,\n } = this.state;\n\n let conversionDate: number | null = null;\n let conversionRate: number | null = null;\n let usdConversionRate: number | null = null;\n const currentCurrency = pendingCurrentCurrency ?? stateCurrentCurrency;\n const nativeCurrency = pendingNativeCurrency ?? stateNativeCurrency;\n\n // For preloaded testnets (Rinkeby, Ropsten, Goerli, Kovan) we want to fetch exchange rate for real ETH.\n const nativeCurrencyForExchangeRate = Object.values(\n TESTNET_TICKER_SYMBOLS,\n ).includes(nativeCurrency)\n ? FALL_BACK_VS_CURRENCY // ETH\n : nativeCurrency;\n\n try {\n if (\n currentCurrency &&\n nativeCurrency &&\n // if either currency is an empty string we can skip the comparison\n // because it will result in an error from the api and ultimately\n // a null conversionRate either way.\n currentCurrency !== '' &&\n nativeCurrency !== ''\n ) {\n ({ conversionRate, usdConversionRate } = await this.fetchExchangeRate(\n currentCurrency,\n nativeCurrencyForExchangeRate,\n this.includeUsdRate,\n ));\n conversionDate = Date.now() / 1000;\n }\n } catch (error) {\n if (\n !(\n error instanceof Error &&\n error.message.includes('market does not exist for this coin pair')\n )\n ) {\n throw error;\n }\n } finally {\n try {\n this.update(() => {\n return {\n conversionDate,\n conversionRate,\n // we currently allow and handle an empty string as a valid nativeCurrency\n // in cases where a user has not entered a native ticker symbol for a custom network\n // currentCurrency is not from user input but this protects us from unexpected changes.\n nativeCurrency,\n currentCurrency,\n pendingCurrentCurrency: null,\n pendingNativeCurrency: null,\n usdConversionRate,\n };\n });\n } finally {\n releaseLock();\n }\n }\n return this.state;\n }\n}\n\nexport default CurrencyRateController;\n"]} \ No newline at end of file diff --git a/dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.d.ts b/dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.d.ts deleted file mode 100644 index 06e5050ce2..0000000000 --- a/dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.d.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Web3 } from '../../standards-types'; -export declare class ERC1155Standard { - private web3; - constructor(web3: Web3); - /** - * Query if contract implements ERC1155 URI Metadata interface. - * - * @param address - ERC1155 asset contract address. - * @returns Promise resolving to whether the contract implements ERC1155 URI Metadata interface. - */ - contractSupportsURIMetadataInterface: (address: string) => Promise; - /** - * Query if contract implements ERC1155 Token Receiver interface. - * - * @param address - ERC1155 asset contract address. - * @returns Promise resolving to whether the contract implements ERC1155 Token Receiver interface. - */ - contractSupportsTokenReceiverInterface: (address: string) => Promise; - /** - * Query if contract implements ERC1155 interface. - * - * @param address - ERC1155 asset contract address. - * @returns Promise resolving to whether the contract implements the base ERC1155 interface. - */ - contractSupportsBase1155Interface: (address: string) => Promise; - /** - * Query for tokenURI for a given asset. - * - * @param address - ERC1155 asset contract address. - * @param tokenId - ERC1155 asset identifier. - * @returns Promise resolving to the 'tokenURI'. - */ - getTokenURI: (address: string, tokenId: string) => Promise; - /** - * Query for balance of a given ERC1155 token. - * - * @param contractAddress - ERC1155 asset contract address. - * @param address - Wallet public address. - * @param tokenId - ERC1155 asset identifier. - * @returns Promise resolving to the 'balanceOf'. - */ - getBalanceOf: (contractAddress: string, address: string, tokenId: string) => Promise; - /** - * Transfer single ERC1155 token. - * When minting/creating tokens, the from arg MUST be set to 0x0 (i.e. zero address). - * When burning/destroying tokens, the to arg MUST be set to 0x0 (i.e. zero address). - * - * @param operator - ERC1155 token address. - * @param from - ERC1155 token holder. - * @param to - ERC1155 token recipient. - * @param id - ERC1155 token id. - * @param value - Number of tokens to be sent. - * @returns Promise resolving to the 'transferSingle'. - */ - transferSingle: (operator: string, from: string, to: string, id: string, value: string) => Promise; - /** - * Query if a contract implements an interface. - * - * @param address - ERC1155 asset contract address. - * @param interfaceId - Interface identifier. - * @returns Promise resolving to whether the contract implements `interfaceID`. - */ - private contractSupportsInterface; - /** - * Query if a contract implements an interface. - * - * @param address - Asset contract address. - * @param ipfsGateway - The user's preferred IPFS gateway. - * @param tokenId - tokenId of a given token in the contract. - * @returns Promise resolving an object containing the standard, tokenURI, symbol and name of the given contract/tokenId pair. - */ - getDetails: (address: string, ipfsGateway: string, tokenId?: string | undefined) => Promise<{ - standard: string; - tokenURI: string | undefined; - image: string | undefined; - }>; -} diff --git a/dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.js b/dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.js deleted file mode 100644 index 034c2feb49..0000000000 --- a/dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.js +++ /dev/null @@ -1,173 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.ERC1155Standard = void 0; -const metamask_eth_abis_1 = require("@metamask/metamask-eth-abis"); -const constants_1 = require("../../../../constants"); -const util_1 = require("../../../../util"); -class ERC1155Standard { - constructor(web3) { - /** - * Query if contract implements ERC1155 URI Metadata interface. - * - * @param address - ERC1155 asset contract address. - * @returns Promise resolving to whether the contract implements ERC1155 URI Metadata interface. - */ - this.contractSupportsURIMetadataInterface = (address) => __awaiter(this, void 0, void 0, function* () { - return this.contractSupportsInterface(address, constants_1.ERC1155_METADATA_URI_INTERFACE_ID); - }); - /** - * Query if contract implements ERC1155 Token Receiver interface. - * - * @param address - ERC1155 asset contract address. - * @returns Promise resolving to whether the contract implements ERC1155 Token Receiver interface. - */ - this.contractSupportsTokenReceiverInterface = (address) => __awaiter(this, void 0, void 0, function* () { - return this.contractSupportsInterface(address, constants_1.ERC1155_TOKEN_RECEIVER_INTERFACE_ID); - }); - /** - * Query if contract implements ERC1155 interface. - * - * @param address - ERC1155 asset contract address. - * @returns Promise resolving to whether the contract implements the base ERC1155 interface. - */ - this.contractSupportsBase1155Interface = (address) => __awaiter(this, void 0, void 0, function* () { - return this.contractSupportsInterface(address, constants_1.ERC1155_INTERFACE_ID); - }); - /** - * Query for tokenURI for a given asset. - * - * @param address - ERC1155 asset contract address. - * @param tokenId - ERC1155 asset identifier. - * @returns Promise resolving to the 'tokenURI'. - */ - this.getTokenURI = (address, tokenId) => __awaiter(this, void 0, void 0, function* () { - const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC1155).at(address); - return new Promise((resolve, reject) => { - contract.uri(tokenId, (error, result) => { - /* istanbul ignore if */ - if (error) { - reject(error); - return; - } - resolve(result); - }); - }); - }); - /** - * Query for balance of a given ERC1155 token. - * - * @param contractAddress - ERC1155 asset contract address. - * @param address - Wallet public address. - * @param tokenId - ERC1155 asset identifier. - * @returns Promise resolving to the 'balanceOf'. - */ - this.getBalanceOf = (contractAddress, address, tokenId) => __awaiter(this, void 0, void 0, function* () { - const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC1155).at(contractAddress); - return new Promise((resolve, reject) => { - contract.balanceOf(address, tokenId, (error, result) => { - /* istanbul ignore if */ - if (error) { - reject(error); - return; - } - resolve(result); - }); - }); - }); - /** - * Transfer single ERC1155 token. - * When minting/creating tokens, the from arg MUST be set to 0x0 (i.e. zero address). - * When burning/destroying tokens, the to arg MUST be set to 0x0 (i.e. zero address). - * - * @param operator - ERC1155 token address. - * @param from - ERC1155 token holder. - * @param to - ERC1155 token recipient. - * @param id - ERC1155 token id. - * @param value - Number of tokens to be sent. - * @returns Promise resolving to the 'transferSingle'. - */ - this.transferSingle = (operator, from, to, id, value) => __awaiter(this, void 0, void 0, function* () { - const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC1155).at(operator); - return new Promise((resolve, reject) => { - contract.transferSingle(operator, from, to, id, value, (error, result) => { - /* istanbul ignore if */ - if (error) { - reject(error); - return; - } - resolve(result); - }); - }); - }); - /** - * Query if a contract implements an interface. - * - * @param address - ERC1155 asset contract address. - * @param interfaceId - Interface identifier. - * @returns Promise resolving to whether the contract implements `interfaceID`. - */ - this.contractSupportsInterface = (address, interfaceId) => __awaiter(this, void 0, void 0, function* () { - const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC1155).at(address); - return new Promise((resolve, reject) => { - contract.supportsInterface(interfaceId, (error, result) => { - /* istanbul ignore if */ - if (error) { - reject(error); - return; - } - resolve(result); - }); - }); - }); - /** - * Query if a contract implements an interface. - * - * @param address - Asset contract address. - * @param ipfsGateway - The user's preferred IPFS gateway. - * @param tokenId - tokenId of a given token in the contract. - * @returns Promise resolving an object containing the standard, tokenURI, symbol and name of the given contract/tokenId pair. - */ - this.getDetails = (address, ipfsGateway, tokenId) => __awaiter(this, void 0, void 0, function* () { - const isERC1155 = yield this.contractSupportsBase1155Interface(address); - if (!isERC1155) { - throw new Error("This isn't a valid ERC1155 contract"); - } - let tokenURI, image; - if (tokenId) { - tokenURI = yield this.getTokenURI(address, tokenId); - if (tokenURI.startsWith('ipfs://')) { - tokenURI = (0, util_1.getFormattedIpfsUrl)(ipfsGateway, tokenURI, true); - } - try { - const response = yield (0, util_1.timeoutFetch)(tokenURI); - const object = yield response.json(); - image = object === null || object === void 0 ? void 0 : object.image; - if (image === null || image === void 0 ? void 0 : image.startsWith('ipfs://')) { - image = (0, util_1.getFormattedIpfsUrl)(ipfsGateway, image, true); - } - } - catch (_a) { - // ignore - } - } - // TODO consider querying to the metadata to get name. - return { - standard: constants_1.ERC1155, - tokenURI, - image, - }; - }); - this.web3 = web3; - } -} -exports.ERC1155Standard = ERC1155Standard; -//# sourceMappingURL=ERC1155Standard.js.map \ No newline at end of file diff --git a/dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.js.map b/dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.js.map deleted file mode 100644 index 753a1a528b..0000000000 --- a/dist/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"ERC1155Standard.js","sourceRoot":"","sources":["../../../../../src/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.ts"],"names":[],"mappings":"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","sourcesContent":["import { abiERC1155 } from '@metamask/metamask-eth-abis';\nimport {\n ERC1155,\n ERC1155_INTERFACE_ID,\n ERC1155_METADATA_URI_INTERFACE_ID,\n ERC1155_TOKEN_RECEIVER_INTERFACE_ID,\n} from '../../../../constants';\nimport { getFormattedIpfsUrl, timeoutFetch } from '../../../../util';\nimport { Web3 } from '../../standards-types';\n\nexport class ERC1155Standard {\n private web3: Web3;\n\n constructor(web3: Web3) {\n this.web3 = web3;\n }\n\n /**\n * Query if contract implements ERC1155 URI Metadata interface.\n *\n * @param address - ERC1155 asset contract address.\n * @returns Promise resolving to whether the contract implements ERC1155 URI Metadata interface.\n */\n contractSupportsURIMetadataInterface = async (\n address: string,\n ): Promise => {\n return this.contractSupportsInterface(\n address,\n ERC1155_METADATA_URI_INTERFACE_ID,\n );\n };\n\n /**\n * Query if contract implements ERC1155 Token Receiver interface.\n *\n * @param address - ERC1155 asset contract address.\n * @returns Promise resolving to whether the contract implements ERC1155 Token Receiver interface.\n */\n contractSupportsTokenReceiverInterface = async (\n address: string,\n ): Promise => {\n return this.contractSupportsInterface(\n address,\n ERC1155_TOKEN_RECEIVER_INTERFACE_ID,\n );\n };\n\n /**\n * Query if contract implements ERC1155 interface.\n *\n * @param address - ERC1155 asset contract address.\n * @returns Promise resolving to whether the contract implements the base ERC1155 interface.\n */\n contractSupportsBase1155Interface = async (\n address: string,\n ): Promise => {\n return this.contractSupportsInterface(address, ERC1155_INTERFACE_ID);\n };\n\n /**\n * Query for tokenURI for a given asset.\n *\n * @param address - ERC1155 asset contract address.\n * @param tokenId - ERC1155 asset identifier.\n * @returns Promise resolving to the 'tokenURI'.\n */\n getTokenURI = async (address: string, tokenId: string): Promise => {\n const contract = this.web3.eth.contract(abiERC1155).at(address);\n return new Promise((resolve, reject) => {\n contract.uri(tokenId, (error: Error, result: string) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n });\n });\n };\n\n /**\n * Query for balance of a given ERC1155 token.\n *\n * @param contractAddress - ERC1155 asset contract address.\n * @param address - Wallet public address.\n * @param tokenId - ERC1155 asset identifier.\n * @returns Promise resolving to the 'balanceOf'.\n */\n getBalanceOf = async (\n contractAddress: string,\n address: string,\n tokenId: string,\n ): Promise => {\n const contract = this.web3.eth.contract(abiERC1155).at(contractAddress);\n return new Promise((resolve, reject) => {\n contract.balanceOf(address, tokenId, (error: Error, result: number) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n });\n });\n };\n\n /**\n * Transfer single ERC1155 token.\n * When minting/creating tokens, the from arg MUST be set to 0x0 (i.e. zero address).\n * When burning/destroying tokens, the to arg MUST be set to 0x0 (i.e. zero address).\n *\n * @param operator - ERC1155 token address.\n * @param from - ERC1155 token holder.\n * @param to - ERC1155 token recipient.\n * @param id - ERC1155 token id.\n * @param value - Number of tokens to be sent.\n * @returns Promise resolving to the 'transferSingle'.\n */\n transferSingle = async (\n operator: string,\n from: string,\n to: string,\n id: string,\n value: string,\n ): Promise => {\n const contract = this.web3.eth.contract(abiERC1155).at(operator);\n return new Promise((resolve, reject) => {\n contract.transferSingle(\n operator,\n from,\n to,\n id,\n value,\n (error: Error, result: void) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n },\n );\n });\n };\n\n /**\n * Query if a contract implements an interface.\n *\n * @param address - ERC1155 asset contract address.\n * @param interfaceId - Interface identifier.\n * @returns Promise resolving to whether the contract implements `interfaceID`.\n */\n private contractSupportsInterface = async (\n address: string,\n interfaceId: string,\n ): Promise => {\n const contract = this.web3.eth.contract(abiERC1155).at(address);\n return new Promise((resolve, reject) => {\n contract.supportsInterface(\n interfaceId,\n (error: Error, result: boolean) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n },\n );\n });\n };\n\n /**\n * Query if a contract implements an interface.\n *\n * @param address - Asset contract address.\n * @param ipfsGateway - The user's preferred IPFS gateway.\n * @param tokenId - tokenId of a given token in the contract.\n * @returns Promise resolving an object containing the standard, tokenURI, symbol and name of the given contract/tokenId pair.\n */\n getDetails = async (\n address: string,\n ipfsGateway: string,\n tokenId?: string,\n ): Promise<{\n standard: string;\n tokenURI: string | undefined;\n image: string | undefined;\n }> => {\n const isERC1155 = await this.contractSupportsBase1155Interface(address);\n\n if (!isERC1155) {\n throw new Error(\"This isn't a valid ERC1155 contract\");\n }\n let tokenURI, image;\n\n if (tokenId) {\n tokenURI = await this.getTokenURI(address, tokenId);\n if (tokenURI.startsWith('ipfs://')) {\n tokenURI = getFormattedIpfsUrl(ipfsGateway, tokenURI, true);\n }\n\n try {\n const response = await timeoutFetch(tokenURI);\n const object = await response.json();\n image = object?.image;\n if (image?.startsWith('ipfs://')) {\n image = getFormattedIpfsUrl(ipfsGateway, image, true);\n }\n } catch {\n // ignore\n }\n }\n\n // TODO consider querying to the metadata to get name.\n return {\n standard: ERC1155,\n tokenURI,\n image,\n };\n };\n}\n"]} \ No newline at end of file diff --git a/dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.d.ts b/dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.d.ts deleted file mode 100644 index 06f56d75d5..0000000000 --- a/dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.d.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Web3 } from '../../standards-types'; -export declare class ERC721Standard { - private web3; - constructor(web3: Web3); - /** - * Query if contract implements ERC721Metadata interface. - * - * @param address - ERC721 asset contract address. - * @returns Promise resolving to whether the contract implements ERC721Metadata interface. - */ - contractSupportsMetadataInterface: (address: string) => Promise; - /** - * Query if contract implements ERC721Enumerable interface. - * - * @param address - ERC721 asset contract address. - * @returns Promise resolving to whether the contract implements ERC721Enumerable interface. - */ - contractSupportsEnumerableInterface: (address: string) => Promise; - /** - * Query if contract implements ERC721 interface. - * - * @param address - ERC721 asset contract address. - * @returns Promise resolving to whether the contract implements ERC721 interface. - */ - contractSupportsBase721Interface: (address: string) => Promise; - /** - * Enumerate assets assigned to an owner. - * - * @param address - ERC721 asset contract address. - * @param selectedAddress - Current account public address. - * @param index - A collectible counter less than `balanceOf(selectedAddress)`. - * @returns Promise resolving to token identifier for the 'index'th asset assigned to 'selectedAddress'. - */ - getCollectibleTokenId: (address: string, selectedAddress: string, index: number) => Promise; - /** - * Query for tokenURI for a given asset. - * - * @param address - ERC721 asset contract address. - * @param tokenId - ERC721 asset identifier. - * @returns Promise resolving to the 'tokenURI'. - */ - getTokenURI: (address: string, tokenId: string) => Promise; - /** - * Query for name for a given asset. - * - * @param address - ERC721 asset contract address. - * @returns Promise resolving to the 'name'. - */ - getAssetName: (address: string) => Promise; - /** - * Query for symbol for a given asset. - * - * @param address - ERC721 asset contract address. - * @returns Promise resolving to the 'symbol'. - */ - getAssetSymbol: (address: string) => Promise; - /** - * Query for owner for a given ERC721 asset. - * - * @param address - ERC721 asset contract address. - * @param tokenId - ERC721 asset identifier. - * @returns Promise resolving to the owner address. - */ - getOwnerOf(address: string, tokenId: string): Promise; - /** - * Query if a contract implements an interface. - * - * @param address - Asset contract address. - * @param interfaceId - Interface identifier. - * @returns Promise resolving to whether the contract implements `interfaceID`. - */ - private contractSupportsInterface; - /** - * Query if a contract implements an interface. - * - * @param address - Asset contract address. - * @param ipfsGateway - The user's preferred IPFS gateway. - * @param tokenId - tokenId of a given token in the contract. - * @returns Promise resolving an object containing the standard, tokenURI, symbol and name of the given contract/tokenId pair. - */ - getDetails: (address: string, ipfsGateway: string, tokenId?: string | undefined) => Promise<{ - standard: string; - tokenURI: string | undefined; - symbol: string | undefined; - name: string | undefined; - image: string | undefined; - }>; -} diff --git a/dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.js b/dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.js deleted file mode 100644 index b840109e29..0000000000 --- a/dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.js +++ /dev/null @@ -1,226 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.ERC721Standard = void 0; -const metamask_eth_abis_1 = require("@metamask/metamask-eth-abis"); -const util_1 = require("../../../../util"); -const constants_1 = require("../../../../constants"); -class ERC721Standard { - constructor(web3) { - /** - * Query if contract implements ERC721Metadata interface. - * - * @param address - ERC721 asset contract address. - * @returns Promise resolving to whether the contract implements ERC721Metadata interface. - */ - this.contractSupportsMetadataInterface = (address) => __awaiter(this, void 0, void 0, function* () { - return this.contractSupportsInterface(address, constants_1.ERC721_METADATA_INTERFACE_ID); - }); - /** - * Query if contract implements ERC721Enumerable interface. - * - * @param address - ERC721 asset contract address. - * @returns Promise resolving to whether the contract implements ERC721Enumerable interface. - */ - this.contractSupportsEnumerableInterface = (address) => __awaiter(this, void 0, void 0, function* () { - return this.contractSupportsInterface(address, constants_1.ERC721_ENUMERABLE_INTERFACE_ID); - }); - /** - * Query if contract implements ERC721 interface. - * - * @param address - ERC721 asset contract address. - * @returns Promise resolving to whether the contract implements ERC721 interface. - */ - this.contractSupportsBase721Interface = (address) => __awaiter(this, void 0, void 0, function* () { - return this.contractSupportsInterface(address, constants_1.ERC721_INTERFACE_ID); - }); - /** - * Enumerate assets assigned to an owner. - * - * @param address - ERC721 asset contract address. - * @param selectedAddress - Current account public address. - * @param index - A collectible counter less than `balanceOf(selectedAddress)`. - * @returns Promise resolving to token identifier for the 'index'th asset assigned to 'selectedAddress'. - */ - this.getCollectibleTokenId = (address, selectedAddress, index) => __awaiter(this, void 0, void 0, function* () { - const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC721).at(address); - return new Promise((resolve, reject) => { - contract.tokenOfOwnerByIndex(selectedAddress, index, (error, result) => { - /* istanbul ignore if */ - if (error) { - reject(error); - return; - } - resolve(result); - }); - }); - }); - /** - * Query for tokenURI for a given asset. - * - * @param address - ERC721 asset contract address. - * @param tokenId - ERC721 asset identifier. - * @returns Promise resolving to the 'tokenURI'. - */ - this.getTokenURI = (address, tokenId) => __awaiter(this, void 0, void 0, function* () { - const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC721).at(address); - const supportsMetadata = yield this.contractSupportsMetadataInterface(address); - if (!supportsMetadata) { - throw new Error('Contract does not support ERC721 metadata interface.'); - } - return new Promise((resolve, reject) => { - contract.tokenURI(tokenId, (error, result) => { - /* istanbul ignore if */ - if (error) { - reject(error); - return; - } - resolve(result); - }); - }); - }); - /** - * Query for name for a given asset. - * - * @param address - ERC721 asset contract address. - * @returns Promise resolving to the 'name'. - */ - this.getAssetName = (address) => __awaiter(this, void 0, void 0, function* () { - const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC721).at(address); - return new Promise((resolve, reject) => { - contract.name((error, result) => { - /* istanbul ignore if */ - if (error) { - reject(error); - return; - } - resolve(result); - }); - }); - }); - /** - * Query for symbol for a given asset. - * - * @param address - ERC721 asset contract address. - * @returns Promise resolving to the 'symbol'. - */ - this.getAssetSymbol = (address) => __awaiter(this, void 0, void 0, function* () { - const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC721).at(address); - return new Promise((resolve, reject) => { - contract.symbol((error, result) => { - /* istanbul ignore if */ - if (error) { - reject(error); - return; - } - resolve(result); - }); - }); - }); - /** - * Query if a contract implements an interface. - * - * @param address - Asset contract address. - * @param interfaceId - Interface identifier. - * @returns Promise resolving to whether the contract implements `interfaceID`. - */ - this.contractSupportsInterface = (address, interfaceId) => __awaiter(this, void 0, void 0, function* () { - const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC721).at(address); - return new Promise((resolve, reject) => { - contract.supportsInterface(interfaceId, (error, result) => { - /* istanbul ignore if */ - if (error) { - reject(error); - return; - } - resolve(result); - }); - }); - }); - /** - * Query if a contract implements an interface. - * - * @param address - Asset contract address. - * @param ipfsGateway - The user's preferred IPFS gateway. - * @param tokenId - tokenId of a given token in the contract. - * @returns Promise resolving an object containing the standard, tokenURI, symbol and name of the given contract/tokenId pair. - */ - this.getDetails = (address, ipfsGateway, tokenId) => __awaiter(this, void 0, void 0, function* () { - const isERC721 = yield this.contractSupportsBase721Interface(address); - if (!isERC721) { - throw new Error("This isn't a valid ERC721 contract"); - } - let tokenURI, image, symbol, name; - // TODO upgrade to use Promise.allSettled for name/symbol when we can refactor to use es2020 in tsconfig - try { - symbol = yield this.getAssetSymbol(address); - } - catch (_a) { - // ignore - } - try { - name = yield this.getAssetName(address); - } - catch (_b) { - // ignore - } - if (tokenId) { - try { - tokenURI = yield this.getTokenURI(address, tokenId); - if (tokenURI.startsWith('ipfs://')) { - tokenURI = (0, util_1.getFormattedIpfsUrl)(ipfsGateway, tokenURI, true); - } - const response = yield (0, util_1.timeoutFetch)(tokenURI); - const object = yield response.json(); - image = object === null || object === void 0 ? void 0 : object.image; - if (image === null || image === void 0 ? void 0 : image.startsWith('ipfs://')) { - image = (0, util_1.getFormattedIpfsUrl)(ipfsGateway, image, true); - } - } - catch (_c) { - // ignore - } - } - return { - standard: constants_1.ERC721, - tokenURI, - symbol, - name, - image, - }; - }); - this.web3 = web3; - } - /** - * Query for owner for a given ERC721 asset. - * - * @param address - ERC721 asset contract address. - * @param tokenId - ERC721 asset identifier. - * @returns Promise resolving to the owner address. - */ - getOwnerOf(address, tokenId) { - return __awaiter(this, void 0, void 0, function* () { - const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC721).at(address); - return new Promise((resolve, reject) => { - contract.ownerOf(tokenId, (error, result) => { - /* istanbul ignore if */ - if (error) { - reject(error); - return; - } - resolve(result); - }); - }); - }); - } -} -exports.ERC721Standard = ERC721Standard; -//# sourceMappingURL=ERC721Standard.js.map \ No newline at end of file diff --git a/dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.js.map b/dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.js.map deleted file mode 100644 index a11354b5da..0000000000 --- a/dist/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"ERC721Standard.js","sourceRoot":"","sources":["../../../../../src/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.ts"],"names":[],"mappings":"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","sourcesContent":["import { abiERC721 } from '@metamask/metamask-eth-abis';\nimport { Web3 } from '../../standards-types';\nimport { getFormattedIpfsUrl, timeoutFetch } from '../../../../util';\nimport {\n ERC721_INTERFACE_ID,\n ERC721_METADATA_INTERFACE_ID,\n ERC721_ENUMERABLE_INTERFACE_ID,\n ERC721,\n} from '../../../../constants';\n\nexport class ERC721Standard {\n private web3: Web3;\n\n constructor(web3: Web3) {\n this.web3 = web3;\n }\n\n /**\n * Query if contract implements ERC721Metadata interface.\n *\n * @param address - ERC721 asset contract address.\n * @returns Promise resolving to whether the contract implements ERC721Metadata interface.\n */\n contractSupportsMetadataInterface = async (\n address: string,\n ): Promise => {\n return this.contractSupportsInterface(\n address,\n ERC721_METADATA_INTERFACE_ID,\n );\n };\n\n /**\n * Query if contract implements ERC721Enumerable interface.\n *\n * @param address - ERC721 asset contract address.\n * @returns Promise resolving to whether the contract implements ERC721Enumerable interface.\n */\n contractSupportsEnumerableInterface = async (\n address: string,\n ): Promise => {\n return this.contractSupportsInterface(\n address,\n ERC721_ENUMERABLE_INTERFACE_ID,\n );\n };\n\n /**\n * Query if contract implements ERC721 interface.\n *\n * @param address - ERC721 asset contract address.\n * @returns Promise resolving to whether the contract implements ERC721 interface.\n */\n contractSupportsBase721Interface = async (\n address: string,\n ): Promise => {\n return this.contractSupportsInterface(address, ERC721_INTERFACE_ID);\n };\n\n /**\n * Enumerate assets assigned to an owner.\n *\n * @param address - ERC721 asset contract address.\n * @param selectedAddress - Current account public address.\n * @param index - A collectible counter less than `balanceOf(selectedAddress)`.\n * @returns Promise resolving to token identifier for the 'index'th asset assigned to 'selectedAddress'.\n */\n getCollectibleTokenId = async (\n address: string,\n selectedAddress: string,\n index: number,\n ): Promise => {\n const contract = this.web3.eth.contract(abiERC721).at(address);\n return new Promise((resolve, reject) => {\n contract.tokenOfOwnerByIndex(\n selectedAddress,\n index,\n (error: Error, result: string) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n },\n );\n });\n };\n\n /**\n * Query for tokenURI for a given asset.\n *\n * @param address - ERC721 asset contract address.\n * @param tokenId - ERC721 asset identifier.\n * @returns Promise resolving to the 'tokenURI'.\n */\n getTokenURI = async (address: string, tokenId: string): Promise => {\n const contract = this.web3.eth.contract(abiERC721).at(address);\n const supportsMetadata = await this.contractSupportsMetadataInterface(\n address,\n );\n if (!supportsMetadata) {\n throw new Error('Contract does not support ERC721 metadata interface.');\n }\n return new Promise((resolve, reject) => {\n contract.tokenURI(tokenId, (error: Error, result: string) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n });\n });\n };\n\n /**\n * Query for name for a given asset.\n *\n * @param address - ERC721 asset contract address.\n * @returns Promise resolving to the 'name'.\n */\n getAssetName = async (address: string): Promise => {\n const contract = this.web3.eth.contract(abiERC721).at(address);\n return new Promise((resolve, reject) => {\n contract.name((error: Error, result: string) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n });\n });\n };\n\n /**\n * Query for symbol for a given asset.\n *\n * @param address - ERC721 asset contract address.\n * @returns Promise resolving to the 'symbol'.\n */\n getAssetSymbol = async (address: string): Promise => {\n const contract = this.web3.eth.contract(abiERC721).at(address);\n return new Promise((resolve, reject) => {\n contract.symbol((error: Error, result: string) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n });\n });\n };\n\n /**\n * Query for owner for a given ERC721 asset.\n *\n * @param address - ERC721 asset contract address.\n * @param tokenId - ERC721 asset identifier.\n * @returns Promise resolving to the owner address.\n */\n async getOwnerOf(address: string, tokenId: string): Promise {\n const contract = this.web3.eth.contract(abiERC721).at(address);\n return new Promise((resolve, reject) => {\n contract.ownerOf(tokenId, (error: Error, result: string) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n });\n });\n }\n\n /**\n * Query if a contract implements an interface.\n *\n * @param address - Asset contract address.\n * @param interfaceId - Interface identifier.\n * @returns Promise resolving to whether the contract implements `interfaceID`.\n */\n private contractSupportsInterface = async (\n address: string,\n interfaceId: string,\n ): Promise => {\n const contract = this.web3.eth.contract(abiERC721).at(address);\n return new Promise((resolve, reject) => {\n contract.supportsInterface(\n interfaceId,\n (error: Error, result: boolean) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n },\n );\n });\n };\n\n /**\n * Query if a contract implements an interface.\n *\n * @param address - Asset contract address.\n * @param ipfsGateway - The user's preferred IPFS gateway.\n * @param tokenId - tokenId of a given token in the contract.\n * @returns Promise resolving an object containing the standard, tokenURI, symbol and name of the given contract/tokenId pair.\n */\n getDetails = async (\n address: string,\n ipfsGateway: string,\n tokenId?: string,\n ): Promise<{\n standard: string;\n tokenURI: string | undefined;\n symbol: string | undefined;\n name: string | undefined;\n image: string | undefined;\n }> => {\n const isERC721 = await this.contractSupportsBase721Interface(address);\n if (!isERC721) {\n throw new Error(\"This isn't a valid ERC721 contract\");\n }\n\n let tokenURI, image, symbol, name;\n\n // TODO upgrade to use Promise.allSettled for name/symbol when we can refactor to use es2020 in tsconfig\n try {\n symbol = await this.getAssetSymbol(address);\n } catch {\n // ignore\n }\n\n try {\n name = await this.getAssetName(address);\n } catch {\n // ignore\n }\n\n if (tokenId) {\n try {\n tokenURI = await this.getTokenURI(address, tokenId);\n if (tokenURI.startsWith('ipfs://')) {\n tokenURI = getFormattedIpfsUrl(ipfsGateway, tokenURI, true);\n }\n\n const response = await timeoutFetch(tokenURI);\n const object = await response.json();\n image = object?.image;\n if (image?.startsWith('ipfs://')) {\n image = getFormattedIpfsUrl(ipfsGateway, image, true);\n }\n } catch {\n // ignore\n }\n }\n\n return {\n standard: ERC721,\n tokenURI,\n symbol,\n name,\n image,\n };\n };\n}\n"]} \ No newline at end of file diff --git a/dist/assets/Standards/ERC20Standard.d.ts b/dist/assets/Standards/ERC20Standard.d.ts deleted file mode 100644 index 3ef53b51a5..0000000000 --- a/dist/assets/Standards/ERC20Standard.d.ts +++ /dev/null @@ -1,42 +0,0 @@ -/// -import { BN } from 'ethereumjs-util'; -import { Web3 } from './standards-types'; -export declare class ERC20Standard { - private web3; - constructor(web3: Web3); - /** - * Get balance or count for current account on specific asset contract. - * - * @param address - Asset ERC20 contract address. - * @param selectedAddress - Current account public address. - * @returns Promise resolving to BN object containing balance for current account on specific asset contract. - */ - getBalanceOf(address: string, selectedAddress: string): Promise; - /** - * Query for the decimals for a given ERC20 asset. - * - * @param address - ERC20 asset contract string. - * @returns Promise resolving to the 'decimals'. - */ - getTokenDecimals(address: string): Promise; - /** - * Query for symbol for a given ERC20 asset. - * - * @param address - ERC20 asset contract address. - * @returns Promise resolving to the 'symbol'. - */ - getTokenSymbol(address: string): Promise; - /** - * Query if a contract implements an interface. - * - * @param address - Asset contract address. - * @param userAddress - The public address for the currently active user's account. - * @returns Promise resolving an object containing the standard, decimals, symbol and balance of the given contract/userAddress pair. - */ - getDetails(address: string, userAddress?: string): Promise<{ - standard: string; - symbol: string | undefined; - decimals: string | undefined; - balance: BN | undefined; - }>; -} diff --git a/dist/assets/Standards/ERC20Standard.js b/dist/assets/Standards/ERC20Standard.js deleted file mode 100644 index ad0d74d39d..0000000000 --- a/dist/assets/Standards/ERC20Standard.js +++ /dev/null @@ -1,134 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.ERC20Standard = void 0; -const metamask_eth_abis_1 = require("@metamask/metamask-eth-abis"); -const ethereumjs_util_1 = require("ethereumjs-util"); -const utils_1 = require("ethers/lib/utils"); -const constants_1 = require("../../constants"); -class ERC20Standard { - constructor(web3) { - this.web3 = web3; - } - /** - * Get balance or count for current account on specific asset contract. - * - * @param address - Asset ERC20 contract address. - * @param selectedAddress - Current account public address. - * @returns Promise resolving to BN object containing balance for current account on specific asset contract. - */ - getBalanceOf(address, selectedAddress) { - return __awaiter(this, void 0, void 0, function* () { - const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC20).at(address); - return new Promise((resolve, reject) => { - contract.balanceOf(selectedAddress, (error, result) => { - /* istanbul ignore if */ - if (error) { - reject(error); - return; - } - resolve(result); - }); - }); - }); - } - /** - * Query for the decimals for a given ERC20 asset. - * - * @param address - ERC20 asset contract string. - * @returns Promise resolving to the 'decimals'. - */ - getTokenDecimals(address) { - return __awaiter(this, void 0, void 0, function* () { - const contract = this.web3.eth.contract(metamask_eth_abis_1.abiERC20).at(address); - return new Promise((resolve, reject) => { - contract.decimals((error, result) => { - /* istanbul ignore if */ - if (error) { - reject(error); - return; - } - resolve(result.toString()); - }); - }); - }); - } - /** - * Query for symbol for a given ERC20 asset. - * - * @param address - ERC20 asset contract address. - * @returns Promise resolving to the 'symbol'. - */ - getTokenSymbol(address) { - return __awaiter(this, void 0, void 0, function* () { - // Signature for calling `symbol()` - const payload = { to: address, data: '0x95d89b41' }; - return new Promise((resolve, reject) => { - this.web3.eth.call(payload, undefined, (error, result) => { - /* istanbul ignore if */ - if (error) { - reject(error); - return; - } - const abiCoder = new utils_1.AbiCoder(); - // Parse as string - try { - const decoded = abiCoder.decode(['string'], result)[0]; - if (decoded) { - resolve(decoded); - return; - } - } - catch (_a) { - // Ignore error - } - // Parse as bytes - try { - const utf8 = (0, ethereumjs_util_1.toUtf8)(result); - resolve(utf8); - return; - } - catch (_b) { - // Ignore error - } - reject(new Error('Failed to parse token symbol')); - }); - }); - }); - } - /** - * Query if a contract implements an interface. - * - * @param address - Asset contract address. - * @param userAddress - The public address for the currently active user's account. - * @returns Promise resolving an object containing the standard, decimals, symbol and balance of the given contract/userAddress pair. - */ - getDetails(address, userAddress) { - return __awaiter(this, void 0, void 0, function* () { - const [decimals, symbol] = yield Promise.all([ - this.getTokenDecimals(address), - this.getTokenSymbol(address), - ]); - let balance; - if (userAddress) { - balance = yield this.getBalanceOf(address, userAddress); - } - return { - decimals, - symbol, - balance, - standard: constants_1.ERC20, - }; - }); - } -} -exports.ERC20Standard = ERC20Standard; -//# sourceMappingURL=ERC20Standard.js.map \ No newline at end of file diff --git a/dist/assets/Standards/ERC20Standard.js.map b/dist/assets/Standards/ERC20Standard.js.map deleted file mode 100644 index 0fdcc0a406..0000000000 --- a/dist/assets/Standards/ERC20Standard.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"ERC20Standard.js","sourceRoot":"","sources":["../../../src/assets/Standards/ERC20Standard.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,mEAAuD;AACvD,qDAA6C;AAC7C,4CAA4C;AAC5C,+CAAwC;AAGxC,MAAa,aAAa;IAGxB,YAAY,IAAU;QACpB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED;;;;;;OAMG;IACG,YAAY,CAAC,OAAe,EAAE,eAAuB;;YACzD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,4BAAQ,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;YAC9D,OAAO,IAAI,OAAO,CAAK,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBACzC,QAAQ,CAAC,SAAS,CAAC,eAAe,EAAE,CAAC,KAAY,EAAE,MAAU,EAAE,EAAE;oBAC/D,wBAAwB;oBACxB,IAAI,KAAK,EAAE;wBACT,MAAM,CAAC,KAAK,CAAC,CAAC;wBACd,OAAO;qBACR;oBACD,OAAO,CAAC,MAAM,CAAC,CAAC;gBAClB,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC;KAAA;IAED;;;;;OAKG;IACG,gBAAgB,CAAC,OAAe;;YACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,4BAAQ,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;YAC9D,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC7C,QAAQ,CAAC,QAAQ,CAAC,CAAC,KAAY,EAAE,MAAmB,EAAE,EAAE;oBACtD,wBAAwB;oBACxB,IAAI,KAAK,EAAE;wBACT,MAAM,CAAC,KAAK,CAAC,CAAC;wBACd,OAAO;qBACR;oBACD,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;gBAC7B,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC;KAAA;IAED;;;;;OAKG;IACG,cAAc,CAAC,OAAe;;YAClC,mCAAmC;YACnC,MAAM,OAAO,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;YACpD,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC7C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC,KAAY,EAAE,MAAc,EAAE,EAAE;oBACtE,wBAAwB;oBACxB,IAAI,KAAK,EAAE;wBACT,MAAM,CAAC,KAAK,CAAC,CAAC;wBACd,OAAO;qBACR;oBAED,MAAM,QAAQ,GAAG,IAAI,gBAAQ,EAAE,CAAC;oBAEhC,kBAAkB;oBAClB,IAAI;wBACF,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;wBACvD,IAAI,OAAO,EAAE;4BACX,OAAO,CAAC,OAAO,CAAC,CAAC;4BACjB,OAAO;yBACR;qBACF;oBAAC,WAAM;wBACN,eAAe;qBAChB;oBAED,iBAAiB;oBACjB,IAAI;wBACF,MAAM,IAAI,GAAG,IAAA,wBAAM,EAAC,MAAM,CAAC,CAAC;wBAC5B,OAAO,CAAC,IAAI,CAAC,CAAC;wBACd,OAAO;qBACR;oBAAC,WAAM;wBACN,eAAe;qBAChB;oBAED,MAAM,CAAC,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC,CAAC;gBACpD,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC;KAAA;IAED;;;;;;OAMG;IACG,UAAU,CACd,OAAe,EACf,WAAoB;;YAOpB,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBAC3C,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC;gBAC9B,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC;aAC7B,CAAC,CAAC;YACH,IAAI,OAAO,CAAC;YACZ,IAAI,WAAW,EAAE;gBACf,OAAO,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;aACzD;YACD,OAAO;gBACL,QAAQ;gBACR,MAAM;gBACN,OAAO;gBACP,QAAQ,EAAE,iBAAK;aAChB,CAAC;QACJ,CAAC;KAAA;CACF;AA3HD,sCA2HC","sourcesContent":["import { abiERC20 } from '@metamask/metamask-eth-abis';\nimport { BN, toUtf8 } from 'ethereumjs-util';\nimport { AbiCoder } from 'ethers/lib/utils';\nimport { ERC20 } from '../../constants';\nimport { Web3 } from './standards-types';\n\nexport class ERC20Standard {\n private web3: Web3;\n\n constructor(web3: Web3) {\n this.web3 = web3;\n }\n\n /**\n * Get balance or count for current account on specific asset contract.\n *\n * @param address - Asset ERC20 contract address.\n * @param selectedAddress - Current account public address.\n * @returns Promise resolving to BN object containing balance for current account on specific asset contract.\n */\n async getBalanceOf(address: string, selectedAddress: string): Promise {\n const contract = this.web3.eth.contract(abiERC20).at(address);\n return new Promise((resolve, reject) => {\n contract.balanceOf(selectedAddress, (error: Error, result: BN) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n });\n });\n }\n\n /**\n * Query for the decimals for a given ERC20 asset.\n *\n * @param address - ERC20 asset contract string.\n * @returns Promise resolving to the 'decimals'.\n */\n async getTokenDecimals(address: string): Promise {\n const contract = this.web3.eth.contract(abiERC20).at(address);\n return new Promise((resolve, reject) => {\n contract.decimals((error: Error, result: BN | string) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n resolve(result.toString());\n });\n });\n }\n\n /**\n * Query for symbol for a given ERC20 asset.\n *\n * @param address - ERC20 asset contract address.\n * @returns Promise resolving to the 'symbol'.\n */\n async getTokenSymbol(address: string): Promise {\n // Signature for calling `symbol()`\n const payload = { to: address, data: '0x95d89b41' };\n return new Promise((resolve, reject) => {\n this.web3.eth.call(payload, undefined, (error: Error, result: string) => {\n /* istanbul ignore if */\n if (error) {\n reject(error);\n return;\n }\n\n const abiCoder = new AbiCoder();\n\n // Parse as string\n try {\n const decoded = abiCoder.decode(['string'], result)[0];\n if (decoded) {\n resolve(decoded);\n return;\n }\n } catch {\n // Ignore error\n }\n\n // Parse as bytes\n try {\n const utf8 = toUtf8(result);\n resolve(utf8);\n return;\n } catch {\n // Ignore error\n }\n\n reject(new Error('Failed to parse token symbol'));\n });\n });\n }\n\n /**\n * Query if a contract implements an interface.\n *\n * @param address - Asset contract address.\n * @param userAddress - The public address for the currently active user's account.\n * @returns Promise resolving an object containing the standard, decimals, symbol and balance of the given contract/userAddress pair.\n */\n async getDetails(\n address: string,\n userAddress?: string,\n ): Promise<{\n standard: string;\n symbol: string | undefined;\n decimals: string | undefined;\n balance: BN | undefined;\n }> {\n const [decimals, symbol] = await Promise.all([\n this.getTokenDecimals(address),\n this.getTokenSymbol(address),\n ]);\n let balance;\n if (userAddress) {\n balance = await this.getBalanceOf(address, userAddress);\n }\n return {\n decimals,\n symbol,\n balance,\n standard: ERC20,\n };\n }\n}\n"]} \ No newline at end of file diff --git a/dist/assets/Standards/standards-types.d.ts b/dist/assets/Standards/standards-types.d.ts deleted file mode 100644 index 8efcd998d0..0000000000 --- a/dist/assets/Standards/standards-types.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { abiERC20, abiERC1155, abiERC721 } from '@metamask/metamask-eth-abis'; -declare type Contract = { - at(address: string): any; -}; -export declare type Web3 = { - eth: { - call(payload: { - to: string; - data: string; - }, block: undefined, callback: (error: Error, result: string) => void): void; - contract(abi: typeof abiERC20 | typeof abiERC721 | typeof abiERC1155): Contract; - }; -}; -export {}; diff --git a/dist/assets/Standards/standards-types.js b/dist/assets/Standards/standards-types.js deleted file mode 100644 index a9479e91d8..0000000000 --- a/dist/assets/Standards/standards-types.js +++ /dev/null @@ -1,3 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -//# sourceMappingURL=standards-types.js.map \ No newline at end of file diff --git a/dist/assets/Standards/standards-types.js.map b/dist/assets/Standards/standards-types.js.map deleted file mode 100644 index dbe4038382..0000000000 --- a/dist/assets/Standards/standards-types.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"standards-types.js","sourceRoot":"","sources":["../../../src/assets/Standards/standards-types.ts"],"names":[],"mappings":"","sourcesContent":["import { abiERC20, abiERC1155, abiERC721 } from '@metamask/metamask-eth-abis';\n\ntype Contract = {\n at(address: string): any;\n};\n\nexport type Web3 = {\n eth: {\n call(\n payload: { to: string; data: string },\n block: undefined,\n callback: (error: Error, result: string) => void,\n ): void;\n contract(\n abi: typeof abiERC20 | typeof abiERC721 | typeof abiERC1155,\n ): Contract;\n };\n};\n"]} \ No newline at end of file diff --git a/dist/assets/TokenBalancesController.d.ts b/dist/assets/TokenBalancesController.d.ts deleted file mode 100644 index 9cac44f43d..0000000000 --- a/dist/assets/TokenBalancesController.d.ts +++ /dev/null @@ -1,69 +0,0 @@ -/// -import { BN } from 'ethereumjs-util'; -import { BaseController, BaseConfig, BaseState } from '../BaseController'; -import type { PreferencesState } from '../user/PreferencesController'; -import { Token } from './TokenRatesController'; -import { TokensState } from './TokensController'; -import type { AssetsContractController } from './AssetsContractController'; -export { BN }; -/** - * @type TokenBalancesConfig - * - * Token balances controller configuration - * @property interval - Polling interval used to fetch new token balances - * @property tokens - List of tokens to track balances for - */ -export interface TokenBalancesConfig extends BaseConfig { - interval: number; - tokens: Token[]; -} -/** - * @type TokenBalancesState - * - * Token balances controller state - * @property contractBalances - Hash of token contract addresses to balances - */ -export interface TokenBalancesState extends BaseState { - contractBalances: { - [address: string]: BN; - }; -} -/** - * Controller that passively polls on a set interval token balances - * for tokens stored in the TokensController - */ -export declare class TokenBalancesController extends BaseController { - private handle?; - /** - * Name of this controller used during composition - */ - name: string; - private getSelectedAddress; - private getERC20BalanceOf; - /** - * Creates a TokenBalancesController instance. - * - * @param options - The controller options. - * @param options.onTokensStateChange - Allows subscribing to assets controller state changes. - * @param options.getSelectedAddress - Gets the current selected address. - * @param options.getERC20BalanceOf - Gets the balance of the given account at the given contract address. - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor({ onTokensStateChange, getSelectedAddress, getERC20BalanceOf, }: { - onTokensStateChange: (listener: (tokenState: TokensState) => void) => void; - getSelectedAddress: () => PreferencesState['selectedAddress']; - getERC20BalanceOf: AssetsContractController['getERC20BalanceOf']; - }, config?: Partial, state?: Partial); - /** - * Starts a new polling interval. - * - * @param interval - Polling interval used to fetch new token balances. - */ - poll(interval?: number): Promise; - /** - * Updates balances for all tokens. - */ - updateBalances(): Promise; -} -export default TokenBalancesController; diff --git a/dist/assets/TokenBalancesController.js b/dist/assets/TokenBalancesController.js deleted file mode 100644 index dd08cee0dd..0000000000 --- a/dist/assets/TokenBalancesController.js +++ /dev/null @@ -1,94 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.TokenBalancesController = exports.BN = void 0; -const ethereumjs_util_1 = require("ethereumjs-util"); -Object.defineProperty(exports, "BN", { enumerable: true, get: function () { return ethereumjs_util_1.BN; } }); -const BaseController_1 = require("../BaseController"); -const util_1 = require("../util"); -/** - * Controller that passively polls on a set interval token balances - * for tokens stored in the TokensController - */ -class TokenBalancesController extends BaseController_1.BaseController { - /** - * Creates a TokenBalancesController instance. - * - * @param options - The controller options. - * @param options.onTokensStateChange - Allows subscribing to assets controller state changes. - * @param options.getSelectedAddress - Gets the current selected address. - * @param options.getERC20BalanceOf - Gets the balance of the given account at the given contract address. - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor({ onTokensStateChange, getSelectedAddress, getERC20BalanceOf, }, config, state) { - super(config, state); - /** - * Name of this controller used during composition - */ - this.name = 'TokenBalancesController'; - this.defaultConfig = { - interval: 180000, - tokens: [], - }; - this.defaultState = { contractBalances: {} }; - this.initialize(); - onTokensStateChange(({ tokens, detectedTokens }) => { - this.configure({ tokens: [...tokens, ...detectedTokens] }); - this.updateBalances(); - }); - this.getSelectedAddress = getSelectedAddress; - this.getERC20BalanceOf = getERC20BalanceOf; - this.poll(); - } - /** - * Starts a new polling interval. - * - * @param interval - Polling interval used to fetch new token balances. - */ - poll(interval) { - return __awaiter(this, void 0, void 0, function* () { - interval && this.configure({ interval }, false, false); - this.handle && clearTimeout(this.handle); - yield (0, util_1.safelyExecute)(() => this.updateBalances()); - this.handle = setTimeout(() => { - this.poll(this.config.interval); - }, this.config.interval); - }); - } - /** - * Updates balances for all tokens. - */ - updateBalances() { - return __awaiter(this, void 0, void 0, function* () { - if (this.disabled) { - return; - } - const { tokens } = this.config; - const newContractBalances = {}; - for (const i in tokens) { - const { address } = tokens[i]; - try { - newContractBalances[address] = yield this.getERC20BalanceOf(address, this.getSelectedAddress()); - tokens[i].balanceError = null; - } - catch (error) { - newContractBalances[address] = new ethereumjs_util_1.BN(0); - tokens[i].balanceError = error; - } - } - this.update({ contractBalances: newContractBalances }); - }); - } -} -exports.TokenBalancesController = TokenBalancesController; -exports.default = TokenBalancesController; -//# sourceMappingURL=TokenBalancesController.js.map \ No newline at end of file diff --git a/dist/assets/TokenBalancesController.js.map b/dist/assets/TokenBalancesController.js.map deleted file mode 100644 index 1c339b8317..0000000000 --- a/dist/assets/TokenBalancesController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"TokenBalancesController.js","sourceRoot":"","sources":["../../src/assets/TokenBalancesController.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAqC;AAS5B,mFATA,oBAAE,OASA;AARX,sDAA0E;AAC1E,kCAAwC;AA+BxC;;;GAGG;AACH,MAAa,uBAAwB,SAAQ,+BAG5C;IAYC;;;;;;;;;OASG;IACH,YACE,EACE,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GAOlB,EACD,MAAqC,EACrC,KAAmC;QAEnC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAlCvB;;WAEG;QACM,SAAI,GAAG,yBAAyB,CAAC;QAgCxC,IAAI,CAAC,aAAa,GAAG;YACnB,QAAQ,EAAE,MAAM;YAChB,MAAM,EAAE,EAAE;SACX,CAAC;QACF,IAAI,CAAC,YAAY,GAAG,EAAE,gBAAgB,EAAE,EAAE,EAAE,CAAC;QAC7C,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,EAAE,EAAE;YACjD,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,GAAG,cAAc,CAAC,EAAE,CAAC,CAAC;YAC3D,IAAI,CAAC,cAAc,EAAE,CAAC;QACxB,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,kBAAkB,GAAG,kBAAkB,CAAC;QAC7C,IAAI,CAAC,iBAAiB,GAAG,iBAAiB,CAAC;QAC3C,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAED;;;;OAIG;IACG,IAAI,CAAC,QAAiB;;YAC1B,QAAQ,IAAI,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;YACvD,IAAI,CAAC,MAAM,IAAI,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACzC,MAAM,IAAA,oBAAa,EAAC,GAAG,EAAE,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC;YACjD,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAClC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC3B,CAAC;KAAA;IAED;;OAEG;IACG,cAAc;;YAClB,IAAI,IAAI,CAAC,QAAQ,EAAE;gBACjB,OAAO;aACR;YACD,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC;YAC/B,MAAM,mBAAmB,GAA8B,EAAE,CAAC;YAC1D,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE;gBACtB,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;gBAC9B,IAAI;oBACF,mBAAmB,CAAC,OAAO,CAAC,GAAG,MAAM,IAAI,CAAC,iBAAiB,CACzD,OAAO,EACP,IAAI,CAAC,kBAAkB,EAAE,CAC1B,CAAC;oBACF,MAAM,CAAC,CAAC,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC;iBAC/B;gBAAC,OAAO,KAAK,EAAE;oBACd,mBAAmB,CAAC,OAAO,CAAC,GAAG,IAAI,oBAAE,CAAC,CAAC,CAAC,CAAC;oBACzC,MAAM,CAAC,CAAC,CAAC,CAAC,YAAY,GAAG,KAAK,CAAC;iBAChC;aACF;YACD,IAAI,CAAC,MAAM,CAAC,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,CAAC,CAAC;QACzD,CAAC;KAAA;CACF;AA9FD,0DA8FC;AAED,kBAAe,uBAAuB,CAAC","sourcesContent":["import { BN } from 'ethereumjs-util';\nimport { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport { safelyExecute } from '../util';\nimport type { PreferencesState } from '../user/PreferencesController';\nimport { Token } from './TokenRatesController';\nimport { TokensState } from './TokensController';\nimport type { AssetsContractController } from './AssetsContractController';\n\n// TODO: Remove this export in the next major release\nexport { BN };\n\n/**\n * @type TokenBalancesConfig\n *\n * Token balances controller configuration\n * @property interval - Polling interval used to fetch new token balances\n * @property tokens - List of tokens to track balances for\n */\nexport interface TokenBalancesConfig extends BaseConfig {\n interval: number;\n tokens: Token[];\n}\n\n/**\n * @type TokenBalancesState\n *\n * Token balances controller state\n * @property contractBalances - Hash of token contract addresses to balances\n */\nexport interface TokenBalancesState extends BaseState {\n contractBalances: { [address: string]: BN };\n}\n\n/**\n * Controller that passively polls on a set interval token balances\n * for tokens stored in the TokensController\n */\nexport class TokenBalancesController extends BaseController<\n TokenBalancesConfig,\n TokenBalancesState\n> {\n private handle?: NodeJS.Timer;\n\n /**\n * Name of this controller used during composition\n */\n override name = 'TokenBalancesController';\n\n private getSelectedAddress: () => PreferencesState['selectedAddress'];\n\n private getERC20BalanceOf: AssetsContractController['getERC20BalanceOf'];\n\n /**\n * Creates a TokenBalancesController instance.\n *\n * @param options - The controller options.\n * @param options.onTokensStateChange - Allows subscribing to assets controller state changes.\n * @param options.getSelectedAddress - Gets the current selected address.\n * @param options.getERC20BalanceOf - Gets the balance of the given account at the given contract address.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n onTokensStateChange,\n getSelectedAddress,\n getERC20BalanceOf,\n }: {\n onTokensStateChange: (\n listener: (tokenState: TokensState) => void,\n ) => void;\n getSelectedAddress: () => PreferencesState['selectedAddress'];\n getERC20BalanceOf: AssetsContractController['getERC20BalanceOf'];\n },\n config?: Partial,\n state?: Partial,\n ) {\n super(config, state);\n this.defaultConfig = {\n interval: 180000,\n tokens: [],\n };\n this.defaultState = { contractBalances: {} };\n this.initialize();\n onTokensStateChange(({ tokens, detectedTokens }) => {\n this.configure({ tokens: [...tokens, ...detectedTokens] });\n this.updateBalances();\n });\n this.getSelectedAddress = getSelectedAddress;\n this.getERC20BalanceOf = getERC20BalanceOf;\n this.poll();\n }\n\n /**\n * Starts a new polling interval.\n *\n * @param interval - Polling interval used to fetch new token balances.\n */\n async poll(interval?: number): Promise {\n interval && this.configure({ interval }, false, false);\n this.handle && clearTimeout(this.handle);\n await safelyExecute(() => this.updateBalances());\n this.handle = setTimeout(() => {\n this.poll(this.config.interval);\n }, this.config.interval);\n }\n\n /**\n * Updates balances for all tokens.\n */\n async updateBalances() {\n if (this.disabled) {\n return;\n }\n const { tokens } = this.config;\n const newContractBalances: { [address: string]: BN } = {};\n for (const i in tokens) {\n const { address } = tokens[i];\n try {\n newContractBalances[address] = await this.getERC20BalanceOf(\n address,\n this.getSelectedAddress(),\n );\n tokens[i].balanceError = null;\n } catch (error) {\n newContractBalances[address] = new BN(0);\n tokens[i].balanceError = error;\n }\n }\n this.update({ contractBalances: newContractBalances });\n }\n}\n\nexport default TokenBalancesController;\n"]} \ No newline at end of file diff --git a/dist/assets/TokenDetectionController.d.ts b/dist/assets/TokenDetectionController.d.ts deleted file mode 100644 index 298c2b1291..0000000000 --- a/dist/assets/TokenDetectionController.d.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { BaseController, BaseConfig, BaseState } from '../BaseController'; -import type { NetworkState } from '../network/NetworkController'; -import type { PreferencesState } from '../user/PreferencesController'; -import type { TokensController, TokensState } from './TokensController'; -import type { AssetsContractController } from './AssetsContractController'; -import { TokenListState } from './TokenListController'; -/** - * @type TokenDetectionConfig - * - * TokenDetection configuration - * @property interval - Polling interval used to fetch new token rates - * @property selectedAddress - Vault selected address - * @property chainId - The chain ID of the current network - * @property isDetectionEnabledFromPreferences - Boolean to track if detection is enabled from PreferencesController - * @property isDetectionEnabledForNetwork - Boolean to track if detected is enabled for current network - */ -export interface TokenDetectionConfig extends BaseConfig { - interval: number; - selectedAddress: string; - chainId: string; - isDetectionEnabledFromPreferences: boolean; - isDetectionEnabledForNetwork: boolean; -} -/** - * Controller that passively polls on a set interval for Tokens auto detection - */ -export declare class TokenDetectionController extends BaseController { - private intervalId?; - /** - * Name of this controller used during composition - */ - name: string; - private getBalancesInSingleCall; - private addDetectedTokens; - private getTokensState; - private getTokenListState; - /** - * Creates a TokenDetectionController instance. - * - * @param options - The controller options. - * @param options.onPreferencesStateChange - Allows subscribing to preferences controller state changes. - * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. - * @param options.onTokenListStateChange - Allows subscribing to token list controller state changes. - * @param options.getBalancesInSingleCall - Gets the balances of a list of tokens for the given address. - * @param options.addDetectedTokens - Add a list of detected tokens. - * @param options.getTokenListState - Gets the current state of the TokenList controller. - * @param options.getTokensState - Gets the current state of the Tokens controller. - * @param options.getNetworkState - Gets the state of the network controller. - * @param options.getPreferencesState - Gets the state of the preferences controller. - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor({ onPreferencesStateChange, onNetworkStateChange, onTokenListStateChange, getBalancesInSingleCall, addDetectedTokens, getTokenListState, getTokensState, getNetworkState, getPreferencesState, }: { - onPreferencesStateChange: (listener: (preferencesState: PreferencesState) => void) => void; - onNetworkStateChange: (listener: (networkState: NetworkState) => void) => void; - onTokenListStateChange: (listener: (tokenListState: TokenListState) => void) => void; - getBalancesInSingleCall: AssetsContractController['getBalancesInSingleCall']; - addDetectedTokens: TokensController['addDetectedTokens']; - getTokenListState: () => TokenListState; - getTokensState: () => TokensState; - getNetworkState: () => NetworkState; - getPreferencesState: () => PreferencesState; - }, config?: Partial, state?: Partial); - /** - * Start polling for detected tokens. - */ - start(): Promise; - /** - * Stop polling for detected tokens. - */ - stop(): void; - private stopPolling; - /** - * Starts a new polling interval. - * - * @param interval - An interval on which to poll. - */ - private startPolling; - /** - * Triggers asset ERC20 token auto detection for each contract address in contract metadata on mainnet. - */ - detectTokens(): Promise; -} -export default TokenDetectionController; diff --git a/dist/assets/TokenDetectionController.js b/dist/assets/TokenDetectionController.js deleted file mode 100644 index d873dc0778..0000000000 --- a/dist/assets/TokenDetectionController.js +++ /dev/null @@ -1,184 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.TokenDetectionController = void 0; -const BaseController_1 = require("../BaseController"); -const util_1 = require("../util"); -const DEFAULT_INTERVAL = 180000; -/** - * Controller that passively polls on a set interval for Tokens auto detection - */ -class TokenDetectionController extends BaseController_1.BaseController { - /** - * Creates a TokenDetectionController instance. - * - * @param options - The controller options. - * @param options.onPreferencesStateChange - Allows subscribing to preferences controller state changes. - * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. - * @param options.onTokenListStateChange - Allows subscribing to token list controller state changes. - * @param options.getBalancesInSingleCall - Gets the balances of a list of tokens for the given address. - * @param options.addDetectedTokens - Add a list of detected tokens. - * @param options.getTokenListState - Gets the current state of the TokenList controller. - * @param options.getTokensState - Gets the current state of the Tokens controller. - * @param options.getNetworkState - Gets the state of the network controller. - * @param options.getPreferencesState - Gets the state of the preferences controller. - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor({ onPreferencesStateChange, onNetworkStateChange, onTokenListStateChange, getBalancesInSingleCall, addDetectedTokens, getTokenListState, getTokensState, getNetworkState, getPreferencesState, }, config, state) { - const { provider: { chainId: defaultChainId }, } = getNetworkState(); - const { useTokenDetection: defaultUseTokenDetection } = getPreferencesState(); - super(config, state); - /** - * Name of this controller used during composition - */ - this.name = 'TokenDetectionController'; - this.defaultConfig = Object.assign({ interval: DEFAULT_INTERVAL, selectedAddress: '', disabled: true, chainId: defaultChainId, isDetectionEnabledFromPreferences: defaultUseTokenDetection, isDetectionEnabledForNetwork: (0, util_1.isTokenDetectionSupportedForNetwork)(defaultChainId) }, config); - this.initialize(); - this.getTokensState = getTokensState; - this.getTokenListState = getTokenListState; - this.addDetectedTokens = addDetectedTokens; - this.getBalancesInSingleCall = getBalancesInSingleCall; - onTokenListStateChange(({ tokenList }) => { - const hasTokens = Object.keys(tokenList).length; - if (hasTokens) { - this.detectTokens(); - } - }); - onPreferencesStateChange(({ selectedAddress, useTokenDetection }) => { - const { selectedAddress: currentSelectedAddress, isDetectionEnabledFromPreferences, } = this.config; - const isSelectedAddressChanged = selectedAddress !== currentSelectedAddress; - const isDetectionChangedFromPreferences = isDetectionEnabledFromPreferences !== useTokenDetection; - this.configure({ - isDetectionEnabledFromPreferences: useTokenDetection, - selectedAddress, - }); - if (useTokenDetection && - (isSelectedAddressChanged || isDetectionChangedFromPreferences)) { - this.detectTokens(); - } - }); - onNetworkStateChange(({ provider: { chainId } }) => { - const { chainId: currentChainId } = this.config; - const isDetectionEnabledForNetwork = (0, util_1.isTokenDetectionSupportedForNetwork)(chainId); - const isChainIdChanged = currentChainId !== chainId; - this.configure({ - chainId, - isDetectionEnabledForNetwork, - }); - if (isDetectionEnabledForNetwork && isChainIdChanged) { - this.detectTokens(); - } - }); - } - /** - * Start polling for detected tokens. - */ - start() { - return __awaiter(this, void 0, void 0, function* () { - this.configure({ disabled: false }); - yield this.startPolling(); - }); - } - /** - * Stop polling for detected tokens. - */ - stop() { - this.configure({ disabled: true }); - this.stopPolling(); - } - stopPolling() { - if (this.intervalId) { - clearInterval(this.intervalId); - } - } - /** - * Starts a new polling interval. - * - * @param interval - An interval on which to poll. - */ - startPolling(interval) { - return __awaiter(this, void 0, void 0, function* () { - interval && this.configure({ interval }, false, false); - this.stopPolling(); - yield this.detectTokens(); - this.intervalId = setInterval(() => __awaiter(this, void 0, void 0, function* () { - yield this.detectTokens(); - }), this.config.interval); - }); - } - /** - * Triggers asset ERC20 token auto detection for each contract address in contract metadata on mainnet. - */ - detectTokens() { - return __awaiter(this, void 0, void 0, function* () { - const { disabled, isDetectionEnabledForNetwork, isDetectionEnabledFromPreferences, } = this.config; - if (disabled || - !isDetectionEnabledForNetwork || - !isDetectionEnabledFromPreferences) { - return; - } - const { tokens } = this.getTokensState(); - const { selectedAddress } = this.config; - const tokensAddresses = tokens.map( - /* istanbul ignore next*/ (token) => token.address.toLowerCase()); - const { tokenList } = this.getTokenListState(); - const tokensToDetect = []; - for (const address in tokenList) { - if (!tokensAddresses.includes(address)) { - tokensToDetect.push(address); - } - } - const sliceOfTokensToDetect = []; - sliceOfTokensToDetect[0] = tokensToDetect.slice(0, 1000); - sliceOfTokensToDetect[1] = tokensToDetect.slice(1000, tokensToDetect.length - 1); - /* istanbul ignore else */ - if (!selectedAddress) { - return; - } - for (const tokensSlice of sliceOfTokensToDetect) { - if (tokensSlice.length === 0) { - break; - } - yield (0, util_1.safelyExecute)(() => __awaiter(this, void 0, void 0, function* () { - const balances = yield this.getBalancesInSingleCall(selectedAddress, tokensSlice); - const tokensToAdd = []; - for (const tokenAddress in balances) { - let ignored; - /* istanbul ignore else */ - const { ignoredTokens } = this.getTokensState(); - if (ignoredTokens.length) { - ignored = ignoredTokens.find((ignoredTokenAddress) => ignoredTokenAddress === (0, util_1.toChecksumHexAddress)(tokenAddress)); - } - const caseInsensitiveTokenKey = Object.keys(tokenList).find((i) => i.toLowerCase() === tokenAddress.toLowerCase()) || ''; - if (ignored === undefined) { - const { decimals, symbol, aggregators, iconUrl } = tokenList[caseInsensitiveTokenKey]; - tokensToAdd.push({ - address: tokenAddress, - decimals, - symbol, - aggregators, - image: iconUrl, - isERC721: false, - }); - } - } - if (tokensToAdd.length) { - yield this.addDetectedTokens(tokensToAdd); - } - })); - } - }); - } -} -exports.TokenDetectionController = TokenDetectionController; -exports.default = TokenDetectionController; -//# sourceMappingURL=TokenDetectionController.js.map \ No newline at end of file diff --git a/dist/assets/TokenDetectionController.js.map b/dist/assets/TokenDetectionController.js.map deleted file mode 100644 index da45459977..0000000000 --- a/dist/assets/TokenDetectionController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"TokenDetectionController.js","sourceRoot":"","sources":["../../src/assets/TokenDetectionController.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport type { NetworkState } from '../network/NetworkController';\nimport type { PreferencesState } from '../user/PreferencesController';\nimport {\n safelyExecute,\n toChecksumHexAddress,\n isTokenDetectionSupportedForNetwork,\n} from '../util';\nimport type { TokensController, TokensState } from './TokensController';\nimport type { AssetsContractController } from './AssetsContractController';\nimport { Token } from './TokenRatesController';\nimport { TokenListState } from './TokenListController';\n\nconst DEFAULT_INTERVAL = 180000;\n\n/**\n * @type TokenDetectionConfig\n *\n * TokenDetection configuration\n * @property interval - Polling interval used to fetch new token rates\n * @property selectedAddress - Vault selected address\n * @property chainId - The chain ID of the current network\n * @property isDetectionEnabledFromPreferences - Boolean to track if detection is enabled from PreferencesController\n * @property isDetectionEnabledForNetwork - Boolean to track if detected is enabled for current network\n */\nexport interface TokenDetectionConfig extends BaseConfig {\n interval: number;\n selectedAddress: string;\n chainId: string;\n isDetectionEnabledFromPreferences: boolean;\n isDetectionEnabledForNetwork: boolean;\n}\n\n/**\n * Controller that passively polls on a set interval for Tokens auto detection\n */\nexport class TokenDetectionController extends BaseController<\n TokenDetectionConfig,\n BaseState\n> {\n private intervalId?: NodeJS.Timeout;\n\n /**\n * Name of this controller used during composition\n */\n override name = 'TokenDetectionController';\n\n private getBalancesInSingleCall: AssetsContractController['getBalancesInSingleCall'];\n\n private addDetectedTokens: TokensController['addDetectedTokens'];\n\n private getTokensState: () => TokensState;\n\n private getTokenListState: () => TokenListState;\n\n /**\n * Creates a TokenDetectionController instance.\n *\n * @param options - The controller options.\n * @param options.onPreferencesStateChange - Allows subscribing to preferences controller state changes.\n * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.\n * @param options.onTokenListStateChange - Allows subscribing to token list controller state changes.\n * @param options.getBalancesInSingleCall - Gets the balances of a list of tokens for the given address.\n * @param options.addDetectedTokens - Add a list of detected tokens.\n * @param options.getTokenListState - Gets the current state of the TokenList controller.\n * @param options.getTokensState - Gets the current state of the Tokens controller.\n * @param options.getNetworkState - Gets the state of the network controller.\n * @param options.getPreferencesState - Gets the state of the preferences controller.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n onPreferencesStateChange,\n onNetworkStateChange,\n onTokenListStateChange,\n getBalancesInSingleCall,\n addDetectedTokens,\n getTokenListState,\n getTokensState,\n getNetworkState,\n getPreferencesState,\n }: {\n onPreferencesStateChange: (\n listener: (preferencesState: PreferencesState) => void,\n ) => void;\n onNetworkStateChange: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n onTokenListStateChange: (\n listener: (tokenListState: TokenListState) => void,\n ) => void;\n getBalancesInSingleCall: AssetsContractController['getBalancesInSingleCall'];\n addDetectedTokens: TokensController['addDetectedTokens'];\n getTokenListState: () => TokenListState;\n getTokensState: () => TokensState;\n getNetworkState: () => NetworkState;\n getPreferencesState: () => PreferencesState;\n },\n config?: Partial,\n state?: Partial,\n ) {\n const {\n provider: { chainId: defaultChainId },\n } = getNetworkState();\n const { useTokenDetection: defaultUseTokenDetection } =\n getPreferencesState();\n\n super(config, state);\n this.defaultConfig = {\n interval: DEFAULT_INTERVAL,\n selectedAddress: '',\n disabled: true,\n chainId: defaultChainId,\n isDetectionEnabledFromPreferences: defaultUseTokenDetection,\n isDetectionEnabledForNetwork:\n isTokenDetectionSupportedForNetwork(defaultChainId),\n ...config,\n };\n\n this.initialize();\n this.getTokensState = getTokensState;\n this.getTokenListState = getTokenListState;\n this.addDetectedTokens = addDetectedTokens;\n this.getBalancesInSingleCall = getBalancesInSingleCall;\n\n onTokenListStateChange(({ tokenList }) => {\n const hasTokens = Object.keys(tokenList).length;\n\n if (hasTokens) {\n this.detectTokens();\n }\n });\n\n onPreferencesStateChange(({ selectedAddress, useTokenDetection }) => {\n const {\n selectedAddress: currentSelectedAddress,\n isDetectionEnabledFromPreferences,\n } = this.config;\n const isSelectedAddressChanged =\n selectedAddress !== currentSelectedAddress;\n const isDetectionChangedFromPreferences =\n isDetectionEnabledFromPreferences !== useTokenDetection;\n\n this.configure({\n isDetectionEnabledFromPreferences: useTokenDetection,\n selectedAddress,\n });\n\n if (\n useTokenDetection &&\n (isSelectedAddressChanged || isDetectionChangedFromPreferences)\n ) {\n this.detectTokens();\n }\n });\n\n onNetworkStateChange(({ provider: { chainId } }) => {\n const { chainId: currentChainId } = this.config;\n const isDetectionEnabledForNetwork =\n isTokenDetectionSupportedForNetwork(chainId);\n const isChainIdChanged = currentChainId !== chainId;\n\n this.configure({\n chainId,\n isDetectionEnabledForNetwork,\n });\n\n if (isDetectionEnabledForNetwork && isChainIdChanged) {\n this.detectTokens();\n }\n });\n }\n\n /**\n * Start polling for detected tokens.\n */\n async start() {\n this.configure({ disabled: false });\n await this.startPolling();\n }\n\n /**\n * Stop polling for detected tokens.\n */\n stop() {\n this.configure({ disabled: true });\n this.stopPolling();\n }\n\n private stopPolling() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n }\n\n /**\n * Starts a new polling interval.\n *\n * @param interval - An interval on which to poll.\n */\n private async startPolling(interval?: number): Promise {\n interval && this.configure({ interval }, false, false);\n this.stopPolling();\n await this.detectTokens();\n this.intervalId = setInterval(async () => {\n await this.detectTokens();\n }, this.config.interval);\n }\n\n /**\n * Triggers asset ERC20 token auto detection for each contract address in contract metadata on mainnet.\n */\n async detectTokens() {\n const {\n disabled,\n isDetectionEnabledForNetwork,\n isDetectionEnabledFromPreferences,\n } = this.config;\n if (\n disabled ||\n !isDetectionEnabledForNetwork ||\n !isDetectionEnabledFromPreferences\n ) {\n return;\n }\n const { tokens } = this.getTokensState();\n const { selectedAddress } = this.config;\n\n const tokensAddresses = tokens.map(\n /* istanbul ignore next*/ (token) => token.address.toLowerCase(),\n );\n const { tokenList } = this.getTokenListState();\n const tokensToDetect: string[] = [];\n for (const address in tokenList) {\n if (!tokensAddresses.includes(address)) {\n tokensToDetect.push(address);\n }\n }\n const sliceOfTokensToDetect = [];\n sliceOfTokensToDetect[0] = tokensToDetect.slice(0, 1000);\n sliceOfTokensToDetect[1] = tokensToDetect.slice(\n 1000,\n tokensToDetect.length - 1,\n );\n\n /* istanbul ignore else */\n if (!selectedAddress) {\n return;\n }\n\n for (const tokensSlice of sliceOfTokensToDetect) {\n if (tokensSlice.length === 0) {\n break;\n }\n\n await safelyExecute(async () => {\n const balances = await this.getBalancesInSingleCall(\n selectedAddress,\n tokensSlice,\n );\n const tokensToAdd: Token[] = [];\n for (const tokenAddress in balances) {\n let ignored;\n /* istanbul ignore else */\n const { ignoredTokens } = this.getTokensState();\n if (ignoredTokens.length) {\n ignored = ignoredTokens.find(\n (ignoredTokenAddress) =>\n ignoredTokenAddress === toChecksumHexAddress(tokenAddress),\n );\n }\n const caseInsensitiveTokenKey =\n Object.keys(tokenList).find(\n (i) => i.toLowerCase() === tokenAddress.toLowerCase(),\n ) || '';\n\n if (ignored === undefined) {\n const { decimals, symbol, aggregators, iconUrl } =\n tokenList[caseInsensitiveTokenKey];\n tokensToAdd.push({\n address: tokenAddress,\n decimals,\n symbol,\n aggregators,\n image: iconUrl,\n isERC721: false,\n });\n }\n }\n\n if (tokensToAdd.length) {\n await this.addDetectedTokens(tokensToAdd);\n }\n });\n }\n }\n}\n\nexport default TokenDetectionController;\n"]} \ No newline at end of file diff --git a/dist/assets/TokenListController.d.ts b/dist/assets/TokenListController.d.ts deleted file mode 100644 index c874988238..0000000000 --- a/dist/assets/TokenListController.d.ts +++ /dev/null @@ -1,101 +0,0 @@ -import type { Patch } from 'immer'; -import { BaseController } from '../BaseControllerV2'; -import type { RestrictedControllerMessenger } from '../ControllerMessenger'; -import { NetworkState } from '../network/NetworkController'; -declare const name = "TokenListController"; -export declare type TokenListToken = { - name: string; - symbol: string; - decimals: number; - address: string; - occurrences: number; - aggregators: string[]; - iconUrl: string; -}; -export declare type TokenListMap = Record; -declare type DataCache = { - timestamp: number; - data: TokenListMap; -}; -declare type TokensChainsCache = { - [chainSlug: string]: DataCache; -}; -export declare type TokenListState = { - tokenList: TokenListMap; - tokensChainsCache: TokensChainsCache; -}; -export declare type TokenListStateChange = { - type: `${typeof name}:stateChange`; - payload: [TokenListState, Patch[]]; -}; -export declare type GetTokenListState = { - type: `${typeof name}:getState`; - handler: () => TokenListState; -}; -declare type TokenListMessenger = RestrictedControllerMessenger; -/** - * Controller that passively polls on a set interval for the list of tokens from metaswaps api - */ -export declare class TokenListController extends BaseController { - private mutex; - private intervalId?; - private intervalDelay; - private cacheRefreshThreshold; - private chainId; - private abortController; - /** - * Creates a TokenListController instance. - * - * @param options - The controller options. - * @param options.chainId - The chain ID of the current network. - * @param options.onNetworkStateChange - A function for registering an event handler for network state changes. - * @param options.interval - The polling interval, in milliseconds. - * @param options.cacheRefreshThreshold - The token cache expiry time, in milliseconds. - * @param options.messenger - A restricted controller messenger. - * @param options.state - Initial state to set on this controller. - */ - constructor({ chainId, onNetworkStateChange, interval, cacheRefreshThreshold, messenger, state, }: { - chainId: string; - onNetworkStateChange: (listener: (networkState: NetworkState) => void) => void; - interval?: number; - cacheRefreshThreshold?: number; - messenger: TokenListMessenger; - state?: Partial; - }); - /** - * Start polling for the token list. - */ - start(): Promise; - /** - * Restart polling for the token list. - */ - restart(): Promise; - /** - * Stop polling for the token list. - */ - stop(): void; - /** - * Prepare to discard this controller. - * - * This stops any active polling. - */ - destroy(): void; - private stopPolling; - /** - * Starts a new polling interval. - */ - private startPolling; - /** - * Fetching token list from the Token Service API. - */ - fetchTokenList(): Promise; - /** - * Checks if the Cache timestamp is valid, - * if yes data in cache will be returned - * otherwise null will be returned. - * - * @returns The cached data, or `null` if the cache was expired. - */ - fetchFromCache(): Promise; -} -export default TokenListController; diff --git a/dist/assets/TokenListController.js b/dist/assets/TokenListController.js deleted file mode 100644 index 48b5c7115f..0000000000 --- a/dist/assets/TokenListController.js +++ /dev/null @@ -1,207 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.TokenListController = void 0; -const async_mutex_1 = require("async-mutex"); -const abort_controller_1 = require("abort-controller"); -const BaseControllerV2_1 = require("../BaseControllerV2"); -const util_1 = require("../util"); -const token_service_1 = require("../apis/token-service"); -const assetsUtil_1 = require("./assetsUtil"); -const DEFAULT_INTERVAL = 24 * 60 * 60 * 1000; -const DEFAULT_THRESHOLD = 24 * 60 * 60 * 1000; -const name = 'TokenListController'; -const metadata = { - tokenList: { persist: true, anonymous: true }, - tokensChainsCache: { persist: true, anonymous: true }, -}; -const defaultState = { - tokenList: {}, - tokensChainsCache: {}, -}; -/** - * Controller that passively polls on a set interval for the list of tokens from metaswaps api - */ -class TokenListController extends BaseControllerV2_1.BaseController { - /** - * Creates a TokenListController instance. - * - * @param options - The controller options. - * @param options.chainId - The chain ID of the current network. - * @param options.onNetworkStateChange - A function for registering an event handler for network state changes. - * @param options.interval - The polling interval, in milliseconds. - * @param options.cacheRefreshThreshold - The token cache expiry time, in milliseconds. - * @param options.messenger - A restricted controller messenger. - * @param options.state - Initial state to set on this controller. - */ - constructor({ chainId, onNetworkStateChange, interval = DEFAULT_INTERVAL, cacheRefreshThreshold = DEFAULT_THRESHOLD, messenger, state, }) { - super({ - name, - metadata, - messenger, - state: Object.assign(Object.assign({}, defaultState), state), - }); - this.mutex = new async_mutex_1.Mutex(); - this.intervalDelay = interval; - this.cacheRefreshThreshold = cacheRefreshThreshold; - this.chainId = chainId; - this.abortController = new abort_controller_1.AbortController(); - onNetworkStateChange((networkState) => __awaiter(this, void 0, void 0, function* () { - if (this.chainId !== networkState.provider.chainId) { - this.abortController.abort(); - this.abortController = new abort_controller_1.AbortController(); - this.chainId = networkState.provider.chainId; - // Ensure tokenList is referencing data from correct network - this.update(() => { - var _a; - return Object.assign(Object.assign({}, this.state), { tokenList: ((_a = this.state.tokensChainsCache[this.chainId]) === null || _a === void 0 ? void 0 : _a.data) || {} }); - }); - yield this.restart(); - } - })); - } - /** - * Start polling for the token list. - */ - start() { - return __awaiter(this, void 0, void 0, function* () { - if (!(0, util_1.isTokenDetectionSupportedForNetwork)(this.chainId)) { - return; - } - yield this.startPolling(); - }); - } - /** - * Restart polling for the token list. - */ - restart() { - return __awaiter(this, void 0, void 0, function* () { - this.stopPolling(); - yield this.startPolling(); - }); - } - /** - * Stop polling for the token list. - */ - stop() { - this.stopPolling(); - } - /** - * Prepare to discard this controller. - * - * This stops any active polling. - */ - destroy() { - super.destroy(); - this.stopPolling(); - } - stopPolling() { - if (this.intervalId) { - clearInterval(this.intervalId); - } - } - /** - * Starts a new polling interval. - */ - startPolling() { - return __awaiter(this, void 0, void 0, function* () { - yield (0, util_1.safelyExecute)(() => this.fetchTokenList()); - this.intervalId = setInterval(() => __awaiter(this, void 0, void 0, function* () { - yield (0, util_1.safelyExecute)(() => this.fetchTokenList()); - }), this.intervalDelay); - }); - } - /** - * Fetching token list from the Token Service API. - */ - fetchTokenList() { - var _a; - return __awaiter(this, void 0, void 0, function* () { - const releaseLock = yield this.mutex.acquire(); - try { - const { tokensChainsCache } = this.state; - let tokenList = {}; - const cachedTokens = yield (0, util_1.safelyExecute)(() => this.fetchFromCache()); - if (cachedTokens) { - // Use non-expired cached tokens - tokenList = Object.assign({}, cachedTokens); - } - else { - // Fetch fresh token list - const tokensFromAPI = yield (0, util_1.safelyExecute)(() => (0, token_service_1.fetchTokenList)(this.chainId, this.abortController.signal)); - if (!tokensFromAPI) { - // Fallback to expired cached tokens - tokenList = Object.assign({}, (((_a = tokensChainsCache[this.chainId]) === null || _a === void 0 ? void 0 : _a.data) || {})); - this.update(() => { - return { - tokenList, - tokensChainsCache, - }; - }); - return; - } - // Filtering out tokens with less than 3 occurrences and native tokens - const filteredTokenList = tokensFromAPI.filter((token) => token.occurrences && - token.occurrences >= 3 && - token.address !== '0x0000000000000000000000000000000000000000'); - // Removing the tokens with symbol conflicts - const symbolsList = filteredTokenList.map((token) => token.symbol); - const duplicateSymbols = [ - ...new Set(symbolsList.filter((symbol, index) => symbolsList.indexOf(symbol) !== index)), - ]; - const uniqueTokenList = filteredTokenList.filter((token) => !duplicateSymbols.includes(token.symbol)); - for (const token of uniqueTokenList) { - const formattedToken = Object.assign(Object.assign({}, token), { aggregators: (0, assetsUtil_1.formatAggregatorNames)(token.aggregators), iconUrl: (0, assetsUtil_1.formatIconUrlWithProxy)({ - chainId: this.chainId, - tokenAddress: token.address, - }) }); - tokenList[token.address] = formattedToken; - } - } - const updatedTokensChainsCache = Object.assign(Object.assign({}, tokensChainsCache), { [this.chainId]: { - timestamp: Date.now(), - data: tokenList, - } }); - this.update(() => { - return { - tokenList, - tokensChainsCache: updatedTokensChainsCache, - }; - }); - } - finally { - releaseLock(); - } - }); - } - /** - * Checks if the Cache timestamp is valid, - * if yes data in cache will be returned - * otherwise null will be returned. - * - * @returns The cached data, or `null` if the cache was expired. - */ - fetchFromCache() { - return __awaiter(this, void 0, void 0, function* () { - const { tokensChainsCache } = this.state; - const dataCache = tokensChainsCache[this.chainId]; - const now = Date.now(); - if ((dataCache === null || dataCache === void 0 ? void 0 : dataCache.data) && - now - (dataCache === null || dataCache === void 0 ? void 0 : dataCache.timestamp) < this.cacheRefreshThreshold) { - return dataCache.data; - } - return null; - }); - } -} -exports.TokenListController = TokenListController; -exports.default = TokenListController; -//# sourceMappingURL=TokenListController.js.map \ No newline at end of file diff --git a/dist/assets/TokenListController.js.map b/dist/assets/TokenListController.js.map deleted file mode 100644 index e6fdec76e5..0000000000 --- a/dist/assets/TokenListController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"TokenListController.js","sourceRoot":"","sources":["../../src/assets/TokenListController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Patch } from 'immer';\nimport { Mutex } from 'async-mutex';\nimport { AbortController } from 'abort-controller';\nimport { BaseController } from '../BaseControllerV2';\nimport type { RestrictedControllerMessenger } from '../ControllerMessenger';\nimport { safelyExecute, isTokenDetectionSupportedForNetwork } from '../util';\nimport { fetchTokenList } from '../apis/token-service';\nimport { NetworkState } from '../network/NetworkController';\nimport { formatAggregatorNames, formatIconUrlWithProxy } from './assetsUtil';\n\nconst DEFAULT_INTERVAL = 24 * 60 * 60 * 1000;\nconst DEFAULT_THRESHOLD = 24 * 60 * 60 * 1000;\n\nconst name = 'TokenListController';\n\nexport type TokenListToken = {\n name: string;\n symbol: string;\n decimals: number;\n address: string;\n occurrences: number;\n aggregators: string[];\n iconUrl: string;\n};\n\nexport type TokenListMap = Record;\n\ntype DataCache = {\n timestamp: number;\n data: TokenListMap;\n};\ntype TokensChainsCache = {\n [chainSlug: string]: DataCache;\n};\n\nexport type TokenListState = {\n tokenList: TokenListMap;\n tokensChainsCache: TokensChainsCache;\n};\n\nexport type TokenListStateChange = {\n type: `${typeof name}:stateChange`;\n payload: [TokenListState, Patch[]];\n};\n\nexport type GetTokenListState = {\n type: `${typeof name}:getState`;\n handler: () => TokenListState;\n};\n\ntype TokenListMessenger = RestrictedControllerMessenger<\n typeof name,\n GetTokenListState,\n TokenListStateChange,\n never,\n TokenListStateChange['type']\n>;\n\nconst metadata = {\n tokenList: { persist: true, anonymous: true },\n tokensChainsCache: { persist: true, anonymous: true },\n};\n\nconst defaultState: TokenListState = {\n tokenList: {},\n tokensChainsCache: {},\n};\n\n/**\n * Controller that passively polls on a set interval for the list of tokens from metaswaps api\n */\nexport class TokenListController extends BaseController<\n typeof name,\n TokenListState,\n TokenListMessenger\n> {\n private mutex = new Mutex();\n\n private intervalId?: NodeJS.Timeout;\n\n private intervalDelay: number;\n\n private cacheRefreshThreshold: number;\n\n private chainId: string;\n\n private abortController: AbortController;\n\n /**\n * Creates a TokenListController instance.\n *\n * @param options - The controller options.\n * @param options.chainId - The chain ID of the current network.\n * @param options.onNetworkStateChange - A function for registering an event handler for network state changes.\n * @param options.interval - The polling interval, in milliseconds.\n * @param options.cacheRefreshThreshold - The token cache expiry time, in milliseconds.\n * @param options.messenger - A restricted controller messenger.\n * @param options.state - Initial state to set on this controller.\n */\n constructor({\n chainId,\n onNetworkStateChange,\n interval = DEFAULT_INTERVAL,\n cacheRefreshThreshold = DEFAULT_THRESHOLD,\n messenger,\n state,\n }: {\n chainId: string;\n onNetworkStateChange: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n interval?: number;\n cacheRefreshThreshold?: number;\n messenger: TokenListMessenger;\n state?: Partial;\n }) {\n super({\n name,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n this.intervalDelay = interval;\n this.cacheRefreshThreshold = cacheRefreshThreshold;\n this.chainId = chainId;\n this.abortController = new AbortController();\n onNetworkStateChange(async (networkState) => {\n if (this.chainId !== networkState.provider.chainId) {\n this.abortController.abort();\n this.abortController = new AbortController();\n this.chainId = networkState.provider.chainId;\n // Ensure tokenList is referencing data from correct network\n this.update(() => {\n return {\n ...this.state,\n tokenList: this.state.tokensChainsCache[this.chainId]?.data || {},\n };\n });\n await this.restart();\n }\n });\n }\n\n /**\n * Start polling for the token list.\n */\n async start() {\n if (!isTokenDetectionSupportedForNetwork(this.chainId)) {\n return;\n }\n await this.startPolling();\n }\n\n /**\n * Restart polling for the token list.\n */\n async restart() {\n this.stopPolling();\n await this.startPolling();\n }\n\n /**\n * Stop polling for the token list.\n */\n stop() {\n this.stopPolling();\n }\n\n /**\n * Prepare to discard this controller.\n *\n * This stops any active polling.\n */\n override destroy() {\n super.destroy();\n this.stopPolling();\n }\n\n private stopPolling() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n }\n\n /**\n * Starts a new polling interval.\n */\n private async startPolling(): Promise {\n await safelyExecute(() => this.fetchTokenList());\n this.intervalId = setInterval(async () => {\n await safelyExecute(() => this.fetchTokenList());\n }, this.intervalDelay);\n }\n\n /**\n * Fetching token list from the Token Service API.\n */\n async fetchTokenList(): Promise {\n const releaseLock = await this.mutex.acquire();\n try {\n const { tokensChainsCache } = this.state;\n let tokenList: TokenListMap = {};\n const cachedTokens: TokenListMap = await safelyExecute(() =>\n this.fetchFromCache(),\n );\n if (cachedTokens) {\n // Use non-expired cached tokens\n tokenList = { ...cachedTokens };\n } else {\n // Fetch fresh token list\n const tokensFromAPI: TokenListToken[] = await safelyExecute(() =>\n fetchTokenList(this.chainId, this.abortController.signal),\n );\n\n if (!tokensFromAPI) {\n // Fallback to expired cached tokens\n tokenList = { ...(tokensChainsCache[this.chainId]?.data || {}) };\n\n this.update(() => {\n return {\n tokenList,\n tokensChainsCache,\n };\n });\n return;\n }\n // Filtering out tokens with less than 3 occurrences and native tokens\n const filteredTokenList = tokensFromAPI.filter(\n (token) =>\n token.occurrences &&\n token.occurrences >= 3 &&\n token.address !== '0x0000000000000000000000000000000000000000',\n );\n // Removing the tokens with symbol conflicts\n const symbolsList = filteredTokenList.map((token) => token.symbol);\n const duplicateSymbols = [\n ...new Set(\n symbolsList.filter(\n (symbol, index) => symbolsList.indexOf(symbol) !== index,\n ),\n ),\n ];\n const uniqueTokenList = filteredTokenList.filter(\n (token) => !duplicateSymbols.includes(token.symbol),\n );\n for (const token of uniqueTokenList) {\n const formattedToken: TokenListToken = {\n ...token,\n aggregators: formatAggregatorNames(token.aggregators),\n iconUrl: formatIconUrlWithProxy({\n chainId: this.chainId,\n tokenAddress: token.address,\n }),\n };\n tokenList[token.address] = formattedToken;\n }\n }\n const updatedTokensChainsCache: TokensChainsCache = {\n ...tokensChainsCache,\n [this.chainId]: {\n timestamp: Date.now(),\n data: tokenList,\n },\n };\n this.update(() => {\n return {\n tokenList,\n tokensChainsCache: updatedTokensChainsCache,\n };\n });\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Checks if the Cache timestamp is valid,\n * if yes data in cache will be returned\n * otherwise null will be returned.\n *\n * @returns The cached data, or `null` if the cache was expired.\n */\n async fetchFromCache(): Promise {\n const { tokensChainsCache }: TokenListState = this.state;\n const dataCache = tokensChainsCache[this.chainId];\n const now = Date.now();\n if (\n dataCache?.data &&\n now - dataCache?.timestamp < this.cacheRefreshThreshold\n ) {\n return dataCache.data;\n }\n return null;\n }\n}\n\nexport default TokenListController;\n"]} \ No newline at end of file diff --git a/dist/assets/TokenRatesController.d.ts b/dist/assets/TokenRatesController.d.ts deleted file mode 100644 index 40e8e609ac..0000000000 --- a/dist/assets/TokenRatesController.d.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { BaseController, BaseConfig, BaseState } from '../BaseController'; -import type { NetworkState } from '../network/NetworkController'; -import type { TokensState } from './TokensController'; -import type { CurrencyRateState } from './CurrencyRateController'; -/** - * @type CoinGeckoResponse - * - * CoinGecko API response representation - */ -export interface CoinGeckoResponse { - [address: string]: { - [currency: string]: number; - }; -} -/** - * @type CoinGeckoPlatform - * - * CoinGecko supported platform API representation - */ -export interface CoinGeckoPlatform { - id: string; - chain_identifier: null | number; - name: string; - shortname: string; -} -/** - * @type Token - * - * Token representation - * @property address - Hex address of the token contract - * @property decimals - Number of decimals the token uses - * @property symbol - Symbol of the token - * @property image - Image of the token, url or bit32 image - */ -export interface Token { - address: string; - decimals: number; - symbol: string; - aggregators?: string[]; - image?: string; - balanceError?: unknown; - isERC721?: boolean; -} -/** - * @type TokenRatesConfig - * - * Token rates controller configuration - * @property interval - Polling interval used to fetch new token rates - * @property nativeCurrency - Current native currency selected to use base of rates - * @property chainId - Current network chainId - * @property tokens - List of tokens to track exchange rates for - * @property threshold - Threshold to invalidate the supportedChains - */ -export interface TokenRatesConfig extends BaseConfig { - interval: number; - nativeCurrency: string; - chainId: string; - tokens: Token[]; - threshold: number; -} -interface ContractExchangeRates { - [address: string]: number | undefined; -} -/** - * @type TokenRatesState - * - * Token rates controller state - * @property contractExchangeRates - Hash of token contract addresses to exchange rates - * @property supportedChains - Cached chain data - */ -export interface TokenRatesState extends BaseState { - contractExchangeRates: ContractExchangeRates; -} -/** - * Controller that passively polls on a set interval for token-to-fiat exchange rates - * for tokens stored in the TokensController - */ -export declare class TokenRatesController extends BaseController { - private handle?; - private tokenList; - private supportedChains; - private supportedVsCurrencies; - /** - * Name of this controller used during composition - */ - name: string; - /** - * Creates a TokenRatesController instance. - * - * @param options - The controller options. - * @param options.onTokensStateChange - Allows subscribing to token controller state changes. - * @param options.onCurrencyRateStateChange - Allows subscribing to currency rate controller state changes. - * @param options.onNetworkStateChange - Allows subscribing to network state changes. - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor({ onTokensStateChange, onCurrencyRateStateChange, onNetworkStateChange, }: { - onTokensStateChange: (listener: (tokensState: TokensState) => void) => void; - onCurrencyRateStateChange: (listener: (currencyRateState: CurrencyRateState) => void) => void; - onNetworkStateChange: (listener: (networkState: NetworkState) => void) => void; - }, config?: Partial, state?: Partial); - /** - * Sets a new polling interval. - * - * @param interval - Polling interval used to fetch new token rates. - */ - poll(interval?: number): Promise; - /** - * Sets a new chainId. - * - * TODO: Replace this with a method. - * - * @param _chainId - The current chain ID. - */ - set chainId(_chainId: string); - get chainId(): string; - /** - * Sets a new token list to track prices. - * - * TODO: Replace this with a method. - * - * @param tokens - List of tokens to track exchange rates for. - */ - set tokens(tokens: Token[]); - get tokens(): Token[]; - /** - * Fetches a pairs of token address and native currency. - * - * @param chainSlug - Chain string identifier. - * @param vsCurrency - Query according to tokens in tokenList and native currency. - * @returns The exchange rates for the given pairs. - */ - fetchExchangeRate(chainSlug: string, vsCurrency: string): Promise; - /** - * Checks if the current native currency is a supported vs currency to use - * to query for token exchange rates. - * - * @param nativeCurrency - The native currency of the currently active network. - * @returns A boolean indicating whether it's a supported vsCurrency. - */ - private checkIsSupportedVsCurrency; - /** - * Gets current chain ID slug from cached supported platforms CoinGecko API response. - * If cached supported platforms response is stale, fetches and updates it. - * - * @returns The CoinGecko slug for the current chain ID. - */ - getChainSlug(): Promise; - /** - * Updates exchange rates for all tokens. - */ - updateExchangeRates(): Promise; - /** - * Checks if the active network's native currency is supported by the coingecko API. - * If supported, it fetches and maps contractExchange rates to a format to be consumed by the UI. - * If not supported, it fetches contractExchange rates and maps them from token/fallback-currency - * to token/nativeCurrency. - * - * @param nativeCurrency - The native currency of the currently active network. - * @param slug - The unique slug used to id the chain by the coingecko api - * should be used to query token exchange rates. - * @returns An object with conversion rates for each token - * related to the network's native currency. - */ - fetchAndMapExchangeRates(nativeCurrency: string, slug: string): Promise; -} -export default TokenRatesController; diff --git a/dist/assets/TokenRatesController.js b/dist/assets/TokenRatesController.js deleted file mode 100644 index b170a3dcfa..0000000000 --- a/dist/assets/TokenRatesController.js +++ /dev/null @@ -1,285 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.TokenRatesController = void 0; -const BaseController_1 = require("../BaseController"); -const util_1 = require("../util"); -const constants_1 = require("../constants"); -const crypto_compare_1 = require("../apis/crypto-compare"); -const CoinGeckoApi = { - BASE_URL: 'https://api.coingecko.com/api/v3', - getTokenPriceURL(chainSlug, query) { - return `${this.BASE_URL}/simple/token_price/${chainSlug}?${query}`; - }, - getPlatformsURL() { - return `${this.BASE_URL}/asset_platforms`; - }, - getSupportedVsCurrencies() { - return `${this.BASE_URL}/simple/supported_vs_currencies`; - }, -}; -/** - * Finds the chain slug in the data array given a chainId. - * - * @param chainId - The current chain ID. - * @param data - A list platforms supported by the CoinGecko API. - * @returns The CoinGecko slug for the given chain ID, or `null` if the slug was not found. - */ -function findChainSlug(chainId, data) { - var _a; - if (!data) { - return null; - } - const chain = (_a = data.find(({ chain_identifier }) => chain_identifier !== null && String(chain_identifier) === chainId)) !== null && _a !== void 0 ? _a : null; - return (chain === null || chain === void 0 ? void 0 : chain.id) || null; -} -/** - * Controller that passively polls on a set interval for token-to-fiat exchange rates - * for tokens stored in the TokensController - */ -class TokenRatesController extends BaseController_1.BaseController { - /** - * Creates a TokenRatesController instance. - * - * @param options - The controller options. - * @param options.onTokensStateChange - Allows subscribing to token controller state changes. - * @param options.onCurrencyRateStateChange - Allows subscribing to currency rate controller state changes. - * @param options.onNetworkStateChange - Allows subscribing to network state changes. - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor({ onTokensStateChange, onCurrencyRateStateChange, onNetworkStateChange, }, config, state) { - super(config, state); - this.tokenList = []; - this.supportedChains = { - timestamp: 0, - data: null, - }; - this.supportedVsCurrencies = { - timestamp: 0, - data: [], - }; - /** - * Name of this controller used during composition - */ - this.name = 'TokenRatesController'; - this.defaultConfig = { - disabled: true, - interval: 3 * 60 * 1000, - nativeCurrency: 'eth', - chainId: '', - tokens: [], - threshold: 6 * 60 * 60 * 1000, - }; - this.defaultState = { - contractExchangeRates: {}, - }; - this.initialize(); - this.configure({ disabled: false }, false, false); - onTokensStateChange(({ tokens, detectedTokens }) => { - this.configure({ tokens: [...tokens, ...detectedTokens] }); - }); - onCurrencyRateStateChange((currencyRateState) => { - this.configure({ nativeCurrency: currencyRateState.nativeCurrency }); - }); - onNetworkStateChange(({ provider }) => { - const { chainId } = provider; - this.update({ contractExchangeRates: {} }); - this.configure({ chainId }); - }); - this.poll(); - } - /** - * Sets a new polling interval. - * - * @param interval - Polling interval used to fetch new token rates. - */ - poll(interval) { - return __awaiter(this, void 0, void 0, function* () { - interval && this.configure({ interval }, false, false); - this.handle && clearTimeout(this.handle); - yield (0, util_1.safelyExecute)(() => this.updateExchangeRates()); - this.handle = setTimeout(() => { - this.poll(this.config.interval); - }, this.config.interval); - }); - } - /** - * Sets a new chainId. - * - * TODO: Replace this with a method. - * - * @param _chainId - The current chain ID. - */ - set chainId(_chainId) { - !this.disabled && (0, util_1.safelyExecute)(() => this.updateExchangeRates()); - } - get chainId() { - throw new Error('Property only used for setting'); - } - /** - * Sets a new token list to track prices. - * - * TODO: Replace this with a method. - * - * @param tokens - List of tokens to track exchange rates for. - */ - set tokens(tokens) { - this.tokenList = tokens; - !this.disabled && (0, util_1.safelyExecute)(() => this.updateExchangeRates()); - } - get tokens() { - throw new Error('Property only used for setting'); - } - /** - * Fetches a pairs of token address and native currency. - * - * @param chainSlug - Chain string identifier. - * @param vsCurrency - Query according to tokens in tokenList and native currency. - * @returns The exchange rates for the given pairs. - */ - fetchExchangeRate(chainSlug, vsCurrency) { - return __awaiter(this, void 0, void 0, function* () { - const tokenPairs = this.tokenList.map((token) => token.address).join(','); - const query = `contract_addresses=${tokenPairs}&vs_currencies=${vsCurrency.toLowerCase()}`; - return (0, util_1.handleFetch)(CoinGeckoApi.getTokenPriceURL(chainSlug, query)); - }); - } - /** - * Checks if the current native currency is a supported vs currency to use - * to query for token exchange rates. - * - * @param nativeCurrency - The native currency of the currently active network. - * @returns A boolean indicating whether it's a supported vsCurrency. - */ - checkIsSupportedVsCurrency(nativeCurrency) { - return __awaiter(this, void 0, void 0, function* () { - const { threshold } = this.config; - const { timestamp, data } = this.supportedVsCurrencies; - const now = Date.now(); - if (now - timestamp > threshold) { - const currencies = yield (0, util_1.handleFetch)(CoinGeckoApi.getSupportedVsCurrencies()); - this.supportedVsCurrencies = { - data: currencies, - timestamp: Date.now(), - }; - return currencies.includes(nativeCurrency.toLowerCase()); - } - return data.includes(nativeCurrency.toLowerCase()); - }); - } - /** - * Gets current chain ID slug from cached supported platforms CoinGecko API response. - * If cached supported platforms response is stale, fetches and updates it. - * - * @returns The CoinGecko slug for the current chain ID. - */ - getChainSlug() { - return __awaiter(this, void 0, void 0, function* () { - const { threshold, chainId } = this.config; - const { data, timestamp } = this.supportedChains; - const now = Date.now(); - if (now - timestamp > threshold) { - const platforms = yield (0, util_1.handleFetch)(CoinGeckoApi.getPlatformsURL()); - this.supportedChains = { - data: platforms, - timestamp: Date.now(), - }; - return findChainSlug(chainId, platforms); - } - return findChainSlug(chainId, data); - }); - } - /** - * Updates exchange rates for all tokens. - */ - updateExchangeRates() { - return __awaiter(this, void 0, void 0, function* () { - if (this.tokenList.length === 0 || this.disabled) { - return; - } - const slug = yield this.getChainSlug(); - let newContractExchangeRates = {}; - if (!slug) { - this.tokenList.forEach((token) => { - const address = (0, util_1.toChecksumHexAddress)(token.address); - newContractExchangeRates[address] = undefined; - }); - } - else { - const { nativeCurrency } = this.config; - newContractExchangeRates = yield this.fetchAndMapExchangeRates(nativeCurrency, slug); - } - this.update({ contractExchangeRates: newContractExchangeRates }); - }); - } - /** - * Checks if the active network's native currency is supported by the coingecko API. - * If supported, it fetches and maps contractExchange rates to a format to be consumed by the UI. - * If not supported, it fetches contractExchange rates and maps them from token/fallback-currency - * to token/nativeCurrency. - * - * @param nativeCurrency - The native currency of the currently active network. - * @param slug - The unique slug used to id the chain by the coingecko api - * should be used to query token exchange rates. - * @returns An object with conversion rates for each token - * related to the network's native currency. - */ - fetchAndMapExchangeRates(nativeCurrency, slug) { - return __awaiter(this, void 0, void 0, function* () { - const contractExchangeRates = {}; - // check if native currency is supported as a vs_currency by the API - const nativeCurrencySupported = yield this.checkIsSupportedVsCurrency(nativeCurrency); - if (nativeCurrencySupported) { - // If it is we can do a simple fetch against the CoinGecko API - const prices = yield this.fetchExchangeRate(slug, nativeCurrency); - this.tokenList.forEach((token) => { - const price = prices[token.address.toLowerCase()]; - contractExchangeRates[(0, util_1.toChecksumHexAddress)(token.address)] = price - ? price[nativeCurrency.toLowerCase()] - : 0; - }); - } - else { - // if native currency is not supported we need to use a fallback vsCurrency, get the exchange rates - // in token/fallback-currency format and convert them to expected token/nativeCurrency format. - let tokenExchangeRates; - let vsCurrencyToNativeCurrencyConversionRate = 0; - try { - [ - tokenExchangeRates, - { conversionRate: vsCurrencyToNativeCurrencyConversionRate }, - ] = yield Promise.all([ - this.fetchExchangeRate(slug, constants_1.FALL_BACK_VS_CURRENCY), - (0, crypto_compare_1.fetchExchangeRate)(nativeCurrency, constants_1.FALL_BACK_VS_CURRENCY, false), - ]); - } - catch (error) { - if (error instanceof Error && - error.message.includes('market does not exist for this coin pair')) { - return {}; - } - throw error; - } - for (const [tokenAddress, conversion] of Object.entries(tokenExchangeRates)) { - const tokenToVsCurrencyConversionRate = conversion[constants_1.FALL_BACK_VS_CURRENCY.toLowerCase()]; - contractExchangeRates[(0, util_1.toChecksumHexAddress)(tokenAddress)] = - tokenToVsCurrencyConversionRate * - vsCurrencyToNativeCurrencyConversionRate; - } - } - return contractExchangeRates; - }); - } -} -exports.TokenRatesController = TokenRatesController; -exports.default = TokenRatesController; -//# sourceMappingURL=TokenRatesController.js.map \ No newline at end of file diff --git a/dist/assets/TokenRatesController.js.map b/dist/assets/TokenRatesController.js.map deleted file mode 100644 index b50ffe6df9..0000000000 --- a/dist/assets/TokenRatesController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"TokenRatesController.js","sourceRoot":"","sources":["../../src/assets/TokenRatesController.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport { safelyExecute, handleFetch, toChecksumHexAddress } from '../util';\n\nimport type { NetworkState } from '../network/NetworkController';\nimport { FALL_BACK_VS_CURRENCY } from '../constants';\nimport { fetchExchangeRate as fetchNativeExchangeRate } from '../apis/crypto-compare';\nimport type { TokensState } from './TokensController';\nimport type { CurrencyRateState } from './CurrencyRateController';\n\n/**\n * @type CoinGeckoResponse\n *\n * CoinGecko API response representation\n */\nexport interface CoinGeckoResponse {\n [address: string]: {\n [currency: string]: number;\n };\n}\n/**\n * @type CoinGeckoPlatform\n *\n * CoinGecko supported platform API representation\n */\nexport interface CoinGeckoPlatform {\n id: string;\n chain_identifier: null | number;\n name: string;\n shortname: string;\n}\n\n/**\n * @type Token\n *\n * Token representation\n * @property address - Hex address of the token contract\n * @property decimals - Number of decimals the token uses\n * @property symbol - Symbol of the token\n * @property image - Image of the token, url or bit32 image\n */\nexport interface Token {\n address: string;\n decimals: number;\n symbol: string;\n aggregators?: string[];\n image?: string;\n balanceError?: unknown;\n isERC721?: boolean;\n}\n\n/**\n * @type TokenRatesConfig\n *\n * Token rates controller configuration\n * @property interval - Polling interval used to fetch new token rates\n * @property nativeCurrency - Current native currency selected to use base of rates\n * @property chainId - Current network chainId\n * @property tokens - List of tokens to track exchange rates for\n * @property threshold - Threshold to invalidate the supportedChains\n */\nexport interface TokenRatesConfig extends BaseConfig {\n interval: number;\n nativeCurrency: string;\n chainId: string;\n tokens: Token[];\n threshold: number;\n}\n\ninterface ContractExchangeRates {\n [address: string]: number | undefined;\n}\n\ninterface SupportedChainsCache {\n timestamp: number;\n data: CoinGeckoPlatform[] | null;\n}\n\ninterface SupportedVsCurrenciesCache {\n timestamp: number;\n data: string[];\n}\n\n/**\n * @type TokenRatesState\n *\n * Token rates controller state\n * @property contractExchangeRates - Hash of token contract addresses to exchange rates\n * @property supportedChains - Cached chain data\n */\nexport interface TokenRatesState extends BaseState {\n contractExchangeRates: ContractExchangeRates;\n}\n\nconst CoinGeckoApi = {\n BASE_URL: 'https://api.coingecko.com/api/v3',\n getTokenPriceURL(chainSlug: string, query: string) {\n return `${this.BASE_URL}/simple/token_price/${chainSlug}?${query}`;\n },\n getPlatformsURL() {\n return `${this.BASE_URL}/asset_platforms`;\n },\n getSupportedVsCurrencies() {\n return `${this.BASE_URL}/simple/supported_vs_currencies`;\n },\n};\n\n/**\n * Finds the chain slug in the data array given a chainId.\n *\n * @param chainId - The current chain ID.\n * @param data - A list platforms supported by the CoinGecko API.\n * @returns The CoinGecko slug for the given chain ID, or `null` if the slug was not found.\n */\nfunction findChainSlug(\n chainId: string,\n data: CoinGeckoPlatform[] | null,\n): string | null {\n if (!data) {\n return null;\n }\n const chain =\n data.find(\n ({ chain_identifier }) =>\n chain_identifier !== null && String(chain_identifier) === chainId,\n ) ?? null;\n return chain?.id || null;\n}\n\n/**\n * Controller that passively polls on a set interval for token-to-fiat exchange rates\n * for tokens stored in the TokensController\n */\nexport class TokenRatesController extends BaseController<\n TokenRatesConfig,\n TokenRatesState\n> {\n private handle?: NodeJS.Timer;\n\n private tokenList: Token[] = [];\n\n private supportedChains: SupportedChainsCache = {\n timestamp: 0,\n data: null,\n };\n\n private supportedVsCurrencies: SupportedVsCurrenciesCache = {\n timestamp: 0,\n data: [],\n };\n\n /**\n * Name of this controller used during composition\n */\n override name = 'TokenRatesController';\n\n /**\n * Creates a TokenRatesController instance.\n *\n * @param options - The controller options.\n * @param options.onTokensStateChange - Allows subscribing to token controller state changes.\n * @param options.onCurrencyRateStateChange - Allows subscribing to currency rate controller state changes.\n * @param options.onNetworkStateChange - Allows subscribing to network state changes.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n onTokensStateChange,\n onCurrencyRateStateChange,\n onNetworkStateChange,\n }: {\n onTokensStateChange: (\n listener: (tokensState: TokensState) => void,\n ) => void;\n onCurrencyRateStateChange: (\n listener: (currencyRateState: CurrencyRateState) => void,\n ) => void;\n onNetworkStateChange: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n },\n config?: Partial,\n state?: Partial,\n ) {\n super(config, state);\n this.defaultConfig = {\n disabled: true,\n interval: 3 * 60 * 1000,\n nativeCurrency: 'eth',\n chainId: '',\n tokens: [],\n threshold: 6 * 60 * 60 * 1000,\n };\n\n this.defaultState = {\n contractExchangeRates: {},\n };\n this.initialize();\n this.configure({ disabled: false }, false, false);\n onTokensStateChange(({ tokens, detectedTokens }) => {\n this.configure({ tokens: [...tokens, ...detectedTokens] });\n });\n\n onCurrencyRateStateChange((currencyRateState) => {\n this.configure({ nativeCurrency: currencyRateState.nativeCurrency });\n });\n\n onNetworkStateChange(({ provider }) => {\n const { chainId } = provider;\n this.update({ contractExchangeRates: {} });\n this.configure({ chainId });\n });\n this.poll();\n }\n\n /**\n * Sets a new polling interval.\n *\n * @param interval - Polling interval used to fetch new token rates.\n */\n async poll(interval?: number): Promise {\n interval && this.configure({ interval }, false, false);\n this.handle && clearTimeout(this.handle);\n await safelyExecute(() => this.updateExchangeRates());\n this.handle = setTimeout(() => {\n this.poll(this.config.interval);\n }, this.config.interval);\n }\n\n /**\n * Sets a new chainId.\n *\n * TODO: Replace this with a method.\n *\n * @param _chainId - The current chain ID.\n */\n set chainId(_chainId: string) {\n !this.disabled && safelyExecute(() => this.updateExchangeRates());\n }\n\n get chainId() {\n throw new Error('Property only used for setting');\n }\n\n /**\n * Sets a new token list to track prices.\n *\n * TODO: Replace this with a method.\n *\n * @param tokens - List of tokens to track exchange rates for.\n */\n set tokens(tokens: Token[]) {\n this.tokenList = tokens;\n !this.disabled && safelyExecute(() => this.updateExchangeRates());\n }\n\n get tokens() {\n throw new Error('Property only used for setting');\n }\n\n /**\n * Fetches a pairs of token address and native currency.\n *\n * @param chainSlug - Chain string identifier.\n * @param vsCurrency - Query according to tokens in tokenList and native currency.\n * @returns The exchange rates for the given pairs.\n */\n async fetchExchangeRate(\n chainSlug: string,\n vsCurrency: string,\n ): Promise {\n const tokenPairs = this.tokenList.map((token) => token.address).join(',');\n const query = `contract_addresses=${tokenPairs}&vs_currencies=${vsCurrency.toLowerCase()}`;\n return handleFetch(CoinGeckoApi.getTokenPriceURL(chainSlug, query));\n }\n\n /**\n * Checks if the current native currency is a supported vs currency to use\n * to query for token exchange rates.\n *\n * @param nativeCurrency - The native currency of the currently active network.\n * @returns A boolean indicating whether it's a supported vsCurrency.\n */\n private async checkIsSupportedVsCurrency(nativeCurrency: string) {\n const { threshold } = this.config;\n const { timestamp, data } = this.supportedVsCurrencies;\n\n const now = Date.now();\n\n if (now - timestamp > threshold) {\n const currencies = await handleFetch(\n CoinGeckoApi.getSupportedVsCurrencies(),\n );\n this.supportedVsCurrencies = {\n data: currencies,\n timestamp: Date.now(),\n };\n return currencies.includes(nativeCurrency.toLowerCase());\n }\n\n return data.includes(nativeCurrency.toLowerCase());\n }\n\n /**\n * Gets current chain ID slug from cached supported platforms CoinGecko API response.\n * If cached supported platforms response is stale, fetches and updates it.\n *\n * @returns The CoinGecko slug for the current chain ID.\n */\n async getChainSlug(): Promise {\n const { threshold, chainId } = this.config;\n const { data, timestamp } = this.supportedChains;\n\n const now = Date.now();\n\n if (now - timestamp > threshold) {\n const platforms = await handleFetch(CoinGeckoApi.getPlatformsURL());\n this.supportedChains = {\n data: platforms,\n timestamp: Date.now(),\n };\n return findChainSlug(chainId, platforms);\n }\n\n return findChainSlug(chainId, data);\n }\n\n /**\n * Updates exchange rates for all tokens.\n */\n async updateExchangeRates() {\n if (this.tokenList.length === 0 || this.disabled) {\n return;\n }\n const slug = await this.getChainSlug();\n\n let newContractExchangeRates: ContractExchangeRates = {};\n if (!slug) {\n this.tokenList.forEach((token) => {\n const address = toChecksumHexAddress(token.address);\n newContractExchangeRates[address] = undefined;\n });\n } else {\n const { nativeCurrency } = this.config;\n newContractExchangeRates = await this.fetchAndMapExchangeRates(\n nativeCurrency,\n slug,\n );\n }\n this.update({ contractExchangeRates: newContractExchangeRates });\n }\n\n /**\n * Checks if the active network's native currency is supported by the coingecko API.\n * If supported, it fetches and maps contractExchange rates to a format to be consumed by the UI.\n * If not supported, it fetches contractExchange rates and maps them from token/fallback-currency\n * to token/nativeCurrency.\n *\n * @param nativeCurrency - The native currency of the currently active network.\n * @param slug - The unique slug used to id the chain by the coingecko api\n * should be used to query token exchange rates.\n * @returns An object with conversion rates for each token\n * related to the network's native currency.\n */\n async fetchAndMapExchangeRates(\n nativeCurrency: string,\n slug: string,\n ): Promise {\n const contractExchangeRates: ContractExchangeRates = {};\n\n // check if native currency is supported as a vs_currency by the API\n const nativeCurrencySupported = await this.checkIsSupportedVsCurrency(\n nativeCurrency,\n );\n\n if (nativeCurrencySupported) {\n // If it is we can do a simple fetch against the CoinGecko API\n const prices = await this.fetchExchangeRate(slug, nativeCurrency);\n this.tokenList.forEach((token) => {\n const price = prices[token.address.toLowerCase()];\n contractExchangeRates[toChecksumHexAddress(token.address)] = price\n ? price[nativeCurrency.toLowerCase()]\n : 0;\n });\n } else {\n // if native currency is not supported we need to use a fallback vsCurrency, get the exchange rates\n // in token/fallback-currency format and convert them to expected token/nativeCurrency format.\n let tokenExchangeRates;\n let vsCurrencyToNativeCurrencyConversionRate = 0;\n try {\n [\n tokenExchangeRates,\n { conversionRate: vsCurrencyToNativeCurrencyConversionRate },\n ] = await Promise.all([\n this.fetchExchangeRate(slug, FALL_BACK_VS_CURRENCY),\n fetchNativeExchangeRate(nativeCurrency, FALL_BACK_VS_CURRENCY, false),\n ]);\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes('market does not exist for this coin pair')\n ) {\n return {};\n }\n throw error;\n }\n\n for (const [tokenAddress, conversion] of Object.entries(\n tokenExchangeRates,\n )) {\n const tokenToVsCurrencyConversionRate =\n conversion[FALL_BACK_VS_CURRENCY.toLowerCase()];\n contractExchangeRates[toChecksumHexAddress(tokenAddress)] =\n tokenToVsCurrencyConversionRate *\n vsCurrencyToNativeCurrencyConversionRate;\n }\n }\n\n return contractExchangeRates;\n }\n}\n\nexport default TokenRatesController;\n"]} \ No newline at end of file diff --git a/dist/assets/TokensController.d.ts b/dist/assets/TokensController.d.ts deleted file mode 100644 index 3223cff838..0000000000 --- a/dist/assets/TokensController.d.ts +++ /dev/null @@ -1,236 +0,0 @@ -/// -import { EventEmitter } from 'events'; -import { BaseController, BaseConfig, BaseState } from '../BaseController'; -import type { PreferencesState } from '../user/PreferencesController'; -import type { NetworkState, NetworkType } from '../network/NetworkController'; -import type { Token } from './TokenRatesController'; -/** - * @type TokensConfig - * - * Tokens controller configuration - * @property networkType - Network ID as per net_version - * @property selectedAddress - Vault selected address - */ -export interface TokensConfig extends BaseConfig { - networkType: NetworkType; - selectedAddress: string; - chainId: string; - provider: any; -} -/** - * @type AssetSuggestionResult - * @property result - Promise resolving to a new suggested asset address - * @property suggestedAssetMeta - Meta information about this new suggested asset - */ -interface AssetSuggestionResult { - result: Promise; - suggestedAssetMeta: SuggestedAssetMeta; -} -declare enum SuggestedAssetStatus { - accepted = "accepted", - failed = "failed", - pending = "pending", - rejected = "rejected" -} -export declare type SuggestedAssetMetaBase = { - id: string; - time: number; - type: string; - asset: Token; -}; -/** - * @type SuggestedAssetMeta - * - * Suggested asset by EIP747 meta data - * @property error - Synthesized error information for failed asset suggestions - * @property id - Generated UUID associated with this suggested asset - * @property status - String status of this this suggested asset - * @property time - Timestamp associated with this this suggested asset - * @property type - Type type this suggested asset - * @property asset - Asset suggested object - */ -export declare type SuggestedAssetMeta = (SuggestedAssetMetaBase & { - status: SuggestedAssetStatus.failed; - error: Error; -}) | (SuggestedAssetMetaBase & { - status: SuggestedAssetStatus.accepted | SuggestedAssetStatus.rejected | SuggestedAssetStatus.pending; -}); -/** - * @type TokensState - * - * Assets controller state - * @property tokens - List of tokens associated with the active network and address pair - * @property ignoredTokens - List of ignoredTokens associated with the active network and address pair - * @property detectedTokens - List of detected tokens associated with the active network and address pair - * @property allTokens - Object containing tokens by network and account - * @property allIgnoredTokens - Object containing hidden/ignored tokens by network and account - * @property allDetectedTokens - Object containing tokens detected with non-zero balances - * @property suggestedAssets - List of pending suggested assets to be added or canceled - */ -export interface TokensState extends BaseState { - tokens: Token[]; - ignoredTokens: string[]; - detectedTokens: Token[]; - allTokens: { - [key: string]: { - [key: string]: Token[]; - }; - }; - allIgnoredTokens: { - [key: string]: { - [key: string]: string[]; - }; - }; - allDetectedTokens: { - [key: string]: { - [key: string]: Token[]; - }; - }; - suggestedAssets: SuggestedAssetMeta[]; -} -/** - * Controller that stores assets and exposes convenience methods - */ -export declare class TokensController extends BaseController { - private mutex; - private ethersProvider; - private abortController; - private failSuggestedAsset; - /** - * Fetch metadata for a token. - * - * @param tokenAddress - The address of the token. - * @returns The token metadata. - */ - private fetchTokenMetadata; - /** - * EventEmitter instance used to listen to specific EIP747 events - */ - hub: EventEmitter; - /** - * Name of this controller used during composition - */ - name: string; - /** - * Creates a TokensController instance. - * - * @param options - The controller options. - * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes. - * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. - * @param options.config - Initial options used to configure this controller. - * @param options.state - Initial state to set on this controller. - */ - constructor({ onPreferencesStateChange, onNetworkStateChange, config, state, }: { - onPreferencesStateChange: (listener: (preferencesState: PreferencesState) => void) => void; - onNetworkStateChange: (listener: (networkState: NetworkState) => void) => void; - config?: Partial; - state?: Partial; - }); - _instantiateNewEthersProvider(): any; - /** - * Adds a token to the stored token list. - * - * @param address - Hex address of the token contract. - * @param symbol - Symbol of the token. - * @param decimals - Number of decimals the token uses. - * @param image - Image of the token. - * @returns Current token list. - */ - addToken(address: string, symbol: string, decimals: number, image?: string): Promise; - /** - * Add a batch of tokens. - * - * @param tokensToImport - Array of tokens to import. - */ - addTokens(tokensToImport: Token[]): Promise; - /** - * Ignore a batch of tokens. - * - * @param tokenAddressesToIgnore - Array of token addresses to ignore. - */ - ignoreTokens(tokenAddressesToIgnore: string[]): void; - /** - * Adds a batch of detected tokens to the stored token list. - * - * @param incomingDetectedTokens - Array of detected tokens to be added or updated. - */ - addDetectedTokens(incomingDetectedTokens: Token[]): Promise; - /** - * Adds isERC721 field to token object. This is called when a user attempts to add tokens that - * were previously added which do not yet had isERC721 field. - * - * @param tokenAddress - The contract address of the token requiring the isERC721 field added. - * @returns The new token object with the added isERC721 field. - */ - updateTokenType(tokenAddress: string): Promise; - /** - * Detects whether or not a token is ERC-721 compatible. - * - * @param tokenAddress - The token contract address. - * @returns A boolean indicating whether the token address passed in supports the EIP-721 - * interface. - */ - _detectIsERC721(tokenAddress: string): Promise; - _createEthersContract(tokenAddress: string, abi: string, ethersProvider: any): Promise; - _generateRandomId(): string; - /** - * Adds a new suggestedAsset to state. Parameters will be validated according to - * asset type being watched. A `:pending` hub event will be emitted once added. - * - * @param asset - The asset to be watched. For now only ERC20 tokens are accepted. - * @param type - The asset type. - * @returns Object containing a Promise resolving to the suggestedAsset address if accepted. - */ - watchAsset(asset: Token, type: string): Promise; - /** - * Accepts to watch an asset and updates it's status and deletes the suggestedAsset from state, - * adding the asset to corresponding asset state. In this case ERC20 tokens. - * A `:finished` hub event is fired after accepted or failure. - * - * @param suggestedAssetID - The ID of the suggestedAsset to accept. - */ - acceptWatchAsset(suggestedAssetID: string): Promise; - /** - * Rejects a watchAsset request based on its ID by setting its status to "rejected" - * and emitting a `:finished` hub event. - * - * @param suggestedAssetID - The ID of the suggestedAsset to accept. - */ - rejectWatchAsset(suggestedAssetID: string): void; - /** - * Takes a new tokens and ignoredTokens array for the current network/account combination - * and returns new allTokens and allIgnoredTokens state to update to. - * - * @param params - Object that holds token params. - * @param params.newTokens - The new tokens to set for the current network and selected account. - * @param params.newIgnoredTokens - The new ignored tokens to set for the current network and selected account. - * @param params.newDetectedTokens - The new detected tokens to set for the current network and selected account. - * @returns The updated `allTokens` and `allIgnoredTokens` state. - */ - _getNewAllTokensState(params: { - newTokens?: Token[]; - newIgnoredTokens?: string[]; - newDetectedTokens?: Token[]; - }): { - newAllTokens: { - [key: string]: { - [key: string]: Token[]; - }; - }; - newAllIgnoredTokens: { - [key: string]: { - [key: string]: string[]; - }; - }; - newAllDetectedTokens: { - [key: string]: { - [key: string]: Token[]; - }; - }; - }; - /** - * Removes all tokens from the ignored list. - */ - clearIgnoredTokens(): void; -} -export default TokensController; diff --git a/dist/assets/TokensController.js b/dist/assets/TokensController.js deleted file mode 100644 index 519c020655..0000000000 --- a/dist/assets/TokensController.js +++ /dev/null @@ -1,532 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.TokensController = void 0; -const events_1 = require("events"); -const contract_metadata_1 = __importDefault(require("@metamask/contract-metadata")); -const metamask_eth_abis_1 = require("@metamask/metamask-eth-abis"); -const uuid_1 = require("uuid"); -const async_mutex_1 = require("async-mutex"); -const ethers_1 = require("ethers"); -const abort_controller_1 = require("abort-controller"); -const BaseController_1 = require("../BaseController"); -const util_1 = require("../util"); -const constants_1 = require("../constants"); -const token_service_1 = require("../apis/token-service"); -const assetsUtil_1 = require("./assetsUtil"); -var SuggestedAssetStatus; -(function (SuggestedAssetStatus) { - SuggestedAssetStatus["accepted"] = "accepted"; - SuggestedAssetStatus["failed"] = "failed"; - SuggestedAssetStatus["pending"] = "pending"; - SuggestedAssetStatus["rejected"] = "rejected"; -})(SuggestedAssetStatus || (SuggestedAssetStatus = {})); -/** - * Controller that stores assets and exposes convenience methods - */ -class TokensController extends BaseController_1.BaseController { - /** - * Creates a TokensController instance. - * - * @param options - The controller options. - * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes. - * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. - * @param options.config - Initial options used to configure this controller. - * @param options.state - Initial state to set on this controller. - */ - constructor({ onPreferencesStateChange, onNetworkStateChange, config, state, }) { - super(config, state); - this.mutex = new async_mutex_1.Mutex(); - /** - * EventEmitter instance used to listen to specific EIP747 events - */ - this.hub = new events_1.EventEmitter(); - /** - * Name of this controller used during composition - */ - this.name = 'TokensController'; - this.defaultConfig = Object.assign({ networkType: constants_1.MAINNET, selectedAddress: '', chainId: '', provider: undefined }, config); - this.defaultState = Object.assign({ tokens: [], ignoredTokens: [], detectedTokens: [], allTokens: {}, allIgnoredTokens: {}, allDetectedTokens: {}, suggestedAssets: [] }, state); - this.initialize(); - this.abortController = new abort_controller_1.AbortController(); - onPreferencesStateChange(({ selectedAddress }) => { - var _a, _b, _c; - const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state; - const { chainId } = this.config; - this.configure({ selectedAddress }); - this.update({ - tokens: ((_a = allTokens[chainId]) === null || _a === void 0 ? void 0 : _a[selectedAddress]) || [], - ignoredTokens: ((_b = allIgnoredTokens[chainId]) === null || _b === void 0 ? void 0 : _b[selectedAddress]) || [], - detectedTokens: ((_c = allDetectedTokens[chainId]) === null || _c === void 0 ? void 0 : _c[selectedAddress]) || [], - }); - }); - onNetworkStateChange(({ provider }) => { - var _a, _b, _c; - const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state; - const { selectedAddress } = this.config; - const { chainId } = provider; - this.abortController.abort(); - this.abortController = new abort_controller_1.AbortController(); - this.configure({ chainId }); - this.ethersProvider = this._instantiateNewEthersProvider(); - this.update({ - tokens: ((_a = allTokens[chainId]) === null || _a === void 0 ? void 0 : _a[selectedAddress]) || [], - ignoredTokens: ((_b = allIgnoredTokens[chainId]) === null || _b === void 0 ? void 0 : _b[selectedAddress]) || [], - detectedTokens: ((_c = allDetectedTokens[chainId]) === null || _c === void 0 ? void 0 : _c[selectedAddress]) || [], - }); - }); - } - failSuggestedAsset(suggestedAssetMeta, error) { - const failedSuggestedAssetMeta = Object.assign(Object.assign({}, suggestedAssetMeta), { status: SuggestedAssetStatus.failed, error }); - this.hub.emit(`${suggestedAssetMeta.id}:finished`, failedSuggestedAssetMeta); - } - /** - * Fetch metadata for a token. - * - * @param tokenAddress - The address of the token. - * @returns The token metadata. - */ - fetchTokenMetadata(tokenAddress) { - return __awaiter(this, void 0, void 0, function* () { - try { - const token = yield (0, token_service_1.fetchTokenMetadata)(this.config.chainId, tokenAddress, this.abortController.signal); - return token; - } - catch (error) { - if (error instanceof Error && - error.message.includes(token_service_1.TOKEN_METADATA_NO_SUPPORT_ERROR)) { - return undefined; - } - throw error; - } - }); - } - _instantiateNewEthersProvider() { - var _a; - return new ethers_1.ethers.providers.Web3Provider((_a = this.config) === null || _a === void 0 ? void 0 : _a.provider); - } - /** - * Adds a token to the stored token list. - * - * @param address - Hex address of the token contract. - * @param symbol - Symbol of the token. - * @param decimals - Number of decimals the token uses. - * @param image - Image of the token. - * @returns Current token list. - */ - addToken(address, symbol, decimals, image) { - return __awaiter(this, void 0, void 0, function* () { - const currentChainId = this.config.chainId; - const releaseLock = yield this.mutex.acquire(); - try { - address = (0, util_1.toChecksumHexAddress)(address); - const { tokens, ignoredTokens, detectedTokens } = this.state; - const newTokens = [...tokens]; - const [isERC721, tokenMetadata] = yield Promise.all([ - this._detectIsERC721(address), - this.fetchTokenMetadata(address), - ]); - if (currentChainId !== this.config.chainId) { - throw new Error('TokensController Error: Switched networks while adding token'); - } - const newEntry = { - address, - symbol, - decimals, - image: image || - (0, assetsUtil_1.formatIconUrlWithProxy)({ - chainId: this.config.chainId, - tokenAddress: address, - }), - isERC721, - aggregators: (0, assetsUtil_1.formatAggregatorNames)((tokenMetadata === null || tokenMetadata === void 0 ? void 0 : tokenMetadata.aggregators) || []), - }; - const previousEntry = newTokens.find((token) => token.address.toLowerCase() === address.toLowerCase()); - if (previousEntry) { - const previousIndex = newTokens.indexOf(previousEntry); - newTokens[previousIndex] = newEntry; - } - else { - newTokens.push(newEntry); - } - const newIgnoredTokens = ignoredTokens.filter((tokenAddress) => tokenAddress.toLowerCase() !== address.toLowerCase()); - const newDetectedTokens = detectedTokens.filter((token) => token.address.toLowerCase() !== address.toLowerCase()); - const { newAllTokens, newAllIgnoredTokens, newAllDetectedTokens } = this._getNewAllTokensState({ - newTokens, - newIgnoredTokens, - newDetectedTokens, - }); - this.update({ - tokens: newTokens, - ignoredTokens: newIgnoredTokens, - detectedTokens: newDetectedTokens, - allTokens: newAllTokens, - allIgnoredTokens: newAllIgnoredTokens, - allDetectedTokens: newAllDetectedTokens, - }); - return newTokens; - } - finally { - releaseLock(); - } - }); - } - /** - * Add a batch of tokens. - * - * @param tokensToImport - Array of tokens to import. - */ - addTokens(tokensToImport) { - return __awaiter(this, void 0, void 0, function* () { - const releaseLock = yield this.mutex.acquire(); - const { tokens, detectedTokens, ignoredTokens } = this.state; - const importedTokensMap = {}; - // Used later to dedupe imported tokens - const newTokensMap = tokens.reduce((output, current) => { - output[current.address] = current; - return output; - }, {}); - try { - tokensToImport.forEach((tokenToAdd) => { - const { address, symbol, decimals, image, aggregators } = tokenToAdd; - const checksumAddress = (0, util_1.toChecksumHexAddress)(address); - const formattedToken = { - address: checksumAddress, - symbol, - decimals, - image, - aggregators, - }; - newTokensMap[address] = formattedToken; - importedTokensMap[address.toLowerCase()] = true; - return formattedToken; - }); - const newTokens = Object.values(newTokensMap); - const newDetectedTokens = detectedTokens.filter((token) => !importedTokensMap[token.address.toLowerCase()]); - const newIgnoredTokens = ignoredTokens.filter((tokenAddress) => !newTokensMap[tokenAddress.toLowerCase()]); - const { newAllTokens, newAllDetectedTokens, newAllIgnoredTokens } = this._getNewAllTokensState({ - newTokens, - newDetectedTokens, - newIgnoredTokens, - }); - this.update({ - tokens: newTokens, - allTokens: newAllTokens, - detectedTokens: newDetectedTokens, - allDetectedTokens: newAllDetectedTokens, - ignoredTokens: newIgnoredTokens, - allIgnoredTokens: newAllIgnoredTokens, - }); - } - finally { - releaseLock(); - } - }); - } - /** - * Ignore a batch of tokens. - * - * @param tokenAddressesToIgnore - Array of token addresses to ignore. - */ - ignoreTokens(tokenAddressesToIgnore) { - const { ignoredTokens, detectedTokens, tokens } = this.state; - const ignoredTokensMap = {}; - let newIgnoredTokens = [...ignoredTokens]; - const checksummedTokenAddresses = tokenAddressesToIgnore.map((address) => { - const checksumAddress = (0, util_1.toChecksumHexAddress)(address); - ignoredTokensMap[address.toLowerCase()] = true; - return checksumAddress; - }); - newIgnoredTokens = [...ignoredTokens, ...checksummedTokenAddresses]; - const newDetectedTokens = detectedTokens.filter((token) => !ignoredTokensMap[token.address.toLowerCase()]); - const newTokens = tokens.filter((token) => !ignoredTokensMap[token.address.toLowerCase()]); - const { newAllIgnoredTokens, newAllDetectedTokens, newAllTokens } = this._getNewAllTokensState({ - newIgnoredTokens, - newDetectedTokens, - newTokens, - }); - this.update({ - ignoredTokens: newIgnoredTokens, - tokens: newTokens, - detectedTokens: newDetectedTokens, - allIgnoredTokens: newAllIgnoredTokens, - allDetectedTokens: newAllDetectedTokens, - allTokens: newAllTokens, - }); - } - /** - * Adds a batch of detected tokens to the stored token list. - * - * @param incomingDetectedTokens - Array of detected tokens to be added or updated. - */ - addDetectedTokens(incomingDetectedTokens) { - return __awaiter(this, void 0, void 0, function* () { - const releaseLock = yield this.mutex.acquire(); - const { tokens, detectedTokens, ignoredTokens } = this.state; - const newTokens = [...tokens]; - const newDetectedTokens = [...detectedTokens]; - try { - incomingDetectedTokens.forEach((tokenToAdd) => { - const { address, symbol, decimals, image, aggregators, isERC721 } = tokenToAdd; - const checksumAddress = (0, util_1.toChecksumHexAddress)(address); - const newEntry = { - address: checksumAddress, - symbol, - decimals, - image, - isERC721, - aggregators, - }; - const previousImportedEntry = newTokens.find((token) => token.address.toLowerCase() === checksumAddress.toLowerCase()); - if (previousImportedEntry) { - // Update existing data of imported token - const previousImportedIndex = newTokens.indexOf(previousImportedEntry); - newTokens[previousImportedIndex] = newEntry; - } - else { - const ignoredTokenIndex = ignoredTokens.indexOf(address); - if (ignoredTokenIndex === -1) { - // Add detected token - const previousDetectedEntry = newDetectedTokens.find((token) => token.address.toLowerCase() === checksumAddress.toLowerCase()); - if (previousDetectedEntry) { - const previousDetectedIndex = newDetectedTokens.indexOf(previousDetectedEntry); - newDetectedTokens[previousDetectedIndex] = newEntry; - } - else { - newDetectedTokens.push(newEntry); - } - } - } - }); - const { newAllTokens, newAllDetectedTokens } = this._getNewAllTokensState({ - newTokens, - newDetectedTokens, - }); - this.update({ - tokens: newTokens, - allTokens: newAllTokens, - detectedTokens: newDetectedTokens, - allDetectedTokens: newAllDetectedTokens, - }); - } - finally { - releaseLock(); - } - }); - } - /** - * Adds isERC721 field to token object. This is called when a user attempts to add tokens that - * were previously added which do not yet had isERC721 field. - * - * @param tokenAddress - The contract address of the token requiring the isERC721 field added. - * @returns The new token object with the added isERC721 field. - */ - updateTokenType(tokenAddress) { - return __awaiter(this, void 0, void 0, function* () { - const isERC721 = yield this._detectIsERC721(tokenAddress); - const { tokens } = this.state; - const tokenIndex = tokens.findIndex((token) => { - return token.address.toLowerCase() === tokenAddress.toLowerCase(); - }); - tokens[tokenIndex].isERC721 = isERC721; - this.update({ tokens }); - return tokens[tokenIndex]; - }); - } - /** - * Detects whether or not a token is ERC-721 compatible. - * - * @param tokenAddress - The token contract address. - * @returns A boolean indicating whether the token address passed in supports the EIP-721 - * interface. - */ - _detectIsERC721(tokenAddress) { - var _a, _b; - return __awaiter(this, void 0, void 0, function* () { - const checksumAddress = (0, util_1.toChecksumHexAddress)(tokenAddress); - // if this token is already in our contract metadata map we don't need - // to check against the contract - if (((_a = contract_metadata_1.default[checksumAddress]) === null || _a === void 0 ? void 0 : _a.erc721) === true) { - return Promise.resolve(true); - } - else if (((_b = contract_metadata_1.default[checksumAddress]) === null || _b === void 0 ? void 0 : _b.erc20) === true) { - return Promise.resolve(false); - } - const tokenContract = yield this._createEthersContract(tokenAddress, metamask_eth_abis_1.abiERC721, this.ethersProvider); - try { - return yield tokenContract.supportsInterface(constants_1.ERC721_INTERFACE_ID); - } - catch (error) { - // currently we see a variety of errors across different networks when - // token contracts are not ERC721 compatible. We need to figure out a better - // way of differentiating token interface types but for now if we get an error - // we have to assume the token is not ERC721 compatible. - return false; - } - }); - } - _createEthersContract(tokenAddress, abi, ethersProvider) { - return __awaiter(this, void 0, void 0, function* () { - const tokenContract = yield new ethers_1.ethers.Contract(tokenAddress, abi, ethersProvider); - return tokenContract; - }); - } - _generateRandomId() { - return (0, uuid_1.v1)(); - } - /** - * Adds a new suggestedAsset to state. Parameters will be validated according to - * asset type being watched. A `:pending` hub event will be emitted once added. - * - * @param asset - The asset to be watched. For now only ERC20 tokens are accepted. - * @param type - The asset type. - * @returns Object containing a Promise resolving to the suggestedAsset address if accepted. - */ - watchAsset(asset, type) { - return __awaiter(this, void 0, void 0, function* () { - const suggestedAssetMeta = { - asset, - id: this._generateRandomId(), - status: SuggestedAssetStatus.pending, - time: Date.now(), - type, - }; - try { - switch (type) { - case 'ERC20': - (0, util_1.validateTokenToWatch)(asset); - break; - default: - throw new Error(`Asset of type ${type} not supported`); - } - } - catch (error) { - this.failSuggestedAsset(suggestedAssetMeta, error); - return Promise.reject(error); - } - const result = new Promise((resolve, reject) => { - this.hub.once(`${suggestedAssetMeta.id}:finished`, (meta) => { - switch (meta.status) { - case SuggestedAssetStatus.accepted: - return resolve(meta.asset.address); - case SuggestedAssetStatus.rejected: - return reject(new Error('User rejected to watch the asset.')); - case SuggestedAssetStatus.failed: - return reject(new Error(meta.error.message)); - /* istanbul ignore next */ - default: - return reject(new Error(`Unknown status: ${meta.status}`)); - } - }); - }); - const { suggestedAssets } = this.state; - suggestedAssets.push(suggestedAssetMeta); - this.update({ suggestedAssets: [...suggestedAssets] }); - this.hub.emit('pendingSuggestedAsset', suggestedAssetMeta); - return { result, suggestedAssetMeta }; - }); - } - /** - * Accepts to watch an asset and updates it's status and deletes the suggestedAsset from state, - * adding the asset to corresponding asset state. In this case ERC20 tokens. - * A `:finished` hub event is fired after accepted or failure. - * - * @param suggestedAssetID - The ID of the suggestedAsset to accept. - */ - acceptWatchAsset(suggestedAssetID) { - return __awaiter(this, void 0, void 0, function* () { - const { suggestedAssets } = this.state; - const index = suggestedAssets.findIndex(({ id }) => suggestedAssetID === id); - const suggestedAssetMeta = suggestedAssets[index]; - try { - switch (suggestedAssetMeta.type) { - case 'ERC20': - const { address, symbol, decimals, image } = suggestedAssetMeta.asset; - yield this.addToken(address, symbol, decimals, image); - suggestedAssetMeta.status = SuggestedAssetStatus.accepted; - this.hub.emit(`${suggestedAssetMeta.id}:finished`, suggestedAssetMeta); - break; - default: - throw new Error(`Asset of type ${suggestedAssetMeta.type} not supported`); - } - } - catch (error) { - this.failSuggestedAsset(suggestedAssetMeta, error); - } - const newSuggestedAssets = suggestedAssets.filter(({ id }) => id !== suggestedAssetID); - this.update({ suggestedAssets: [...newSuggestedAssets] }); - }); - } - /** - * Rejects a watchAsset request based on its ID by setting its status to "rejected" - * and emitting a `:finished` hub event. - * - * @param suggestedAssetID - The ID of the suggestedAsset to accept. - */ - rejectWatchAsset(suggestedAssetID) { - const { suggestedAssets } = this.state; - const index = suggestedAssets.findIndex(({ id }) => suggestedAssetID === id); - const suggestedAssetMeta = suggestedAssets[index]; - if (!suggestedAssetMeta) { - return; - } - suggestedAssetMeta.status = SuggestedAssetStatus.rejected; - this.hub.emit(`${suggestedAssetMeta.id}:finished`, suggestedAssetMeta); - const newSuggestedAssets = suggestedAssets.filter(({ id }) => id !== suggestedAssetID); - this.update({ suggestedAssets: [...newSuggestedAssets] }); - } - /** - * Takes a new tokens and ignoredTokens array for the current network/account combination - * and returns new allTokens and allIgnoredTokens state to update to. - * - * @param params - Object that holds token params. - * @param params.newTokens - The new tokens to set for the current network and selected account. - * @param params.newIgnoredTokens - The new ignored tokens to set for the current network and selected account. - * @param params.newDetectedTokens - The new detected tokens to set for the current network and selected account. - * @returns The updated `allTokens` and `allIgnoredTokens` state. - */ - _getNewAllTokensState(params) { - const { newTokens, newIgnoredTokens, newDetectedTokens } = params; - const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state; - const { chainId, selectedAddress } = this.config; - let newAllTokens = allTokens; - if (newTokens) { - const networkTokens = allTokens[chainId]; - const newNetworkTokens = Object.assign(Object.assign({}, networkTokens), { [selectedAddress]: newTokens }); - newAllTokens = Object.assign(Object.assign({}, allTokens), { [chainId]: newNetworkTokens }); - } - let newAllIgnoredTokens = allIgnoredTokens; - if (newIgnoredTokens) { - const networkIgnoredTokens = allIgnoredTokens[chainId]; - const newIgnoredNetworkTokens = Object.assign(Object.assign({}, networkIgnoredTokens), { [selectedAddress]: newIgnoredTokens }); - newAllIgnoredTokens = Object.assign(Object.assign({}, allIgnoredTokens), { [chainId]: newIgnoredNetworkTokens }); - } - let newAllDetectedTokens = allDetectedTokens; - if (newDetectedTokens) { - const networkDetectedTokens = allDetectedTokens[chainId]; - const newDetectedNetworkTokens = Object.assign(Object.assign({}, networkDetectedTokens), { [selectedAddress]: newDetectedTokens }); - newAllDetectedTokens = Object.assign(Object.assign({}, allDetectedTokens), { [chainId]: newDetectedNetworkTokens }); - } - return { newAllTokens, newAllIgnoredTokens, newAllDetectedTokens }; - } - /** - * Removes all tokens from the ignored list. - */ - clearIgnoredTokens() { - this.update({ ignoredTokens: [], allIgnoredTokens: {} }); - } -} -exports.TokensController = TokensController; -exports.default = TokensController; -//# sourceMappingURL=TokensController.js.map \ No newline at end of file diff --git a/dist/assets/TokensController.js.map b/dist/assets/TokensController.js.map deleted file mode 100644 index b85eefa511..0000000000 --- a/dist/assets/TokensController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"TokensController.js","sourceRoot":"","sources":["../../src/assets/TokensController.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from 'events';\nimport contractsMap from '@metamask/contract-metadata';\nimport { abiERC721 } from '@metamask/metamask-eth-abis';\nimport { v1 as random } from 'uuid';\nimport { Mutex } from 'async-mutex';\nimport { ethers } from 'ethers';\nimport { AbortController } from 'abort-controller';\nimport { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport type { PreferencesState } from '../user/PreferencesController';\nimport type { NetworkState, NetworkType } from '../network/NetworkController';\nimport { validateTokenToWatch, toChecksumHexAddress } from '../util';\nimport { MAINNET, ERC721_INTERFACE_ID } from '../constants';\nimport {\n fetchTokenMetadata,\n TOKEN_METADATA_NO_SUPPORT_ERROR,\n} from '../apis/token-service';\nimport type { Token } from './TokenRatesController';\nimport { TokenListToken } from './TokenListController';\nimport { formatAggregatorNames, formatIconUrlWithProxy } from './assetsUtil';\n\n/**\n * @type TokensConfig\n *\n * Tokens controller configuration\n * @property networkType - Network ID as per net_version\n * @property selectedAddress - Vault selected address\n */\nexport interface TokensConfig extends BaseConfig {\n networkType: NetworkType;\n selectedAddress: string;\n chainId: string;\n provider: any;\n}\n\n/**\n * @type AssetSuggestionResult\n * @property result - Promise resolving to a new suggested asset address\n * @property suggestedAssetMeta - Meta information about this new suggested asset\n */\ninterface AssetSuggestionResult {\n result: Promise;\n suggestedAssetMeta: SuggestedAssetMeta;\n}\n\nenum SuggestedAssetStatus {\n accepted = 'accepted',\n failed = 'failed',\n pending = 'pending',\n rejected = 'rejected',\n}\n\nexport type SuggestedAssetMetaBase = {\n id: string;\n time: number;\n type: string;\n asset: Token;\n};\n\n/**\n * @type SuggestedAssetMeta\n *\n * Suggested asset by EIP747 meta data\n * @property error - Synthesized error information for failed asset suggestions\n * @property id - Generated UUID associated with this suggested asset\n * @property status - String status of this this suggested asset\n * @property time - Timestamp associated with this this suggested asset\n * @property type - Type type this suggested asset\n * @property asset - Asset suggested object\n */\nexport type SuggestedAssetMeta =\n | (SuggestedAssetMetaBase & {\n status: SuggestedAssetStatus.failed;\n error: Error;\n })\n | (SuggestedAssetMetaBase & {\n status:\n | SuggestedAssetStatus.accepted\n | SuggestedAssetStatus.rejected\n | SuggestedAssetStatus.pending;\n });\n\n/**\n * @type TokensState\n *\n * Assets controller state\n * @property tokens - List of tokens associated with the active network and address pair\n * @property ignoredTokens - List of ignoredTokens associated with the active network and address pair\n * @property detectedTokens - List of detected tokens associated with the active network and address pair\n * @property allTokens - Object containing tokens by network and account\n * @property allIgnoredTokens - Object containing hidden/ignored tokens by network and account\n * @property allDetectedTokens - Object containing tokens detected with non-zero balances\n * @property suggestedAssets - List of pending suggested assets to be added or canceled\n */\nexport interface TokensState extends BaseState {\n tokens: Token[];\n ignoredTokens: string[];\n detectedTokens: Token[];\n allTokens: { [key: string]: { [key: string]: Token[] } };\n allIgnoredTokens: { [key: string]: { [key: string]: string[] } };\n allDetectedTokens: { [key: string]: { [key: string]: Token[] } };\n suggestedAssets: SuggestedAssetMeta[];\n}\n\n/**\n * Controller that stores assets and exposes convenience methods\n */\nexport class TokensController extends BaseController<\n TokensConfig,\n TokensState\n> {\n private mutex = new Mutex();\n\n private ethersProvider: any;\n\n private abortController: AbortController;\n\n private failSuggestedAsset(\n suggestedAssetMeta: SuggestedAssetMeta,\n error: unknown,\n ) {\n const failedSuggestedAssetMeta = {\n ...suggestedAssetMeta,\n status: SuggestedAssetStatus.failed,\n error,\n };\n this.hub.emit(\n `${suggestedAssetMeta.id}:finished`,\n failedSuggestedAssetMeta,\n );\n }\n\n /**\n * Fetch metadata for a token.\n *\n * @param tokenAddress - The address of the token.\n * @returns The token metadata.\n */\n private async fetchTokenMetadata(\n tokenAddress: string,\n ): Promise {\n try {\n const token = await fetchTokenMetadata(\n this.config.chainId,\n tokenAddress,\n this.abortController.signal,\n );\n return token;\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes(TOKEN_METADATA_NO_SUPPORT_ERROR)\n ) {\n return undefined;\n }\n throw error;\n }\n }\n\n /**\n * EventEmitter instance used to listen to specific EIP747 events\n */\n hub = new EventEmitter();\n\n /**\n * Name of this controller used during composition\n */\n override name = 'TokensController';\n\n /**\n * Creates a TokensController instance.\n *\n * @param options - The controller options.\n * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes.\n * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.\n * @param options.config - Initial options used to configure this controller.\n * @param options.state - Initial state to set on this controller.\n */\n constructor({\n onPreferencesStateChange,\n onNetworkStateChange,\n config,\n state,\n }: {\n onPreferencesStateChange: (\n listener: (preferencesState: PreferencesState) => void,\n ) => void;\n onNetworkStateChange: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n config?: Partial;\n state?: Partial;\n }) {\n super(config, state);\n\n this.defaultConfig = {\n networkType: MAINNET,\n selectedAddress: '',\n chainId: '',\n provider: undefined,\n ...config,\n };\n\n this.defaultState = {\n tokens: [],\n ignoredTokens: [],\n detectedTokens: [],\n allTokens: {},\n allIgnoredTokens: {},\n allDetectedTokens: {},\n suggestedAssets: [],\n ...state,\n };\n\n this.initialize();\n this.abortController = new AbortController();\n\n onPreferencesStateChange(({ selectedAddress }) => {\n const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;\n const { chainId } = this.config;\n this.configure({ selectedAddress });\n this.update({\n tokens: allTokens[chainId]?.[selectedAddress] || [],\n ignoredTokens: allIgnoredTokens[chainId]?.[selectedAddress] || [],\n detectedTokens: allDetectedTokens[chainId]?.[selectedAddress] || [],\n });\n });\n\n onNetworkStateChange(({ provider }) => {\n const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;\n const { selectedAddress } = this.config;\n const { chainId } = provider;\n this.abortController.abort();\n this.abortController = new AbortController();\n this.configure({ chainId });\n this.ethersProvider = this._instantiateNewEthersProvider();\n this.update({\n tokens: allTokens[chainId]?.[selectedAddress] || [],\n ignoredTokens: allIgnoredTokens[chainId]?.[selectedAddress] || [],\n detectedTokens: allDetectedTokens[chainId]?.[selectedAddress] || [],\n });\n });\n }\n\n _instantiateNewEthersProvider(): any {\n return new ethers.providers.Web3Provider(this.config?.provider);\n }\n\n /**\n * Adds a token to the stored token list.\n *\n * @param address - Hex address of the token contract.\n * @param symbol - Symbol of the token.\n * @param decimals - Number of decimals the token uses.\n * @param image - Image of the token.\n * @returns Current token list.\n */\n async addToken(\n address: string,\n symbol: string,\n decimals: number,\n image?: string,\n ): Promise {\n const currentChainId = this.config.chainId;\n const releaseLock = await this.mutex.acquire();\n try {\n address = toChecksumHexAddress(address);\n const { tokens, ignoredTokens, detectedTokens } = this.state;\n const newTokens: Token[] = [...tokens];\n const [isERC721, tokenMetadata] = await Promise.all([\n this._detectIsERC721(address),\n this.fetchTokenMetadata(address),\n ]);\n if (currentChainId !== this.config.chainId) {\n throw new Error(\n 'TokensController Error: Switched networks while adding token',\n );\n }\n const newEntry: Token = {\n address,\n symbol,\n decimals,\n image:\n image ||\n formatIconUrlWithProxy({\n chainId: this.config.chainId,\n tokenAddress: address,\n }),\n isERC721,\n aggregators: formatAggregatorNames(tokenMetadata?.aggregators || []),\n };\n const previousEntry = newTokens.find(\n (token) => token.address.toLowerCase() === address.toLowerCase(),\n );\n if (previousEntry) {\n const previousIndex = newTokens.indexOf(previousEntry);\n newTokens[previousIndex] = newEntry;\n } else {\n newTokens.push(newEntry);\n }\n\n const newIgnoredTokens = ignoredTokens.filter(\n (tokenAddress) => tokenAddress.toLowerCase() !== address.toLowerCase(),\n );\n const newDetectedTokens = detectedTokens.filter(\n (token) => token.address.toLowerCase() !== address.toLowerCase(),\n );\n const { newAllTokens, newAllIgnoredTokens, newAllDetectedTokens } =\n this._getNewAllTokensState({\n newTokens,\n newIgnoredTokens,\n newDetectedTokens,\n });\n\n this.update({\n tokens: newTokens,\n ignoredTokens: newIgnoredTokens,\n detectedTokens: newDetectedTokens,\n allTokens: newAllTokens,\n allIgnoredTokens: newAllIgnoredTokens,\n allDetectedTokens: newAllDetectedTokens,\n });\n return newTokens;\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Add a batch of tokens.\n *\n * @param tokensToImport - Array of tokens to import.\n */\n async addTokens(tokensToImport: Token[]) {\n const releaseLock = await this.mutex.acquire();\n const { tokens, detectedTokens, ignoredTokens } = this.state;\n const importedTokensMap: { [key: string]: true } = {};\n // Used later to dedupe imported tokens\n const newTokensMap = tokens.reduce((output, current) => {\n output[current.address] = current;\n return output;\n }, {} as { [address: string]: Token });\n\n try {\n tokensToImport.forEach((tokenToAdd) => {\n const { address, symbol, decimals, image, aggregators } = tokenToAdd;\n const checksumAddress = toChecksumHexAddress(address);\n const formattedToken: Token = {\n address: checksumAddress,\n symbol,\n decimals,\n image,\n aggregators,\n };\n newTokensMap[address] = formattedToken;\n importedTokensMap[address.toLowerCase()] = true;\n return formattedToken;\n });\n const newTokens = Object.values(newTokensMap);\n\n const newDetectedTokens = detectedTokens.filter(\n (token) => !importedTokensMap[token.address.toLowerCase()],\n );\n const newIgnoredTokens = ignoredTokens.filter(\n (tokenAddress) => !newTokensMap[tokenAddress.toLowerCase()],\n );\n\n const { newAllTokens, newAllDetectedTokens, newAllIgnoredTokens } =\n this._getNewAllTokensState({\n newTokens,\n newDetectedTokens,\n newIgnoredTokens,\n });\n\n this.update({\n tokens: newTokens,\n allTokens: newAllTokens,\n detectedTokens: newDetectedTokens,\n allDetectedTokens: newAllDetectedTokens,\n ignoredTokens: newIgnoredTokens,\n allIgnoredTokens: newAllIgnoredTokens,\n });\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Ignore a batch of tokens.\n *\n * @param tokenAddressesToIgnore - Array of token addresses to ignore.\n */\n ignoreTokens(tokenAddressesToIgnore: string[]) {\n const { ignoredTokens, detectedTokens, tokens } = this.state;\n const ignoredTokensMap: { [key: string]: true } = {};\n let newIgnoredTokens: string[] = [...ignoredTokens];\n\n const checksummedTokenAddresses = tokenAddressesToIgnore.map((address) => {\n const checksumAddress = toChecksumHexAddress(address);\n ignoredTokensMap[address.toLowerCase()] = true;\n return checksumAddress;\n });\n newIgnoredTokens = [...ignoredTokens, ...checksummedTokenAddresses];\n const newDetectedTokens = detectedTokens.filter(\n (token) => !ignoredTokensMap[token.address.toLowerCase()],\n );\n const newTokens = tokens.filter(\n (token) => !ignoredTokensMap[token.address.toLowerCase()],\n );\n\n const { newAllIgnoredTokens, newAllDetectedTokens, newAllTokens } =\n this._getNewAllTokensState({\n newIgnoredTokens,\n newDetectedTokens,\n newTokens,\n });\n\n this.update({\n ignoredTokens: newIgnoredTokens,\n tokens: newTokens,\n detectedTokens: newDetectedTokens,\n allIgnoredTokens: newAllIgnoredTokens,\n allDetectedTokens: newAllDetectedTokens,\n allTokens: newAllTokens,\n });\n }\n\n /**\n * Adds a batch of detected tokens to the stored token list.\n *\n * @param incomingDetectedTokens - Array of detected tokens to be added or updated.\n */\n async addDetectedTokens(incomingDetectedTokens: Token[]) {\n const releaseLock = await this.mutex.acquire();\n const { tokens, detectedTokens, ignoredTokens } = this.state;\n const newTokens: Token[] = [...tokens];\n const newDetectedTokens: Token[] = [...detectedTokens];\n\n try {\n incomingDetectedTokens.forEach((tokenToAdd) => {\n const { address, symbol, decimals, image, aggregators, isERC721 } =\n tokenToAdd;\n const checksumAddress = toChecksumHexAddress(address);\n const newEntry: Token = {\n address: checksumAddress,\n symbol,\n decimals,\n image,\n isERC721,\n aggregators,\n };\n const previousImportedEntry = newTokens.find(\n (token) =>\n token.address.toLowerCase() === checksumAddress.toLowerCase(),\n );\n if (previousImportedEntry) {\n // Update existing data of imported token\n const previousImportedIndex = newTokens.indexOf(\n previousImportedEntry,\n );\n newTokens[previousImportedIndex] = newEntry;\n } else {\n const ignoredTokenIndex = ignoredTokens.indexOf(address);\n if (ignoredTokenIndex === -1) {\n // Add detected token\n const previousDetectedEntry = newDetectedTokens.find(\n (token) =>\n token.address.toLowerCase() === checksumAddress.toLowerCase(),\n );\n if (previousDetectedEntry) {\n const previousDetectedIndex = newDetectedTokens.indexOf(\n previousDetectedEntry,\n );\n newDetectedTokens[previousDetectedIndex] = newEntry;\n } else {\n newDetectedTokens.push(newEntry);\n }\n }\n }\n });\n\n const { newAllTokens, newAllDetectedTokens } = this._getNewAllTokensState(\n {\n newTokens,\n newDetectedTokens,\n },\n );\n\n this.update({\n tokens: newTokens,\n allTokens: newAllTokens,\n detectedTokens: newDetectedTokens,\n allDetectedTokens: newAllDetectedTokens,\n });\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Adds isERC721 field to token object. This is called when a user attempts to add tokens that\n * were previously added which do not yet had isERC721 field.\n *\n * @param tokenAddress - The contract address of the token requiring the isERC721 field added.\n * @returns The new token object with the added isERC721 field.\n */\n async updateTokenType(tokenAddress: string) {\n const isERC721 = await this._detectIsERC721(tokenAddress);\n const { tokens } = this.state;\n const tokenIndex = tokens.findIndex((token) => {\n return token.address.toLowerCase() === tokenAddress.toLowerCase();\n });\n tokens[tokenIndex].isERC721 = isERC721;\n this.update({ tokens });\n return tokens[tokenIndex];\n }\n\n /**\n * Detects whether or not a token is ERC-721 compatible.\n *\n * @param tokenAddress - The token contract address.\n * @returns A boolean indicating whether the token address passed in supports the EIP-721\n * interface.\n */\n async _detectIsERC721(tokenAddress: string) {\n const checksumAddress = toChecksumHexAddress(tokenAddress);\n // if this token is already in our contract metadata map we don't need\n // to check against the contract\n if (contractsMap[checksumAddress]?.erc721 === true) {\n return Promise.resolve(true);\n } else if (contractsMap[checksumAddress]?.erc20 === true) {\n return Promise.resolve(false);\n }\n\n const tokenContract = await this._createEthersContract(\n tokenAddress,\n abiERC721,\n this.ethersProvider,\n );\n try {\n return await tokenContract.supportsInterface(ERC721_INTERFACE_ID);\n } catch (error: any) {\n // currently we see a variety of errors across different networks when\n // token contracts are not ERC721 compatible. We need to figure out a better\n // way of differentiating token interface types but for now if we get an error\n // we have to assume the token is not ERC721 compatible.\n return false;\n }\n }\n\n async _createEthersContract(\n tokenAddress: string,\n abi: string,\n ethersProvider: any,\n ): Promise {\n const tokenContract = await new ethers.Contract(\n tokenAddress,\n abi,\n ethersProvider,\n );\n return tokenContract;\n }\n\n _generateRandomId(): string {\n return random();\n }\n\n /**\n * Adds a new suggestedAsset to state. Parameters will be validated according to\n * asset type being watched. A `:pending` hub event will be emitted once added.\n *\n * @param asset - The asset to be watched. For now only ERC20 tokens are accepted.\n * @param type - The asset type.\n * @returns Object containing a Promise resolving to the suggestedAsset address if accepted.\n */\n async watchAsset(asset: Token, type: string): Promise {\n const suggestedAssetMeta = {\n asset,\n id: this._generateRandomId(),\n status: SuggestedAssetStatus.pending as SuggestedAssetStatus.pending,\n time: Date.now(),\n type,\n };\n try {\n switch (type) {\n case 'ERC20':\n validateTokenToWatch(asset);\n break;\n default:\n throw new Error(`Asset of type ${type} not supported`);\n }\n } catch (error) {\n this.failSuggestedAsset(suggestedAssetMeta, error);\n return Promise.reject(error);\n }\n\n const result: Promise = new Promise((resolve, reject) => {\n this.hub.once(\n `${suggestedAssetMeta.id}:finished`,\n (meta: SuggestedAssetMeta) => {\n switch (meta.status) {\n case SuggestedAssetStatus.accepted:\n return resolve(meta.asset.address);\n case SuggestedAssetStatus.rejected:\n return reject(new Error('User rejected to watch the asset.'));\n case SuggestedAssetStatus.failed:\n return reject(new Error(meta.error.message));\n /* istanbul ignore next */\n default:\n return reject(new Error(`Unknown status: ${meta.status}`));\n }\n },\n );\n });\n\n const { suggestedAssets } = this.state;\n suggestedAssets.push(suggestedAssetMeta);\n this.update({ suggestedAssets: [...suggestedAssets] });\n this.hub.emit('pendingSuggestedAsset', suggestedAssetMeta);\n return { result, suggestedAssetMeta };\n }\n\n /**\n * Accepts to watch an asset and updates it's status and deletes the suggestedAsset from state,\n * adding the asset to corresponding asset state. In this case ERC20 tokens.\n * A `:finished` hub event is fired after accepted or failure.\n *\n * @param suggestedAssetID - The ID of the suggestedAsset to accept.\n */\n async acceptWatchAsset(suggestedAssetID: string): Promise {\n const { suggestedAssets } = this.state;\n const index = suggestedAssets.findIndex(\n ({ id }) => suggestedAssetID === id,\n );\n const suggestedAssetMeta = suggestedAssets[index];\n try {\n switch (suggestedAssetMeta.type) {\n case 'ERC20':\n const { address, symbol, decimals, image } = suggestedAssetMeta.asset;\n await this.addToken(address, symbol, decimals, image);\n suggestedAssetMeta.status = SuggestedAssetStatus.accepted;\n this.hub.emit(\n `${suggestedAssetMeta.id}:finished`,\n suggestedAssetMeta,\n );\n break;\n default:\n throw new Error(\n `Asset of type ${suggestedAssetMeta.type} not supported`,\n );\n }\n } catch (error) {\n this.failSuggestedAsset(suggestedAssetMeta, error);\n }\n const newSuggestedAssets = suggestedAssets.filter(\n ({ id }) => id !== suggestedAssetID,\n );\n this.update({ suggestedAssets: [...newSuggestedAssets] });\n }\n\n /**\n * Rejects a watchAsset request based on its ID by setting its status to \"rejected\"\n * and emitting a `:finished` hub event.\n *\n * @param suggestedAssetID - The ID of the suggestedAsset to accept.\n */\n rejectWatchAsset(suggestedAssetID: string) {\n const { suggestedAssets } = this.state;\n const index = suggestedAssets.findIndex(\n ({ id }) => suggestedAssetID === id,\n );\n const suggestedAssetMeta = suggestedAssets[index];\n if (!suggestedAssetMeta) {\n return;\n }\n suggestedAssetMeta.status = SuggestedAssetStatus.rejected;\n this.hub.emit(`${suggestedAssetMeta.id}:finished`, suggestedAssetMeta);\n const newSuggestedAssets = suggestedAssets.filter(\n ({ id }) => id !== suggestedAssetID,\n );\n this.update({ suggestedAssets: [...newSuggestedAssets] });\n }\n\n /**\n * Takes a new tokens and ignoredTokens array for the current network/account combination\n * and returns new allTokens and allIgnoredTokens state to update to.\n *\n * @param params - Object that holds token params.\n * @param params.newTokens - The new tokens to set for the current network and selected account.\n * @param params.newIgnoredTokens - The new ignored tokens to set for the current network and selected account.\n * @param params.newDetectedTokens - The new detected tokens to set for the current network and selected account.\n * @returns The updated `allTokens` and `allIgnoredTokens` state.\n */\n _getNewAllTokensState(params: {\n newTokens?: Token[];\n newIgnoredTokens?: string[];\n newDetectedTokens?: Token[];\n }) {\n const { newTokens, newIgnoredTokens, newDetectedTokens } = params;\n const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state;\n const { chainId, selectedAddress } = this.config;\n\n let newAllTokens = allTokens;\n if (newTokens) {\n const networkTokens = allTokens[chainId];\n const newNetworkTokens = {\n ...networkTokens,\n ...{ [selectedAddress]: newTokens },\n };\n newAllTokens = {\n ...allTokens,\n ...{ [chainId]: newNetworkTokens },\n };\n }\n\n let newAllIgnoredTokens = allIgnoredTokens;\n if (newIgnoredTokens) {\n const networkIgnoredTokens = allIgnoredTokens[chainId];\n const newIgnoredNetworkTokens = {\n ...networkIgnoredTokens,\n ...{ [selectedAddress]: newIgnoredTokens },\n };\n newAllIgnoredTokens = {\n ...allIgnoredTokens,\n ...{ [chainId]: newIgnoredNetworkTokens },\n };\n }\n\n let newAllDetectedTokens = allDetectedTokens;\n if (newDetectedTokens) {\n const networkDetectedTokens = allDetectedTokens[chainId];\n const newDetectedNetworkTokens = {\n ...networkDetectedTokens,\n ...{ [selectedAddress]: newDetectedTokens },\n };\n newAllDetectedTokens = {\n ...allDetectedTokens,\n ...{ [chainId]: newDetectedNetworkTokens },\n };\n }\n return { newAllTokens, newAllIgnoredTokens, newAllDetectedTokens };\n }\n\n /**\n * Removes all tokens from the ignored list.\n */\n clearIgnoredTokens() {\n this.update({ ignoredTokens: [], allIgnoredTokens: {} });\n }\n}\n\nexport default TokensController;\n"]} \ No newline at end of file diff --git a/dist/assets/assetsUtil.d.ts b/dist/assets/assetsUtil.d.ts deleted file mode 100644 index 011688ad1d..0000000000 --- a/dist/assets/assetsUtil.d.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Collectible, CollectibleMetadata } from './CollectiblesController'; -/** - * Compares collectible metadata entries to any collectible entry. - * We need this method when comparing a new fetched collectible metadata, in case a entry changed to a defined value, - * there's a need to update the collectible in state. - * - * @param newCollectibleMetadata - Collectible metadata object. - * @param collectible - Collectible object to compare with. - * @returns Whether there are differences. - */ -export declare function compareCollectiblesMetadata(newCollectibleMetadata: CollectibleMetadata, collectible: Collectible): boolean; -/** - * Formats aggregator names to presentable format. - * - * @param aggregators - List of token list names in camelcase. - * @returns Formatted aggregator names. - */ -export declare const formatAggregatorNames: (aggregators: string[]) => string[]; -/** - * Format token list assets to use image proxy from Codefi. - * - * @param params - Object that contains chainID and tokenAddress. - * @param params.chainId - ChainID of network in decimal or hexadecimal format. - * @param params.tokenAddress - Address of token in mixed or lowercase. - * @returns Formatted image url - */ -export declare const formatIconUrlWithProxy: ({ chainId, tokenAddress, }: { - chainId: string; - tokenAddress: string; -}) => string; diff --git a/dist/assets/assetsUtil.js b/dist/assets/assetsUtil.js deleted file mode 100644 index 9875770cac..0000000000 --- a/dist/assets/assetsUtil.js +++ /dev/null @@ -1,91 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.formatIconUrlWithProxy = exports.formatAggregatorNames = exports.compareCollectiblesMetadata = void 0; -const ethereumjs_util_1 = require("ethereumjs-util"); -const util_1 = require("../util"); -/** - * Compares collectible metadata entries to any collectible entry. - * We need this method when comparing a new fetched collectible metadata, in case a entry changed to a defined value, - * there's a need to update the collectible in state. - * - * @param newCollectibleMetadata - Collectible metadata object. - * @param collectible - Collectible object to compare with. - * @returns Whether there are differences. - */ -function compareCollectiblesMetadata(newCollectibleMetadata, collectible) { - const keys = [ - 'image', - 'backgroundColor', - 'imagePreview', - 'imageThumbnail', - 'imageOriginal', - 'animation', - 'animationOriginal', - 'externalLink', - ]; - const differentValues = keys.reduce((value, key) => { - if (newCollectibleMetadata[key] && - newCollectibleMetadata[key] !== collectible[key]) { - return value + 1; - } - return value; - }, 0); - return differentValues > 0; -} -exports.compareCollectiblesMetadata = compareCollectiblesMetadata; -const aggregatorNameByKey = { - aave: 'Aave', - bancor: 'Bancor', - cmc: 'CMC', - cryptocom: 'Crypto.com', - coinGecko: 'CoinGecko', - oneInch: '1inch', - paraswap: 'Paraswap', - pmm: 'PMM', - zapper: 'Zapper', - zerion: 'Zerion', - zeroEx: '0x', - synthetix: 'Synthetix', - yearn: 'Yearn', - apeswap: 'ApeSwap', - binanceDex: 'BinanceDex', - pancakeTop100: 'PancakeTop100', - pancakeExtended: 'PancakeExtended', - balancer: 'Balancer', - quickswap: 'QuickSwap', - matcha: 'Matcha', - pangolinDex: 'PangolinDex', - pangolinDexStableCoin: 'PangolinDexStableCoin', - pangolinDexAvaxBridge: 'PangolinDexAvaxBridge', - traderJoe: 'TraderJoe', - airswapLight: 'AirswapLight', - kleros: 'Kleros', -}; -/** - * Formats aggregator names to presentable format. - * - * @param aggregators - List of token list names in camelcase. - * @returns Formatted aggregator names. - */ -const formatAggregatorNames = (aggregators) => { - return aggregators.map((key) => aggregatorNameByKey[key] || - `${key[0].toUpperCase()}${key.substring(1, key.length)}`); -}; -exports.formatAggregatorNames = formatAggregatorNames; -/** - * Format token list assets to use image proxy from Codefi. - * - * @param params - Object that contains chainID and tokenAddress. - * @param params.chainId - ChainID of network in decimal or hexadecimal format. - * @param params.tokenAddress - Address of token in mixed or lowercase. - * @returns Formatted image url - */ -const formatIconUrlWithProxy = ({ chainId, tokenAddress, }) => { - let chainIdDec = chainId; - if ((0, ethereumjs_util_1.isHexString)(chainId)) { - chainIdDec = (0, util_1.convertHexToDecimal)(chainId).toString(); - } - return `https://static.metaswap.codefi.network/api/v1/tokenIcons/${chainIdDec}/${tokenAddress.toLowerCase()}.png`; -}; -exports.formatIconUrlWithProxy = formatIconUrlWithProxy; -//# sourceMappingURL=assetsUtil.js.map \ No newline at end of file diff --git a/dist/assets/assetsUtil.js.map b/dist/assets/assetsUtil.js.map deleted file mode 100644 index 5be8ed27b3..0000000000 --- a/dist/assets/assetsUtil.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"assetsUtil.js","sourceRoot":"","sources":["../../src/assets/assetsUtil.ts"],"names":[],"mappings":";;;AAAA,qDAA8C;AAC9C,kCAA8C;AAG9C;;;;;;;;GAQG;AACH,SAAgB,2BAA2B,CACzC,sBAA2C,EAC3C,WAAwB;IAExB,MAAM,IAAI,GAAkC;QAC1C,OAAO;QACP,iBAAiB;QACjB,cAAc;QACd,gBAAgB;QAChB,eAAe;QACf,WAAW;QACX,mBAAmB;QACnB,cAAc;KACf,CAAC;IACF,MAAM,eAAe,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACjD,IACE,sBAAsB,CAAC,GAAG,CAAC;YAC3B,sBAAsB,CAAC,GAAG,CAAC,KAAK,WAAW,CAAC,GAAG,CAAC,EAChD;YACA,OAAO,KAAK,GAAG,CAAC,CAAC;SAClB;QACD,OAAO,KAAK,CAAC;IACf,CAAC,EAAE,CAAC,CAAC,CAAC;IACN,OAAO,eAAe,GAAG,CAAC,CAAC;AAC7B,CAAC;AAxBD,kEAwBC;AAED,MAAM,mBAAmB,GAA2B;IAClD,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,QAAQ;IAChB,GAAG,EAAE,KAAK;IACV,SAAS,EAAE,YAAY;IACvB,SAAS,EAAE,WAAW;IACtB,OAAO,EAAE,OAAO;IAChB,QAAQ,EAAE,UAAU;IACpB,GAAG,EAAE,KAAK;IACV,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,IAAI;IACZ,SAAS,EAAE,WAAW;IACtB,KAAK,EAAE,OAAO;IACd,OAAO,EAAE,SAAS;IAClB,UAAU,EAAE,YAAY;IACxB,aAAa,EAAE,eAAe;IAC9B,eAAe,EAAE,iBAAiB;IAClC,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,WAAW;IACtB,MAAM,EAAE,QAAQ;IAChB,WAAW,EAAE,aAAa;IAC1B,qBAAqB,EAAE,uBAAuB;IAC9C,qBAAqB,EAAE,uBAAuB;IAC9C,SAAS,EAAE,WAAW;IACtB,YAAY,EAAE,cAAc;IAC5B,MAAM,EAAE,QAAQ;CACjB,CAAC;AAEF;;;;;GAKG;AACI,MAAM,qBAAqB,GAAG,CAAC,WAAqB,EAAE,EAAE;IAC7D,OAAO,WAAW,CAAC,GAAG,CACpB,CAAC,GAAG,EAAE,EAAE,CACN,mBAAmB,CAAC,GAAG,CAAC;QACxB,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAC3D,CAAC;AACJ,CAAC,CAAC;AANW,QAAA,qBAAqB,yBAMhC;AAEF;;;;;;;GAOG;AACI,MAAM,sBAAsB,GAAG,CAAC,EACrC,OAAO,EACP,YAAY,GAIb,EAAE,EAAE;IACH,IAAI,UAAU,GAAG,OAAO,CAAC;IACzB,IAAI,IAAA,6BAAW,EAAC,OAAO,CAAC,EAAE;QACxB,UAAU,GAAG,IAAA,0BAAmB,EAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;KACtD;IACD,OAAO,4DAA4D,UAAU,IAAI,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC;AACpH,CAAC,CAAC;AAZW,QAAA,sBAAsB,0BAYjC","sourcesContent":["import { isHexString } from 'ethereumjs-util';\nimport { convertHexToDecimal } from '../util';\nimport { Collectible, CollectibleMetadata } from './CollectiblesController';\n\n/**\n * Compares collectible metadata entries to any collectible entry.\n * We need this method when comparing a new fetched collectible metadata, in case a entry changed to a defined value,\n * there's a need to update the collectible in state.\n *\n * @param newCollectibleMetadata - Collectible metadata object.\n * @param collectible - Collectible object to compare with.\n * @returns Whether there are differences.\n */\nexport function compareCollectiblesMetadata(\n newCollectibleMetadata: CollectibleMetadata,\n collectible: Collectible,\n) {\n const keys: (keyof CollectibleMetadata)[] = [\n 'image',\n 'backgroundColor',\n 'imagePreview',\n 'imageThumbnail',\n 'imageOriginal',\n 'animation',\n 'animationOriginal',\n 'externalLink',\n ];\n const differentValues = keys.reduce((value, key) => {\n if (\n newCollectibleMetadata[key] &&\n newCollectibleMetadata[key] !== collectible[key]\n ) {\n return value + 1;\n }\n return value;\n }, 0);\n return differentValues > 0;\n}\n\nconst aggregatorNameByKey: Record = {\n aave: 'Aave',\n bancor: 'Bancor',\n cmc: 'CMC',\n cryptocom: 'Crypto.com',\n coinGecko: 'CoinGecko',\n oneInch: '1inch',\n paraswap: 'Paraswap',\n pmm: 'PMM',\n zapper: 'Zapper',\n zerion: 'Zerion',\n zeroEx: '0x',\n synthetix: 'Synthetix',\n yearn: 'Yearn',\n apeswap: 'ApeSwap',\n binanceDex: 'BinanceDex',\n pancakeTop100: 'PancakeTop100',\n pancakeExtended: 'PancakeExtended',\n balancer: 'Balancer',\n quickswap: 'QuickSwap',\n matcha: 'Matcha',\n pangolinDex: 'PangolinDex',\n pangolinDexStableCoin: 'PangolinDexStableCoin',\n pangolinDexAvaxBridge: 'PangolinDexAvaxBridge',\n traderJoe: 'TraderJoe',\n airswapLight: 'AirswapLight',\n kleros: 'Kleros',\n};\n\n/**\n * Formats aggregator names to presentable format.\n *\n * @param aggregators - List of token list names in camelcase.\n * @returns Formatted aggregator names.\n */\nexport const formatAggregatorNames = (aggregators: string[]) => {\n return aggregators.map(\n (key) =>\n aggregatorNameByKey[key] ||\n `${key[0].toUpperCase()}${key.substring(1, key.length)}`,\n );\n};\n\n/**\n * Format token list assets to use image proxy from Codefi.\n *\n * @param params - Object that contains chainID and tokenAddress.\n * @param params.chainId - ChainID of network in decimal or hexadecimal format.\n * @param params.tokenAddress - Address of token in mixed or lowercase.\n * @returns Formatted image url\n */\nexport const formatIconUrlWithProxy = ({\n chainId,\n tokenAddress,\n}: {\n chainId: string;\n tokenAddress: string;\n}) => {\n let chainIdDec = chainId;\n if (isHexString(chainId)) {\n chainIdDec = convertHexToDecimal(chainId).toString();\n }\n return `https://static.metaswap.codefi.network/api/v1/tokenIcons/${chainIdDec}/${tokenAddress.toLowerCase()}.png`;\n};\n"]} \ No newline at end of file diff --git a/dist/constants.d.ts b/dist/constants.d.ts deleted file mode 100644 index d4cb157aa8..0000000000 --- a/dist/constants.d.ts +++ /dev/null @@ -1,29 +0,0 @@ -export declare const MAINNET = "mainnet"; -export declare const RPC = "rpc"; -export declare const FALL_BACK_VS_CURRENCY = "ETH"; -export declare const IPFS_DEFAULT_GATEWAY_URL = "https://cloudflare-ipfs.com/ipfs/"; -export declare const RINKEBY_CHAIN_ID = "4"; -export declare const ERC721 = "ERC721"; -export declare const ERC1155 = "ERC1155"; -export declare const ERC20 = "ERC20"; -export declare const ERC721_INTERFACE_ID = "0x80ac58cd"; -export declare const ERC721_METADATA_INTERFACE_ID = "0x5b5e139f"; -export declare const ERC721_ENUMERABLE_INTERFACE_ID = "0x780e9d63"; -export declare const ERC1155_INTERFACE_ID = "0xd9b67a26"; -export declare const ERC1155_METADATA_URI_INTERFACE_ID = "0x0e89341c"; -export declare const ERC1155_TOKEN_RECEIVER_INTERFACE_ID = "0x4e2312e0"; -export declare const GWEI = "gwei"; -export declare const ASSET_TYPES: { - NATIVE: string; - TOKEN: string; - COLLECTIBLE: string; - UNKNOWN: string; -}; -declare type tickerType = { - [key: string]: string; -}; -export declare const TESTNET_TICKER_SYMBOLS: tickerType; -export declare const OPENSEA_PROXY_URL = "https://proxy.metaswap.codefi.network/opensea/v1/api/v1"; -export declare const OPENSEA_API_URL = "https://api.opensea.io/api/v1"; -export declare const OPENSEA_TEST_API_URL = "https://testnets-api.opensea.io/api/v1"; -export {}; diff --git a/dist/constants.js b/dist/constants.js deleted file mode 100644 index 0adf66e427..0000000000 --- a/dist/constants.js +++ /dev/null @@ -1,41 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.OPENSEA_TEST_API_URL = exports.OPENSEA_API_URL = exports.OPENSEA_PROXY_URL = exports.TESTNET_TICKER_SYMBOLS = exports.ASSET_TYPES = exports.GWEI = exports.ERC1155_TOKEN_RECEIVER_INTERFACE_ID = exports.ERC1155_METADATA_URI_INTERFACE_ID = exports.ERC1155_INTERFACE_ID = exports.ERC721_ENUMERABLE_INTERFACE_ID = exports.ERC721_METADATA_INTERFACE_ID = exports.ERC721_INTERFACE_ID = exports.ERC20 = exports.ERC1155 = exports.ERC721 = exports.RINKEBY_CHAIN_ID = exports.IPFS_DEFAULT_GATEWAY_URL = exports.FALL_BACK_VS_CURRENCY = exports.RPC = exports.MAINNET = void 0; -exports.MAINNET = 'mainnet'; -exports.RPC = 'rpc'; -exports.FALL_BACK_VS_CURRENCY = 'ETH'; -exports.IPFS_DEFAULT_GATEWAY_URL = 'https://cloudflare-ipfs.com/ipfs/'; -// NETWORKS ID -exports.RINKEBY_CHAIN_ID = '4'; -// TOKEN STANDARDS -exports.ERC721 = 'ERC721'; -exports.ERC1155 = 'ERC1155'; -exports.ERC20 = 'ERC20'; -// TOKEN INTERFACE IDS -exports.ERC721_INTERFACE_ID = '0x80ac58cd'; -exports.ERC721_METADATA_INTERFACE_ID = '0x5b5e139f'; -exports.ERC721_ENUMERABLE_INTERFACE_ID = '0x780e9d63'; -exports.ERC1155_INTERFACE_ID = '0xd9b67a26'; -exports.ERC1155_METADATA_URI_INTERFACE_ID = '0x0e89341c'; -exports.ERC1155_TOKEN_RECEIVER_INTERFACE_ID = '0x4e2312e0'; -// UNITS -exports.GWEI = 'gwei'; -// ASSET TYPES -exports.ASSET_TYPES = { - NATIVE: 'NATIVE', - TOKEN: 'TOKEN', - COLLECTIBLE: 'COLLECTIBLE', - UNKNOWN: 'UNKNOWN', -}; -// TICKER SYMBOLS -exports.TESTNET_TICKER_SYMBOLS = { - RINKEBY: 'RinkebyETH', - GOERLI: 'GoerliETH', - ROPSTEN: 'RopstenETH', - KOVAN: 'KovanETH', -}; -// APIs -exports.OPENSEA_PROXY_URL = 'https://proxy.metaswap.codefi.network/opensea/v1/api/v1'; -exports.OPENSEA_API_URL = 'https://api.opensea.io/api/v1'; -exports.OPENSEA_TEST_API_URL = 'https://testnets-api.opensea.io/api/v1'; -//# sourceMappingURL=constants.js.map \ No newline at end of file diff --git a/dist/constants.js.map b/dist/constants.js.map deleted file mode 100644 index 8e136440b7..0000000000 --- a/dist/constants.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":";;;AAAa,QAAA,OAAO,GAAG,SAAS,CAAC;AACpB,QAAA,GAAG,GAAG,KAAK,CAAC;AACZ,QAAA,qBAAqB,GAAG,KAAK,CAAC;AAC9B,QAAA,wBAAwB,GAAG,mCAAmC,CAAC;AAE5E,cAAc;AACD,QAAA,gBAAgB,GAAG,GAAG,CAAC;AAEpC,kBAAkB;AACL,QAAA,MAAM,GAAG,QAAQ,CAAC;AAClB,QAAA,OAAO,GAAG,SAAS,CAAC;AACpB,QAAA,KAAK,GAAG,OAAO,CAAC;AAE7B,sBAAsB;AACT,QAAA,mBAAmB,GAAG,YAAY,CAAC;AACnC,QAAA,4BAA4B,GAAG,YAAY,CAAC;AAC5C,QAAA,8BAA8B,GAAG,YAAY,CAAC;AAC9C,QAAA,oBAAoB,GAAG,YAAY,CAAC;AACpC,QAAA,iCAAiC,GAAG,YAAY,CAAC;AACjD,QAAA,mCAAmC,GAAG,YAAY,CAAC;AAEhE,QAAQ;AACK,QAAA,IAAI,GAAG,MAAM,CAAC;AAE3B,cAAc;AACD,QAAA,WAAW,GAAG;IACzB,MAAM,EAAE,QAAQ;IAChB,KAAK,EAAE,OAAO;IACd,WAAW,EAAE,aAAa;IAC1B,OAAO,EAAE,SAAS;CACnB,CAAC;AAMF,iBAAiB;AACJ,QAAA,sBAAsB,GAAe;IAChD,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,YAAY;IACrB,KAAK,EAAE,UAAU;CAClB,CAAC;AAEF,OAAO;AACM,QAAA,iBAAiB,GAC5B,yDAAyD,CAAC;AAC/C,QAAA,eAAe,GAAG,+BAA+B,CAAC;AAClD,QAAA,oBAAoB,GAAG,wCAAwC,CAAC","sourcesContent":["export const MAINNET = 'mainnet';\nexport const RPC = 'rpc';\nexport const FALL_BACK_VS_CURRENCY = 'ETH';\nexport const IPFS_DEFAULT_GATEWAY_URL = 'https://cloudflare-ipfs.com/ipfs/';\n\n// NETWORKS ID\nexport const RINKEBY_CHAIN_ID = '4';\n\n// TOKEN STANDARDS\nexport const ERC721 = 'ERC721';\nexport const ERC1155 = 'ERC1155';\nexport const ERC20 = 'ERC20';\n\n// TOKEN INTERFACE IDS\nexport const ERC721_INTERFACE_ID = '0x80ac58cd';\nexport const ERC721_METADATA_INTERFACE_ID = '0x5b5e139f';\nexport const ERC721_ENUMERABLE_INTERFACE_ID = '0x780e9d63';\nexport const ERC1155_INTERFACE_ID = '0xd9b67a26';\nexport const ERC1155_METADATA_URI_INTERFACE_ID = '0x0e89341c';\nexport const ERC1155_TOKEN_RECEIVER_INTERFACE_ID = '0x4e2312e0';\n\n// UNITS\nexport const GWEI = 'gwei';\n\n// ASSET TYPES\nexport const ASSET_TYPES = {\n NATIVE: 'NATIVE',\n TOKEN: 'TOKEN',\n COLLECTIBLE: 'COLLECTIBLE',\n UNKNOWN: 'UNKNOWN',\n};\n\ntype tickerType = {\n [key: string]: string;\n};\n\n// TICKER SYMBOLS\nexport const TESTNET_TICKER_SYMBOLS: tickerType = {\n RINKEBY: 'RinkebyETH',\n GOERLI: 'GoerliETH',\n ROPSTEN: 'RopstenETH',\n KOVAN: 'KovanETH',\n};\n\n// APIs\nexport const OPENSEA_PROXY_URL =\n 'https://proxy.metaswap.codefi.network/opensea/v1/api/v1';\nexport const OPENSEA_API_URL = 'https://api.opensea.io/api/v1';\nexport const OPENSEA_TEST_API_URL = 'https://testnets-api.opensea.io/api/v1';\n"]} \ No newline at end of file diff --git a/dist/gas/GasFeeController.d.ts b/dist/gas/GasFeeController.d.ts deleted file mode 100644 index 368e527849..0000000000 --- a/dist/gas/GasFeeController.d.ts +++ /dev/null @@ -1,230 +0,0 @@ -import type { Patch } from 'immer'; -import { BaseController } from '../BaseControllerV2'; -import type { RestrictedControllerMessenger } from '../ControllerMessenger'; -import type { NetworkController, NetworkState } from '../network/NetworkController'; -export declare const LEGACY_GAS_PRICES_API_URL = "https://api.metaswap.codefi.network/gasPrices"; -export declare type unknownString = 'unknown'; -export declare type FeeMarketEstimateType = 'fee-market'; -export declare type LegacyEstimateType = 'legacy'; -export declare type EthGasPriceEstimateType = 'eth_gasPrice'; -export declare type NoEstimateType = 'none'; -/** - * Indicates which type of gasEstimate the controller is currently returning. - * This is useful as a way of asserting that the shape of gasEstimates matches - * expectations. NONE is a special case indicating that no previous gasEstimate - * has been fetched. - */ -export declare const GAS_ESTIMATE_TYPES: { - FEE_MARKET: "fee-market"; - LEGACY: "legacy"; - ETH_GASPRICE: "eth_gasPrice"; - NONE: "none"; -}; -export declare type GasEstimateType = FeeMarketEstimateType | EthGasPriceEstimateType | LegacyEstimateType | NoEstimateType; -export declare type EstimatedGasFeeTimeBounds = { - lowerTimeBound: number | null; - upperTimeBound: number | unknownString; -}; -/** - * @type EthGasPriceEstimate - * - * A single gas price estimate for networks and accounts that don't support EIP-1559 - * This estimate comes from eth_gasPrice but is converted to dec gwei to match other - * return values - * @property gasPrice - A GWEI dec string - */ -export declare type EthGasPriceEstimate = { - gasPrice: string; -}; -/** - * @type LegacyGasPriceEstimate - * - * A set of gas price estimates for networks and accounts that don't support EIP-1559 - * These estimates include low, medium and high all as strings representing gwei in - * decimal format. - * @property high - gasPrice, in decimal gwei string format, suggested for fast inclusion - * @property medium - gasPrice, in decimal gwei string format, suggested for avg inclusion - * @property low - gasPrice, in decimal gwei string format, suggested for slow inclusion - */ -export declare type LegacyGasPriceEstimate = { - high: string; - medium: string; - low: string; -}; -/** - * @type Eip1559GasFee - * - * Data necessary to provide an estimate of a gas fee with a specific tip - * @property minWaitTimeEstimate - The fastest the transaction will take, in milliseconds - * @property maxWaitTimeEstimate - The slowest the transaction will take, in milliseconds - * @property suggestedMaxPriorityFeePerGas - A suggested "tip", a GWEI hex number - * @property suggestedMaxFeePerGas - A suggested max fee, the most a user will pay. a GWEI hex number - */ -export declare type Eip1559GasFee = { - minWaitTimeEstimate: number; - maxWaitTimeEstimate: number; - suggestedMaxPriorityFeePerGas: string; - suggestedMaxFeePerGas: string; -}; -/** - * @type GasFeeEstimates - * - * Data necessary to provide multiple GasFee estimates, and supporting information, to the user - * @property low - A GasFee for a minimum necessary combination of tip and maxFee - * @property medium - A GasFee for a recommended combination of tip and maxFee - * @property high - A GasFee for a high combination of tip and maxFee - * @property estimatedBaseFee - An estimate of what the base fee will be for the pending/next block. A GWEI dec number - * @property networkCongestion - A normalized number that can be used to gauge the congestion - * level of the network, with 0 meaning not congested and 1 meaning extremely congested - */ -export declare type GasFeeEstimates = SourcedGasFeeEstimates | FallbackGasFeeEstimates; -declare type SourcedGasFeeEstimates = { - low: Eip1559GasFee; - medium: Eip1559GasFee; - high: Eip1559GasFee; - estimatedBaseFee: string; - historicalBaseFeeRange: [string, string]; - baseFeeTrend: 'up' | 'down' | 'level'; - latestPriorityFeeRange: [string, string]; - historicalPriorityFeeRange: [string, string]; - priorityFeeTrend: 'up' | 'down' | 'level'; - networkCongestion: number; -}; -declare type FallbackGasFeeEstimates = { - low: Eip1559GasFee; - medium: Eip1559GasFee; - high: Eip1559GasFee; - estimatedBaseFee: string; - historicalBaseFeeRange: null; - baseFeeTrend: null; - latestPriorityFeeRange: null; - historicalPriorityFeeRange: null; - priorityFeeTrend: null; - networkCongestion: null; -}; -export declare type GasFeeStateEthGasPrice = { - gasFeeEstimates: EthGasPriceEstimate; - estimatedGasFeeTimeBounds: Record; - gasEstimateType: EthGasPriceEstimateType; -}; -export declare type GasFeeStateFeeMarket = { - gasFeeEstimates: GasFeeEstimates; - estimatedGasFeeTimeBounds: EstimatedGasFeeTimeBounds | Record; - gasEstimateType: FeeMarketEstimateType; -}; -export declare type GasFeeStateLegacy = { - gasFeeEstimates: LegacyGasPriceEstimate; - estimatedGasFeeTimeBounds: Record; - gasEstimateType: LegacyEstimateType; -}; -export declare type GasFeeStateNoEstimates = { - gasFeeEstimates: Record; - estimatedGasFeeTimeBounds: Record; - gasEstimateType: NoEstimateType; -}; -export declare type FetchGasFeeEstimateOptions = { - shouldUpdateState?: boolean; -}; -/** - * @type GasFeeState - * - * Gas Fee controller state - * @property gasFeeEstimates - Gas fee estimate data based on new EIP-1559 properties - * @property estimatedGasFeeTimeBounds - Estimates representing the minimum and maximum - */ -export declare type GasFeeState = GasFeeStateEthGasPrice | GasFeeStateFeeMarket | GasFeeStateLegacy | GasFeeStateNoEstimates; -declare const name = "GasFeeController"; -export declare type GasFeeStateChange = { - type: `${typeof name}:stateChange`; - payload: [GasFeeState, Patch[]]; -}; -export declare type GetGasFeeState = { - type: `${typeof name}:getState`; - handler: () => GasFeeState; -}; -declare type GasFeeMessenger = RestrictedControllerMessenger; -/** - * Controller that retrieves gas fee estimate data and polls for updated data on a set interval - */ -export declare class GasFeeController extends BaseController { - private intervalId?; - private intervalDelay; - private pollTokens; - private legacyAPIEndpoint; - private EIP1559APIEndpoint; - private getCurrentNetworkEIP1559Compatibility; - private getCurrentNetworkLegacyGasAPICompatibility; - private getCurrentAccountEIP1559Compatibility; - private getChainId; - private currentChainId; - private ethQuery; - private clientId?; - /** - * Creates a GasFeeController instance. - * - * @param options - The controller options. - * @param options.interval - The time in milliseconds to wait between polls. - * @param options.messenger - The controller messenger. - * @param options.state - The initial state. - * @param options.getCurrentNetworkEIP1559Compatibility - Determines whether or not the current - * network is EIP-1559 compatible. - * @param options.getCurrentNetworkLegacyGasAPICompatibility - Determines whether or not the - * current network is compatible with the legacy gas price API. - * @param options.getCurrentAccountEIP1559Compatibility - Determines whether or not the current - * account is EIP-1559 compatible. - * @param options.getChainId - Returns the current chain ID. - * @param options.getProvider - Returns a network provider for the current network. - * @param options.onNetworkStateChange - A function for registering an event handler for the - * network state change event. - * @param options.legacyAPIEndpoint - The legacy gas price API URL. This option is primarily for - * testing purposes. - * @param options.EIP1559APIEndpoint - The EIP-1559 gas price API URL. This option is primarily - * for testing purposes. - * @param options.clientId - The client ID used to identify to the gas estimation API who is - * asking for estimates. - */ - constructor({ interval, messenger, state, getCurrentNetworkEIP1559Compatibility, getCurrentAccountEIP1559Compatibility, getChainId, getCurrentNetworkLegacyGasAPICompatibility, getProvider, onNetworkStateChange, legacyAPIEndpoint, EIP1559APIEndpoint, clientId, }: { - interval?: number; - messenger: GasFeeMessenger; - state?: GasFeeState; - getCurrentNetworkEIP1559Compatibility: () => Promise; - getCurrentNetworkLegacyGasAPICompatibility: () => boolean; - getCurrentAccountEIP1559Compatibility?: () => boolean; - getChainId: () => `0x${string}` | `${number}` | number; - getProvider: () => NetworkController['provider']; - onNetworkStateChange: (listener: (state: NetworkState) => void) => void; - legacyAPIEndpoint?: string; - EIP1559APIEndpoint?: string; - clientId?: string; - }); - resetPolling(): Promise; - fetchGasFeeEstimates(options?: FetchGasFeeEstimateOptions): Promise; - getGasFeeEstimatesAndStartPolling(pollToken: string | undefined): Promise; - /** - * Gets and sets gasFeeEstimates in state. - * - * @param options - The gas fee estimate options. - * @param options.shouldUpdateState - Determines whether the state should be updated with the - * updated gas estimates. - * @returns The gas fee estimates. - */ - _fetchGasFeeEstimateData(options?: FetchGasFeeEstimateOptions): Promise; - /** - * Remove the poll token, and stop polling if the set of poll tokens is empty. - * - * @param pollToken - The poll token to disconnect. - */ - disconnectPoller(pollToken: string): void; - stopPolling(): void; - /** - * Prepare to discard this controller. - * - * This stops any active polling. - */ - destroy(): void; - private _poll; - private resetState; - private getEIP1559Compatibility; - getTimeEstimate(maxPriorityFeePerGas: string, maxFeePerGas: string): EstimatedGasFeeTimeBounds | Record; -} -export default GasFeeController; diff --git a/dist/gas/GasFeeController.js b/dist/gas/GasFeeController.js deleted file mode 100644 index 9666218ba0..0000000000 --- a/dist/gas/GasFeeController.js +++ /dev/null @@ -1,243 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.GasFeeController = exports.GAS_ESTIMATE_TYPES = exports.LEGACY_GAS_PRICES_API_URL = void 0; -const eth_query_1 = __importDefault(require("eth-query")); -const uuid_1 = require("uuid"); -const ethereumjs_util_1 = require("ethereumjs-util"); -const BaseControllerV2_1 = require("../BaseControllerV2"); -const util_1 = require("../util"); -const gas_util_1 = require("./gas-util"); -const determineGasFeeCalculations_1 = __importDefault(require("./determineGasFeeCalculations")); -const fetchGasEstimatesViaEthFeeHistory_1 = __importDefault(require("./fetchGasEstimatesViaEthFeeHistory")); -const GAS_FEE_API = 'https://mock-gas-server.herokuapp.com/'; -exports.LEGACY_GAS_PRICES_API_URL = `https://api.metaswap.codefi.network/gasPrices`; -/** - * Indicates which type of gasEstimate the controller is currently returning. - * This is useful as a way of asserting that the shape of gasEstimates matches - * expectations. NONE is a special case indicating that no previous gasEstimate - * has been fetched. - */ -exports.GAS_ESTIMATE_TYPES = { - FEE_MARKET: 'fee-market', - LEGACY: 'legacy', - ETH_GASPRICE: 'eth_gasPrice', - NONE: 'none', -}; -const metadata = { - gasFeeEstimates: { persist: true, anonymous: false }, - estimatedGasFeeTimeBounds: { persist: true, anonymous: false }, - gasEstimateType: { persist: true, anonymous: false }, -}; -const name = 'GasFeeController'; -const defaultState = { - gasFeeEstimates: {}, - estimatedGasFeeTimeBounds: {}, - gasEstimateType: exports.GAS_ESTIMATE_TYPES.NONE, -}; -/** - * Controller that retrieves gas fee estimate data and polls for updated data on a set interval - */ -class GasFeeController extends BaseControllerV2_1.BaseController { - /** - * Creates a GasFeeController instance. - * - * @param options - The controller options. - * @param options.interval - The time in milliseconds to wait between polls. - * @param options.messenger - The controller messenger. - * @param options.state - The initial state. - * @param options.getCurrentNetworkEIP1559Compatibility - Determines whether or not the current - * network is EIP-1559 compatible. - * @param options.getCurrentNetworkLegacyGasAPICompatibility - Determines whether or not the - * current network is compatible with the legacy gas price API. - * @param options.getCurrentAccountEIP1559Compatibility - Determines whether or not the current - * account is EIP-1559 compatible. - * @param options.getChainId - Returns the current chain ID. - * @param options.getProvider - Returns a network provider for the current network. - * @param options.onNetworkStateChange - A function for registering an event handler for the - * network state change event. - * @param options.legacyAPIEndpoint - The legacy gas price API URL. This option is primarily for - * testing purposes. - * @param options.EIP1559APIEndpoint - The EIP-1559 gas price API URL. This option is primarily - * for testing purposes. - * @param options.clientId - The client ID used to identify to the gas estimation API who is - * asking for estimates. - */ - constructor({ interval = 15000, messenger, state, getCurrentNetworkEIP1559Compatibility, getCurrentAccountEIP1559Compatibility, getChainId, getCurrentNetworkLegacyGasAPICompatibility, getProvider, onNetworkStateChange, legacyAPIEndpoint = exports.LEGACY_GAS_PRICES_API_URL, EIP1559APIEndpoint = GAS_FEE_API, clientId, }) { - super({ - name, - metadata, - messenger, - state: Object.assign(Object.assign({}, defaultState), state), - }); - this.intervalDelay = interval; - this.pollTokens = new Set(); - this.getCurrentNetworkEIP1559Compatibility = - getCurrentNetworkEIP1559Compatibility; - this.getCurrentNetworkLegacyGasAPICompatibility = - getCurrentNetworkLegacyGasAPICompatibility; - this.getCurrentAccountEIP1559Compatibility = - getCurrentAccountEIP1559Compatibility; - this.EIP1559APIEndpoint = EIP1559APIEndpoint; - this.legacyAPIEndpoint = legacyAPIEndpoint; - this.getChainId = getChainId; - this.currentChainId = this.getChainId(); - const provider = getProvider(); - this.ethQuery = new eth_query_1.default(provider); - this.clientId = clientId; - onNetworkStateChange(() => __awaiter(this, void 0, void 0, function* () { - const newProvider = getProvider(); - const newChainId = this.getChainId(); - this.ethQuery = new eth_query_1.default(newProvider); - if (this.currentChainId !== newChainId) { - this.currentChainId = newChainId; - yield this.resetPolling(); - } - })); - } - resetPolling() { - return __awaiter(this, void 0, void 0, function* () { - if (this.pollTokens.size !== 0) { - const tokens = Array.from(this.pollTokens); - this.stopPolling(); - yield this.getGasFeeEstimatesAndStartPolling(tokens[0]); - tokens.slice(1).forEach((token) => { - this.pollTokens.add(token); - }); - } - }); - } - fetchGasFeeEstimates(options) { - return __awaiter(this, void 0, void 0, function* () { - return yield this._fetchGasFeeEstimateData(options); - }); - } - getGasFeeEstimatesAndStartPolling(pollToken) { - return __awaiter(this, void 0, void 0, function* () { - const _pollToken = pollToken || (0, uuid_1.v1)(); - this.pollTokens.add(_pollToken); - if (this.pollTokens.size === 1) { - yield this._fetchGasFeeEstimateData(); - this._poll(); - } - return _pollToken; - }); - } - /** - * Gets and sets gasFeeEstimates in state. - * - * @param options - The gas fee estimate options. - * @param options.shouldUpdateState - Determines whether the state should be updated with the - * updated gas estimates. - * @returns The gas fee estimates. - */ - _fetchGasFeeEstimateData(options = {}) { - return __awaiter(this, void 0, void 0, function* () { - const { shouldUpdateState = true } = options; - let isEIP1559Compatible; - const isLegacyGasAPICompatible = this.getCurrentNetworkLegacyGasAPICompatibility(); - let chainId = this.getChainId(); - if (typeof chainId === 'string' && (0, ethereumjs_util_1.isHexString)(chainId)) { - chainId = parseInt(chainId, 16); - } - try { - isEIP1559Compatible = yield this.getEIP1559Compatibility(); - } - catch (e) { - console.error(e); - isEIP1559Compatible = false; - } - const gasFeeCalculations = yield (0, determineGasFeeCalculations_1.default)({ - isEIP1559Compatible, - isLegacyGasAPICompatible, - fetchGasEstimates: gas_util_1.fetchGasEstimates, - fetchGasEstimatesUrl: this.EIP1559APIEndpoint.replace('', `${chainId}`), - fetchGasEstimatesViaEthFeeHistory: fetchGasEstimatesViaEthFeeHistory_1.default, - fetchLegacyGasPriceEstimates: gas_util_1.fetchLegacyGasPriceEstimates, - fetchLegacyGasPriceEstimatesUrl: this.legacyAPIEndpoint.replace('', `${chainId}`), - fetchEthGasPriceEstimate: gas_util_1.fetchEthGasPriceEstimate, - calculateTimeEstimate: gas_util_1.calculateTimeEstimate, - clientId: this.clientId, - ethQuery: this.ethQuery, - }); - if (shouldUpdateState) { - this.update((state) => { - state.gasFeeEstimates = gasFeeCalculations.gasFeeEstimates; - state.estimatedGasFeeTimeBounds = - gasFeeCalculations.estimatedGasFeeTimeBounds; - state.gasEstimateType = gasFeeCalculations.gasEstimateType; - }); - } - return gasFeeCalculations; - }); - } - /** - * Remove the poll token, and stop polling if the set of poll tokens is empty. - * - * @param pollToken - The poll token to disconnect. - */ - disconnectPoller(pollToken) { - this.pollTokens.delete(pollToken); - if (this.pollTokens.size === 0) { - this.stopPolling(); - } - } - stopPolling() { - if (this.intervalId) { - clearInterval(this.intervalId); - } - this.pollTokens.clear(); - this.resetState(); - } - /** - * Prepare to discard this controller. - * - * This stops any active polling. - */ - destroy() { - super.destroy(); - this.stopPolling(); - } - _poll() { - if (this.intervalId) { - clearInterval(this.intervalId); - } - this.intervalId = setInterval(() => __awaiter(this, void 0, void 0, function* () { - yield (0, util_1.safelyExecute)(() => this._fetchGasFeeEstimateData()); - }), this.intervalDelay); - } - resetState() { - this.update(() => { - return defaultState; - }); - } - getEIP1559Compatibility() { - var _a, _b; - return __awaiter(this, void 0, void 0, function* () { - const currentNetworkIsEIP1559Compatible = yield this.getCurrentNetworkEIP1559Compatibility(); - const currentAccountIsEIP1559Compatible = (_b = (_a = this.getCurrentAccountEIP1559Compatibility) === null || _a === void 0 ? void 0 : _a.call(this)) !== null && _b !== void 0 ? _b : true; - return (currentNetworkIsEIP1559Compatible && currentAccountIsEIP1559Compatible); - }); - } - getTimeEstimate(maxPriorityFeePerGas, maxFeePerGas) { - if (!this.state.gasFeeEstimates || - this.state.gasEstimateType !== exports.GAS_ESTIMATE_TYPES.FEE_MARKET) { - return {}; - } - return (0, gas_util_1.calculateTimeEstimate)(maxPriorityFeePerGas, maxFeePerGas, this.state.gasFeeEstimates); - } -} -exports.GasFeeController = GasFeeController; -exports.default = GasFeeController; -//# sourceMappingURL=GasFeeController.js.map \ No newline at end of file diff --git a/dist/gas/GasFeeController.js.map b/dist/gas/GasFeeController.js.map deleted file mode 100644 index c98e0342b9..0000000000 --- a/dist/gas/GasFeeController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"GasFeeController.js","sourceRoot":"","sources":["../../src/gas/GasFeeController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Patch } from 'immer';\n\nimport EthQuery from 'eth-query';\nimport { v1 as random } from 'uuid';\nimport { isHexString } from 'ethereumjs-util';\nimport { BaseController } from '../BaseControllerV2';\nimport { safelyExecute } from '../util';\nimport type { RestrictedControllerMessenger } from '../ControllerMessenger';\nimport type {\n NetworkController,\n NetworkState,\n} from '../network/NetworkController';\nimport {\n fetchGasEstimates,\n fetchLegacyGasPriceEstimates,\n fetchEthGasPriceEstimate,\n calculateTimeEstimate,\n} from './gas-util';\nimport determineGasFeeCalculations from './determineGasFeeCalculations';\nimport fetchGasEstimatesViaEthFeeHistory from './fetchGasEstimatesViaEthFeeHistory';\n\nconst GAS_FEE_API = 'https://mock-gas-server.herokuapp.com/';\nexport const LEGACY_GAS_PRICES_API_URL = `https://api.metaswap.codefi.network/gasPrices`;\n\nexport type unknownString = 'unknown';\n\n// Fee Market describes the way gas is set after the london hardfork, and was\n// defined by EIP-1559.\nexport type FeeMarketEstimateType = 'fee-market';\n// Legacy describes gasPrice estimates from before london hardfork, when the\n// user is connected to mainnet and are presented with fast/average/slow\n// estimate levels to choose from.\nexport type LegacyEstimateType = 'legacy';\n// EthGasPrice describes a gasPrice estimate received from eth_gasPrice. Post\n// london this value should only be used for legacy type transactions when on\n// networks that support EIP-1559. This type of estimate is the most accurate\n// to display on custom networks that don't support EIP-1559.\nexport type EthGasPriceEstimateType = 'eth_gasPrice';\n// NoEstimate describes the state of the controller before receiving its first\n// estimate.\nexport type NoEstimateType = 'none';\n\n/**\n * Indicates which type of gasEstimate the controller is currently returning.\n * This is useful as a way of asserting that the shape of gasEstimates matches\n * expectations. NONE is a special case indicating that no previous gasEstimate\n * has been fetched.\n */\nexport const GAS_ESTIMATE_TYPES = {\n FEE_MARKET: 'fee-market' as FeeMarketEstimateType,\n LEGACY: 'legacy' as LegacyEstimateType,\n ETH_GASPRICE: 'eth_gasPrice' as EthGasPriceEstimateType,\n NONE: 'none' as NoEstimateType,\n};\n\nexport type GasEstimateType =\n | FeeMarketEstimateType\n | EthGasPriceEstimateType\n | LegacyEstimateType\n | NoEstimateType;\n\nexport type EstimatedGasFeeTimeBounds = {\n lowerTimeBound: number | null;\n upperTimeBound: number | unknownString;\n};\n\n/**\n * @type EthGasPriceEstimate\n *\n * A single gas price estimate for networks and accounts that don't support EIP-1559\n * This estimate comes from eth_gasPrice but is converted to dec gwei to match other\n * return values\n * @property gasPrice - A GWEI dec string\n */\n\nexport type EthGasPriceEstimate = {\n gasPrice: string;\n};\n\n/**\n * @type LegacyGasPriceEstimate\n *\n * A set of gas price estimates for networks and accounts that don't support EIP-1559\n * These estimates include low, medium and high all as strings representing gwei in\n * decimal format.\n * @property high - gasPrice, in decimal gwei string format, suggested for fast inclusion\n * @property medium - gasPrice, in decimal gwei string format, suggested for avg inclusion\n * @property low - gasPrice, in decimal gwei string format, suggested for slow inclusion\n */\nexport type LegacyGasPriceEstimate = {\n high: string;\n medium: string;\n low: string;\n};\n\n/**\n * @type Eip1559GasFee\n *\n * Data necessary to provide an estimate of a gas fee with a specific tip\n * @property minWaitTimeEstimate - The fastest the transaction will take, in milliseconds\n * @property maxWaitTimeEstimate - The slowest the transaction will take, in milliseconds\n * @property suggestedMaxPriorityFeePerGas - A suggested \"tip\", a GWEI hex number\n * @property suggestedMaxFeePerGas - A suggested max fee, the most a user will pay. a GWEI hex number\n */\n\nexport type Eip1559GasFee = {\n minWaitTimeEstimate: number; // a time duration in milliseconds\n maxWaitTimeEstimate: number; // a time duration in milliseconds\n suggestedMaxPriorityFeePerGas: string; // a GWEI decimal number\n suggestedMaxFeePerGas: string; // a GWEI decimal number\n};\n\n/**\n * @type GasFeeEstimates\n *\n * Data necessary to provide multiple GasFee estimates, and supporting information, to the user\n * @property low - A GasFee for a minimum necessary combination of tip and maxFee\n * @property medium - A GasFee for a recommended combination of tip and maxFee\n * @property high - A GasFee for a high combination of tip and maxFee\n * @property estimatedBaseFee - An estimate of what the base fee will be for the pending/next block. A GWEI dec number\n * @property networkCongestion - A normalized number that can be used to gauge the congestion\n * level of the network, with 0 meaning not congested and 1 meaning extremely congested\n */\n\nexport type GasFeeEstimates = SourcedGasFeeEstimates | FallbackGasFeeEstimates;\n\ntype SourcedGasFeeEstimates = {\n low: Eip1559GasFee;\n medium: Eip1559GasFee;\n high: Eip1559GasFee;\n estimatedBaseFee: string;\n historicalBaseFeeRange: [string, string];\n baseFeeTrend: 'up' | 'down' | 'level';\n latestPriorityFeeRange: [string, string];\n historicalPriorityFeeRange: [string, string];\n priorityFeeTrend: 'up' | 'down' | 'level';\n networkCongestion: number;\n};\n\ntype FallbackGasFeeEstimates = {\n low: Eip1559GasFee;\n medium: Eip1559GasFee;\n high: Eip1559GasFee;\n estimatedBaseFee: string;\n historicalBaseFeeRange: null;\n baseFeeTrend: null;\n latestPriorityFeeRange: null;\n historicalPriorityFeeRange: null;\n priorityFeeTrend: null;\n networkCongestion: null;\n};\n\nconst metadata = {\n gasFeeEstimates: { persist: true, anonymous: false },\n estimatedGasFeeTimeBounds: { persist: true, anonymous: false },\n gasEstimateType: { persist: true, anonymous: false },\n};\n\nexport type GasFeeStateEthGasPrice = {\n gasFeeEstimates: EthGasPriceEstimate;\n estimatedGasFeeTimeBounds: Record;\n gasEstimateType: EthGasPriceEstimateType;\n};\n\nexport type GasFeeStateFeeMarket = {\n gasFeeEstimates: GasFeeEstimates;\n estimatedGasFeeTimeBounds: EstimatedGasFeeTimeBounds | Record;\n gasEstimateType: FeeMarketEstimateType;\n};\n\nexport type GasFeeStateLegacy = {\n gasFeeEstimates: LegacyGasPriceEstimate;\n estimatedGasFeeTimeBounds: Record;\n gasEstimateType: LegacyEstimateType;\n};\n\nexport type GasFeeStateNoEstimates = {\n gasFeeEstimates: Record;\n estimatedGasFeeTimeBounds: Record;\n gasEstimateType: NoEstimateType;\n};\n\nexport type FetchGasFeeEstimateOptions = {\n shouldUpdateState?: boolean;\n};\n\n/**\n * @type GasFeeState\n *\n * Gas Fee controller state\n * @property gasFeeEstimates - Gas fee estimate data based on new EIP-1559 properties\n * @property estimatedGasFeeTimeBounds - Estimates representing the minimum and maximum\n */\nexport type GasFeeState =\n | GasFeeStateEthGasPrice\n | GasFeeStateFeeMarket\n | GasFeeStateLegacy\n | GasFeeStateNoEstimates;\n\nconst name = 'GasFeeController';\n\nexport type GasFeeStateChange = {\n type: `${typeof name}:stateChange`;\n payload: [GasFeeState, Patch[]];\n};\n\nexport type GetGasFeeState = {\n type: `${typeof name}:getState`;\n handler: () => GasFeeState;\n};\n\ntype GasFeeMessenger = RestrictedControllerMessenger<\n typeof name,\n GetGasFeeState,\n GasFeeStateChange,\n never,\n never\n>;\n\nconst defaultState: GasFeeState = {\n gasFeeEstimates: {},\n estimatedGasFeeTimeBounds: {},\n gasEstimateType: GAS_ESTIMATE_TYPES.NONE,\n};\n\n/**\n * Controller that retrieves gas fee estimate data and polls for updated data on a set interval\n */\nexport class GasFeeController extends BaseController<\n typeof name,\n GasFeeState,\n GasFeeMessenger\n> {\n private intervalId?: NodeJS.Timeout;\n\n private intervalDelay;\n\n private pollTokens: Set;\n\n private legacyAPIEndpoint: string;\n\n private EIP1559APIEndpoint: string;\n\n private getCurrentNetworkEIP1559Compatibility;\n\n private getCurrentNetworkLegacyGasAPICompatibility;\n\n private getCurrentAccountEIP1559Compatibility;\n\n private getChainId;\n\n private currentChainId;\n\n private ethQuery: any;\n\n private clientId?: string;\n\n /**\n * Creates a GasFeeController instance.\n *\n * @param options - The controller options.\n * @param options.interval - The time in milliseconds to wait between polls.\n * @param options.messenger - The controller messenger.\n * @param options.state - The initial state.\n * @param options.getCurrentNetworkEIP1559Compatibility - Determines whether or not the current\n * network is EIP-1559 compatible.\n * @param options.getCurrentNetworkLegacyGasAPICompatibility - Determines whether or not the\n * current network is compatible with the legacy gas price API.\n * @param options.getCurrentAccountEIP1559Compatibility - Determines whether or not the current\n * account is EIP-1559 compatible.\n * @param options.getChainId - Returns the current chain ID.\n * @param options.getProvider - Returns a network provider for the current network.\n * @param options.onNetworkStateChange - A function for registering an event handler for the\n * network state change event.\n * @param options.legacyAPIEndpoint - The legacy gas price API URL. This option is primarily for\n * testing purposes.\n * @param options.EIP1559APIEndpoint - The EIP-1559 gas price API URL. This option is primarily\n * for testing purposes.\n * @param options.clientId - The client ID used to identify to the gas estimation API who is\n * asking for estimates.\n */\n constructor({\n interval = 15000,\n messenger,\n state,\n getCurrentNetworkEIP1559Compatibility,\n getCurrentAccountEIP1559Compatibility,\n getChainId,\n getCurrentNetworkLegacyGasAPICompatibility,\n getProvider,\n onNetworkStateChange,\n legacyAPIEndpoint = LEGACY_GAS_PRICES_API_URL,\n EIP1559APIEndpoint = GAS_FEE_API,\n clientId,\n }: {\n interval?: number;\n messenger: GasFeeMessenger;\n state?: GasFeeState;\n getCurrentNetworkEIP1559Compatibility: () => Promise;\n getCurrentNetworkLegacyGasAPICompatibility: () => boolean;\n getCurrentAccountEIP1559Compatibility?: () => boolean;\n getChainId: () => `0x${string}` | `${number}` | number;\n getProvider: () => NetworkController['provider'];\n onNetworkStateChange: (listener: (state: NetworkState) => void) => void;\n legacyAPIEndpoint?: string;\n EIP1559APIEndpoint?: string;\n clientId?: string;\n }) {\n super({\n name,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n this.intervalDelay = interval;\n this.pollTokens = new Set();\n this.getCurrentNetworkEIP1559Compatibility =\n getCurrentNetworkEIP1559Compatibility;\n\n this.getCurrentNetworkLegacyGasAPICompatibility =\n getCurrentNetworkLegacyGasAPICompatibility;\n\n this.getCurrentAccountEIP1559Compatibility =\n getCurrentAccountEIP1559Compatibility;\n this.EIP1559APIEndpoint = EIP1559APIEndpoint;\n this.legacyAPIEndpoint = legacyAPIEndpoint;\n this.getChainId = getChainId;\n this.currentChainId = this.getChainId();\n const provider = getProvider();\n this.ethQuery = new EthQuery(provider);\n this.clientId = clientId;\n onNetworkStateChange(async () => {\n const newProvider = getProvider();\n const newChainId = this.getChainId();\n this.ethQuery = new EthQuery(newProvider);\n if (this.currentChainId !== newChainId) {\n this.currentChainId = newChainId;\n await this.resetPolling();\n }\n });\n }\n\n async resetPolling() {\n if (this.pollTokens.size !== 0) {\n const tokens = Array.from(this.pollTokens);\n this.stopPolling();\n await this.getGasFeeEstimatesAndStartPolling(tokens[0]);\n tokens.slice(1).forEach((token) => {\n this.pollTokens.add(token);\n });\n }\n }\n\n async fetchGasFeeEstimates(options?: FetchGasFeeEstimateOptions) {\n return await this._fetchGasFeeEstimateData(options);\n }\n\n async getGasFeeEstimatesAndStartPolling(\n pollToken: string | undefined,\n ): Promise {\n const _pollToken = pollToken || random();\n\n this.pollTokens.add(_pollToken);\n\n if (this.pollTokens.size === 1) {\n await this._fetchGasFeeEstimateData();\n this._poll();\n }\n\n return _pollToken;\n }\n\n /**\n * Gets and sets gasFeeEstimates in state.\n *\n * @param options - The gas fee estimate options.\n * @param options.shouldUpdateState - Determines whether the state should be updated with the\n * updated gas estimates.\n * @returns The gas fee estimates.\n */\n async _fetchGasFeeEstimateData(\n options: FetchGasFeeEstimateOptions = {},\n ): Promise {\n const { shouldUpdateState = true } = options;\n let isEIP1559Compatible;\n const isLegacyGasAPICompatible =\n this.getCurrentNetworkLegacyGasAPICompatibility();\n\n let chainId = this.getChainId();\n if (typeof chainId === 'string' && isHexString(chainId)) {\n chainId = parseInt(chainId, 16);\n }\n\n try {\n isEIP1559Compatible = await this.getEIP1559Compatibility();\n } catch (e) {\n console.error(e);\n isEIP1559Compatible = false;\n }\n\n const gasFeeCalculations = await determineGasFeeCalculations({\n isEIP1559Compatible,\n isLegacyGasAPICompatible,\n fetchGasEstimates,\n fetchGasEstimatesUrl: this.EIP1559APIEndpoint.replace(\n '',\n `${chainId}`,\n ),\n fetchGasEstimatesViaEthFeeHistory,\n fetchLegacyGasPriceEstimates,\n fetchLegacyGasPriceEstimatesUrl: this.legacyAPIEndpoint.replace(\n '',\n `${chainId}`,\n ),\n fetchEthGasPriceEstimate,\n calculateTimeEstimate,\n clientId: this.clientId,\n ethQuery: this.ethQuery,\n });\n\n if (shouldUpdateState) {\n this.update((state) => {\n state.gasFeeEstimates = gasFeeCalculations.gasFeeEstimates;\n state.estimatedGasFeeTimeBounds =\n gasFeeCalculations.estimatedGasFeeTimeBounds;\n state.gasEstimateType = gasFeeCalculations.gasEstimateType;\n });\n }\n\n return gasFeeCalculations;\n }\n\n /**\n * Remove the poll token, and stop polling if the set of poll tokens is empty.\n *\n * @param pollToken - The poll token to disconnect.\n */\n disconnectPoller(pollToken: string) {\n this.pollTokens.delete(pollToken);\n if (this.pollTokens.size === 0) {\n this.stopPolling();\n }\n }\n\n stopPolling() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n this.pollTokens.clear();\n this.resetState();\n }\n\n /**\n * Prepare to discard this controller.\n *\n * This stops any active polling.\n */\n override destroy() {\n super.destroy();\n this.stopPolling();\n }\n\n private _poll() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n\n this.intervalId = setInterval(async () => {\n await safelyExecute(() => this._fetchGasFeeEstimateData());\n }, this.intervalDelay);\n }\n\n private resetState() {\n this.update(() => {\n return defaultState;\n });\n }\n\n private async getEIP1559Compatibility() {\n const currentNetworkIsEIP1559Compatible =\n await this.getCurrentNetworkEIP1559Compatibility();\n const currentAccountIsEIP1559Compatible =\n this.getCurrentAccountEIP1559Compatibility?.() ?? true;\n\n return (\n currentNetworkIsEIP1559Compatible && currentAccountIsEIP1559Compatible\n );\n }\n\n getTimeEstimate(\n maxPriorityFeePerGas: string,\n maxFeePerGas: string,\n ): EstimatedGasFeeTimeBounds | Record {\n if (\n !this.state.gasFeeEstimates ||\n this.state.gasEstimateType !== GAS_ESTIMATE_TYPES.FEE_MARKET\n ) {\n return {};\n }\n return calculateTimeEstimate(\n maxPriorityFeePerGas,\n maxFeePerGas,\n this.state.gasFeeEstimates,\n );\n }\n}\n\nexport default GasFeeController;\n"]} \ No newline at end of file diff --git a/dist/gas/determineGasFeeCalculations.d.ts b/dist/gas/determineGasFeeCalculations.d.ts deleted file mode 100644 index dfc404ffb6..0000000000 --- a/dist/gas/determineGasFeeCalculations.d.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { EstimatedGasFeeTimeBounds, EthGasPriceEstimate, GasFeeEstimates, GasFeeState as GasFeeCalculations, LegacyGasPriceEstimate } from './GasFeeController'; -/** - * Obtains a set of max base and priority fee estimates along with time estimates so that we - * can present them to users when they are sending transactions or making swaps. - * - * @param args - The arguments. - * @param args.isEIP1559Compatible - Governs whether or not we can use an EIP-1559-only method to - * produce estimates. - * @param args.isLegacyGasAPICompatible - Governs whether or not we can use a non-EIP-1559 method to - * produce estimates (for instance, testnets do not support estimates altogether). - * @param args.fetchGasEstimates - A function that fetches gas estimates using an EIP-1559-specific - * API. - * @param args.fetchGasEstimatesUrl - The URL for the API we can use to obtain EIP-1559-specific - * estimates. - * @param args.fetchGasEstimatesViaEthFeeHistory - A function that fetches gas estimates using - * `eth_feeHistory` (an EIP-1559 feature). - * @param args.fetchLegacyGasPriceEstimates - A function that fetches gas estimates using an - * non-EIP-1559-specific API. - * @param args.fetchLegacyGasPriceEstimatesUrl - The URL for the API we can use to obtain - * non-EIP-1559-specific estimates. - * @param args.fetchEthGasPriceEstimate - A function that fetches gas estimates using - * `eth_gasPrice`. - * @param args.calculateTimeEstimate - A function that determine time estimate bounds. - * @param args.clientId - An identifier that an API can use to know who is asking for estimates. - * @param args.ethQuery - An EthQuery instance we can use to talk to Ethereum directly. - * @returns The gas fee calculations. - */ -export default function determineGasFeeCalculations({ isEIP1559Compatible, isLegacyGasAPICompatible, fetchGasEstimates, fetchGasEstimatesUrl, fetchGasEstimatesViaEthFeeHistory, fetchLegacyGasPriceEstimates, fetchLegacyGasPriceEstimatesUrl, fetchEthGasPriceEstimate, calculateTimeEstimate, clientId, ethQuery, }: { - isEIP1559Compatible: boolean; - isLegacyGasAPICompatible: boolean; - fetchGasEstimates: (url: string, clientId?: string) => Promise; - fetchGasEstimatesUrl: string; - fetchGasEstimatesViaEthFeeHistory: (ethQuery: any) => Promise; - fetchLegacyGasPriceEstimates: (url: string, clientId?: string) => Promise; - fetchLegacyGasPriceEstimatesUrl: string; - fetchEthGasPriceEstimate: (ethQuery: any) => Promise; - calculateTimeEstimate: (maxPriorityFeePerGas: string, maxFeePerGas: string, gasFeeEstimates: GasFeeEstimates) => EstimatedGasFeeTimeBounds; - clientId: string | undefined; - ethQuery: any; -}): Promise; diff --git a/dist/gas/determineGasFeeCalculations.js b/dist/gas/determineGasFeeCalculations.js deleted file mode 100644 index 481f03056b..0000000000 --- a/dist/gas/determineGasFeeCalculations.js +++ /dev/null @@ -1,87 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const GasFeeController_1 = require("./GasFeeController"); -/** - * Obtains a set of max base and priority fee estimates along with time estimates so that we - * can present them to users when they are sending transactions or making swaps. - * - * @param args - The arguments. - * @param args.isEIP1559Compatible - Governs whether or not we can use an EIP-1559-only method to - * produce estimates. - * @param args.isLegacyGasAPICompatible - Governs whether or not we can use a non-EIP-1559 method to - * produce estimates (for instance, testnets do not support estimates altogether). - * @param args.fetchGasEstimates - A function that fetches gas estimates using an EIP-1559-specific - * API. - * @param args.fetchGasEstimatesUrl - The URL for the API we can use to obtain EIP-1559-specific - * estimates. - * @param args.fetchGasEstimatesViaEthFeeHistory - A function that fetches gas estimates using - * `eth_feeHistory` (an EIP-1559 feature). - * @param args.fetchLegacyGasPriceEstimates - A function that fetches gas estimates using an - * non-EIP-1559-specific API. - * @param args.fetchLegacyGasPriceEstimatesUrl - The URL for the API we can use to obtain - * non-EIP-1559-specific estimates. - * @param args.fetchEthGasPriceEstimate - A function that fetches gas estimates using - * `eth_gasPrice`. - * @param args.calculateTimeEstimate - A function that determine time estimate bounds. - * @param args.clientId - An identifier that an API can use to know who is asking for estimates. - * @param args.ethQuery - An EthQuery instance we can use to talk to Ethereum directly. - * @returns The gas fee calculations. - */ -function determineGasFeeCalculations({ isEIP1559Compatible, isLegacyGasAPICompatible, fetchGasEstimates, fetchGasEstimatesUrl, fetchGasEstimatesViaEthFeeHistory, fetchLegacyGasPriceEstimates, fetchLegacyGasPriceEstimatesUrl, fetchEthGasPriceEstimate, calculateTimeEstimate, clientId, ethQuery, }) { - return __awaiter(this, void 0, void 0, function* () { - try { - if (isEIP1559Compatible) { - let estimates; - try { - estimates = yield fetchGasEstimates(fetchGasEstimatesUrl, clientId); - } - catch (_a) { - estimates = yield fetchGasEstimatesViaEthFeeHistory(ethQuery); - } - const { suggestedMaxPriorityFeePerGas, suggestedMaxFeePerGas } = estimates.medium; - const estimatedGasFeeTimeBounds = calculateTimeEstimate(suggestedMaxPriorityFeePerGas, suggestedMaxFeePerGas, estimates); - return { - gasFeeEstimates: estimates, - estimatedGasFeeTimeBounds, - gasEstimateType: GasFeeController_1.GAS_ESTIMATE_TYPES.FEE_MARKET, - }; - } - else if (isLegacyGasAPICompatible) { - const estimates = yield fetchLegacyGasPriceEstimates(fetchLegacyGasPriceEstimatesUrl, clientId); - return { - gasFeeEstimates: estimates, - estimatedGasFeeTimeBounds: {}, - gasEstimateType: GasFeeController_1.GAS_ESTIMATE_TYPES.LEGACY, - }; - } - throw new Error('Main gas fee/price estimation failed. Use fallback'); - } - catch (_b) { - try { - const estimates = yield fetchEthGasPriceEstimate(ethQuery); - return { - gasFeeEstimates: estimates, - estimatedGasFeeTimeBounds: {}, - gasEstimateType: GasFeeController_1.GAS_ESTIMATE_TYPES.ETH_GASPRICE, - }; - } - catch (error) { - if (error instanceof Error) { - throw new Error(`Gas fee/price estimation failed. Message: ${error.message}`); - } - throw error; - } - } - }); -} -exports.default = determineGasFeeCalculations; -//# sourceMappingURL=determineGasFeeCalculations.js.map \ No newline at end of file diff --git a/dist/gas/determineGasFeeCalculations.js.map b/dist/gas/determineGasFeeCalculations.js.map deleted file mode 100644 index 83d3d56665..0000000000 --- a/dist/gas/determineGasFeeCalculations.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"determineGasFeeCalculations.js","sourceRoot":"","sources":["../../src/gas/determineGasFeeCalculations.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,yDAO4B;AAE5B;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,SAA8B,2BAA2B,CAAC,EACxD,mBAAmB,EACnB,wBAAwB,EACxB,iBAAiB,EACjB,oBAAoB,EACpB,iCAAiC,EACjC,4BAA4B,EAC5B,+BAA+B,EAC/B,wBAAwB,EACxB,qBAAqB,EACrB,QAAQ,EACR,QAAQ,GAyBT;;QACC,IAAI;YACF,IAAI,mBAAmB,EAAE;gBACvB,IAAI,SAA0B,CAAC;gBAC/B,IAAI;oBACF,SAAS,GAAG,MAAM,iBAAiB,CAAC,oBAAoB,EAAE,QAAQ,CAAC,CAAC;iBACrE;gBAAC,WAAM;oBACN,SAAS,GAAG,MAAM,iCAAiC,CAAC,QAAQ,CAAC,CAAC;iBAC/D;gBACD,MAAM,EAAE,6BAA6B,EAAE,qBAAqB,EAAE,GAC5D,SAAS,CAAC,MAAM,CAAC;gBACnB,MAAM,yBAAyB,GAAG,qBAAqB,CACrD,6BAA6B,EAC7B,qBAAqB,EACrB,SAAS,CACV,CAAC;gBACF,OAAO;oBACL,eAAe,EAAE,SAAS;oBAC1B,yBAAyB;oBACzB,eAAe,EAAE,qCAAkB,CAAC,UAAU;iBAC/C,CAAC;aACH;iBAAM,IAAI,wBAAwB,EAAE;gBACnC,MAAM,SAAS,GAAG,MAAM,4BAA4B,CAClD,+BAA+B,EAC/B,QAAQ,CACT,CAAC;gBACF,OAAO;oBACL,eAAe,EAAE,SAAS;oBAC1B,yBAAyB,EAAE,EAAE;oBAC7B,eAAe,EAAE,qCAAkB,CAAC,MAAM;iBAC3C,CAAC;aACH;YACD,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;SACvE;QAAC,WAAM;YACN,IAAI;gBACF,MAAM,SAAS,GAAG,MAAM,wBAAwB,CAAC,QAAQ,CAAC,CAAC;gBAC3D,OAAO;oBACL,eAAe,EAAE,SAAS;oBAC1B,yBAAyB,EAAE,EAAE;oBAC7B,eAAe,EAAE,qCAAkB,CAAC,YAAY;iBACjD,CAAC;aACH;YAAC,OAAO,KAAK,EAAE;gBACd,IAAI,KAAK,YAAY,KAAK,EAAE;oBAC1B,MAAM,IAAI,KAAK,CACb,6CAA6C,KAAK,CAAC,OAAO,EAAE,CAC7D,CAAC;iBACH;gBACD,MAAM,KAAK,CAAC;aACb;SACF;IACH,CAAC;CAAA;AAtFD,8CAsFC","sourcesContent":["import {\n GAS_ESTIMATE_TYPES,\n EstimatedGasFeeTimeBounds,\n EthGasPriceEstimate,\n GasFeeEstimates,\n GasFeeState as GasFeeCalculations,\n LegacyGasPriceEstimate,\n} from './GasFeeController';\n\n/**\n * Obtains a set of max base and priority fee estimates along with time estimates so that we\n * can present them to users when they are sending transactions or making swaps.\n *\n * @param args - The arguments.\n * @param args.isEIP1559Compatible - Governs whether or not we can use an EIP-1559-only method to\n * produce estimates.\n * @param args.isLegacyGasAPICompatible - Governs whether or not we can use a non-EIP-1559 method to\n * produce estimates (for instance, testnets do not support estimates altogether).\n * @param args.fetchGasEstimates - A function that fetches gas estimates using an EIP-1559-specific\n * API.\n * @param args.fetchGasEstimatesUrl - The URL for the API we can use to obtain EIP-1559-specific\n * estimates.\n * @param args.fetchGasEstimatesViaEthFeeHistory - A function that fetches gas estimates using\n * `eth_feeHistory` (an EIP-1559 feature).\n * @param args.fetchLegacyGasPriceEstimates - A function that fetches gas estimates using an\n * non-EIP-1559-specific API.\n * @param args.fetchLegacyGasPriceEstimatesUrl - The URL for the API we can use to obtain\n * non-EIP-1559-specific estimates.\n * @param args.fetchEthGasPriceEstimate - A function that fetches gas estimates using\n * `eth_gasPrice`.\n * @param args.calculateTimeEstimate - A function that determine time estimate bounds.\n * @param args.clientId - An identifier that an API can use to know who is asking for estimates.\n * @param args.ethQuery - An EthQuery instance we can use to talk to Ethereum directly.\n * @returns The gas fee calculations.\n */\nexport default async function determineGasFeeCalculations({\n isEIP1559Compatible,\n isLegacyGasAPICompatible,\n fetchGasEstimates,\n fetchGasEstimatesUrl,\n fetchGasEstimatesViaEthFeeHistory,\n fetchLegacyGasPriceEstimates,\n fetchLegacyGasPriceEstimatesUrl,\n fetchEthGasPriceEstimate,\n calculateTimeEstimate,\n clientId,\n ethQuery,\n}: {\n isEIP1559Compatible: boolean;\n isLegacyGasAPICompatible: boolean;\n fetchGasEstimates: (\n url: string,\n clientId?: string,\n ) => Promise;\n fetchGasEstimatesUrl: string;\n fetchGasEstimatesViaEthFeeHistory: (\n ethQuery: any,\n ) => Promise;\n fetchLegacyGasPriceEstimates: (\n url: string,\n clientId?: string,\n ) => Promise;\n fetchLegacyGasPriceEstimatesUrl: string;\n fetchEthGasPriceEstimate: (ethQuery: any) => Promise;\n calculateTimeEstimate: (\n maxPriorityFeePerGas: string,\n maxFeePerGas: string,\n gasFeeEstimates: GasFeeEstimates,\n ) => EstimatedGasFeeTimeBounds;\n clientId: string | undefined;\n ethQuery: any;\n}): Promise {\n try {\n if (isEIP1559Compatible) {\n let estimates: GasFeeEstimates;\n try {\n estimates = await fetchGasEstimates(fetchGasEstimatesUrl, clientId);\n } catch {\n estimates = await fetchGasEstimatesViaEthFeeHistory(ethQuery);\n }\n const { suggestedMaxPriorityFeePerGas, suggestedMaxFeePerGas } =\n estimates.medium;\n const estimatedGasFeeTimeBounds = calculateTimeEstimate(\n suggestedMaxPriorityFeePerGas,\n suggestedMaxFeePerGas,\n estimates,\n );\n return {\n gasFeeEstimates: estimates,\n estimatedGasFeeTimeBounds,\n gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,\n };\n } else if (isLegacyGasAPICompatible) {\n const estimates = await fetchLegacyGasPriceEstimates(\n fetchLegacyGasPriceEstimatesUrl,\n clientId,\n );\n return {\n gasFeeEstimates: estimates,\n estimatedGasFeeTimeBounds: {},\n gasEstimateType: GAS_ESTIMATE_TYPES.LEGACY,\n };\n }\n throw new Error('Main gas fee/price estimation failed. Use fallback');\n } catch {\n try {\n const estimates = await fetchEthGasPriceEstimate(ethQuery);\n return {\n gasFeeEstimates: estimates,\n estimatedGasFeeTimeBounds: {},\n gasEstimateType: GAS_ESTIMATE_TYPES.ETH_GASPRICE,\n };\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(\n `Gas fee/price estimation failed. Message: ${error.message}`,\n );\n }\n throw error;\n }\n }\n}\n"]} \ No newline at end of file diff --git a/dist/gas/fetchBlockFeeHistory.d.ts b/dist/gas/fetchBlockFeeHistory.d.ts deleted file mode 100644 index 7fdab8e603..0000000000 --- a/dist/gas/fetchBlockFeeHistory.d.ts +++ /dev/null @@ -1,115 +0,0 @@ -/// -import { BN } from 'ethereumjs-util'; -declare type EthQuery = any; -/** - * @type EthFeeHistoryResponse - * - * Response data for `eth_feeHistory`. - * @property oldestBlock - The id of the oldest block (in hex format) in the range of blocks - * requested. - * @property baseFeePerGas - Base fee per gas for each block in the range of blocks requested. - * For go-ethereum based chains baseFeePerGas will not returned in case of empty results - * - * @property gasUsedRatio - A number between 0 and 1 that represents the gas used vs. gas limit for - * each block in the range of blocks requested. - * @property reward - The priority fee at the percentiles requested for each block in the range of - * blocks requested. - */ -export declare type EthFeeHistoryResponse = { - oldestBlock: string; - baseFeePerGas?: string[]; - gasUsedRatio: number[]; - reward?: string[][]; -}; -/** - * @type ExistingFeeHistoryBlock - * - * Historical data for a particular block that exists on the blockchain. - * @property number - The number of the block, as a BN. - * @property baseFeePerGas - The base fee per gas for the block in WEI, as a BN. - * @property gasUsedRatio - A number between 0 and 1 that represents the ratio between the gas paid - * for the block and its set gas limit. - * @property priorityFeesByPercentile - The priority fees paid for the transactions in the block - * that occurred at particular levels at which those transactions contributed to the overall gas - * used for the block, indexed by those percentiles. (See docs for {@link fetchBlockFeeHistory} for more - * on how this works.) - */ -declare type ExistingFeeHistoryBlock = { - number: BN; - baseFeePerGas: BN; - gasUsedRatio: number; - priorityFeesByPercentile: Record; -}; -/** - * @type NextFeeHistoryBlock - * - * Historical data for a theoretical block that could exist in the future. - * @property number - The number of the block, as a BN. - * @property baseFeePerGas - The estimated base fee per gas for the block in WEI, as a BN. - */ -declare type NextFeeHistoryBlock = { - number: BN; - baseFeePerGas: BN; -}; -/** - * @type FeeHistoryBlock - * - * Historical data for a particular block. - * @property number - The number of the block, as a BN. - * @property baseFeePerGas - The base fee per gas for the block in WEI, as a BN. - * @property gasUsedRatio - A number between 0 and 1 that represents the ratio between the gas paid - * for the block and its set gas limit. - * @property priorityFeesByPercentile - The priority fees paid for the transactions in the block - * that occurred at particular levels at which those transactions contributed to the overall gas - * used for the block, indexed by those percentiles. (See docs for {@link fetchBlockFeeHistory} for more - * on how this works.) - */ -export declare type FeeHistoryBlock = ExistingFeeHistoryBlock | NextFeeHistoryBlock; -/** - * @type ExtractPercentileFrom - * - * Extracts the percentiles that the type assigned to an array of FeeHistoryBlock has been created - * with. This makes use of the `infer` keyword to read the type argument. - */ -export declare type ExtractPercentileFrom = T extends FeeHistoryBlock[] ? P : never; -/** - * Uses `eth_feeHistory` (an EIP-1559 feature) to obtain information about gas fees from a range of - * blocks that have occurred recently on a network. - * - * To learn more, see these resources: - * - * - - * - - * - - * - - * - - * - * @param args - The arguments to this function. - * @param args.ethQuery - An EthQuery instance that wraps a provider for the network in question. - * @param args.endBlock - The desired end of the requested block range. Can be "latest" if you want - * to start from the latest successful block or the number of a known past block. - * @param args.numberOfBlocks - How many total blocks to fetch. Note that if this is more than 1024, - * multiple calls to `eth_feeHistory` will be made. - * @param args.percentiles - A set of numbers between 1 and 100 which will dictate how - * `priorityFeesByPercentile` in each returned block will be formed. When Ethereum runs the - * `eth_feeHistory` method, for each block it is considering, it will first sort all transactions by - * the priority fee. It will then go through each transaction and add the total amount of gas paid - * for that transaction to a bucket which maxes out at the total gas used for the whole block. As - * the bucket fills, it will cross percentages which correspond to the percentiles specified here, - * and the priority fees of the first transactions which cause it to reach those percentages will be - * recorded. Hence, `priorityFeesByPercentile` represents the priority fees of transactions at key - * gas used contribution levels, where earlier levels have smaller contributions and later levels - * have higher contributions. - * @param args.includeNextBlock - Whether to include an extra block that represents the next - * block after the latest one. Only the `baseFeePerGas` will be filled in for this block (which is - * estimated). - * @returns The list of blocks and their fee data, sorted from oldest to newest. - */ -export default function fetchBlockFeeHistory({ ethQuery, numberOfBlocks: totalNumberOfBlocks, endBlock: givenEndBlock, percentiles: givenPercentiles, includeNextBlock, }: { - ethQuery: EthQuery; - numberOfBlocks: number; - endBlock?: 'latest' | BN; - percentiles?: readonly Percentile[]; - includeNextBlock?: boolean; -}): Promise[]>; -export {}; diff --git a/dist/gas/fetchBlockFeeHistory.js b/dist/gas/fetchBlockFeeHistory.js deleted file mode 100644 index c5011d3a18..0000000000 --- a/dist/gas/fetchBlockFeeHistory.js +++ /dev/null @@ -1,202 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const ethereumjs_util_1 = require("ethereumjs-util"); -const util_1 = require("../util"); -const MAX_NUMBER_OF_BLOCKS_PER_ETH_FEE_HISTORY_CALL = 1024; -/** - * Uses `eth_feeHistory` (an EIP-1559 feature) to obtain information about gas fees from a range of - * blocks that have occurred recently on a network. - * - * To learn more, see these resources: - * - * - - * - - * - - * - - * - - * - * @param args - The arguments to this function. - * @param args.ethQuery - An EthQuery instance that wraps a provider for the network in question. - * @param args.endBlock - The desired end of the requested block range. Can be "latest" if you want - * to start from the latest successful block or the number of a known past block. - * @param args.numberOfBlocks - How many total blocks to fetch. Note that if this is more than 1024, - * multiple calls to `eth_feeHistory` will be made. - * @param args.percentiles - A set of numbers between 1 and 100 which will dictate how - * `priorityFeesByPercentile` in each returned block will be formed. When Ethereum runs the - * `eth_feeHistory` method, for each block it is considering, it will first sort all transactions by - * the priority fee. It will then go through each transaction and add the total amount of gas paid - * for that transaction to a bucket which maxes out at the total gas used for the whole block. As - * the bucket fills, it will cross percentages which correspond to the percentiles specified here, - * and the priority fees of the first transactions which cause it to reach those percentages will be - * recorded. Hence, `priorityFeesByPercentile` represents the priority fees of transactions at key - * gas used contribution levels, where earlier levels have smaller contributions and later levels - * have higher contributions. - * @param args.includeNextBlock - Whether to include an extra block that represents the next - * block after the latest one. Only the `baseFeePerGas` will be filled in for this block (which is - * estimated). - * @returns The list of blocks and their fee data, sorted from oldest to newest. - */ -function fetchBlockFeeHistory({ ethQuery, numberOfBlocks: totalNumberOfBlocks, endBlock: givenEndBlock = 'latest', percentiles: givenPercentiles = [], includeNextBlock = false, }) { - return __awaiter(this, void 0, void 0, function* () { - const percentiles = givenPercentiles.length > 0 - ? Array.from(new Set(givenPercentiles)).sort((a, b) => a - b) - : []; - const finalEndBlockNumber = givenEndBlock === 'latest' - ? (0, util_1.fromHex)(yield (0, util_1.query)(ethQuery, 'blockNumber')) - : givenEndBlock; - const requestChunkSpecifiers = determineRequestChunkSpecifiers(finalEndBlockNumber, totalNumberOfBlocks); - const blockChunks = yield Promise.all(requestChunkSpecifiers.map(({ numberOfBlocks, endBlockNumber }, i) => { - return i === requestChunkSpecifiers.length - 1 - ? makeRequestForChunk({ - ethQuery, - numberOfBlocks, - endBlockNumber, - percentiles, - includeNextBlock, - }) - : makeRequestForChunk({ - ethQuery, - numberOfBlocks, - endBlockNumber, - percentiles, - includeNextBlock: false, - }); - })); - return blockChunks.reduce((array, blocks) => [...array, ...blocks], []); - }); -} -exports.default = fetchBlockFeeHistory; -/** - * Builds an ExistingFeeHistoryBlock. - * - * @param args - The args to this function. - * @param args.number - The number of the block. - * @param args.baseFeePerGas - The base fee per gas of the block. - * @param args.blockIndex - The index of the block in the source chunk. - * @param args.gasUsedRatios - The gas used ratios for the block. - * @param args.priorityFeePercentileGroups - The priority fee percentile groups for the block. - * @param args.percentiles - The percentiles used to fetch the source chunk. - * @returns The ExistingFeeHistoryBlock. - */ -function buildExistingFeeHistoryBlock({ baseFeePerGas, number, blockIndex, gasUsedRatios, priorityFeePercentileGroups, percentiles, }) { - const gasUsedRatio = gasUsedRatios[blockIndex]; - const priorityFeesForEachPercentile = priorityFeePercentileGroups[blockIndex]; - const priorityFeesByPercentile = percentiles.reduce((obj, percentile, percentileIndex) => { - const priorityFee = priorityFeesForEachPercentile[percentileIndex]; - return Object.assign(Object.assign({}, obj), { [percentile]: (0, util_1.fromHex)(priorityFee) }); - }, {}); - return { - number, - baseFeePerGas, - gasUsedRatio, - priorityFeesByPercentile, - }; -} -/** - * Builds a NextFeeHistoryBlock. - * - * @param args - The args to this function. - * @param args.baseFeePerGas - The base fee per gas of the block. - * @param args.number - The number of the block. - * @returns The NextFeeHistoryBlock. - */ -function buildNextFeeHistoryBlock({ baseFeePerGas, number, }) { - return { - number, - baseFeePerGas, - gasUsedRatio: null, - priorityFeesByPercentile: null, - }; -} -/** - * Uses eth_feeHistory to request historical data about a group of blocks (max size 1024). - * - * @param args - The arguments - * @param args.ethQuery - An EthQuery instance. - * @param args.numberOfBlocks - The number of blocks in the chunk. Must be at most 1024, as this is - * the maximum that `eth_feeHistory` can return in one call. - * @param args.endBlockNumber - The end of the requested block range. - * @param args.percentiles - A set of numbers between 1 and 100 that will be used to pull priority - * fees for each block. - * @param args.includeNextBlock - Whether to include an extra block that represents the next - * block after the latest one. Only the `baseFeePerGas` will be filled in for this block (which is - * estimated). - * @returns A list of block data. - */ -function makeRequestForChunk({ ethQuery, numberOfBlocks, endBlockNumber, percentiles, includeNextBlock, }) { - var _a; - return __awaiter(this, void 0, void 0, function* () { - const response = yield (0, util_1.query)(ethQuery, 'eth_feeHistory', [(0, util_1.toHex)(numberOfBlocks), (0, util_1.toHex)(endBlockNumber), percentiles]); - const startBlockNumber = (0, util_1.fromHex)(response.oldestBlock); - if (response.baseFeePerGas !== undefined && - response.baseFeePerGas.length > 0 && - response.gasUsedRatio.length > 0 && - (response.reward === undefined || response.reward.length > 0)) { - // Per - // , - // baseFeePerGas will always include an extra item which is the calculated base fee for the - // next (future) block. We may or may not care about this; if we don't, chop it off. - const baseFeesPerGasAsHex = includeNextBlock - ? response.baseFeePerGas - : response.baseFeePerGas.slice(0, numberOfBlocks); - const gasUsedRatios = response.gasUsedRatio; - const priorityFeePercentileGroups = (_a = response.reward) !== null && _a !== void 0 ? _a : []; - // Chain is allowed to return fewer number of block results - const numberOfExistingResults = gasUsedRatios.length; - return baseFeesPerGasAsHex.map((baseFeePerGasAsHex, blockIndex) => { - const baseFeePerGas = (0, util_1.fromHex)(baseFeePerGasAsHex); - const number = startBlockNumber.addn(blockIndex); - return blockIndex >= numberOfExistingResults - ? buildNextFeeHistoryBlock({ baseFeePerGas, number }) - : buildExistingFeeHistoryBlock({ - baseFeePerGas, - number, - blockIndex, - gasUsedRatios, - priorityFeePercentileGroups, - percentiles, - }); - }); - } - return []; - }); -} -/** - * Divides a block range (specified by a range size and the end of the range) into chunks based on - * the maximum number of blocks that `eth_feeHistory` can return in a single call. - * - * If the requested totalNumberOfBlocks exceed endBlockNumber, totalNumberOfBlocks is - * truncated to avoid requesting chunks with negative endBlockNumber. - * - * @param endBlockNumber - The final block in the complete desired block range after all - * `eth_feeHistory` requests have been made. - * @param totalNumberOfBlocks - The total number of desired blocks after all `eth_feeHistory` - * requests have been made. - * @returns A set of arguments that can be used to make requests to `eth_feeHistory` in order to - * retrieve all of the requested blocks, sorted from oldest block to newest block. - */ -function determineRequestChunkSpecifiers(endBlockNumber, totalNumberOfBlocks) { - if (endBlockNumber.lt(new ethereumjs_util_1.BN(totalNumberOfBlocks))) { - totalNumberOfBlocks = endBlockNumber.toNumber(); - } - const specifiers = []; - for (let chunkStartBlockNumber = endBlockNumber.subn(totalNumberOfBlocks); chunkStartBlockNumber.lt(endBlockNumber); chunkStartBlockNumber = chunkStartBlockNumber.addn(MAX_NUMBER_OF_BLOCKS_PER_ETH_FEE_HISTORY_CALL)) { - const distanceToEnd = endBlockNumber.sub(chunkStartBlockNumber).toNumber(); - const numberOfBlocks = distanceToEnd < MAX_NUMBER_OF_BLOCKS_PER_ETH_FEE_HISTORY_CALL - ? distanceToEnd - : MAX_NUMBER_OF_BLOCKS_PER_ETH_FEE_HISTORY_CALL; - const chunkEndBlockNumber = chunkStartBlockNumber.addn(numberOfBlocks); - specifiers.push({ numberOfBlocks, endBlockNumber: chunkEndBlockNumber }); - } - return specifiers; -} -//# sourceMappingURL=fetchBlockFeeHistory.js.map \ No newline at end of file diff --git a/dist/gas/fetchBlockFeeHistory.js.map b/dist/gas/fetchBlockFeeHistory.js.map deleted file mode 100644 index 973e76f88d..0000000000 --- a/dist/gas/fetchBlockFeeHistory.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"fetchBlockFeeHistory.js","sourceRoot":"","sources":["../../src/gas/fetchBlockFeeHistory.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,qDAAqC;AACrC,kCAAgD;AAiGhD,MAAM,6CAA6C,GAAG,IAAI,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,SAA8B,oBAAoB,CAA4B,EAC5E,QAAQ,EACR,cAAc,EAAE,mBAAmB,EACnC,QAAQ,EAAE,aAAa,GAAG,QAAQ,EAClC,WAAW,EAAE,gBAAgB,GAAG,EAAE,EAClC,gBAAgB,GAAG,KAAK,GAOzB;;QACC,MAAM,WAAW,GACf,gBAAgB,CAAC,MAAM,GAAG,CAAC;YACzB,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;YAC7D,CAAC,CAAC,EAAE,CAAC;QAET,MAAM,mBAAmB,GACvB,aAAa,KAAK,QAAQ;YACxB,CAAC,CAAC,IAAA,cAAO,EAAC,MAAM,IAAA,YAAK,EAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;YAC/C,CAAC,CAAC,aAAa,CAAC;QAEpB,MAAM,sBAAsB,GAAG,+BAA+B,CAC5D,mBAAmB,EACnB,mBAAmB,CACpB,CAAC;QAEF,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,GAAG,CACnC,sBAAsB,CAAC,GAAG,CAAC,CAAC,EAAE,cAAc,EAAE,cAAc,EAAE,EAAE,CAAC,EAAE,EAAE;YACnE,OAAO,CAAC,KAAK,sBAAsB,CAAC,MAAM,GAAG,CAAC;gBAC5C,CAAC,CAAC,mBAAmB,CAAC;oBAClB,QAAQ;oBACR,cAAc;oBACd,cAAc;oBACd,WAAW;oBACX,gBAAgB;iBACjB,CAAC;gBACJ,CAAC,CAAC,mBAAmB,CAAC;oBAClB,QAAQ;oBACR,cAAc;oBACd,cAAc;oBACd,WAAW;oBACX,gBAAgB,EAAE,KAAK;iBACxB,CAAC,CAAC;QACT,CAAC,CAAC,CACH,CAAC;QAEF,OAAO,WAAW,CAAC,MAAM,CACvB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,GAAG,KAAK,EAAE,GAAG,MAAM,CAAC,EACxC,EAAmC,CACpC,CAAC;IACJ,CAAC;CAAA;AApDD,uCAoDC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,4BAA4B,CAA4B,EAC/D,aAAa,EACb,MAAM,EACN,UAAU,EACV,aAAa,EACb,2BAA2B,EAC3B,WAAW,GAQZ;IACC,MAAM,YAAY,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;IAC/C,MAAM,6BAA6B,GAAG,2BAA2B,CAAC,UAAU,CAAC,CAAC;IAC9E,MAAM,wBAAwB,GAAG,WAAW,CAAC,MAAM,CACjD,CAAC,GAAG,EAAE,UAAU,EAAE,eAAe,EAAE,EAAE;QACnC,MAAM,WAAW,GAAG,6BAA6B,CAAC,eAAe,CAAC,CAAC;QACnE,uCAAY,GAAG,KAAE,CAAC,UAAU,CAAC,EAAE,IAAA,cAAO,EAAC,WAAW,CAAC,IAAG;IACxD,CAAC,EACD,EAA4B,CAC7B,CAAC;IAEF,OAAO;QACL,MAAM;QACN,aAAa;QACb,YAAY;QACZ,wBAAwB;KACzB,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,wBAAwB,CAAC,EAChC,aAAa,EACb,MAAM,GAIP;IACC,OAAO;QACL,MAAM;QACN,aAAa;QACb,YAAY,EAAE,IAAI;QAClB,wBAAwB,EAAE,IAAI;KAC/B,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAe,mBAAmB,CAA4B,EAC5D,QAAQ,EACR,cAAc,EACd,cAAc,EACd,WAAW,EACX,gBAAgB,GAOjB;;;QACC,MAAM,QAAQ,GAA0B,MAAM,IAAA,YAAK,EACjD,QAAQ,EACR,gBAAgB,EAChB,CAAC,IAAA,YAAK,EAAC,cAAc,CAAC,EAAE,IAAA,YAAK,EAAC,cAAc,CAAC,EAAE,WAAW,CAAC,CAC5D,CAAC;QAEF,MAAM,gBAAgB,GAAG,IAAA,cAAO,EAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAEvD,IACE,QAAQ,CAAC,aAAa,KAAK,SAAS;YACpC,QAAQ,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC;YACjC,QAAQ,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC;YAChC,CAAC,QAAQ,CAAC,MAAM,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,EAC7D;YACA,MAAM;YACN,gIAAgI;YAChI,2FAA2F;YAC3F,oFAAoF;YACpF,MAAM,mBAAmB,GAAG,gBAAgB;gBAC1C,CAAC,CAAC,QAAQ,CAAC,aAAa;gBACxB,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;YACpD,MAAM,aAAa,GAAG,QAAQ,CAAC,YAAY,CAAC;YAC5C,MAAM,2BAA2B,GAAG,MAAA,QAAQ,CAAC,MAAM,mCAAI,EAAE,CAAC;YAC1D,2DAA2D;YAC3D,MAAM,uBAAuB,GAAG,aAAa,CAAC,MAAM,CAAC;YAErD,OAAO,mBAAmB,CAAC,GAAG,CAAC,CAAC,kBAAkB,EAAE,UAAU,EAAE,EAAE;gBAChE,MAAM,aAAa,GAAG,IAAA,cAAO,EAAC,kBAAkB,CAAC,CAAC;gBAClD,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;gBAEjD,OAAO,UAAU,IAAI,uBAAuB;oBAC1C,CAAC,CAAC,wBAAwB,CAAC,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC;oBACrD,CAAC,CAAC,4BAA4B,CAAC;wBAC3B,aAAa;wBACb,MAAM;wBACN,UAAU;wBACV,aAAa;wBACb,2BAA2B;wBAC3B,WAAW;qBACZ,CAAC,CAAC;YACT,CAAC,CAAC,CAAC;SACJ;QAED,OAAO,EAAE,CAAC;;CACX;AAED;;;;;;;;;;;;;GAaG;AACH,SAAS,+BAA+B,CACtC,cAAkB,EAClB,mBAA2B;IAE3B,IAAI,cAAc,CAAC,EAAE,CAAC,IAAI,oBAAE,CAAC,mBAAmB,CAAC,CAAC,EAAE;QAClD,mBAAmB,GAAG,cAAc,CAAC,QAAQ,EAAE,CAAC;KACjD;IAED,MAAM,UAAU,GAAG,EAAE,CAAC;IACtB,KACE,IAAI,qBAAqB,GAAG,cAAc,CAAC,IAAI,CAAC,mBAAmB,CAAC,EACpE,qBAAqB,CAAC,EAAE,CAAC,cAAc,CAAC,EACxC,qBAAqB,GAAG,qBAAqB,CAAC,IAAI,CAChD,6CAA6C,CAC9C,EACD;QACA,MAAM,aAAa,GAAG,cAAc,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC3E,MAAM,cAAc,GAClB,aAAa,GAAG,6CAA6C;YAC3D,CAAC,CAAC,aAAa;YACf,CAAC,CAAC,6CAA6C,CAAC;QACpD,MAAM,mBAAmB,GAAG,qBAAqB,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACvE,UAAU,CAAC,IAAI,CAAC,EAAE,cAAc,EAAE,cAAc,EAAE,mBAAmB,EAAE,CAAC,CAAC;KAC1E;IACD,OAAO,UAAU,CAAC;AACpB,CAAC","sourcesContent":["import { BN } from 'ethereumjs-util';\nimport { query, fromHex, toHex } from '../util';\n\ntype EthQuery = any;\n\n/**\n * @type RequestChunkSpecifier\n *\n * Arguments to `eth_feeHistory` that can be used to fetch a set of historical data.\n * @property blockCount - The number of blocks requested.\n * @property endBlockNumber - The number of the block at the end of the requested range.\n */\ntype RequestChunkSpecifier = {\n numberOfBlocks: number;\n endBlockNumber: BN;\n};\n\n/**\n * @type EthFeeHistoryResponse\n *\n * Response data for `eth_feeHistory`.\n * @property oldestBlock - The id of the oldest block (in hex format) in the range of blocks\n * requested.\n * @property baseFeePerGas - Base fee per gas for each block in the range of blocks requested.\n * For go-ethereum based chains baseFeePerGas will not returned in case of empty results\n * \n * @property gasUsedRatio - A number between 0 and 1 that represents the gas used vs. gas limit for\n * each block in the range of blocks requested.\n * @property reward - The priority fee at the percentiles requested for each block in the range of\n * blocks requested.\n */\n\nexport type EthFeeHistoryResponse = {\n oldestBlock: string;\n baseFeePerGas?: string[];\n gasUsedRatio: number[];\n reward?: string[][];\n};\n\n/**\n * @type ExistingFeeHistoryBlock\n *\n * Historical data for a particular block that exists on the blockchain.\n * @property number - The number of the block, as a BN.\n * @property baseFeePerGas - The base fee per gas for the block in WEI, as a BN.\n * @property gasUsedRatio - A number between 0 and 1 that represents the ratio between the gas paid\n * for the block and its set gas limit.\n * @property priorityFeesByPercentile - The priority fees paid for the transactions in the block\n * that occurred at particular levels at which those transactions contributed to the overall gas\n * used for the block, indexed by those percentiles. (See docs for {@link fetchBlockFeeHistory} for more\n * on how this works.)\n */\ntype ExistingFeeHistoryBlock = {\n number: BN;\n baseFeePerGas: BN;\n gasUsedRatio: number;\n priorityFeesByPercentile: Record;\n};\n\n/**\n * @type NextFeeHistoryBlock\n *\n * Historical data for a theoretical block that could exist in the future.\n * @property number - The number of the block, as a BN.\n * @property baseFeePerGas - The estimated base fee per gas for the block in WEI, as a BN.\n */\ntype NextFeeHistoryBlock = {\n number: BN;\n baseFeePerGas: BN;\n};\n\n/**\n * @type FeeHistoryBlock\n *\n * Historical data for a particular block.\n * @property number - The number of the block, as a BN.\n * @property baseFeePerGas - The base fee per gas for the block in WEI, as a BN.\n * @property gasUsedRatio - A number between 0 and 1 that represents the ratio between the gas paid\n * for the block and its set gas limit.\n * @property priorityFeesByPercentile - The priority fees paid for the transactions in the block\n * that occurred at particular levels at which those transactions contributed to the overall gas\n * used for the block, indexed by those percentiles. (See docs for {@link fetchBlockFeeHistory} for more\n * on how this works.)\n */\nexport type FeeHistoryBlock =\n | ExistingFeeHistoryBlock\n | NextFeeHistoryBlock;\n\n/**\n * @type ExtractPercentileFrom\n *\n * Extracts the percentiles that the type assigned to an array of FeeHistoryBlock has been created\n * with. This makes use of the `infer` keyword to read the type argument.\n */\nexport type ExtractPercentileFrom = T extends FeeHistoryBlock[]\n ? P\n : never;\n\nconst MAX_NUMBER_OF_BLOCKS_PER_ETH_FEE_HISTORY_CALL = 1024;\n\n/**\n * Uses `eth_feeHistory` (an EIP-1559 feature) to obtain information about gas fees from a range of\n * blocks that have occurred recently on a network.\n *\n * To learn more, see these resources:\n *\n * - \n * - \n * - \n * - \n * - \n *\n * @param args - The arguments to this function.\n * @param args.ethQuery - An EthQuery instance that wraps a provider for the network in question.\n * @param args.endBlock - The desired end of the requested block range. Can be \"latest\" if you want\n * to start from the latest successful block or the number of a known past block.\n * @param args.numberOfBlocks - How many total blocks to fetch. Note that if this is more than 1024,\n * multiple calls to `eth_feeHistory` will be made.\n * @param args.percentiles - A set of numbers between 1 and 100 which will dictate how\n * `priorityFeesByPercentile` in each returned block will be formed. When Ethereum runs the\n * `eth_feeHistory` method, for each block it is considering, it will first sort all transactions by\n * the priority fee. It will then go through each transaction and add the total amount of gas paid\n * for that transaction to a bucket which maxes out at the total gas used for the whole block. As\n * the bucket fills, it will cross percentages which correspond to the percentiles specified here,\n * and the priority fees of the first transactions which cause it to reach those percentages will be\n * recorded. Hence, `priorityFeesByPercentile` represents the priority fees of transactions at key\n * gas used contribution levels, where earlier levels have smaller contributions and later levels\n * have higher contributions.\n * @param args.includeNextBlock - Whether to include an extra block that represents the next\n * block after the latest one. Only the `baseFeePerGas` will be filled in for this block (which is\n * estimated).\n * @returns The list of blocks and their fee data, sorted from oldest to newest.\n */\nexport default async function fetchBlockFeeHistory({\n ethQuery,\n numberOfBlocks: totalNumberOfBlocks,\n endBlock: givenEndBlock = 'latest',\n percentiles: givenPercentiles = [],\n includeNextBlock = false,\n}: {\n ethQuery: EthQuery;\n numberOfBlocks: number;\n endBlock?: 'latest' | BN;\n percentiles?: readonly Percentile[];\n includeNextBlock?: boolean;\n}): Promise[]> {\n const percentiles =\n givenPercentiles.length > 0\n ? Array.from(new Set(givenPercentiles)).sort((a, b) => a - b)\n : [];\n\n const finalEndBlockNumber =\n givenEndBlock === 'latest'\n ? fromHex(await query(ethQuery, 'blockNumber'))\n : givenEndBlock;\n\n const requestChunkSpecifiers = determineRequestChunkSpecifiers(\n finalEndBlockNumber,\n totalNumberOfBlocks,\n );\n\n const blockChunks = await Promise.all(\n requestChunkSpecifiers.map(({ numberOfBlocks, endBlockNumber }, i) => {\n return i === requestChunkSpecifiers.length - 1\n ? makeRequestForChunk({\n ethQuery,\n numberOfBlocks,\n endBlockNumber,\n percentiles,\n includeNextBlock,\n })\n : makeRequestForChunk({\n ethQuery,\n numberOfBlocks,\n endBlockNumber,\n percentiles,\n includeNextBlock: false,\n });\n }),\n );\n\n return blockChunks.reduce(\n (array, blocks) => [...array, ...blocks],\n [] as FeeHistoryBlock[],\n );\n}\n\n/**\n * Builds an ExistingFeeHistoryBlock.\n *\n * @param args - The args to this function.\n * @param args.number - The number of the block.\n * @param args.baseFeePerGas - The base fee per gas of the block.\n * @param args.blockIndex - The index of the block in the source chunk.\n * @param args.gasUsedRatios - The gas used ratios for the block.\n * @param args.priorityFeePercentileGroups - The priority fee percentile groups for the block.\n * @param args.percentiles - The percentiles used to fetch the source chunk.\n * @returns The ExistingFeeHistoryBlock.\n */\nfunction buildExistingFeeHistoryBlock({\n baseFeePerGas,\n number,\n blockIndex,\n gasUsedRatios,\n priorityFeePercentileGroups,\n percentiles,\n}: {\n baseFeePerGas: BN;\n number: BN;\n blockIndex: number;\n gasUsedRatios: number[];\n priorityFeePercentileGroups: string[][];\n percentiles: readonly Percentile[];\n}): ExistingFeeHistoryBlock {\n const gasUsedRatio = gasUsedRatios[blockIndex];\n const priorityFeesForEachPercentile = priorityFeePercentileGroups[blockIndex];\n const priorityFeesByPercentile = percentiles.reduce(\n (obj, percentile, percentileIndex) => {\n const priorityFee = priorityFeesForEachPercentile[percentileIndex];\n return { ...obj, [percentile]: fromHex(priorityFee) };\n },\n {} as Record,\n );\n\n return {\n number,\n baseFeePerGas,\n gasUsedRatio,\n priorityFeesByPercentile,\n };\n}\n\n/**\n * Builds a NextFeeHistoryBlock.\n *\n * @param args - The args to this function.\n * @param args.baseFeePerGas - The base fee per gas of the block.\n * @param args.number - The number of the block.\n * @returns The NextFeeHistoryBlock.\n */\nfunction buildNextFeeHistoryBlock({\n baseFeePerGas,\n number,\n}: {\n baseFeePerGas: BN;\n number: BN;\n}) {\n return {\n number,\n baseFeePerGas,\n gasUsedRatio: null,\n priorityFeesByPercentile: null,\n };\n}\n\n/**\n * Uses eth_feeHistory to request historical data about a group of blocks (max size 1024).\n *\n * @param args - The arguments\n * @param args.ethQuery - An EthQuery instance.\n * @param args.numberOfBlocks - The number of blocks in the chunk. Must be at most 1024, as this is\n * the maximum that `eth_feeHistory` can return in one call.\n * @param args.endBlockNumber - The end of the requested block range.\n * @param args.percentiles - A set of numbers between 1 and 100 that will be used to pull priority\n * fees for each block.\n * @param args.includeNextBlock - Whether to include an extra block that represents the next\n * block after the latest one. Only the `baseFeePerGas` will be filled in for this block (which is\n * estimated).\n * @returns A list of block data.\n */\nasync function makeRequestForChunk({\n ethQuery,\n numberOfBlocks,\n endBlockNumber,\n percentiles,\n includeNextBlock,\n}: {\n ethQuery: EthQuery;\n numberOfBlocks: number;\n endBlockNumber: BN;\n percentiles: readonly Percentile[];\n includeNextBlock: boolean;\n}): Promise[]> {\n const response: EthFeeHistoryResponse = await query(\n ethQuery,\n 'eth_feeHistory',\n [toHex(numberOfBlocks), toHex(endBlockNumber), percentiles],\n );\n\n const startBlockNumber = fromHex(response.oldestBlock);\n\n if (\n response.baseFeePerGas !== undefined &&\n response.baseFeePerGas.length > 0 &&\n response.gasUsedRatio.length > 0 &&\n (response.reward === undefined || response.reward.length > 0)\n ) {\n // Per\n // ,\n // baseFeePerGas will always include an extra item which is the calculated base fee for the\n // next (future) block. We may or may not care about this; if we don't, chop it off.\n const baseFeesPerGasAsHex = includeNextBlock\n ? response.baseFeePerGas\n : response.baseFeePerGas.slice(0, numberOfBlocks);\n const gasUsedRatios = response.gasUsedRatio;\n const priorityFeePercentileGroups = response.reward ?? [];\n // Chain is allowed to return fewer number of block results\n const numberOfExistingResults = gasUsedRatios.length;\n\n return baseFeesPerGasAsHex.map((baseFeePerGasAsHex, blockIndex) => {\n const baseFeePerGas = fromHex(baseFeePerGasAsHex);\n const number = startBlockNumber.addn(blockIndex);\n\n return blockIndex >= numberOfExistingResults\n ? buildNextFeeHistoryBlock({ baseFeePerGas, number })\n : buildExistingFeeHistoryBlock({\n baseFeePerGas,\n number,\n blockIndex,\n gasUsedRatios,\n priorityFeePercentileGroups,\n percentiles,\n });\n });\n }\n\n return [];\n}\n\n/**\n * Divides a block range (specified by a range size and the end of the range) into chunks based on\n * the maximum number of blocks that `eth_feeHistory` can return in a single call.\n *\n * If the requested totalNumberOfBlocks exceed endBlockNumber, totalNumberOfBlocks is\n * truncated to avoid requesting chunks with negative endBlockNumber.\n *\n * @param endBlockNumber - The final block in the complete desired block range after all\n * `eth_feeHistory` requests have been made.\n * @param totalNumberOfBlocks - The total number of desired blocks after all `eth_feeHistory`\n * requests have been made.\n * @returns A set of arguments that can be used to make requests to `eth_feeHistory` in order to\n * retrieve all of the requested blocks, sorted from oldest block to newest block.\n */\nfunction determineRequestChunkSpecifiers(\n endBlockNumber: BN,\n totalNumberOfBlocks: number,\n): RequestChunkSpecifier[] {\n if (endBlockNumber.lt(new BN(totalNumberOfBlocks))) {\n totalNumberOfBlocks = endBlockNumber.toNumber();\n }\n\n const specifiers = [];\n for (\n let chunkStartBlockNumber = endBlockNumber.subn(totalNumberOfBlocks);\n chunkStartBlockNumber.lt(endBlockNumber);\n chunkStartBlockNumber = chunkStartBlockNumber.addn(\n MAX_NUMBER_OF_BLOCKS_PER_ETH_FEE_HISTORY_CALL,\n )\n ) {\n const distanceToEnd = endBlockNumber.sub(chunkStartBlockNumber).toNumber();\n const numberOfBlocks =\n distanceToEnd < MAX_NUMBER_OF_BLOCKS_PER_ETH_FEE_HISTORY_CALL\n ? distanceToEnd\n : MAX_NUMBER_OF_BLOCKS_PER_ETH_FEE_HISTORY_CALL;\n const chunkEndBlockNumber = chunkStartBlockNumber.addn(numberOfBlocks);\n specifiers.push({ numberOfBlocks, endBlockNumber: chunkEndBlockNumber });\n }\n return specifiers;\n}\n"]} \ No newline at end of file diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory.d.ts b/dist/gas/fetchGasEstimatesViaEthFeeHistory.d.ts deleted file mode 100644 index 8b4ca02c5d..0000000000 --- a/dist/gas/fetchGasEstimatesViaEthFeeHistory.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { GasFeeEstimates } from './GasFeeController'; -import { EthQuery } from './fetchGasEstimatesViaEthFeeHistory/types'; -/** - * Generates gas fee estimates based on gas fees that have been used in the recent past so that - * those estimates can be displayed to users. - * - * To produce the estimates, the last 5 blocks are read from the network, and for each block, the - * priority fees for transactions at the 10th, 20th, and 30th percentiles are also read (here - * "percentile" signifies the level at which those transactions contribute to the overall gas used - * for the block, where higher percentiles correspond to higher fees). This information is used to - * calculate reasonable max priority and max fees for three different priority levels (higher - * priority = higher fee). - * - * Note that properties are returned for other data that are normally obtained via the API; however, - * to prevent extra requests to Infura, these properties are empty. - * - * @param ethQuery - An EthQuery instance. - * @returns Base and priority fee estimates, categorized by priority level, as well as an estimate - * for the next block's base fee. - */ -export default function fetchGasEstimatesViaEthFeeHistory(ethQuery: EthQuery): Promise; diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory.js b/dist/gas/fetchGasEstimatesViaEthFeeHistory.js deleted file mode 100644 index 9669c69093..0000000000 --- a/dist/gas/fetchGasEstimatesViaEthFeeHistory.js +++ /dev/null @@ -1,53 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const ethjs_unit_1 = require("ethjs-unit"); -const constants_1 = require("../constants"); -const fetchBlockFeeHistory_1 = __importDefault(require("./fetchBlockFeeHistory")); -const fetchLatestBlock_1 = __importDefault(require("./fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock")); -const calculateGasFeeEstimatesForPriorityLevels_1 = __importDefault(require("./fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels")); -/** - * Generates gas fee estimates based on gas fees that have been used in the recent past so that - * those estimates can be displayed to users. - * - * To produce the estimates, the last 5 blocks are read from the network, and for each block, the - * priority fees for transactions at the 10th, 20th, and 30th percentiles are also read (here - * "percentile" signifies the level at which those transactions contribute to the overall gas used - * for the block, where higher percentiles correspond to higher fees). This information is used to - * calculate reasonable max priority and max fees for three different priority levels (higher - * priority = higher fee). - * - * Note that properties are returned for other data that are normally obtained via the API; however, - * to prevent extra requests to Infura, these properties are empty. - * - * @param ethQuery - An EthQuery instance. - * @returns Base and priority fee estimates, categorized by priority level, as well as an estimate - * for the next block's base fee. - */ -function fetchGasEstimatesViaEthFeeHistory(ethQuery) { - return __awaiter(this, void 0, void 0, function* () { - const latestBlock = yield (0, fetchLatestBlock_1.default)(ethQuery); - const blocks = yield (0, fetchBlockFeeHistory_1.default)({ - ethQuery, - endBlock: latestBlock.number, - numberOfBlocks: 5, - percentiles: [10, 20, 30], - }); - const estimatedBaseFee = (0, ethjs_unit_1.fromWei)(latestBlock.baseFeePerGas, constants_1.GWEI); - const levelSpecificEstimates = (0, calculateGasFeeEstimatesForPriorityLevels_1.default)(blocks); - return Object.assign(Object.assign({}, levelSpecificEstimates), { estimatedBaseFee, historicalBaseFeeRange: null, baseFeeTrend: null, latestPriorityFeeRange: null, historicalPriorityFeeRange: null, priorityFeeTrend: null, networkCongestion: null }); - }); -} -exports.default = fetchGasEstimatesViaEthFeeHistory; -//# sourceMappingURL=fetchGasEstimatesViaEthFeeHistory.js.map \ No newline at end of file diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory.js.map b/dist/gas/fetchGasEstimatesViaEthFeeHistory.js.map deleted file mode 100644 index c7fbbbb554..0000000000 --- a/dist/gas/fetchGasEstimatesViaEthFeeHistory.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"fetchGasEstimatesViaEthFeeHistory.js","sourceRoot":"","sources":["../../src/gas/fetchGasEstimatesViaEthFeeHistory.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,2CAAqC;AACrC,4CAAoC;AAGpC,kFAA0D;AAC1D,4GAAoF;AACpF,8JAAsI;AAEtI;;;;;;;;;;;;;;;;;GAiBG;AACH,SAA8B,iCAAiC,CAC7D,QAAkB;;QAElB,MAAM,WAAW,GAAG,MAAM,IAAA,0BAAgB,EAAC,QAAQ,CAAC,CAAC;QACrD,MAAM,MAAM,GAAG,MAAM,IAAA,8BAAoB,EAAC;YACxC,QAAQ;YACR,QAAQ,EAAE,WAAW,CAAC,MAAM;YAC5B,cAAc,EAAE,CAAC;YACjB,WAAW,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;SAC1B,CAAC,CAAC;QACH,MAAM,gBAAgB,GAAG,IAAA,oBAAO,EAAC,WAAW,CAAC,aAAa,EAAE,gBAAI,CAAC,CAAC;QAElE,MAAM,sBAAsB,GAC1B,IAAA,mDAAyC,EAAC,MAAM,CAAC,CAAC;QAEpD,uCACK,sBAAsB,KACzB,gBAAgB,EAChB,sBAAsB,EAAE,IAAI,EAC5B,YAAY,EAAE,IAAI,EAClB,sBAAsB,EAAE,IAAI,EAC5B,0BAA0B,EAAE,IAAI,EAChC,gBAAgB,EAAE,IAAI,EACtB,iBAAiB,EAAE,IAAI,IACvB;IACJ,CAAC;CAAA;AAzBD,oDAyBC","sourcesContent":["import { fromWei } from 'ethjs-unit';\nimport { GWEI } from '../constants';\nimport { GasFeeEstimates } from './GasFeeController';\nimport { EthQuery } from './fetchGasEstimatesViaEthFeeHistory/types';\nimport fetchBlockFeeHistory from './fetchBlockFeeHistory';\nimport fetchLatestBlock from './fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock';\nimport calculateGasFeeEstimatesForPriorityLevels from './fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels';\n\n/**\n * Generates gas fee estimates based on gas fees that have been used in the recent past so that\n * those estimates can be displayed to users.\n *\n * To produce the estimates, the last 5 blocks are read from the network, and for each block, the\n * priority fees for transactions at the 10th, 20th, and 30th percentiles are also read (here\n * \"percentile\" signifies the level at which those transactions contribute to the overall gas used\n * for the block, where higher percentiles correspond to higher fees). This information is used to\n * calculate reasonable max priority and max fees for three different priority levels (higher\n * priority = higher fee).\n *\n * Note that properties are returned for other data that are normally obtained via the API; however,\n * to prevent extra requests to Infura, these properties are empty.\n *\n * @param ethQuery - An EthQuery instance.\n * @returns Base and priority fee estimates, categorized by priority level, as well as an estimate\n * for the next block's base fee.\n */\nexport default async function fetchGasEstimatesViaEthFeeHistory(\n ethQuery: EthQuery,\n): Promise {\n const latestBlock = await fetchLatestBlock(ethQuery);\n const blocks = await fetchBlockFeeHistory({\n ethQuery,\n endBlock: latestBlock.number,\n numberOfBlocks: 5,\n percentiles: [10, 20, 30],\n });\n const estimatedBaseFee = fromWei(latestBlock.baseFeePerGas, GWEI);\n\n const levelSpecificEstimates =\n calculateGasFeeEstimatesForPriorityLevels(blocks);\n\n return {\n ...levelSpecificEstimates,\n estimatedBaseFee,\n historicalBaseFeeRange: null,\n baseFeeTrend: null,\n latestPriorityFeeRange: null,\n historicalPriorityFeeRange: null,\n priorityFeeTrend: null,\n networkCongestion: null,\n };\n}\n"]} \ No newline at end of file diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.d.ts b/dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.d.ts deleted file mode 100644 index df00bb5ccf..0000000000 --- a/dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { GasFeeEstimates } from '../GasFeeController'; -import { FeeHistoryBlock } from '../fetchBlockFeeHistory'; -export declare type PriorityLevel = typeof PRIORITY_LEVELS[number]; -export declare type Percentile = typeof PRIORITY_LEVEL_PERCENTILES[number]; -declare const PRIORITY_LEVELS: readonly ["low", "medium", "high"]; -declare const PRIORITY_LEVEL_PERCENTILES: readonly [10, 20, 30]; -/** - * Calculates a set of estimates suitable for different priority levels based on the data returned - * by `eth_feeHistory`. - * - * @param blocks - A set of blocks populated with data for priority fee percentiles 10, 20, and 30, - * obtained via {@link BlockFeeHistoryDatasetFetcher}. - * @returns The estimates. - */ -export default function calculateGasFeeEstimatesForPriorityLevels(blocks: FeeHistoryBlock[]): Pick; -export {}; diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.js b/dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.js deleted file mode 100644 index c306543241..0000000000 --- a/dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.js +++ /dev/null @@ -1,89 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const ethereumjs_util_1 = require("ethereumjs-util"); -const ethjs_unit_1 = require("ethjs-unit"); -const constants_1 = require("../../constants"); -const medianOf_1 = __importDefault(require("./medianOf")); -const PRIORITY_LEVELS = ['low', 'medium', 'high']; -const PRIORITY_LEVEL_PERCENTILES = [10, 20, 30]; -const SETTINGS_BY_PRIORITY_LEVEL = { - low: { - percentile: 10, - baseFeePercentageMultiplier: new ethereumjs_util_1.BN(110), - priorityFeePercentageMultiplier: new ethereumjs_util_1.BN(94), - minSuggestedMaxPriorityFeePerGas: new ethereumjs_util_1.BN(1000000000), - estimatedWaitTimes: { - minWaitTimeEstimate: 15000, - maxWaitTimeEstimate: 30000, - }, - }, - medium: { - percentile: 20, - baseFeePercentageMultiplier: new ethereumjs_util_1.BN(120), - priorityFeePercentageMultiplier: new ethereumjs_util_1.BN(97), - minSuggestedMaxPriorityFeePerGas: new ethereumjs_util_1.BN(1500000000), - estimatedWaitTimes: { - minWaitTimeEstimate: 15000, - maxWaitTimeEstimate: 45000, - }, - }, - high: { - percentile: 30, - baseFeePercentageMultiplier: new ethereumjs_util_1.BN(125), - priorityFeePercentageMultiplier: new ethereumjs_util_1.BN(98), - minSuggestedMaxPriorityFeePerGas: new ethereumjs_util_1.BN(2000000000), - estimatedWaitTimes: { - minWaitTimeEstimate: 15000, - maxWaitTimeEstimate: 60000, - }, - }, -}; -/** - * Calculates a set of estimates assigned to a particular priority level based on the data returned - * by `eth_feeHistory`. - * - * @param priorityLevel - The level of fees that dictates how soon a transaction may go through - * ("low", "medium", or "high"). - * @param blocks - A set of blocks as obtained from {@link fetchBlockFeeHistory}. - * @returns The estimates. - */ -function calculateEstimatesForPriorityLevel(priorityLevel, blocks) { - const settings = SETTINGS_BY_PRIORITY_LEVEL[priorityLevel]; - const latestBaseFeePerGas = blocks[blocks.length - 1].baseFeePerGas; - const adjustedBaseFee = latestBaseFeePerGas - .mul(settings.baseFeePercentageMultiplier) - .divn(100); - const priorityFees = blocks - .map((block) => { - return 'priorityFeesByPercentile' in block - ? block.priorityFeesByPercentile[settings.percentile] - : null; - }) - .filter(ethereumjs_util_1.BN.isBN); - const medianPriorityFee = (0, medianOf_1.default)(priorityFees); - const adjustedPriorityFee = medianPriorityFee - .mul(settings.priorityFeePercentageMultiplier) - .divn(100); - const suggestedMaxPriorityFeePerGas = ethereumjs_util_1.BN.max(adjustedPriorityFee, settings.minSuggestedMaxPriorityFeePerGas); - const suggestedMaxFeePerGas = adjustedBaseFee.add(suggestedMaxPriorityFeePerGas); - return Object.assign(Object.assign({}, settings.estimatedWaitTimes), { suggestedMaxPriorityFeePerGas: (0, ethjs_unit_1.fromWei)(suggestedMaxPriorityFeePerGas, constants_1.GWEI), suggestedMaxFeePerGas: (0, ethjs_unit_1.fromWei)(suggestedMaxFeePerGas, constants_1.GWEI) }); -} -/** - * Calculates a set of estimates suitable for different priority levels based on the data returned - * by `eth_feeHistory`. - * - * @param blocks - A set of blocks populated with data for priority fee percentiles 10, 20, and 30, - * obtained via {@link BlockFeeHistoryDatasetFetcher}. - * @returns The estimates. - */ -function calculateGasFeeEstimatesForPriorityLevels(blocks) { - return PRIORITY_LEVELS.reduce((obj, priorityLevel) => { - const gasEstimatesForPriorityLevel = calculateEstimatesForPriorityLevel(priorityLevel, blocks); - return Object.assign(Object.assign({}, obj), { [priorityLevel]: gasEstimatesForPriorityLevel }); - }, {}); -} -exports.default = calculateGasFeeEstimatesForPriorityLevels; -//# sourceMappingURL=calculateGasFeeEstimatesForPriorityLevels.js.map \ No newline at end of file diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.js.map b/dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.js.map deleted file mode 100644 index 1803a010fa..0000000000 --- a/dist/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"calculateGasFeeEstimatesForPriorityLevels.js","sourceRoot":"","sources":["../../../src/gas/fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels.ts"],"names":[],"mappings":"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","sourcesContent":["import { BN } from 'ethereumjs-util';\nimport { fromWei } from 'ethjs-unit';\nimport { Eip1559GasFee, GasFeeEstimates } from '../GasFeeController';\nimport { FeeHistoryBlock } from '../fetchBlockFeeHistory';\nimport { GWEI } from '../../constants';\nimport medianOf from './medianOf';\n\nexport type PriorityLevel = typeof PRIORITY_LEVELS[number];\nexport type Percentile = typeof PRIORITY_LEVEL_PERCENTILES[number];\n\nconst PRIORITY_LEVELS = ['low', 'medium', 'high'] as const;\nconst PRIORITY_LEVEL_PERCENTILES = [10, 20, 30] as const;\nconst SETTINGS_BY_PRIORITY_LEVEL = {\n low: {\n percentile: 10 as Percentile,\n baseFeePercentageMultiplier: new BN(110),\n priorityFeePercentageMultiplier: new BN(94),\n minSuggestedMaxPriorityFeePerGas: new BN(1_000_000_000),\n estimatedWaitTimes: {\n minWaitTimeEstimate: 15_000,\n maxWaitTimeEstimate: 30_000,\n },\n },\n medium: {\n percentile: 20 as Percentile,\n baseFeePercentageMultiplier: new BN(120),\n priorityFeePercentageMultiplier: new BN(97),\n minSuggestedMaxPriorityFeePerGas: new BN(1_500_000_000),\n estimatedWaitTimes: {\n minWaitTimeEstimate: 15_000,\n maxWaitTimeEstimate: 45_000,\n },\n },\n high: {\n percentile: 30 as Percentile,\n baseFeePercentageMultiplier: new BN(125),\n priorityFeePercentageMultiplier: new BN(98),\n minSuggestedMaxPriorityFeePerGas: new BN(2_000_000_000),\n estimatedWaitTimes: {\n minWaitTimeEstimate: 15_000,\n maxWaitTimeEstimate: 60_000,\n },\n },\n};\n\n/**\n * Calculates a set of estimates assigned to a particular priority level based on the data returned\n * by `eth_feeHistory`.\n *\n * @param priorityLevel - The level of fees that dictates how soon a transaction may go through\n * (\"low\", \"medium\", or \"high\").\n * @param blocks - A set of blocks as obtained from {@link fetchBlockFeeHistory}.\n * @returns The estimates.\n */\nfunction calculateEstimatesForPriorityLevel(\n priorityLevel: PriorityLevel,\n blocks: FeeHistoryBlock[],\n): Eip1559GasFee {\n const settings = SETTINGS_BY_PRIORITY_LEVEL[priorityLevel];\n\n const latestBaseFeePerGas = blocks[blocks.length - 1].baseFeePerGas;\n\n const adjustedBaseFee = latestBaseFeePerGas\n .mul(settings.baseFeePercentageMultiplier)\n .divn(100);\n const priorityFees = blocks\n .map((block) => {\n return 'priorityFeesByPercentile' in block\n ? block.priorityFeesByPercentile[settings.percentile]\n : null;\n })\n .filter(BN.isBN);\n const medianPriorityFee = medianOf(priorityFees);\n const adjustedPriorityFee = medianPriorityFee\n .mul(settings.priorityFeePercentageMultiplier)\n .divn(100);\n\n const suggestedMaxPriorityFeePerGas = BN.max(\n adjustedPriorityFee,\n settings.minSuggestedMaxPriorityFeePerGas,\n );\n const suggestedMaxFeePerGas = adjustedBaseFee.add(\n suggestedMaxPriorityFeePerGas,\n );\n\n return {\n ...settings.estimatedWaitTimes,\n suggestedMaxPriorityFeePerGas: fromWei(suggestedMaxPriorityFeePerGas, GWEI),\n suggestedMaxFeePerGas: fromWei(suggestedMaxFeePerGas, GWEI),\n };\n}\n\n/**\n * Calculates a set of estimates suitable for different priority levels based on the data returned\n * by `eth_feeHistory`.\n *\n * @param blocks - A set of blocks populated with data for priority fee percentiles 10, 20, and 30,\n * obtained via {@link BlockFeeHistoryDatasetFetcher}.\n * @returns The estimates.\n */\nexport default function calculateGasFeeEstimatesForPriorityLevels(\n blocks: FeeHistoryBlock[],\n): Pick {\n return PRIORITY_LEVELS.reduce((obj, priorityLevel) => {\n const gasEstimatesForPriorityLevel = calculateEstimatesForPriorityLevel(\n priorityLevel,\n blocks,\n );\n return { ...obj, [priorityLevel]: gasEstimatesForPriorityLevel };\n }, {} as Pick);\n}\n"]} \ No newline at end of file diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.d.ts b/dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.d.ts deleted file mode 100644 index a1cf6f5773..0000000000 --- a/dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { EthBlock, EthQuery } from './types'; -/** - * Returns information about the latest completed block. - * - * @param ethQuery - An EthQuery instance - * @param includeFullTransactionData - Whether or not to include all data for transactions as - * opposed to merely hashes. False by default. - * @returns The block. - */ -export default function fetchLatestBlock(ethQuery: EthQuery, includeFullTransactionData?: boolean): Promise; diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.js b/dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.js deleted file mode 100644 index 99b0fb7475..0000000000 --- a/dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.js +++ /dev/null @@ -1,32 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const util_1 = require("../../util"); -/** - * Returns information about the latest completed block. - * - * @param ethQuery - An EthQuery instance - * @param includeFullTransactionData - Whether or not to include all data for transactions as - * opposed to merely hashes. False by default. - * @returns The block. - */ -function fetchLatestBlock(ethQuery, includeFullTransactionData = false) { - return __awaiter(this, void 0, void 0, function* () { - const blockNumber = yield (0, util_1.query)(ethQuery, 'blockNumber'); - const block = yield (0, util_1.query)(ethQuery, 'getBlockByNumber', [ - blockNumber, - includeFullTransactionData, - ]); - return Object.assign(Object.assign({}, block), { number: (0, util_1.fromHex)(block.number), baseFeePerGas: (0, util_1.fromHex)(block.baseFeePerGas) }); - }); -} -exports.default = fetchLatestBlock; -//# sourceMappingURL=fetchLatestBlock.js.map \ No newline at end of file diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.js.map b/dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.js.map deleted file mode 100644 index 554499bdec..0000000000 --- a/dist/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"fetchLatestBlock.js","sourceRoot":"","sources":["../../../src/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,qCAA4C;AAG5C;;;;;;;GAOG;AACH,SAA8B,gBAAgB,CAC5C,QAAkB,EAClB,0BAA0B,GAAG,KAAK;;QAElC,MAAM,WAAW,GAAG,MAAM,IAAA,YAAK,EAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;QACzD,MAAM,KAAK,GAAG,MAAM,IAAA,YAAK,EAAC,QAAQ,EAAE,kBAAkB,EAAE;YACtD,WAAW;YACX,0BAA0B;SAC3B,CAAC,CAAC;QACH,uCACK,KAAK,KACR,MAAM,EAAE,IAAA,cAAO,EAAC,KAAK,CAAC,MAAM,CAAC,EAC7B,aAAa,EAAE,IAAA,cAAO,EAAC,KAAK,CAAC,aAAa,CAAC,IAC3C;IACJ,CAAC;CAAA;AAdD,mCAcC","sourcesContent":["import { query, fromHex } from '../../util';\nimport { EthBlock, EthQuery } from './types';\n\n/**\n * Returns information about the latest completed block.\n *\n * @param ethQuery - An EthQuery instance\n * @param includeFullTransactionData - Whether or not to include all data for transactions as\n * opposed to merely hashes. False by default.\n * @returns The block.\n */\nexport default async function fetchLatestBlock(\n ethQuery: EthQuery,\n includeFullTransactionData = false,\n): Promise {\n const blockNumber = await query(ethQuery, 'blockNumber');\n const block = await query(ethQuery, 'getBlockByNumber', [\n blockNumber,\n includeFullTransactionData,\n ]);\n return {\n ...block,\n number: fromHex(block.number),\n baseFeePerGas: fromHex(block.baseFeePerGas),\n };\n}\n"]} \ No newline at end of file diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.d.ts b/dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.d.ts deleted file mode 100644 index 2079cec1e9..0000000000 --- a/dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -/// -import { BN } from 'ethereumjs-util'; -/** - * Finds the median among a list of numbers. Note that this is different from the implementation - * in the MetaSwap API, as we want to hold to using BN as much as possible. - * - * @param numbers - A list of numbers, as BNs. Will be sorted automatically if unsorted. - * @returns The median number. - */ -export default function medianOf(numbers: BN[]): BN; diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.js b/dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.js deleted file mode 100644 index 31a1a71423..0000000000 --- a/dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.js +++ /dev/null @@ -1,17 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -/** - * Finds the median among a list of numbers. Note that this is different from the implementation - * in the MetaSwap API, as we want to hold to using BN as much as possible. - * - * @param numbers - A list of numbers, as BNs. Will be sorted automatically if unsorted. - * @returns The median number. - */ -function medianOf(numbers) { - const sortedNumbers = numbers.slice().sort((a, b) => a.cmp(b)); - const len = sortedNumbers.length; - const index = Math.floor((len - 1) / 2); - return sortedNumbers[index]; -} -exports.default = medianOf; -//# sourceMappingURL=medianOf.js.map \ No newline at end of file diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.js.map b/dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.js.map deleted file mode 100644 index 2230e3972c..0000000000 --- a/dist/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"medianOf.js","sourceRoot":"","sources":["../../../src/gas/fetchGasEstimatesViaEthFeeHistory/medianOf.ts"],"names":[],"mappings":";;AAEA;;;;;;GAMG;AACH,SAAwB,QAAQ,CAAC,OAAa;IAC5C,MAAM,aAAa,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/D,MAAM,GAAG,GAAG,aAAa,CAAC,MAAM,CAAC;IACjC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACxC,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC;AALD,2BAKC","sourcesContent":["import { BN } from 'ethereumjs-util';\n\n/**\n * Finds the median among a list of numbers. Note that this is different from the implementation\n * in the MetaSwap API, as we want to hold to using BN as much as possible.\n *\n * @param numbers - A list of numbers, as BNs. Will be sorted automatically if unsorted.\n * @returns The median number.\n */\nexport default function medianOf(numbers: BN[]): BN {\n const sortedNumbers = numbers.slice().sort((a, b) => a.cmp(b));\n const len = sortedNumbers.length;\n const index = Math.floor((len - 1) / 2);\n return sortedNumbers[index];\n}\n"]} \ No newline at end of file diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/types.d.ts b/dist/gas/fetchGasEstimatesViaEthFeeHistory/types.d.ts deleted file mode 100644 index ddf9ad3a22..0000000000 --- a/dist/gas/fetchGasEstimatesViaEthFeeHistory/types.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -/// -import { BN } from 'ethereumjs-util'; -export declare type EthBlock = { - number: BN; - baseFeePerGas: BN; -}; -export declare type EthQuery = { - getBlockByNumber: (blockNumber: BN | 'latest' | 'earliest' | 'pending') => Promise; -}; -export declare type FeeRange = [string, string]; diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/types.js b/dist/gas/fetchGasEstimatesViaEthFeeHistory/types.js deleted file mode 100644 index 11e638d1ee..0000000000 --- a/dist/gas/fetchGasEstimatesViaEthFeeHistory/types.js +++ /dev/null @@ -1,3 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -//# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/dist/gas/fetchGasEstimatesViaEthFeeHistory/types.js.map b/dist/gas/fetchGasEstimatesViaEthFeeHistory/types.js.map deleted file mode 100644 index 5081b9250f..0000000000 --- a/dist/gas/fetchGasEstimatesViaEthFeeHistory/types.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/gas/fetchGasEstimatesViaEthFeeHistory/types.ts"],"names":[],"mappings":"","sourcesContent":["import { BN } from 'ethereumjs-util';\n\nexport type EthBlock = {\n number: BN;\n baseFeePerGas: BN;\n};\n\nexport type EthQuery = {\n getBlockByNumber: (\n blockNumber: BN | 'latest' | 'earliest' | 'pending',\n ) => Promise;\n};\n\nexport type FeeRange = [string, string];\n"]} \ No newline at end of file diff --git a/dist/gas/gas-util.d.ts b/dist/gas/gas-util.d.ts deleted file mode 100644 index 05ac93f4f8..0000000000 --- a/dist/gas/gas-util.d.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { GasFeeEstimates, EthGasPriceEstimate, EstimatedGasFeeTimeBounds, LegacyGasPriceEstimate } from './GasFeeController'; -/** - * Convert a decimal GWEI value to a decimal string rounded to the nearest WEI. - * - * @param n - The input GWEI amount, as a decimal string or a number. - * @returns The decimal string GWEI amount. - */ -export declare function normalizeGWEIDecimalNumbers(n: string | number): any; -/** - * Fetch gas estimates from the given URL. - * - * @param url - The gas estimate URL. - * @param clientId - The client ID used to identify to the API who is asking for estimates. - * @returns The gas estimates. - */ -export declare function fetchGasEstimates(url: string, clientId?: string): Promise; -/** - * Hit the legacy MetaSwaps gasPrices estimate api and return the low, medium - * high values from that API. - * - * @param url - The URL to fetch gas price estimates from. - * @param clientId - The client ID used to identify to the API who is asking for estimates. - * @returns The gas price estimates. - */ -export declare function fetchLegacyGasPriceEstimates(url: string, clientId?: string): Promise; -/** - * Get a gas price estimate from the network using the `eth_gasPrice` method. - * - * @param ethQuery - The EthQuery instance to call the network with. - * @returns A gas price estimate. - */ -export declare function fetchEthGasPriceEstimate(ethQuery: any): Promise; -/** - * Estimate the time it will take for a transaction to be confirmed. - * - * @param maxPriorityFeePerGas - The max priority fee per gas. - * @param maxFeePerGas - The max fee per gas. - * @param gasFeeEstimates - The gas fee estimates. - * @returns The estimated lower and upper bounds for when this transaction will be confirmed. - */ -export declare function calculateTimeEstimate(maxPriorityFeePerGas: string, maxFeePerGas: string, gasFeeEstimates: GasFeeEstimates): EstimatedGasFeeTimeBounds; diff --git a/dist/gas/gas-util.js b/dist/gas/gas-util.js deleted file mode 100644 index 143a3e9ad3..0000000000 --- a/dist/gas/gas-util.js +++ /dev/null @@ -1,140 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.calculateTimeEstimate = exports.fetchEthGasPriceEstimate = exports.fetchLegacyGasPriceEstimates = exports.fetchGasEstimates = exports.normalizeGWEIDecimalNumbers = void 0; -const ethereumjs_util_1 = require("ethereumjs-util"); -const util_1 = require("../util"); -const makeClientIdHeader = (clientId) => ({ 'X-Client-Id': clientId }); -/** - * Convert a decimal GWEI value to a decimal string rounded to the nearest WEI. - * - * @param n - The input GWEI amount, as a decimal string or a number. - * @returns The decimal string GWEI amount. - */ -function normalizeGWEIDecimalNumbers(n) { - const numberAsWEIHex = (0, util_1.gweiDecToWEIBN)(n).toString(16); - const numberAsGWEI = (0, util_1.weiHexToGweiDec)(numberAsWEIHex).toString(10); - return numberAsGWEI; -} -exports.normalizeGWEIDecimalNumbers = normalizeGWEIDecimalNumbers; -/** - * Fetch gas estimates from the given URL. - * - * @param url - The gas estimate URL. - * @param clientId - The client ID used to identify to the API who is asking for estimates. - * @returns The gas estimates. - */ -function fetchGasEstimates(url, clientId) { - return __awaiter(this, void 0, void 0, function* () { - const estimates = yield (0, util_1.handleFetch)(url, clientId ? { headers: makeClientIdHeader(clientId) } : undefined); - return { - low: Object.assign(Object.assign({}, estimates.low), { suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(estimates.low.suggestedMaxPriorityFeePerGas), suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(estimates.low.suggestedMaxFeePerGas) }), - medium: Object.assign(Object.assign({}, estimates.medium), { suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(estimates.medium.suggestedMaxPriorityFeePerGas), suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(estimates.medium.suggestedMaxFeePerGas) }), - high: Object.assign(Object.assign({}, estimates.high), { suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(estimates.high.suggestedMaxPriorityFeePerGas), suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(estimates.high.suggestedMaxFeePerGas) }), - estimatedBaseFee: normalizeGWEIDecimalNumbers(estimates.estimatedBaseFee), - historicalBaseFeeRange: estimates.historicalBaseFeeRange, - baseFeeTrend: estimates.baseFeeTrend, - latestPriorityFeeRange: estimates.latestPriorityFeeRange, - historicalPriorityFeeRange: estimates.historicalPriorityFeeRange, - priorityFeeTrend: estimates.priorityFeeTrend, - networkCongestion: estimates.networkCongestion, - }; - }); -} -exports.fetchGasEstimates = fetchGasEstimates; -/** - * Hit the legacy MetaSwaps gasPrices estimate api and return the low, medium - * high values from that API. - * - * @param url - The URL to fetch gas price estimates from. - * @param clientId - The client ID used to identify to the API who is asking for estimates. - * @returns The gas price estimates. - */ -function fetchLegacyGasPriceEstimates(url, clientId) { - return __awaiter(this, void 0, void 0, function* () { - const result = yield (0, util_1.handleFetch)(url, { - referrer: url, - referrerPolicy: 'no-referrer-when-downgrade', - method: 'GET', - mode: 'cors', - headers: Object.assign({ 'Content-Type': 'application/json' }, (clientId && makeClientIdHeader(clientId))), - }); - return { - low: result.SafeGasPrice, - medium: result.ProposeGasPrice, - high: result.FastGasPrice, - }; - }); -} -exports.fetchLegacyGasPriceEstimates = fetchLegacyGasPriceEstimates; -/** - * Get a gas price estimate from the network using the `eth_gasPrice` method. - * - * @param ethQuery - The EthQuery instance to call the network with. - * @returns A gas price estimate. - */ -function fetchEthGasPriceEstimate(ethQuery) { - return __awaiter(this, void 0, void 0, function* () { - const gasPrice = yield (0, util_1.query)(ethQuery, 'gasPrice'); - return { - gasPrice: (0, util_1.weiHexToGweiDec)(gasPrice).toString(), - }; - }); -} -exports.fetchEthGasPriceEstimate = fetchEthGasPriceEstimate; -/** - * Estimate the time it will take for a transaction to be confirmed. - * - * @param maxPriorityFeePerGas - The max priority fee per gas. - * @param maxFeePerGas - The max fee per gas. - * @param gasFeeEstimates - The gas fee estimates. - * @returns The estimated lower and upper bounds for when this transaction will be confirmed. - */ -function calculateTimeEstimate(maxPriorityFeePerGas, maxFeePerGas, gasFeeEstimates) { - const { low, medium, high, estimatedBaseFee } = gasFeeEstimates; - const maxPriorityFeePerGasInWEI = (0, util_1.gweiDecToWEIBN)(maxPriorityFeePerGas); - const maxFeePerGasInWEI = (0, util_1.gweiDecToWEIBN)(maxFeePerGas); - const estimatedBaseFeeInWEI = (0, util_1.gweiDecToWEIBN)(estimatedBaseFee); - const effectiveMaxPriorityFee = ethereumjs_util_1.BN.min(maxPriorityFeePerGasInWEI, maxFeePerGasInWEI.sub(estimatedBaseFeeInWEI)); - const lowMaxPriorityFeeInWEI = (0, util_1.gweiDecToWEIBN)(low.suggestedMaxPriorityFeePerGas); - const mediumMaxPriorityFeeInWEI = (0, util_1.gweiDecToWEIBN)(medium.suggestedMaxPriorityFeePerGas); - const highMaxPriorityFeeInWEI = (0, util_1.gweiDecToWEIBN)(high.suggestedMaxPriorityFeePerGas); - let lowerTimeBound; - let upperTimeBound; - if (effectiveMaxPriorityFee.lt(lowMaxPriorityFeeInWEI)) { - lowerTimeBound = null; - upperTimeBound = 'unknown'; - } - else if (effectiveMaxPriorityFee.gte(lowMaxPriorityFeeInWEI) && - effectiveMaxPriorityFee.lt(mediumMaxPriorityFeeInWEI)) { - lowerTimeBound = low.minWaitTimeEstimate; - upperTimeBound = low.maxWaitTimeEstimate; - } - else if (effectiveMaxPriorityFee.gte(mediumMaxPriorityFeeInWEI) && - effectiveMaxPriorityFee.lt(highMaxPriorityFeeInWEI)) { - lowerTimeBound = medium.minWaitTimeEstimate; - upperTimeBound = medium.maxWaitTimeEstimate; - } - else if (effectiveMaxPriorityFee.eq(highMaxPriorityFeeInWEI)) { - lowerTimeBound = high.minWaitTimeEstimate; - upperTimeBound = high.maxWaitTimeEstimate; - } - else { - lowerTimeBound = 0; - upperTimeBound = high.maxWaitTimeEstimate; - } - return { - lowerTimeBound, - upperTimeBound, - }; -} -exports.calculateTimeEstimate = calculateTimeEstimate; -//# sourceMappingURL=gas-util.js.map \ No newline at end of file diff --git a/dist/gas/gas-util.js.map b/dist/gas/gas-util.js.map deleted file mode 100644 index 582f092840..0000000000 --- a/dist/gas/gas-util.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"gas-util.js","sourceRoot":"","sources":["../../src/gas/gas-util.ts"],"names":[],"mappings":"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","sourcesContent":["import { BN } from 'ethereumjs-util';\nimport { query, handleFetch, gweiDecToWEIBN, weiHexToGweiDec } from '../util';\nimport {\n GasFeeEstimates,\n EthGasPriceEstimate,\n EstimatedGasFeeTimeBounds,\n unknownString,\n LegacyGasPriceEstimate,\n} from './GasFeeController';\n\nconst makeClientIdHeader = (clientId: string) => ({ 'X-Client-Id': clientId });\n\n/**\n * Convert a decimal GWEI value to a decimal string rounded to the nearest WEI.\n *\n * @param n - The input GWEI amount, as a decimal string or a number.\n * @returns The decimal string GWEI amount.\n */\nexport function normalizeGWEIDecimalNumbers(n: string | number) {\n const numberAsWEIHex = gweiDecToWEIBN(n).toString(16);\n const numberAsGWEI = weiHexToGweiDec(numberAsWEIHex).toString(10);\n return numberAsGWEI;\n}\n\n/**\n * Fetch gas estimates from the given URL.\n *\n * @param url - The gas estimate URL.\n * @param clientId - The client ID used to identify to the API who is asking for estimates.\n * @returns The gas estimates.\n */\nexport async function fetchGasEstimates(\n url: string,\n clientId?: string,\n): Promise {\n const estimates = await handleFetch(\n url,\n clientId ? { headers: makeClientIdHeader(clientId) } : undefined,\n );\n return {\n low: {\n ...estimates.low,\n suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.low.suggestedMaxPriorityFeePerGas,\n ),\n suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.low.suggestedMaxFeePerGas,\n ),\n },\n medium: {\n ...estimates.medium,\n suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.medium.suggestedMaxPriorityFeePerGas,\n ),\n suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.medium.suggestedMaxFeePerGas,\n ),\n },\n high: {\n ...estimates.high,\n suggestedMaxPriorityFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.high.suggestedMaxPriorityFeePerGas,\n ),\n suggestedMaxFeePerGas: normalizeGWEIDecimalNumbers(\n estimates.high.suggestedMaxFeePerGas,\n ),\n },\n estimatedBaseFee: normalizeGWEIDecimalNumbers(estimates.estimatedBaseFee),\n historicalBaseFeeRange: estimates.historicalBaseFeeRange,\n baseFeeTrend: estimates.baseFeeTrend,\n latestPriorityFeeRange: estimates.latestPriorityFeeRange,\n historicalPriorityFeeRange: estimates.historicalPriorityFeeRange,\n priorityFeeTrend: estimates.priorityFeeTrend,\n networkCongestion: estimates.networkCongestion,\n };\n}\n\n/**\n * Hit the legacy MetaSwaps gasPrices estimate api and return the low, medium\n * high values from that API.\n *\n * @param url - The URL to fetch gas price estimates from.\n * @param clientId - The client ID used to identify to the API who is asking for estimates.\n * @returns The gas price estimates.\n */\nexport async function fetchLegacyGasPriceEstimates(\n url: string,\n clientId?: string,\n): Promise {\n const result = await handleFetch(url, {\n referrer: url,\n referrerPolicy: 'no-referrer-when-downgrade',\n method: 'GET',\n mode: 'cors',\n headers: {\n 'Content-Type': 'application/json',\n ...(clientId && makeClientIdHeader(clientId)),\n },\n });\n return {\n low: result.SafeGasPrice,\n medium: result.ProposeGasPrice,\n high: result.FastGasPrice,\n };\n}\n\n/**\n * Get a gas price estimate from the network using the `eth_gasPrice` method.\n *\n * @param ethQuery - The EthQuery instance to call the network with.\n * @returns A gas price estimate.\n */\nexport async function fetchEthGasPriceEstimate(\n ethQuery: any,\n): Promise {\n const gasPrice = await query(ethQuery, 'gasPrice');\n return {\n gasPrice: weiHexToGweiDec(gasPrice).toString(),\n };\n}\n\n/**\n * Estimate the time it will take for a transaction to be confirmed.\n *\n * @param maxPriorityFeePerGas - The max priority fee per gas.\n * @param maxFeePerGas - The max fee per gas.\n * @param gasFeeEstimates - The gas fee estimates.\n * @returns The estimated lower and upper bounds for when this transaction will be confirmed.\n */\nexport function calculateTimeEstimate(\n maxPriorityFeePerGas: string,\n maxFeePerGas: string,\n gasFeeEstimates: GasFeeEstimates,\n): EstimatedGasFeeTimeBounds {\n const { low, medium, high, estimatedBaseFee } = gasFeeEstimates;\n\n const maxPriorityFeePerGasInWEI = gweiDecToWEIBN(maxPriorityFeePerGas);\n const maxFeePerGasInWEI = gweiDecToWEIBN(maxFeePerGas);\n const estimatedBaseFeeInWEI = gweiDecToWEIBN(estimatedBaseFee);\n\n const effectiveMaxPriorityFee = BN.min(\n maxPriorityFeePerGasInWEI,\n maxFeePerGasInWEI.sub(estimatedBaseFeeInWEI),\n );\n\n const lowMaxPriorityFeeInWEI = gweiDecToWEIBN(\n low.suggestedMaxPriorityFeePerGas,\n );\n const mediumMaxPriorityFeeInWEI = gweiDecToWEIBN(\n medium.suggestedMaxPriorityFeePerGas,\n );\n const highMaxPriorityFeeInWEI = gweiDecToWEIBN(\n high.suggestedMaxPriorityFeePerGas,\n );\n\n let lowerTimeBound;\n let upperTimeBound;\n\n if (effectiveMaxPriorityFee.lt(lowMaxPriorityFeeInWEI)) {\n lowerTimeBound = null;\n upperTimeBound = 'unknown' as unknownString;\n } else if (\n effectiveMaxPriorityFee.gte(lowMaxPriorityFeeInWEI) &&\n effectiveMaxPriorityFee.lt(mediumMaxPriorityFeeInWEI)\n ) {\n lowerTimeBound = low.minWaitTimeEstimate;\n upperTimeBound = low.maxWaitTimeEstimate;\n } else if (\n effectiveMaxPriorityFee.gte(mediumMaxPriorityFeeInWEI) &&\n effectiveMaxPriorityFee.lt(highMaxPriorityFeeInWEI)\n ) {\n lowerTimeBound = medium.minWaitTimeEstimate;\n upperTimeBound = medium.maxWaitTimeEstimate;\n } else if (effectiveMaxPriorityFee.eq(highMaxPriorityFeeInWEI)) {\n lowerTimeBound = high.minWaitTimeEstimate;\n upperTimeBound = high.maxWaitTimeEstimate;\n } else {\n lowerTimeBound = 0;\n upperTimeBound = high.maxWaitTimeEstimate;\n }\n\n return {\n lowerTimeBound,\n upperTimeBound,\n };\n}\n"]} \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts deleted file mode 100644 index 7ac466bc27..0000000000 --- a/dist/index.d.ts +++ /dev/null @@ -1,33 +0,0 @@ -import 'isomorphic-fetch'; -import * as util from './util'; -export * from './assets/AccountTrackerController'; -export * from './user/AddressBookController'; -export * from './approval/ApprovalController'; -export * from './assets/AssetsContractController'; -export * from './BaseController'; -export { BaseController as BaseControllerV2, getPersistentState, getAnonymizedState, Json, StateDeriver, StateMetadata, StatePropertyMetadata, } from './BaseControllerV2'; -export * from './ComposableController'; -export * from './ControllerMessenger'; -export * from './assets/CurrencyRateController'; -export * from './keyring/KeyringController'; -export * from './message-manager/MessageManager'; -export * from './network/NetworkController'; -export * from './third-party/PhishingController'; -export * from './user/PreferencesController'; -export * from './assets/TokenBalancesController'; -export * from './assets/TokenRatesController'; -export * from './transaction/TransactionController'; -export * from './message-manager/PersonalMessageManager'; -export * from './message-manager/TypedMessageManager'; -export * from './announcement/AnnouncementController'; -export * from './assets/TokenListController'; -export * from './gas/GasFeeController'; -export * from './assets/TokensController'; -export * from './assets/CollectiblesController'; -export * from './assets/TokenDetectionController'; -export * from './assets/CollectibleDetectionController'; -export * from './permissions'; -export * from './subject-metadata'; -export * from './ratelimit/RateLimitController'; -export * from './notification/NotificationController'; -export { util }; diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index 04e1d0cd4b..0000000000 --- a/dist/index.js +++ /dev/null @@ -1,66 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -var __exportStar = (this && this.__exportStar) || function(m, exports) { - for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.util = exports.getAnonymizedState = exports.getPersistentState = exports.BaseControllerV2 = void 0; -require("isomorphic-fetch"); -const util = __importStar(require("./util")); -exports.util = util; -__exportStar(require("./assets/AccountTrackerController"), exports); -__exportStar(require("./user/AddressBookController"), exports); -__exportStar(require("./approval/ApprovalController"), exports); -__exportStar(require("./assets/AssetsContractController"), exports); -__exportStar(require("./BaseController"), exports); -var BaseControllerV2_1 = require("./BaseControllerV2"); -Object.defineProperty(exports, "BaseControllerV2", { enumerable: true, get: function () { return BaseControllerV2_1.BaseController; } }); -Object.defineProperty(exports, "getPersistentState", { enumerable: true, get: function () { return BaseControllerV2_1.getPersistentState; } }); -Object.defineProperty(exports, "getAnonymizedState", { enumerable: true, get: function () { return BaseControllerV2_1.getAnonymizedState; } }); -__exportStar(require("./ComposableController"), exports); -__exportStar(require("./ControllerMessenger"), exports); -__exportStar(require("./assets/CurrencyRateController"), exports); -__exportStar(require("./keyring/KeyringController"), exports); -__exportStar(require("./message-manager/MessageManager"), exports); -__exportStar(require("./network/NetworkController"), exports); -__exportStar(require("./third-party/PhishingController"), exports); -__exportStar(require("./user/PreferencesController"), exports); -__exportStar(require("./assets/TokenBalancesController"), exports); -__exportStar(require("./assets/TokenRatesController"), exports); -__exportStar(require("./transaction/TransactionController"), exports); -__exportStar(require("./message-manager/PersonalMessageManager"), exports); -__exportStar(require("./message-manager/TypedMessageManager"), exports); -__exportStar(require("./announcement/AnnouncementController"), exports); -__exportStar(require("./assets/TokenListController"), exports); -__exportStar(require("./gas/GasFeeController"), exports); -__exportStar(require("./assets/TokensController"), exports); -__exportStar(require("./assets/CollectiblesController"), exports); -__exportStar(require("./assets/TokenDetectionController"), exports); -__exportStar(require("./assets/CollectibleDetectionController"), exports); -__exportStar(require("./permissions"), exports); -__exportStar(require("./subject-metadata"), exports); -__exportStar(require("./ratelimit/RateLimitController"), exports); -__exportStar(require("./notification/NotificationController"), exports); -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map deleted file mode 100644 index 06147eff6f..0000000000 --- a/dist/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,4BAA0B;AAC1B,6CAA+B;AAwCtB,oBAAI;AAtCb,oEAAkD;AAClD,+DAA6C;AAC7C,gEAA8C;AAC9C,oEAAkD;AAClD,mDAAiC;AACjC,uDAQ4B;AAP1B,oHAAA,cAAc,OAAoB;AAClC,sHAAA,kBAAkB,OAAA;AAClB,sHAAA,kBAAkB,OAAA;AAMpB,yDAAuC;AACvC,wDAAsC;AACtC,kEAAgD;AAChD,8DAA4C;AAC5C,mEAAiD;AACjD,8DAA4C;AAC5C,mEAAiD;AACjD,+DAA6C;AAC7C,mEAAiD;AACjD,gEAA8C;AAC9C,sEAAoD;AACpD,2EAAyD;AACzD,wEAAsD;AACtD,wEAAsD;AACtD,+DAA6C;AAC7C,yDAAuC;AACvC,4DAA0C;AAC1C,kEAAgD;AAChD,oEAAkD;AAClD,0EAAwD;AACxD,gDAA8B;AAC9B,qDAAmC;AACnC,kEAAgD;AAChD,wEAAsD","sourcesContent":["import 'isomorphic-fetch';\nimport * as util from './util';\n\nexport * from './assets/AccountTrackerController';\nexport * from './user/AddressBookController';\nexport * from './approval/ApprovalController';\nexport * from './assets/AssetsContractController';\nexport * from './BaseController';\nexport {\n BaseController as BaseControllerV2,\n getPersistentState,\n getAnonymizedState,\n Json,\n StateDeriver,\n StateMetadata,\n StatePropertyMetadata,\n} from './BaseControllerV2';\nexport * from './ComposableController';\nexport * from './ControllerMessenger';\nexport * from './assets/CurrencyRateController';\nexport * from './keyring/KeyringController';\nexport * from './message-manager/MessageManager';\nexport * from './network/NetworkController';\nexport * from './third-party/PhishingController';\nexport * from './user/PreferencesController';\nexport * from './assets/TokenBalancesController';\nexport * from './assets/TokenRatesController';\nexport * from './transaction/TransactionController';\nexport * from './message-manager/PersonalMessageManager';\nexport * from './message-manager/TypedMessageManager';\nexport * from './announcement/AnnouncementController';\nexport * from './assets/TokenListController';\nexport * from './gas/GasFeeController';\nexport * from './assets/TokensController';\nexport * from './assets/CollectiblesController';\nexport * from './assets/TokenDetectionController';\nexport * from './assets/CollectibleDetectionController';\nexport * from './permissions';\nexport * from './subject-metadata';\nexport * from './ratelimit/RateLimitController';\nexport * from './notification/NotificationController';\nexport { util };\n"]} \ No newline at end of file diff --git a/dist/keyring/KeyringController.d.ts b/dist/keyring/KeyringController.d.ts deleted file mode 100644 index 23ee5e3da0..0000000000 --- a/dist/keyring/KeyringController.d.ts +++ /dev/null @@ -1,308 +0,0 @@ -import { MetaMaskKeyring as QRKeyring, IKeyringState as IQRKeyringState } from '@keystonehq/metamask-airgapped-keyring'; -import { BaseController, BaseConfig, BaseState, Listener } from '../BaseController'; -import { PreferencesController } from '../user/PreferencesController'; -import { PersonalMessageParams } from '../message-manager/PersonalMessageManager'; -import { TypedMessageParams } from '../message-manager/TypedMessageManager'; -/** - * Available keyring types - */ -export declare enum KeyringTypes { - simple = "Simple Key Pair", - hd = "HD Key Tree", - qr = "QR Hardware Wallet Device" -} -/** - * @type KeyringObject - * - * Keyring object - * @property type - Keyring type - * @property accounts - Associated accounts - * @function getAccounts - Get associated accounts - */ -export interface KeyringObject { - type: string; - accounts: string[]; - getAccounts(): string[]; -} -/** - * @type KeyringState - * - * Keyring controller state - * @property vault - Encrypted string representing keyring data - * @property keyrings - Group of accounts - */ -export interface KeyringState extends BaseState { - vault?: string; - keyrings: Keyring[]; -} -/** - * @type KeyringMemState - * - * Keyring mem controller state - * @property isUnlocked - Whether vault is unlocked - * @property keyringTypes - Account types - * @property keyrings - Group of accounts - */ -export interface KeyringMemState extends BaseState { - isUnlocked: boolean; - keyringTypes: string[]; - keyrings: Keyring[]; -} -/** - * @type KeyringConfig - * - * Keyring controller configuration - * @property encryptor - Keyring encryptor - */ -export interface KeyringConfig extends BaseConfig { - encryptor?: any; - keyringTypes?: any[]; -} -/** - * @type Keyring - * - * Keyring object to return in fullUpdate - * @property type - Keyring type - * @property accounts - Associated accounts - * @property index - Associated index - */ -export interface Keyring { - accounts: string[]; - type: string; - index?: number; -} -/** - * A strategy for importing an account - */ -export declare enum AccountImportStrategy { - privateKey = "privateKey", - json = "json" -} -/** - * The `signTypedMessage` version - * - * @see https://docs.metamask.io/guide/signing-data.html - */ -export declare enum SignTypedDataVersion { - V1 = "V1", - V3 = "V3", - V4 = "V4" -} -/** - * Controller responsible for establishing and managing user identity - */ -export declare class KeyringController extends BaseController { - #private; - private mutex; - /** - * Name of this controller used during composition - */ - name: string; - private removeIdentity; - private syncIdentities; - private updateIdentities; - private setSelectedAddress; - private setAccountLabel?; - /** - * Creates a KeyringController instance. - * - * @param options - The controller options. - * @param options.removeIdentity - Remove the identity with the given address. - * @param options.syncIdentities - Sync identities with the given list of addresses. - * @param options.updateIdentities - Generate an identity for each address given that doesn't already have an identity. - * @param options.setSelectedAddress - Set the selected address. - * @param options.setAccountLabel - Set a new name for account. - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor({ removeIdentity, syncIdentities, updateIdentities, setSelectedAddress, setAccountLabel, }: { - removeIdentity: PreferencesController['removeIdentity']; - syncIdentities: PreferencesController['syncIdentities']; - updateIdentities: PreferencesController['updateIdentities']; - setSelectedAddress: PreferencesController['setSelectedAddress']; - setAccountLabel?: PreferencesController['setAccountLabel']; - }, config?: Partial, state?: Partial); - /** - * Adds a new account to the default (first) HD seed phrase keyring. - * - * @returns Promise resolving to current state when the account is added. - */ - addNewAccount(): Promise; - /** - * Adds a new account to the default (first) HD seed phrase keyring without updating identities in preferences. - * - * @returns Promise resolving to current state when the account is added. - */ - addNewAccountWithoutUpdate(): Promise; - /** - * Effectively the same as creating a new keychain then populating it - * using the given seed phrase. - * - * @param password - Password to unlock keychain. - * @param seed - A BIP39-compliant seed phrase, - * either as a string or an array of UTF-8 bytes that represent the string. - * @returns Promise resolving to the restored keychain object. - */ - createNewVaultAndRestore(password: string, seed: string | number[]): Promise; - /** - * Create a new primary keychain and wipe any previous keychains. - * - * @param password - Password to unlock the new vault. - * @returns Newly-created keychain object. - */ - createNewVaultAndKeychain(password: string): Promise; - /** - * Returns the status of the vault. - * - * @returns Boolean returning true if the vault is unlocked. - */ - isUnlocked(): boolean; - /** - * Gets the seed phrase of the HD keyring. - * - * @param password - Password of the keyring. - * @returns Promise resolving to the seed phrase. - */ - exportSeedPhrase(password: string): any; - /** - * Gets the private key from the keyring controlling an address. - * - * @param password - Password of the keyring. - * @param address - Address to export. - * @returns Promise resolving to the private key for an address. - */ - exportAccount(password: string, address: string): Promise; - /** - * Returns the public addresses of all accounts for the current keyring. - * - * @returns A promise resolving to an array of addresses. - */ - getAccounts(): Promise; - /** - * Imports an account with the specified import strategy. - * - * @param strategy - Import strategy name. - * @param args - Array of arguments to pass to the underlying stategy. - * @throws Will throw when passed an unrecognized strategy. - * @returns Promise resolving to current state when the import is complete. - */ - importAccountWithStrategy(strategy: AccountImportStrategy, args: any[]): Promise; - /** - * Removes an account from keyring state. - * - * @param address - Address of the account to remove. - * @returns Promise resolving current state when this account removal completes. - */ - removeAccount(address: string): Promise; - /** - * Deallocates all secrets and locks the wallet. - * - * @returns Promise resolving to current state. - */ - setLocked(): Promise; - /** - * Signs message by calling down into a specific keyring. - * - * @param messageParams - PersonalMessageParams object to sign. - * @returns Promise resolving to a signed message string. - */ - signMessage(messageParams: PersonalMessageParams): any; - /** - * Signs personal message by calling down into a specific keyring. - * - * @param messageParams - PersonalMessageParams object to sign. - * @returns Promise resolving to a signed message string. - */ - signPersonalMessage(messageParams: PersonalMessageParams): any; - /** - * Signs typed message by calling down into a specific keyring. - * - * @param messageParams - TypedMessageParams object to sign. - * @param version - Compatibility version EIP712. - * @throws Will throw when passed an unrecognized version. - * @returns Promise resolving to a signed message string or an error if any. - */ - signTypedMessage(messageParams: TypedMessageParams, version: SignTypedDataVersion): Promise; - /** - * Signs a transaction by calling down into a specific keyring. - * - * @param transaction - Transaction object to sign. Must be a `ethereumjs-tx` transaction instance. - * @param from - Address to sign from, should be in keychain. - * @returns Promise resolving to a signed transaction string. - */ - signTransaction(transaction: unknown, from: string): any; - /** - * Attempts to decrypt the current vault and load its keyrings. - * - * @param password - Password to unlock the keychain. - * @returns Promise resolving to the current state. - */ - submitPassword(password: string): Promise; - /** - * Adds new listener to be notified of state changes. - * - * @param listener - Callback triggered when state changes. - */ - subscribe(listener: Listener): void; - /** - * Removes existing listener from receiving state changes. - * - * @param listener - Callback to remove. - * @returns True if a listener is found and unsubscribed. - */ - unsubscribe(listener: Listener): any; - /** - * Adds new listener to be notified when the wallet is locked. - * - * @param listener - Callback triggered when wallet is locked. - * @returns EventEmitter if listener added. - */ - onLock(listener: () => void): any; - /** - * Adds new listener to be notified when the wallet is unlocked. - * - * @param listener - Callback triggered when wallet is unlocked. - * @returns EventEmitter if listener added. - */ - onUnlock(listener: () => void): any; - /** - * Verifies the that the seed phrase restores the current keychain's accounts. - * - * @returns Whether the verification succeeds. - */ - verifySeedPhrase(): Promise; - /** - * Update keyrings in state and calls KeyringController fullUpdate method returning current state. - * - * @returns The current state. - */ - fullUpdate(): Promise; - /** - * Add qr hardware keyring. - * - * @returns The added keyring - */ - private addQRKeyring; - /** - * Get qr hardware keyring. - * - * @returns The added keyring - */ - getOrAddQRKeyring(): Promise; - restoreQRKeyring(serialized: any): Promise; - resetQRKeyringState(): Promise; - getQRKeyringState(): Promise; - submitQRCryptoHDKey(cryptoHDKey: string): Promise; - submitQRCryptoAccount(cryptoAccount: string): Promise; - submitQRSignature(requestId: string, ethSignature: string): Promise; - cancelQRSignRequest(): Promise; - connectQRHardware(page: number): Promise<{ - balance: string; - address: string; - index: number; - }[]>; - unlockQRHardwareWalletAccount(index: number): Promise; - getAccountKeyringType(account: string): Promise; - forgetQRDevice(): Promise; -} -export default KeyringController; diff --git a/dist/keyring/KeyringController.js b/dist/keyring/KeyringController.js deleted file mode 100644 index 8d63c54469..0000000000 --- a/dist/keyring/KeyringController.js +++ /dev/null @@ -1,636 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { - if (kind === "m") throw new TypeError("Private method is not writable"); - if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); - if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); - return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; -}; -var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { - if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); - if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); - return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -var _KeyringController_keyring; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.KeyringController = exports.SignTypedDataVersion = exports.AccountImportStrategy = exports.KeyringTypes = void 0; -const ethereumjs_util_1 = require("ethereumjs-util"); -const eth_sig_util_1 = require("eth-sig-util"); -const ethereumjs_wallet_1 = __importStar(require("ethereumjs-wallet")); -const eth_keyring_controller_1 = __importDefault(require("eth-keyring-controller")); -const async_mutex_1 = require("async-mutex"); -const BaseController_1 = require("../BaseController"); -const util_1 = require("../util"); -/** - * Available keyring types - */ -var KeyringTypes; -(function (KeyringTypes) { - KeyringTypes["simple"] = "Simple Key Pair"; - KeyringTypes["hd"] = "HD Key Tree"; - KeyringTypes["qr"] = "QR Hardware Wallet Device"; -})(KeyringTypes = exports.KeyringTypes || (exports.KeyringTypes = {})); -/** - * A strategy for importing an account - */ -var AccountImportStrategy; -(function (AccountImportStrategy) { - AccountImportStrategy["privateKey"] = "privateKey"; - AccountImportStrategy["json"] = "json"; -})(AccountImportStrategy = exports.AccountImportStrategy || (exports.AccountImportStrategy = {})); -/** - * The `signTypedMessage` version - * - * @see https://docs.metamask.io/guide/signing-data.html - */ -var SignTypedDataVersion; -(function (SignTypedDataVersion) { - SignTypedDataVersion["V1"] = "V1"; - SignTypedDataVersion["V3"] = "V3"; - SignTypedDataVersion["V4"] = "V4"; -})(SignTypedDataVersion = exports.SignTypedDataVersion || (exports.SignTypedDataVersion = {})); -/** - * Controller responsible for establishing and managing user identity - */ -class KeyringController extends BaseController_1.BaseController { - /** - * Creates a KeyringController instance. - * - * @param options - The controller options. - * @param options.removeIdentity - Remove the identity with the given address. - * @param options.syncIdentities - Sync identities with the given list of addresses. - * @param options.updateIdentities - Generate an identity for each address given that doesn't already have an identity. - * @param options.setSelectedAddress - Set the selected address. - * @param options.setAccountLabel - Set a new name for account. - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor({ removeIdentity, syncIdentities, updateIdentities, setSelectedAddress, setAccountLabel, }, config, state) { - super(config, state); - this.mutex = new async_mutex_1.Mutex(); - /** - * Name of this controller used during composition - */ - this.name = 'KeyringController'; - _KeyringController_keyring.set(this, void 0); - __classPrivateFieldSet(this, _KeyringController_keyring, new eth_keyring_controller_1.default(Object.assign({ initState: state }, config)), "f"); - this.defaultState = Object.assign(Object.assign({}, __classPrivateFieldGet(this, _KeyringController_keyring, "f").store.getState()), { keyrings: [] }); - this.removeIdentity = removeIdentity; - this.syncIdentities = syncIdentities; - this.updateIdentities = updateIdentities; - this.setSelectedAddress = setSelectedAddress; - this.setAccountLabel = setAccountLabel; - this.initialize(); - this.fullUpdate(); - } - /** - * Adds a new account to the default (first) HD seed phrase keyring. - * - * @returns Promise resolving to current state when the account is added. - */ - addNewAccount() { - return __awaiter(this, void 0, void 0, function* () { - const primaryKeyring = __classPrivateFieldGet(this, _KeyringController_keyring, "f").getKeyringsByType('HD Key Tree')[0]; - /* istanbul ignore if */ - if (!primaryKeyring) { - throw new Error('No HD keyring found'); - } - const oldAccounts = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts(); - yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").addNewAccount(primaryKeyring); - const newAccounts = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts(); - yield this.verifySeedPhrase(); - this.updateIdentities(newAccounts); - newAccounts.forEach((selectedAddress) => { - if (!oldAccounts.includes(selectedAddress)) { - this.setSelectedAddress(selectedAddress); - } - }); - return this.fullUpdate(); - }); - } - /** - * Adds a new account to the default (first) HD seed phrase keyring without updating identities in preferences. - * - * @returns Promise resolving to current state when the account is added. - */ - addNewAccountWithoutUpdate() { - return __awaiter(this, void 0, void 0, function* () { - const primaryKeyring = __classPrivateFieldGet(this, _KeyringController_keyring, "f").getKeyringsByType('HD Key Tree')[0]; - /* istanbul ignore if */ - if (!primaryKeyring) { - throw new Error('No HD keyring found'); - } - yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").addNewAccount(primaryKeyring); - yield this.verifySeedPhrase(); - return this.fullUpdate(); - }); - } - /** - * Effectively the same as creating a new keychain then populating it - * using the given seed phrase. - * - * @param password - Password to unlock keychain. - * @param seed - A BIP39-compliant seed phrase, - * either as a string or an array of UTF-8 bytes that represent the string. - * @returns Promise resolving to the restored keychain object. - */ - createNewVaultAndRestore(password, seed) { - return __awaiter(this, void 0, void 0, function* () { - const releaseLock = yield this.mutex.acquire(); - if (!password || !password.length) { - throw new Error('Invalid password'); - } - try { - this.updateIdentities([]); - const vault = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").createNewVaultAndRestore(password, seed); - this.updateIdentities(yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts()); - this.fullUpdate(); - return vault; - } - finally { - releaseLock(); - } - }); - } - /** - * Create a new primary keychain and wipe any previous keychains. - * - * @param password - Password to unlock the new vault. - * @returns Newly-created keychain object. - */ - createNewVaultAndKeychain(password) { - return __awaiter(this, void 0, void 0, function* () { - const releaseLock = yield this.mutex.acquire(); - try { - const vault = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").createNewVaultAndKeychain(password); - this.updateIdentities(yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts()); - this.fullUpdate(); - return vault; - } - finally { - releaseLock(); - } - }); - } - /** - * Returns the status of the vault. - * - * @returns Boolean returning true if the vault is unlocked. - */ - isUnlocked() { - return __classPrivateFieldGet(this, _KeyringController_keyring, "f").memStore.getState().isUnlocked; - } - /** - * Gets the seed phrase of the HD keyring. - * - * @param password - Password of the keyring. - * @returns Promise resolving to the seed phrase. - */ - exportSeedPhrase(password) { - if (__classPrivateFieldGet(this, _KeyringController_keyring, "f").password === password) { - return __classPrivateFieldGet(this, _KeyringController_keyring, "f").keyrings[0].mnemonic; - } - throw new Error('Invalid password'); - } - /** - * Gets the private key from the keyring controlling an address. - * - * @param password - Password of the keyring. - * @param address - Address to export. - * @returns Promise resolving to the private key for an address. - */ - exportAccount(password, address) { - if (__classPrivateFieldGet(this, _KeyringController_keyring, "f").password === password) { - return __classPrivateFieldGet(this, _KeyringController_keyring, "f").exportAccount(address); - } - throw new Error('Invalid password'); - } - /** - * Returns the public addresses of all accounts for the current keyring. - * - * @returns A promise resolving to an array of addresses. - */ - getAccounts() { - return __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts(); - } - /** - * Imports an account with the specified import strategy. - * - * @param strategy - Import strategy name. - * @param args - Array of arguments to pass to the underlying stategy. - * @throws Will throw when passed an unrecognized strategy. - * @returns Promise resolving to current state when the import is complete. - */ - importAccountWithStrategy(strategy, args) { - return __awaiter(this, void 0, void 0, function* () { - let privateKey; - switch (strategy) { - case 'privateKey': - const [importedKey] = args; - if (!importedKey) { - throw new Error('Cannot import an empty key.'); - } - const prefixed = (0, ethereumjs_util_1.addHexPrefix)(importedKey); - let bufferedPrivateKey; - try { - bufferedPrivateKey = (0, ethereumjs_util_1.toBuffer)(prefixed); - } - catch (_a) { - throw new Error('Cannot import invalid private key.'); - } - /* istanbul ignore if */ - if (!(0, ethereumjs_util_1.isValidPrivate)(bufferedPrivateKey)) { - throw new Error('Cannot import invalid private key.'); - } - privateKey = (0, ethereumjs_util_1.stripHexPrefix)(prefixed); - break; - case 'json': - let wallet; - const [input, password] = args; - try { - wallet = ethereumjs_wallet_1.thirdparty.fromEtherWallet(input, password); - } - catch (e) { - wallet = wallet || (yield ethereumjs_wallet_1.default.fromV3(input, password, true)); - } - privateKey = (0, ethereumjs_util_1.bufferToHex)(wallet.getPrivateKey()); - break; - default: - throw new Error(`Unexpected import strategy: '${strategy}'`); - } - const newKeyring = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").addNewKeyring(KeyringTypes.simple, [ - privateKey, - ]); - const accounts = yield newKeyring.getAccounts(); - const allAccounts = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts(); - this.updateIdentities(allAccounts); - this.setSelectedAddress(accounts[0]); - return this.fullUpdate(); - }); - } - /** - * Removes an account from keyring state. - * - * @param address - Address of the account to remove. - * @returns Promise resolving current state when this account removal completes. - */ - removeAccount(address) { - return __awaiter(this, void 0, void 0, function* () { - this.removeIdentity(address); - yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").removeAccount(address); - return this.fullUpdate(); - }); - } - /** - * Deallocates all secrets and locks the wallet. - * - * @returns Promise resolving to current state. - */ - setLocked() { - return __classPrivateFieldGet(this, _KeyringController_keyring, "f").setLocked(); - } - /** - * Signs message by calling down into a specific keyring. - * - * @param messageParams - PersonalMessageParams object to sign. - * @returns Promise resolving to a signed message string. - */ - signMessage(messageParams) { - return __classPrivateFieldGet(this, _KeyringController_keyring, "f").signMessage(messageParams); - } - /** - * Signs personal message by calling down into a specific keyring. - * - * @param messageParams - PersonalMessageParams object to sign. - * @returns Promise resolving to a signed message string. - */ - signPersonalMessage(messageParams) { - return __classPrivateFieldGet(this, _KeyringController_keyring, "f").signPersonalMessage(messageParams); - } - /** - * Signs typed message by calling down into a specific keyring. - * - * @param messageParams - TypedMessageParams object to sign. - * @param version - Compatibility version EIP712. - * @throws Will throw when passed an unrecognized version. - * @returns Promise resolving to a signed message string or an error if any. - */ - signTypedMessage(messageParams, version) { - return __awaiter(this, void 0, void 0, function* () { - try { - const address = (0, eth_sig_util_1.normalize)(messageParams.from); - const qrKeyring = yield this.getOrAddQRKeyring(); - const qrAccounts = yield qrKeyring.getAccounts(); - if (qrAccounts.findIndex((qrAddress) => qrAddress.toLowerCase() === address.toLowerCase()) !== -1) { - const messageParamsClone = Object.assign({}, messageParams); - if (version !== SignTypedDataVersion.V1 && - typeof messageParamsClone.data === 'string') { - messageParamsClone.data = JSON.parse(messageParamsClone.data); - } - return __classPrivateFieldGet(this, _KeyringController_keyring, "f").signTypedMessage(messageParamsClone, { version }); - } - const { password } = __classPrivateFieldGet(this, _KeyringController_keyring, "f"); - const privateKey = yield this.exportAccount(password, address); - const privateKeyBuffer = (0, ethereumjs_util_1.toBuffer)((0, ethereumjs_util_1.addHexPrefix)(privateKey)); - switch (version) { - case SignTypedDataVersion.V1: - // signTypedDataLegacy will throw if the data is invalid. - return (0, eth_sig_util_1.signTypedDataLegacy)(privateKeyBuffer, { - data: messageParams.data, - }); - case SignTypedDataVersion.V3: - return (0, eth_sig_util_1.signTypedData)(privateKeyBuffer, { - data: JSON.parse(messageParams.data), - }); - case SignTypedDataVersion.V4: - return (0, eth_sig_util_1.signTypedData_v4)(privateKeyBuffer, { - data: JSON.parse(messageParams.data), - }); - default: - throw new Error(`Unexpected signTypedMessage version: '${version}'`); - } - } - catch (error) { - throw new Error(`Keyring Controller signTypedMessage: ${error}`); - } - }); - } - /** - * Signs a transaction by calling down into a specific keyring. - * - * @param transaction - Transaction object to sign. Must be a `ethereumjs-tx` transaction instance. - * @param from - Address to sign from, should be in keychain. - * @returns Promise resolving to a signed transaction string. - */ - signTransaction(transaction, from) { - return __classPrivateFieldGet(this, _KeyringController_keyring, "f").signTransaction(transaction, from); - } - /** - * Attempts to decrypt the current vault and load its keyrings. - * - * @param password - Password to unlock the keychain. - * @returns Promise resolving to the current state. - */ - submitPassword(password) { - return __awaiter(this, void 0, void 0, function* () { - yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").submitPassword(password); - const accounts = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts(); - yield this.syncIdentities(accounts); - return this.fullUpdate(); - }); - } - /** - * Adds new listener to be notified of state changes. - * - * @param listener - Callback triggered when state changes. - */ - subscribe(listener) { - __classPrivateFieldGet(this, _KeyringController_keyring, "f").store.subscribe(listener); - } - /** - * Removes existing listener from receiving state changes. - * - * @param listener - Callback to remove. - * @returns True if a listener is found and unsubscribed. - */ - unsubscribe(listener) { - return __classPrivateFieldGet(this, _KeyringController_keyring, "f").store.unsubscribe(listener); - } - /** - * Adds new listener to be notified when the wallet is locked. - * - * @param listener - Callback triggered when wallet is locked. - * @returns EventEmitter if listener added. - */ - onLock(listener) { - return __classPrivateFieldGet(this, _KeyringController_keyring, "f").on('lock', listener); - } - /** - * Adds new listener to be notified when the wallet is unlocked. - * - * @param listener - Callback triggered when wallet is unlocked. - * @returns EventEmitter if listener added. - */ - onUnlock(listener) { - return __classPrivateFieldGet(this, _KeyringController_keyring, "f").on('unlock', listener); - } - /** - * Verifies the that the seed phrase restores the current keychain's accounts. - * - * @returns Whether the verification succeeds. - */ - verifySeedPhrase() { - return __awaiter(this, void 0, void 0, function* () { - const primaryKeyring = __classPrivateFieldGet(this, _KeyringController_keyring, "f").getKeyringsByType(KeyringTypes.hd)[0]; - /* istanbul ignore if */ - if (!primaryKeyring) { - throw new Error('No HD keyring found.'); - } - const seedWords = (yield primaryKeyring.serialize()).mnemonic; - const accounts = yield primaryKeyring.getAccounts(); - /* istanbul ignore if */ - if (accounts.length === 0) { - throw new Error('Cannot verify an empty keyring.'); - } - const TestKeyringClass = __classPrivateFieldGet(this, _KeyringController_keyring, "f").getKeyringClassForType(KeyringTypes.hd); - const testKeyring = new TestKeyringClass({ - mnemonic: seedWords, - numberOfAccounts: accounts.length, - }); - const testAccounts = yield testKeyring.getAccounts(); - /* istanbul ignore if */ - if (testAccounts.length !== accounts.length) { - throw new Error('Seed phrase imported incorrect number of accounts.'); - } - testAccounts.forEach((account, i) => { - /* istanbul ignore if */ - if (account.toLowerCase() !== accounts[i].toLowerCase()) { - throw new Error('Seed phrase imported different accounts.'); - } - }); - return seedWords; - }); - } - /** - * Update keyrings in state and calls KeyringController fullUpdate method returning current state. - * - * @returns The current state. - */ - fullUpdate() { - return __awaiter(this, void 0, void 0, function* () { - const keyrings = yield Promise.all(__classPrivateFieldGet(this, _KeyringController_keyring, "f").keyrings.map((keyring, index) => __awaiter(this, void 0, void 0, function* () { - const keyringAccounts = yield keyring.getAccounts(); - const accounts = Array.isArray(keyringAccounts) - ? keyringAccounts.map((address) => (0, util_1.toChecksumHexAddress)(address)) - : /* istanbul ignore next */ []; - return { - accounts, - index, - type: keyring.type, - }; - }))); - this.update({ keyrings: [...keyrings] }); - return __classPrivateFieldGet(this, _KeyringController_keyring, "f").fullUpdate(); - }); - } - // QR Hardware related methods - /** - * Add qr hardware keyring. - * - * @returns The added keyring - */ - addQRKeyring() { - return __awaiter(this, void 0, void 0, function* () { - const keyring = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").addNewKeyring(KeyringTypes.qr); - yield this.fullUpdate(); - return keyring; - }); - } - /** - * Get qr hardware keyring. - * - * @returns The added keyring - */ - getOrAddQRKeyring() { - return __awaiter(this, void 0, void 0, function* () { - const keyring = __classPrivateFieldGet(this, _KeyringController_keyring, "f").getKeyringsByType(KeyringTypes.qr)[0]; - return keyring || (yield this.addQRKeyring()); - }); - } - restoreQRKeyring(serialized) { - return __awaiter(this, void 0, void 0, function* () { - (yield this.getOrAddQRKeyring()).deserialize(serialized); - this.updateIdentities(yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts()); - yield this.fullUpdate(); - }); - } - resetQRKeyringState() { - return __awaiter(this, void 0, void 0, function* () { - (yield this.getOrAddQRKeyring()).resetStore(); - }); - } - getQRKeyringState() { - return __awaiter(this, void 0, void 0, function* () { - return (yield this.getOrAddQRKeyring()).getMemStore(); - }); - } - submitQRCryptoHDKey(cryptoHDKey) { - return __awaiter(this, void 0, void 0, function* () { - (yield this.getOrAddQRKeyring()).submitCryptoHDKey(cryptoHDKey); - }); - } - submitQRCryptoAccount(cryptoAccount) { - return __awaiter(this, void 0, void 0, function* () { - (yield this.getOrAddQRKeyring()).submitCryptoAccount(cryptoAccount); - }); - } - submitQRSignature(requestId, ethSignature) { - return __awaiter(this, void 0, void 0, function* () { - (yield this.getOrAddQRKeyring()).submitSignature(requestId, ethSignature); - }); - } - cancelQRSignRequest() { - return __awaiter(this, void 0, void 0, function* () { - (yield this.getOrAddQRKeyring()).cancelSignRequest(); - }); - } - connectQRHardware(page) { - return __awaiter(this, void 0, void 0, function* () { - try { - const keyring = yield this.getOrAddQRKeyring(); - let accounts; - switch (page) { - case -1: - accounts = yield keyring.getPreviousPage(); - break; - case 1: - accounts = yield keyring.getNextPage(); - break; - default: - accounts = yield keyring.getFirstPage(); - } - return accounts.map((account) => { - return Object.assign(Object.assign({}, account), { balance: '0x0' }); - }); - } - catch (e) { - throw new Error(`Unspecified error when connect QR Hardware, ${e}`); - } - }); - } - unlockQRHardwareWalletAccount(index) { - return __awaiter(this, void 0, void 0, function* () { - const keyring = yield this.getOrAddQRKeyring(); - keyring.setAccountToUnlock(index); - const oldAccounts = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts(); - yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").addNewAccount(keyring); - const newAccounts = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts(); - this.updateIdentities(newAccounts); - newAccounts.forEach((address) => { - if (!oldAccounts.includes(address)) { - if (this.setAccountLabel) { - this.setAccountLabel(address, `${keyring.getName()} ${index}`); - } - this.setSelectedAddress(address); - } - }); - yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").persistAllKeyrings(); - yield this.fullUpdate(); - }); - } - getAccountKeyringType(account) { - return __awaiter(this, void 0, void 0, function* () { - return (yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getKeyringForAccount(account)).type; - }); - } - forgetQRDevice() { - return __awaiter(this, void 0, void 0, function* () { - const keyring = yield this.getOrAddQRKeyring(); - keyring.forgetDevice(); - const accounts = (yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts()); - accounts.forEach((account) => { - this.setSelectedAddress(account); - }); - yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").persistAllKeyrings(); - yield this.fullUpdate(); - }); - } -} -exports.KeyringController = KeyringController; -_KeyringController_keyring = new WeakMap(); -exports.default = KeyringController; -//# sourceMappingURL=KeyringController.js.map \ No newline at end of file diff --git a/dist/keyring/KeyringController.js.map b/dist/keyring/KeyringController.js.map deleted file mode 100644 index 3d4787d7dd..0000000000 --- a/dist/keyring/KeyringController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"KeyringController.js","sourceRoot":"","sources":["../../src/keyring/KeyringController.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n addHexPrefix,\n bufferToHex,\n isValidPrivate,\n toBuffer,\n stripHexPrefix,\n} from 'ethereumjs-util';\nimport {\n normalize as normalizeAddress,\n signTypedData,\n signTypedData_v4,\n signTypedDataLegacy,\n} from 'eth-sig-util';\nimport Wallet, { thirdparty as importers } from 'ethereumjs-wallet';\nimport Keyring from 'eth-keyring-controller';\nimport { Mutex } from 'async-mutex';\nimport {\n MetaMaskKeyring as QRKeyring,\n IKeyringState as IQRKeyringState,\n} from '@keystonehq/metamask-airgapped-keyring';\nimport {\n BaseController,\n BaseConfig,\n BaseState,\n Listener,\n} from '../BaseController';\nimport { PreferencesController } from '../user/PreferencesController';\nimport { PersonalMessageParams } from '../message-manager/PersonalMessageManager';\nimport { TypedMessageParams } from '../message-manager/TypedMessageManager';\nimport { toChecksumHexAddress } from '../util';\n\n/**\n * Available keyring types\n */\nexport enum KeyringTypes {\n simple = 'Simple Key Pair',\n hd = 'HD Key Tree',\n qr = 'QR Hardware Wallet Device',\n}\n\n/**\n * @type KeyringObject\n *\n * Keyring object\n * @property type - Keyring type\n * @property accounts - Associated accounts\n * @function getAccounts - Get associated accounts\n */\nexport interface KeyringObject {\n type: string;\n accounts: string[];\n getAccounts(): string[];\n}\n\n/**\n * @type KeyringState\n *\n * Keyring controller state\n * @property vault - Encrypted string representing keyring data\n * @property keyrings - Group of accounts\n */\nexport interface KeyringState extends BaseState {\n vault?: string;\n keyrings: Keyring[];\n}\n\n/**\n * @type KeyringMemState\n *\n * Keyring mem controller state\n * @property isUnlocked - Whether vault is unlocked\n * @property keyringTypes - Account types\n * @property keyrings - Group of accounts\n */\nexport interface KeyringMemState extends BaseState {\n isUnlocked: boolean;\n keyringTypes: string[];\n keyrings: Keyring[];\n}\n\n/**\n * @type KeyringConfig\n *\n * Keyring controller configuration\n * @property encryptor - Keyring encryptor\n */\nexport interface KeyringConfig extends BaseConfig {\n encryptor?: any;\n keyringTypes?: any[];\n}\n\n/**\n * @type Keyring\n *\n * Keyring object to return in fullUpdate\n * @property type - Keyring type\n * @property accounts - Associated accounts\n * @property index - Associated index\n */\nexport interface Keyring {\n accounts: string[];\n type: string;\n index?: number;\n}\n\n/**\n * A strategy for importing an account\n */\nexport enum AccountImportStrategy {\n privateKey = 'privateKey',\n json = 'json',\n}\n\n/**\n * The `signTypedMessage` version\n *\n * @see https://docs.metamask.io/guide/signing-data.html\n */\nexport enum SignTypedDataVersion {\n V1 = 'V1',\n V3 = 'V3',\n V4 = 'V4',\n}\n\n/**\n * Controller responsible for establishing and managing user identity\n */\nexport class KeyringController extends BaseController<\n KeyringConfig,\n KeyringState\n> {\n private mutex = new Mutex();\n\n /**\n * Name of this controller used during composition\n */\n override name = 'KeyringController';\n\n private removeIdentity: PreferencesController['removeIdentity'];\n\n private syncIdentities: PreferencesController['syncIdentities'];\n\n private updateIdentities: PreferencesController['updateIdentities'];\n\n private setSelectedAddress: PreferencesController['setSelectedAddress'];\n\n private setAccountLabel?: PreferencesController['setAccountLabel'];\n\n #keyring: typeof Keyring;\n\n /**\n * Creates a KeyringController instance.\n *\n * @param options - The controller options.\n * @param options.removeIdentity - Remove the identity with the given address.\n * @param options.syncIdentities - Sync identities with the given list of addresses.\n * @param options.updateIdentities - Generate an identity for each address given that doesn't already have an identity.\n * @param options.setSelectedAddress - Set the selected address.\n * @param options.setAccountLabel - Set a new name for account.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n removeIdentity,\n syncIdentities,\n updateIdentities,\n setSelectedAddress,\n setAccountLabel,\n }: {\n removeIdentity: PreferencesController['removeIdentity'];\n syncIdentities: PreferencesController['syncIdentities'];\n updateIdentities: PreferencesController['updateIdentities'];\n setSelectedAddress: PreferencesController['setSelectedAddress'];\n setAccountLabel?: PreferencesController['setAccountLabel'];\n },\n config?: Partial,\n state?: Partial,\n ) {\n super(config, state);\n this.#keyring = new Keyring(Object.assign({ initState: state }, config));\n\n this.defaultState = {\n ...this.#keyring.store.getState(),\n keyrings: [],\n };\n this.removeIdentity = removeIdentity;\n this.syncIdentities = syncIdentities;\n this.updateIdentities = updateIdentities;\n this.setSelectedAddress = setSelectedAddress;\n this.setAccountLabel = setAccountLabel;\n this.initialize();\n this.fullUpdate();\n }\n\n /**\n * Adds a new account to the default (first) HD seed phrase keyring.\n *\n * @returns Promise resolving to current state when the account is added.\n */\n async addNewAccount(): Promise {\n const primaryKeyring = this.#keyring.getKeyringsByType('HD Key Tree')[0];\n /* istanbul ignore if */\n if (!primaryKeyring) {\n throw new Error('No HD keyring found');\n }\n const oldAccounts = await this.#keyring.getAccounts();\n await this.#keyring.addNewAccount(primaryKeyring);\n const newAccounts = await this.#keyring.getAccounts();\n\n await this.verifySeedPhrase();\n\n this.updateIdentities(newAccounts);\n newAccounts.forEach((selectedAddress: string) => {\n if (!oldAccounts.includes(selectedAddress)) {\n this.setSelectedAddress(selectedAddress);\n }\n });\n return this.fullUpdate();\n }\n\n /**\n * Adds a new account to the default (first) HD seed phrase keyring without updating identities in preferences.\n *\n * @returns Promise resolving to current state when the account is added.\n */\n async addNewAccountWithoutUpdate(): Promise {\n const primaryKeyring = this.#keyring.getKeyringsByType('HD Key Tree')[0];\n /* istanbul ignore if */\n if (!primaryKeyring) {\n throw new Error('No HD keyring found');\n }\n await this.#keyring.addNewAccount(primaryKeyring);\n await this.verifySeedPhrase();\n return this.fullUpdate();\n }\n\n /**\n * Effectively the same as creating a new keychain then populating it\n * using the given seed phrase.\n *\n * @param password - Password to unlock keychain.\n * @param seed - A BIP39-compliant seed phrase,\n * either as a string or an array of UTF-8 bytes that represent the string.\n * @returns Promise resolving to the restored keychain object.\n */\n async createNewVaultAndRestore(password: string, seed: string | number[]) {\n const releaseLock = await this.mutex.acquire();\n if (!password || !password.length) {\n throw new Error('Invalid password');\n }\n\n try {\n this.updateIdentities([]);\n const vault = await this.#keyring.createNewVaultAndRestore(\n password,\n seed,\n );\n this.updateIdentities(await this.#keyring.getAccounts());\n this.fullUpdate();\n return vault;\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Create a new primary keychain and wipe any previous keychains.\n *\n * @param password - Password to unlock the new vault.\n * @returns Newly-created keychain object.\n */\n async createNewVaultAndKeychain(password: string) {\n const releaseLock = await this.mutex.acquire();\n try {\n const vault = await this.#keyring.createNewVaultAndKeychain(password);\n this.updateIdentities(await this.#keyring.getAccounts());\n this.fullUpdate();\n return vault;\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Returns the status of the vault.\n *\n * @returns Boolean returning true if the vault is unlocked.\n */\n isUnlocked(): boolean {\n return this.#keyring.memStore.getState().isUnlocked;\n }\n\n /**\n * Gets the seed phrase of the HD keyring.\n *\n * @param password - Password of the keyring.\n * @returns Promise resolving to the seed phrase.\n */\n exportSeedPhrase(password: string) {\n if (this.#keyring.password === password) {\n return this.#keyring.keyrings[0].mnemonic;\n }\n throw new Error('Invalid password');\n }\n\n /**\n * Gets the private key from the keyring controlling an address.\n *\n * @param password - Password of the keyring.\n * @param address - Address to export.\n * @returns Promise resolving to the private key for an address.\n */\n exportAccount(password: string, address: string): Promise {\n if (this.#keyring.password === password) {\n return this.#keyring.exportAccount(address);\n }\n throw new Error('Invalid password');\n }\n\n /**\n * Returns the public addresses of all accounts for the current keyring.\n *\n * @returns A promise resolving to an array of addresses.\n */\n getAccounts(): Promise {\n return this.#keyring.getAccounts();\n }\n\n /**\n * Imports an account with the specified import strategy.\n *\n * @param strategy - Import strategy name.\n * @param args - Array of arguments to pass to the underlying stategy.\n * @throws Will throw when passed an unrecognized strategy.\n * @returns Promise resolving to current state when the import is complete.\n */\n async importAccountWithStrategy(\n strategy: AccountImportStrategy,\n args: any[],\n ): Promise {\n let privateKey;\n switch (strategy) {\n case 'privateKey':\n const [importedKey] = args;\n if (!importedKey) {\n throw new Error('Cannot import an empty key.');\n }\n const prefixed = addHexPrefix(importedKey);\n\n let bufferedPrivateKey;\n try {\n bufferedPrivateKey = toBuffer(prefixed);\n } catch {\n throw new Error('Cannot import invalid private key.');\n }\n\n /* istanbul ignore if */\n if (!isValidPrivate(bufferedPrivateKey)) {\n throw new Error('Cannot import invalid private key.');\n }\n\n privateKey = stripHexPrefix(prefixed);\n break;\n case 'json':\n let wallet;\n const [input, password] = args;\n try {\n wallet = importers.fromEtherWallet(input, password);\n } catch (e) {\n wallet = wallet || (await Wallet.fromV3(input, password, true));\n }\n privateKey = bufferToHex(wallet.getPrivateKey());\n break;\n default:\n throw new Error(`Unexpected import strategy: '${strategy}'`);\n }\n const newKeyring = await this.#keyring.addNewKeyring(KeyringTypes.simple, [\n privateKey,\n ]);\n const accounts = await newKeyring.getAccounts();\n const allAccounts = await this.#keyring.getAccounts();\n this.updateIdentities(allAccounts);\n this.setSelectedAddress(accounts[0]);\n return this.fullUpdate();\n }\n\n /**\n * Removes an account from keyring state.\n *\n * @param address - Address of the account to remove.\n * @returns Promise resolving current state when this account removal completes.\n */\n async removeAccount(address: string): Promise {\n this.removeIdentity(address);\n await this.#keyring.removeAccount(address);\n return this.fullUpdate();\n }\n\n /**\n * Deallocates all secrets and locks the wallet.\n *\n * @returns Promise resolving to current state.\n */\n setLocked(): Promise {\n return this.#keyring.setLocked();\n }\n\n /**\n * Signs message by calling down into a specific keyring.\n *\n * @param messageParams - PersonalMessageParams object to sign.\n * @returns Promise resolving to a signed message string.\n */\n signMessage(messageParams: PersonalMessageParams) {\n return this.#keyring.signMessage(messageParams);\n }\n\n /**\n * Signs personal message by calling down into a specific keyring.\n *\n * @param messageParams - PersonalMessageParams object to sign.\n * @returns Promise resolving to a signed message string.\n */\n signPersonalMessage(messageParams: PersonalMessageParams) {\n return this.#keyring.signPersonalMessage(messageParams);\n }\n\n /**\n * Signs typed message by calling down into a specific keyring.\n *\n * @param messageParams - TypedMessageParams object to sign.\n * @param version - Compatibility version EIP712.\n * @throws Will throw when passed an unrecognized version.\n * @returns Promise resolving to a signed message string or an error if any.\n */\n async signTypedMessage(\n messageParams: TypedMessageParams,\n version: SignTypedDataVersion,\n ) {\n try {\n const address = normalizeAddress(messageParams.from);\n const qrKeyring = await this.getOrAddQRKeyring();\n const qrAccounts = await qrKeyring.getAccounts();\n if (\n qrAccounts.findIndex(\n (qrAddress: string) =>\n qrAddress.toLowerCase() === address.toLowerCase(),\n ) !== -1\n ) {\n const messageParamsClone = { ...messageParams };\n if (\n version !== SignTypedDataVersion.V1 &&\n typeof messageParamsClone.data === 'string'\n ) {\n messageParamsClone.data = JSON.parse(messageParamsClone.data);\n }\n return this.#keyring.signTypedMessage(messageParamsClone, { version });\n }\n\n const { password } = this.#keyring;\n const privateKey = await this.exportAccount(password, address);\n const privateKeyBuffer = toBuffer(addHexPrefix(privateKey));\n switch (version) {\n case SignTypedDataVersion.V1:\n // signTypedDataLegacy will throw if the data is invalid.\n return signTypedDataLegacy(privateKeyBuffer, {\n data: messageParams.data as any,\n });\n case SignTypedDataVersion.V3:\n return signTypedData(privateKeyBuffer, {\n data: JSON.parse(messageParams.data as string),\n });\n case SignTypedDataVersion.V4:\n return signTypedData_v4(privateKeyBuffer, {\n data: JSON.parse(messageParams.data as string),\n });\n default:\n throw new Error(`Unexpected signTypedMessage version: '${version}'`);\n }\n } catch (error) {\n throw new Error(`Keyring Controller signTypedMessage: ${error}`);\n }\n }\n\n /**\n * Signs a transaction by calling down into a specific keyring.\n *\n * @param transaction - Transaction object to sign. Must be a `ethereumjs-tx` transaction instance.\n * @param from - Address to sign from, should be in keychain.\n * @returns Promise resolving to a signed transaction string.\n */\n signTransaction(transaction: unknown, from: string) {\n return this.#keyring.signTransaction(transaction, from);\n }\n\n /**\n * Attempts to decrypt the current vault and load its keyrings.\n *\n * @param password - Password to unlock the keychain.\n * @returns Promise resolving to the current state.\n */\n async submitPassword(password: string): Promise {\n await this.#keyring.submitPassword(password);\n const accounts = await this.#keyring.getAccounts();\n await this.syncIdentities(accounts);\n return this.fullUpdate();\n }\n\n /**\n * Adds new listener to be notified of state changes.\n *\n * @param listener - Callback triggered when state changes.\n */\n override subscribe(listener: Listener) {\n this.#keyring.store.subscribe(listener);\n }\n\n /**\n * Removes existing listener from receiving state changes.\n *\n * @param listener - Callback to remove.\n * @returns True if a listener is found and unsubscribed.\n */\n override unsubscribe(listener: Listener) {\n return this.#keyring.store.unsubscribe(listener);\n }\n\n /**\n * Adds new listener to be notified when the wallet is locked.\n *\n * @param listener - Callback triggered when wallet is locked.\n * @returns EventEmitter if listener added.\n */\n onLock(listener: () => void) {\n return this.#keyring.on('lock', listener);\n }\n\n /**\n * Adds new listener to be notified when the wallet is unlocked.\n *\n * @param listener - Callback triggered when wallet is unlocked.\n * @returns EventEmitter if listener added.\n */\n onUnlock(listener: () => void) {\n return this.#keyring.on('unlock', listener);\n }\n\n /**\n * Verifies the that the seed phrase restores the current keychain's accounts.\n *\n * @returns Whether the verification succeeds.\n */\n async verifySeedPhrase(): Promise {\n const primaryKeyring = this.#keyring.getKeyringsByType(KeyringTypes.hd)[0];\n /* istanbul ignore if */\n if (!primaryKeyring) {\n throw new Error('No HD keyring found.');\n }\n\n const seedWords = (await primaryKeyring.serialize()).mnemonic;\n const accounts = await primaryKeyring.getAccounts();\n /* istanbul ignore if */\n if (accounts.length === 0) {\n throw new Error('Cannot verify an empty keyring.');\n }\n\n const TestKeyringClass = this.#keyring.getKeyringClassForType(\n KeyringTypes.hd,\n );\n const testKeyring = new TestKeyringClass({\n mnemonic: seedWords,\n numberOfAccounts: accounts.length,\n });\n const testAccounts = await testKeyring.getAccounts();\n /* istanbul ignore if */\n if (testAccounts.length !== accounts.length) {\n throw new Error('Seed phrase imported incorrect number of accounts.');\n }\n\n testAccounts.forEach((account: string, i: number) => {\n /* istanbul ignore if */\n if (account.toLowerCase() !== accounts[i].toLowerCase()) {\n throw new Error('Seed phrase imported different accounts.');\n }\n });\n\n return seedWords;\n }\n\n /**\n * Update keyrings in state and calls KeyringController fullUpdate method returning current state.\n *\n * @returns The current state.\n */\n async fullUpdate(): Promise {\n const keyrings: Keyring[] = await Promise.all(\n this.#keyring.keyrings.map(\n async (keyring: KeyringObject, index: number): Promise => {\n const keyringAccounts = await keyring.getAccounts();\n const accounts = Array.isArray(keyringAccounts)\n ? keyringAccounts.map((address) => toChecksumHexAddress(address))\n : /* istanbul ignore next */ [];\n return {\n accounts,\n index,\n type: keyring.type,\n };\n },\n ),\n );\n this.update({ keyrings: [...keyrings] });\n return this.#keyring.fullUpdate();\n }\n\n // QR Hardware related methods\n\n /**\n * Add qr hardware keyring.\n *\n * @returns The added keyring\n */\n private async addQRKeyring(): Promise {\n const keyring = await this.#keyring.addNewKeyring(KeyringTypes.qr);\n await this.fullUpdate();\n return keyring;\n }\n\n /**\n * Get qr hardware keyring.\n *\n * @returns The added keyring\n */\n async getOrAddQRKeyring(): Promise {\n const keyring = this.#keyring.getKeyringsByType(KeyringTypes.qr)[0];\n return keyring || (await this.addQRKeyring());\n }\n\n async restoreQRKeyring(serialized: any): Promise {\n (await this.getOrAddQRKeyring()).deserialize(serialized);\n this.updateIdentities(await this.#keyring.getAccounts());\n await this.fullUpdate();\n }\n\n async resetQRKeyringState(): Promise {\n (await this.getOrAddQRKeyring()).resetStore();\n }\n\n async getQRKeyringState(): Promise {\n return (await this.getOrAddQRKeyring()).getMemStore();\n }\n\n async submitQRCryptoHDKey(cryptoHDKey: string): Promise {\n (await this.getOrAddQRKeyring()).submitCryptoHDKey(cryptoHDKey);\n }\n\n async submitQRCryptoAccount(cryptoAccount: string): Promise {\n (await this.getOrAddQRKeyring()).submitCryptoAccount(cryptoAccount);\n }\n\n async submitQRSignature(\n requestId: string,\n ethSignature: string,\n ): Promise {\n (await this.getOrAddQRKeyring()).submitSignature(requestId, ethSignature);\n }\n\n async cancelQRSignRequest(): Promise {\n (await this.getOrAddQRKeyring()).cancelSignRequest();\n }\n\n async connectQRHardware(\n page: number,\n ): Promise<{ balance: string; address: string; index: number }[]> {\n try {\n const keyring = await this.getOrAddQRKeyring();\n let accounts;\n switch (page) {\n case -1:\n accounts = await keyring.getPreviousPage();\n break;\n case 1:\n accounts = await keyring.getNextPage();\n break;\n default:\n accounts = await keyring.getFirstPage();\n }\n return accounts.map((account: any) => {\n return {\n ...account,\n balance: '0x0',\n };\n });\n } catch (e) {\n throw new Error(`Unspecified error when connect QR Hardware, ${e}`);\n }\n }\n\n async unlockQRHardwareWalletAccount(index: number): Promise {\n const keyring = await this.getOrAddQRKeyring();\n\n keyring.setAccountToUnlock(index);\n const oldAccounts = await this.#keyring.getAccounts();\n await this.#keyring.addNewAccount(keyring);\n const newAccounts = await this.#keyring.getAccounts();\n this.updateIdentities(newAccounts);\n newAccounts.forEach((address: string) => {\n if (!oldAccounts.includes(address)) {\n if (this.setAccountLabel) {\n this.setAccountLabel(address, `${keyring.getName()} ${index}`);\n }\n this.setSelectedAddress(address);\n }\n });\n await this.#keyring.persistAllKeyrings();\n await this.fullUpdate();\n }\n\n async getAccountKeyringType(account: string): Promise {\n return (await this.#keyring.getKeyringForAccount(account)).type;\n }\n\n async forgetQRDevice(): Promise {\n const keyring = await this.getOrAddQRKeyring();\n keyring.forgetDevice();\n const accounts = (await this.#keyring.getAccounts()) as string[];\n accounts.forEach((account) => {\n this.setSelectedAddress(account);\n });\n await this.#keyring.persistAllKeyrings();\n await this.fullUpdate();\n }\n}\n\nexport default KeyringController;\n"]} \ No newline at end of file diff --git a/dist/message-manager/AbstractMessageManager.d.ts b/dist/message-manager/AbstractMessageManager.d.ts deleted file mode 100644 index 6226fc8bf2..0000000000 --- a/dist/message-manager/AbstractMessageManager.d.ts +++ /dev/null @@ -1,172 +0,0 @@ -/// -import { EventEmitter } from 'events'; -import { BaseController, BaseConfig, BaseState } from '../BaseController'; -/** - * @type OriginalRequest - * - * Represents the original request object for adding a message. - * @property origin? - Is it is specified, represents the origin - */ -export interface OriginalRequest { - origin?: string; -} -/** - * @type Message - * - * Represents and contains data about a signing type signature request. - * @property id - An id to track and identify the message object - * @property type - The json-prc signing method for which a signature request has been made. - * A 'Message' which always has a signing type - * @property rawSig - Raw data of the signature request - */ -export interface AbstractMessage { - id: string; - time: number; - status: string; - type: string; - rawSig?: string; -} -/** - * @type MessageParams - * - * Represents the parameters to pass to the signing method once the signature request is approved. - * @property from - Address to sign this message from - * @property origin? - Added for request origin identification - */ -export interface AbstractMessageParams { - from: string; - origin?: string; -} -/** - * @type MessageParamsMetamask - * - * Represents the parameters to pass to the signing method once the signature request is approved - * plus data added by MetaMask. - * @property metamaskId - Added for tracking and identification within MetaMask - * @property from - Address to sign this message from - * @property origin? - Added for request origin identification - */ -export interface AbstractMessageParamsMetamask extends AbstractMessageParams { - metamaskId?: string; -} -/** - * @type MessageManagerState - * - * Message Manager state - * @property unapprovedMessages - A collection of all Messages in the 'unapproved' state - * @property unapprovedMessagesCount - The count of all Messages in this.unapprovedMessages - */ -export interface MessageManagerState extends BaseState { - unapprovedMessages: { - [key: string]: M; - }; - unapprovedMessagesCount: number; -} -/** - * Controller in charge of managing - storing, adding, removing, updating - Messages. - */ -export declare abstract class AbstractMessageManager extends BaseController> { - protected messages: M[]; - /** - * Saves the unapproved messages, and their count to state. - * - */ - protected saveMessageList(): void; - /** - * Updates the status of a Message in this.messages. - * - * @param messageId - The id of the Message to update. - * @param status - The new status of the Message. - */ - protected setMessageStatus(messageId: string, status: string): void; - /** - * Sets a Message in this.messages to the passed Message if the ids are equal. - * Then saves the unapprovedMessage list to storage. - * - * @param message - A Message that will replace an existing Message (with the id) in this.messages. - */ - protected updateMessage(message: M): void; - /** - * EventEmitter instance used to listen to specific message events - */ - hub: EventEmitter; - /** - * Name of this controller used during composition - */ - name: string; - /** - * Creates an AbstractMessageManager instance. - * - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor(config?: Partial, state?: Partial>); - /** - * A getter for the number of 'unapproved' Messages in this.messages. - * - * @returns The number of 'unapproved' Messages in this.messages. - */ - getUnapprovedMessagesCount(): number; - /** - * A getter for the 'unapproved' Messages in state messages. - * - * @returns An index of Message ids to Messages, for all 'unapproved' Messages in this.messages. - */ - getUnapprovedMessages(): { - [key: string]: M; - }; - /** - * Adds a passed Message to this.messages, and calls this.saveMessageList() to save - * the unapproved Messages from that list to this.messages. - * - * @param message - The Message to add to this.messages. - */ - addMessage(message: M): void; - /** - * Returns a specified Message. - * - * @param messageId - The id of the Message to get. - * @returns The Message with the id that matches the passed messageId, or undefined - * if no Message has that id. - */ - getMessage(messageId: string): M | undefined; - /** - * Approves a Message. Sets the message status via a call to this.setMessageStatusApproved, - * and returns a promise with any the message params modified for proper signing. - * - * @param messageParams - The messageParams to be used when signing method is called, - * plus data added by MetaMask. - * @returns Promise resolving to the messageParams with the metamaskId property removed. - */ - approveMessage(messageParams: PM): Promise

; - /** - * Sets a Message status to 'approved' via a call to this.setMessageStatus. - * - * @param messageId - The id of the Message to approve. - */ - setMessageStatusApproved(messageId: string): void; - /** - * Sets a Message status to 'signed' via a call to this.setMessageStatus and updates - * that Message in this.messages by adding the raw signature data of the signature - * request to the Message. - * - * @param messageId - The id of the Message to sign. - * @param rawSig - The raw data of the signature request. - */ - setMessageStatusSigned(messageId: string, rawSig: string): void; - /** - * Removes the metamaskId property from passed messageParams and returns a promise which - * resolves the updated messageParams - * - * @param messageParams - The messageParams to modify - * @returns Promise resolving to the messageParams with the metamaskId property removed - */ - abstract prepMessageForSigning(messageParams: PM): Promise

; - /** - * Sets a Message status to 'rejected' via a call to this.setMessageStatus. - * - * @param messageId - The id of the Message to reject. - */ - rejectMessage(messageId: string): void; -} -export default AbstractMessageManager; diff --git a/dist/message-manager/AbstractMessageManager.js b/dist/message-manager/AbstractMessageManager.js deleted file mode 100644 index faec6ab542..0000000000 --- a/dist/message-manager/AbstractMessageManager.js +++ /dev/null @@ -1,167 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.AbstractMessageManager = void 0; -const events_1 = require("events"); -const BaseController_1 = require("../BaseController"); -/** - * Controller in charge of managing - storing, adding, removing, updating - Messages. - */ -class AbstractMessageManager extends BaseController_1.BaseController { - /** - * Creates an AbstractMessageManager instance. - * - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor(config, state) { - super(config, state); - /** - * EventEmitter instance used to listen to specific message events - */ - this.hub = new events_1.EventEmitter(); - /** - * Name of this controller used during composition - */ - this.name = 'AbstractMessageManager'; - this.defaultState = { - unapprovedMessages: {}, - unapprovedMessagesCount: 0, - }; - this.messages = []; - this.initialize(); - } - /** - * Saves the unapproved messages, and their count to state. - * - */ - saveMessageList() { - const unapprovedMessages = this.getUnapprovedMessages(); - const unapprovedMessagesCount = this.getUnapprovedMessagesCount(); - this.update({ unapprovedMessages, unapprovedMessagesCount }); - this.hub.emit('updateBadge'); - } - /** - * Updates the status of a Message in this.messages. - * - * @param messageId - The id of the Message to update. - * @param status - The new status of the Message. - */ - setMessageStatus(messageId, status) { - const message = this.getMessage(messageId); - if (!message) { - throw new Error(`${this.name}: Message not found for id: ${messageId}.`); - } - message.status = status; - this.updateMessage(message); - this.hub.emit(`${messageId}:${status}`, message); - if (status === 'rejected' || status === 'signed' || status === 'errored') { - this.hub.emit(`${messageId}:finished`, message); - } - } - /** - * Sets a Message in this.messages to the passed Message if the ids are equal. - * Then saves the unapprovedMessage list to storage. - * - * @param message - A Message that will replace an existing Message (with the id) in this.messages. - */ - updateMessage(message) { - const index = this.messages.findIndex((msg) => message.id === msg.id); - /* istanbul ignore next */ - if (index !== -1) { - this.messages[index] = message; - } - this.saveMessageList(); - } - /** - * A getter for the number of 'unapproved' Messages in this.messages. - * - * @returns The number of 'unapproved' Messages in this.messages. - */ - getUnapprovedMessagesCount() { - return Object.keys(this.getUnapprovedMessages()).length; - } - /** - * A getter for the 'unapproved' Messages in state messages. - * - * @returns An index of Message ids to Messages, for all 'unapproved' Messages in this.messages. - */ - getUnapprovedMessages() { - return this.messages - .filter((message) => message.status === 'unapproved') - .reduce((result, message) => { - result[message.id] = message; - return result; - }, {}); - } - /** - * Adds a passed Message to this.messages, and calls this.saveMessageList() to save - * the unapproved Messages from that list to this.messages. - * - * @param message - The Message to add to this.messages. - */ - addMessage(message) { - this.messages.push(message); - this.saveMessageList(); - } - /** - * Returns a specified Message. - * - * @param messageId - The id of the Message to get. - * @returns The Message with the id that matches the passed messageId, or undefined - * if no Message has that id. - */ - getMessage(messageId) { - return this.messages.find((message) => message.id === messageId); - } - /** - * Approves a Message. Sets the message status via a call to this.setMessageStatusApproved, - * and returns a promise with any the message params modified for proper signing. - * - * @param messageParams - The messageParams to be used when signing method is called, - * plus data added by MetaMask. - * @returns Promise resolving to the messageParams with the metamaskId property removed. - */ - approveMessage(messageParams) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - this.setMessageStatusApproved(messageParams.metamaskId); - return this.prepMessageForSigning(messageParams); - } - /** - * Sets a Message status to 'approved' via a call to this.setMessageStatus. - * - * @param messageId - The id of the Message to approve. - */ - setMessageStatusApproved(messageId) { - this.setMessageStatus(messageId, 'approved'); - } - /** - * Sets a Message status to 'signed' via a call to this.setMessageStatus and updates - * that Message in this.messages by adding the raw signature data of the signature - * request to the Message. - * - * @param messageId - The id of the Message to sign. - * @param rawSig - The raw data of the signature request. - */ - setMessageStatusSigned(messageId, rawSig) { - const message = this.getMessage(messageId); - /* istanbul ignore if */ - if (!message) { - return; - } - message.rawSig = rawSig; - this.updateMessage(message); - this.setMessageStatus(messageId, 'signed'); - } - /** - * Sets a Message status to 'rejected' via a call to this.setMessageStatus. - * - * @param messageId - The id of the Message to reject. - */ - rejectMessage(messageId) { - this.setMessageStatus(messageId, 'rejected'); - } -} -exports.AbstractMessageManager = AbstractMessageManager; -exports.default = AbstractMessageManager; -//# sourceMappingURL=AbstractMessageManager.js.map \ No newline at end of file diff --git a/dist/message-manager/AbstractMessageManager.js.map b/dist/message-manager/AbstractMessageManager.js.map deleted file mode 100644 index 210ce2fc23..0000000000 --- a/dist/message-manager/AbstractMessageManager.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"AbstractMessageManager.js","sourceRoot":"","sources":["../../src/message-manager/AbstractMessageManager.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from 'events';\nimport { BaseController, BaseConfig, BaseState } from '../BaseController';\n\n/**\n * @type OriginalRequest\n *\n * Represents the original request object for adding a message.\n * @property origin? - Is it is specified, represents the origin\n */\nexport interface OriginalRequest {\n origin?: string;\n}\n\n/**\n * @type Message\n *\n * Represents and contains data about a signing type signature request.\n * @property id - An id to track and identify the message object\n * @property type - The json-prc signing method for which a signature request has been made.\n * A 'Message' which always has a signing type\n * @property rawSig - Raw data of the signature request\n */\nexport interface AbstractMessage {\n id: string;\n time: number;\n status: string;\n type: string;\n rawSig?: string;\n}\n\n/**\n * @type MessageParams\n *\n * Represents the parameters to pass to the signing method once the signature request is approved.\n * @property from - Address to sign this message from\n * @property origin? - Added for request origin identification\n */\nexport interface AbstractMessageParams {\n from: string;\n origin?: string;\n}\n\n/**\n * @type MessageParamsMetamask\n *\n * Represents the parameters to pass to the signing method once the signature request is approved\n * plus data added by MetaMask.\n * @property metamaskId - Added for tracking and identification within MetaMask\n * @property from - Address to sign this message from\n * @property origin? - Added for request origin identification\n */\nexport interface AbstractMessageParamsMetamask extends AbstractMessageParams {\n metamaskId?: string;\n}\n\n/**\n * @type MessageManagerState\n *\n * Message Manager state\n * @property unapprovedMessages - A collection of all Messages in the 'unapproved' state\n * @property unapprovedMessagesCount - The count of all Messages in this.unapprovedMessages\n */\nexport interface MessageManagerState\n extends BaseState {\n unapprovedMessages: { [key: string]: M };\n unapprovedMessagesCount: number;\n}\n\n/**\n * Controller in charge of managing - storing, adding, removing, updating - Messages.\n */\nexport abstract class AbstractMessageManager<\n M extends AbstractMessage,\n P extends AbstractMessageParams,\n PM extends AbstractMessageParamsMetamask,\n> extends BaseController> {\n protected messages: M[];\n\n /**\n * Saves the unapproved messages, and their count to state.\n *\n */\n protected saveMessageList() {\n const unapprovedMessages = this.getUnapprovedMessages();\n const unapprovedMessagesCount = this.getUnapprovedMessagesCount();\n this.update({ unapprovedMessages, unapprovedMessagesCount });\n this.hub.emit('updateBadge');\n }\n\n /**\n * Updates the status of a Message in this.messages.\n *\n * @param messageId - The id of the Message to update.\n * @param status - The new status of the Message.\n */\n protected setMessageStatus(messageId: string, status: string) {\n const message = this.getMessage(messageId);\n if (!message) {\n throw new Error(`${this.name}: Message not found for id: ${messageId}.`);\n }\n message.status = status;\n this.updateMessage(message);\n this.hub.emit(`${messageId}:${status}`, message);\n if (status === 'rejected' || status === 'signed' || status === 'errored') {\n this.hub.emit(`${messageId}:finished`, message);\n }\n }\n\n /**\n * Sets a Message in this.messages to the passed Message if the ids are equal.\n * Then saves the unapprovedMessage list to storage.\n *\n * @param message - A Message that will replace an existing Message (with the id) in this.messages.\n */\n protected updateMessage(message: M) {\n const index = this.messages.findIndex((msg) => message.id === msg.id);\n /* istanbul ignore next */\n if (index !== -1) {\n this.messages[index] = message;\n }\n this.saveMessageList();\n }\n\n /**\n * EventEmitter instance used to listen to specific message events\n */\n hub = new EventEmitter();\n\n /**\n * Name of this controller used during composition\n */\n override name = 'AbstractMessageManager';\n\n /**\n * Creates an AbstractMessageManager instance.\n *\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n config?: Partial,\n state?: Partial>,\n ) {\n super(config, state);\n this.defaultState = {\n unapprovedMessages: {},\n unapprovedMessagesCount: 0,\n };\n this.messages = [];\n this.initialize();\n }\n\n /**\n * A getter for the number of 'unapproved' Messages in this.messages.\n *\n * @returns The number of 'unapproved' Messages in this.messages.\n */\n getUnapprovedMessagesCount() {\n return Object.keys(this.getUnapprovedMessages()).length;\n }\n\n /**\n * A getter for the 'unapproved' Messages in state messages.\n *\n * @returns An index of Message ids to Messages, for all 'unapproved' Messages in this.messages.\n */\n getUnapprovedMessages() {\n return this.messages\n .filter((message) => message.status === 'unapproved')\n .reduce((result: { [key: string]: M }, message: M) => {\n result[message.id] = message;\n return result;\n }, {}) as { [key: string]: M };\n }\n\n /**\n * Adds a passed Message to this.messages, and calls this.saveMessageList() to save\n * the unapproved Messages from that list to this.messages.\n *\n * @param message - The Message to add to this.messages.\n */\n addMessage(message: M) {\n this.messages.push(message);\n this.saveMessageList();\n }\n\n /**\n * Returns a specified Message.\n *\n * @param messageId - The id of the Message to get.\n * @returns The Message with the id that matches the passed messageId, or undefined\n * if no Message has that id.\n */\n getMessage(messageId: string) {\n return this.messages.find((message) => message.id === messageId);\n }\n\n /**\n * Approves a Message. Sets the message status via a call to this.setMessageStatusApproved,\n * and returns a promise with any the message params modified for proper signing.\n *\n * @param messageParams - The messageParams to be used when signing method is called,\n * plus data added by MetaMask.\n * @returns Promise resolving to the messageParams with the metamaskId property removed.\n */\n approveMessage(messageParams: PM): Promise

{\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n this.setMessageStatusApproved(messageParams.metamaskId);\n return this.prepMessageForSigning(messageParams);\n }\n\n /**\n * Sets a Message status to 'approved' via a call to this.setMessageStatus.\n *\n * @param messageId - The id of the Message to approve.\n */\n setMessageStatusApproved(messageId: string) {\n this.setMessageStatus(messageId, 'approved');\n }\n\n /**\n * Sets a Message status to 'signed' via a call to this.setMessageStatus and updates\n * that Message in this.messages by adding the raw signature data of the signature\n * request to the Message.\n *\n * @param messageId - The id of the Message to sign.\n * @param rawSig - The raw data of the signature request.\n */\n setMessageStatusSigned(messageId: string, rawSig: string) {\n const message = this.getMessage(messageId);\n /* istanbul ignore if */\n if (!message) {\n return;\n }\n message.rawSig = rawSig;\n this.updateMessage(message);\n this.setMessageStatus(messageId, 'signed');\n }\n\n /**\n * Removes the metamaskId property from passed messageParams and returns a promise which\n * resolves the updated messageParams\n *\n * @param messageParams - The messageParams to modify\n * @returns Promise resolving to the messageParams with the metamaskId property removed\n */\n abstract prepMessageForSigning(messageParams: PM): Promise

;\n\n /**\n * Sets a Message status to 'rejected' via a call to this.setMessageStatus.\n *\n * @param messageId - The id of the Message to reject.\n */\n rejectMessage(messageId: string) {\n this.setMessageStatus(messageId, 'rejected');\n }\n}\n\nexport default AbstractMessageManager;\n"]} \ No newline at end of file diff --git a/dist/message-manager/MessageManager.d.ts b/dist/message-manager/MessageManager.d.ts deleted file mode 100644 index c8fb2fbe01..0000000000 --- a/dist/message-manager/MessageManager.d.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { AbstractMessageManager, AbstractMessage, AbstractMessageParams, AbstractMessageParamsMetamask, OriginalRequest } from './AbstractMessageManager'; -/** - * @type Message - * - * Represents and contains data about a 'eth_sign' type signature request. - * These are created when a signature for an eth_sign call is requested. - * @property id - An id to track and identify the message object - * @property messageParams - The parameters to pass to the eth_sign method once the signature request is approved - * @property type - The json-prc signing method for which a signature request has been made. - * A 'Message' which always has a 'eth_sign' type - * @property rawSig - Raw data of the signature request - */ -export interface Message extends AbstractMessage { - messageParams: MessageParams; -} -/** - * @type PersonalMessageParams - * - * Represents the parameters to pass to the eth_sign method once the signature request is approved. - * @property data - A hex string conversion of the raw buffer data of the signature request - * @property from - Address to sign this message from - * @property origin? - Added for request origin identification - */ -export interface MessageParams extends AbstractMessageParams { - data: string; -} -/** - * @type MessageParamsMetamask - * - * Represents the parameters to pass to the eth_sign method once the signature request is approved - * plus data added by MetaMask. - * @property metamaskId - Added for tracking and identification within MetaMask - * @property data - A hex string conversion of the raw buffer data of the signature request - * @property from - Address to sign this message from - * @property origin? - Added for request origin identification - */ -export interface MessageParamsMetamask extends AbstractMessageParamsMetamask { - data: string; -} -/** - * Controller in charge of managing - storing, adding, removing, updating - Messages. - */ -export declare class MessageManager extends AbstractMessageManager { - /** - * Name of this controller used during composition - */ - name: string; - /** - * Creates a new Message with an 'unapproved' status using the passed messageParams. - * this.addMessage is called to add the new Message to this.messages, and to save the unapproved Messages. - * - * @param messageParams - The params for the eth_sign call to be made after the message is approved. - * @param req - The original request object possibly containing the origin. - * @returns Promise resolving to the raw data of the signature request. - */ - addUnapprovedMessageAsync(messageParams: MessageParams, req?: OriginalRequest): Promise; - /** - * Creates a new Message with an 'unapproved' status using the passed messageParams. - * this.addMessage is called to add the new Message to this.messages, and to save the - * unapproved Messages. - * - * @param messageParams - The params for the eth_sign call to be made after the message - * is approved. - * @param req - The original request object possibly containing the origin. - * @returns The id of the newly created message. - */ - addUnapprovedMessage(messageParams: MessageParams, req?: OriginalRequest): string; - /** - * Removes the metamaskId property from passed messageParams and returns a promise which - * resolves the updated messageParams. - * - * @param messageParams - The messageParams to modify. - * @returns Promise resolving to the messageParams with the metamaskId property removed. - */ - prepMessageForSigning(messageParams: MessageParamsMetamask): Promise; -} -export default MessageManager; diff --git a/dist/message-manager/MessageManager.js b/dist/message-manager/MessageManager.js deleted file mode 100644 index a89a95b46d..0000000000 --- a/dist/message-manager/MessageManager.js +++ /dev/null @@ -1,83 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.MessageManager = void 0; -const uuid_1 = require("uuid"); -const util_1 = require("../util"); -const AbstractMessageManager_1 = require("./AbstractMessageManager"); -/** - * Controller in charge of managing - storing, adding, removing, updating - Messages. - */ -class MessageManager extends AbstractMessageManager_1.AbstractMessageManager { - constructor() { - super(...arguments); - /** - * Name of this controller used during composition - */ - this.name = 'MessageManager'; - } - /** - * Creates a new Message with an 'unapproved' status using the passed messageParams. - * this.addMessage is called to add the new Message to this.messages, and to save the unapproved Messages. - * - * @param messageParams - The params for the eth_sign call to be made after the message is approved. - * @param req - The original request object possibly containing the origin. - * @returns Promise resolving to the raw data of the signature request. - */ - addUnapprovedMessageAsync(messageParams, req) { - return new Promise((resolve, reject) => { - (0, util_1.validateSignMessageData)(messageParams); - const messageId = this.addUnapprovedMessage(messageParams, req); - this.hub.once(`${messageId}:finished`, (data) => { - switch (data.status) { - case 'signed': - return resolve(data.rawSig); - case 'rejected': - return reject(new Error('MetaMask Message Signature: User denied message signature.')); - default: - return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(messageParams)}`)); - } - }); - }); - } - /** - * Creates a new Message with an 'unapproved' status using the passed messageParams. - * this.addMessage is called to add the new Message to this.messages, and to save the - * unapproved Messages. - * - * @param messageParams - The params for the eth_sign call to be made after the message - * is approved. - * @param req - The original request object possibly containing the origin. - * @returns The id of the newly created message. - */ - addUnapprovedMessage(messageParams, req) { - if (req) { - messageParams.origin = req.origin; - } - messageParams.data = (0, util_1.normalizeMessageData)(messageParams.data); - const messageId = (0, uuid_1.v1)(); - const messageData = { - id: messageId, - messageParams, - status: 'unapproved', - time: Date.now(), - type: 'eth_sign', - }; - this.addMessage(messageData); - this.hub.emit(`unapprovedMessage`, Object.assign(Object.assign({}, messageParams), { metamaskId: messageId })); - return messageId; - } - /** - * Removes the metamaskId property from passed messageParams and returns a promise which - * resolves the updated messageParams. - * - * @param messageParams - The messageParams to modify. - * @returns Promise resolving to the messageParams with the metamaskId property removed. - */ - prepMessageForSigning(messageParams) { - delete messageParams.metamaskId; - return Promise.resolve(messageParams); - } -} -exports.MessageManager = MessageManager; -exports.default = MessageManager; -//# sourceMappingURL=MessageManager.js.map \ No newline at end of file diff --git a/dist/message-manager/MessageManager.js.map b/dist/message-manager/MessageManager.js.map deleted file mode 100644 index 56fb1bc15c..0000000000 --- a/dist/message-manager/MessageManager.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"MessageManager.js","sourceRoot":"","sources":["../../src/message-manager/MessageManager.ts"],"names":[],"mappings":";;;AAAA,+BAAoC;AACpC,kCAAwE;AACxE,qEAMkC;AA2ClC;;GAEG;AACH,MAAa,cAAe,SAAQ,+CAInC;IAJD;;QAKE;;WAEG;QACM,SAAI,GAAG,gBAAgB,CAAC;IAoFnC,CAAC;IAlFC;;;;;;;OAOG;IACH,yBAAyB,CACvB,aAA4B,EAC5B,GAAqB;QAErB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAA,8BAAuB,EAAC,aAAa,CAAC,CAAC;YACvC,MAAM,SAAS,GAAG,IAAI,CAAC,oBAAoB,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;YAChE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,SAAS,WAAW,EAAE,CAAC,IAAa,EAAE,EAAE;gBACvD,QAAQ,IAAI,CAAC,MAAM,EAAE;oBACnB,KAAK,QAAQ;wBACX,OAAO,OAAO,CAAC,IAAI,CAAC,MAAgB,CAAC,CAAC;oBACxC,KAAK,UAAU;wBACb,OAAO,MAAM,CACX,IAAI,KAAK,CACP,4DAA4D,CAC7D,CACF,CAAC;oBACJ;wBACE,OAAO,MAAM,CACX,IAAI,KAAK,CACP,gDAAgD,IAAI,CAAC,SAAS,CAC5D,aAAa,CACd,EAAE,CACJ,CACF,CAAC;iBACL;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;OASG;IACH,oBAAoB,CAAC,aAA4B,EAAE,GAAqB;QACtE,IAAI,GAAG,EAAE;YACP,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;SACnC;QACD,aAAa,CAAC,IAAI,GAAG,IAAA,2BAAoB,EAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QAC9D,MAAM,SAAS,GAAG,IAAA,SAAM,GAAE,CAAC;QAC3B,MAAM,WAAW,GAAY;YAC3B,EAAE,EAAE,SAAS;YACb,aAAa;YACb,MAAM,EAAE,YAAY;YACpB,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE;YAChB,IAAI,EAAE,UAAU;SACjB,CAAC;QACF,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;QAC7B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,mBAAmB,kCAC5B,aAAa,GACb,EAAE,UAAU,EAAE,SAAS,EAAE,EAC5B,CAAC;QACH,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;;;;;OAMG;IACH,qBAAqB,CACnB,aAAoC;QAEpC,OAAO,aAAa,CAAC,UAAU,CAAC;QAChC,OAAO,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IACxC,CAAC;CACF;AA5FD,wCA4FC;AAED,kBAAe,cAAc,CAAC","sourcesContent":["import { v1 as random } from 'uuid';\nimport { validateSignMessageData, normalizeMessageData } from '../util';\nimport {\n AbstractMessageManager,\n AbstractMessage,\n AbstractMessageParams,\n AbstractMessageParamsMetamask,\n OriginalRequest,\n} from './AbstractMessageManager';\n\n/**\n * @type Message\n *\n * Represents and contains data about a 'eth_sign' type signature request.\n * These are created when a signature for an eth_sign call is requested.\n * @property id - An id to track and identify the message object\n * @property messageParams - The parameters to pass to the eth_sign method once the signature request is approved\n * @property type - The json-prc signing method for which a signature request has been made.\n * A 'Message' which always has a 'eth_sign' type\n * @property rawSig - Raw data of the signature request\n */\nexport interface Message extends AbstractMessage {\n messageParams: MessageParams;\n}\n\n/**\n * @type PersonalMessageParams\n *\n * Represents the parameters to pass to the eth_sign method once the signature request is approved.\n * @property data - A hex string conversion of the raw buffer data of the signature request\n * @property from - Address to sign this message from\n * @property origin? - Added for request origin identification\n */\nexport interface MessageParams extends AbstractMessageParams {\n data: string;\n}\n\n/**\n * @type MessageParamsMetamask\n *\n * Represents the parameters to pass to the eth_sign method once the signature request is approved\n * plus data added by MetaMask.\n * @property metamaskId - Added for tracking and identification within MetaMask\n * @property data - A hex string conversion of the raw buffer data of the signature request\n * @property from - Address to sign this message from\n * @property origin? - Added for request origin identification\n */\nexport interface MessageParamsMetamask extends AbstractMessageParamsMetamask {\n data: string;\n}\n\n/**\n * Controller in charge of managing - storing, adding, removing, updating - Messages.\n */\nexport class MessageManager extends AbstractMessageManager<\n Message,\n MessageParams,\n MessageParamsMetamask\n> {\n /**\n * Name of this controller used during composition\n */\n override name = 'MessageManager';\n\n /**\n * Creates a new Message with an 'unapproved' status using the passed messageParams.\n * this.addMessage is called to add the new Message to this.messages, and to save the unapproved Messages.\n *\n * @param messageParams - The params for the eth_sign call to be made after the message is approved.\n * @param req - The original request object possibly containing the origin.\n * @returns Promise resolving to the raw data of the signature request.\n */\n addUnapprovedMessageAsync(\n messageParams: MessageParams,\n req?: OriginalRequest,\n ): Promise {\n return new Promise((resolve, reject) => {\n validateSignMessageData(messageParams);\n const messageId = this.addUnapprovedMessage(messageParams, req);\n this.hub.once(`${messageId}:finished`, (data: Message) => {\n switch (data.status) {\n case 'signed':\n return resolve(data.rawSig as string);\n case 'rejected':\n return reject(\n new Error(\n 'MetaMask Message Signature: User denied message signature.',\n ),\n );\n default:\n return reject(\n new Error(\n `MetaMask Message Signature: Unknown problem: ${JSON.stringify(\n messageParams,\n )}`,\n ),\n );\n }\n });\n });\n }\n\n /**\n * Creates a new Message with an 'unapproved' status using the passed messageParams.\n * this.addMessage is called to add the new Message to this.messages, and to save the\n * unapproved Messages.\n *\n * @param messageParams - The params for the eth_sign call to be made after the message\n * is approved.\n * @param req - The original request object possibly containing the origin.\n * @returns The id of the newly created message.\n */\n addUnapprovedMessage(messageParams: MessageParams, req?: OriginalRequest) {\n if (req) {\n messageParams.origin = req.origin;\n }\n messageParams.data = normalizeMessageData(messageParams.data);\n const messageId = random();\n const messageData: Message = {\n id: messageId,\n messageParams,\n status: 'unapproved',\n time: Date.now(),\n type: 'eth_sign',\n };\n this.addMessage(messageData);\n this.hub.emit(`unapprovedMessage`, {\n ...messageParams,\n ...{ metamaskId: messageId },\n });\n return messageId;\n }\n\n /**\n * Removes the metamaskId property from passed messageParams and returns a promise which\n * resolves the updated messageParams.\n *\n * @param messageParams - The messageParams to modify.\n * @returns Promise resolving to the messageParams with the metamaskId property removed.\n */\n prepMessageForSigning(\n messageParams: MessageParamsMetamask,\n ): Promise {\n delete messageParams.metamaskId;\n return Promise.resolve(messageParams);\n }\n}\n\nexport default MessageManager;\n"]} \ No newline at end of file diff --git a/dist/message-manager/PersonalMessageManager.d.ts b/dist/message-manager/PersonalMessageManager.d.ts deleted file mode 100644 index 999eeb3614..0000000000 --- a/dist/message-manager/PersonalMessageManager.d.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { AbstractMessageManager, AbstractMessage, AbstractMessageParams, AbstractMessageParamsMetamask, OriginalRequest } from './AbstractMessageManager'; -/** - * @type Message - * - * Represents and contains data about a 'personal_sign' type signature request. - * These are created when a signature for a personal_sign call is requested. - * @property id - An id to track and identify the message object - * @property messageParams - The parameters to pass to the personal_sign method once the signature request is approved - * @property type - The json-prc signing method for which a signature request has been made. - * A 'Message' which always has a 'personal_sign' type - * @property rawSig - Raw data of the signature request - */ -export interface PersonalMessage extends AbstractMessage { - messageParams: PersonalMessageParams; -} -/** - * @type PersonalMessageParams - * - * Represents the parameters to pass to the personal_sign method once the signature request is approved. - * @property data - A hex string conversion of the raw buffer data of the signature request - * @property from - Address to sign this message from - * @property origin? - Added for request origin identification - */ -export interface PersonalMessageParams extends AbstractMessageParams { - data: string; -} -/** - * @type MessageParamsMetamask - * - * Represents the parameters to pass to the personal_sign method once the signature request is approved - * plus data added by MetaMask. - * @property metamaskId - Added for tracking and identification within MetaMask - * @property data - A hex string conversion of the raw buffer data of the signature request - * @property from - Address to sign this message from - * @property origin? - Added for request origin identification - */ -export interface PersonalMessageParamsMetamask extends AbstractMessageParamsMetamask { - data: string; -} -/** - * Controller in charge of managing - storing, adding, removing, updating - Messages. - */ -export declare class PersonalMessageManager extends AbstractMessageManager { - /** - * Name of this controller used during composition - */ - name: string; - /** - * Creates a new Message with an 'unapproved' status using the passed messageParams. - * this.addMessage is called to add the new Message to this.messages, and to save the unapproved Messages. - * - * @param messageParams - The params for the personal_sign call to be made after the message is approved. - * @param req - The original request object possibly containing the origin. - * @returns Promise resolving to the raw data of the signature request. - */ - addUnapprovedMessageAsync(messageParams: PersonalMessageParams, req?: OriginalRequest): Promise; - /** - * Creates a new Message with an 'unapproved' status using the passed messageParams. - * this.addMessage is called to add the new Message to this.messages, and to save the - * unapproved Messages. - * - * @param messageParams - The params for the personal_sign call to be made after the message - * is approved. - * @param req - The original request object possibly containing the origin. - * @returns The id of the newly created message. - */ - addUnapprovedMessage(messageParams: PersonalMessageParams, req?: OriginalRequest): string; - /** - * Removes the metamaskId property from passed messageParams and returns a promise which - * resolves the updated messageParams. - * - * @param messageParams - The messageParams to modify. - * @returns Promise resolving to the messageParams with the metamaskId property removed. - */ - prepMessageForSigning(messageParams: PersonalMessageParamsMetamask): Promise; -} -export default PersonalMessageManager; diff --git a/dist/message-manager/PersonalMessageManager.js b/dist/message-manager/PersonalMessageManager.js deleted file mode 100644 index 12f7215c3b..0000000000 --- a/dist/message-manager/PersonalMessageManager.js +++ /dev/null @@ -1,83 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.PersonalMessageManager = void 0; -const uuid_1 = require("uuid"); -const util_1 = require("../util"); -const AbstractMessageManager_1 = require("./AbstractMessageManager"); -/** - * Controller in charge of managing - storing, adding, removing, updating - Messages. - */ -class PersonalMessageManager extends AbstractMessageManager_1.AbstractMessageManager { - constructor() { - super(...arguments); - /** - * Name of this controller used during composition - */ - this.name = 'PersonalMessageManager'; - } - /** - * Creates a new Message with an 'unapproved' status using the passed messageParams. - * this.addMessage is called to add the new Message to this.messages, and to save the unapproved Messages. - * - * @param messageParams - The params for the personal_sign call to be made after the message is approved. - * @param req - The original request object possibly containing the origin. - * @returns Promise resolving to the raw data of the signature request. - */ - addUnapprovedMessageAsync(messageParams, req) { - return new Promise((resolve, reject) => { - (0, util_1.validateSignMessageData)(messageParams); - const messageId = this.addUnapprovedMessage(messageParams, req); - this.hub.once(`${messageId}:finished`, (data) => { - switch (data.status) { - case 'signed': - return resolve(data.rawSig); - case 'rejected': - return reject(new Error('MetaMask Personal Message Signature: User denied message signature.')); - default: - return reject(new Error(`MetaMask Personal Message Signature: Unknown problem: ${JSON.stringify(messageParams)}`)); - } - }); - }); - } - /** - * Creates a new Message with an 'unapproved' status using the passed messageParams. - * this.addMessage is called to add the new Message to this.messages, and to save the - * unapproved Messages. - * - * @param messageParams - The params for the personal_sign call to be made after the message - * is approved. - * @param req - The original request object possibly containing the origin. - * @returns The id of the newly created message. - */ - addUnapprovedMessage(messageParams, req) { - if (req) { - messageParams.origin = req.origin; - } - messageParams.data = (0, util_1.normalizeMessageData)(messageParams.data); - const messageId = (0, uuid_1.v1)(); - const messageData = { - id: messageId, - messageParams, - status: 'unapproved', - time: Date.now(), - type: 'personal_sign', - }; - this.addMessage(messageData); - this.hub.emit(`unapprovedMessage`, Object.assign(Object.assign({}, messageParams), { metamaskId: messageId })); - return messageId; - } - /** - * Removes the metamaskId property from passed messageParams and returns a promise which - * resolves the updated messageParams. - * - * @param messageParams - The messageParams to modify. - * @returns Promise resolving to the messageParams with the metamaskId property removed. - */ - prepMessageForSigning(messageParams) { - delete messageParams.metamaskId; - return Promise.resolve(messageParams); - } -} -exports.PersonalMessageManager = PersonalMessageManager; -exports.default = PersonalMessageManager; -//# sourceMappingURL=PersonalMessageManager.js.map \ No newline at end of file diff --git a/dist/message-manager/PersonalMessageManager.js.map b/dist/message-manager/PersonalMessageManager.js.map deleted file mode 100644 index ef1f8134c2..0000000000 --- a/dist/message-manager/PersonalMessageManager.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"PersonalMessageManager.js","sourceRoot":"","sources":["../../src/message-manager/PersonalMessageManager.ts"],"names":[],"mappings":";;;AAAA,+BAAoC;AACpC,kCAAwE;AACxE,qEAMkC;AA4ClC;;GAEG;AACH,MAAa,sBAAuB,SAAQ,+CAI3C;IAJD;;QAKE;;WAEG;QACM,SAAI,GAAG,wBAAwB,CAAC;IAuF3C,CAAC;IArFC;;;;;;;OAOG;IACH,yBAAyB,CACvB,aAAoC,EACpC,GAAqB;QAErB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAA,8BAAuB,EAAC,aAAa,CAAC,CAAC;YACvC,MAAM,SAAS,GAAG,IAAI,CAAC,oBAAoB,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;YAChE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,SAAS,WAAW,EAAE,CAAC,IAAqB,EAAE,EAAE;gBAC/D,QAAQ,IAAI,CAAC,MAAM,EAAE;oBACnB,KAAK,QAAQ;wBACX,OAAO,OAAO,CAAC,IAAI,CAAC,MAAgB,CAAC,CAAC;oBACxC,KAAK,UAAU;wBACb,OAAO,MAAM,CACX,IAAI,KAAK,CACP,qEAAqE,CACtE,CACF,CAAC;oBACJ;wBACE,OAAO,MAAM,CACX,IAAI,KAAK,CACP,yDAAyD,IAAI,CAAC,SAAS,CACrE,aAAa,CACd,EAAE,CACJ,CACF,CAAC;iBACL;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;OASG;IACH,oBAAoB,CAClB,aAAoC,EACpC,GAAqB;QAErB,IAAI,GAAG,EAAE;YACP,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;SACnC;QACD,aAAa,CAAC,IAAI,GAAG,IAAA,2BAAoB,EAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QAC9D,MAAM,SAAS,GAAG,IAAA,SAAM,GAAE,CAAC;QAC3B,MAAM,WAAW,GAAoB;YACnC,EAAE,EAAE,SAAS;YACb,aAAa;YACb,MAAM,EAAE,YAAY;YACpB,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE;YAChB,IAAI,EAAE,eAAe;SACtB,CAAC;QACF,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;QAC7B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,mBAAmB,kCAC5B,aAAa,GACb,EAAE,UAAU,EAAE,SAAS,EAAE,EAC5B,CAAC;QACH,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;;;;;OAMG;IACH,qBAAqB,CACnB,aAA4C;QAE5C,OAAO,aAAa,CAAC,UAAU,CAAC;QAChC,OAAO,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IACxC,CAAC;CACF;AA/FD,wDA+FC;AAED,kBAAe,sBAAsB,CAAC","sourcesContent":["import { v1 as random } from 'uuid';\nimport { validateSignMessageData, normalizeMessageData } from '../util';\nimport {\n AbstractMessageManager,\n AbstractMessage,\n AbstractMessageParams,\n AbstractMessageParamsMetamask,\n OriginalRequest,\n} from './AbstractMessageManager';\n\n/**\n * @type Message\n *\n * Represents and contains data about a 'personal_sign' type signature request.\n * These are created when a signature for a personal_sign call is requested.\n * @property id - An id to track and identify the message object\n * @property messageParams - The parameters to pass to the personal_sign method once the signature request is approved\n * @property type - The json-prc signing method for which a signature request has been made.\n * A 'Message' which always has a 'personal_sign' type\n * @property rawSig - Raw data of the signature request\n */\nexport interface PersonalMessage extends AbstractMessage {\n messageParams: PersonalMessageParams;\n}\n\n/**\n * @type PersonalMessageParams\n *\n * Represents the parameters to pass to the personal_sign method once the signature request is approved.\n * @property data - A hex string conversion of the raw buffer data of the signature request\n * @property from - Address to sign this message from\n * @property origin? - Added for request origin identification\n */\nexport interface PersonalMessageParams extends AbstractMessageParams {\n data: string;\n}\n\n/**\n * @type MessageParamsMetamask\n *\n * Represents the parameters to pass to the personal_sign method once the signature request is approved\n * plus data added by MetaMask.\n * @property metamaskId - Added for tracking and identification within MetaMask\n * @property data - A hex string conversion of the raw buffer data of the signature request\n * @property from - Address to sign this message from\n * @property origin? - Added for request origin identification\n */\nexport interface PersonalMessageParamsMetamask\n extends AbstractMessageParamsMetamask {\n data: string;\n}\n\n/**\n * Controller in charge of managing - storing, adding, removing, updating - Messages.\n */\nexport class PersonalMessageManager extends AbstractMessageManager<\n PersonalMessage,\n PersonalMessageParams,\n PersonalMessageParamsMetamask\n> {\n /**\n * Name of this controller used during composition\n */\n override name = 'PersonalMessageManager';\n\n /**\n * Creates a new Message with an 'unapproved' status using the passed messageParams.\n * this.addMessage is called to add the new Message to this.messages, and to save the unapproved Messages.\n *\n * @param messageParams - The params for the personal_sign call to be made after the message is approved.\n * @param req - The original request object possibly containing the origin.\n * @returns Promise resolving to the raw data of the signature request.\n */\n addUnapprovedMessageAsync(\n messageParams: PersonalMessageParams,\n req?: OriginalRequest,\n ): Promise {\n return new Promise((resolve, reject) => {\n validateSignMessageData(messageParams);\n const messageId = this.addUnapprovedMessage(messageParams, req);\n this.hub.once(`${messageId}:finished`, (data: PersonalMessage) => {\n switch (data.status) {\n case 'signed':\n return resolve(data.rawSig as string);\n case 'rejected':\n return reject(\n new Error(\n 'MetaMask Personal Message Signature: User denied message signature.',\n ),\n );\n default:\n return reject(\n new Error(\n `MetaMask Personal Message Signature: Unknown problem: ${JSON.stringify(\n messageParams,\n )}`,\n ),\n );\n }\n });\n });\n }\n\n /**\n * Creates a new Message with an 'unapproved' status using the passed messageParams.\n * this.addMessage is called to add the new Message to this.messages, and to save the\n * unapproved Messages.\n *\n * @param messageParams - The params for the personal_sign call to be made after the message\n * is approved.\n * @param req - The original request object possibly containing the origin.\n * @returns The id of the newly created message.\n */\n addUnapprovedMessage(\n messageParams: PersonalMessageParams,\n req?: OriginalRequest,\n ) {\n if (req) {\n messageParams.origin = req.origin;\n }\n messageParams.data = normalizeMessageData(messageParams.data);\n const messageId = random();\n const messageData: PersonalMessage = {\n id: messageId,\n messageParams,\n status: 'unapproved',\n time: Date.now(),\n type: 'personal_sign',\n };\n this.addMessage(messageData);\n this.hub.emit(`unapprovedMessage`, {\n ...messageParams,\n ...{ metamaskId: messageId },\n });\n return messageId;\n }\n\n /**\n * Removes the metamaskId property from passed messageParams and returns a promise which\n * resolves the updated messageParams.\n *\n * @param messageParams - The messageParams to modify.\n * @returns Promise resolving to the messageParams with the metamaskId property removed.\n */\n prepMessageForSigning(\n messageParams: PersonalMessageParamsMetamask,\n ): Promise {\n delete messageParams.metamaskId;\n return Promise.resolve(messageParams);\n }\n}\n\nexport default PersonalMessageManager;\n"]} \ No newline at end of file diff --git a/dist/message-manager/TypedMessageManager.d.ts b/dist/message-manager/TypedMessageManager.d.ts deleted file mode 100644 index a47573c075..0000000000 --- a/dist/message-manager/TypedMessageManager.d.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { AbstractMessageManager, AbstractMessage, AbstractMessageParams, AbstractMessageParamsMetamask, OriginalRequest } from './AbstractMessageManager'; -/** - * @type TypedMessage - * - * Represents and contains data about an 'eth_signTypedData' type signature request. - * These are created when a signature for an eth_signTypedData call is requested. - * @property id - An id to track and identify the message object - * @property error - Error corresponding to eth_signTypedData error in failure case - * @property messageParams - The parameters to pass to the eth_signTypedData method once - * the signature request is approved - * @property type - The json-prc signing method for which a signature request has been made. - * A 'TypedMessage' which always has a 'eth_signTypedData' type - * @property rawSig - Raw data of the signature request - */ -export interface TypedMessage extends AbstractMessage { - error?: string; - messageParams: TypedMessageParams; - time: number; - status: string; - type: string; - rawSig?: string; -} -/** - * @type TypedMessageParams - * - * Represents the parameters to pass to the eth_signTypedData method once the signature request is approved. - * @property data - A hex string conversion of the raw buffer or an object containing data of the signature - * request depending on version - * @property from - Address to sign this message from - * @property origin? - Added for request origin identification - */ -export interface TypedMessageParams extends AbstractMessageParams { - data: Record[] | string; -} -/** - * @type TypedMessageParamsMetamask - * - * Represents the parameters to pass to the eth_signTypedData method once the signature request is approved - * plus data added by MetaMask. - * @property metamaskId - Added for tracking and identification within MetaMask - * @property data - A hex string conversion of the raw buffer or an object containing data of the signature - * request depending on version - * @property error? - Added for message errored - * @property from - Address to sign this message from - * @property origin? - Added for request origin identification - * @property version - Compatibility version EIP712 - */ -export interface TypedMessageParamsMetamask extends AbstractMessageParamsMetamask { - data: Record[] | string; - metamaskId?: string; - error?: string; - version?: string; -} -/** - * Controller in charge of managing - storing, adding, removing, updating - TypedMessages. - */ -export declare class TypedMessageManager extends AbstractMessageManager { - /** - * Name of this controller used during composition - */ - name: string; - /** - * Creates a new TypedMessage with an 'unapproved' status using the passed messageParams. - * this.addMessage is called to add the new TypedMessage to this.messages, and to save the unapproved TypedMessages. - * - * @param messageParams - The params for the eth_signTypedData call to be made after the message is approved. - * @param version - Compatibility version EIP712. - * @param req - The original request object possibly containing the origin. - * @returns Promise resolving to the raw data of the signature request. - */ - addUnapprovedMessageAsync(messageParams: TypedMessageParams, version: string, req?: OriginalRequest): Promise; - /** - * Creates a new TypedMessage with an 'unapproved' status using the passed messageParams. - * this.addMessage is called to add the new TypedMessage to this.messages, and to save the - * unapproved TypedMessages. - * - * @param messageParams - The params for the 'eth_signTypedData' call to be made after the message - * is approved. - * @param version - Compatibility version EIP712. - * @param req - The original request object possibly containing the origin. - * @returns The id of the newly created TypedMessage. - */ - addUnapprovedMessage(messageParams: TypedMessageParams, version: string, req?: OriginalRequest): string; - /** - * Sets a TypedMessage status to 'errored' via a call to this.setMessageStatus. - * - * @param messageId - The id of the TypedMessage to error. - * @param error - The error to be included in TypedMessage. - */ - setMessageStatusErrored(messageId: string, error: string): void; - /** - * Removes the metamaskId and version properties from passed messageParams and returns a promise which - * resolves the updated messageParams. - * - * @param messageParams - The messageParams to modify. - * @returns Promise resolving to the messageParams with the metamaskId and version properties removed. - */ - prepMessageForSigning(messageParams: TypedMessageParamsMetamask): Promise; -} -export default TypedMessageManager; diff --git a/dist/message-manager/TypedMessageManager.js b/dist/message-manager/TypedMessageManager.js deleted file mode 100644 index f22d79778d..0000000000 --- a/dist/message-manager/TypedMessageManager.js +++ /dev/null @@ -1,109 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.TypedMessageManager = void 0; -const uuid_1 = require("uuid"); -const util_1 = require("../util"); -const AbstractMessageManager_1 = require("./AbstractMessageManager"); -/** - * Controller in charge of managing - storing, adding, removing, updating - TypedMessages. - */ -class TypedMessageManager extends AbstractMessageManager_1.AbstractMessageManager { - constructor() { - super(...arguments); - /** - * Name of this controller used during composition - */ - this.name = 'TypedMessageManager'; - } - /** - * Creates a new TypedMessage with an 'unapproved' status using the passed messageParams. - * this.addMessage is called to add the new TypedMessage to this.messages, and to save the unapproved TypedMessages. - * - * @param messageParams - The params for the eth_signTypedData call to be made after the message is approved. - * @param version - Compatibility version EIP712. - * @param req - The original request object possibly containing the origin. - * @returns Promise resolving to the raw data of the signature request. - */ - addUnapprovedMessageAsync(messageParams, version, req) { - return new Promise((resolve, reject) => { - if (version === 'V1') { - (0, util_1.validateTypedSignMessageDataV1)(messageParams); - } - if (version === 'V3') { - (0, util_1.validateTypedSignMessageDataV3)(messageParams); - } - const messageId = this.addUnapprovedMessage(messageParams, version, req); - this.hub.once(`${messageId}:finished`, (data) => { - switch (data.status) { - case 'signed': - return resolve(data.rawSig); - case 'rejected': - return reject(new Error('MetaMask Typed Message Signature: User denied message signature.')); - case 'errored': - return reject(new Error(`MetaMask Typed Message Signature: ${data.error}`)); - default: - return reject(new Error(`MetaMask Typed Message Signature: Unknown problem: ${JSON.stringify(messageParams)}`)); - } - }); - }); - } - /** - * Creates a new TypedMessage with an 'unapproved' status using the passed messageParams. - * this.addMessage is called to add the new TypedMessage to this.messages, and to save the - * unapproved TypedMessages. - * - * @param messageParams - The params for the 'eth_signTypedData' call to be made after the message - * is approved. - * @param version - Compatibility version EIP712. - * @param req - The original request object possibly containing the origin. - * @returns The id of the newly created TypedMessage. - */ - addUnapprovedMessage(messageParams, version, req) { - const messageId = (0, uuid_1.v1)(); - const messageParamsMetamask = Object.assign(Object.assign({}, messageParams), { metamaskId: messageId, version }); - if (req) { - messageParams.origin = req.origin; - } - const messageData = { - id: messageId, - messageParams, - status: 'unapproved', - time: Date.now(), - type: 'eth_signTypedData', - }; - this.addMessage(messageData); - this.hub.emit(`unapprovedMessage`, messageParamsMetamask); - return messageId; - } - /** - * Sets a TypedMessage status to 'errored' via a call to this.setMessageStatus. - * - * @param messageId - The id of the TypedMessage to error. - * @param error - The error to be included in TypedMessage. - */ - setMessageStatusErrored(messageId, error) { - const message = this.getMessage(messageId); - /* istanbul ignore if */ - if (!message) { - return; - } - message.error = error; - this.updateMessage(message); - this.setMessageStatus(messageId, 'errored'); - } - /** - * Removes the metamaskId and version properties from passed messageParams and returns a promise which - * resolves the updated messageParams. - * - * @param messageParams - The messageParams to modify. - * @returns Promise resolving to the messageParams with the metamaskId and version properties removed. - */ - prepMessageForSigning(messageParams) { - delete messageParams.metamaskId; - delete messageParams.version; - return Promise.resolve(messageParams); - } -} -exports.TypedMessageManager = TypedMessageManager; -exports.default = TypedMessageManager; -//# sourceMappingURL=TypedMessageManager.js.map \ No newline at end of file diff --git a/dist/message-manager/TypedMessageManager.js.map b/dist/message-manager/TypedMessageManager.js.map deleted file mode 100644 index 710b94731e..0000000000 --- a/dist/message-manager/TypedMessageManager.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"TypedMessageManager.js","sourceRoot":"","sources":["../../src/message-manager/TypedMessageManager.ts"],"names":[],"mappings":"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","sourcesContent":["import { v1 as random } from 'uuid';\nimport {\n validateTypedSignMessageDataV3,\n validateTypedSignMessageDataV1,\n} from '../util';\nimport {\n AbstractMessageManager,\n AbstractMessage,\n AbstractMessageParams,\n AbstractMessageParamsMetamask,\n OriginalRequest,\n} from './AbstractMessageManager';\n\n/**\n * @type TypedMessage\n *\n * Represents and contains data about an 'eth_signTypedData' type signature request.\n * These are created when a signature for an eth_signTypedData call is requested.\n * @property id - An id to track and identify the message object\n * @property error - Error corresponding to eth_signTypedData error in failure case\n * @property messageParams - The parameters to pass to the eth_signTypedData method once\n * the signature request is approved\n * @property type - The json-prc signing method for which a signature request has been made.\n * A 'TypedMessage' which always has a 'eth_signTypedData' type\n * @property rawSig - Raw data of the signature request\n */\nexport interface TypedMessage extends AbstractMessage {\n error?: string;\n messageParams: TypedMessageParams;\n time: number;\n status: string;\n type: string;\n rawSig?: string;\n}\n\n/**\n * @type TypedMessageParams\n *\n * Represents the parameters to pass to the eth_signTypedData method once the signature request is approved.\n * @property data - A hex string conversion of the raw buffer or an object containing data of the signature\n * request depending on version\n * @property from - Address to sign this message from\n * @property origin? - Added for request origin identification\n */\nexport interface TypedMessageParams extends AbstractMessageParams {\n data: Record[] | string;\n}\n\n/**\n * @type TypedMessageParamsMetamask\n *\n * Represents the parameters to pass to the eth_signTypedData method once the signature request is approved\n * plus data added by MetaMask.\n * @property metamaskId - Added for tracking and identification within MetaMask\n * @property data - A hex string conversion of the raw buffer or an object containing data of the signature\n * request depending on version\n * @property error? - Added for message errored\n * @property from - Address to sign this message from\n * @property origin? - Added for request origin identification\n * @property version - Compatibility version EIP712\n */\nexport interface TypedMessageParamsMetamask\n extends AbstractMessageParamsMetamask {\n data: Record[] | string;\n metamaskId?: string;\n error?: string;\n version?: string;\n}\n\n/**\n * Controller in charge of managing - storing, adding, removing, updating - TypedMessages.\n */\nexport class TypedMessageManager extends AbstractMessageManager<\n TypedMessage,\n TypedMessageParams,\n TypedMessageParamsMetamask\n> {\n /**\n * Name of this controller used during composition\n */\n override name = 'TypedMessageManager';\n\n /**\n * Creates a new TypedMessage with an 'unapproved' status using the passed messageParams.\n * this.addMessage is called to add the new TypedMessage to this.messages, and to save the unapproved TypedMessages.\n *\n * @param messageParams - The params for the eth_signTypedData call to be made after the message is approved.\n * @param version - Compatibility version EIP712.\n * @param req - The original request object possibly containing the origin.\n * @returns Promise resolving to the raw data of the signature request.\n */\n addUnapprovedMessageAsync(\n messageParams: TypedMessageParams,\n version: string,\n req?: OriginalRequest,\n ): Promise {\n return new Promise((resolve, reject) => {\n if (version === 'V1') {\n validateTypedSignMessageDataV1(messageParams);\n }\n\n if (version === 'V3') {\n validateTypedSignMessageDataV3(messageParams);\n }\n const messageId = this.addUnapprovedMessage(messageParams, version, req);\n this.hub.once(`${messageId}:finished`, (data: TypedMessage) => {\n switch (data.status) {\n case 'signed':\n return resolve(data.rawSig as string);\n case 'rejected':\n return reject(\n new Error(\n 'MetaMask Typed Message Signature: User denied message signature.',\n ),\n );\n case 'errored':\n return reject(\n new Error(`MetaMask Typed Message Signature: ${data.error}`),\n );\n default:\n return reject(\n new Error(\n `MetaMask Typed Message Signature: Unknown problem: ${JSON.stringify(\n messageParams,\n )}`,\n ),\n );\n }\n });\n });\n }\n\n /**\n * Creates a new TypedMessage with an 'unapproved' status using the passed messageParams.\n * this.addMessage is called to add the new TypedMessage to this.messages, and to save the\n * unapproved TypedMessages.\n *\n * @param messageParams - The params for the 'eth_signTypedData' call to be made after the message\n * is approved.\n * @param version - Compatibility version EIP712.\n * @param req - The original request object possibly containing the origin.\n * @returns The id of the newly created TypedMessage.\n */\n addUnapprovedMessage(\n messageParams: TypedMessageParams,\n version: string,\n req?: OriginalRequest,\n ) {\n const messageId = random();\n const messageParamsMetamask = {\n ...messageParams,\n metamaskId: messageId,\n version,\n };\n if (req) {\n messageParams.origin = req.origin;\n }\n const messageData: TypedMessage = {\n id: messageId,\n messageParams,\n status: 'unapproved',\n time: Date.now(),\n type: 'eth_signTypedData',\n };\n this.addMessage(messageData);\n this.hub.emit(`unapprovedMessage`, messageParamsMetamask);\n return messageId;\n }\n\n /**\n * Sets a TypedMessage status to 'errored' via a call to this.setMessageStatus.\n *\n * @param messageId - The id of the TypedMessage to error.\n * @param error - The error to be included in TypedMessage.\n */\n setMessageStatusErrored(messageId: string, error: string) {\n const message = this.getMessage(messageId);\n /* istanbul ignore if */\n if (!message) {\n return;\n }\n message.error = error;\n this.updateMessage(message);\n this.setMessageStatus(messageId, 'errored');\n }\n\n /**\n * Removes the metamaskId and version properties from passed messageParams and returns a promise which\n * resolves the updated messageParams.\n *\n * @param messageParams - The messageParams to modify.\n * @returns Promise resolving to the messageParams with the metamaskId and version properties removed.\n */\n prepMessageForSigning(\n messageParams: TypedMessageParamsMetamask,\n ): Promise {\n delete messageParams.metamaskId;\n delete messageParams.version;\n return Promise.resolve(messageParams);\n }\n}\n\nexport default TypedMessageManager;\n"]} \ No newline at end of file diff --git a/dist/network/NetworkController.d.ts b/dist/network/NetworkController.d.ts deleted file mode 100644 index 8c55e27a56..0000000000 --- a/dist/network/NetworkController.d.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { BaseController, BaseConfig, BaseState } from '../BaseController'; -/** - * Human-readable network name - */ -export declare type NetworkType = 'kovan' | 'localhost' | 'mainnet' | 'rinkeby' | 'goerli' | 'ropsten' | 'rpc' | 'optimism' | 'optimismTest'; -export declare enum NetworksChainId { - mainnet = "1", - kovan = "42", - rinkeby = "4", - goerli = "5", - ropsten = "3", - localhost = "", - rpc = "", - optimism = "10", - optimismTest = "69" -} -/** - * @type ProviderConfig - * - * Configuration passed to web3-provider-engine - * @property rpcTarget - RPC target URL. - * @property type - Human-readable network name. - * @property chainId - Network ID as per EIP-155. - * @property ticker - Currency ticker. - * @property nickname - Personalized network name. - */ -export interface ProviderConfig { - rpcTarget?: string; - type: NetworkType; - chainId: string; - ticker?: string; - nickname?: string; -} -export interface Block { - baseFeePerGas?: string; -} -export interface NetworkProperties { - isEIP1559Compatible?: boolean; -} -/** - * @type NetworkConfig - * - * Network controller configuration - * @property infuraProjectId - an Infura project ID - * @property providerConfig - web3-provider-engine configuration - */ -export interface NetworkConfig extends BaseConfig { - infuraProjectId?: string; - providerConfig: ProviderConfig; -} -/** - * @type NetworkState - * - * Network controller state - * @property network - Network ID as per net_version - * @property isCustomNetwork - Identifies if the network is a custom network - * @property provider - RPC URL and network name provider settings - */ -export interface NetworkState extends BaseState { - network: string; - isCustomNetwork: boolean; - provider: ProviderConfig; - properties: NetworkProperties; -} -/** - * Controller that creates and manages an Ethereum network provider - */ -export declare class NetworkController extends BaseController { - private ethQuery; - private internalProviderConfig; - private mutex; - private initializeProvider; - private refreshNetwork; - private registerProvider; - private setupInfuraProvider; - private getIsCustomNetwork; - private setupStandardProvider; - private updateProvider; - private safelyStopProvider; - private verifyNetwork; - /** - * Name of this controller used during composition - */ - name: string; - /** - * Ethereum provider object for the current network - */ - provider: any; - /** - * Creates a NetworkController instance. - * - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor(config?: Partial, state?: Partial); - /** - * Sets a new configuration for web3-provider-engine. - * - * TODO: Replace this wth a method. - * - * @param providerConfig - The web3-provider-engine configuration. - */ - set providerConfig(providerConfig: ProviderConfig); - get providerConfig(): ProviderConfig; - /** - * Refreshes the current network code. - */ - lookupNetwork(): Promise; - /** - * Convenience method to update provider network type settings. - * - * @param type - Human readable network name. - */ - setProviderType(type: NetworkType): void; - /** - * Convenience method to update provider RPC settings. - * - * @param rpcTarget - The RPC endpoint URL. - * @param chainId - The chain ID as per EIP-155. - * @param ticker - The currency ticker. - * @param nickname - Personalized network name. - */ - setRpcTarget(rpcTarget: string, chainId: string, ticker?: string, nickname?: string): void; - getEIP1559Compatibility(): Promise; -} -export default NetworkController; diff --git a/dist/network/NetworkController.js b/dist/network/NetworkController.js deleted file mode 100644 index ce1ada7078..0000000000 --- a/dist/network/NetworkController.js +++ /dev/null @@ -1,251 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __rest = (this && this.__rest) || function (s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.NetworkController = exports.NetworksChainId = void 0; -const eth_query_1 = __importDefault(require("eth-query")); -const provider_1 = __importDefault(require("web3-provider-engine/subproviders/provider")); -const createProvider_1 = __importDefault(require("eth-json-rpc-infura/src/createProvider")); -const zero_1 = __importDefault(require("web3-provider-engine/zero")); -const async_mutex_1 = require("async-mutex"); -const BaseController_1 = require("../BaseController"); -const constants_1 = require("../constants"); -var NetworksChainId; -(function (NetworksChainId) { - NetworksChainId["mainnet"] = "1"; - NetworksChainId["kovan"] = "42"; - NetworksChainId["rinkeby"] = "4"; - NetworksChainId["goerli"] = "5"; - NetworksChainId["ropsten"] = "3"; - NetworksChainId["localhost"] = ""; - NetworksChainId["rpc"] = ""; - NetworksChainId["optimism"] = "10"; - NetworksChainId["optimismTest"] = "69"; -})(NetworksChainId = exports.NetworksChainId || (exports.NetworksChainId = {})); -const LOCALHOST_RPC_URL = 'http://localhost:8545'; -/** - * Controller that creates and manages an Ethereum network provider - */ -class NetworkController extends BaseController_1.BaseController { - /** - * Creates a NetworkController instance. - * - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor(config, state) { - super(config, state); - this.internalProviderConfig = {}; - this.mutex = new async_mutex_1.Mutex(); - /** - * Name of this controller used during composition - */ - this.name = 'NetworkController'; - this.defaultState = { - network: 'loading', - isCustomNetwork: false, - provider: { type: constants_1.MAINNET, chainId: NetworksChainId.mainnet }, - properties: { isEIP1559Compatible: false }, - }; - this.initialize(); - this.getEIP1559Compatibility(); - } - initializeProvider(type, rpcTarget, chainId, ticker, nickname) { - this.update({ isCustomNetwork: this.getIsCustomNetwork(chainId) }); - switch (type) { - case 'kovan': - case constants_1.MAINNET: - case 'rinkeby': - case 'goerli': - case 'optimism': - case 'optimismTest': - case 'ropsten': - this.setupInfuraProvider(type); - break; - case 'localhost': - this.setupStandardProvider(LOCALHOST_RPC_URL); - break; - case constants_1.RPC: - rpcTarget && - this.setupStandardProvider(rpcTarget, chainId, ticker, nickname); - break; - default: - throw new Error(`Unrecognized network type: '${type}'`); - } - } - refreshNetwork() { - this.update({ network: 'loading', properties: {} }); - const { rpcTarget, type, chainId, ticker } = this.state.provider; - this.initializeProvider(type, rpcTarget, chainId, ticker); - this.lookupNetwork(); - } - registerProvider() { - this.provider.on('error', this.verifyNetwork.bind(this)); - this.ethQuery = new eth_query_1.default(this.provider); - } - setupInfuraProvider(type) { - const infuraProvider = (0, createProvider_1.default)({ - network: type, - projectId: this.config.infuraProjectId, - }); - const infuraSubprovider = new provider_1.default(infuraProvider); - const config = Object.assign(Object.assign({}, this.internalProviderConfig), { - dataSubprovider: infuraSubprovider, - engineParams: { - blockTrackerProvider: infuraProvider, - pollingInterval: 12000, - }, - }); - this.updateProvider((0, zero_1.default)(config)); - } - getIsCustomNetwork(chainId) { - return (chainId !== NetworksChainId.mainnet && - chainId !== NetworksChainId.kovan && - chainId !== NetworksChainId.rinkeby && - chainId !== NetworksChainId.goerli && - chainId !== NetworksChainId.ropsten && - chainId !== NetworksChainId.localhost); - } - setupStandardProvider(rpcTarget, chainId, ticker, nickname) { - const config = Object.assign(Object.assign({}, this.internalProviderConfig), { - chainId, - engineParams: { pollingInterval: 12000 }, - nickname, - rpcUrl: rpcTarget, - ticker, - }); - this.updateProvider((0, zero_1.default)(config)); - } - updateProvider(provider) { - this.safelyStopProvider(this.provider); - this.provider = provider; - this.registerProvider(); - } - safelyStopProvider(provider) { - setTimeout(() => { - provider === null || provider === void 0 ? void 0 : provider.stop(); - }, 500); - } - verifyNetwork() { - this.state.network === 'loading' && this.lookupNetwork(); - } - /** - * Sets a new configuration for web3-provider-engine. - * - * TODO: Replace this wth a method. - * - * @param providerConfig - The web3-provider-engine configuration. - */ - set providerConfig(providerConfig) { - this.internalProviderConfig = providerConfig; - const { type, rpcTarget, chainId, ticker, nickname } = this.state.provider; - this.initializeProvider(type, rpcTarget, chainId, ticker, nickname); - this.registerProvider(); - this.lookupNetwork(); - } - get providerConfig() { - throw new Error('Property only used for setting'); - } - /** - * Refreshes the current network code. - */ - lookupNetwork() { - return __awaiter(this, void 0, void 0, function* () { - /* istanbul ignore if */ - if (!this.ethQuery || !this.ethQuery.sendAsync) { - return; - } - const releaseLock = yield this.mutex.acquire(); - this.ethQuery.sendAsync({ method: 'net_version' }, (error, network) => { - this.update({ - network: error ? /* istanbul ignore next*/ 'loading' : network, - }); - releaseLock(); - }); - }); - } - /** - * Convenience method to update provider network type settings. - * - * @param type - Human readable network name. - */ - setProviderType(type) { - const _a = this.state.provider, { rpcTarget, chainId, nickname } = _a, providerState = __rest(_a, ["rpcTarget", "chainId", "nickname"]); - // If testnet the ticker symbol should use a testnet prefix - const testNetTicker = constants_1.TESTNET_TICKER_SYMBOLS[type.toUpperCase()]; - this.update({ - provider: Object.assign(Object.assign({}, providerState), { - type, - ticker: testNetTicker || 'ETH', - chainId: NetworksChainId[type], - }), - }); - this.refreshNetwork(); - } - /** - * Convenience method to update provider RPC settings. - * - * @param rpcTarget - The RPC endpoint URL. - * @param chainId - The chain ID as per EIP-155. - * @param ticker - The currency ticker. - * @param nickname - Personalized network name. - */ - setRpcTarget(rpcTarget, chainId, ticker, nickname) { - this.update({ - provider: Object.assign(Object.assign({}, this.state.provider), { type: constants_1.RPC, ticker, rpcTarget, chainId, nickname }), - }); - this.refreshNetwork(); - } - getEIP1559Compatibility() { - var _a; - const { properties = {} } = this.state; - if (!properties.isEIP1559Compatible) { - if (typeof ((_a = this.ethQuery) === null || _a === void 0 ? void 0 : _a.sendAsync) !== 'function') { - return Promise.resolve(true); - } - return new Promise((resolve, reject) => { - this.ethQuery.sendAsync({ method: 'eth_getBlockByNumber', params: ['latest', false] }, (error, block) => { - if (error) { - reject(error); - } - else { - const isEIP1559Compatible = typeof block.baseFeePerGas !== 'undefined'; - if (properties.isEIP1559Compatible !== isEIP1559Compatible) { - this.update({ - properties: { - isEIP1559Compatible, - }, - }); - } - resolve(isEIP1559Compatible); - } - }); - }); - } - return Promise.resolve(true); - } -} -exports.NetworkController = NetworkController; -exports.default = NetworkController; -//# sourceMappingURL=NetworkController.js.map \ No newline at end of file diff --git a/dist/network/NetworkController.js.map b/dist/network/NetworkController.js.map deleted file mode 100644 index b9215a4189..0000000000 --- a/dist/network/NetworkController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"NetworkController.js","sourceRoot":"","sources":["../../src/network/NetworkController.ts"],"names":[],"mappings":"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","sourcesContent":["import EthQuery from 'eth-query';\nimport Subprovider from 'web3-provider-engine/subproviders/provider';\nimport createInfuraProvider from 'eth-json-rpc-infura/src/createProvider';\nimport createMetamaskProvider from 'web3-provider-engine/zero';\nimport { Mutex } from 'async-mutex';\nimport { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport { MAINNET, RPC, TESTNET_TICKER_SYMBOLS } from '../constants';\n\n/**\n * Human-readable network name\n */\nexport type NetworkType =\n | 'kovan'\n | 'localhost'\n | 'mainnet'\n | 'rinkeby'\n | 'goerli'\n | 'ropsten'\n | 'rpc'\n | 'optimism'\n | 'optimismTest';\n\nexport enum NetworksChainId {\n mainnet = '1',\n kovan = '42',\n rinkeby = '4',\n goerli = '5',\n ropsten = '3',\n localhost = '',\n rpc = '',\n optimism = '10',\n optimismTest = '69',\n}\n\n/**\n * @type ProviderConfig\n *\n * Configuration passed to web3-provider-engine\n * @property rpcTarget - RPC target URL.\n * @property type - Human-readable network name.\n * @property chainId - Network ID as per EIP-155.\n * @property ticker - Currency ticker.\n * @property nickname - Personalized network name.\n */\nexport interface ProviderConfig {\n rpcTarget?: string;\n type: NetworkType;\n chainId: string;\n ticker?: string;\n nickname?: string;\n}\n\nexport interface Block {\n baseFeePerGas?: string;\n}\n\nexport interface NetworkProperties {\n isEIP1559Compatible?: boolean;\n}\n\n/**\n * @type NetworkConfig\n *\n * Network controller configuration\n * @property infuraProjectId - an Infura project ID\n * @property providerConfig - web3-provider-engine configuration\n */\nexport interface NetworkConfig extends BaseConfig {\n infuraProjectId?: string;\n providerConfig: ProviderConfig;\n}\n\n/**\n * @type NetworkState\n *\n * Network controller state\n * @property network - Network ID as per net_version\n * @property isCustomNetwork - Identifies if the network is a custom network\n * @property provider - RPC URL and network name provider settings\n */\nexport interface NetworkState extends BaseState {\n network: string;\n isCustomNetwork: boolean;\n provider: ProviderConfig;\n properties: NetworkProperties;\n}\n\nconst LOCALHOST_RPC_URL = 'http://localhost:8545';\n\n/**\n * Controller that creates and manages an Ethereum network provider\n */\nexport class NetworkController extends BaseController<\n NetworkConfig,\n NetworkState\n> {\n private ethQuery: any;\n\n private internalProviderConfig: ProviderConfig = {} as ProviderConfig;\n\n private mutex = new Mutex();\n\n private initializeProvider(\n type: NetworkType,\n rpcTarget?: string,\n chainId?: string,\n ticker?: string,\n nickname?: string,\n ) {\n this.update({ isCustomNetwork: this.getIsCustomNetwork(chainId) });\n switch (type) {\n case 'kovan':\n case MAINNET:\n case 'rinkeby':\n case 'goerli':\n case 'optimism':\n case 'optimismTest':\n case 'ropsten':\n this.setupInfuraProvider(type);\n break;\n case 'localhost':\n this.setupStandardProvider(LOCALHOST_RPC_URL);\n break;\n case RPC:\n rpcTarget &&\n this.setupStandardProvider(rpcTarget, chainId, ticker, nickname);\n break;\n default:\n throw new Error(`Unrecognized network type: '${type}'`);\n }\n }\n\n private refreshNetwork() {\n this.update({ network: 'loading', properties: {} });\n const { rpcTarget, type, chainId, ticker } = this.state.provider;\n this.initializeProvider(type, rpcTarget, chainId, ticker);\n this.lookupNetwork();\n }\n\n private registerProvider() {\n this.provider.on('error', this.verifyNetwork.bind(this));\n this.ethQuery = new EthQuery(this.provider);\n }\n\n private setupInfuraProvider(type: NetworkType) {\n const infuraProvider = createInfuraProvider({\n network: type,\n projectId: this.config.infuraProjectId,\n });\n const infuraSubprovider = new Subprovider(infuraProvider);\n const config = {\n ...this.internalProviderConfig,\n ...{\n dataSubprovider: infuraSubprovider,\n engineParams: {\n blockTrackerProvider: infuraProvider,\n pollingInterval: 12000,\n },\n },\n };\n this.updateProvider(createMetamaskProvider(config));\n }\n\n private getIsCustomNetwork(chainId?: string) {\n return (\n chainId !== NetworksChainId.mainnet &&\n chainId !== NetworksChainId.kovan &&\n chainId !== NetworksChainId.rinkeby &&\n chainId !== NetworksChainId.goerli &&\n chainId !== NetworksChainId.ropsten &&\n chainId !== NetworksChainId.localhost\n );\n }\n\n private setupStandardProvider(\n rpcTarget: string,\n chainId?: string,\n ticker?: string,\n nickname?: string,\n ) {\n const config = {\n ...this.internalProviderConfig,\n ...{\n chainId,\n engineParams: { pollingInterval: 12000 },\n nickname,\n rpcUrl: rpcTarget,\n ticker,\n },\n };\n this.updateProvider(createMetamaskProvider(config));\n }\n\n private updateProvider(provider: any) {\n this.safelyStopProvider(this.provider);\n this.provider = provider;\n this.registerProvider();\n }\n\n private safelyStopProvider(provider: any) {\n setTimeout(() => {\n provider?.stop();\n }, 500);\n }\n\n private verifyNetwork() {\n this.state.network === 'loading' && this.lookupNetwork();\n }\n\n /**\n * Name of this controller used during composition\n */\n override name = 'NetworkController';\n\n /**\n * Ethereum provider object for the current network\n */\n provider: any;\n\n /**\n * Creates a NetworkController instance.\n *\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(config?: Partial, state?: Partial) {\n super(config, state);\n this.defaultState = {\n network: 'loading',\n isCustomNetwork: false,\n provider: { type: MAINNET, chainId: NetworksChainId.mainnet },\n properties: { isEIP1559Compatible: false },\n };\n this.initialize();\n this.getEIP1559Compatibility();\n }\n\n /**\n * Sets a new configuration for web3-provider-engine.\n *\n * TODO: Replace this wth a method.\n *\n * @param providerConfig - The web3-provider-engine configuration.\n */\n set providerConfig(providerConfig: ProviderConfig) {\n this.internalProviderConfig = providerConfig;\n const { type, rpcTarget, chainId, ticker, nickname } = this.state.provider;\n this.initializeProvider(type, rpcTarget, chainId, ticker, nickname);\n this.registerProvider();\n this.lookupNetwork();\n }\n\n get providerConfig() {\n throw new Error('Property only used for setting');\n }\n\n /**\n * Refreshes the current network code.\n */\n async lookupNetwork() {\n /* istanbul ignore if */\n if (!this.ethQuery || !this.ethQuery.sendAsync) {\n return;\n }\n const releaseLock = await this.mutex.acquire();\n this.ethQuery.sendAsync(\n { method: 'net_version' },\n (error: Error, network: string) => {\n this.update({\n network: error ? /* istanbul ignore next*/ 'loading' : network,\n });\n releaseLock();\n },\n );\n }\n\n /**\n * Convenience method to update provider network type settings.\n *\n * @param type - Human readable network name.\n */\n setProviderType(type: NetworkType) {\n const { rpcTarget, chainId, nickname, ...providerState } =\n this.state.provider;\n\n // If testnet the ticker symbol should use a testnet prefix\n const testNetTicker = TESTNET_TICKER_SYMBOLS[type.toUpperCase()];\n\n this.update({\n provider: {\n ...providerState,\n ...{\n type,\n ticker: testNetTicker || 'ETH',\n chainId: NetworksChainId[type],\n },\n },\n });\n this.refreshNetwork();\n }\n\n /**\n * Convenience method to update provider RPC settings.\n *\n * @param rpcTarget - The RPC endpoint URL.\n * @param chainId - The chain ID as per EIP-155.\n * @param ticker - The currency ticker.\n * @param nickname - Personalized network name.\n */\n setRpcTarget(\n rpcTarget: string,\n chainId: string,\n ticker?: string,\n nickname?: string,\n ) {\n this.update({\n provider: {\n ...this.state.provider,\n ...{ type: RPC, ticker, rpcTarget, chainId, nickname },\n },\n });\n this.refreshNetwork();\n }\n\n getEIP1559Compatibility() {\n const { properties = {} } = this.state;\n\n if (!properties.isEIP1559Compatible) {\n if (typeof this.ethQuery?.sendAsync !== 'function') {\n return Promise.resolve(true);\n }\n return new Promise((resolve, reject) => {\n this.ethQuery.sendAsync(\n { method: 'eth_getBlockByNumber', params: ['latest', false] },\n (error: Error, block: Block) => {\n if (error) {\n reject(error);\n } else {\n const isEIP1559Compatible =\n typeof block.baseFeePerGas !== 'undefined';\n if (properties.isEIP1559Compatible !== isEIP1559Compatible) {\n this.update({\n properties: {\n isEIP1559Compatible,\n },\n });\n }\n resolve(isEIP1559Compatible);\n }\n },\n );\n });\n }\n return Promise.resolve(true);\n }\n}\n\nexport default NetworkController;\n"]} \ No newline at end of file diff --git a/dist/notification/NotificationController.d.ts b/dist/notification/NotificationController.d.ts deleted file mode 100644 index c0460f9c4b..0000000000 --- a/dist/notification/NotificationController.d.ts +++ /dev/null @@ -1,84 +0,0 @@ -import type { Patch } from 'immer'; -import { BaseController } from '../BaseControllerV2'; -import type { RestrictedControllerMessenger } from '../ControllerMessenger'; -/** - * @typedef NotificationControllerState - * @property notifications - Stores existing notifications to be shown in the UI - */ -export declare type NotificationControllerState = { - notifications: Record; -}; -/** - * @typedef Notification - Stores information about in-app notifications, to be shown in the UI - * @property id - A UUID that identifies the notification - * @property origin - The origin that requested the notification - * @property createdDate - The notification creation date in milliseconds elapsed since the UNIX epoch - * @property readDate - The notification read date in milliseconds elapsed since the UNIX epoch or null if unread - * @property message - The notification message - */ -export declare type Notification = { - id: string; - origin: string; - createdDate: number; - readDate: number | null; - message: string; -}; -declare const name = "NotificationController"; -export declare type NotificationControllerStateChange = { - type: `${typeof name}:stateChange`; - payload: [NotificationControllerState, Patch[]]; -}; -export declare type GetNotificationControllerState = { - type: `${typeof name}:getState`; - handler: () => NotificationControllerState; -}; -export declare type ShowNotification = { - type: `${typeof name}:show`; - handler: NotificationController['show']; -}; -export declare type DismissNotification = { - type: `${typeof name}:dismiss`; - handler: NotificationController['dismiss']; -}; -export declare type MarkNotificationRead = { - type: `${typeof name}:markRead`; - handler: NotificationController['markRead']; -}; -export declare type NotificationControllerActions = GetNotificationControllerState | ShowNotification | DismissNotification | MarkNotificationRead; -export declare type NotificationControllerMessenger = RestrictedControllerMessenger; -/** - * Controller that handles storing notifications and showing them to the user - */ -export declare class NotificationController extends BaseController { - /** - * Creates a NotificationController instance. - * - * @param options - Constructor options. - * @param options.messenger - A reference to the messaging system. - * @param options.state - Initial state to set on this controller. - */ - constructor({ messenger, state, }: { - messenger: NotificationControllerMessenger; - state?: Partial; - }); - /** - * Shows a notification. - * - * @param origin - The origin trying to send a notification - * @param message - A message to show on the notification - */ - show(origin: string, message: string): void; - /** - * Dimisses a list of notifications. - * - * @param ids - A list of notification IDs - */ - dismiss(ids: string[]): void; - /** - * Marks a list of notifications as read. - * - * @param ids - A list of notification IDs - */ - markRead(ids: string[]): void; -} -export {}; diff --git a/dist/notification/NotificationController.js b/dist/notification/NotificationController.js deleted file mode 100644 index 7d8a4b7cbe..0000000000 --- a/dist/notification/NotificationController.js +++ /dev/null @@ -1,85 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.NotificationController = void 0; -const nanoid_1 = require("nanoid"); -const util_1 = require("../util"); -const BaseControllerV2_1 = require("../BaseControllerV2"); -const name = 'NotificationController'; -const metadata = { - notifications: { persist: true, anonymous: false }, -}; -const defaultState = { - notifications: {}, -}; -/** - * Controller that handles storing notifications and showing them to the user - */ -class NotificationController extends BaseControllerV2_1.BaseController { - /** - * Creates a NotificationController instance. - * - * @param options - Constructor options. - * @param options.messenger - A reference to the messaging system. - * @param options.state - Initial state to set on this controller. - */ - constructor({ messenger, state, }) { - super({ - name, - metadata, - messenger, - state: Object.assign(Object.assign({}, defaultState), state), - }); - this.messagingSystem.registerActionHandler(`${name}:show`, (origin, message) => this.show(origin, message)); - this.messagingSystem.registerActionHandler(`${name}:dismiss`, (ids) => this.dismiss(ids)); - this.messagingSystem.registerActionHandler(`${name}:markRead`, (ids) => this.markRead(ids)); - } - /** - * Shows a notification. - * - * @param origin - The origin trying to send a notification - * @param message - A message to show on the notification - */ - show(origin, message) { - const id = (0, nanoid_1.nanoid)(); - const notification = { - id, - origin, - createdDate: Date.now(), - readDate: null, - message, - }; - this.update((state) => { - state.notifications[id] = notification; - }); - } - /** - * Dimisses a list of notifications. - * - * @param ids - A list of notification IDs - */ - dismiss(ids) { - this.update((state) => { - for (const id of ids) { - if ((0, util_1.hasProperty)(state.notifications, id)) { - delete state.notifications[id]; - } - } - }); - } - /** - * Marks a list of notifications as read. - * - * @param ids - A list of notification IDs - */ - markRead(ids) { - this.update((state) => { - for (const id of ids) { - if ((0, util_1.hasProperty)(state.notifications, id)) { - state.notifications[id].readDate = Date.now(); - } - } - }); - } -} -exports.NotificationController = NotificationController; -//# sourceMappingURL=NotificationController.js.map \ No newline at end of file diff --git a/dist/notification/NotificationController.js.map b/dist/notification/NotificationController.js.map deleted file mode 100644 index c2d3c0ea34..0000000000 --- a/dist/notification/NotificationController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"NotificationController.js","sourceRoot":"","sources":["../../src/notification/NotificationController.ts"],"names":[],"mappings":";;;AACA,mCAAgC;AAEhC,kCAAsC;AACtC,0DAAqD;AA4BrD,MAAM,IAAI,GAAG,wBAAwB,CAAC;AAyCtC,MAAM,QAAQ,GAAG;IACf,aAAa,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE;CACnD,CAAC;AAEF,MAAM,YAAY,GAAG;IACnB,aAAa,EAAE,EAAE;CAClB,CAAC;AAEF;;GAEG;AACH,MAAa,sBAAuB,SAAQ,iCAI3C;IACC;;;;;;OAMG;IACH,YAAY,EACV,SAAS,EACT,KAAK,GAIN;QACC,KAAK,CAAC;YACJ,IAAI;YACJ,QAAQ;YACR,SAAS;YACT,KAAK,kCAAO,YAAY,GAAK,KAAK,CAAE;SACrC,CAAC,CAAC;QAEH,IAAI,CAAC,eAAe,CAAC,qBAAqB,CACxC,GAAG,IAAI,OAAgB,EACvB,CAAC,MAAc,EAAE,OAAe,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAChE,CAAC;QAEF,IAAI,CAAC,eAAe,CAAC,qBAAqB,CACxC,GAAG,IAAI,UAAmB,EAC1B,CAAC,GAAa,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CACrC,CAAC;QAEF,IAAI,CAAC,eAAe,CAAC,qBAAqB,CACxC,GAAG,IAAI,WAAoB,EAC3B,CAAC,GAAa,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CACtC,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACH,IAAI,CAAC,MAAc,EAAE,OAAe;QAClC,MAAM,EAAE,GAAG,IAAA,eAAM,GAAE,CAAC;QACpB,MAAM,YAAY,GAAG;YACnB,EAAE;YACF,MAAM;YACN,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE;YACvB,QAAQ,EAAE,IAAI;YACd,OAAO;SACR,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACpB,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC;QACzC,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,OAAO,CAAC,GAAa;QACnB,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACpB,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE;gBACpB,IAAI,IAAA,kBAAW,EAAC,KAAK,CAAC,aAAa,EAAE,EAAE,CAAC,EAAE;oBACxC,OAAO,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;iBAChC;aACF;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,QAAQ,CAAC,GAAa;QACpB,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACpB,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE;gBACpB,IAAI,IAAA,kBAAW,EAAC,KAAK,CAAC,aAAa,EAAE,EAAE,CAAC,EAAE;oBACxC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;iBAC/C;aACF;QACH,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AA3FD,wDA2FC","sourcesContent":["import type { Patch } from 'immer';\nimport { nanoid } from 'nanoid';\n\nimport { hasProperty } from '../util';\nimport { BaseController } from '../BaseControllerV2';\n\nimport type { RestrictedControllerMessenger } from '../ControllerMessenger';\n\n/**\n * @typedef NotificationControllerState\n * @property notifications - Stores existing notifications to be shown in the UI\n */\nexport type NotificationControllerState = {\n notifications: Record;\n};\n\n/**\n * @typedef Notification - Stores information about in-app notifications, to be shown in the UI\n * @property id - A UUID that identifies the notification\n * @property origin - The origin that requested the notification\n * @property createdDate - The notification creation date in milliseconds elapsed since the UNIX epoch\n * @property readDate - The notification read date in milliseconds elapsed since the UNIX epoch or null if unread\n * @property message - The notification message\n */\nexport type Notification = {\n id: string;\n origin: string;\n createdDate: number;\n readDate: number | null;\n message: string;\n};\n\nconst name = 'NotificationController';\n\nexport type NotificationControllerStateChange = {\n type: `${typeof name}:stateChange`;\n payload: [NotificationControllerState, Patch[]];\n};\n\nexport type GetNotificationControllerState = {\n type: `${typeof name}:getState`;\n handler: () => NotificationControllerState;\n};\n\nexport type ShowNotification = {\n type: `${typeof name}:show`;\n handler: NotificationController['show'];\n};\n\nexport type DismissNotification = {\n type: `${typeof name}:dismiss`;\n handler: NotificationController['dismiss'];\n};\n\nexport type MarkNotificationRead = {\n type: `${typeof name}:markRead`;\n handler: NotificationController['markRead'];\n};\n\nexport type NotificationControllerActions =\n | GetNotificationControllerState\n | ShowNotification\n | DismissNotification\n | MarkNotificationRead;\n\nexport type NotificationControllerMessenger = RestrictedControllerMessenger<\n typeof name,\n NotificationControllerActions,\n NotificationControllerStateChange,\n never,\n never\n>;\n\nconst metadata = {\n notifications: { persist: true, anonymous: false },\n};\n\nconst defaultState = {\n notifications: {},\n};\n\n/**\n * Controller that handles storing notifications and showing them to the user\n */\nexport class NotificationController extends BaseController<\n typeof name,\n NotificationControllerState,\n NotificationControllerMessenger\n> {\n /**\n * Creates a NotificationController instance.\n *\n * @param options - Constructor options.\n * @param options.messenger - A reference to the messaging system.\n * @param options.state - Initial state to set on this controller.\n */\n constructor({\n messenger,\n state,\n }: {\n messenger: NotificationControllerMessenger;\n state?: Partial;\n }) {\n super({\n name,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n\n this.messagingSystem.registerActionHandler(\n `${name}:show` as const,\n (origin: string, message: string) => this.show(origin, message),\n );\n\n this.messagingSystem.registerActionHandler(\n `${name}:dismiss` as const,\n (ids: string[]) => this.dismiss(ids),\n );\n\n this.messagingSystem.registerActionHandler(\n `${name}:markRead` as const,\n (ids: string[]) => this.markRead(ids),\n );\n }\n\n /**\n * Shows a notification.\n *\n * @param origin - The origin trying to send a notification\n * @param message - A message to show on the notification\n */\n show(origin: string, message: string) {\n const id = nanoid();\n const notification = {\n id,\n origin,\n createdDate: Date.now(),\n readDate: null,\n message,\n };\n this.update((state) => {\n state.notifications[id] = notification;\n });\n }\n\n /**\n * Dimisses a list of notifications.\n *\n * @param ids - A list of notification IDs\n */\n dismiss(ids: string[]) {\n this.update((state) => {\n for (const id of ids) {\n if (hasProperty(state.notifications, id)) {\n delete state.notifications[id];\n }\n }\n });\n }\n\n /**\n * Marks a list of notifications as read.\n *\n * @param ids - A list of notification IDs\n */\n markRead(ids: string[]) {\n this.update((state) => {\n for (const id of ids) {\n if (hasProperty(state.notifications, id)) {\n state.notifications[id].readDate = Date.now();\n }\n }\n });\n }\n}\n"]} \ No newline at end of file diff --git a/dist/permissions/Caveat.d.ts b/dist/permissions/Caveat.d.ts deleted file mode 100644 index 4a0cf17300..0000000000 --- a/dist/permissions/Caveat.d.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { Json } from '@metamask/types'; -import { AsyncRestrictedMethod, RestrictedMethod, PermissionConstraint, RestrictedMethodParameters } from './Permission'; -export declare type CaveatConstraint = { - /** - * The type of the caveat. The type is presumed to be meaningful in the - * context of the capability it is associated with. - * - * In MetaMask, every permission can only have one caveat of each type. - */ - readonly type: string; - /** - * Any additional data necessary to enforce the caveat. - */ - readonly value: Json; -}; -/** - * A `ZCAP-LD`-like caveat object. A caveat is associated with a particular - * permission, and stored in its `caveats` array. Conceptually, a caveat is - * an arbitrary attenuation of the authority granted by its associated - * permission. It is the responsibility of the host to interpret and apply - * the restriction represented by a caveat. - * - * @template Type - The type of the caveat. - * @template Value - The value associated with the caveat. - */ -export declare type Caveat = { - /** - * The type of the caveat. The type is presumed to be meaningful in the - * context of the capability it is associated with. - * - * In MetaMask, every permission can only have one caveat of each type. - */ - readonly type: Type; - /** - * Any additional data necessary to enforce the caveat. - */ - readonly value: Value; -}; -/** - * A function for applying caveats to a restricted method request. - * - * @template ParentCaveat - The caveat type associated with this decorator. - * @param decorated - The restricted method implementation to be decorated. - * The method may have already been decorated with other caveats. - * @param caveat - The caveat object. - * @returns The decorated restricted method implementation. - */ -export declare type CaveatDecorator = (decorated: AsyncRestrictedMethod, caveat: ParentCaveat) => AsyncRestrictedMethod; -/** - * Extracts a caveat value type from a caveat decorator. - * - * @template Decorator - The {@link CaveatDecorator} to extract a caveat value - * type from. - */ -declare type ExtractCaveatValueFromDecorator> = Decorator extends (decorated: any, caveat: infer ParentCaveat) => AsyncRestrictedMethod ? ParentCaveat extends CaveatConstraint ? ParentCaveat['value'] : never : never; -/** - * A function for validating caveats of a particular type. - * - * @template ParentCaveat - The caveat type associated with this validator. - * @param caveat - The caveat object to validate. - * @param origin - The origin associated with the parent permission. - * @param target - The target of the parent permission. - */ -export declare type CaveatValidator = (caveat: { - type: ParentCaveat['type']; - value: unknown; -}, origin?: string, target?: string) => void; -/** - * The constraint for caveat specification objects. Every {@link Caveat} - * supported by a {@link PermissionController} must have an associated - * specification, which is the source of truth for all caveat-related types. - * In addition, a caveat specification includes the decorator function used - * to apply the caveat's attenuation to a restricted method, and any validator - * function specified by the consumer. - * - * See the README for more details. - */ -export declare type CaveatSpecificationConstraint = { - /** - * The string type of the caveat. - */ - type: string; - /** - * The decorator function used to apply the caveat to restricted method - * requests. - */ - decorator: CaveatDecorator; - /** - * The validator function used to validate caveats of the associated type - * whenever they are instantiated. Caveat are instantiated whenever they are - * created or mutated. - * - * The validator should throw an appropriate JSON-RPC error if validation fails. - * - * If no validator is specified, no validation of caveat values will be - * performed. Although caveats can also be validated by permission validators, - * validating caveat values separately is strongly recommended. - */ - validator?: CaveatValidator; -}; -/** - * Options for {@link CaveatSpecificationBuilder} functions. - */ -declare type CaveatSpecificationBuilderOptions, ValidatorHooks extends Record> = { - type?: string; - decoratorHooks?: DecoratorHooks; - validatorHooks?: ValidatorHooks; -}; -/** - * A function that builds caveat specifications. Modules that specify caveats - * for external consumption should make this their primary / default export so - * that host applications can use them to generate concrete specifications - * tailored to their requirements. - */ -export declare type CaveatSpecificationBuilder, Specification extends CaveatSpecificationConstraint> = (options: Options) => Specification; -/** - * A caveat specification export object, containing the - * {@link CaveatSpecificationBuilder} function and "hook name" objects. - */ -export declare type CaveatSpecificationBuilderExportConstraint = { - specificationBuilder: CaveatSpecificationBuilder, CaveatSpecificationConstraint>; - decoratorHookNames?: Record; - validatorHookNames?: Record; -}; -/** - * The specifications for all caveats supported by a particular - * {@link PermissionController}. - * - * @template Specifications - The union of all {@link CaveatSpecificationConstraint} types. - */ -export declare type CaveatSpecificationMap = Record; -/** - * Extracts the union of all caveat types specified by the given - * {@link CaveatSpecificationConstraint} type. - * - * @template CaveatSpecification - The {@link CaveatSpecificationConstraint} to extract a - * caveat type union from. - */ -export declare type ExtractCaveats = CaveatSpecification extends any ? Caveat> : never; -/** - * Extracts the type of a specific {@link Caveat} from a union of caveat - * specifications. - * - * @template CaveatSpecifications - The union of all caveat specifications. - * @template CaveatType - The type of the caveat to extract. - */ -export declare type ExtractCaveat = Extract, { - type: CaveatType; -}>; -/** - * Extracts the value type of a specific {@link Caveat} from a union of caveat - * specifications. - * - * @template CaveatSpecifications - The union of all caveat specifications. - * @template CaveatType - The type of the caveat whose value to extract. - */ -export declare type ExtractCaveatValue = ExtractCaveat['value']; -/** - * Decorate a restricted method implementation with its caveats. - * - * Note that all caveat functions (i.e. the argument and return value of the - * decorator) must be awaited. - * - * @param methodImplementation - The restricted method implementation - * @param permission - The origin's potential permission - * @param caveatSpecifications - All caveat implementations - * @returns The decorated method implementation - */ -export declare function decorateWithCaveats(methodImplementation: RestrictedMethod, permission: Readonly, // bound to the requesting origin -caveatSpecifications: CaveatSpecificationMap): RestrictedMethod; -export {}; diff --git a/dist/permissions/Caveat.js b/dist/permissions/Caveat.js deleted file mode 100644 index af74c17872..0000000000 --- a/dist/permissions/Caveat.js +++ /dev/null @@ -1,42 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.decorateWithCaveats = void 0; -const errors_1 = require("./errors"); -/** - * Decorate a restricted method implementation with its caveats. - * - * Note that all caveat functions (i.e. the argument and return value of the - * decorator) must be awaited. - * - * @param methodImplementation - The restricted method implementation - * @param permission - The origin's potential permission - * @param caveatSpecifications - All caveat implementations - * @returns The decorated method implementation - */ -function decorateWithCaveats(methodImplementation, permission, // bound to the requesting origin -caveatSpecifications) { - const { caveats } = permission; - if (!caveats) { - return methodImplementation; - } - let decorated = (args) => __awaiter(this, void 0, void 0, function* () { return methodImplementation(args); }); - for (const caveat of caveats) { - const specification = caveatSpecifications[caveat.type]; - if (!specification) { - throw new errors_1.UnrecognizedCaveatTypeError(caveat.type); - } - decorated = specification.decorator(decorated, caveat); - } - return decorated; -} -exports.decorateWithCaveats = decorateWithCaveats; -//# sourceMappingURL=Caveat.js.map \ No newline at end of file diff --git a/dist/permissions/Caveat.js.map b/dist/permissions/Caveat.js.map deleted file mode 100644 index 76d665f32e..0000000000 --- a/dist/permissions/Caveat.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"Caveat.js","sourceRoot":"","sources":["../../src/permissions/Caveat.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,qCAAuD;AA+NvD;;;;;;;;;;GAUG;AACH,SAAgB,mBAAmB,CAGjC,oBAAwE,EACxE,UAA0C,EAAE,iCAAiC;AAC7E,oBAAkE;IAElE,MAAM,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC;IAC/B,IAAI,CAAC,OAAO,EAAE;QACZ,OAAO,oBAAoB,CAAC;KAC7B;IAED,IAAI,SAAS,GAAG,CACd,IAAuE,EACvE,EAAE,gDAAC,OAAA,oBAAoB,CAAC,IAAI,CAAC,CAAA,GAAA,CAAC;IAEhC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE;QAC5B,MAAM,aAAa,GACjB,oBAAoB,CAAC,MAAM,CAAC,IAAoC,CAAC,CAAC;QACpE,IAAI,CAAC,aAAa,EAAE;YAClB,MAAM,IAAI,oCAA2B,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;SACpD;QAED,SAAS,GAAG,aAAa,CAAC,SAAS,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;KACxD;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AA3BD,kDA2BC","sourcesContent":["import { Json } from '@metamask/types';\nimport { UnrecognizedCaveatTypeError } from './errors';\nimport {\n AsyncRestrictedMethod,\n RestrictedMethod,\n PermissionConstraint,\n RestrictedMethodParameters,\n} from './Permission';\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nimport type { PermissionController } from './PermissionController';\n\nexport type CaveatConstraint = {\n /**\n * The type of the caveat. The type is presumed to be meaningful in the\n * context of the capability it is associated with.\n *\n * In MetaMask, every permission can only have one caveat of each type.\n */\n readonly type: string;\n\n // TODO:TS4.4 Make optional\n /**\n * Any additional data necessary to enforce the caveat.\n */\n readonly value: Json;\n};\n\n/**\n * A `ZCAP-LD`-like caveat object. A caveat is associated with a particular\n * permission, and stored in its `caveats` array. Conceptually, a caveat is\n * an arbitrary attenuation of the authority granted by its associated\n * permission. It is the responsibility of the host to interpret and apply\n * the restriction represented by a caveat.\n *\n * @template Type - The type of the caveat.\n * @template Value - The value associated with the caveat.\n */\nexport type Caveat = {\n /**\n * The type of the caveat. The type is presumed to be meaningful in the\n * context of the capability it is associated with.\n *\n * In MetaMask, every permission can only have one caveat of each type.\n */\n readonly type: Type;\n\n // TODO:TS4.4 Make optional\n /**\n * Any additional data necessary to enforce the caveat.\n */\n readonly value: Value;\n};\n\n// Next, we define types used for specifying caveats at the consumer layer,\n// and a function for applying caveats to a restricted method request. This is\n// Accomplished by decorating the restricted method implementation with the\n// the corresponding caveat functions.\n\n/**\n * A function for applying caveats to a restricted method request.\n *\n * @template ParentCaveat - The caveat type associated with this decorator.\n * @param decorated - The restricted method implementation to be decorated.\n * The method may have already been decorated with other caveats.\n * @param caveat - The caveat object.\n * @returns The decorated restricted method implementation.\n */\nexport type CaveatDecorator = (\n decorated: AsyncRestrictedMethod,\n caveat: ParentCaveat,\n) => AsyncRestrictedMethod;\n\n/**\n * Extracts a caveat value type from a caveat decorator.\n *\n * @template Decorator - The {@link CaveatDecorator} to extract a caveat value\n * type from.\n */\ntype ExtractCaveatValueFromDecorator> =\n Decorator extends (\n decorated: any,\n caveat: infer ParentCaveat,\n ) => AsyncRestrictedMethod\n ? ParentCaveat extends CaveatConstraint\n ? ParentCaveat['value']\n : never\n : never;\n\n/**\n * A function for validating caveats of a particular type.\n *\n * @template ParentCaveat - The caveat type associated with this validator.\n * @param caveat - The caveat object to validate.\n * @param origin - The origin associated with the parent permission.\n * @param target - The target of the parent permission.\n */\nexport type CaveatValidator = (\n caveat: { type: ParentCaveat['type']; value: unknown },\n origin?: string,\n target?: string,\n) => void;\n\n/**\n * The constraint for caveat specification objects. Every {@link Caveat}\n * supported by a {@link PermissionController} must have an associated\n * specification, which is the source of truth for all caveat-related types.\n * In addition, a caveat specification includes the decorator function used\n * to apply the caveat's attenuation to a restricted method, and any validator\n * function specified by the consumer.\n *\n * See the README for more details.\n */\nexport type CaveatSpecificationConstraint = {\n /**\n * The string type of the caveat.\n */\n type: string;\n\n /**\n * The decorator function used to apply the caveat to restricted method\n * requests.\n */\n decorator: CaveatDecorator;\n\n /**\n * The validator function used to validate caveats of the associated type\n * whenever they are instantiated. Caveat are instantiated whenever they are\n * created or mutated.\n *\n * The validator should throw an appropriate JSON-RPC error if validation fails.\n *\n * If no validator is specified, no validation of caveat values will be\n * performed. Although caveats can also be validated by permission validators,\n * validating caveat values separately is strongly recommended.\n */\n validator?: CaveatValidator;\n};\n\n/**\n * Options for {@link CaveatSpecificationBuilder} functions.\n */\ntype CaveatSpecificationBuilderOptions<\n DecoratorHooks extends Record,\n ValidatorHooks extends Record,\n> = {\n type?: string;\n decoratorHooks?: DecoratorHooks;\n validatorHooks?: ValidatorHooks;\n};\n\n/**\n * A function that builds caveat specifications. Modules that specify caveats\n * for external consumption should make this their primary / default export so\n * that host applications can use them to generate concrete specifications\n * tailored to their requirements.\n */\nexport type CaveatSpecificationBuilder<\n Options extends CaveatSpecificationBuilderOptions,\n Specification extends CaveatSpecificationConstraint,\n> = (options: Options) => Specification;\n\n/**\n * A caveat specification export object, containing the\n * {@link CaveatSpecificationBuilder} function and \"hook name\" objects.\n */\nexport type CaveatSpecificationBuilderExportConstraint = {\n specificationBuilder: CaveatSpecificationBuilder<\n CaveatSpecificationBuilderOptions,\n CaveatSpecificationConstraint\n >;\n decoratorHookNames?: Record;\n validatorHookNames?: Record;\n};\n\n/**\n * The specifications for all caveats supported by a particular\n * {@link PermissionController}.\n *\n * @template Specifications - The union of all {@link CaveatSpecificationConstraint} types.\n */\nexport type CaveatSpecificationMap<\n CaveatSpecification extends CaveatSpecificationConstraint,\n> = Record;\n\n/**\n * Extracts the union of all caveat types specified by the given\n * {@link CaveatSpecificationConstraint} type.\n *\n * @template CaveatSpecification - The {@link CaveatSpecificationConstraint} to extract a\n * caveat type union from.\n */\nexport type ExtractCaveats<\n CaveatSpecification extends CaveatSpecificationConstraint,\n> = CaveatSpecification extends any\n ? Caveat<\n CaveatSpecification['type'],\n ExtractCaveatValueFromDecorator\n >\n : never;\n\n/**\n * Extracts the type of a specific {@link Caveat} from a union of caveat\n * specifications.\n *\n * @template CaveatSpecifications - The union of all caveat specifications.\n * @template CaveatType - The type of the caveat to extract.\n */\nexport type ExtractCaveat<\n CaveatSpecifications extends CaveatSpecificationConstraint,\n CaveatType extends string,\n> = Extract, { type: CaveatType }>;\n\n/**\n * Extracts the value type of a specific {@link Caveat} from a union of caveat\n * specifications.\n *\n * @template CaveatSpecifications - The union of all caveat specifications.\n * @template CaveatType - The type of the caveat whose value to extract.\n */\nexport type ExtractCaveatValue<\n CaveatSpecifications extends CaveatSpecificationConstraint,\n CaveatType extends string,\n> = ExtractCaveat['value'];\n\n/**\n * Decorate a restricted method implementation with its caveats.\n *\n * Note that all caveat functions (i.e. the argument and return value of the\n * decorator) must be awaited.\n *\n * @param methodImplementation - The restricted method implementation\n * @param permission - The origin's potential permission\n * @param caveatSpecifications - All caveat implementations\n * @returns The decorated method implementation\n */\nexport function decorateWithCaveats<\n CaveatSpecifications extends CaveatSpecificationConstraint,\n>(\n methodImplementation: RestrictedMethod,\n permission: Readonly, // bound to the requesting origin\n caveatSpecifications: CaveatSpecificationMap, // all caveat implementations\n): RestrictedMethod {\n const { caveats } = permission;\n if (!caveats) {\n return methodImplementation;\n }\n\n let decorated = async (\n args: Parameters>[0],\n ) => methodImplementation(args);\n\n for (const caveat of caveats) {\n const specification =\n caveatSpecifications[caveat.type as CaveatSpecifications['type']];\n if (!specification) {\n throw new UnrecognizedCaveatTypeError(caveat.type);\n }\n\n decorated = specification.decorator(decorated, caveat);\n }\n\n return decorated;\n}\n"]} \ No newline at end of file diff --git a/dist/permissions/Permission.d.ts b/dist/permissions/Permission.d.ts deleted file mode 100644 index 7ba47e4208..0000000000 --- a/dist/permissions/Permission.d.ts +++ /dev/null @@ -1,426 +0,0 @@ -import { Json } from '@metamask/types'; -import { NonEmptyArray } from '../util'; -import { CaveatConstraint } from './Caveat'; -/** - * The origin of a subject. - * Effectively the GUID of an entity that can have permissions. - */ -export declare type OriginString = string; -/** - * The name of a permission target. - */ -declare type TargetName = string; -/** - * A `ZCAP-LD`-like permission object. A permission is associated with a - * particular `invoker`, which is the holder of the permission. Possessing the - * permission grants access to a particular restricted resource, identified by - * the `parentCapability`. The use of the restricted resource may be further - * restricted by any `caveats` associated with the permission. - * - * See the README for details. - */ -export declare type PermissionConstraint = { - /** - * The context(s) in which this capability is meaningful. - * - * It is required by the standard, but we make it optional since there is only - * one context in our usage (i.e. the user's MetaMask instance). - */ - readonly '@context'?: NonEmptyArray; - /** - * The caveats of the permission. - * - * @see {@link Caveat} For more information. - */ - readonly caveats: null | NonEmptyArray; - /** - * The creation date of the permission, in UNIX epoch time. - */ - readonly date: number; - /** - * The GUID of the permission object. - */ - readonly id: string; - /** - * The origin string of the subject that has the permission. - */ - readonly invoker: OriginString; - /** - * A pointer to the resource that possession of the capability grants - * access to, for example a JSON-RPC method or endowment. - */ - readonly parentCapability: string; -}; -/** - * A `ZCAP-LD`-like permission object. A permission is associated with a - * particular `invoker`, which is the holder of the permission. Possessing the - * permission grants access to a particular restricted resource, identified by - * the `parentCapability`. The use of the restricted resource may be further - * restricted by any `caveats` associated with the permission. - * - * See the README for details. - * - * @template TargetKey - They key of the permission target that the permission - * corresponds to. - * @template AllowedCaveat - A union of the allowed {@link Caveat} types - * for the permission. - */ -export declare type ValidPermission = PermissionConstraint & { - /** - * The caveats of the permission. - * - * @see {@link Caveat} For more information. - */ - readonly caveats: AllowedCaveat extends never ? null : NonEmptyArray | null; - /** - * A pointer to the resource that possession of the capability grants - * access to, for example a JSON-RPC method or endowment. - */ - readonly parentCapability: ExtractPermissionTargetNames; -}; -/** - * A utility type for ensuring that the given permission target name conforms to - * our naming conventions. - * - * See the README for the distinction between target names and keys. - */ -declare type ValidTargetName = Name extends `${string}*` ? never : Name extends `${string}_` ? never : Name; -/** - * A utility type for extracting permission target names from a union of target - * keys. - * - * See the README for the distinction between target names and keys. - * - * @template Key - The target key type to extract target names from. - */ -export declare type ExtractPermissionTargetNames = ValidTargetName; -/** - * Extracts the permission key of a particular name from a union of keys. - * An internal utility type used in {@link ExtractPermissionTargetKey}. - * - * @template Key - The target key type to extract from. - * @template Name - The name whose key to extract. - */ -declare type KeyOfTargetName = Name extends ExtractPermissionTargetNames ? Key : never; -/** - * A utility type for finding the permission target key corresponding to a - * target name. In a way, the inverse of {@link ExtractPermissionTargetNames}. - * - * See the README for the distinction between target names and keys. - * - * @template Key - The target key type to extract from. - * @template Name - The name whose key to extract. - */ -export declare type ExtractPermissionTargetKey = Key extends Name ? Key : Extract>; -/** - * Internal utility for extracting the members types of an array. The type - * evalutes to `never` if the specified type is the empty tuple or neither - * an array nor a tuple. - * - * @template ArrayType - The array type whose members to extract. - */ -declare type ExtractArrayMembers = ArrayType extends [] ? never : ArrayType extends any[] | readonly any[] ? ArrayType[number] : never; -/** - * A utility type for extracting the allowed caveat types for a particular - * permission from a permission specification type. - * - * @template PermissionSpecification - The permission specification type to - * extract valid caveat types from. - */ -export declare type ExtractAllowedCaveatTypes = ExtractArrayMembers; -/** - * The options object of {@link constructPermission}. - * - * @template TargetPermission - The {@link Permission} that will be constructed. - */ -export declare type PermissionOptions = { - target: TargetPermission['parentCapability']; - /** - * The origin string of the subject that has the permission. - */ - invoker: OriginString; - /** - * The caveats of the permission. - * See {@link Caveat}. - */ - caveats?: NonEmptyArray; -}; -/** - * The default permission factory function. Naively constructs a permission from - * the inputs. Sets a default, random `id` if none is provided. - * - * @see {@link Permission} For more details. - * @template TargetPermission- - The {@link Permission} that will be constructed. - * @param options - The options for the permission. - * @returns The new permission object. - */ -export declare function constructPermission(options: PermissionOptions): TargetPermission; -/** - * Gets the caveat of the specified type belonging to the specified permission. - * - * @param permission - The permission whose caveat to retrieve. - * @param caveatType - The type of the caveat to retrieve. - * @returns The caveat, or undefined if no such caveat exists. - */ -export declare function findCaveat(permission: PermissionConstraint, caveatType: string): CaveatConstraint | undefined; -/** - * A requested permission object. Just an object with any of the properties - * of a {@link PermissionConstraint} object. - */ -declare type RequestedPermission = Partial; -/** - * A record of target names and their {@link RequestedPermission} objects. - */ -export declare type RequestedPermissions = Record; -/** - * The restricted method context object. Essentially a way to pass internal - * arguments to restricted methods and caveat functions, most importantly the - * requesting origin. - */ -declare type RestrictedMethodContext = Readonly<{ - origin: OriginString; - [key: string]: any; -}>; -export declare type RestrictedMethodParameters = Json[] | Record | void; -/** - * The arguments passed to a restricted method implementation. - * - * @template Params - The JSON-RPC parameters of the restricted method. - */ -export declare type RestrictedMethodOptions = { - method: TargetName; - params?: Params; - context: RestrictedMethodContext; -}; -/** - * A synchronous restricted method implementation. - * - * @template Params - The JSON-RPC parameters of the restricted method. - * @template Result - The JSON-RPC result of the restricted method. - */ -export declare type SyncRestrictedMethod = (args: RestrictedMethodOptions) => Result; -/** - * An asynchronous restricted method implementation. - * - * @template Params - The JSON-RPC parameters of the restricted method. - * @template Result - The JSON-RPC result of the restricted method. - */ -export declare type AsyncRestrictedMethod = (args: RestrictedMethodOptions) => Promise; -/** - * A synchronous or asynchronous restricted method implementation. - * - * @template Params - The JSON-RPC parameters of the restricted method. - * @template Result - The JSON-RPC result of the restricted method. - */ -export declare type RestrictedMethod = SyncRestrictedMethod | AsyncRestrictedMethod; -export declare type ValidRestrictedMethod> = MethodImplementation extends (args: infer Options) => Json | Promise ? Options extends RestrictedMethodOptions ? MethodImplementation : never : never; -/** - * {@link EndowmentGetter} parameter object. - */ -export declare type EndowmentGetterParams = { - /** - * The origin of the requesting subject. - */ - origin: string; - /** - * Any additional data associated with the request. - */ - requestData?: unknown; - [key: string]: unknown; -}; -/** - * A synchronous or asynchronous function that gets the endowments for a - * particular endowment permission. The getter receives the origin of the - * requesting subject and, optionally, additional request metadata. - */ -export declare type EndowmentGetter = (options: EndowmentGetterParams) => Endowments | Promise; -export declare type PermissionFactory> = (options: PermissionOptions, requestData?: RequestData) => TargetPermission; -export declare type PermissionValidatorConstraint = (permission: PermissionConstraint, origin?: OriginString, target?: string) => void; -/** - * A utility type for ensuring that the given permission target key conforms to - * our naming conventions. - * - * See the README for the distinction between target names and keys. - * - * @template Key - The target key string to apply the constraint to. - */ -declare type ValidTargetKey = Key extends `${string}_*` ? Key : Key extends `${string}_` ? never : Key extends `${string}*` ? never : Key; -/** - * The different possible types of permissions. - */ -export declare enum PermissionType { - /** - * A restricted JSON-RPC method. A subject must have the requisite permission - * to call a restricted JSON-RPC method. - */ - RestrictedMethod = "RestrictedMethod", - /** - * An "endowment" granted to subjects that possess the requisite permission, - * such as a global environment variable exposing a restricted API, etc. - */ - Endowment = "Endowment" -} -/** - * The base constraint for permission specification objects. Every - * {@link Permission} supported by a {@link PermissionController} must have an - * associated specification, which is the source of truth for all permission- - * related types. A permission specification includes the list of permitted - * caveats, and any factory and validation functions specified by the consumer. - * A concrete permission specification may specify further fields as necessary. - * - * See the README for more details. - */ -declare type PermissionSpecificationBase = { - /** - * The type of the specified permission. - */ - permissionType: Type; - /** - * The target resource of the permission. The shape of this string depends on - * the permission type. For example, a restricted method target key will - * consist of either a complete method name or the prefix of a namespaced - * method, e.g. `wallet_snap_*`. - */ - targetKey: string; - /** - * An array of the caveat types that may be added to instances of this - * permission. - */ - allowedCaveats: Readonly> | null; - /** - * The factory function used to get permission objects. Permissions returned - * by this function are presumed to valid, and they will not be passed to the - * validator function associated with this specification (if any). In other - * words, the factory function should validate the permissions it creates. - * - * If no factory is specified, the {@link Permission} constructor will be - * used, and the validator function (if specified) will be called on newly - * constructed permissions. - */ - factory?: PermissionFactory>; - /** - * The validator function used to validate permissions of the associated type - * whenever they are mutated. The only way a permission can be legally mutated - * is when its caveats are modified by the permission controller. - * - * The validator should throw an appropriate JSON-RPC error if validation fails. - */ - validator?: PermissionValidatorConstraint; -}; -/** - * The constraint for restricted method permission specification objects. - * Permissions that correspond to JSON-RPC methods are specified using objects - * that conform to this type. - * - * See the README for more details. - */ -export declare type RestrictedMethodSpecificationConstraint = PermissionSpecificationBase & { - /** - * The implementation of the restricted method that the permission - * corresponds to. - */ - methodImplementation: RestrictedMethod; -}; -/** - * The constraint for endowment permission specification objects. Permissions - * that endow callers with some restricted resource are specified using objects - * that conform to this type. - * - * See the README for more details. - */ -export declare type EndowmentSpecificationConstraint = PermissionSpecificationBase & { - /** - * Endowment permissions do not support caveats. - */ - allowedCaveats: null; - /** - * The {@link EndowmentGetter} function for the permission. This function - * will be called by the {@link PermissionController} whenever the - * permission is invoked, after which the host can apply the endowments to - * the requesting subject in the intended manner. - */ - endowmentGetter: EndowmentGetter; -}; -/** - * The constraint for permission specification objects. Every {@link Permission} - * supported by a {@link PermissionController} must have an associated - * specification, which is the source of truth for all permission-related types. - * All specifications must adhere to the {@link PermissionSpecificationBase} - * interface, but specifications may have different fields depending on the - * {@link PermissionType}. - * - * See the README for more details. - */ -export declare type PermissionSpecificationConstraint = EndowmentSpecificationConstraint | RestrictedMethodSpecificationConstraint; -/** - * Options for {@link PermissionSpecificationBuilder} functions. - */ -declare type PermissionSpecificationBuilderOptions, MethodHooks extends Record, ValidatorHooks extends Record> = { - targetKey?: string; - allowedCaveats?: Readonly> | null; - factoryHooks?: FactoryHooks; - methodHooks?: MethodHooks; - validatorHooks?: ValidatorHooks; -}; -/** - * A function that builds a permission specification. Modules that specify - * permissions for external consumption should make this their primary / - * default export so that host applications can use them to generate concrete - * specifications tailored to their requirements. - */ -export declare type PermissionSpecificationBuilder, Specification extends PermissionSpecificationConstraint & { - permissionType: Type; -}> = (options: Options) => Specification; -/** - * A restricted method permission export object, containing the - * {@link PermissionSpecificationBuilder} function and "hook name" objects. - */ -export declare type PermissionSpecificationBuilderExportConstraint = { - targetKey: string; - specificationBuilder: PermissionSpecificationBuilder, PermissionSpecificationConstraint>; - factoryHookNames?: Record; - methodHookNames?: Record; - validatorHookNames?: Record; -}; -declare type ValidRestrictedMethodSpecification = Specification['methodImplementation'] extends ValidRestrictedMethod ? Specification : never; -/** - * Constraint for {@link PermissionSpecificationConstraint} objects that - * evaluates to `never` if the specification contains any invalid fields. - * - * @template Specification - The permission specification to validate. - */ -export declare type ValidPermissionSpecification = Specification['targetKey'] extends ValidTargetKey ? Specification['permissionType'] extends PermissionType.Endowment ? Specification : Specification['permissionType'] extends PermissionType.RestrictedMethod ? ValidRestrictedMethodSpecification> : never : never; -/** - * Checks that the specification has the expected permission type. - * - * @param specification - The specification to check. - * @param expectedType - The expected permission type. - * @template Specification - The specification to check. - * @template Type - The expected permission type. - * @returns Whether or not the specification is of the expected type. - */ -export declare function hasSpecificationType(specification: Specification, expectedType: Type): specification is Specification & { - permissionType: Type; -}; -/** - * The specifications for all permissions supported by a particular - * {@link PermissionController}. - * - * @template Specifications - The union of all {@link PermissionSpecificationConstraint} types. - */ -export declare type PermissionSpecificationMap = { - [TargetKey in Specification['targetKey']]: Specification extends { - targetKey: TargetKey; - } ? Specification : never; -}; -/** - * Extracts a specific {@link PermissionSpecificationConstraint} from a union of - * permission specifications. - * - * @template Specification - The specification union type to extract from. - * @template TargetKey - The `targetKey` of the specification to extract. - */ -export declare type ExtractPermissionSpecification = Specification extends { - targetKey: TargetKey; -} ? Specification : never; -export {}; diff --git a/dist/permissions/Permission.js b/dist/permissions/Permission.js deleted file mode 100644 index b1f7f2f589..0000000000 --- a/dist/permissions/Permission.js +++ /dev/null @@ -1,66 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.hasSpecificationType = exports.PermissionType = exports.findCaveat = exports.constructPermission = void 0; -const nanoid_1 = require("nanoid"); -/** - * The default permission factory function. Naively constructs a permission from - * the inputs. Sets a default, random `id` if none is provided. - * - * @see {@link Permission} For more details. - * @template TargetPermission- - The {@link Permission} that will be constructed. - * @param options - The options for the permission. - * @returns The new permission object. - */ -function constructPermission(options) { - const { caveats = null, invoker, target } = options; - return { - id: (0, nanoid_1.nanoid)(), - parentCapability: target, - invoker, - caveats, - date: new Date().getTime(), - }; -} -exports.constructPermission = constructPermission; -/** - * Gets the caveat of the specified type belonging to the specified permission. - * - * @param permission - The permission whose caveat to retrieve. - * @param caveatType - The type of the caveat to retrieve. - * @returns The caveat, or undefined if no such caveat exists. - */ -function findCaveat(permission, caveatType) { - var _a; - return (_a = permission.caveats) === null || _a === void 0 ? void 0 : _a.find((caveat) => caveat.type === caveatType); -} -exports.findCaveat = findCaveat; -/** - * The different possible types of permissions. - */ -var PermissionType; -(function (PermissionType) { - /** - * A restricted JSON-RPC method. A subject must have the requisite permission - * to call a restricted JSON-RPC method. - */ - PermissionType["RestrictedMethod"] = "RestrictedMethod"; - /** - * An "endowment" granted to subjects that possess the requisite permission, - * such as a global environment variable exposing a restricted API, etc. - */ - PermissionType["Endowment"] = "Endowment"; -})(PermissionType = exports.PermissionType || (exports.PermissionType = {})); -/** - * Checks that the specification has the expected permission type. - * - * @param specification - The specification to check. - * @param expectedType - The expected permission type. - * @template Specification - The specification to check. - * @template Type - The expected permission type. - * @returns Whether or not the specification is of the expected type. - */ -function hasSpecificationType(specification, expectedType) { - return specification.permissionType === expectedType; -} -exports.hasSpecificationType = hasSpecificationType; -//# sourceMappingURL=Permission.js.map \ No newline at end of file diff --git a/dist/permissions/Permission.js.map b/dist/permissions/Permission.js.map deleted file mode 100644 index df7ad30fa3..0000000000 --- a/dist/permissions/Permission.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"Permission.js","sourceRoot":"","sources":["../../src/permissions/Permission.ts"],"names":[],"mappings":";;;AACA,mCAAgC;AAmMhC;;;;;;;;GAQG;AACH,SAAgB,mBAAmB,CAEjC,OAA4C;IAC5C,MAAM,EAAE,OAAO,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAEpD,OAAO;QACL,EAAE,EAAE,IAAA,eAAM,GAAE;QACZ,gBAAgB,EAAE,MAAM;QACxB,OAAO;QACP,OAAO;QACP,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE;KACP,CAAC;AACxB,CAAC;AAZD,kDAYC;AAED;;;;;;GAMG;AACH,SAAgB,UAAU,CACxB,UAAgC,EAChC,UAAkB;;IAElB,OAAO,MAAA,UAAU,CAAC,OAAO,0CAAE,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;AAC1E,CAAC;AALD,gCAKC;AAwID;;GAEG;AACH,IAAY,cAYX;AAZD,WAAY,cAAc;IACxB;;;OAGG;IACH,uDAAqC,CAAA;IAErC;;;OAGG;IACH,yCAAuB,CAAA;AACzB,CAAC,EAZW,cAAc,GAAd,sBAAc,KAAd,sBAAc,QAYzB;AAoLD;;;;;;;;GAQG;AACH,SAAgB,oBAAoB,CAIlC,aAA4B,EAC5B,YAAkB;IAIlB,OAAO,aAAa,CAAC,cAAc,KAAK,YAAY,CAAC;AACvD,CAAC;AAVD,oDAUC","sourcesContent":["import { Json } from '@metamask/types';\nimport { nanoid } from 'nanoid';\nimport { NonEmptyArray } from '../util';\nimport { CaveatConstraint } from './Caveat';\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nimport type { PermissionController } from './PermissionController';\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nimport type { Caveat } from './Caveat';\n\n/**\n * The origin of a subject.\n * Effectively the GUID of an entity that can have permissions.\n */\nexport type OriginString = string;\n\n/**\n * The name of a permission target.\n */\ntype TargetName = string;\n\n/**\n * A `ZCAP-LD`-like permission object. A permission is associated with a\n * particular `invoker`, which is the holder of the permission. Possessing the\n * permission grants access to a particular restricted resource, identified by\n * the `parentCapability`. The use of the restricted resource may be further\n * restricted by any `caveats` associated with the permission.\n *\n * See the README for details.\n */\nexport type PermissionConstraint = {\n /**\n * The context(s) in which this capability is meaningful.\n *\n * It is required by the standard, but we make it optional since there is only\n * one context in our usage (i.e. the user's MetaMask instance).\n */\n readonly '@context'?: NonEmptyArray;\n\n // TODO:TS4.4 Make optional\n /**\n * The caveats of the permission.\n *\n * @see {@link Caveat} For more information.\n */\n readonly caveats: null | NonEmptyArray;\n\n /**\n * The creation date of the permission, in UNIX epoch time.\n */\n readonly date: number;\n\n /**\n * The GUID of the permission object.\n */\n readonly id: string;\n\n /**\n * The origin string of the subject that has the permission.\n */\n readonly invoker: OriginString;\n\n /**\n * A pointer to the resource that possession of the capability grants\n * access to, for example a JSON-RPC method or endowment.\n */\n readonly parentCapability: string;\n};\n\n/**\n * A `ZCAP-LD`-like permission object. A permission is associated with a\n * particular `invoker`, which is the holder of the permission. Possessing the\n * permission grants access to a particular restricted resource, identified by\n * the `parentCapability`. The use of the restricted resource may be further\n * restricted by any `caveats` associated with the permission.\n *\n * See the README for details.\n *\n * @template TargetKey - They key of the permission target that the permission\n * corresponds to.\n * @template AllowedCaveat - A union of the allowed {@link Caveat} types\n * for the permission.\n */\nexport type ValidPermission<\n TargetKey extends TargetName,\n AllowedCaveat extends CaveatConstraint,\n> = PermissionConstraint & {\n // TODO:TS4.4 Make optional\n /**\n * The caveats of the permission.\n *\n * @see {@link Caveat} For more information.\n */\n readonly caveats: AllowedCaveat extends never\n ? null\n : NonEmptyArray | null;\n\n /**\n * A pointer to the resource that possession of the capability grants\n * access to, for example a JSON-RPC method or endowment.\n */\n readonly parentCapability: ExtractPermissionTargetNames;\n};\n\n/**\n * A utility type for ensuring that the given permission target name conforms to\n * our naming conventions.\n *\n * See the README for the distinction between target names and keys.\n */\ntype ValidTargetName = Name extends `${string}*`\n ? never\n : Name extends `${string}_`\n ? never\n : Name;\n\n/**\n * A utility type for extracting permission target names from a union of target\n * keys.\n *\n * See the README for the distinction between target names and keys.\n *\n * @template Key - The target key type to extract target names from.\n */\nexport type ExtractPermissionTargetNames = ValidTargetName<\n Key extends `${infer Base}_*` ? `${Base}_${string}` : Key\n>;\n\n/**\n * Extracts the permission key of a particular name from a union of keys.\n * An internal utility type used in {@link ExtractPermissionTargetKey}.\n *\n * @template Key - The target key type to extract from.\n * @template Name - The name whose key to extract.\n */\ntype KeyOfTargetName<\n Key extends string,\n Name extends string,\n> = Name extends ExtractPermissionTargetNames ? Key : never;\n\n/**\n * A utility type for finding the permission target key corresponding to a\n * target name. In a way, the inverse of {@link ExtractPermissionTargetNames}.\n *\n * See the README for the distinction between target names and keys.\n *\n * @template Key - The target key type to extract from.\n * @template Name - The name whose key to extract.\n */\nexport type ExtractPermissionTargetKey<\n Key extends string,\n Name extends string,\n> = Key extends Name ? Key : Extract>;\n\n/**\n * Internal utility for extracting the members types of an array. The type\n * evalutes to `never` if the specified type is the empty tuple or neither\n * an array nor a tuple.\n *\n * @template ArrayType - The array type whose members to extract.\n */\ntype ExtractArrayMembers = ArrayType extends []\n ? never\n : ArrayType extends any[] | readonly any[]\n ? ArrayType[number]\n : never;\n\n/**\n * A utility type for extracting the allowed caveat types for a particular\n * permission from a permission specification type.\n *\n * @template PermissionSpecification - The permission specification type to\n * extract valid caveat types from.\n */\nexport type ExtractAllowedCaveatTypes<\n PermissionSpecification extends PermissionSpecificationConstraint,\n> = ExtractArrayMembers;\n\n/**\n * The options object of {@link constructPermission}.\n *\n * @template TargetPermission - The {@link Permission} that will be constructed.\n */\nexport type PermissionOptions = {\n target: TargetPermission['parentCapability'];\n /**\n * The origin string of the subject that has the permission.\n */\n invoker: OriginString;\n\n /**\n * The caveats of the permission.\n * See {@link Caveat}.\n */\n caveats?: NonEmptyArray;\n};\n\n/**\n * The default permission factory function. Naively constructs a permission from\n * the inputs. Sets a default, random `id` if none is provided.\n *\n * @see {@link Permission} For more details.\n * @template TargetPermission- - The {@link Permission} that will be constructed.\n * @param options - The options for the permission.\n * @returns The new permission object.\n */\nexport function constructPermission<\n TargetPermission extends PermissionConstraint,\n>(options: PermissionOptions): TargetPermission {\n const { caveats = null, invoker, target } = options;\n\n return {\n id: nanoid(),\n parentCapability: target,\n invoker,\n caveats,\n date: new Date().getTime(),\n } as TargetPermission;\n}\n\n/**\n * Gets the caveat of the specified type belonging to the specified permission.\n *\n * @param permission - The permission whose caveat to retrieve.\n * @param caveatType - The type of the caveat to retrieve.\n * @returns The caveat, or undefined if no such caveat exists.\n */\nexport function findCaveat(\n permission: PermissionConstraint,\n caveatType: string,\n): CaveatConstraint | undefined {\n return permission.caveats?.find((caveat) => caveat.type === caveatType);\n}\n\n/**\n * A requested permission object. Just an object with any of the properties\n * of a {@link PermissionConstraint} object.\n */\ntype RequestedPermission = Partial;\n\n/**\n * A record of target names and their {@link RequestedPermission} objects.\n */\nexport type RequestedPermissions = Record;\n\n/**\n * The restricted method context object. Essentially a way to pass internal\n * arguments to restricted methods and caveat functions, most importantly the\n * requesting origin.\n */\ntype RestrictedMethodContext = Readonly<{\n origin: OriginString;\n [key: string]: any;\n}>;\n\nexport type RestrictedMethodParameters = Json[] | Record | void;\n\n/**\n * The arguments passed to a restricted method implementation.\n *\n * @template Params - The JSON-RPC parameters of the restricted method.\n */\nexport type RestrictedMethodOptions =\n {\n method: TargetName;\n params?: Params;\n context: RestrictedMethodContext;\n };\n\n/**\n * A synchronous restricted method implementation.\n *\n * @template Params - The JSON-RPC parameters of the restricted method.\n * @template Result - The JSON-RPC result of the restricted method.\n */\nexport type SyncRestrictedMethod<\n Params extends RestrictedMethodParameters,\n Result extends Json,\n> = (args: RestrictedMethodOptions) => Result;\n\n/**\n * An asynchronous restricted method implementation.\n *\n * @template Params - The JSON-RPC parameters of the restricted method.\n * @template Result - The JSON-RPC result of the restricted method.\n */\nexport type AsyncRestrictedMethod<\n Params extends RestrictedMethodParameters,\n Result extends Json,\n> = (args: RestrictedMethodOptions) => Promise;\n\n/**\n * A synchronous or asynchronous restricted method implementation.\n *\n * @template Params - The JSON-RPC parameters of the restricted method.\n * @template Result - The JSON-RPC result of the restricted method.\n */\nexport type RestrictedMethod<\n Params extends RestrictedMethodParameters,\n Result extends Json,\n> =\n | SyncRestrictedMethod\n | AsyncRestrictedMethod;\n\nexport type ValidRestrictedMethod<\n MethodImplementation extends RestrictedMethod,\n> = MethodImplementation extends (args: infer Options) => Json | Promise\n ? Options extends RestrictedMethodOptions\n ? MethodImplementation\n : never\n : never;\n\n/**\n * {@link EndowmentGetter} parameter object.\n */\nexport type EndowmentGetterParams = {\n /**\n * The origin of the requesting subject.\n */\n origin: string;\n\n /**\n * Any additional data associated with the request.\n */\n requestData?: unknown;\n\n [key: string]: unknown;\n};\n\n/**\n * A synchronous or asynchronous function that gets the endowments for a\n * particular endowment permission. The getter receives the origin of the\n * requesting subject and, optionally, additional request metadata.\n */\nexport type EndowmentGetter = (\n options: EndowmentGetterParams,\n) => Endowments | Promise;\n\nexport type PermissionFactory<\n TargetPermission extends PermissionConstraint,\n RequestData extends Record,\n> = (\n options: PermissionOptions,\n requestData?: RequestData,\n) => TargetPermission;\n\nexport type PermissionValidatorConstraint = (\n permission: PermissionConstraint,\n origin?: OriginString,\n target?: string,\n) => void;\n\n/**\n * A utility type for ensuring that the given permission target key conforms to\n * our naming conventions.\n *\n * See the README for the distinction between target names and keys.\n *\n * @template Key - The target key string to apply the constraint to.\n */\ntype ValidTargetKey = Key extends `${string}_*`\n ? Key\n : Key extends `${string}_`\n ? never\n : Key extends `${string}*`\n ? never\n : Key;\n\n/**\n * The different possible types of permissions.\n */\nexport enum PermissionType {\n /**\n * A restricted JSON-RPC method. A subject must have the requisite permission\n * to call a restricted JSON-RPC method.\n */\n RestrictedMethod = 'RestrictedMethod',\n\n /**\n * An \"endowment\" granted to subjects that possess the requisite permission,\n * such as a global environment variable exposing a restricted API, etc.\n */\n Endowment = 'Endowment',\n}\n\n/**\n * The base constraint for permission specification objects. Every\n * {@link Permission} supported by a {@link PermissionController} must have an\n * associated specification, which is the source of truth for all permission-\n * related types. A permission specification includes the list of permitted\n * caveats, and any factory and validation functions specified by the consumer.\n * A concrete permission specification may specify further fields as necessary.\n *\n * See the README for more details.\n */\ntype PermissionSpecificationBase = {\n /**\n * The type of the specified permission.\n */\n permissionType: Type;\n\n /**\n * The target resource of the permission. The shape of this string depends on\n * the permission type. For example, a restricted method target key will\n * consist of either a complete method name or the prefix of a namespaced\n * method, e.g. `wallet_snap_*`.\n */\n targetKey: string;\n\n /**\n * An array of the caveat types that may be added to instances of this\n * permission.\n */\n allowedCaveats: Readonly> | null;\n\n /**\n * The factory function used to get permission objects. Permissions returned\n * by this function are presumed to valid, and they will not be passed to the\n * validator function associated with this specification (if any). In other\n * words, the factory function should validate the permissions it creates.\n *\n * If no factory is specified, the {@link Permission} constructor will be\n * used, and the validator function (if specified) will be called on newly\n * constructed permissions.\n */\n factory?: PermissionFactory>;\n\n /**\n * The validator function used to validate permissions of the associated type\n * whenever they are mutated. The only way a permission can be legally mutated\n * is when its caveats are modified by the permission controller.\n *\n * The validator should throw an appropriate JSON-RPC error if validation fails.\n */\n validator?: PermissionValidatorConstraint;\n};\n\n/**\n * The constraint for restricted method permission specification objects.\n * Permissions that correspond to JSON-RPC methods are specified using objects\n * that conform to this type.\n *\n * See the README for more details.\n */\nexport type RestrictedMethodSpecificationConstraint =\n PermissionSpecificationBase & {\n /**\n * The implementation of the restricted method that the permission\n * corresponds to.\n */\n methodImplementation: RestrictedMethod;\n };\n\n/**\n * The constraint for endowment permission specification objects. Permissions\n * that endow callers with some restricted resource are specified using objects\n * that conform to this type.\n *\n * See the README for more details.\n */\nexport type EndowmentSpecificationConstraint =\n PermissionSpecificationBase & {\n /**\n * Endowment permissions do not support caveats.\n */\n allowedCaveats: null;\n\n /**\n * The {@link EndowmentGetter} function for the permission. This function\n * will be called by the {@link PermissionController} whenever the\n * permission is invoked, after which the host can apply the endowments to\n * the requesting subject in the intended manner.\n */\n endowmentGetter: EndowmentGetter;\n };\n\n/**\n * The constraint for permission specification objects. Every {@link Permission}\n * supported by a {@link PermissionController} must have an associated\n * specification, which is the source of truth for all permission-related types.\n * All specifications must adhere to the {@link PermissionSpecificationBase}\n * interface, but specifications may have different fields depending on the\n * {@link PermissionType}.\n *\n * See the README for more details.\n */\nexport type PermissionSpecificationConstraint =\n | EndowmentSpecificationConstraint\n | RestrictedMethodSpecificationConstraint;\n\n/**\n * Options for {@link PermissionSpecificationBuilder} functions.\n */\ntype PermissionSpecificationBuilderOptions<\n FactoryHooks extends Record,\n MethodHooks extends Record,\n ValidatorHooks extends Record,\n> = {\n targetKey?: string;\n allowedCaveats?: Readonly> | null;\n factoryHooks?: FactoryHooks;\n methodHooks?: MethodHooks;\n validatorHooks?: ValidatorHooks;\n};\n\n/**\n * A function that builds a permission specification. Modules that specify\n * permissions for external consumption should make this their primary /\n * default export so that host applications can use them to generate concrete\n * specifications tailored to their requirements.\n */\nexport type PermissionSpecificationBuilder<\n Type extends PermissionType,\n Options extends PermissionSpecificationBuilderOptions,\n Specification extends PermissionSpecificationConstraint & {\n permissionType: Type;\n },\n> = (options: Options) => Specification;\n\n/**\n * A restricted method permission export object, containing the\n * {@link PermissionSpecificationBuilder} function and \"hook name\" objects.\n */\nexport type PermissionSpecificationBuilderExportConstraint = {\n targetKey: string;\n specificationBuilder: PermissionSpecificationBuilder<\n PermissionType,\n PermissionSpecificationBuilderOptions,\n PermissionSpecificationConstraint\n >;\n factoryHookNames?: Record;\n methodHookNames?: Record;\n validatorHookNames?: Record;\n};\n\ntype ValidRestrictedMethodSpecification<\n Specification extends RestrictedMethodSpecificationConstraint,\n> = Specification['methodImplementation'] extends ValidRestrictedMethod<\n Specification['methodImplementation']\n>\n ? Specification\n : never;\n\n/**\n * Constraint for {@link PermissionSpecificationConstraint} objects that\n * evaluates to `never` if the specification contains any invalid fields.\n *\n * @template Specification - The permission specification to validate.\n */\nexport type ValidPermissionSpecification<\n Specification extends PermissionSpecificationConstraint,\n> = Specification['targetKey'] extends ValidTargetKey<\n Specification['targetKey']\n>\n ? Specification['permissionType'] extends PermissionType.Endowment\n ? Specification\n : Specification['permissionType'] extends PermissionType.RestrictedMethod\n ? ValidRestrictedMethodSpecification<\n Extract\n >\n : never\n : never;\n\n/**\n * Checks that the specification has the expected permission type.\n *\n * @param specification - The specification to check.\n * @param expectedType - The expected permission type.\n * @template Specification - The specification to check.\n * @template Type - The expected permission type.\n * @returns Whether or not the specification is of the expected type.\n */\nexport function hasSpecificationType<\n Specification extends PermissionSpecificationConstraint,\n Type extends PermissionType,\n>(\n specification: Specification,\n expectedType: Type,\n): specification is Specification & {\n permissionType: Type;\n} {\n return specification.permissionType === expectedType;\n}\n\n/**\n * The specifications for all permissions supported by a particular\n * {@link PermissionController}.\n *\n * @template Specifications - The union of all {@link PermissionSpecificationConstraint} types.\n */\nexport type PermissionSpecificationMap<\n Specification extends PermissionSpecificationConstraint,\n> = {\n [TargetKey in Specification['targetKey']]: Specification extends {\n targetKey: TargetKey;\n }\n ? Specification\n : never;\n};\n\n/**\n * Extracts a specific {@link PermissionSpecificationConstraint} from a union of\n * permission specifications.\n *\n * @template Specification - The specification union type to extract from.\n * @template TargetKey - The `targetKey` of the specification to extract.\n */\nexport type ExtractPermissionSpecification<\n Specification extends PermissionSpecificationConstraint,\n TargetKey extends Specification['targetKey'],\n> = Specification extends {\n targetKey: TargetKey;\n}\n ? Specification\n : never;\n"]} \ No newline at end of file diff --git a/dist/permissions/PermissionController.d.ts b/dist/permissions/PermissionController.d.ts deleted file mode 100644 index 6b56c9831a..0000000000 --- a/dist/permissions/PermissionController.d.ts +++ /dev/null @@ -1,870 +0,0 @@ -import { Patch } from 'immer'; -import { AcceptRequest as AcceptApprovalRequest, AddApprovalRequest, HasApprovalRequest, RejectRequest as RejectApprovalRequest } from '../approval/ApprovalController'; -import { BaseController, Json } from '../BaseControllerV2'; -import { RestrictedControllerMessenger } from '../ControllerMessenger'; -import { NonEmptyArray } from '../util'; -import { CaveatConstraint, CaveatSpecificationConstraint, CaveatSpecificationMap, ExtractCaveat, ExtractCaveats, ExtractCaveatValue } from './Caveat'; -import { EndowmentSpecificationConstraint, ExtractAllowedCaveatTypes, OriginString, PermissionConstraint, PermissionSpecificationConstraint, PermissionSpecificationMap, RequestedPermissions, RestrictedMethod, RestrictedMethodParameters, RestrictedMethodSpecificationConstraint, ValidPermission, ValidPermissionSpecification } from './Permission'; -import { getPermissionMiddlewareFactory } from './permission-middleware'; -/** - * Metadata associated with {@link PermissionController} subjects. - */ -export declare type PermissionSubjectMetadata = { - origin: OriginString; -}; -/** - * Metadata associated with permission requests. - */ -export declare type PermissionsRequestMetadata = PermissionSubjectMetadata & { - id: string; -}; -/** - * Used for prompting the user about a proposed new permission. - * Includes information about the grantee subject, requested permissions, and - * any additional information added by the consumer. - * - * All properties except `permissions` are passed to any factories found for - * the requested permissions. - */ -export declare type PermissionsRequest = { - metadata: PermissionsRequestMetadata; - permissions: RequestedPermissions; - [key: string]: Json; -}; -/** - * The name of the {@link PermissionController}. - */ -declare const controllerName = "PermissionController"; -/** - * Permissions associated with a {@link PermissionController} subject. - */ -export declare type SubjectPermissions = Record; -/** - * Permissions and metadata associated with a {@link PermissionController} - * subject. - */ -export declare type PermissionSubjectEntry = { - origin: SubjectPermission['invoker']; - permissions: SubjectPermissions; -}; -/** - * All subjects of a {@link PermissionController}. - * - * @template SubjectPermission - The permissions of the subject. - */ -export declare type PermissionControllerSubjects = Record>; -/** - * The state of a {@link PermissionController}. - * - * @template Permission - The controller's permission type union. - */ -export declare type PermissionControllerState = Permission extends PermissionConstraint ? { - subjects: PermissionControllerSubjects; -} : never; -/** - * Gets the state of the {@link PermissionController}. - */ -export declare type GetPermissionControllerState = { - type: `${typeof controllerName}:getState`; - handler: () => PermissionControllerState; -}; -/** - * Gets the names of all subjects from the {@link PermissionController}. - */ -export declare type GetSubjects = { - type: `${typeof controllerName}:getSubjectNames`; - handler: () => (keyof PermissionControllerSubjects)[]; -}; -/** - * Gets the permissions for specified subject - */ -export declare type GetPermissions = { - type: `${typeof controllerName}:getPermissions`; - handler: GenericPermissionController['getPermissions']; -}; -/** - * Checks whether the specified subject has any permissions. - */ -export declare type HasPermissions = { - type: `${typeof controllerName}:hasPermissions`; - handler: GenericPermissionController['hasPermissions']; -}; -/** - * Checks whether the specified subject has a specific permission. - */ -export declare type HasPermission = { - type: `${typeof controllerName}:hasPermission`; - handler: GenericPermissionController['hasPermission']; -}; -/** - * Directly grants given permissions for a specificed origin without requesting user approval - */ -export declare type GrantPermissions = { - type: `${typeof controllerName}:grantPermissions`; - handler: GenericPermissionController['grantPermissions']; -}; -/** - * Requests given permissions for a specified origin - */ -export declare type RequestPermissions = { - type: `${typeof controllerName}:requestPermissions`; - handler: GenericPermissionController['requestPermissions']; -}; -/** - * Removes the specified permissions for each origin. - */ -export declare type RevokePermissions = { - type: `${typeof controllerName}:revokePermissions`; - handler: GenericPermissionController['revokePermissions']; -}; -/** - * Removes all permissions for a given origin - */ -export declare type RevokeAllPermissions = { - type: `${typeof controllerName}:revokeAllPermissions`; - handler: GenericPermissionController['revokeAllPermissions']; -}; -/** - * Revokes all permissions corresponding to the specified target for all subjects. - * Does nothing if no subjects or no such permission exists. - */ -export declare type RevokePermissionForAllSubjects = { - type: `${typeof controllerName}:revokePermissionForAllSubjects`; - handler: GenericPermissionController['revokePermissionForAllSubjects']; -}; -/** - * Clears all permissions from the {@link PermissionController}. - */ -export declare type ClearPermissions = { - type: `${typeof controllerName}:clearPermissions`; - handler: () => void; -}; -/** - * Gets the endowments for the given subject and permission. - */ -export declare type GetEndowments = { - type: `${typeof controllerName}:getEndowments`; - handler: GenericPermissionController['getEndowments']; -}; -/** - * The {@link ControllerMessenger} actions of the {@link PermissionController}. - */ -export declare type PermissionControllerActions = ClearPermissions | GetEndowments | GetPermissionControllerState | GetSubjects | GetPermissions | HasPermission | HasPermissions | GrantPermissions | RequestPermissions | RevokeAllPermissions | RevokePermissionForAllSubjects | RevokePermissions; -/** - * The generic state change event of the {@link PermissionController}. - */ -export declare type PermissionControllerStateChange = { - type: `${typeof controllerName}:stateChange`; - payload: [PermissionControllerState, Patch[]]; -}; -/** - * The {@link ControllerMessenger} events of the {@link PermissionController}. - * - * The permission controller only emits its generic state change events. - * Consumers should use selector subscriptions to subscribe to relevant - * substate. - */ -export declare type PermissionControllerEvents = PermissionControllerStateChange; -/** - * The external {@link ControllerMessenger} actions available to the - * {@link PermissionController}. - */ -declare type AllowedActions = AddApprovalRequest | HasApprovalRequest | AcceptApprovalRequest | RejectApprovalRequest; -/** - * The messenger of the {@link PermissionController}. - */ -export declare type PermissionControllerMessenger = RestrictedControllerMessenger; -/** - * A generic {@link PermissionController}. - */ -export declare type GenericPermissionController = PermissionController; -/** - * Describes the possible results of a {@link CaveatMutator} function. - */ -export declare enum CaveatMutatorOperation { - noop = 0, - updateValue = 1, - deleteCaveat = 2, - revokePermission = 3 -} -/** - * Given a caveat value, returns a {@link CaveatMutatorOperation} and, optionally, - * a new caveat value. - * - * @see {@link PermissionController.updatePermissionsByCaveat} for more details. - * @template Caveat - The caveat type for which this mutator is intended. - * @param caveatValue - The existing value of the caveat being mutated. - * @returns A tuple of the mutation result and, optionally, the new caveat - * value. - */ -export declare type CaveatMutator = (caveatValue: TargetCaveat['value']) => CaveatMutatorResult; -declare type CaveatMutatorResult = Readonly<{ - operation: CaveatMutatorOperation.updateValue; - value: CaveatConstraint['value']; -}> | Readonly<{ - operation: Exclude; -}>; -/** - * Extracts the permission(s) specified by the given permission and caveat - * specifications. - * - * @template ControllerPermissionSpecification - The permission specification(s) - * to extract from. - * @template ControllerCaveatSpecification - The caveat specification(s) to - * extract from. Necessary because {@link Permission} has a generic parameter - * that describes the allowed caveats for the permission. - */ -export declare type ExtractPermission = ControllerPermissionSpecification extends ValidPermissionSpecification ? ValidPermission> : never; -/** - * Extracts the restricted method permission(s) specified by the given - * permission and caveat specifications. - * - * @template ControllerPermissionSpecification - The permission specification(s) - * to extract from. - * @template ControllerCaveatSpecification - The caveat specification(s) to - * extract from. Necessary because {@link Permission} has a generic parameter - * that describes the allowed caveats for the permission. - */ -export declare type ExtractRestrictedMethodPermission = ExtractPermission, ControllerCaveatSpecification>; -/** - * Extracts the endowment permission(s) specified by the given permission and - * caveat specifications. - * - * @template ControllerPermissionSpecification - The permission specification(s) - * to extract from. - * @template ControllerCaveatSpecification - The caveat specification(s) to - * extract from. Necessary because {@link Permission} has a generic parameter - * that describes the allowed caveats for the permission. - */ -export declare type ExtractEndowmentPermission = ExtractPermission, ControllerCaveatSpecification>; -/** - * Options for the {@link PermissionController} constructor. - * - * @template ControllerPermissionSpecification - A union of the types of all - * permission specifications available to the controller. Any referenced caveats - * must be included in the controller's caveat specifications. - * @template ControllerCaveatSpecification - A union of the types of all - * caveat specifications available to the controller. - */ -export declare type PermissionControllerOptions = { - messenger: PermissionControllerMessenger; - caveatSpecifications: CaveatSpecificationMap; - permissionSpecifications: PermissionSpecificationMap; - unrestrictedMethods: string[]; - state?: Partial>>; -}; -/** - * The permission controller. See the README for details. - * - * Assumes the existence of an {@link ApprovalController} reachable via the - * {@link ControllerMessenger}. - * - * @template ControllerPermissionSpecification - A union of the types of all - * permission specifications available to the controller. Any referenced caveats - * must be included in the controller's caveat specifications. - * @template ControllerCaveatSpecification - A union of the types of all - * caveat specifications available to the controller. - */ -export declare class PermissionController extends BaseController>, PermissionControllerMessenger> { - private readonly _caveatSpecifications; - private readonly _permissionSpecifications; - private readonly _unrestrictedMethods; - /** - * The names of all JSON-RPC methods that will be ignored by the controller. - * - * @returns The names of all unrestricted JSON-RPC methods - */ - get unrestrictedMethods(): ReadonlySet; - /** - * Returns a `json-rpc-engine` middleware function factory, so that the rules - * described by the state of this controller can be applied to incoming - * JSON-RPC requests. - * - * The middleware **must** be added in the correct place in the middleware - * stack in order for it to work. See the README for an example. - */ - createPermissionMiddleware: ReturnType; - /** - * Constructs the PermissionController. - * - * @param options - Permission controller options. - * @param options.caveatSpecifications - The specifications of all caveats - * available to the controller. See {@link CaveatSpecificationMap} and the - * documentation for more details. - * @param options.permissionSpecifications - The specifications of all - * permissions available to the controller. See - * {@link PermissionSpecificationMap} and the README for more details. - * @param options.unrestrictedMethods - The callable names of all JSON-RPC - * methods ignored by the new controller. - * @param options.messenger - The controller messenger. See - * {@link BaseController} for more information. - * @param options.state - Existing state to hydrate the controller with at - * initialization. - */ - constructor(options: PermissionControllerOptions); - /** - * Gets a permission specification. - * - * @param targetKey - The target key of the permission specification to get. - * @returns The permission specification with the specified target key. - */ - private getPermissionSpecification; - /** - * Gets a caveat specification. - * - * @param caveatType - The type of the caveat specification to get. - * @returns The caveat specification with the specified type. - */ - private getCaveatSpecification; - /** - * Constructor helper for validating permission specifications. This is - * intended to prevent the use of invalid target keys which, while impossible - * to add in TypeScript, could rather easily occur in plain JavaScript. - * - * Throws an error if validation fails. - * - * @param permissionSpecifications - The permission specifications passed to - * this controller's constructor. - * @param caveatSpecifications - The caveat specifications passed to this - * controller. - */ - private validatePermissionSpecifications; - /** - * Constructor helper for registering the controller's messaging system - * actions. - */ - private registerMessageHandlers; - /** - * Clears the state of the controller. - */ - clearState(): void; - /** - * Gets the permission specification corresponding to the given permission - * type and target name. Throws an error if the target name does not - * correspond to a permission, or if the specification is not of the - * given permission type. - * - * @template Type - The type of the permission specification to get. - * @param permissionType - The type of the permission specification to get. - * @param targetName - The name of the permission whose specification to get. - * @param requestingOrigin - The origin of the requesting subject, if any. - * Will be added to any thrown errors. - * @returns The specification object corresponding to the given type and - * target name. - */ - private getTypedPermissionSpecification; - /** - * Gets the implementation of the specified restricted method. - * - * A JSON-RPC error is thrown if the method does not exist. - * - * @see {@link PermissionController.executeRestrictedMethod} and - * {@link PermissionController.createPermissionMiddleware} for internal usage. - * @param method - The name of the restricted method. - * @param origin - The origin associated with the request for the restricted - * method, if any. - * @returns The restricted method implementation. - */ - getRestrictedMethod(method: string, origin?: string): RestrictedMethod; - /** - * Gets a list of all origins of subjects. - * - * @returns The origins (i.e. IDs) of all subjects. - */ - getSubjectNames(): OriginString[]; - /** - * Gets the permission for the specified target of the subject corresponding - * to the specified origin. - * - * @param origin - The origin of the subject. - * @param targetName - The method name as invoked by a third party (i.e., not - * a method key). - * @returns The permission if it exists, or undefined otherwise. - */ - getPermission>(origin: OriginString, targetName: SubjectPermission['parentCapability']): SubjectPermission | undefined; - /** - * Gets all permissions for the specified subject, if any. - * - * @param origin - The origin of the subject. - * @returns The permissions of the subject, if any. - */ - getPermissions(origin: OriginString): SubjectPermissions>> | undefined; - /** - * Checks whether the subject with the specified origin has the specified - * permission. - * - * @param origin - The origin of the subject. - * @param target - The target name of the permission. - * @returns Whether the subject has the permission. - */ - hasPermission(origin: OriginString, target: ExtractPermission['parentCapability']): boolean; - /** - * Checks whether the subject with the specified origin has any permissions. - * Use this if you want to know if a subject "exists". - * - * @param origin - The origin of the subject to check. - * @returns Whether the subject has any permissions. - */ - hasPermissions(origin: OriginString): boolean; - /** - * Revokes all permissions from the specified origin. - * - * Throws an error of the origin has no permissions. - * - * @param origin - The origin whose permissions to revoke. - */ - revokeAllPermissions(origin: OriginString): void; - /** - * Revokes the specified permission from the subject with the specified - * origin. - * - * Throws an error if the subject or the permission does not exist. - * - * @param origin - The origin of the subject whose permission to revoke. - * @param target - The target name of the permission to revoke. - */ - revokePermission(origin: OriginString, target: ExtractPermission['parentCapability']): void; - /** - * Revokes the specified permissions from the specified subjects. - * - * Throws an error if any of the subjects or permissions do not exist. - * - * @param subjectsAndPermissions - An object mapping subject origins - * to arrays of permission target names to revoke. - */ - revokePermissions(subjectsAndPermissions: Record['parentCapability']>>): void; - /** - * Revokes all permissions corresponding to the specified target for all subjects. - * Does nothing if no subjects or no such permission exists. - * - * @param target - The name of the target to revoke all permissions for. - */ - revokePermissionForAllSubjects(target: ExtractPermission['parentCapability']): void; - /** - * Deletes the permission identified by the given origin and target. If the - * permission is the single remaining permission of its subject, the subject - * is also deleted. - * - * @param subjects - The draft permission controller subjects. - * @param origin - The origin of the subject associated with the permission - * to delete. - * @param target - The target name of the permission to delete. - */ - private deletePermission; - /** - * Checks whether the permission of the subject corresponding to the given - * origin has a caveat of the specified type. - * - * Throws an error if the subject does not have a permission with the - * specified target name. - * - * @template TargetName - The permission target name. Should be inferred. - * @template CaveatType - The valid caveat types for the permission. Should - * be inferred. - * @param origin - The origin of the subject. - * @param target - The target name of the permission. - * @param caveatType - The type of the caveat to check for. - * @returns Whether the permission has the specified caveat. - */ - hasCaveat['parentCapability'], CaveatType extends ExtractAllowedCaveatTypes>(origin: OriginString, target: TargetName, caveatType: CaveatType): boolean; - /** - * Gets the caveat of the specified type, if any, for the permission of - * the subject corresponding to the given origin. - * - * Throws an error if the subject does not have a permission with the - * specified target name. - * - * @template TargetName - The permission target name. Should be inferred. - * @template CaveatType - The valid caveat types for the permission. Should - * be inferred. - * @param origin - The origin of the subject. - * @param target - The target name of the permission. - * @param caveatType - The type of the caveat to get. - * @returns The caveat, or `undefined` if no such caveat exists. - */ - getCaveat['parentCapability'], CaveatType extends ExtractAllowedCaveatTypes>(origin: OriginString, target: TargetName, caveatType: CaveatType): ExtractCaveat | undefined; - /** - * Adds a caveat of the specified type, with the specified caveat value, to - * the permission corresponding to the given subject origin and permission - * target. - * - * For modifying existing caveats, use - * {@link PermissionController.updateCaveat}. - * - * Throws an error if no such permission exists, or if the caveat already - * exists. - * - * @template TargetName - The permission target name. Should be inferred. - * @template CaveatType - The valid caveat types for the permission. Should - * be inferred. - * @param origin - The origin of the subject. - * @param target - The target name of the permission. - * @param caveatType - The type of the caveat to add. - * @param caveatValue - The value of the caveat to add. - */ - addCaveat['parentCapability'], CaveatType extends ExtractAllowedCaveatTypes>(origin: OriginString, target: TargetName, caveatType: CaveatType, caveatValue: ExtractCaveatValue): void; - /** - * Updates the value of the caveat of the specified type belonging to the - * permission corresponding to the given subject origin and permission - * target. - * - * For adding new caveats, use - * {@link PermissionController.addCaveat}. - * - * Throws an error if no such permission or caveat exists. - * - * @template TargetName - The permission target name. Should be inferred. - * @template CaveatType - The valid caveat types for the permission. Should - * be inferred. - * @param origin - The origin of the subject. - * @param target - The target name of the permission. - * @param caveatType - The type of the caveat to update. - * @param caveatValue - The new value of the caveat. - */ - updateCaveat['parentCapability'], CaveatType extends ExtractAllowedCaveatTypes, CaveatValue extends ExtractCaveatValue>(origin: OriginString, target: TargetName, caveatType: CaveatType, caveatValue: CaveatValue): void; - /** - * Sets the specified caveat on the specified permission. Overwrites existing - * caveats of the same type in-place (preserving array order), and adds the - * caveat to the end of the array otherwise. - * - * Throws an error if the permission does not exist or fails to validate after - * its caveats have been modified. - * - * @see {@link PermissionController.addCaveat} - * @see {@link PermissionController.updateCaveat} - * @template TargetName - The permission target name. Should be inferred. - * @template CaveatType - The valid caveat types for the permission. Should - * be inferred. - * @param origin - The origin of the subject. - * @param target - The target name of the permission. - * @param caveatType - The type of the caveat to set. - * @param caveatValue - The value of the caveat to set. - */ - private setCaveat; - /** - * Updates all caveats with the specified type for all subjects and - * permissions by applying the specified mutator function to them. - * - * ATTN: Permissions can be revoked entirely by the action of this method, - * read on for details. - * - * Caveat mutators are functions that receive a caveat value and return a - * tuple consisting of a {@link CaveatMutatorOperation} and, optionally, a new - * value to update the existing caveat with. - * - * For each caveat, depending on the mutator result, this method will: - * - Do nothing ({@link CaveatMutatorOperation.noop}) - * - Update the value of the caveat ({@link CaveatMutatorOperation.updateValue}). The caveat specification validator, if any, will be called after updating the value. - * - Delete the caveat ({@link CaveatMutatorOperation.deleteCaveat}). The permission specification validator, if any, will be called after deleting the caveat. - * - Revoke the parent permission ({@link CaveatMutatorOperation.revokePermission}) - * - * This method throws if the validation of any caveat or permission fails. - * - * @param targetCaveatType - The type of the caveats to update. - * @param mutator - The mutator function which will be applied to all caveat - * values. - */ - updatePermissionsByCaveat['type'], TargetCaveat extends ExtractCaveat>(targetCaveatType: CaveatType, mutator: CaveatMutator): void; - /** - * Removes the caveat of the specified type from the permission corresponding - * to the given subject origin and target name. - * - * Throws an error if no such permission or caveat exists. - * - * @template TargetName - The permission target name. Should be inferred. - * @template CaveatType - The valid caveat types for the permission. Should - * be inferred. - * @param origin - The origin of the subject. - * @param target - The target name of the permission. - * @param caveatType - The type of the caveat to remove. - */ - removeCaveat['parentCapability'], CaveatType extends ExtractAllowedCaveatTypes>(origin: OriginString, target: TargetName, caveatType: CaveatType): void; - /** - * Deletes the specified caveat from the specified permission. If no caveats - * remain after deletion, the permission's caveat property is set to `null`. - * The permission is validated after being modified. - * - * Throws an error if the permission does not have a caveat with the specified - * type. - * - * @param permission - The permission whose caveat to delete. - * @param caveatType - The type of the caveat to delete. - * @param origin - The origin the permission subject. - * @param target - The name of the permission target. - */ - private deleteCaveat; - /** - * Validates the specified modified permission. Should **always** be invoked - * on a permission after its caveats have been modified. - * - * Just like {@link PermissionController.validatePermission}, except that the - * corresponding target key and specification are retrieved first, and an - * error is thrown if the target key does not exist. - * - * @param permission - The modified permission to validate. - * @param origin - The origin associated with the permission. - * @param targetName - The target name name of the permission. - */ - private validateModifiedPermission; - /** - * Gets the key for the specified permission target. - * - * Used to support our namespaced permission target feature, which is used - * to implement namespaced restricted JSON-RPC methods. - * - * @param target - The requested permission target. - * @returns The internal key of the permission target. - */ - private getTargetKey; - /** - * Grants _approved_ permissions to the specified subject. Every permission and - * caveat is stringently validated – including by calling every specification - * validator – and an error is thrown if any validation fails. - * - * ATTN: This method does **not** prompt the user for approval. - * - * @see {@link PermissionController.requestPermissions} For initiating a - * permissions request requiring user approval. - * @param options - Options bag. - * @param options.approvedPermissions - The requested permissions approved by - * the user. - * @param options.requestData - Permission request data. Passed to permission - * factory functions. - * @param options.preserveExistingPermissions - Whether to preserve the - * subject's existing permissions. - * @param options.subject - The subject to grant permissions to. - * @returns The granted permissions. - */ - grantPermissions({ approvedPermissions, requestData, preserveExistingPermissions, subject, }: { - approvedPermissions: RequestedPermissions; - subject: PermissionSubjectMetadata; - preserveExistingPermissions?: boolean; - requestData?: Record; - }): SubjectPermissions>; - /** - * Validates the specified permission by: - * - Ensuring that its `caveats` property is either `null` or a non-empty array. - * - Ensuring that it only includes caveats allowed by its specification. - * - Ensuring that it includes no duplicate caveats (by caveat type). - * - Validating each caveat object, if `performCaveatValidation` is `true`. - * - Calling the validator of its specification, if one exists and `invokePermissionValidator` is `true`. - * - * An error is thrown if validation fails. - * - * @param specification - The specification of the permission. - * @param permission - The permission to validate. - * @param origin - The origin associated with the permission. - * @param targetName - The target name of the permission. - * @param validationOptions - Validation options. - * @param validationOptions.invokePermissionValidator - Whether to invoke the - * permission's consumer-specified validator function, if any. - * @param validationOptions.performCaveatValidation - Whether to invoke - * {@link PermissionController.validateCaveat} on each of the permission's - * caveats. - */ - private validatePermission; - /** - * Assigns the specified permissions to the subject with the given origin. - * Overwrites all existing permissions, and creates a subject entry if it - * doesn't already exist. - * - * ATTN: Assumes that the new permissions have been validated. - * - * @param origin - The origin of the grantee subject. - * @param permissions - The new permissions for the grantee subject. - */ - private setValidatedPermissions; - /** - * Validates the requested caveats for the permission of the specified - * subject origin and target name and returns the validated caveat array. - * - * Throws an error if validation fails. - * - * @param origin - The origin of the permission subject. - * @param target - The permission target name. - * @param requestedCaveats - The requested caveats to construct. - * @returns The constructed caveats. - */ - private constructCaveats; - /** - * This methods validates that the specified caveat is an object with the - * expected properties and types. It also ensures that a caveat specification - * exists for the requested caveat type, and calls the specification - * validator, if it exists, on the caveat object. - * - * Throws an error if validation fails. - * - * @param caveat - The caveat object to validate. - * @param origin - The origin associated with the subject of the parent - * permission. - * @param target - The target name associated with the parent permission. - */ - private validateCaveat; - /** - * Initiates a permission request that requires user approval. This should - * always be used to grant additional permissions to a subject, unless user - * approval has been obtained through some other means. - * - * Permissions are validated at every step of the approval process, and this - * method will reject if validation fails. - * - * @see {@link ApprovalController} For the user approval logic. - * @see {@link PermissionController.acceptPermissionsRequest} For the method - * that _accepts_ the request and resolves the user approval promise. - * @see {@link PermissionController.rejectPermissionsRequest} For the method - * that _rejects_ the request and the user approval promise. - * @param subject - The grantee subject. - * @param requestedPermissions - The requested permissions. - * @param options - Additional options. - * @param options.id - The id of the permissions request. Defaults to a unique - * id. - * @param options.preserveExistingPermissions - Whether to preserve the - * subject's existing permissions. Defaults to `true`. - * @returns The granted permissions and request metadata. - */ - requestPermissions(subject: PermissionSubjectMetadata, requestedPermissions: RequestedPermissions, options?: { - id?: string; - preserveExistingPermissions?: boolean; - }): Promise<[ - SubjectPermissions>, - { - id: string; - origin: OriginString; - } - ]>; - /** - * Validates requested permissions. Throws if validation fails. - * - * This method ensures that the requested permissions are a properly - * formatted {@link RequestedPermissions} object, and performs the same - * validation as {@link PermissionController.grantPermissions}, except that - * consumer-specified permission validator functions are not called, since - * they are only called on fully constructed, approved permissions that are - * otherwise completely valid. - * - * Unrecognzied properties on requested permissions are ignored. - * - * @param origin - The origin of the grantee subject. - * @param requestedPermissions - The requested permissions. - */ - private validateRequestedPermissions; - /** - * Adds a request to the {@link ApprovalController} using the - * {@link AddApprovalRequest} action. Also validates the resulting approved - * permissions request, and throws an error if validation fails. - * - * @param permissionsRequest - The permissions request object. - * @returns The approved permissions request object. - */ - private requestUserApproval; - /** - * Validates an approved {@link PermissionsRequest} object. The approved - * request must have the required `metadata` and `permissions` properties, - * the `id` and `origin` of the `metadata` must match the original request - * metadata, and the requested permissions must be valid per - * {@link PermissionController.validateRequestedPermissions}. Any extra - * metadata properties are ignored. - * - * An error is thrown if validation fails. - * - * @param approvedRequest - The approved permissions request object. - * @param originalMetadata - The original request metadata. - */ - private validateApprovedPermissions; - /** - * Accepts a permissions request created by - * {@link PermissionController.requestPermissions}. - * - * @param request - The permissions request. - */ - acceptPermissionsRequest(request: PermissionsRequest): Promise; - /** - * Rejects a permissions request created by - * {@link PermissionController.requestPermissions}. - * - * @param id - The id of the request to be rejected. - */ - rejectPermissionsRequest(id: string): Promise; - /** - * Checks whether the {@link ApprovalController} has a particular permissions - * request. - * - * @see {@link PermissionController.acceptPermissionsRequest} and - * {@link PermissionController.rejectPermissionsRequest} for usage. - * @param options - The {@link HasApprovalRequest} options. - * @param options.id - The id of the approval request to check for. - * @returns Whether the specified request exists. - */ - private hasApprovalRequest; - /** - * Rejects the permissions request with the specified id, with the specified - * error as the reason. This method is effectively a wrapper around a - * messenger call for the `ApprovalController:rejectRequest` action. - * - * @see {@link PermissionController.acceptPermissionsRequest} and - * {@link PermissionController.rejectPermissionsRequest} for usage. - * @param id - The id of the request to reject. - * @param error - The error associated with the rejection. - * @returns Nothing - */ - private _rejectPermissionsRequest; - /** - * Gets the subject's endowments per the specified endowment permission. - * Throws if the subject does not have the required permission or if the - * permission is not an endowment permission. - * - * @param origin - The origin of the subject whose endowments to retrieve. - * @param targetName - The name of the endowment permission. This must be a - * valid permission target name. - * @param requestData - Additional data associated with the request, if any. - * Forwarded to the endowment getter function for the permission. - * @returns The endowments, if any. - */ - getEndowments(origin: string, targetName: ExtractEndowmentPermission['parentCapability'], requestData?: unknown): Promise; - /** - * Executes a restricted method as the subject with the given origin. - * The specified params, if any, will be passed to the method implementation. - * - * ATTN: Great caution should be exercised in the use of this method. - * Methods that cause side effects or affect application state should - * be avoided. - * - * This method will first attempt to retrieve the requested restricted method - * implementation, throwing if it does not exist. The method will then be - * invoked as though the subject with the specified origin had invoked it with - * the specified parameters. This means that any existing caveats will be - * applied to the restricted method, and this method will throw if the - * restricted method or its caveat decorators throw. - * - * In addition, this method will throw if the subject does not have a - * permission for the specified restricted method. - * - * @param origin - The origin of the subject to execute the method on behalf - * of. - * @param targetName - The name of the method to execute. This must be a valid - * permission target name. - * @param params - The parameters to pass to the method implementation. - * @returns The result of the executed method. - */ - executeRestrictedMethod(origin: OriginString, targetName: ExtractRestrictedMethodPermission['parentCapability'], params?: RestrictedMethodParameters): Promise; - /** - * An internal method used in the controller's `json-rpc-engine` middleware - * and {@link PermissionController.executeRestrictedMethod}. Calls the - * specified restricted method implementation after decorating it with the - * caveats of its permission. Throws if the subject does not have the - * requisite permission. - * - * ATTN: Parameter validation is the responsibility of the caller, or - * the restricted method implementation in the case of `params`. - * - * @see {@link PermissionController.executeRestrictedMethod} and - * {@link PermissionController.createPermissionMiddleware} for usage. - * @param methodImplementation - The implementation of the method to call. - * @param subject - Metadata about the subject that made the request. - * @param method - The method name - * @param params - Params needed for executing the restricted method - * @returns The result of the restricted method implementation - */ - private _executeRestrictedMethod; -} -export {}; diff --git a/dist/permissions/PermissionController.js b/dist/permissions/PermissionController.js deleted file mode 100644 index e1661ca5a2..0000000000 --- a/dist/permissions/PermissionController.js +++ /dev/null @@ -1,1220 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __rest = (this && this.__rest) || function (s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.PermissionController = exports.CaveatMutatorOperation = void 0; -const deep_freeze_strict_1 = __importDefault(require("deep-freeze-strict")); -const immer_1 = require("immer"); -const nanoid_1 = require("nanoid"); -const eth_rpc_errors_1 = require("eth-rpc-errors"); -const BaseControllerV2_1 = require("../BaseControllerV2"); -const util_1 = require("../util"); -const Caveat_1 = require("./Caveat"); -const errors_1 = require("./errors"); -const Permission_1 = require("./Permission"); -const permission_middleware_1 = require("./permission-middleware"); -const utils_1 = require("./utils"); -/** - * The name of the {@link PermissionController}. - */ -const controllerName = 'PermissionController'; -/** - * Get the state metadata of the {@link PermissionController}. - * - * @template Permission - The controller's permission type union. - * @returns The state metadata - */ -function getStateMetadata() { - return { subjects: { anonymous: true, persist: true } }; -} -/** - * Get the default state of the {@link PermissionController}. - * - * @template Permission - The controller's permission type union. - * @returns The default state of the controller - */ -function getDefaultState() { - return { subjects: {} }; -} -/** - * Describes the possible results of a {@link CaveatMutator} function. - */ -var CaveatMutatorOperation; -(function (CaveatMutatorOperation) { - CaveatMutatorOperation[CaveatMutatorOperation["noop"] = 0] = "noop"; - CaveatMutatorOperation[CaveatMutatorOperation["updateValue"] = 1] = "updateValue"; - CaveatMutatorOperation[CaveatMutatorOperation["deleteCaveat"] = 2] = "deleteCaveat"; - CaveatMutatorOperation[CaveatMutatorOperation["revokePermission"] = 3] = "revokePermission"; -})(CaveatMutatorOperation = exports.CaveatMutatorOperation || (exports.CaveatMutatorOperation = {})); -/** - * The permission controller. See the README for details. - * - * Assumes the existence of an {@link ApprovalController} reachable via the - * {@link ControllerMessenger}. - * - * @template ControllerPermissionSpecification - A union of the types of all - * permission specifications available to the controller. Any referenced caveats - * must be included in the controller's caveat specifications. - * @template ControllerCaveatSpecification - A union of the types of all - * caveat specifications available to the controller. - */ -class PermissionController extends BaseControllerV2_1.BaseController { - /** - * Constructs the PermissionController. - * - * @param options - Permission controller options. - * @param options.caveatSpecifications - The specifications of all caveats - * available to the controller. See {@link CaveatSpecificationMap} and the - * documentation for more details. - * @param options.permissionSpecifications - The specifications of all - * permissions available to the controller. See - * {@link PermissionSpecificationMap} and the README for more details. - * @param options.unrestrictedMethods - The callable names of all JSON-RPC - * methods ignored by the new controller. - * @param options.messenger - The controller messenger. See - * {@link BaseController} for more information. - * @param options.state - Existing state to hydrate the controller with at - * initialization. - */ - constructor(options) { - const { caveatSpecifications, permissionSpecifications, unrestrictedMethods, messenger, state = {}, } = options; - super({ - name: controllerName, - metadata: getStateMetadata(), - messenger, - state: Object.assign(Object.assign({}, getDefaultState()), state), - }); - this._unrestrictedMethods = new Set(unrestrictedMethods); - this._caveatSpecifications = (0, deep_freeze_strict_1.default)(Object.assign({}, caveatSpecifications)); - this.validatePermissionSpecifications(permissionSpecifications, this._caveatSpecifications); - this._permissionSpecifications = (0, deep_freeze_strict_1.default)(Object.assign({}, permissionSpecifications)); - this.registerMessageHandlers(); - this.createPermissionMiddleware = (0, permission_middleware_1.getPermissionMiddlewareFactory)({ - executeRestrictedMethod: this._executeRestrictedMethod.bind(this), - getRestrictedMethod: this.getRestrictedMethod.bind(this), - isUnrestrictedMethod: this.unrestrictedMethods.has.bind(this.unrestrictedMethods), - }); - } - /** - * The names of all JSON-RPC methods that will be ignored by the controller. - * - * @returns The names of all unrestricted JSON-RPC methods - */ - get unrestrictedMethods() { - return this._unrestrictedMethods; - } - /** - * Gets a permission specification. - * - * @param targetKey - The target key of the permission specification to get. - * @returns The permission specification with the specified target key. - */ - getPermissionSpecification(targetKey) { - return this._permissionSpecifications[targetKey]; - } - /** - * Gets a caveat specification. - * - * @param caveatType - The type of the caveat specification to get. - * @returns The caveat specification with the specified type. - */ - getCaveatSpecification(caveatType) { - return this._caveatSpecifications[caveatType]; - } - /** - * Constructor helper for validating permission specifications. This is - * intended to prevent the use of invalid target keys which, while impossible - * to add in TypeScript, could rather easily occur in plain JavaScript. - * - * Throws an error if validation fails. - * - * @param permissionSpecifications - The permission specifications passed to - * this controller's constructor. - * @param caveatSpecifications - The caveat specifications passed to this - * controller. - */ - validatePermissionSpecifications(permissionSpecifications, caveatSpecifications) { - Object.entries(permissionSpecifications).forEach(([targetKey, { permissionType, targetKey: innerTargetKey, allowedCaveats },]) => { - if (!permissionType || !(0, util_1.hasProperty)(Permission_1.PermissionType, permissionType)) { - throw new Error(`Invalid permission type: "${permissionType}"`); - } - // Check if the target key is the empty string, ends with "_", or ends - // with "*" but not "_*" - if (!targetKey || /_$/u.test(targetKey) || /[^_]\*$/u.test(targetKey)) { - throw new Error(`Invalid permission target key: "${targetKey}"`); - } - if (targetKey !== innerTargetKey) { - throw new Error(`Invalid permission specification: key "${targetKey}" must match specification.target value "${innerTargetKey}".`); - } - if (allowedCaveats) { - allowedCaveats.forEach((caveatType) => { - if (!(0, util_1.hasProperty)(caveatSpecifications, caveatType)) { - throw new errors_1.UnrecognizedCaveatTypeError(caveatType); - } - }); - } - }); - } - /** - * Constructor helper for registering the controller's messaging system - * actions. - */ - registerMessageHandlers() { - this.messagingSystem.registerActionHandler(`${controllerName}:clearPermissions`, () => this.clearState()); - this.messagingSystem.registerActionHandler(`${controllerName}:getEndowments`, (origin, targetName, requestData) => this.getEndowments(origin, targetName, requestData)); - this.messagingSystem.registerActionHandler(`${controllerName}:getSubjectNames`, () => this.getSubjectNames()); - this.messagingSystem.registerActionHandler(`${controllerName}:getPermissions`, (origin) => this.getPermissions(origin)); - this.messagingSystem.registerActionHandler(`${controllerName}:hasPermission`, (origin, targetName) => this.hasPermission(origin, targetName)); - this.messagingSystem.registerActionHandler(`${controllerName}:hasPermissions`, (origin) => this.hasPermissions(origin)); - this.messagingSystem.registerActionHandler(`${controllerName}:grantPermissions`, this.grantPermissions.bind(this)); - this.messagingSystem.registerActionHandler(`${controllerName}:requestPermissions`, (subject, permissions) => this.requestPermissions(subject, permissions)); - this.messagingSystem.registerActionHandler(`${controllerName}:revokeAllPermissions`, (origin) => this.revokeAllPermissions(origin)); - this.messagingSystem.registerActionHandler(`${controllerName}:revokePermissionForAllSubjects`, (target) => this.revokePermissionForAllSubjects(target)); - this.messagingSystem.registerActionHandler(`${controllerName}:revokePermissions`, this.revokePermissions.bind(this)); - } - /** - * Clears the state of the controller. - */ - clearState() { - this.update((_draftState) => { - return Object.assign({}, getDefaultState()); - }); - } - /** - * Gets the permission specification corresponding to the given permission - * type and target name. Throws an error if the target name does not - * correspond to a permission, or if the specification is not of the - * given permission type. - * - * @template Type - The type of the permission specification to get. - * @param permissionType - The type of the permission specification to get. - * @param targetName - The name of the permission whose specification to get. - * @param requestingOrigin - The origin of the requesting subject, if any. - * Will be added to any thrown errors. - * @returns The specification object corresponding to the given type and - * target name. - */ - getTypedPermissionSpecification(permissionType, targetName, requestingOrigin) { - const failureError = permissionType === Permission_1.PermissionType.RestrictedMethod - ? (0, errors_1.methodNotFound)(targetName, requestingOrigin ? { origin: requestingOrigin } : undefined) - : new errors_1.EndowmentPermissionDoesNotExistError(targetName, requestingOrigin); - const targetKey = this.getTargetKey(targetName); - if (!targetKey) { - throw failureError; - } - const specification = this.getPermissionSpecification(targetKey); - if (!(0, Permission_1.hasSpecificationType)(specification, permissionType)) { - throw failureError; - } - return specification; - } - /** - * Gets the implementation of the specified restricted method. - * - * A JSON-RPC error is thrown if the method does not exist. - * - * @see {@link PermissionController.executeRestrictedMethod} and - * {@link PermissionController.createPermissionMiddleware} for internal usage. - * @param method - The name of the restricted method. - * @param origin - The origin associated with the request for the restricted - * method, if any. - * @returns The restricted method implementation. - */ - getRestrictedMethod(method, origin) { - return this.getTypedPermissionSpecification(Permission_1.PermissionType.RestrictedMethod, method, origin).methodImplementation; - } - /** - * Gets a list of all origins of subjects. - * - * @returns The origins (i.e. IDs) of all subjects. - */ - getSubjectNames() { - return Object.keys(this.state.subjects); - } - /** - * Gets the permission for the specified target of the subject corresponding - * to the specified origin. - * - * @param origin - The origin of the subject. - * @param targetName - The method name as invoked by a third party (i.e., not - * a method key). - * @returns The permission if it exists, or undefined otherwise. - */ - getPermission(origin, targetName) { - var _a; - return (_a = this.state.subjects[origin]) === null || _a === void 0 ? void 0 : _a.permissions[targetName]; - } - /** - * Gets all permissions for the specified subject, if any. - * - * @param origin - The origin of the subject. - * @returns The permissions of the subject, if any. - */ - getPermissions(origin) { - var _a; - return (_a = this.state.subjects[origin]) === null || _a === void 0 ? void 0 : _a.permissions; - } - /** - * Checks whether the subject with the specified origin has the specified - * permission. - * - * @param origin - The origin of the subject. - * @param target - The target name of the permission. - * @returns Whether the subject has the permission. - */ - hasPermission(origin, target) { - return Boolean(this.getPermission(origin, target)); - } - /** - * Checks whether the subject with the specified origin has any permissions. - * Use this if you want to know if a subject "exists". - * - * @param origin - The origin of the subject to check. - * @returns Whether the subject has any permissions. - */ - hasPermissions(origin) { - return Boolean(this.state.subjects[origin]); - } - /** - * Revokes all permissions from the specified origin. - * - * Throws an error of the origin has no permissions. - * - * @param origin - The origin whose permissions to revoke. - */ - revokeAllPermissions(origin) { - this.update((draftState) => { - if (!draftState.subjects[origin]) { - throw new errors_1.UnrecognizedSubjectError(origin); - } - delete draftState.subjects[origin]; - }); - } - /** - * Revokes the specified permission from the subject with the specified - * origin. - * - * Throws an error if the subject or the permission does not exist. - * - * @param origin - The origin of the subject whose permission to revoke. - * @param target - The target name of the permission to revoke. - */ - revokePermission(origin, target) { - this.revokePermissions({ [origin]: [target] }); - } - /** - * Revokes the specified permissions from the specified subjects. - * - * Throws an error if any of the subjects or permissions do not exist. - * - * @param subjectsAndPermissions - An object mapping subject origins - * to arrays of permission target names to revoke. - */ - revokePermissions(subjectsAndPermissions) { - this.update((draftState) => { - Object.keys(subjectsAndPermissions).forEach((origin) => { - if (!(0, util_1.hasProperty)(draftState.subjects, origin)) { - throw new errors_1.UnrecognizedSubjectError(origin); - } - subjectsAndPermissions[origin].forEach((target) => { - const { permissions } = draftState.subjects[origin]; - if (!(0, util_1.hasProperty)(permissions, target)) { - throw new errors_1.PermissionDoesNotExistError(origin, target); - } - this.deletePermission(draftState.subjects, origin, target); - }); - }); - }); - } - /** - * Revokes all permissions corresponding to the specified target for all subjects. - * Does nothing if no subjects or no such permission exists. - * - * @param target - The name of the target to revoke all permissions for. - */ - revokePermissionForAllSubjects(target) { - if (this.getSubjectNames().length === 0) { - return; - } - this.update((draftState) => { - Object.entries(draftState.subjects).forEach(([origin, subject]) => { - const { permissions } = subject; - if ((0, util_1.hasProperty)(permissions, target)) { - this.deletePermission(draftState.subjects, origin, target); - } - }); - }); - } - /** - * Deletes the permission identified by the given origin and target. If the - * permission is the single remaining permission of its subject, the subject - * is also deleted. - * - * @param subjects - The draft permission controller subjects. - * @param origin - The origin of the subject associated with the permission - * to delete. - * @param target - The target name of the permission to delete. - */ - deletePermission(subjects, origin, target) { - const { permissions } = subjects[origin]; - if (Object.keys(permissions).length > 1) { - delete permissions[target]; - } - else { - delete subjects[origin]; - } - } - /** - * Checks whether the permission of the subject corresponding to the given - * origin has a caveat of the specified type. - * - * Throws an error if the subject does not have a permission with the - * specified target name. - * - * @template TargetName - The permission target name. Should be inferred. - * @template CaveatType - The valid caveat types for the permission. Should - * be inferred. - * @param origin - The origin of the subject. - * @param target - The target name of the permission. - * @param caveatType - The type of the caveat to check for. - * @returns Whether the permission has the specified caveat. - */ - hasCaveat(origin, target, caveatType) { - return Boolean(this.getCaveat(origin, target, caveatType)); - } - /** - * Gets the caveat of the specified type, if any, for the permission of - * the subject corresponding to the given origin. - * - * Throws an error if the subject does not have a permission with the - * specified target name. - * - * @template TargetName - The permission target name. Should be inferred. - * @template CaveatType - The valid caveat types for the permission. Should - * be inferred. - * @param origin - The origin of the subject. - * @param target - The target name of the permission. - * @param caveatType - The type of the caveat to get. - * @returns The caveat, or `undefined` if no such caveat exists. - */ - getCaveat(origin, target, caveatType) { - const permission = this.getPermission(origin, target); - if (!permission) { - throw new errors_1.PermissionDoesNotExistError(origin, target); - } - return (0, Permission_1.findCaveat)(permission, caveatType); - } - /** - * Adds a caveat of the specified type, with the specified caveat value, to - * the permission corresponding to the given subject origin and permission - * target. - * - * For modifying existing caveats, use - * {@link PermissionController.updateCaveat}. - * - * Throws an error if no such permission exists, or if the caveat already - * exists. - * - * @template TargetName - The permission target name. Should be inferred. - * @template CaveatType - The valid caveat types for the permission. Should - * be inferred. - * @param origin - The origin of the subject. - * @param target - The target name of the permission. - * @param caveatType - The type of the caveat to add. - * @param caveatValue - The value of the caveat to add. - */ - addCaveat(origin, target, caveatType, caveatValue) { - if (this.hasCaveat(origin, target, caveatType)) { - throw new errors_1.CaveatAlreadyExistsError(origin, target, caveatType); - } - this.setCaveat(origin, target, caveatType, caveatValue); - } - /** - * Updates the value of the caveat of the specified type belonging to the - * permission corresponding to the given subject origin and permission - * target. - * - * For adding new caveats, use - * {@link PermissionController.addCaveat}. - * - * Throws an error if no such permission or caveat exists. - * - * @template TargetName - The permission target name. Should be inferred. - * @template CaveatType - The valid caveat types for the permission. Should - * be inferred. - * @param origin - The origin of the subject. - * @param target - The target name of the permission. - * @param caveatType - The type of the caveat to update. - * @param caveatValue - The new value of the caveat. - */ - updateCaveat(origin, target, caveatType, caveatValue) { - if (!this.hasCaveat(origin, target, caveatType)) { - throw new errors_1.CaveatDoesNotExistError(origin, target, caveatType); - } - this.setCaveat(origin, target, caveatType, caveatValue); - } - /** - * Sets the specified caveat on the specified permission. Overwrites existing - * caveats of the same type in-place (preserving array order), and adds the - * caveat to the end of the array otherwise. - * - * Throws an error if the permission does not exist or fails to validate after - * its caveats have been modified. - * - * @see {@link PermissionController.addCaveat} - * @see {@link PermissionController.updateCaveat} - * @template TargetName - The permission target name. Should be inferred. - * @template CaveatType - The valid caveat types for the permission. Should - * be inferred. - * @param origin - The origin of the subject. - * @param target - The target name of the permission. - * @param caveatType - The type of the caveat to set. - * @param caveatValue - The value of the caveat to set. - */ - setCaveat(origin, target, caveatType, caveatValue) { - this.update((draftState) => { - const subject = draftState.subjects[origin]; - // Unreachable because `hasCaveat` is always called before this, and it - // throws if permissions are missing. TypeScript needs this, however. - /* istanbul ignore if */ - if (!subject) { - throw new errors_1.UnrecognizedSubjectError(origin); - } - const permission = subject.permissions[target]; - /* istanbul ignore if: practically impossible, but TypeScript wants it */ - if (!permission) { - throw new errors_1.PermissionDoesNotExistError(origin, target); - } - const caveat = { - type: caveatType, - value: caveatValue, - }; - this.validateCaveat(caveat, origin, target); - if (permission.caveats) { - const caveatIndex = permission.caveats.findIndex((existingCaveat) => existingCaveat.type === caveat.type); - if (caveatIndex === -1) { - permission.caveats.push(caveat); - } - else { - permission.caveats.splice(caveatIndex, 1, caveat); - } - } - else { - // Typecast: At this point, we don't know if the specific permission - // is allowed to have caveats, but it should be impossible to call - // this method for a permission that may not have any caveats. - // If all else fails, the permission validator is also called. - permission.caveats = [caveat]; - } - this.validateModifiedPermission(permission, origin, target); - }); - } - /** - * Updates all caveats with the specified type for all subjects and - * permissions by applying the specified mutator function to them. - * - * ATTN: Permissions can be revoked entirely by the action of this method, - * read on for details. - * - * Caveat mutators are functions that receive a caveat value and return a - * tuple consisting of a {@link CaveatMutatorOperation} and, optionally, a new - * value to update the existing caveat with. - * - * For each caveat, depending on the mutator result, this method will: - * - Do nothing ({@link CaveatMutatorOperation.noop}) - * - Update the value of the caveat ({@link CaveatMutatorOperation.updateValue}). The caveat specification validator, if any, will be called after updating the value. - * - Delete the caveat ({@link CaveatMutatorOperation.deleteCaveat}). The permission specification validator, if any, will be called after deleting the caveat. - * - Revoke the parent permission ({@link CaveatMutatorOperation.revokePermission}) - * - * This method throws if the validation of any caveat or permission fails. - * - * @param targetCaveatType - The type of the caveats to update. - * @param mutator - The mutator function which will be applied to all caveat - * values. - */ - updatePermissionsByCaveat(targetCaveatType, mutator) { - if (Object.keys(this.state.subjects).length === 0) { - return; - } - this.update((draftState) => { - Object.values(draftState.subjects).forEach((subject) => { - Object.values(subject.permissions).forEach((permission) => { - const { caveats } = permission; - const targetCaveat = caveats === null || caveats === void 0 ? void 0 : caveats.find(({ type }) => type === targetCaveatType); - if (!targetCaveat) { - return; - } - // The mutator may modify the caveat value in place, and must always - // return a valid mutation result. - const mutatorResult = mutator(targetCaveat.value); - switch (mutatorResult.operation) { - case CaveatMutatorOperation.noop: - break; - case CaveatMutatorOperation.updateValue: - // Typecast: `Mutable` is used here to assign to a readonly - // property. `targetConstraint` should already be mutable because - // it's part of a draft, but for some reason it's not. We can't - // use the more-correct `Draft` type here either because it - // results in an error. - targetCaveat.value = - mutatorResult.value; - this.validateCaveat(targetCaveat, subject.origin, permission.parentCapability); - break; - case CaveatMutatorOperation.deleteCaveat: - this.deleteCaveat(permission, targetCaveatType, subject.origin, permission.parentCapability); - break; - case CaveatMutatorOperation.revokePermission: - this.deletePermission(draftState.subjects, subject.origin, permission.parentCapability); - break; - default: { - // This type check ensures that the switch statement is - // exhaustive. - const _exhaustiveCheck = mutatorResult; - throw new Error(`Unrecognized mutation result: "${_exhaustiveCheck.operation}"`); - } - } - }); - }); - }); - } - /** - * Removes the caveat of the specified type from the permission corresponding - * to the given subject origin and target name. - * - * Throws an error if no such permission or caveat exists. - * - * @template TargetName - The permission target name. Should be inferred. - * @template CaveatType - The valid caveat types for the permission. Should - * be inferred. - * @param origin - The origin of the subject. - * @param target - The target name of the permission. - * @param caveatType - The type of the caveat to remove. - */ - removeCaveat(origin, target, caveatType) { - this.update((draftState) => { - var _a; - const permission = (_a = draftState.subjects[origin]) === null || _a === void 0 ? void 0 : _a.permissions[target]; - if (!permission) { - throw new errors_1.PermissionDoesNotExistError(origin, target); - } - if (!permission.caveats) { - throw new errors_1.CaveatDoesNotExistError(origin, target, caveatType); - } - this.deleteCaveat(permission, caveatType, origin, target); - }); - } - /** - * Deletes the specified caveat from the specified permission. If no caveats - * remain after deletion, the permission's caveat property is set to `null`. - * The permission is validated after being modified. - * - * Throws an error if the permission does not have a caveat with the specified - * type. - * - * @param permission - The permission whose caveat to delete. - * @param caveatType - The type of the caveat to delete. - * @param origin - The origin the permission subject. - * @param target - The name of the permission target. - */ - deleteCaveat(permission, caveatType, origin, target) { - /* istanbul ignore if: not possible in our usage */ - if (!permission.caveats) { - throw new errors_1.CaveatDoesNotExistError(origin, target, caveatType); - } - const caveatIndex = permission.caveats.findIndex((existingCaveat) => existingCaveat.type === caveatType); - if (caveatIndex === -1) { - throw new errors_1.CaveatDoesNotExistError(origin, target, caveatType); - } - if (permission.caveats.length === 1) { - permission.caveats = null; - } - else { - permission.caveats.splice(caveatIndex, 1); - } - this.validateModifiedPermission(permission, origin, target); - } - /** - * Validates the specified modified permission. Should **always** be invoked - * on a permission after its caveats have been modified. - * - * Just like {@link PermissionController.validatePermission}, except that the - * corresponding target key and specification are retrieved first, and an - * error is thrown if the target key does not exist. - * - * @param permission - The modified permission to validate. - * @param origin - The origin associated with the permission. - * @param targetName - The target name name of the permission. - */ - validateModifiedPermission(permission, origin, targetName) { - const targetKey = this.getTargetKey(permission.parentCapability); - /* istanbul ignore if: this should be impossible */ - if (!targetKey) { - throw new Error(`Fatal: Existing permission target key "${targetKey}" has no specification.`); - } - this.validatePermission(this.getPermissionSpecification(targetKey), permission, origin, targetName); - } - /** - * Gets the key for the specified permission target. - * - * Used to support our namespaced permission target feature, which is used - * to implement namespaced restricted JSON-RPC methods. - * - * @param target - The requested permission target. - * @returns The internal key of the permission target. - */ - getTargetKey(target) { - if ((0, util_1.hasProperty)(this._permissionSpecifications, target)) { - return target; - } - const namespacedTargetsWithoutWildcard = {}; - for (const targetKey of Object.keys(this._permissionSpecifications)) { - const wildCardMatch = targetKey.match(/(.+)\*$/u); - if (wildCardMatch) { - namespacedTargetsWithoutWildcard[wildCardMatch[1]] = true; - } - } - // Check for potentially nested namespaces: - // Ex: wildzone_ - // Ex: eth_plugin_ - const segments = target.split('_'); - let targetKey = ''; - while (segments.length > 0 && - !(0, util_1.hasProperty)(this._permissionSpecifications, targetKey) && - !namespacedTargetsWithoutWildcard[targetKey]) { - targetKey += `${segments.shift()}_`; - } - if (namespacedTargetsWithoutWildcard[targetKey]) { - return `${targetKey}*`; - } - return undefined; - } - /** - * Grants _approved_ permissions to the specified subject. Every permission and - * caveat is stringently validated – including by calling every specification - * validator – and an error is thrown if any validation fails. - * - * ATTN: This method does **not** prompt the user for approval. - * - * @see {@link PermissionController.requestPermissions} For initiating a - * permissions request requiring user approval. - * @param options - Options bag. - * @param options.approvedPermissions - The requested permissions approved by - * the user. - * @param options.requestData - Permission request data. Passed to permission - * factory functions. - * @param options.preserveExistingPermissions - Whether to preserve the - * subject's existing permissions. - * @param options.subject - The subject to grant permissions to. - * @returns The granted permissions. - */ - grantPermissions({ approvedPermissions, requestData, preserveExistingPermissions = true, subject, }) { - const { origin } = subject; - if (!origin || typeof origin !== 'string') { - throw new errors_1.InvalidSubjectIdentifierError(origin); - } - const permissions = (preserveExistingPermissions - ? Object.assign({}, this.getPermissions(origin)) : {}); - for (const [requestedTarget, approvedPermission] of Object.entries(approvedPermissions)) { - const targetKey = this.getTargetKey(requestedTarget); - if (!targetKey) { - throw (0, errors_1.methodNotFound)(requestedTarget); - } - if (approvedPermission.parentCapability !== undefined && - requestedTarget !== approvedPermission.parentCapability) { - throw new errors_1.InvalidApprovedPermissionError(origin, requestedTarget, approvedPermission); - } - // The requested target must be a valid target name if we found its key. - // We reassign it to change its type. - const targetName = requestedTarget; - const specification = this.getPermissionSpecification(targetKey); - // The requested caveats are validated here. - const caveats = this.constructCaveats(origin, targetName, approvedPermission.caveats); - const permissionOptions = { - caveats, - invoker: origin, - target: targetName, - }; - let permission; - if (specification.factory) { - permission = specification.factory(permissionOptions, requestData); - // Full caveat and permission validation is performed here since the - // factory function can arbitrarily modify the entire permission object, - // including its caveats. - this.validatePermission(specification, permission, origin, targetName); - } - else { - permission = (0, Permission_1.constructPermission)(permissionOptions); - // We do not need to validate caveats in this case, because the plain - // permission constructor function does not modify the caveats, which - // were already validated by `constructCaveats` above. - this.validatePermission(specification, permission, origin, targetName, { - invokePermissionValidator: true, - performCaveatValidation: false, - }); - } - permissions[targetName] = permission; - } - this.setValidatedPermissions(origin, permissions); - return permissions; - } - /** - * Validates the specified permission by: - * - Ensuring that its `caveats` property is either `null` or a non-empty array. - * - Ensuring that it only includes caveats allowed by its specification. - * - Ensuring that it includes no duplicate caveats (by caveat type). - * - Validating each caveat object, if `performCaveatValidation` is `true`. - * - Calling the validator of its specification, if one exists and `invokePermissionValidator` is `true`. - * - * An error is thrown if validation fails. - * - * @param specification - The specification of the permission. - * @param permission - The permission to validate. - * @param origin - The origin associated with the permission. - * @param targetName - The target name of the permission. - * @param validationOptions - Validation options. - * @param validationOptions.invokePermissionValidator - Whether to invoke the - * permission's consumer-specified validator function, if any. - * @param validationOptions.performCaveatValidation - Whether to invoke - * {@link PermissionController.validateCaveat} on each of the permission's - * caveats. - */ - validatePermission(specification, permission, origin, targetName, { invokePermissionValidator, performCaveatValidation } = { - invokePermissionValidator: true, - performCaveatValidation: true, - }) { - const { allowedCaveats, validator } = specification; - if ((0, util_1.hasProperty)(permission, 'caveats')) { - const { caveats } = permission; - if (caveats !== null && !(Array.isArray(caveats) && caveats.length > 0)) { - throw new errors_1.InvalidCaveatsPropertyError(origin, targetName, caveats); - } - const seenCaveatTypes = new Set(); - caveats === null || caveats === void 0 ? void 0 : caveats.forEach((caveat) => { - if (performCaveatValidation) { - this.validateCaveat(caveat, origin, targetName); - } - if (!(allowedCaveats === null || allowedCaveats === void 0 ? void 0 : allowedCaveats.includes(caveat.type))) { - throw new errors_1.ForbiddenCaveatError(caveat.type, origin, targetName); - } - if (seenCaveatTypes.has(caveat.type)) { - throw new errors_1.DuplicateCaveatError(caveat.type, origin, targetName); - } - seenCaveatTypes.add(caveat.type); - }); - } - if (invokePermissionValidator && validator) { - validator(permission, origin, targetName); - } - } - /** - * Assigns the specified permissions to the subject with the given origin. - * Overwrites all existing permissions, and creates a subject entry if it - * doesn't already exist. - * - * ATTN: Assumes that the new permissions have been validated. - * - * @param origin - The origin of the grantee subject. - * @param permissions - The new permissions for the grantee subject. - */ - setValidatedPermissions(origin, permissions) { - this.update((draftState) => { - if (!draftState.subjects[origin]) { - draftState.subjects[origin] = { origin, permissions: {} }; - } - draftState.subjects[origin].permissions = (0, immer_1.castDraft)(permissions); - }); - } - /** - * Validates the requested caveats for the permission of the specified - * subject origin and target name and returns the validated caveat array. - * - * Throws an error if validation fails. - * - * @param origin - The origin of the permission subject. - * @param target - The permission target name. - * @param requestedCaveats - The requested caveats to construct. - * @returns The constructed caveats. - */ - constructCaveats(origin, target, requestedCaveats) { - const caveatArray = requestedCaveats === null || requestedCaveats === void 0 ? void 0 : requestedCaveats.map((requestedCaveat) => { - this.validateCaveat(requestedCaveat, origin, target); - // Reassign so that we have a fresh object. - const { type, value } = requestedCaveat; - return { type, value }; - }); - return caveatArray && (0, util_1.isNonEmptyArray)(caveatArray) - ? caveatArray - : undefined; - } - /** - * This methods validates that the specified caveat is an object with the - * expected properties and types. It also ensures that a caveat specification - * exists for the requested caveat type, and calls the specification - * validator, if it exists, on the caveat object. - * - * Throws an error if validation fails. - * - * @param caveat - The caveat object to validate. - * @param origin - The origin associated with the subject of the parent - * permission. - * @param target - The target name associated with the parent permission. - */ - validateCaveat(caveat, origin, target) { - var _a; - if (!(0, util_1.isPlainObject)(caveat)) { - throw new errors_1.InvalidCaveatError(caveat, origin, target); - } - if (Object.keys(caveat).length !== 2) { - throw new errors_1.InvalidCaveatFieldsError(caveat, origin, target); - } - if (typeof caveat.type !== 'string') { - throw new errors_1.InvalidCaveatTypeError(caveat, origin, target); - } - const specification = this.getCaveatSpecification(caveat.type); - if (!specification) { - throw new errors_1.UnrecognizedCaveatTypeError(caveat.type, origin, target); - } - if (!(0, util_1.hasProperty)(caveat, 'value') || caveat.value === undefined) { - throw new errors_1.CaveatMissingValueError(caveat, origin, target); - } - if (!(0, util_1.isValidJson)(caveat.value)) { - throw new errors_1.CaveatInvalidJsonError(caveat, origin, target); - } - // Typecast: TypeScript still believes that the caveat is a PlainObject. - (_a = specification.validator) === null || _a === void 0 ? void 0 : _a.call(specification, caveat, origin, target); - } - /** - * Initiates a permission request that requires user approval. This should - * always be used to grant additional permissions to a subject, unless user - * approval has been obtained through some other means. - * - * Permissions are validated at every step of the approval process, and this - * method will reject if validation fails. - * - * @see {@link ApprovalController} For the user approval logic. - * @see {@link PermissionController.acceptPermissionsRequest} For the method - * that _accepts_ the request and resolves the user approval promise. - * @see {@link PermissionController.rejectPermissionsRequest} For the method - * that _rejects_ the request and the user approval promise. - * @param subject - The grantee subject. - * @param requestedPermissions - The requested permissions. - * @param options - Additional options. - * @param options.id - The id of the permissions request. Defaults to a unique - * id. - * @param options.preserveExistingPermissions - Whether to preserve the - * subject's existing permissions. Defaults to `true`. - * @returns The granted permissions and request metadata. - */ - requestPermissions(subject, requestedPermissions, options = {}) { - return __awaiter(this, void 0, void 0, function* () { - const { origin } = subject; - const { id = (0, nanoid_1.nanoid)(), preserveExistingPermissions = true } = options; - this.validateRequestedPermissions(origin, requestedPermissions); - const metadata = { - id, - origin, - }; - const permissionsRequest = { - metadata, - permissions: requestedPermissions, - }; - const _a = yield this.requestUserApproval(permissionsRequest), { permissions: approvedPermissions } = _a, requestData = __rest(_a, ["permissions"]); - return [ - this.grantPermissions({ - subject, - approvedPermissions, - preserveExistingPermissions, - requestData, - }), - metadata, - ]; - }); - } - /** - * Validates requested permissions. Throws if validation fails. - * - * This method ensures that the requested permissions are a properly - * formatted {@link RequestedPermissions} object, and performs the same - * validation as {@link PermissionController.grantPermissions}, except that - * consumer-specified permission validator functions are not called, since - * they are only called on fully constructed, approved permissions that are - * otherwise completely valid. - * - * Unrecognzied properties on requested permissions are ignored. - * - * @param origin - The origin of the grantee subject. - * @param requestedPermissions - The requested permissions. - */ - validateRequestedPermissions(origin, requestedPermissions) { - if (!(0, util_1.isPlainObject)(requestedPermissions)) { - throw (0, errors_1.invalidParams)({ - message: `Requested permissions for origin "${origin}" is not a plain object.`, - data: { origin, requestedPermissions }, - }); - } - if (Object.keys(requestedPermissions).length === 0) { - throw (0, errors_1.invalidParams)({ - message: `Permissions request for origin "${origin}" contains no permissions.`, - data: { requestedPermissions }, - }); - } - for (const targetName of Object.keys(requestedPermissions)) { - const permission = requestedPermissions[targetName]; - const targetKey = this.getTargetKey(targetName); - if (!targetKey) { - throw (0, errors_1.methodNotFound)(targetName, { origin, requestedPermissions }); - } - if (!(0, util_1.isPlainObject)(permission) || - (permission.parentCapability !== undefined && - targetName !== permission.parentCapability)) { - throw (0, errors_1.invalidParams)({ - message: `Permissions request for origin "${origin}" contains invalid requested permission(s).`, - data: { origin, requestedPermissions }, - }); - } - // Here we validate the permission without invoking its validator, if any. - // The validator will be invoked after the permission has been approved. - this.validatePermission(this.getPermissionSpecification(targetKey), - // Typecast: The permission is still a "PlainObject" here. - permission, origin, targetName, { invokePermissionValidator: false, performCaveatValidation: true }); - } - } - /** - * Adds a request to the {@link ApprovalController} using the - * {@link AddApprovalRequest} action. Also validates the resulting approved - * permissions request, and throws an error if validation fails. - * - * @param permissionsRequest - The permissions request object. - * @returns The approved permissions request object. - */ - requestUserApproval(permissionsRequest) { - return __awaiter(this, void 0, void 0, function* () { - const { origin, id } = permissionsRequest.metadata; - const approvedRequest = yield this.messagingSystem.call('ApprovalController:addRequest', { - id, - origin, - requestData: permissionsRequest, - type: utils_1.MethodNames.requestPermissions, - }, true); - this.validateApprovedPermissions(approvedRequest, { id, origin }); - return approvedRequest; - }); - } - /** - * Validates an approved {@link PermissionsRequest} object. The approved - * request must have the required `metadata` and `permissions` properties, - * the `id` and `origin` of the `metadata` must match the original request - * metadata, and the requested permissions must be valid per - * {@link PermissionController.validateRequestedPermissions}. Any extra - * metadata properties are ignored. - * - * An error is thrown if validation fails. - * - * @param approvedRequest - The approved permissions request object. - * @param originalMetadata - The original request metadata. - */ - validateApprovedPermissions(approvedRequest, originalMetadata) { - const { id, origin } = originalMetadata; - if (!(0, util_1.isPlainObject)(approvedRequest) || - !(0, util_1.isPlainObject)(approvedRequest.metadata)) { - throw (0, errors_1.internalError)(`Approved permissions request for subject "${origin}" is invalid.`, { data: { approvedRequest } }); - } - const { metadata: { id: newId, origin: newOrigin }, permissions, } = approvedRequest; - if (newId !== id) { - throw (0, errors_1.internalError)(`Approved permissions request for subject "${origin}" mutated its id.`, { originalId: id, mutatedId: newId }); - } - if (newOrigin !== origin) { - throw (0, errors_1.internalError)(`Approved permissions request for subject "${origin}" mutated its origin.`, { originalOrigin: origin, mutatedOrigin: newOrigin }); - } - try { - this.validateRequestedPermissions(origin, permissions); - } - catch (error) { - if (error instanceof eth_rpc_errors_1.EthereumRpcError) { - // Re-throw as an internal error; we should never receive invalid approved - // permissions. - throw (0, errors_1.internalError)(`Invalid approved permissions request: ${error.message}`, error.data); - } - throw (0, errors_1.internalError)('Unrecognized error type', { error }); - } - } - /** - * Accepts a permissions request created by - * {@link PermissionController.requestPermissions}. - * - * @param request - The permissions request. - */ - acceptPermissionsRequest(request) { - return __awaiter(this, void 0, void 0, function* () { - const { id } = request.metadata; - if (!this.hasApprovalRequest({ id })) { - throw new errors_1.PermissionsRequestNotFoundError(id); - } - if (Object.keys(request.permissions).length === 0) { - this._rejectPermissionsRequest(id, (0, errors_1.invalidParams)({ - message: 'Must request at least one permission.', - })); - return; - } - try { - this.messagingSystem.call('ApprovalController:acceptRequest', id, request); - } - catch (error) { - // If accepting unexpectedly fails, reject the request and re-throw the - // error - this._rejectPermissionsRequest(id, error); - throw error; - } - }); - } - /** - * Rejects a permissions request created by - * {@link PermissionController.requestPermissions}. - * - * @param id - The id of the request to be rejected. - */ - rejectPermissionsRequest(id) { - return __awaiter(this, void 0, void 0, function* () { - if (!this.hasApprovalRequest({ id })) { - throw new errors_1.PermissionsRequestNotFoundError(id); - } - this._rejectPermissionsRequest(id, (0, errors_1.userRejectedRequest)()); - }); - } - /** - * Checks whether the {@link ApprovalController} has a particular permissions - * request. - * - * @see {@link PermissionController.acceptPermissionsRequest} and - * {@link PermissionController.rejectPermissionsRequest} for usage. - * @param options - The {@link HasApprovalRequest} options. - * @param options.id - The id of the approval request to check for. - * @returns Whether the specified request exists. - */ - hasApprovalRequest(options) { - return this.messagingSystem.call('ApprovalController:hasRequest', - // Typecast: For some reason, the type here expects all of the possible - // HasApprovalRequest options to be specified, when they're actually all - // optional. Passing just the id is definitely valid, so we just cast it. - options); - } - /** - * Rejects the permissions request with the specified id, with the specified - * error as the reason. This method is effectively a wrapper around a - * messenger call for the `ApprovalController:rejectRequest` action. - * - * @see {@link PermissionController.acceptPermissionsRequest} and - * {@link PermissionController.rejectPermissionsRequest} for usage. - * @param id - The id of the request to reject. - * @param error - The error associated with the rejection. - * @returns Nothing - */ - _rejectPermissionsRequest(id, error) { - return this.messagingSystem.call('ApprovalController:rejectRequest', id, error); - } - /** - * Gets the subject's endowments per the specified endowment permission. - * Throws if the subject does not have the required permission or if the - * permission is not an endowment permission. - * - * @param origin - The origin of the subject whose endowments to retrieve. - * @param targetName - The name of the endowment permission. This must be a - * valid permission target name. - * @param requestData - Additional data associated with the request, if any. - * Forwarded to the endowment getter function for the permission. - * @returns The endowments, if any. - */ - getEndowments(origin, targetName, requestData) { - return __awaiter(this, void 0, void 0, function* () { - if (!this.hasPermission(origin, targetName)) { - throw (0, errors_1.unauthorized)({ data: { origin, targetName } }); - } - return this.getTypedPermissionSpecification(Permission_1.PermissionType.Endowment, targetName, origin).endowmentGetter({ origin, requestData }); - }); - } - /** - * Executes a restricted method as the subject with the given origin. - * The specified params, if any, will be passed to the method implementation. - * - * ATTN: Great caution should be exercised in the use of this method. - * Methods that cause side effects or affect application state should - * be avoided. - * - * This method will first attempt to retrieve the requested restricted method - * implementation, throwing if it does not exist. The method will then be - * invoked as though the subject with the specified origin had invoked it with - * the specified parameters. This means that any existing caveats will be - * applied to the restricted method, and this method will throw if the - * restricted method or its caveat decorators throw. - * - * In addition, this method will throw if the subject does not have a - * permission for the specified restricted method. - * - * @param origin - The origin of the subject to execute the method on behalf - * of. - * @param targetName - The name of the method to execute. This must be a valid - * permission target name. - * @param params - The parameters to pass to the method implementation. - * @returns The result of the executed method. - */ - executeRestrictedMethod(origin, targetName, params) { - return __awaiter(this, void 0, void 0, function* () { - // Throws if the method does not exist - const methodImplementation = this.getRestrictedMethod(targetName, origin); - const result = yield this._executeRestrictedMethod(methodImplementation, { origin }, targetName, params); - if (result === undefined) { - throw new Error(`Internal request for method "${targetName}" as origin "${origin}" returned no result.`); - } - return result; - }); - } - /** - * An internal method used in the controller's `json-rpc-engine` middleware - * and {@link PermissionController.executeRestrictedMethod}. Calls the - * specified restricted method implementation after decorating it with the - * caveats of its permission. Throws if the subject does not have the - * requisite permission. - * - * ATTN: Parameter validation is the responsibility of the caller, or - * the restricted method implementation in the case of `params`. - * - * @see {@link PermissionController.executeRestrictedMethod} and - * {@link PermissionController.createPermissionMiddleware} for usage. - * @param methodImplementation - The implementation of the method to call. - * @param subject - Metadata about the subject that made the request. - * @param method - The method name - * @param params - Params needed for executing the restricted method - * @returns The result of the restricted method implementation - */ - _executeRestrictedMethod(methodImplementation, subject, method, params = []) { - const { origin } = subject; - const permission = this.getPermission(origin, method); - if (!permission) { - throw (0, errors_1.unauthorized)({ data: { origin, method } }); - } - return (0, Caveat_1.decorateWithCaveats)(methodImplementation, permission, this._caveatSpecifications)({ method, params, context: { origin } }); - } -} -exports.PermissionController = PermissionController; -//# sourceMappingURL=PermissionController.js.map \ No newline at end of file diff --git a/dist/permissions/PermissionController.js.map b/dist/permissions/PermissionController.js.map deleted file mode 100644 index a1ff067265..0000000000 --- a/dist/permissions/PermissionController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"PermissionController.js","sourceRoot":"","sources":["../../src/permissions/PermissionController.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-enable @typescript-eslint/no-unused-vars */\nimport { Mutable } from '@metamask/types';\nimport deepFreeze from 'deep-freeze-strict';\nimport { castDraft, Draft, Patch } from 'immer';\nimport { nanoid } from 'nanoid';\nimport { EthereumRpcError } from 'eth-rpc-errors';\nimport {\n AcceptRequest as AcceptApprovalRequest,\n AddApprovalRequest,\n HasApprovalRequest,\n RejectRequest as RejectApprovalRequest,\n} from '../approval/ApprovalController';\nimport { BaseController, Json, StateMetadata } from '../BaseControllerV2';\nimport { RestrictedControllerMessenger } from '../ControllerMessenger';\nimport {\n hasProperty,\n isNonEmptyArray,\n isPlainObject,\n isValidJson,\n NonEmptyArray,\n} from '../util';\nimport {\n CaveatConstraint,\n CaveatSpecificationConstraint,\n CaveatSpecificationMap,\n decorateWithCaveats,\n ExtractCaveat,\n ExtractCaveats,\n ExtractCaveatValue,\n} from './Caveat';\nimport {\n CaveatAlreadyExistsError,\n CaveatDoesNotExistError,\n CaveatInvalidJsonError,\n CaveatMissingValueError,\n DuplicateCaveatError,\n EndowmentPermissionDoesNotExistError,\n ForbiddenCaveatError,\n internalError,\n InvalidApprovedPermissionError,\n InvalidCaveatError,\n InvalidCaveatFieldsError,\n InvalidCaveatsPropertyError,\n InvalidCaveatTypeError,\n invalidParams,\n InvalidSubjectIdentifierError,\n methodNotFound,\n PermissionDoesNotExistError,\n PermissionsRequestNotFoundError,\n unauthorized,\n UnrecognizedCaveatTypeError,\n UnrecognizedSubjectError,\n userRejectedRequest,\n} from './errors';\nimport {\n constructPermission,\n EndowmentSpecificationConstraint,\n ExtractAllowedCaveatTypes,\n ExtractPermissionSpecification,\n findCaveat,\n hasSpecificationType,\n OriginString,\n PermissionConstraint,\n PermissionSpecificationConstraint,\n PermissionSpecificationMap,\n PermissionType,\n RequestedPermissions,\n RestrictedMethod,\n RestrictedMethodParameters,\n RestrictedMethodSpecificationConstraint,\n ValidPermission,\n ValidPermissionSpecification,\n} from './Permission';\nimport { getPermissionMiddlewareFactory } from './permission-middleware';\nimport { MethodNames } from './utils';\n\n/**\n * Metadata associated with {@link PermissionController} subjects.\n */\nexport type PermissionSubjectMetadata = {\n origin: OriginString;\n};\n\n/**\n * Metadata associated with permission requests.\n */\nexport type PermissionsRequestMetadata = PermissionSubjectMetadata & {\n id: string;\n};\n\n/**\n * Used for prompting the user about a proposed new permission.\n * Includes information about the grantee subject, requested permissions, and\n * any additional information added by the consumer.\n *\n * All properties except `permissions` are passed to any factories found for\n * the requested permissions.\n */\nexport type PermissionsRequest = {\n metadata: PermissionsRequestMetadata;\n permissions: RequestedPermissions;\n [key: string]: Json;\n};\n\n/**\n * The name of the {@link PermissionController}.\n */\nconst controllerName = 'PermissionController';\n\n/**\n * Permissions associated with a {@link PermissionController} subject.\n */\nexport type SubjectPermissions =\n Record;\n\n/**\n * Permissions and metadata associated with a {@link PermissionController}\n * subject.\n */\nexport type PermissionSubjectEntry<\n SubjectPermission extends PermissionConstraint,\n> = {\n origin: SubjectPermission['invoker'];\n permissions: SubjectPermissions;\n};\n\n/**\n * All subjects of a {@link PermissionController}.\n *\n * @template SubjectPermission - The permissions of the subject.\n */\nexport type PermissionControllerSubjects<\n SubjectPermission extends PermissionConstraint,\n> = Record<\n SubjectPermission['invoker'],\n PermissionSubjectEntry\n>;\n\n// TODO:TS4.4 Enable compiler flags to forbid unchecked member access\n/**\n * The state of a {@link PermissionController}.\n *\n * @template Permission - The controller's permission type union.\n */\nexport type PermissionControllerState =\n Permission extends PermissionConstraint\n ? {\n subjects: PermissionControllerSubjects;\n }\n : never;\n\n/**\n * Get the state metadata of the {@link PermissionController}.\n *\n * @template Permission - The controller's permission type union.\n * @returns The state metadata\n */\nfunction getStateMetadata() {\n return { subjects: { anonymous: true, persist: true } } as StateMetadata<\n PermissionControllerState\n >;\n}\n\n/**\n * Get the default state of the {@link PermissionController}.\n *\n * @template Permission - The controller's permission type union.\n * @returns The default state of the controller\n */\nfunction getDefaultState() {\n return { subjects: {} } as PermissionControllerState;\n}\n\n/**\n * Gets the state of the {@link PermissionController}.\n */\nexport type GetPermissionControllerState = {\n type: `${typeof controllerName}:getState`;\n handler: () => PermissionControllerState;\n};\n\n/**\n * Gets the names of all subjects from the {@link PermissionController}.\n */\nexport type GetSubjects = {\n type: `${typeof controllerName}:getSubjectNames`;\n handler: () => (keyof PermissionControllerSubjects)[];\n};\n\n/**\n * Gets the permissions for specified subject\n */\nexport type GetPermissions = {\n type: `${typeof controllerName}:getPermissions`;\n handler: GenericPermissionController['getPermissions'];\n};\n\n/**\n * Checks whether the specified subject has any permissions.\n */\nexport type HasPermissions = {\n type: `${typeof controllerName}:hasPermissions`;\n handler: GenericPermissionController['hasPermissions'];\n};\n\n/**\n * Checks whether the specified subject has a specific permission.\n */\nexport type HasPermission = {\n type: `${typeof controllerName}:hasPermission`;\n handler: GenericPermissionController['hasPermission'];\n};\n\n/**\n * Directly grants given permissions for a specificed origin without requesting user approval\n */\nexport type GrantPermissions = {\n type: `${typeof controllerName}:grantPermissions`;\n handler: GenericPermissionController['grantPermissions'];\n};\n\n/**\n * Requests given permissions for a specified origin\n */\nexport type RequestPermissions = {\n type: `${typeof controllerName}:requestPermissions`;\n handler: GenericPermissionController['requestPermissions'];\n};\n\n/**\n * Removes the specified permissions for each origin.\n */\nexport type RevokePermissions = {\n type: `${typeof controllerName}:revokePermissions`;\n handler: GenericPermissionController['revokePermissions'];\n};\n\n/**\n * Removes all permissions for a given origin\n */\nexport type RevokeAllPermissions = {\n type: `${typeof controllerName}:revokeAllPermissions`;\n handler: GenericPermissionController['revokeAllPermissions'];\n};\n\n/**\n * Revokes all permissions corresponding to the specified target for all subjects.\n * Does nothing if no subjects or no such permission exists.\n */\nexport type RevokePermissionForAllSubjects = {\n type: `${typeof controllerName}:revokePermissionForAllSubjects`;\n handler: GenericPermissionController['revokePermissionForAllSubjects'];\n};\n\n/**\n * Clears all permissions from the {@link PermissionController}.\n */\nexport type ClearPermissions = {\n type: `${typeof controllerName}:clearPermissions`;\n handler: () => void;\n};\n\n/**\n * Gets the endowments for the given subject and permission.\n */\nexport type GetEndowments = {\n type: `${typeof controllerName}:getEndowments`;\n handler: GenericPermissionController['getEndowments'];\n};\n\n/**\n * The {@link ControllerMessenger} actions of the {@link PermissionController}.\n */\nexport type PermissionControllerActions =\n | ClearPermissions\n | GetEndowments\n | GetPermissionControllerState\n | GetSubjects\n | GetPermissions\n | HasPermission\n | HasPermissions\n | GrantPermissions\n | RequestPermissions\n | RevokeAllPermissions\n | RevokePermissionForAllSubjects\n | RevokePermissions;\n\n/**\n * The generic state change event of the {@link PermissionController}.\n */\nexport type PermissionControllerStateChange = {\n type: `${typeof controllerName}:stateChange`;\n payload: [PermissionControllerState, Patch[]];\n};\n\n/**\n * The {@link ControllerMessenger} events of the {@link PermissionController}.\n *\n * The permission controller only emits its generic state change events.\n * Consumers should use selector subscriptions to subscribe to relevant\n * substate.\n */\nexport type PermissionControllerEvents = PermissionControllerStateChange;\n\n/**\n * The external {@link ControllerMessenger} actions available to the\n * {@link PermissionController}.\n */\ntype AllowedActions =\n | AddApprovalRequest\n | HasApprovalRequest\n | AcceptApprovalRequest\n | RejectApprovalRequest;\n\n/**\n * The messenger of the {@link PermissionController}.\n */\nexport type PermissionControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n PermissionControllerActions | AllowedActions,\n PermissionControllerEvents,\n AllowedActions['type'],\n never\n>;\n\n/**\n * A generic {@link PermissionController}.\n */\nexport type GenericPermissionController = PermissionController<\n PermissionSpecificationConstraint,\n CaveatSpecificationConstraint\n>;\n\n/**\n * Describes the possible results of a {@link CaveatMutator} function.\n */\nexport enum CaveatMutatorOperation {\n noop,\n updateValue,\n deleteCaveat,\n revokePermission,\n}\n\n/**\n * Given a caveat value, returns a {@link CaveatMutatorOperation} and, optionally,\n * a new caveat value.\n *\n * @see {@link PermissionController.updatePermissionsByCaveat} for more details.\n * @template Caveat - The caveat type for which this mutator is intended.\n * @param caveatValue - The existing value of the caveat being mutated.\n * @returns A tuple of the mutation result and, optionally, the new caveat\n * value.\n */\nexport type CaveatMutator = (\n caveatValue: TargetCaveat['value'],\n) => CaveatMutatorResult;\n\ntype CaveatMutatorResult =\n | Readonly<{\n operation: CaveatMutatorOperation.updateValue;\n value: CaveatConstraint['value'];\n }>\n | Readonly<{\n operation: Exclude<\n CaveatMutatorOperation,\n CaveatMutatorOperation.updateValue\n >;\n }>;\n\n/**\n * Extracts the permission(s) specified by the given permission and caveat\n * specifications.\n *\n * @template ControllerPermissionSpecification - The permission specification(s)\n * to extract from.\n * @template ControllerCaveatSpecification - The caveat specification(s) to\n * extract from. Necessary because {@link Permission} has a generic parameter\n * that describes the allowed caveats for the permission.\n */\nexport type ExtractPermission<\n ControllerPermissionSpecification extends PermissionSpecificationConstraint,\n ControllerCaveatSpecification extends CaveatSpecificationConstraint,\n> = ControllerPermissionSpecification extends ValidPermissionSpecification\n ? ValidPermission<\n ControllerPermissionSpecification['targetKey'],\n ExtractCaveats\n >\n : never;\n\n/**\n * Extracts the restricted method permission(s) specified by the given\n * permission and caveat specifications.\n *\n * @template ControllerPermissionSpecification - The permission specification(s)\n * to extract from.\n * @template ControllerCaveatSpecification - The caveat specification(s) to\n * extract from. Necessary because {@link Permission} has a generic parameter\n * that describes the allowed caveats for the permission.\n */\nexport type ExtractRestrictedMethodPermission<\n ControllerPermissionSpecification extends PermissionSpecificationConstraint,\n ControllerCaveatSpecification extends CaveatSpecificationConstraint,\n> = ExtractPermission<\n Extract<\n ControllerPermissionSpecification,\n RestrictedMethodSpecificationConstraint\n >,\n ControllerCaveatSpecification\n>;\n\n/**\n * Extracts the endowment permission(s) specified by the given permission and\n * caveat specifications.\n *\n * @template ControllerPermissionSpecification - The permission specification(s)\n * to extract from.\n * @template ControllerCaveatSpecification - The caveat specification(s) to\n * extract from. Necessary because {@link Permission} has a generic parameter\n * that describes the allowed caveats for the permission.\n */\nexport type ExtractEndowmentPermission<\n ControllerPermissionSpecification extends PermissionSpecificationConstraint,\n ControllerCaveatSpecification extends CaveatSpecificationConstraint,\n> = ExtractPermission<\n Extract,\n ControllerCaveatSpecification\n>;\n\n/**\n * Options for the {@link PermissionController} constructor.\n *\n * @template ControllerPermissionSpecification - A union of the types of all\n * permission specifications available to the controller. Any referenced caveats\n * must be included in the controller's caveat specifications.\n * @template ControllerCaveatSpecification - A union of the types of all\n * caveat specifications available to the controller.\n */\nexport type PermissionControllerOptions<\n ControllerPermissionSpecification extends PermissionSpecificationConstraint,\n ControllerCaveatSpecification extends CaveatSpecificationConstraint,\n> = {\n messenger: PermissionControllerMessenger;\n caveatSpecifications: CaveatSpecificationMap;\n permissionSpecifications: PermissionSpecificationMap;\n unrestrictedMethods: string[];\n state?: Partial<\n PermissionControllerState<\n ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >\n >\n >;\n};\n\n/**\n * The permission controller. See the README for details.\n *\n * Assumes the existence of an {@link ApprovalController} reachable via the\n * {@link ControllerMessenger}.\n *\n * @template ControllerPermissionSpecification - A union of the types of all\n * permission specifications available to the controller. Any referenced caveats\n * must be included in the controller's caveat specifications.\n * @template ControllerCaveatSpecification - A union of the types of all\n * caveat specifications available to the controller.\n */\nexport class PermissionController<\n ControllerPermissionSpecification extends PermissionSpecificationConstraint,\n ControllerCaveatSpecification extends CaveatSpecificationConstraint,\n> extends BaseController<\n typeof controllerName,\n PermissionControllerState<\n ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >\n >,\n PermissionControllerMessenger\n> {\n private readonly _caveatSpecifications: Readonly<\n CaveatSpecificationMap\n >;\n\n private readonly _permissionSpecifications: Readonly<\n PermissionSpecificationMap\n >;\n\n private readonly _unrestrictedMethods: ReadonlySet;\n\n /**\n * The names of all JSON-RPC methods that will be ignored by the controller.\n *\n * @returns The names of all unrestricted JSON-RPC methods\n */\n public get unrestrictedMethods(): ReadonlySet {\n return this._unrestrictedMethods;\n }\n\n /**\n * Returns a `json-rpc-engine` middleware function factory, so that the rules\n * described by the state of this controller can be applied to incoming\n * JSON-RPC requests.\n *\n * The middleware **must** be added in the correct place in the middleware\n * stack in order for it to work. See the README for an example.\n */\n public createPermissionMiddleware: ReturnType<\n typeof getPermissionMiddlewareFactory\n >;\n\n /**\n * Constructs the PermissionController.\n *\n * @param options - Permission controller options.\n * @param options.caveatSpecifications - The specifications of all caveats\n * available to the controller. See {@link CaveatSpecificationMap} and the\n * documentation for more details.\n * @param options.permissionSpecifications - The specifications of all\n * permissions available to the controller. See\n * {@link PermissionSpecificationMap} and the README for more details.\n * @param options.unrestrictedMethods - The callable names of all JSON-RPC\n * methods ignored by the new controller.\n * @param options.messenger - The controller messenger. See\n * {@link BaseController} for more information.\n * @param options.state - Existing state to hydrate the controller with at\n * initialization.\n */\n constructor(\n options: PermissionControllerOptions<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >,\n ) {\n const {\n caveatSpecifications,\n permissionSpecifications,\n unrestrictedMethods,\n messenger,\n state = {},\n } = options;\n\n super({\n name: controllerName,\n metadata:\n getStateMetadata<\n ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >\n >(),\n messenger,\n state: {\n ...getDefaultState<\n ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >\n >(),\n ...state,\n },\n });\n\n this._unrestrictedMethods = new Set(unrestrictedMethods);\n this._caveatSpecifications = deepFreeze({ ...caveatSpecifications });\n\n this.validatePermissionSpecifications(\n permissionSpecifications,\n this._caveatSpecifications,\n );\n\n this._permissionSpecifications = deepFreeze({\n ...permissionSpecifications,\n });\n\n this.registerMessageHandlers();\n this.createPermissionMiddleware = getPermissionMiddlewareFactory({\n executeRestrictedMethod: this._executeRestrictedMethod.bind(this),\n getRestrictedMethod: this.getRestrictedMethod.bind(this),\n isUnrestrictedMethod: this.unrestrictedMethods.has.bind(\n this.unrestrictedMethods,\n ),\n });\n }\n\n /**\n * Gets a permission specification.\n *\n * @param targetKey - The target key of the permission specification to get.\n * @returns The permission specification with the specified target key.\n */\n private getPermissionSpecification<\n TargetKey extends ControllerPermissionSpecification['targetKey'],\n >(\n targetKey: TargetKey,\n ): ExtractPermissionSpecification<\n ControllerPermissionSpecification,\n TargetKey\n > {\n return this._permissionSpecifications[targetKey];\n }\n\n /**\n * Gets a caveat specification.\n *\n * @param caveatType - The type of the caveat specification to get.\n * @returns The caveat specification with the specified type.\n */\n private getCaveatSpecification<\n CaveatType extends ControllerCaveatSpecification['type'],\n >(caveatType: CaveatType) {\n return this._caveatSpecifications[caveatType];\n }\n\n /**\n * Constructor helper for validating permission specifications. This is\n * intended to prevent the use of invalid target keys which, while impossible\n * to add in TypeScript, could rather easily occur in plain JavaScript.\n *\n * Throws an error if validation fails.\n *\n * @param permissionSpecifications - The permission specifications passed to\n * this controller's constructor.\n * @param caveatSpecifications - The caveat specifications passed to this\n * controller.\n */\n private validatePermissionSpecifications(\n permissionSpecifications: PermissionSpecificationMap,\n caveatSpecifications: CaveatSpecificationMap,\n ) {\n Object.entries(\n permissionSpecifications,\n ).forEach(\n ([\n targetKey,\n { permissionType, targetKey: innerTargetKey, allowedCaveats },\n ]) => {\n if (!permissionType || !hasProperty(PermissionType, permissionType)) {\n throw new Error(`Invalid permission type: \"${permissionType}\"`);\n }\n\n // Check if the target key is the empty string, ends with \"_\", or ends\n // with \"*\" but not \"_*\"\n if (!targetKey || /_$/u.test(targetKey) || /[^_]\\*$/u.test(targetKey)) {\n throw new Error(`Invalid permission target key: \"${targetKey}\"`);\n }\n\n if (targetKey !== innerTargetKey) {\n throw new Error(\n `Invalid permission specification: key \"${targetKey}\" must match specification.target value \"${innerTargetKey}\".`,\n );\n }\n\n if (allowedCaveats) {\n allowedCaveats.forEach((caveatType) => {\n if (!hasProperty(caveatSpecifications, caveatType)) {\n throw new UnrecognizedCaveatTypeError(caveatType);\n }\n });\n }\n },\n );\n }\n\n /**\n * Constructor helper for registering the controller's messaging system\n * actions.\n */\n private registerMessageHandlers(): void {\n this.messagingSystem.registerActionHandler(\n `${controllerName}:clearPermissions` as const,\n () => this.clearState(),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:getEndowments` as const,\n (origin: string, targetName: string, requestData?: unknown) =>\n this.getEndowments(origin, targetName, requestData),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:getSubjectNames` as const,\n () => this.getSubjectNames(),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:getPermissions` as const,\n (origin: OriginString) => this.getPermissions(origin),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:hasPermission` as const,\n (origin: OriginString, targetName: string) =>\n this.hasPermission(origin, targetName),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:hasPermissions` as const,\n (origin: OriginString) => this.hasPermissions(origin),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:grantPermissions` as const,\n this.grantPermissions.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:requestPermissions` as const,\n (subject: PermissionSubjectMetadata, permissions: RequestedPermissions) =>\n this.requestPermissions(subject, permissions),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:revokeAllPermissions` as const,\n (origin: OriginString) => this.revokeAllPermissions(origin),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:revokePermissionForAllSubjects` as const,\n (\n target: ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n ) => this.revokePermissionForAllSubjects(target),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:revokePermissions` as const,\n this.revokePermissions.bind(this),\n );\n }\n\n /**\n * Clears the state of the controller.\n */\n clearState(): void {\n this.update((_draftState) => {\n return {\n ...getDefaultState<\n ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >\n >(),\n };\n });\n }\n\n /**\n * Gets the permission specification corresponding to the given permission\n * type and target name. Throws an error if the target name does not\n * correspond to a permission, or if the specification is not of the\n * given permission type.\n *\n * @template Type - The type of the permission specification to get.\n * @param permissionType - The type of the permission specification to get.\n * @param targetName - The name of the permission whose specification to get.\n * @param requestingOrigin - The origin of the requesting subject, if any.\n * Will be added to any thrown errors.\n * @returns The specification object corresponding to the given type and\n * target name.\n */\n private getTypedPermissionSpecification(\n permissionType: Type,\n targetName: string,\n requestingOrigin?: string,\n ): ControllerPermissionSpecification & { permissionType: Type } {\n const failureError =\n permissionType === PermissionType.RestrictedMethod\n ? methodNotFound(\n targetName,\n requestingOrigin ? { origin: requestingOrigin } : undefined,\n )\n : new EndowmentPermissionDoesNotExistError(\n targetName,\n requestingOrigin,\n );\n\n const targetKey = this.getTargetKey(targetName);\n if (!targetKey) {\n throw failureError;\n }\n\n const specification = this.getPermissionSpecification(targetKey);\n if (!hasSpecificationType(specification, permissionType)) {\n throw failureError;\n }\n\n return specification;\n }\n\n /**\n * Gets the implementation of the specified restricted method.\n *\n * A JSON-RPC error is thrown if the method does not exist.\n *\n * @see {@link PermissionController.executeRestrictedMethod} and\n * {@link PermissionController.createPermissionMiddleware} for internal usage.\n * @param method - The name of the restricted method.\n * @param origin - The origin associated with the request for the restricted\n * method, if any.\n * @returns The restricted method implementation.\n */\n getRestrictedMethod(\n method: string,\n origin?: string,\n ): RestrictedMethod {\n return this.getTypedPermissionSpecification(\n PermissionType.RestrictedMethod,\n method,\n origin,\n ).methodImplementation;\n }\n\n /**\n * Gets a list of all origins of subjects.\n *\n * @returns The origins (i.e. IDs) of all subjects.\n */\n getSubjectNames(): OriginString[] {\n return Object.keys(this.state.subjects);\n }\n\n /**\n * Gets the permission for the specified target of the subject corresponding\n * to the specified origin.\n *\n * @param origin - The origin of the subject.\n * @param targetName - The method name as invoked by a third party (i.e., not\n * a method key).\n * @returns The permission if it exists, or undefined otherwise.\n */\n getPermission<\n SubjectPermission extends ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >,\n >(\n origin: OriginString,\n targetName: SubjectPermission['parentCapability'],\n ): SubjectPermission | undefined {\n return this.state.subjects[origin]?.permissions[targetName] as\n | SubjectPermission\n | undefined;\n }\n\n /**\n * Gets all permissions for the specified subject, if any.\n *\n * @param origin - The origin of the subject.\n * @returns The permissions of the subject, if any.\n */\n getPermissions(\n origin: OriginString,\n ):\n | SubjectPermissions<\n ValidPermission>\n >\n | undefined {\n return this.state.subjects[origin]?.permissions;\n }\n\n /**\n * Checks whether the subject with the specified origin has the specified\n * permission.\n *\n * @param origin - The origin of the subject.\n * @param target - The target name of the permission.\n * @returns Whether the subject has the permission.\n */\n hasPermission(\n origin: OriginString,\n target: ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n ): boolean {\n return Boolean(this.getPermission(origin, target));\n }\n\n /**\n * Checks whether the subject with the specified origin has any permissions.\n * Use this if you want to know if a subject \"exists\".\n *\n * @param origin - The origin of the subject to check.\n * @returns Whether the subject has any permissions.\n */\n hasPermissions(origin: OriginString): boolean {\n return Boolean(this.state.subjects[origin]);\n }\n\n /**\n * Revokes all permissions from the specified origin.\n *\n * Throws an error of the origin has no permissions.\n *\n * @param origin - The origin whose permissions to revoke.\n */\n revokeAllPermissions(origin: OriginString): void {\n this.update((draftState) => {\n if (!draftState.subjects[origin]) {\n throw new UnrecognizedSubjectError(origin);\n }\n delete draftState.subjects[origin];\n });\n }\n\n /**\n * Revokes the specified permission from the subject with the specified\n * origin.\n *\n * Throws an error if the subject or the permission does not exist.\n *\n * @param origin - The origin of the subject whose permission to revoke.\n * @param target - The target name of the permission to revoke.\n */\n revokePermission(\n origin: OriginString,\n target: ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n ): void {\n this.revokePermissions({ [origin]: [target] });\n }\n\n /**\n * Revokes the specified permissions from the specified subjects.\n *\n * Throws an error if any of the subjects or permissions do not exist.\n *\n * @param subjectsAndPermissions - An object mapping subject origins\n * to arrays of permission target names to revoke.\n */\n revokePermissions(\n subjectsAndPermissions: Record<\n OriginString,\n NonEmptyArray<\n ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability']\n >\n >,\n ): void {\n this.update((draftState) => {\n Object.keys(subjectsAndPermissions).forEach((origin) => {\n if (!hasProperty(draftState.subjects, origin)) {\n throw new UnrecognizedSubjectError(origin);\n }\n\n subjectsAndPermissions[origin].forEach((target) => {\n const { permissions } = draftState.subjects[origin];\n if (!hasProperty(permissions as Record, target)) {\n throw new PermissionDoesNotExistError(origin, target);\n }\n\n this.deletePermission(draftState.subjects, origin, target);\n });\n });\n });\n }\n\n /**\n * Revokes all permissions corresponding to the specified target for all subjects.\n * Does nothing if no subjects or no such permission exists.\n *\n * @param target - The name of the target to revoke all permissions for.\n */\n revokePermissionForAllSubjects(\n target: ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n ): void {\n if (this.getSubjectNames().length === 0) {\n return;\n }\n\n this.update((draftState) => {\n Object.entries(draftState.subjects).forEach(([origin, subject]) => {\n const { permissions } = subject;\n\n if (hasProperty(permissions as Record, target)) {\n this.deletePermission(draftState.subjects, origin, target);\n }\n });\n });\n }\n\n /**\n * Deletes the permission identified by the given origin and target. If the\n * permission is the single remaining permission of its subject, the subject\n * is also deleted.\n *\n * @param subjects - The draft permission controller subjects.\n * @param origin - The origin of the subject associated with the permission\n * to delete.\n * @param target - The target name of the permission to delete.\n */\n private deletePermission(\n subjects: Draft>,\n origin: OriginString,\n target: ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n ): void {\n const { permissions } = subjects[origin];\n if (Object.keys(permissions).length > 1) {\n delete permissions[target];\n } else {\n delete subjects[origin];\n }\n }\n\n /**\n * Checks whether the permission of the subject corresponding to the given\n * origin has a caveat of the specified type.\n *\n * Throws an error if the subject does not have a permission with the\n * specified target name.\n *\n * @template TargetName - The permission target name. Should be inferred.\n * @template CaveatType - The valid caveat types for the permission. Should\n * be inferred.\n * @param origin - The origin of the subject.\n * @param target - The target name of the permission.\n * @param caveatType - The type of the caveat to check for.\n * @returns Whether the permission has the specified caveat.\n */\n hasCaveat<\n TargetName extends ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n CaveatType extends ExtractAllowedCaveatTypes,\n >(origin: OriginString, target: TargetName, caveatType: CaveatType): boolean {\n return Boolean(this.getCaveat(origin, target, caveatType));\n }\n\n /**\n * Gets the caveat of the specified type, if any, for the permission of\n * the subject corresponding to the given origin.\n *\n * Throws an error if the subject does not have a permission with the\n * specified target name.\n *\n * @template TargetName - The permission target name. Should be inferred.\n * @template CaveatType - The valid caveat types for the permission. Should\n * be inferred.\n * @param origin - The origin of the subject.\n * @param target - The target name of the permission.\n * @param caveatType - The type of the caveat to get.\n * @returns The caveat, or `undefined` if no such caveat exists.\n */\n getCaveat<\n TargetName extends ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n CaveatType extends ExtractAllowedCaveatTypes,\n >(\n origin: OriginString,\n target: TargetName,\n caveatType: CaveatType,\n ): ExtractCaveat | undefined {\n const permission = this.getPermission(origin, target);\n if (!permission) {\n throw new PermissionDoesNotExistError(origin, target);\n }\n\n return findCaveat(permission, caveatType) as\n | ExtractCaveat\n | undefined;\n }\n\n /**\n * Adds a caveat of the specified type, with the specified caveat value, to\n * the permission corresponding to the given subject origin and permission\n * target.\n *\n * For modifying existing caveats, use\n * {@link PermissionController.updateCaveat}.\n *\n * Throws an error if no such permission exists, or if the caveat already\n * exists.\n *\n * @template TargetName - The permission target name. Should be inferred.\n * @template CaveatType - The valid caveat types for the permission. Should\n * be inferred.\n * @param origin - The origin of the subject.\n * @param target - The target name of the permission.\n * @param caveatType - The type of the caveat to add.\n * @param caveatValue - The value of the caveat to add.\n */\n addCaveat<\n TargetName extends ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n CaveatType extends ExtractAllowedCaveatTypes,\n >(\n origin: OriginString,\n target: TargetName,\n caveatType: CaveatType,\n caveatValue: ExtractCaveatValue,\n ): void {\n if (this.hasCaveat(origin, target, caveatType)) {\n throw new CaveatAlreadyExistsError(origin, target, caveatType);\n }\n\n this.setCaveat(origin, target, caveatType, caveatValue);\n }\n\n /**\n * Updates the value of the caveat of the specified type belonging to the\n * permission corresponding to the given subject origin and permission\n * target.\n *\n * For adding new caveats, use\n * {@link PermissionController.addCaveat}.\n *\n * Throws an error if no such permission or caveat exists.\n *\n * @template TargetName - The permission target name. Should be inferred.\n * @template CaveatType - The valid caveat types for the permission. Should\n * be inferred.\n * @param origin - The origin of the subject.\n * @param target - The target name of the permission.\n * @param caveatType - The type of the caveat to update.\n * @param caveatValue - The new value of the caveat.\n */\n updateCaveat<\n TargetName extends ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n CaveatType extends ExtractAllowedCaveatTypes,\n CaveatValue extends ExtractCaveatValue<\n ControllerCaveatSpecification,\n CaveatType\n >,\n >(\n origin: OriginString,\n target: TargetName,\n caveatType: CaveatType,\n caveatValue: CaveatValue,\n ): void {\n if (!this.hasCaveat(origin, target, caveatType)) {\n throw new CaveatDoesNotExistError(origin, target, caveatType);\n }\n\n this.setCaveat(origin, target, caveatType, caveatValue);\n }\n\n /**\n * Sets the specified caveat on the specified permission. Overwrites existing\n * caveats of the same type in-place (preserving array order), and adds the\n * caveat to the end of the array otherwise.\n *\n * Throws an error if the permission does not exist or fails to validate after\n * its caveats have been modified.\n *\n * @see {@link PermissionController.addCaveat}\n * @see {@link PermissionController.updateCaveat}\n * @template TargetName - The permission target name. Should be inferred.\n * @template CaveatType - The valid caveat types for the permission. Should\n * be inferred.\n * @param origin - The origin of the subject.\n * @param target - The target name of the permission.\n * @param caveatType - The type of the caveat to set.\n * @param caveatValue - The value of the caveat to set.\n */\n private setCaveat<\n TargetName extends ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n CaveatType extends ExtractAllowedCaveatTypes,\n >(\n origin: OriginString,\n target: TargetName,\n caveatType: CaveatType,\n caveatValue: ExtractCaveatValue,\n ): void {\n this.update((draftState) => {\n const subject = draftState.subjects[origin];\n\n // Unreachable because `hasCaveat` is always called before this, and it\n // throws if permissions are missing. TypeScript needs this, however.\n /* istanbul ignore if */\n if (!subject) {\n throw new UnrecognizedSubjectError(origin);\n }\n\n const permission = subject.permissions[target];\n\n /* istanbul ignore if: practically impossible, but TypeScript wants it */\n if (!permission) {\n throw new PermissionDoesNotExistError(origin, target);\n }\n\n const caveat = {\n type: caveatType,\n value: caveatValue,\n };\n this.validateCaveat(caveat, origin, target);\n\n if (permission.caveats) {\n const caveatIndex = permission.caveats.findIndex(\n (existingCaveat) => existingCaveat.type === caveat.type,\n );\n\n if (caveatIndex === -1) {\n permission.caveats.push(caveat);\n } else {\n permission.caveats.splice(caveatIndex, 1, caveat);\n }\n } else {\n // Typecast: At this point, we don't know if the specific permission\n // is allowed to have caveats, but it should be impossible to call\n // this method for a permission that may not have any caveats.\n // If all else fails, the permission validator is also called.\n permission.caveats = [caveat] as any;\n }\n\n this.validateModifiedPermission(permission, origin, target);\n });\n }\n\n /**\n * Updates all caveats with the specified type for all subjects and\n * permissions by applying the specified mutator function to them.\n *\n * ATTN: Permissions can be revoked entirely by the action of this method,\n * read on for details.\n *\n * Caveat mutators are functions that receive a caveat value and return a\n * tuple consisting of a {@link CaveatMutatorOperation} and, optionally, a new\n * value to update the existing caveat with.\n *\n * For each caveat, depending on the mutator result, this method will:\n * - Do nothing ({@link CaveatMutatorOperation.noop})\n * - Update the value of the caveat ({@link CaveatMutatorOperation.updateValue}). The caveat specification validator, if any, will be called after updating the value.\n * - Delete the caveat ({@link CaveatMutatorOperation.deleteCaveat}). The permission specification validator, if any, will be called after deleting the caveat.\n * - Revoke the parent permission ({@link CaveatMutatorOperation.revokePermission})\n *\n * This method throws if the validation of any caveat or permission fails.\n *\n * @param targetCaveatType - The type of the caveats to update.\n * @param mutator - The mutator function which will be applied to all caveat\n * values.\n */\n updatePermissionsByCaveat<\n CaveatType extends ExtractCaveats['type'],\n TargetCaveat extends ExtractCaveat<\n ControllerCaveatSpecification,\n CaveatType\n >,\n >(targetCaveatType: CaveatType, mutator: CaveatMutator): void {\n if (Object.keys(this.state.subjects).length === 0) {\n return;\n }\n\n this.update((draftState) => {\n Object.values(draftState.subjects).forEach((subject) => {\n Object.values(subject.permissions).forEach((permission) => {\n const { caveats } = permission;\n const targetCaveat = caveats?.find(\n ({ type }) => type === targetCaveatType,\n );\n if (!targetCaveat) {\n return;\n }\n\n // The mutator may modify the caveat value in place, and must always\n // return a valid mutation result.\n const mutatorResult = mutator(targetCaveat.value);\n switch (mutatorResult.operation) {\n case CaveatMutatorOperation.noop:\n break;\n\n case CaveatMutatorOperation.updateValue:\n // Typecast: `Mutable` is used here to assign to a readonly\n // property. `targetConstraint` should already be mutable because\n // it's part of a draft, but for some reason it's not. We can't\n // use the more-correct `Draft` type here either because it\n // results in an error.\n (targetCaveat as Mutable).value =\n mutatorResult.value;\n\n this.validateCaveat(\n targetCaveat,\n subject.origin,\n permission.parentCapability,\n );\n break;\n\n case CaveatMutatorOperation.deleteCaveat:\n this.deleteCaveat(\n permission,\n targetCaveatType,\n subject.origin,\n permission.parentCapability,\n );\n break;\n\n case CaveatMutatorOperation.revokePermission:\n this.deletePermission(\n draftState.subjects,\n subject.origin,\n permission.parentCapability,\n );\n break;\n\n default: {\n // This type check ensures that the switch statement is\n // exhaustive.\n const _exhaustiveCheck: never = mutatorResult;\n throw new Error(\n `Unrecognized mutation result: \"${\n (_exhaustiveCheck as any).operation\n }\"`,\n );\n }\n }\n });\n });\n });\n }\n\n /**\n * Removes the caveat of the specified type from the permission corresponding\n * to the given subject origin and target name.\n *\n * Throws an error if no such permission or caveat exists.\n *\n * @template TargetName - The permission target name. Should be inferred.\n * @template CaveatType - The valid caveat types for the permission. Should\n * be inferred.\n * @param origin - The origin of the subject.\n * @param target - The target name of the permission.\n * @param caveatType - The type of the caveat to remove.\n */\n removeCaveat<\n TargetName extends ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n CaveatType extends ExtractAllowedCaveatTypes,\n >(origin: OriginString, target: TargetName, caveatType: CaveatType): void {\n this.update((draftState) => {\n const permission = draftState.subjects[origin]?.permissions[target];\n if (!permission) {\n throw new PermissionDoesNotExistError(origin, target);\n }\n\n if (!permission.caveats) {\n throw new CaveatDoesNotExistError(origin, target, caveatType);\n }\n\n this.deleteCaveat(permission, caveatType, origin, target);\n });\n }\n\n /**\n * Deletes the specified caveat from the specified permission. If no caveats\n * remain after deletion, the permission's caveat property is set to `null`.\n * The permission is validated after being modified.\n *\n * Throws an error if the permission does not have a caveat with the specified\n * type.\n *\n * @param permission - The permission whose caveat to delete.\n * @param caveatType - The type of the caveat to delete.\n * @param origin - The origin the permission subject.\n * @param target - The name of the permission target.\n */\n private deleteCaveat<\n TargetName extends ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n CaveatType extends ExtractCaveats['type'],\n >(\n permission: Draft,\n caveatType: CaveatType,\n origin: OriginString,\n target: TargetName,\n ): void {\n /* istanbul ignore if: not possible in our usage */\n if (!permission.caveats) {\n throw new CaveatDoesNotExistError(origin, target, caveatType);\n }\n\n const caveatIndex = permission.caveats.findIndex(\n (existingCaveat) => existingCaveat.type === caveatType,\n );\n\n if (caveatIndex === -1) {\n throw new CaveatDoesNotExistError(origin, target, caveatType);\n }\n\n if (permission.caveats.length === 1) {\n permission.caveats = null;\n } else {\n permission.caveats.splice(caveatIndex, 1);\n }\n\n this.validateModifiedPermission(permission, origin, target);\n }\n\n /**\n * Validates the specified modified permission. Should **always** be invoked\n * on a permission after its caveats have been modified.\n *\n * Just like {@link PermissionController.validatePermission}, except that the\n * corresponding target key and specification are retrieved first, and an\n * error is thrown if the target key does not exist.\n *\n * @param permission - The modified permission to validate.\n * @param origin - The origin associated with the permission.\n * @param targetName - The target name name of the permission.\n */\n private validateModifiedPermission(\n permission: Draft,\n origin: OriginString,\n targetName: ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n ): void {\n const targetKey = this.getTargetKey(permission.parentCapability);\n /* istanbul ignore if: this should be impossible */\n if (!targetKey) {\n throw new Error(\n `Fatal: Existing permission target key \"${targetKey}\" has no specification.`,\n );\n }\n\n this.validatePermission(\n this.getPermissionSpecification(targetKey),\n permission as PermissionConstraint,\n origin,\n targetName,\n );\n }\n\n /**\n * Gets the key for the specified permission target.\n *\n * Used to support our namespaced permission target feature, which is used\n * to implement namespaced restricted JSON-RPC methods.\n *\n * @param target - The requested permission target.\n * @returns The internal key of the permission target.\n */\n private getTargetKey(\n target: string,\n ): ControllerPermissionSpecification['targetKey'] | undefined {\n if (hasProperty(this._permissionSpecifications, target)) {\n return target;\n }\n\n const namespacedTargetsWithoutWildcard: Record = {};\n for (const targetKey of Object.keys(this._permissionSpecifications)) {\n const wildCardMatch = targetKey.match(/(.+)\\*$/u);\n if (wildCardMatch) {\n namespacedTargetsWithoutWildcard[wildCardMatch[1]] = true;\n }\n }\n\n // Check for potentially nested namespaces:\n // Ex: wildzone_\n // Ex: eth_plugin_\n const segments = target.split('_');\n let targetKey = '';\n\n while (\n segments.length > 0 &&\n !hasProperty(this._permissionSpecifications, targetKey) &&\n !namespacedTargetsWithoutWildcard[targetKey]\n ) {\n targetKey += `${segments.shift()}_`;\n }\n\n if (namespacedTargetsWithoutWildcard[targetKey]) {\n return `${targetKey}*`;\n }\n\n return undefined;\n }\n\n /**\n * Grants _approved_ permissions to the specified subject. Every permission and\n * caveat is stringently validated – including by calling every specification\n * validator – and an error is thrown if any validation fails.\n *\n * ATTN: This method does **not** prompt the user for approval.\n *\n * @see {@link PermissionController.requestPermissions} For initiating a\n * permissions request requiring user approval.\n * @param options - Options bag.\n * @param options.approvedPermissions - The requested permissions approved by\n * the user.\n * @param options.requestData - Permission request data. Passed to permission\n * factory functions.\n * @param options.preserveExistingPermissions - Whether to preserve the\n * subject's existing permissions.\n * @param options.subject - The subject to grant permissions to.\n * @returns The granted permissions.\n */\n grantPermissions({\n approvedPermissions,\n requestData,\n preserveExistingPermissions = true,\n subject,\n }: {\n approvedPermissions: RequestedPermissions;\n subject: PermissionSubjectMetadata;\n preserveExistingPermissions?: boolean;\n requestData?: Record;\n }): SubjectPermissions<\n ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >\n > {\n const { origin } = subject;\n\n if (!origin || typeof origin !== 'string') {\n throw new InvalidSubjectIdentifierError(origin);\n }\n\n const permissions = (\n preserveExistingPermissions\n ? {\n ...this.getPermissions(origin),\n }\n : {}\n ) as SubjectPermissions<\n ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >\n >;\n\n for (const [requestedTarget, approvedPermission] of Object.entries(\n approvedPermissions,\n )) {\n const targetKey = this.getTargetKey(requestedTarget);\n if (!targetKey) {\n throw methodNotFound(requestedTarget);\n }\n\n if (\n approvedPermission.parentCapability !== undefined &&\n requestedTarget !== approvedPermission.parentCapability\n ) {\n throw new InvalidApprovedPermissionError(\n origin,\n requestedTarget,\n approvedPermission,\n );\n }\n\n // The requested target must be a valid target name if we found its key.\n // We reassign it to change its type.\n const targetName = requestedTarget as ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'];\n const specification = this.getPermissionSpecification(targetKey);\n\n // The requested caveats are validated here.\n const caveats = this.constructCaveats(\n origin,\n targetName,\n approvedPermission.caveats,\n );\n\n const permissionOptions = {\n caveats,\n invoker: origin,\n target: targetName,\n };\n\n let permission: ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >;\n if (specification.factory) {\n permission = specification.factory(permissionOptions, requestData);\n\n // Full caveat and permission validation is performed here since the\n // factory function can arbitrarily modify the entire permission object,\n // including its caveats.\n this.validatePermission(specification, permission, origin, targetName);\n } else {\n permission = constructPermission(permissionOptions);\n\n // We do not need to validate caveats in this case, because the plain\n // permission constructor function does not modify the caveats, which\n // were already validated by `constructCaveats` above.\n this.validatePermission(specification, permission, origin, targetName, {\n invokePermissionValidator: true,\n performCaveatValidation: false,\n });\n }\n permissions[targetName] = permission;\n }\n\n this.setValidatedPermissions(origin, permissions);\n return permissions;\n }\n\n /**\n * Validates the specified permission by:\n * - Ensuring that its `caveats` property is either `null` or a non-empty array.\n * - Ensuring that it only includes caveats allowed by its specification.\n * - Ensuring that it includes no duplicate caveats (by caveat type).\n * - Validating each caveat object, if `performCaveatValidation` is `true`.\n * - Calling the validator of its specification, if one exists and `invokePermissionValidator` is `true`.\n *\n * An error is thrown if validation fails.\n *\n * @param specification - The specification of the permission.\n * @param permission - The permission to validate.\n * @param origin - The origin associated with the permission.\n * @param targetName - The target name of the permission.\n * @param validationOptions - Validation options.\n * @param validationOptions.invokePermissionValidator - Whether to invoke the\n * permission's consumer-specified validator function, if any.\n * @param validationOptions.performCaveatValidation - Whether to invoke\n * {@link PermissionController.validateCaveat} on each of the permission's\n * caveats.\n */\n private validatePermission(\n specification: PermissionSpecificationConstraint,\n permission: PermissionConstraint,\n origin: OriginString,\n targetName: ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n { invokePermissionValidator, performCaveatValidation } = {\n invokePermissionValidator: true,\n performCaveatValidation: true,\n },\n ): void {\n const { allowedCaveats, validator } = specification;\n if (hasProperty(permission, 'caveats')) {\n const { caveats } = permission;\n\n if (caveats !== null && !(Array.isArray(caveats) && caveats.length > 0)) {\n throw new InvalidCaveatsPropertyError(origin, targetName, caveats);\n }\n\n const seenCaveatTypes = new Set();\n caveats?.forEach((caveat) => {\n if (performCaveatValidation) {\n this.validateCaveat(caveat, origin, targetName);\n }\n\n if (!allowedCaveats?.includes(caveat.type)) {\n throw new ForbiddenCaveatError(caveat.type, origin, targetName);\n }\n\n if (seenCaveatTypes.has(caveat.type)) {\n throw new DuplicateCaveatError(caveat.type, origin, targetName);\n }\n seenCaveatTypes.add(caveat.type);\n });\n }\n\n if (invokePermissionValidator && validator) {\n validator(permission, origin, targetName);\n }\n }\n\n /**\n * Assigns the specified permissions to the subject with the given origin.\n * Overwrites all existing permissions, and creates a subject entry if it\n * doesn't already exist.\n *\n * ATTN: Assumes that the new permissions have been validated.\n *\n * @param origin - The origin of the grantee subject.\n * @param permissions - The new permissions for the grantee subject.\n */\n private setValidatedPermissions(\n origin: OriginString,\n permissions: Record<\n string,\n ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >\n >,\n ): void {\n this.update((draftState) => {\n if (!draftState.subjects[origin]) {\n draftState.subjects[origin] = { origin, permissions: {} };\n }\n\n draftState.subjects[origin].permissions = castDraft(permissions);\n });\n }\n\n /**\n * Validates the requested caveats for the permission of the specified\n * subject origin and target name and returns the validated caveat array.\n *\n * Throws an error if validation fails.\n *\n * @param origin - The origin of the permission subject.\n * @param target - The permission target name.\n * @param requestedCaveats - The requested caveats to construct.\n * @returns The constructed caveats.\n */\n private constructCaveats(\n origin: OriginString,\n target: ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n requestedCaveats?: unknown[] | null,\n ): NonEmptyArray> | undefined {\n const caveatArray = requestedCaveats?.map((requestedCaveat) => {\n this.validateCaveat(requestedCaveat, origin, target);\n\n // Reassign so that we have a fresh object.\n const { type, value } = requestedCaveat as CaveatConstraint;\n return { type, value } as ExtractCaveats;\n });\n\n return caveatArray && isNonEmptyArray(caveatArray)\n ? caveatArray\n : undefined;\n }\n\n /**\n * This methods validates that the specified caveat is an object with the\n * expected properties and types. It also ensures that a caveat specification\n * exists for the requested caveat type, and calls the specification\n * validator, if it exists, on the caveat object.\n *\n * Throws an error if validation fails.\n *\n * @param caveat - The caveat object to validate.\n * @param origin - The origin associated with the subject of the parent\n * permission.\n * @param target - The target name associated with the parent permission.\n */\n private validateCaveat(\n caveat: unknown,\n origin: OriginString,\n target: string,\n ): void {\n if (!isPlainObject(caveat)) {\n throw new InvalidCaveatError(caveat, origin, target);\n }\n\n if (Object.keys(caveat).length !== 2) {\n throw new InvalidCaveatFieldsError(caveat, origin, target);\n }\n\n if (typeof caveat.type !== 'string') {\n throw new InvalidCaveatTypeError(caveat, origin, target);\n }\n\n const specification = this.getCaveatSpecification(caveat.type);\n if (!specification) {\n throw new UnrecognizedCaveatTypeError(caveat.type, origin, target);\n }\n\n if (!hasProperty(caveat, 'value') || caveat.value === undefined) {\n throw new CaveatMissingValueError(caveat, origin, target);\n }\n\n if (!isValidJson(caveat.value)) {\n throw new CaveatInvalidJsonError(caveat, origin, target);\n }\n\n // Typecast: TypeScript still believes that the caveat is a PlainObject.\n specification.validator?.(caveat as CaveatConstraint, origin, target);\n }\n\n /**\n * Initiates a permission request that requires user approval. This should\n * always be used to grant additional permissions to a subject, unless user\n * approval has been obtained through some other means.\n *\n * Permissions are validated at every step of the approval process, and this\n * method will reject if validation fails.\n *\n * @see {@link ApprovalController} For the user approval logic.\n * @see {@link PermissionController.acceptPermissionsRequest} For the method\n * that _accepts_ the request and resolves the user approval promise.\n * @see {@link PermissionController.rejectPermissionsRequest} For the method\n * that _rejects_ the request and the user approval promise.\n * @param subject - The grantee subject.\n * @param requestedPermissions - The requested permissions.\n * @param options - Additional options.\n * @param options.id - The id of the permissions request. Defaults to a unique\n * id.\n * @param options.preserveExistingPermissions - Whether to preserve the\n * subject's existing permissions. Defaults to `true`.\n * @returns The granted permissions and request metadata.\n */\n async requestPermissions(\n subject: PermissionSubjectMetadata,\n requestedPermissions: RequestedPermissions,\n options: {\n id?: string;\n preserveExistingPermissions?: boolean;\n } = {},\n ): Promise<\n [\n SubjectPermissions<\n ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >\n >,\n { id: string; origin: OriginString },\n ]\n > {\n const { origin } = subject;\n const { id = nanoid(), preserveExistingPermissions = true } = options;\n this.validateRequestedPermissions(origin, requestedPermissions);\n\n const metadata = {\n id,\n origin,\n };\n\n const permissionsRequest = {\n metadata,\n permissions: requestedPermissions,\n };\n\n const { permissions: approvedPermissions, ...requestData } =\n await this.requestUserApproval(permissionsRequest);\n\n return [\n this.grantPermissions({\n subject,\n approvedPermissions,\n preserveExistingPermissions,\n requestData,\n }),\n metadata,\n ];\n }\n\n /**\n * Validates requested permissions. Throws if validation fails.\n *\n * This method ensures that the requested permissions are a properly\n * formatted {@link RequestedPermissions} object, and performs the same\n * validation as {@link PermissionController.grantPermissions}, except that\n * consumer-specified permission validator functions are not called, since\n * they are only called on fully constructed, approved permissions that are\n * otherwise completely valid.\n *\n * Unrecognzied properties on requested permissions are ignored.\n *\n * @param origin - The origin of the grantee subject.\n * @param requestedPermissions - The requested permissions.\n */\n private validateRequestedPermissions(\n origin: OriginString,\n requestedPermissions: unknown,\n ): void {\n if (!isPlainObject(requestedPermissions)) {\n throw invalidParams({\n message: `Requested permissions for origin \"${origin}\" is not a plain object.`,\n data: { origin, requestedPermissions },\n });\n }\n\n if (Object.keys(requestedPermissions).length === 0) {\n throw invalidParams({\n message: `Permissions request for origin \"${origin}\" contains no permissions.`,\n data: { requestedPermissions },\n });\n }\n\n for (const targetName of Object.keys(requestedPermissions)) {\n const permission = requestedPermissions[targetName];\n const targetKey = this.getTargetKey(targetName);\n\n if (!targetKey) {\n throw methodNotFound(targetName, { origin, requestedPermissions });\n }\n\n if (\n !isPlainObject(permission) ||\n (permission.parentCapability !== undefined &&\n targetName !== permission.parentCapability)\n ) {\n throw invalidParams({\n message: `Permissions request for origin \"${origin}\" contains invalid requested permission(s).`,\n data: { origin, requestedPermissions },\n });\n }\n\n // Here we validate the permission without invoking its validator, if any.\n // The validator will be invoked after the permission has been approved.\n this.validatePermission(\n this.getPermissionSpecification(targetKey),\n // Typecast: The permission is still a \"PlainObject\" here.\n permission as PermissionConstraint,\n origin,\n targetName,\n { invokePermissionValidator: false, performCaveatValidation: true },\n );\n }\n }\n\n /**\n * Adds a request to the {@link ApprovalController} using the\n * {@link AddApprovalRequest} action. Also validates the resulting approved\n * permissions request, and throws an error if validation fails.\n *\n * @param permissionsRequest - The permissions request object.\n * @returns The approved permissions request object.\n */\n private async requestUserApproval(permissionsRequest: PermissionsRequest) {\n const { origin, id } = permissionsRequest.metadata;\n const approvedRequest = await this.messagingSystem.call(\n 'ApprovalController:addRequest',\n {\n id,\n origin,\n requestData: permissionsRequest,\n type: MethodNames.requestPermissions,\n },\n true,\n );\n\n this.validateApprovedPermissions(approvedRequest, { id, origin });\n return approvedRequest as PermissionsRequest;\n }\n\n /**\n * Validates an approved {@link PermissionsRequest} object. The approved\n * request must have the required `metadata` and `permissions` properties,\n * the `id` and `origin` of the `metadata` must match the original request\n * metadata, and the requested permissions must be valid per\n * {@link PermissionController.validateRequestedPermissions}. Any extra\n * metadata properties are ignored.\n *\n * An error is thrown if validation fails.\n *\n * @param approvedRequest - The approved permissions request object.\n * @param originalMetadata - The original request metadata.\n */\n private validateApprovedPermissions(\n approvedRequest: unknown,\n originalMetadata: PermissionsRequestMetadata,\n ) {\n const { id, origin } = originalMetadata;\n\n if (\n !isPlainObject(approvedRequest) ||\n !isPlainObject(approvedRequest.metadata)\n ) {\n throw internalError(\n `Approved permissions request for subject \"${origin}\" is invalid.`,\n { data: { approvedRequest } },\n );\n }\n\n const {\n metadata: { id: newId, origin: newOrigin },\n permissions,\n } = approvedRequest;\n\n if (newId !== id) {\n throw internalError(\n `Approved permissions request for subject \"${origin}\" mutated its id.`,\n { originalId: id, mutatedId: newId },\n );\n }\n\n if (newOrigin !== origin) {\n throw internalError(\n `Approved permissions request for subject \"${origin}\" mutated its origin.`,\n { originalOrigin: origin, mutatedOrigin: newOrigin },\n );\n }\n\n try {\n this.validateRequestedPermissions(origin, permissions);\n } catch (error) {\n if (error instanceof EthereumRpcError) {\n // Re-throw as an internal error; we should never receive invalid approved\n // permissions.\n throw internalError(\n `Invalid approved permissions request: ${error.message}`,\n error.data,\n );\n }\n throw internalError('Unrecognized error type', { error });\n }\n }\n\n /**\n * Accepts a permissions request created by\n * {@link PermissionController.requestPermissions}.\n *\n * @param request - The permissions request.\n */\n async acceptPermissionsRequest(request: PermissionsRequest): Promise {\n const { id } = request.metadata;\n\n if (!this.hasApprovalRequest({ id })) {\n throw new PermissionsRequestNotFoundError(id);\n }\n\n if (Object.keys(request.permissions).length === 0) {\n this._rejectPermissionsRequest(\n id,\n invalidParams({\n message: 'Must request at least one permission.',\n }),\n );\n return;\n }\n\n try {\n this.messagingSystem.call(\n 'ApprovalController:acceptRequest',\n id,\n request,\n );\n } catch (error) {\n // If accepting unexpectedly fails, reject the request and re-throw the\n // error\n this._rejectPermissionsRequest(id, error);\n throw error;\n }\n }\n\n /**\n * Rejects a permissions request created by\n * {@link PermissionController.requestPermissions}.\n *\n * @param id - The id of the request to be rejected.\n */\n async rejectPermissionsRequest(id: string): Promise {\n if (!this.hasApprovalRequest({ id })) {\n throw new PermissionsRequestNotFoundError(id);\n }\n\n this._rejectPermissionsRequest(id, userRejectedRequest());\n }\n\n /**\n * Checks whether the {@link ApprovalController} has a particular permissions\n * request.\n *\n * @see {@link PermissionController.acceptPermissionsRequest} and\n * {@link PermissionController.rejectPermissionsRequest} for usage.\n * @param options - The {@link HasApprovalRequest} options.\n * @param options.id - The id of the approval request to check for.\n * @returns Whether the specified request exists.\n */\n private hasApprovalRequest(options: { id: string }): boolean {\n return this.messagingSystem.call(\n 'ApprovalController:hasRequest',\n // Typecast: For some reason, the type here expects all of the possible\n // HasApprovalRequest options to be specified, when they're actually all\n // optional. Passing just the id is definitely valid, so we just cast it.\n options as any,\n );\n }\n\n /**\n * Rejects the permissions request with the specified id, with the specified\n * error as the reason. This method is effectively a wrapper around a\n * messenger call for the `ApprovalController:rejectRequest` action.\n *\n * @see {@link PermissionController.acceptPermissionsRequest} and\n * {@link PermissionController.rejectPermissionsRequest} for usage.\n * @param id - The id of the request to reject.\n * @param error - The error associated with the rejection.\n * @returns Nothing\n */\n private _rejectPermissionsRequest(id: string, error: unknown): void {\n return this.messagingSystem.call(\n 'ApprovalController:rejectRequest',\n id,\n error,\n );\n }\n\n /**\n * Gets the subject's endowments per the specified endowment permission.\n * Throws if the subject does not have the required permission or if the\n * permission is not an endowment permission.\n *\n * @param origin - The origin of the subject whose endowments to retrieve.\n * @param targetName - The name of the endowment permission. This must be a\n * valid permission target name.\n * @param requestData - Additional data associated with the request, if any.\n * Forwarded to the endowment getter function for the permission.\n * @returns The endowments, if any.\n */\n async getEndowments(\n origin: string,\n targetName: ExtractEndowmentPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n requestData?: unknown,\n ): Promise {\n if (!this.hasPermission(origin, targetName)) {\n throw unauthorized({ data: { origin, targetName } });\n }\n\n return this.getTypedPermissionSpecification(\n PermissionType.Endowment,\n targetName,\n origin,\n ).endowmentGetter({ origin, requestData });\n }\n\n /**\n * Executes a restricted method as the subject with the given origin.\n * The specified params, if any, will be passed to the method implementation.\n *\n * ATTN: Great caution should be exercised in the use of this method.\n * Methods that cause side effects or affect application state should\n * be avoided.\n *\n * This method will first attempt to retrieve the requested restricted method\n * implementation, throwing if it does not exist. The method will then be\n * invoked as though the subject with the specified origin had invoked it with\n * the specified parameters. This means that any existing caveats will be\n * applied to the restricted method, and this method will throw if the\n * restricted method or its caveat decorators throw.\n *\n * In addition, this method will throw if the subject does not have a\n * permission for the specified restricted method.\n *\n * @param origin - The origin of the subject to execute the method on behalf\n * of.\n * @param targetName - The name of the method to execute. This must be a valid\n * permission target name.\n * @param params - The parameters to pass to the method implementation.\n * @returns The result of the executed method.\n */\n async executeRestrictedMethod(\n origin: OriginString,\n targetName: ExtractRestrictedMethodPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n params?: RestrictedMethodParameters,\n ): Promise {\n // Throws if the method does not exist\n const methodImplementation = this.getRestrictedMethod(targetName, origin);\n\n const result = await this._executeRestrictedMethod(\n methodImplementation,\n { origin },\n targetName,\n params,\n );\n\n if (result === undefined) {\n throw new Error(\n `Internal request for method \"${targetName}\" as origin \"${origin}\" returned no result.`,\n );\n }\n\n return result;\n }\n\n /**\n * An internal method used in the controller's `json-rpc-engine` middleware\n * and {@link PermissionController.executeRestrictedMethod}. Calls the\n * specified restricted method implementation after decorating it with the\n * caveats of its permission. Throws if the subject does not have the\n * requisite permission.\n *\n * ATTN: Parameter validation is the responsibility of the caller, or\n * the restricted method implementation in the case of `params`.\n *\n * @see {@link PermissionController.executeRestrictedMethod} and\n * {@link PermissionController.createPermissionMiddleware} for usage.\n * @param methodImplementation - The implementation of the method to call.\n * @param subject - Metadata about the subject that made the request.\n * @param method - The method name\n * @param params - Params needed for executing the restricted method\n * @returns The result of the restricted method implementation\n */\n private _executeRestrictedMethod(\n methodImplementation: RestrictedMethod,\n subject: PermissionSubjectMetadata,\n method: ExtractPermission<\n ControllerPermissionSpecification,\n ControllerCaveatSpecification\n >['parentCapability'],\n params: RestrictedMethodParameters = [],\n ): ReturnType> {\n const { origin } = subject;\n\n const permission = this.getPermission(origin, method);\n if (!permission) {\n throw unauthorized({ data: { origin, method } });\n }\n\n return decorateWithCaveats(\n methodImplementation,\n permission,\n this._caveatSpecifications,\n )({ method, params, context: { origin } });\n }\n}\n"]} \ No newline at end of file diff --git a/dist/permissions/errors.d.ts b/dist/permissions/errors.d.ts deleted file mode 100644 index 2d043dd4aa..0000000000 --- a/dist/permissions/errors.d.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { EthereumRpcError } from 'eth-rpc-errors'; -declare type UnauthorizedArg = { - data?: Record; -}; -/** - * Utility function for building an "unauthorized" error. - * - * @param opts - Optional arguments that add extra context - * @returns The built error - */ -export declare function unauthorized(opts: UnauthorizedArg): import("eth-rpc-errors").EthereumProviderError>; -/** - * Utility function for building a "method not found" error. - * - * @param method - The method in question. - * @param data - Optional data for context. - * @returns The built error - */ -export declare function methodNotFound(method: string, data?: unknown): EthereumRpcError; -declare type InvalidParamsArg = { - message?: string; - data?: unknown; -}; -/** - * Utility function for building an "invalid params" error. - * - * @param opts - Optional arguments that add extra context - * @returns The built error - */ -export declare function invalidParams(opts: InvalidParamsArg): EthereumRpcError; -/** - * Utility function for building an "user rejected request" error. - * - * @param data - Optional data to add extra context - * @returns The built error - */ -export declare function userRejectedRequest>(data?: Data): EthereumRpcError; -/** - * Utility function for building an internal error. - * - * @param message - The error message - * @param data - Optional data to add extra context - * @returns The built error - */ -export declare function internalError>(message: string, data?: Data): EthereumRpcError; -export declare class InvalidSubjectIdentifierError extends Error { - constructor(origin: unknown); -} -export declare class UnrecognizedSubjectError extends Error { - constructor(origin: string); -} -export declare class InvalidApprovedPermissionError extends Error { - data: { - origin: string; - target: string; - approvedPermission: Record; - }; - constructor(origin: string, target: string, approvedPermission: Record); -} -export declare class PermissionDoesNotExistError extends Error { - constructor(origin: string, target: string); -} -export declare class EndowmentPermissionDoesNotExistError extends Error { - data?: { - origin: string; - }; - constructor(target: string, origin?: string); -} -export declare class UnrecognizedCaveatTypeError extends Error { - data: { - caveatType: string; - origin?: string; - target?: string; - }; - constructor(caveatType: string); - constructor(caveatType: string, origin: string, target: string); -} -export declare class InvalidCaveatsPropertyError extends Error { - data: { - origin: string; - target: string; - caveatsProperty: unknown; - }; - constructor(origin: string, target: string, caveatsProperty: unknown); -} -export declare class CaveatDoesNotExistError extends Error { - constructor(origin: string, target: string, caveatType: string); -} -export declare class CaveatAlreadyExistsError extends Error { - constructor(origin: string, target: string, caveatType: string); -} -export declare class InvalidCaveatError extends EthereumRpcError { - data: { - origin: string; - target: string; - }; - constructor(receivedCaveat: unknown, origin: string, target: string); -} -export declare class InvalidCaveatTypeError extends Error { - data: { - caveat: Record; - origin: string; - target: string; - }; - constructor(caveat: Record, origin: string, target: string); -} -export declare class CaveatMissingValueError extends Error { - data: { - caveat: Record; - origin: string; - target: string; - }; - constructor(caveat: Record, origin: string, target: string); -} -export declare class CaveatInvalidJsonError extends Error { - data: { - caveat: Record; - origin: string; - target: string; - }; - constructor(caveat: Record, origin: string, target: string); -} -export declare class InvalidCaveatFieldsError extends Error { - data: { - caveat: Record; - origin: string; - target: string; - }; - constructor(caveat: Record, origin: string, target: string); -} -export declare class ForbiddenCaveatError extends Error { - data: { - caveatType: string; - origin: string; - target: string; - }; - constructor(caveatType: string, origin: string, targetName: string); -} -export declare class DuplicateCaveatError extends Error { - data: { - caveatType: string; - origin: string; - target: string; - }; - constructor(caveatType: string, origin: string, targetName: string); -} -export declare class PermissionsRequestNotFoundError extends Error { - constructor(id: string); -} -export {}; diff --git a/dist/permissions/errors.js b/dist/permissions/errors.js deleted file mode 100644 index f293fb0fe0..0000000000 --- a/dist/permissions/errors.js +++ /dev/null @@ -1,189 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.PermissionsRequestNotFoundError = exports.DuplicateCaveatError = exports.ForbiddenCaveatError = exports.InvalidCaveatFieldsError = exports.CaveatInvalidJsonError = exports.CaveatMissingValueError = exports.InvalidCaveatTypeError = exports.InvalidCaveatError = exports.CaveatAlreadyExistsError = exports.CaveatDoesNotExistError = exports.InvalidCaveatsPropertyError = exports.UnrecognizedCaveatTypeError = exports.EndowmentPermissionDoesNotExistError = exports.PermissionDoesNotExistError = exports.InvalidApprovedPermissionError = exports.UnrecognizedSubjectError = exports.InvalidSubjectIdentifierError = exports.internalError = exports.userRejectedRequest = exports.invalidParams = exports.methodNotFound = exports.unauthorized = void 0; -const eth_rpc_errors_1 = require("eth-rpc-errors"); -/** - * Utility function for building an "unauthorized" error. - * - * @param opts - Optional arguments that add extra context - * @returns The built error - */ -function unauthorized(opts) { - return eth_rpc_errors_1.ethErrors.provider.unauthorized({ - message: 'Unauthorized to perform action. Try requesting the required permission(s) first. For more information, see: https://docs.metamask.io/guide/rpc-api.html#permissions', - data: opts.data, - }); -} -exports.unauthorized = unauthorized; -/** - * Utility function for building a "method not found" error. - * - * @param method - The method in question. - * @param data - Optional data for context. - * @returns The built error - */ -function methodNotFound(method, data) { - const message = `The method "${method}" does not exist / is not available.`; - const opts = { message }; - if (data !== undefined) { - opts.data = data; - } - return eth_rpc_errors_1.ethErrors.rpc.methodNotFound(opts); -} -exports.methodNotFound = methodNotFound; -/** - * Utility function for building an "invalid params" error. - * - * @param opts - Optional arguments that add extra context - * @returns The built error - */ -function invalidParams(opts) { - return eth_rpc_errors_1.ethErrors.rpc.invalidParams({ - data: opts.data, - message: opts.message, - }); -} -exports.invalidParams = invalidParams; -/** - * Utility function for building an "user rejected request" error. - * - * @param data - Optional data to add extra context - * @returns The built error - */ -function userRejectedRequest(data) { - return eth_rpc_errors_1.ethErrors.provider.userRejectedRequest({ data }); -} -exports.userRejectedRequest = userRejectedRequest; -/** - * Utility function for building an internal error. - * - * @param message - The error message - * @param data - Optional data to add extra context - * @returns The built error - */ -function internalError(message, data) { - return eth_rpc_errors_1.ethErrors.rpc.internal({ message, data }); -} -exports.internalError = internalError; -class InvalidSubjectIdentifierError extends Error { - constructor(origin) { - super(`Invalid subject identifier: "${typeof origin === 'string' ? origin : typeof origin}"`); - } -} -exports.InvalidSubjectIdentifierError = InvalidSubjectIdentifierError; -class UnrecognizedSubjectError extends Error { - constructor(origin) { - super(`Unrecognized subject: "${origin}" has no permissions.`); - } -} -exports.UnrecognizedSubjectError = UnrecognizedSubjectError; -class InvalidApprovedPermissionError extends Error { - constructor(origin, target, approvedPermission) { - super(`Invalid approved permission for origin "${origin}" and target "${target}".`); - this.data = { origin, target, approvedPermission }; - } -} -exports.InvalidApprovedPermissionError = InvalidApprovedPermissionError; -class PermissionDoesNotExistError extends Error { - constructor(origin, target) { - super(`Subject "${origin}" has no permission for "${target}".`); - } -} -exports.PermissionDoesNotExistError = PermissionDoesNotExistError; -class EndowmentPermissionDoesNotExistError extends Error { - constructor(target, origin) { - super(`Subject "${origin}" has no permission for "${target}".`); - if (origin) { - this.data = { origin }; - } - } -} -exports.EndowmentPermissionDoesNotExistError = EndowmentPermissionDoesNotExistError; -class UnrecognizedCaveatTypeError extends Error { - constructor(caveatType, origin, target) { - super(`Unrecognized caveat type: "${caveatType}"`); - this.data = { caveatType }; - if (origin !== undefined) { - this.data.origin = origin; - } - if (target !== undefined) { - this.data.target = target; - } - } -} -exports.UnrecognizedCaveatTypeError = UnrecognizedCaveatTypeError; -class InvalidCaveatsPropertyError extends Error { - constructor(origin, target, caveatsProperty) { - super(`The "caveats" property of permission for "${target}" of subject "${origin}" is invalid. It must be a non-empty array if specified.`); - this.data = { origin, target, caveatsProperty }; - } -} -exports.InvalidCaveatsPropertyError = InvalidCaveatsPropertyError; -class CaveatDoesNotExistError extends Error { - constructor(origin, target, caveatType) { - super(`Permission for "${target}" of subject "${origin}" has no caveat of type "${caveatType}".`); - } -} -exports.CaveatDoesNotExistError = CaveatDoesNotExistError; -class CaveatAlreadyExistsError extends Error { - constructor(origin, target, caveatType) { - super(`Permission for "${target}" of subject "${origin}" already has a caveat of type "${caveatType}".`); - } -} -exports.CaveatAlreadyExistsError = CaveatAlreadyExistsError; -class InvalidCaveatError extends eth_rpc_errors_1.EthereumRpcError { - constructor(receivedCaveat, origin, target) { - super(eth_rpc_errors_1.errorCodes.rpc.invalidParams, `Invalid caveat. Caveats must be plain objects.`, { receivedCaveat }); - this.data = { origin, target }; - } -} -exports.InvalidCaveatError = InvalidCaveatError; -class InvalidCaveatTypeError extends Error { - constructor(caveat, origin, target) { - super(`Caveat types must be strings. Received: "${typeof caveat.type}"`); - this.data = { caveat, origin, target }; - } -} -exports.InvalidCaveatTypeError = InvalidCaveatTypeError; -class CaveatMissingValueError extends Error { - constructor(caveat, origin, target) { - super(`Caveat is missing "value" field.`); - this.data = { caveat, origin, target }; - } -} -exports.CaveatMissingValueError = CaveatMissingValueError; -class CaveatInvalidJsonError extends Error { - constructor(caveat, origin, target) { - super(`Caveat "value" is invalid JSON.`); - this.data = { caveat, origin, target }; - } -} -exports.CaveatInvalidJsonError = CaveatInvalidJsonError; -class InvalidCaveatFieldsError extends Error { - constructor(caveat, origin, target) { - super(`Caveat has unexpected number of fields: "${Object.keys(caveat).length}"`); - this.data = { caveat, origin, target }; - } -} -exports.InvalidCaveatFieldsError = InvalidCaveatFieldsError; -class ForbiddenCaveatError extends Error { - constructor(caveatType, origin, targetName) { - super(`Permissions for target "${targetName}" may not have caveats of type "${caveatType}".`); - this.data = { caveatType, origin, target: targetName }; - } -} -exports.ForbiddenCaveatError = ForbiddenCaveatError; -class DuplicateCaveatError extends Error { - constructor(caveatType, origin, targetName) { - super(`Permissions for target "${targetName}" contains multiple caveats of type "${caveatType}".`); - this.data = { caveatType, origin, target: targetName }; - } -} -exports.DuplicateCaveatError = DuplicateCaveatError; -class PermissionsRequestNotFoundError extends Error { - constructor(id) { - super(`Permissions request with id "${id}" not found.`); - } -} -exports.PermissionsRequestNotFoundError = PermissionsRequestNotFoundError; -//# sourceMappingURL=errors.js.map \ No newline at end of file diff --git a/dist/permissions/errors.js.map b/dist/permissions/errors.js.map deleted file mode 100644 index ce45f83276..0000000000 --- a/dist/permissions/errors.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/permissions/errors.ts"],"names":[],"mappings":"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","sourcesContent":["import { errorCodes, ethErrors, EthereumRpcError } from 'eth-rpc-errors';\n\ntype UnauthorizedArg = {\n data?: Record;\n};\n\n/**\n * Utility function for building an \"unauthorized\" error.\n *\n * @param opts - Optional arguments that add extra context\n * @returns The built error\n */\nexport function unauthorized(opts: UnauthorizedArg) {\n return ethErrors.provider.unauthorized({\n message:\n 'Unauthorized to perform action. Try requesting the required permission(s) first. For more information, see: https://docs.metamask.io/guide/rpc-api.html#permissions',\n data: opts.data,\n });\n}\n\n/**\n * Utility function for building a \"method not found\" error.\n *\n * @param method - The method in question.\n * @param data - Optional data for context.\n * @returns The built error\n */\nexport function methodNotFound(method: string, data?: unknown) {\n const message = `The method \"${method}\" does not exist / is not available.`;\n\n const opts: Parameters[0] = { message };\n if (data !== undefined) {\n opts.data = data;\n }\n return ethErrors.rpc.methodNotFound(opts);\n}\n\ntype InvalidParamsArg = {\n message?: string;\n data?: unknown;\n};\n\n/**\n * Utility function for building an \"invalid params\" error.\n *\n * @param opts - Optional arguments that add extra context\n * @returns The built error\n */\nexport function invalidParams(opts: InvalidParamsArg) {\n return ethErrors.rpc.invalidParams({\n data: opts.data,\n message: opts.message,\n });\n}\n\n/**\n * Utility function for building an \"user rejected request\" error.\n *\n * @param data - Optional data to add extra context\n * @returns The built error\n */\nexport function userRejectedRequest>(\n data?: Data,\n): EthereumRpcError {\n return ethErrors.provider.userRejectedRequest({ data });\n}\n\n/**\n * Utility function for building an internal error.\n *\n * @param message - The error message\n * @param data - Optional data to add extra context\n * @returns The built error\n */\nexport function internalError>(\n message: string,\n data?: Data,\n): EthereumRpcError {\n return ethErrors.rpc.internal({ message, data });\n}\n\nexport class InvalidSubjectIdentifierError extends Error {\n constructor(origin: unknown) {\n super(\n `Invalid subject identifier: \"${\n typeof origin === 'string' ? origin : typeof origin\n }\"`,\n );\n }\n}\n\nexport class UnrecognizedSubjectError extends Error {\n constructor(origin: string) {\n super(`Unrecognized subject: \"${origin}\" has no permissions.`);\n }\n}\n\nexport class InvalidApprovedPermissionError extends Error {\n public data: {\n origin: string;\n target: string;\n approvedPermission: Record;\n };\n\n constructor(\n origin: string,\n target: string,\n approvedPermission: Record,\n ) {\n super(\n `Invalid approved permission for origin \"${origin}\" and target \"${target}\".`,\n );\n this.data = { origin, target, approvedPermission };\n }\n}\nexport class PermissionDoesNotExistError extends Error {\n constructor(origin: string, target: string) {\n super(`Subject \"${origin}\" has no permission for \"${target}\".`);\n }\n}\n\nexport class EndowmentPermissionDoesNotExistError extends Error {\n public data?: { origin: string };\n\n constructor(target: string, origin?: string) {\n super(`Subject \"${origin}\" has no permission for \"${target}\".`);\n if (origin) {\n this.data = { origin };\n }\n }\n}\n\nexport class UnrecognizedCaveatTypeError extends Error {\n public data: {\n caveatType: string;\n origin?: string;\n target?: string;\n };\n\n constructor(caveatType: string);\n\n constructor(caveatType: string, origin: string, target: string);\n\n constructor(caveatType: string, origin?: string, target?: string) {\n super(`Unrecognized caveat type: \"${caveatType}\"`);\n this.data = { caveatType };\n if (origin !== undefined) {\n this.data.origin = origin;\n }\n\n if (target !== undefined) {\n this.data.target = target;\n }\n }\n}\n\nexport class InvalidCaveatsPropertyError extends Error {\n public data: { origin: string; target: string; caveatsProperty: unknown };\n\n constructor(origin: string, target: string, caveatsProperty: unknown) {\n super(\n `The \"caveats\" property of permission for \"${target}\" of subject \"${origin}\" is invalid. It must be a non-empty array if specified.`,\n );\n this.data = { origin, target, caveatsProperty };\n }\n}\n\nexport class CaveatDoesNotExistError extends Error {\n constructor(origin: string, target: string, caveatType: string) {\n super(\n `Permission for \"${target}\" of subject \"${origin}\" has no caveat of type \"${caveatType}\".`,\n );\n }\n}\n\nexport class CaveatAlreadyExistsError extends Error {\n constructor(origin: string, target: string, caveatType: string) {\n super(\n `Permission for \"${target}\" of subject \"${origin}\" already has a caveat of type \"${caveatType}\".`,\n );\n }\n}\n\nexport class InvalidCaveatError extends EthereumRpcError {\n public override data: { origin: string; target: string };\n\n constructor(receivedCaveat: unknown, origin: string, target: string) {\n super(\n errorCodes.rpc.invalidParams,\n `Invalid caveat. Caveats must be plain objects.`,\n { receivedCaveat },\n );\n this.data = { origin, target };\n }\n}\n\nexport class InvalidCaveatTypeError extends Error {\n public data: {\n caveat: Record;\n origin: string;\n target: string;\n };\n\n constructor(caveat: Record, origin: string, target: string) {\n super(`Caveat types must be strings. Received: \"${typeof caveat.type}\"`);\n this.data = { caveat, origin, target };\n }\n}\n\nexport class CaveatMissingValueError extends Error {\n public data: {\n caveat: Record;\n origin: string;\n target: string;\n };\n\n constructor(caveat: Record, origin: string, target: string) {\n super(`Caveat is missing \"value\" field.`);\n this.data = { caveat, origin, target };\n }\n}\n\nexport class CaveatInvalidJsonError extends Error {\n public data: {\n caveat: Record;\n origin: string;\n target: string;\n };\n\n constructor(caveat: Record, origin: string, target: string) {\n super(`Caveat \"value\" is invalid JSON.`);\n this.data = { caveat, origin, target };\n }\n}\n\nexport class InvalidCaveatFieldsError extends Error {\n public data: {\n caveat: Record;\n origin: string;\n target: string;\n };\n\n constructor(caveat: Record, origin: string, target: string) {\n super(\n `Caveat has unexpected number of fields: \"${Object.keys(caveat).length}\"`,\n );\n this.data = { caveat, origin, target };\n }\n}\n\nexport class ForbiddenCaveatError extends Error {\n public data: {\n caveatType: string;\n origin: string;\n target: string;\n };\n\n constructor(caveatType: string, origin: string, targetName: string) {\n super(\n `Permissions for target \"${targetName}\" may not have caveats of type \"${caveatType}\".`,\n );\n this.data = { caveatType, origin, target: targetName };\n }\n}\n\nexport class DuplicateCaveatError extends Error {\n public data: {\n caveatType: string;\n origin: string;\n target: string;\n };\n\n constructor(caveatType: string, origin: string, targetName: string) {\n super(\n `Permissions for target \"${targetName}\" contains multiple caveats of type \"${caveatType}\".`,\n );\n this.data = { caveatType, origin, target: targetName };\n }\n}\n\nexport class PermissionsRequestNotFoundError extends Error {\n constructor(id: string) {\n super(`Permissions request with id \"${id}\" not found.`);\n }\n}\n"]} \ No newline at end of file diff --git a/dist/permissions/index.d.ts b/dist/permissions/index.d.ts deleted file mode 100644 index dced5a9ac3..0000000000 --- a/dist/permissions/index.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './Caveat'; -export * from './Permission'; -export * from './PermissionController'; -export * from './utils'; -export * as permissionRpcMethods from './rpc-methods'; diff --git a/dist/permissions/index.js b/dist/permissions/index.js deleted file mode 100644 index 53edaa8197..0000000000 --- a/dist/permissions/index.js +++ /dev/null @@ -1,35 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __exportStar = (this && this.__exportStar) || function(m, exports) { - for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); -}; -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.permissionRpcMethods = void 0; -__exportStar(require("./Caveat"), exports); -__exportStar(require("./Permission"), exports); -__exportStar(require("./PermissionController"), exports); -__exportStar(require("./utils"), exports); -exports.permissionRpcMethods = __importStar(require("./rpc-methods")); -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/permissions/index.js.map b/dist/permissions/index.js.map deleted file mode 100644 index d3ce12bea1..0000000000 --- a/dist/permissions/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/permissions/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,+CAA6B;AAC7B,yDAAuC;AACvC,0CAAwB;AACxB,sEAAsD","sourcesContent":["export * from './Caveat';\nexport * from './Permission';\nexport * from './PermissionController';\nexport * from './utils';\nexport * as permissionRpcMethods from './rpc-methods';\n"]} \ No newline at end of file diff --git a/dist/permissions/permission-middleware.d.ts b/dist/permissions/permission-middleware.d.ts deleted file mode 100644 index 66bb51f09a..0000000000 --- a/dist/permissions/permission-middleware.d.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { Json } from '@metamask/types'; -import { JsonRpcMiddleware } from 'json-rpc-engine'; -import { GenericPermissionController, PermissionSubjectMetadata, RestrictedMethodParameters } from '.'; -declare type PermissionMiddlewareFactoryOptions = { - executeRestrictedMethod: GenericPermissionController['_executeRestrictedMethod']; - getRestrictedMethod: GenericPermissionController['getRestrictedMethod']; - isUnrestrictedMethod: (method: string) => boolean; -}; -/** - * Creates a permission middleware function factory. Intended for internal use - * in the {@link PermissionController}. Like any {@link JsonRpcEngine} - * middleware, each middleware will only receive requests from a particular - * subject / origin. However, each middleware also requires access to some - * `PermissionController` internals, which is why this "factory factory" exists. - * - * The middlewares returned by the factory will pass through requests for - * unrestricted methods, and attempt to execute restricted methods. If a method - * is neither restricted nor unrestricted, a "method not found" error will be - * returned. - * If a method is restricted, the middleware will first attempt to retrieve the - * subject's permission for that method. If the permission is found, the method - * will be executed. Otherwise, an "unauthorized" error will be returned. - * - * @param options - Options bag. - * @param options.executeRestrictedMethod - {@link PermissionController._executeRestrictedMethod}. - * @param options.getRestrictedMethod - {@link PermissionController.getRestrictedMethod}. - * @param options.isUnrestrictedMethod - A function that checks whether a - * particular method is unrestricted. - * @returns A permission middleware factory function. - */ -export declare function getPermissionMiddlewareFactory({ executeRestrictedMethod, getRestrictedMethod, isUnrestrictedMethod, }: PermissionMiddlewareFactoryOptions): (subject: PermissionSubjectMetadata) => JsonRpcMiddleware; -export {}; diff --git a/dist/permissions/permission-middleware.js b/dist/permissions/permission-middleware.js deleted file mode 100644 index 402983e774..0000000000 --- a/dist/permissions/permission-middleware.js +++ /dev/null @@ -1,64 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.getPermissionMiddlewareFactory = void 0; -const json_rpc_engine_1 = require("json-rpc-engine"); -const errors_1 = require("./errors"); -/** - * Creates a permission middleware function factory. Intended for internal use - * in the {@link PermissionController}. Like any {@link JsonRpcEngine} - * middleware, each middleware will only receive requests from a particular - * subject / origin. However, each middleware also requires access to some - * `PermissionController` internals, which is why this "factory factory" exists. - * - * The middlewares returned by the factory will pass through requests for - * unrestricted methods, and attempt to execute restricted methods. If a method - * is neither restricted nor unrestricted, a "method not found" error will be - * returned. - * If a method is restricted, the middleware will first attempt to retrieve the - * subject's permission for that method. If the permission is found, the method - * will be executed. Otherwise, an "unauthorized" error will be returned. - * - * @param options - Options bag. - * @param options.executeRestrictedMethod - {@link PermissionController._executeRestrictedMethod}. - * @param options.getRestrictedMethod - {@link PermissionController.getRestrictedMethod}. - * @param options.isUnrestrictedMethod - A function that checks whether a - * particular method is unrestricted. - * @returns A permission middleware factory function. - */ -function getPermissionMiddlewareFactory({ executeRestrictedMethod, getRestrictedMethod, isUnrestrictedMethod, }) { - return function createPermissionMiddleware(subject) { - const { origin } = subject; - if (typeof origin !== 'string' || !origin) { - throw new Error('The subject "origin" must be a non-empty string.'); - } - const permissionsMiddleware = (req, res, next) => __awaiter(this, void 0, void 0, function* () { - const { method, params } = req; - // Skip registered unrestricted methods. - if (isUnrestrictedMethod(method)) { - return next(); - } - // This will throw if no restricted method implementation is found. - const methodImplementation = getRestrictedMethod(method, origin); - // This will throw if the permission does not exist. - const result = yield executeRestrictedMethod(methodImplementation, subject, method, params); - if (result === undefined) { - res.error = (0, errors_1.internalError)(`Request for method "${req.method}" returned undefined result.`, { request: req }); - return undefined; - } - res.result = result; - return undefined; - }); - return (0, json_rpc_engine_1.createAsyncMiddleware)(permissionsMiddleware); - }; -} -exports.getPermissionMiddlewareFactory = getPermissionMiddlewareFactory; -//# sourceMappingURL=permission-middleware.js.map \ No newline at end of file diff --git a/dist/permissions/permission-middleware.js.map b/dist/permissions/permission-middleware.js.map deleted file mode 100644 index ec6364b68c..0000000000 --- a/dist/permissions/permission-middleware.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"permission-middleware.js","sourceRoot":"","sources":["../../src/permissions/permission-middleware.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,qDAMyB;AAGzB,qCAAyC;AAezC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,SAAgB,8BAA8B,CAAC,EAC7C,uBAAuB,EACvB,mBAAmB,EACnB,oBAAoB,GACe;IACnC,OAAO,SAAS,0BAA0B,CACxC,OAAkC;QAElC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAC3B,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,MAAM,EAAE;YACzC,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;SACrE;QAED,MAAM,qBAAqB,GAAG,CAC5B,GAA+C,EAC/C,GAAiC,EACjC,IAAoC,EACrB,EAAE;YACjB,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;YAE/B,wCAAwC;YACxC,IAAI,oBAAoB,CAAC,MAAM,CAAC,EAAE;gBAChC,OAAO,IAAI,EAAE,CAAC;aACf;YAED,mEAAmE;YACnE,MAAM,oBAAoB,GAAG,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAEjE,oDAAoD;YACpD,MAAM,MAAM,GAAG,MAAM,uBAAuB,CAC1C,oBAAoB,EACpB,OAAO,EACP,MAAM,EACN,MAAM,CACP,CAAC;YAEF,IAAI,MAAM,KAAK,SAAS,EAAE;gBACxB,GAAG,CAAC,KAAK,GAAG,IAAA,sBAAa,EACvB,uBAAuB,GAAG,CAAC,MAAM,8BAA8B,EAC/D,EAAE,OAAO,EAAE,GAAG,EAAE,CACjB,CAAC;gBACF,OAAO,SAAS,CAAC;aAClB;YAED,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC;YACpB,OAAO,SAAS,CAAC;QACnB,CAAC,CAAA,CAAC;QAEF,OAAO,IAAA,uCAAqB,EAAC,qBAAqB,CAAC,CAAC;IACtD,CAAC,CAAC;AACJ,CAAC;AAlDD,wEAkDC","sourcesContent":["import type { Json } from '@metamask/types';\nimport {\n JsonRpcMiddleware,\n AsyncJsonRpcEngineNextCallback,\n createAsyncMiddleware,\n PendingJsonRpcResponse,\n JsonRpcRequest,\n} from 'json-rpc-engine';\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nimport type { JsonRpcEngine } from 'json-rpc-engine';\nimport { internalError } from './errors';\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nimport type { PermissionController } from './PermissionController';\nimport {\n GenericPermissionController,\n PermissionSubjectMetadata,\n RestrictedMethodParameters,\n} from '.';\n\ntype PermissionMiddlewareFactoryOptions = {\n executeRestrictedMethod: GenericPermissionController['_executeRestrictedMethod'];\n getRestrictedMethod: GenericPermissionController['getRestrictedMethod'];\n isUnrestrictedMethod: (method: string) => boolean;\n};\n\n/**\n * Creates a permission middleware function factory. Intended for internal use\n * in the {@link PermissionController}. Like any {@link JsonRpcEngine}\n * middleware, each middleware will only receive requests from a particular\n * subject / origin. However, each middleware also requires access to some\n * `PermissionController` internals, which is why this \"factory factory\" exists.\n *\n * The middlewares returned by the factory will pass through requests for\n * unrestricted methods, and attempt to execute restricted methods. If a method\n * is neither restricted nor unrestricted, a \"method not found\" error will be\n * returned.\n * If a method is restricted, the middleware will first attempt to retrieve the\n * subject's permission for that method. If the permission is found, the method\n * will be executed. Otherwise, an \"unauthorized\" error will be returned.\n *\n * @param options - Options bag.\n * @param options.executeRestrictedMethod - {@link PermissionController._executeRestrictedMethod}.\n * @param options.getRestrictedMethod - {@link PermissionController.getRestrictedMethod}.\n * @param options.isUnrestrictedMethod - A function that checks whether a\n * particular method is unrestricted.\n * @returns A permission middleware factory function.\n */\nexport function getPermissionMiddlewareFactory({\n executeRestrictedMethod,\n getRestrictedMethod,\n isUnrestrictedMethod,\n}: PermissionMiddlewareFactoryOptions) {\n return function createPermissionMiddleware(\n subject: PermissionSubjectMetadata,\n ): JsonRpcMiddleware {\n const { origin } = subject;\n if (typeof origin !== 'string' || !origin) {\n throw new Error('The subject \"origin\" must be a non-empty string.');\n }\n\n const permissionsMiddleware = async (\n req: JsonRpcRequest,\n res: PendingJsonRpcResponse,\n next: AsyncJsonRpcEngineNextCallback,\n ): Promise => {\n const { method, params } = req;\n\n // Skip registered unrestricted methods.\n if (isUnrestrictedMethod(method)) {\n return next();\n }\n\n // This will throw if no restricted method implementation is found.\n const methodImplementation = getRestrictedMethod(method, origin);\n\n // This will throw if the permission does not exist.\n const result = await executeRestrictedMethod(\n methodImplementation,\n subject,\n method,\n params,\n );\n\n if (result === undefined) {\n res.error = internalError(\n `Request for method \"${req.method}\" returned undefined result.`,\n { request: req },\n );\n return undefined;\n }\n\n res.result = result;\n return undefined;\n };\n\n return createAsyncMiddleware(permissionsMiddleware);\n };\n}\n"]} \ No newline at end of file diff --git a/dist/permissions/rpc-methods/getPermissions.d.ts b/dist/permissions/rpc-methods/getPermissions.d.ts deleted file mode 100644 index 9380ecb422..0000000000 --- a/dist/permissions/rpc-methods/getPermissions.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { PermittedHandlerExport } from '@metamask/types'; -import type { PermissionConstraint } from '../Permission'; -import type { SubjectPermissions } from '../PermissionController'; -export declare const getPermissionsHandler: PermittedHandlerExport; -export declare type GetPermissionsHooks = { - getPermissionsForOrigin: () => SubjectPermissions; -}; diff --git a/dist/permissions/rpc-methods/getPermissions.js b/dist/permissions/rpc-methods/getPermissions.js deleted file mode 100644 index 74fb13017a..0000000000 --- a/dist/permissions/rpc-methods/getPermissions.js +++ /dev/null @@ -1,38 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.getPermissionsHandler = void 0; -const utils_1 = require("../utils"); -exports.getPermissionsHandler = { - methodNames: [utils_1.MethodNames.getPermissions], - implementation: getPermissionsImplementation, - hookNames: { - getPermissionsForOrigin: true, - }, -}; -/** - * Get Permissions implementation to be used in JsonRpcEngine middleware. - * - * @param _req - The JsonRpcEngine request - unused - * @param res - The JsonRpcEngine result object - * @param _next - JsonRpcEngine next() callback - unused - * @param end - JsonRpcEngine end() callback - * @param options - Method hooks passed to the method implementation - * @param options.getPermissionsForOrigin - The specific method hook needed for this method implementation - * @returns A promise that resolves to nothing - */ -function getPermissionsImplementation(_req, res, _next, end, { getPermissionsForOrigin }) { - return __awaiter(this, void 0, void 0, function* () { - res.result = Object.values(getPermissionsForOrigin() || {}); - return end(); - }); -} -//# sourceMappingURL=getPermissions.js.map \ No newline at end of file diff --git a/dist/permissions/rpc-methods/getPermissions.js.map b/dist/permissions/rpc-methods/getPermissions.js.map deleted file mode 100644 index f3fde5bf04..0000000000 --- a/dist/permissions/rpc-methods/getPermissions.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"getPermissions.js","sourceRoot":"","sources":["../../../src/permissions/rpc-methods/getPermissions.ts"],"names":[],"mappings":";;;;;;;;;;;;AAKA,oCAAuC;AAK1B,QAAA,qBAAqB,GAI9B;IACF,WAAW,EAAE,CAAC,mBAAW,CAAC,cAAc,CAAC;IACzC,cAAc,EAAE,4BAA4B;IAC5C,SAAS,EAAE;QACT,uBAAuB,EAAE,IAAI;KAC9B;CACF,CAAC;AAOF;;;;;;;;;;GAUG;AACH,SAAe,4BAA4B,CACzC,IAAa,EACb,GAAmD,EACnD,KAAc,EACd,GAA6B,EAC7B,EAAE,uBAAuB,EAAuB;;QAEhD,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,uBAAuB,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5D,OAAO,GAAG,EAAE,CAAC;IACf,CAAC;CAAA","sourcesContent":["import type {\n JsonRpcEngineEndCallback,\n PendingJsonRpcResponse,\n PermittedHandlerExport,\n} from '@metamask/types';\nimport { MethodNames } from '../utils';\n\nimport type { PermissionConstraint } from '../Permission';\nimport type { SubjectPermissions } from '../PermissionController';\n\nexport const getPermissionsHandler: PermittedHandlerExport<\n GetPermissionsHooks,\n void,\n PermissionConstraint[]\n> = {\n methodNames: [MethodNames.getPermissions],\n implementation: getPermissionsImplementation,\n hookNames: {\n getPermissionsForOrigin: true,\n },\n};\n\nexport type GetPermissionsHooks = {\n // This must be bound to the requesting origin.\n getPermissionsForOrigin: () => SubjectPermissions;\n};\n\n/**\n * Get Permissions implementation to be used in JsonRpcEngine middleware.\n *\n * @param _req - The JsonRpcEngine request - unused\n * @param res - The JsonRpcEngine result object\n * @param _next - JsonRpcEngine next() callback - unused\n * @param end - JsonRpcEngine end() callback\n * @param options - Method hooks passed to the method implementation\n * @param options.getPermissionsForOrigin - The specific method hook needed for this method implementation\n * @returns A promise that resolves to nothing\n */\nasync function getPermissionsImplementation(\n _req: unknown,\n res: PendingJsonRpcResponse,\n _next: unknown,\n end: JsonRpcEngineEndCallback,\n { getPermissionsForOrigin }: GetPermissionsHooks,\n): Promise {\n res.result = Object.values(getPermissionsForOrigin() || {});\n return end();\n}\n"]} \ No newline at end of file diff --git a/dist/permissions/rpc-methods/index.d.ts b/dist/permissions/rpc-methods/index.d.ts deleted file mode 100644 index 940377921b..0000000000 --- a/dist/permissions/rpc-methods/index.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { RequestPermissionsHooks } from './requestPermissions'; -import { GetPermissionsHooks } from './getPermissions'; -export declare type PermittedRpcMethodHooks = RequestPermissionsHooks & GetPermissionsHooks; -export declare const handlers: (import("@metamask/types").PermittedHandlerExport | import("@metamask/types").PermittedHandlerExport)[]; diff --git a/dist/permissions/rpc-methods/index.js b/dist/permissions/rpc-methods/index.js deleted file mode 100644 index 7a1dbd3f51..0000000000 --- a/dist/permissions/rpc-methods/index.js +++ /dev/null @@ -1,7 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.handlers = void 0; -const requestPermissions_1 = require("./requestPermissions"); -const getPermissions_1 = require("./getPermissions"); -exports.handlers = [requestPermissions_1.requestPermissionsHandler, getPermissions_1.getPermissionsHandler]; -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/permissions/rpc-methods/index.js.map b/dist/permissions/rpc-methods/index.js.map deleted file mode 100644 index b4620208b1..0000000000 --- a/dist/permissions/rpc-methods/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/permissions/rpc-methods/index.ts"],"names":[],"mappings":";;;AAAA,6DAG8B;AAC9B,qDAA8E;AAKjE,QAAA,QAAQ,GAAG,CAAC,8CAAyB,EAAE,sCAAqB,CAAC,CAAC","sourcesContent":["import {\n requestPermissionsHandler,\n RequestPermissionsHooks,\n} from './requestPermissions';\nimport { getPermissionsHandler, GetPermissionsHooks } from './getPermissions';\n\nexport type PermittedRpcMethodHooks = RequestPermissionsHooks &\n GetPermissionsHooks;\n\nexport const handlers = [requestPermissionsHandler, getPermissionsHandler];\n"]} \ No newline at end of file diff --git a/dist/permissions/rpc-methods/requestPermissions.d.ts b/dist/permissions/rpc-methods/requestPermissions.d.ts deleted file mode 100644 index 9707420e8f..0000000000 --- a/dist/permissions/rpc-methods/requestPermissions.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { PermittedHandlerExport } from '@metamask/types'; -import type { PermissionConstraint, RequestedPermissions } from '../Permission'; -export declare const requestPermissionsHandler: PermittedHandlerExport; -declare type RequestPermissions = (requestedPermissions: RequestedPermissions, id: string) => Promise<[ - Record, - { - id: string; - origin: string; - } -]>; -export declare type RequestPermissionsHooks = { - requestPermissionsForOrigin: RequestPermissions; -}; -export {}; diff --git a/dist/permissions/rpc-methods/requestPermissions.js b/dist/permissions/rpc-methods/requestPermissions.js deleted file mode 100644 index e8648fcbf6..0000000000 --- a/dist/permissions/rpc-methods/requestPermissions.js +++ /dev/null @@ -1,55 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.requestPermissionsHandler = void 0; -const eth_rpc_errors_1 = require("eth-rpc-errors"); -const utils_1 = require("../utils"); -const errors_1 = require("../errors"); -const util_1 = require("../../util"); -exports.requestPermissionsHandler = { - methodNames: [utils_1.MethodNames.requestPermissions], - implementation: requestPermissionsImplementation, - hookNames: { - requestPermissionsForOrigin: true, - }, -}; -/** - * Request Permissions implementation to be used in JsonRpcEngine middleware. - * - * @param req - The JsonRpcEngine request - * @param res - The JsonRpcEngine result object - * @param _next - JsonRpcEngine next() callback - unused - * @param end - JsonRpcEngine end() callback - * @param options - Method hooks passed to the method implementation - * @param options.requestPermissionsForOrigin - The specific method hook needed for this method implementation - * @returns A promise that resolves to nothing - */ -function requestPermissionsImplementation(req, res, _next, end, { requestPermissionsForOrigin }) { - return __awaiter(this, void 0, void 0, function* () { - const { id, params } = req; - if ((typeof id !== 'number' && typeof id !== 'string') || - (typeof id === 'string' && !id)) { - return end(eth_rpc_errors_1.ethErrors.rpc.invalidRequest({ - message: 'Invalid request: Must specify a valid id.', - data: { request: req }, - })); - } - if (!Array.isArray(params) || !(0, util_1.isPlainObject)(params[0])) { - return end((0, errors_1.invalidParams)({ data: { request: req } })); - } - const [requestedPermissions] = params; - const [grantedPermissions] = yield requestPermissionsForOrigin(requestedPermissions, String(id)); - // `wallet_requestPermission` is specified to return an array. - res.result = Object.values(grantedPermissions); - return end(); - }); -} -//# sourceMappingURL=requestPermissions.js.map \ No newline at end of file diff --git a/dist/permissions/rpc-methods/requestPermissions.js.map b/dist/permissions/rpc-methods/requestPermissions.js.map deleted file mode 100644 index 97bad458d6..0000000000 --- a/dist/permissions/rpc-methods/requestPermissions.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"requestPermissions.js","sourceRoot":"","sources":["../../../src/permissions/rpc-methods/requestPermissions.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,mDAA2C;AAO3C,oCAAuC;AAEvC,sCAA0C;AAE1C,qCAA2C;AAE9B,QAAA,yBAAyB,GAIlC;IACF,WAAW,EAAE,CAAC,mBAAW,CAAC,kBAAkB,CAAC;IAC7C,cAAc,EAAE,gCAAgC;IAChD,SAAS,EAAE;QACT,2BAA2B,EAAE,IAAI;KAClC;CACF,CAAC;AAaF;;;;;;;;;;GAUG;AACH,SAAe,gCAAgC,CAC7C,GAA2C,EAC3C,GAAmD,EACnD,KAAc,EACd,GAA6B,EAC7B,EAAE,2BAA2B,EAA2B;;QAExD,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;QAE3B,IACE,CAAC,OAAO,EAAE,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ,CAAC;YAClD,CAAC,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,EAAE,CAAC,EAC/B;YACA,OAAO,GAAG,CACR,0BAAS,CAAC,GAAG,CAAC,cAAc,CAAC;gBAC3B,OAAO,EAAE,2CAA2C;gBACpD,IAAI,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE;aACvB,CAAC,CACH,CAAC;SACH;QAED,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAA,oBAAa,EAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE;YACvD,OAAO,GAAG,CAAC,IAAA,sBAAa,EAAC,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;SACvD;QAED,MAAM,CAAC,oBAAoB,CAAC,GAAG,MAAM,CAAC;QACtC,MAAM,CAAC,kBAAkB,CAAC,GAAG,MAAM,2BAA2B,CAC5D,oBAAoB,EACpB,MAAM,CAAC,EAAE,CAAC,CACX,CAAC;QAEF,8DAA8D;QAC9D,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC;QAC/C,OAAO,GAAG,EAAE,CAAC;IACf,CAAC;CAAA","sourcesContent":["import { ethErrors } from 'eth-rpc-errors';\nimport type {\n JsonRpcEngineEndCallback,\n JsonRpcRequest,\n PendingJsonRpcResponse,\n PermittedHandlerExport,\n} from '@metamask/types';\nimport { MethodNames } from '../utils';\n\nimport { invalidParams } from '../errors';\nimport type { PermissionConstraint, RequestedPermissions } from '../Permission';\nimport { isPlainObject } from '../../util';\n\nexport const requestPermissionsHandler: PermittedHandlerExport<\n RequestPermissionsHooks,\n [RequestedPermissions],\n PermissionConstraint[]\n> = {\n methodNames: [MethodNames.requestPermissions],\n implementation: requestPermissionsImplementation,\n hookNames: {\n requestPermissionsForOrigin: true,\n },\n};\n\ntype RequestPermissions = (\n requestedPermissions: RequestedPermissions,\n id: string,\n) => Promise<\n [Record, { id: string; origin: string }]\n>;\n\nexport type RequestPermissionsHooks = {\n requestPermissionsForOrigin: RequestPermissions;\n};\n\n/**\n * Request Permissions implementation to be used in JsonRpcEngine middleware.\n *\n * @param req - The JsonRpcEngine request\n * @param res - The JsonRpcEngine result object\n * @param _next - JsonRpcEngine next() callback - unused\n * @param end - JsonRpcEngine end() callback\n * @param options - Method hooks passed to the method implementation\n * @param options.requestPermissionsForOrigin - The specific method hook needed for this method implementation\n * @returns A promise that resolves to nothing\n */\nasync function requestPermissionsImplementation(\n req: JsonRpcRequest<[RequestedPermissions]>,\n res: PendingJsonRpcResponse,\n _next: unknown,\n end: JsonRpcEngineEndCallback,\n { requestPermissionsForOrigin }: RequestPermissionsHooks,\n): Promise {\n const { id, params } = req;\n\n if (\n (typeof id !== 'number' && typeof id !== 'string') ||\n (typeof id === 'string' && !id)\n ) {\n return end(\n ethErrors.rpc.invalidRequest({\n message: 'Invalid request: Must specify a valid id.',\n data: { request: req },\n }),\n );\n }\n\n if (!Array.isArray(params) || !isPlainObject(params[0])) {\n return end(invalidParams({ data: { request: req } }));\n }\n\n const [requestedPermissions] = params;\n const [grantedPermissions] = await requestPermissionsForOrigin(\n requestedPermissions,\n String(id),\n );\n\n // `wallet_requestPermission` is specified to return an array.\n res.result = Object.values(grantedPermissions);\n return end();\n}\n"]} \ No newline at end of file diff --git a/dist/permissions/utils.d.ts b/dist/permissions/utils.d.ts deleted file mode 100644 index 91f3fdf79a..0000000000 --- a/dist/permissions/utils.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { CaveatSpecificationConstraint, CaveatSpecificationMap } from './Caveat'; -import { PermissionSpecificationConstraint, PermissionSpecificationMap } from './Permission'; -export declare enum MethodNames { - requestPermissions = "wallet_requestPermissions", - getPermissions = "wallet_getPermissions" -} -/** - * Utility type for extracting a union of all individual caveat or permission - * specification types from a {@link CaveatSpecificationMap} or - * {@link PermissionSpecificationMap}. - * - * @template SpecificationsMap - The caveat or permission specifications map - * whose specification type union to extract. - */ -export declare type ExtractSpecifications | PermissionSpecificationMap> = SpecificationsMap[keyof SpecificationsMap]; diff --git a/dist/permissions/utils.js b/dist/permissions/utils.js deleted file mode 100644 index 4ba6e95cbe..0000000000 --- a/dist/permissions/utils.js +++ /dev/null @@ -1,9 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.MethodNames = void 0; -var MethodNames; -(function (MethodNames) { - MethodNames["requestPermissions"] = "wallet_requestPermissions"; - MethodNames["getPermissions"] = "wallet_getPermissions"; -})(MethodNames = exports.MethodNames || (exports.MethodNames = {})); -//# sourceMappingURL=utils.js.map \ No newline at end of file diff --git a/dist/permissions/utils.js.map b/dist/permissions/utils.js.map deleted file mode 100644 index c0e270558d..0000000000 --- a/dist/permissions/utils.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/permissions/utils.ts"],"names":[],"mappings":";;;AASA,IAAY,WAGX;AAHD,WAAY,WAAW;IACrB,+DAAgD,CAAA;IAChD,uDAAwC,CAAA;AAC1C,CAAC,EAHW,WAAW,GAAX,mBAAW,KAAX,mBAAW,QAGtB","sourcesContent":["import {\n CaveatSpecificationConstraint,\n CaveatSpecificationMap,\n} from './Caveat';\nimport {\n PermissionSpecificationConstraint,\n PermissionSpecificationMap,\n} from './Permission';\n\nexport enum MethodNames {\n requestPermissions = 'wallet_requestPermissions',\n getPermissions = 'wallet_getPermissions',\n}\n\n/**\n * Utility type for extracting a union of all individual caveat or permission\n * specification types from a {@link CaveatSpecificationMap} or\n * {@link PermissionSpecificationMap}.\n *\n * @template SpecificationsMap - The caveat or permission specifications map\n * whose specification type union to extract.\n */\nexport type ExtractSpecifications<\n SpecificationsMap extends\n | CaveatSpecificationMap\n | PermissionSpecificationMap,\n> = SpecificationsMap[keyof SpecificationsMap];\n"]} \ No newline at end of file diff --git a/dist/ratelimit/RateLimitController.d.ts b/dist/ratelimit/RateLimitController.d.ts deleted file mode 100644 index bf07da598d..0000000000 --- a/dist/ratelimit/RateLimitController.d.ts +++ /dev/null @@ -1,82 +0,0 @@ -import type { Patch } from 'immer'; -import { BaseController } from '../BaseControllerV2'; -import type { RestrictedControllerMessenger } from '../ControllerMessenger'; -/** - * @type RateLimitState - * @property requests - Object containing number of requests in a given interval for each origin and api type combination - */ -export declare type RateLimitState any>> = { - requests: Record>; -}; -declare const name = "RateLimitController"; -export declare type RateLimitStateChange any>> = { - type: `${typeof name}:stateChange`; - payload: [RateLimitState, Patch[]]; -}; -export declare type GetRateLimitState any>> = { - type: `${typeof name}:getState`; - handler: () => RateLimitState; -}; -export declare type CallApi any>> = { - type: `${typeof name}:call`; - handler: RateLimitController['call']; -}; -export declare type RateLimitControllerActions any>> = GetRateLimitState | CallApi; -export declare type RateLimitMessenger any>> = RestrictedControllerMessenger, RateLimitStateChange, never, never>; -/** - * Controller with logic for rate-limiting API endpoints per requesting origin. - */ -export declare class RateLimitController any>> extends BaseController, RateLimitMessenger> { - private implementations; - private rateLimitTimeout; - private rateLimitCount; - /** - * Creates a RateLimitController instance. - * - * @param options - Constructor options. - * @param options.messenger - A reference to the messaging system. - * @param options.state - Initial state to set on this controller. - * @param options.implementations - Mapping from API type to API implementation. - * @param options.rateLimitTimeout - The time window in which the rate limit is applied (in ms). - * @param options.rateLimitCount - The amount of calls an origin can make in the rate limit time window. - */ - constructor({ rateLimitTimeout, rateLimitCount, messenger, state, implementations, }: { - rateLimitTimeout?: number; - rateLimitCount?: number; - messenger: RateLimitMessenger; - state?: Partial>; - implementations: RateLimitedApis; - }); - /** - * Calls an API if the requesting origin is not rate-limited. - * - * @param origin - The requesting origin. - * @param type - The type of API call to make. - * @param args - Arguments for the API call. - * @returns `false` if rate-limited, and `true` otherwise. - */ - call(origin: string, type: ApiType, ...args: Parameters): Promise>; - /** - * Checks whether an origin is rate limited for the a specific API. - * - * @param api - The API the origin is trying to access. - * @param origin - The origin trying to access the API. - * @returns `true` if rate-limited, and `false` otherwise. - */ - private isRateLimited; - /** - * Records that an origin has made a request to call an API, for rate-limiting purposes. - * - * @param api - The API the origin is trying to access. - * @param origin - The origin trying to access the API. - */ - private recordRequest; - /** - * Resets the request count for a given origin and API combination, for rate-limiting purposes. - * - * @param api - The API in question. - * @param origin - The origin in question. - */ - private resetRequestCount; -} -export {}; diff --git a/dist/ratelimit/RateLimitController.js b/dist/ratelimit/RateLimitController.js deleted file mode 100644 index 79f874801a..0000000000 --- a/dist/ratelimit/RateLimitController.js +++ /dev/null @@ -1,110 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.RateLimitController = void 0; -const eth_rpc_errors_1 = require("eth-rpc-errors"); -const BaseControllerV2_1 = require("../BaseControllerV2"); -const name = 'RateLimitController'; -const metadata = { - requests: { persist: false, anonymous: false }, -}; -/** - * Controller with logic for rate-limiting API endpoints per requesting origin. - */ -class RateLimitController extends BaseControllerV2_1.BaseController { - /** - * Creates a RateLimitController instance. - * - * @param options - Constructor options. - * @param options.messenger - A reference to the messaging system. - * @param options.state - Initial state to set on this controller. - * @param options.implementations - Mapping from API type to API implementation. - * @param options.rateLimitTimeout - The time window in which the rate limit is applied (in ms). - * @param options.rateLimitCount - The amount of calls an origin can make in the rate limit time window. - */ - constructor({ rateLimitTimeout = 5000, rateLimitCount = 1, messenger, state, implementations, }) { - const defaultState = { - requests: Object.keys(implementations).reduce((acc, key) => (Object.assign(Object.assign({}, acc), { [key]: {} })), {}), - }; - super({ - name, - metadata, - messenger, - state: Object.assign(Object.assign({}, defaultState), state), - }); - this.implementations = implementations; - this.rateLimitTimeout = rateLimitTimeout; - this.rateLimitCount = rateLimitCount; - this.messagingSystem.registerActionHandler(`${name}:call`, ((origin, type, ...args) => this.call(origin, type, ...args))); - } - /** - * Calls an API if the requesting origin is not rate-limited. - * - * @param origin - The requesting origin. - * @param type - The type of API call to make. - * @param args - Arguments for the API call. - * @returns `false` if rate-limited, and `true` otherwise. - */ - call(origin, type, ...args) { - return __awaiter(this, void 0, void 0, function* () { - if (this.isRateLimited(type, origin)) { - throw eth_rpc_errors_1.ethErrors.rpc.limitExceeded({ - message: `"${type}" is currently rate-limited. Please try again later.`, - }); - } - this.recordRequest(type, origin); - const implementation = this.implementations[type]; - if (!implementation) { - throw new Error('Invalid api type'); - } - return implementation(...args); - }); - } - /** - * Checks whether an origin is rate limited for the a specific API. - * - * @param api - The API the origin is trying to access. - * @param origin - The origin trying to access the API. - * @returns `true` if rate-limited, and `false` otherwise. - */ - isRateLimited(api, origin) { - return this.state.requests[api][origin] >= this.rateLimitCount; - } - /** - * Records that an origin has made a request to call an API, for rate-limiting purposes. - * - * @param api - The API the origin is trying to access. - * @param origin - The origin trying to access the API. - */ - recordRequest(api, origin) { - this.update((state) => { - var _a; - const previous = (_a = state.requests[api][origin]) !== null && _a !== void 0 ? _a : 0; - state.requests[api][origin] = previous + 1; - if (previous === 0) { - setTimeout(() => this.resetRequestCount(api, origin), this.rateLimitTimeout); - } - }); - } - /** - * Resets the request count for a given origin and API combination, for rate-limiting purposes. - * - * @param api - The API in question. - * @param origin - The origin in question. - */ - resetRequestCount(api, origin) { - this.update((state) => { - state.requests[api][origin] = 0; - }); - } -} -exports.RateLimitController = RateLimitController; -//# sourceMappingURL=RateLimitController.js.map \ No newline at end of file diff --git a/dist/ratelimit/RateLimitController.js.map b/dist/ratelimit/RateLimitController.js.map deleted file mode 100644 index fb0a08d793..0000000000 --- a/dist/ratelimit/RateLimitController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"RateLimitController.js","sourceRoot":"","sources":["../../src/ratelimit/RateLimitController.ts"],"names":[],"mappings":"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","sourcesContent":["import { ethErrors } from 'eth-rpc-errors';\nimport type { Patch } from 'immer';\n\nimport { BaseController } from '../BaseControllerV2';\n\nimport type { RestrictedControllerMessenger } from '../ControllerMessenger';\n\n/**\n * @type RateLimitState\n * @property requests - Object containing number of requests in a given interval for each origin and api type combination\n */\nexport type RateLimitState<\n RateLimitedApis extends Record any>,\n> = {\n requests: Record>;\n};\n\nconst name = 'RateLimitController';\n\nexport type RateLimitStateChange<\n RateLimitedApis extends Record any>,\n> = {\n type: `${typeof name}:stateChange`;\n payload: [RateLimitState, Patch[]];\n};\n\nexport type GetRateLimitState<\n RateLimitedApis extends Record any>,\n> = {\n type: `${typeof name}:getState`;\n handler: () => RateLimitState;\n};\n\nexport type CallApi<\n RateLimitedApis extends Record any>,\n> = {\n type: `${typeof name}:call`;\n handler: RateLimitController['call'];\n};\n\nexport type RateLimitControllerActions<\n RateLimitedApis extends Record any>,\n> = GetRateLimitState | CallApi;\n\nexport type RateLimitMessenger<\n RateLimitedApis extends Record any>,\n> = RestrictedControllerMessenger<\n typeof name,\n RateLimitControllerActions,\n RateLimitStateChange,\n never,\n never\n>;\n\nconst metadata = {\n requests: { persist: false, anonymous: false },\n};\n\n/**\n * Controller with logic for rate-limiting API endpoints per requesting origin.\n */\nexport class RateLimitController<\n RateLimitedApis extends Record any>,\n> extends BaseController<\n typeof name,\n RateLimitState,\n RateLimitMessenger\n> {\n private implementations;\n\n private rateLimitTimeout;\n\n private rateLimitCount;\n\n /**\n * Creates a RateLimitController instance.\n *\n * @param options - Constructor options.\n * @param options.messenger - A reference to the messaging system.\n * @param options.state - Initial state to set on this controller.\n * @param options.implementations - Mapping from API type to API implementation.\n * @param options.rateLimitTimeout - The time window in which the rate limit is applied (in ms).\n * @param options.rateLimitCount - The amount of calls an origin can make in the rate limit time window.\n */\n constructor({\n rateLimitTimeout = 5000,\n rateLimitCount = 1,\n messenger,\n state,\n implementations,\n }: {\n rateLimitTimeout?: number;\n rateLimitCount?: number;\n messenger: RateLimitMessenger;\n state?: Partial>;\n implementations: RateLimitedApis;\n }) {\n const defaultState = {\n requests: Object.keys(implementations).reduce(\n (acc, key) => ({ ...acc, [key]: {} }),\n {} as Record>,\n ),\n };\n super({\n name,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n this.implementations = implementations;\n this.rateLimitTimeout = rateLimitTimeout;\n this.rateLimitCount = rateLimitCount;\n\n this.messagingSystem.registerActionHandler(\n `${name}:call` as const,\n ((\n origin: string,\n type: keyof RateLimitedApis,\n ...args: Parameters\n ) => this.call(origin, type, ...args)) as any,\n );\n }\n\n /**\n * Calls an API if the requesting origin is not rate-limited.\n *\n * @param origin - The requesting origin.\n * @param type - The type of API call to make.\n * @param args - Arguments for the API call.\n * @returns `false` if rate-limited, and `true` otherwise.\n */\n async call(\n origin: string,\n type: ApiType,\n ...args: Parameters\n ): Promise> {\n if (this.isRateLimited(type, origin)) {\n throw ethErrors.rpc.limitExceeded({\n message: `\"${type}\" is currently rate-limited. Please try again later.`,\n });\n }\n this.recordRequest(type, origin);\n\n const implementation = this.implementations[type];\n\n if (!implementation) {\n throw new Error('Invalid api type');\n }\n\n return implementation(...args);\n }\n\n /**\n * Checks whether an origin is rate limited for the a specific API.\n *\n * @param api - The API the origin is trying to access.\n * @param origin - The origin trying to access the API.\n * @returns `true` if rate-limited, and `false` otherwise.\n */\n private isRateLimited(api: keyof RateLimitedApis, origin: string) {\n return this.state.requests[api][origin] >= this.rateLimitCount;\n }\n\n /**\n * Records that an origin has made a request to call an API, for rate-limiting purposes.\n *\n * @param api - The API the origin is trying to access.\n * @param origin - The origin trying to access the API.\n */\n private recordRequest(api: keyof RateLimitedApis, origin: string) {\n this.update((state) => {\n const previous = (state as any).requests[api][origin] ?? 0;\n (state as any).requests[api][origin] = previous + 1;\n\n if (previous === 0) {\n setTimeout(\n () => this.resetRequestCount(api, origin),\n this.rateLimitTimeout,\n );\n }\n });\n }\n\n /**\n * Resets the request count for a given origin and API combination, for rate-limiting purposes.\n *\n * @param api - The API in question.\n * @param origin - The origin in question.\n */\n private resetRequestCount(api: keyof RateLimitedApis, origin: string) {\n this.update((state) => {\n (state as any).requests[api][origin] = 0;\n });\n }\n}\n"]} \ No newline at end of file diff --git a/dist/subject-metadata/SubjectMetadataController.d.ts b/dist/subject-metadata/SubjectMetadataController.d.ts deleted file mode 100644 index 9d17cc167c..0000000000 --- a/dist/subject-metadata/SubjectMetadataController.d.ts +++ /dev/null @@ -1,85 +0,0 @@ -import type { Patch } from 'immer'; -import { Json } from '@metamask/types'; -import { BaseController } from '../BaseControllerV2'; -import { RestrictedControllerMessenger } from '../ControllerMessenger'; -import type { PermissionSubjectMetadata, HasPermissions } from '../permissions'; -declare const controllerName = "SubjectMetadataController"; -declare type SubjectOrigin = string; -export declare type SubjectMetadata = PermissionSubjectMetadata & { - [key: string]: Json; - name: string | null; - extensionId: string | null; - iconUrl: string | null; -}; -declare type SubjectMetadataToAdd = PermissionSubjectMetadata & { - name?: string | null; - extensionId?: string | null; - iconUrl?: string | null; -} & Record; -export declare type SubjectMetadataControllerState = { - subjectMetadata: Record; -}; -export declare type GetSubjectMetadataState = { - type: `${typeof controllerName}:getState`; - handler: () => SubjectMetadataControllerState; -}; -export declare type SubjectMetadataControllerActions = GetSubjectMetadataState; -export declare type SubjectMetadataStateChange = { - type: `${typeof controllerName}:stateChange`; - payload: [SubjectMetadataControllerState, Patch[]]; -}; -export declare type SubjectMetadataControllerEvents = SubjectMetadataStateChange; -declare type AllowedActions = HasPermissions; -export declare type SubjectMetadataControllerMessenger = RestrictedControllerMessenger; -declare type SubjectMetadataControllerOptions = { - messenger: SubjectMetadataControllerMessenger; - subjectCacheLimit: number; - state?: Partial; -}; -/** - * A controller for storing metadata associated with permission subjects. More - * or less, a cache. - */ -export declare class SubjectMetadataController extends BaseController { - private subjectCacheLimit; - private subjectsWithoutPermissionsEcounteredSinceStartup; - private subjectHasPermissions; - constructor({ messenger, subjectCacheLimit, state, }: SubjectMetadataControllerOptions); - /** - * Clears the state of this controller. Also resets the cache of subjects - * encountered since startup, so as to not prematurely reach the cache limit. - */ - clearState(): void; - /** - * Stores domain metadata for the given origin (subject). Deletes metadata for - * subjects without permissions in a FIFO manner once more than - * {@link SubjectMetadataController.subjectCacheLimit} distinct origins have - * been added since boot. - * - * In order to prevent a degraded user experience, - * metadata is never deleted for subjects with permissions, since metadata - * cannot yet be requested on demand. - * - * @param metadata - The subject metadata to store. - */ - addSubjectMetadata(metadata: SubjectMetadataToAdd): void; - /** - * Deletes all subjects without permissions from the controller's state. - */ - trimMetadataState(): void; - /** - * Returns a new state object that only includes subjects with permissions. - * This method is static because we want to call it in the constructor, before - * the controller's state is initialized. - * - * @param state - The state object to trim. - * @param hasPermissions - A function that returns a boolean indicating - * whether a particular subject (identified by its origin) has any - * permissions. - * @returns The new state object. If the specified `state` object has no - * subject metadata, the returned object will be equivalent to the default - * state of this controller. - */ - private static getTrimmedState; -} -export {}; diff --git a/dist/subject-metadata/SubjectMetadataController.js b/dist/subject-metadata/SubjectMetadataController.js deleted file mode 100644 index a5cbc9d619..0000000000 --- a/dist/subject-metadata/SubjectMetadataController.js +++ /dev/null @@ -1,117 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.SubjectMetadataController = void 0; -const BaseControllerV2_1 = require("../BaseControllerV2"); -const controllerName = 'SubjectMetadataController'; -const stateMetadata = { - subjectMetadata: { persist: true, anonymous: false }, -}; -const defaultState = { - subjectMetadata: {}, -}; -/** - * A controller for storing metadata associated with permission subjects. More - * or less, a cache. - */ -class SubjectMetadataController extends BaseControllerV2_1.BaseController { - constructor({ messenger, subjectCacheLimit, state = {}, }) { - if (!Number.isInteger(subjectCacheLimit) || subjectCacheLimit < 1) { - throw new Error(`subjectCacheLimit must be a positive integer. Received: "${subjectCacheLimit}"`); - } - const hasPermissions = (origin) => { - return messenger.call('PermissionController:hasPermissions', origin); - }; - super({ - name: controllerName, - metadata: stateMetadata, - messenger, - state: Object.assign({}, SubjectMetadataController.getTrimmedState(state, hasPermissions)), - }); - this.subjectHasPermissions = hasPermissions; - this.subjectCacheLimit = subjectCacheLimit; - this.subjectsWithoutPermissionsEcounteredSinceStartup = new Set(); - } - /** - * Clears the state of this controller. Also resets the cache of subjects - * encountered since startup, so as to not prematurely reach the cache limit. - */ - clearState() { - this.subjectsWithoutPermissionsEcounteredSinceStartup.clear(); - this.update((_draftState) => { - return Object.assign({}, defaultState); - }); - } - /** - * Stores domain metadata for the given origin (subject). Deletes metadata for - * subjects without permissions in a FIFO manner once more than - * {@link SubjectMetadataController.subjectCacheLimit} distinct origins have - * been added since boot. - * - * In order to prevent a degraded user experience, - * metadata is never deleted for subjects with permissions, since metadata - * cannot yet be requested on demand. - * - * @param metadata - The subject metadata to store. - */ - addSubjectMetadata(metadata) { - const { origin } = metadata; - const newMetadata = Object.assign(Object.assign({}, metadata), { extensionId: metadata.extensionId || null, iconUrl: metadata.iconUrl || null, name: metadata.name || null }); - let originToForget = null; - // We only delete the oldest encountered subject from the cache, again to - // ensure that the user's experience isn't degraded by missing icons etc. - if (this.subjectsWithoutPermissionsEcounteredSinceStartup.size >= - this.subjectCacheLimit) { - const cachedOrigin = this.subjectsWithoutPermissionsEcounteredSinceStartup - .values() - .next().value; - this.subjectsWithoutPermissionsEcounteredSinceStartup.delete(cachedOrigin); - if (!this.subjectHasPermissions(cachedOrigin)) { - originToForget = cachedOrigin; - } - } - this.subjectsWithoutPermissionsEcounteredSinceStartup.add(origin); - this.update((draftState) => { - // Typecast: ts(2589) - draftState.subjectMetadata[origin] = newMetadata; - if (typeof originToForget === 'string') { - delete draftState.subjectMetadata[originToForget]; - } - }); - } - /** - * Deletes all subjects without permissions from the controller's state. - */ - trimMetadataState() { - this.update((draftState) => { - return SubjectMetadataController.getTrimmedState( - // Typecast: ts(2589) - draftState, this.subjectHasPermissions); - }); - } - /** - * Returns a new state object that only includes subjects with permissions. - * This method is static because we want to call it in the constructor, before - * the controller's state is initialized. - * - * @param state - The state object to trim. - * @param hasPermissions - A function that returns a boolean indicating - * whether a particular subject (identified by its origin) has any - * permissions. - * @returns The new state object. If the specified `state` object has no - * subject metadata, the returned object will be equivalent to the default - * state of this controller. - */ - static getTrimmedState(state, hasPermissions) { - const { subjectMetadata = {} } = state; - return { - subjectMetadata: Object.keys(subjectMetadata).reduce((newSubjectMetadata, origin) => { - if (hasPermissions(origin)) { - newSubjectMetadata[origin] = subjectMetadata[origin]; - } - return newSubjectMetadata; - }, {}), - }; - } -} -exports.SubjectMetadataController = SubjectMetadataController; -//# sourceMappingURL=SubjectMetadataController.js.map \ No newline at end of file diff --git a/dist/subject-metadata/SubjectMetadataController.js.map b/dist/subject-metadata/SubjectMetadataController.js.map deleted file mode 100644 index 8e3141607a..0000000000 --- a/dist/subject-metadata/SubjectMetadataController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"SubjectMetadataController.js","sourceRoot":"","sources":["../../src/subject-metadata/SubjectMetadataController.ts"],"names":[],"mappings":";;;AAEA,0DAAqD;AASrD,MAAM,cAAc,GAAG,2BAA2B,CAAC;AAsBnD,MAAM,aAAa,GAAG;IACpB,eAAe,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE;CACrD,CAAC;AAEF,MAAM,YAAY,GAAmC;IACnD,eAAe,EAAE,EAAE;CACpB,CAAC;AAgCF;;;GAGG;AACH,MAAa,yBAA0B,SAAQ,iCAI9C;IAOC,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,KAAK,GAAG,EAAE,GACuB;QACjC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,iBAAiB,CAAC,IAAI,iBAAiB,GAAG,CAAC,EAAE;YACjE,MAAM,IAAI,KAAK,CACb,4DAA4D,iBAAiB,GAAG,CACjF,CAAC;SACH;QAED,MAAM,cAAc,GAAG,CAAC,MAAc,EAAE,EAAE;YACxC,OAAO,SAAS,CAAC,IAAI,CAAC,qCAAqC,EAAE,MAAM,CAAC,CAAC;QACvE,CAAC,CAAC;QAEF,KAAK,CAAC;YACJ,IAAI,EAAE,cAAc;YACpB,QAAQ,EAAE,aAAa;YACvB,SAAS;YACT,KAAK,oBACA,yBAAyB,CAAC,eAAe,CAAC,KAAK,EAAE,cAAc,CAAC,CACpE;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,qBAAqB,GAAG,cAAc,CAAC;QAC5C,IAAI,CAAC,iBAAiB,GAAG,iBAAiB,CAAC;QAC3C,IAAI,CAAC,gDAAgD,GAAG,IAAI,GAAG,EAAE,CAAC;IACpE,CAAC;IAED;;;OAGG;IACH,UAAU;QACR,IAAI,CAAC,gDAAgD,CAAC,KAAK,EAAE,CAAC;QAC9D,IAAI,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,EAAE;YAC1B,yBAAY,YAAY,EAAG;QAC7B,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;;;OAWG;IACH,kBAAkB,CAAC,QAA8B;QAC/C,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC;QAC5B,MAAM,WAAW,mCACZ,QAAQ,KACX,WAAW,EAAE,QAAQ,CAAC,WAAW,IAAI,IAAI,EACzC,OAAO,EAAE,QAAQ,CAAC,OAAO,IAAI,IAAI,EACjC,IAAI,EAAE,QAAQ,CAAC,IAAI,IAAI,IAAI,GAC5B,CAAC;QAEF,IAAI,cAAc,GAAkB,IAAI,CAAC;QACzC,yEAAyE;QACzE,yEAAyE;QACzE,IACE,IAAI,CAAC,gDAAgD,CAAC,IAAI;YAC1D,IAAI,CAAC,iBAAiB,EACtB;YACA,MAAM,YAAY,GAAG,IAAI,CAAC,gDAAgD;iBACvE,MAAM,EAAE;iBACR,IAAI,EAAE,CAAC,KAAK,CAAC;YAEhB,IAAI,CAAC,gDAAgD,CAAC,MAAM,CAC1D,YAAY,CACb,CAAC;YAEF,IAAI,CAAC,IAAI,CAAC,qBAAqB,CAAC,YAAY,CAAC,EAAE;gBAC7C,cAAc,GAAG,YAAY,CAAC;aAC/B;SACF;QAED,IAAI,CAAC,gDAAgD,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAElE,IAAI,CAAC,MAAM,CAAC,CAAC,UAAU,EAAE,EAAE;YACzB,qBAAqB;YACrB,UAAU,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,WAAkB,CAAC;YACxD,IAAI,OAAO,cAAc,KAAK,QAAQ,EAAE;gBACtC,OAAO,UAAU,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC;aACnD;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,iBAAiB;QACf,IAAI,CAAC,MAAM,CAAC,CAAC,UAAU,EAAE,EAAE;YACzB,OAAO,yBAAyB,CAAC,eAAe;YAC9C,qBAAqB;YACrB,UAAiB,EACjB,IAAI,CAAC,qBAAqB,CAC3B,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;;;;OAYG;IACK,MAAM,CAAC,eAAe,CAC5B,KAA8C,EAC9C,cAAkE;QAElE,MAAM,EAAE,eAAe,GAAG,EAAE,EAAE,GAAG,KAAK,CAAC;QAEvC,OAAO;YACL,eAAe,EAAE,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,MAAM,CAElD,CAAC,kBAAkB,EAAE,MAAM,EAAE,EAAE;gBAC/B,IAAI,cAAc,CAAC,MAAM,CAAC,EAAE;oBAC1B,kBAAkB,CAAC,MAAM,CAAC,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;iBACtD;gBACD,OAAO,kBAAkB,CAAC;YAC5B,CAAC,EAAE,EAAE,CAAC;SACP,CAAC;IACJ,CAAC;CACF;AAlJD,8DAkJC","sourcesContent":["import type { Patch } from 'immer';\nimport { Json } from '@metamask/types';\nimport { BaseController } from '../BaseControllerV2';\nimport { RestrictedControllerMessenger } from '../ControllerMessenger';\n\nimport type {\n GenericPermissionController,\n PermissionSubjectMetadata,\n HasPermissions,\n} from '../permissions';\n\nconst controllerName = 'SubjectMetadataController';\n\ntype SubjectOrigin = string;\n\nexport type SubjectMetadata = PermissionSubjectMetadata & {\n [key: string]: Json;\n // TODO:TS4.4 make optional\n name: string | null;\n extensionId: string | null;\n iconUrl: string | null;\n};\n\ntype SubjectMetadataToAdd = PermissionSubjectMetadata & {\n name?: string | null;\n extensionId?: string | null;\n iconUrl?: string | null;\n} & Record;\n\nexport type SubjectMetadataControllerState = {\n subjectMetadata: Record;\n};\n\nconst stateMetadata = {\n subjectMetadata: { persist: true, anonymous: false },\n};\n\nconst defaultState: SubjectMetadataControllerState = {\n subjectMetadata: {},\n};\n\nexport type GetSubjectMetadataState = {\n type: `${typeof controllerName}:getState`;\n handler: () => SubjectMetadataControllerState;\n};\n\nexport type SubjectMetadataControllerActions = GetSubjectMetadataState;\n\nexport type SubjectMetadataStateChange = {\n type: `${typeof controllerName}:stateChange`;\n payload: [SubjectMetadataControllerState, Patch[]];\n};\n\nexport type SubjectMetadataControllerEvents = SubjectMetadataStateChange;\n\ntype AllowedActions = HasPermissions;\n\nexport type SubjectMetadataControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n SubjectMetadataControllerActions | AllowedActions,\n SubjectMetadataControllerEvents,\n AllowedActions['type'],\n never\n>;\n\ntype SubjectMetadataControllerOptions = {\n messenger: SubjectMetadataControllerMessenger;\n subjectCacheLimit: number;\n state?: Partial;\n};\n\n/**\n * A controller for storing metadata associated with permission subjects. More\n * or less, a cache.\n */\nexport class SubjectMetadataController extends BaseController<\n typeof controllerName,\n SubjectMetadataControllerState,\n SubjectMetadataControllerMessenger\n> {\n private subjectCacheLimit: number;\n\n private subjectsWithoutPermissionsEcounteredSinceStartup: Set;\n\n private subjectHasPermissions: GenericPermissionController['hasPermissions'];\n\n constructor({\n messenger,\n subjectCacheLimit,\n state = {},\n }: SubjectMetadataControllerOptions) {\n if (!Number.isInteger(subjectCacheLimit) || subjectCacheLimit < 1) {\n throw new Error(\n `subjectCacheLimit must be a positive integer. Received: \"${subjectCacheLimit}\"`,\n );\n }\n\n const hasPermissions = (origin: string) => {\n return messenger.call('PermissionController:hasPermissions', origin);\n };\n\n super({\n name: controllerName,\n metadata: stateMetadata,\n messenger,\n state: {\n ...SubjectMetadataController.getTrimmedState(state, hasPermissions),\n },\n });\n\n this.subjectHasPermissions = hasPermissions;\n this.subjectCacheLimit = subjectCacheLimit;\n this.subjectsWithoutPermissionsEcounteredSinceStartup = new Set();\n }\n\n /**\n * Clears the state of this controller. Also resets the cache of subjects\n * encountered since startup, so as to not prematurely reach the cache limit.\n */\n clearState(): void {\n this.subjectsWithoutPermissionsEcounteredSinceStartup.clear();\n this.update((_draftState) => {\n return { ...defaultState };\n });\n }\n\n /**\n * Stores domain metadata for the given origin (subject). Deletes metadata for\n * subjects without permissions in a FIFO manner once more than\n * {@link SubjectMetadataController.subjectCacheLimit} distinct origins have\n * been added since boot.\n *\n * In order to prevent a degraded user experience,\n * metadata is never deleted for subjects with permissions, since metadata\n * cannot yet be requested on demand.\n *\n * @param metadata - The subject metadata to store.\n */\n addSubjectMetadata(metadata: SubjectMetadataToAdd): void {\n const { origin } = metadata;\n const newMetadata: SubjectMetadata = {\n ...metadata,\n extensionId: metadata.extensionId || null,\n iconUrl: metadata.iconUrl || null,\n name: metadata.name || null,\n };\n\n let originToForget: string | null = null;\n // We only delete the oldest encountered subject from the cache, again to\n // ensure that the user's experience isn't degraded by missing icons etc.\n if (\n this.subjectsWithoutPermissionsEcounteredSinceStartup.size >=\n this.subjectCacheLimit\n ) {\n const cachedOrigin = this.subjectsWithoutPermissionsEcounteredSinceStartup\n .values()\n .next().value;\n\n this.subjectsWithoutPermissionsEcounteredSinceStartup.delete(\n cachedOrigin,\n );\n\n if (!this.subjectHasPermissions(cachedOrigin)) {\n originToForget = cachedOrigin;\n }\n }\n\n this.subjectsWithoutPermissionsEcounteredSinceStartup.add(origin);\n\n this.update((draftState) => {\n // Typecast: ts(2589)\n draftState.subjectMetadata[origin] = newMetadata as any;\n if (typeof originToForget === 'string') {\n delete draftState.subjectMetadata[originToForget];\n }\n });\n }\n\n /**\n * Deletes all subjects without permissions from the controller's state.\n */\n trimMetadataState(): void {\n this.update((draftState) => {\n return SubjectMetadataController.getTrimmedState(\n // Typecast: ts(2589)\n draftState as any,\n this.subjectHasPermissions,\n );\n });\n }\n\n /**\n * Returns a new state object that only includes subjects with permissions.\n * This method is static because we want to call it in the constructor, before\n * the controller's state is initialized.\n *\n * @param state - The state object to trim.\n * @param hasPermissions - A function that returns a boolean indicating\n * whether a particular subject (identified by its origin) has any\n * permissions.\n * @returns The new state object. If the specified `state` object has no\n * subject metadata, the returned object will be equivalent to the default\n * state of this controller.\n */\n private static getTrimmedState(\n state: Partial,\n hasPermissions: SubjectMetadataController['subjectHasPermissions'],\n ): SubjectMetadataControllerState {\n const { subjectMetadata = {} } = state;\n\n return {\n subjectMetadata: Object.keys(subjectMetadata).reduce<\n Record\n >((newSubjectMetadata, origin) => {\n if (hasPermissions(origin)) {\n newSubjectMetadata[origin] = subjectMetadata[origin];\n }\n return newSubjectMetadata;\n }, {}),\n };\n }\n}\n"]} \ No newline at end of file diff --git a/dist/subject-metadata/index.d.ts b/dist/subject-metadata/index.d.ts deleted file mode 100644 index 3b0bcaa14b..0000000000 --- a/dist/subject-metadata/index.d.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './SubjectMetadataController'; diff --git a/dist/subject-metadata/index.js b/dist/subject-metadata/index.js deleted file mode 100644 index b02817a57b..0000000000 --- a/dist/subject-metadata/index.js +++ /dev/null @@ -1,18 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __exportStar = (this && this.__exportStar) || function(m, exports) { - for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -__exportStar(require("./SubjectMetadataController"), exports); -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/subject-metadata/index.js.map b/dist/subject-metadata/index.js.map deleted file mode 100644 index ef4fa2f3f1..0000000000 --- a/dist/subject-metadata/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/subject-metadata/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,8DAA4C","sourcesContent":["export * from './SubjectMetadataController';\n"]} \ No newline at end of file diff --git a/dist/third-party/EnsController.d.ts b/dist/third-party/EnsController.d.ts deleted file mode 100644 index e92c9cfd2c..0000000000 --- a/dist/third-party/EnsController.d.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { BaseController, BaseConfig, BaseState } from '../BaseController'; -/** - * @type EnsEntry - * - * ENS entry representation - * @property chainId - Id of the associated chain - * @property ensName - The ENS name - * @property address - Hex address with the ENS name, or null - */ -export interface EnsEntry { - chainId: string; - ensName: string; - address: string | null; -} -/** - * @type EnsState - * - * ENS controller state - * @property ensEntries - Object of ENS entry objects - */ -export interface EnsState extends BaseState { - ensEntries: { - [chainId: string]: { - [ensName: string]: EnsEntry; - }; - }; -} -/** - * Controller that manages a list ENS names and their resolved addresses - * by chainId. A null address indicates an unresolved ENS name. - */ -export declare class EnsController extends BaseController { - /** - * Name of this controller used during composition - */ - name: string; - /** - * Creates an EnsController instance. - * - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor(config?: Partial, state?: Partial); - /** - * Remove all chain Ids and ENS entries from state. - */ - clear(): void; - /** - * Delete an ENS entry. - * - * @param chainId - Parent chain of the ENS entry to delete. - * @param ensName - Name of the ENS entry to delete. - * @returns Boolean indicating if the entry was deleted. - */ - delete(chainId: string, ensName: string): boolean; - /** - * Retrieve a DNS entry. - * - * @param chainId - Parent chain of the ENS entry to retrieve. - * @param ensName - Name of the ENS entry to retrieve. - * @returns The EnsEntry or null if it does not exist. - */ - get(chainId: string, ensName: string): EnsEntry | null; - /** - * Add or update an ENS entry by chainId and ensName. - * - * A null address indicates that the ENS name does not resolve. - * - * @param chainId - Id of the associated chain. - * @param ensName - The ENS name. - * @param address - Associated address (or null) to add or update. - * @returns Boolean indicating if the entry was set. - */ - set(chainId: string, ensName: string, address: string | null): boolean; -} -export default EnsController; diff --git a/dist/third-party/EnsController.js b/dist/third-party/EnsController.js deleted file mode 100644 index d2f68682c3..0000000000 --- a/dist/third-party/EnsController.js +++ /dev/null @@ -1,108 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.EnsController = void 0; -const BaseController_1 = require("../BaseController"); -const util_1 = require("../util"); -/** - * Controller that manages a list ENS names and their resolved addresses - * by chainId. A null address indicates an unresolved ENS name. - */ -class EnsController extends BaseController_1.BaseController { - /** - * Creates an EnsController instance. - * - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor(config, state) { - super(config, state); - /** - * Name of this controller used during composition - */ - this.name = 'EnsController'; - this.defaultState = { ensEntries: {} }; - this.initialize(); - } - /** - * Remove all chain Ids and ENS entries from state. - */ - clear() { - this.update({ ensEntries: {} }); - } - /** - * Delete an ENS entry. - * - * @param chainId - Parent chain of the ENS entry to delete. - * @param ensName - Name of the ENS entry to delete. - * @returns Boolean indicating if the entry was deleted. - */ - delete(chainId, ensName) { - const normalizedEnsName = (0, util_1.normalizeEnsName)(ensName); - if (!normalizedEnsName || - !this.state.ensEntries[chainId] || - !this.state.ensEntries[chainId][normalizedEnsName]) { - return false; - } - const ensEntries = Object.assign({}, this.state.ensEntries); - delete ensEntries[chainId][normalizedEnsName]; - if (Object.keys(ensEntries[chainId]).length === 0) { - delete ensEntries[chainId]; - } - this.update({ ensEntries }); - return true; - } - /** - * Retrieve a DNS entry. - * - * @param chainId - Parent chain of the ENS entry to retrieve. - * @param ensName - Name of the ENS entry to retrieve. - * @returns The EnsEntry or null if it does not exist. - */ - get(chainId, ensName) { - const normalizedEnsName = (0, util_1.normalizeEnsName)(ensName); - // TODO Explicitly handle the case where `normalizedEnsName` is `null` - // eslint-disable-next-line no-implicit-coercion - return !!normalizedEnsName && this.state.ensEntries[chainId] - ? this.state.ensEntries[chainId][normalizedEnsName] || null - : null; - } - /** - * Add or update an ENS entry by chainId and ensName. - * - * A null address indicates that the ENS name does not resolve. - * - * @param chainId - Id of the associated chain. - * @param ensName - The ENS name. - * @param address - Associated address (or null) to add or update. - * @returns Boolean indicating if the entry was set. - */ - set(chainId, ensName, address) { - if (!Number.isInteger(Number.parseInt(chainId, 10)) || - !ensName || - typeof ensName !== 'string' || - (address && !(0, util_1.isValidHexAddress)(address))) { - throw new Error(`Invalid ENS entry: { chainId:${chainId}, ensName:${ensName}, address:${address}}`); - } - const normalizedEnsName = (0, util_1.normalizeEnsName)(ensName); - if (!normalizedEnsName) { - throw new Error(`Invalid ENS name: ${ensName}`); - } - const normalizedAddress = address ? (0, util_1.toChecksumHexAddress)(address) : null; - const subState = this.state.ensEntries[chainId]; - if ((subState === null || subState === void 0 ? void 0 : subState[normalizedEnsName]) && - subState[normalizedEnsName].address === normalizedAddress) { - return false; - } - this.update({ - ensEntries: Object.assign(Object.assign({}, this.state.ensEntries), { [chainId]: Object.assign(Object.assign({}, this.state.ensEntries[chainId]), { [normalizedEnsName]: { - address: normalizedAddress, - chainId, - ensName: normalizedEnsName, - } }) }), - }); - return true; - } -} -exports.EnsController = EnsController; -exports.default = EnsController; -//# sourceMappingURL=EnsController.js.map \ No newline at end of file diff --git a/dist/third-party/EnsController.js.map b/dist/third-party/EnsController.js.map deleted file mode 100644 index 8e01bb711b..0000000000 --- a/dist/third-party/EnsController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"EnsController.js","sourceRoot":"","sources":["../../src/third-party/EnsController.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport {\n normalizeEnsName,\n isValidHexAddress,\n toChecksumHexAddress,\n} from '../util';\n\n/**\n * @type EnsEntry\n *\n * ENS entry representation\n * @property chainId - Id of the associated chain\n * @property ensName - The ENS name\n * @property address - Hex address with the ENS name, or null\n */\nexport interface EnsEntry {\n chainId: string;\n ensName: string;\n address: string | null;\n}\n\n/**\n * @type EnsState\n *\n * ENS controller state\n * @property ensEntries - Object of ENS entry objects\n */\nexport interface EnsState extends BaseState {\n ensEntries: { [chainId: string]: { [ensName: string]: EnsEntry } };\n}\n\n/**\n * Controller that manages a list ENS names and their resolved addresses\n * by chainId. A null address indicates an unresolved ENS name.\n */\nexport class EnsController extends BaseController {\n /**\n * Name of this controller used during composition\n */\n override name = 'EnsController';\n\n /**\n * Creates an EnsController instance.\n *\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(config?: Partial, state?: Partial) {\n super(config, state);\n\n this.defaultState = { ensEntries: {} };\n\n this.initialize();\n }\n\n /**\n * Remove all chain Ids and ENS entries from state.\n */\n clear() {\n this.update({ ensEntries: {} });\n }\n\n /**\n * Delete an ENS entry.\n *\n * @param chainId - Parent chain of the ENS entry to delete.\n * @param ensName - Name of the ENS entry to delete.\n * @returns Boolean indicating if the entry was deleted.\n */\n delete(chainId: string, ensName: string): boolean {\n const normalizedEnsName = normalizeEnsName(ensName);\n if (\n !normalizedEnsName ||\n !this.state.ensEntries[chainId] ||\n !this.state.ensEntries[chainId][normalizedEnsName]\n ) {\n return false;\n }\n\n const ensEntries = Object.assign({}, this.state.ensEntries);\n delete ensEntries[chainId][normalizedEnsName];\n\n if (Object.keys(ensEntries[chainId]).length === 0) {\n delete ensEntries[chainId];\n }\n\n this.update({ ensEntries });\n return true;\n }\n\n /**\n * Retrieve a DNS entry.\n *\n * @param chainId - Parent chain of the ENS entry to retrieve.\n * @param ensName - Name of the ENS entry to retrieve.\n * @returns The EnsEntry or null if it does not exist.\n */\n get(chainId: string, ensName: string): EnsEntry | null {\n const normalizedEnsName = normalizeEnsName(ensName);\n\n // TODO Explicitly handle the case where `normalizedEnsName` is `null`\n // eslint-disable-next-line no-implicit-coercion\n return !!normalizedEnsName && this.state.ensEntries[chainId]\n ? this.state.ensEntries[chainId][normalizedEnsName] || null\n : null;\n }\n\n /**\n * Add or update an ENS entry by chainId and ensName.\n *\n * A null address indicates that the ENS name does not resolve.\n *\n * @param chainId - Id of the associated chain.\n * @param ensName - The ENS name.\n * @param address - Associated address (or null) to add or update.\n * @returns Boolean indicating if the entry was set.\n */\n set(chainId: string, ensName: string, address: string | null): boolean {\n if (\n !Number.isInteger(Number.parseInt(chainId, 10)) ||\n !ensName ||\n typeof ensName !== 'string' ||\n (address && !isValidHexAddress(address))\n ) {\n throw new Error(\n `Invalid ENS entry: { chainId:${chainId}, ensName:${ensName}, address:${address}}`,\n );\n }\n\n const normalizedEnsName = normalizeEnsName(ensName);\n if (!normalizedEnsName) {\n throw new Error(`Invalid ENS name: ${ensName}`);\n }\n\n const normalizedAddress = address ? toChecksumHexAddress(address) : null;\n const subState = this.state.ensEntries[chainId];\n\n if (\n subState?.[normalizedEnsName] &&\n subState[normalizedEnsName].address === normalizedAddress\n ) {\n return false;\n }\n\n this.update({\n ensEntries: {\n ...this.state.ensEntries,\n [chainId]: {\n ...this.state.ensEntries[chainId],\n [normalizedEnsName]: {\n address: normalizedAddress,\n chainId,\n ensName: normalizedEnsName,\n },\n },\n },\n });\n return true;\n }\n}\n\nexport default EnsController;\n"]} \ No newline at end of file diff --git a/dist/third-party/PhishingController.d.ts b/dist/third-party/PhishingController.d.ts deleted file mode 100644 index fc892e328d..0000000000 --- a/dist/third-party/PhishingController.d.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { BaseController, BaseConfig, BaseState } from '../BaseController'; -/** - * @type EthPhishingResponse - * - * Configuration response from the eth-phishing-detect package - * consisting of approved and unapproved website origins - * @property blacklist - List of unapproved origins - * @property fuzzylist - List of fuzzy-matched unapproved origins - * @property tolerance - Fuzzy match tolerance level - * @property version - Version number of this configuration - * @property whitelist - List of approved origins - */ -export interface EthPhishingResponse { - blacklist: string[]; - fuzzylist: string[]; - tolerance: number; - version: number; - whitelist: string[]; -} -/** - * @type EthPhishingDetectConfig - * - * Interface defining expected input to PhishingDetector. - * @property allowlist - List of approved origins (legacy naming "whitelist") - * @property blocklist - List of unapproved origins (legacy naming "blacklist") - * @property fuzzylist - List of fuzzy-matched unapproved origins - * @property tolerance - Fuzzy match tolerance level - */ -export interface EthPhishingDetectConfig { - allowlist: string[]; - blocklist: string[]; - fuzzylist: string[]; - tolerance: number; - name: string; - version: number; -} -/** - * @type EthPhishingDetectResult - * - * Interface that describes the result of the `test` method. - * @property name - Name of the config on which a match was found. - * @property version - Version of the config on which a match was found. - * @property result - Whether a domain was detected as a phishing domain. True means an unsafe domain. - * @property match - The matching fuzzylist origin when a fuzzylist match is found. Returned as undefined for non-fuzzy true results. - * @property type - The field of the config on which a match was found. - */ -export interface EthPhishingDetectResult { - name?: string; - version?: string; - result: boolean; - match?: string; - type: 'all' | 'fuzzy' | 'blocklist' | 'allowlist'; -} -/** - * @type PhishingConfig - * - * Phishing controller configuration - * @property interval - Polling interval used to fetch new block / approve lists - */ -export interface PhishingConfig extends BaseConfig { - interval: number; -} -/** - * @type PhishingState - * - * Phishing controller state - * @property phishing - eth-phishing-detect configuration - * @property whitelist - array of temporarily-approved origins - */ -export interface PhishingState extends BaseState { - phishing: EthPhishingDetectConfig[]; - whitelist: string[]; -} -/** - * Controller that passively polls on a set interval for approved and unapproved website origins - */ -export declare class PhishingController extends BaseController { - private configUrlMetaMask; - private configUrlPhishFortHotlist; - private detector; - private handle?; - /** - * Name of this controller used during composition - */ - name: string; - /** - * Creates a PhishingController instance. - * - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor(config?: Partial, state?: Partial); - /** - * Starts a new polling interval. - * - * @param interval - Polling interval used to fetch new approval lists. - */ - poll(interval?: number): Promise; - /** - * Determines if a given origin is unapproved. - * - * @param origin - Domain origin of a website. - * @returns Whether the origin is an unapproved origin. - */ - test(origin: string): EthPhishingDetectResult; - /** - * Temporarily marks a given origin as approved. - * - * @param origin - The origin to mark as approved. - */ - bypass(origin: string): void; - /** - * Updates lists of approved and unapproved website origins. - */ - updatePhishingLists(): Promise; - private queryConfig; -} -export default PhishingController; diff --git a/dist/third-party/PhishingController.js b/dist/third-party/PhishingController.js deleted file mode 100644 index ea408a4e2a..0000000000 --- a/dist/third-party/PhishingController.js +++ /dev/null @@ -1,165 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.PhishingController = void 0; -const punycode_1 = require("punycode/"); -const config_json_1 = __importDefault(require("eth-phishing-detect/src/config.json")); -const detector_1 = __importDefault(require("eth-phishing-detect/src/detector")); -const BaseController_1 = require("../BaseController"); -const util_1 = require("../util"); -/** - * Controller that passively polls on a set interval for approved and unapproved website origins - */ -class PhishingController extends BaseController_1.BaseController { - /** - * Creates a PhishingController instance. - * - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor(config, state) { - super(config, state); - this.configUrlMetaMask = 'https://cdn.jsdelivr.net/gh/MetaMask/eth-phishing-detect@master/src/config.json'; - this.configUrlPhishFortHotlist = `https://cdn.jsdelivr.net/gh/phishfort/phishfort-lists@master/blacklists/hotlist.json`; - /** - * Name of this controller used during composition - */ - this.name = 'PhishingController'; - this.defaultConfig = { interval: 60 * 60 * 1000 }; - this.defaultState = { - phishing: [ - { - allowlist: config_json_1.default - .whitelist, - blocklist: config_json_1.default - .blacklist, - fuzzylist: config_json_1.default - .fuzzylist, - tolerance: config_json_1.default - .tolerance, - name: `MetaMask`, - version: config_json_1.default.version, - }, - ], - whitelist: [], - }; - this.detector = new detector_1.default(this.defaultState.phishing); - this.initialize(); - this.poll(); - } - /** - * Starts a new polling interval. - * - * @param interval - Polling interval used to fetch new approval lists. - */ - poll(interval) { - return __awaiter(this, void 0, void 0, function* () { - interval && this.configure({ interval }, false, false); - this.handle && clearTimeout(this.handle); - yield (0, util_1.safelyExecute)(() => this.updatePhishingLists()); - this.handle = setTimeout(() => { - this.poll(this.config.interval); - }, this.config.interval); - }); - } - /** - * Determines if a given origin is unapproved. - * - * @param origin - Domain origin of a website. - * @returns Whether the origin is an unapproved origin. - */ - test(origin) { - const punycodeOrigin = (0, punycode_1.toASCII)(origin); - if (this.state.whitelist.indexOf(punycodeOrigin) !== -1) { - return { result: false, type: 'all' }; // Same as whitelisted match returned by detector.check(...). - } - return this.detector.check(punycodeOrigin); - } - /** - * Temporarily marks a given origin as approved. - * - * @param origin - The origin to mark as approved. - */ - bypass(origin) { - const punycodeOrigin = (0, punycode_1.toASCII)(origin); - const { whitelist } = this.state; - if (whitelist.indexOf(punycodeOrigin) !== -1) { - return; - } - this.update({ whitelist: [...whitelist, punycodeOrigin] }); - } - /** - * Updates lists of approved and unapproved website origins. - */ - updatePhishingLists() { - return __awaiter(this, void 0, void 0, function* () { - if (this.disabled) { - return; - } - const configs = []; - const [metamaskConfigLegacy, phishfortHotlist] = yield Promise.all([ - yield this.queryConfig(this.configUrlMetaMask), - yield this.queryConfig(this.configUrlPhishFortHotlist), - ]); - // Correctly shaping MetaMask config. - const metamaskConfig = { - allowlist: metamaskConfigLegacy ? metamaskConfigLegacy.whitelist : [], - blocklist: metamaskConfigLegacy ? metamaskConfigLegacy.blacklist : [], - fuzzylist: metamaskConfigLegacy ? metamaskConfigLegacy.fuzzylist : [], - tolerance: metamaskConfigLegacy ? metamaskConfigLegacy.tolerance : 0, - name: `MetaMask`, - version: metamaskConfigLegacy ? metamaskConfigLegacy.version : 0, - }; - if (metamaskConfigLegacy) { - configs.push(metamaskConfig); - } - // Correctly shaping PhishFort config. - const phishfortConfig = { - allowlist: [], - blocklist: (phishfortHotlist || []).filter((i) => !metamaskConfig.blocklist.includes(i)), - fuzzylist: [], - tolerance: 0, - name: `PhishFort`, - version: 1, - }; - if (phishfortHotlist) { - configs.push(phishfortConfig); - } - // Do not update if all configs are unavailable. - if (!configs.length) { - return; - } - this.detector = new detector_1.default(configs); - this.update({ - phishing: configs, - }); - }); - } - queryConfig(input) { - return __awaiter(this, void 0, void 0, function* () { - const response = yield fetch(input, { cache: 'no-cache' }); - switch (response.status) { - case 200: { - return yield response.json(); - } - default: { - return null; - } - } - }); - } -} -exports.PhishingController = PhishingController; -exports.default = PhishingController; -//# sourceMappingURL=PhishingController.js.map \ No newline at end of file diff --git a/dist/third-party/PhishingController.js.map b/dist/third-party/PhishingController.js.map deleted file mode 100644 index fc34bf44d0..0000000000 --- a/dist/third-party/PhishingController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"PhishingController.js","sourceRoot":"","sources":["../../src/third-party/PhishingController.ts"],"names":[],"mappings":"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","sourcesContent":["import { toASCII } from 'punycode/';\nimport DEFAULT_PHISHING_RESPONSE from 'eth-phishing-detect/src/config.json';\nimport PhishingDetector from 'eth-phishing-detect/src/detector';\nimport { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport { safelyExecute } from '../util';\n\n/**\n * @type EthPhishingResponse\n *\n * Configuration response from the eth-phishing-detect package\n * consisting of approved and unapproved website origins\n * @property blacklist - List of unapproved origins\n * @property fuzzylist - List of fuzzy-matched unapproved origins\n * @property tolerance - Fuzzy match tolerance level\n * @property version - Version number of this configuration\n * @property whitelist - List of approved origins\n */\nexport interface EthPhishingResponse {\n blacklist: string[];\n fuzzylist: string[];\n tolerance: number;\n version: number;\n whitelist: string[];\n}\n\n/**\n * @type EthPhishingDetectConfig\n *\n * Interface defining expected input to PhishingDetector.\n * @property allowlist - List of approved origins (legacy naming \"whitelist\")\n * @property blocklist - List of unapproved origins (legacy naming \"blacklist\")\n * @property fuzzylist - List of fuzzy-matched unapproved origins\n * @property tolerance - Fuzzy match tolerance level\n */\nexport interface EthPhishingDetectConfig {\n allowlist: string[];\n blocklist: string[];\n fuzzylist: string[];\n tolerance: number;\n name: string;\n version: number;\n}\n\n/**\n * @type EthPhishingDetectResult\n *\n * Interface that describes the result of the `test` method.\n * @property name - Name of the config on which a match was found.\n * @property version - Version of the config on which a match was found.\n * @property result - Whether a domain was detected as a phishing domain. True means an unsafe domain.\n * @property match - The matching fuzzylist origin when a fuzzylist match is found. Returned as undefined for non-fuzzy true results.\n * @property type - The field of the config on which a match was found.\n */\nexport interface EthPhishingDetectResult {\n name?: string;\n version?: string;\n result: boolean;\n match?: string; // Returned as undefined for non-fuzzy true results.\n type: 'all' | 'fuzzy' | 'blocklist' | 'allowlist';\n}\n\n/**\n * @type PhishingConfig\n *\n * Phishing controller configuration\n * @property interval - Polling interval used to fetch new block / approve lists\n */\nexport interface PhishingConfig extends BaseConfig {\n interval: number;\n}\n\n/**\n * @type PhishingState\n *\n * Phishing controller state\n * @property phishing - eth-phishing-detect configuration\n * @property whitelist - array of temporarily-approved origins\n */\nexport interface PhishingState extends BaseState {\n phishing: EthPhishingDetectConfig[];\n whitelist: string[];\n}\n\n/**\n * Controller that passively polls on a set interval for approved and unapproved website origins\n */\nexport class PhishingController extends BaseController<\n PhishingConfig,\n PhishingState\n> {\n private configUrlMetaMask =\n 'https://cdn.jsdelivr.net/gh/MetaMask/eth-phishing-detect@master/src/config.json';\n\n private configUrlPhishFortHotlist = `https://cdn.jsdelivr.net/gh/phishfort/phishfort-lists@master/blacklists/hotlist.json`;\n\n private detector: any;\n\n private handle?: NodeJS.Timer;\n\n /**\n * Name of this controller used during composition\n */\n override name = 'PhishingController';\n\n /**\n * Creates a PhishingController instance.\n *\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n config?: Partial,\n state?: Partial,\n ) {\n super(config, state);\n this.defaultConfig = { interval: 60 * 60 * 1000 };\n this.defaultState = {\n phishing: [\n {\n allowlist: (DEFAULT_PHISHING_RESPONSE as EthPhishingResponse)\n .whitelist,\n blocklist: (DEFAULT_PHISHING_RESPONSE as EthPhishingResponse)\n .blacklist,\n fuzzylist: (DEFAULT_PHISHING_RESPONSE as EthPhishingResponse)\n .fuzzylist,\n tolerance: (DEFAULT_PHISHING_RESPONSE as EthPhishingResponse)\n .tolerance,\n name: `MetaMask`,\n version: (DEFAULT_PHISHING_RESPONSE as EthPhishingResponse).version,\n },\n ],\n whitelist: [],\n };\n this.detector = new PhishingDetector(this.defaultState.phishing);\n this.initialize();\n this.poll();\n }\n\n /**\n * Starts a new polling interval.\n *\n * @param interval - Polling interval used to fetch new approval lists.\n */\n async poll(interval?: number): Promise {\n interval && this.configure({ interval }, false, false);\n this.handle && clearTimeout(this.handle);\n await safelyExecute(() => this.updatePhishingLists());\n this.handle = setTimeout(() => {\n this.poll(this.config.interval);\n }, this.config.interval);\n }\n\n /**\n * Determines if a given origin is unapproved.\n *\n * @param origin - Domain origin of a website.\n * @returns Whether the origin is an unapproved origin.\n */\n test(origin: string): EthPhishingDetectResult {\n const punycodeOrigin = toASCII(origin);\n if (this.state.whitelist.indexOf(punycodeOrigin) !== -1) {\n return { result: false, type: 'all' }; // Same as whitelisted match returned by detector.check(...).\n }\n return this.detector.check(punycodeOrigin);\n }\n\n /**\n * Temporarily marks a given origin as approved.\n *\n * @param origin - The origin to mark as approved.\n */\n bypass(origin: string) {\n const punycodeOrigin = toASCII(origin);\n const { whitelist } = this.state;\n if (whitelist.indexOf(punycodeOrigin) !== -1) {\n return;\n }\n this.update({ whitelist: [...whitelist, punycodeOrigin] });\n }\n\n /**\n * Updates lists of approved and unapproved website origins.\n */\n async updatePhishingLists() {\n if (this.disabled) {\n return;\n }\n\n const configs: EthPhishingDetectConfig[] = [];\n\n const [metamaskConfigLegacy, phishfortHotlist] = await Promise.all([\n await this.queryConfig(this.configUrlMetaMask),\n await this.queryConfig(this.configUrlPhishFortHotlist),\n ]);\n\n // Correctly shaping MetaMask config.\n const metamaskConfig: EthPhishingDetectConfig = {\n allowlist: metamaskConfigLegacy ? metamaskConfigLegacy.whitelist : [],\n blocklist: metamaskConfigLegacy ? metamaskConfigLegacy.blacklist : [],\n fuzzylist: metamaskConfigLegacy ? metamaskConfigLegacy.fuzzylist : [],\n tolerance: metamaskConfigLegacy ? metamaskConfigLegacy.tolerance : 0,\n name: `MetaMask`,\n version: metamaskConfigLegacy ? metamaskConfigLegacy.version : 0,\n };\n if (metamaskConfigLegacy) {\n configs.push(metamaskConfig);\n }\n\n // Correctly shaping PhishFort config.\n const phishfortConfig: EthPhishingDetectConfig = {\n allowlist: [],\n blocklist: (phishfortHotlist || []).filter(\n (i) => !metamaskConfig.blocklist.includes(i),\n ), // Removal of duplicates.\n fuzzylist: [],\n tolerance: 0,\n name: `PhishFort`,\n version: 1,\n };\n if (phishfortHotlist) {\n configs.push(phishfortConfig);\n }\n\n // Do not update if all configs are unavailable.\n if (!configs.length) {\n return;\n }\n\n this.detector = new PhishingDetector(configs);\n this.update({\n phishing: configs,\n });\n }\n\n private async queryConfig(\n input: RequestInfo,\n ): Promise {\n const response = await fetch(input, { cache: 'no-cache' });\n\n switch (response.status) {\n case 200: {\n return await response.json();\n }\n\n default: {\n return null;\n }\n }\n }\n}\n\nexport default PhishingController;\n"]} \ No newline at end of file diff --git a/dist/transaction/TransactionController.d.ts b/dist/transaction/TransactionController.d.ts deleted file mode 100644 index 8152a26aec..0000000000 --- a/dist/transaction/TransactionController.d.ts +++ /dev/null @@ -1,460 +0,0 @@ -/// -import { EventEmitter } from 'events'; -import Common from '@ethereumjs/common'; -import { TypedTransaction } from '@ethereumjs/tx'; -import { BaseController, BaseConfig, BaseState } from '../BaseController'; -import type { NetworkState, NetworkController } from '../network/NetworkController'; -/** - * @type Result - * @property result - Promise resolving to a new transaction hash - * @property transactionMeta - Meta information about this new transaction - */ -export interface Result { - result: Promise; - transactionMeta: TransactionMeta; -} -/** - * @type Fetch All Options - * @property fromBlock - String containing a specific block decimal number - * @property etherscanApiKey - API key to be used to fetch token transactions - */ -export interface FetchAllOptions { - fromBlock?: string; - etherscanApiKey?: string; -} -/** - * @type Transaction - * - * Transaction representation - * @property chainId - Network ID as per EIP-155 - * @property data - Data to pass with this transaction - * @property from - Address to send this transaction from - * @property gas - Gas to send with this transaction - * @property gasPrice - Price of gas with this transaction - * @property gasUsed - Gas used in the transaction - * @property nonce - Unique number to prevent replay attacks - * @property to - Address to send this transaction to - * @property value - Value associated with this transaction - */ -export interface Transaction { - chainId?: number; - data?: string; - from: string; - gas?: string; - gasPrice?: string; - gasUsed?: string; - nonce?: string; - to?: string; - value?: string; - maxFeePerGas?: string; - maxPriorityFeePerGas?: string; - estimatedBaseFee?: string; -} -export interface GasPriceValue { - gasPrice: string; -} -export interface FeeMarketEIP1559Values { - maxFeePerGas: string; - maxPriorityFeePerGas: string; -} -/** - * The status of the transaction. Each status represents the state of the transaction internally - * in the wallet. Some of these correspond with the state of the transaction on the network, but - * some are wallet-specific. - */ -export declare enum TransactionStatus { - approved = "approved", - cancelled = "cancelled", - confirmed = "confirmed", - failed = "failed", - rejected = "rejected", - signed = "signed", - submitted = "submitted", - unapproved = "unapproved" -} -/** - * Options for wallet device. - */ -export declare enum WalletDevice { - MM_MOBILE = "metamask_mobile", - MM_EXTENSION = "metamask_extension", - OTHER = "other_device" -} -declare type TransactionMetaBase = { - isTransfer?: boolean; - transferInformation?: { - symbol: string; - contractAddress: string; - decimals: number; - }; - id: string; - networkID?: string; - chainId?: string; - origin?: string; - rawTransaction?: string; - time: number; - toSmartContract?: boolean; - transaction: Transaction; - transactionHash?: string; - blockNumber?: string; - deviceConfirmedOn?: WalletDevice; - verifiedOnBlockchain?: boolean; -}; -/** - * @type TransactionMeta - * - * TransactionMeta representation - * @property error - Synthesized error information for failed transactions - * @property id - Generated UUID associated with this transaction - * @property networkID - Network code as per EIP-155 for this transaction - * @property origin - Origin this transaction was sent from - * @property deviceConfirmedOn - string to indicate what device the transaction was confirmed - * @property rawTransaction - Hex representation of the underlying transaction - * @property status - String status of this transaction - * @property time - Timestamp associated with this transaction - * @property toSmartContract - Whether transaction recipient is a smart contract - * @property transaction - Underlying Transaction object - * @property transactionHash - Hash of a successful transaction - * @property blockNumber - Number of the block where the transaction has been included - */ -export declare type TransactionMeta = ({ - status: Exclude; -} & TransactionMetaBase) | ({ - status: TransactionStatus.failed; - error: Error; -} & TransactionMetaBase); -/** - * @type EtherscanTransactionMeta - * - * EtherscanTransactionMeta representation - * @property blockNumber - Number of the block where the transaction has been included - * @property timeStamp - Timestamp associated with this transaction - * @property hash - Hash of a successful transaction - * @property nonce - Nonce of the transaction - * @property blockHash - Hash of the block where the transaction has been included - * @property transactionIndex - Etherscan internal index for this transaction - * @property from - Address to send this transaction from - * @property to - Address to send this transaction to - * @property gas - Gas to send with this transaction - * @property gasPrice - Price of gas with this transaction - * @property isError - Synthesized error information for failed transactions - * @property txreceipt_status - Receipt status for this transaction - * @property input - input of the transaction - * @property contractAddress - Address of the contract - * @property cumulativeGasUsed - Amount of gas used - * @property confirmations - Number of confirmations - */ -export interface EtherscanTransactionMeta { - blockNumber: string; - timeStamp: string; - hash: string; - nonce: string; - blockHash: string; - transactionIndex: string; - from: string; - to: string; - value: string; - gas: string; - gasPrice: string; - cumulativeGasUsed: string; - gasUsed: string; - isError: string; - txreceipt_status: string; - input: string; - contractAddress: string; - confirmations: string; - tokenDecimal: string; - tokenSymbol: string; -} -/** - * @type TransactionConfig - * - * Transaction controller configuration - * @property interval - Polling interval used to fetch new currency rate - * @property provider - Provider used to create a new underlying EthQuery instance - * @property sign - Method used to sign transactions - */ -export interface TransactionConfig extends BaseConfig { - interval: number; - sign?: (transaction: Transaction, from: string) => Promise; - txHistoryLimit: number; -} -/** - * @type MethodData - * - * Method data registry object - * @property registryMethod - Registry method raw string - * @property parsedRegistryMethod - Registry method object, containing name and method arguments - */ -export interface MethodData { - registryMethod: string; - parsedRegistryMethod: Record; -} -/** - * @type TransactionState - * - * Transaction controller state - * @property transactions - A list of TransactionMeta objects - * @property methodData - Object containing all known method data information - */ -export interface TransactionState extends BaseState { - transactions: TransactionMeta[]; - methodData: { - [key: string]: MethodData; - }; -} -/** - * Multiplier used to determine a transaction's increased gas fee during cancellation - */ -export declare const CANCEL_RATE = 1.5; -/** - * Multiplier used to determine a transaction's increased gas fee during speed up - */ -export declare const SPEED_UP_RATE = 1.1; -/** - * Controller responsible for submitting and managing transactions - */ -export declare class TransactionController extends BaseController { - private ethQuery; - private registry; - private handle?; - private mutex; - private getNetworkState; - private failTransaction; - private registryLookup; - /** - * Normalizes the transaction information from etherscan - * to be compatible with the TransactionMeta interface. - * - * @param txMeta - The transaction. - * @param currentNetworkID - The current network ID. - * @param currentChainId - The current chain ID. - * @returns The normalized transaction. - */ - private normalizeTx; - private normalizeTokenTx; - /** - * EventEmitter instance used to listen to specific transactional events - */ - hub: EventEmitter; - /** - * Name of this controller used during composition - */ - name: string; - /** - * Method used to sign transactions - */ - sign?: (transaction: TypedTransaction, from: string) => Promise; - /** - * Creates a TransactionController instance. - * - * @param options - The controller options. - * @param options.getNetworkState - Gets the state of the network controller. - * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. - * @param options.getProvider - Returns a provider for the current network. - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor({ getNetworkState, onNetworkStateChange, getProvider, }: { - getNetworkState: () => NetworkState; - onNetworkStateChange: (listener: (state: NetworkState) => void) => void; - getProvider: () => NetworkController['provider']; - }, config?: Partial, state?: Partial); - /** - * Starts a new polling interval. - * - * @param interval - The polling interval used to fetch new transaction statuses. - */ - poll(interval?: number): Promise; - /** - * Handle new method data request. - * - * @param fourBytePrefix - The method prefix. - * @returns The method data object corresponding to the given signature prefix. - */ - handleMethodData(fourBytePrefix: string): Promise; - /** - * Add a new unapproved transaction to state. Parameters will be validated, a - * unique transaction id will be generated, and gas and gasPrice will be calculated - * if not provided. If A `:unapproved` hub event will be emitted once added. - * - * @param transaction - The transaction object to add. - * @param origin - The domain origin to append to the generated TransactionMeta. - * @param deviceConfirmedOn - An enum to indicate what device the transaction was confirmed to append to the generated TransactionMeta. - * @returns Object containing a promise resolving to the transaction hash if approved. - */ - addTransaction(transaction: Transaction, origin?: string, deviceConfirmedOn?: WalletDevice): Promise; - prepareUnsignedEthTx(txParams: Record): TypedTransaction; - /** - * `@ethereumjs/tx` uses `@ethereumjs/common` as a configuration tool for - * specifying which chain, network, hardfork and EIPs to support for - * a transaction. By referencing this configuration, and analyzing the fields - * specified in txParams, @ethereumjs/tx is able to determine which EIP-2718 - * transaction type to use. - * - * @returns {Common} common configuration object - */ - getCommonConfiguration(): Common; - /** - * Approves a transaction and updates it's status in state. If this is not a - * retry transaction, a nonce will be generated. The transaction is signed - * using the sign configuration property, then published to the blockchain. - * A `:finished` hub event is fired after success or failure. - * - * @param transactionID - The ID of the transaction to approve. - */ - approveTransaction(transactionID: string): Promise; - /** - * Cancels a transaction based on its ID by setting its status to "rejected" - * and emitting a `:finished` hub event. - * - * @param transactionID - The ID of the transaction to cancel. - */ - cancelTransaction(transactionID: string): void; - /** - * Attempts to cancel a transaction based on its ID by setting its status to "rejected" - * and emitting a `:finished` hub event. - * - * @param transactionID - The ID of the transaction to cancel. - * @param gasValues - The gas values to use for the cancellation transation. - */ - stopTransaction(transactionID: string, gasValues?: GasPriceValue | FeeMarketEIP1559Values): Promise; - /** - * Attempts to speed up a transaction increasing transaction gasPrice by ten percent. - * - * @param transactionID - The ID of the transaction to speed up. - * @param gasValues - The gas values to use for the speed up transation. - */ - speedUpTransaction(transactionID: string, gasValues?: GasPriceValue | FeeMarketEIP1559Values): Promise; - /** - * Estimates required gas for a given transaction. - * - * @param transaction - The transaction to estimate gas for. - * @returns The gas and gas price. - */ - estimateGas(transaction: Transaction): Promise<{ - gas: string; - gasPrice: any; - }>; - /** - * Check the status of submitted transactions on the network to determine whether they have - * been included in a block. Any that have been included in a block are marked as confirmed. - */ - queryTransactionStatuses(): Promise; - /** - * Updates an existing transaction in state. - * - * @param transactionMeta - The new transaction to store in state. - */ - updateTransaction(transactionMeta: TransactionMeta): void; - /** - * Removes all transactions from state, optionally based on the current network. - * - * @param ignoreNetwork - Determines whether to wipe all transactions, or just those on the - * current network. If `true`, all transactions are wiped. - */ - wipeTransactions(ignoreNetwork?: boolean): void; - /** - * Get transactions from Etherscan for the given address. By default all transactions are - * returned, but the `fromBlock` option can be given to filter just for transactions from a - * specific block onward. - * - * @param address - The address to fetch the transactions for. - * @param opt - Object containing optional data, fromBlock and Etherscan API key. - * @returns The block number of the latest incoming transaction. - */ - fetchAll(address: string, opt?: FetchAllOptions): Promise; - /** - * Trim the amount of transactions that are set on the state. Checks - * if the length of the tx history is longer then desired persistence - * limit and then if it is removes the oldest confirmed or rejected tx. - * Pending or unapproved transactions will not be removed by this - * operation. For safety of presenting a fully functional transaction UI - * representation, this function will not break apart transactions with the - * same nonce, created on the same day, per network. Not accounting for transactions of the same - * nonce, same day and network combo can result in confusing or broken experiences - * in the UI. The transactions are then updated using the BaseController update. - * - * @param transactions - The transactions to be applied to the state. - * @returns The trimmed list of transactions. - */ - private trimTransactionsForState; - /** - * Determines if the transaction is in a final state. - * - * @param status - The transaction status. - * @returns Whether the transaction is in a final state. - */ - private isFinalState; - /** - * Method to verify the state of a transaction using the Blockchain as a source of truth. - * - * @param meta - The local transaction to verify on the blockchain. - * @returns A tuple containing the updated transaction, and whether or not an update was required. - */ - private blockchainTransactionStateReconciler; - /** - * Method to check if a tx has failed according to their receipt - * According to the Web3 docs: - * TRUE if the transaction was successful, FALSE if the EVM reverted the transaction. - * The receipt is not available for pending transactions and returns null. - * - * @param txHash - The transaction hash. - * @returns Whether the transaction has failed. - */ - private checkTxReceiptStatusIsFailed; - /** - * Method to verify the state of transactions using Etherscan as a source of truth. - * - * @param remoteTxs - Transactions to reconcile that are from a remote source. - * @param localTxs - Transactions to reconcile that are local. - * @returns A tuple containing a boolean indicating whether or not an update was required, and the updated transaction. - */ - private etherscanTransactionStateReconciler; - /** - * Get all transactions that are in the remote transactions array - * but not in the local transactions array. - * - * @param remoteTxs - Array of transactions from remote source. - * @param localTxs - Array of transactions stored locally. - * @returns The new transactions. - */ - private getNewTransactions; - /** - * Get all the transactions that are locally outdated with respect - * to a remote source (etherscan or blockchain). The returned array - * contains the transactions with the updated data. - * - * @param remoteTxs - Array of transactions from remote source. - * @param localTxs - Array of transactions stored locally. - * @returns The updated transactions. - */ - private getUpdatedTransactions; - /** - * Verifies if a local transaction is outdated with respect to the remote transaction. - * - * @param remoteTx - The remote transaction from Etherscan. - * @param localTx - The local transaction. - * @returns Whether the transaction is outdated. - */ - private isTransactionOutdated; - /** - * Verifies if the status of a local transaction is outdated with respect to the remote transaction. - * - * @param remoteTxHash - Remote transaction hash. - * @param localTxHash - Local transaction hash. - * @param remoteTxStatus - Remote transaction status. - * @param localTxStatus - Local transaction status. - * @returns Whether the status is outdated. - */ - private isStatusOutdated; - /** - * Verifies if the gas data of a local transaction is outdated with respect to the remote transaction. - * - * @param remoteGasUsed - Remote gas used in the transaction. - * @param localGasUsed - Local gas used in the transaction. - * @returns Whether the gas data is outdated. - */ - private isGasDataOutdated; -} -export default TransactionController; diff --git a/dist/transaction/TransactionController.js b/dist/transaction/TransactionController.js deleted file mode 100644 index 70ab12e0e4..0000000000 --- a/dist/transaction/TransactionController.js +++ /dev/null @@ -1,915 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.TransactionController = exports.SPEED_UP_RATE = exports.CANCEL_RATE = exports.WalletDevice = exports.TransactionStatus = void 0; -const events_1 = require("events"); -const ethereumjs_util_1 = require("ethereumjs-util"); -const eth_rpc_errors_1 = require("eth-rpc-errors"); -const eth_method_registry_1 = __importDefault(require("eth-method-registry")); -const eth_query_1 = __importDefault(require("eth-query")); -const common_1 = __importDefault(require("@ethereumjs/common")); -const tx_1 = require("@ethereumjs/tx"); -const uuid_1 = require("uuid"); -const async_mutex_1 = require("async-mutex"); -const BaseController_1 = require("../BaseController"); -const util_1 = require("../util"); -const constants_1 = require("../constants"); -const HARDFORK = 'london'; -/** - * The status of the transaction. Each status represents the state of the transaction internally - * in the wallet. Some of these correspond with the state of the transaction on the network, but - * some are wallet-specific. - */ -var TransactionStatus; -(function (TransactionStatus) { - TransactionStatus["approved"] = "approved"; - TransactionStatus["cancelled"] = "cancelled"; - TransactionStatus["confirmed"] = "confirmed"; - TransactionStatus["failed"] = "failed"; - TransactionStatus["rejected"] = "rejected"; - TransactionStatus["signed"] = "signed"; - TransactionStatus["submitted"] = "submitted"; - TransactionStatus["unapproved"] = "unapproved"; -})(TransactionStatus = exports.TransactionStatus || (exports.TransactionStatus = {})); -/** - * Options for wallet device. - */ -var WalletDevice; -(function (WalletDevice) { - WalletDevice["MM_MOBILE"] = "metamask_mobile"; - WalletDevice["MM_EXTENSION"] = "metamask_extension"; - WalletDevice["OTHER"] = "other_device"; -})(WalletDevice = exports.WalletDevice || (exports.WalletDevice = {})); -/** - * Multiplier used to determine a transaction's increased gas fee during cancellation - */ -exports.CANCEL_RATE = 1.5; -/** - * Multiplier used to determine a transaction's increased gas fee during speed up - */ -exports.SPEED_UP_RATE = 1.1; -/** - * Controller responsible for submitting and managing transactions - */ -class TransactionController extends BaseController_1.BaseController { - /** - * Creates a TransactionController instance. - * - * @param options - The controller options. - * @param options.getNetworkState - Gets the state of the network controller. - * @param options.onNetworkStateChange - Allows subscribing to network controller state changes. - * @param options.getProvider - Returns a provider for the current network. - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor({ getNetworkState, onNetworkStateChange, getProvider, }, config, state) { - super(config, state); - this.mutex = new async_mutex_1.Mutex(); - this.normalizeTokenTx = (txMeta, currentNetworkID, currentChainId) => { - const time = parseInt(txMeta.timeStamp, 10) * 1000; - const { to, from, gas, gasPrice, gasUsed, hash, contractAddress, tokenDecimal, tokenSymbol, value, } = txMeta; - return { - id: (0, uuid_1.v1)({ msecs: time }), - isTransfer: true, - networkID: currentNetworkID, - chainId: currentChainId, - status: TransactionStatus.confirmed, - time, - transaction: { - chainId: 1, - from, - gas, - gasPrice, - gasUsed, - to, - value, - }, - transactionHash: hash, - transferInformation: { - contractAddress, - decimals: Number(tokenDecimal), - symbol: tokenSymbol, - }, - verifiedOnBlockchain: false, - }; - }; - /** - * EventEmitter instance used to listen to specific transactional events - */ - this.hub = new events_1.EventEmitter(); - /** - * Name of this controller used during composition - */ - this.name = 'TransactionController'; - this.defaultConfig = { - interval: 15000, - txHistoryLimit: 40, - }; - this.defaultState = { - methodData: {}, - transactions: [], - }; - this.initialize(); - const provider = getProvider(); - this.getNetworkState = getNetworkState; - this.ethQuery = new eth_query_1.default(provider); - this.registry = new eth_method_registry_1.default({ provider }); - onNetworkStateChange(() => { - const newProvider = getProvider(); - this.ethQuery = new eth_query_1.default(newProvider); - this.registry = new eth_method_registry_1.default({ provider: newProvider }); - }); - this.poll(); - } - failTransaction(transactionMeta, error) { - const newTransactionMeta = Object.assign(Object.assign({}, transactionMeta), { error, status: TransactionStatus.failed }); - this.updateTransaction(newTransactionMeta); - this.hub.emit(`${transactionMeta.id}:finished`, newTransactionMeta); - } - registryLookup(fourBytePrefix) { - return __awaiter(this, void 0, void 0, function* () { - const registryMethod = yield this.registry.lookup(fourBytePrefix); - const parsedRegistryMethod = this.registry.parse(registryMethod); - return { registryMethod, parsedRegistryMethod }; - }); - } - /** - * Normalizes the transaction information from etherscan - * to be compatible with the TransactionMeta interface. - * - * @param txMeta - The transaction. - * @param currentNetworkID - The current network ID. - * @param currentChainId - The current chain ID. - * @returns The normalized transaction. - */ - normalizeTx(txMeta, currentNetworkID, currentChainId) { - const time = parseInt(txMeta.timeStamp, 10) * 1000; - const normalizedTransactionBase = { - blockNumber: txMeta.blockNumber, - id: (0, uuid_1.v1)({ msecs: time }), - networkID: currentNetworkID, - chainId: currentChainId, - time, - transaction: { - data: txMeta.input, - from: txMeta.from, - gas: (0, util_1.BNToHex)(new ethereumjs_util_1.BN(txMeta.gas)), - gasPrice: (0, util_1.BNToHex)(new ethereumjs_util_1.BN(txMeta.gasPrice)), - gasUsed: (0, util_1.BNToHex)(new ethereumjs_util_1.BN(txMeta.gasUsed)), - nonce: (0, util_1.BNToHex)(new ethereumjs_util_1.BN(txMeta.nonce)), - to: txMeta.to, - value: (0, util_1.BNToHex)(new ethereumjs_util_1.BN(txMeta.value)), - }, - transactionHash: txMeta.hash, - verifiedOnBlockchain: false, - }; - /* istanbul ignore else */ - if (txMeta.isError === '0') { - return Object.assign(Object.assign({}, normalizedTransactionBase), { status: TransactionStatus.confirmed }); - } - /* istanbul ignore next */ - return Object.assign(Object.assign({}, normalizedTransactionBase), { error: new Error('Transaction failed'), status: TransactionStatus.failed }); - } - /** - * Starts a new polling interval. - * - * @param interval - The polling interval used to fetch new transaction statuses. - */ - poll(interval) { - return __awaiter(this, void 0, void 0, function* () { - interval && this.configure({ interval }, false, false); - this.handle && clearTimeout(this.handle); - yield (0, util_1.safelyExecute)(() => this.queryTransactionStatuses()); - this.handle = setTimeout(() => { - this.poll(this.config.interval); - }, this.config.interval); - }); - } - /** - * Handle new method data request. - * - * @param fourBytePrefix - The method prefix. - * @returns The method data object corresponding to the given signature prefix. - */ - handleMethodData(fourBytePrefix) { - return __awaiter(this, void 0, void 0, function* () { - const releaseLock = yield this.mutex.acquire(); - try { - const { methodData } = this.state; - const knownMethod = Object.keys(methodData).find((knownFourBytePrefix) => fourBytePrefix === knownFourBytePrefix); - if (knownMethod) { - return methodData[fourBytePrefix]; - } - const registry = yield this.registryLookup(fourBytePrefix); - this.update({ - methodData: Object.assign(Object.assign({}, methodData), { [fourBytePrefix]: registry }), - }); - return registry; - } - finally { - releaseLock(); - } - }); - } - /** - * Add a new unapproved transaction to state. Parameters will be validated, a - * unique transaction id will be generated, and gas and gasPrice will be calculated - * if not provided. If A `:unapproved` hub event will be emitted once added. - * - * @param transaction - The transaction object to add. - * @param origin - The domain origin to append to the generated TransactionMeta. - * @param deviceConfirmedOn - An enum to indicate what device the transaction was confirmed to append to the generated TransactionMeta. - * @returns Object containing a promise resolving to the transaction hash if approved. - */ - addTransaction(transaction, origin, deviceConfirmedOn) { - return __awaiter(this, void 0, void 0, function* () { - const { provider, network } = this.getNetworkState(); - const { transactions } = this.state; - transaction = (0, util_1.normalizeTransaction)(transaction); - (0, util_1.validateTransaction)(transaction); - const transactionMeta = { - id: (0, uuid_1.v1)(), - networkID: network, - chainId: provider.chainId, - origin, - status: TransactionStatus.unapproved, - time: Date.now(), - transaction, - deviceConfirmedOn, - verifiedOnBlockchain: false, - }; - try { - const { gas } = yield this.estimateGas(transaction); - transaction.gas = gas; - } - catch (error) { - this.failTransaction(transactionMeta, error); - return Promise.reject(error); - } - const result = new Promise((resolve, reject) => { - this.hub.once(`${transactionMeta.id}:finished`, (meta) => { - switch (meta.status) { - case TransactionStatus.submitted: - return resolve(meta.transactionHash); - case TransactionStatus.rejected: - return reject(eth_rpc_errors_1.ethErrors.provider.userRejectedRequest('User rejected the transaction')); - case TransactionStatus.cancelled: - return reject(eth_rpc_errors_1.ethErrors.rpc.internal('User cancelled the transaction')); - case TransactionStatus.failed: - return reject(eth_rpc_errors_1.ethErrors.rpc.internal(meta.error.message)); - /* istanbul ignore next */ - default: - return reject(eth_rpc_errors_1.ethErrors.rpc.internal(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(meta)}`)); - } - }); - }); - transactions.push(transactionMeta); - this.update({ transactions: this.trimTransactionsForState(transactions) }); - this.hub.emit(`unapprovedTransaction`, transactionMeta); - return { result, transactionMeta }; - }); - } - prepareUnsignedEthTx(txParams) { - return tx_1.TransactionFactory.fromTxData(txParams, { - common: this.getCommonConfiguration(), - freeze: false, - }); - } - /** - * `@ethereumjs/tx` uses `@ethereumjs/common` as a configuration tool for - * specifying which chain, network, hardfork and EIPs to support for - * a transaction. By referencing this configuration, and analyzing the fields - * specified in txParams, @ethereumjs/tx is able to determine which EIP-2718 - * transaction type to use. - * - * @returns {Common} common configuration object - */ - getCommonConfiguration() { - const { network: networkId, provider: { type: chain, chainId, nickname: name }, } = this.getNetworkState(); - if (chain !== constants_1.RPC) { - return new common_1.default({ chain, hardfork: HARDFORK }); - } - const customChainParams = { - name, - chainId: parseInt(chainId, undefined), - networkId: parseInt(networkId, undefined), - }; - return common_1.default.forCustomChain(constants_1.MAINNET, customChainParams, HARDFORK); - } - /** - * Approves a transaction and updates it's status in state. If this is not a - * retry transaction, a nonce will be generated. The transaction is signed - * using the sign configuration property, then published to the blockchain. - * A `:finished` hub event is fired after success or failure. - * - * @param transactionID - The ID of the transaction to approve. - */ - approveTransaction(transactionID) { - return __awaiter(this, void 0, void 0, function* () { - const { transactions } = this.state; - const releaseLock = yield this.mutex.acquire(); - const { provider } = this.getNetworkState(); - const { chainId: currentChainId } = provider; - const index = transactions.findIndex(({ id }) => transactionID === id); - const transactionMeta = transactions[index]; - const { nonce } = transactionMeta.transaction; - try { - const { from } = transactionMeta.transaction; - if (!this.sign) { - releaseLock(); - this.failTransaction(transactionMeta, new Error('No sign method defined.')); - return; - } - else if (!currentChainId) { - releaseLock(); - this.failTransaction(transactionMeta, new Error('No chainId defined.')); - return; - } - const chainId = parseInt(currentChainId, undefined); - const { approved: status } = TransactionStatus; - const txNonce = nonce || - (yield (0, util_1.query)(this.ethQuery, 'getTransactionCount', [from, 'pending'])); - transactionMeta.status = status; - transactionMeta.transaction.nonce = txNonce; - transactionMeta.transaction.chainId = chainId; - const baseTxParams = Object.assign(Object.assign({}, transactionMeta.transaction), { gasLimit: transactionMeta.transaction.gas, chainId, nonce: txNonce, status }); - const isEIP1559 = (0, util_1.isEIP1559Transaction)(transactionMeta.transaction); - const txParams = isEIP1559 - ? Object.assign(Object.assign({}, baseTxParams), { maxFeePerGas: transactionMeta.transaction.maxFeePerGas, maxPriorityFeePerGas: transactionMeta.transaction.maxPriorityFeePerGas, estimatedBaseFee: transactionMeta.transaction.estimatedBaseFee, - // specify type 2 if maxFeePerGas and maxPriorityFeePerGas are set - type: 2 }) : baseTxParams; - // delete gasPrice if maxFeePerGas and maxPriorityFeePerGas are set - if (isEIP1559) { - delete txParams.gasPrice; - } - const unsignedEthTx = this.prepareUnsignedEthTx(txParams); - const signedTx = yield this.sign(unsignedEthTx, from); - transactionMeta.status = TransactionStatus.signed; - this.updateTransaction(transactionMeta); - const rawTransaction = (0, ethereumjs_util_1.bufferToHex)(signedTx.serialize()); - transactionMeta.rawTransaction = rawTransaction; - this.updateTransaction(transactionMeta); - const transactionHash = yield (0, util_1.query)(this.ethQuery, 'sendRawTransaction', [ - rawTransaction, - ]); - transactionMeta.transactionHash = transactionHash; - transactionMeta.status = TransactionStatus.submitted; - this.updateTransaction(transactionMeta); - this.hub.emit(`${transactionMeta.id}:finished`, transactionMeta); - } - catch (error) { - this.failTransaction(transactionMeta, error); - } - finally { - releaseLock(); - } - }); - } - /** - * Cancels a transaction based on its ID by setting its status to "rejected" - * and emitting a `:finished` hub event. - * - * @param transactionID - The ID of the transaction to cancel. - */ - cancelTransaction(transactionID) { - const transactionMeta = this.state.transactions.find(({ id }) => id === transactionID); - if (!transactionMeta) { - return; - } - transactionMeta.status = TransactionStatus.rejected; - this.hub.emit(`${transactionMeta.id}:finished`, transactionMeta); - const transactions = this.state.transactions.filter(({ id }) => id !== transactionID); - this.update({ transactions: this.trimTransactionsForState(transactions) }); - } - /** - * Attempts to cancel a transaction based on its ID by setting its status to "rejected" - * and emitting a `:finished` hub event. - * - * @param transactionID - The ID of the transaction to cancel. - * @param gasValues - The gas values to use for the cancellation transation. - */ - stopTransaction(transactionID, gasValues) { - var _a, _b; - return __awaiter(this, void 0, void 0, function* () { - if (gasValues) { - (0, util_1.validateGasValues)(gasValues); - } - const transactionMeta = this.state.transactions.find(({ id }) => id === transactionID); - if (!transactionMeta) { - return; - } - if (!this.sign) { - throw new Error('No sign method defined.'); - } - // gasPrice (legacy non EIP1559) - const minGasPrice = (0, util_1.getIncreasedPriceFromExisting)(transactionMeta.transaction.gasPrice, exports.CANCEL_RATE); - const gasPriceFromValues = (0, util_1.isGasPriceValue)(gasValues) && gasValues.gasPrice; - const newGasPrice = (gasPriceFromValues && - (0, util_1.validateMinimumIncrease)(gasPriceFromValues, minGasPrice)) || - minGasPrice; - // maxFeePerGas (EIP1559) - const existingMaxFeePerGas = (_a = transactionMeta.transaction) === null || _a === void 0 ? void 0 : _a.maxFeePerGas; - const minMaxFeePerGas = (0, util_1.getIncreasedPriceFromExisting)(existingMaxFeePerGas, exports.CANCEL_RATE); - const maxFeePerGasValues = (0, util_1.isFeeMarketEIP1559Values)(gasValues) && gasValues.maxFeePerGas; - const newMaxFeePerGas = (maxFeePerGasValues && - (0, util_1.validateMinimumIncrease)(maxFeePerGasValues, minMaxFeePerGas)) || - (existingMaxFeePerGas && minMaxFeePerGas); - // maxPriorityFeePerGas (EIP1559) - const existingMaxPriorityFeePerGas = (_b = transactionMeta.transaction) === null || _b === void 0 ? void 0 : _b.maxPriorityFeePerGas; - const minMaxPriorityFeePerGas = (0, util_1.getIncreasedPriceFromExisting)(existingMaxPriorityFeePerGas, exports.CANCEL_RATE); - const maxPriorityFeePerGasValues = (0, util_1.isFeeMarketEIP1559Values)(gasValues) && gasValues.maxPriorityFeePerGas; - const newMaxPriorityFeePerGas = (maxPriorityFeePerGasValues && - (0, util_1.validateMinimumIncrease)(maxPriorityFeePerGasValues, minMaxPriorityFeePerGas)) || - (existingMaxPriorityFeePerGas && minMaxPriorityFeePerGas); - const txParams = newMaxFeePerGas && newMaxPriorityFeePerGas - ? { - from: transactionMeta.transaction.from, - gasLimit: transactionMeta.transaction.gas, - maxFeePerGas: newMaxFeePerGas, - maxPriorityFeePerGas: newMaxPriorityFeePerGas, - type: 2, - nonce: transactionMeta.transaction.nonce, - to: transactionMeta.transaction.from, - value: '0x0', - } - : { - from: transactionMeta.transaction.from, - gasLimit: transactionMeta.transaction.gas, - gasPrice: newGasPrice, - nonce: transactionMeta.transaction.nonce, - to: transactionMeta.transaction.from, - value: '0x0', - }; - const unsignedEthTx = this.prepareUnsignedEthTx(txParams); - const signedTx = yield this.sign(unsignedEthTx, transactionMeta.transaction.from); - const rawTransaction = (0, ethereumjs_util_1.bufferToHex)(signedTx.serialize()); - yield (0, util_1.query)(this.ethQuery, 'sendRawTransaction', [rawTransaction]); - transactionMeta.status = TransactionStatus.cancelled; - this.hub.emit(`${transactionMeta.id}:finished`, transactionMeta); - }); - } - /** - * Attempts to speed up a transaction increasing transaction gasPrice by ten percent. - * - * @param transactionID - The ID of the transaction to speed up. - * @param gasValues - The gas values to use for the speed up transation. - */ - speedUpTransaction(transactionID, gasValues) { - var _a, _b; - return __awaiter(this, void 0, void 0, function* () { - if (gasValues) { - (0, util_1.validateGasValues)(gasValues); - } - const transactionMeta = this.state.transactions.find(({ id }) => id === transactionID); - /* istanbul ignore next */ - if (!transactionMeta) { - return; - } - /* istanbul ignore next */ - if (!this.sign) { - throw new Error('No sign method defined.'); - } - const { transactions } = this.state; - // gasPrice (legacy non EIP1559) - const minGasPrice = (0, util_1.getIncreasedPriceFromExisting)(transactionMeta.transaction.gasPrice, exports.SPEED_UP_RATE); - const gasPriceFromValues = (0, util_1.isGasPriceValue)(gasValues) && gasValues.gasPrice; - const newGasPrice = (gasPriceFromValues && - (0, util_1.validateMinimumIncrease)(gasPriceFromValues, minGasPrice)) || - minGasPrice; - // maxFeePerGas (EIP1559) - const existingMaxFeePerGas = (_a = transactionMeta.transaction) === null || _a === void 0 ? void 0 : _a.maxFeePerGas; - const minMaxFeePerGas = (0, util_1.getIncreasedPriceFromExisting)(existingMaxFeePerGas, exports.SPEED_UP_RATE); - const maxFeePerGasValues = (0, util_1.isFeeMarketEIP1559Values)(gasValues) && gasValues.maxFeePerGas; - const newMaxFeePerGas = (maxFeePerGasValues && - (0, util_1.validateMinimumIncrease)(maxFeePerGasValues, minMaxFeePerGas)) || - (existingMaxFeePerGas && minMaxFeePerGas); - // maxPriorityFeePerGas (EIP1559) - const existingMaxPriorityFeePerGas = (_b = transactionMeta.transaction) === null || _b === void 0 ? void 0 : _b.maxPriorityFeePerGas; - const minMaxPriorityFeePerGas = (0, util_1.getIncreasedPriceFromExisting)(existingMaxPriorityFeePerGas, exports.SPEED_UP_RATE); - const maxPriorityFeePerGasValues = (0, util_1.isFeeMarketEIP1559Values)(gasValues) && gasValues.maxPriorityFeePerGas; - const newMaxPriorityFeePerGas = (maxPriorityFeePerGasValues && - (0, util_1.validateMinimumIncrease)(maxPriorityFeePerGasValues, minMaxPriorityFeePerGas)) || - (existingMaxPriorityFeePerGas && minMaxPriorityFeePerGas); - const txParams = newMaxFeePerGas && newMaxPriorityFeePerGas - ? Object.assign(Object.assign({}, transactionMeta.transaction), { gasLimit: transactionMeta.transaction.gas, maxFeePerGas: newMaxFeePerGas, maxPriorityFeePerGas: newMaxPriorityFeePerGas, type: 2 }) : Object.assign(Object.assign({}, transactionMeta.transaction), { gasLimit: transactionMeta.transaction.gas, gasPrice: newGasPrice }); - const unsignedEthTx = this.prepareUnsignedEthTx(txParams); - const signedTx = yield this.sign(unsignedEthTx, transactionMeta.transaction.from); - const rawTransaction = (0, ethereumjs_util_1.bufferToHex)(signedTx.serialize()); - const transactionHash = yield (0, util_1.query)(this.ethQuery, 'sendRawTransaction', [ - rawTransaction, - ]); - const baseTransactionMeta = Object.assign(Object.assign({}, transactionMeta), { id: (0, uuid_1.v1)(), time: Date.now(), transactionHash }); - const newTransactionMeta = newMaxFeePerGas && newMaxPriorityFeePerGas - ? Object.assign(Object.assign({}, baseTransactionMeta), { transaction: Object.assign(Object.assign({}, transactionMeta.transaction), { maxFeePerGas: newMaxFeePerGas, maxPriorityFeePerGas: newMaxPriorityFeePerGas }) }) : Object.assign(Object.assign({}, baseTransactionMeta), { transaction: Object.assign(Object.assign({}, transactionMeta.transaction), { gasPrice: newGasPrice }) }); - transactions.push(newTransactionMeta); - this.update({ transactions: this.trimTransactionsForState(transactions) }); - this.hub.emit(`${transactionMeta.id}:speedup`, newTransactionMeta); - }); - } - /** - * Estimates required gas for a given transaction. - * - * @param transaction - The transaction to estimate gas for. - * @returns The gas and gas price. - */ - estimateGas(transaction) { - return __awaiter(this, void 0, void 0, function* () { - const estimatedTransaction = Object.assign({}, transaction); - const { gas, gasPrice: providedGasPrice, to, value, data, } = estimatedTransaction; - const gasPrice = typeof providedGasPrice === 'undefined' - ? yield (0, util_1.query)(this.ethQuery, 'gasPrice') - : providedGasPrice; - const { isCustomNetwork } = this.getNetworkState(); - // 1. If gas is already defined on the transaction, use it - if (typeof gas !== 'undefined') { - return { gas, gasPrice }; - } - const { gasLimit } = yield (0, util_1.query)(this.ethQuery, 'getBlockByNumber', [ - 'latest', - false, - ]); - // 2. If to is not defined or this is not a contract address, and there is no data use 0x5208 / 21000. - // If the newtwork is a custom network then bypass this check and fetch 'estimateGas'. - /* istanbul ignore next */ - const code = to ? yield (0, util_1.query)(this.ethQuery, 'getCode', [to]) : undefined; - /* istanbul ignore next */ - if (!isCustomNetwork && - (!to || (to && !data && (!code || code === '0x')))) { - return { gas: '0x5208', gasPrice }; - } - // if data, should be hex string format - estimatedTransaction.data = !data - ? data - : /* istanbul ignore next */ (0, ethereumjs_util_1.addHexPrefix)(data); - // 3. If this is a contract address, safely estimate gas using RPC - estimatedTransaction.value = - typeof value === 'undefined' ? '0x0' : /* istanbul ignore next */ value; - const gasLimitBN = (0, util_1.hexToBN)(gasLimit); - estimatedTransaction.gas = (0, util_1.BNToHex)((0, util_1.fractionBN)(gasLimitBN, 19, 20)); - const gasHex = yield (0, util_1.query)(this.ethQuery, 'estimateGas', [ - estimatedTransaction, - ]); - // 4. Pad estimated gas without exceeding the most recent block gasLimit. If the network is a - // a custom network then return the eth_estimateGas value. - const gasBN = (0, util_1.hexToBN)(gasHex); - const maxGasBN = gasLimitBN.muln(0.9); - const paddedGasBN = gasBN.muln(1.5); - /* istanbul ignore next */ - if (gasBN.gt(maxGasBN) || isCustomNetwork) { - return { gas: (0, ethereumjs_util_1.addHexPrefix)(gasHex), gasPrice }; - } - /* istanbul ignore next */ - if (paddedGasBN.lt(maxGasBN)) { - return { gas: (0, ethereumjs_util_1.addHexPrefix)((0, util_1.BNToHex)(paddedGasBN)), gasPrice }; - } - return { gas: (0, ethereumjs_util_1.addHexPrefix)((0, util_1.BNToHex)(maxGasBN)), gasPrice }; - }); - } - /** - * Check the status of submitted transactions on the network to determine whether they have - * been included in a block. Any that have been included in a block are marked as confirmed. - */ - queryTransactionStatuses() { - return __awaiter(this, void 0, void 0, function* () { - const { transactions } = this.state; - const { provider, network: currentNetworkID } = this.getNetworkState(); - const { chainId: currentChainId } = provider; - let gotUpdates = false; - yield (0, util_1.safelyExecute)(() => Promise.all(transactions.map((meta, index) => __awaiter(this, void 0, void 0, function* () { - // Using fallback to networkID only when there is no chainId present. - // Should be removed when networkID is completely removed. - const txBelongsToCurrentChain = meta.chainId === currentChainId || - (!meta.chainId && meta.networkID === currentNetworkID); - if (!meta.verifiedOnBlockchain && txBelongsToCurrentChain) { - const [reconciledTx, updateRequired] = yield this.blockchainTransactionStateReconciler(meta); - if (updateRequired) { - transactions[index] = reconciledTx; - gotUpdates = updateRequired; - } - } - })))); - /* istanbul ignore else */ - if (gotUpdates) { - this.update({ - transactions: this.trimTransactionsForState(transactions), - }); - } - }); - } - /** - * Updates an existing transaction in state. - * - * @param transactionMeta - The new transaction to store in state. - */ - updateTransaction(transactionMeta) { - const { transactions } = this.state; - transactionMeta.transaction = (0, util_1.normalizeTransaction)(transactionMeta.transaction); - (0, util_1.validateTransaction)(transactionMeta.transaction); - const index = transactions.findIndex(({ id }) => transactionMeta.id === id); - transactions[index] = transactionMeta; - this.update({ transactions: this.trimTransactionsForState(transactions) }); - } - /** - * Removes all transactions from state, optionally based on the current network. - * - * @param ignoreNetwork - Determines whether to wipe all transactions, or just those on the - * current network. If `true`, all transactions are wiped. - */ - wipeTransactions(ignoreNetwork) { - /* istanbul ignore next */ - if (ignoreNetwork) { - this.update({ transactions: [] }); - return; - } - const { provider, network: currentNetworkID } = this.getNetworkState(); - const { chainId: currentChainId } = provider; - const newTransactions = this.state.transactions.filter(({ networkID, chainId }) => { - // Using fallback to networkID only when there is no chainId present. Should be removed when networkID is completely removed. - const isCurrentNetwork = chainId === currentChainId || - (!chainId && networkID === currentNetworkID); - return !isCurrentNetwork; - }); - this.update({ - transactions: this.trimTransactionsForState(newTransactions), - }); - } - /** - * Get transactions from Etherscan for the given address. By default all transactions are - * returned, but the `fromBlock` option can be given to filter just for transactions from a - * specific block onward. - * - * @param address - The address to fetch the transactions for. - * @param opt - Object containing optional data, fromBlock and Etherscan API key. - * @returns The block number of the latest incoming transaction. - */ - fetchAll(address, opt) { - return __awaiter(this, void 0, void 0, function* () { - const { provider, network: currentNetworkID } = this.getNetworkState(); - const { chainId: currentChainId, type: networkType } = provider; - const { transactions } = this.state; - const supportedNetworkIds = ['1', '3', '4', '42']; - /* istanbul ignore next */ - if (supportedNetworkIds.indexOf(currentNetworkID) === -1) { - return undefined; - } - const [etherscanTxResponse, etherscanTokenResponse] = yield (0, util_1.handleTransactionFetch)(networkType, address, this.config.txHistoryLimit, opt); - const normalizedTxs = etherscanTxResponse.result.map((tx) => this.normalizeTx(tx, currentNetworkID, currentChainId)); - const normalizedTokenTxs = etherscanTokenResponse.result.map((tx) => this.normalizeTokenTx(tx, currentNetworkID, currentChainId)); - const [updateRequired, allTxs] = this.etherscanTransactionStateReconciler([...normalizedTxs, ...normalizedTokenTxs], transactions); - allTxs.sort((a, b) => (a.time < b.time ? -1 : 1)); - let latestIncomingTxBlockNumber; - allTxs.forEach((tx) => __awaiter(this, void 0, void 0, function* () { - /* istanbul ignore next */ - if ( - // Using fallback to networkID only when there is no chainId present. Should be removed when networkID is completely removed. - (tx.chainId === currentChainId || - (!tx.chainId && tx.networkID === currentNetworkID)) && - tx.transaction.to && - tx.transaction.to.toLowerCase() === address.toLowerCase()) { - if (tx.blockNumber && - (!latestIncomingTxBlockNumber || - parseInt(latestIncomingTxBlockNumber, 10) < - parseInt(tx.blockNumber, 10))) { - latestIncomingTxBlockNumber = tx.blockNumber; - } - } - /* istanbul ignore else */ - if (tx.toSmartContract === undefined) { - // If not `to` is a contract deploy, if not `data` is send eth - if (tx.transaction.to && - (!tx.transaction.data || tx.transaction.data !== '0x')) { - const code = yield (0, util_1.query)(this.ethQuery, 'getCode', [ - tx.transaction.to, - ]); - tx.toSmartContract = (0, util_1.isSmartContractCode)(code); - } - else { - tx.toSmartContract = false; - } - } - })); - // Update state only if new transactions were fetched or - // the status or gas data of a transaction has changed - if (updateRequired) { - this.update({ transactions: this.trimTransactionsForState(allTxs) }); - } - return latestIncomingTxBlockNumber; - }); - } - /** - * Trim the amount of transactions that are set on the state. Checks - * if the length of the tx history is longer then desired persistence - * limit and then if it is removes the oldest confirmed or rejected tx. - * Pending or unapproved transactions will not be removed by this - * operation. For safety of presenting a fully functional transaction UI - * representation, this function will not break apart transactions with the - * same nonce, created on the same day, per network. Not accounting for transactions of the same - * nonce, same day and network combo can result in confusing or broken experiences - * in the UI. The transactions are then updated using the BaseController update. - * - * @param transactions - The transactions to be applied to the state. - * @returns The trimmed list of transactions. - */ - trimTransactionsForState(transactions) { - const nonceNetworkSet = new Set(); - const txsToKeep = transactions.reverse().filter((tx) => { - const { chainId, networkID, status, transaction, time } = tx; - if (transaction) { - const key = `${transaction.nonce}-${chainId !== null && chainId !== void 0 ? chainId : networkID}-${new Date(time).toDateString()}`; - if (nonceNetworkSet.has(key)) { - return true; - } - else if (nonceNetworkSet.size < this.config.txHistoryLimit || - !this.isFinalState(status)) { - nonceNetworkSet.add(key); - return true; - } - } - return false; - }); - txsToKeep.reverse(); - return txsToKeep; - } - /** - * Determines if the transaction is in a final state. - * - * @param status - The transaction status. - * @returns Whether the transaction is in a final state. - */ - isFinalState(status) { - return (status === TransactionStatus.rejected || - status === TransactionStatus.confirmed || - status === TransactionStatus.failed || - status === TransactionStatus.cancelled); - } - /** - * Method to verify the state of a transaction using the Blockchain as a source of truth. - * - * @param meta - The local transaction to verify on the blockchain. - * @returns A tuple containing the updated transaction, and whether or not an update was required. - */ - blockchainTransactionStateReconciler(meta) { - return __awaiter(this, void 0, void 0, function* () { - const { status, transactionHash } = meta; - switch (status) { - case TransactionStatus.confirmed: - const txReceipt = yield (0, util_1.query)(this.ethQuery, 'getTransactionReceipt', [ - transactionHash, - ]); - if (!txReceipt) { - return [meta, false]; - } - meta.verifiedOnBlockchain = true; - meta.transaction.gasUsed = txReceipt.gasUsed; - // According to the Web3 docs: - // TRUE if the transaction was successful, FALSE if the EVM reverted the transaction. - if (Number(txReceipt.status) === 0) { - const error = new Error('Transaction failed. The transaction was reversed'); - this.failTransaction(meta, error); - return [meta, false]; - } - return [meta, true]; - case TransactionStatus.submitted: - const txObj = yield (0, util_1.query)(this.ethQuery, 'getTransactionByHash', [ - transactionHash, - ]); - if (!txObj) { - const receiptShowsFailedStatus = yield this.checkTxReceiptStatusIsFailed(transactionHash); - // Case the txObj is evaluated as false, a second check will - // determine if the tx failed or it is pending or confirmed - if (receiptShowsFailedStatus) { - const error = new Error('Transaction failed. The transaction was dropped or replaced by a new one'); - this.failTransaction(meta, error); - } - } - /* istanbul ignore next */ - if (txObj === null || txObj === void 0 ? void 0 : txObj.blockNumber) { - meta.status = TransactionStatus.confirmed; - this.hub.emit(`${meta.id}:confirmed`, meta); - return [meta, true]; - } - return [meta, false]; - default: - return [meta, false]; - } - }); - } - /** - * Method to check if a tx has failed according to their receipt - * According to the Web3 docs: - * TRUE if the transaction was successful, FALSE if the EVM reverted the transaction. - * The receipt is not available for pending transactions and returns null. - * - * @param txHash - The transaction hash. - * @returns Whether the transaction has failed. - */ - checkTxReceiptStatusIsFailed(txHash) { - return __awaiter(this, void 0, void 0, function* () { - const txReceipt = yield (0, util_1.query)(this.ethQuery, 'getTransactionReceipt', [ - txHash, - ]); - if (!txReceipt) { - // Transaction is pending - return false; - } - return Number(txReceipt.status) === 0; - }); - } - /** - * Method to verify the state of transactions using Etherscan as a source of truth. - * - * @param remoteTxs - Transactions to reconcile that are from a remote source. - * @param localTxs - Transactions to reconcile that are local. - * @returns A tuple containing a boolean indicating whether or not an update was required, and the updated transaction. - */ - etherscanTransactionStateReconciler(remoteTxs, localTxs) { - const updatedTxs = this.getUpdatedTransactions(remoteTxs, localTxs); - const newTxs = this.getNewTransactions(remoteTxs, localTxs); - const updatedLocalTxs = localTxs.map((tx) => { - const txIdx = updatedTxs.findIndex(({ transactionHash }) => transactionHash === tx.transactionHash); - return txIdx === -1 ? tx : updatedTxs[txIdx]; - }); - const updateRequired = newTxs.length > 0 || updatedLocalTxs.length > 0; - return [updateRequired, [...newTxs, ...updatedLocalTxs]]; - } - /** - * Get all transactions that are in the remote transactions array - * but not in the local transactions array. - * - * @param remoteTxs - Array of transactions from remote source. - * @param localTxs - Array of transactions stored locally. - * @returns The new transactions. - */ - getNewTransactions(remoteTxs, localTxs) { - return remoteTxs.filter((tx) => { - const alreadyInTransactions = localTxs.find(({ transactionHash }) => transactionHash === tx.transactionHash); - return !alreadyInTransactions; - }); - } - /** - * Get all the transactions that are locally outdated with respect - * to a remote source (etherscan or blockchain). The returned array - * contains the transactions with the updated data. - * - * @param remoteTxs - Array of transactions from remote source. - * @param localTxs - Array of transactions stored locally. - * @returns The updated transactions. - */ - getUpdatedTransactions(remoteTxs, localTxs) { - return remoteTxs.filter((remoteTx) => { - const isTxOutdated = localTxs.find((localTx) => { - return (remoteTx.transactionHash === localTx.transactionHash && - this.isTransactionOutdated(remoteTx, localTx)); - }); - return isTxOutdated; - }); - } - /** - * Verifies if a local transaction is outdated with respect to the remote transaction. - * - * @param remoteTx - The remote transaction from Etherscan. - * @param localTx - The local transaction. - * @returns Whether the transaction is outdated. - */ - isTransactionOutdated(remoteTx, localTx) { - const statusOutdated = this.isStatusOutdated(remoteTx.transactionHash, localTx.transactionHash, remoteTx.status, localTx.status); - const gasDataOutdated = this.isGasDataOutdated(remoteTx.transaction.gasUsed, localTx.transaction.gasUsed); - return statusOutdated || gasDataOutdated; - } - /** - * Verifies if the status of a local transaction is outdated with respect to the remote transaction. - * - * @param remoteTxHash - Remote transaction hash. - * @param localTxHash - Local transaction hash. - * @param remoteTxStatus - Remote transaction status. - * @param localTxStatus - Local transaction status. - * @returns Whether the status is outdated. - */ - isStatusOutdated(remoteTxHash, localTxHash, remoteTxStatus, localTxStatus) { - return remoteTxHash === localTxHash && remoteTxStatus !== localTxStatus; - } - /** - * Verifies if the gas data of a local transaction is outdated with respect to the remote transaction. - * - * @param remoteGasUsed - Remote gas used in the transaction. - * @param localGasUsed - Local gas used in the transaction. - * @returns Whether the gas data is outdated. - */ - isGasDataOutdated(remoteGasUsed, localGasUsed) { - return remoteGasUsed !== localGasUsed; - } -} -exports.TransactionController = TransactionController; -exports.default = TransactionController; -//# sourceMappingURL=TransactionController.js.map \ No newline at end of file diff --git a/dist/transaction/TransactionController.js.map b/dist/transaction/TransactionController.js.map deleted file mode 100644 index ee17936aea..0000000000 --- a/dist/transaction/TransactionController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"TransactionController.js","sourceRoot":"","sources":["../../src/transaction/TransactionController.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from 'events';\nimport { addHexPrefix, bufferToHex, BN } from 'ethereumjs-util';\nimport { ethErrors } from 'eth-rpc-errors';\nimport MethodRegistry from 'eth-method-registry';\nimport EthQuery from 'eth-query';\nimport Common from '@ethereumjs/common';\nimport { TransactionFactory, TypedTransaction } from '@ethereumjs/tx';\nimport { v1 as random } from 'uuid';\nimport { Mutex } from 'async-mutex';\nimport { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport type {\n NetworkState,\n NetworkController,\n} from '../network/NetworkController';\nimport {\n BNToHex,\n fractionBN,\n hexToBN,\n normalizeTransaction,\n safelyExecute,\n validateTransaction,\n isSmartContractCode,\n handleTransactionFetch,\n query,\n getIncreasedPriceFromExisting,\n isEIP1559Transaction,\n isGasPriceValue,\n isFeeMarketEIP1559Values,\n validateGasValues,\n validateMinimumIncrease,\n} from '../util';\nimport { MAINNET, RPC } from '../constants';\n\nconst HARDFORK = 'london';\n\n/**\n * @type Result\n * @property result - Promise resolving to a new transaction hash\n * @property transactionMeta - Meta information about this new transaction\n */\nexport interface Result {\n result: Promise;\n transactionMeta: TransactionMeta;\n}\n\n/**\n * @type Fetch All Options\n * @property fromBlock - String containing a specific block decimal number\n * @property etherscanApiKey - API key to be used to fetch token transactions\n */\nexport interface FetchAllOptions {\n fromBlock?: string;\n etherscanApiKey?: string;\n}\n\n/**\n * @type Transaction\n *\n * Transaction representation\n * @property chainId - Network ID as per EIP-155\n * @property data - Data to pass with this transaction\n * @property from - Address to send this transaction from\n * @property gas - Gas to send with this transaction\n * @property gasPrice - Price of gas with this transaction\n * @property gasUsed - Gas used in the transaction\n * @property nonce - Unique number to prevent replay attacks\n * @property to - Address to send this transaction to\n * @property value - Value associated with this transaction\n */\nexport interface Transaction {\n chainId?: number;\n data?: string;\n from: string;\n gas?: string;\n gasPrice?: string;\n gasUsed?: string;\n nonce?: string;\n to?: string;\n value?: string;\n maxFeePerGas?: string;\n maxPriorityFeePerGas?: string;\n estimatedBaseFee?: string;\n}\n\nexport interface GasPriceValue {\n gasPrice: string;\n}\n\nexport interface FeeMarketEIP1559Values {\n maxFeePerGas: string;\n maxPriorityFeePerGas: string;\n}\n\n/**\n * The status of the transaction. Each status represents the state of the transaction internally\n * in the wallet. Some of these correspond with the state of the transaction on the network, but\n * some are wallet-specific.\n */\nexport enum TransactionStatus {\n approved = 'approved',\n cancelled = 'cancelled',\n confirmed = 'confirmed',\n failed = 'failed',\n rejected = 'rejected',\n signed = 'signed',\n submitted = 'submitted',\n unapproved = 'unapproved',\n}\n\n/**\n * Options for wallet device.\n */\nexport enum WalletDevice {\n MM_MOBILE = 'metamask_mobile',\n MM_EXTENSION = 'metamask_extension',\n OTHER = 'other_device',\n}\n\ntype TransactionMetaBase = {\n isTransfer?: boolean;\n transferInformation?: {\n symbol: string;\n contractAddress: string;\n decimals: number;\n };\n id: string;\n networkID?: string;\n chainId?: string;\n origin?: string;\n rawTransaction?: string;\n time: number;\n toSmartContract?: boolean;\n transaction: Transaction;\n transactionHash?: string;\n blockNumber?: string;\n deviceConfirmedOn?: WalletDevice;\n verifiedOnBlockchain?: boolean;\n};\n\n/**\n * @type TransactionMeta\n *\n * TransactionMeta representation\n * @property error - Synthesized error information for failed transactions\n * @property id - Generated UUID associated with this transaction\n * @property networkID - Network code as per EIP-155 for this transaction\n * @property origin - Origin this transaction was sent from\n * @property deviceConfirmedOn - string to indicate what device the transaction was confirmed\n * @property rawTransaction - Hex representation of the underlying transaction\n * @property status - String status of this transaction\n * @property time - Timestamp associated with this transaction\n * @property toSmartContract - Whether transaction recipient is a smart contract\n * @property transaction - Underlying Transaction object\n * @property transactionHash - Hash of a successful transaction\n * @property blockNumber - Number of the block where the transaction has been included\n */\nexport type TransactionMeta =\n | ({\n status: Exclude;\n } & TransactionMetaBase)\n | ({ status: TransactionStatus.failed; error: Error } & TransactionMetaBase);\n\n/**\n * @type EtherscanTransactionMeta\n *\n * EtherscanTransactionMeta representation\n * @property blockNumber - Number of the block where the transaction has been included\n * @property timeStamp - Timestamp associated with this transaction\n * @property hash - Hash of a successful transaction\n * @property nonce - Nonce of the transaction\n * @property blockHash - Hash of the block where the transaction has been included\n * @property transactionIndex - Etherscan internal index for this transaction\n * @property from - Address to send this transaction from\n * @property to - Address to send this transaction to\n * @property gas - Gas to send with this transaction\n * @property gasPrice - Price of gas with this transaction\n * @property isError - Synthesized error information for failed transactions\n * @property txreceipt_status - Receipt status for this transaction\n * @property input - input of the transaction\n * @property contractAddress - Address of the contract\n * @property cumulativeGasUsed - Amount of gas used\n * @property confirmations - Number of confirmations\n */\nexport interface EtherscanTransactionMeta {\n blockNumber: string;\n timeStamp: string;\n hash: string;\n nonce: string;\n blockHash: string;\n transactionIndex: string;\n from: string;\n to: string;\n value: string;\n gas: string;\n gasPrice: string;\n cumulativeGasUsed: string;\n gasUsed: string;\n isError: string;\n txreceipt_status: string;\n input: string;\n contractAddress: string;\n confirmations: string;\n tokenDecimal: string;\n tokenSymbol: string;\n}\n\n/**\n * @type TransactionConfig\n *\n * Transaction controller configuration\n * @property interval - Polling interval used to fetch new currency rate\n * @property provider - Provider used to create a new underlying EthQuery instance\n * @property sign - Method used to sign transactions\n */\nexport interface TransactionConfig extends BaseConfig {\n interval: number;\n sign?: (transaction: Transaction, from: string) => Promise;\n txHistoryLimit: number;\n}\n\n/**\n * @type MethodData\n *\n * Method data registry object\n * @property registryMethod - Registry method raw string\n * @property parsedRegistryMethod - Registry method object, containing name and method arguments\n */\nexport interface MethodData {\n registryMethod: string;\n parsedRegistryMethod: Record;\n}\n\n/**\n * @type TransactionState\n *\n * Transaction controller state\n * @property transactions - A list of TransactionMeta objects\n * @property methodData - Object containing all known method data information\n */\nexport interface TransactionState extends BaseState {\n transactions: TransactionMeta[];\n methodData: { [key: string]: MethodData };\n}\n\n/**\n * Multiplier used to determine a transaction's increased gas fee during cancellation\n */\nexport const CANCEL_RATE = 1.5;\n\n/**\n * Multiplier used to determine a transaction's increased gas fee during speed up\n */\nexport const SPEED_UP_RATE = 1.1;\n\n/**\n * Controller responsible for submitting and managing transactions\n */\nexport class TransactionController extends BaseController<\n TransactionConfig,\n TransactionState\n> {\n private ethQuery: any;\n\n private registry: any;\n\n private handle?: NodeJS.Timer;\n\n private mutex = new Mutex();\n\n private getNetworkState: () => NetworkState;\n\n private failTransaction(transactionMeta: TransactionMeta, error: Error) {\n const newTransactionMeta = {\n ...transactionMeta,\n error,\n status: TransactionStatus.failed,\n };\n this.updateTransaction(newTransactionMeta);\n this.hub.emit(`${transactionMeta.id}:finished`, newTransactionMeta);\n }\n\n private async registryLookup(fourBytePrefix: string): Promise {\n const registryMethod = await this.registry.lookup(fourBytePrefix);\n const parsedRegistryMethod = this.registry.parse(registryMethod);\n return { registryMethod, parsedRegistryMethod };\n }\n\n /**\n * Normalizes the transaction information from etherscan\n * to be compatible with the TransactionMeta interface.\n *\n * @param txMeta - The transaction.\n * @param currentNetworkID - The current network ID.\n * @param currentChainId - The current chain ID.\n * @returns The normalized transaction.\n */\n private normalizeTx(\n txMeta: EtherscanTransactionMeta,\n currentNetworkID: string,\n currentChainId: string,\n ): TransactionMeta {\n const time = parseInt(txMeta.timeStamp, 10) * 1000;\n const normalizedTransactionBase = {\n blockNumber: txMeta.blockNumber,\n id: random({ msecs: time }),\n networkID: currentNetworkID,\n chainId: currentChainId,\n time,\n transaction: {\n data: txMeta.input,\n from: txMeta.from,\n gas: BNToHex(new BN(txMeta.gas)),\n gasPrice: BNToHex(new BN(txMeta.gasPrice)),\n gasUsed: BNToHex(new BN(txMeta.gasUsed)),\n nonce: BNToHex(new BN(txMeta.nonce)),\n to: txMeta.to,\n value: BNToHex(new BN(txMeta.value)),\n },\n transactionHash: txMeta.hash,\n verifiedOnBlockchain: false,\n };\n\n /* istanbul ignore else */\n if (txMeta.isError === '0') {\n return {\n ...normalizedTransactionBase,\n status: TransactionStatus.confirmed,\n };\n }\n\n /* istanbul ignore next */\n return {\n ...normalizedTransactionBase,\n error: new Error('Transaction failed'),\n status: TransactionStatus.failed,\n };\n }\n\n private normalizeTokenTx = (\n txMeta: EtherscanTransactionMeta,\n currentNetworkID: string,\n currentChainId: string,\n ): TransactionMeta => {\n const time = parseInt(txMeta.timeStamp, 10) * 1000;\n const {\n to,\n from,\n gas,\n gasPrice,\n gasUsed,\n hash,\n contractAddress,\n tokenDecimal,\n tokenSymbol,\n value,\n } = txMeta;\n return {\n id: random({ msecs: time }),\n isTransfer: true,\n networkID: currentNetworkID,\n chainId: currentChainId,\n status: TransactionStatus.confirmed,\n time,\n transaction: {\n chainId: 1,\n from,\n gas,\n gasPrice,\n gasUsed,\n to,\n value,\n },\n transactionHash: hash,\n transferInformation: {\n contractAddress,\n decimals: Number(tokenDecimal),\n symbol: tokenSymbol,\n },\n verifiedOnBlockchain: false,\n };\n };\n\n /**\n * EventEmitter instance used to listen to specific transactional events\n */\n hub = new EventEmitter();\n\n /**\n * Name of this controller used during composition\n */\n override name = 'TransactionController';\n\n /**\n * Method used to sign transactions\n */\n sign?: (\n transaction: TypedTransaction,\n from: string,\n ) => Promise;\n\n /**\n * Creates a TransactionController instance.\n *\n * @param options - The controller options.\n * @param options.getNetworkState - Gets the state of the network controller.\n * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.\n * @param options.getProvider - Returns a provider for the current network.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n getNetworkState,\n onNetworkStateChange,\n getProvider,\n }: {\n getNetworkState: () => NetworkState;\n onNetworkStateChange: (listener: (state: NetworkState) => void) => void;\n getProvider: () => NetworkController['provider'];\n },\n config?: Partial,\n state?: Partial,\n ) {\n super(config, state);\n this.defaultConfig = {\n interval: 15000,\n txHistoryLimit: 40,\n };\n\n this.defaultState = {\n methodData: {},\n transactions: [],\n };\n this.initialize();\n const provider = getProvider();\n this.getNetworkState = getNetworkState;\n this.ethQuery = new EthQuery(provider);\n this.registry = new MethodRegistry({ provider });\n onNetworkStateChange(() => {\n const newProvider = getProvider();\n this.ethQuery = new EthQuery(newProvider);\n this.registry = new MethodRegistry({ provider: newProvider });\n });\n this.poll();\n }\n\n /**\n * Starts a new polling interval.\n *\n * @param interval - The polling interval used to fetch new transaction statuses.\n */\n async poll(interval?: number): Promise {\n interval && this.configure({ interval }, false, false);\n this.handle && clearTimeout(this.handle);\n await safelyExecute(() => this.queryTransactionStatuses());\n this.handle = setTimeout(() => {\n this.poll(this.config.interval);\n }, this.config.interval);\n }\n\n /**\n * Handle new method data request.\n *\n * @param fourBytePrefix - The method prefix.\n * @returns The method data object corresponding to the given signature prefix.\n */\n async handleMethodData(fourBytePrefix: string): Promise {\n const releaseLock = await this.mutex.acquire();\n try {\n const { methodData } = this.state;\n const knownMethod = Object.keys(methodData).find(\n (knownFourBytePrefix) => fourBytePrefix === knownFourBytePrefix,\n );\n if (knownMethod) {\n return methodData[fourBytePrefix];\n }\n const registry = await this.registryLookup(fourBytePrefix);\n this.update({\n methodData: { ...methodData, ...{ [fourBytePrefix]: registry } },\n });\n return registry;\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Add a new unapproved transaction to state. Parameters will be validated, a\n * unique transaction id will be generated, and gas and gasPrice will be calculated\n * if not provided. If A `:unapproved` hub event will be emitted once added.\n *\n * @param transaction - The transaction object to add.\n * @param origin - The domain origin to append to the generated TransactionMeta.\n * @param deviceConfirmedOn - An enum to indicate what device the transaction was confirmed to append to the generated TransactionMeta.\n * @returns Object containing a promise resolving to the transaction hash if approved.\n */\n async addTransaction(\n transaction: Transaction,\n origin?: string,\n deviceConfirmedOn?: WalletDevice,\n ): Promise {\n const { provider, network } = this.getNetworkState();\n const { transactions } = this.state;\n transaction = normalizeTransaction(transaction);\n validateTransaction(transaction);\n\n const transactionMeta: TransactionMeta = {\n id: random(),\n networkID: network,\n chainId: provider.chainId,\n origin,\n status: TransactionStatus.unapproved as TransactionStatus.unapproved,\n time: Date.now(),\n transaction,\n deviceConfirmedOn,\n verifiedOnBlockchain: false,\n };\n\n try {\n const { gas } = await this.estimateGas(transaction);\n transaction.gas = gas;\n } catch (error: any) {\n this.failTransaction(transactionMeta, error);\n return Promise.reject(error);\n }\n\n const result: Promise = new Promise((resolve, reject) => {\n this.hub.once(\n `${transactionMeta.id}:finished`,\n (meta: TransactionMeta) => {\n switch (meta.status) {\n case TransactionStatus.submitted:\n return resolve(meta.transactionHash as string);\n case TransactionStatus.rejected:\n return reject(\n ethErrors.provider.userRejectedRequest(\n 'User rejected the transaction',\n ),\n );\n case TransactionStatus.cancelled:\n return reject(\n ethErrors.rpc.internal('User cancelled the transaction'),\n );\n case TransactionStatus.failed:\n return reject(ethErrors.rpc.internal(meta.error.message));\n /* istanbul ignore next */\n default:\n return reject(\n ethErrors.rpc.internal(\n `MetaMask Tx Signature: Unknown problem: ${JSON.stringify(\n meta,\n )}`,\n ),\n );\n }\n },\n );\n });\n\n transactions.push(transactionMeta);\n this.update({ transactions: this.trimTransactionsForState(transactions) });\n this.hub.emit(`unapprovedTransaction`, transactionMeta);\n return { result, transactionMeta };\n }\n\n prepareUnsignedEthTx(txParams: Record): TypedTransaction {\n return TransactionFactory.fromTxData(txParams, {\n common: this.getCommonConfiguration(),\n freeze: false,\n });\n }\n\n /**\n * `@ethereumjs/tx` uses `@ethereumjs/common` as a configuration tool for\n * specifying which chain, network, hardfork and EIPs to support for\n * a transaction. By referencing this configuration, and analyzing the fields\n * specified in txParams, @ethereumjs/tx is able to determine which EIP-2718\n * transaction type to use.\n *\n * @returns {Common} common configuration object\n */\n\n getCommonConfiguration(): Common {\n const {\n network: networkId,\n provider: { type: chain, chainId, nickname: name },\n } = this.getNetworkState();\n\n if (chain !== RPC) {\n return new Common({ chain, hardfork: HARDFORK });\n }\n\n const customChainParams = {\n name,\n chainId: parseInt(chainId, undefined),\n networkId: parseInt(networkId, undefined),\n };\n\n return Common.forCustomChain(MAINNET, customChainParams, HARDFORK);\n }\n\n /**\n * Approves a transaction and updates it's status in state. If this is not a\n * retry transaction, a nonce will be generated. The transaction is signed\n * using the sign configuration property, then published to the blockchain.\n * A `:finished` hub event is fired after success or failure.\n *\n * @param transactionID - The ID of the transaction to approve.\n */\n async approveTransaction(transactionID: string) {\n const { transactions } = this.state;\n const releaseLock = await this.mutex.acquire();\n const { provider } = this.getNetworkState();\n const { chainId: currentChainId } = provider;\n const index = transactions.findIndex(({ id }) => transactionID === id);\n const transactionMeta = transactions[index];\n const { nonce } = transactionMeta.transaction;\n\n try {\n const { from } = transactionMeta.transaction;\n if (!this.sign) {\n releaseLock();\n this.failTransaction(\n transactionMeta,\n new Error('No sign method defined.'),\n );\n return;\n } else if (!currentChainId) {\n releaseLock();\n this.failTransaction(transactionMeta, new Error('No chainId defined.'));\n return;\n }\n\n const chainId = parseInt(currentChainId, undefined);\n const { approved: status } = TransactionStatus;\n\n const txNonce =\n nonce ||\n (await query(this.ethQuery, 'getTransactionCount', [from, 'pending']));\n\n transactionMeta.status = status;\n transactionMeta.transaction.nonce = txNonce;\n transactionMeta.transaction.chainId = chainId;\n\n const baseTxParams = {\n ...transactionMeta.transaction,\n gasLimit: transactionMeta.transaction.gas,\n chainId,\n nonce: txNonce,\n status,\n };\n\n const isEIP1559 = isEIP1559Transaction(transactionMeta.transaction);\n\n const txParams = isEIP1559\n ? {\n ...baseTxParams,\n maxFeePerGas: transactionMeta.transaction.maxFeePerGas,\n maxPriorityFeePerGas:\n transactionMeta.transaction.maxPriorityFeePerGas,\n estimatedBaseFee: transactionMeta.transaction.estimatedBaseFee,\n // specify type 2 if maxFeePerGas and maxPriorityFeePerGas are set\n type: 2,\n }\n : baseTxParams;\n\n // delete gasPrice if maxFeePerGas and maxPriorityFeePerGas are set\n if (isEIP1559) {\n delete txParams.gasPrice;\n }\n\n const unsignedEthTx = this.prepareUnsignedEthTx(txParams);\n const signedTx = await this.sign(unsignedEthTx, from);\n transactionMeta.status = TransactionStatus.signed;\n this.updateTransaction(transactionMeta);\n const rawTransaction = bufferToHex(signedTx.serialize());\n\n transactionMeta.rawTransaction = rawTransaction;\n this.updateTransaction(transactionMeta);\n const transactionHash = await query(this.ethQuery, 'sendRawTransaction', [\n rawTransaction,\n ]);\n transactionMeta.transactionHash = transactionHash;\n transactionMeta.status = TransactionStatus.submitted;\n this.updateTransaction(transactionMeta);\n this.hub.emit(`${transactionMeta.id}:finished`, transactionMeta);\n } catch (error: any) {\n this.failTransaction(transactionMeta, error);\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Cancels a transaction based on its ID by setting its status to \"rejected\"\n * and emitting a `:finished` hub event.\n *\n * @param transactionID - The ID of the transaction to cancel.\n */\n cancelTransaction(transactionID: string) {\n const transactionMeta = this.state.transactions.find(\n ({ id }) => id === transactionID,\n );\n if (!transactionMeta) {\n return;\n }\n transactionMeta.status = TransactionStatus.rejected;\n this.hub.emit(`${transactionMeta.id}:finished`, transactionMeta);\n const transactions = this.state.transactions.filter(\n ({ id }) => id !== transactionID,\n );\n this.update({ transactions: this.trimTransactionsForState(transactions) });\n }\n\n /**\n * Attempts to cancel a transaction based on its ID by setting its status to \"rejected\"\n * and emitting a `:finished` hub event.\n *\n * @param transactionID - The ID of the transaction to cancel.\n * @param gasValues - The gas values to use for the cancellation transation.\n */\n async stopTransaction(\n transactionID: string,\n gasValues?: GasPriceValue | FeeMarketEIP1559Values,\n ) {\n if (gasValues) {\n validateGasValues(gasValues);\n }\n const transactionMeta = this.state.transactions.find(\n ({ id }) => id === transactionID,\n );\n if (!transactionMeta) {\n return;\n }\n\n if (!this.sign) {\n throw new Error('No sign method defined.');\n }\n\n // gasPrice (legacy non EIP1559)\n const minGasPrice = getIncreasedPriceFromExisting(\n transactionMeta.transaction.gasPrice,\n CANCEL_RATE,\n );\n\n const gasPriceFromValues = isGasPriceValue(gasValues) && gasValues.gasPrice;\n\n const newGasPrice =\n (gasPriceFromValues &&\n validateMinimumIncrease(gasPriceFromValues, minGasPrice)) ||\n minGasPrice;\n\n // maxFeePerGas (EIP1559)\n const existingMaxFeePerGas = transactionMeta.transaction?.maxFeePerGas;\n const minMaxFeePerGas = getIncreasedPriceFromExisting(\n existingMaxFeePerGas,\n CANCEL_RATE,\n );\n const maxFeePerGasValues =\n isFeeMarketEIP1559Values(gasValues) && gasValues.maxFeePerGas;\n const newMaxFeePerGas =\n (maxFeePerGasValues &&\n validateMinimumIncrease(maxFeePerGasValues, minMaxFeePerGas)) ||\n (existingMaxFeePerGas && minMaxFeePerGas);\n\n // maxPriorityFeePerGas (EIP1559)\n const existingMaxPriorityFeePerGas =\n transactionMeta.transaction?.maxPriorityFeePerGas;\n const minMaxPriorityFeePerGas = getIncreasedPriceFromExisting(\n existingMaxPriorityFeePerGas,\n CANCEL_RATE,\n );\n const maxPriorityFeePerGasValues =\n isFeeMarketEIP1559Values(gasValues) && gasValues.maxPriorityFeePerGas;\n const newMaxPriorityFeePerGas =\n (maxPriorityFeePerGasValues &&\n validateMinimumIncrease(\n maxPriorityFeePerGasValues,\n minMaxPriorityFeePerGas,\n )) ||\n (existingMaxPriorityFeePerGas && minMaxPriorityFeePerGas);\n\n const txParams =\n newMaxFeePerGas && newMaxPriorityFeePerGas\n ? {\n from: transactionMeta.transaction.from,\n gasLimit: transactionMeta.transaction.gas,\n maxFeePerGas: newMaxFeePerGas,\n maxPriorityFeePerGas: newMaxPriorityFeePerGas,\n type: 2,\n nonce: transactionMeta.transaction.nonce,\n to: transactionMeta.transaction.from,\n value: '0x0',\n }\n : {\n from: transactionMeta.transaction.from,\n gasLimit: transactionMeta.transaction.gas,\n gasPrice: newGasPrice,\n nonce: transactionMeta.transaction.nonce,\n to: transactionMeta.transaction.from,\n value: '0x0',\n };\n\n const unsignedEthTx = this.prepareUnsignedEthTx(txParams);\n\n const signedTx = await this.sign(\n unsignedEthTx,\n transactionMeta.transaction.from,\n );\n const rawTransaction = bufferToHex(signedTx.serialize());\n await query(this.ethQuery, 'sendRawTransaction', [rawTransaction]);\n transactionMeta.status = TransactionStatus.cancelled;\n this.hub.emit(`${transactionMeta.id}:finished`, transactionMeta);\n }\n\n /**\n * Attempts to speed up a transaction increasing transaction gasPrice by ten percent.\n *\n * @param transactionID - The ID of the transaction to speed up.\n * @param gasValues - The gas values to use for the speed up transation.\n */\n async speedUpTransaction(\n transactionID: string,\n gasValues?: GasPriceValue | FeeMarketEIP1559Values,\n ) {\n if (gasValues) {\n validateGasValues(gasValues);\n }\n const transactionMeta = this.state.transactions.find(\n ({ id }) => id === transactionID,\n );\n /* istanbul ignore next */\n if (!transactionMeta) {\n return;\n }\n\n /* istanbul ignore next */\n if (!this.sign) {\n throw new Error('No sign method defined.');\n }\n\n const { transactions } = this.state;\n\n // gasPrice (legacy non EIP1559)\n const minGasPrice = getIncreasedPriceFromExisting(\n transactionMeta.transaction.gasPrice,\n SPEED_UP_RATE,\n );\n\n const gasPriceFromValues = isGasPriceValue(gasValues) && gasValues.gasPrice;\n\n const newGasPrice =\n (gasPriceFromValues &&\n validateMinimumIncrease(gasPriceFromValues, minGasPrice)) ||\n minGasPrice;\n\n // maxFeePerGas (EIP1559)\n const existingMaxFeePerGas = transactionMeta.transaction?.maxFeePerGas;\n const minMaxFeePerGas = getIncreasedPriceFromExisting(\n existingMaxFeePerGas,\n SPEED_UP_RATE,\n );\n const maxFeePerGasValues =\n isFeeMarketEIP1559Values(gasValues) && gasValues.maxFeePerGas;\n const newMaxFeePerGas =\n (maxFeePerGasValues &&\n validateMinimumIncrease(maxFeePerGasValues, minMaxFeePerGas)) ||\n (existingMaxFeePerGas && minMaxFeePerGas);\n\n // maxPriorityFeePerGas (EIP1559)\n const existingMaxPriorityFeePerGas =\n transactionMeta.transaction?.maxPriorityFeePerGas;\n const minMaxPriorityFeePerGas = getIncreasedPriceFromExisting(\n existingMaxPriorityFeePerGas,\n SPEED_UP_RATE,\n );\n const maxPriorityFeePerGasValues =\n isFeeMarketEIP1559Values(gasValues) && gasValues.maxPriorityFeePerGas;\n const newMaxPriorityFeePerGas =\n (maxPriorityFeePerGasValues &&\n validateMinimumIncrease(\n maxPriorityFeePerGasValues,\n minMaxPriorityFeePerGas,\n )) ||\n (existingMaxPriorityFeePerGas && minMaxPriorityFeePerGas);\n\n const txParams =\n newMaxFeePerGas && newMaxPriorityFeePerGas\n ? {\n ...transactionMeta.transaction,\n gasLimit: transactionMeta.transaction.gas,\n maxFeePerGas: newMaxFeePerGas,\n maxPriorityFeePerGas: newMaxPriorityFeePerGas,\n type: 2,\n }\n : {\n ...transactionMeta.transaction,\n gasLimit: transactionMeta.transaction.gas,\n gasPrice: newGasPrice,\n };\n\n const unsignedEthTx = this.prepareUnsignedEthTx(txParams);\n\n const signedTx = await this.sign(\n unsignedEthTx,\n transactionMeta.transaction.from,\n );\n const rawTransaction = bufferToHex(signedTx.serialize());\n const transactionHash = await query(this.ethQuery, 'sendRawTransaction', [\n rawTransaction,\n ]);\n const baseTransactionMeta = {\n ...transactionMeta,\n id: random(),\n time: Date.now(),\n transactionHash,\n };\n const newTransactionMeta =\n newMaxFeePerGas && newMaxPriorityFeePerGas\n ? {\n ...baseTransactionMeta,\n transaction: {\n ...transactionMeta.transaction,\n maxFeePerGas: newMaxFeePerGas,\n maxPriorityFeePerGas: newMaxPriorityFeePerGas,\n },\n }\n : {\n ...baseTransactionMeta,\n transaction: {\n ...transactionMeta.transaction,\n gasPrice: newGasPrice,\n },\n };\n transactions.push(newTransactionMeta);\n this.update({ transactions: this.trimTransactionsForState(transactions) });\n this.hub.emit(`${transactionMeta.id}:speedup`, newTransactionMeta);\n }\n\n /**\n * Estimates required gas for a given transaction.\n *\n * @param transaction - The transaction to estimate gas for.\n * @returns The gas and gas price.\n */\n async estimateGas(transaction: Transaction) {\n const estimatedTransaction = { ...transaction };\n const {\n gas,\n gasPrice: providedGasPrice,\n to,\n value,\n data,\n } = estimatedTransaction;\n const gasPrice =\n typeof providedGasPrice === 'undefined'\n ? await query(this.ethQuery, 'gasPrice')\n : providedGasPrice;\n const { isCustomNetwork } = this.getNetworkState();\n // 1. If gas is already defined on the transaction, use it\n if (typeof gas !== 'undefined') {\n return { gas, gasPrice };\n }\n const { gasLimit } = await query(this.ethQuery, 'getBlockByNumber', [\n 'latest',\n false,\n ]);\n\n // 2. If to is not defined or this is not a contract address, and there is no data use 0x5208 / 21000.\n // If the newtwork is a custom network then bypass this check and fetch 'estimateGas'.\n /* istanbul ignore next */\n const code = to ? await query(this.ethQuery, 'getCode', [to]) : undefined;\n /* istanbul ignore next */\n if (\n !isCustomNetwork &&\n (!to || (to && !data && (!code || code === '0x')))\n ) {\n return { gas: '0x5208', gasPrice };\n }\n\n // if data, should be hex string format\n estimatedTransaction.data = !data\n ? data\n : /* istanbul ignore next */ addHexPrefix(data);\n\n // 3. If this is a contract address, safely estimate gas using RPC\n estimatedTransaction.value =\n typeof value === 'undefined' ? '0x0' : /* istanbul ignore next */ value;\n const gasLimitBN = hexToBN(gasLimit);\n estimatedTransaction.gas = BNToHex(fractionBN(gasLimitBN, 19, 20));\n const gasHex = await query(this.ethQuery, 'estimateGas', [\n estimatedTransaction,\n ]);\n\n // 4. Pad estimated gas without exceeding the most recent block gasLimit. If the network is a\n // a custom network then return the eth_estimateGas value.\n const gasBN = hexToBN(gasHex);\n const maxGasBN = gasLimitBN.muln(0.9);\n const paddedGasBN = gasBN.muln(1.5);\n /* istanbul ignore next */\n if (gasBN.gt(maxGasBN) || isCustomNetwork) {\n return { gas: addHexPrefix(gasHex), gasPrice };\n }\n\n /* istanbul ignore next */\n if (paddedGasBN.lt(maxGasBN)) {\n return { gas: addHexPrefix(BNToHex(paddedGasBN)), gasPrice };\n }\n return { gas: addHexPrefix(BNToHex(maxGasBN)), gasPrice };\n }\n\n /**\n * Check the status of submitted transactions on the network to determine whether they have\n * been included in a block. Any that have been included in a block are marked as confirmed.\n */\n async queryTransactionStatuses() {\n const { transactions } = this.state;\n const { provider, network: currentNetworkID } = this.getNetworkState();\n const { chainId: currentChainId } = provider;\n let gotUpdates = false;\n await safelyExecute(() =>\n Promise.all(\n transactions.map(async (meta, index) => {\n // Using fallback to networkID only when there is no chainId present.\n // Should be removed when networkID is completely removed.\n const txBelongsToCurrentChain =\n meta.chainId === currentChainId ||\n (!meta.chainId && meta.networkID === currentNetworkID);\n\n if (!meta.verifiedOnBlockchain && txBelongsToCurrentChain) {\n const [reconciledTx, updateRequired] =\n await this.blockchainTransactionStateReconciler(meta);\n if (updateRequired) {\n transactions[index] = reconciledTx;\n gotUpdates = updateRequired;\n }\n }\n }),\n ),\n );\n\n /* istanbul ignore else */\n if (gotUpdates) {\n this.update({\n transactions: this.trimTransactionsForState(transactions),\n });\n }\n }\n\n /**\n * Updates an existing transaction in state.\n *\n * @param transactionMeta - The new transaction to store in state.\n */\n updateTransaction(transactionMeta: TransactionMeta) {\n const { transactions } = this.state;\n transactionMeta.transaction = normalizeTransaction(\n transactionMeta.transaction,\n );\n validateTransaction(transactionMeta.transaction);\n const index = transactions.findIndex(({ id }) => transactionMeta.id === id);\n transactions[index] = transactionMeta;\n this.update({ transactions: this.trimTransactionsForState(transactions) });\n }\n\n /**\n * Removes all transactions from state, optionally based on the current network.\n *\n * @param ignoreNetwork - Determines whether to wipe all transactions, or just those on the\n * current network. If `true`, all transactions are wiped.\n */\n wipeTransactions(ignoreNetwork?: boolean) {\n /* istanbul ignore next */\n if (ignoreNetwork) {\n this.update({ transactions: [] });\n return;\n }\n const { provider, network: currentNetworkID } = this.getNetworkState();\n const { chainId: currentChainId } = provider;\n const newTransactions = this.state.transactions.filter(\n ({ networkID, chainId }) => {\n // Using fallback to networkID only when there is no chainId present. Should be removed when networkID is completely removed.\n const isCurrentNetwork =\n chainId === currentChainId ||\n (!chainId && networkID === currentNetworkID);\n return !isCurrentNetwork;\n },\n );\n\n this.update({\n transactions: this.trimTransactionsForState(newTransactions),\n });\n }\n\n /**\n * Get transactions from Etherscan for the given address. By default all transactions are\n * returned, but the `fromBlock` option can be given to filter just for transactions from a\n * specific block onward.\n *\n * @param address - The address to fetch the transactions for.\n * @param opt - Object containing optional data, fromBlock and Etherscan API key.\n * @returns The block number of the latest incoming transaction.\n */\n async fetchAll(\n address: string,\n opt?: FetchAllOptions,\n ): Promise {\n const { provider, network: currentNetworkID } = this.getNetworkState();\n const { chainId: currentChainId, type: networkType } = provider;\n const { transactions } = this.state;\n\n const supportedNetworkIds = ['1', '3', '4', '42'];\n /* istanbul ignore next */\n if (supportedNetworkIds.indexOf(currentNetworkID) === -1) {\n return undefined;\n }\n\n const [etherscanTxResponse, etherscanTokenResponse] =\n await handleTransactionFetch(\n networkType,\n address,\n this.config.txHistoryLimit,\n opt,\n );\n\n const normalizedTxs = etherscanTxResponse.result.map(\n (tx: EtherscanTransactionMeta) =>\n this.normalizeTx(tx, currentNetworkID, currentChainId),\n );\n const normalizedTokenTxs = etherscanTokenResponse.result.map(\n (tx: EtherscanTransactionMeta) =>\n this.normalizeTokenTx(tx, currentNetworkID, currentChainId),\n );\n\n const [updateRequired, allTxs] = this.etherscanTransactionStateReconciler(\n [...normalizedTxs, ...normalizedTokenTxs],\n transactions,\n );\n\n allTxs.sort((a, b) => (a.time < b.time ? -1 : 1));\n\n let latestIncomingTxBlockNumber: string | undefined;\n allTxs.forEach(async (tx) => {\n /* istanbul ignore next */\n if (\n // Using fallback to networkID only when there is no chainId present. Should be removed when networkID is completely removed.\n (tx.chainId === currentChainId ||\n (!tx.chainId && tx.networkID === currentNetworkID)) &&\n tx.transaction.to &&\n tx.transaction.to.toLowerCase() === address.toLowerCase()\n ) {\n if (\n tx.blockNumber &&\n (!latestIncomingTxBlockNumber ||\n parseInt(latestIncomingTxBlockNumber, 10) <\n parseInt(tx.blockNumber, 10))\n ) {\n latestIncomingTxBlockNumber = tx.blockNumber;\n }\n }\n\n /* istanbul ignore else */\n if (tx.toSmartContract === undefined) {\n // If not `to` is a contract deploy, if not `data` is send eth\n if (\n tx.transaction.to &&\n (!tx.transaction.data || tx.transaction.data !== '0x')\n ) {\n const code = await query(this.ethQuery, 'getCode', [\n tx.transaction.to,\n ]);\n tx.toSmartContract = isSmartContractCode(code);\n } else {\n tx.toSmartContract = false;\n }\n }\n });\n\n // Update state only if new transactions were fetched or\n // the status or gas data of a transaction has changed\n if (updateRequired) {\n this.update({ transactions: this.trimTransactionsForState(allTxs) });\n }\n return latestIncomingTxBlockNumber;\n }\n\n /**\n * Trim the amount of transactions that are set on the state. Checks\n * if the length of the tx history is longer then desired persistence\n * limit and then if it is removes the oldest confirmed or rejected tx.\n * Pending or unapproved transactions will not be removed by this\n * operation. For safety of presenting a fully functional transaction UI\n * representation, this function will not break apart transactions with the\n * same nonce, created on the same day, per network. Not accounting for transactions of the same\n * nonce, same day and network combo can result in confusing or broken experiences\n * in the UI. The transactions are then updated using the BaseController update.\n *\n * @param transactions - The transactions to be applied to the state.\n * @returns The trimmed list of transactions.\n */\n private trimTransactionsForState(\n transactions: TransactionMeta[],\n ): TransactionMeta[] {\n const nonceNetworkSet = new Set();\n const txsToKeep = transactions.reverse().filter((tx) => {\n const { chainId, networkID, status, transaction, time } = tx;\n if (transaction) {\n const key = `${transaction.nonce}-${chainId ?? networkID}-${new Date(\n time,\n ).toDateString()}`;\n if (nonceNetworkSet.has(key)) {\n return true;\n } else if (\n nonceNetworkSet.size < this.config.txHistoryLimit ||\n !this.isFinalState(status)\n ) {\n nonceNetworkSet.add(key);\n return true;\n }\n }\n return false;\n });\n txsToKeep.reverse();\n return txsToKeep;\n }\n\n /**\n * Determines if the transaction is in a final state.\n *\n * @param status - The transaction status.\n * @returns Whether the transaction is in a final state.\n */\n private isFinalState(status: TransactionStatus): boolean {\n return (\n status === TransactionStatus.rejected ||\n status === TransactionStatus.confirmed ||\n status === TransactionStatus.failed ||\n status === TransactionStatus.cancelled\n );\n }\n\n /**\n * Method to verify the state of a transaction using the Blockchain as a source of truth.\n *\n * @param meta - The local transaction to verify on the blockchain.\n * @returns A tuple containing the updated transaction, and whether or not an update was required.\n */\n private async blockchainTransactionStateReconciler(\n meta: TransactionMeta,\n ): Promise<[TransactionMeta, boolean]> {\n const { status, transactionHash } = meta;\n switch (status) {\n case TransactionStatus.confirmed:\n const txReceipt = await query(this.ethQuery, 'getTransactionReceipt', [\n transactionHash,\n ]);\n\n if (!txReceipt) {\n return [meta, false];\n }\n\n meta.verifiedOnBlockchain = true;\n meta.transaction.gasUsed = txReceipt.gasUsed;\n\n // According to the Web3 docs:\n // TRUE if the transaction was successful, FALSE if the EVM reverted the transaction.\n if (Number(txReceipt.status) === 0) {\n const error: Error = new Error(\n 'Transaction failed. The transaction was reversed',\n );\n this.failTransaction(meta, error);\n return [meta, false];\n }\n\n return [meta, true];\n case TransactionStatus.submitted:\n const txObj = await query(this.ethQuery, 'getTransactionByHash', [\n transactionHash,\n ]);\n\n if (!txObj) {\n const receiptShowsFailedStatus =\n await this.checkTxReceiptStatusIsFailed(transactionHash);\n\n // Case the txObj is evaluated as false, a second check will\n // determine if the tx failed or it is pending or confirmed\n if (receiptShowsFailedStatus) {\n const error: Error = new Error(\n 'Transaction failed. The transaction was dropped or replaced by a new one',\n );\n this.failTransaction(meta, error);\n }\n }\n\n /* istanbul ignore next */\n if (txObj?.blockNumber) {\n meta.status = TransactionStatus.confirmed;\n this.hub.emit(`${meta.id}:confirmed`, meta);\n return [meta, true];\n }\n\n return [meta, false];\n default:\n return [meta, false];\n }\n }\n\n /**\n * Method to check if a tx has failed according to their receipt\n * According to the Web3 docs:\n * TRUE if the transaction was successful, FALSE if the EVM reverted the transaction.\n * The receipt is not available for pending transactions and returns null.\n *\n * @param txHash - The transaction hash.\n * @returns Whether the transaction has failed.\n */\n private async checkTxReceiptStatusIsFailed(\n txHash: string | undefined,\n ): Promise {\n const txReceipt = await query(this.ethQuery, 'getTransactionReceipt', [\n txHash,\n ]);\n if (!txReceipt) {\n // Transaction is pending\n return false;\n }\n return Number(txReceipt.status) === 0;\n }\n\n /**\n * Method to verify the state of transactions using Etherscan as a source of truth.\n *\n * @param remoteTxs - Transactions to reconcile that are from a remote source.\n * @param localTxs - Transactions to reconcile that are local.\n * @returns A tuple containing a boolean indicating whether or not an update was required, and the updated transaction.\n */\n private etherscanTransactionStateReconciler(\n remoteTxs: TransactionMeta[],\n localTxs: TransactionMeta[],\n ): [boolean, TransactionMeta[]] {\n const updatedTxs: TransactionMeta[] = this.getUpdatedTransactions(\n remoteTxs,\n localTxs,\n );\n\n const newTxs: TransactionMeta[] = this.getNewTransactions(\n remoteTxs,\n localTxs,\n );\n\n const updatedLocalTxs = localTxs.map((tx: TransactionMeta) => {\n const txIdx = updatedTxs.findIndex(\n ({ transactionHash }) => transactionHash === tx.transactionHash,\n );\n return txIdx === -1 ? tx : updatedTxs[txIdx];\n });\n\n const updateRequired = newTxs.length > 0 || updatedLocalTxs.length > 0;\n\n return [updateRequired, [...newTxs, ...updatedLocalTxs]];\n }\n\n /**\n * Get all transactions that are in the remote transactions array\n * but not in the local transactions array.\n *\n * @param remoteTxs - Array of transactions from remote source.\n * @param localTxs - Array of transactions stored locally.\n * @returns The new transactions.\n */\n private getNewTransactions(\n remoteTxs: TransactionMeta[],\n localTxs: TransactionMeta[],\n ): TransactionMeta[] {\n return remoteTxs.filter((tx) => {\n const alreadyInTransactions = localTxs.find(\n ({ transactionHash }) => transactionHash === tx.transactionHash,\n );\n return !alreadyInTransactions;\n });\n }\n\n /**\n * Get all the transactions that are locally outdated with respect\n * to a remote source (etherscan or blockchain). The returned array\n * contains the transactions with the updated data.\n *\n * @param remoteTxs - Array of transactions from remote source.\n * @param localTxs - Array of transactions stored locally.\n * @returns The updated transactions.\n */\n private getUpdatedTransactions(\n remoteTxs: TransactionMeta[],\n localTxs: TransactionMeta[],\n ): TransactionMeta[] {\n return remoteTxs.filter((remoteTx) => {\n const isTxOutdated = localTxs.find((localTx) => {\n return (\n remoteTx.transactionHash === localTx.transactionHash &&\n this.isTransactionOutdated(remoteTx, localTx)\n );\n });\n return isTxOutdated;\n });\n }\n\n /**\n * Verifies if a local transaction is outdated with respect to the remote transaction.\n *\n * @param remoteTx - The remote transaction from Etherscan.\n * @param localTx - The local transaction.\n * @returns Whether the transaction is outdated.\n */\n private isTransactionOutdated(\n remoteTx: TransactionMeta,\n localTx: TransactionMeta,\n ): boolean {\n const statusOutdated = this.isStatusOutdated(\n remoteTx.transactionHash,\n localTx.transactionHash,\n remoteTx.status,\n localTx.status,\n );\n const gasDataOutdated = this.isGasDataOutdated(\n remoteTx.transaction.gasUsed,\n localTx.transaction.gasUsed,\n );\n return statusOutdated || gasDataOutdated;\n }\n\n /**\n * Verifies if the status of a local transaction is outdated with respect to the remote transaction.\n *\n * @param remoteTxHash - Remote transaction hash.\n * @param localTxHash - Local transaction hash.\n * @param remoteTxStatus - Remote transaction status.\n * @param localTxStatus - Local transaction status.\n * @returns Whether the status is outdated.\n */\n private isStatusOutdated(\n remoteTxHash: string | undefined,\n localTxHash: string | undefined,\n remoteTxStatus: TransactionStatus,\n localTxStatus: TransactionStatus,\n ): boolean {\n return remoteTxHash === localTxHash && remoteTxStatus !== localTxStatus;\n }\n\n /**\n * Verifies if the gas data of a local transaction is outdated with respect to the remote transaction.\n *\n * @param remoteGasUsed - Remote gas used in the transaction.\n * @param localGasUsed - Local gas used in the transaction.\n * @returns Whether the gas data is outdated.\n */\n private isGasDataOutdated(\n remoteGasUsed: string | undefined,\n localGasUsed: string | undefined,\n ): boolean {\n return remoteGasUsed !== localGasUsed;\n }\n}\n\nexport default TransactionController;\n"]} \ No newline at end of file diff --git a/dist/transaction/mocks/txsMock.d.ts b/dist/transaction/mocks/txsMock.d.ts deleted file mode 100644 index 649d313651..0000000000 --- a/dist/transaction/mocks/txsMock.d.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { TransactionMeta } from '../TransactionController'; -export declare const ethTxsMock: (ethTxHash: string) => ({ - blockNumber: string; - confirmations: string; - contractAddress: string; - cumulativeGasUsed: string; - from: string; - gas: string; - gasPrice: string; - gasUsed: string; - hash: string; - input: string; - isError: string; - nonce: string; - timeStamp: string; - to: string; - transactionIndex: string; - txreceipt_status: string; - value: string; -} | { - blockNumber: string; - confirmations: string; - contractAddress: string; - cumulativeGasUsed: string; - from: string; - gas: string; - gasPrice: string; - gasUsed: string; - hash: string; - input: string; - isError: string; - nonce: string; - timeStamp: string; - transactionIndex: string; - txreceipt_status: string; - value: string; - to?: undefined; -})[]; -export declare const tokenTxsMock: (tokenTxHash: string) => { - blockNumber: string; - timeStamp: string; - hash: string; - nonce: string; - blockHash: string; - from: string; - contractAddress: string; - to: string; - value: string; - tokenName: string; - tokenSymbol: string; - tokenDecimal: string; - transactionIndex: string; - gas: string; - gasPrice: string; - gasUsed: string; - cumulativeGasUsed: string; - input: string; - confirmations: string; -}[]; -export declare const txsInStateMock: (ethTxHash: string, tokenTxHash: string) => TransactionMeta[]; -export declare const txsInStateWithOutdatedStatusMock: (ethTxHash: string, tokenTxHash: string) => TransactionMeta[]; -export declare const txsInStateWithOutdatedGasDataMock: (ethTxHash: string, tokenTxHash: string) => TransactionMeta[]; -export declare const txsInStateWithOutdatedStatusAndGasDataMock: (ethTxHash: string, tokenTxHash: string) => TransactionMeta[]; diff --git a/dist/transaction/mocks/txsMock.js b/dist/transaction/mocks/txsMock.js deleted file mode 100644 index 47cf349940..0000000000 --- a/dist/transaction/mocks/txsMock.js +++ /dev/null @@ -1,515 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.txsInStateWithOutdatedStatusAndGasDataMock = exports.txsInStateWithOutdatedGasDataMock = exports.txsInStateWithOutdatedStatusMock = exports.txsInStateMock = exports.tokenTxsMock = exports.ethTxsMock = void 0; -const TransactionController_1 = require("../TransactionController"); -const ethTxsMock = (ethTxHash) => [ - { - blockNumber: '4535101', - confirmations: '10', - contractAddress: '', - cumulativeGasUsed: '120607', - from: '0xe46abaf75cfbff815c0b7ffed6f02b0760ea27f1', - gas: '335208', - gasPrice: '10000000000', - gasUsed: '21000', - hash: ethTxHash, - input: '0x', - isError: '0', - nonce: '9', - timeStamp: '1543596286', - to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - transactionIndex: '2', - txreceipt_status: '1', - value: '100000000000000000', - }, - { - blockNumber: '4535108', - confirmations: '3', - contractAddress: '', - cumulativeGasUsed: '693910', - from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - gas: '335208', - gasPrice: '20000000000', - gasUsed: '21000', - hash: '0x342e9d73e10004af41d04973339fc7219dbadcbb5629730cfe65e9f9cb15ff92', - input: '0x', - isError: '0', - nonce: '0', - timeStamp: '1543596378', - to: '0xb2d191b6fe03c5b8a1ab249cfe88c37553357a23', - transactionIndex: '12', - txreceipt_status: '1', - value: '50000000000000000', - }, - { - blockNumber: '4535105', - confirmations: '4', - contractAddress: '', - cumulativeGasUsed: '693910', - from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - gas: '335208', - gasPrice: '20000000000', - gasUsed: '21000', - hash: '0x342e9d73e10004af41d04973339fc7219dbadcbb5629730cfe65e9f9cb15ff91', - input: '0x', - isError: '0', - nonce: '1', - timeStamp: '1543596356', - transactionIndex: '13', - txreceipt_status: '1', - value: '50000000000000000', - }, - { - blockNumber: '4535106', - confirmations: '4', - contractAddress: '', - cumulativeGasUsed: '693910', - from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - gas: '335208', - gasPrice: '20000000000', - gasUsed: '21000', - hash: '0x342e9d73e10004af41d04973139fc7219dbadcbb5629730cfe65e9f9cb15ff91', - input: '0x11', - isError: '0', - nonce: '3', - timeStamp: '1543596356', - to: '0xb2d191b6fe03c5b8a1ab249cfe88c37553357a23', - transactionIndex: '13', - txreceipt_status: '1', - value: '50000000000000000', - }, -]; -exports.ethTxsMock = ethTxsMock; -const tokenTxsMock = (tokenTxHash) => [ - { - blockNumber: '8222239', - timeStamp: '1564091067', - hash: tokenTxHash, - nonce: '2329', - blockHash: '0x3c30a9be9aea7be13caad419444140c11839d72e70479ec7e9c6d8bd08c533bc', - from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071', - contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', - to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - value: '0', - tokenName: 'Sai Stablecoin v1.0', - tokenSymbol: 'SAI', - tokenDecimal: '18', - transactionIndex: '69', - gas: '624874', - gasPrice: '20000000000', - gasUsed: '21000', - cumulativeGasUsed: '3203881', - input: 'deprecated', - confirmations: '3659676', - }, - { - blockNumber: '8222250', - timeStamp: '1564091247', - hash: '0xdcd1c8bee545d3f76d80b20a23ad44276ba2e376681228eb4570cf3518491279', - nonce: '2330', - blockHash: '0x16986dd66bedb20a5b846ec2b6c0ecaa62f1c4b51fac58c1326101fd9126dd82', - from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071', - contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', - to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - value: '0', - tokenName: 'Sai Stablecoin v1.0', - tokenSymbol: 'SAI', - tokenDecimal: '18', - transactionIndex: '40', - gas: '594268', - gasPrice: '20000000000', - gasUsed: '579268', - cumulativeGasUsed: '2009011', - input: 'deprecated', - confirmations: '3659665', - }, - { - blockNumber: '8223771', - timeStamp: '1564111652', - hash: '0x070369e6f560b0deca52e050ff1a961fa7b688bbec5cea08435921c9d9b0f52e', - nonce: '2333', - blockHash: '0x0aff8b36881be99df6d176d7c64c2171672c0483684a10c112d2c90fefe30a0a', - from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071', - contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', - to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - value: '0', - tokenName: 'Sai Stablecoin v1.0', - tokenSymbol: 'SAI', - tokenDecimal: '18', - transactionIndex: '132', - gas: '583810', - gasPrice: '6000000000', - gasUsed: '568810', - cumulativeGasUsed: '6956245', - input: 'deprecated', - confirmations: '3658144', - }, - { - blockNumber: '8224850', - timeStamp: '1564126442', - hash: '0x8ef20ec9597c8c2e945bcc76d2668e5d3bb088b081fe8c5b5af2e1cbd315a20f', - nonce: '31', - blockHash: '0xb80d4d861ecb7a3cb14e591c0aaeb226842d0267772affa2acc1a590c7535647', - from: '0x6c70e3563cef0c6835703bb2664c9f59a92353e4', - contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', - to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - value: '10000000000000000000', - tokenName: 'Sai Stablecoin v1.0', - tokenSymbol: 'SAI', - tokenDecimal: '18', - transactionIndex: '169', - gas: '78447', - gasPrice: '2000000000', - gasUsed: '52298', - cumulativeGasUsed: '7047823', - input: 'deprecated', - confirmations: '3657065', - }, - { - blockNumber: '8228053', - timeStamp: '1564168901', - hash: '0xa0f2d7b558bb3cc28fa568f6feb8ed30eb28a01a674d7c0d4ae603fc691e6020', - nonce: '2368', - blockHash: '0x62c515ea049842c968ca67499f47a32a11394364d319d9c9cc0a0211652a7294', - from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071', - contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', - to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - value: '0', - tokenName: 'Sai Stablecoin v1.0', - tokenSymbol: 'SAI', - tokenDecimal: '18', - transactionIndex: '43', - gas: '567156', - gasPrice: '3000000000', - gasUsed: '552156', - cumulativeGasUsed: '3048261', - input: 'deprecated', - confirmations: '3653862', - }, - { - blockNumber: '8315335', - timeStamp: '1565339223', - hash: '0x464df60fe00b6dd04c9e8ab341d02af9b10a619d2fcd60fd2971f10edf12118f', - nonce: '206760', - blockHash: '0x98275388ef6708debe35ac7bf2e30143c9b1fd9e0e457ca03598fc1f4209e273', - from: '0x00cfbbaf7ddb3a1476767101c12a0162e241fbad', - contractAddress: '0x4dc3643dbc642b72c158e7f3d2ff232df61cb6ce', - to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - value: '100000000000000000', - tokenName: 'Amber', - tokenSymbol: 'AMB', - tokenDecimal: '18', - transactionIndex: '186', - gas: '60000', - gasPrice: '2000000000', - gasUsed: '52108', - cumulativeGasUsed: '7490707', - input: 'deprecated', - confirmations: '3566580', - }, - { - blockNumber: '8350846', - timeStamp: '1565815049', - hash: '0xc0682327ad3efd56dfa33e8206b4e09efad4e419a6191076069d217e3ee2341f', - nonce: '2506', - blockHash: '0xd0aa3c0e319fdfeb21b0192cf77b9760b8668060a5977a5f10f8413531083afa', - from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071', - contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', - to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - value: '4', - tokenName: 'Sai Stablecoin v1.0', - tokenSymbol: 'SAI', - tokenDecimal: '18', - transactionIndex: '48', - gas: '578737', - gasPrice: '3000000000', - gasUsed: '518737', - cumulativeGasUsed: '2848015', - input: 'deprecated', - confirmations: '3531069', - }, - { - blockNumber: '8350859', - timeStamp: '1565815221', - hash: '0x989ea9f3ee576fa43957f44363e839adf1a4a397c3d8392a4f7cbbf7949fd0ae', - nonce: '2', - blockHash: '0xb9cf1d29c665c052e3831b5754903e539c5b0b1d33b8bcab6cd2d450764d601f', - from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', - to: '0x09cabec1ead1c0ba254b09efb3ee13841712be14', - value: '10000000000000000000', - tokenName: 'Sai Stablecoin v1.0', - tokenSymbol: 'SAI', - tokenDecimal: '18', - transactionIndex: '31', - gas: '60734', - gasPrice: '1000000000', - gasUsed: '54745', - cumulativeGasUsed: '7833857', - input: 'deprecated', - confirmations: '3531056', - }, - { - blockNumber: '8679548', - timeStamp: '1570244087', - hash: '0xc0016b89b3b525b30d73f242653b0d80ec3ebf285376dff5bb52cef3261498b2', - nonce: '3', - blockHash: '0x1ceb2f8b83087f010773e2acf63d1526633c8a884bd1980f118a1bba576be69f', - from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', - to: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071', - value: '0', - tokenName: 'Sai Stablecoin v1.0', - tokenSymbol: 'SAI', - tokenDecimal: '18', - transactionIndex: '56', - gas: '993379', - gasPrice: '1440000000', - gasUsed: '647253', - cumulativeGasUsed: '3562204', - input: 'deprecated', - confirmations: '3202367', - }, - { - blockNumber: '8679548', - timeStamp: '1570244087', - hash: '0xc0016b89b3b525b30d73f242653b0d80ec3ebf285376dff5bb52cef3261498b2', - nonce: '3', - blockHash: '0x1ceb2f8b83087f010773e2acf63d1526633c8a884bd1980f118a1bba576be69f', - from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071', - contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', - to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - value: '0', - tokenName: 'Sai Stablecoin v1.0', - tokenSymbol: 'SAI', - tokenDecimal: '18', - transactionIndex: '56', - gas: '993379', - gasPrice: '1440000000', - gasUsed: '647253', - cumulativeGasUsed: '3562204', - input: 'deprecated', - confirmations: '3202367', - }, - { - blockNumber: '8694142', - timeStamp: '1570440625', - hash: '0xd8397138bb93d56e50d01e92a9eae99ebd3ae28844acdaa4663976a5501116cf', - nonce: '2837', - blockHash: '0xba45dd64e71e146066af9b6d2dd3bc5d72f4a3399148c155dced74c139fc3c51', - from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071', - contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', - to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - value: '0', - tokenName: 'Sai Stablecoin v1.0', - tokenSymbol: 'SAI', - tokenDecimal: '18', - transactionIndex: '217', - gas: '600632', - gasPrice: '9000000000', - gasUsed: '570632', - cumulativeGasUsed: '9023725', - input: 'deprecated', - confirmations: '3187773', - }, - { - blockNumber: '10877041', - timeStamp: '1600310867', - hash: '0xc8bd16b6b41b4c24849eb6869702e1489c808cb5b125b01f084e38fefcb5ea77', - nonce: '4', - blockHash: '0x7fa16a022bcf1f69c2d7adf6bd7d3f058e808eec5c66aaa910dfa8016a5333d1', - from: '0x090d4613473dee047c3f2706764f49e0821d256e', - contractAddress: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', - to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - value: '400000000000000000000', - tokenName: 'Uniswap', - tokenSymbol: 'UNI', - tokenDecimal: '18', - transactionIndex: '42', - gas: '90038', - gasPrice: '550000000000', - gasUsed: '81853', - cumulativeGasUsed: '3163540', - input: 'deprecated', - confirmations: '1004874', - }, - { - blockNumber: '10877897', - timeStamp: '1600321973', - hash: '0xa7162489faef826ee77862ed5210b01726524f09428f69842118dad394842d62', - nonce: '6', - blockHash: '0xa74eb9d16f65f307dde4ce58c813c981b28f242edf1090ee2ac42caac9dccaca', - from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - contractAddress: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', - to: '0x5e736f1f25992b2cad20ded179a52823d3d24b26', - value: '400000000000000000000', - tokenName: 'Uniswap', - tokenSymbol: 'UNI', - tokenDecimal: '18', - transactionIndex: '86', - gas: '60759', - gasPrice: '640000000000', - gasUsed: '25506', - cumulativeGasUsed: '4408393', - input: 'deprecated', - confirmations: '1004018', - }, -]; -exports.tokenTxsMock = tokenTxsMock; -const txsInStateMock = (ethTxHash, tokenTxHash) => [ - { - id: 'token-transaction-id', - chainId: '1', - status: TransactionController_1.TransactionStatus.confirmed, - time: 1615497996125, - transaction: { - from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - data: '0x', - gas: '624874', - gasPrice: '20000000000', - gasUsed: '21000', - nonce: '0x12', - to: '0x881d40237659c251811cec9c364ef91dc08d300c', - value: '0x0', - }, - transactionHash: tokenTxHash, - toSmartContract: true, - }, - { - id: 'eth-transaction-id', - chainId: '1', - status: TransactionController_1.TransactionStatus.confirmed, - time: 1615497996125, - transaction: { - from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - data: '0x', - gas: '0x51d68', - gasPrice: '0x2540be400', - gasUsed: '0x5208', - nonce: '0x12', - to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - value: '100000000000000000', - }, - transactionHash: ethTxHash, - toSmartContract: false, - }, -]; -exports.txsInStateMock = txsInStateMock; -const txsInStateWithOutdatedStatusMock = (ethTxHash, tokenTxHash) => [ - { - id: 'token-transaction-id', - chainId: '1', - status: TransactionController_1.TransactionStatus.rejected, - time: 1615497996125, - transaction: { - from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - data: '0x', - gas: '624874', - gasPrice: '20000000000', - gasUsed: '21000', - nonce: '0x12', - to: '0x881d40237659c251811cec9c364ef91dc08d300c', - value: '0x0', - }, - transactionHash: tokenTxHash, - toSmartContract: true, - }, - { - id: 'eth-transaction-id', - chainId: '1', - status: TransactionController_1.TransactionStatus.rejected, - time: 1615497996125, - transaction: { - from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - data: '0x', - gas: '0x51d68', - gasPrice: '0x2540be400', - gasUsed: '0x5208', - nonce: '0x12', - to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - value: '100000000000000000', - }, - transactionHash: ethTxHash, - toSmartContract: false, - }, -]; -exports.txsInStateWithOutdatedStatusMock = txsInStateWithOutdatedStatusMock; -const txsInStateWithOutdatedGasDataMock = (ethTxHash, tokenTxHash) => [ - { - id: 'token-transaction-id', - chainId: '1', - status: TransactionController_1.TransactionStatus.confirmed, - time: 1615497996125, - transaction: { - from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - data: '0x', - gas: '624874', - gasPrice: '20000000000', - gasUsed: undefined, - nonce: '0x12', - to: '0x881d40237659c251811cec9c364ef91dc08d300c', - value: '0x0', - }, - transactionHash: tokenTxHash, - toSmartContract: true, - }, - { - id: 'eth-transaction-id', - chainId: '1', - status: TransactionController_1.TransactionStatus.confirmed, - time: 1615497996125, - transaction: { - from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - data: '0x', - gas: '0x51d68', - gasPrice: '0x2540be400', - gasUsed: undefined, - nonce: '0x12', - to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - value: '100000000000000000', - }, - transactionHash: ethTxHash, - toSmartContract: false, - }, -]; -exports.txsInStateWithOutdatedGasDataMock = txsInStateWithOutdatedGasDataMock; -const txsInStateWithOutdatedStatusAndGasDataMock = (ethTxHash, tokenTxHash) => [ - { - id: 'token-transaction-id', - chainId: '1', - status: TransactionController_1.TransactionStatus.rejected, - time: 1615497996125, - transaction: { - from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - data: '0x', - gas: '624874', - gasPrice: '20000000000', - gasUsed: undefined, - nonce: '0x12', - to: '0x881d40237659c251811cec9c364ef91dc08d300c', - value: '0x0', - }, - transactionHash: tokenTxHash, - toSmartContract: true, - }, - { - id: 'eth-transaction-id', - chainId: '1', - status: TransactionController_1.TransactionStatus.rejected, - time: 1615497996125, - transaction: { - from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - data: '0x', - gas: '0x51d68', - gasPrice: '0x2540be400', - gasUsed: undefined, - nonce: '0x12', - to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207', - value: '100000000000000000', - }, - transactionHash: ethTxHash, - toSmartContract: false, - }, -]; -exports.txsInStateWithOutdatedStatusAndGasDataMock = txsInStateWithOutdatedStatusAndGasDataMock; -//# sourceMappingURL=txsMock.js.map \ No newline at end of file diff --git a/dist/transaction/mocks/txsMock.js.map b/dist/transaction/mocks/txsMock.js.map deleted file mode 100644 index f6a5db83da..0000000000 --- a/dist/transaction/mocks/txsMock.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"txsMock.js","sourceRoot":"","sources":["../../../src/transaction/mocks/txsMock.ts"],"names":[],"mappings":"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","sourcesContent":["import { TransactionMeta, TransactionStatus } from '../TransactionController';\n\nexport const ethTxsMock = (ethTxHash: string) => [\n {\n blockNumber: '4535101',\n confirmations: '10',\n contractAddress: '',\n cumulativeGasUsed: '120607',\n from: '0xe46abaf75cfbff815c0b7ffed6f02b0760ea27f1',\n gas: '335208',\n gasPrice: '10000000000',\n gasUsed: '21000',\n hash: ethTxHash,\n input: '0x',\n isError: '0',\n nonce: '9',\n timeStamp: '1543596286',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n transactionIndex: '2',\n txreceipt_status: '1',\n value: '100000000000000000',\n },\n {\n blockNumber: '4535108',\n confirmations: '3',\n contractAddress: '',\n cumulativeGasUsed: '693910',\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n gas: '335208',\n gasPrice: '20000000000',\n gasUsed: '21000',\n hash: '0x342e9d73e10004af41d04973339fc7219dbadcbb5629730cfe65e9f9cb15ff92',\n input: '0x',\n isError: '0',\n nonce: '0',\n timeStamp: '1543596378',\n to: '0xb2d191b6fe03c5b8a1ab249cfe88c37553357a23',\n transactionIndex: '12',\n txreceipt_status: '1',\n value: '50000000000000000',\n },\n {\n blockNumber: '4535105',\n confirmations: '4',\n contractAddress: '',\n cumulativeGasUsed: '693910',\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n gas: '335208',\n gasPrice: '20000000000',\n gasUsed: '21000',\n hash: '0x342e9d73e10004af41d04973339fc7219dbadcbb5629730cfe65e9f9cb15ff91',\n input: '0x',\n isError: '0',\n nonce: '1',\n timeStamp: '1543596356',\n transactionIndex: '13',\n txreceipt_status: '1',\n value: '50000000000000000',\n },\n {\n blockNumber: '4535106',\n confirmations: '4',\n contractAddress: '',\n cumulativeGasUsed: '693910',\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n gas: '335208',\n gasPrice: '20000000000',\n gasUsed: '21000',\n hash: '0x342e9d73e10004af41d04973139fc7219dbadcbb5629730cfe65e9f9cb15ff91',\n input: '0x11',\n isError: '0',\n nonce: '3',\n timeStamp: '1543596356',\n to: '0xb2d191b6fe03c5b8a1ab249cfe88c37553357a23',\n transactionIndex: '13',\n txreceipt_status: '1',\n value: '50000000000000000',\n },\n];\n\nexport const tokenTxsMock = (tokenTxHash: string) => [\n {\n blockNumber: '8222239',\n timeStamp: '1564091067',\n hash: tokenTxHash,\n nonce: '2329',\n blockHash:\n '0x3c30a9be9aea7be13caad419444140c11839d72e70479ec7e9c6d8bd08c533bc',\n from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071',\n contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '0',\n tokenName: 'Sai Stablecoin v1.0',\n tokenSymbol: 'SAI',\n tokenDecimal: '18',\n transactionIndex: '69',\n gas: '624874',\n gasPrice: '20000000000',\n gasUsed: '21000',\n cumulativeGasUsed: '3203881',\n input: 'deprecated',\n confirmations: '3659676',\n },\n {\n blockNumber: '8222250',\n timeStamp: '1564091247',\n hash: '0xdcd1c8bee545d3f76d80b20a23ad44276ba2e376681228eb4570cf3518491279',\n nonce: '2330',\n blockHash:\n '0x16986dd66bedb20a5b846ec2b6c0ecaa62f1c4b51fac58c1326101fd9126dd82',\n from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071',\n contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '0',\n tokenName: 'Sai Stablecoin v1.0',\n tokenSymbol: 'SAI',\n tokenDecimal: '18',\n transactionIndex: '40',\n gas: '594268',\n gasPrice: '20000000000',\n gasUsed: '579268',\n cumulativeGasUsed: '2009011',\n input: 'deprecated',\n confirmations: '3659665',\n },\n {\n blockNumber: '8223771',\n timeStamp: '1564111652',\n hash: '0x070369e6f560b0deca52e050ff1a961fa7b688bbec5cea08435921c9d9b0f52e',\n nonce: '2333',\n blockHash:\n '0x0aff8b36881be99df6d176d7c64c2171672c0483684a10c112d2c90fefe30a0a',\n from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071',\n contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '0',\n tokenName: 'Sai Stablecoin v1.0',\n tokenSymbol: 'SAI',\n tokenDecimal: '18',\n transactionIndex: '132',\n gas: '583810',\n gasPrice: '6000000000',\n gasUsed: '568810',\n cumulativeGasUsed: '6956245',\n input: 'deprecated',\n confirmations: '3658144',\n },\n {\n blockNumber: '8224850',\n timeStamp: '1564126442',\n hash: '0x8ef20ec9597c8c2e945bcc76d2668e5d3bb088b081fe8c5b5af2e1cbd315a20f',\n nonce: '31',\n blockHash:\n '0xb80d4d861ecb7a3cb14e591c0aaeb226842d0267772affa2acc1a590c7535647',\n from: '0x6c70e3563cef0c6835703bb2664c9f59a92353e4',\n contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '10000000000000000000',\n tokenName: 'Sai Stablecoin v1.0',\n tokenSymbol: 'SAI',\n tokenDecimal: '18',\n transactionIndex: '169',\n gas: '78447',\n gasPrice: '2000000000',\n gasUsed: '52298',\n cumulativeGasUsed: '7047823',\n input: 'deprecated',\n confirmations: '3657065',\n },\n {\n blockNumber: '8228053',\n timeStamp: '1564168901',\n hash: '0xa0f2d7b558bb3cc28fa568f6feb8ed30eb28a01a674d7c0d4ae603fc691e6020',\n nonce: '2368',\n blockHash:\n '0x62c515ea049842c968ca67499f47a32a11394364d319d9c9cc0a0211652a7294',\n from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071',\n contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '0',\n tokenName: 'Sai Stablecoin v1.0',\n tokenSymbol: 'SAI',\n tokenDecimal: '18',\n transactionIndex: '43',\n gas: '567156',\n gasPrice: '3000000000',\n gasUsed: '552156',\n cumulativeGasUsed: '3048261',\n input: 'deprecated',\n confirmations: '3653862',\n },\n {\n blockNumber: '8315335',\n timeStamp: '1565339223',\n hash: '0x464df60fe00b6dd04c9e8ab341d02af9b10a619d2fcd60fd2971f10edf12118f',\n nonce: '206760',\n blockHash:\n '0x98275388ef6708debe35ac7bf2e30143c9b1fd9e0e457ca03598fc1f4209e273',\n from: '0x00cfbbaf7ddb3a1476767101c12a0162e241fbad',\n contractAddress: '0x4dc3643dbc642b72c158e7f3d2ff232df61cb6ce',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '100000000000000000',\n tokenName: 'Amber',\n tokenSymbol: 'AMB',\n tokenDecimal: '18',\n transactionIndex: '186',\n gas: '60000',\n gasPrice: '2000000000',\n gasUsed: '52108',\n cumulativeGasUsed: '7490707',\n input: 'deprecated',\n confirmations: '3566580',\n },\n {\n blockNumber: '8350846',\n timeStamp: '1565815049',\n hash: '0xc0682327ad3efd56dfa33e8206b4e09efad4e419a6191076069d217e3ee2341f',\n nonce: '2506',\n blockHash:\n '0xd0aa3c0e319fdfeb21b0192cf77b9760b8668060a5977a5f10f8413531083afa',\n from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071',\n contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '4',\n tokenName: 'Sai Stablecoin v1.0',\n tokenSymbol: 'SAI',\n tokenDecimal: '18',\n transactionIndex: '48',\n gas: '578737',\n gasPrice: '3000000000',\n gasUsed: '518737',\n cumulativeGasUsed: '2848015',\n input: 'deprecated',\n confirmations: '3531069',\n },\n {\n blockNumber: '8350859',\n timeStamp: '1565815221',\n hash: '0x989ea9f3ee576fa43957f44363e839adf1a4a397c3d8392a4f7cbbf7949fd0ae',\n nonce: '2',\n blockHash:\n '0xb9cf1d29c665c052e3831b5754903e539c5b0b1d33b8bcab6cd2d450764d601f',\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',\n to: '0x09cabec1ead1c0ba254b09efb3ee13841712be14',\n value: '10000000000000000000',\n tokenName: 'Sai Stablecoin v1.0',\n tokenSymbol: 'SAI',\n tokenDecimal: '18',\n transactionIndex: '31',\n gas: '60734',\n gasPrice: '1000000000',\n gasUsed: '54745',\n cumulativeGasUsed: '7833857',\n input: 'deprecated',\n confirmations: '3531056',\n },\n {\n blockNumber: '8679548',\n timeStamp: '1570244087',\n hash: '0xc0016b89b3b525b30d73f242653b0d80ec3ebf285376dff5bb52cef3261498b2',\n nonce: '3',\n blockHash:\n '0x1ceb2f8b83087f010773e2acf63d1526633c8a884bd1980f118a1bba576be69f',\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',\n to: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071',\n value: '0',\n tokenName: 'Sai Stablecoin v1.0',\n tokenSymbol: 'SAI',\n tokenDecimal: '18',\n transactionIndex: '56',\n gas: '993379',\n gasPrice: '1440000000',\n gasUsed: '647253',\n cumulativeGasUsed: '3562204',\n input: 'deprecated',\n confirmations: '3202367',\n },\n {\n blockNumber: '8679548',\n timeStamp: '1570244087',\n hash: '0xc0016b89b3b525b30d73f242653b0d80ec3ebf285376dff5bb52cef3261498b2',\n nonce: '3',\n blockHash:\n '0x1ceb2f8b83087f010773e2acf63d1526633c8a884bd1980f118a1bba576be69f',\n from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071',\n contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '0',\n tokenName: 'Sai Stablecoin v1.0',\n tokenSymbol: 'SAI',\n tokenDecimal: '18',\n transactionIndex: '56',\n gas: '993379',\n gasPrice: '1440000000',\n gasUsed: '647253',\n cumulativeGasUsed: '3562204',\n input: 'deprecated',\n confirmations: '3202367',\n },\n {\n blockNumber: '8694142',\n timeStamp: '1570440625',\n hash: '0xd8397138bb93d56e50d01e92a9eae99ebd3ae28844acdaa4663976a5501116cf',\n nonce: '2837',\n blockHash:\n '0xba45dd64e71e146066af9b6d2dd3bc5d72f4a3399148c155dced74c139fc3c51',\n from: '0xdfa6edae2ec0cf1d4a60542422724a48195a5071',\n contractAddress: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '0',\n tokenName: 'Sai Stablecoin v1.0',\n tokenSymbol: 'SAI',\n tokenDecimal: '18',\n transactionIndex: '217',\n gas: '600632',\n gasPrice: '9000000000',\n gasUsed: '570632',\n cumulativeGasUsed: '9023725',\n input: 'deprecated',\n confirmations: '3187773',\n },\n {\n blockNumber: '10877041',\n timeStamp: '1600310867',\n hash: '0xc8bd16b6b41b4c24849eb6869702e1489c808cb5b125b01f084e38fefcb5ea77',\n nonce: '4',\n blockHash:\n '0x7fa16a022bcf1f69c2d7adf6bd7d3f058e808eec5c66aaa910dfa8016a5333d1',\n from: '0x090d4613473dee047c3f2706764f49e0821d256e',\n contractAddress: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '400000000000000000000',\n tokenName: 'Uniswap',\n tokenSymbol: 'UNI',\n tokenDecimal: '18',\n transactionIndex: '42',\n gas: '90038',\n gasPrice: '550000000000',\n gasUsed: '81853',\n cumulativeGasUsed: '3163540',\n input: 'deprecated',\n confirmations: '1004874',\n },\n {\n blockNumber: '10877897',\n timeStamp: '1600321973',\n hash: '0xa7162489faef826ee77862ed5210b01726524f09428f69842118dad394842d62',\n nonce: '6',\n blockHash:\n '0xa74eb9d16f65f307dde4ce58c813c981b28f242edf1090ee2ac42caac9dccaca',\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n contractAddress: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',\n to: '0x5e736f1f25992b2cad20ded179a52823d3d24b26',\n value: '400000000000000000000',\n tokenName: 'Uniswap',\n tokenSymbol: 'UNI',\n tokenDecimal: '18',\n transactionIndex: '86',\n gas: '60759',\n gasPrice: '640000000000',\n gasUsed: '25506',\n cumulativeGasUsed: '4408393',\n input: 'deprecated',\n confirmations: '1004018',\n },\n];\n\nexport const txsInStateMock = (\n ethTxHash: string,\n tokenTxHash: string,\n): TransactionMeta[] => [\n {\n id: 'token-transaction-id',\n chainId: '1',\n status: TransactionStatus.confirmed,\n time: 1615497996125,\n transaction: {\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n data: '0x',\n gas: '624874',\n gasPrice: '20000000000',\n gasUsed: '21000',\n nonce: '0x12',\n to: '0x881d40237659c251811cec9c364ef91dc08d300c',\n value: '0x0',\n },\n transactionHash: tokenTxHash,\n toSmartContract: true,\n },\n {\n id: 'eth-transaction-id',\n chainId: '1',\n status: TransactionStatus.confirmed,\n time: 1615497996125,\n transaction: {\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n data: '0x',\n gas: '0x51d68',\n gasPrice: '0x2540be400',\n gasUsed: '0x5208',\n nonce: '0x12',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '100000000000000000',\n },\n transactionHash: ethTxHash,\n toSmartContract: false,\n },\n];\n\nexport const txsInStateWithOutdatedStatusMock = (\n ethTxHash: string,\n tokenTxHash: string,\n): TransactionMeta[] => [\n {\n id: 'token-transaction-id',\n chainId: '1',\n status: TransactionStatus.rejected,\n time: 1615497996125,\n transaction: {\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n data: '0x',\n gas: '624874',\n gasPrice: '20000000000',\n gasUsed: '21000',\n nonce: '0x12',\n to: '0x881d40237659c251811cec9c364ef91dc08d300c',\n value: '0x0',\n },\n transactionHash: tokenTxHash,\n toSmartContract: true,\n },\n {\n id: 'eth-transaction-id',\n chainId: '1',\n status: TransactionStatus.rejected,\n time: 1615497996125,\n transaction: {\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n data: '0x',\n gas: '0x51d68',\n gasPrice: '0x2540be400',\n gasUsed: '0x5208',\n nonce: '0x12',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '100000000000000000',\n },\n transactionHash: ethTxHash,\n toSmartContract: false,\n },\n];\n\nexport const txsInStateWithOutdatedGasDataMock = (\n ethTxHash: string,\n tokenTxHash: string,\n): TransactionMeta[] => [\n {\n id: 'token-transaction-id',\n chainId: '1',\n status: TransactionStatus.confirmed,\n time: 1615497996125,\n transaction: {\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n data: '0x',\n gas: '624874',\n gasPrice: '20000000000',\n gasUsed: undefined,\n nonce: '0x12',\n to: '0x881d40237659c251811cec9c364ef91dc08d300c',\n value: '0x0',\n },\n transactionHash: tokenTxHash,\n toSmartContract: true,\n },\n {\n id: 'eth-transaction-id',\n chainId: '1',\n status: TransactionStatus.confirmed,\n time: 1615497996125,\n transaction: {\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n data: '0x',\n gas: '0x51d68',\n gasPrice: '0x2540be400',\n gasUsed: undefined,\n nonce: '0x12',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '100000000000000000',\n },\n transactionHash: ethTxHash,\n toSmartContract: false,\n },\n];\n\nexport const txsInStateWithOutdatedStatusAndGasDataMock = (\n ethTxHash: string,\n tokenTxHash: string,\n): TransactionMeta[] => [\n {\n id: 'token-transaction-id',\n chainId: '1',\n status: TransactionStatus.rejected,\n time: 1615497996125,\n transaction: {\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n data: '0x',\n gas: '624874',\n gasPrice: '20000000000',\n gasUsed: undefined,\n nonce: '0x12',\n to: '0x881d40237659c251811cec9c364ef91dc08d300c',\n value: '0x0',\n },\n transactionHash: tokenTxHash,\n toSmartContract: true,\n },\n {\n id: 'eth-transaction-id',\n chainId: '1',\n status: TransactionStatus.rejected,\n time: 1615497996125,\n transaction: {\n from: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n data: '0x',\n gas: '0x51d68',\n gasPrice: '0x2540be400',\n gasUsed: undefined,\n nonce: '0x12',\n to: '0x6bf137f335ea1b8f193b8f6ea92561a60d23a207',\n value: '100000000000000000',\n },\n transactionHash: ethTxHash,\n toSmartContract: false,\n },\n];\n"]} \ No newline at end of file diff --git a/dist/user/AddressBookController.d.ts b/dist/user/AddressBookController.d.ts deleted file mode 100644 index 8a94510a71..0000000000 --- a/dist/user/AddressBookController.d.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { BaseController, BaseConfig, BaseState } from '../BaseController'; -/** - * @type ContactEntry - * - * ContactEntry representation - * @property address - Hex address of a recipient account - * @property name - Nickname associated with this address - * @property importTime - Data time when an account as created/imported - */ -export interface ContactEntry { - address: string; - name: string; - importTime?: number; -} -/** - * @type AddressBookEntry - * - * AddressBookEntry representation - * @property address - Hex address of a recipient account - * @property name - Nickname associated with this address - * @property chainId - Chain id identifies the current chain - * @property memo - User's note about address - * @property isEns - is the entry an ENS name - */ -export interface AddressBookEntry { - address: string; - name: string; - chainId: string; - memo: string; - isEns: boolean; -} -/** - * @type AddressBookState - * - * Address book controller state - * @property addressBook - Array of contact entry objects - */ -export interface AddressBookState extends BaseState { - addressBook: { - [chainId: string]: { - [address: string]: AddressBookEntry; - }; - }; -} -/** - * Controller that manages a list of recipient addresses associated with nicknames - */ -export declare class AddressBookController extends BaseController { - /** - * Name of this controller used during composition - */ - name: string; - /** - * Creates an AddressBookController instance. - * - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor(config?: Partial, state?: Partial); - /** - * Remove all contract entries. - */ - clear(): void; - /** - * Remove a contract entry by address. - * - * @param chainId - Chain id identifies the current chain. - * @param address - Recipient address to delete. - * @returns Whether the entry was deleted. - */ - delete(chainId: string, address: string): boolean; - /** - * Add or update a contact entry by address. - * - * @param address - Recipient address to add or update. - * @param name - Nickname to associate with this address. - * @param chainId - Chain id identifies the current chain. - * @param memo - User's note about address. - * @returns Boolean indicating if the address was successfully set. - */ - set(address: string, name: string, chainId?: string, memo?: string): boolean; -} -export default AddressBookController; diff --git a/dist/user/AddressBookController.js b/dist/user/AddressBookController.js deleted file mode 100644 index 97e088e14a..0000000000 --- a/dist/user/AddressBookController.js +++ /dev/null @@ -1,87 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.AddressBookController = void 0; -const util_1 = require("../util"); -const BaseController_1 = require("../BaseController"); -/** - * Controller that manages a list of recipient addresses associated with nicknames - */ -class AddressBookController extends BaseController_1.BaseController { - /** - * Creates an AddressBookController instance. - * - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor(config, state) { - super(config, state); - /** - * Name of this controller used during composition - */ - this.name = 'AddressBookController'; - this.defaultState = { addressBook: {} }; - this.initialize(); - } - /** - * Remove all contract entries. - */ - clear() { - this.update({ addressBook: {} }); - } - /** - * Remove a contract entry by address. - * - * @param chainId - Chain id identifies the current chain. - * @param address - Recipient address to delete. - * @returns Whether the entry was deleted. - */ - delete(chainId, address) { - address = (0, util_1.toChecksumHexAddress)(address); - if (!(0, util_1.isValidHexAddress)(address) || - !this.state.addressBook[chainId] || - !this.state.addressBook[chainId][address]) { - return false; - } - const addressBook = Object.assign({}, this.state.addressBook); - delete addressBook[chainId][address]; - if (Object.keys(addressBook[chainId]).length === 0) { - delete addressBook[chainId]; - } - this.update({ addressBook }); - return true; - } - /** - * Add or update a contact entry by address. - * - * @param address - Recipient address to add or update. - * @param name - Nickname to associate with this address. - * @param chainId - Chain id identifies the current chain. - * @param memo - User's note about address. - * @returns Boolean indicating if the address was successfully set. - */ - set(address, name, chainId = '1', memo = '') { - address = (0, util_1.toChecksumHexAddress)(address); - if (!(0, util_1.isValidHexAddress)(address)) { - return false; - } - const entry = { - address, - chainId, - isEns: false, - memo, - name, - }; - const ensName = (0, util_1.normalizeEnsName)(name); - if (ensName) { - entry.name = ensName; - entry.isEns = true; - } - this.update({ - addressBook: Object.assign(Object.assign({}, this.state.addressBook), { [chainId]: Object.assign(Object.assign({}, this.state.addressBook[chainId]), { [address]: entry }) }), - }); - return true; - } -} -exports.AddressBookController = AddressBookController; -exports.default = AddressBookController; -//# sourceMappingURL=AddressBookController.js.map \ No newline at end of file diff --git a/dist/user/AddressBookController.js.map b/dist/user/AddressBookController.js.map deleted file mode 100644 index 7ee321edb1..0000000000 --- a/dist/user/AddressBookController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"AddressBookController.js","sourceRoot":"","sources":["../../src/user/AddressBookController.ts"],"names":[],"mappings":";;;AAAA,kCAIiB;AACjB,sDAA0E;AA4C1E;;GAEG;AACH,MAAa,qBAAsB,SAAQ,+BAG1C;IAMC;;;;;OAKG;IACH,YAAY,MAA4B,EAAE,KAAiC;QACzE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAZvB;;WAEG;QACM,SAAI,GAAG,uBAAuB,CAAC;QAWtC,IAAI,CAAC,YAAY,GAAG,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;QAExC,IAAI,CAAC,UAAU,EAAE,CAAC;IACpB,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;IACnC,CAAC;IAED;;;;;;OAMG;IACH,MAAM,CAAC,OAAe,EAAE,OAAe;QACrC,OAAO,GAAG,IAAA,2BAAoB,EAAC,OAAO,CAAC,CAAC;QACxC,IACE,CAAC,IAAA,wBAAiB,EAAC,OAAO,CAAC;YAC3B,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC;YAChC,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EACzC;YACA,OAAO,KAAK,CAAC;SACd;QAED,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAC9D,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC;QAErC,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE;YAClD,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC;SAC7B;QAED,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;;;OAQG;IACH,GAAG,CAAC,OAAe,EAAE,IAAY,EAAE,OAAO,GAAG,GAAG,EAAE,IAAI,GAAG,EAAE;QACzD,OAAO,GAAG,IAAA,2BAAoB,EAAC,OAAO,CAAC,CAAC;QACxC,IAAI,CAAC,IAAA,wBAAiB,EAAC,OAAO,CAAC,EAAE;YAC/B,OAAO,KAAK,CAAC;SACd;QAED,MAAM,KAAK,GAAG;YACZ,OAAO;YACP,OAAO;YACP,KAAK,EAAE,KAAK;YACZ,IAAI;YACJ,IAAI;SACL,CAAC;QAEF,MAAM,OAAO,GAAG,IAAA,uBAAgB,EAAC,IAAI,CAAC,CAAC;QACvC,IAAI,OAAO,EAAE;YACX,KAAK,CAAC,IAAI,GAAG,OAAO,CAAC;YACrB,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC;SACpB;QAED,IAAI,CAAC,MAAM,CAAC;YACV,WAAW,kCACN,IAAI,CAAC,KAAK,CAAC,WAAW,KACzB,CAAC,OAAO,CAAC,kCACJ,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,KAClC,CAAC,OAAO,CAAC,EAAE,KAAK,MAEnB;SACF,CAAC,CAAC;QAEH,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAnGD,sDAmGC;AAED,kBAAe,qBAAqB,CAAC","sourcesContent":["import {\n normalizeEnsName,\n isValidHexAddress,\n toChecksumHexAddress,\n} from '../util';\nimport { BaseController, BaseConfig, BaseState } from '../BaseController';\n\n/**\n * @type ContactEntry\n *\n * ContactEntry representation\n * @property address - Hex address of a recipient account\n * @property name - Nickname associated with this address\n * @property importTime - Data time when an account as created/imported\n */\nexport interface ContactEntry {\n address: string;\n name: string;\n importTime?: number;\n}\n\n/**\n * @type AddressBookEntry\n *\n * AddressBookEntry representation\n * @property address - Hex address of a recipient account\n * @property name - Nickname associated with this address\n * @property chainId - Chain id identifies the current chain\n * @property memo - User's note about address\n * @property isEns - is the entry an ENS name\n */\nexport interface AddressBookEntry {\n address: string;\n name: string;\n chainId: string;\n memo: string;\n isEns: boolean;\n}\n\n/**\n * @type AddressBookState\n *\n * Address book controller state\n * @property addressBook - Array of contact entry objects\n */\nexport interface AddressBookState extends BaseState {\n addressBook: { [chainId: string]: { [address: string]: AddressBookEntry } };\n}\n\n/**\n * Controller that manages a list of recipient addresses associated with nicknames\n */\nexport class AddressBookController extends BaseController<\n BaseConfig,\n AddressBookState\n> {\n /**\n * Name of this controller used during composition\n */\n override name = 'AddressBookController';\n\n /**\n * Creates an AddressBookController instance.\n *\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(config?: Partial, state?: Partial) {\n super(config, state);\n\n this.defaultState = { addressBook: {} };\n\n this.initialize();\n }\n\n /**\n * Remove all contract entries.\n */\n clear() {\n this.update({ addressBook: {} });\n }\n\n /**\n * Remove a contract entry by address.\n *\n * @param chainId - Chain id identifies the current chain.\n * @param address - Recipient address to delete.\n * @returns Whether the entry was deleted.\n */\n delete(chainId: string, address: string) {\n address = toChecksumHexAddress(address);\n if (\n !isValidHexAddress(address) ||\n !this.state.addressBook[chainId] ||\n !this.state.addressBook[chainId][address]\n ) {\n return false;\n }\n\n const addressBook = Object.assign({}, this.state.addressBook);\n delete addressBook[chainId][address];\n\n if (Object.keys(addressBook[chainId]).length === 0) {\n delete addressBook[chainId];\n }\n\n this.update({ addressBook });\n return true;\n }\n\n /**\n * Add or update a contact entry by address.\n *\n * @param address - Recipient address to add or update.\n * @param name - Nickname to associate with this address.\n * @param chainId - Chain id identifies the current chain.\n * @param memo - User's note about address.\n * @returns Boolean indicating if the address was successfully set.\n */\n set(address: string, name: string, chainId = '1', memo = '') {\n address = toChecksumHexAddress(address);\n if (!isValidHexAddress(address)) {\n return false;\n }\n\n const entry = {\n address,\n chainId,\n isEns: false,\n memo,\n name,\n };\n\n const ensName = normalizeEnsName(name);\n if (ensName) {\n entry.name = ensName;\n entry.isEns = true;\n }\n\n this.update({\n addressBook: {\n ...this.state.addressBook,\n [chainId]: {\n ...this.state.addressBook[chainId],\n [address]: entry,\n },\n },\n });\n\n return true;\n }\n}\n\nexport default AddressBookController;\n"]} \ No newline at end of file diff --git a/dist/user/PreferencesController.d.ts b/dist/user/PreferencesController.d.ts deleted file mode 100644 index 532860bc8f..0000000000 --- a/dist/user/PreferencesController.d.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { BaseController, BaseConfig, BaseState } from '../BaseController'; -import { ContactEntry } from './AddressBookController'; -/** - * Custom RPC network information - * - * @property rpcUrl - RPC target URL. - * @property chainId - Network ID as per EIP-155 - * @property nickname - Personalized network name. - * @property ticker - Currency ticker. - * @property rpcPrefs - Personalized preferences. - */ -export interface FrequentRpc { - rpcUrl: string; - chainId?: number; - nickname?: string; - ticker?: string; - rpcPrefs?: RpcPreferences; -} -/** - * Custom RPC network preferences - * - * @param blockExplorerUrl - Block explorer URL. - */ -export interface RpcPreferences { - blockExplorerUrl: string; -} -/** - * @type PreferencesState - * - * Preferences controller state - * @property featureFlags - Map of specific features to enable or disable - * @property frequentRpcList - A list of custom RPCs to provide the user - * @property identities - Map of addresses to ContactEntry objects - * @property lostIdentities - Map of lost addresses to ContactEntry objects - * @property selectedAddress - Current coinbase account - */ -export interface PreferencesState extends BaseState { - featureFlags: { - [feature: string]: boolean; - }; - frequentRpcList: FrequentRpc[]; - ipfsGateway: string; - identities: { - [address: string]: ContactEntry; - }; - lostIdentities: { - [address: string]: ContactEntry; - }; - selectedAddress: string; - useTokenDetection: boolean; - useCollectibleDetection: boolean; - openSeaEnabled: boolean; -} -/** - * Controller that stores shared settings and exposes convenience methods - */ -export declare class PreferencesController extends BaseController { - /** - * Name of this controller used during composition - */ - name: string; - /** - * Creates a PreferencesController instance. - * - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor(config?: Partial, state?: Partial); - /** - * Adds identities to state. - * - * @param addresses - List of addresses to use to generate new identities. - */ - addIdentities(addresses: string[]): void; - /** - * Removes an identity from state. - * - * @param address - Address of the identity to remove. - */ - removeIdentity(address: string): void; - /** - * Associates a new label with an identity. - * - * @param address - Address of the identity to associate. - * @param label - New label to assign. - */ - setAccountLabel(address: string, label: string): void; - /** - * Enable or disable a specific feature flag. - * - * @param feature - Feature to toggle. - * @param activated - Value to assign. - */ - setFeatureFlag(feature: string, activated: boolean): void; - /** - * Synchronizes the current identity list with new identities. - * - * @param addresses - List of addresses corresponding to identities to sync. - * @returns Newly-selected address after syncing. - */ - syncIdentities(addresses: string[]): string; - /** - * Generates and stores a new list of stored identities based on address. If the selected address - * is unset, or if it refers to an identity that was removed, it will be set to the first - * identity. - * - * @param addresses - List of addresses to use as a basis for each identity. - */ - updateIdentities(addresses: string[]): void; - /** - * Adds custom RPC URL to state. - * - * @param url - The custom RPC URL. - * @param chainId - The chain ID of the network, as per EIP-155. - * @param ticker - Currency ticker. - * @param nickname - Personalized network name. - * @param rpcPrefs - Personalized preferences. - */ - addToFrequentRpcList(url: string, chainId?: number, ticker?: string, nickname?: string, rpcPrefs?: RpcPreferences): void; - /** - * Removes custom RPC URL from state. - * - * @param url - Custom RPC URL. - */ - removeFromFrequentRpcList(url: string): void; - /** - * Sets selected address. - * - * @param selectedAddress - Ethereum address. - */ - setSelectedAddress(selectedAddress: string): void; - /** - * Sets new IPFS gateway. - * - * @param ipfsGateway - IPFS gateway string. - */ - setIpfsGateway(ipfsGateway: string): void; - /** - * Toggle the token detection setting. - * - * @param useTokenDetection - Boolean indicating user preference on token detection. - */ - setUseTokenDetection(useTokenDetection: boolean): void; - /** - * Toggle the collectible detection setting. - * - * @param useCollectibleDetection - Boolean indicating user preference on collectible detection. - */ - setUseCollectibleDetection(useCollectibleDetection: boolean): void; - /** - * Toggle the opensea enabled setting. - * - * @param openSeaEnabled - Boolean indicating user preference on using OpenSea's API. - */ - setOpenSeaEnabled(openSeaEnabled: boolean): void; -} -export default PreferencesController; diff --git a/dist/user/PreferencesController.js b/dist/user/PreferencesController.js deleted file mode 100644 index e0d8c2354f..0000000000 --- a/dist/user/PreferencesController.js +++ /dev/null @@ -1,243 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.PreferencesController = void 0; -const BaseController_1 = require("../BaseController"); -const util_1 = require("../util"); -/** - * Controller that stores shared settings and exposes convenience methods - */ -class PreferencesController extends BaseController_1.BaseController { - /** - * Creates a PreferencesController instance. - * - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. - */ - constructor(config, state) { - super(config, state); - /** - * Name of this controller used during composition - */ - this.name = 'PreferencesController'; - this.defaultState = { - featureFlags: {}, - frequentRpcList: [], - identities: {}, - ipfsGateway: 'https://ipfs.io/ipfs/', - lostIdentities: {}, - selectedAddress: '', - useTokenDetection: true, - useCollectibleDetection: false, - openSeaEnabled: false, - }; - this.initialize(); - } - /** - * Adds identities to state. - * - * @param addresses - List of addresses to use to generate new identities. - */ - addIdentities(addresses) { - const { identities } = this.state; - addresses.forEach((address) => { - address = (0, util_1.toChecksumHexAddress)(address); - if (identities[address]) { - return; - } - const identityCount = Object.keys(identities).length; - identities[address] = { - name: `Account ${identityCount + 1}`, - address, - importTime: Date.now(), - }; - }); - this.update({ identities: Object.assign({}, identities) }); - } - /** - * Removes an identity from state. - * - * @param address - Address of the identity to remove. - */ - removeIdentity(address) { - address = (0, util_1.toChecksumHexAddress)(address); - const { identities } = this.state; - if (!identities[address]) { - return; - } - delete identities[address]; - this.update({ identities: Object.assign({}, identities) }); - if (address === this.state.selectedAddress) { - this.update({ selectedAddress: Object.keys(identities)[0] }); - } - } - /** - * Associates a new label with an identity. - * - * @param address - Address of the identity to associate. - * @param label - New label to assign. - */ - setAccountLabel(address, label) { - address = (0, util_1.toChecksumHexAddress)(address); - const { identities } = this.state; - identities[address] = identities[address] || {}; - identities[address].name = label; - this.update({ identities: Object.assign({}, identities) }); - } - /** - * Enable or disable a specific feature flag. - * - * @param feature - Feature to toggle. - * @param activated - Value to assign. - */ - setFeatureFlag(feature, activated) { - const oldFeatureFlags = this.state.featureFlags; - const featureFlags = Object.assign(Object.assign({}, oldFeatureFlags), { [feature]: activated }); - this.update({ featureFlags: Object.assign({}, featureFlags) }); - } - /** - * Synchronizes the current identity list with new identities. - * - * @param addresses - List of addresses corresponding to identities to sync. - * @returns Newly-selected address after syncing. - */ - syncIdentities(addresses) { - addresses = addresses.map((address) => (0, util_1.toChecksumHexAddress)(address)); - const { identities, lostIdentities } = this.state; - const newlyLost = {}; - for (const identity in identities) { - if (addresses.indexOf(identity) === -1) { - newlyLost[identity] = identities[identity]; - delete identities[identity]; - } - } - if (Object.keys(newlyLost).length > 0) { - for (const key in newlyLost) { - lostIdentities[key] = newlyLost[key]; - } - } - this.update({ - identities: Object.assign({}, identities), - lostIdentities: Object.assign({}, lostIdentities), - }); - this.addIdentities(addresses); - if (addresses.indexOf(this.state.selectedAddress) === -1) { - this.update({ selectedAddress: addresses[0] }); - } - return this.state.selectedAddress; - } - /** - * Generates and stores a new list of stored identities based on address. If the selected address - * is unset, or if it refers to an identity that was removed, it will be set to the first - * identity. - * - * @param addresses - List of addresses to use as a basis for each identity. - */ - updateIdentities(addresses) { - addresses = addresses.map((address) => (0, util_1.toChecksumHexAddress)(address)); - const oldIdentities = this.state.identities; - const identities = addresses.reduce((ids, address, index) => { - ids[address] = oldIdentities[address] || { - address, - name: `Account ${index + 1}`, - importTime: Date.now(), - }; - return ids; - }, {}); - let { selectedAddress } = this.state; - if (!Object.keys(identities).includes(selectedAddress)) { - selectedAddress = Object.keys(identities)[0]; - } - this.update({ identities: Object.assign({}, identities), selectedAddress }); - } - /** - * Adds custom RPC URL to state. - * - * @param url - The custom RPC URL. - * @param chainId - The chain ID of the network, as per EIP-155. - * @param ticker - Currency ticker. - * @param nickname - Personalized network name. - * @param rpcPrefs - Personalized preferences. - */ - addToFrequentRpcList(url, chainId, ticker, nickname, rpcPrefs) { - const { frequentRpcList } = this.state; - const index = frequentRpcList.findIndex(({ rpcUrl }) => { - return rpcUrl === url; - }); - if (index !== -1) { - frequentRpcList.splice(index, 1); - } - const newFrequestRpc = { - rpcUrl: url, - chainId, - ticker, - nickname, - rpcPrefs, - }; - frequentRpcList.push(newFrequestRpc); - this.update({ frequentRpcList: [...frequentRpcList] }); - } - /** - * Removes custom RPC URL from state. - * - * @param url - Custom RPC URL. - */ - removeFromFrequentRpcList(url) { - const { frequentRpcList } = this.state; - const index = frequentRpcList.findIndex(({ rpcUrl }) => { - return rpcUrl === url; - }); - if (index !== -1) { - frequentRpcList.splice(index, 1); - } - this.update({ frequentRpcList: [...frequentRpcList] }); - } - /** - * Sets selected address. - * - * @param selectedAddress - Ethereum address. - */ - setSelectedAddress(selectedAddress) { - this.update({ selectedAddress: (0, util_1.toChecksumHexAddress)(selectedAddress) }); - } - /** - * Sets new IPFS gateway. - * - * @param ipfsGateway - IPFS gateway string. - */ - setIpfsGateway(ipfsGateway) { - this.update({ ipfsGateway }); - } - /** - * Toggle the token detection setting. - * - * @param useTokenDetection - Boolean indicating user preference on token detection. - */ - setUseTokenDetection(useTokenDetection) { - this.update({ useTokenDetection }); - } - /** - * Toggle the collectible detection setting. - * - * @param useCollectibleDetection - Boolean indicating user preference on collectible detection. - */ - setUseCollectibleDetection(useCollectibleDetection) { - if (useCollectibleDetection && !this.state.openSeaEnabled) { - throw new Error('useCollectibleDetection cannot be enabled if openSeaEnabled is false'); - } - this.update({ useCollectibleDetection }); - } - /** - * Toggle the opensea enabled setting. - * - * @param openSeaEnabled - Boolean indicating user preference on using OpenSea's API. - */ - setOpenSeaEnabled(openSeaEnabled) { - this.update({ openSeaEnabled }); - if (!openSeaEnabled) { - this.update({ useCollectibleDetection: false }); - } - } -} -exports.PreferencesController = PreferencesController; -exports.default = PreferencesController; -//# sourceMappingURL=PreferencesController.js.map \ No newline at end of file diff --git a/dist/user/PreferencesController.js.map b/dist/user/PreferencesController.js.map deleted file mode 100644 index 7e569e5a74..0000000000 --- a/dist/user/PreferencesController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"PreferencesController.js","sourceRoot":"","sources":["../../src/user/PreferencesController.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseController, BaseConfig, BaseState } from '../BaseController';\nimport { toChecksumHexAddress } from '../util';\nimport { ContactEntry } from './AddressBookController';\n\n/**\n * Custom RPC network information\n *\n * @property rpcUrl - RPC target URL.\n * @property chainId - Network ID as per EIP-155\n * @property nickname - Personalized network name.\n * @property ticker - Currency ticker.\n * @property rpcPrefs - Personalized preferences.\n */\nexport interface FrequentRpc {\n rpcUrl: string;\n chainId?: number;\n nickname?: string;\n ticker?: string;\n rpcPrefs?: RpcPreferences;\n}\n\n/**\n * Custom RPC network preferences\n *\n * @param blockExplorerUrl - Block explorer URL.\n */\nexport interface RpcPreferences {\n blockExplorerUrl: string;\n}\n\n/**\n * @type PreferencesState\n *\n * Preferences controller state\n * @property featureFlags - Map of specific features to enable or disable\n * @property frequentRpcList - A list of custom RPCs to provide the user\n * @property identities - Map of addresses to ContactEntry objects\n * @property lostIdentities - Map of lost addresses to ContactEntry objects\n * @property selectedAddress - Current coinbase account\n */\nexport interface PreferencesState extends BaseState {\n featureFlags: { [feature: string]: boolean };\n frequentRpcList: FrequentRpc[];\n ipfsGateway: string;\n identities: { [address: string]: ContactEntry };\n lostIdentities: { [address: string]: ContactEntry };\n selectedAddress: string;\n useTokenDetection: boolean;\n useCollectibleDetection: boolean;\n openSeaEnabled: boolean;\n}\n\n/**\n * Controller that stores shared settings and exposes convenience methods\n */\nexport class PreferencesController extends BaseController<\n BaseConfig,\n PreferencesState\n> {\n /**\n * Name of this controller used during composition\n */\n override name = 'PreferencesController';\n\n /**\n * Creates a PreferencesController instance.\n *\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(config?: Partial, state?: Partial) {\n super(config, state);\n this.defaultState = {\n featureFlags: {},\n frequentRpcList: [],\n identities: {},\n ipfsGateway: 'https://ipfs.io/ipfs/',\n lostIdentities: {},\n selectedAddress: '',\n useTokenDetection: true,\n useCollectibleDetection: false,\n openSeaEnabled: false,\n };\n this.initialize();\n }\n\n /**\n * Adds identities to state.\n *\n * @param addresses - List of addresses to use to generate new identities.\n */\n addIdentities(addresses: string[]) {\n const { identities } = this.state;\n addresses.forEach((address) => {\n address = toChecksumHexAddress(address);\n if (identities[address]) {\n return;\n }\n const identityCount = Object.keys(identities).length;\n\n identities[address] = {\n name: `Account ${identityCount + 1}`,\n address,\n importTime: Date.now(),\n };\n });\n this.update({ identities: { ...identities } });\n }\n\n /**\n * Removes an identity from state.\n *\n * @param address - Address of the identity to remove.\n */\n removeIdentity(address: string) {\n address = toChecksumHexAddress(address);\n const { identities } = this.state;\n if (!identities[address]) {\n return;\n }\n delete identities[address];\n this.update({ identities: { ...identities } });\n if (address === this.state.selectedAddress) {\n this.update({ selectedAddress: Object.keys(identities)[0] });\n }\n }\n\n /**\n * Associates a new label with an identity.\n *\n * @param address - Address of the identity to associate.\n * @param label - New label to assign.\n */\n setAccountLabel(address: string, label: string) {\n address = toChecksumHexAddress(address);\n const { identities } = this.state;\n identities[address] = identities[address] || {};\n identities[address].name = label;\n this.update({ identities: { ...identities } });\n }\n\n /**\n * Enable or disable a specific feature flag.\n *\n * @param feature - Feature to toggle.\n * @param activated - Value to assign.\n */\n setFeatureFlag(feature: string, activated: boolean) {\n const oldFeatureFlags = this.state.featureFlags;\n const featureFlags = { ...oldFeatureFlags, ...{ [feature]: activated } };\n this.update({ featureFlags: { ...featureFlags } });\n }\n\n /**\n * Synchronizes the current identity list with new identities.\n *\n * @param addresses - List of addresses corresponding to identities to sync.\n * @returns Newly-selected address after syncing.\n */\n syncIdentities(addresses: string[]) {\n addresses = addresses.map((address: string) =>\n toChecksumHexAddress(address),\n );\n const { identities, lostIdentities } = this.state;\n const newlyLost: { [address: string]: ContactEntry } = {};\n\n for (const identity in identities) {\n if (addresses.indexOf(identity) === -1) {\n newlyLost[identity] = identities[identity];\n delete identities[identity];\n }\n }\n\n if (Object.keys(newlyLost).length > 0) {\n for (const key in newlyLost) {\n lostIdentities[key] = newlyLost[key];\n }\n }\n\n this.update({\n identities: { ...identities },\n lostIdentities: { ...lostIdentities },\n });\n this.addIdentities(addresses);\n\n if (addresses.indexOf(this.state.selectedAddress) === -1) {\n this.update({ selectedAddress: addresses[0] });\n }\n\n return this.state.selectedAddress;\n }\n\n /**\n * Generates and stores a new list of stored identities based on address. If the selected address\n * is unset, or if it refers to an identity that was removed, it will be set to the first\n * identity.\n *\n * @param addresses - List of addresses to use as a basis for each identity.\n */\n updateIdentities(addresses: string[]) {\n addresses = addresses.map((address: string) =>\n toChecksumHexAddress(address),\n );\n const oldIdentities = this.state.identities;\n const identities = addresses.reduce(\n (ids: { [address: string]: ContactEntry }, address, index) => {\n ids[address] = oldIdentities[address] || {\n address,\n name: `Account ${index + 1}`,\n importTime: Date.now(),\n };\n return ids;\n },\n {},\n );\n let { selectedAddress } = this.state;\n if (!Object.keys(identities).includes(selectedAddress)) {\n selectedAddress = Object.keys(identities)[0];\n }\n this.update({ identities: { ...identities }, selectedAddress });\n }\n\n /**\n * Adds custom RPC URL to state.\n *\n * @param url - The custom RPC URL.\n * @param chainId - The chain ID of the network, as per EIP-155.\n * @param ticker - Currency ticker.\n * @param nickname - Personalized network name.\n * @param rpcPrefs - Personalized preferences.\n */\n addToFrequentRpcList(\n url: string,\n chainId?: number,\n ticker?: string,\n nickname?: string,\n rpcPrefs?: RpcPreferences,\n ) {\n const { frequentRpcList } = this.state;\n const index = frequentRpcList.findIndex(({ rpcUrl }) => {\n return rpcUrl === url;\n });\n if (index !== -1) {\n frequentRpcList.splice(index, 1);\n }\n const newFrequestRpc: FrequentRpc = {\n rpcUrl: url,\n chainId,\n ticker,\n nickname,\n rpcPrefs,\n };\n frequentRpcList.push(newFrequestRpc);\n this.update({ frequentRpcList: [...frequentRpcList] });\n }\n\n /**\n * Removes custom RPC URL from state.\n *\n * @param url - Custom RPC URL.\n */\n removeFromFrequentRpcList(url: string) {\n const { frequentRpcList } = this.state;\n const index = frequentRpcList.findIndex(({ rpcUrl }) => {\n return rpcUrl === url;\n });\n if (index !== -1) {\n frequentRpcList.splice(index, 1);\n }\n this.update({ frequentRpcList: [...frequentRpcList] });\n }\n\n /**\n * Sets selected address.\n *\n * @param selectedAddress - Ethereum address.\n */\n setSelectedAddress(selectedAddress: string) {\n this.update({ selectedAddress: toChecksumHexAddress(selectedAddress) });\n }\n\n /**\n * Sets new IPFS gateway.\n *\n * @param ipfsGateway - IPFS gateway string.\n */\n setIpfsGateway(ipfsGateway: string) {\n this.update({ ipfsGateway });\n }\n\n /**\n * Toggle the token detection setting.\n *\n * @param useTokenDetection - Boolean indicating user preference on token detection.\n */\n setUseTokenDetection(useTokenDetection: boolean) {\n this.update({ useTokenDetection });\n }\n\n /**\n * Toggle the collectible detection setting.\n *\n * @param useCollectibleDetection - Boolean indicating user preference on collectible detection.\n */\n setUseCollectibleDetection(useCollectibleDetection: boolean) {\n if (useCollectibleDetection && !this.state.openSeaEnabled) {\n throw new Error(\n 'useCollectibleDetection cannot be enabled if openSeaEnabled is false',\n );\n }\n this.update({ useCollectibleDetection });\n }\n\n /**\n * Toggle the opensea enabled setting.\n *\n * @param openSeaEnabled - Boolean indicating user preference on using OpenSea's API.\n */\n setOpenSeaEnabled(openSeaEnabled: boolean) {\n this.update({ openSeaEnabled });\n if (!openSeaEnabled) {\n this.update({ useCollectibleDetection: false });\n }\n }\n}\n\nexport default PreferencesController;\n"]} \ No newline at end of file diff --git a/dist/util.d.ts b/dist/util.d.ts deleted file mode 100644 index c2d98aa84d..0000000000 --- a/dist/util.d.ts +++ /dev/null @@ -1,357 +0,0 @@ -/// -import { BN } from 'ethereumjs-util'; -import { Transaction, FetchAllOptions, GasPriceValue, FeeMarketEIP1559Values } from './transaction/TransactionController'; -import { MessageParams } from './message-manager/MessageManager'; -import { PersonalMessageParams } from './message-manager/PersonalMessageManager'; -import { TypedMessageParams } from './message-manager/TypedMessageManager'; -import { Token } from './assets/TokenRatesController'; -import { Json } from './BaseControllerV2'; -/** - * Converts a BN object to a hex string with a '0x' prefix. - * - * @param inputBn - BN instance to convert to a hex string. - * @returns A '0x'-prefixed hex string. - */ -export declare function BNToHex(inputBn: any): string; -/** - * Used to multiply a BN by a fraction. - * - * @param targetBN - Number to multiply by a fraction. - * @param numerator - Numerator of the fraction multiplier. - * @param denominator - Denominator of the fraction multiplier. - * @returns Product of the multiplication. - */ -export declare function fractionBN(targetBN: any, numerator: number | string, denominator: number | string): any; -/** - * Used to convert a base-10 number from GWEI to WEI. Can handle numbers with decimal parts. - * - * @param n - The base 10 number to convert to WEI. - * @returns The number in WEI, as a BN. - */ -export declare function gweiDecToWEIBN(n: number | string): any; -/** - * Used to convert values from wei hex format to dec gwei format. - * - * @param hex - The value in hex wei. - * @returns The value in dec gwei as string. - */ -export declare function weiHexToGweiDec(hex: string): any; -/** - * Return a URL that can be used to obtain ETH for a given network. - * - * @param networkCode - Network code of desired network. - * @param address - Address to deposit obtained ETH. - * @param amount - How much ETH is desired. - * @returns URL to buy ETH based on network. - */ -export declare function getBuyURL(networkCode?: string, address?: string, amount?: number): string | undefined; -/** - * Return a URL that can be used to fetch ETH transactions. - * - * @param networkType - Network type of desired network. - * @param urlParams - The parameters used to construct the URL. - * @returns URL to fetch the access the endpoint. - */ -export declare function getEtherscanApiUrl(networkType: string, urlParams: any): string; -/** - * Handles the fetch of incoming transactions. - * - * @param networkType - Network type of desired network. - * @param address - Address to get the transactions from. - * @param txHistoryLimit - The maximum number of transactions to fetch. - * @param opt - Object that can contain fromBlock and Etherscan service API key. - * @returns Responses for both ETH and ERC20 token transactions. - */ -export declare function handleTransactionFetch(networkType: string, address: string, txHistoryLimit: number, opt?: FetchAllOptions): Promise<[{ - [result: string]: []; -}, { - [result: string]: []; -}]>; -/** - * Converts a hex string to a BN object. - * - * @param inputHex - Number represented as a hex string. - * @returns A BN instance. - */ -export declare function hexToBN(inputHex: string): BN; -/** - * A helper function that converts hex data to human readable string. - * - * @param hex - The hex string to convert to string. - * @returns A human readable string conversion. - */ -export declare function hexToText(hex: string): string; -/** - * Parses a hex string and converts it into a number that can be operated on in a bignum-safe, - * base-10 way. - * - * @param value - A base-16 number encoded as a string. - * @returns The number as a BN object in base-16 mode. - */ -export declare function fromHex(value: string | BN): BN; -/** - * Converts an integer to a hexadecimal representation. - * - * @param value - An integer, an integer encoded as a base-10 string, or a BN. - * @returns The integer encoded as a hex string. - */ -export declare function toHex(value: number | string | BN): string; -/** - * Normalizes properties on a Transaction object. - * - * @param transaction - Transaction object to normalize. - * @returns Normalized Transaction object. - */ -export declare function normalizeTransaction(transaction: Transaction): Transaction; -/** - * Execute and return an asynchronous operation without throwing errors. - * - * @param operation - Function returning a Promise. - * @param logError - Determines if the error should be logged. - * @returns Promise resolving to the result of the async operation. - */ -export declare function safelyExecute(operation: () => Promise, logError?: boolean): Promise; -/** - * Execute and return an asynchronous operation with a timeout. - * - * @param operation - Function returning a Promise. - * @param logError - Determines if the error should be logged. - * @param timeout - Timeout to fail the operation. - * @returns Promise resolving to the result of the async operation. - */ -export declare function safelyExecuteWithTimeout(operation: () => Promise, logError?: boolean, timeout?: number): Promise; -/** - * Convert an address to a checksummed hexidecimal address. - * - * @param address - The address to convert. - * @returns A 0x-prefixed hexidecimal checksummed address. - */ -export declare function toChecksumHexAddress(address: string): string; -/** - * Validates that the input is a hex address. This utility method is a thin - * wrapper around ethereumjs-util.isValidAddress, with the exception that it - * does not throw an error when provided values that are not hex strings. In - * addition, and by default, this method will return true for hex strings that - * meet the length requirement of a hex address, but are not prefixed with `0x` - * Finally, if the mixedCaseUseChecksum flag is true and a mixed case string is - * provided this method will validate it has the proper checksum formatting. - * - * @param possibleAddress - Input parameter to check against. - * @param options - The validation options. - * @param options.allowNonPrefixed - If true will first ensure '0x' is prepended to the string. - * @returns Whether or not the input is a valid hex address. - */ -export declare function isValidHexAddress(possibleAddress: string, { allowNonPrefixed }?: { - allowNonPrefixed?: boolean | undefined; -}): boolean; -/** - * Validates a Transaction object for required properties and throws in - * the event of any validation error. - * - * @param transaction - Transaction object to validate. - */ -export declare function validateTransaction(transaction: Transaction): void; -/** - * A helper function that converts rawmessageData buffer data to a hex, or just returns the data if - * it is already formatted as a hex. - * - * @param data - The buffer data to convert to a hex. - * @returns A hex string conversion of the buffer data. - */ -export declare function normalizeMessageData(data: string): string; -/** - * Validates a PersonalMessageParams and MessageParams objects for required properties and throws in - * the event of any validation error. - * - * @param messageData - PersonalMessageParams object to validate. - */ -export declare function validateSignMessageData(messageData: PersonalMessageParams | MessageParams): void; -/** - * Validates a TypedMessageParams object for required properties and throws in - * the event of any validation error for eth_signTypedMessage_V1. - * - * @param messageData - TypedMessageParams object to validate. - */ -export declare function validateTypedSignMessageDataV1(messageData: TypedMessageParams): void; -/** - * Validates a TypedMessageParams object for required properties and throws in - * the event of any validation error for eth_signTypedMessage_V3. - * - * @param messageData - TypedMessageParams object to validate. - */ -export declare function validateTypedSignMessageDataV3(messageData: TypedMessageParams): void; -/** - * Validates a ERC20 token to be added with EIP747. - * - * @param token - Token object to validate. - */ -export declare function validateTokenToWatch(token: Token): void; -/** - * Returns whether the given code corresponds to a smart contract. - * - * @param code - The potential smart contract code. - * @returns Whether the code was smart contract code or not. - */ -export declare function isSmartContractCode(code: string): boolean; -/** - * Execute fetch and verify that the response was successful. - * - * @param request - Request information. - * @param options - Fetch options. - * @returns The fetch response. - */ -export declare function successfulFetch(request: string, options?: RequestInit): Promise; -/** - * Execute fetch and return object response. - * - * @param request - The request information. - * @param options - The fetch options. - * @returns The fetch response JSON data. - */ -export declare function handleFetch(request: string, options?: RequestInit): Promise; -/** - * Execute fetch and return object response, log if known error thrown, otherwise rethrow error. - * - * @param request - the request options object - * @param request.url - The request url to query. - * @param request.options - The fetch options. - * @param request.timeout - Timeout to fail request - * @param request.errorCodesToCatch - array of error codes for errors we want to catch in a particular context - * @returns The fetch response JSON data or undefined (if error occurs). - */ -export declare function fetchWithErrorHandling({ url, options, timeout, errorCodesToCatch, }: { - url: string; - options?: RequestInit; - timeout?: number; - errorCodesToCatch?: number[]; -}): Promise; -/** - * Fetch that fails after timeout. - * - * @param url - Url to fetch. - * @param options - Options to send with the request. - * @param timeout - Timeout to fail request. - * @returns Promise resolving the request. - */ -export declare function timeoutFetch(url: string, options?: RequestInit, timeout?: number): Promise; -/** - * Normalizes the given ENS name. - * - * @param ensName - The ENS name. - * @returns The normalized ENS name string. - */ -export declare function normalizeEnsName(ensName: string): string | null; -/** - * Wrapper method to handle EthQuery requests. - * - * @param ethQuery - EthQuery object initialized with a provider. - * @param method - Method to request. - * @param args - Arguments to send. - * @returns Promise resolving the request. - */ -export declare function query(ethQuery: any, method: string, args?: any[]): Promise; -/** - * Checks if a transaction is EIP-1559 by checking for the existence of - * maxFeePerGas and maxPriorityFeePerGas within its parameters. - * - * @param transaction - Transaction object to add. - * @returns Boolean that is true if the transaction is EIP-1559 (has maxFeePerGas and maxPriorityFeePerGas), otherwise returns false. - */ -export declare const isEIP1559Transaction: (transaction: Transaction) => boolean; -export declare const convertHexToDecimal: (value: string | undefined) => number; -export declare const getIncreasedPriceHex: (value: number, rate: number) => string; -export declare const getIncreasedPriceFromExisting: (value: string | undefined, rate: number) => string; -export declare const validateGasValues: (gasValues: GasPriceValue | FeeMarketEIP1559Values) => void; -export declare const isFeeMarketEIP1559Values: (gasValues?: GasPriceValue | FeeMarketEIP1559Values | undefined) => gasValues is FeeMarketEIP1559Values; -export declare const isGasPriceValue: (gasValues?: GasPriceValue | FeeMarketEIP1559Values | undefined) => gasValues is GasPriceValue; -/** - * Validates that the proposed value is greater than or equal to the minimum value. - * - * @param proposed - The proposed value. - * @param min - The minimum value. - * @returns The proposed value. - * @throws Will throw if the proposed value is too low. - */ -export declare function validateMinimumIncrease(proposed: string, min: string): string; -/** - * Removes IPFS protocol prefix from input string. - * - * @param ipfsUrl - An IPFS url (e.g. ipfs://{content id}) - * @returns IPFS content identifier and (possibly) path in a string - * @throws Will throw if the url passed is not IPFS. - */ -export declare function removeIpfsProtocolPrefix(ipfsUrl: string): string; -/** - * Extracts content identifier and path from an input string. - * - * @param ipfsUrl - An IPFS URL minus the IPFS protocol prefix - * @returns IFPS content identifier (cid) and sub path as string. - * @throws Will throw if the url passed is not ipfs. - */ -export declare function getIpfsCIDv1AndPath(ipfsUrl: string): { - cid: string; - path?: string; -}; -/** - * Adds URL protocol prefix to input URL string if missing. - * - * @param urlString - An IPFS URL. - * @returns A URL with a https:// prepended. - */ -export declare function addUrlProtocolPrefix(urlString: string): string; -/** - * Formats URL correctly for use retrieving assets hosted on IPFS. - * - * @param ipfsGateway - The users preferred IPFS gateway (full URL or just host). - * @param ipfsUrl - The IFPS URL pointed at the asset. - * @param subdomainSupported - Boolean indicating whether the URL should be formatted with subdomains or not. - * @returns A formatted URL, with the user's preferred IPFS gateway and format (subdomain or not), pointing to an asset hosted on IPFS. - */ -export declare function getFormattedIpfsUrl(ipfsGateway: string, ipfsUrl: string, subdomainSupported: boolean): string; -declare type PlainObject = Record; -/** - * Determines whether a value is a "plain" object. - * - * @param value - A value to check - * @returns True if the passed value is a plain object - */ -export declare function isPlainObject(value: unknown): value is PlainObject; -export declare const hasProperty: (object: PlainObject, key: string | number | symbol) => boolean; -/** - * Like {@link Array}, but always non-empty. - * - * @template T - The non-empty array member type. - */ -export declare type NonEmptyArray = [T, ...T[]]; -/** - * Type guard for {@link NonEmptyArray}. - * - * @template T - The non-empty array member type. - * @param value - The value to check. - * @returns Whether the value is a non-empty array. - */ -export declare function isNonEmptyArray(value: T[]): value is NonEmptyArray; -/** - * Type guard for {@link Json}. - * - * @param value - The value to check. - * @returns Whether the value is valid JSON. - */ -export declare function isValidJson(value: unknown): value is Json; -/** - * Networks where token detection is supported - Values are in decimal format - */ -export declare enum SupportedTokenDetectionNetworks { - mainnet = "1", - bsc = "56", - polygon = "137", - avax = "43114" -} -/** - * Check if token detection is enabled for certain networks. - * - * @param chainId - ChainID of network - * @returns Whether the current network supports token detection - */ -export declare function isTokenDetectionSupportedForNetwork(chainId: string): boolean; -export {}; diff --git a/dist/util.js b/dist/util.js deleted file mode 100644 index 574c57b7d1..0000000000 --- a/dist/util.js +++ /dev/null @@ -1,871 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.isTokenDetectionSupportedForNetwork = exports.SupportedTokenDetectionNetworks = exports.isValidJson = exports.isNonEmptyArray = exports.hasProperty = exports.isPlainObject = exports.getFormattedIpfsUrl = exports.addUrlProtocolPrefix = exports.getIpfsCIDv1AndPath = exports.removeIpfsProtocolPrefix = exports.validateMinimumIncrease = exports.isGasPriceValue = exports.isFeeMarketEIP1559Values = exports.validateGasValues = exports.getIncreasedPriceFromExisting = exports.getIncreasedPriceHex = exports.convertHexToDecimal = exports.isEIP1559Transaction = exports.query = exports.normalizeEnsName = exports.timeoutFetch = exports.fetchWithErrorHandling = exports.handleFetch = exports.successfulFetch = exports.isSmartContractCode = exports.validateTokenToWatch = exports.validateTypedSignMessageDataV3 = exports.validateTypedSignMessageDataV1 = exports.validateSignMessageData = exports.normalizeMessageData = exports.validateTransaction = exports.isValidHexAddress = exports.toChecksumHexAddress = exports.safelyExecuteWithTimeout = exports.safelyExecute = exports.normalizeTransaction = exports.toHex = exports.fromHex = exports.hexToText = exports.hexToBN = exports.handleTransactionFetch = exports.getEtherscanApiUrl = exports.getBuyURL = exports.weiHexToGweiDec = exports.gweiDecToWEIBN = exports.fractionBN = exports.BNToHex = void 0; -const ethereumjs_util_1 = require("ethereumjs-util"); -const ethjs_unit_1 = require("ethjs-unit"); -const eth_rpc_errors_1 = require("eth-rpc-errors"); -const eth_ens_namehash_1 = __importDefault(require("eth-ens-namehash")); -const eth_sig_util_1 = require("eth-sig-util"); -const jsonschema_1 = require("jsonschema"); -const cid_1 = require("multiformats/cid"); -const fast_deep_equal_1 = __importDefault(require("fast-deep-equal")); -const constants_1 = require("./constants"); -const TIMEOUT_ERROR = new Error('timeout'); -const hexRe = /^[0-9A-Fa-f]+$/gu; -const NORMALIZERS = { - data: (data) => (0, ethereumjs_util_1.addHexPrefix)(data), - from: (from) => (0, ethereumjs_util_1.addHexPrefix)(from).toLowerCase(), - gas: (gas) => (0, ethereumjs_util_1.addHexPrefix)(gas), - gasPrice: (gasPrice) => (0, ethereumjs_util_1.addHexPrefix)(gasPrice), - nonce: (nonce) => (0, ethereumjs_util_1.addHexPrefix)(nonce), - to: (to) => (0, ethereumjs_util_1.addHexPrefix)(to).toLowerCase(), - value: (value) => (0, ethereumjs_util_1.addHexPrefix)(value), - maxFeePerGas: (maxFeePerGas) => (0, ethereumjs_util_1.addHexPrefix)(maxFeePerGas), - maxPriorityFeePerGas: (maxPriorityFeePerGas) => (0, ethereumjs_util_1.addHexPrefix)(maxPriorityFeePerGas), - estimatedBaseFee: (maxPriorityFeePerGas) => (0, ethereumjs_util_1.addHexPrefix)(maxPriorityFeePerGas), -}; -/** - * Converts a BN object to a hex string with a '0x' prefix. - * - * @param inputBn - BN instance to convert to a hex string. - * @returns A '0x'-prefixed hex string. - */ -function BNToHex(inputBn) { - return (0, ethereumjs_util_1.addHexPrefix)(inputBn.toString(16)); -} -exports.BNToHex = BNToHex; -/** - * Used to multiply a BN by a fraction. - * - * @param targetBN - Number to multiply by a fraction. - * @param numerator - Numerator of the fraction multiplier. - * @param denominator - Denominator of the fraction multiplier. - * @returns Product of the multiplication. - */ -function fractionBN(targetBN, numerator, denominator) { - const numBN = new ethereumjs_util_1.BN(numerator); - const denomBN = new ethereumjs_util_1.BN(denominator); - return targetBN.mul(numBN).div(denomBN); -} -exports.fractionBN = fractionBN; -/** - * Used to convert a base-10 number from GWEI to WEI. Can handle numbers with decimal parts. - * - * @param n - The base 10 number to convert to WEI. - * @returns The number in WEI, as a BN. - */ -function gweiDecToWEIBN(n) { - if (Number.isNaN(n)) { - return new ethereumjs_util_1.BN(0); - } - const parts = n.toString().split('.'); - const wholePart = parts[0] || '0'; - let decimalPart = parts[1] || ''; - if (!decimalPart) { - return (0, ethjs_unit_1.toWei)(wholePart, 'gwei'); - } - if (decimalPart.length <= 9) { - return (0, ethjs_unit_1.toWei)(`${wholePart}.${decimalPart}`, 'gwei'); - } - const decimalPartToRemove = decimalPart.slice(9); - const decimalRoundingDigit = decimalPartToRemove[0]; - decimalPart = decimalPart.slice(0, 9); - let wei = (0, ethjs_unit_1.toWei)(`${wholePart}.${decimalPart}`, 'gwei'); - if (Number(decimalRoundingDigit) >= 5) { - wei = wei.add(new ethereumjs_util_1.BN(1)); - } - return wei; -} -exports.gweiDecToWEIBN = gweiDecToWEIBN; -/** - * Used to convert values from wei hex format to dec gwei format. - * - * @param hex - The value in hex wei. - * @returns The value in dec gwei as string. - */ -function weiHexToGweiDec(hex) { - const hexWei = new ethereumjs_util_1.BN((0, ethereumjs_util_1.stripHexPrefix)(hex), 16); - return (0, ethjs_unit_1.fromWei)(hexWei, 'gwei').toString(10); -} -exports.weiHexToGweiDec = weiHexToGweiDec; -/** - * Return a URL that can be used to obtain ETH for a given network. - * - * @param networkCode - Network code of desired network. - * @param address - Address to deposit obtained ETH. - * @param amount - How much ETH is desired. - * @returns URL to buy ETH based on network. - */ -function getBuyURL(networkCode = '1', address, amount = 5) { - switch (networkCode) { - case '1': - return `https://buy.coinbase.com/?code=9ec56d01-7e81-5017-930c-513daa27bb6a&amount=${amount}&address=${address}&crypto_currency=ETH`; - case '3': - return 'https://faucet.metamask.io/'; - case '4': - return 'https://www.rinkeby.io/'; - case '5': - return 'https://goerli-faucet.slock.it/'; - case '42': - return 'https://github.com/kovan-testnet/faucet'; - default: - return undefined; - } -} -exports.getBuyURL = getBuyURL; -/** - * Return a URL that can be used to fetch ETH transactions. - * - * @param networkType - Network type of desired network. - * @param urlParams - The parameters used to construct the URL. - * @returns URL to fetch the access the endpoint. - */ -function getEtherscanApiUrl(networkType, urlParams) { - let etherscanSubdomain = 'api'; - if (networkType !== constants_1.MAINNET) { - etherscanSubdomain = `api-${networkType}`; - } - const apiUrl = `https://${etherscanSubdomain}.etherscan.io`; - let url = `${apiUrl}/api?`; - for (const paramKey in urlParams) { - if (urlParams[paramKey]) { - url += `${paramKey}=${urlParams[paramKey]}&`; - } - } - url += 'tag=latest&page=1'; - return url; -} -exports.getEtherscanApiUrl = getEtherscanApiUrl; -/** - * Handles the fetch of incoming transactions. - * - * @param networkType - Network type of desired network. - * @param address - Address to get the transactions from. - * @param txHistoryLimit - The maximum number of transactions to fetch. - * @param opt - Object that can contain fromBlock and Etherscan service API key. - * @returns Responses for both ETH and ERC20 token transactions. - */ -function handleTransactionFetch(networkType, address, txHistoryLimit, opt) { - return __awaiter(this, void 0, void 0, function* () { - // transactions - const urlParams = { - module: 'account', - address, - startBlock: opt === null || opt === void 0 ? void 0 : opt.fromBlock, - apikey: opt === null || opt === void 0 ? void 0 : opt.etherscanApiKey, - offset: txHistoryLimit.toString(), - order: 'desc', - }; - const etherscanTxUrl = getEtherscanApiUrl(networkType, Object.assign(Object.assign({}, urlParams), { action: 'txlist' })); - const etherscanTxResponsePromise = handleFetch(etherscanTxUrl); - // tokens - const etherscanTokenUrl = getEtherscanApiUrl(networkType, Object.assign(Object.assign({}, urlParams), { action: 'tokentx' })); - const etherscanTokenResponsePromise = handleFetch(etherscanTokenUrl); - let [etherscanTxResponse, etherscanTokenResponse] = yield Promise.all([ - etherscanTxResponsePromise, - etherscanTokenResponsePromise, - ]); - if (etherscanTxResponse.status === '0' || - etherscanTxResponse.result.length <= 0) { - etherscanTxResponse = { status: etherscanTxResponse.status, result: [] }; - } - if (etherscanTokenResponse.status === '0' || - etherscanTokenResponse.result.length <= 0) { - etherscanTokenResponse = { - status: etherscanTokenResponse.status, - result: [], - }; - } - return [etherscanTxResponse, etherscanTokenResponse]; - }); -} -exports.handleTransactionFetch = handleTransactionFetch; -/** - * Converts a hex string to a BN object. - * - * @param inputHex - Number represented as a hex string. - * @returns A BN instance. - */ -function hexToBN(inputHex) { - return new ethereumjs_util_1.BN((0, ethereumjs_util_1.stripHexPrefix)(inputHex), 16); -} -exports.hexToBN = hexToBN; -/** - * A helper function that converts hex data to human readable string. - * - * @param hex - The hex string to convert to string. - * @returns A human readable string conversion. - */ -function hexToText(hex) { - try { - const stripped = (0, ethereumjs_util_1.stripHexPrefix)(hex); - const buff = Buffer.from(stripped, 'hex'); - return buff.toString('utf8'); - } - catch (e) { - /* istanbul ignore next */ - return hex; - } -} -exports.hexToText = hexToText; -/** - * Parses a hex string and converts it into a number that can be operated on in a bignum-safe, - * base-10 way. - * - * @param value - A base-16 number encoded as a string. - * @returns The number as a BN object in base-16 mode. - */ -function fromHex(value) { - if (ethereumjs_util_1.BN.isBN(value)) { - return value; - } - return new ethereumjs_util_1.BN(hexToBN(value).toString(10)); -} -exports.fromHex = fromHex; -/** - * Converts an integer to a hexadecimal representation. - * - * @param value - An integer, an integer encoded as a base-10 string, or a BN. - * @returns The integer encoded as a hex string. - */ -function toHex(value) { - if (typeof value === 'string' && (0, ethereumjs_util_1.isHexString)(value)) { - return value; - } - const hexString = ethereumjs_util_1.BN.isBN(value) - ? value.toString(16) - : new ethereumjs_util_1.BN(value.toString(), 10).toString(16); - return `0x${hexString}`; -} -exports.toHex = toHex; -/** - * Normalizes properties on a Transaction object. - * - * @param transaction - Transaction object to normalize. - * @returns Normalized Transaction object. - */ -function normalizeTransaction(transaction) { - const normalizedTransaction = { from: '' }; - let key; - for (key in NORMALIZERS) { - if (transaction[key]) { - normalizedTransaction[key] = NORMALIZERS[key](transaction[key]); - } - } - return normalizedTransaction; -} -exports.normalizeTransaction = normalizeTransaction; -/** - * Execute and return an asynchronous operation without throwing errors. - * - * @param operation - Function returning a Promise. - * @param logError - Determines if the error should be logged. - * @returns Promise resolving to the result of the async operation. - */ -function safelyExecute(operation, logError = false) { - return __awaiter(this, void 0, void 0, function* () { - try { - return yield operation(); - } - catch (error) { - /* istanbul ignore next */ - if (logError) { - console.error(error); - } - return undefined; - } - }); -} -exports.safelyExecute = safelyExecute; -/** - * Execute and return an asynchronous operation with a timeout. - * - * @param operation - Function returning a Promise. - * @param logError - Determines if the error should be logged. - * @param timeout - Timeout to fail the operation. - * @returns Promise resolving to the result of the async operation. - */ -function safelyExecuteWithTimeout(operation, logError = false, timeout = 500) { - return __awaiter(this, void 0, void 0, function* () { - try { - return yield Promise.race([ - operation(), - new Promise((_, reject) => setTimeout(() => { - reject(TIMEOUT_ERROR); - }, timeout)), - ]); - } - catch (error) { - /* istanbul ignore next */ - if (logError) { - console.error(error); - } - return undefined; - } - }); -} -exports.safelyExecuteWithTimeout = safelyExecuteWithTimeout; -/** - * Convert an address to a checksummed hexidecimal address. - * - * @param address - The address to convert. - * @returns A 0x-prefixed hexidecimal checksummed address. - */ -function toChecksumHexAddress(address) { - const hexPrefixed = (0, ethereumjs_util_1.addHexPrefix)(address); - if (!(0, ethereumjs_util_1.isHexString)(hexPrefixed)) { - // Version 5.1 of ethereumjs-utils would have returned '0xY' for input 'y' - // but we shouldn't waste effort trying to change case on a clearly invalid - // string. Instead just return the hex prefixed original string which most - // closely mimics the original behavior. - return hexPrefixed; - } - return (0, ethereumjs_util_1.toChecksumAddress)(hexPrefixed); -} -exports.toChecksumHexAddress = toChecksumHexAddress; -/** - * Validates that the input is a hex address. This utility method is a thin - * wrapper around ethereumjs-util.isValidAddress, with the exception that it - * does not throw an error when provided values that are not hex strings. In - * addition, and by default, this method will return true for hex strings that - * meet the length requirement of a hex address, but are not prefixed with `0x` - * Finally, if the mixedCaseUseChecksum flag is true and a mixed case string is - * provided this method will validate it has the proper checksum formatting. - * - * @param possibleAddress - Input parameter to check against. - * @param options - The validation options. - * @param options.allowNonPrefixed - If true will first ensure '0x' is prepended to the string. - * @returns Whether or not the input is a valid hex address. - */ -function isValidHexAddress(possibleAddress, { allowNonPrefixed = true } = {}) { - const addressToCheck = allowNonPrefixed - ? (0, ethereumjs_util_1.addHexPrefix)(possibleAddress) - : possibleAddress; - if (!(0, ethereumjs_util_1.isHexString)(addressToCheck)) { - return false; - } - return (0, ethereumjs_util_1.isValidAddress)(addressToCheck); -} -exports.isValidHexAddress = isValidHexAddress; -/** - * Validates a Transaction object for required properties and throws in - * the event of any validation error. - * - * @param transaction - Transaction object to validate. - */ -function validateTransaction(transaction) { - if (!transaction.from || - typeof transaction.from !== 'string' || - !isValidHexAddress(transaction.from)) { - throw new Error(`Invalid "from" address: ${transaction.from} must be a valid string.`); - } - if (transaction.to === '0x' || transaction.to === undefined) { - if (transaction.data) { - delete transaction.to; - } - else { - throw new Error(`Invalid "to" address: ${transaction.to} must be a valid string.`); - } - } - else if (transaction.to !== undefined && - !isValidHexAddress(transaction.to)) { - throw new Error(`Invalid "to" address: ${transaction.to} must be a valid string.`); - } - if (transaction.value !== undefined) { - const value = transaction.value.toString(); - if (value.includes('-')) { - throw new Error(`Invalid "value": ${value} is not a positive number.`); - } - if (value.includes('.')) { - throw new Error(`Invalid "value": ${value} number must be denominated in wei.`); - } - const intValue = parseInt(transaction.value, 10); - const isValid = Number.isFinite(intValue) && - !Number.isNaN(intValue) && - !isNaN(Number(value)) && - Number.isSafeInteger(intValue); - if (!isValid) { - throw new Error(`Invalid "value": ${value} number must be a valid number.`); - } - } -} -exports.validateTransaction = validateTransaction; -/** - * A helper function that converts rawmessageData buffer data to a hex, or just returns the data if - * it is already formatted as a hex. - * - * @param data - The buffer data to convert to a hex. - * @returns A hex string conversion of the buffer data. - */ -function normalizeMessageData(data) { - try { - const stripped = (0, ethereumjs_util_1.stripHexPrefix)(data); - if (stripped.match(hexRe)) { - return (0, ethereumjs_util_1.addHexPrefix)(stripped); - } - } - catch (e) { - /* istanbul ignore next */ - } - return (0, ethereumjs_util_1.bufferToHex)(Buffer.from(data, 'utf8')); -} -exports.normalizeMessageData = normalizeMessageData; -/** - * Validates a PersonalMessageParams and MessageParams objects for required properties and throws in - * the event of any validation error. - * - * @param messageData - PersonalMessageParams object to validate. - */ -function validateSignMessageData(messageData) { - const { from, data } = messageData; - if (!from || typeof from !== 'string' || !isValidHexAddress(from)) { - throw new Error(`Invalid "from" address: ${from} must be a valid string.`); - } - if (!data || typeof data !== 'string') { - throw new Error(`Invalid message "data": ${data} must be a valid string.`); - } -} -exports.validateSignMessageData = validateSignMessageData; -/** - * Validates a TypedMessageParams object for required properties and throws in - * the event of any validation error for eth_signTypedMessage_V1. - * - * @param messageData - TypedMessageParams object to validate. - */ -function validateTypedSignMessageDataV1(messageData) { - if (!messageData.from || - typeof messageData.from !== 'string' || - !isValidHexAddress(messageData.from)) { - throw new Error(`Invalid "from" address: ${messageData.from} must be a valid string.`); - } - if (!messageData.data || !Array.isArray(messageData.data)) { - throw new Error(`Invalid message "data": ${messageData.data} must be a valid array.`); - } - try { - // typedSignatureHash will throw if the data is invalid. - (0, eth_sig_util_1.typedSignatureHash)(messageData.data); - } - catch (e) { - throw new Error(`Expected EIP712 typed data.`); - } -} -exports.validateTypedSignMessageDataV1 = validateTypedSignMessageDataV1; -/** - * Validates a TypedMessageParams object for required properties and throws in - * the event of any validation error for eth_signTypedMessage_V3. - * - * @param messageData - TypedMessageParams object to validate. - */ -function validateTypedSignMessageDataV3(messageData) { - if (!messageData.from || - typeof messageData.from !== 'string' || - !isValidHexAddress(messageData.from)) { - throw new Error(`Invalid "from" address: ${messageData.from} must be a valid string.`); - } - if (!messageData.data || typeof messageData.data !== 'string') { - throw new Error(`Invalid message "data": ${messageData.data} must be a valid array.`); - } - let data; - try { - data = JSON.parse(messageData.data); - } - catch (e) { - throw new Error('Data must be passed as a valid JSON string.'); - } - const validation = (0, jsonschema_1.validate)(data, eth_sig_util_1.TYPED_MESSAGE_SCHEMA); - if (validation.errors.length > 0) { - throw new Error('Data must conform to EIP-712 schema. See https://git.io/fNtcx.'); - } -} -exports.validateTypedSignMessageDataV3 = validateTypedSignMessageDataV3; -/** - * Validates a ERC20 token to be added with EIP747. - * - * @param token - Token object to validate. - */ -function validateTokenToWatch(token) { - const { address, symbol, decimals } = token; - if (!address || !symbol || typeof decimals === 'undefined') { - throw eth_rpc_errors_1.ethErrors.rpc.invalidParams(`Must specify address, symbol, and decimals.`); - } - if (typeof symbol !== 'string') { - throw eth_rpc_errors_1.ethErrors.rpc.invalidParams(`Invalid symbol: not a string.`); - } - if (symbol.length > 11) { - throw eth_rpc_errors_1.ethErrors.rpc.invalidParams(`Invalid symbol "${symbol}": longer than 11 characters.`); - } - const numDecimals = parseInt(decimals, 10); - if (isNaN(numDecimals) || numDecimals > 36 || numDecimals < 0) { - throw eth_rpc_errors_1.ethErrors.rpc.invalidParams(`Invalid decimals "${decimals}": must be 0 <= 36.`); - } - if (!isValidHexAddress(address)) { - throw eth_rpc_errors_1.ethErrors.rpc.invalidParams(`Invalid address "${address}".`); - } -} -exports.validateTokenToWatch = validateTokenToWatch; -/** - * Returns whether the given code corresponds to a smart contract. - * - * @param code - The potential smart contract code. - * @returns Whether the code was smart contract code or not. - */ -function isSmartContractCode(code) { - /* istanbul ignore if */ - if (!code) { - return false; - } - // Geth will return '0x', and ganache-core v2.2.1 will return '0x0' - const smartContractCode = code !== '0x' && code !== '0x0'; - return smartContractCode; -} -exports.isSmartContractCode = isSmartContractCode; -/** - * Execute fetch and verify that the response was successful. - * - * @param request - Request information. - * @param options - Fetch options. - * @returns The fetch response. - */ -function successfulFetch(request, options) { - return __awaiter(this, void 0, void 0, function* () { - const response = yield fetch(request, options); - if (!response.ok) { - throw new Error(`Fetch failed with status '${response.status}' for request '${request}'`); - } - return response; - }); -} -exports.successfulFetch = successfulFetch; -/** - * Execute fetch and return object response. - * - * @param request - The request information. - * @param options - The fetch options. - * @returns The fetch response JSON data. - */ -function handleFetch(request, options) { - return __awaiter(this, void 0, void 0, function* () { - const response = yield successfulFetch(request, options); - const object = yield response.json(); - return object; - }); -} -exports.handleFetch = handleFetch; -/** - * Execute fetch and return object response, log if known error thrown, otherwise rethrow error. - * - * @param request - the request options object - * @param request.url - The request url to query. - * @param request.options - The fetch options. - * @param request.timeout - Timeout to fail request - * @param request.errorCodesToCatch - array of error codes for errors we want to catch in a particular context - * @returns The fetch response JSON data or undefined (if error occurs). - */ -function fetchWithErrorHandling({ url, options, timeout, errorCodesToCatch, }) { - return __awaiter(this, void 0, void 0, function* () { - let result; - try { - if (timeout) { - result = Promise.race([ - yield handleFetch(url, options), - new Promise((_, reject) => setTimeout(() => { - reject(TIMEOUT_ERROR); - }, timeout)), - ]); - } - else { - result = yield handleFetch(url, options); - } - } - catch (e) { - logOrRethrowError(e, errorCodesToCatch); - } - return result; - }); -} -exports.fetchWithErrorHandling = fetchWithErrorHandling; -/** - * Fetch that fails after timeout. - * - * @param url - Url to fetch. - * @param options - Options to send with the request. - * @param timeout - Timeout to fail request. - * @returns Promise resolving the request. - */ -function timeoutFetch(url, options, timeout = 500) { - return __awaiter(this, void 0, void 0, function* () { - return Promise.race([ - successfulFetch(url, options), - new Promise((_, reject) => setTimeout(() => { - reject(TIMEOUT_ERROR); - }, timeout)), - ]); - }); -} -exports.timeoutFetch = timeoutFetch; -/** - * Normalizes the given ENS name. - * - * @param ensName - The ENS name. - * @returns The normalized ENS name string. - */ -function normalizeEnsName(ensName) { - if (ensName && typeof ensName === 'string') { - try { - const normalized = eth_ens_namehash_1.default.normalize(ensName.trim()); - // this regex is only sufficient with the above call to ensNamehash.normalize - // TODO: change 7 in regex to 3 when shorter ENS domains are live - if (normalized.match(/^(([\w\d-]+)\.)*[\w\d-]{7,}\.(eth|test)$/u)) { - return normalized; - } - } - catch (_) { - // do nothing - } - } - return null; -} -exports.normalizeEnsName = normalizeEnsName; -/** - * Wrapper method to handle EthQuery requests. - * - * @param ethQuery - EthQuery object initialized with a provider. - * @param method - Method to request. - * @param args - Arguments to send. - * @returns Promise resolving the request. - */ -function query(ethQuery, method, args = []) { - return new Promise((resolve, reject) => { - const cb = (error, result) => { - if (error) { - reject(error); - return; - } - resolve(result); - }; - if (typeof ethQuery[method] === 'function') { - ethQuery[method](...args, cb); - } - else { - ethQuery.sendAsync({ method, params: args }, cb); - } - }); -} -exports.query = query; -/** - * Checks if a transaction is EIP-1559 by checking for the existence of - * maxFeePerGas and maxPriorityFeePerGas within its parameters. - * - * @param transaction - Transaction object to add. - * @returns Boolean that is true if the transaction is EIP-1559 (has maxFeePerGas and maxPriorityFeePerGas), otherwise returns false. - */ -const isEIP1559Transaction = (transaction) => { - const hasOwnProp = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key); - return (hasOwnProp(transaction, 'maxFeePerGas') && - hasOwnProp(transaction, 'maxPriorityFeePerGas')); -}; -exports.isEIP1559Transaction = isEIP1559Transaction; -const convertHexToDecimal = (value) => parseInt(value === undefined ? '0x0' : value, 16); -exports.convertHexToDecimal = convertHexToDecimal; -const getIncreasedPriceHex = (value, rate) => (0, ethereumjs_util_1.addHexPrefix)(`${parseInt(`${value * rate}`, 10).toString(16)}`); -exports.getIncreasedPriceHex = getIncreasedPriceHex; -const getIncreasedPriceFromExisting = (value, rate) => { - return (0, exports.getIncreasedPriceHex)((0, exports.convertHexToDecimal)(value), rate); -}; -exports.getIncreasedPriceFromExisting = getIncreasedPriceFromExisting; -const validateGasValues = (gasValues) => { - Object.keys(gasValues).forEach((key) => { - const value = gasValues[key]; - if (typeof value !== 'string' || !(0, ethereumjs_util_1.isHexString)(value)) { - throw new TypeError(`expected hex string for ${key} but received: ${value}`); - } - }); -}; -exports.validateGasValues = validateGasValues; -const isFeeMarketEIP1559Values = (gasValues) => (gasValues === null || gasValues === void 0 ? void 0 : gasValues.maxFeePerGas) !== undefined || - (gasValues === null || gasValues === void 0 ? void 0 : gasValues.maxPriorityFeePerGas) !== undefined; -exports.isFeeMarketEIP1559Values = isFeeMarketEIP1559Values; -const isGasPriceValue = (gasValues) => (gasValues === null || gasValues === void 0 ? void 0 : gasValues.gasPrice) !== undefined; -exports.isGasPriceValue = isGasPriceValue; -/** - * Validates that the proposed value is greater than or equal to the minimum value. - * - * @param proposed - The proposed value. - * @param min - The minimum value. - * @returns The proposed value. - * @throws Will throw if the proposed value is too low. - */ -function validateMinimumIncrease(proposed, min) { - const proposedDecimal = (0, exports.convertHexToDecimal)(proposed); - const minDecimal = (0, exports.convertHexToDecimal)(min); - if (proposedDecimal >= minDecimal) { - return proposed; - } - const errorMsg = `The proposed value: ${proposedDecimal} should meet or exceed the minimum value: ${minDecimal}`; - throw new Error(errorMsg); -} -exports.validateMinimumIncrease = validateMinimumIncrease; -/** - * Removes IPFS protocol prefix from input string. - * - * @param ipfsUrl - An IPFS url (e.g. ipfs://{content id}) - * @returns IPFS content identifier and (possibly) path in a string - * @throws Will throw if the url passed is not IPFS. - */ -function removeIpfsProtocolPrefix(ipfsUrl) { - if (ipfsUrl.startsWith('ipfs://ipfs/')) { - return ipfsUrl.replace('ipfs://ipfs/', ''); - } - else if (ipfsUrl.startsWith('ipfs://')) { - return ipfsUrl.replace('ipfs://', ''); - } - // this method should not be used with non-ipfs urls (i.e. startsWith('ipfs://') === true) - throw new Error('this method should not be used with non ipfs urls'); -} -exports.removeIpfsProtocolPrefix = removeIpfsProtocolPrefix; -/** - * Extracts content identifier and path from an input string. - * - * @param ipfsUrl - An IPFS URL minus the IPFS protocol prefix - * @returns IFPS content identifier (cid) and sub path as string. - * @throws Will throw if the url passed is not ipfs. - */ -function getIpfsCIDv1AndPath(ipfsUrl) { - const url = removeIpfsProtocolPrefix(ipfsUrl); - // check if there is a path - // (CID is everything preceding first forward slash, path is everything after) - const index = url.indexOf('/'); - const cid = index !== -1 ? url.substring(0, index) : url; - const path = index !== -1 ? url.substring(index) : undefined; - // We want to ensure that the CID is v1 (https://docs.ipfs.io/concepts/content-addressing/#identifier-formats) - // because most cid v0s appear to be incompatible with IPFS subdomains - return { - cid: cid_1.CID.parse(cid).toV1().toString(), - path, - }; -} -exports.getIpfsCIDv1AndPath = getIpfsCIDv1AndPath; -/** - * Adds URL protocol prefix to input URL string if missing. - * - * @param urlString - An IPFS URL. - * @returns A URL with a https:// prepended. - */ -function addUrlProtocolPrefix(urlString) { - if (!urlString.match(/(^http:\/\/)|(^https:\/\/)/u)) { - return `https://${urlString}`; - } - return urlString; -} -exports.addUrlProtocolPrefix = addUrlProtocolPrefix; -/** - * Formats URL correctly for use retrieving assets hosted on IPFS. - * - * @param ipfsGateway - The users preferred IPFS gateway (full URL or just host). - * @param ipfsUrl - The IFPS URL pointed at the asset. - * @param subdomainSupported - Boolean indicating whether the URL should be formatted with subdomains or not. - * @returns A formatted URL, with the user's preferred IPFS gateway and format (subdomain or not), pointing to an asset hosted on IPFS. - */ -function getFormattedIpfsUrl(ipfsGateway, ipfsUrl, subdomainSupported) { - const { host, protocol, origin } = new URL(addUrlProtocolPrefix(ipfsGateway)); - if (subdomainSupported) { - const { cid, path } = getIpfsCIDv1AndPath(ipfsUrl); - return `${protocol}//${cid}.ipfs.${host}${path !== null && path !== void 0 ? path : ''}`; - } - const cidAndPath = removeIpfsProtocolPrefix(ipfsUrl); - return `${origin}/ipfs/${cidAndPath}`; -} -exports.getFormattedIpfsUrl = getFormattedIpfsUrl; -/** - * Determines whether a value is a "plain" object. - * - * @param value - A value to check - * @returns True if the passed value is a plain object - */ -function isPlainObject(value) { - return Boolean(value) && typeof value === 'object' && !Array.isArray(value); -} -exports.isPlainObject = isPlainObject; -const hasProperty = (object, key) => Reflect.hasOwnProperty.call(object, key); -exports.hasProperty = hasProperty; -/** - * Type guard for {@link NonEmptyArray}. - * - * @template T - The non-empty array member type. - * @param value - The value to check. - * @returns Whether the value is a non-empty array. - */ -function isNonEmptyArray(value) { - return Array.isArray(value) && value.length > 0; -} -exports.isNonEmptyArray = isNonEmptyArray; -/** - * Type guard for {@link Json}. - * - * @param value - The value to check. - * @returns Whether the value is valid JSON. - */ -function isValidJson(value) { - try { - return (0, fast_deep_equal_1.default)(value, JSON.parse(JSON.stringify(value))); - } - catch (_) { - return false; - } -} -exports.isValidJson = isValidJson; -/** - * Networks where token detection is supported - Values are in decimal format - */ -var SupportedTokenDetectionNetworks; -(function (SupportedTokenDetectionNetworks) { - SupportedTokenDetectionNetworks["mainnet"] = "1"; - SupportedTokenDetectionNetworks["bsc"] = "56"; - SupportedTokenDetectionNetworks["polygon"] = "137"; - SupportedTokenDetectionNetworks["avax"] = "43114"; -})(SupportedTokenDetectionNetworks = exports.SupportedTokenDetectionNetworks || (exports.SupportedTokenDetectionNetworks = {})); -/** - * Check if token detection is enabled for certain networks. - * - * @param chainId - ChainID of network - * @returns Whether the current network supports token detection - */ -function isTokenDetectionSupportedForNetwork(chainId) { - return Object.values(SupportedTokenDetectionNetworks).includes(chainId); -} -exports.isTokenDetectionSupportedForNetwork = isTokenDetectionSupportedForNetwork; -/** - * Utility method to log if error is a common fetch error and otherwise rethrow it. - * - * @param error - Caught error that we should either rethrow or log to console - * @param codesToCatch - array of error codes for errors we want to catch and log in a particular context - */ -function logOrRethrowError(error, codesToCatch = []) { - var _a; - if (!error) { - return; - } - const includesErrorCodeToCatch = codesToCatch.some((code) => { var _a; return (_a = error.message) === null || _a === void 0 ? void 0 : _a.includes(`Fetch failed with status '${code}'`); }); - if (error instanceof Error && - (includesErrorCodeToCatch || - ((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('Failed to fetch')) || - error === TIMEOUT_ERROR)) { - console.error(error); - } - else { - throw error; - } -} -//# sourceMappingURL=util.js.map \ No newline at end of file diff --git a/dist/util.js.map b/dist/util.js.map deleted file mode 100644 index 5a0b373692..0000000000 --- a/dist/util.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n addHexPrefix,\n isValidAddress,\n isHexString,\n bufferToHex,\n BN,\n toChecksumAddress,\n stripHexPrefix,\n} from 'ethereumjs-util';\nimport { fromWei, toWei } from 'ethjs-unit';\nimport { ethErrors } from 'eth-rpc-errors';\nimport ensNamehash from 'eth-ens-namehash';\nimport { TYPED_MESSAGE_SCHEMA, typedSignatureHash } from 'eth-sig-util';\nimport { validate } from 'jsonschema';\nimport { CID } from 'multiformats/cid';\nimport deepEqual from 'fast-deep-equal';\nimport {\n Transaction,\n FetchAllOptions,\n GasPriceValue,\n FeeMarketEIP1559Values,\n} from './transaction/TransactionController';\nimport { MessageParams } from './message-manager/MessageManager';\nimport { PersonalMessageParams } from './message-manager/PersonalMessageManager';\nimport { TypedMessageParams } from './message-manager/TypedMessageManager';\nimport { Token } from './assets/TokenRatesController';\nimport { MAINNET } from './constants';\nimport { Json } from './BaseControllerV2';\n\nconst TIMEOUT_ERROR = new Error('timeout');\n\nconst hexRe = /^[0-9A-Fa-f]+$/gu;\n\nconst NORMALIZERS: { [param in keyof Transaction]: any } = {\n data: (data: string) => addHexPrefix(data),\n from: (from: string) => addHexPrefix(from).toLowerCase(),\n gas: (gas: string) => addHexPrefix(gas),\n gasPrice: (gasPrice: string) => addHexPrefix(gasPrice),\n nonce: (nonce: string) => addHexPrefix(nonce),\n to: (to: string) => addHexPrefix(to).toLowerCase(),\n value: (value: string) => addHexPrefix(value),\n maxFeePerGas: (maxFeePerGas: string) => addHexPrefix(maxFeePerGas),\n maxPriorityFeePerGas: (maxPriorityFeePerGas: string) =>\n addHexPrefix(maxPriorityFeePerGas),\n estimatedBaseFee: (maxPriorityFeePerGas: string) =>\n addHexPrefix(maxPriorityFeePerGas),\n};\n\n/**\n * Converts a BN object to a hex string with a '0x' prefix.\n *\n * @param inputBn - BN instance to convert to a hex string.\n * @returns A '0x'-prefixed hex string.\n */\nexport function BNToHex(inputBn: any) {\n return addHexPrefix(inputBn.toString(16));\n}\n\n/**\n * Used to multiply a BN by a fraction.\n *\n * @param targetBN - Number to multiply by a fraction.\n * @param numerator - Numerator of the fraction multiplier.\n * @param denominator - Denominator of the fraction multiplier.\n * @returns Product of the multiplication.\n */\nexport function fractionBN(\n targetBN: any,\n numerator: number | string,\n denominator: number | string,\n) {\n const numBN = new BN(numerator);\n const denomBN = new BN(denominator);\n return targetBN.mul(numBN).div(denomBN);\n}\n\n/**\n * Used to convert a base-10 number from GWEI to WEI. Can handle numbers with decimal parts.\n *\n * @param n - The base 10 number to convert to WEI.\n * @returns The number in WEI, as a BN.\n */\nexport function gweiDecToWEIBN(n: number | string) {\n if (Number.isNaN(n)) {\n return new BN(0);\n }\n\n const parts = n.toString().split('.');\n const wholePart = parts[0] || '0';\n let decimalPart = parts[1] || '';\n\n if (!decimalPart) {\n return toWei(wholePart, 'gwei');\n }\n\n if (decimalPart.length <= 9) {\n return toWei(`${wholePart}.${decimalPart}`, 'gwei');\n }\n\n const decimalPartToRemove = decimalPart.slice(9);\n const decimalRoundingDigit = decimalPartToRemove[0];\n\n decimalPart = decimalPart.slice(0, 9);\n let wei = toWei(`${wholePart}.${decimalPart}`, 'gwei');\n\n if (Number(decimalRoundingDigit) >= 5) {\n wei = wei.add(new BN(1));\n }\n\n return wei;\n}\n\n/**\n * Used to convert values from wei hex format to dec gwei format.\n *\n * @param hex - The value in hex wei.\n * @returns The value in dec gwei as string.\n */\nexport function weiHexToGweiDec(hex: string) {\n const hexWei = new BN(stripHexPrefix(hex), 16);\n return fromWei(hexWei, 'gwei').toString(10);\n}\n\n/**\n * Return a URL that can be used to obtain ETH for a given network.\n *\n * @param networkCode - Network code of desired network.\n * @param address - Address to deposit obtained ETH.\n * @param amount - How much ETH is desired.\n * @returns URL to buy ETH based on network.\n */\nexport function getBuyURL(\n networkCode = '1',\n address?: string,\n amount = 5,\n): string | undefined {\n switch (networkCode) {\n case '1':\n return `https://buy.coinbase.com/?code=9ec56d01-7e81-5017-930c-513daa27bb6a&amount=${amount}&address=${address}&crypto_currency=ETH`;\n case '3':\n return 'https://faucet.metamask.io/';\n case '4':\n return 'https://www.rinkeby.io/';\n case '5':\n return 'https://goerli-faucet.slock.it/';\n case '42':\n return 'https://github.com/kovan-testnet/faucet';\n default:\n return undefined;\n }\n}\n\n/**\n * Return a URL that can be used to fetch ETH transactions.\n *\n * @param networkType - Network type of desired network.\n * @param urlParams - The parameters used to construct the URL.\n * @returns URL to fetch the access the endpoint.\n */\nexport function getEtherscanApiUrl(\n networkType: string,\n urlParams: any,\n): string {\n let etherscanSubdomain = 'api';\n if (networkType !== MAINNET) {\n etherscanSubdomain = `api-${networkType}`;\n }\n const apiUrl = `https://${etherscanSubdomain}.etherscan.io`;\n let url = `${apiUrl}/api?`;\n\n for (const paramKey in urlParams) {\n if (urlParams[paramKey]) {\n url += `${paramKey}=${urlParams[paramKey]}&`;\n }\n }\n url += 'tag=latest&page=1';\n return url;\n}\n\n/**\n * Handles the fetch of incoming transactions.\n *\n * @param networkType - Network type of desired network.\n * @param address - Address to get the transactions from.\n * @param txHistoryLimit - The maximum number of transactions to fetch.\n * @param opt - Object that can contain fromBlock and Etherscan service API key.\n * @returns Responses for both ETH and ERC20 token transactions.\n */\nexport async function handleTransactionFetch(\n networkType: string,\n address: string,\n txHistoryLimit: number,\n opt?: FetchAllOptions,\n): Promise<[{ [result: string]: [] }, { [result: string]: [] }]> {\n // transactions\n const urlParams = {\n module: 'account',\n address,\n startBlock: opt?.fromBlock,\n apikey: opt?.etherscanApiKey,\n offset: txHistoryLimit.toString(),\n order: 'desc',\n };\n const etherscanTxUrl = getEtherscanApiUrl(networkType, {\n ...urlParams,\n action: 'txlist',\n });\n const etherscanTxResponsePromise = handleFetch(etherscanTxUrl);\n\n // tokens\n const etherscanTokenUrl = getEtherscanApiUrl(networkType, {\n ...urlParams,\n action: 'tokentx',\n });\n const etherscanTokenResponsePromise = handleFetch(etherscanTokenUrl);\n\n let [etherscanTxResponse, etherscanTokenResponse] = await Promise.all([\n etherscanTxResponsePromise,\n etherscanTokenResponsePromise,\n ]);\n\n if (\n etherscanTxResponse.status === '0' ||\n etherscanTxResponse.result.length <= 0\n ) {\n etherscanTxResponse = { status: etherscanTxResponse.status, result: [] };\n }\n\n if (\n etherscanTokenResponse.status === '0' ||\n etherscanTokenResponse.result.length <= 0\n ) {\n etherscanTokenResponse = {\n status: etherscanTokenResponse.status,\n result: [],\n };\n }\n\n return [etherscanTxResponse, etherscanTokenResponse];\n}\n\n/**\n * Converts a hex string to a BN object.\n *\n * @param inputHex - Number represented as a hex string.\n * @returns A BN instance.\n */\nexport function hexToBN(inputHex: string) {\n return new BN(stripHexPrefix(inputHex), 16);\n}\n\n/**\n * A helper function that converts hex data to human readable string.\n *\n * @param hex - The hex string to convert to string.\n * @returns A human readable string conversion.\n */\nexport function hexToText(hex: string) {\n try {\n const stripped = stripHexPrefix(hex);\n const buff = Buffer.from(stripped, 'hex');\n return buff.toString('utf8');\n } catch (e) {\n /* istanbul ignore next */\n return hex;\n }\n}\n\n/**\n * Parses a hex string and converts it into a number that can be operated on in a bignum-safe,\n * base-10 way.\n *\n * @param value - A base-16 number encoded as a string.\n * @returns The number as a BN object in base-16 mode.\n */\nexport function fromHex(value: string | BN): BN {\n if (BN.isBN(value)) {\n return value;\n }\n return new BN(hexToBN(value).toString(10));\n}\n\n/**\n * Converts an integer to a hexadecimal representation.\n *\n * @param value - An integer, an integer encoded as a base-10 string, or a BN.\n * @returns The integer encoded as a hex string.\n */\nexport function toHex(value: number | string | BN): string {\n if (typeof value === 'string' && isHexString(value)) {\n return value;\n }\n const hexString = BN.isBN(value)\n ? value.toString(16)\n : new BN(value.toString(), 10).toString(16);\n return `0x${hexString}`;\n}\n\n/**\n * Normalizes properties on a Transaction object.\n *\n * @param transaction - Transaction object to normalize.\n * @returns Normalized Transaction object.\n */\nexport function normalizeTransaction(transaction: Transaction) {\n const normalizedTransaction: Transaction = { from: '' };\n let key: keyof Transaction;\n for (key in NORMALIZERS) {\n if (transaction[key as keyof Transaction]) {\n normalizedTransaction[key] = NORMALIZERS[key](transaction[key]) as never;\n }\n }\n return normalizedTransaction;\n}\n\n/**\n * Execute and return an asynchronous operation without throwing errors.\n *\n * @param operation - Function returning a Promise.\n * @param logError - Determines if the error should be logged.\n * @returns Promise resolving to the result of the async operation.\n */\nexport async function safelyExecute(\n operation: () => Promise,\n logError = false,\n) {\n try {\n return await operation();\n } catch (error: any) {\n /* istanbul ignore next */\n if (logError) {\n console.error(error);\n }\n return undefined;\n }\n}\n\n/**\n * Execute and return an asynchronous operation with a timeout.\n *\n * @param operation - Function returning a Promise.\n * @param logError - Determines if the error should be logged.\n * @param timeout - Timeout to fail the operation.\n * @returns Promise resolving to the result of the async operation.\n */\nexport async function safelyExecuteWithTimeout(\n operation: () => Promise,\n logError = false,\n timeout = 500,\n) {\n try {\n return await Promise.race([\n operation(),\n new Promise((_, reject) =>\n setTimeout(() => {\n reject(TIMEOUT_ERROR);\n }, timeout),\n ),\n ]);\n } catch (error) {\n /* istanbul ignore next */\n if (logError) {\n console.error(error);\n }\n return undefined;\n }\n}\n\n/**\n * Convert an address to a checksummed hexidecimal address.\n *\n * @param address - The address to convert.\n * @returns A 0x-prefixed hexidecimal checksummed address.\n */\nexport function toChecksumHexAddress(address: string) {\n const hexPrefixed = addHexPrefix(address);\n if (!isHexString(hexPrefixed)) {\n // Version 5.1 of ethereumjs-utils would have returned '0xY' for input 'y'\n // but we shouldn't waste effort trying to change case on a clearly invalid\n // string. Instead just return the hex prefixed original string which most\n // closely mimics the original behavior.\n return hexPrefixed;\n }\n return toChecksumAddress(hexPrefixed);\n}\n\n/**\n * Validates that the input is a hex address. This utility method is a thin\n * wrapper around ethereumjs-util.isValidAddress, with the exception that it\n * does not throw an error when provided values that are not hex strings. In\n * addition, and by default, this method will return true for hex strings that\n * meet the length requirement of a hex address, but are not prefixed with `0x`\n * Finally, if the mixedCaseUseChecksum flag is true and a mixed case string is\n * provided this method will validate it has the proper checksum formatting.\n *\n * @param possibleAddress - Input parameter to check against.\n * @param options - The validation options.\n * @param options.allowNonPrefixed - If true will first ensure '0x' is prepended to the string.\n * @returns Whether or not the input is a valid hex address.\n */\nexport function isValidHexAddress(\n possibleAddress: string,\n { allowNonPrefixed = true } = {},\n) {\n const addressToCheck = allowNonPrefixed\n ? addHexPrefix(possibleAddress)\n : possibleAddress;\n if (!isHexString(addressToCheck)) {\n return false;\n }\n\n return isValidAddress(addressToCheck);\n}\n\n/**\n * Validates a Transaction object for required properties and throws in\n * the event of any validation error.\n *\n * @param transaction - Transaction object to validate.\n */\nexport function validateTransaction(transaction: Transaction) {\n if (\n !transaction.from ||\n typeof transaction.from !== 'string' ||\n !isValidHexAddress(transaction.from)\n ) {\n throw new Error(\n `Invalid \"from\" address: ${transaction.from} must be a valid string.`,\n );\n }\n\n if (transaction.to === '0x' || transaction.to === undefined) {\n if (transaction.data) {\n delete transaction.to;\n } else {\n throw new Error(\n `Invalid \"to\" address: ${transaction.to} must be a valid string.`,\n );\n }\n } else if (\n transaction.to !== undefined &&\n !isValidHexAddress(transaction.to)\n ) {\n throw new Error(\n `Invalid \"to\" address: ${transaction.to} must be a valid string.`,\n );\n }\n\n if (transaction.value !== undefined) {\n const value = transaction.value.toString();\n if (value.includes('-')) {\n throw new Error(`Invalid \"value\": ${value} is not a positive number.`);\n }\n\n if (value.includes('.')) {\n throw new Error(\n `Invalid \"value\": ${value} number must be denominated in wei.`,\n );\n }\n const intValue = parseInt(transaction.value, 10);\n const isValid =\n Number.isFinite(intValue) &&\n !Number.isNaN(intValue) &&\n !isNaN(Number(value)) &&\n Number.isSafeInteger(intValue);\n if (!isValid) {\n throw new Error(\n `Invalid \"value\": ${value} number must be a valid number.`,\n );\n }\n }\n}\n\n/**\n * A helper function that converts rawmessageData buffer data to a hex, or just returns the data if\n * it is already formatted as a hex.\n *\n * @param data - The buffer data to convert to a hex.\n * @returns A hex string conversion of the buffer data.\n */\nexport function normalizeMessageData(data: string) {\n try {\n const stripped = stripHexPrefix(data);\n if (stripped.match(hexRe)) {\n return addHexPrefix(stripped);\n }\n } catch (e) {\n /* istanbul ignore next */\n }\n return bufferToHex(Buffer.from(data, 'utf8'));\n}\n\n/**\n * Validates a PersonalMessageParams and MessageParams objects for required properties and throws in\n * the event of any validation error.\n *\n * @param messageData - PersonalMessageParams object to validate.\n */\nexport function validateSignMessageData(\n messageData: PersonalMessageParams | MessageParams,\n) {\n const { from, data } = messageData;\n if (!from || typeof from !== 'string' || !isValidHexAddress(from)) {\n throw new Error(`Invalid \"from\" address: ${from} must be a valid string.`);\n }\n\n if (!data || typeof data !== 'string') {\n throw new Error(`Invalid message \"data\": ${data} must be a valid string.`);\n }\n}\n\n/**\n * Validates a TypedMessageParams object for required properties and throws in\n * the event of any validation error for eth_signTypedMessage_V1.\n *\n * @param messageData - TypedMessageParams object to validate.\n */\nexport function validateTypedSignMessageDataV1(\n messageData: TypedMessageParams,\n) {\n if (\n !messageData.from ||\n typeof messageData.from !== 'string' ||\n !isValidHexAddress(messageData.from)\n ) {\n throw new Error(\n `Invalid \"from\" address: ${messageData.from} must be a valid string.`,\n );\n }\n\n if (!messageData.data || !Array.isArray(messageData.data)) {\n throw new Error(\n `Invalid message \"data\": ${messageData.data} must be a valid array.`,\n );\n }\n\n try {\n // typedSignatureHash will throw if the data is invalid.\n typedSignatureHash(messageData.data as any);\n } catch (e) {\n throw new Error(`Expected EIP712 typed data.`);\n }\n}\n\n/**\n * Validates a TypedMessageParams object for required properties and throws in\n * the event of any validation error for eth_signTypedMessage_V3.\n *\n * @param messageData - TypedMessageParams object to validate.\n */\nexport function validateTypedSignMessageDataV3(\n messageData: TypedMessageParams,\n) {\n if (\n !messageData.from ||\n typeof messageData.from !== 'string' ||\n !isValidHexAddress(messageData.from)\n ) {\n throw new Error(\n `Invalid \"from\" address: ${messageData.from} must be a valid string.`,\n );\n }\n\n if (!messageData.data || typeof messageData.data !== 'string') {\n throw new Error(\n `Invalid message \"data\": ${messageData.data} must be a valid array.`,\n );\n }\n let data;\n try {\n data = JSON.parse(messageData.data);\n } catch (e) {\n throw new Error('Data must be passed as a valid JSON string.');\n }\n const validation = validate(data, TYPED_MESSAGE_SCHEMA);\n if (validation.errors.length > 0) {\n throw new Error(\n 'Data must conform to EIP-712 schema. See https://git.io/fNtcx.',\n );\n }\n}\n\n/**\n * Validates a ERC20 token to be added with EIP747.\n *\n * @param token - Token object to validate.\n */\nexport function validateTokenToWatch(token: Token) {\n const { address, symbol, decimals } = token;\n if (!address || !symbol || typeof decimals === 'undefined') {\n throw ethErrors.rpc.invalidParams(\n `Must specify address, symbol, and decimals.`,\n );\n }\n\n if (typeof symbol !== 'string') {\n throw ethErrors.rpc.invalidParams(`Invalid symbol: not a string.`);\n }\n\n if (symbol.length > 11) {\n throw ethErrors.rpc.invalidParams(\n `Invalid symbol \"${symbol}\": longer than 11 characters.`,\n );\n }\n const numDecimals = parseInt(decimals as unknown as string, 10);\n if (isNaN(numDecimals) || numDecimals > 36 || numDecimals < 0) {\n throw ethErrors.rpc.invalidParams(\n `Invalid decimals \"${decimals}\": must be 0 <= 36.`,\n );\n }\n\n if (!isValidHexAddress(address)) {\n throw ethErrors.rpc.invalidParams(`Invalid address \"${address}\".`);\n }\n}\n\n/**\n * Returns whether the given code corresponds to a smart contract.\n *\n * @param code - The potential smart contract code.\n * @returns Whether the code was smart contract code or not.\n */\nexport function isSmartContractCode(code: string) {\n /* istanbul ignore if */\n if (!code) {\n return false;\n }\n // Geth will return '0x', and ganache-core v2.2.1 will return '0x0'\n const smartContractCode = code !== '0x' && code !== '0x0';\n return smartContractCode;\n}\n\n/**\n * Execute fetch and verify that the response was successful.\n *\n * @param request - Request information.\n * @param options - Fetch options.\n * @returns The fetch response.\n */\nexport async function successfulFetch(request: string, options?: RequestInit) {\n const response = await fetch(request, options);\n if (!response.ok) {\n throw new Error(\n `Fetch failed with status '${response.status}' for request '${request}'`,\n );\n }\n return response;\n}\n\n/**\n * Execute fetch and return object response.\n *\n * @param request - The request information.\n * @param options - The fetch options.\n * @returns The fetch response JSON data.\n */\nexport async function handleFetch(request: string, options?: RequestInit) {\n const response = await successfulFetch(request, options);\n const object = await response.json();\n return object;\n}\n\n/**\n * Execute fetch and return object response, log if known error thrown, otherwise rethrow error.\n *\n * @param request - the request options object\n * @param request.url - The request url to query.\n * @param request.options - The fetch options.\n * @param request.timeout - Timeout to fail request\n * @param request.errorCodesToCatch - array of error codes for errors we want to catch in a particular context\n * @returns The fetch response JSON data or undefined (if error occurs).\n */\nexport async function fetchWithErrorHandling({\n url,\n options,\n timeout,\n errorCodesToCatch,\n}: {\n url: string;\n options?: RequestInit;\n timeout?: number;\n errorCodesToCatch?: number[];\n}) {\n let result;\n try {\n if (timeout) {\n result = Promise.race([\n await handleFetch(url, options),\n new Promise((_, reject) =>\n setTimeout(() => {\n reject(TIMEOUT_ERROR);\n }, timeout),\n ),\n ]);\n } else {\n result = await handleFetch(url, options);\n }\n } catch (e) {\n logOrRethrowError(e, errorCodesToCatch);\n }\n return result;\n}\n\n/**\n * Fetch that fails after timeout.\n *\n * @param url - Url to fetch.\n * @param options - Options to send with the request.\n * @param timeout - Timeout to fail request.\n * @returns Promise resolving the request.\n */\nexport async function timeoutFetch(\n url: string,\n options?: RequestInit,\n timeout = 500,\n): Promise {\n return Promise.race([\n successfulFetch(url, options),\n new Promise((_, reject) =>\n setTimeout(() => {\n reject(TIMEOUT_ERROR);\n }, timeout),\n ),\n ]);\n}\n\n/**\n * Normalizes the given ENS name.\n *\n * @param ensName - The ENS name.\n * @returns The normalized ENS name string.\n */\nexport function normalizeEnsName(ensName: string): string | null {\n if (ensName && typeof ensName === 'string') {\n try {\n const normalized = ensNamehash.normalize(ensName.trim());\n // this regex is only sufficient with the above call to ensNamehash.normalize\n // TODO: change 7 in regex to 3 when shorter ENS domains are live\n if (normalized.match(/^(([\\w\\d-]+)\\.)*[\\w\\d-]{7,}\\.(eth|test)$/u)) {\n return normalized;\n }\n } catch (_) {\n // do nothing\n }\n }\n return null;\n}\n\n/**\n * Wrapper method to handle EthQuery requests.\n *\n * @param ethQuery - EthQuery object initialized with a provider.\n * @param method - Method to request.\n * @param args - Arguments to send.\n * @returns Promise resolving the request.\n */\nexport function query(\n ethQuery: any,\n method: string,\n args: any[] = [],\n): Promise {\n return new Promise((resolve, reject) => {\n const cb = (error: Error, result: any) => {\n if (error) {\n reject(error);\n return;\n }\n resolve(result);\n };\n\n if (typeof ethQuery[method] === 'function') {\n ethQuery[method](...args, cb);\n } else {\n ethQuery.sendAsync({ method, params: args }, cb);\n }\n });\n}\n\n/**\n * Checks if a transaction is EIP-1559 by checking for the existence of\n * maxFeePerGas and maxPriorityFeePerGas within its parameters.\n *\n * @param transaction - Transaction object to add.\n * @returns Boolean that is true if the transaction is EIP-1559 (has maxFeePerGas and maxPriorityFeePerGas), otherwise returns false.\n */\nexport const isEIP1559Transaction = (transaction: Transaction): boolean => {\n const hasOwnProp = (obj: Transaction, key: string) =>\n Object.prototype.hasOwnProperty.call(obj, key);\n return (\n hasOwnProp(transaction, 'maxFeePerGas') &&\n hasOwnProp(transaction, 'maxPriorityFeePerGas')\n );\n};\n\nexport const convertHexToDecimal = (value: string | undefined): number =>\n parseInt(value === undefined ? '0x0' : value, 16);\n\nexport const getIncreasedPriceHex = (value: number, rate: number): string =>\n addHexPrefix(`${parseInt(`${value * rate}`, 10).toString(16)}`);\n\nexport const getIncreasedPriceFromExisting = (\n value: string | undefined,\n rate: number,\n): string => {\n return getIncreasedPriceHex(convertHexToDecimal(value), rate);\n};\n\nexport const validateGasValues = (\n gasValues: GasPriceValue | FeeMarketEIP1559Values,\n) => {\n Object.keys(gasValues).forEach((key) => {\n const value = (gasValues as any)[key];\n if (typeof value !== 'string' || !isHexString(value)) {\n throw new TypeError(\n `expected hex string for ${key} but received: ${value}`,\n );\n }\n });\n};\n\nexport const isFeeMarketEIP1559Values = (\n gasValues?: GasPriceValue | FeeMarketEIP1559Values,\n): gasValues is FeeMarketEIP1559Values =>\n (gasValues as FeeMarketEIP1559Values)?.maxFeePerGas !== undefined ||\n (gasValues as FeeMarketEIP1559Values)?.maxPriorityFeePerGas !== undefined;\n\nexport const isGasPriceValue = (\n gasValues?: GasPriceValue | FeeMarketEIP1559Values,\n): gasValues is GasPriceValue =>\n (gasValues as GasPriceValue)?.gasPrice !== undefined;\n\n/**\n * Validates that the proposed value is greater than or equal to the minimum value.\n *\n * @param proposed - The proposed value.\n * @param min - The minimum value.\n * @returns The proposed value.\n * @throws Will throw if the proposed value is too low.\n */\nexport function validateMinimumIncrease(proposed: string, min: string) {\n const proposedDecimal = convertHexToDecimal(proposed);\n const minDecimal = convertHexToDecimal(min);\n if (proposedDecimal >= minDecimal) {\n return proposed;\n }\n const errorMsg = `The proposed value: ${proposedDecimal} should meet or exceed the minimum value: ${minDecimal}`;\n throw new Error(errorMsg);\n}\n\n/**\n * Removes IPFS protocol prefix from input string.\n *\n * @param ipfsUrl - An IPFS url (e.g. ipfs://{content id})\n * @returns IPFS content identifier and (possibly) path in a string\n * @throws Will throw if the url passed is not IPFS.\n */\nexport function removeIpfsProtocolPrefix(ipfsUrl: string) {\n if (ipfsUrl.startsWith('ipfs://ipfs/')) {\n return ipfsUrl.replace('ipfs://ipfs/', '');\n } else if (ipfsUrl.startsWith('ipfs://')) {\n return ipfsUrl.replace('ipfs://', '');\n }\n // this method should not be used with non-ipfs urls (i.e. startsWith('ipfs://') === true)\n throw new Error('this method should not be used with non ipfs urls');\n}\n\n/**\n * Extracts content identifier and path from an input string.\n *\n * @param ipfsUrl - An IPFS URL minus the IPFS protocol prefix\n * @returns IFPS content identifier (cid) and sub path as string.\n * @throws Will throw if the url passed is not ipfs.\n */\nexport function getIpfsCIDv1AndPath(ipfsUrl: string): {\n cid: string;\n path?: string;\n} {\n const url = removeIpfsProtocolPrefix(ipfsUrl);\n\n // check if there is a path\n // (CID is everything preceding first forward slash, path is everything after)\n const index = url.indexOf('/');\n const cid = index !== -1 ? url.substring(0, index) : url;\n const path = index !== -1 ? url.substring(index) : undefined;\n\n // We want to ensure that the CID is v1 (https://docs.ipfs.io/concepts/content-addressing/#identifier-formats)\n // because most cid v0s appear to be incompatible with IPFS subdomains\n return {\n cid: CID.parse(cid).toV1().toString(),\n path,\n };\n}\n\n/**\n * Adds URL protocol prefix to input URL string if missing.\n *\n * @param urlString - An IPFS URL.\n * @returns A URL with a https:// prepended.\n */\nexport function addUrlProtocolPrefix(urlString: string): string {\n if (!urlString.match(/(^http:\\/\\/)|(^https:\\/\\/)/u)) {\n return `https://${urlString}`;\n }\n return urlString;\n}\n\n/**\n * Formats URL correctly for use retrieving assets hosted on IPFS.\n *\n * @param ipfsGateway - The users preferred IPFS gateway (full URL or just host).\n * @param ipfsUrl - The IFPS URL pointed at the asset.\n * @param subdomainSupported - Boolean indicating whether the URL should be formatted with subdomains or not.\n * @returns A formatted URL, with the user's preferred IPFS gateway and format (subdomain or not), pointing to an asset hosted on IPFS.\n */\nexport function getFormattedIpfsUrl(\n ipfsGateway: string,\n ipfsUrl: string,\n subdomainSupported: boolean,\n): string {\n const { host, protocol, origin } = new URL(addUrlProtocolPrefix(ipfsGateway));\n if (subdomainSupported) {\n const { cid, path } = getIpfsCIDv1AndPath(ipfsUrl);\n return `${protocol}//${cid}.ipfs.${host}${path ?? ''}`;\n }\n const cidAndPath = removeIpfsProtocolPrefix(ipfsUrl);\n return `${origin}/ipfs/${cidAndPath}`;\n}\n\ntype PlainObject = Record;\n\n/**\n * Determines whether a value is a \"plain\" object.\n *\n * @param value - A value to check\n * @returns True if the passed value is a plain object\n */\nexport function isPlainObject(value: unknown): value is PlainObject {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value);\n}\n\nexport const hasProperty = (\n object: PlainObject,\n key: string | number | symbol,\n) => Reflect.hasOwnProperty.call(object, key);\n\n/**\n * Like {@link Array}, but always non-empty.\n *\n * @template T - The non-empty array member type.\n */\nexport type NonEmptyArray = [T, ...T[]];\n\n/**\n * Type guard for {@link NonEmptyArray}.\n *\n * @template T - The non-empty array member type.\n * @param value - The value to check.\n * @returns Whether the value is a non-empty array.\n */\nexport function isNonEmptyArray(value: T[]): value is NonEmptyArray {\n return Array.isArray(value) && value.length > 0;\n}\n\n/**\n * Type guard for {@link Json}.\n *\n * @param value - The value to check.\n * @returns Whether the value is valid JSON.\n */\nexport function isValidJson(value: unknown): value is Json {\n try {\n return deepEqual(value, JSON.parse(JSON.stringify(value)));\n } catch (_) {\n return false;\n }\n}\n\n/**\n * Networks where token detection is supported - Values are in decimal format\n */\nexport enum SupportedTokenDetectionNetworks {\n mainnet = '1',\n bsc = '56',\n polygon = '137',\n avax = '43114',\n}\n\n/**\n * Check if token detection is enabled for certain networks.\n *\n * @param chainId - ChainID of network\n * @returns Whether the current network supports token detection\n */\nexport function isTokenDetectionSupportedForNetwork(chainId: string): boolean {\n return Object.values(SupportedTokenDetectionNetworks).includes(\n chainId,\n );\n}\n\n/**\n * Utility method to log if error is a common fetch error and otherwise rethrow it.\n *\n * @param error - Caught error that we should either rethrow or log to console\n * @param codesToCatch - array of error codes for errors we want to catch and log in a particular context\n */\nfunction logOrRethrowError(error: any, codesToCatch: number[] = []) {\n if (!error) {\n return;\n }\n\n const includesErrorCodeToCatch = codesToCatch.some((code) =>\n error.message?.includes(`Fetch failed with status '${code}'`),\n );\n\n if (\n error instanceof Error &&\n (includesErrorCodeToCatch ||\n error.message?.includes('Failed to fetch') ||\n error === TIMEOUT_ERROR)\n ) {\n console.error(error);\n } else {\n throw error;\n }\n}\n"]} \ No newline at end of file