diff --git a/src/components/FormPopover.tsx b/src/components/FormPopover.tsx new file mode 100644 index 0000000..a27148a --- /dev/null +++ b/src/components/FormPopover.tsx @@ -0,0 +1,68 @@ +import * as Popover from '@radix-ui/react-popover' +import { type ReactNode, useState } from 'react' + +import { Box, Button, Stack } from '~/design-system' +import * as Form from './form' + +export function FormPopover({ + children, + disabled, + onSubmit, +}: { + children: ReactNode + disabled: boolean + onSubmit: (e: React.FormEvent) => void +}) { + const [open, setOpen] = useState(false) + + return ( + + + + setOpen(true)} + symbol="square.and.pencil" + variant="ghost primary" + /> + + + + setOpen(false)} + onPointerDownOutside={() => setOpen(false)} + style={{ zIndex: 1 }} + > + + { + onSubmit(e) + setOpen(false) + }} + > + + {children} + + + + + + + + ) +} diff --git a/src/components/index.ts b/src/components/index.ts index eebadff..91b9463 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -9,6 +9,7 @@ export { DecodedLogs } from './logs' export { TabsContent, TabsList } from './tabs' export { Container } from './Container' +export { FormPopover } from './FormPopover' export { Header } from './Header' export { LabelledContent } from './LabelledContent' export { LoadMore } from './LoadMore' diff --git a/src/screens/contract-details.tsx b/src/screens/contract-details.tsx index 5af7640..ccc1377 100644 --- a/src/screens/contract-details.tsx +++ b/src/screens/contract-details.tsx @@ -2,15 +2,18 @@ import * as Tabs from '@radix-ui/react-tabs' import { useMutation } from '@tanstack/react-query' import type { Abi, AbiFunction } from 'abitype' import { useMemo } from 'react' +import { useForm } from 'react-hook-form' import { useParams } from 'react-router' import { AbiFunctionsAccordion, Container, + FormPopover, LabelledContent, TabsContent, TabsList, } from '~/components' +import * as Form from '~/components/form' import { Bleed, Box, Stack, Text } from '~/design-system' import { useAutoloadAbi } from '~/hooks/useAutoloadAbi' import { useContracts } from '~/hooks/useContracts' @@ -20,11 +23,19 @@ export default function ContractDetails() { const { contracts, updateContract } = useContracts({ enabled: false }) const contract = contracts.find((c) => c.address === contractAddress) - const { - data: autoloadAbi, - isLoading, - isFetched, - } = useAutoloadAbi({ + //////////////////////////////////////////////////////////////////////// + + const { formState, register, handleSubmit } = useForm({ + defaultValues: { name: contract?.name }, + }) + + const update = handleSubmit(({ name }) => { + updateContract({ address: contract?.address!, name }) + }) + + //////////////////////////////////////////////////////////////////////// + + const { data: autoloadAbi, isLoading } = useAutoloadAbi({ address: contract?.address, enabled: Boolean(!contract?.abi && contract?.address), }) @@ -66,18 +77,40 @@ export default function ContractDetails() { return [read as {} as AbiFunction[], write as {} as AbiFunction[]] }, [abiFunctions, hasStateMutability]) + //////////////////////////////////////////////////////////////////////// + return ( - - - {contract?.address} - - + + + + } + > + {contract?.name ?? 'Unnamed Contract'} + + + {contract?.address} + {(isGuessedAbi || contract?.abi) && ( - updateContract({ abi, address: contract?.address! }) + onUpload={({ abi, file }) => + updateContract({ + abi, + address: contract?.address!, + name: file.name.replace('.json', ''), + }) } /> )} @@ -98,7 +131,7 @@ export default function ContractDetails() { Loading... )} - {isFetched && ( + {abi && ( {hasStateMutability ? ( @@ -150,7 +183,9 @@ export default function ContractDetails() { ) } -function UploadAbi({ onUpload }: { onUpload: (abi: { abi: Abi }) => void }) { +function UploadAbi({ + onUpload, +}: { onUpload: (abi: { abi: Abi; file: File }) => void }) { const { error, mutateAsync: uploadAbi } = useMutation({ async mutationFn(files: FileList | null) { if (!files) return @@ -166,7 +201,7 @@ function UploadAbi({ onUpload }: { onUpload: (abi: { abi: Abi }) => void }) { if (!isAbi) throw new Error('ABI is not valid. Please upload a valid ABI.') - onUpload({ abi }) + onUpload({ abi, file }) }, }) diff --git a/src/screens/index.tsx b/src/screens/index.tsx index 3992c1c..20c8118 100644 --- a/src/screens/index.tsx +++ b/src/screens/index.tsx @@ -833,7 +833,7 @@ function Contracts() { (_, index) => ({ index, - size: 32, + size: 40, type: 'item', }) as const, ), @@ -908,19 +908,39 @@ function Contracts() { backgroundColor={{ hover: 'surface/fill/quarternary' }} paddingHorizontal="8px" paddingVertical="8px" - height="full" + style={{ minHeight: '40px' }} > - + - {contract.address !== '0x' ? ( - - {contract.address} - - ) : ( - - Deploying... - - )} + + {contract.address !== '0x' ? ( + <> + + {contract.name || 'Unnamed Contract'} + + + {contract.address} + + + ) : ( + + Deploying... + + )} + + - + } > @@ -277,10 +274,7 @@ function SendTransactionRequest(args: { + - + } > @@ -304,10 +298,7 @@ function SendTransactionRequest(args: { + - + } > @@ -340,10 +331,7 @@ function SendTransactionRequest(args: { + - + } > @@ -367,10 +355,7 @@ function SendTransactionRequest(args: { + - + } > @@ -406,10 +391,7 @@ function SendTransactionRequest(args: { + - + } > @@ -447,10 +429,7 @@ function SendTransactionRequest(args: { + - + } > {nonce} @@ -487,7 +466,7 @@ function SendTransactionRequest(args: { address={to} data={data || '0x'} labelRight={ - @@ -502,7 +481,7 @@ function SendTransactionRequest(args: { })} style={{ width: '360px' }} /> - + } /> @@ -518,69 +497,6 @@ function SendTransactionRequest(args: { ) } -function UpdateFieldPopover({ - children, - disabled, - onSubmit, -}: { - children: ReactNode - disabled: boolean - onSubmit: (e: React.FormEvent) => void -}) { - const [open, setOpen] = useState(false) - - return ( - - - - setOpen(true)} - symbol="square.and.pencil" - variant="ghost primary" - /> - - - - setOpen(false)} - onPointerDownOutside={() => setOpen(false)} - style={{ zIndex: 1 }} - > - - { - onSubmit(e) - setOpen(false) - }} - > - - {children} - - - - - - - - ) -} - //////////////////////////////////////////////////////////////////////// // Request Accounts View diff --git a/src/zustand/contracts.ts b/src/zustand/contracts.ts index 77324e2..d5ca6af 100644 --- a/src/zustand/contracts.ts +++ b/src/zustand/contracts.ts @@ -1,3 +1,4 @@ +import { selectorsFromBytecode } from '@shazow/whatsabi' import type { Abi, Address } from 'abitype' import type { Hex, TransactionReceipt } from 'viem' import { useStore } from 'zustand' @@ -9,6 +10,7 @@ type Contract = { abi?: Abi address: Address bytecode?: Hex | null + name?: string key: string receipt?: TransactionReceipt state: 'loaded' | 'loading' @@ -72,7 +74,14 @@ export const contractsStore = createStore( set((state) => { const contracts = { ...state.contracts } + const { abi, name } = getContractAbi({ + contracts, + contract, + }) + const contract_ = { + abi, + name, key: `${contract.address}-${contract.bytecode}`, state: 'loaded', visible: true, @@ -162,9 +171,15 @@ export const contractsStore = createStore( const exists = (contracts[serializedKey] || []).some( (x) => x.address === contract.address, ) - if (!exists) + if (!exists) { + const { abi, name } = getContractAbi({ + contracts, + contract, + }) contracts[serializedKey] = [ { + abi, + name, ...contract, key: contract.address, state: 'loaded', @@ -172,6 +187,7 @@ export const contractsStore = createStore( }, ...(contracts[serializedKey] || []), ] + } } contracts[serializedKey] = contracts[serializedKey]?.filter( @@ -194,3 +210,27 @@ export const contractsStore = createStore( ) export const useContractsStore = () => useStore(contractsStore) + +/////////////////////////////////////////////////////////////// + +function getContractAbi({ + contracts, + contract, +}: { contract: Partial; contracts: ContractsState['contracts'] }) { + if (contract.abi) return contract + + const allContracts = Object.values(contracts).flat() + const contracts_ = allContracts.filter( + (c) => + c?.abi && + c.bytecode && + contract.bytecode && + selectorsFromBytecode(c.bytecode).join() === + selectorsFromBytecode(contract.bytecode).join(), + ) + const contract_ = contracts_?.[0] + return { + abi: contract_?.abi, + name: contract_?.name, + } +}