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,
},
};