1+ import type { InternalAccount } from '@metamask/keyring-internal-api' ;
12import { assert } from '@metamask/snaps-sdk' ;
23import type {
34 FormState ,
@@ -17,6 +18,7 @@ import type {
1718 RadioElement ,
1819 SelectorElement ,
1920 SelectorOptionElement ,
21+ AccountSelectorElement ,
2022} from '@metamask/snaps-sdk/jsx' ;
2123import { isJSXElementUnsafe } from '@metamask/snaps-sdk/jsx' ;
2224import {
@@ -25,6 +27,17 @@ import {
2527 getJsxElementFromComponent ,
2628 walkJsx ,
2729} from '@metamask/snaps-utils' ;
30+ import type { CaipAccountId , CaipChainId } from '@metamask/utils' ;
31+ import {
32+ parseCaipChainId ,
33+ toCaipAccountId ,
34+ type CaipAccountAddress ,
35+ } from '@metamask/utils' ;
36+
37+ type GetSelectedAccount = ( ) => InternalAccount | undefined ;
38+ type GetAccountByAddress = (
39+ address : CaipAccountAddress ,
40+ ) => InternalAccount | undefined ;
2841
2942/**
3043 * Get a JSX element from a component or JSX element. If the component is a
@@ -55,13 +68,31 @@ export function assertNameIsUnique(state: InterfaceState, name: string) {
5568 ) ;
5669}
5770
71+ /**
72+ * Create a list of CAIP account IDs from an address and a list of scopes.
73+ *
74+ * @param address - The address to create the account IDs from.
75+ * @param scopes - The scopes to create the account IDs from.
76+ * @returns The list of CAIP account IDs.
77+ */
78+ export function createAddressList (
79+ address : string ,
80+ scopes : CaipChainId [ ] ,
81+ ) : CaipAccountId [ ] {
82+ return scopes . map ( ( scope ) => {
83+ const { namespace, reference } = parseCaipChainId ( scope ) ;
84+ return toCaipAccountId ( namespace , reference , address ) ;
85+ } ) ;
86+ }
87+
5888/**
5989 * Construct default state for a component.
6090 *
6191 * This function is meant to be used inside constructInputState to account
6292 * for component specific defaults and will not override the component value or existing form state.
6393 *
6494 * @param element - The input element.
95+ * @param getSelectedAccount - A function to get the selected account in the client.
6596 * @returns The default state for the specific component, if any.
6697 */
6798function constructComponentSpecificDefaultState (
@@ -70,7 +101,9 @@ function constructComponentSpecificDefaultState(
70101 | DropdownElement
71102 | RadioGroupElement
72103 | CheckboxElement
73- | SelectorElement ,
104+ | SelectorElement
105+ | AccountSelectorElement ,
106+ getSelectedAccount : GetSelectedAccount ,
74107) {
75108 switch ( element . type ) {
76109 case 'Dropdown' : {
@@ -88,6 +121,20 @@ function constructComponentSpecificDefaultState(
88121 return children [ 0 ] ?. props . value ;
89122 }
90123
124+ case 'AccountSelector' : {
125+ const account = getSelectedAccount ( ) ;
126+
127+ if ( ! account ) {
128+ return null ;
129+ }
130+
131+ const { id, address, scopes } = account ;
132+
133+ const addresses = createAddressList ( address , scopes ) ;
134+
135+ return { accountId : id , addresses } ;
136+ }
137+
91138 case 'Checkbox' :
92139 return false ;
93140
@@ -103,20 +150,41 @@ function constructComponentSpecificDefaultState(
103150 * This function exists to account for components where that isn't the case.
104151 *
105152 * @param element - The input element.
153+ * @param getAccountByAddress - A function to get an account by address.
106154 * @returns The state value for a given component.
107155 */
108156function getComponentStateValue (
109157 element :
158+ | AccountSelectorElement
110159 | InputElement
111160 | DropdownElement
112161 | RadioGroupElement
113162 | CheckboxElement
114163 | SelectorElement ,
164+ getAccountByAddress : GetAccountByAddress ,
115165) {
116166 switch ( element . type ) {
117167 case 'Checkbox' :
118168 return element . props . checked ;
119169
170+ case 'AccountSelector' : {
171+ if ( ! element . props . selectedAddress ) {
172+ return undefined ;
173+ }
174+
175+ const account = getAccountByAddress ( element . props . selectedAddress ) ;
176+
177+ if ( ! account ) {
178+ return undefined ;
179+ }
180+
181+ const { id, address, scopes } = account ;
182+
183+ const addresses = createAddressList ( address , scopes ) ;
184+
185+ return { accountId : id , addresses } ;
186+ }
187+
120188 default :
121189 return element . props . value ;
122190 }
@@ -127,18 +195,23 @@ function getComponentStateValue(
127195 *
128196 * @param oldState - The previous state.
129197 * @param element - The input element.
198+ * @param getSelectedAccount - A function to get the selected account in the client.
199+ * @param getAccountByAddress - A function to get an account by address.
130200 * @param form - An optional form that the input is enclosed in.
131201 * @returns The input state.
132202 */
133203function constructInputState (
134204 oldState : InterfaceState ,
135205 element :
206+ | AccountSelectorElement
136207 | InputElement
137208 | DropdownElement
138209 | RadioGroupElement
139210 | FileInputElement
140211 | CheckboxElement
141212 | SelectorElement ,
213+ getSelectedAccount : GetSelectedAccount ,
214+ getAccountByAddress : GetAccountByAddress ,
142215 form ?: string ,
143216) {
144217 const oldStateUnwrapped = form ? ( oldState [ form ] as FormState ) : oldState ;
@@ -149,9 +222,9 @@ function constructInputState(
149222 }
150223
151224 return (
152- getComponentStateValue ( element ) ??
225+ getComponentStateValue ( element , getAccountByAddress ) ??
153226 oldInputState ??
154- constructComponentSpecificDefaultState ( element ) ??
227+ constructComponentSpecificDefaultState ( element , getSelectedAccount ) ??
155228 null
156229 ) ;
157230}
@@ -161,11 +234,15 @@ function constructInputState(
161234 *
162235 * @param oldState - The previous state.
163236 * @param rootComponent - The UI component to construct state from.
237+ * @param getSelectedAccount - A function to get the selected account in the client.
238+ * @param getAccountByAddress - A function to get an account by address.
164239 * @returns The interface state of the passed component.
165240 */
166241export function constructState (
167242 oldState : InterfaceState ,
168243 rootComponent : JSXElement ,
244+ getSelectedAccount : GetSelectedAccount ,
245+ getAccountByAddress : GetAccountByAddress ,
169246) : InterfaceState {
170247 const newState : InterfaceState = { } ;
171248
@@ -196,13 +273,16 @@ export function constructState(
196273 component . type === 'RadioGroup' ||
197274 component . type === 'FileInput' ||
198275 component . type === 'Checkbox' ||
199- component . type === 'Selector' )
276+ component . type === 'Selector' ||
277+ component . type === 'AccountSelector' )
200278 ) {
201279 const formState = newState [ currentForm . name ] as FormState ;
202280 assertNameIsUnique ( formState , component . props . name ) ;
203281 formState [ component . props . name ] = constructInputState (
204282 oldState ,
205283 component ,
284+ getSelectedAccount ,
285+ getAccountByAddress ,
206286 currentForm . name ,
207287 ) ;
208288 return ;
@@ -215,10 +295,16 @@ export function constructState(
215295 component . type === 'RadioGroup' ||
216296 component . type === 'FileInput' ||
217297 component . type === 'Checkbox' ||
218- component . type === 'Selector'
298+ component . type === 'Selector' ||
299+ component . type === 'AccountSelector'
219300 ) {
220301 assertNameIsUnique ( newState , component . props . name ) ;
221- newState [ component . props . name ] = constructInputState ( oldState , component ) ;
302+ newState [ component . props . name ] = constructInputState (
303+ oldState ,
304+ component ,
305+ getSelectedAccount ,
306+ getAccountByAddress ,
307+ ) ;
222308 }
223309 } ) ;
224310
0 commit comments