diff --git a/jest.config.js b/jest.config.js index 41ae9b2e..8a41f8ba 100644 --- a/jest.config.js +++ b/jest.config.js @@ -28,7 +28,7 @@ module.exports = { coverageDirectory: 'coverage', // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: ['/node_modules/', '/src/ppom.ts'], + coveragePathIgnorePatterns: ['/node_modules/', 'src/ppom.ts', 'src/index.ts'], // Indicates which provider should be used to instrument code for coverage coverageProvider: 'babel', diff --git a/package.json b/package.json index 75a804e1..d2578461 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,11 @@ "test": "jest && jest-it-up", "test:watch": "jest --watch" }, + "dependencies": { + "@metamask/base-controller": "^3.0.0", + "@metamask/controller-utils": "^4.0.0", + "await-semaphore": "^0.1.3" + }, "devDependencies": { "@lavamoat/allow-scripts": "^2.3.1", "@lavamoat/preinstall-always-fail": "^1.0.0", @@ -70,7 +75,9 @@ }, "lavamoat": { "allowScripts": { - "@lavamoat/preinstall-always-fail": false + "@lavamoat/preinstall-always-fail": false, + "@metamask/controller-utils>ethereumjs-util>ethereum-cryptography>keccak": false, + "@metamask/controller-utils>ethereumjs-util>ethereum-cryptography>secp256k1": false } } } diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..5b271c24 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,3 @@ +export * from './ppom-controller'; + +export type { StorageBackend, StorageKey } from './ppom-storage'; diff --git a/src/ppom-controller.test.ts b/src/ppom-controller.test.ts new file mode 100644 index 00000000..eb76ee27 --- /dev/null +++ b/src/ppom-controller.test.ts @@ -0,0 +1,281 @@ +import { ControllerMessenger } from '@metamask/base-controller'; + +import { VERSION_INFO, storageBackendReturningData } from '../test/test-utils'; +import { PPOM } from './ppom'; +import { PPOMController, DAY_IN_MILLISECONDS } from './ppom-controller'; + +Object.defineProperty(globalThis, 'fetch', { + writable: true, + value: () => undefined, +}); + +jest.mock('./ppom.ts', () => ({ + PPOM: class PPOMClass { + #jsonRpcRequest; + + constructor(jsonRpcRequest: any) { + this.#jsonRpcRequest = jsonRpcRequest; + } + + validateJsonRpc = async () => { + return Promise.resolve(); + }; + + free = () => undefined; + + testJsonRPCRequest = async () => await this.#jsonRpcRequest(); + }, + ppomInit: () => undefined, +})); + +const PPOM_VERSION_PATH = + 'https://storage.googleapis.com/ppom-cdn/ppom_version.json'; + +const buildFetchSpy = ( + versionData: any = { + status: 200, + json: () => VERSION_INFO, + }, + blobData: any = { + status: 200, + arrayBuffer: () => new ArrayBuffer(123), + }, +) => { + return jest + .spyOn(globalThis, 'fetch' as any) + .mockImplementation((url: any) => { + if (url === PPOM_VERSION_PATH) { + return versionData; + } + return blobData; + }); +}; + +const buildPPOMController = (args?: any) => { + const controllerMessenger = new ControllerMessenger(); + const ppomController = new PPOMController({ + storageBackend: storageBackendReturningData, + provider: () => undefined, + chainId: '0x1', + onNetworkChange: () => undefined, + messenger: controllerMessenger.getRestricted({ + name: 'PPOMController', + }), + ...args, + }); + return ppomController; +}; + +describe('PPOMController', () => { + describe('usePPOM', () => { + it('should provide instance of ppom to the passed ballback', async () => { + const ppomController = buildPPOMController(); + buildFetchSpy(); + + await ppomController.usePPOM(async (ppom: PPOM) => { + expect(ppom).toBeDefined(); + return Promise.resolve(); + }); + }); + + it('should return the value returned by callback', async () => { + const ppomController = buildPPOMController(); + buildFetchSpy(); + + const result = await ppomController.usePPOM(async (ppom: PPOM) => { + expect(ppom).toBeDefined(); + return Promise.resolve('DUMMY_VALUE'); + }); + expect(result).toBe('DUMMY_VALUE'); + }); + + it('should refresh data if network is changed', async () => { + const spy = buildFetchSpy({ + status: 200, + json: () => [ + ...VERSION_INFO, + { + name: 'data', + chainId: '0x2', + version: '1.0.3', + checksum: + '409a7f83ac6b31dc8c77e3ec18038f209bd2f545e0f4177c2e2381aa4e067b49', + filePath: 'data', + }, + ], + }); + + let callBack: any; + const ppomController = buildPPOMController({ + onNetworkChange: (func: any) => { + callBack = func; + }, + }); + await ppomController.usePPOM(async () => { + return Promise.resolve(); + }); + expect(spy).toHaveBeenCalledTimes(3); + await ppomController.usePPOM(async () => { + return Promise.resolve(); + }); + expect(spy).toHaveBeenCalledTimes(3); + + callBack('0x2'); + await ppomController.usePPOM(async () => { + return Promise.resolve(); + }); + expect(spy).toHaveBeenCalledTimes(4); + callBack('0x1'); + }); + + it('should pass instance of provider to ppom to enable it to send JSON RPC request on it', async () => { + buildFetchSpy(); + + const ppomController = buildPPOMController({ + provider: { + sendAsync: (_arg1: any, arg2: any) => { + arg2(undefined, 'DUMMY_VALUE'); + }, + }, + }); + + await ppomController.usePPOM(async (ppom: PPOM) => { + const result = await (ppom as any).testJsonRPCRequest({}); + expect(result).toBe('DUMMY_VALUE'); + }); + }); + + it('should propogate to ppom if JSON RPC request on provider fails', async () => { + const ppomController = buildPPOMController({ + provider: { + sendAsync: (_arg1: any, arg2: any) => { + arg2('DUMMY_ERROR'); + }, + }, + }); + buildFetchSpy(); + await ppomController.usePPOM(async (ppom: PPOM) => { + (ppom as any).testJsonRPCRequest({}).catch((exp: any) => { + // eslint-disable-next-line jest/no-conditional-expect + expect(exp).toBe('DUMMY_ERROR'); + }); + }); + }); + }); + + describe('updatePPOM', () => { + it('should not fetch file if chainId of the file is different from current chainId', async () => { + const spy = buildFetchSpy({ + status: 200, + json: () => [ + ...VERSION_INFO, + { + name: 'data', + chainId: '0x2', + version: '1.0.3', + checksum: + '409a7f83ac6b31dc8c77e3ec18038f209bd2f545e0f4177c2e2381aa4e067b49', + filePath: 'data', + }, + ], + }); + + const ppomController = buildPPOMController(); + await ppomController.usePPOM(async () => { + return Promise.resolve(); + }); + + expect(spy).toHaveBeenCalledTimes(3); + }); + + it('should not fetch file if it already exists', async () => { + const spy = buildFetchSpy(); + + const ppomController = buildPPOMController(); + await ppomController.usePPOM(async () => { + return Promise.resolve(); + }); + expect(spy).toHaveBeenCalledTimes(3); + await ppomController.usePPOM(async () => { + return Promise.resolve(); + }); + expect(spy).toHaveBeenCalledTimes(3); + }); + + it('should throw error if fetch for version info return 500', async () => { + buildFetchSpy({ + status: 500, + }); + + const ppomController = buildPPOMController(); + await expect(async () => { + await ppomController.usePPOM(async () => { + return Promise.resolve(); + }); + }).rejects.toThrow('Failed to fetch version info'); + }); + + it('should throw error if fetch for blob return 500', async () => { + buildFetchSpy(undefined, { + status: 500, + }); + + const ppomController = buildPPOMController(); + await expect(async () => { + await ppomController.usePPOM(async () => { + return Promise.resolve(); + }); + }).rejects.toThrow( + 'Failed to fetch file with url https://storage.googleapis.com/ppom-cdn/blob', + ); + }); + }); + + describe('setRefreshInterval', () => { + it('should update refresh interval', async () => { + const ppomController = buildPPOMController(); + const spy = buildFetchSpy(); + + // controller fetches new data files is difference from last updated time + // is greater than refresh interval. + await ppomController.usePPOM(async () => { + return Promise.resolve(); + }); + expect(spy).toHaveBeenCalledTimes(3); + ppomController.setRefreshInterval(0); + await ppomController.usePPOM(async () => { + return Promise.resolve(); + }); + expect(spy).toHaveBeenCalledTimes(4); + + ppomController.setRefreshInterval(1000); + await ppomController.usePPOM(async () => { + return Promise.resolve(); + }); + expect(spy).toHaveBeenCalledTimes(4); + ppomController.setRefreshInterval(DAY_IN_MILLISECONDS); + }); + }); + + describe('clear', () => { + it('should clear controller state', async () => { + const ppomController = buildPPOMController(); + const spy = buildFetchSpy(); + + // controller fetches new data files when state is cleared + await ppomController.usePPOM(async () => { + return Promise.resolve(); + }); + expect(spy).toHaveBeenCalledTimes(3); + await ppomController.usePPOM(async () => { + return Promise.resolve(); + }); + expect(spy).toHaveBeenCalledTimes(3); + ppomController.clear(); + await ppomController.usePPOM(async () => { + return Promise.resolve(); + }); + expect(spy).toHaveBeenCalledTimes(6); + }); + }); +}); diff --git a/src/ppom-controller.ts b/src/ppom-controller.ts new file mode 100644 index 00000000..5b0c9893 --- /dev/null +++ b/src/ppom-controller.ts @@ -0,0 +1,476 @@ +import { + BaseControllerV2, + RestrictedControllerMessenger, +} from '@metamask/base-controller'; +import { safelyExecute } from '@metamask/controller-utils'; +import { Mutex } from 'await-semaphore'; + +import { ppomInit, PPOM } from './ppom'; +import { + StorageBackend, + PPOMStorage, + FileMetadataList, + FileMetadata, +} from './ppom-storage'; + +export const DAY_IN_MILLISECONDS = 1000 * 60 * 60 * 24; + +/** + * @type PPOMFileVersion + * @augments FileMetadata + * @property filePath - Path of the file in CDN. + */ +type PPOMFileVersion = FileMetadata & { + filePath: string; +}; + +/** + * @type PPOMFileVersion + * @augments FileMetadata + * @property filePath - Path of the file in CDN. + */ +type PPOMFile = FileMetadata & { + filePath: string; + data: ArrayBuffer; +}; + +/** + * @type PPOMVersionResponse - array of objects of type PPOMFileVersion + */ +type PPOMVersionResponse = PPOMFileVersion[]; + +/** + * @type PPOMControllerState + * + * Controller state + * @property lastFetched - Time when files were last updated. + * @property lastChainId - ChainId for which files were last updated. + * @property newChainId - ChainIf of currently selected network. + * @property versionInfo - Version information fetched from CDN. + * @property storageMetadata - Metadata of files storaged in storage. + */ +export type PPOMControllerState = { + lastFetched: number; + lastChainId: string; + newChainId: string; + versionInfo: PPOMVersionResponse; + storageMetadata: FileMetadataList; + refreshInterval: number; +}; + +const stateMetaData = { + lastFetched: { persist: false, anonymous: false }, + lastChainId: { persist: false, anonymous: false }, + newChainId: { persist: false, anonymous: false }, + versionInfo: { persist: false, anonymous: false }, + storageMetadata: { persist: false, anonymous: false }, + refreshInterval: { persist: false, anonymous: false }, +}; + +// TODO: replace with metamask cdn +const PPOM_CDN_BASE_URL = 'https://storage.googleapis.com/ppom-cdn/'; +const PPOM_VERSION = 'ppom_version.json'; +const PPOM_VERSION_PATH = `${PPOM_CDN_BASE_URL}${PPOM_VERSION}`; + +const controllerName = 'PPOMController'; + +export type Clear = { + type: `${typeof controllerName}:clear`; + handler: () => void; +}; + +export type UsePPOM = { + type: `${typeof controllerName}:usePPOM`; + handler: (callback: (ppom: PPOM) => Promise) => Promise; +}; + +export type SetRefreshInterval = { + type: `${typeof controllerName}:setRefreshInterval`; + handler: (interval: number) => void; +}; + +export type UpdatePPOM = { + type: `${typeof controllerName}:updatePPOM`; + handler: () => void; +}; + +export type PPOMControllerActions = + | Clear + | UsePPOM + | SetRefreshInterval + | UpdatePPOM; + +export type PPOMControllerMessenger = RestrictedControllerMessenger< + typeof controllerName, + PPOMControllerActions, + never, + never, + never +>; + +/** + * PPOMController + * Controller responsible for managing the PPOM + * + * @property config - The controller configuration + * @property state - The controller state + * @property storage - The controller storage + * @property ppom - The PPOM instance + * @property provider - The provider used to create the PPOM instance + */ +export class PPOMController extends BaseControllerV2< + typeof controllerName, + PPOMControllerState, + PPOMControllerMessenger +> { + #ppom: PPOM | undefined; + + #provider: any; + + #storage: PPOMStorage; + + /* + * This mutex is used to prevent concurrent usage of the PPOM instance + * and protect the PPOM instance from being used while it is being initialized/updated + */ + #ppomMutex: Mutex; + + #defaultState: PPOMControllerState; + + /** + * Creates a PPOMController instance. + * + * @param options - Constructor options. + * @param options.chainId - Id of current chain. + * @param options.messenger - Controller messenger. + * @param options.onNetworkChange - Callback tobe invoked when network changes. + * @param options.provider - The provider used to create the PPOM instance. + * @param options.state - The controller state. + * @param options.storageBackend - The storage backend to use for storing PPOM data. + * @returns The PPOMController instance. + */ + constructor({ + chainId, + messenger, + onNetworkChange, + provider, + state, + storageBackend, + }: { + chainId: string; + messenger: PPOMControllerMessenger; + onNetworkChange: (callback: (chainId: string) => void) => void; + provider: any; + state?: PPOMControllerState; + storageBackend: StorageBackend; + }) { + const defaultState = { + lastFetched: 0, + versionInfo: [], + storageMetadata: [], + lastChainId: '', + newChainId: chainId, + refreshInterval: DAY_IN_MILLISECONDS, + }; + super({ + name: controllerName, + metadata: stateMetaData, + messenger, + state: { ...defaultState, ...state }, + }); + + this.#defaultState = defaultState; + + this.#provider = provider; + this.#storage = new PPOMStorage({ + storageBackend, + readMetadata: () => { + return JSON.parse(JSON.stringify(this.state.storageMetadata)); + }, + writeMetadata: (metadata) => { + this.update((draftState) => { + draftState.storageMetadata = metadata; + }); + }, + }); + this.#ppomMutex = new Mutex(); + + onNetworkChange((id: string) => { + this.update((draftState) => { + draftState.newChainId = id; + }); + }); + + this.#registerMessageHandlers(); + } + + /** + * Clear the controller state. + */ + clear(): void { + this.update(() => this.#defaultState); + } + + /** + * Set the interval at which the ppom version info will be fetched. + * Fetching will only occur on the next call to test/bypass. + * For immediate update to the ppom lists, call updatePPOM directly. + * + * @param interval - The new interval in ms. + */ + setRefreshInterval(interval: number) { + this.update((draftState) => { + draftState.refreshInterval = interval; + }); + } + + /** + * Update the PPOM configuration. + * This function will fetch the latest version info when needed, and update the PPOM storage. + */ + async updatePPOM() { + if (this.#ppom) { + this.#ppom.free(); + this.#ppom = undefined; + } + + if (this.#isOutOfDate()) { + await this.#updateVersionInfo(); + } + + this.update((draftState) => { + draftState.lastChainId = this.state.newChainId; + }); + + const storageMetadata = await this.#storage.syncMetadata( + this.state.versionInfo, + ); + const newFiles = await this.#getNewFiles( + this.state.newChainId, + storageMetadata, + ); + + for (const file of newFiles) { + await this.#storage.writeFile(file); + } + } + + /** + * Use the PPOM. + * This function receives a callback that will be called with the PPOM. + * The callback will be called with the PPOM after it has been initialized. + * + * @param callback - Callback to be invoked with PPOM. + */ + async usePPOM(callback: (ppom: PPOM) => Promise): Promise { + return await this.#ppomMutex.use(async () => { + await this.#maybeUpdatePPOM(); + + if (!this.#ppom) { + this.#ppom = await this.#getPPOM(); + } + + return await callback(this.#ppom); + }); + } + + /** + * Constructor helper for registering this controller's messaging system + * actions. + */ + #registerMessageHandlers(): void { + this.messagingSystem.registerActionHandler( + `${controllerName}:clear` as const, + this.clear.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `${controllerName}:usePPOM` as const, + this.usePPOM.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `${controllerName}:setRefreshInterval` as const, + this.setRefreshInterval.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `${controllerName}:updatePPOM` as const, + this.updatePPOM.bind(this), + ); + } + + /** + * Determine if an update to the ppom configuration is needed. + * The function will return true if + * - the chainId has changed + * - the ppom is out of date + * - the ppom is not initialized. + * + * @returns True if PPOM data requires update. + */ + async #shouldUpdate(): Promise { + if ( + this.state.newChainId !== this.state.lastChainId || + this.#isOutOfDate() + ) { + return true; + } + + return this.#ppom === undefined; + } + + /* + * check if the ppom is out of date + */ + #isOutOfDate(): boolean { + return Date.now() - this.state.lastFetched >= this.state.refreshInterval; + } + + /** + * Returns an array of new files that should be downloaded and saved to storage. + * + * @param chainId - The chain ID to check for files. + * @param storageMetadata - An array of file metadata objects already in storage. + * @returns A promise that resolves to an array of new files to download and save to storage. + */ + async #getNewFiles( + chainId: string, + storageMetadata: FileMetadataList, + ): Promise { + const newFiles: PPOMFile[] = []; + + for (const fileVersionInfo of this.state.versionInfo) { + // download all files for the current chain + generally required files. + if (fileVersionInfo.chainId && fileVersionInfo.chainId !== chainId) { + continue; + } + + // check if file is already in storage + if ( + storageMetadata.find( + (file) => + file.name === fileVersionInfo.name && + file.chainId === fileVersionInfo.chainId && + file.version === fileVersionInfo.version && + file.checksum === fileVersionInfo.checksum, + ) + ) { + continue; + } + + const fileUrl = `${PPOM_CDN_BASE_URL}${fileVersionInfo.filePath}`; + const fileData = await this.#fetchBlob(fileUrl); + + newFiles.push({ + data: fileData, + ...fileVersionInfo, + }); + } + + return newFiles; + } + + /* + * Fetch the version info from the PPOM cdn. + * update the version info in state. + */ + async #updateVersionInfo() { + const versionInfo = await this.#fetchVersionInfo(PPOM_VERSION_PATH); + + this.update((draftState) => { + draftState.versionInfo = versionInfo; + draftState.lastFetched = Date.now(); + }); + } + + /** + * Conditionally update the ppom configuration. + * + * If the ppom configuration is out of date, this function will call `updatePPOM` + * to update the configuration. + */ + async #maybeUpdatePPOM() { + if (await this.#shouldUpdate()) { + await this.updatePPOM(); + } + } + + /* + * Fetch the blob from the PPOM cdn. + */ + async #fetchBlob(fileUrl: string): Promise { + const response = await safelyExecute( + async () => fetch(fileUrl, { cache: 'no-cache' }), + true, + ); + + switch (response?.status) { + case 200: { + return await response.arrayBuffer(); + } + + default: { + throw new Error(`Failed to fetch file with url ${fileUrl}`); + } + } + } + + /* + * Fetch the version info from the PPOM cdn. + */ + async #fetchVersionInfo(url: string): Promise { + const response = await safelyExecute( + async () => fetch(url, { cache: 'no-cache' }), + true, + ); + switch (response?.status) { + case 200: { + return response.json(); + } + + default: { + throw new Error(`Failed to fetch version info url: ${url}`); + } + } + } + + /* + * Send a JSON RPC request to the provider. + * This method is used by the PPOM to make requests to the provider. + */ + async #jsonRpcRequest(req: any): Promise { + return new Promise((resolve, reject) => { + this.#provider.sendAsync(req, (error: any, res: any) => { + if (error) { + reject(error); + } else { + resolve(res); + } + }); + }); + } + + /* + * Initialize the PPOM. + * This function will be called when the PPOM is first used. + * or when the PPOM is out of date. + * It will load the PPOM data from storage and initialize the PPOM. + */ + async #getPPOM(): Promise { + await ppomInit('./ppom.wasm'); + + const chainId = this.state.lastChainId; + + const files = await Promise.all( + this.state.versionInfo + .filter((file) => !file.chainId || file.chainId === chainId) + .map(async (file) => { + const data = await this.#storage.readFile(file.name, file.chainId); + return [file.name, new Uint8Array(data)]; + }), + ); + + return new PPOM(this.#jsonRpcRequest.bind(this), files); + } +} diff --git a/src/ppom-storage.test.ts b/src/ppom-storage.test.ts index 2bf789f6..bfea4375 100644 --- a/src/ppom-storage.test.ts +++ b/src/ppom-storage.test.ts @@ -1,27 +1,14 @@ +import { + DUMMY_ARRAY_BUFFER_DATA, + buildStorageBackend, + simpleStorageBackend, + storageBackendReturningData, +} from '../test/test-utils'; import { PPOMStorage, StorageKey } from './ppom-storage'; -const buildStorageBackend = (obj = {}) => { - return { - read: async (_key: StorageKey): Promise => Promise.resolve(), - write: async (_key: StorageKey, _data: any): Promise => - Promise.resolve(), - delete: async (_key: StorageKey): Promise => Promise.resolve(), - dir: async (): Promise => Promise.resolve([]), - ...obj, - }; -}; - -const simpleStorageBackend = buildStorageBackend(); - -const buildStorageBackendReturningData = (data: ArrayBuffer) => - buildStorageBackend({ - read: async (_key: StorageKey): Promise => Promise.resolve(data), - }); - const DUMMY_CHECKSUM = 'DUMMY_CHECKSUM'; -const DUMMY_NAME = 'DUMMY'; +const DUMMY_NAME = 'DUMMY_NAME'; const DUMMY_CHAINID = '1'; -const ARRAY_BUFFER_DATA = new ArrayBuffer(123); const getFileData = (data = {}) => ({ chainId: DUMMY_CHAINID, @@ -37,17 +24,17 @@ describe('PPOMStorage', () => { describe('readFile', () => { it('should return data', async () => { const ppomStorage = new PPOMStorage({ - storageBackend: buildStorageBackendReturningData(ARRAY_BUFFER_DATA), + storageBackend: storageBackendReturningData, readMetadata: () => [simpleFileData], writeMetadata: () => undefined, }); const data = await ppomStorage.readFile(DUMMY_NAME, DUMMY_CHAINID); - expect(data).toStrictEqual(ARRAY_BUFFER_DATA); + expect(data).toStrictEqual(DUMMY_ARRAY_BUFFER_DATA); }); it('should throw error if file metadata not found', async () => { const ppomStorage = new PPOMStorage({ - storageBackend: buildStorageBackendReturningData(ARRAY_BUFFER_DATA), + storageBackend: storageBackendReturningData, readMetadata: () => [], writeMetadata: () => undefined, }); @@ -81,7 +68,7 @@ describe('PPOMStorage', () => { writeMetadata: () => undefined, }); await ppomStorage.writeFile({ - data: ARRAY_BUFFER_DATA, + data: DUMMY_ARRAY_BUFFER_DATA, ...simpleFileData, }); expect(mockWrite).toHaveBeenCalledTimes(1); @@ -95,7 +82,7 @@ describe('PPOMStorage', () => { writeMetadata: mockWriteMetadata, }); await ppomStorage.writeFile({ - data: ARRAY_BUFFER_DATA, + data: DUMMY_ARRAY_BUFFER_DATA, ...simpleFileData, }); expect(mockWriteMetadata).toHaveBeenCalledWith([simpleFileData]); @@ -109,7 +96,7 @@ describe('PPOMStorage', () => { writeMetadata: mockWriteMetadata, }); await ppomStorage.writeFile({ - data: ARRAY_BUFFER_DATA, + data: DUMMY_ARRAY_BUFFER_DATA, ...simpleFileData, }); expect(mockWriteMetadata).toHaveBeenCalledWith([simpleFileData]); @@ -120,7 +107,7 @@ describe('PPOMStorage', () => { it('should return metadata of file if updated file is found in storage', async () => { const mockWriteMetadata = jest.fn(); const ppomStorage = new PPOMStorage({ - storageBackend: buildStorageBackendReturningData(ARRAY_BUFFER_DATA), + storageBackend: storageBackendReturningData, readMetadata: () => [simpleFileData], writeMetadata: mockWriteMetadata, }); @@ -151,7 +138,7 @@ describe('PPOMStorage', () => { const ppomStorage = new PPOMStorage({ storageBackend: buildStorageBackend({ read: async (_key: StorageKey): Promise => - Promise.resolve(ARRAY_BUFFER_DATA), + Promise.resolve(DUMMY_ARRAY_BUFFER_DATA), dir: async () => Promise.resolve([storageFileData]), delete: mockDelete, }), @@ -178,7 +165,7 @@ describe('PPOMStorage', () => { const ppomStorage = new PPOMStorage({ storageBackend: buildStorageBackend({ read: async (_key: StorageKey): Promise => - Promise.resolve(ARRAY_BUFFER_DATA), + Promise.resolve(DUMMY_ARRAY_BUFFER_DATA), dir: async () => Promise.resolve([fileDataInStorage]), delete: mockDelete, }), @@ -203,7 +190,7 @@ describe('PPOMStorage', () => { const ppomStorage = new PPOMStorage({ storageBackend: buildStorageBackend({ read: async (_key: StorageKey): Promise => - Promise.resolve(ARRAY_BUFFER_DATA), + Promise.resolve(DUMMY_ARRAY_BUFFER_DATA), dir: async () => Promise.resolve([fileDataInStorage]), delete: mockDelete, }), diff --git a/src/ppom-storage.ts b/src/ppom-storage.ts index 95b653a7..1f74efaa 100644 --- a/src/ppom-storage.ts +++ b/src/ppom-storage.ts @@ -1,8 +1,12 @@ /** - * FileMetadata Type + * @type FileMetadata * Defined type for information about file saved in storage backend. + * @property name - Name of the file. + * @property chainId - ChainId for file. + * @property version - File version. + * @property checksum - Checksum of file data. */ -type FileMetadata = { +export type FileMetadata = { name: string; chainId: string; version: string; @@ -10,15 +14,17 @@ type FileMetadata = { }; /** - * FileMetadataList + * @type FileMetadataList * This is type of metadata about files saved in storage, * this information is saved in PPOMController state. */ export type FileMetadataList = FileMetadata[]; /** - * StorageKey Type + * @type StorageKey * This defines type of key that is used for indexing file data saved in StorageBackend. + * @property name - Name of the file. + * @property chainId - ChainId for file. */ export type StorageKey = { name: string; @@ -26,11 +32,15 @@ export type StorageKey = { }; /** - * StorageBackend Type + * @type StorageBackend * This defines type for storage backend implementation. * There will be different storage implementations depending on platform: * 1. extension - indexDB * 2. mobile app - + * @property read - Read file from storage. + * @property write - Write file to storage. + * @property delete - Delete file from storage. + * @property dir - Get list of all files in storage. */ export type StorageBackend = { read(key: StorageKey, checksum: string): Promise; @@ -40,7 +50,7 @@ export type StorageBackend = { }; /** - * PPOMStorage class + * @class PPOMStorage * This class is responsible for managing the local storage * It provides the following functionalities: * 1. Sync the metadata with the version info from the cdn @@ -91,7 +101,7 @@ export class PPOMStorage { */ async syncMetadata(versionInfo: FileMetadataList): Promise { const metadata = this.#readMetadata(); - const syncedMetadata = []; + const syncedMetadata: FileMetadataList = []; for (const fileMetadata of metadata) { // check if the file is readable (e.g. corrupted or deleted) diff --git a/src/ppom.ts b/src/ppom.ts index d9c6be70..f63fa025 100644 --- a/src/ppom.ts +++ b/src/ppom.ts @@ -481,5 +481,4 @@ async function init(input) { return finalizeInit(instance, module); } -export { initSync }; -export default init; +export { initSync, init as ppomInit }; diff --git a/test/test-utils.ts b/test/test-utils.ts new file mode 100644 index 00000000..ebee1726 --- /dev/null +++ b/test/test-utils.ts @@ -0,0 +1,40 @@ +import { StorageKey } from '../src/ppom-storage'; + +export const buildStorageBackend = (obj = {}) => { + return { + read: async (_key: StorageKey): Promise => Promise.resolve(), + write: async (_key: StorageKey, _data: any): Promise => + Promise.resolve(), + delete: async (_key: StorageKey): Promise => Promise.resolve(), + dir: async (): Promise => Promise.resolve([]), + ...obj, + }; +}; + +export const simpleStorageBackend = buildStorageBackend(); + +export const DUMMY_ARRAY_BUFFER_DATA = new ArrayBuffer(123); + +export const storageBackendReturningData = buildStorageBackend({ + read: async (_key: StorageKey): Promise => + Promise.resolve(DUMMY_ARRAY_BUFFER_DATA), +}); + +export const VERSION_INFO = [ + { + name: 'blob', + chainId: '', + version: '1.0.0', + checksum: + '409a7f83ac6b31dc8c77e3ec18038f209bd2f545e0f4177c2e2381aa4e067b49', + filePath: 'blob', + }, + { + name: 'data', + chainId: '0x1', + version: '1.0.3', + checksum: + '409a7f83ac6b31dc8c77e3ec18038f209bd2f545e0f4177c2e2381aa4e067b49', + filePath: 'data', + }, +]; diff --git a/tsconfig.json b/tsconfig.json index 23c8ceb7..7483086d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "esModuleInterop": true, "exactOptionalPropertyTypes": true, "forceConsistentCasingInFileNames": true, - "lib": ["ES2020"], + "lib": ["ES2020", "DOM"], "module": "CommonJS", "moduleResolution": "node", "noEmit": true, diff --git a/yarn.lock b/yarn.lock index 7023f527..d9fa283d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -405,6 +405,33 @@ __metadata: languageName: node linkType: hard +"@chainsafe/as-sha256@npm:^0.4.1": + version: 0.4.1 + resolution: "@chainsafe/as-sha256@npm:0.4.1" + checksum: 6d86975e648ecdafd366802278ac15b392b252e967f3681412ec48b5a3518b936cc5e977517499882b084991446d25787d98f8f585891943688cc81549a44e9a + languageName: node + linkType: hard + +"@chainsafe/persistent-merkle-tree@npm:^0.6.1": + version: 0.6.1 + resolution: "@chainsafe/persistent-merkle-tree@npm:0.6.1" + dependencies: + "@chainsafe/as-sha256": ^0.4.1 + "@noble/hashes": ^1.3.0 + checksum: 74614b8d40970dc930d5bf741619498b0bbbde5ff24ce45fce6ad122143aa77bf57249a28175b1b972cf56bff57d529a4258b7222ab4e60c1261119b5986c51b + languageName: node + linkType: hard + +"@chainsafe/ssz@npm:^0.11.1": + version: 0.11.1 + resolution: "@chainsafe/ssz@npm:0.11.1" + dependencies: + "@chainsafe/as-sha256": ^0.4.1 + "@chainsafe/persistent-merkle-tree": ^0.6.1 + checksum: e3c2928f9ab4a0544e645f0302b9535046d1e6e1d4b3bd1c3dd6bc8e6302fddad6036d65e7900d1446f285f496051da05fa14c1bde590b511d03033907175c8f + languageName: node + linkType: hard + "@cspotcode/source-map-consumer@npm:0.8.0": version: 0.8.0 resolution: "@cspotcode/source-map-consumer@npm:0.8.0" @@ -449,6 +476,55 @@ __metadata: languageName: node linkType: hard +"@ethereumjs/common@npm:^3.1.2": + version: 3.1.2 + resolution: "@ethereumjs/common@npm:3.1.2" + dependencies: + "@ethereumjs/util": ^8.0.6 + crc-32: ^1.2.0 + checksum: e80a8bc86476f1ce878bacb1915d91681671bb5303291cdcece26e456ac13a6158f0f59625cb02a1cfbdd7c9a7dc8b175f8d8f0fee596b3eb9dfb965465ad43d + languageName: node + linkType: hard + +"@ethereumjs/rlp@npm:^4.0.1": + version: 4.0.1 + resolution: "@ethereumjs/rlp@npm:4.0.1" + bin: + rlp: bin/rlp + checksum: 30db19c78faa2b6ff27275ab767646929207bb207f903f09eb3e4c273ce2738b45f3c82169ddacd67468b4f063d8d96035f2bf36f02b6b7e4d928eefe2e3ecbc + languageName: node + linkType: hard + +"@ethereumjs/tx@npm:^4.1.2": + version: 4.1.2 + resolution: "@ethereumjs/tx@npm:4.1.2" + dependencies: + "@chainsafe/ssz": ^0.11.1 + "@ethereumjs/common": ^3.1.2 + "@ethereumjs/rlp": ^4.0.1 + "@ethereumjs/util": ^8.0.6 + ethereum-cryptography: ^2.0.0 + peerDependencies: + c-kzg: ^1.0.8 + peerDependenciesMeta: + c-kzg: + optional: true + checksum: ad2fb692c3746cd5935b01c98b6b54046ae2a1fccff57ad2209e10446f3b279a204d7477accf05b27078445b14379314077769662142ac07117c45a5a1ea427f + languageName: node + linkType: hard + +"@ethereumjs/util@npm:^8.0.6": + version: 8.0.6 + resolution: "@ethereumjs/util@npm:8.0.6" + dependencies: + "@chainsafe/ssz": ^0.11.1 + "@ethereumjs/rlp": ^4.0.1 + ethereum-cryptography: ^2.0.0 + micro-ftch: ^0.3.1 + checksum: 034e06cddec27417318434a1a7cd7a9dc0f0b447c1f54423c515d8809c9697386eee6429d0a1c13517a85c696e6fdba570b243d882e65764c274859606027015 + languageName: node + linkType: hard + "@gar/promisify@npm:^1.1.3": version: 1.1.3 resolution: "@gar/promisify@npm:1.1.3" @@ -859,6 +935,31 @@ __metadata: languageName: node linkType: hard +"@metamask/base-controller@npm:^3.0.0": + version: 3.0.0 + resolution: "@metamask/base-controller@npm:3.0.0" + dependencies: + "@metamask/utils": ^5.0.2 + immer: ^9.0.6 + checksum: a0853d90b024466c4108531cbf4459bd2f66fa6e0b912e42bd27cdf54262411a5601117649b6061424475ffa6b9714c5199d686c21e4d07c3b7b1ee0b4c17caa + languageName: node + linkType: hard + +"@metamask/controller-utils@npm:^4.0.0": + version: 4.0.0 + resolution: "@metamask/controller-utils@npm:4.0.0" + dependencies: + "@metamask/utils": ^5.0.2 + "@spruceid/siwe-parser": 1.1.3 + eth-ens-namehash: ^2.0.8 + eth-rpc-errors: ^4.0.2 + ethereumjs-util: ^7.0.10 + ethjs-unit: ^0.1.6 + fast-deep-equal: ^3.1.3 + checksum: 3efdaf9b0c9f6d3bb633eeb926e78bedd637b0e042bb1e3974b9829e38456709a2f26d02405499934239351be8bff1854e267df8d0a522738d630bac0aadb732 + languageName: node + linkType: hard + "@metamask/eslint-config-jest@npm:^11.0.0": version: 11.1.0 resolution: "@metamask/eslint-config-jest@npm:11.1.0" @@ -915,6 +1016,8 @@ __metadata: "@lavamoat/allow-scripts": ^2.3.1 "@lavamoat/preinstall-always-fail": ^1.0.0 "@metamask/auto-changelog": ^3.1.0 + "@metamask/base-controller": ^3.0.0 + "@metamask/controller-utils": ^4.0.0 "@metamask/eslint-config": ^11.0.1 "@metamask/eslint-config-jest": ^11.0.0 "@metamask/eslint-config-nodejs": ^11.0.1 @@ -923,6 +1026,7 @@ __metadata: "@types/node": ^16 "@typescript-eslint/eslint-plugin": ^5.43.0 "@typescript-eslint/parser": ^5.43.0 + await-semaphore: ^0.1.3 depcheck: ^1.4.3 eslint: ^8.27.0 eslint-config-prettier: ^8.5.0 @@ -943,6 +1047,35 @@ __metadata: languageName: unknown linkType: soft +"@metamask/utils@npm:^5.0.2": + version: 5.0.2 + resolution: "@metamask/utils@npm:5.0.2" + dependencies: + "@ethereumjs/tx": ^4.1.2 + "@types/debug": ^4.1.7 + debug: ^4.3.4 + semver: ^7.3.8 + superstruct: ^1.0.3 + checksum: eca82e42911b2840deb4f32f0f215c5ffd14d22d68afbbe92d3180e920e509e310777b15eab29def3448f3535b66596ceb4c23666ec846adacc8e1bb093ff882 + languageName: node + linkType: hard + +"@noble/curves@npm:1.0.0, @noble/curves@npm:~1.0.0": + version: 1.0.0 + resolution: "@noble/curves@npm:1.0.0" + dependencies: + "@noble/hashes": 1.3.0 + checksum: 6bcef44d626c640dc8961819d68dd67dffb907e3b973b7c27efe0ecdd9a5c6ce62c7b9e3dfc930c66605dced7f1ec0514d191c09a2ce98d6d52b66e3315ffa79 + languageName: node + linkType: hard + +"@noble/hashes@npm:1.3.0, @noble/hashes@npm:^1.3.0, @noble/hashes@npm:~1.3.0": + version: 1.3.0 + resolution: "@noble/hashes@npm:1.3.0" + checksum: d7ddb6d7c60f1ce1f87facbbef5b724cdea536fc9e7f59ae96e0fc9de96c8f1a2ae2bdedbce10f7dcc621338dfef8533daa73c873f2b5c87fa1a4e05a95c2e2e + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -1019,6 +1152,34 @@ __metadata: languageName: node linkType: hard +"@scure/base@npm:~1.1.0": + version: 1.1.1 + resolution: "@scure/base@npm:1.1.1" + checksum: b4fc810b492693e7e8d0107313ac74c3646970c198bbe26d7332820886fa4f09441991023ec9aa3a2a51246b74409ab5ebae2e8ef148bbc253da79ac49130309 + languageName: node + linkType: hard + +"@scure/bip32@npm:1.3.0": + version: 1.3.0 + resolution: "@scure/bip32@npm:1.3.0" + dependencies: + "@noble/curves": ~1.0.0 + "@noble/hashes": ~1.3.0 + "@scure/base": ~1.1.0 + checksum: 6eae997f9bdf41fe848134898960ac48e645fa10e63d579be965ca331afd0b7c1b8ebac170770d237ab4099dafc35e5a82995384510025ccf2abe669f85e8918 + languageName: node + linkType: hard + +"@scure/bip39@npm:1.2.0": + version: 1.2.0 + resolution: "@scure/bip39@npm:1.2.0" + dependencies: + "@noble/hashes": ~1.3.0 + "@scure/base": ~1.1.0 + checksum: 980d761f53e63de04a9e4db840eb13bfb1bd1b664ecb04a71824c12c190f4972fd84146f3ed89b2a8e4c6bd2c17c15f8b592b7ac029e903323b0f9e2dae6916b + languageName: node + linkType: hard + "@sinclair/typebox@npm:^0.24.1": version: 0.24.28 resolution: "@sinclair/typebox@npm:0.24.28" @@ -1044,6 +1205,15 @@ __metadata: languageName: node linkType: hard +"@spruceid/siwe-parser@npm:1.1.3": + version: 1.1.3 + resolution: "@spruceid/siwe-parser@npm:1.1.3" + dependencies: + apg-js: ^4.1.1 + checksum: 708786ba2f10987c45c1fd8a6243ba6572ee7f320531616d71ff66044828bc24af66f5537ce09c9272bdae93fcc35b566a7804fe7997284f2ee5445a36e6add2 + languageName: node + linkType: hard + "@tootallnate/once@npm:2": version: 2.0.0 resolution: "@tootallnate/once@npm:2.0.0" @@ -1120,6 +1290,15 @@ __metadata: languageName: node linkType: hard +"@types/bn.js@npm:^5.1.0": + version: 5.1.1 + resolution: "@types/bn.js@npm:5.1.1" + dependencies: + "@types/node": "*" + checksum: e50ed2dd3abe997e047caf90e0352c71e54fc388679735217978b4ceb7e336e51477791b715f49fd77195ac26dd296c7bad08a3be9750e235f9b2e1edb1b51c2 + languageName: node + linkType: hard + "@types/color-name@npm:^1.1.1": version: 1.1.1 resolution: "@types/color-name@npm:1.1.1" @@ -1127,6 +1306,15 @@ __metadata: languageName: node linkType: hard +"@types/debug@npm:^4.1.7": + version: 4.1.8 + resolution: "@types/debug@npm:4.1.8" + dependencies: + "@types/ms": "*" + checksum: a9a9bb40a199e9724aa944e139a7659173a9b274798ea7efbc277cb084bc37d32fc4c00877c3496fac4fed70a23243d284adb75c00b5fdabb38a22154d18e5df + languageName: node + linkType: hard + "@types/glob@npm:^7.1.1": version: 7.1.3 resolution: "@types/glob@npm:7.1.3" @@ -1202,6 +1390,13 @@ __metadata: languageName: node linkType: hard +"@types/ms@npm:*": + version: 0.7.31 + resolution: "@types/ms@npm:0.7.31" + checksum: daadd354aedde024cce6f5aa873fefe7b71b22cd0e28632a69e8b677aeb48ae8caa1c60e5919bb781df040d116b01cb4316335167a3fc0ef6a63fa3614c0f6da + languageName: node + linkType: hard + "@types/node@npm:*, @types/node@npm:^16": version: 16.18.31 resolution: "@types/node@npm:16.18.31" @@ -1216,6 +1411,15 @@ __metadata: languageName: node linkType: hard +"@types/pbkdf2@npm:^3.0.0": + version: 3.1.0 + resolution: "@types/pbkdf2@npm:3.1.0" + dependencies: + "@types/node": "*" + checksum: d15024b1957c21cf3b8887329d9bd8dfde754cf13a09d76ae25f1391cfc62bb8b8d7b760773c5dbaa748172fba8b3e0c3dbe962af6ccbd69b76df12a48dfba40 + languageName: node + linkType: hard + "@types/prettier@npm:^2.1.5": version: 2.4.4 resolution: "@types/prettier@npm:2.4.4" @@ -1223,6 +1427,15 @@ __metadata: languageName: node linkType: hard +"@types/secp256k1@npm:^4.0.1": + version: 4.0.3 + resolution: "@types/secp256k1@npm:4.0.3" + dependencies: + "@types/node": "*" + checksum: 1bd10b9afa724084b655dc81b7b315def3d2d0e272014ef16009fa76e17537411c07c0695fdea412bc7b36d2a02687f5fea33522d55b8ef29eda42992f812913 + languageName: node + linkType: hard + "@types/semver@npm:^7.3.12": version: 7.3.13 resolution: "@types/semver@npm:7.3.13" @@ -1576,6 +1789,13 @@ __metadata: languageName: node linkType: hard +"apg-js@npm:^4.1.1": + version: 4.1.3 + resolution: "apg-js@npm:4.1.3" + checksum: fa815838fc3c4b2fa5801419d050c5cf0ac8b5dbbc7476a9c9b8e1ae5fc78feccf01ff3ff52ef1e80c72f8b7bf39f589f1b8aaad5f5aeba399523a9ba18da127 + languageName: node + linkType: hard + "aproba@npm:^1.0.3 || ^2.0.0": version: 2.0.0 resolution: "aproba@npm:2.0.0" @@ -1662,6 +1882,13 @@ __metadata: languageName: node linkType: hard +"await-semaphore@npm:^0.1.3": + version: 0.1.3 + resolution: "await-semaphore@npm:0.1.3" + checksum: 334c86541e446378dd832168de431327a77146f70cd80b57c99cd483ce5996e3bfdadea9d795e36f0b4faacb5121f5f7a99d94297ac2bdafbc690e5b0aa5cc32 + languageName: node + linkType: hard + "babel-jest@npm:^28.1.3": version: 28.1.3 resolution: "babel-jest@npm:28.1.3" @@ -1745,6 +1972,15 @@ __metadata: languageName: node linkType: hard +"base-x@npm:^3.0.2": + version: 3.0.9 + resolution: "base-x@npm:3.0.9" + dependencies: + safe-buffer: ^5.0.1 + checksum: 957101d6fd09e1903e846fd8f69fd7e5e3e50254383e61ab667c725866bec54e5ece5ba49ce385128ae48f9ec93a26567d1d5ebb91f4d56ef4a9cc0d5a5481e8 + languageName: node + linkType: hard + "bin-links@npm:4.0.1": version: 4.0.1 resolution: "bin-links@npm:4.0.1" @@ -1764,6 +2000,34 @@ __metadata: languageName: node linkType: hard +"blakejs@npm:^1.1.0": + version: 1.2.1 + resolution: "blakejs@npm:1.2.1" + checksum: d699ba116cfa21d0b01d12014a03e484dd76d483133e6dc9eb415aa70a119f08beb3bcefb8c71840106a00b542cba77383f8be60cd1f0d4589cb8afb922eefbe + languageName: node + linkType: hard + +"bn.js@npm:4.11.6": + version: 4.11.6 + resolution: "bn.js@npm:4.11.6" + checksum: db23047bf06fdf9cf74401c8e76bca9f55313c81df382247d2c753868b368562e69171716b81b7038ada8860af18346fd4bcd1cf9d4963f923fe8e54e61cb58a + languageName: node + linkType: hard + +"bn.js@npm:^4.11.9": + version: 4.12.0 + resolution: "bn.js@npm:4.12.0" + checksum: 39afb4f15f4ea537b55eaf1446c896af28ac948fdcf47171961475724d1bb65118cca49fa6e3d67706e4790955ec0e74de584e45c8f1ef89f46c812bee5b5a12 + languageName: node + linkType: hard + +"bn.js@npm:^5.1.2, bn.js@npm:^5.2.0": + version: 5.2.1 + resolution: "bn.js@npm:5.2.1" + checksum: 3dd8c8d38055fedfa95c1d5fc3c99f8dd547b36287b37768db0abab3c239711f88ff58d18d155dd8ad902b0b0cee973747b7ae20ea12a09473272b0201c9edd3 + languageName: node + linkType: hard + "brace-expansion@npm:^1.1.7": version: 1.1.11 resolution: "brace-expansion@npm:1.1.11" @@ -1792,6 +2056,27 @@ __metadata: languageName: node linkType: hard +"brorand@npm:^1.1.0": + version: 1.1.0 + resolution: "brorand@npm:1.1.0" + checksum: 8a05c9f3c4b46572dec6ef71012b1946db6cae8c7bb60ccd4b7dd5a84655db49fe043ecc6272e7ef1f69dc53d6730b9e2a3a03a8310509a3d797a618cbee52be + languageName: node + linkType: hard + +"browserify-aes@npm:^1.2.0": + version: 1.2.0 + resolution: "browserify-aes@npm:1.2.0" + dependencies: + buffer-xor: ^1.0.3 + cipher-base: ^1.0.0 + create-hash: ^1.1.0 + evp_bytestokey: ^1.0.3 + inherits: ^2.0.1 + safe-buffer: ^5.0.1 + checksum: 4a17c3eb55a2aa61c934c286f34921933086bf6d67f02d4adb09fcc6f2fc93977b47d9d884c25619144fccd47b3b3a399e1ad8b3ff5a346be47270114bcf7104 + languageName: node + linkType: hard + "browserslist@npm:^4.20.2": version: 4.21.3 resolution: "browserslist@npm:4.21.3" @@ -1815,6 +2100,26 @@ __metadata: languageName: node linkType: hard +"bs58@npm:^4.0.0": + version: 4.0.1 + resolution: "bs58@npm:4.0.1" + dependencies: + base-x: ^3.0.2 + checksum: b3c5365bb9e0c561e1a82f1a2d809a1a692059fae016be233a6127ad2f50a6b986467c3a50669ce4c18929dcccb297c5909314dd347a25a68c21b68eb3e95ac2 + languageName: node + linkType: hard + +"bs58check@npm:^2.1.2": + version: 2.1.2 + resolution: "bs58check@npm:2.1.2" + dependencies: + bs58: ^4.0.0 + create-hash: ^1.1.0 + safe-buffer: ^5.1.2 + checksum: 43bdf08a5dd04581b78f040bc4169480e17008da482ffe2a6507327bbc4fc5c28de0501f7faf22901cfe57fbca79cbb202ca529003fedb4cb8dccd265b38e54d + languageName: node + linkType: hard + "bser@npm:2.1.1": version: 2.1.1 resolution: "bser@npm:2.1.1" @@ -1831,6 +2136,13 @@ __metadata: languageName: node linkType: hard +"buffer-xor@npm:^1.0.3": + version: 1.0.3 + resolution: "buffer-xor@npm:1.0.3" + checksum: 10c520df29d62fa6e785e2800e586a20fc4f6dfad84bcdbd12e1e8a83856de1cb75c7ebd7abe6d036bbfab738a6cf18a3ae9c8e5a2e2eb3167ca7399ce65373a + languageName: node + linkType: hard + "cacache@npm:^16.1.0": version: 16.1.1 resolution: "cacache@npm:16.1.1" @@ -1956,6 +2268,16 @@ __metadata: languageName: node linkType: hard +"cipher-base@npm:^1.0.0, cipher-base@npm:^1.0.1, cipher-base@npm:^1.0.3": + version: 1.0.4 + resolution: "cipher-base@npm:1.0.4" + dependencies: + inherits: ^2.0.1 + safe-buffer: ^5.0.1 + checksum: 47d3568dbc17431a339bad1fe7dff83ac0891be8206911ace3d3b818fc695f376df809bea406e759cdea07fff4b454fa25f1013e648851bec790c1d75763032e + languageName: node + linkType: hard + "cjs-module-lexer@npm:^1.0.0": version: 1.2.2 resolution: "cjs-module-lexer@npm:1.2.2" @@ -2107,6 +2429,42 @@ __metadata: languageName: node linkType: hard +"crc-32@npm:^1.2.0": + version: 1.2.2 + resolution: "crc-32@npm:1.2.2" + bin: + crc32: bin/crc32.njs + checksum: ad2d0ad0cbd465b75dcaeeff0600f8195b686816ab5f3ba4c6e052a07f728c3e70df2e3ca9fd3d4484dc4ba70586e161ca5a2334ec8bf5a41bf022a6103ff243 + languageName: node + linkType: hard + +"create-hash@npm:^1.1.0, create-hash@npm:^1.1.2, create-hash@npm:^1.2.0": + version: 1.2.0 + resolution: "create-hash@npm:1.2.0" + dependencies: + cipher-base: ^1.0.1 + inherits: ^2.0.1 + md5.js: ^1.3.4 + ripemd160: ^2.0.1 + sha.js: ^2.4.0 + checksum: 02a6ae3bb9cd4afee3fabd846c1d8426a0e6b495560a977ba46120c473cb283be6aa1cace76b5f927cf4e499c6146fb798253e48e83d522feba807d6b722eaa9 + languageName: node + linkType: hard + +"create-hmac@npm:^1.1.4, create-hmac@npm:^1.1.7": + version: 1.1.7 + resolution: "create-hmac@npm:1.1.7" + dependencies: + cipher-base: ^1.0.3 + create-hash: ^1.1.0 + inherits: ^2.0.1 + ripemd160: ^2.0.0 + safe-buffer: ^5.0.1 + sha.js: ^2.4.8 + checksum: ba12bb2257b585a0396108c72830e85f882ab659c3320c83584b1037f8ab72415095167ced80dc4ce8e446a8ecc4b2acf36d87befe0707d73b26cf9dc77440ed + languageName: node + linkType: hard + "create-require@npm:^1.1.0": version: 1.1.1 resolution: "create-require@npm:1.1.1" @@ -2309,6 +2667,21 @@ __metadata: languageName: node linkType: hard +"elliptic@npm:^6.5.4": + version: 6.5.4 + resolution: "elliptic@npm:6.5.4" + dependencies: + bn.js: ^4.11.9 + brorand: ^1.1.0 + hash.js: ^1.0.0 + hmac-drbg: ^1.0.1 + inherits: ^2.0.4 + minimalistic-assert: ^1.0.1 + minimalistic-crypto-utils: ^1.0.1 + checksum: d56d21fd04e97869f7ffcc92e18903b9f67f2d4637a23c860492fbbff5a3155fd9ca0184ce0c865dd6eb2487d234ce9551335c021c376cd2d3b7cb749c7d10f4 + languageName: node + linkType: hard + "emittery@npm:^0.10.2": version: 0.10.2 resolution: "emittery@npm:0.10.2" @@ -2744,6 +3117,94 @@ __metadata: languageName: node linkType: hard +"eth-ens-namehash@npm:^2.0.8": + version: 2.0.8 + resolution: "eth-ens-namehash@npm:2.0.8" + dependencies: + idna-uts46-hx: ^2.3.1 + js-sha3: ^0.5.7 + checksum: 40ce4aeedaa4e7eb4485c8d8857457ecc46a4652396981d21b7e3a5f922d5beff63c71cb4b283c935293e530eba50b329d9248be3c433949c6bc40c850c202a3 + languageName: node + linkType: hard + +"eth-rpc-errors@npm:^4.0.2": + version: 4.0.3 + resolution: "eth-rpc-errors@npm:4.0.3" + dependencies: + fast-safe-stringify: ^2.0.6 + checksum: 5fa31d1a10fdb340733b9a55e38e7687222c501052ca20743cef4d0c911a9bbcc0cad54aa6bf3e4b428604c071ff519803060e1cbc79ddb7c9257c11d407d32a + languageName: node + linkType: hard + +"ethereum-cryptography@npm:^0.1.3": + version: 0.1.3 + resolution: "ethereum-cryptography@npm:0.1.3" + dependencies: + "@types/pbkdf2": ^3.0.0 + "@types/secp256k1": ^4.0.1 + blakejs: ^1.1.0 + browserify-aes: ^1.2.0 + bs58check: ^2.1.2 + create-hash: ^1.2.0 + create-hmac: ^1.1.7 + hash.js: ^1.1.7 + keccak: ^3.0.0 + pbkdf2: ^3.0.17 + randombytes: ^2.1.0 + safe-buffer: ^5.1.2 + scrypt-js: ^3.0.0 + secp256k1: ^4.0.1 + setimmediate: ^1.0.5 + checksum: 54bae7a4a96bd81398cdc35c91cfcc74339f71a95ed1b5b694663782e69e8e3afd21357de3b8bac9ff4877fd6f043601e200a7ad9133d94be6fd7d898ee0a449 + languageName: node + linkType: hard + +"ethereum-cryptography@npm:^2.0.0": + version: 2.0.0 + resolution: "ethereum-cryptography@npm:2.0.0" + dependencies: + "@noble/curves": 1.0.0 + "@noble/hashes": 1.3.0 + "@scure/bip32": 1.3.0 + "@scure/bip39": 1.2.0 + checksum: 958f8aab2d1b32aa759fb27a27877b3647410e8bb9aca7d65d1d477db4864cf7fc46b918eb52a1e246c25e98ee0a35a632c88b496aeaefa13469ee767a76c8db + languageName: node + linkType: hard + +"ethereumjs-util@npm:^7.0.10": + version: 7.1.5 + resolution: "ethereumjs-util@npm:7.1.5" + dependencies: + "@types/bn.js": ^5.1.0 + bn.js: ^5.1.2 + create-hash: ^1.1.2 + ethereum-cryptography: ^0.1.3 + rlp: ^2.2.4 + checksum: 27a3c79d6e06b2df34b80d478ce465b371c8458b58f5afc14d91c8564c13363ad336e6e83f57eb0bd719fde94d10ee5697ceef78b5aa932087150c5287b286d1 + languageName: node + linkType: hard + +"ethjs-unit@npm:^0.1.6": + version: 0.1.6 + resolution: "ethjs-unit@npm:0.1.6" + dependencies: + bn.js: 4.11.6 + number-to-bn: 1.7.0 + checksum: df6b4752ff7461a59a20219f4b1684c631ea601241c39660e3f6c6bd63c950189723841c22b3c6c0ebeb3c9fc99e0e803e3c613101206132603705fcbcf4def5 + languageName: node + linkType: hard + +"evp_bytestokey@npm:^1.0.3": + version: 1.0.3 + resolution: "evp_bytestokey@npm:1.0.3" + dependencies: + md5.js: ^1.3.4 + node-gyp: latest + safe-buffer: ^5.1.1 + checksum: ad4e1577f1a6b721c7800dcc7c733fe01f6c310732bb5bf2240245c2a5b45a38518b91d8be2c610611623160b9d1c0e91f1ce96d639f8b53e8894625cf20fa45 + languageName: node + linkType: hard + "execa@npm:^5.0.0, execa@npm:^5.1.1": version: 5.1.1 resolution: "execa@npm:5.1.1" @@ -2822,6 +3283,13 @@ __metadata: languageName: node linkType: hard +"fast-safe-stringify@npm:^2.0.6": + version: 2.1.1 + resolution: "fast-safe-stringify@npm:2.1.1" + checksum: a851cbddc451745662f8f00ddb622d6766f9bd97642dabfd9a405fb0d646d69fc0b9a1243cbf67f5f18a39f40f6fa821737651ff1bceeba06c9992ca2dc5bd3d + languageName: node + linkType: hard + "fastq@npm:^1.6.0": version: 1.11.0 resolution: "fastq@npm:1.11.0" @@ -3195,6 +3663,38 @@ __metadata: languageName: node linkType: hard +"hash-base@npm:^3.0.0": + version: 3.1.0 + resolution: "hash-base@npm:3.1.0" + dependencies: + inherits: ^2.0.4 + readable-stream: ^3.6.0 + safe-buffer: ^5.2.0 + checksum: 26b7e97ac3de13cb23fc3145e7e3450b0530274a9562144fc2bf5c1e2983afd0e09ed7cc3b20974ba66039fad316db463da80eb452e7373e780cbee9a0d2f2dc + languageName: node + linkType: hard + +"hash.js@npm:^1.0.0, hash.js@npm:^1.0.3, hash.js@npm:^1.1.7": + version: 1.1.7 + resolution: "hash.js@npm:1.1.7" + dependencies: + inherits: ^2.0.3 + minimalistic-assert: ^1.0.1 + checksum: e350096e659c62422b85fa508e4b3669017311aa4c49b74f19f8e1bc7f3a54a584fdfd45326d4964d6011f2b2d882e38bea775a96046f2a61b7779a979629d8f + languageName: node + linkType: hard + +"hmac-drbg@npm:^1.0.1": + version: 1.0.1 + resolution: "hmac-drbg@npm:1.0.1" + dependencies: + hash.js: ^1.0.3 + minimalistic-assert: ^1.0.0 + minimalistic-crypto-utils: ^1.0.1 + checksum: bd30b6a68d7f22d63f10e1888aee497d7c2c5c0bb469e66bbdac99f143904d1dfe95f8131f95b3e86c86dd239963c9d972fcbe147e7cffa00e55d18585c43fe0 + languageName: node + linkType: hard + "html-escaper@npm:^2.0.0": version: 2.0.2 resolution: "html-escaper@npm:2.0.2" @@ -3255,6 +3755,15 @@ __metadata: languageName: node linkType: hard +"idna-uts46-hx@npm:^2.3.1": + version: 2.3.1 + resolution: "idna-uts46-hx@npm:2.3.1" + dependencies: + punycode: 2.1.0 + checksum: d434c3558d2bc1090eb90f978f995101f469cb26593414ac57aa082c9352e49972b332c6e4188b9b15538172ccfeae3121e5a19b96972a97e6aeb0676d86639c + languageName: node + linkType: hard + "ignore@npm:^5.1.1, ignore@npm:^5.1.8, ignore@npm:^5.2.0": version: 5.2.1 resolution: "ignore@npm:5.2.1" @@ -3262,6 +3771,13 @@ __metadata: languageName: node linkType: hard +"immer@npm:^9.0.6": + version: 9.0.21 + resolution: "immer@npm:9.0.21" + checksum: 70e3c274165995352f6936695f0ef4723c52c92c92dd0e9afdfe008175af39fa28e76aafb3a2ca9d57d1fb8f796efc4dd1e1cc36f18d33fa5b74f3dfb0375432 + languageName: node + linkType: hard + "immutable@npm:^4.0.0": version: 4.1.0 resolution: "immutable@npm:4.1.0" @@ -3322,7 +3838,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:^2.0.3": +"inherits@npm:2, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 4a48a733847879d6cf6691860a6b1e3f0f4754176e4d71494c41f3475553768b10f84b5ce1d40fbd0e34e6bfbb864ee35858ad4dd2cf31e02fc4a154b724d7f1 @@ -3441,6 +3957,13 @@ __metadata: languageName: node linkType: hard +"is-hex-prefixed@npm:1.0.0": + version: 1.0.0 + resolution: "is-hex-prefixed@npm:1.0.0" + checksum: 5ac58e6e528fb029cc43140f6eeb380fad23d0041cc23154b87f7c9a1b728bcf05909974e47248fd0b7fcc11ba33cf7e58d64804883056fabd23e2b898be41de + languageName: node + linkType: hard + "is-lambda@npm:^1.0.1": version: 1.0.1 resolution: "is-lambda@npm:1.0.1" @@ -4054,6 +4577,13 @@ __metadata: languageName: node linkType: hard +"js-sha3@npm:^0.5.7": + version: 0.5.7 + resolution: "js-sha3@npm:0.5.7" + checksum: 973a28ea4b26cc7f12d2ab24f796e24ee4a71eef45a6634a052f6eb38cf8b2333db798e896e6e094ea6fa4dfe8e42a2a7942b425cf40da3f866623fd05bb91ea + languageName: node + linkType: hard + "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -4155,6 +4685,18 @@ __metadata: languageName: node linkType: hard +"keccak@npm:^3.0.0": + version: 3.0.3 + resolution: "keccak@npm:3.0.3" + dependencies: + node-addon-api: ^2.0.0 + node-gyp: latest + node-gyp-build: ^4.2.0 + readable-stream: ^3.6.0 + checksum: f08f04f5cc87013a3fc9e87262f761daff38945c86dd09c01a7f7930a15ae3e14f93b310ef821dcc83675a7b814eb1c983222399a2f263ad980251201d1b9a99 + languageName: node + linkType: hard + "kleur@npm:^3.0.3": version: 3.0.3 resolution: "kleur@npm:3.0.3" @@ -4326,6 +4868,17 @@ __metadata: languageName: node linkType: hard +"md5.js@npm:^1.3.4": + version: 1.3.5 + resolution: "md5.js@npm:1.3.5" + dependencies: + hash-base: ^3.0.0 + inherits: ^2.0.1 + safe-buffer: ^5.1.2 + checksum: 098494d885684bcc4f92294b18ba61b7bd353c23147fbc4688c75b45cb8590f5a95fd4584d742415dcc52487f7a1ef6ea611cfa1543b0dc4492fe026357f3f0c + languageName: node + linkType: hard + "merge-stream@npm:^2.0.0": version: 2.0.0 resolution: "merge-stream@npm:2.0.0" @@ -4340,6 +4893,13 @@ __metadata: languageName: node linkType: hard +"micro-ftch@npm:^0.3.1": + version: 0.3.1 + resolution: "micro-ftch@npm:0.3.1" + checksum: 0e496547253a36e98a83fb00c628c53c3fb540fa5aaeaf718438873785afd193244988c09d219bb1802984ff227d04938d9571ef90fe82b48bd282262586aaff + languageName: node + linkType: hard + "micromatch@npm:^4.0.4": version: 4.0.4 resolution: "micromatch@npm:4.0.4" @@ -4357,6 +4917,20 @@ __metadata: languageName: node linkType: hard +"minimalistic-assert@npm:^1.0.0, minimalistic-assert@npm:^1.0.1": + version: 1.0.1 + resolution: "minimalistic-assert@npm:1.0.1" + checksum: cc7974a9268fbf130fb055aff76700d7e2d8be5f761fb5c60318d0ed010d839ab3661a533ad29a5d37653133385204c503bfac995aaa4236f4e847461ea32ba7 + languageName: node + linkType: hard + +"minimalistic-crypto-utils@npm:^1.0.1": + version: 1.0.1 + resolution: "minimalistic-crypto-utils@npm:1.0.1" + checksum: 6e8a0422b30039406efd4c440829ea8f988845db02a3299f372fceba56ffa94994a9c0f2fd70c17f9969eedfbd72f34b5070ead9656a34d3f71c0bd72583a0ed + languageName: node + linkType: hard + "minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -4532,6 +5106,26 @@ __metadata: languageName: node linkType: hard +"node-addon-api@npm:^2.0.0": + version: 2.0.2 + resolution: "node-addon-api@npm:2.0.2" + dependencies: + node-gyp: latest + checksum: 31fb22d674648204f8dd94167eb5aac896c841b84a9210d614bf5d97c74ef059cc6326389cf0c54d2086e35312938401d4cc82e5fcd679202503eb8ac84814f8 + languageName: node + linkType: hard + +"node-gyp-build@npm:^4.2.0": + version: 4.6.0 + resolution: "node-gyp-build@npm:4.6.0" + bin: + node-gyp-build: bin.js + node-gyp-build-optional: optional.js + node-gyp-build-test: build-test.js + checksum: 25d78c5ef1f8c24291f4a370c47ba52fcea14f39272041a90a7894cd50d766f7c8cb8fb06c0f42bf6f69b204b49d9be3c8fc344aac09714d5bdb95965499eb15 + languageName: node + linkType: hard + "node-gyp@npm:^9.0.0": version: 9.3.1 resolution: "node-gyp@npm:9.3.1" @@ -4643,6 +5237,16 @@ __metadata: languageName: node linkType: hard +"number-to-bn@npm:1.7.0": + version: 1.7.0 + resolution: "number-to-bn@npm:1.7.0" + dependencies: + bn.js: 4.11.6 + strip-hex-prefix: 1.0.0 + checksum: 5b8c9dbe7b49dc7a069e5f0ba4e197257c89db11463478cb002fee7a34dc8868636952bd9f6310e5fdf22b266e0e6dffb5f9537c741734718107e90ae59b3de4 + languageName: node + linkType: hard + "object-inspect@npm:^1.12.0, object-inspect@npm:^1.9.0": version: 1.12.2 resolution: "object-inspect@npm:1.12.2" @@ -4820,6 +5424,19 @@ __metadata: languageName: node linkType: hard +"pbkdf2@npm:^3.0.17": + version: 3.1.2 + resolution: "pbkdf2@npm:3.1.2" + dependencies: + create-hash: ^1.1.2 + create-hmac: ^1.1.4 + ripemd160: ^2.0.1 + safe-buffer: ^5.0.1 + sha.js: ^2.4.8 + checksum: 2c950a100b1da72123449208e231afc188d980177d021d7121e96a2de7f2abbc96ead2b87d03d8fe5c318face097f203270d7e27908af9f471c165a4e8e69c92 + languageName: node + linkType: hard + "picocolors@npm:^1.0.0": version: 1.0.0 resolution: "picocolors@npm:1.0.0" @@ -4948,6 +5565,13 @@ __metadata: languageName: node linkType: hard +"punycode@npm:2.1.0": + version: 2.1.0 + resolution: "punycode@npm:2.1.0" + checksum: d125d8f86cd89303c33bad829388c49ca23197e16ccf8cd398dcbd81b026978f6543f5066c66825b25b1dfea7790a42edbeea82908e103474931789714ab86cd + languageName: node + linkType: hard + "punycode@npm:^2.1.0": version: 2.1.1 resolution: "punycode@npm:2.1.1" @@ -4972,6 +5596,15 @@ __metadata: languageName: node linkType: hard +"randombytes@npm:^2.1.0": + version: 2.1.0 + resolution: "randombytes@npm:2.1.0" + dependencies: + safe-buffer: ^5.1.0 + checksum: d779499376bd4cbb435ef3ab9a957006c8682f343f14089ed5f27764e4645114196e75b7f6abf1cbd84fd247c0cb0651698444df8c9bf30e62120fbbc52269d6 + languageName: node + linkType: hard + "react-is@npm:^18.0.0": version: 18.2.0 resolution: "react-is@npm:18.2.0" @@ -5129,6 +5762,27 @@ __metadata: languageName: node linkType: hard +"ripemd160@npm:^2.0.0, ripemd160@npm:^2.0.1": + version: 2.0.2 + resolution: "ripemd160@npm:2.0.2" + dependencies: + hash-base: ^3.0.0 + inherits: ^2.0.1 + checksum: 006accc40578ee2beae382757c4ce2908a826b27e2b079efdcd2959ee544ddf210b7b5d7d5e80467807604244e7388427330f5c6d4cd61e6edaddc5773ccc393 + languageName: node + linkType: hard + +"rlp@npm:^2.2.4": + version: 2.2.7 + resolution: "rlp@npm:2.2.7" + dependencies: + bn.js: ^5.2.0 + bin: + rlp: bin/rlp + checksum: 3db4dfe5c793f40ac7e0be689a1f75d05e6f2ca0c66189aeb62adab8c436b857ab4420a419251ee60370d41d957a55698fc5e23ab1e1b41715f33217bc4bb558 + languageName: node + linkType: hard + "run-async@npm:^2.3.0": version: 2.4.1 resolution: "run-async@npm:2.4.1" @@ -5145,6 +5799,13 @@ __metadata: languageName: node linkType: hard +"safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:~5.2.0": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: b99c4b41fdd67a6aaf280fcd05e9ffb0813654894223afb78a31f14a19ad220bba8aba1cb14eddce1fcfb037155fe6de4e861784eb434f7d11ed58d1e70dd491 + languageName: node + linkType: hard + "safe-buffer@npm:~5.1.1": version: 5.1.2 resolution: "safe-buffer@npm:5.1.2" @@ -5152,13 +5813,6 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:~5.2.0": - version: 5.2.1 - resolution: "safe-buffer@npm:5.2.1" - checksum: b99c4b41fdd67a6aaf280fcd05e9ffb0813654894223afb78a31f14a19ad220bba8aba1cb14eddce1fcfb037155fe6de4e861784eb434f7d11ed58d1e70dd491 - languageName: node - linkType: hard - "safer-buffer@npm:>= 2.1.2 < 3.0.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -5179,6 +5833,13 @@ __metadata: languageName: node linkType: hard +"scrypt-js@npm:^3.0.0": + version: 3.0.1 + resolution: "scrypt-js@npm:3.0.1" + checksum: b7c7d1a68d6ca946f2fbb0778e0c4ec63c65501b54023b2af7d7e9f48fdb6c6580d6f7675cd53bda5944c5ebc057560d5a6365079752546865defb3b79dea454 + languageName: node + linkType: hard + "scss-parser@npm:^1.0.4": version: 1.0.6 resolution: "scss-parser@npm:1.0.6" @@ -5189,6 +5850,18 @@ __metadata: languageName: node linkType: hard +"secp256k1@npm:^4.0.1": + version: 4.0.3 + resolution: "secp256k1@npm:4.0.3" + dependencies: + elliptic: ^6.5.4 + node-addon-api: ^2.0.0 + node-gyp: latest + node-gyp-build: ^4.2.0 + checksum: 21e219adc0024fbd75021001358780a3cc6ac21273c3fcaef46943af73969729709b03f1df7c012a0baab0830fb9a06ccc6b42f8d50050c665cb98078eab477b + languageName: node + linkType: hard + "semver-compare@npm:^1.0.0": version: 1.0.0 resolution: "semver-compare@npm:1.0.0" @@ -5223,6 +5896,25 @@ __metadata: languageName: node linkType: hard +"setimmediate@npm:^1.0.5": + version: 1.0.5 + resolution: "setimmediate@npm:1.0.5" + checksum: c9a6f2c5b51a2dabdc0247db9c46460152ffc62ee139f3157440bd48e7c59425093f42719ac1d7931f054f153e2d26cf37dfeb8da17a794a58198a2705e527fd + languageName: node + linkType: hard + +"sha.js@npm:^2.4.0, sha.js@npm:^2.4.8": + version: 2.4.11 + resolution: "sha.js@npm:2.4.11" + dependencies: + inherits: ^2.0.1 + safe-buffer: ^5.0.1 + bin: + sha.js: ./bin.js + checksum: ebd3f59d4b799000699097dadb831c8e3da3eb579144fd7eb7a19484cbcbb7aca3c68ba2bb362242eb09e33217de3b4ea56e4678184c334323eca24a58e3ad07 + languageName: node + linkType: hard + "shebang-command@npm:^2.0.0": version: 2.0.0 resolution: "shebang-command@npm:2.0.0" @@ -5502,6 +6194,15 @@ __metadata: languageName: node linkType: hard +"strip-hex-prefix@npm:1.0.0": + version: 1.0.0 + resolution: "strip-hex-prefix@npm:1.0.0" + dependencies: + is-hex-prefixed: 1.0.0 + checksum: 4cafe7caee1d281d3694d14920fd5d3c11adf09371cef7e2ccedd5b83efd9e9bd2219b5d6ce6e809df6e0f437dc9d30db1192116580875698aad164a6d6b285b + languageName: node + linkType: hard + "strip-json-comments@npm:^3.1.0, strip-json-comments@npm:^3.1.1": version: 3.1.1 resolution: "strip-json-comments@npm:3.1.1" @@ -5509,6 +6210,13 @@ __metadata: languageName: node linkType: hard +"superstruct@npm:^1.0.3": + version: 1.0.3 + resolution: "superstruct@npm:1.0.3" + checksum: 761790bb111e6e21ddd608299c252f3be35df543263a7ebbc004e840d01fcf8046794c274bcb351bdf3eae4600f79d317d085cdbb19ca05803a4361840cc9bb1 + languageName: node + linkType: hard + "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0"