Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: custom 5792 transactions #2666

Merged
merged 13 commits into from
Oct 2, 2024
Merged
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)
ganchoradkov marked this conversation as resolved.
Show resolved Hide resolved

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)}
/>
ganchoradkov marked this conversation as resolved.
Show resolved Hide resolved
</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
Loading