"use client"; import { ArrowPathIcon } from "@heroicons/react/24/outline"; import PythAbi from "@pythnetwork/pyth-sdk-solidity/abis/IPyth.json"; import PythErrorsAbi from "@pythnetwork/pyth-sdk-solidity/abis/PythErrors.json"; import { ConnectKitButton, Avatar } from "connectkit"; import { useCallback, useMemo, useState } from "react"; import { ContractFunctionExecutionError } from "viem"; import { useAccount, useConfig } from "wagmi"; import { readContract, simulateContract, writeContract } from "wagmi/actions"; import { type Parameter, TRANSFORMS } from "./parameter"; import { getContractAddress } from "../../evm-networks"; import { useIsMounted } from "../../use-is-mounted"; import { Button } from "../Button"; import { Code } from "../Code"; import { InlineLink } from "../InlineLink"; const abi = [...PythAbi, ...PythErrorsAbi] as const; type RunButtonProps = ( | Read | Write ) & { functionName: (typeof PythAbi)[number]["name"]; parameters: Parameter[]; paramValues: Partial>; }; type Read = { type: EvmApiType.Read; valueParam?: undefined; }; type Write = { type: EvmApiType.Write; valueParam: ParameterName; }; export enum EvmApiType { Read, Write, } export const RunButton = ( props: RunButtonProps, ) => { const { isConnected } = useAccount(); const isMounted = useIsMounted(); const { status, run, disabled } = useRunButton(props); return ( <> {props.type === EvmApiType.Write && ( {({ show, isConnected, address, truncatedAddress, ensName }) => ( {isConnected ? ( <> Wallet: {ensName ?? truncatedAddress} ) : ( "Connect Wallet to Run" )} )} )} {(props.type === EvmApiType.Read || (isMounted && isConnected)) && ( )} {status.type === StatusType.Results && (

Results

{stringifyResponse(status.data)}
)} {status.type === StatusType.Error && (

Error

{showError(status.error)}

)} ); }; const useRunButton = ({ functionName, parameters, paramValues, ...props }: RunButtonProps) => { const config = useConfig(); const [status, setStatus] = useState(None()); const args = useMemo(() => { const allParams = props.type === EvmApiType.Write ? parameters.filter((parameter) => parameter.name !== props.valueParam) : parameters; const orderedParams = allParams.map(({ name, type }) => { const transform = TRANSFORMS[type]; const value = paramValues[name]; return transform && value ? transform(value) : value; }); return orderedParams.every((value) => value !== undefined) ? orderedParams : undefined; }, [parameters, paramValues, props]); const value = useMemo(() => { if (props.type === EvmApiType.Write) { const value = paramValues[props.valueParam]; return value ? BigInt(value) : undefined; } else { return; } }, [paramValues, props]); const run = useCallback(() => { setStatus(Loading()); if (args === undefined) { setStatus(ErrorStatus(new Error("Invalid parameters!"))); } else { const address = getContractAddress(config.state.chainId); if (!address) { throw new Error( `No contract for chain id: ${config.state.chainId.toString()}`, ); } switch (props.type) { case EvmApiType.Read: { readContract(config, { abi, address, functionName, args }) .then((result) => { setStatus(Results(result)); }) .catch((error: unknown) => { setStatus(ErrorStatus(error)); }); return; } case EvmApiType.Write: { if (value === undefined) { setStatus(ErrorStatus(new Error("Missing value!"))); } else { simulateContract(config, { abi, address, functionName, args, value, }) .then(({ request }) => writeContract(config, request)) .then((result) => { setStatus(Results(result)); }) .catch((error: unknown) => { setStatus(ErrorStatus(error)); }); } return; } } } }, [config, functionName, setStatus, args, value, props.type]); const { isConnected } = useAccount(); const disabled = useMemo( () => args === undefined || status.type === StatusType.Loading || (props.type === EvmApiType.Write && (!isConnected || value === undefined)), [args, status, props, isConnected, value], ); return { status, run, disabled }; }; enum StatusType { None, Loading, Error, Results, } const None = () => ({ type: StatusType.None as const }); const Loading = () => ({ type: StatusType.Loading as const }); const ErrorStatus = (error: unknown) => ({ type: StatusType.Error as const, error, }); const Results = (data: unknown) => ({ type: StatusType.Results as const, data, }); type Status = | ReturnType | ReturnType | ReturnType | ReturnType; const showError = (error: unknown): string => { if (typeof error === "string") { return error; } else if (error instanceof ContractFunctionExecutionError) { return error.cause.metaMessages?.[0] ?? error.message; } else if (error instanceof Error) { return error.toString(); } else { return "An unknown error occurred"; } }; const stringifyResponse = (response: unknown): string => { switch (typeof response) { case "string": { return `"${response}"`; } case "number": case "boolean": case "function": { return response.toString(); } case "bigint": { return `${response.toString()}n`; } case "symbol": { return `Symbol(${response.toString()})`; } case "undefined": { return "undefined"; } case "object": { return response === null ? "null" : `{\n${Object.entries(response) .map(([key, value]) => ` ${key}: ${stringifyResponse(value)}`) .join(",\n")}\n}`; } } };