diff --git a/components/contract-execution/contract-functions-list.tsx b/components/contract-execution/components/contract-functions-list.tsx similarity index 78% rename from components/contract-execution/contract-functions-list.tsx rename to components/contract-execution/components/contract-functions-list.tsx index fa9ddc7..1e359c8 100644 --- a/components/contract-execution/contract-functions-list.tsx +++ b/components/contract-execution/components/contract-functions-list.tsx @@ -1,13 +1,14 @@ import { Search } from "lucide-react"; import { useMemo, useState } from "react"; import type { AbiFunction } from "viem"; -import { cn } from "../../lib/utils.js"; -import { Accordion } from "../shadcn/accordion.js"; -import { Input } from "../shadcn/input.js"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "../shadcn/tabs.js"; +import { cn } from "../../../lib/utils.js"; +import { Accordion } from "../../shadcn/accordion.js"; +import { Input } from "../../shadcn/input.js"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../shadcn/tabs.js"; +import type { ContractFunctionsListProps } from "../types.js"; import { FunctionItem } from "./function-item.js"; import { RawOperations } from "./raw-operations.js"; -import type { ContractFunctionsListProps } from "./types.js"; +import { SignatureOperations } from "./signature-operations.js"; export function ContractFunctionsList({ abi, @@ -22,9 +23,11 @@ export function ContractFunctionsList({ onSimulate, onRawCall, onRawTransaction, + enableSignature, addressRenderer, onHashClick, title, + NoAbiComponent, }: ContractFunctionsListProps) { const [searchTerm, setSearchTerm] = useState(""); @@ -51,7 +54,8 @@ export function ContractFunctionsList({ }, [contractFunctions, searchTerm]); const hasRawOperations = onRawCall || onRawTransaction; - const tabCount = hasRawOperations ? 3 : 2; + const hasSignature = !!enableSignature; + const tabCount = 2 + (hasRawOperations ? 1 : 0) + (hasSignature ? 1 : 0); return (
@@ -76,6 +80,7 @@ export function ContractFunctionsList({ "grid w-full", tabCount === 2 && "grid-cols-2", tabCount === 3 && "grid-cols-3", + tabCount === 4 && "grid-cols-4", )} > )} + {hasSignature && ( + + Signature + + )} @@ -129,6 +142,10 @@ export function ContractFunctionsList({ /> ))} + ) : NoAbiComponent && + !searchTerm && + contractFunctions.length === 0 ? ( + ) : (
{searchTerm @@ -162,6 +179,10 @@ export function ContractFunctionsList({ /> ))} + ) : NoAbiComponent && + !searchTerm && + contractFunctions.length === 0 ? ( + ) : (
{searchTerm @@ -188,6 +209,24 @@ export function ContractFunctionsList({ /> )} + + {hasSignature && ( + + + + )}
diff --git a/components/contract-execution/function-item.tsx b/components/contract-execution/components/function-item.tsx similarity index 52% rename from components/contract-execution/function-item.tsx rename to components/contract-execution/components/function-item.tsx index fc17f3d..f3281b6 100644 --- a/components/contract-execution/function-item.tsx +++ b/components/contract-execution/components/function-item.tsx @@ -2,51 +2,23 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { memo, useCallback, useState } from "react"; import { FormProvider, useForm } from "react-hook-form"; import type { AbiFunction, Address } from "viem"; -import { decodeFunctionResult, isAddress } from "viem"; +import { isAddress } from "viem"; import { z } from "zod"; -import { AbiItemFormWithPreview } from "../abi-form/abi-item-form-with-preview.js"; -import type { AddressData } from "../address-autocomplete-input.js"; +import { AbiItemFormWithPreview } from "../../abi-form/abi-item-form-with-preview.js"; +import type { AddressData } from "../../address-autocomplete-input.js"; import { AccordionContent, AccordionItem, AccordionTrigger, -} from "../shadcn/accordion.js"; +} from "../../shadcn/accordion.js"; +import type { ExecutionParams } from "../types.js"; +import { useFunctionExecution } from "../use-function-execution.js"; import { DefaultResultDisplay } from "./result-display.js"; import { ActionButtons, ConnectWalletAlert, MsgSenderInput, } from "./shared-components.js"; -import type { ExecutionParams } from "./types.js"; - -type InternalResult = { - type: "call" | "simulation" | "execution" | "error"; - data?: string; - hash?: string; - cleanResult?: string; - error?: string; -}; - -function formatDecodedResult(result: unknown): string { - if (typeof result === "bigint") { - return result.toString(); - } - if (Array.isArray(result)) { - return JSON.stringify( - result, - (_, v) => (typeof v === "bigint" ? v.toString() : v), - 2, - ); - } - if (typeof result === "object" && result !== null) { - return JSON.stringify( - result, - (_, v) => (typeof v === "bigint" ? v.toString() : v), - 2, - ); - } - return String(result); -} const executionFormSchema = z.object({ msgSender: z @@ -94,9 +66,8 @@ export const FunctionItem = memo( onHashClick, }: FunctionItemProps) => { const [callData, setCallData] = useState(""); - const [result, setResult] = useState(null); - const [isSimulating, setIsSimulating] = useState(false); - const [isExecuting, setIsExecuting] = useState(false); + const { result, isSimulating, isExecuting, simulate, execute } = + useFunctionExecution(); const form = useForm({ mode: "onChange", @@ -118,104 +89,26 @@ export const FunctionItem = memo( [], ); - const handleSimulate = async () => { - if (!callData || !onSimulate) return; - setIsSimulating(true); - try { - const rawResult = await onSimulate({ - abiFunction: func, - callData: callData as `0x${string}`, - msgSender: msgSender ? (msgSender as Address) : undefined, - }); - - if (isWrite) { - setResult({ - type: "simulation", - cleanResult: "Simulation successful", - data: rawResult, - }); - } else { - try { - const decoded = decodeFunctionResult({ - abi: [func], - functionName: func.name, - data: rawResult, - }); - - setResult({ - type: "simulation", - cleanResult: formatDecodedResult(decoded), - data: rawResult, - }); - } catch { - setResult({ - type: "simulation", - data: rawResult, - }); - } - } - } catch (error) { - setResult({ - type: "error", - error: error instanceof Error ? error.message : "Unknown error", - }); - } finally { - setIsSimulating(false); - } + const handleSimulate = () => { + simulate({ + abiFunction: func, + callData, + msgSender: msgSender ? (msgSender as Address) : undefined, + onQuery, + onWrite, + onSimulate, + }); }; - const handleExecute = async () => { - if (!callData) return; - setIsExecuting(true); - try { - if (isWrite) { - // Call write function - returns transaction hash - const hash = await onWrite({ - abiFunction: func, - callData: callData as `0x${string}`, - msgSender: msgSender ? (msgSender as Address) : undefined, - }); - - setResult({ - type: "execution", - hash, - cleanResult: "Transaction submitted", - }); - } else { - // Call query function - returns raw hex - const rawResult = await onQuery({ - abiFunction: func, - callData: callData as `0x${string}`, - msgSender: msgSender ? (msgSender as Address) : undefined, - }); - - try { - const decoded = decodeFunctionResult({ - abi: [func], - functionName: func.name, - data: rawResult, - }); - - setResult({ - type: "call", - cleanResult: formatDecodedResult(decoded), - data: rawResult, - }); - } catch { - setResult({ - type: "call", - data: rawResult, - }); - } - } - } catch (error) { - setResult({ - type: "error", - error: error instanceof Error ? error.message : "Unknown error", - }); - } finally { - setIsExecuting(false); - } + const handleExecute = () => { + execute({ + abiFunction: func, + callData, + msgSender: msgSender ? (msgSender as Address) : undefined, + onQuery, + onWrite, + onSimulate, + }); }; const functionKey = `${func.name}-${index}`; diff --git a/components/contract-execution/raw-operations.tsx b/components/contract-execution/components/raw-operations.tsx similarity index 95% rename from components/contract-execution/raw-operations.tsx rename to components/contract-execution/components/raw-operations.tsx index fed8749..4cf108e 100644 --- a/components/contract-execution/raw-operations.tsx +++ b/components/contract-execution/components/raw-operations.tsx @@ -4,18 +4,18 @@ import { FormProvider, useForm } from "react-hook-form"; import type { Address } from "viem"; import { isAddress } from "viem"; import { z } from "zod"; -import { AbiItemFormWithPreview } from "../abi-form/abi-item-form-with-preview.js"; -import type { AddressData } from "../address-autocomplete-input.js"; +import { AbiItemFormWithPreview } from "../../abi-form/abi-item-form-with-preview.js"; +import type { AddressData } from "../../address-autocomplete-input.js"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, -} from "../shadcn/accordion.js"; -import { Button } from "../shadcn/button.js"; +} from "../../shadcn/accordion.js"; +import { Button } from "../../shadcn/button.js"; +import type { RawCallParams } from "../types.js"; import { DefaultResultDisplay } from "./result-display.js"; import { ConnectWalletAlert, MsgSenderInput } from "./shared-components.js"; -import type { RawCallParams } from "./types.js"; type InternalResult = { type: "call" | "execution" | "error"; diff --git a/components/contract-execution/result-display.tsx b/components/contract-execution/components/result-display.tsx similarity index 98% rename from components/contract-execution/result-display.tsx rename to components/contract-execution/components/result-display.tsx index c1c9580..644b0ce 100644 --- a/components/contract-execution/result-display.tsx +++ b/components/contract-execution/components/result-display.tsx @@ -1,7 +1,7 @@ import clsx from "clsx"; import { ChevronDown, ChevronUp, ExternalLink } from "lucide-react"; import { useState } from "react"; -import { Button } from "../shadcn/button.js"; +import { Button } from "../../shadcn/button.js"; type InternalResult = { type: "call" | "simulation" | "execution" | "error"; diff --git a/components/contract-execution/shared-components.tsx b/components/contract-execution/components/shared-components.tsx similarity index 91% rename from components/contract-execution/shared-components.tsx rename to components/contract-execution/components/shared-components.tsx index 7a9e6be..46a64ff 100644 --- a/components/contract-execution/shared-components.tsx +++ b/components/contract-execution/components/shared-components.tsx @@ -1,7 +1,7 @@ import { Info } from "lucide-react"; -import { Form } from "../form/index.js"; -import { Alert, AlertDescription, AlertTitle } from "../shadcn/alert.js"; -import { Button } from "../shadcn/button.js"; +import { Form } from "../../form/index.js"; +import { Alert, AlertDescription, AlertTitle } from "../../shadcn/alert.js"; +import { Button } from "../../shadcn/button.js"; export function MsgSenderInput() { return ( diff --git a/components/contract-execution/components/signature-operations.tsx b/components/contract-execution/components/signature-operations.tsx new file mode 100644 index 0000000..c8e59cc --- /dev/null +++ b/components/contract-execution/components/signature-operations.tsx @@ -0,0 +1,204 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { useCallback, useMemo, useState } from "react"; +import { FormProvider, useForm } from "react-hook-form"; +import type { AbiFunction, Address } from "viem"; +import { isAddress, parseAbiItem } from "viem"; +import { z } from "zod"; +import { AbiItemFormWithPreview } from "../../abi-form/abi-item-form-with-preview.js"; +import type { AddressData } from "../../address-autocomplete-input.js"; +import { Input } from "../../shadcn/input.js"; +import { Label } from "../../shadcn/label.js"; +import type { ExecutionParams } from "../types.js"; +import { useFunctionExecution } from "../use-function-execution.js"; +import { DefaultResultDisplay } from "./result-display.js"; +import { + ActionButtons, + ConnectWalletAlert, + MsgSenderInput, +} from "./shared-components.js"; + +const signatureFormSchema = z.object({ + signature: z.string().refine( + (val) => { + if (!val) return false; + try { + parseAbiItem(val); + return true; + } catch { + return false; + } + }, + { message: "Invalid function signature" }, + ), + msgSender: z + .string() + .refine( + (val) => { + if (!val) return true; + return isAddress(val); + }, + { message: "Invalid address format" }, + ) + .optional(), +}); + +interface SignatureOperationsProps { + address: Address; + chainId: number; + sender?: Address; + addresses?: AddressData[]; + requiresConnection: boolean; + isConnected: boolean; + onQuery: (params: ExecutionParams) => Promise<`0x${string}`>; + onWrite: (params: ExecutionParams) => Promise<`0x${string}`>; + onSimulate?: (params: ExecutionParams) => Promise<`0x${string}`>; + addressRenderer?: (address: Address) => React.ReactNode; + onHashClick?: (hash: string) => void; +} + +export function SignatureOperations({ + address, + chainId, + sender, + addresses, + requiresConnection, + isConnected, + onQuery, + onWrite, + onSimulate, + addressRenderer, + onHashClick, +}: SignatureOperationsProps) { + const [callData, setCallData] = useState(""); + const { result, isSimulating, isExecuting, simulate, execute } = + useFunctionExecution(); + + const form = useForm({ + mode: "onChange", + resolver: zodResolver(signatureFormSchema), + defaultValues: { + signature: "", + msgSender: "", + }, + }); + + const signature = form.watch("signature"); + const msgSender = form.watch("msgSender") || ""; + + const isValidSignature = + form.getFieldState("signature").invalid === false && signature.length > 0; + + const parsedAbiFunction = useMemo(() => { + if (!isValidSignature || !signature) return null; + try { + return parseAbiItem(signature) as AbiFunction; + } catch { + return null; + } + }, [isValidSignature, signature]); + + const isWrite = + parsedAbiFunction?.stateMutability !== "view" && + parsedAbiFunction?.stateMutability !== "pure"; + + const handleCallDataChange = useCallback( + ({ data }: { data?: `0x${string}`; value?: bigint }) => { + setCallData(data || ""); + }, + [], + ); + + const handleSimulate = () => { + if (!parsedAbiFunction) return; + simulate({ + abiFunction: parsedAbiFunction, + callData, + msgSender: msgSender ? (msgSender as Address) : undefined, + onQuery, + onWrite, + onSimulate, + }); + }; + + const handleExecute = () => { + if (!parsedAbiFunction) return; + execute({ + abiFunction: parsedAbiFunction, + callData, + msgSender: msgSender ? (msgSender as Address) : undefined, + onQuery, + onWrite, + onSimulate, + }); + }; + + return ( +
+ +
+
+ + + {form.formState.errors.signature && ( +

+ {form.formState.errors.signature.message} +

+ )} +
+ + {isValidSignature && parsedAbiFunction && ( + <> + {isWrite && } + + {isWrite && requiresConnection && !isConnected && ( + + )} + + + + + + {result && ( + + )} + + )} +
+
+
+ ); +} diff --git a/components/contract-execution/index.ts b/components/contract-execution/index.ts index 30e7b14..d4013d2 100644 --- a/components/contract-execution/index.ts +++ b/components/contract-execution/index.ts @@ -1,15 +1,19 @@ // biome-ignore lint/performance/noBarrelFile: This is a public API entry point -export { ContractFunctionsList } from "./contract-functions-list.js"; -export { FunctionItem } from "./function-item.js"; -export { RawOperations } from "./raw-operations.js"; -export { DefaultResultDisplay } from "./result-display.js"; +export { ContractFunctionsList } from "./components/contract-functions-list.js"; +export { FunctionItem } from "./components/function-item.js"; +export { RawOperations } from "./components/raw-operations.js"; +export { DefaultResultDisplay } from "./components/result-display.js"; export { ActionButtons, ConnectWalletAlert, MsgSenderInput, -} from "./shared-components.js"; +} from "./components/shared-components.js"; +export { SignatureOperations } from "./components/signature-operations.js"; export type { ContractFunctionsListProps, ExecutionParams, RawCallParams, } from "./types.js"; +export type { InternalResult } from "./use-function-execution.js"; +export { useFunctionExecution } from "./use-function-execution.js"; +export { formatDecodedResult } from "./utils.js"; diff --git a/components/contract-execution/types.ts b/components/contract-execution/types.ts index 659f3f2..96fe822 100644 --- a/components/contract-execution/types.ts +++ b/components/contract-execution/types.ts @@ -39,10 +39,14 @@ export interface ContractFunctionsListProps { onRawCall?: (params: RawCallParams) => Promise<`0x${string}`>; /** Optional raw transaction function (send transaction with arbitrary data) - returns transaction hash */ onRawTransaction?: (params: RawCallParams) => Promise<`0x${string}`>; + /** Enable signature-based interaction tab */ + enableSignature?: boolean; /** Custom address renderer for form inputs */ addressRenderer?: (address: Address) => React.ReactNode; /** Callback when transaction hash is clicked (for custom navigation) */ onHashClick?: (hash: string) => void; /** Optional title to display above the list */ title?: string; + /** Optional component to render when there are no functions in the ABI */ + NoAbiComponent?: React.ComponentType; } diff --git a/components/contract-execution/use-function-execution.ts b/components/contract-execution/use-function-execution.ts new file mode 100644 index 0000000..7e8f255 --- /dev/null +++ b/components/contract-execution/use-function-execution.ts @@ -0,0 +1,161 @@ +import { useCallback, useState } from "react"; +import type { AbiFunction, Address } from "viem"; +import { decodeFunctionResult } from "viem"; +import type { ExecutionParams } from "./types.js"; +import { formatDecodedResult } from "./utils.js"; + +export type InternalResult = { + type: "call" | "simulation" | "execution" | "error"; + data?: string; + hash?: string; + cleanResult?: string; + error?: string; +}; + +interface UseFunctionExecutionParams { + abiFunction: AbiFunction; + callData: string; + msgSender?: Address; + onQuery: (params: ExecutionParams) => Promise<`0x${string}`>; + onWrite: (params: ExecutionParams) => Promise<`0x${string}`>; + onSimulate?: (params: ExecutionParams) => Promise<`0x${string}`>; +} + +export function useFunctionExecution() { + const [result, setResult] = useState(null); + const [isSimulating, setIsSimulating] = useState(false); + const [isExecuting, setIsExecuting] = useState(false); + + const simulate = useCallback( + async ({ + abiFunction, + callData, + msgSender, + onSimulate, + }: UseFunctionExecutionParams) => { + if (!callData || !onSimulate) return; + + const isWrite = + abiFunction.stateMutability !== "view" && + abiFunction.stateMutability !== "pure"; + + setIsSimulating(true); + try { + const rawResult = await onSimulate({ + abiFunction, + callData: callData as `0x${string}`, + msgSender, + }); + + if (isWrite) { + setResult({ + type: "simulation", + cleanResult: "Simulation successful", + data: rawResult, + }); + } else { + try { + const decoded = decodeFunctionResult({ + abi: [abiFunction], + functionName: abiFunction.name, + data: rawResult, + }); + + setResult({ + type: "simulation", + cleanResult: formatDecodedResult(decoded), + data: rawResult, + }); + } catch { + setResult({ + type: "simulation", + data: rawResult, + }); + } + } + } catch (error) { + setResult({ + type: "error", + error: error instanceof Error ? error.message : "Unknown error", + }); + } finally { + setIsSimulating(false); + } + }, + [], + ); + + const execute = useCallback( + async ({ + abiFunction, + callData, + msgSender, + onQuery, + onWrite, + }: UseFunctionExecutionParams) => { + if (!callData) return; + + const isWrite = + abiFunction.stateMutability !== "view" && + abiFunction.stateMutability !== "pure"; + + setIsExecuting(true); + try { + if (isWrite) { + const hash = await onWrite({ + abiFunction, + callData: callData as `0x${string}`, + msgSender, + }); + + setResult({ + type: "execution", + hash, + cleanResult: "Transaction submitted", + }); + } else { + const rawResult = await onQuery({ + abiFunction, + callData: callData as `0x${string}`, + msgSender, + }); + + try { + const decoded = decodeFunctionResult({ + abi: [abiFunction], + functionName: abiFunction.name, + data: rawResult, + }); + + setResult({ + type: "call", + cleanResult: formatDecodedResult(decoded), + data: rawResult, + }); + } catch { + setResult({ + type: "call", + data: rawResult, + }); + } + } + } catch (error) { + setResult({ + type: "error", + error: error instanceof Error ? error.message : "Unknown error", + }); + } finally { + setIsExecuting(false); + } + }, + [], + ); + + return { + result, + isSimulating, + isExecuting, + simulate, + execute, + }; +} diff --git a/components/contract-execution/utils.ts b/components/contract-execution/utils.ts new file mode 100644 index 0000000..f8f327a --- /dev/null +++ b/components/contract-execution/utils.ts @@ -0,0 +1,20 @@ +export function formatDecodedResult(result: unknown): string { + if (typeof result === "bigint") { + return result.toString(); + } + if (Array.isArray(result)) { + return JSON.stringify( + result, + (_, v) => (typeof v === "bigint" ? v.toString() : v), + 2, + ); + } + if (typeof result === "object" && result !== null) { + return JSON.stringify( + result, + (_, v) => (typeof v === "bigint" ? v.toString() : v), + 2, + ); + } + return String(result); +} diff --git a/stories/contract-functions-list.stories.tsx b/stories/contract-functions-list.stories.tsx index c9bf24c..6db2aa3 100644 --- a/stories/contract-functions-list.stories.tsx +++ b/stories/contract-functions-list.stories.tsx @@ -177,8 +177,8 @@ const mockRawTransaction = async ( return "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"; }; -// Story: Connected wallet with all features (including raw operations) -export const Connected: Story = { +// Story: Basic usage with all features +export const Default: Story = { args: { abi: mockERC20Abi, address: "0x1234567890123456789012345678901234567890", @@ -192,28 +192,7 @@ export const Connected: Story = { onSimulate: mockSimulate, onRawCall: mockRawCall, onRawTransaction: mockRawTransaction, - onHashClick: (hash) => { - console.log("Hash clicked:", hash); - window.open(`https://etherscan.io/tx/${hash}`, "_blank"); - }, - title: "Contract Functions", - }, -}; - -// Story: Disconnected wallet (shows connection alert for write functions and raw transaction) -export const Disconnected: Story = { - args: { - abi: mockERC20Abi, - address: "0x1234567890123456789012345678901234567890", - chainId: 1, - addresses, - requiresConnection: true, - isConnected: false, - onQuery: mockQuery, - onWrite: mockWrite, - onSimulate: mockSimulate, - onRawCall: mockRawCall, - onRawTransaction: mockRawTransaction, + enableSignature: true, }, }; @@ -273,16 +252,15 @@ export const Interactive: Story = { render: () => , }; -// Story: With custom address renderer -const customAddressRenderer = (address: string) => { - return ( - - 📍 {address.slice(0, 6)}...{address.slice(-4)} - - ); +// Story: Error handling - errors are now thrown instead of returned +const mockWriteWithError = async ( + _params: ExecutionParams, +): Promise<`0x${string}`> => { + await new Promise((resolve) => setTimeout(resolve, 1000)); + throw new Error("Execution reverted: insufficient balance"); }; -export const CustomAddressRenderer: Story = { +export const WithError: Story = { args: { abi: mockERC20Abi, address: "0x1234567890123456789012345678901234567890", @@ -292,25 +270,51 @@ export const CustomAddressRenderer: Story = { requiresConnection: true, isConnected: true, onQuery: mockQuery, - onWrite: mockWrite, + onWrite: mockWriteWithError, onSimulate: mockSimulate, onRawCall: mockRawCall, onRawTransaction: mockRawTransaction, - addressRenderer: customAddressRenderer, }, }; -// Story: Error handling - errors are now thrown instead of returned -const mockWriteWithError = async ( - _params: ExecutionParams, -): Promise<`0x${string}`> => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - throw new Error("Execution reverted: insufficient balance"); +// Story: Empty ABI (shows only signature and raw operations) +export const EmptyAbi: Story = { + args: { + abi: [] as Abi, + address: "0x1234567890123456789012345678901234567890", + chainId: 1, + sender: "0x0077014b4C74d9b1688847386B24Ed23Fdf14Be8", + addresses, + requiresConnection: true, + isConnected: true, + onQuery: mockQuery, + onWrite: mockWrite, + onSimulate: mockSimulate, + onRawCall: mockRawCall, + onRawTransaction: mockRawTransaction, + enableSignature: true, + }, }; -export const WithError: Story = { +// Custom component to render when there's no ABI +function CustomNoAbi() { + return ( +
+
+

No ABI Available

+

+ This contract doesn't have a verified ABI. You can still interact with + it using the Raw or Signature tabs. +

+
+
+ ); +} + +// Story: Empty ABI with custom no ABI component +export const EmptyAbiWithCustomComponent: Story = { args: { - abi: mockERC20Abi, + abi: [] as Abi, address: "0x1234567890123456789012345678901234567890", chainId: 1, sender: "0x0077014b4C74d9b1688847386B24Ed23Fdf14Be8", @@ -318,9 +322,11 @@ export const WithError: Story = { requiresConnection: true, isConnected: true, onQuery: mockQuery, - onWrite: mockWriteWithError, + onWrite: mockWrite, onSimulate: mockSimulate, onRawCall: mockRawCall, onRawTransaction: mockRawTransaction, + enableSignature: true, + NoAbiComponent: CustomNoAbi, }, };