@@ -7,11 +7,15 @@ import {
7
7
Alert ,
8
8
AmountInput ,
9
9
Input ,
10
+ Option ,
10
11
Select ,
11
12
} from "@namada/components" ;
12
13
import { Account } from "@namada/types" ;
13
14
import { bech32mValidation , shortenAddress } from "@namada/utils" ;
14
15
16
+ import { chains } from "@namada/chains" ;
17
+ import { useUntil } from "@namada/hooks" ;
18
+ import { Namada } from "@namada/integrations" ;
15
19
import { Data , PowChallenge , TransferResponse } from "../utils" ;
16
20
import { AppContext } from "./App" ;
17
21
import {
@@ -33,18 +37,28 @@ enum Status {
33
37
}
34
38
35
39
type Props = {
36
- accounts : Account [ ] ;
37
40
isTestnetLive : boolean ;
38
41
} ;
39
42
40
43
const bech32mPrefix = "tnam" ;
41
44
42
- export const FaucetForm : React . FC < Props > = ( { accounts, isTestnetLive } ) => {
45
+ enum ExtensionAttachStatus {
46
+ PendingDetection ,
47
+ NotInstalled ,
48
+ Installed ,
49
+ }
50
+
51
+ export const FaucetForm : React . FC < Props > = ( { isTestnetLive } ) => {
43
52
const {
44
53
api,
45
54
settings : { difficulty, tokens, withdrawLimit } ,
46
55
} = useContext ( AppContext ) ! ;
56
+ const [ extensionAttachStatus , setExtensionAttachStatus ] = useState (
57
+ ExtensionAttachStatus . PendingDetection
58
+ ) ;
59
+ const [ isExtensionConnected , setIsExtensionConnected ] = useState ( false ) ;
47
60
61
+ const [ accounts , setAccounts ] = useState < Account [ ] > ( [ ] ) ;
48
62
const accountLookup = accounts . reduce (
49
63
( acc , account ) => {
50
64
acc [ account . address ] = account ;
@@ -53,24 +67,37 @@ export const FaucetForm: React.FC<Props> = ({ accounts, isTestnetLive }) => {
53
67
{ } as Record < string , Account >
54
68
) ;
55
69
56
- const [ account , setAccount ] = useState < Account > ( accounts [ 0 ] ) ;
70
+ const chain = chains . namada ;
71
+ const integration = new Namada ( chain ) ;
72
+ const [ account , setAccount ] = useState < Account > ( ) ;
73
+ const [ accountsSelectData , setAccountsSelectData ] = useState <
74
+ Option < string > [ ]
75
+ > ( [ ] ) ;
76
+ const [ target , setTarget ] = useState < string > ( ) ;
57
77
const [ tokenAddress , setTokenAddress ] = useState < string > ( ) ;
58
78
const [ amount , setAmount ] = useState < number | undefined > ( undefined ) ;
59
79
const [ error , setError ] = useState < string > ( ) ;
60
80
const [ status , setStatus ] = useState ( Status . Completed ) ;
61
81
const [ statusText , setStatusText ] = useState < string > ( ) ;
62
82
const [ responseDetails , setResponseDetails ] = useState < TransferResponse > ( ) ;
63
83
64
- const accountsSelectData = accounts . map ( ( { alias, address } ) => ( {
65
- label : `${ alias } - ${ shortenAddress ( address ) } ` ,
66
- value : address ,
67
- } ) ) ;
68
-
69
84
const powSolver : Worker = useMemo (
70
85
( ) => new Worker ( new URL ( "../workers/powWorker.ts" , import . meta. url ) ) ,
71
86
[ ]
72
87
) ;
73
88
89
+ useEffect ( ( ) => {
90
+ if ( accounts ) {
91
+ setAccountsSelectData (
92
+ accounts . map ( ( { alias, address } ) => ( {
93
+ label : `${ alias } - ${ shortenAddress ( address ) } ` ,
94
+ value : address ,
95
+ } ) )
96
+ ) ;
97
+ setAccount ( accounts [ 0 ] ) ;
98
+ }
99
+ } , [ accounts ] ) ;
100
+
74
101
useEffect ( ( ) => {
75
102
if ( tokens ?. NAM ) {
76
103
setTokenAddress ( tokens . NAM ) ;
@@ -81,7 +108,7 @@ export const FaucetForm: React.FC<Props> = ({ accounts, isTestnetLive }) => {
81
108
Boolean ( tokenAddress ) &&
82
109
Boolean ( amount ) &&
83
110
( amount || 0 ) <= withdrawLimit &&
84
- Boolean ( account ) &&
111
+ Boolean ( target ) &&
85
112
status !== Status . PendingPowSolution &&
86
113
status !== Status . PendingTransfer &&
87
114
typeof difficulty !== "undefined" &&
@@ -127,7 +154,7 @@ export const FaucetForm: React.FC<Props> = ({ accounts, isTestnetLive }) => {
127
154
async ( e : React . MouseEvent < HTMLButtonElement > ) => {
128
155
e . preventDefault ( ) ;
129
156
if (
130
- ! account ||
157
+ ! target ||
131
158
! amount ||
132
159
! tokenAddress ||
133
160
typeof difficulty === "undefined"
@@ -145,9 +172,9 @@ export const FaucetForm: React.FC<Props> = ({ accounts, isTestnetLive }) => {
145
172
return ;
146
173
}
147
174
148
- if ( ! account ) {
175
+ if ( ! target ) {
149
176
setStatus ( Status . Error ) ;
150
- setError ( "No account found !" ) ;
177
+ setError ( "No target specified !" ) ;
151
178
return ;
152
179
}
153
180
@@ -175,7 +202,7 @@ export const FaucetForm: React.FC<Props> = ({ accounts, isTestnetLive }) => {
175
202
tag,
176
203
challenge,
177
204
transfer : {
178
- target : account . address ,
205
+ target,
179
206
token : sanitizedToken ,
180
207
amount : amount * 1_000_000 ,
181
208
} ,
@@ -190,14 +217,59 @@ export const FaucetForm: React.FC<Props> = ({ accounts, isTestnetLive }) => {
190
217
[ account , tokenAddress , amount ]
191
218
) ;
192
219
220
+ useUntil (
221
+ {
222
+ predFn : async ( ) => Promise . resolve ( integration . detect ( ) ) ,
223
+ onSuccess : ( ) => {
224
+ setExtensionAttachStatus ( ExtensionAttachStatus . Installed ) ;
225
+ } ,
226
+ onFail : ( ) => {
227
+ setExtensionAttachStatus ( ExtensionAttachStatus . NotInstalled ) ;
228
+ } ,
229
+ } ,
230
+ { tries : 5 , ms : 300 } ,
231
+ [ integration ]
232
+ ) ;
233
+
234
+ const handleConnectExtensionClick = useCallback (
235
+ async ( e : React . MouseEvent < HTMLButtonElement > ) : Promise < void > => {
236
+ e . preventDefault ( ) ;
237
+ if ( integration ) {
238
+ try {
239
+ const isIntegrationDetected = integration . detect ( ) ;
240
+
241
+ if ( ! isIntegrationDetected ) {
242
+ throw new Error ( "Extension not installed!" ) ;
243
+ }
244
+
245
+ await integration . connect ( ) ;
246
+ const accounts = await integration . accounts ( ) ;
247
+ if ( accounts ) {
248
+ setAccounts ( accounts . filter ( ( account ) => ! account . isShielded ) ) ;
249
+ }
250
+ setIsExtensionConnected ( true ) ;
251
+ } catch ( e ) {
252
+ console . error ( e ) ;
253
+ }
254
+ }
255
+ } ,
256
+ [ integration ]
257
+ ) ;
258
+
259
+ useEffect ( ( ) => {
260
+ if ( account ) {
261
+ setTarget ( account . address ) ;
262
+ }
263
+ } , [ account ] ) ;
264
+
193
265
return (
194
266
< FaucetFormContainer >
195
267
< InputContainer >
196
- { accounts . length > 0 ?
268
+ { account && accounts . length > 0 ?
197
269
< Select
198
270
data = { accountsSelectData }
199
271
value = { account . address }
200
- label = "Account "
272
+ label = "Target "
201
273
onChange = { ( e ) => setAccount ( accountLookup [ e . target . value ] ) }
202
274
/>
203
275
: < div >
@@ -207,12 +279,29 @@ export const FaucetForm: React.FC<Props> = ({ accounts, isTestnetLive }) => {
207
279
}
208
280
</ InputContainer >
209
281
282
+ < InputContainer >
283
+ < Input
284
+ label = "Target Address"
285
+ value = { target }
286
+ onChange = { ( e ) => setTarget ( e . target . value ) }
287
+ autoFocus = { true }
288
+ />
289
+ </ InputContainer >
290
+
291
+ { extensionAttachStatus === ExtensionAttachStatus . Installed &&
292
+ ! isExtensionConnected && (
293
+ < InputContainer >
294
+ < ActionButton onClick = { handleConnectExtensionClick } >
295
+ Load Accounts from Extension
296
+ </ ActionButton >
297
+ </ InputContainer >
298
+ ) }
299
+
210
300
< InputContainer >
211
301
< Input
212
302
label = "Token Address (defaults to NAM)"
213
303
value = { tokenAddress }
214
304
onChange = { ( e ) => setTokenAddress ( e . target . value ) }
215
- autoFocus = { true }
216
305
/>
217
306
</ InputContainer >
218
307
0 commit comments