@@ -10,6 +10,7 @@ import {
1010 Input ,
1111 Radio ,
1212 RadioGroup ,
13+ Select ,
1314 Stack ,
1415 Text ,
1516 VStack ,
@@ -25,7 +26,7 @@ import {
2526 toHex ,
2627} from 'viem' ;
2728import { privateKeyToAccount } from 'viem/accounts' ;
28- import { baseSepolia } from 'viem/chains' ;
29+ import * as chains from 'viem/chains' ;
2930import { useConfig } from '../../context/ConfigContextProvider' ;
3031import { useEIP1193Provider } from '../../context/EIP1193ProviderContextProvider' ;
3132import { unsafe_generateOrLoadPrivateKey } from '../../utils/unsafe_generateOrLoadPrivateKey' ;
@@ -38,6 +39,7 @@ interface WalletConnectResponse {
3839 address : string ;
3940 capabilities ?: Record < string , unknown > ;
4041 } > ;
42+ chainIds ?: string [ ] ;
4143}
4244
4345const LOCAL_STORAGE_KEY = 'ba-playground:config' ;
@@ -53,6 +55,8 @@ export default function AutoSubAccount() {
5355 siwe : false ,
5456 addSubAccount : false ,
5557 } ) ;
58+ const [ availableChains , setAvailableChains ] = useState < string [ ] > ( [ ] ) ;
59+ const [ currentChainId , setCurrentChainId ] = useState < string > ( '' ) ;
5660 const { subAccountsConfig, setSubAccountsConfig, config, setConfig } = useConfig ( ) ;
5761 const { provider } = useEIP1193Provider ( ) ;
5862
@@ -125,6 +129,31 @@ export default function AutoSubAccount() {
125129 setSubAccountsConfig ( ( prev ) => ( { ...prev , toOwnerAccount : getSigner } ) ) ;
126130 } , [ signerType , setSubAccountsConfig ] ) ;
127131
132+ const getPublicClient = async ( ) => {
133+ if ( ! provider ) throw new Error ( 'Provider not initialized' ) ;
134+
135+ // Get current chain ID if not already set
136+ const chainId =
137+ currentChainId ||
138+ ( ( await provider . request ( {
139+ method : 'eth_chainId' ,
140+ params : [ ] ,
141+ } ) ) as string ) ;
142+
143+ const chainIdDecimal = Number . parseInt ( chainId , 16 ) ;
144+
145+ // Find the chain in viem/chains
146+ const chain = Object . values ( chains ) . find ( ( c ) => c . id === chainIdDecimal ) ;
147+ if ( ! chain ) {
148+ throw new Error ( `Chain with ID ${ chainIdDecimal } (${ chainId } ) not found in viem/chains` ) ;
149+ }
150+
151+ return createPublicClient ( {
152+ chain,
153+ transport : http ( ) ,
154+ } ) ;
155+ } ;
156+
128157 const handleRequestAccounts = async ( ) => {
129158 if ( ! provider ) return ;
130159
@@ -191,10 +220,7 @@ export default function AutoSubAccount() {
191220 params : [ hexMessage , accounts [ 0 ] ] ,
192221 } ) ;
193222
194- const publicClient = createPublicClient ( {
195- chain : baseSepolia ,
196- transport : http ( ) ,
197- } ) ;
223+ const publicClient = await getPublicClient ( ) ;
198224
199225 const isValid = await publicClient . verifyMessage ( {
200226 address : accounts [ 0 ] as `0x${string } `,
@@ -213,6 +239,9 @@ export default function AutoSubAccount() {
213239 if ( ! provider || ! accounts . length ) return ;
214240
215241 try {
242+ const publicClient = await getPublicClient ( ) ;
243+ const chainIdDecimal = publicClient . chain . id ;
244+
216245 const typedData = {
217246 types : {
218247 EIP712Domain : [
@@ -230,7 +259,7 @@ export default function AutoSubAccount() {
230259 domain : {
231260 name : 'Test Domain' ,
232261 version : '1' ,
233- chainId : baseSepolia . id ,
262+ chainId : chainIdDecimal ,
234263 verifyingContract : '0x0000000000000000000000000000000000000000' as `0x${string } `,
235264 } ,
236265 message : {
@@ -244,11 +273,6 @@ export default function AutoSubAccount() {
244273 params : [ accounts [ 0 ] , JSON . stringify ( typedData ) ] ,
245274 } ) ;
246275
247- const publicClient = createPublicClient ( {
248- chain : baseSepolia ,
249- transport : http ( ) ,
250- } ) ;
251-
252276 const isValid = await publicClient . verifyTypedData ( {
253277 address : accounts [ 0 ] as `0x${string } `,
254278 domain : typedData . domain ,
@@ -311,14 +335,88 @@ export default function AutoSubAccount() {
311335 method : 'wallet_connect' ,
312336 params,
313337 } ) ) as WalletConnectResponse ;
314- setLastResult ( JSON . stringify ( response , null , 2 ) ) ;
338+
339+ // Verify SIWE signature if present
340+ let verificationResult = '' ;
341+ if ( response . accounts && response . accounts . length > 0 ) {
342+ const account = response . accounts [ 0 ] ;
343+ if ( account . capabilities && 'signInWithEthereum' in account . capabilities ) {
344+ const siweCapability = account . capabilities . signInWithEthereum as {
345+ message : string ;
346+ signature : string ;
347+ } ;
348+
349+ try {
350+ // Parse chain ID from SIWE message
351+ const chainIdMatch = siweCapability . message . match ( / C h a i n I D : ( \d + ) / ) ;
352+ if ( ! chainIdMatch ) {
353+ throw new Error ( 'Could not extract chain ID from SIWE message' ) ;
354+ }
355+ const siweChainId = Number . parseInt ( chainIdMatch [ 1 ] , 10 ) ;
356+
357+ // Find the chain in viem/chains
358+ const chain = Object . values ( chains ) . find ( ( c ) => c . id === siweChainId ) ;
359+ if ( ! chain ) {
360+ throw new Error ( `Chain with ID ${ siweChainId } not found in viem/chains` ) ;
361+ }
362+
363+ // Create a public client for the SIWE chain
364+ const publicClient = createPublicClient ( {
365+ chain,
366+ transport : http ( ) ,
367+ } ) ;
368+
369+ // Verify the SIWE signature
370+ const isValid = await publicClient . verifyMessage ( {
371+ address : account . address as `0x${string } `,
372+ message : siweCapability . message ,
373+ signature : siweCapability . signature as `0x${string } `,
374+ } ) ;
375+
376+ verificationResult = `SIWE Signature Verification: ${ isValid ? '✓ VALID' : '✗ INVALID' } (Chain ID: ${ siweChainId } )\n\n` ;
377+ } catch ( verifyError ) {
378+ console . error ( 'SIWE verification error:' , verifyError ) ;
379+ verificationResult = `SIWE Signature Verification: ERROR - ${ verifyError instanceof Error ? verifyError . message : String ( verifyError ) } \n\n` ;
380+ }
381+ }
382+ }
383+
384+ setLastResult ( verificationResult + JSON . stringify ( response , null , 2 ) ) ;
385+
386+ // Extract available chains from response
387+ if ( response . chainIds && response . chainIds . length > 0 ) {
388+ setAvailableChains ( response . chainIds ) ;
389+ }
315390
316391 // Call eth_accounts to get and set the accounts after successful connection
317392 const accountsResponse = await provider . request ( {
318393 method : 'eth_accounts' ,
319394 params : [ ] ,
320395 } ) ;
321396 setAccounts ( accountsResponse as string [ ] ) ;
397+
398+ // Get current chain ID
399+ const chainId = await provider . request ( {
400+ method : 'eth_chainId' ,
401+ params : [ ] ,
402+ } ) ;
403+ setCurrentChainId ( chainId as string ) ;
404+ } catch ( e ) {
405+ console . error ( 'error' , e ) ;
406+ setLastResult ( JSON . stringify ( e , null , 2 ) ) ;
407+ }
408+ } ;
409+
410+ const handleSwitchChain = async ( chainId : string ) => {
411+ if ( ! provider ) return ;
412+
413+ try {
414+ await provider . request ( {
415+ method : 'wallet_switchEthereumChain' ,
416+ params : [ { chainId } ] ,
417+ } ) ;
418+ setCurrentChainId ( chainId ) ;
419+ setLastResult ( `Switched to chain: ${ chainId } ` ) ;
322420 } catch ( e ) {
323421 console . error ( 'error' , e ) ;
324422 setLastResult ( JSON . stringify ( e , null , 2 ) ) ;
@@ -347,13 +445,21 @@ export default function AutoSubAccount() {
347445 ] ,
348446 } ) ;
349447 } else {
448+ // Get current chain ID if not already set
449+ const chainId =
450+ currentChainId ||
451+ ( ( await provider . request ( {
452+ method : 'eth_chainId' ,
453+ params : [ ] ,
454+ } ) ) as string ) ;
455+
350456 // wallet_sendCalls with paymaster support
351457 response = await provider . request ( {
352458 method : 'wallet_sendCalls' ,
353459 params : [
354460 {
355461 version : '1.0' ,
356- chainId : numberToHex ( baseSepolia . id ) ,
462+ chainId : chainId ,
357463 from : accounts [ 0 ] ,
358464 calls : [
359465 {
@@ -381,11 +487,31 @@ export default function AutoSubAccount() {
381487 } ;
382488
383489 const handleUsdcSend = async ( amount : string ) => {
384- if ( ! provider || accounts . length < 2 ) return ;
490+ if ( ! provider || ! accounts . length ) return ;
385491
386492 try {
387493 setSendingUsdcAmounts ( ( prev ) => ( { ...prev , [ amount ] : true } ) ) ;
388- const usdcAddress = '0x036cbd53842c5426634e7929541ec2318f3dcf7e' ;
494+
495+ // Get current chain ID if not already set
496+ const chainId =
497+ currentChainId ||
498+ ( ( await provider . request ( {
499+ method : 'eth_chainId' ,
500+ params : [ ] ,
501+ } ) ) as string ) ;
502+ const chainIdDecimal = Number . parseInt ( chainId , 16 ) ;
503+
504+ // USDC contract addresses by chain ID
505+ const usdcAddresses : Record < number , string > = {
506+ 8453 : '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' , // Base mainnet
507+ 84532 : '0x036cbd53842c5426634e7929541ec2318f3dcf7e' , // Base Sepolia
508+ } ;
509+
510+ const usdcAddress = usdcAddresses [ chainIdDecimal ] ;
511+ if ( ! usdcAddress ) {
512+ throw new Error ( `USDC not supported on chain ID ${ chainIdDecimal } ` ) ;
513+ }
514+
389515 const to = '0x8d25687829d6b85d9e0020b8c89e3ca24de20a89' ;
390516 const value = parseUnits ( amount , 6 ) ; // USDC has 6 decimals
391517
@@ -406,17 +532,44 @@ export default function AutoSubAccount() {
406532 args : [ to , value ] ,
407533 } ) ;
408534
409- const response = await provider . request ( {
410- method : 'eth_sendTransaction' ,
411- params : [
412- {
413- from : accounts [ 0 ] ,
414- to : usdcAddress ,
415- value : '0x0' ,
416- data,
417- } ,
418- ] ,
419- } ) ;
535+ let response ;
536+ if ( sendMethod === 'eth_sendTransaction' ) {
537+ response = await provider . request ( {
538+ method : 'eth_sendTransaction' ,
539+ params : [
540+ {
541+ from : accounts [ 0 ] ,
542+ to : usdcAddress ,
543+ value : '0x0' ,
544+ data,
545+ } ,
546+ ] ,
547+ } ) ;
548+ } else {
549+ // wallet_sendCalls with paymaster support
550+ response = await provider . request ( {
551+ method : 'wallet_sendCalls' ,
552+ params : [
553+ {
554+ version : '1.0' ,
555+ chainId : chainId ,
556+ from : accounts [ 0 ] ,
557+ calls : [
558+ {
559+ to : usdcAddress ,
560+ value : '0x0' ,
561+ data,
562+ } ,
563+ ] ,
564+ capabilities : {
565+ paymasterService : {
566+ url : 'https://api.developer.coinbase.com/rpc/v1/base-sepolia/S-fOd2n2Oi4fl4e1Crm83XeDXZ7tkg8O' ,
567+ } ,
568+ } ,
569+ } ,
570+ ] ,
571+ } ) ;
572+ }
420573 setLastResult ( JSON . stringify ( response , null , 2 ) ) ;
421574 } catch ( e ) {
422575 console . error ( 'error' , e ) ;
@@ -598,6 +751,31 @@ export default function AutoSubAccount() {
598751 </ VStack >
599752 </ Box >
600753 ) }
754+ { availableChains . length > 0 && (
755+ < FormControl >
756+ < FormLabel > Available Chains</ FormLabel >
757+ < Select
758+ value = { currentChainId }
759+ onChange = { ( e ) => handleSwitchChain ( e . target . value ) }
760+ placeholder = "Select chain"
761+ >
762+ { availableChains . map ( ( chainId ) => (
763+ < option key = { chainId } value = { chainId } >
764+ Chain ID: { chainId } { ' ' }
765+ { Number . parseInt ( chainId , 16 ) ? `(${ Number . parseInt ( chainId , 16 ) } )` : '' }
766+ </ option >
767+ ) ) }
768+ </ Select >
769+ { currentChainId && (
770+ < Text mt = { 2 } fontSize = "sm" color = "gray.600" _dark = { { color : 'gray.400' } } >
771+ Current Chain: { currentChainId } { ' ' }
772+ { Number . parseInt ( currentChainId , 16 )
773+ ? `(${ Number . parseInt ( currentChainId , 16 ) } )`
774+ : '' }
775+ </ Text >
776+ ) }
777+ </ FormControl >
778+ ) }
601779 < Box w = "full" textAlign = "left" fontSize = "lg" fontWeight = "bold" >
602780 RPCs
603781 </ Box >
@@ -741,12 +919,12 @@ export default function AutoSubAccount() {
741919 Send USDC
742920 </ Box >
743921 < HStack w = "full" spacing = { 4 } >
744- { [ '0.01' , '0.1' , '1' ] . map ( ( amount ) => (
922+ { [ '0.001' , '0. 01', '0.1' , '1' ] . map ( ( amount ) => (
745923 < Button
746924 key = { amount }
747925 flex = { 1 }
748926 onClick = { ( ) => handleUsdcSend ( amount ) }
749- isDisabled = { accounts . length < 2 || sendingUsdcAmounts [ amount ] }
927+ isDisabled = { ! accounts . length || sendingUsdcAmounts [ amount ] }
750928 isLoading = { sendingUsdcAmounts [ amount ] }
751929 loadingText = "Sending..."
752930 size = "lg"
0 commit comments