diff --git a/packages/examples/packages/name-lookup/src/index.test.ts b/packages/examples/packages/name-lookup/src/index.test.ts index dbd671defc..e09a4a87b7 100644 --- a/packages/examples/packages/name-lookup/src/index.test.ts +++ b/packages/examples/packages/name-lookup/src/index.test.ts @@ -1,8 +1,7 @@ -import { describe, it } from '@jest/globals'; +import { describe, expect, it } from '@jest/globals'; +import { installSnap } from '@metamask/snaps-jest'; import type { ChainId } from '@metamask/snaps-sdk'; -import { onNameLookup } from '.'; - const DOMAIN_MOCK = 'test.domain'; const ADDRESS_MOCK = '0xc0ffee254729296a45a3885639AC7E10F9d54979'; const CHAIN_ID_MOCK = 'eip155:1' as ChainId; @@ -14,7 +13,10 @@ describe('onNameLookup', () => { chainId: CHAIN_ID_MOCK, }; - expect(await onNameLookup(request)).toStrictEqual({ + const { onNameLookup } = await installSnap(); + const response = await onNameLookup(request); + + expect(response).toRespondWith({ resolvedAddresses: [ { resolvedAddress: '0xc0ffee254729296a45a3885639AC7E10F9d54979', @@ -31,7 +33,9 @@ describe('onNameLookup', () => { chainId: CHAIN_ID_MOCK, }; - expect(await onNameLookup(request)).toStrictEqual({ + const { onNameLookup } = await installSnap(); + + expect(await onNameLookup(request)).toRespondWith({ resolvedDomains: [ { resolvedDomain: 'c0f.1.test.domain', protocol: 'test protocol' }, ], @@ -45,7 +49,9 @@ describe('onNameLookup', () => { chainId: CHAIN_ID_MOCK, } as any; - expect(await onNameLookup(request)).toStrictEqual({ + const { onNameLookup } = await installSnap(); + + expect(await onNameLookup(request)).toRespondWith({ resolvedDomains: [ { resolvedDomain: 'c0f.1.test.domain', protocol: 'test protocol' }, ], @@ -57,7 +63,8 @@ describe('onNameLookup', () => { chainId: CHAIN_ID_MOCK, }; - // @ts-expect-error - Testing invalid request. + const { onNameLookup } = await installSnap(); + expect(await onNameLookup(request)).toBeNull(); }); }); diff --git a/packages/snaps-jest/src/helpers.test.tsx b/packages/snaps-jest/src/helpers.test.tsx index e010e6c016..684490c54a 100644 --- a/packages/snaps-jest/src/helpers.test.tsx +++ b/packages/snaps-jest/src/helpers.test.tsx @@ -675,6 +675,49 @@ describe('installSnap', () => { }); }); + describe('onNameLookup', () => { + it('sends a name lookup request and returns the result', async () => { + jest.spyOn(console, 'log').mockImplementation(); + const MOCK_DOMAIN = 'test.domain'; + + const { snapId, close: closeServer } = await getMockServer({ + sourceCode: ` + module.exports.onNameLookup = async (request) => { + return { + resolvedAddress: '0xc0ffee254729296a45a3885639AC7E10F9d54979', + protocol: 'test protocol', + domainName: request.domain, + }; + }; + `, + }); + + const { onNameLookup, close } = await installSnap(snapId); + const response = await onNameLookup({ + chainId: 'eip155:1', + domain: MOCK_DOMAIN, + }); + + expect(response).toStrictEqual( + expect.objectContaining({ + response: { + result: { + resolvedAddress: '0xc0ffee254729296a45a3885639AC7E10F9d54979', + protocol: 'test protocol', + domainName: MOCK_DOMAIN, + }, + }, + }), + ); + + // `close` is deprecated because the Jest environment will automatically + // close the Snap when the test finishes. However, we still need to close + // the Snap in this test because it's run outside the Jest environment. + await close(); + await closeServer(); + }); + }); + describe('runCronjob', () => { it('runs a cronjob and returns the result', async () => { jest.spyOn(console, 'log').mockImplementation(); diff --git a/packages/snaps-jest/src/helpers.ts b/packages/snaps-jest/src/helpers.ts index fe1e12c060..a4d3d38d33 100644 --- a/packages/snaps-jest/src/helpers.ts +++ b/packages/snaps-jest/src/helpers.ts @@ -181,6 +181,7 @@ export async function installSnap< onKeyringRequest, onInstall, onUpdate, + onNameLookup, mockJsonRpc, close, } = await getEnvironment().installSnap(...resolvedOptions); @@ -196,6 +197,7 @@ export async function installSnap< onKeyringRequest, onInstall, onUpdate, + onNameLookup, mockJsonRpc, close: async () => { log('Closing execution service.'); diff --git a/packages/snaps-simulation/src/helpers.test.tsx b/packages/snaps-simulation/src/helpers.test.tsx index b492af9c94..fde44fe9a0 100644 --- a/packages/snaps-simulation/src/helpers.test.tsx +++ b/packages/snaps-simulation/src/helpers.test.tsx @@ -409,6 +409,49 @@ describe('helpers', () => { }); }); + describe('onNameLookup', () => { + it('sends a name lookup request and returns the result', async () => { + jest.spyOn(console, 'log').mockImplementation(); + const MOCK_DOMAIN = 'test.domain'; + + const { snapId, close: closeServer } = await getMockServer({ + sourceCode: ` + module.exports.onNameLookup = async (request) => { + return { + resolvedAddress: '0xc0ffee254729296a45a3885639AC7E10F9d54979', + protocol: 'test protocol', + domainName: request.domain, + }; + }; + `, + }); + + const { onNameLookup, close } = await installSnap(snapId); + const response = await onNameLookup({ + chainId: 'eip155:1', + domain: MOCK_DOMAIN, + }); + + expect(response).toStrictEqual( + expect.objectContaining({ + response: { + result: { + resolvedAddress: '0xc0ffee254729296a45a3885639AC7E10F9d54979', + protocol: 'test protocol', + domainName: MOCK_DOMAIN, + }, + }, + }), + ); + + // `close` is deprecated because the Jest environment will automatically + // close the Snap when the test finishes. However, we still need to close + // the Snap in this test because it's run outside the Jest environment. + await close(); + await closeServer(); + }); + }); + describe('runCronjob', () => { it('runs a cronjob and returns the result', async () => { jest.spyOn(console, 'log').mockImplementation(); diff --git a/packages/snaps-simulation/src/helpers.ts b/packages/snaps-simulation/src/helpers.ts index 6dfe5ebbcc..b3123b2dd1 100644 --- a/packages/snaps-simulation/src/helpers.ts +++ b/packages/snaps-simulation/src/helpers.ts @@ -10,6 +10,7 @@ import { addJsonRpcMock, removeJsonRpcMock } from './store'; import { assertIsResponseWithInterface, JsonRpcMockOptionsStruct, + NameLookupOptionsStruct, SignatureOptionsStruct, TransactionOptionsStruct, } from './structs'; @@ -17,6 +18,7 @@ import type { CronjobOptions, JsonRpcMockOptions, KeyringOptions, + NameLookupOptions, RequestOptions, SignatureOptions, SnapRequest, @@ -140,6 +142,15 @@ export type SnapHelpers = { */ onUpdate(request?: Pick): SnapRequest; + /** + * Get the response from the Snap's `onNameLookup` handler. + * + * @returns The response. + */ + onNameLookup( + request: NameLookupOptions, + ): Promise; + /** * Mock a JSON-RPC request. This will cause the snap to respond with the * specified response when a request with the specified method is sent. @@ -314,6 +325,34 @@ export function getHelpers({ }); }, + onNameLookup: async ( + nameLookupOptions: NameLookupOptions, + ): Promise => { + log('Requesting name lookup %o.', nameLookupOptions); + + const { ...requestParams } = create( + nameLookupOptions, + NameLookupOptionsStruct, + ); + + const response = await handleRequest({ + snapId, + store, + executionService, + controllerMessenger, + runSaga, + handler: HandlerType.OnNameLookup, + request: { + method: '', + params: { + ...requestParams, + }, + }, + }); + + return response; + }, + onSignature: async ( request: unknown, ): Promise => { diff --git a/packages/snaps-simulation/src/structs.ts b/packages/snaps-simulation/src/structs.ts index a9a51351d3..55db6e0863 100644 --- a/packages/snaps-simulation/src/structs.ts +++ b/packages/snaps-simulation/src/structs.ts @@ -190,6 +190,23 @@ export const SignatureOptionsStruct = object({ ), }); +export const NameLookupOptionsStruct = object({ + /** + * The CAIP-2 chain ID. Defaults to `eip155:1`. + */ + chainId: defaulted(string(), 'eip155:1'), + + /** + * Domain name to lookup. + */ + domain: optional(string()), + + /** + * Address to lookup. + */ + address: optional(string()), +}); + export const SnapOptionsStruct = object({ /** * The timeout in milliseconds to use for requests to the snap. Defaults to diff --git a/packages/snaps-simulation/src/types.ts b/packages/snaps-simulation/src/types.ts index 7707775213..aa3a210316 100644 --- a/packages/snaps-simulation/src/types.ts +++ b/packages/snaps-simulation/src/types.ts @@ -5,6 +5,7 @@ import type { Infer } from '@metamask/superstruct'; import type { Json, JsonRpcId, JsonRpcParams } from '@metamask/utils'; import type { + NameLookupOptionsStruct, SignatureOptionsStruct, SnapOptionsStruct, SnapResponseStruct, @@ -67,6 +68,16 @@ export type TransactionOptions = Infer; */ export type KeyringOptions = RequestOptions; +/** + * The options to use for name lookup requests. + * + * @property origin - The origin of the name lookup request (optional).`. + * @property chainId - Chain ID. + * @property domain - Domain name to lookup and resolve. + * @property address - Address to lookup and resolve. + */ +export type NameLookupOptions = Infer; + /** * The options to use for signature requests. * @@ -434,6 +445,15 @@ export type Snap = { */ onUpdate(request?: Pick): SnapRequest; + /** + * Get the response from the Snap's `onNameLookup` handler. + * + * @returns The response. + */ + onNameLookup( + nameLookupRequest: NameLookupOptions, + ): Promise; + /** * Mock a JSON-RPC request. This will cause the snap to respond with the * specified response when a request with the specified method is sent.