1
+ import React , { useId } from "react" ;
1
2
import { useCallback , useEffect , useState } from "react" ;
2
3
import { Scope , SessionData } from "@metamask/multichain-sdk" ;
3
4
import { CaipAccountId } from "@metamask/utils" ;
@@ -7,144 +8,116 @@ import DynamicInputs, { INPUT_LABEL_TYPE } from "./components/DynamicInputs";
7
8
import { FEATURED_NETWORKS } from "./constants/networks" ;
8
9
import { ScopeCard } from "./components/ScopeCard" ;
9
10
10
-
11
-
12
-
13
11
function App ( ) {
14
- const [ customScopes , setCustomScopes ] = useState < string [ ] > ( [ "eip155:1" ] ) ;
15
- const [ caipAccountIds , setCaipAccountIds ] = useState < CaipAccountId [ ] > ( [ ] ) ;
16
- const [ extensionId , setExtensionId ] = useState < string > ( METAMASK_PROD_CHROME_ID ) ;
17
-
18
- const {
19
- isConnected,
20
- session,
21
- connect : sdkConnect ,
22
- disconnect : sdkDisconnect ,
23
- } = useSDK ( {
24
- extensionId,
25
- } ) ;
26
-
27
- useEffect ( ( ) => {
28
- if ( session ) {
29
- const scopes = Object . keys ( session . sessionScopes ) ;
30
- setCustomScopes ( scopes ) ;
31
-
32
- // Accumulate all accounts from all scopes
33
- const allAccounts : CaipAccountId [ ] = [ ] ;
34
- for ( const scope of scopes ) {
35
- const { accounts } = session . sessionScopes [ scope as keyof typeof session . sessionScopes ] ?? { } ;
36
- if ( accounts ) {
37
- allAccounts . push ( ...accounts ) ;
38
- }
39
- }
40
- setCaipAccountIds ( allAccounts ) ;
41
- }
42
- } , [ session ] )
43
-
44
- // Check if current scope selection differs from connected session scopes
45
- const scopesHaveChanged = ( ) => {
46
- if ( ! session ) return false ;
47
- const sessionScopes = Object . keys ( session . sessionScopes ) ;
48
- const currentScopes = customScopes . filter ( scope => scope . length ) ;
49
-
50
- if ( sessionScopes . length !== currentScopes . length ) return true ;
51
-
52
- return ! sessionScopes . every ( scope => currentScopes . includes ( scope ) ) ||
53
- ! currentScopes . every ( scope => sessionScopes . includes ( scope ) ) ;
54
- } ;
55
-
56
- const connect = async ( ) => {
57
- try {
58
- const selectedScopesArray = customScopes . filter ( ( scope ) => scope . length )
59
- const filteredAccountIds = caipAccountIds . filter ( ( addr ) => addr . trim ( ) !== "" ) ;
60
- await sdkConnect (
61
- selectedScopesArray as Scope [ ] ,
62
- filteredAccountIds as CaipAccountId [ ] ,
63
- ) ;
64
- } catch ( error ) {
65
- console . error ( "Error creating session:" , error ) ;
66
- }
67
- }
68
-
69
- const disconnect = useCallback ( async ( ) => {
70
- await sdkDisconnect ( )
71
- } , [ sdkDisconnect ] )
72
-
73
- const availableOptions = Object . keys ( FEATURED_NETWORKS ) . reduce < { name : string , value : string } [ ] > ( ( all , networkName ) => {
74
- const networkCaipValue = FEATURED_NETWORKS [ networkName as keyof typeof FEATURED_NETWORKS ] ;
75
- return [
76
- ...all ,
77
- { name : networkName , value : networkCaipValue , }
78
- ] ;
79
- } , [ ] )
80
-
81
-
82
-
83
-
84
-
85
- return (
86
- < div className = "min-h-screen bg-gray-50 flex justify-center" >
87
- < div className = "max-w-6xl w-full p-8" >
88
- < h1 className = "text-slate-800 text-4xl font-bold mb-8 text-center" >
89
- MetaMask MultiChain API Test Dapp
90
- </ h1 >
91
- < section className = "bg-white rounded-lg p-8 mb-6 shadow-sm" >
92
- < label className = "flex flex-col gap-2 mb-4" >
93
- < span className = "text-gray-700 font-medium" > Extension ID:</ span >
94
- < input
95
- type = "text"
96
- placeholder = "Enter extension ID"
97
- value = { extensionId }
98
- onChange = { ( evt ) => setExtensionId ( evt . target . value ) }
99
- disabled = { true }
100
- readOnly = { true }
101
- data-testid = "extension-id-input"
102
- id = "extension-id-input"
103
- className = "p-2 border border-gray-300 rounded text-base bg-gray-50"
104
- />
105
- </ label >
106
- < div className = "mb-4" >
107
- < DynamicInputs
108
- availableOptions = { availableOptions }
109
- inputArray = { customScopes }
110
- setInputArray = { setCustomScopes }
111
- label = { INPUT_LABEL_TYPE . SCOPE }
112
- />
113
- </ div >
114
- { ( ! isConnected || scopesHaveChanged ( ) ) && (
115
- < button
116
- onClick = { connect }
117
- className = "bg-blue-500 text-white px-5 py-2 rounded text-base mr-2 hover:bg-blue-600 transition-colors"
118
- >
119
- { isConnected && scopesHaveChanged ( ) ? "Re Establish Connection" : "Connect" }
120
- </ button >
121
- ) }
122
- {
123
- isConnected && (
124
- < button
125
- onClick = { disconnect }
126
- className = "bg-blue-500 text-white px-5 py-2 rounded text-base hover:bg-blue-600 transition-colors"
127
- >
128
- Disconnect
129
- </ button >
130
- )
131
- }
132
- </ section >
133
- < section className = "bg-white rounded-lg p-8 mb-6 shadow-sm" >
134
- { Object . keys ( session ?. sessionScopes ?? { } ) . length > 0 && (
135
- < section className = "mb-6" >
136
- < h2 className = "text-2xl font-bold text-gray-800 mb-6" > Connected Networks</ h2 >
137
- < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" >
138
- { Object . entries ( session ?. sessionScopes ?? { } ) . map ( ( [ scope , details ] ) => {
139
- return < ScopeCard key = { scope } scope = { scope as Scope } details = { details as SessionData [ 'sessionScopes' ] [ Scope ] } />
140
- } ) }
141
- </ div >
142
- </ section >
143
- ) }
144
- </ section >
145
- </ div >
146
- </ div >
147
- ) ;
12
+ const id = useId ( ) ;
13
+ const [ customScopes , setCustomScopes ] = useState < string [ ] > ( [ "eip155:1" ] ) ;
14
+ const [ caipAccountIds , setCaipAccountIds ] = useState < CaipAccountId [ ] > ( [ ] ) ;
15
+ const [ extensionId , setExtensionId ] = useState < string > ( METAMASK_PROD_CHROME_ID ) ;
16
+
17
+ const {
18
+ isConnected,
19
+ session,
20
+ connect : sdkConnect ,
21
+ disconnect : sdkDisconnect ,
22
+ } = useSDK ( {
23
+ extensionId,
24
+ } ) ;
25
+
26
+ useEffect ( ( ) => {
27
+ if ( session ) {
28
+ const scopes = Object . keys ( session . sessionScopes ) ;
29
+ setCustomScopes ( scopes ) ;
30
+
31
+ // Accumulate all accounts from all scopes
32
+ const allAccounts : CaipAccountId [ ] = [ ] ;
33
+ for ( const scope of scopes ) {
34
+ const { accounts } = session . sessionScopes [ scope as keyof typeof session . sessionScopes ] ?? { } ;
35
+ if ( accounts ) {
36
+ allAccounts . push ( ...accounts ) ;
37
+ }
38
+ }
39
+ setCaipAccountIds ( allAccounts ) ;
40
+ }
41
+ } , [ session ] ) ;
42
+
43
+ // Check if current scope selection differs from connected session scopes
44
+ const scopesHaveChanged = ( ) => {
45
+ if ( ! session ) return false ;
46
+ const sessionScopes = Object . keys ( session . sessionScopes ) ;
47
+ const currentScopes = customScopes . filter ( ( scope ) => scope . length ) ;
48
+
49
+ if ( sessionScopes . length !== currentScopes . length ) return true ;
50
+
51
+ return ! sessionScopes . every ( ( scope ) => currentScopes . includes ( scope ) ) || ! currentScopes . every ( ( scope ) => sessionScopes . includes ( scope ) ) ;
52
+ } ;
53
+
54
+ const connect = async ( ) => {
55
+ try {
56
+ const selectedScopesArray = customScopes . filter ( ( scope ) => scope . length ) ;
57
+ const filteredAccountIds = caipAccountIds . filter ( ( addr ) => addr . trim ( ) !== "" ) ;
58
+ await sdkConnect ( selectedScopesArray as Scope [ ] , filteredAccountIds as CaipAccountId [ ] ) ;
59
+ } catch ( error ) {
60
+ console . error ( "Error creating session:" , error ) ;
61
+ }
62
+ } ;
63
+
64
+ const disconnect = useCallback ( async ( ) => {
65
+ await sdkDisconnect ( ) ;
66
+ } , [ sdkDisconnect ] ) ;
67
+
68
+ const availableOptions = Object . keys ( FEATURED_NETWORKS ) . reduce < { name : string ; value : string } [ ] > ( ( all , networkName ) => {
69
+ const networkCaipValue = FEATURED_NETWORKS [ networkName as keyof typeof FEATURED_NETWORKS ] ;
70
+ return [ ...all , { name : networkName , value : networkCaipValue } ] ;
71
+ } , [ ] ) ;
72
+
73
+ return (
74
+ < div className = "min-h-screen bg-gray-50 flex justify-center" >
75
+ < div className = "max-w-6xl w-full p-8" >
76
+ < h1 className = "text-slate-800 text-4xl font-bold mb-8 text-center" > MetaMask MultiChain API Test Dapp</ h1 >
77
+ < section className = "bg-white rounded-lg p-8 mb-6 shadow-sm" >
78
+ < label className = "flex flex-col gap-2 mb-4" >
79
+ < span className = "text-gray-700 font-medium" > Extension ID:</ span >
80
+ < input
81
+ type = "text"
82
+ placeholder = "Enter extension ID"
83
+ value = { extensionId }
84
+ onChange = { ( evt ) => setExtensionId ( evt . target . value ) }
85
+ disabled = { true }
86
+ readOnly = { true }
87
+ data-testid = "extension-id-input"
88
+ id = { id }
89
+ className = "p-2 border border-gray-300 rounded text-base bg-gray-50"
90
+ />
91
+ </ label >
92
+ < div className = "mb-4" >
93
+ < DynamicInputs availableOptions = { availableOptions } inputArray = { customScopes } setInputArray = { setCustomScopes } label = { INPUT_LABEL_TYPE . SCOPE } />
94
+ </ div >
95
+ { ( ! isConnected || scopesHaveChanged ( ) ) && (
96
+ < button type = "button" onClick = { connect } className = "bg-blue-500 text-white px-5 py-2 rounded text-base mr-2 hover:bg-blue-600 transition-colors" >
97
+ { isConnected && scopesHaveChanged ( ) ? "Re Establish Connection" : "Connect" }
98
+ </ button >
99
+ ) }
100
+ { isConnected && (
101
+ < button type = "button" onClick = { disconnect } className = "bg-blue-500 text-white px-5 py-2 rounded text-base hover:bg-blue-600 transition-colors" >
102
+ Disconnect
103
+ </ button >
104
+ ) }
105
+ </ section >
106
+ < section className = "bg-white rounded-lg p-8 mb-6 shadow-sm" >
107
+ { Object . keys ( session ?. sessionScopes ?? { } ) . length > 0 && (
108
+ < section className = "mb-6" >
109
+ < h2 className = "text-2xl font-bold text-gray-800 mb-6" > Connected Networks</ h2 >
110
+ < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" >
111
+ { Object . entries ( session ?. sessionScopes ?? { } ) . map ( ( [ scope , details ] ) => {
112
+ return < ScopeCard key = { scope } scope = { scope as Scope } details = { details as SessionData [ "sessionScopes" ] [ Scope ] } /> ;
113
+ } ) }
114
+ </ div >
115
+ </ section >
116
+ ) }
117
+ </ section >
118
+ </ div >
119
+ </ div >
120
+ ) ;
148
121
}
149
122
150
123
export default App ;
0 commit comments