From 0e96a62c48ebc7d8490fb0292deec83d2d11c6d5 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Tue, 18 Feb 2025 17:50:49 -0500 Subject: [PATCH 01/12] add address input component --- .../jsx/components/form/AddressInput.test.tsx | 16 +++++++++ .../src/jsx/components/form/AddressInput.ts | 28 +++++++++++++++ .../src/jsx/components/form/Field.test.tsx | 25 +++++++++++++ .../src/jsx/components/form/Field.ts | 4 ++- .../src/jsx/components/form/index.ts | 3 ++ .../snaps-sdk/src/jsx/validation.test.tsx | 35 +++++++++++++++++++ packages/snaps-sdk/src/jsx/validation.ts | 19 +++++++++- 7 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 packages/snaps-sdk/src/jsx/components/form/AddressInput.test.tsx create mode 100644 packages/snaps-sdk/src/jsx/components/form/AddressInput.ts diff --git a/packages/snaps-sdk/src/jsx/components/form/AddressInput.test.tsx b/packages/snaps-sdk/src/jsx/components/form/AddressInput.test.tsx new file mode 100644 index 0000000000..2fcc03b39f --- /dev/null +++ b/packages/snaps-sdk/src/jsx/components/form/AddressInput.test.tsx @@ -0,0 +1,16 @@ +import { AddressInput } from './AddressInput'; + +describe('AddressInput', () => { + it('renders an address input', () => { + const result = ; + + expect(result).toStrictEqual({ + type: 'AddressInput', + props: { + name: 'address', + chainId: 'eip155:1', + }, + key: null, + }); + }); +}); diff --git a/packages/snaps-sdk/src/jsx/components/form/AddressInput.ts b/packages/snaps-sdk/src/jsx/components/form/AddressInput.ts new file mode 100644 index 0000000000..8318ab5644 --- /dev/null +++ b/packages/snaps-sdk/src/jsx/components/form/AddressInput.ts @@ -0,0 +1,28 @@ +import type { CaipChainId } from '@metamask/utils'; + +import { createSnapComponent } from '../../component'; + +export type AddressInputProps = { + name: string; + value?: string | undefined; + chainId: CaipChainId; +}; + +const TYPE = 'AddressInput'; + +/** + * An input component for entering an address. Resolves the address to a display name and avatar. + * + * @param props - The props of the component. + * @param props.name - The name of the input field. + * @param props.value - The value of the input field. + * @param props.chainId - The CAIP-2 chain ID of the address. + * @returns An input element. + * @example + * + */ +export const AddressInput = createSnapComponent( + TYPE, +); + +export type AddressInputElement = ReturnType; diff --git a/packages/snaps-sdk/src/jsx/components/form/Field.test.tsx b/packages/snaps-sdk/src/jsx/components/form/Field.test.tsx index 1daf74e1f7..e0918de112 100644 --- a/packages/snaps-sdk/src/jsx/components/form/Field.test.tsx +++ b/packages/snaps-sdk/src/jsx/components/form/Field.test.tsx @@ -1,3 +1,4 @@ +import { AddressInput } from './AddressInput'; import { Button } from './Button'; import { Dropdown } from './Dropdown'; import { Field } from './Field'; @@ -323,6 +324,30 @@ describe('Field', () => { }); }); + it('renders a field element with an address input', () => { + const result = ( + + + + ); + + expect(result).toStrictEqual({ + type: 'Field', + key: null, + props: { + label: 'Label', + children: { + type: 'AddressInput', + key: null, + props: { + name: 'address', + chainId: 'eip155:1', + }, + }, + }, + }); + }); + it('renders a field with a conditional', () => { const result = ( diff --git a/packages/snaps-sdk/src/jsx/components/form/Field.ts b/packages/snaps-sdk/src/jsx/components/form/Field.ts index 27b6f8286d..2cce3bdd47 100644 --- a/packages/snaps-sdk/src/jsx/components/form/Field.ts +++ b/packages/snaps-sdk/src/jsx/components/form/Field.ts @@ -1,3 +1,4 @@ +import type { AddressInputElement } from './AddressInput'; import type { AssetSelectorElement } from './AssetSelector'; import type { CheckboxElement } from './Checkbox'; import type { DropdownElement } from './Dropdown'; @@ -28,7 +29,8 @@ export type FieldProps = { | InputElement | CheckboxElement | SelectorElement - | AssetSelectorElement; + | AssetSelectorElement + | AddressInputElement; }; const TYPE = 'Field'; diff --git a/packages/snaps-sdk/src/jsx/components/form/index.ts b/packages/snaps-sdk/src/jsx/components/form/index.ts index 7512d750f6..245fa13227 100644 --- a/packages/snaps-sdk/src/jsx/components/form/index.ts +++ b/packages/snaps-sdk/src/jsx/components/form/index.ts @@ -1,3 +1,4 @@ +import type { AddressInputElement } from './AddressInput'; import type { AssetSelectorElement } from './AssetSelector'; import type { ButtonElement } from './Button'; import type { CheckboxElement } from './Checkbox'; @@ -25,9 +26,11 @@ export * from './Form'; export * from './Input'; export * from './Selector'; export * from './SelectorOption'; +export * from './AddressInput'; export type StandardFormElement = | AssetSelectorElement + | AddressInputElement | ButtonElement | CheckboxElement | FormElement diff --git a/packages/snaps-sdk/src/jsx/validation.test.tsx b/packages/snaps-sdk/src/jsx/validation.test.tsx index 132bdeb48e..8e01601df6 100644 --- a/packages/snaps-sdk/src/jsx/validation.test.tsx +++ b/packages/snaps-sdk/src/jsx/validation.test.tsx @@ -36,6 +36,7 @@ import { Banner, Skeleton, AssetSelector, + AddressInput, } from './components'; import { AddressStruct, @@ -76,6 +77,7 @@ import { BannerStruct, SkeletonStruct, AssetSelectorStruct, + AddressInputStruct, } from './validation'; describe('KeyStruct', () => { @@ -209,6 +211,36 @@ describe('ButtonStruct', () => { }); }); +describe('AddressInputStruct', () => { + it.each([ + , + , + ])('validates an address input element', (value) => { + expect(is(value, AddressInputStruct)).toBe(true); + }); + + it.each([ + 'foo', + 42, + null, + undefined, + {}, + [], + // @ts-expect-error - Invalid props. + , + // @ts-expect-error - Invalid props. + , + // @ts-expect-error - Invalid props. + , + ])('does not validate "%p"', (value) => { + expect(is(value, AddressInputStruct)).toBe(false); + }); +}); + describe('InputStruct', () => { it.each([ , @@ -322,6 +354,9 @@ describe('FieldStruct', () => { ]} /> , + + + , ])('validates a field element', (value) => { expect(is(value, FieldStruct)).toBe(true); }); diff --git a/packages/snaps-sdk/src/jsx/validation.ts b/packages/snaps-sdk/src/jsx/validation.ts index ccb1108bb8..5788ff25b0 100644 --- a/packages/snaps-sdk/src/jsx/validation.ts +++ b/packages/snaps-sdk/src/jsx/validation.ts @@ -21,8 +21,10 @@ import { assign, union, } from '@metamask/superstruct'; +import type { CaipChainId } from '@metamask/utils'; import { CaipAccountIdStruct, + CaipChainIdStruct, hasProperty, HexChecksumAddressStruct, isPlainObject, @@ -40,6 +42,7 @@ import type { StringElement, } from './component'; import type { + AddressInputElement, AssetSelectorElement, AvatarElement, SkeletonElement, @@ -345,6 +348,15 @@ export const InputStruct: Describe = elementWithSelectiveProps( }, ); +export const AddressInputStruct: Describe = element( + 'AddressInput', + { + name: string(), + chainId: CaipChainIdStruct as unknown as Struct, + value: optional(string()), + }, +); + /** * A struct for the {@link OptionElement} type. */ @@ -499,6 +511,7 @@ const BOX_INPUT_BOTH = [ */ const FIELD_CHILDREN_ARRAY = [ AssetSelectorStruct, + AddressInputStruct, InputStruct, DropdownStruct, RadioGroupStruct, @@ -507,6 +520,7 @@ const FIELD_CHILDREN_ARRAY = [ SelectorStruct, ] as [ typeof AssetSelectorStruct, + typeof AddressInputStruct, typeof InputStruct, typeof DropdownStruct, typeof RadioGroupStruct, @@ -552,7 +566,8 @@ const FieldChildStruct = selectiveUnion((value) => { | InputElement | CheckboxElement | SelectorElement - | AssetSelectorElement, + | AssetSelectorElement + | AddressInputElement, null >; @@ -900,6 +915,7 @@ export const SpinnerStruct: Describe = element('Spinner'); export const BoxChildStruct = typedUnion([ AddressStruct, AssetSelectorStruct, + AddressInputStruct, BoldStruct, BoxStruct, ButtonStruct, @@ -964,6 +980,7 @@ export const RootJSXElementStruct = typedUnion([ */ export const JSXElementStruct: Describe = typedUnion([ AssetSelectorStruct, + AddressInputStruct, ButtonStruct, InputStruct, FileInputStruct, From 0388d82376052bbdfc69fe8409e9582a8dfcb3d3 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Tue, 18 Feb 2025 18:16:25 -0500 Subject: [PATCH 02/12] fix tests --- .../snaps-rpc-methods/src/permitted/createInterface.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/snaps-rpc-methods/src/permitted/createInterface.test.tsx b/packages/snaps-rpc-methods/src/permitted/createInterface.test.tsx index b39283fc89..a0e3921c49 100644 --- a/packages/snaps-rpc-methods/src/permitted/createInterface.test.tsx +++ b/packages/snaps-rpc-methods/src/permitted/createInterface.test.tsx @@ -141,7 +141,7 @@ describe('snap_createInterface', () => { error: { code: -32602, message: - 'Invalid params: At path: ui -- Expected type to be one of: "Address", "AssetSelector", "Bold", "Box", "Button", "Copyable", "Divider", "Dropdown", "RadioGroup", "Field", "FileInput", "Form", "Heading", "Input", "Image", "Italic", "Link", "Row", "Spinner", "Text", "Tooltip", "Checkbox", "Card", "Icon", "Selector", "Section", "Avatar", "Banner", "Skeleton", "Container", but received: undefined.', + 'Invalid params: At path: ui -- Expected type to be one of: "Address", "AddressInput", "AssetSelector", "Bold", "Box", "Button", "Copyable", "Divider", "Dropdown", "RadioGroup", "Field", "FileInput", "Form", "Heading", "Input", "Image", "Italic", "Link", "Row", "Spinner", "Text", "Tooltip", "Checkbox", "Card", "Icon", "Selector", "Section", "Avatar", "Banner", "Skeleton", "Container", but received: undefined.', stack: expect.any(String), }, id: 1, @@ -191,7 +191,7 @@ describe('snap_createInterface', () => { error: { code: -32602, message: - 'Invalid params: At path: ui.props.children.props.children -- Expected type to be one of: "AssetSelector", "Input", "Dropdown", "RadioGroup", "FileInput", "Checkbox", "Selector", but received: "Copyable".', + 'Invalid params: At path: ui.props.children.props.children -- Expected type to be one of: "AddressInput", "AssetSelector", "Input", "Dropdown", "RadioGroup", "FileInput", "Checkbox", "Selector", but received: "Copyable".', stack: expect.any(String), }, id: 1, From d96265cfb6e318a92d4415aa059cd2539a62d3ea Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Wed, 19 Feb 2025 09:01:06 -0500 Subject: [PATCH 03/12] add address input to state creation utils --- .../src/interface/utils.test.tsx | 44 +++++++++++++++++++ .../snaps-controllers/src/interface/utils.ts | 16 ++++--- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/packages/snaps-controllers/src/interface/utils.test.tsx b/packages/snaps-controllers/src/interface/utils.test.tsx index a2a333359d..2c0840d2e4 100644 --- a/packages/snaps-controllers/src/interface/utils.test.tsx +++ b/packages/snaps-controllers/src/interface/utils.test.tsx @@ -16,6 +16,7 @@ import { Card, SelectorOption, AssetSelector, + AddressInput, } from '@metamask/snaps-sdk/jsx'; import { @@ -296,6 +297,49 @@ describe('constructState', () => { }); }); + it('handles root level AddressInput with value', () => { + const element = ( + + + + ); + + const result = constructState({}, element); + expect(result).toStrictEqual({ + foo: '0x123', + }); + }); + + it('handles root level AddressInput without value', () => { + const element = ( + + + + ); + + const result = constructState({}, element); + expect(result).toStrictEqual({ + foo: null, + }); + }); + + it('handles AddressInput in forms', () => { + const element = ( + +
+ + + +
+
+ ); + + const result = constructState({}, element); + expect(result).toStrictEqual({ + form: { foo: null }, + }); + }); + it('sets default value for root level dropdown', () => { const element = ( diff --git a/packages/snaps-controllers/src/interface/utils.ts b/packages/snaps-controllers/src/interface/utils.ts index 1d3bd77c52..3c4d8aab21 100644 --- a/packages/snaps-controllers/src/interface/utils.ts +++ b/packages/snaps-controllers/src/interface/utils.ts @@ -21,6 +21,7 @@ import type { SelectorElement, SelectorOptionElement, AssetSelectorElement, + AddressInputElement, } from '@metamask/snaps-sdk/jsx'; import { isJSXElementUnsafe } from '@metamask/snaps-sdk/jsx'; import type { InternalAccount } from '@metamask/snaps-utils'; @@ -183,7 +184,8 @@ function constructComponentSpecificDefaultState( | RadioGroupElement | CheckboxElement | SelectorElement - | AssetSelectorElement, + | AssetSelectorElement + | AddressInputElement, elementDataGetters: ElementDataGetters, ) { switch (element.type) { @@ -264,7 +266,8 @@ function getComponentStateValue( | RadioGroupElement | CheckboxElement | SelectorElement - | AssetSelectorElement, + | AssetSelectorElement + | AddressInputElement, { getAssetsState }: ElementDataGetters, ) { switch (element.type) { @@ -297,7 +300,8 @@ function constructInputState( | FileInputElement | CheckboxElement | SelectorElement - | AssetSelectorElement, + | AssetSelectorElement + | AddressInputElement, elementDataGetters: ElementDataGetters, form?: string, ) { @@ -360,7 +364,8 @@ export function constructState( component.type === 'FileInput' || component.type === 'Checkbox' || component.type === 'Selector' || - component.type === 'AssetSelector') + component.type === 'AssetSelector' || + component.type === 'AddressInput') ) { const formState = newState[currentForm.name] as FormState; assertNameIsUnique(formState, component.props.name); @@ -382,7 +387,8 @@ export function constructState( component.type === 'FileInput' || component.type === 'Checkbox' || component.type === 'Selector' || - component.type === 'AssetSelector' + component.type === 'AssetSelector' || + component.type === 'AddressInput' ) { assertNameIsUnique(newState, component.props.name); newState[component.props.name] = constructInputState( From a63492d356f1827e738cb8309521f9adef696071 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 20 Feb 2025 12:25:02 -0500 Subject: [PATCH 04/12] update addressinput state --- packages/snaps-controllers/src/interface/utils.test.tsx | 2 +- packages/snaps-controllers/src/interface/utils.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/snaps-controllers/src/interface/utils.test.tsx b/packages/snaps-controllers/src/interface/utils.test.tsx index 2c0840d2e4..160815b03b 100644 --- a/packages/snaps-controllers/src/interface/utils.test.tsx +++ b/packages/snaps-controllers/src/interface/utils.test.tsx @@ -306,7 +306,7 @@ describe('constructState', () => { const result = constructState({}, element); expect(result).toStrictEqual({ - foo: '0x123', + foo: 'eip155:1:0x123', }); }); diff --git a/packages/snaps-controllers/src/interface/utils.ts b/packages/snaps-controllers/src/interface/utils.ts index 3c4d8aab21..a5739c82d0 100644 --- a/packages/snaps-controllers/src/interface/utils.ts +++ b/packages/snaps-controllers/src/interface/utils.ts @@ -277,6 +277,14 @@ function getComponentStateValue( case 'AssetSelector': return getAssetSelectorStateValue(element.props.value, getAssetsState); + case 'AddressInput': + if (!element.props.value) { + return null; + } + + // Construct CAIP-10 Id + return `${element.props.chainId}:${element.props.value}`; + default: return element.props.value; } From e20b6550b45a84e8967905f7a3fe9f0811371f5c Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Thu, 20 Feb 2025 13:00:51 -0500 Subject: [PATCH 05/12] lint fix --- packages/snaps-sdk/src/jsx/components/form/Field.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/snaps-sdk/src/jsx/components/form/Field.ts b/packages/snaps-sdk/src/jsx/components/form/Field.ts index 2cce3bdd47..fc3c1ddcca 100644 --- a/packages/snaps-sdk/src/jsx/components/form/Field.ts +++ b/packages/snaps-sdk/src/jsx/components/form/Field.ts @@ -6,8 +6,8 @@ import type { FileInputElement } from './FileInput'; import type { InputElement } from './Input'; import type { RadioGroupElement } from './RadioGroup'; import type { SelectorElement } from './Selector'; -import { createSnapComponent } from '../../component'; import type { GenericSnapChildren } from '../../component'; +import { createSnapComponent } from '../../component'; /** * The props of the {@link Field} component. From 7475965e629036712ccc32cba0bc0c89ae800960 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Mon, 24 Feb 2025 12:42:56 -0500 Subject: [PATCH 06/12] update types, use util to construct caip account id --- packages/snaps-controllers/src/interface/utils.ts | 9 +++++---- packages/snaps-sdk/src/types/interface.ts | 8 +++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/snaps-controllers/src/interface/utils.ts b/packages/snaps-controllers/src/interface/utils.ts index a5739c82d0..229561daca 100644 --- a/packages/snaps-controllers/src/interface/utils.ts +++ b/packages/snaps-controllers/src/interface/utils.ts @@ -37,6 +37,7 @@ import { parseCaipAccountId, parseCaipAssetType, } from '@metamask/utils'; +import { toCaipAccountId } from '@metamask/utils'; /** * A function to get the MultichainAssetController state. @@ -71,7 +72,6 @@ type ElementDataGetters = { getAssetsState: GetAssetsState; getAccountByAddress: GetAccountByAddress; }; - /** * Get a JSX element from a component or JSX element. If the component is a * JSX element, it is returned as is. Otherwise, the component is converted to @@ -277,14 +277,15 @@ function getComponentStateValue( case 'AssetSelector': return getAssetSelectorStateValue(element.props.value, getAssetsState); - case 'AddressInput': + case 'AddressInput': { if (!element.props.value) { return null; } // Construct CAIP-10 Id - return `${element.props.chainId}:${element.props.value}`; - + const [namespace, reference] = element.props.chainId.split(':'); + return toCaipAccountId(namespace, reference, element.props.value); + } default: return element.props.value; } diff --git a/packages/snaps-sdk/src/types/interface.ts b/packages/snaps-sdk/src/types/interface.ts index d5086350a2..47af64c78b 100644 --- a/packages/snaps-sdk/src/types/interface.ts +++ b/packages/snaps-sdk/src/types/interface.ts @@ -6,7 +6,12 @@ import { string, union, } from '@metamask/superstruct'; -import { JsonStruct, hasProperty, isObject } from '@metamask/utils'; +import { + CaipAccountIdStruct, + JsonStruct, + hasProperty, + isObject, +} from '@metamask/utils'; import { AssetSelectorStateStruct, FileStruct } from './handlers'; import { selectiveUnion } from '../internals'; @@ -27,6 +32,7 @@ export const StateStruct = union([ FileStruct, string(), boolean(), + CaipAccountIdStruct, ]); export const FormStateStruct = record(string(), nullable(StateStruct)); From c491b1fca4fa459107de1959259cb9178c55a875 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Mon, 24 Feb 2025 14:22:44 -0500 Subject: [PATCH 07/12] Add placeholder and disabled prop --- packages/snaps-sdk/src/jsx/components/form/AddressInput.ts | 4 ++++ packages/snaps-sdk/src/jsx/validation.test.tsx | 6 ++++++ packages/snaps-sdk/src/jsx/validation.ts | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/packages/snaps-sdk/src/jsx/components/form/AddressInput.ts b/packages/snaps-sdk/src/jsx/components/form/AddressInput.ts index 8318ab5644..af86807b83 100644 --- a/packages/snaps-sdk/src/jsx/components/form/AddressInput.ts +++ b/packages/snaps-sdk/src/jsx/components/form/AddressInput.ts @@ -6,6 +6,8 @@ export type AddressInputProps = { name: string; value?: string | undefined; chainId: CaipChainId; + placeholder?: string | undefined; + disabled?: boolean | undefined; }; const TYPE = 'AddressInput'; @@ -17,6 +19,8 @@ const TYPE = 'AddressInput'; * @param props.name - The name of the input field. * @param props.value - The value of the input field. * @param props.chainId - The CAIP-2 chain ID of the address. + * @param props.placeholder - The placeholder text of the input field. + * @param props.disabled - Whether the input field is disabled. * @returns An input element. * @example * diff --git a/packages/snaps-sdk/src/jsx/validation.test.tsx b/packages/snaps-sdk/src/jsx/validation.test.tsx index 8e01601df6..9497371af0 100644 --- a/packages/snaps-sdk/src/jsx/validation.test.tsx +++ b/packages/snaps-sdk/src/jsx/validation.test.tsx @@ -219,6 +219,8 @@ describe('AddressInputStruct', () => { chainId="eip155:1" value="0x1234567890abcdef1234567890abcdef12345678" />, + , + , ])('validates an address input element', (value) => { expect(is(value, AddressInputStruct)).toBe(true); }); @@ -236,6 +238,10 @@ describe('AddressInputStruct', () => { , // @ts-expect-error - Invalid props. , + // @ts-expect-error - Invalid props. + , + // @ts-expect-error - Invalid props. + , ])('does not validate "%p"', (value) => { expect(is(value, AddressInputStruct)).toBe(false); }); diff --git a/packages/snaps-sdk/src/jsx/validation.ts b/packages/snaps-sdk/src/jsx/validation.ts index 5788ff25b0..433e4422b2 100644 --- a/packages/snaps-sdk/src/jsx/validation.ts +++ b/packages/snaps-sdk/src/jsx/validation.ts @@ -348,12 +348,17 @@ export const InputStruct: Describe = elementWithSelectiveProps( }, ); +/** + * A struct for the {@link AddressInputElement} type. + */ export const AddressInputStruct: Describe = element( 'AddressInput', { name: string(), chainId: CaipChainIdStruct as unknown as Struct, value: optional(string()), + placeholder: optional(string()), + disabled: optional(boolean()), }, ); From fe7dd0b535bdb89d03c011a6feb5625843fbb3c4 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Mon, 24 Feb 2025 15:54:50 -0500 Subject: [PATCH 08/12] use parser from utils --- packages/snaps-controllers/src/interface/utils.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/snaps-controllers/src/interface/utils.ts b/packages/snaps-controllers/src/interface/utils.ts index 229561daca..0b01f3d1be 100644 --- a/packages/snaps-controllers/src/interface/utils.ts +++ b/packages/snaps-controllers/src/interface/utils.ts @@ -36,8 +36,9 @@ import { type CaipAccountId, parseCaipAccountId, parseCaipAssetType, + toCaipAccountId, + parseCaipChainId, } from '@metamask/utils'; -import { toCaipAccountId } from '@metamask/utils'; /** * A function to get the MultichainAssetController state. @@ -283,7 +284,7 @@ function getComponentStateValue( } // Construct CAIP-10 Id - const [namespace, reference] = element.props.chainId.split(':'); + const { namespace, reference } = parseCaipChainId(element.props.chainId); return toCaipAccountId(namespace, reference, element.props.value); } default: From b3d23a3de7462609d801986ac7d558282fdfcdce Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Tue, 4 Mar 2025 07:39:27 -0500 Subject: [PATCH 09/12] update import --- packages/snaps-sdk/src/jsx/validation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/snaps-sdk/src/jsx/validation.ts b/packages/snaps-sdk/src/jsx/validation.ts index 433e4422b2..5b54487c94 100644 --- a/packages/snaps-sdk/src/jsx/validation.ts +++ b/packages/snaps-sdk/src/jsx/validation.ts @@ -21,7 +21,6 @@ import { assign, union, } from '@metamask/superstruct'; -import type { CaipChainId } from '@metamask/utils'; import { CaipAccountIdStruct, CaipChainIdStruct, @@ -30,6 +29,7 @@ import { isPlainObject, JsonStruct, } from '@metamask/utils'; +import type { CaipChainId } from '@metamask/utils'; import type { GenericSnapChildren, From 1d0a65b1032c38bb441384ec4baa2b86471145d2 Mon Sep 17 00:00:00 2001 From: Hassan Malik Date: Wed, 5 Mar 2025 17:48:46 -0500 Subject: [PATCH 10/12] fix struct schema --- packages/snaps-sdk/src/internals/jsx.ts | 11 +++++++---- packages/snaps-sdk/src/jsx/validation.ts | 3 +-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/snaps-sdk/src/internals/jsx.ts b/packages/snaps-sdk/src/internals/jsx.ts index 18d8a09fab..a7080cdcba 100644 --- a/packages/snaps-sdk/src/internals/jsx.ts +++ b/packages/snaps-sdk/src/internals/jsx.ts @@ -10,6 +10,7 @@ import type { Struct, UnionToIntersection, } from '@metamask/superstruct'; +import type { CaipChainId } from '@metamask/utils'; import { union } from './structs'; import type { EmptyObject } from '../types'; @@ -34,11 +35,13 @@ type StructSchema = : [Type] extends [string | undefined | null] ? [Type] extends [`0x${string}`] ? null - : [Type] extends [IsMatch] + : [Type] extends [CaipChainId] ? null - : [Type] extends [IsUnion] - ? EnumSchema - : Type + : [Type] extends [IsMatch] + ? null + : [Type] extends [IsUnion] + ? EnumSchema + : Type : [Type] extends [number | undefined | null] ? [Type] extends [IsMatch] ? null diff --git a/packages/snaps-sdk/src/jsx/validation.ts b/packages/snaps-sdk/src/jsx/validation.ts index 5b54487c94..dfe8932b34 100644 --- a/packages/snaps-sdk/src/jsx/validation.ts +++ b/packages/snaps-sdk/src/jsx/validation.ts @@ -29,7 +29,6 @@ import { isPlainObject, JsonStruct, } from '@metamask/utils'; -import type { CaipChainId } from '@metamask/utils'; import type { GenericSnapChildren, @@ -355,7 +354,7 @@ export const AddressInputStruct: Describe = element( 'AddressInput', { name: string(), - chainId: CaipChainIdStruct as unknown as Struct, + chainId: CaipChainIdStruct, value: optional(string()), placeholder: optional(string()), disabled: optional(boolean()), From 613ec912fe06097f9438f78cf16881e7a2261e7c Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Fri, 21 Mar 2025 11:53:52 +0100 Subject: [PATCH 11/12] fixes after rebase --- .../examples/packages/browserify-plugin/snap.manifest.json | 2 +- packages/examples/packages/browserify/snap.manifest.json | 2 +- packages/snaps-controllers/coverage.json | 2 +- packages/snaps-controllers/src/interface/utils.test.tsx | 6 +++--- .../src/permitted/createInterface.test.tsx | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/examples/packages/browserify-plugin/snap.manifest.json b/packages/examples/packages/browserify-plugin/snap.manifest.json index 94b8b38f71..c533278abe 100644 --- a/packages/examples/packages/browserify-plugin/snap.manifest.json +++ b/packages/examples/packages/browserify-plugin/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "7m/tln4qf/bu8u9PdJnluGBWg7949ema1QUhYrL6Kys=", + "shasum": "e3eXjWGO/nmmRxBt/caaktmqj/3chjABsSkFq6leppU=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/browserify/snap.manifest.json b/packages/examples/packages/browserify/snap.manifest.json index ca88f95503..0c470e9a01 100644 --- a/packages/examples/packages/browserify/snap.manifest.json +++ b/packages/examples/packages/browserify/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "SeDH2s8fzM2/cxqbhyhF7G3TeztLsn01kRiWige7l2M=", + "shasum": "yvblLjVAXActFtH/3q3GxeTcbRopWjZCX2h4S21hscs=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/snaps-controllers/coverage.json b/packages/snaps-controllers/coverage.json index a9a891e1f3..eb91c87495 100644 --- a/packages/snaps-controllers/coverage.json +++ b/packages/snaps-controllers/coverage.json @@ -1,5 +1,5 @@ { - "branches": 93.46, + "branches": 93.51, "functions": 97.36, "lines": 98.33, "statements": 98.06 diff --git a/packages/snaps-controllers/src/interface/utils.test.tsx b/packages/snaps-controllers/src/interface/utils.test.tsx index 160815b03b..8265cb8e59 100644 --- a/packages/snaps-controllers/src/interface/utils.test.tsx +++ b/packages/snaps-controllers/src/interface/utils.test.tsx @@ -304,7 +304,7 @@ describe('constructState', () => { ); - const result = constructState({}, element); + const result = constructState({}, element, elementDataGetters); expect(result).toStrictEqual({ foo: 'eip155:1:0x123', }); @@ -317,7 +317,7 @@ describe('constructState', () => { ); - const result = constructState({}, element); + const result = constructState({}, element, elementDataGetters); expect(result).toStrictEqual({ foo: null, }); @@ -334,7 +334,7 @@ describe('constructState', () => { ); - const result = constructState({}, element); + const result = constructState({}, element, elementDataGetters); expect(result).toStrictEqual({ form: { foo: null }, }); diff --git a/packages/snaps-rpc-methods/src/permitted/createInterface.test.tsx b/packages/snaps-rpc-methods/src/permitted/createInterface.test.tsx index a0e3921c49..7dc3472dc9 100644 --- a/packages/snaps-rpc-methods/src/permitted/createInterface.test.tsx +++ b/packages/snaps-rpc-methods/src/permitted/createInterface.test.tsx @@ -141,7 +141,7 @@ describe('snap_createInterface', () => { error: { code: -32602, message: - 'Invalid params: At path: ui -- Expected type to be one of: "Address", "AddressInput", "AssetSelector", "Bold", "Box", "Button", "Copyable", "Divider", "Dropdown", "RadioGroup", "Field", "FileInput", "Form", "Heading", "Input", "Image", "Italic", "Link", "Row", "Spinner", "Text", "Tooltip", "Checkbox", "Card", "Icon", "Selector", "Section", "Avatar", "Banner", "Skeleton", "Container", but received: undefined.', + 'Invalid params: At path: ui -- Expected type to be one of: "Address", "AssetSelector", "AddressInput", "Bold", "Box", "Button", "Copyable", "Divider", "Dropdown", "RadioGroup", "Field", "FileInput", "Form", "Heading", "Input", "Image", "Italic", "Link", "Row", "Spinner", "Text", "Tooltip", "Checkbox", "Card", "Icon", "Selector", "Section", "Avatar", "Banner", "Skeleton", "Container", but received: undefined.', stack: expect.any(String), }, id: 1, @@ -191,7 +191,7 @@ describe('snap_createInterface', () => { error: { code: -32602, message: - 'Invalid params: At path: ui.props.children.props.children -- Expected type to be one of: "AddressInput", "AssetSelector", "Input", "Dropdown", "RadioGroup", "FileInput", "Checkbox", "Selector", but received: "Copyable".', + 'Invalid params: At path: ui.props.children.props.children -- Expected type to be one of: "AssetSelector", "AddressInput", "Input", "Dropdown", "RadioGroup", "FileInput", "Checkbox", "Selector", but received: "Copyable".', stack: expect.any(String), }, id: 1, From fb343af6f2247bbd8450dfb6ce4d85dd69a22448 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Fri, 21 Mar 2025 12:28:58 +0100 Subject: [PATCH 12/12] re-add nextline --- packages/snaps-controllers/src/interface/utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/snaps-controllers/src/interface/utils.ts b/packages/snaps-controllers/src/interface/utils.ts index 0b01f3d1be..2e5a321ccc 100644 --- a/packages/snaps-controllers/src/interface/utils.ts +++ b/packages/snaps-controllers/src/interface/utils.ts @@ -73,6 +73,7 @@ type ElementDataGetters = { getAssetsState: GetAssetsState; getAccountByAddress: GetAccountByAddress; }; + /** * Get a JSX element from a component or JSX element. If the component is a * JSX element, it is returned as is. Otherwise, the component is converted to