Skip to content

Commit

Permalink
feat: custom 5792 transactions (#2666)
Browse files Browse the repository at this point in the history
  • Loading branch information
ganchoradkov authored Oct 2, 2024
1 parent 0de5030 commit bc148e3
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 22 deletions.
93 changes: 93 additions & 0 deletions apps/laboratory/src/components/AddTransactionModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import {
Box,
Button,
Input,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay
} from '@chakra-ui/react'
import { useState } from 'react'
import { ethers } from 'ethers'
import { useChakraToast } from './Toast'

type IAddTransactionModalProps = {
isOpen: boolean
onClose: () => void
onSubmit: (params: { eth: string; to: string }) => void
}
export function AddTransactionModal({ isOpen, onClose, onSubmit }: IAddTransactionModalProps) {
const toast = useChakraToast()
const [eth, setEth] = useState(0)
const [to, setTo] = useState('')
function onAddTransaction() {
if (!ethers.isAddress(to)) {
toast({
title: 'Error',
description: 'Invalid address',
type: 'error'
})

return
}
if (!eth || isNaN(eth)) {
toast({
title: 'Error',
description: 'Invalid ETH amount',
type: 'error'
})

return
}
onSubmit({ eth: eth.toString(), to })
reset()
onClose()
}

function reset() {
setEth(0)
setTo('')
}

return (
<>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Add a transaction</ModalHeader>
<ModalCloseButton />
<ModalBody>
Transactions will be batched and sent together to your wallet for approval
<Box mt={4}>
<label>Amount ETH</label>
<Input
placeholder="0.001"
type="number"
onChange={event => setEth(event.target.valueAsNumber)}
/>
</Box>
<Box mt={4}>
<label>To</label>
<Input
placeholder="0x0..."
type="text"
onChange={event => setTo(event.target.value)}
/>
</Box>
</ModalBody>
<ModalFooter>
<Button variant="ghost" mr={3} onClick={onClose}>
Cancel
</Button>
<Button colorScheme="blue" onClick={onAddTransaction}>
Add
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
)
}
5 changes: 3 additions & 2 deletions apps/laboratory/src/components/Ethers/Ethers5Tests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { EthersSendCallsWithPaymasterServiceTest } from './EthersSendCallsWithPa

export function Ethers5Tests() {
const [ready, setReady] = React.useState(false)
const [callsHash, setCallsHash] = React.useState<string>('')
const { isConnected } = useAppKitAccount()

React.useEffect(() => {
Expand Down Expand Up @@ -59,13 +60,13 @@ export function Ethers5Tests() {
<Heading size="xs" textTransform="uppercase" pb="2">
Send Calls (Atomic Batch)
</Heading>
<EthersSendCallsTest />
<EthersSendCallsTest onCallsHash={setCallsHash} />
</Box>
<Box>
<Heading size="xs" textTransform="uppercase" pb="2">
Get Calls Status
</Heading>
<EthersGetCallsStatusTest />
<EthersGetCallsStatusTest callsHash={callsHash} />
</Box>
<Box>
<Heading size="xs" textTransform="uppercase" pb="2">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Button, Stack, Text, Input } from '@chakra-ui/react'
import { useState } from 'react'
import { useEffect, useState } from 'react'
import {
useAppKitAccount,
useAppKitNetwork,
Expand All @@ -13,15 +13,19 @@ import { W3mFrameProvider } from '@reown/appkit-wallet'
import { type GetCallsStatusParams } from '../../types/EIP5792'
import { EIP_5792_RPC_METHODS } from '../../utils/EIP5792Utils'

export function EthersGetCallsStatusTest() {
export function EthersGetCallsStatusTest({ callsHash }: { callsHash: string }) {
const [isLoading, setLoading] = useState(false)
const [batchCallId, setBatchCallId] = useState('')
const [batchCallId, setBatchCallId] = useState(callsHash)

const { chainId } = useAppKitNetwork()
const { address, isConnected } = useAppKitAccount()
const { walletProvider } = useAppKitProvider<Provider>('eip155')
const toast = useChakraToast()

useEffect(() => {
setBatchCallId(callsHash)
}, [callsHash])

async function onGetCallsStatus() {
try {
setLoading(true)
Expand Down
111 changes: 96 additions & 15 deletions apps/laboratory/src/components/Ethers/EthersSendCallsTest.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,59 @@
import { Button, Stack, Text, Spacer, Heading } from '@chakra-ui/react'
import {
Box,
Link,
Stat,
StatLabel,
StatNumber,
StatHelpText,
Card,
CardBody
} from '@chakra-ui/react'
import { useState, useEffect } from 'react'
import { Button, Stack, Text, Spacer, Heading } from '@chakra-ui/react'
import { useAppKitAccount, useAppKitNetwork, useAppKitProvider } from '@reown/appkit/react'
import { UniversalProvider } from '@walletconnect/universal-provider'
import { useChakraToast } from '../Toast'
import type { Address } from 'viem'
import { parseGwei, type Address } from 'viem'
import { vitalikEthAddress } from '../../utils/DataUtil'
import { BrowserProvider, type Eip1193Provider } from 'ethers'
import {
EIP_5792_RPC_METHODS,
WALLET_CAPABILITIES,
getCapabilitySupportedChainInfo
} from '../../utils/EIP5792Utils'
import { AddIcon, DeleteIcon } from '@chakra-ui/icons'
import { AddTransactionModal } from '../AddTransactionModal'
import { W3mFrameProvider } from '@reown/appkit-wallet'
type Provider = W3mFrameProvider | Awaited<ReturnType<(typeof UniversalProvider)['init']>>

export function EthersSendCallsTest() {
export function EthersSendCallsTest({ onCallsHash }: { onCallsHash: (hash: string) => void }) {
const [loading, setLoading] = useState(false)

const { chainId } = useAppKitNetwork()
const { address, isConnected } = useAppKitAccount()
const { walletProvider } = useAppKitProvider<Eip1193Provider>('eip155')
const [transactionsToBatch, setTransactionsToBatch] = useState<{ value: string; to: string }[]>(
[]
)
const toast = useChakraToast()

const [isOpen, setIsOpen] = useState(false)
function onSubmit(args: { to: string; eth: string }) {
setLastCallsBatchId(null)
setTransactionsToBatch(prev => [
...prev,
{
to: args.to as `0x${string}`,
data: '0x' as `0x${string}`,
value: `0x${parseGwei(args.eth).toString(16)}`
}
])
}

function onClose() {
setIsOpen(false)
}

const [atomicBatchSupportedChains, setAtomicBatchSupportedChains] = useState<
Awaited<ReturnType<typeof getCapabilitySupportedChainInfo>>
>([])
Expand All @@ -47,6 +79,14 @@ export function EthersSendCallsTest() {
chainInfo => chainInfo.chainId === Number(chainId)
)

function onAddTransactionButtonClick() {
setIsOpen(true)
}

function onRemoveTransaction(index: number) {
setTransactionsToBatch(prev => prev.filter((_, i) => i !== index))
}

async function onSendCalls() {
try {
setLoading(true)
Expand All @@ -73,7 +113,7 @@ export function EthersSendCallsTest() {
version: '1.0',
chainId: `0x${BigInt(chainId).toString(16)}`,
from: address,
calls
calls: transactionsToBatch.length ? transactionsToBatch : calls
}
const batchCallHash = await provider.send(EIP_5792_RPC_METHODS.WALLET_SEND_CALLS, [
sendCallsParams
Expand All @@ -85,6 +125,8 @@ export function EthersSendCallsTest() {
description: batchCallHash,
type: 'success'
})
setTransactionsToBatch([])
onCallsHash(batchCallHash)
} catch {
toast({
title: 'Error',
Expand Down Expand Up @@ -133,23 +175,62 @@ export function EthersSendCallsTest() {
}

return currentChainsInfo ? (
<Stack direction={['column', 'column']}>
<Button
data-testid="send-calls-button"
onClick={onSendCalls}
isDisabled={loading}
maxWidth={'50%'}
>
Send Batch Calls to Vitalik
</Button>
<Spacer />
<>
<Box>
<Stack direction={['column', 'column', 'row']}>
<Stack direction={['column']}>
(
{transactionsToBatch.length ? (
transactionsToBatch.map((tx, index) => (
<>
<Card>
<CardBody>
<Stat>
<StatLabel>
({index + 1}) Sending
<DeleteIcon
style={{ float: 'right', cursor: 'pointer' }}
onClick={() => onRemoveTransaction(index)}
/>
</StatLabel>
<StatNumber>{parseInt(tx.value, 16) / 1000000000} ETH</StatNumber>
<StatHelpText>to {tx.to}</StatHelpText>
</Stat>
</CardBody>
</Card>{' '}
<Spacer />
</>
))
) : (
<Button data-test-id="send-calls-button" onClick={onSendCalls} isDisabled={loading}>
Send Batch Calls to Vitalik
</Button>
)}
)
</Stack>
<Spacer />
<Link onClick={onAddTransactionButtonClick}>
<Button variant="outline" colorScheme="blue" isDisabled={loading}>
<AddIcon mr={2} /> Add Transaction
</Button>
</Link>
</Stack>
{transactionsToBatch.length ? (
<Button data-test-id="send-calls-button" onClick={onSendCalls} isDisabled={loading}>
Send Calls
</Button>
) : null}
</Box>
<AddTransactionModal isOpen={isOpen} onSubmit={onSubmit} onClose={onClose} />

<Spacer m={2} />
{lastCallsBatchId && (
<>
<Heading size="xs">Last batch call ID:</Heading>
<Text data-testid="send-calls-id">{lastCallsBatchId}</Text>
</>
)}
</Stack>
</>
) : (
<Text fontSize="md" color="yellow">
Switch to {atomicBatchSupportedChainsNames} to test atomic batch feature
Expand Down
16 changes: 14 additions & 2 deletions apps/laboratory/src/components/Ethers/EthersTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,20 @@ import { EthersSendCallsWithPaymasterServiceTest } from './EthersSendCallsWithPa

export function EthersTests() {
const [ready, setReady] = React.useState(false)

const [callsHash, setCallsHash] = React.useState<string>('')
const { isConnected } = useAppKitAccount()

const onCallsHash = React.useCallback((hash: string) => {
setCallsHash(hash)
}, [])

React.useEffect(() => {
if (!isConnected) {
setCallsHash('')
}
}, [isConnected])

React.useEffect(() => {
setReady(true)
}, [])
Expand Down Expand Up @@ -59,13 +71,13 @@ export function EthersTests() {
<Heading size="xs" textTransform="uppercase" pb="2">
Send Calls (Atomic Batch)
</Heading>
<EthersSendCallsTest />
<EthersSendCallsTest onCallsHash={onCallsHash} />
</Box>
<Box>
<Heading size="xs" textTransform="uppercase" pb="2">
Get Calls Status
</Heading>
<EthersGetCallsStatusTest />
<EthersGetCallsStatusTest callsHash={callsHash} />
</Box>
<Box>
<Heading size="xs" textTransform="uppercase" pb="2">
Expand Down

0 comments on commit bc148e3

Please sign in to comment.