@@ -11,14 +11,15 @@ import {
1111} from "viem" ;
1212import { waitForTransactionReceipt } from "viem/actions" ;
1313
14- import { api , applyChainId , isOk , renderJSON } from "./utils/helpers.ts" ;
14+ import { api , applyChainId , isOk , renderJSON , renderMaybeParsedJSON } from "./utils/helpers.ts" ;
1515import type {
1616 ApiErr ,
1717 ApiOk ,
1818 EIP1193 ,
1919 EIP6963AnnounceProviderEvent ,
2020 EIP6963ProviderInfo ,
2121 PendingAny ,
22+ PendingSigning ,
2223} from "./utils/types.ts" ;
2324
2425export function App ( ) {
@@ -33,17 +34,21 @@ export function App() {
3334 ) ;
3435
3536 const [ confirmed , setConfirmed ] = useState < boolean > ( false ) ;
36- const [ pending , setPending ] = useState < PendingAny | null > ( null ) ;
37+ const [ pendingTx , setPendingTx ] = useState < PendingAny | null > ( null ) ;
38+ const [ pendingSigning , setPendingSigning ] = useState < PendingSigning | null > ( null ) ;
3739 const [ selectedUuid , setSelectedUuid ] = useState < string | null > ( null ) ;
3840 const selected = providers . find ( ( p ) => p . info . uuid === selectedUuid ) ?? null ;
3941
4042 const [ account , setAccount ] = useState < Address > ( ) ;
4143 const [ chainId , setChainId ] = useState < number > ( ) ;
4244 const [ chain , setChain ] = useState < Chain > ( ) ;
45+
4346 const [ lastTxReceipt , setLastTxReceipt ] = useState < TransactionReceipt | null > ( null ) ;
4447 const [ lastTxHash , setLastTxHash ] = useState < string | null > ( null ) ;
48+ const [ lastSignature , setLastSignature ] = useState < string | null > ( null ) ;
4549
46- const pollIntervalRef = useRef < number | null > ( null ) ;
50+ const pollTxRef = useRef < number | null > ( null ) ;
51+ const pollSigningRef = useRef < number | null > ( null ) ;
4752 const prevSelectedUuidRef = useRef < string | null > ( null ) ;
4853
4954 const connect = async ( ) => {
@@ -79,9 +84,70 @@ export function App() {
7984 setConfirmed ( true ) ;
8085 } ;
8186
87+ const signCurrentMessage = async ( ) => {
88+ if ( ! selected || ! pendingSigning ) return ;
89+
90+ const { id, signType, request } = pendingSigning ;
91+ const signer = request . address ;
92+ const msg = request . message ;
93+
94+ try {
95+ let signature : string ;
96+
97+ switch ( signType ) {
98+ case "PersonalSign" :
99+ // Standard message signing
100+ signature = ( await selected . provider . request ( {
101+ method : "personal_sign" ,
102+ params : [ msg , signer ] ,
103+ } ) ) as string ;
104+ break ;
105+
106+ case "SignTypedDataV4" :
107+ // EIP-712 typed data signing
108+ signature = ( await selected . provider . request ( {
109+ method : "eth_signTypedData_v4" ,
110+ params : [ signer , msg ] ,
111+ } ) ) as string ;
112+ break ;
113+
114+ default :
115+ throw new Error ( `Unsupported signType: ${ signType } ` ) ;
116+ }
117+
118+ await api ( "/api/signing/response" , "POST" , {
119+ id,
120+ signature,
121+ error : null ,
122+ } ) ;
123+
124+ setLastSignature ( signature ) ;
125+ setPendingSigning ( null ) ;
126+ } catch ( e : unknown ) {
127+ const errMsg =
128+ typeof e === "object" &&
129+ e &&
130+ "message" in e &&
131+ typeof ( e as { message ?: unknown } ) . message === "string"
132+ ? ( e as { message : string } ) . message
133+ : String ( e ) ;
134+
135+ try {
136+ await api ( "/api/signing/response" , "POST" , {
137+ id,
138+ signature : null ,
139+ error : errMsg ,
140+ } ) ;
141+ } catch { }
142+
143+ setLastSignature ( null ) ;
144+ setPendingSigning ( null ) ;
145+ }
146+ } ;
147+
82148 // Sign and send the current pending transaction.
83- const signAndSendCurrent = async ( ) => {
84- if ( ! selected || ! pending ?. request ) return ;
149+ const signAndSendCurrentTx = async ( ) => {
150+ if ( ! selected || ! pendingTx ?. request ) return ;
85151
86152 const walletClient = createWalletClient ( {
87153 transport : custom ( selected . provider ) ,
@@ -91,18 +157,14 @@ export function App() {
91157 try {
92158 const hash = ( await selected . provider . request ( {
93159 method : "eth_sendTransaction" ,
94- params : [ pending . request ] ,
160+ params : [ pendingTx . request ] ,
95161 } ) ) as `0x${string } `;
96162 setLastTxHash ( hash ) ;
97163
98- await api ( "/api/transaction/response" , "POST" , { id : pending . id , hash, error : null } ) ;
99-
100- console . log ( "sent tx:" , hash ) ;
164+ await api ( "/api/transaction/response" , "POST" , { id : pendingTx . id , hash, error : null } ) ;
101165
102166 const receipt = await waitForTransactionReceipt ( walletClient , { hash } ) ;
103167 setLastTxReceipt ( receipt ) ;
104-
105- console . log ( "tx receipt:" , receipt ) ;
106168 } catch ( e : unknown ) {
107169 const msg =
108170 typeof e === "object" &&
@@ -116,7 +178,7 @@ export function App() {
116178
117179 try {
118180 await api ( "/api/transaction/response" , "POST" , {
119- id : pending . id ,
181+ id : pendingTx . id ,
120182 hash : null ,
121183 error : msg ,
122184 } ) ;
@@ -126,12 +188,18 @@ export function App() {
126188
127189 // Reset all client state.
128190 const resetClientState = useCallback ( ( ) => {
129- if ( pollIntervalRef . current ) {
130- window . clearInterval ( pollIntervalRef . current ) ;
131- pollIntervalRef . current = null ;
191+ if ( pollTxRef . current ) {
192+ window . clearInterval ( pollTxRef . current ) ;
193+ pollTxRef . current = null ;
194+ }
195+
196+ if ( pollSigningRef . current ) {
197+ window . clearInterval ( pollSigningRef . current ) ;
198+ pollSigningRef . current = null ;
132199 }
133200
134- setPending ( null ) ;
201+ setPendingTx ( null ) ;
202+ setPendingSigning ( null ) ;
135203 setLastTxHash ( null ) ;
136204 setLastTxReceipt ( null ) ;
137205
@@ -205,9 +273,9 @@ export function App() {
205273 } , [ selected , confirmed ] ) ;
206274
207275 // Poll for pending transaction requests.
208- // Stops when one is found.
276+ // Stops when one is found or when a pending signing request is found .
209277 useEffect ( ( ) => {
210- if ( ! confirmed || pending ) return ;
278+ if ( ! confirmed || pendingTx || pendingSigning ) return ;
211279
212280 let active = true ;
213281
@@ -218,27 +286,60 @@ export function App() {
218286 if ( isOk ( resp ) ) {
219287 window . clearInterval ( id ) ;
220288 if ( active ) {
221- setPending ( resp . data ) ;
289+ setPendingTx ( resp . data ) ;
222290 }
223291 }
224292 } catch { }
225293 } , 1000 ) ;
226294
227- pollIntervalRef . current = id ;
295+ pollTxRef . current = id ;
228296
229297 return ( ) => {
230298 active = false ;
231299 window . clearInterval ( id ) ;
232- if ( pollIntervalRef . current === id ) {
233- pollIntervalRef . current = null ;
300+ if ( pollTxRef . current === id ) {
301+ pollTxRef . current = null ;
234302 }
235303 } ;
236- } , [ confirmed , pending ] ) ;
304+ } , [ confirmed , pendingTx , pendingSigning ] ) ;
305+
306+ // Poll for pending signing requests.
307+ // Stops when one is found or when a pending transaction request is found.
308+ useEffect ( ( ) => {
309+ if ( ! confirmed || pendingSigning || pendingTx ) return ;
310+
311+ let active = true ;
312+
313+ const id = window . setInterval ( async ( ) => {
314+ if ( ! active ) return ;
315+ try {
316+ const resp = await api < ApiOk < PendingSigning > | ApiErr > ( "/api/signing/request" ) ;
317+ if ( isOk ( resp ) ) {
318+ window . clearInterval ( id ) ;
319+ if ( active ) {
320+ setPendingSigning ( resp . data ) ;
321+ }
322+ }
323+ } catch { }
324+ } , 1000 ) ;
325+
326+ pollSigningRef . current = id ;
327+
328+ return ( ) => {
329+ active = false ;
330+ window . clearInterval ( id ) ;
331+ if ( pollSigningRef . current === id ) {
332+ pollSigningRef . current = null ;
333+ }
334+ } ;
335+ } , [ confirmed , pendingSigning , pendingTx ] ) ;
237336
238337 return (
239338 < div className = "wrapper" >
240339 < div className = "container" >
241- < div className = "notice" > Browser wallet is still in early development. Use with caution!</ div >
340+ < div className = "notice" >
341+ Browser wallet is still in early development. Use with caution!
342+ </ div >
242343
243344 < img className = "banner" src = "banner.png" alt = "Foundry Browser Wallet" />
244345
@@ -294,12 +395,42 @@ rpc: ${chain?.rpcUrls?.default?.http?.[0] ?? chain?.rpcUrls?.public?.http?.[
294395 </ >
295396 ) }
296397
297- { selected && account && confirmed && ! lastTxHash && (
398+ { selected &&
399+ account &&
400+ confirmed &&
401+ ! pendingTx &&
402+ ! pendingSigning &&
403+ ! lastTxHash &&
404+ ! lastSignature && (
405+ < >
406+ < div className = "section-title" > Transaction To Sign</ div >
407+ < div className = "box" >
408+ < pre > No pending transaction or signing request</ pre >
409+ </ div >
410+ </ >
411+ ) }
412+
413+ { selected && account && confirmed && ! lastTxHash && pendingTx && (
414+ < >
415+ < div className = "section-title" > Transaction to Sign & Send </ div >
416+ < div className = "box" >
417+ < pre > { renderJSON ( pendingTx . request ) } </ pre >
418+ </ div >
419+ < button type = "button" className = "wallet-send" onClick = { signAndSendCurrentTx } >
420+ Sign & Send
421+ </ button >
422+ </ >
423+ ) }
424+
425+ { selected && account && confirmed && ! pendingTx && pendingSigning && (
298426 < >
299- < div className = "section-title" > To Sign</ div >
427+ < div className = "section-title" > Message / Data to Sign</ div >
300428 < div className = "box" >
301- < pre > { pending ? renderJSON ( pending ) : "No pending transaction" } </ pre >
429+ < pre > { renderMaybeParsedJSON ( pendingSigning . request ) } </ pre >
302430 </ div >
431+ < button type = "button" className = "wallet-send" onClick = { signCurrentMessage } >
432+ Sign
433+ </ button >
303434 </ >
304435 ) }
305436
@@ -317,10 +448,11 @@ rpc: ${chain?.rpcUrls?.default?.http?.[0] ?? chain?.rpcUrls?.public?.http?.[
317448 </ >
318449 ) }
319450
320- { selected && account && pending && confirmed && ! lastTxHash && (
321- < button type = "button" className = "wallet-send" onClick = { signAndSendCurrent } >
322- Sign & Send
323- </ button >
451+ { selected && account && confirmed && lastSignature && (
452+ < >
453+ < div className = "section-title" > Signature Result</ div >
454+ < pre className = "box" > { lastSignature } </ pre >
455+ </ >
324456 ) }
325457 </ div >
326458 </ div >
0 commit comments