diff --git a/apps/laboratory/src/components/AddTransactionModal.tsx b/apps/laboratory/src/components/AddTransactionModal.tsx index cf4fb55555..81a50f7292 100644 --- a/apps/laboratory/src/components/AddTransactionModal.tsx +++ b/apps/laboratory/src/components/AddTransactionModal.tsx @@ -3,6 +3,9 @@ import { useState } from 'react' import { Box, Button, + FormControl, + FormHelperText, + FormLabel, Input, Modal, ModalBody, @@ -10,7 +13,8 @@ import { ModalContent, ModalFooter, ModalHeader, - ModalOverlay + ModalOverlay, + Tooltip } from '@chakra-ui/react' import { ethers } from 'ethers' @@ -19,12 +23,24 @@ import { useChakraToast } from './Toast' type IAddTransactionModalProps = { isOpen: boolean onClose: () => void - onSubmit: (params: { eth: string; to: string }) => void + onSubmit: (params: { eth: string; to: string; data?: string }) => void } export function AddTransactionModal({ isOpen, onClose, onSubmit }: IAddTransactionModalProps) { const toast = useChakraToast() const [eth, setEth] = useState(0) const [to, setTo] = useState('') + const [data, setData] = useState('') + const [mode, setMode] = useState<'simple' | 'advanced'>('simple') + + function isValidHex(value: string): boolean { + // Empty is valid (optional field) + if (!value) { + return true + } + + return /^0x[0-9a-fA-F]*$/u.test(value) + } + function onAddTransaction() { if (!ethers.isAddress(to)) { toast({ @@ -44,7 +60,16 @@ export function AddTransactionModal({ isOpen, onClose, onSubmit }: IAddTransacti return } - onSubmit({ eth: eth.toString(), to }) + if (data && !isValidHex(data)) { + toast({ + title: 'Error', + description: 'Invalid hex data format. Must start with 0x', + type: 'error' + }) + + return + } + onSubmit({ eth: eth.toString(), to, data: data || undefined }) reset() onClose() } @@ -52,6 +77,8 @@ export function AddTransactionModal({ isOpen, onClose, onSubmit }: IAddTransacti function reset() { setEth(0) setTo('') + setData('') + setMode('simple') } return ( @@ -64,21 +91,66 @@ export function AddTransactionModal({ isOpen, onClose, onSubmit }: IAddTransacti Transactions will be batched and sent together to your wallet for approval - + + + + + + Amount ETH + + setEth(event.target.valueAsNumber)} /> - - - + Amount in ETH (e.g., 0.001) + + + + + To Address + + setTo(event.target.value)} /> - + Recipient Ethereum address + + {mode === 'advanced' && ( + + + + Data (Optional) + + + setData(event.target.value)} + isInvalid={data !== '' && !isValidHex(data)} + /> + + Hex-encoded transaction data (must start with 0x). Leave empty for simple + transfers. + + + )} - - - - - - - - - - + + {customTx && ( + + )} + + + + + + + + + + + + + ) : ( Feature not enabled on Ethereum Mainnet diff --git a/apps/laboratory/src/components/Wagmi/WagmiSendCallsTest.tsx b/apps/laboratory/src/components/Wagmi/WagmiSendCallsTest.tsx index e8032c6761..c193e4e48b 100644 --- a/apps/laboratory/src/components/Wagmi/WagmiSendCallsTest.tsx +++ b/apps/laboratory/src/components/Wagmi/WagmiSendCallsTest.tsx @@ -1,13 +1,29 @@ import { useCallback, useState } from 'react' -import { Button, Heading, Stack, Text } from '@chakra-ui/react' +import { AddIcon, DeleteIcon } from '@chakra-ui/icons' +import { + Box, + Button, + Card, + CardBody, + Heading, + Link, + Spacer, + Stack, + Stat, + StatHelpText, + StatLabel, + StatNumber, + Text +} from '@chakra-ui/react' import { type WalletCapabilities, parseGwei, toHex } from 'viem' import { useAccount } from 'wagmi' import { useSendCalls } from 'wagmi/experimental' -import type { Address } from '@reown/appkit-common' +import type { Address, Hex } from '@reown/appkit-common' import { useAppKitAccount } from '@reown/appkit/react' +import { AddTransactionModal } from '@/src/components/AddTransactionModal' import { useChakraToast } from '@/src/components/Toast' import { useCapabilities } from '@/src/hooks/useCapabilities' import { useWagmiAvailableCapabilities } from '@/src/hooks/useWagmiActiveCapabilities' @@ -76,6 +92,10 @@ export function WagmiSendCallsTest({ capabilities }: { capabilities: WalletCapab function ConnectedTestContent() { const toast = useChakraToast() const [lastCallsBatchId, setLastCallsBatchId] = useState(null) + const [transactionsToBatch, setTransactionsToBatch] = useState< + { to: Address; value: bigint; data?: Hex }[] + >([]) + const [isModalOpen, setIsModalOpen] = useState(false) const { sendCalls, isPending: isLoading } = useSendCalls({ mutation: { @@ -86,6 +106,7 @@ function ConnectedTestContent() { description: hash.id, type: 'success' }) + setTransactionsToBatch([]) }, onError: () => { toast({ @@ -96,27 +117,105 @@ function ConnectedTestContent() { } } }) + const onSendCalls = useCallback(() => { + const calls = transactionsToBatch.length ? transactionsToBatch : [TEST_TX_1, TEST_TX_2] sendCalls({ - calls: [TEST_TX_1, TEST_TX_2] + calls }) - }, [sendCalls]) + }, [sendCalls, transactionsToBatch]) + + function onSubmit(args: { to: string; eth: string; data?: string }) { + setLastCallsBatchId(null) + setTransactionsToBatch(prev => [ + ...prev, + { + to: args.to as Address, + value: parseGwei(args.eth), + data: args.data as Hex | undefined + } + ]) + } + + function onClose() { + setIsModalOpen(false) + } + + function onAddTransactionButtonClick() { + setIsModalOpen(true) + } + + function onRemoveTransaction(index: number) { + setTransactionsToBatch(prev => prev.filter((_, i) => i !== index)) + } return ( - - - <> - Last batch call ID: - {lastCallsBatchId} - - + <> + + + + {transactionsToBatch.length ? ( + transactionsToBatch.map((tx, index) => ( + + + + + + ({index + 1}) Sending + onRemoveTransaction(index)} + /> + + {Number(tx.value) / 1000000000} ETH + to {tx.to} + {tx.data && data: {tx.data}} + + + + + + )) + ) : ( + + )} + + + + + + + {transactionsToBatch.length ? ( + + ) : null} + + + + + {lastCallsBatchId && ( + <> + Last batch call ID: + {lastCallsBatchId} + + )} + ) } diff --git a/apps/laboratory/src/components/Wagmi/WagmiTransactionTest.tsx b/apps/laboratory/src/components/Wagmi/WagmiTransactionTest.tsx index e6628771e1..fba44546c2 100644 --- a/apps/laboratory/src/components/Wagmi/WagmiTransactionTest.tsx +++ b/apps/laboratory/src/components/Wagmi/WagmiTransactionTest.tsx @@ -1,10 +1,12 @@ import { useCallback, useState } from 'react' +import { AddIcon } from '@chakra-ui/icons' import { Button, Link, Spacer, Stack, Text } from '@chakra-ui/react' -import { type Address, parseGwei } from 'viem' +import { type Address, type Hex, parseGwei } from 'viem' import { useAccount, useEstimateGas, useSendTransaction } from 'wagmi' import { mainnet } from 'wagmi/chains' +import { AddTransactionModal } from '@/src/components/AddTransactionModal' import { useChakraToast } from '@/src/components/Toast' import { vitalikEthAddress } from '@/src/utils/DataUtil' @@ -27,8 +29,17 @@ export function WagmiTransactionTest() { function AvailableTestContent() { const toast = useChakraToast() + const [customTx, setCustomTx] = useState<{ + to: Address + value: bigint + data?: Hex + } | null>(null) + const [isModalOpen, setIsModalOpen] = useState(false) + + const txToSend = customTx || TEST_TX + const { refetch: estimateGas, isFetching: isEstimateGasFetching } = useEstimateGas({ - ...TEST_TX, + ...txToSend, query: { enabled: false } @@ -68,37 +79,77 @@ function AvailableTestContent() { } else { setLoading(true) sendTransaction({ - ...TEST_TX, + ...txToSend, gas }) } - }, [sendTransaction, estimateGas]) + }, [sendTransaction, estimateGas, txToSend]) + + function onConfigureTransaction(params: { eth: string; to: string; data?: string }) { + setCustomTx({ + to: params.to as Address, + value: parseGwei(params.eth), + data: params.data as Hex | undefined + }) + setIsModalOpen(false) + } + + function onCloseModal() { + setIsModalOpen(false) + } return ( - - - - - - - - - - - - + + {customTx && ( + + )} + + + + + + + + + + + + + ) }