diff --git a/packages/svelte/src/lib/hooks/useReadContract.svelte.test.ts b/packages/svelte/src/lib/hooks/useReadContract.svelte.test.ts new file mode 100644 index 0000000000..d7ec37ec7e --- /dev/null +++ b/packages/svelte/src/lib/hooks/useReadContract.svelte.test.ts @@ -0,0 +1,204 @@ +import { abi, address, bytecode, chain, config, wait } from '@wagmi/test' +import { expect, test } from 'vitest' +import { testHook } from './test.svelte.js' +import { useReadContract } from './useReadContract.svelte.js' + +test( + 'default', + testHook(async () => { + const result = $derived.by( + useReadContract(() => ({ + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'] as const, + })), + ) + + await expect.poll(() => result.isSuccess).toBeTruthy() + + expect(result).toMatchInlineSnapshot(` + { + "data": 4n, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "readContract", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": [ + "0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC", + ], + "chainId": 1, + "functionName": "balanceOf", + }, + ], + "refetch": [Function], + "status": "success", + } + `) + }), +) + +test( + 'parameters: chainId', + testHook(async () => { + const result = $derived.by( + useReadContract(() => ({ + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'] as const, + chainId: chain.mainnet2.id, + })), + ) + + await expect.poll(() => result.isSuccess).toBeTruthy() + + expect(result).toMatchInlineSnapshot(` + { + "data": 4n, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "readContract", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": [ + "0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC", + ], + "chainId": 456, + "functionName": "balanceOf", + }, + ], + "refetch": [Function], + "status": "success", + } + `) + }), +) + +test( + 'parameters: config', + testHook( + async () => { + const result = $derived.by( + useReadContract(() => ({ + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'] as const, + config, + })), + ) + + await expect.poll(() => result.isSuccess).toBeTruthy() + + expect(result).toMatchInlineSnapshot(` + { + "data": 4n, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "readContract", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": [ + "0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC", + ], + "chainId": 1, + "functionName": "balanceOf", + }, + ], + "refetch": [Function], + "status": "success", + } + `) + }, + { shouldMockConfig: false }, + ), +) + +test( + 'parameters: deployless read (bytecode)', + testHook(async () => { + const result = $derived.by( + useReadContract(() => ({ + abi: abi.wagmiMintExample, + functionName: 'name', + code: bytecode.wagmiMintExample, + })), + ) + + await expect.poll(() => result.isSuccess).toBeTruthy() + + expect(result.data).toMatchInlineSnapshot(`"wagmi"`) + }), +) + +test( + 'behavior: disabled when properties missing', + testHook(async () => { + const result = $derived.by(useReadContract()) + + await wait(100) + await expect.poll(() => result.isPending).toBeTruthy() + }), +) diff --git a/packages/svelte/src/lib/hooks/useReadContract.svelte.ts b/packages/svelte/src/lib/hooks/useReadContract.svelte.ts new file mode 100644 index 0000000000..66f4eacf1e --- /dev/null +++ b/packages/svelte/src/lib/hooks/useReadContract.svelte.ts @@ -0,0 +1,109 @@ +'use client' + +import { type CreateQueryReturnType, createQuery } from '$lib/query.svelte.js' +import type { + ConfigParameter, + QueryParameter, + RuneParameters, + RuneReturnType, +} from '$lib/types.js' +import type { + Config, + ReadContractErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { UnionCompute } from '@wagmi/core/internal' +import { + type ReadContractData, + type ReadContractOptions, + type ReadContractQueryFnData, + type ReadContractQueryKey, + readContractQueryOptions, + structuralSharing, +} from '@wagmi/core/query' +import type { Abi, ContractFunctionArgs, ContractFunctionName, Hex } from 'viem' +import { useChainId } from './useChainId.svelte.js' +import { useConfig } from './useConfig.svelte.js' + +export type UseReadContractParameters< + abi extends Abi | readonly unknown[] = Abi, + functionName extends ContractFunctionName< + abi, + 'pure' | 'view' + > = ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'pure' | 'view', + functionName + > = ContractFunctionArgs, + config extends Config = Config, + selectData = ReadContractData, +> = RuneParameters< + UnionCompute< + ReadContractOptions & + ConfigParameter & + QueryParameter< + ReadContractQueryFnData, + ReadContractErrorType, + selectData, + ReadContractQueryKey + > + > +> + +export type UseReadContractReturnType< + abi extends Abi | readonly unknown[] = Abi, + functionName extends ContractFunctionName< + abi, + 'pure' | 'view' + > = ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'pure' | 'view', + functionName + > = ContractFunctionArgs, + selectData = ReadContractData, +> = RuneReturnType> + +/** https://wagmi.sh/react/api/hooks/useReadContract */ +export function useReadContract< + const abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs, + config extends Config = ResolvedRegister['config'], + selectData = ReadContractData, +>( + parameters: UseReadContractParameters< + abi, + functionName, + args, + config, + selectData + > = () => ({}) as any, +): UseReadContractReturnType { + const { abi, address, functionName, query = {} } = $derived.by(parameters) + // @ts-ignore + const code = $derived(parameters().code as Hex | undefined) + + const config = $derived.by(useConfig(parameters)) + const chainId = $derived.by(useChainId(() => ({ config }))) + + const options = $derived( + readContractQueryOptions(config, { + ...(parameters() as any), + chainId: parameters().chainId ?? chainId, + }), + ) + const enabled = $derived( + Boolean( + (address || code) && abi && functionName && (query.enabled ?? true), + ), + ) + + return createQuery(() => ({ + ...query, + ...options, + enabled, + structuralSharing: query.structuralSharing ?? structuralSharing, + })) +}