diff --git a/.gitignore b/.gitignore index 1834fe72ab..dde82b8cb4 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,10 @@ tsconfig.tsbuildinfo .env.production.local .envrc +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* + # proxy packages packages/cli/config packages/cli/plugins @@ -37,3 +41,6 @@ packages/vue/chains packages/vue/connectors packages/vue/nuxt packages/vue/query +packages/svelte/.svelte-kit +packages/svelte/.output + diff --git a/biome.json b/biome.json index ce99662cb0..3d3bacce99 100644 --- a/biome.json +++ b/biome.json @@ -11,7 +11,12 @@ "lineWidth": 80 }, "linter": { - "ignore": ["packages/create-wagmi/templates/*"], + "ignore": [ + "packages/create-wagmi/templates/*", + "packages/svelte/**/*.svelte", + "playgrounds/sveltekit/.svelte-kit/**", + "playgrounds/sveltekit/**/*.svelte" + ], "enabled": true, "rules": { "recommended": true, diff --git a/package.json b/package.json index 23205a136c..9ec30c872a 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "dev:nuxt": "pnpm --filter nuxt-app dev", "dev:react": "pnpm --filter vite-react dev", "dev:vue": "pnpm --filter vite-vue dev", + "dev:svelte": "pnpm --filter sveltekit dev", "docs:dev": "pnpm --filter site dev", "format": "biome format --write", "postinstall": "pnpm preconstruct", @@ -37,6 +38,7 @@ "test:update": "vitest --update", "test:ui": "vitest --ui", "test:vue": "vitest --project @wagmi/vue", + "test:svelte": "vitest --project @wagmi/svelte", "version:update": "bun scripts/updateVersion.ts", "version:update:viem": "bun scripts/updateViemVersion.ts" }, diff --git a/packages/svelte/.prettierignore b/packages/svelte/.prettierignore new file mode 100644 index 0000000000..ab78a95ddd --- /dev/null +++ b/packages/svelte/.prettierignore @@ -0,0 +1,4 @@ +# Package Managers +package-lock.json +pnpm-lock.yaml +yarn.lock diff --git a/packages/svelte/.prettierrc b/packages/svelte/.prettierrc new file mode 100644 index 0000000000..3f7802c372 --- /dev/null +++ b/packages/svelte/.prettierrc @@ -0,0 +1,15 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "plugins": ["prettier-plugin-svelte"], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ] +} diff --git a/packages/svelte/README.md b/packages/svelte/README.md new file mode 100644 index 0000000000..89f30467b8 --- /dev/null +++ b/packages/svelte/README.md @@ -0,0 +1,13 @@ +# @wagmi/vue + +Svelte Runes for Ethereum + +## Installation + +```bash +pnpm add @wagmi/svelte viem https://pkg.pr.new/@tanstack/svelte-query@ccce0b8 +``` + +## Documentation + +For documentation and guides, visit [wagmi.sh](https://wagmi.sh). diff --git a/packages/svelte/eslint.config.js b/packages/svelte/eslint.config.js new file mode 100644 index 0000000000..ae06ec3043 --- /dev/null +++ b/packages/svelte/eslint.config.js @@ -0,0 +1,33 @@ +import js from '@eslint/js' +import prettier from 'eslint-config-prettier' +import svelte from 'eslint-plugin-svelte' +import globals from 'globals' +import ts from 'typescript-eslint' + +export default ts.config( + js.configs.recommended, + ...ts.configs.recommended, + ...svelte.configs['flat/recommended'], + prettier, + ...svelte.configs['flat/prettier'], + { + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + }, + }, + }, + { + files: ['**/*.svelte'], + + languageOptions: { + parserOptions: { + parser: ts.parser, + }, + }, + }, + { + ignores: ['build/', '.svelte-kit/', 'dist/'], + }, +) diff --git a/packages/svelte/package.json b/packages/svelte/package.json new file mode 100644 index 0000000000..b77e6497db --- /dev/null +++ b/packages/svelte/package.json @@ -0,0 +1,51 @@ +{ + "name": "@wagmi/svelte", + "version": "0.0.1", + "scripts": { + "build": "vite build && npm run package", + "preview": "vite preview", + "package": "svelte-kit sync && svelte-package && publint", + "prepublishOnly": "npm run package", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" + }, + "files": ["dist", "!dist/**/*.test.*", "!dist/**/*.spec.*"], + "sideEffects": ["**/*.css"], + "svelte": "./dist/index.js", + "types": "./dist/index.d.ts", + "type": "module", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "svelte": "./dist/index.js" + }, + "./chains": { + "types": "./dist/exports/chains.d.ts", + "default": "./dist/exports/chains.js" + } + }, + "peerDependencies": { + "@tanstack/svelte-query": "https://pkg.pr.new/@tanstack/svelte-query@ccce0b8", + "viem": "2.x", + "svelte": "^5.0.0" + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^3.0.0", + "@sveltejs/kit": "^2.0.0", + "@sveltejs/package": "^2.0.0", + "@sveltejs/vite-plugin-svelte": "^4.0.0", + "@tanstack/svelte-query": "https://pkg.pr.new/@tanstack/svelte-query@ccce0b8", + "@wagmi/test": "workspace:*", + "globals": "^15.0.0", + "publint": "^0.2.0", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "typescript": "^5.0.0", + "vite": "^5.0.11", + "vitest": "^2.0.4" + }, + "dependencies": { + "@wagmi/connectors": "workspace:*", + "@wagmi/core": "workspace:*" + } +} diff --git a/packages/svelte/src/WagmiProvider.svelte b/packages/svelte/src/WagmiProvider.svelte new file mode 100644 index 0000000000..9833ff4705 --- /dev/null +++ b/packages/svelte/src/WagmiProvider.svelte @@ -0,0 +1,43 @@ + + +{@render children()} diff --git a/packages/svelte/src/app.d.ts b/packages/svelte/src/app.d.ts new file mode 100644 index 0000000000..7ebc6f0435 --- /dev/null +++ b/packages/svelte/src/app.d.ts @@ -0,0 +1,15 @@ +// See https://svelte.dev/docs/kit/types#app +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } + + const __VERSION__: string +} + +export {} diff --git a/packages/svelte/src/app.html b/packages/svelte/src/app.html new file mode 100644 index 0000000000..f22aeaad5e --- /dev/null +++ b/packages/svelte/src/app.html @@ -0,0 +1,12 @@ + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/packages/svelte/src/context.ts b/packages/svelte/src/context.ts new file mode 100644 index 0000000000..37edb84c22 --- /dev/null +++ b/packages/svelte/src/context.ts @@ -0,0 +1,15 @@ +import type { ResolvedRegister } from '@wagmi/core' +import { getContext, setContext } from 'svelte' + +type ContextConfig = ResolvedRegister['config'] +const WAGMI_CONFIG_KEY = Symbol('WAGMI_CONFIG') + +export const getWagmiConfig = (): ContextConfig => { + const client = getContext(WAGMI_CONFIG_KEY) + + return client +} + +export const setWagmiConfig = (config: ContextConfig) => { + setContext(WAGMI_CONFIG_KEY, config) +} diff --git a/packages/svelte/src/errors.test.ts b/packages/svelte/src/errors.test.ts new file mode 100644 index 0000000000..7a9fea3135 --- /dev/null +++ b/packages/svelte/src/errors.test.ts @@ -0,0 +1,164 @@ +import { expect, test } from 'vitest' + +import { BaseError, WagmiProviderNotFoundError } from './errors.js' + +test('BaseError', () => { + expect(new BaseError('An error occurred.')).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Version: @wagmi/svelte@x.y.z] + `) + + expect( + new BaseError('An error occurred.', { details: 'details' }), + ).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Details: details + Version: @wagmi/svelte@x.y.z] + `) + + expect(new BaseError('', { details: 'details' })).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Details: details + Version: @wagmi/svelte@x.y.z] + `) +}) + +test('BaseError (w/ docsPath)', () => { + expect( + new BaseError('An error occurred.', { + details: 'details', + docsPath: '/lol', + }), + ).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Docs: https://wagmi.sh/react/lol.html + Details: details + Version: @wagmi/svelte@x.y.z] + `) + expect( + new BaseError('An error occurred.', { + cause: new BaseError('error', { docsPath: '/docs' }), + }), + ).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Docs: https://wagmi.sh/react/docs.html + Version: @wagmi/svelte@x.y.z] + `) + expect( + new BaseError('An error occurred.', { + cause: new BaseError('error'), + docsPath: '/lol', + }), + ).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Docs: https://wagmi.sh/react/lol.html + Version: @wagmi/svelte@x.y.z] + `) + expect( + new BaseError('An error occurred.', { + details: 'details', + docsPath: '/lol', + docsSlug: 'test', + }), + ).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Docs: https://wagmi.sh/react/lol.html#test + Details: details + Version: @wagmi/svelte@x.y.z] + `) +}) + +test('BaseError (w/ metaMessages)', () => { + expect( + new BaseError('An error occurred.', { + details: 'details', + metaMessages: ['Reason: idk', 'Cause: lol'], + }), + ).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Reason: idk + Cause: lol + + Details: details + Version: @wagmi/svelte@x.y.z] + `) +}) + +test('inherited BaseError', () => { + const err = new BaseError('An error occurred.', { + details: 'details', + docsPath: '/lol', + }) + expect( + new BaseError('An internal error occurred.', { + cause: err, + }), + ).toMatchInlineSnapshot(` + [WagmiError: An internal error occurred. + + Docs: https://wagmi.sh/react/lol.html + Details: details + Version: @wagmi/svelte@x.y.z] + `) +}) + +test('inherited Error', () => { + const err = new Error('details') + expect( + new BaseError('An internal error occurred.', { + cause: err, + docsPath: '/lol', + }), + ).toMatchInlineSnapshot(` + [WagmiError: An internal error occurred. + + Docs: https://wagmi.sh/react/lol.html + Details: details + Version: @wagmi/svelte@x.y.z] + `) +}) + +test('walk: no predicate fn (walks to leaf)', () => { + class FooError extends BaseError {} + class BarError extends BaseError {} + + const err = new BaseError('test1', { + cause: new FooError('test2', { cause: new BarError('test3') }), + }) + expect(err.walk()).toMatchInlineSnapshot(` + [WagmiError: test3 + + Version: @wagmi/svelte@x.y.z] + `) +}) + +test('walk: predicate fn', () => { + class FooError extends BaseError {} + class BarError extends BaseError {} + + const err = new BaseError('test1', { + cause: new FooError('test2', { cause: new BarError('test3') }), + }) + expect(err.walk((err) => err instanceof FooError)).toMatchInlineSnapshot(` + [WagmiError: test2 + + Version: @wagmi/svelte@x.y.z] + `) +}) + +test('WagmiProviderNotFoundError', () => { + expect(new WagmiProviderNotFoundError()).toMatchInlineSnapshot(` + [WagmiProviderNotFoundError: \`useConfig\` must be used within \`WagmiProvider\`. + + Docs: https://wagmi.sh/react/api/WagmiProvider.html + Version: @wagmi/svelte@x.y.z] + `) +}) diff --git a/packages/svelte/src/errors.ts b/packages/svelte/src/errors.ts new file mode 100644 index 0000000000..91ec8e8722 --- /dev/null +++ b/packages/svelte/src/errors.ts @@ -0,0 +1,27 @@ +import { BaseError as CoreError } from '@wagmi/core' + +import { getVersion } from './version.js' + +export type BaseErrorType = BaseError & { name: 'WagmiError' } + +export class BaseError extends CoreError { + override name = 'WagmiError' + override get docsBaseUrl() { + return 'https://wagmi.sh/react' + } + override get version() { + return getVersion() + } +} + +export type WagmiProviderNotFoundErrorType = WagmiProviderNotFoundError & { + name: 'WagmiProviderNotFoundError' +} +export class WagmiProviderNotFoundError extends BaseError { + override name = 'WagmiProviderNotFoundError' + constructor() { + super('`useConfig` must be used within `WagmiProvider`.', { + docsPath: '/api/WagmiProvider', + }) + } +} diff --git a/packages/svelte/src/exports/chains.ts b/packages/svelte/src/exports/chains.ts new file mode 100644 index 0000000000..dfe5ccc8c0 --- /dev/null +++ b/packages/svelte/src/exports/chains.ts @@ -0,0 +1,3 @@ +// biome-ignore lint/performance/noBarrelFile: entrypoint module +// biome-ignore lint/performance/noReExportAll: entrypoint module +export * from 'viem/chains' diff --git a/packages/svelte/src/exports/index.ts b/packages/svelte/src/exports/index.ts new file mode 100644 index 0000000000..be80b3e1bc --- /dev/null +++ b/packages/svelte/src/exports/index.ts @@ -0,0 +1,219 @@ +//////////////////////////////////////////////////////////////////////////////// +// Errors +//////////////////////////////////////////////////////////////////////////////// + +// biome-ignore lint/performance/noBarrelFile: entrypoint module +export { + type BaseErrorType, + BaseError, + type WagmiProviderNotFoundErrorType, + WagmiProviderNotFoundError, +} from '../errors.js' + +//////////////////////////////////////////////////////////////////////////////// +// Hooks +//////////////////////////////////////////////////////////////////////////////// + +export { default as WagmiProvider } from '../WagmiProvider.svelte' + +export { + type UseAccountParameters, + type UseAccountReturnType, + useAccount, +} from '../hooks/useAccount.svelte.js' + +export { + type UseAccountEffectParameters, + useAccountEffect, +} from '../hooks/useAccountEffect.svelte.js' + +export { + type UseBalanceParameters, + type UseBalanceReturnType, + useBalance, +} from '../hooks/useBalance.svelte.js' + +export { + type UseBlockNumberParameters, + type UseBlockNumberReturnType, + useBlockNumber, +} from '../hooks/useBlockNumber.svelte.js' + +export { + type UseChainIdParameters, + type UseChainIdReturnType, + useChainId, +} from '../hooks/useChainId.svelte.js' + +export { + type UseChainsParameters, + type UseChainsReturnType, + useChains, +} from '../hooks/useChains.svelte.js' + +export { + type UseConfigParameters, + type UseConfigReturnType, + useConfig, +} from '../hooks/useConfig.svelte.js' + +export { + type UseConnectParameters, + type UseConnectReturnType, + useConnect, +} from '../hooks/useConnect.svelte.js' + +export { + type UseConnectionsParameters, + type UseConnectionsReturnType, + useConnections, +} from '../hooks/useConnections.svelte.js' + +export { + type UseConnectorsParameters, + type UseConnectorsReturnType, + useConnectors, +} from '../hooks/useConnectors.svelte.js' + +export { + type UseDisconnectParameters, + type UseDisconnectReturnType, + useDisconnect, +} from '../hooks/useDisconnect.svelte.js' + +export { + type UseEnsAvatarParameters, + type UseEnsAvatarReturnType, + useEnsAvatar, +} from '../hooks/useEnsAvatar.svelte.js' + +export { + type UseEnsNameParameters, + type UseEnsNameReturnType, + useEnsName, +} from '../hooks/useEnsName.svelte.js' + +export { + type UseGasPriceParameters, + type UseGasPriceReturnType, + useGasPrice, +} from '../hooks/useGasPrice.svelte.js' + +export { + type UseReadContractParameters, + type UseReadContractReturnType, + useReadContract, + /** @deprecated Use `useWriteContract` instead */ + useReadContract as useContractRead, +} from '../hooks/useReadContract.svelte.js' + +export { + type UseReadContractsParameters, + type UseReadContractsReturnType, + useReadContracts, + /** @deprecated Use `useWriteContract` instead */ + useReadContracts as useContractReads, +} from '../hooks/useReadContracts.svelte.js' + +export { + type UseSendTransactionParameters, + type UseSendTransactionReturnType, + useSendTransaction, +} from '../hooks/useSendTransaction.svelte.js' + +export { + type UseSignMessageParameters, + type UseSignMessageReturnType, + useSignMessage, +} from '../hooks/useSignMessage.svelte.js' + +export { + type UseSwitchAccountParameters, + type UseSwitchAccountReturnType, + useSwitchAccount, +} from '../hooks/useSwitchAccount.svelte.js' + +export { + type UseSwitchChainParameters, + type UseSwitchChainReturnType, + useSwitchChain, +} from '../hooks/useSwitchChain.svelte.js' + +export { + type UseWaitForTransactionReceiptParameters, + type UseWaitForTransactionReceiptReturnType, + useWaitForTransactionReceipt, +} from '../hooks/useWaitForTransactionReceipt.svelte.js' + +export { + type UseWatchBlockNumberParameters, + type UseWatchBlockNumberReturnType, + useWatchBlockNumber, +} from '../hooks/useWatchBlockNumber.svelte.js' + +export { + type UseWriteContractParameters, + type UseWriteContractReturnType, + useWriteContract, + /** @deprecated Use `useWriteContract` instead */ + useWriteContract as useContractWrite, +} from '../hooks/useWriteContract.svelte.js' + +//////////////////////////////////////////////////////////////////////////////// +// @wagmi/core +//////////////////////////////////////////////////////////////////////////////// + +export { + // Config + type Connection, + type Connector, + type Config, + type CreateConfigParameters, + type PartializedState, + type State, + createConfig, + // Connector + type ConnectorEventMap, + type CreateConnectorFn, + createConnector, + // Errors + type ChainNotConfiguredErrorType, + ChainNotConfiguredError, + type ConnectorAlreadyConnectedErrorType, + ConnectorAlreadyConnectedError, + type ConnectorNotFoundErrorType, + ConnectorNotFoundError, + type ConnectorAccountNotFoundErrorType, + ConnectorAccountNotFoundError, + type ConnectorChainMismatchErrorType, + ConnectorChainMismatchError, + type ConnectorUnavailableReconnectingErrorType, + ConnectorUnavailableReconnectingError, + type ProviderNotFoundErrorType, + ProviderNotFoundError, + type SwitchChainNotSupportedErrorType, + SwitchChainNotSupportedError, + // Storage + type CreateStorageParameters, + type Storage, + createStorage, + noopStorage, + // Transports + custom, + fallback, + http, + webSocket, + unstable_connector, + type Transport, + // Types + type Register, + type ResolvedRegister, + // Utilities + cookieStorage, + cookieToInitialState, + deepEqual, + deserialize, + normalizeChainId, + parseCookie, + serialize, +} from '@wagmi/core' diff --git a/packages/svelte/src/hooks/useAccount.svelte.test.ts b/packages/svelte/src/hooks/useAccount.svelte.test.ts new file mode 100644 index 0000000000..eb8a6599fa --- /dev/null +++ b/packages/svelte/src/hooks/useAccount.svelte.test.ts @@ -0,0 +1,33 @@ +import { connect, disconnect } from '@wagmi/core' +import { config } from '@wagmi/test' +import { testHook } from '@wagmi/test/svelte' +import { expect, test } from 'vitest' +import { useAccount } from './useAccount.svelte.js' + +test( + 'default', + testHook(async () => { + const result = $derived.by(useAccount()) + + expect(result.address).not.toBeDefined() + expect(result.status).toEqual('disconnected') + + await connect(config, { connector: config.connectors[0]! }) + + await expect.poll(() => result.address).toBeDefined() + await expect.poll(() => result.status).toEqual('connected') + + await disconnect(config) + }), +) + +test( + 'parameters: config', + testHook( + () => { + const result = $derived.by(() => useAccount(() => ({ config }))) + expect(result).toBeDefined() + }, + { shouldMockConfig: false }, + ), +) diff --git a/packages/svelte/src/hooks/useAccount.svelte.ts b/packages/svelte/src/hooks/useAccount.svelte.ts new file mode 100644 index 0000000000..9198473b76 --- /dev/null +++ b/packages/svelte/src/hooks/useAccount.svelte.ts @@ -0,0 +1,40 @@ +import { + type Config, + type GetAccountReturnType, + type ResolvedRegister, + getAccount, + watchAccount, +} from '@wagmi/core' +import type { + ConfigParameter, + RuneParameters, + RuneReturnType, +} from '../types.js' +import { useConfig } from './useConfig.svelte.js' + +export type UseAccountParameters = + RuneParameters> + +export type UseAccountReturnType = + RuneReturnType> + +/** https://wagmi.sh/react/api/hooks/useAccount */ +export function useAccount( + parameters: UseAccountParameters = () => ({}), +): UseAccountReturnType { + const config = $derived.by(useConfig(parameters)) + + let account = $state(getAccount(config)) + + $effect(() => { + account = getAccount(config) + + return watchAccount(config, { + onChange(newAccount) { + account = newAccount + }, + }) + }) + + return () => account +} diff --git a/packages/svelte/src/hooks/useAccountEffect.svelte.test.ts b/packages/svelte/src/hooks/useAccountEffect.svelte.test.ts new file mode 100644 index 0000000000..b11b554585 --- /dev/null +++ b/packages/svelte/src/hooks/useAccountEffect.svelte.test.ts @@ -0,0 +1,92 @@ +import { mock } from '@wagmi/connectors' +import { + http, + connect, + createConfig, + disconnect, + getAccount, + reconnect, +} from '@wagmi/core' +import { accounts, chain, config, wait } from '@wagmi/test' +import { testHook } from '@wagmi/test/svelte' +import { expect, test, vi } from 'vitest' +import { useAccountEffect } from './useAccountEffect.svelte' +import { useConnect } from './useConnect.svelte' +import { useDisconnect } from './useDisconnect.svelte' + +test( + 'parameters: config', + testHook( + () => { + const result = useAccountEffect(() => ({ config })) + expect(result).toBeUndefined() + }, + { shouldMockConfig: false }, + ), +) + +test( + 'behavior: connect and disconnect called once', + testHook(async () => { + const onConnect = vi.fn() + const onDisconnect = vi.fn() + + const connectHook = $derived.by(useConnect()) + const disconnectHook = $derived.by(useDisconnect()) + + useAccountEffect(() => ({ onConnect, onDisconnect })) + + connectHook.connect({ connector: connectHook.connectors[0]! }) + await expect.poll(() => connectHook.data?.accounts).toBeTruthy() + + connectHook.connect({ connector: connectHook.connectors[0]! }) + await expect.poll(() => connectHook.isSuccess).toBeTruthy() + + await wait(1000) // TODO: why is this needed? + + disconnectHook.disconnect() + await expect.poll(() => disconnectHook.isSuccess).toBeTruthy() + + disconnectHook.disconnect() + await expect.poll(() => disconnectHook.isSuccess).toBeTruthy() + + expect(onConnect).toBeCalledTimes(1) + expect(onDisconnect).toBeCalledTimes(1) + }), +) + +const mockConnector = mock({ + accounts, + features: { reconnect: true }, +}) +const newConfig = createConfig({ + chains: [chain.mainnet], + connectors: [mockConnector], + transports: { [chain.mainnet.id]: http() }, +}) + +test( + 'behavior: connect called on reconnect', + testHook( + async () => { + const onConnect = vi.fn((data) => { + expect(data.isReconnected).toBeTruthy() + }) + + useAccountEffect(() => ({ onConnect })) + + await reconnect(newConfig) + + await connect(newConfig, { connector: mockConnector }) + + await expect.poll(() => onConnect).toHaveBeenCalledOnce() + }, + { mockConfigOverride: newConfig, reconnectOnMount: true }, + async () => { + await connect(newConfig, { connector: mockConnector }) + }, + async () => { + await disconnect(newConfig) + }, + ), +) diff --git a/packages/svelte/src/hooks/useAccountEffect.svelte.ts b/packages/svelte/src/hooks/useAccountEffect.svelte.ts new file mode 100644 index 0000000000..623829ced2 --- /dev/null +++ b/packages/svelte/src/hooks/useAccountEffect.svelte.ts @@ -0,0 +1,63 @@ +import { type GetAccountReturnType, watchAccount } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import type { RuneParameters } from '../types.js' +import type { ConfigParameter } from '../types.js' +import { useConfig } from './useConfig.svelte.js' + +export type UseAccountEffectParameters = RuneParameters< + Compute< + { + onConnect?( + data: Compute< + Pick< + Extract, + 'address' | 'addresses' | 'chain' | 'chainId' | 'connector' + > & { + isReconnected: boolean + } + >, + ): void + onDisconnect?(): void + } & ConfigParameter + > +> + +/** https://wagmi.sh/react/api/hooks/useAccountEffect */ +export function useAccountEffect( + parameters: UseAccountEffectParameters = () => ({}), +) { + const { onConnect, onDisconnect } = $derived(parameters()) + + const config = $derived.by(useConfig(parameters)) + + $effect(() => { + return watchAccount(config, { + onChange(data, prevData) { + if ( + (prevData.status === 'reconnecting' || + (prevData.status === 'connecting' && + prevData.address === undefined)) && + data.status === 'connected' + ) { + const { address, addresses, chain, chainId, connector } = data + const isReconnected = + prevData.status === 'reconnecting' || + // if `previousAccount.status` is `undefined`, the connector connected immediately. + prevData.status === undefined + onConnect?.({ + address, + addresses, + chain, + chainId, + connector, + isReconnected, + }) + } else if ( + prevData.status === 'connected' && + data.status === 'disconnected' + ) + onDisconnect?.() + }, + }) + }) +} diff --git a/packages/svelte/src/hooks/useBalance.svelte.test.ts b/packages/svelte/src/hooks/useBalance.svelte.test.ts new file mode 100644 index 0000000000..b9f4edd56a --- /dev/null +++ b/packages/svelte/src/hooks/useBalance.svelte.test.ts @@ -0,0 +1,333 @@ +import { accounts, chain, testClient, wait } from '@wagmi/test' +import { testHook } from '@wagmi/test/svelte' +import { flushSync } from 'svelte' +import { type Address, parseEther } from 'viem' +import { beforeEach, expect, test } from 'vitest' +import { useBalance } from './useBalance.svelte.js' + +const address = accounts[0] + +beforeEach(async () => { + await testClient.mainnet.setBalance({ address, value: parseEther('10000') }) + // await testClient.mainnet.mine({ blocks: 1 }) + // await testClient.mainnet2.setBalance({ address, value: parseEther('69') }) + // await testClient.mainnet2.mine({ blocks: 1 }) +}) + +test( + 'default', + testHook(async () => { + const result = $derived.by(useBalance(() => ({ address }))) + + await expect.poll(() => result.isSuccess).toBeTruthy() + + const { data, ...rest } = result + expect(data).toMatchObject( + expect.objectContaining({ + decimals: expect.any(Number), + formatted: expect.any(String), + symbol: expect.any(String), + value: expect.any(BigInt), + }), + ) + expect(rest).toMatchInlineSnapshot(` + { + "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": [ + "balance", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) + }), +) + +test( + 'parameters: chainId', + testHook(async () => { + const result = $derived.by( + useBalance(() => ({ address, chainId: chain.mainnet2.id })), + ) + + await expect.poll(() => result.isSuccess).toBeTruthy() + + expect(result).toMatchInlineSnapshot(` + { + "data": { + "decimals": 18, + "formatted": "10000", + "symbol": "WAG", + "value": 10000000000000000000000n, + }, + "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": [ + "balance", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 456, + }, + ], + "refetch": [Function], + "status": "success", + } + `) + }), +) + +test( + 'parameters: token', + testHook(async () => { + const result = $derived.by( + useBalance(() => ({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + token: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + })), + ) + + await expect.poll(() => result.isSuccess).toBeTruthy() + + expect(result).toMatchInlineSnapshot(` + { + "data": { + "decimals": 18, + "formatted": "0.559062564299199392", + "symbol": "DAI", + "value": 559062564299199392n, + }, + "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": [ + "balance", + { + "address": "0x4557B18E779944BFE9d78A672452331C186a9f48", + "chainId": 1, + "token": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + }, + ], + "refetch": [Function], + "status": "success", + } + `) + }), +) + +test( + 'parameters: unit', + testHook(async () => { + const result = $derived.by( + useBalance(() => ({ + address, + chainId: chain.mainnet2.id, + unit: 'wei', + })), + ) + + await expect.poll(() => result.isSuccess).toBeTruthy() + + expect(result).toMatchInlineSnapshot(` + { + "data": { + "decimals": 18, + "formatted": "10000000000000000000000", + "symbol": "WAG", + "value": 10000000000000000000000n, + }, + "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": [ + "balance", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 456, + "unit": "wei", + }, + ], + "refetch": [Function], + "status": "success", + } + `) + }), +) + +test( + 'behavior: address: undefined -> defined', + testHook(async () => { + let address: Address | undefined = $state(undefined) + const result = $derived.by(useBalance(() => ({ address }))) + + expect(result).toMatchInlineSnapshot(` + { + "data": undefined, + "dataUpdatedAt": 0, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": false, + "isFetchedAfterMount": false, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": true, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": false, + "isSuccess": false, + "queryKey": [ + "balance", + { + "address": undefined, + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "pending", + } + `) + + address = accounts[0] + flushSync() + + await expect.poll(() => result.isSuccess).toBeTruthy() + + expect(result).toMatchInlineSnapshot(` + { + "data": { + "decimals": 18, + "formatted": "10000", + "symbol": "ETH", + "value": 10000000000000000000000n, + }, + "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": [ + "balance", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) + }), +) + +test( + 'behavior: disabled when properties missing', + testHook(async () => { + const result = $derived.by(useBalance()) + + await wait(100) + await expect.poll(() => result.isPending).toBeTruthy() + }), +) diff --git a/packages/svelte/src/hooks/useBalance.svelte.ts b/packages/svelte/src/hooks/useBalance.svelte.ts new file mode 100644 index 0000000000..62131f7751 --- /dev/null +++ b/packages/svelte/src/hooks/useBalance.svelte.ts @@ -0,0 +1,62 @@ +import type { Config, GetBalanceErrorType, ResolvedRegister } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import type { GetBalanceQueryFnData } from '@wagmi/core/query' +import { + type GetBalanceData, + type GetBalanceOptions, + type GetBalanceQueryKey, + getBalanceQueryOptions, +} from '@wagmi/core/query' + +import { type CreateQueryReturnType, createQuery } from '../query.svelte.js' +import type { + ConfigParameter, + QueryParameter, + RuneParameters, + RuneReturnType, +} from '../types.js' +import { useChainId } from './useChainId.svelte.js' +import { useConfig } from './useConfig.svelte.js' + +export type UseBalanceParameters< + config extends Config = Config, + selectData = GetBalanceData, +> = RuneParameters< + Compute< + GetBalanceOptions & + ConfigParameter & + QueryParameter< + GetBalanceQueryFnData, + GetBalanceErrorType, + selectData, + GetBalanceQueryKey + > + > +> + +export type UseBalanceReturnType = RuneReturnType< + CreateQueryReturnType +> + +/** https://wagmi.sh/react/api/hooks/useBalance */ +export function useBalance< + config extends Config = ResolvedRegister['config'], + selectData = GetBalanceData, +>( + parameters: UseBalanceParameters = () => ({}), +): UseBalanceReturnType { + const { address, query = {} } = $derived(parameters()) + + const config = $derived.by(useConfig(parameters)) + const chainId = $derived.by(useChainId(parameters)) + + const options = $derived( + getBalanceQueryOptions(config, { + ...parameters(), + chainId: parameters().chainId ?? chainId, + }), + ) + const enabled = $derived(Boolean(address && (query.enabled ?? true))) + + return createQuery(() => ({ ...query, ...options, enabled })) +} diff --git a/packages/svelte/src/hooks/useBlockNumber.svelte.test.ts b/packages/svelte/src/hooks/useBlockNumber.svelte.test.ts new file mode 100644 index 0000000000..9632fc5953 --- /dev/null +++ b/packages/svelte/src/hooks/useBlockNumber.svelte.test.ts @@ -0,0 +1,79 @@ +import { getBlockNumber } from '@wagmi/core' +import { config, testClient, wait } from '@wagmi/test' +import { testHook } from '@wagmi/test/svelte' +import { flushSync } from 'svelte' +import { expect, test } from 'vitest' +import { useBlockNumber } from './useBlockNumber.svelte.js' + +test( + 'mounts', + testHook( + async () => { + const result = $derived.by(useBlockNumber()) + + await expect.poll(() => result.isSuccess).toBeTruthy() + + expect(result).toMatchInlineSnapshot(` + { + "data": 19258213n, + "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": [ + "blockNumber", + { + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) + }, + {}, + async () => { + await testClient.mainnet.resetFork() + }, + ), +) + +test( + 'parameters: watch', + testHook( + async () => { + const result = $derived.by(useBlockNumber(() => ({ watch: true }))) + + await expect.poll(() => result.isSuccess).toBeTruthy() + const blockNumber = result.data! + expect(result.data).toMatchInlineSnapshot('19258213n') + + await testClient.mainnet.mine({ blocks: 1 }) + await expect.poll(() => result.data).toEqual(blockNumber + 1n) + + await testClient.mainnet.mine({ blocks: 1 }) + await expect.poll(() => result.data).toEqual(blockNumber + 2n) + }, + {}, + async () => { + await testClient.mainnet.resetFork() + }, + ), +) diff --git a/packages/svelte/src/hooks/useBlockNumber.svelte.ts b/packages/svelte/src/hooks/useBlockNumber.svelte.ts new file mode 100644 index 0000000000..e4f9be5da0 --- /dev/null +++ b/packages/svelte/src/hooks/useBlockNumber.svelte.ts @@ -0,0 +1,108 @@ +import { useQueryClient } from '@tanstack/svelte-query' +import type { + Config, + GetBlockNumberErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { + Compute, + UnionCompute, + UnionStrictOmit, +} from '@wagmi/core/internal' +import { + type GetBlockNumberData, + type GetBlockNumberOptions, + type GetBlockNumberQueryFnData, + type GetBlockNumberQueryKey, + getBlockNumberQueryOptions, +} from '@wagmi/core/query' + +import { type CreateQueryReturnType, createQuery } from '../query.svelte.js' +import type { + ConfigParameter, + QueryParameter, + RuneParameters, + RuneReturnType, +} from '../types.js' +import { useChainId } from './useChainId.svelte.js' +import { useConfig } from './useConfig.svelte.js' +import { + type UseWatchBlockNumberParameters, + useWatchBlockNumber, +} from './useWatchBlockNumber.svelte.js' + +export type UseBlockNumberParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetBlockNumberData, +> = RuneParameters< + Compute< + GetBlockNumberOptions & + ConfigParameter & + QueryParameter< + GetBlockNumberQueryFnData, + GetBlockNumberErrorType, + selectData, + GetBlockNumberQueryKey + > & { + watch?: + | boolean + | UnionCompute< + UnionStrictOmit< + ReturnType>, + 'chainId' | 'config' | 'onBlockNumber' | 'onError' + > + > + | undefined + } + > +> + +export type UseBlockNumberReturnType = + RuneReturnType> + +/** https://wagmi.sh/react/api/hooks/useBlockNumber */ +export function useBlockNumber< + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetBlockNumberData, +>( + parameters: UseBlockNumberParameters< + config, + chainId, + selectData + > = () => ({}), +): UseBlockNumberReturnType { + const { query = {}, watch } = $derived(parameters()) + + const config = $derived.by(useConfig(parameters)) + const queryClient = useQueryClient() + const configChainId = $derived.by(useChainId(parameters)) + const chainId = $derived(parameters().chainId ?? configChainId) + + const options = $derived( + getBlockNumberQueryOptions(config, { + ...parameters(), + chainId, + }), + ) + + useWatchBlockNumber(() => ({ + ...({ + config: parameters().config, + chainId: parameters().chainId, + ...(typeof watch === 'object' ? watch : {}), + } as ReturnType), + enabled: Boolean( + (query.enabled ?? true) && + (typeof watch === 'object' ? watch.enabled : watch), + ), + onBlockNumber(blockNumber) { + queryClient.setQueryData(options.queryKey, blockNumber) + }, + })) + + return createQuery(() => ({ ...query, ...options })) +} diff --git a/packages/svelte/src/hooks/useChainId.svelte.test.ts b/packages/svelte/src/hooks/useChainId.svelte.test.ts new file mode 100644 index 0000000000..ce0b39a0c4 --- /dev/null +++ b/packages/svelte/src/hooks/useChainId.svelte.test.ts @@ -0,0 +1,30 @@ +import { config } from '@wagmi/test' +import { testHook } from '@wagmi/test/svelte' +import { flushSync } from 'svelte' +import { expect, test, vi } from 'vitest' +import { useChainId } from './useChainId.svelte.js' + +test( + 'default', + testHook(async () => { + const chainId = $derived.by(useChainId()) + expect(chainId).toMatchInlineSnapshot('1') + + config.setState((x) => ({ ...x, chainId: 456 })) + const svelte = await import('svelte') + svelte.getContext = vi.fn().mockReturnValue(config) // TODO: why do i need to mock it again? + flushSync() + expect(chainId).toMatchInlineSnapshot('456') + }), +) + +test( + 'parameters: config', + testHook( + () => { + const chainId = $derived.by(useChainId(() => ({ config }))) + expect(chainId).toBeDefined() + }, + { shouldMockConfig: false }, + ), +) diff --git a/packages/svelte/src/hooks/useChainId.svelte.ts b/packages/svelte/src/hooks/useChainId.svelte.ts new file mode 100644 index 0000000000..4f492a5210 --- /dev/null +++ b/packages/svelte/src/hooks/useChainId.svelte.ts @@ -0,0 +1,41 @@ +import { + type Config, + type GetChainIdReturnType, + type ResolvedRegister, + getChainId, + watchChainId, +} from '@wagmi/core' + +import type { + ConfigParameter, + RuneParameters, + RuneReturnType, +} from '../types.js' +import { useConfig } from './useConfig.svelte.js' + +export type UseChainIdParameters = + RuneParameters> + +export type UseChainIdReturnType = + RuneReturnType> + +/** https://wagmi.sh/react/api/hooks/useChainId */ +export function useChainId( + parameters: UseChainIdParameters = () => ({}), +): UseChainIdReturnType { + const config = $derived.by(useConfig(parameters)) + let chainId = $state(getChainId(config)) + + $effect(() => { + chainId = getChainId(config) + const unsubscribe = watchChainId(config, { + onChange: (newChainId) => { + chainId = newChainId + }, + }) + + return unsubscribe + }) + + return () => chainId +} diff --git a/packages/svelte/src/hooks/useChains.svelte.test.ts b/packages/svelte/src/hooks/useChains.svelte.test.ts new file mode 100644 index 0000000000..c0bf1b3654 --- /dev/null +++ b/packages/svelte/src/hooks/useChains.svelte.test.ts @@ -0,0 +1,36 @@ +import { config } from '@wagmi/test' +import { testHook } from '@wagmi/test/svelte' +import { expect, test } from 'vitest' +import { useChains } from './useChains.svelte.js' + +test( + 'default', + testHook(async () => { + const result = $derived.by(useChains()) + + expect(result.map((x) => x.id)).toMatchInlineSnapshot(` + [ + 1, + 456, + 10, + ] + `) + }), +) + +test( + 'parameters: config', + testHook( + () => { + const result = $derived.by(useChains(() => ({ config }))) + expect(result.map((x) => x.id)).toMatchInlineSnapshot(` + [ + 1, + 456, + 10, + ] + `) + }, + { shouldMockConfig: false }, + ), +) diff --git a/packages/svelte/src/hooks/useChains.svelte.ts b/packages/svelte/src/hooks/useChains.svelte.ts new file mode 100644 index 0000000000..7da83dc375 --- /dev/null +++ b/packages/svelte/src/hooks/useChains.svelte.ts @@ -0,0 +1,41 @@ +import { + type Config, + type GetChainsReturnType, + type ResolvedRegister, + getChains, +} from '@wagmi/core' +import { watchChains } from '@wagmi/core/internal' + +import type { + ConfigParameter, + RuneParameters, + RuneReturnType, +} from '../types.js' +import { useConfig } from './useConfig.svelte.js' + +export type UseChainsParameters = + RuneParameters> + +export type UseChainsReturnType = + RuneReturnType> + +/** https://wagmi.sh/react/api/hooks/useChains */ +export function useChains( + parameters: UseChainsParameters = () => ({}), +): UseChainsReturnType { + const config = $derived.by(useConfig(parameters)) + let chains = $state(getChains(config)) + + $effect(() => { + chains = getChains(config) + const unsubscribe = watchChains(config, { + onChange: (newChains) => { + chains = newChains + }, + }) + + return unsubscribe + }) + + return () => chains +} diff --git a/packages/svelte/src/hooks/useConfig.svelte.test.ts b/packages/svelte/src/hooks/useConfig.svelte.test.ts new file mode 100644 index 0000000000..767dcb5c1e --- /dev/null +++ b/packages/svelte/src/hooks/useConfig.svelte.test.ts @@ -0,0 +1,21 @@ +import { testHook } from '@wagmi/test/svelte' +import { expect, test } from 'vitest' +import { useConfig } from './useConfig.svelte.js' + +test( + 'mounts', + testHook(() => { + const config = useConfig() + expect(config).toBeDefined() + }), +) + +test( + 'behavior: throws when not inside Provider', + testHook( + () => { + expect(() => useConfig()).toThrow() + }, + { shouldMockConfig: false }, + ), +) diff --git a/packages/svelte/src/hooks/useConfig.svelte.ts b/packages/svelte/src/hooks/useConfig.svelte.ts new file mode 100644 index 0000000000..998ae9baaf --- /dev/null +++ b/packages/svelte/src/hooks/useConfig.svelte.ts @@ -0,0 +1,26 @@ +import type { Config, ResolvedRegister } from '@wagmi/core' + +import { getWagmiConfig } from '$lib/context.js' +import { WagmiProviderNotFoundError } from '$lib/errors.js' +import type { + ConfigParameter, + RuneParameters, + RuneReturnType, +} from '../types.js' + +export type UseConfigParameters = + RuneParameters> + +export type UseConfigReturnType = + RuneReturnType + +/** https://wagmi.sh/react/api/hooks/useConfig */ +export function useConfig( + parameters: UseConfigParameters = () => ({}), +): UseConfigReturnType { + const contextConfig = getWagmiConfig() + const config = $derived(parameters().config ?? contextConfig) + if (!config) throw new WagmiProviderNotFoundError() + + return (() => config) as UseConfigReturnType +} diff --git a/packages/svelte/src/hooks/useConnect.svelte.test.ts b/packages/svelte/src/hooks/useConnect.svelte.test.ts new file mode 100644 index 0000000000..f66392db68 --- /dev/null +++ b/packages/svelte/src/hooks/useConnect.svelte.test.ts @@ -0,0 +1,33 @@ +import { disconnect } from '@wagmi/core' +import { config } from '@wagmi/test' +import { testHook } from '@wagmi/test/svelte' +import { afterEach, expect, test } from 'vitest' +import { useAccount } from './useAccount.svelte.js' +import { useConnect } from './useConnect.svelte.js' + +const connector = config.connectors[0]! + +afterEach(async () => { + if (config.state.current === connector.uid) + await disconnect(config, { connector }) +}) + +test( + 'default', + testHook(async () => { + const account = $derived.by(useAccount()) + const connect = $derived.by(useConnect()) + + expect(account.address).not.toBeDefined() + expect(account.status).toEqual('disconnected') + + connect.connect({ + connector: connect.connectors[0]!, + }) + + await expect.poll(() => account.isConnected).toBeTruthy() + + expect(account.address).toBeDefined() + expect(account.status).toEqual('connected') + }), +) diff --git a/packages/svelte/src/hooks/useConnect.svelte.ts b/packages/svelte/src/hooks/useConnect.svelte.ts new file mode 100644 index 0000000000..bfbcae3f78 --- /dev/null +++ b/packages/svelte/src/hooks/useConnect.svelte.ts @@ -0,0 +1,108 @@ +import { + type CreateMutationResult, + createMutation, +} from '@tanstack/svelte-query' +import type { Config, ConnectErrorType, ResolvedRegister } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type ConnectData, + type ConnectMutate, + type ConnectMutateAsync, + type ConnectVariables, + connectMutationOptions, +} from '@wagmi/core/query' +import type { + CreateMutationParameters, + CreateMutationReturnType, +} from '../query.svelte.js' +import type { + ConfigParameter, + RuneParameters, + RuneReturnType, +} from '../types.js' +import { useConfig } from './useConfig.svelte.js' +import { + type UseConnectorsReturnType, + useConnectors, +} from './useConnectors.svelte.js' + +export type UseConnectParameters< + config extends Config = Config, + context = unknown, +> = RuneParameters< + Compute< + ConfigParameter & { + mutation?: + | CreateMutationParameters< + ConnectData, + ConnectErrorType, + ConnectVariables, + context + > + | undefined + } + > +> + +export type UseConnectReturnType< + config extends Config = Config, + context = unknown, +> = RuneReturnType< + Compute< + CreateMutationReturnType< + ConnectData, + ConnectErrorType, + ConnectVariables, + context + > & { + connect: ConnectMutate + connectAsync: ConnectMutateAsync + connectors: Compute> + } + > +> + +/** https://wagmi.sh/react/api/hooks/useConnect */ +export function useConnect< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseConnectParameters = () => ({}), +): UseConnectReturnType { + const { mutation } = $derived.by(parameters) + + const config = $derived.by(useConfig(parameters)) + + const mutationOptions = $derived(connectMutationOptions(config)) + const { mutate, mutateAsync, ...result } = $derived( + createMutation(() => ({ + ...mutation, + ...mutationOptions, + })), + ) + + // Reset mutation back to an idle state when the connector disconnects. + $effect(() => { + ;[ + // deps + config, + result.reset, + ] + return config.subscribe( + ({ status }) => status, + (status, previousStatus) => { + if (previousStatus === 'connected' && status === 'disconnected') + result.reset() + }, + ) + }) + + const connectors = $derived.by(useConnectors(() => ({ config }))) + + return () => ({ + ...result, + connect: mutate, + connectAsync: mutateAsync, + connectors, + }) +} diff --git a/packages/svelte/src/hooks/useConnections.svelte.test.ts b/packages/svelte/src/hooks/useConnections.svelte.test.ts new file mode 100644 index 0000000000..e1a75df810 --- /dev/null +++ b/packages/svelte/src/hooks/useConnections.svelte.test.ts @@ -0,0 +1,30 @@ +import { connect } from '@wagmi/core' +import { config, wait } from '@wagmi/test' +import { testHook } from '@wagmi/test/svelte' +import { flushSync } from 'svelte' +import { expect, test } from 'vitest' +import { useConnections } from './useConnections.svelte.js' + +test( + 'default', + testHook(async () => { + const result = $derived.by(useConnections()) + + expect(result).toEqual([]) + + await connect(config, { connector: config.connectors[0]! }) + + await expect.poll(() => result.length).toEqual(1) + }), +) + +test( + 'parameters: config', + testHook( + () => { + const result = $derived.by(useConnections(() => ({ config }))) + expect(result).toBeDefined() + }, + { shouldMockConfig: false }, + ), +) diff --git a/packages/svelte/src/hooks/useConnections.svelte.ts b/packages/svelte/src/hooks/useConnections.svelte.ts new file mode 100644 index 0000000000..d4d4cde43d --- /dev/null +++ b/packages/svelte/src/hooks/useConnections.svelte.ts @@ -0,0 +1,37 @@ +import { + type GetConnectionsReturnType, + getConnections, + watchConnections, +} from '@wagmi/core' +import type { + ConfigParameter, + RuneParameters, + RuneReturnType, +} from '../types.js' +import { useConfig } from './useConfig.svelte.js' + +export type UseConnectionsParameters = RuneParameters + +export type UseConnectionsReturnType = RuneReturnType + +/** https://wagmi.sh/react/api/hooks/useConnections */ +export function useConnections( + parameters: UseConnectionsParameters = () => ({}), +): UseConnectionsReturnType { + const config = $derived.by(useConfig(parameters)) + + let connections = $state(getConnections(config)) + + $effect(() => { + connections = getConnections(config) + const unsubscribe = watchConnections(config, { + onChange: (newConnections) => { + connections = newConnections + }, + }) + + return unsubscribe + }) + + return () => connections +} diff --git a/packages/svelte/src/hooks/useConnectors.svelte.test.ts b/packages/svelte/src/hooks/useConnectors.svelte.test.ts new file mode 100644 index 0000000000..3f601c47ca --- /dev/null +++ b/packages/svelte/src/hooks/useConnectors.svelte.test.ts @@ -0,0 +1,34 @@ +import { mock } from '@wagmi/connectors' +import { accounts, config } from '@wagmi/test' +import { testHook } from '@wagmi/test/svelte' +import { expect, test } from 'vitest' +import { useConnectors } from './useConnectors.svelte.js' + +test( + 'default', + testHook(async () => { + const result = $derived.by(useConnectors()) + + const count = config.connectors.length + expect(result.length).toBe(count) + expect(result).toEqual(config.connectors) + + config._internal.connectors.setState(() => [ + ...config.connectors, + config._internal.connectors.setup(mock({ accounts })), + ]) + + await expect.poll(() => result.length).toBe(count + 1) + }), +) + +test( + 'parameters: config', + testHook( + () => { + const result = $derived.by(useConnectors(() => ({ config }))) + expect(result).toBeDefined() + }, + { shouldMockConfig: false }, + ), +) diff --git a/packages/svelte/src/hooks/useConnectors.svelte.ts b/packages/svelte/src/hooks/useConnectors.svelte.ts new file mode 100644 index 0000000000..8ee5b4f2f6 --- /dev/null +++ b/packages/svelte/src/hooks/useConnectors.svelte.ts @@ -0,0 +1,37 @@ +import { + type GetConnectorsReturnType, + getConnectors, + watchConnectors, +} from '@wagmi/core' +import type { + ConfigParameter, + RuneParameters, + RuneReturnType, +} from '../types.js' +import { useConfig } from './useConfig.svelte.js' + +export type UseConnectorsParameters = RuneParameters + +export type UseConnectorsReturnType = RuneReturnType + +/** https://wagmi.sh/react/api/hooks/useConnectors */ +export function useConnectors( + parameters: UseConnectorsParameters = () => ({}), +): UseConnectorsReturnType { + const config = $derived.by(useConfig(parameters)) + + let connectors = $state(getConnectors(config)) + + $effect(() => { + connectors = getConnectors(config) + const unsubscribe = watchConnectors(config, { + onChange: (newConnectors) => { + connectors = newConnectors + }, + }) + + return unsubscribe + }) + + return () => connectors +} diff --git a/packages/svelte/src/hooks/useDisconnect.svelte.test.ts b/packages/svelte/src/hooks/useDisconnect.svelte.test.ts new file mode 100644 index 0000000000..d4d76edb4c --- /dev/null +++ b/packages/svelte/src/hooks/useDisconnect.svelte.test.ts @@ -0,0 +1,30 @@ +import { connect } from '@wagmi/core' +import { config } from '@wagmi/test' +import { testHook } from '@wagmi/test/svelte' +import { beforeEach, expect, test } from 'vitest' +import { useAccount } from './useAccount.svelte.js' +import { useDisconnect } from './useDisconnect.svelte.js' + +const connector = config.connectors[0]! + +beforeEach(async () => { + await connect(config, { connector }) +}) + +test( + 'default', + testHook(async () => { + const account = $derived.by(useAccount()) + const disconnect = $derived.by(useDisconnect()) + + expect(account.address).toBeDefined() + expect(account.status).toEqual('connected') + + disconnect.disconnect() + + await expect.poll(() => account.isDisconnected).toBeTruthy() + + expect(account.address).not.toBeDefined() + expect(account.status).toEqual('disconnected') + }), +) diff --git a/packages/svelte/src/hooks/useDisconnect.svelte.ts b/packages/svelte/src/hooks/useDisconnect.svelte.ts new file mode 100644 index 0000000000..dca8c4e755 --- /dev/null +++ b/packages/svelte/src/hooks/useDisconnect.svelte.ts @@ -0,0 +1,80 @@ +import { + type CreateMutationResult, + createMutation, +} from '@tanstack/svelte-query' +import type { Connector, DisconnectErrorType } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type DisconnectData, + type DisconnectMutate, + type DisconnectMutateAsync, + type DisconnectVariables, + disconnectMutationOptions, +} from '@wagmi/core/query' +import type { + CreateMutationParameters, + CreateMutationReturnType, +} from '../query.svelte.js' +import type { + ConfigParameter, + RuneParameters, + RuneReturnType, +} from '../types.js' +import { useConfig } from './useConfig.svelte.js' +import { useConnections } from './useConnections.svelte.js' + +export type UseDisconnectParameters = RuneParameters< + Compute< + ConfigParameter & { + mutation?: + | CreateMutationParameters< + DisconnectData, + DisconnectErrorType, + DisconnectVariables, + context + > + | undefined + } + > +> + +export type UseDisconnectReturnType = RuneReturnType< + Compute< + CreateMutationReturnType< + DisconnectData, + DisconnectErrorType, + DisconnectVariables, + context + > & { + connectors: readonly Connector[] + disconnect: DisconnectMutate + disconnectAsync: DisconnectMutateAsync + } + > +> + +/** https://wagmi.sh/react/api/hooks/useDisconnect */ +export function useDisconnect( + parameters: UseDisconnectParameters = () => ({}), +): UseDisconnectReturnType { + const { mutation } = $derived.by(parameters) + + const config = $derived.by(useConfig(parameters)) + + const mutationOptions = $derived(disconnectMutationOptions(config)) + const query = createMutation(() => ({ + ...mutation, + ...mutationOptions, + })) + const { mutate, mutateAsync, ...result } = $derived(query) + + const connections = $derived.by(useConnections(() => ({ config }))) + const connectors = $derived(connections.map((c) => c.connector)) + + return () => ({ + ...result, + connectors, + disconnect: mutate, + disconnectAsync: mutateAsync, + }) +} diff --git a/packages/svelte/src/hooks/useEnsAvatar.svelte.test.ts b/packages/svelte/src/hooks/useEnsAvatar.svelte.test.ts new file mode 100644 index 0000000000..13505179b4 --- /dev/null +++ b/packages/svelte/src/hooks/useEnsAvatar.svelte.test.ts @@ -0,0 +1,52 @@ +import { testHook } from '@wagmi/test/svelte' +import { expect, test } from 'vitest' +import { useEnsAvatar } from './useEnsAvatar.svelte.js' + +test( + 'default', + testHook(async () => { + const result = $derived.by( + useEnsAvatar(() => ({ + name: 'wevm.eth', + })), + ) + + await expect.poll(() => result.isSuccess).toBeTruthy() + + expect(result).toMatchInlineSnapshot(` + { + "data": "https://euc.li/wevm.eth", + "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": [ + "ensAvatar", + { + "chainId": 1, + "name": "wevm.eth", + }, + ], + "refetch": [Function], + "status": "success", + } + `) + }), +) diff --git a/packages/svelte/src/hooks/useEnsAvatar.svelte.ts b/packages/svelte/src/hooks/useEnsAvatar.svelte.ts new file mode 100644 index 0000000000..6f8282e4a7 --- /dev/null +++ b/packages/svelte/src/hooks/useEnsAvatar.svelte.ts @@ -0,0 +1,62 @@ +import type { + Config, + GetEnsAvatarErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetEnsAvatarData, + type GetEnsAvatarOptions, + type GetEnsAvatarQueryFnData, + type GetEnsAvatarQueryKey, + getEnsAvatarQueryOptions, +} from '@wagmi/core/query' + +import { type CreateQueryReturnType, createQuery } from '../query.svelte.js' +import type { RuneParameters, RuneReturnType } from '../types.js' +import type { ConfigParameter, QueryParameter } from '../types.js' +import { useChainId } from './useChainId.svelte.js' +import { useConfig } from './useConfig.svelte.js' + +export type UseEnsAvatarParameters< + config extends Config = Config, + selectData = GetEnsAvatarData, +> = RuneParameters< + Compute< + GetEnsAvatarOptions & + ConfigParameter & + QueryParameter< + GetEnsAvatarQueryFnData, + GetEnsAvatarErrorType, + selectData, + GetEnsAvatarQueryKey + > + > +> + +export type UseEnsAvatarReturnType = + RuneReturnType> + +/** https://wagmi.sh/react/api/hooks/useEnsAvatar */ +export function useEnsAvatar< + config extends Config = ResolvedRegister['config'], + selectData = GetEnsAvatarData, +>( + parameters: UseEnsAvatarParameters = () => ({}), +): UseEnsAvatarReturnType { + const { name, query = {} } = $derived(parameters()) + + const config = $derived.by(useConfig(parameters)) + const chainId = $derived.by(useChainId(() => ({ config }))) + + const options = $derived( + getEnsAvatarQueryOptions(config, { + ...parameters(), + chainId: parameters().chainId ?? chainId, + }), + ) + + const enabled = $derived(Boolean(name && (query.enabled ?? true))) + + return createQuery(() => ({ ...query, ...options, enabled })) +} diff --git a/packages/svelte/src/hooks/useEnsName.svelte.test.ts b/packages/svelte/src/hooks/useEnsName.svelte.test.ts new file mode 100644 index 0000000000..3f9fc6e3bf --- /dev/null +++ b/packages/svelte/src/hooks/useEnsName.svelte.test.ts @@ -0,0 +1,52 @@ +import { testHook } from '@wagmi/test/svelte' +import { expect, test } from 'vitest' +import { useEnsName } from './useEnsName.svelte' + +test( + 'default', + testHook(async () => { + const result = $derived.by( + useEnsName(() => ({ + address: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + })), + ) + + await expect.poll(() => result.isSuccess).toBeTruthy() + + expect(result).toMatchInlineSnapshot(` + { + "data": "wevm.eth", + "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": [ + "ensName", + { + "address": "0xd2135CfB216b74109775236E36d4b433F1DF507B", + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) + }), +) diff --git a/packages/svelte/src/hooks/useEnsName.svelte.ts b/packages/svelte/src/hooks/useEnsName.svelte.ts new file mode 100644 index 0000000000..724343887a --- /dev/null +++ b/packages/svelte/src/hooks/useEnsName.svelte.ts @@ -0,0 +1,62 @@ +import type { Config, GetEnsNameErrorType, ResolvedRegister } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetEnsNameData, + type GetEnsNameOptions, + type GetEnsNameQueryFnData, + type GetEnsNameQueryKey, + getEnsNameQueryOptions, +} from '@wagmi/core/query' +import { type CreateQueryReturnType, createQuery } from '../query.svelte.js' +import type { + ConfigParameter, + QueryParameter, + RuneParameters, + RuneReturnType, +} from '../types.js' +import { useChainId } from './useChainId.svelte.js' +import { useConfig } from './useConfig.svelte.js' + +export type UseEnsNameParameters< + config extends Config = Config, + selectData = GetEnsNameData, +> = RuneParameters< + Compute< + GetEnsNameOptions & + ConfigParameter & + QueryParameter< + GetEnsNameQueryFnData, + GetEnsNameErrorType, + selectData, + GetEnsNameQueryKey + > + > +> + +export type UseEnsNameReturnType = RuneReturnType< + CreateQueryReturnType +> + +/** https://wagmi.sh/react/api/hooks/useEnsName */ +export function useEnsName< + config extends Config = ResolvedRegister['config'], + selectData = GetEnsNameData, +>( + parameters: UseEnsNameParameters = () => ({}), +): UseEnsNameReturnType { + const { address, query = {} } = $derived(parameters()) + + const config = $derived.by(useConfig(parameters)) + const chainId = $derived.by(useChainId(() => ({ config }))) + + const options = $derived( + getEnsNameQueryOptions(config, { + ...parameters(), + chainId: parameters().chainId ?? chainId, + }), + ) + + const enabled = $derived(Boolean(address && (query.enabled ?? true))) + + return createQuery(() => ({ ...query, ...options, enabled })) +} diff --git a/packages/svelte/src/hooks/useGasPrice.svelte.test.ts b/packages/svelte/src/hooks/useGasPrice.svelte.test.ts new file mode 100644 index 0000000000..b09ac5bd5a --- /dev/null +++ b/packages/svelte/src/hooks/useGasPrice.svelte.test.ts @@ -0,0 +1,116 @@ +import { chain, testClient } from '@wagmi/test' +import { testHook } from '@wagmi/test/svelte' +import { expect, test } from 'vitest' +import { useGasPrice } from './useGasPrice.svelte' + +test( + 'default', + testHook( + async () => { + const result = $derived.by(useGasPrice()) + + await expect.poll(() => result.isSuccess).toBeTruthy() + + expect(result).toMatchInlineSnapshot(` + { + "data": 2750000000n, + "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": [ + "gasPrice", + { + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) + }, + {}, + async () => { + await testClient.mainnet.restart() + + await testClient.mainnet.setNextBlockBaseFeePerGas({ + baseFeePerGas: 2_000_000_000n, + }) + await testClient.mainnet.mine({ blocks: 1 }) + }, + ), +) + +test( + 'parameters: chainId', + testHook( + async () => { + const result = $derived.by( + useGasPrice(() => ({ chainId: chain.mainnet2.id })), + ) + + await expect.poll(() => result.isSuccess).toBeTruthy() + + expect(result).toMatchInlineSnapshot(` + { + "data": 1875000000n, + "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": [ + "gasPrice", + { + "chainId": 456, + }, + ], + "refetch": [Function], + "status": "success", + } + `) + }, + {}, + async () => { + await testClient.mainnet2.restart() + + await testClient.mainnet2.setNextBlockBaseFeePerGas({ + baseFeePerGas: 1_000_000_000n, + }) + await testClient.mainnet2.mine({ blocks: 1 }) + }, + ), +) diff --git a/packages/svelte/src/hooks/useGasPrice.svelte.ts b/packages/svelte/src/hooks/useGasPrice.svelte.ts new file mode 100644 index 0000000000..7e806e8d54 --- /dev/null +++ b/packages/svelte/src/hooks/useGasPrice.svelte.ts @@ -0,0 +1,65 @@ +import type { + Config, + GetGasPriceErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetGasPriceData, + type GetGasPriceOptions, + type GetGasPriceQueryFnData, + type GetGasPriceQueryKey, + getGasPriceQueryOptions, +} from '@wagmi/core/query' + +import { type CreateQueryReturnType, createQuery } from '../query.svelte.js' +import type { RuneParameters, RuneReturnType } from '../types.js' +import type { ConfigParameter, QueryParameter } from '../types.js' +import { useChainId } from './useChainId.svelte.js' +import { useConfig } from './useConfig.svelte.js' + +export type UseGasPriceParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetGasPriceData, +> = RuneParameters< + Compute< + GetGasPriceOptions & + ConfigParameter & + QueryParameter< + GetGasPriceQueryFnData, + GetGasPriceErrorType, + selectData, + GetGasPriceQueryKey + > + > +> + +export type UseGasPriceReturnType = + RuneReturnType> + +/** https://wagmi.sh/react/api/hooks/useGasPrice */ +export function useGasPrice< + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetGasPriceData, +>( + parameters: UseGasPriceParameters = () => ({}), +): UseGasPriceReturnType { + const { query = {} } = $derived(parameters()) + + const config = $derived.by(useConfig(parameters)) + const configChainId = $derived.by(useChainId(() => ({ config }))) + const chainId = $derived(parameters().chainId ?? configChainId) + + const options = $derived( + getGasPriceQueryOptions(config, { + ...parameters(), + chainId, + }), + ) + + return createQuery(() => ({ ...query, ...options })) +} diff --git a/packages/svelte/src/hooks/useReadContract.svelte.test.ts b/packages/svelte/src/hooks/useReadContract.svelte.test.ts new file mode 100644 index 0000000000..c9b040ce10 --- /dev/null +++ b/packages/svelte/src/hooks/useReadContract.svelte.test.ts @@ -0,0 +1,204 @@ +import { abi, address, bytecode, chain, config, wait } from '@wagmi/test' +import { testHook } from '@wagmi/test/svelte' +import { expect, test } from 'vitest' +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/hooks/useReadContract.svelte.ts b/packages/svelte/src/hooks/useReadContract.svelte.ts new file mode 100644 index 0000000000..8a30cb07cc --- /dev/null +++ b/packages/svelte/src/hooks/useReadContract.svelte.ts @@ -0,0 +1,109 @@ +'use client' + +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 { type CreateQueryReturnType, createQuery } from '../query.svelte.js' +import type { + ConfigParameter, + QueryParameter, + RuneParameters, + RuneReturnType, +} from '../types.js' +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, + })) +} diff --git a/packages/svelte/src/hooks/useReadContracts.svelte.test.ts b/packages/svelte/src/hooks/useReadContracts.svelte.test.ts new file mode 100644 index 0000000000..0cb49b10a0 --- /dev/null +++ b/packages/svelte/src/hooks/useReadContracts.svelte.test.ts @@ -0,0 +1,267 @@ +import { abi, address, chain } from '@wagmi/test' +import { testHook } from '@wagmi/test/svelte' +import { expect, test } from 'vitest' +import { useReadContracts } from './useReadContracts.svelte.js' + +test( + 'default', + testHook(async () => { + const result = $derived.by( + useReadContracts(() => ({ + contracts: [ + { + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }, + { + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'symbol', + }, + ], + })), + ) + + await expect.poll(() => result.isSuccess).toBeTruthy() + + expect(result).toMatchInlineSnapshot(` + { + "data": [ + { + "result": 4n, + "status": "success", + }, + { + "result": "WAGMI", + "status": "success", + }, + ], + "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": [ + "readContracts", + { + "chainId": 1, + "contracts": [ + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": [ + "0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC", + ], + "chainId": 1, + "functionName": "balanceOf", + }, + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "chainId": 1, + "functionName": "symbol", + }, + ], + }, + ], + "refetch": [Function], + "status": "success", + } + `) + }), +) + +test( + 'multichain', + testHook(async () => { + const { mainnet, mainnet2, optimism } = chain + const result = $derived.by( + useReadContracts(() => ({ + contracts: [ + { + abi: abi.wagmigotchi, + address: address.wagmigotchi, + chainId: mainnet.id, + functionName: 'love', + args: ['0x27a69ffba1e939ddcfecc8c7e0f967b872bac65c'], + }, + { + abi: abi.wagmigotchi, + address: address.wagmigotchi, + chainId: mainnet.id, + functionName: 'love', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }, + { + abi: abi.wagmigotchi, + address: address.wagmigotchi, + chainId: mainnet.id, + functionName: 'love', + args: ['0xd2135CfB216b74109775236E36d4b433F1DF507B'], + }, + { + abi: abi.wagmigotchi, + address: address.wagmigotchi, + chainId: mainnet2.id, + functionName: 'getAlive', + }, + { + abi: abi.mloot, + address: address.mloot, + chainId: mainnet2.id, + functionName: 'tokenOfOwnerByIndex', + args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', 0n], + }, + { + abi: abi.erc20, + address: address.optimism.usdc, + chainId: optimism.id, + functionName: 'symbol', + }, + { + abi: abi.erc20, + address: address.optimism.usdc, + chainId: optimism.id, + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }, + ], + })), + ) + + await expect.poll(() => result.isSuccess).toBeTruthy() + + expect(result).toMatchInlineSnapshot(` + { + "data": [ + { + "result": 2n, + "status": "success", + }, + { + "result": 1n, + "status": "success", + }, + { + "result": 0n, + "status": "success", + }, + { + "result": false, + "status": "success", + }, + { + "result": 370395n, + "status": "success", + }, + { + "result": "USDC", + "status": "success", + }, + { + "result": 10959340n, + "status": "success", + }, + ], + "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": [ + "readContracts", + { + "chainId": 1, + "contracts": [ + { + "address": "0xecb504d39723b0be0e3a9aa33d646642d1051ee1", + "args": [ + "0x27a69ffba1e939ddcfecc8c7e0f967b872bac65c", + ], + "chainId": 1, + "functionName": "love", + }, + { + "address": "0xecb504d39723b0be0e3a9aa33d646642d1051ee1", + "args": [ + "0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC", + ], + "chainId": 1, + "functionName": "love", + }, + { + "address": "0xecb504d39723b0be0e3a9aa33d646642d1051ee1", + "args": [ + "0xd2135CfB216b74109775236E36d4b433F1DF507B", + ], + "chainId": 1, + "functionName": "love", + }, + { + "address": "0xecb504d39723b0be0e3a9aa33d646642d1051ee1", + "chainId": 456, + "functionName": "getAlive", + }, + { + "address": "0x1dfe7ca09e99d10835bf73044a23b73fc20623df", + "args": [ + "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e", + 0n, + ], + "chainId": 456, + "functionName": "tokenOfOwnerByIndex", + }, + { + "address": "0x7f5c764cbc14f9669b88837ca1490cca17c31607", + "chainId": 10, + "functionName": "symbol", + }, + { + "address": "0x7f5c764cbc14f9669b88837ca1490cca17c31607", + "args": [ + "0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC", + ], + "chainId": 10, + "functionName": "balanceOf", + }, + ], + }, + ], + "refetch": [Function], + "status": "success", + } + `) + }), +) diff --git a/packages/svelte/src/hooks/useReadContracts.svelte.ts b/packages/svelte/src/hooks/useReadContracts.svelte.ts new file mode 100644 index 0000000000..81ce6b9651 --- /dev/null +++ b/packages/svelte/src/hooks/useReadContracts.svelte.ts @@ -0,0 +1,93 @@ +import type { + Config, + ReadContractsErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type ReadContractsData, + type ReadContractsOptions, + type ReadContractsQueryFnData, + type ReadContractsQueryKey, + readContractsQueryOptions, + structuralSharing, +} from '@wagmi/core/query' +import type { ContractFunctionParameters } from 'viem' + +import { type CreateQueryReturnType, createQuery } from '../query.svelte.js' +import type { RuneParameters, RuneReturnType } from '../types.js' +import type { ConfigParameter, QueryParameter } from '../types.js' +import { useChainId } from './useChainId.svelte.js' +import { useConfig } from './useConfig.svelte.js' + +export type UseReadContractsParameters< + contracts extends readonly unknown[] = readonly ContractFunctionParameters[], + allowFailure extends boolean = true, + config extends Config = Config, + selectData = ReadContractsData, +> = RuneParameters< + Compute< + ReadContractsOptions & + ConfigParameter & + QueryParameter< + ReadContractsQueryFnData, + ReadContractsErrorType, + selectData, + ReadContractsQueryKey + > + > +> + +export type UseReadContractsReturnType< + contracts extends readonly unknown[] = readonly ContractFunctionParameters[], + allowFailure extends boolean = true, + selectData = ReadContractsData, +> = RuneReturnType> + +/** https://wagmi.sh/react/api/hooks/useReadContracts */ +export function useReadContracts< + const contracts extends readonly unknown[], + allowFailure extends boolean = true, + config extends Config = ResolvedRegister['config'], + selectData = ReadContractsData, +>( + parameters: UseReadContractsParameters< + contracts, + allowFailure, + config, + selectData + > = () => ({}), +): UseReadContractsReturnType { + const { contracts = [], query = {} } = $derived(parameters()) + + const config = $derived.by(useConfig(parameters)) + const chainId = $derived.by(useChainId(() => ({ config }))) + + const options = $derived( + readContractsQueryOptions(config, { + ...parameters(), + chainId, + }), + ) + + const enabled = $derived(() => { + let isContractsValid = false + for (const contract of contracts) { + const { abi, address, functionName } = + contract as ContractFunctionParameters + if (!abi || !address || !functionName) { + isContractsValid = false + break + } + isContractsValid = true + } + return Boolean(isContractsValid && (query.enabled ?? true)) + }) + + return createQuery(() => ({ + ...options, + ...query, + enabled, + structuralSharing: query.structuralSharing ?? structuralSharing, + })) +} diff --git a/packages/svelte/src/hooks/useSendTransaction.svelte.test.ts b/packages/svelte/src/hooks/useSendTransaction.svelte.test.ts new file mode 100644 index 0000000000..7680628516 --- /dev/null +++ b/packages/svelte/src/hooks/useSendTransaction.svelte.test.ts @@ -0,0 +1,27 @@ +import { connect, disconnect } from '@wagmi/core' +import { config, transactionHashRegex } from '@wagmi/test' +import { parseEther } from 'viem' +import { expect, test } from 'vitest' + +import { setups, teardowns, testHook } from '@wagmi/test/svelte' +import { useSendTransaction } from './useSendTransaction.svelte' + +test( + 'default', + testHook( + async () => { + const sendTransaction = $derived.by(useSendTransaction()) + + sendTransaction.sendTransaction({ + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + }) + await expect.poll(() => sendTransaction.isSuccess).toBeTruthy() + + expect(sendTransaction.data).toMatch(transactionHashRegex) + }, + {}, + setups.connect, + teardowns.disconnect, + ), +) diff --git a/packages/svelte/src/hooks/useSendTransaction.svelte.ts b/packages/svelte/src/hooks/useSendTransaction.svelte.ts new file mode 100644 index 0000000000..f3fc7a7b74 --- /dev/null +++ b/packages/svelte/src/hooks/useSendTransaction.svelte.ts @@ -0,0 +1,82 @@ +import { createMutation } from '@tanstack/svelte-query' +import type { + Config, + ResolvedRegister, + SendTransactionErrorType, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type SendTransactionData, + type SendTransactionMutate, + type SendTransactionMutateAsync, + type SendTransactionVariables, + sendTransactionMutationOptions, +} from '@wagmi/core/query' + +import type { + CreateMutationParameters, + CreateMutationReturnType, +} from '../query.svelte.js' +import type { RuneParameters, RuneReturnType } from '../types.js' +import type { ConfigParameter } from '../types.js' +import { useConfig } from './useConfig.svelte.js' + +export type UseSendTransactionParameters< + config extends Config = Config, + context = unknown, +> = RuneParameters< + Compute< + ConfigParameter & { + mutation?: + | CreateMutationParameters< + SendTransactionData, + SendTransactionErrorType, + SendTransactionVariables, + context + > + | undefined + } + > +> + +export type UseSendTransactionReturnType< + config extends Config = Config, + context = unknown, +> = RuneReturnType< + Compute< + CreateMutationReturnType< + SendTransactionData, + SendTransactionErrorType, + SendTransactionVariables, + context + > & { + sendTransaction: SendTransactionMutate + sendTransactionAsync: SendTransactionMutateAsync + } + > +> + +/** https://wagmi.sh/react/api/hooks/useSendTransaction */ +export function useSendTransaction< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseSendTransactionParameters = () => ({}), +): UseSendTransactionReturnType { + const { mutation } = $derived.by(parameters) + + const config = $derived.by(useConfig(parameters)) + + const mutationOptions = $derived(sendTransactionMutationOptions(config)) + const query = createMutation(() => ({ + ...mutation, + ...mutationOptions, + })) + const { mutate, mutateAsync, ...result } = $derived(query) + + return () => ({ + ...result, + sendTransaction: mutate, + sendTransactionAsync: mutateAsync, + }) +} diff --git a/packages/svelte/src/hooks/useSignMessage.svelte.test.ts b/packages/svelte/src/hooks/useSignMessage.svelte.test.ts new file mode 100644 index 0000000000..5211bbf714 --- /dev/null +++ b/packages/svelte/src/hooks/useSignMessage.svelte.test.ts @@ -0,0 +1,49 @@ +import { connect, disconnect, getAccount } from '@wagmi/core' +import { config, privateKey, wait } from '@wagmi/test' +import { recoverMessageAddress } from 'viem' +import { expect, test, vi } from 'vitest' + +import { setups, teardowns, testHook } from '@wagmi/test/svelte' +import { privateKeyToAccount } from 'viem/accounts' +import { useSignMessage } from './useSignMessage.svelte' + +test( + 'default', + testHook( + async () => { + const signMessage = $derived.by(useSignMessage()) + + signMessage.signMessage({ message: 'foo bar baz' }) + await expect.poll(() => signMessage.isSuccess).toBeTruthy() + + await expect( + recoverMessageAddress({ + message: 'foo bar baz', + signature: signMessage.data!, + }), + ).resolves.toEqual(getAccount(config).address) + }, + {}, + setups.connect, + teardowns.disconnect, + ), +) + +test( + 'behavior: local account', + testHook(async () => { + const signMessage = $derived.by(useSignMessage()) + + const account = privateKeyToAccount(privateKey) + signMessage.signMessage({ account, message: 'foo bar baz' }) + + await expect.poll(() => signMessage.isSuccess).toBeTruthy() + + await expect( + recoverMessageAddress({ + message: 'foo bar baz', + signature: signMessage.data!, + }), + ).resolves.toEqual(account.address) + }), +) diff --git a/packages/svelte/src/hooks/useSignMessage.svelte.ts b/packages/svelte/src/hooks/useSignMessage.svelte.ts new file mode 100644 index 0000000000..b5f087dab3 --- /dev/null +++ b/packages/svelte/src/hooks/useSignMessage.svelte.ts @@ -0,0 +1,69 @@ +import { createMutation } from '@tanstack/svelte-query' +import type { SignMessageErrorType } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type SignMessageData, + type SignMessageMutate, + type SignMessageMutateAsync, + type SignMessageVariables, + signMessageMutationOptions, +} from '@wagmi/core/query' + +import type { + CreateMutationParameters, + CreateMutationReturnType, +} from '../query.svelte.js' +import type { RuneParameters, RuneReturnType } from '../types.js' +import type { ConfigParameter } from '../types.js' +import { useConfig } from './useConfig.svelte.js' + +export type UseSignMessageParameters = RuneParameters< + Compute< + ConfigParameter & { + mutation?: + | CreateMutationParameters< + SignMessageData, + SignMessageErrorType, + SignMessageVariables, + context + > + | undefined + } + > +> + +export type UseSignMessageReturnType = RuneReturnType< + Compute< + CreateMutationReturnType< + SignMessageData, + SignMessageErrorType, + SignMessageVariables, + context + > & { + signMessage: SignMessageMutate + signMessageAsync: SignMessageMutateAsync + } + > +> + +/** https://wagmi.sh/react/api/hooks/useSignMessage */ +export function useSignMessage( + parameters: UseSignMessageParameters = () => ({}), +): UseSignMessageReturnType { + const { mutation } = $derived.by(parameters) + + const config = $derived.by(useConfig(parameters)) + + const mutationOptions = $derived(signMessageMutationOptions(config)) + const query = createMutation(() => ({ + ...mutation, + ...mutationOptions, + })) + const { mutate, mutateAsync, ...result } = $derived(query) + + return () => ({ + ...result, + signMessage: mutate, + signMessageAsync: mutateAsync, + }) +} diff --git a/packages/svelte/src/hooks/useSwitchAccount.svelte.test.ts b/packages/svelte/src/hooks/useSwitchAccount.svelte.test.ts new file mode 100644 index 0000000000..7ecc1f05fa --- /dev/null +++ b/packages/svelte/src/hooks/useSwitchAccount.svelte.test.ts @@ -0,0 +1,45 @@ +import { connect, disconnect } from '@wagmi/core' +import { config } from '@wagmi/test' +import { testHook } from '@wagmi/test/svelte' +import { expect, test } from 'vitest' +import { useAccount } from './useAccount.svelte.js' +import { useSwitchAccount } from './useSwitchAccount.svelte.js' + +const connector1 = config.connectors[0]! +const connector2 = config.connectors[1]! + +test( + 'default', + testHook( + async () => { + const account = $derived.by(useAccount()) + const switchAccount = $derived.by(useSwitchAccount()) + + const address1 = account.address + expect(address1).toBeDefined() + + switchAccount.switchAccount({ connector: connector2 }) + await expect.poll(() => switchAccount.isSuccess).toBeTruthy() + + const address2 = account.address + expect(address2).toBeDefined() + expect(address1).not.toBe(address2) + + switchAccount.switchAccount({ connector: connector1 }) + await expect.poll(() => switchAccount.isSuccess).toBeTruthy() + + const address3 = account.address + expect(address3).toBeDefined() + expect(address1).toBe(address3) + }, + {}, + async () => { + await connect(config, { connector: connector2 }) + await connect(config, { connector: connector1 }) + }, + async () => { + await disconnect(config, { connector: connector1 }) + await disconnect(config, { connector: connector2 }) + }, + ), +) diff --git a/packages/svelte/src/hooks/useSwitchAccount.svelte.ts b/packages/svelte/src/hooks/useSwitchAccount.svelte.ts new file mode 100644 index 0000000000..4a2d780fd6 --- /dev/null +++ b/packages/svelte/src/hooks/useSwitchAccount.svelte.ts @@ -0,0 +1,89 @@ +import { createMutation } from '@tanstack/svelte-query' +import type { + Config, + Connector, + ResolvedRegister, + SwitchAccountErrorType, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type SwitchAccountData, + type SwitchAccountMutate, + type SwitchAccountMutateAsync, + type SwitchAccountVariables, + switchAccountMutationOptions, +} from '@wagmi/core/query' + +import type { + CreateMutationParameters, + CreateMutationReturnType, +} from '../query.svelte.js' +import type { RuneParameters, RuneReturnType } from '../types.js' +import type { ConfigParameter } from '../types.js' +import { useConfig } from './useConfig.svelte.js' +import { useConnections } from './useConnections.svelte.js' + +export type UseSwitchAccountParameters< + config extends Config = Config, + context = unknown, +> = RuneParameters< + Compute< + ConfigParameter & { + mutation?: + | CreateMutationParameters< + SwitchAccountData, + SwitchAccountErrorType, + SwitchAccountVariables, + context + > + | undefined + } + > +> + +export type UseSwitchAccountReturnType< + config extends Config = Config, + context = unknown, +> = RuneReturnType< + Compute< + CreateMutationReturnType< + SwitchAccountData, + SwitchAccountErrorType, + SwitchAccountVariables, + context + > & { + connectors: readonly Connector[] + switchAccount: SwitchAccountMutate + switchAccountAsync: SwitchAccountMutateAsync + } + > +> + +/** https://wagmi.sh/react/api/hooks/useSwitchAccount */ +export function useSwitchAccount< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseSwitchAccountParameters = () => ({}), +): UseSwitchAccountReturnType { + const { mutation } = $derived.by(parameters) + + const config = $derived.by(useConfig(parameters)) + + const mutationOptions = $derived(switchAccountMutationOptions(config)) + const query = createMutation(() => ({ + ...mutation, + ...mutationOptions, + })) + const { mutate, mutateAsync, ...result } = $derived(query) + + const connections = $derived.by(useConnections(() => ({ config }))) + const connectors = $derived(connections.map((c) => c.connector)) + + return () => ({ + ...result, + connectors, + switchAccount: mutate, + switchAccountAsync: mutateAsync, + }) +} diff --git a/packages/svelte/src/hooks/useSwitchChain.svelte.test.ts b/packages/svelte/src/hooks/useSwitchChain.svelte.test.ts new file mode 100644 index 0000000000..7ffa3bbbe8 --- /dev/null +++ b/packages/svelte/src/hooks/useSwitchChain.svelte.test.ts @@ -0,0 +1,112 @@ +import { chain, config, wait } from '@wagmi/test' +import { setups, teardowns, testHook } from '@wagmi/test/svelte' +import { expect, test, vi } from 'vitest' +import { useAccount } from './useAccount.svelte.js' +import { useSwitchChain } from './useSwitchChain.svelte.js' + +test( + 'default', + testHook( + async () => { + const account = $derived.by(useAccount()) + const switchChain = $derived.by(useSwitchChain()) + + const chainId1 = account.chainId + expect(chainId1).toBeDefined() + + switchChain.switchChain({ chainId: chain.mainnet2.id }) + await expect.poll(() => switchChain.isSuccess).toBeTruthy() + + const chainId2 = account.chainId + expect(chainId2).toBeDefined() + expect(chainId1).not.toBe(chainId2) + + switchChain.switchChain({ chainId: chain.mainnet.id }) + await wait(1000) // TODO: why is this needed? + await expect.poll(() => switchChain.isSuccess).toBeTruthy() + + const chainId3 = account.chainId + expect(chainId3).toBeDefined() + expect(chainId1).toBe(chainId3) + }, + {}, + setups.connect, + teardowns.disconnect, + ), +) + +test( + 'behavior: chains updates', + testHook(async () => { + const switchChain = $derived.by(useSwitchChain()) + + const chains = switchChain.chains + + expect( + switchChain.chains.map(({ id, name }) => ({ + id, + name, + })), + ).toMatchInlineSnapshot(` + [ + { + "id": 1, + "name": "Ethereum", + }, + { + "id": 456, + "name": "Ethereum", + }, + { + "id": 10, + "name": "OP Mainnet", + }, + ] + `) + + config._internal.chains.setState([chain.mainnet, chain.mainnet2]) + + await vi.waitFor(() => switchChain.isSuccess) + expect( + switchChain.chains.map(({ id, name }) => ({ + id, + name, + })), + ).toMatchInlineSnapshot(` + [ + { + "id": 1, + "name": "Ethereum", + }, + { + "id": 456, + "name": "Ethereum", + }, + ] + `) + + config._internal.chains.setState(chains) + + expect( + switchChain.chains.map(({ id, name }) => ({ + id, + name, + })), + ).toMatchInlineSnapshot(` + [ + { + "id": 1, + "name": "Ethereum", + }, + { + "id": 456, + "name": "Ethereum", + }, + { + "id": 10, + "name": "OP Mainnet", + }, + ] + `) + }), +) diff --git a/packages/svelte/src/hooks/useSwitchChain.svelte.ts b/packages/svelte/src/hooks/useSwitchChain.svelte.ts new file mode 100644 index 0000000000..b66a85d6e1 --- /dev/null +++ b/packages/svelte/src/hooks/useSwitchChain.svelte.ts @@ -0,0 +1,88 @@ +import { createMutation } from '@tanstack/svelte-query' +import type { + Config, + ResolvedRegister, + SwitchChainErrorType, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type SwitchChainData, + type SwitchChainMutate, + type SwitchChainMutateAsync, + type SwitchChainVariables, + switchChainMutationOptions, +} from '@wagmi/core/query' + +import type { + CreateMutationParameters, + CreateMutationReturnType, +} from '../query.svelte.js' +import type { RuneParameters, RuneReturnType } from '../types.js' +import type { ConfigParameter } from '../types.js' +import { useChains } from './useChains.svelte.js' +import { useConfig } from './useConfig.svelte.js' + +export type UseSwitchChainParameters< + config extends Config = Config, + context = unknown, +> = RuneParameters< + Compute< + ConfigParameter & { + mutation?: + | CreateMutationParameters< + SwitchChainData, + SwitchChainErrorType, + SwitchChainVariables, + context + > + | undefined + } + > +> + +export type UseSwitchChainReturnType< + config extends Config = Config, + context = unknown, +> = RuneReturnType< + Compute< + CreateMutationReturnType< + SwitchChainData, + SwitchChainErrorType, + SwitchChainVariables, + context + > & { + chains: config['chains'] + switchChain: SwitchChainMutate + switchChainAsync: SwitchChainMutateAsync + } + > +> + +/** https://wagmi.sh/react/api/hooks/useSwitchChain */ +export function useSwitchChain< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseSwitchChainParameters = () => ({}), +): UseSwitchChainReturnType { + const { mutation } = $derived.by(parameters) + + const config = $derived.by(useConfig(parameters)) + + const mutationOptions = $derived(switchChainMutationOptions(config)) + const query = createMutation(() => ({ + ...mutation, + ...mutationOptions, + })) + const { mutate, mutateAsync, ...result } = $derived(query) + + const chains = $derived.by(useChains(() => ({ config }))) + + type Return = ReturnType> + return () => ({ + ...result, + chains: chains as unknown as Return['chains'], + switchChain: mutate as Return['switchChain'], + switchChainAsync: mutateAsync as Return['switchChainAsync'], + }) +} diff --git a/packages/svelte/src/hooks/useWaitForTransactionReceipt.svelte.test.ts b/packages/svelte/src/hooks/useWaitForTransactionReceipt.svelte.test.ts new file mode 100644 index 0000000000..b051c326f6 --- /dev/null +++ b/packages/svelte/src/hooks/useWaitForTransactionReceipt.svelte.test.ts @@ -0,0 +1,81 @@ +import { wait } from '@wagmi/test' +import { testHook } from '@wagmi/test/svelte' +import { expect, test, vi } from 'vitest' +import { useWaitForTransactionReceipt } from './useWaitForTransactionReceipt.svelte' + +test( + 'default', + testHook(async () => { + const result = $derived.by( + useWaitForTransactionReceipt(() => ({ + hash: '0x60668ed8c2dc110d61d945a936fcd45b8f13654e5c78481c8c825d1148c7ef30', + })), + ) + + await expect.poll(() => result.isSuccess).toBeTruthy() + + expect(result).toMatchInlineSnapshot(` + { + "data": { + "blockHash": "0xd725a38b51e5ceec8c5f6c9ccfdb2cc423af993bb650af5eedca5e4be7156ba7", + "blockNumber": 15189204n, + "chainId": 1, + "contractAddress": null, + "cumulativeGasUsed": 12949744n, + "effectiveGasPrice": 9371645552n, + "from": "0xa0cf798816d4b9b9866b5330eea46a18382f251e", + "gasUsed": 21000n, + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "success", + "to": "0xd2135cfb216b74109775236e36d4b433f1df507b", + "transactionHash": "0x60668ed8c2dc110d61d945a936fcd45b8f13654e5c78481c8c825d1148c7ef30", + "transactionIndex": 144, + "type": "eip1559", + }, + "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": [ + "waitForTransactionReceipt", + { + "chainId": 1, + "hash": "0x60668ed8c2dc110d61d945a936fcd45b8f13654e5c78481c8c825d1148c7ef30", + }, + ], + "refetch": [Function], + "status": "success", + } + `) + }), +) + +test( + 'disabled when hash is undefined', + testHook(async () => { + const result = $derived.by( + useWaitForTransactionReceipt(() => ({ hash: undefined })), + ) + + await wait(100) + await expect.poll(() => result.isPending).toBeTruthy() + }), +) diff --git a/packages/svelte/src/hooks/useWaitForTransactionReceipt.svelte.ts b/packages/svelte/src/hooks/useWaitForTransactionReceipt.svelte.ts new file mode 100644 index 0000000000..0384f2f20d --- /dev/null +++ b/packages/svelte/src/hooks/useWaitForTransactionReceipt.svelte.ts @@ -0,0 +1,79 @@ +import type { + Config, + ResolvedRegister, + WaitForTransactionReceiptErrorType, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type WaitForTransactionReceiptData, + type WaitForTransactionReceiptOptions, + type WaitForTransactionReceiptQueryFnData, + type WaitForTransactionReceiptQueryKey, + waitForTransactionReceiptQueryOptions, +} from '@wagmi/core/query' + +import { type CreateQueryReturnType, createQuery } from '../query.svelte.js' +import type { RuneParameters, RuneReturnType } from '../types.js' +import type { ConfigParameter, QueryParameter } from '../types.js' +import { useChainId } from './useChainId.svelte.js' +import { useConfig } from './useConfig.svelte.js' + +export type UseWaitForTransactionReceiptParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = WaitForTransactionReceiptData, +> = RuneParameters< + Compute< + WaitForTransactionReceiptOptions & + ConfigParameter & + QueryParameter< + WaitForTransactionReceiptQueryFnData, + WaitForTransactionReceiptErrorType, + selectData, + WaitForTransactionReceiptQueryKey + > + > +> + +export type UseWaitForTransactionReceiptReturnType< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = WaitForTransactionReceiptData, +> = RuneReturnType< + CreateQueryReturnType +> + +/** https://wagmi.sh/react/api/hooks/useWaitForTransactionReceipt */ +export function useWaitForTransactionReceipt< + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = WaitForTransactionReceiptData, +>( + parameters: UseWaitForTransactionReceiptParameters< + config, + chainId, + selectData + > = () => ({}), +): UseWaitForTransactionReceiptReturnType { + const { hash, query = {} } = $derived(parameters()) + + const config = $derived.by(useConfig(parameters)) + const chainId = $derived.by(useChainId(() => ({ config }))) + + const options = $derived( + waitForTransactionReceiptQueryOptions(config, { + ...parameters(), + chainId: parameters().chainId ?? chainId, + }), + ) + const enabled = $derived(Boolean(hash && (query.enabled ?? true))) + + return createQuery(() => ({ + ...(query as any), + ...options, + enabled, + })) +} diff --git a/packages/svelte/src/hooks/useWatchBlockNumber.svelte.test.ts b/packages/svelte/src/hooks/useWatchBlockNumber.svelte.test.ts new file mode 100644 index 0000000000..f1f39e4fb1 --- /dev/null +++ b/packages/svelte/src/hooks/useWatchBlockNumber.svelte.test.ts @@ -0,0 +1,28 @@ +import { testClient, wait } from '@wagmi/test' +import { testHook } from '@wagmi/test/svelte' +import { expect, test } from 'vitest' +import { useWatchBlockNumber } from './useWatchBlockNumber.svelte.js' + +test( + 'default', + testHook(async () => { + const blockNumbers: bigint[] = [] + useWatchBlockNumber(() => ({ + onBlockNumber(blockNumber) { + blockNumbers.push(blockNumber) + }, + })) + + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + + expect(blockNumbers.length).toBe(3) + expect( + blockNumbers.map((blockNumber) => blockNumber - blockNumbers[0]!), + ).toEqual([0n, 1n, 2n]) + }), +) diff --git a/packages/svelte/src/hooks/useWatchBlockNumber.svelte.ts b/packages/svelte/src/hooks/useWatchBlockNumber.svelte.ts new file mode 100644 index 0000000000..20c82ffc1f --- /dev/null +++ b/packages/svelte/src/hooks/useWatchBlockNumber.svelte.ts @@ -0,0 +1,76 @@ +import { + type Config, + type ResolvedRegister, + type WatchBlockNumberParameters, + watchBlockNumber, +} from '@wagmi/core' +import type { UnionCompute, UnionExactPartial } from '@wagmi/core/internal' + +import type { + ConfigParameter, + EnabledParameter, + RuneParameters, +} from '../types.js' +import { useChainId } from './useChainId.svelte.js' +import { useConfig } from './useConfig.svelte.js' + +export type UseWatchBlockNumberParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = RuneParameters< + UnionCompute< + UnionExactPartial> & + ConfigParameter & + EnabledParameter + > +> + +export type UseWatchBlockNumberReturnType = void + +/** https://wagmi.sh/react/api/hooks/useWatchBlockNumber */ +export function useWatchBlockNumber< + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +>( + parameters: UseWatchBlockNumberParameters = () => + ({}) as any, +): UseWatchBlockNumberReturnType { + const { + enabled = true, + onBlockNumber, + config: _, + ...rest + } = $derived(parameters()) + + const config = $derived.by(useConfig(parameters)) + const configChainId = $derived.by(useChainId(parameters)) + const chainId = $derived(rest.chainId ?? configChainId) + + $effect(() => { + ;[ + // deps + chainId, + config, + enabled, + onBlockNumber, + rest.onError, + rest.emitMissed, + rest.emitOnBegin, + rest.poll, + rest.pollingInterval, + rest.syncConnectedChain, + ] + if (!enabled) return + if (!onBlockNumber) return + + const unsubscribe = watchBlockNumber(config, { + ...(rest as any), + chainId, + onBlockNumber, + }) + + return unsubscribe + }) +} diff --git a/packages/svelte/src/hooks/useWriteContract.svelte.test.ts b/packages/svelte/src/hooks/useWriteContract.svelte.test.ts new file mode 100644 index 0000000000..3a25342a6a --- /dev/null +++ b/packages/svelte/src/hooks/useWriteContract.svelte.test.ts @@ -0,0 +1,26 @@ +import { abi, address, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { setups, teardowns, testHook } from '@wagmi/test/svelte' +import { useWriteContract } from './useWriteContract.svelte' + +test( + 'default', + testHook( + async () => { + const writeContract = $derived.by(useWriteContract()) + + writeContract.writeContract({ + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }) + await expect.poll(() => writeContract.isSuccess).toBeTruthy() + + expect(writeContract.data).toBeDefined() + }, + {}, + setups.connect, + teardowns.disconnect, + ), +) diff --git a/packages/svelte/src/hooks/useWriteContract.svelte.ts b/packages/svelte/src/hooks/useWriteContract.svelte.ts new file mode 100644 index 0000000000..c54b2193e0 --- /dev/null +++ b/packages/svelte/src/hooks/useWriteContract.svelte.ts @@ -0,0 +1,96 @@ +import { createMutation } from '@tanstack/svelte-query' +import type { + Config, + ResolvedRegister, + WriteContractErrorType, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type WriteContractData, + type WriteContractMutate, + type WriteContractMutateAsync, + type WriteContractVariables, + writeContractMutationOptions, +} from '@wagmi/core/query' +import type { Abi } from 'viem' + +import type { + CreateMutationParameters, + CreateMutationReturnType, +} from '../query.svelte.js' +import type { RuneParameters, RuneReturnType } from '../types.js' +import type { ConfigParameter } from '../types.js' +import { useConfig } from './useConfig.svelte.js' + +export type UseWriteContractParameters< + config extends Config = Config, + context = unknown, +> = RuneParameters< + Compute< + ConfigParameter & { + mutation?: + | CreateMutationParameters< + WriteContractData, + WriteContractErrorType, + WriteContractVariables< + Abi, + string, + readonly unknown[], + config, + config['chains'][number]['id'] + >, + context + > + | undefined + } + > +> + +export type UseWriteContractReturnType< + config extends Config = Config, + context = unknown, +> = RuneReturnType< + Compute< + CreateMutationReturnType< + WriteContractData, + WriteContractErrorType, + WriteContractVariables< + Abi, + string, + readonly unknown[], + config, + config['chains'][number]['id'] + >, + context + > & { + writeContract: WriteContractMutate + writeContractAsync: WriteContractMutateAsync + } + > +> + +/** https://wagmi.sh/react/api/hooks/useWriteContract */ +export function useWriteContract< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseWriteContractParameters = () => ({}), +): UseWriteContractReturnType { + const { mutation } = $derived.by(parameters) + + const config = $derived.by(useConfig(parameters)) + + const mutationOptions = $derived(writeContractMutationOptions(config)) + const query = createMutation(() => ({ + ...mutation, + ...mutationOptions, + })) + const { mutate, mutateAsync, ...result } = $derived(query) + + type Return = ReturnType> + return () => ({ + ...result, + writeContract: mutate as Return['writeContract'], + writeContractAsync: mutateAsync as Return['writeContractAsync'], + }) +} diff --git a/packages/svelte/src/index.ts b/packages/svelte/src/index.ts new file mode 100644 index 0000000000..8b90f9416b --- /dev/null +++ b/packages/svelte/src/index.ts @@ -0,0 +1,3 @@ +// biome-ignore lint/performance/noBarrelFile: entrypoint module +// biome-ignore lint/performance/noReExportAll: entrypoint module +export * from './exports/index.js' diff --git a/packages/svelte/src/query.svelte.ts b/packages/svelte/src/query.svelte.ts new file mode 100644 index 0000000000..cdea665a71 --- /dev/null +++ b/packages/svelte/src/query.svelte.ts @@ -0,0 +1,91 @@ +import { + type CreateMutationOptions, + type CreateMutationResult, + type CreateQueryOptions, + type CreateQueryResult, + type DefaultError, + type QueryKey, + createMutation, + createQuery as tanstack_createQuery, +} from '@tanstack/svelte-query' +import type { + Compute, + ExactPartial, + UnionStrictOmit, +} from '@wagmi/core/internal' +import { hashFn } from '@wagmi/core/query' +import type { RuneParameters, RuneReturnType } from './types.js' + +export type CreateQueryParameters< + queryFnData = unknown, + error = DefaultError, + data = queryFnData, + queryKey extends QueryKey = QueryKey, +> = Compute< + ExactPartial< + Omit, 'initialData'> + > & { + // Fix `initialData` type + initialData?: + | CreateQueryOptions['initialData'] + | undefined + } +> + +export type CreateQueryReturnType< + data = unknown, + error = DefaultError, +> = Compute< + CreateQueryResult & { + queryKey: QueryKey + } +> + +export function createQuery< + queryFnData, + error, + data, + queryKey extends QueryKey, +>( + parameters: RuneParameters< + CreateQueryParameters & { + queryKey: QueryKey + } + >, +): RuneReturnType> { + const result = tanstack_createQuery(() => ({ + ...parameters(), + queryKeyHashFn: hashFn, // for bigint support + })) + const resultWithQueryKey = $derived({ + ...result, + queryKey: parameters().queryKey, + }) + return () => resultWithQueryKey +} + +export type CreateMutationParameters< + data = unknown, + error = Error, + variables = void, + context = unknown, +> = Compute< + Omit< + CreateMutationOptions, context>, + 'mutationFn' | 'mutationKey' | 'throwOnError' + > +> + +export type CreateMutationReturnType< + data = unknown, + error = Error, + variables = void, + context = unknown, +> = Compute< + UnionStrictOmit< + CreateMutationResult, + 'mutate' | 'mutateAsync' + > +> + +export { createMutation } diff --git a/packages/svelte/src/types.ts b/packages/svelte/src/types.ts new file mode 100644 index 0000000000..465fc087b3 --- /dev/null +++ b/packages/svelte/src/types.ts @@ -0,0 +1,31 @@ +import type { + CreateQueryOptions, + DefaultError, + QueryKey, +} from '@tanstack/svelte-query' +import type { Config } from '@wagmi/core' + +export type RuneParameters = () => T +export type RuneReturnType = () => T + +export type ConfigParameter = { + config?: Config | config | undefined +} + +export type EnabledParameter = { + enabled?: boolean | undefined +} + +export type QueryParameter< + queryFnData = unknown, + error = DefaultError, + data = queryFnData, + queryKey extends QueryKey = QueryKey, +> = { + query?: + | Omit< + CreateQueryOptions, + 'queryFn' | 'queryHash' | 'queryKey' | 'queryKeyHashFn' | 'throwOnError' + > + | undefined +} diff --git a/packages/svelte/src/version.test.ts b/packages/svelte/src/version.test.ts new file mode 100644 index 0000000000..a545b00f84 --- /dev/null +++ b/packages/svelte/src/version.test.ts @@ -0,0 +1,6 @@ +import { expect, test } from 'vitest' +import { getVersion } from './version.js' + +test('default', () => { + expect(getVersion()).toMatchInlineSnapshot(`"@wagmi/svelte@x.y.z"`) +}) diff --git a/packages/svelte/src/version.ts b/packages/svelte/src/version.ts new file mode 100644 index 0000000000..cebf26d12e --- /dev/null +++ b/packages/svelte/src/version.ts @@ -0,0 +1 @@ +export const getVersion = () => `@wagmi/svelte@${__VERSION__}` diff --git a/packages/svelte/svelte.config.js b/packages/svelte/svelte.config.js new file mode 100644 index 0000000000..fb094f9abd --- /dev/null +++ b/packages/svelte/svelte.config.js @@ -0,0 +1,21 @@ +import adapter from '@sveltejs/adapter-auto' +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + // Consult https://svelte.dev/docs/kit/integrations#preprocessors + // for more information about preprocessors + preprocess: vitePreprocess(), + + kit: { + // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://svelte.dev/docs/kit/adapters for more information about adapters. + adapter: adapter(), + files: { + lib: './src', + }, + }, +} + +export default config diff --git a/packages/svelte/test/setup.ts b/packages/svelte/test/setup.ts new file mode 100644 index 0000000000..a47233ce75 --- /dev/null +++ b/packages/svelte/test/setup.ts @@ -0,0 +1,12 @@ +import { beforeEach, vi } from 'vitest' + +vi.mock('svelte') + +beforeEach(async () => { + // Make dates stable across runs + Date.now = vi.fn(() => new Date(Date.UTC(2023, 1, 1)).valueOf()) + + globalThis.__VERSION__ = 'x.y.z' + + vi.restoreAllMocks() +}) diff --git a/packages/svelte/tsconfig.json b/packages/svelte/tsconfig.json new file mode 100644 index 0000000000..8ed3dd7f25 --- /dev/null +++ b/packages/svelte/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "module": "NodeNext", + "moduleResolution": "NodeNext" + } +} diff --git a/packages/svelte/vite.config.ts b/packages/svelte/vite.config.ts new file mode 100644 index 0000000000..66d24534c8 --- /dev/null +++ b/packages/svelte/vite.config.ts @@ -0,0 +1,21 @@ +import { sveltekit } from '@sveltejs/kit/vite' +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + plugins: [sveltekit()], + define: { + __VERSION__: JSON.stringify(require('./package.json').version), + }, + test: { + name: '@wagmi/svelte', + include: ['./src/**/*.test.ts'], + environment: 'happy-dom', + testTimeout: 20_000, + setupFiles: ['./test/setup.ts'], + expect: { + poll: { + timeout: 10_000, + }, + }, + }, +}) diff --git a/packages/test/package.json b/packages/test/package.json index f4e1f9fff4..f34bac074c 100644 --- a/packages/test/package.json +++ b/packages/test/package.json @@ -22,7 +22,8 @@ "!src/**/*.test.ts", "!src/**/*.test-d.ts", "react/**", - "vue/**" + "vue/**", + "svelte/**" ], "sideEffects": false, "type": "module", @@ -42,16 +43,22 @@ "types": "./dist/types/exports/vue.d.ts", "default": "./dist/esm/exports/vue.js" }, + "./svelte": { + "types": "./dist/types/exports/svelte.svelte.d.ts", + "default": "./dist/esm/exports/svelte.svelte.js" + }, "./package.json": "./package.json" }, "typesVersions": { "*": { "react": ["./dist/types/exports/react.d.ts"], - "vue": ["./dist/types/exports/vue.d.ts"] + "vue": ["./dist/types/exports/vue.d.ts"], + "svelte": ["./dist/types/exports/svelte.svelte.d.ts"] } }, "peerDependencies": { "@tanstack/react-query": ">=5.0.0", + "@tanstack/svelte-query": "^5.59.20", "@tanstack/vue-query": ">=5.0.0", "@testing-library/react": ">=14.0.0", "@types/react": ">=18", @@ -73,6 +80,9 @@ "@tanstack/vue-query": { "optional": true }, + "@tanstack/svelte-query": { + "optional": true + }, "@testing-library/react": { "optional": true }, @@ -99,6 +109,7 @@ } }, "devDependencies": { + "@tanstack/svelte-query": "^5.59.20", "@tanstack/react-query": "catalog:", "@tanstack/vue-query": "catalog:", "@testing-library/dom": "catalog:", diff --git a/packages/test/src/exports/svelte.svelte.ts b/packages/test/src/exports/svelte.svelte.ts new file mode 100644 index 0000000000..a5ac75b0b0 --- /dev/null +++ b/packages/test/src/exports/svelte.svelte.ts @@ -0,0 +1,68 @@ +import { QueryClient } from '@tanstack/svelte-query' +import { type Config, connect, disconnect, hydrate } from '@wagmi/core' +import { vi } from 'vitest' +import { config } from '../config.js' + +type TestHookOptions = { + shouldMockConfig?: boolean + mockConfigOverride?: Config + reconnectOnMount?: boolean +} + +const noop = () => {} + +export const setups = { + connect: async () => { + const connector = config.connectors[0]! + await connect(config, { connector }) + }, +} + +export const teardowns = { + disconnect: async () => { + const connector = config.connectors[0]! + await disconnect(config, { connector }) + }, +} + +export const testHook = + ( + fn: () => void, + options: TestHookOptions = {}, + setup: () => void = noop, + teardown: () => void = noop, + ) => + async () => { + const queryClient = new QueryClient() + const svelte = await import('svelte') + svelte.getContext = vi.fn((key: any) => { + if (key === '$$_queryClient') return queryClient + if (key === '$$_isRestoring') return () => false + + if (options.shouldMockConfig ?? true) { + return options.mockConfigOverride ?? config + } + + return undefined + }) as (key: any) => T // match type signature of svelte.getContext + + const { onMount } = hydrate(config, { + reconnectOnMount: options.reconnectOnMount ?? false, + }) + + await onMount() + + await Promise.resolve(setup()) + + let promise: Promise | void + const cleanup = $effect.root(() => { + promise = fn() + }) + try { + // @ts-ignore - this is a hack to wait for the test to finish + return await Promise.resolve(promise) + } finally { + await Promise.resolve(teardown()) + cleanup() + } + } diff --git a/playgrounds/sveltekit/.gitignore b/playgrounds/sveltekit/.gitignore new file mode 100644 index 0000000000..79518f7164 --- /dev/null +++ b/playgrounds/sveltekit/.gitignore @@ -0,0 +1,21 @@ +node_modules + +# Output +.output +.vercel +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/playgrounds/sveltekit/.npmrc b/playgrounds/sveltekit/.npmrc new file mode 100644 index 0000000000..b6f27f1359 --- /dev/null +++ b/playgrounds/sveltekit/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/playgrounds/sveltekit/README.md b/playgrounds/sveltekit/README.md new file mode 100644 index 0000000000..b5b295070b --- /dev/null +++ b/playgrounds/sveltekit/README.md @@ -0,0 +1,38 @@ +# sv + +Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```bash +# create a new project in the current directory +npx sv create + +# create a new project in my-app +npx sv create my-app +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```bash +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```bash +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. diff --git a/playgrounds/sveltekit/package.json b/playgrounds/sveltekit/package.json new file mode 100644 index 0000000000..b1b699c854 --- /dev/null +++ b/playgrounds/sveltekit/package.json @@ -0,0 +1,26 @@ +{ + "name": "sveltekit", + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^3.0.0", + "@sveltejs/kit": "^2.0.0", + "@sveltejs/vite-plugin-svelte": "^4.0.0", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "typescript": "^5.0.0", + "vite": "^5.0.3" + }, + "dependencies": { + "@tanstack/svelte-query": "https://pkg.pr.new/@tanstack/svelte-query@ccce0b8", + "@wagmi/svelte": "workspace:*", + "viem": "^2.21.44" + } +} diff --git a/playgrounds/sveltekit/src/app.d.ts b/playgrounds/sveltekit/src/app.d.ts new file mode 100644 index 0000000000..c0c081684f --- /dev/null +++ b/playgrounds/sveltekit/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {} diff --git a/playgrounds/sveltekit/src/app.html b/playgrounds/sveltekit/src/app.html new file mode 100644 index 0000000000..77a5ff52c9 --- /dev/null +++ b/playgrounds/sveltekit/src/app.html @@ -0,0 +1,12 @@ + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/playgrounds/sveltekit/src/lib/index.ts b/playgrounds/sveltekit/src/lib/index.ts new file mode 100644 index 0000000000..856f2b6c38 --- /dev/null +++ b/playgrounds/sveltekit/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/playgrounds/sveltekit/src/routes/+layout.svelte b/playgrounds/sveltekit/src/routes/+layout.svelte new file mode 100644 index 0000000000..306f926abb --- /dev/null +++ b/playgrounds/sveltekit/src/routes/+layout.svelte @@ -0,0 +1,27 @@ + + + + + {@render children()} + + diff --git a/playgrounds/sveltekit/src/routes/+page.svelte b/playgrounds/sveltekit/src/routes/+page.svelte new file mode 100644 index 0000000000..755089a86e --- /dev/null +++ b/playgrounds/sveltekit/src/routes/+page.svelte @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + diff --git a/playgrounds/sveltekit/src/routes/_components/Account.svelte b/playgrounds/sveltekit/src/routes/_components/Account.svelte new file mode 100644 index 0000000000..43e57344d0 --- /dev/null +++ b/playgrounds/sveltekit/src/routes/_components/Account.svelte @@ -0,0 +1,30 @@ + + +
+

Account

+ +
+ account: {account.address} + {ensName} +
+ chainId: {account.chainId} +
+ status: {account.status} +
+ + {#if account.status !== 'disconnected'} + + {/if} +
diff --git a/playgrounds/sveltekit/src/routes/_components/Balance.svelte b/playgrounds/sveltekit/src/routes/_components/Balance.svelte new file mode 100644 index 0000000000..d69dcb591c --- /dev/null +++ b/playgrounds/sveltekit/src/routes/_components/Balance.svelte @@ -0,0 +1,34 @@ + + +
+

Balance

+ +
+ Balance (Default Chain): + {#if !!default_?.value}{formatEther(default_.value)}{/if} +
+
+ Balance (Account Chain): + {#if !!account_?.value}{formatEther(account_.value)}{/if} +
+
+ Balance (Optimism Chain): + {#if !!optimism_?.value}{formatEther(optimism_.value)}{/if} +
+
diff --git a/playgrounds/sveltekit/src/routes/_components/BlockNumber.svelte b/playgrounds/sveltekit/src/routes/_components/BlockNumber.svelte new file mode 100644 index 0000000000..291a43346a --- /dev/null +++ b/playgrounds/sveltekit/src/routes/_components/BlockNumber.svelte @@ -0,0 +1,16 @@ + + +

Block Number

+ +
Block Number (Default Chain): {defaultBlock.data?.toString()}
+
Block Number (Account Chain): {accountBlock.data?.toString()}
+
Block Number (Optimism): {optimismBlock.data?.toString()}
diff --git a/playgrounds/sveltekit/src/routes/_components/Connect.svelte b/playgrounds/sveltekit/src/routes/_components/Connect.svelte new file mode 100644 index 0000000000..c609e01e3c --- /dev/null +++ b/playgrounds/sveltekit/src/routes/_components/Connect.svelte @@ -0,0 +1,18 @@ + + +
+

Connect

+ {#each connectors as connector (connector.uid)} + + {/each} +
{status}
+
{error?.message}
+
diff --git a/playgrounds/sveltekit/src/routes/_components/Connections.svelte b/playgrounds/sveltekit/src/routes/_components/Connections.svelte new file mode 100644 index 0000000000..6dbe871473 --- /dev/null +++ b/playgrounds/sveltekit/src/routes/_components/Connections.svelte @@ -0,0 +1,17 @@ + + +
+

Connections

+ + {#each connections as connection (connection.connector.uid)} +
+
connector {connection.connector.name}
+
accounts: {JSON.stringify(connection.accounts)}
+
chainId: {connection.chainId}
+
+ {/each} +
diff --git a/playgrounds/sveltekit/src/routes/_components/ReadContract.svelte b/playgrounds/sveltekit/src/routes/_components/ReadContract.svelte new file mode 100644 index 0000000000..ba7bb55d32 --- /dev/null +++ b/playgrounds/sveltekit/src/routes/_components/ReadContract.svelte @@ -0,0 +1,17 @@ + + +
+

Read Contract

+
Balance: {balance?.toString()} (isLoading: {isLoading})
+
diff --git a/playgrounds/sveltekit/src/routes/_components/ReadContracts.svelte b/playgrounds/sveltekit/src/routes/_components/ReadContracts.svelte new file mode 100644 index 0000000000..a1dac91895 --- /dev/null +++ b/playgrounds/sveltekit/src/routes/_components/ReadContracts.svelte @@ -0,0 +1,36 @@ + + + +
+

Read Contract

+
Balance: {balance?.toString()}
+
Owner of Token 69: {ownerOf?.toString()}
+
Total Supply: {totalSupply?.toString()}
+
\ No newline at end of file diff --git a/playgrounds/sveltekit/src/routes/_components/SendTransaction.svelte b/playgrounds/sveltekit/src/routes/_components/SendTransaction.svelte new file mode 100644 index 0000000000..7f2bb546d2 --- /dev/null +++ b/playgrounds/sveltekit/src/routes/_components/SendTransaction.svelte @@ -0,0 +1,50 @@ + + +
+

Send Transaction

+
+ + + +
+ {#if hash} +
Transaction Hash: {hash}
+ {/if} + {#if isConfirming} + Waiting for confirmation... + {/if} + {#if isConfirmed} + Transaction confirmed. + {/if} + {#if error} +
Error: {(error as BaseError).shortMessage || error.message}
+ {/if} +
diff --git a/playgrounds/sveltekit/src/routes/_components/SignMessage.svelte b/playgrounds/sveltekit/src/routes/_components/SignMessage.svelte new file mode 100644 index 0000000000..267163fb74 --- /dev/null +++ b/playgrounds/sveltekit/src/routes/_components/SignMessage.svelte @@ -0,0 +1,23 @@ + + +

Sign Message

+ +
+ + +
+ +{#if data} + {data} +{/if} diff --git a/playgrounds/sveltekit/src/routes/_components/SwitchAccount.svelte b/playgrounds/sveltekit/src/routes/_components/SwitchAccount.svelte new file mode 100644 index 0000000000..4248343252 --- /dev/null +++ b/playgrounds/sveltekit/src/routes/_components/SwitchAccount.svelte @@ -0,0 +1,19 @@ + + +

Switch Account

+ +{#each connectors as connector} + +{/each} diff --git a/playgrounds/sveltekit/src/routes/_components/SwitchChain.svelte b/playgrounds/sveltekit/src/routes/_components/SwitchChain.svelte new file mode 100644 index 0000000000..0768254f5b --- /dev/null +++ b/playgrounds/sveltekit/src/routes/_components/SwitchChain.svelte @@ -0,0 +1,23 @@ + + +

Switch Chain

+ +{#each chains as chain} + +{/each} + +{#if error} + {error.message} +{/if} diff --git a/playgrounds/sveltekit/src/routes/_components/WriteContract.svelte b/playgrounds/sveltekit/src/routes/_components/WriteContract.svelte new file mode 100644 index 0000000000..f46feff2cb --- /dev/null +++ b/playgrounds/sveltekit/src/routes/_components/WriteContract.svelte @@ -0,0 +1,55 @@ + + +

Write Contract

+
+ + +
+ +{#if hash} +
Transaction Hash: {hash}
+{/if} +{#if isConfirming} + Waiting for confirmation... +{/if} +{#if isConfirmed} + Transaction confirmed. +{/if} +{#if error} +
Error: {error.shortMessage || error.message}
+{/if} diff --git a/playgrounds/sveltekit/src/routes/_components/contracts.ts b/playgrounds/sveltekit/src/routes/_components/contracts.ts new file mode 100644 index 0000000000..d7d66754a2 --- /dev/null +++ b/playgrounds/sveltekit/src/routes/_components/contracts.ts @@ -0,0 +1,202 @@ +export const wagmiContractConfig = { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi: [ + { inputs: [], stateMutability: 'nonpayable', type: 'constructor' }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'owner', + type: 'address', + }, + { + indexed: true, + name: 'approved', + type: 'address', + }, + { + indexed: true, + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'owner', + type: 'address', + }, + { + indexed: true, + name: 'operator', + type: 'address', + }, + { + indexed: false, + name: 'approved', + type: 'bool', + }, + ], + name: 'ApprovalForAll', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'from', + type: 'address', + }, + { indexed: true, name: 'to', type: 'address' }, + { + indexed: true, + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + inputs: [ + { name: 'to', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + ], + name: 'approve', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ name: 'owner', type: 'address' }], + name: 'balanceOf', + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ name: 'tokenId', type: 'uint256' }], + name: 'getApproved', + outputs: [{ name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { name: 'owner', type: 'address' }, + { name: 'operator', type: 'address' }, + ], + name: 'isApprovedForAll', + outputs: [{ name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'mint', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'mint', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'name', + outputs: [{ name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ name: 'tokenId', type: 'uint256' }], + name: 'ownerOf', + outputs: [{ name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + { name: '_data', type: 'bytes' }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { name: 'operator', type: 'address' }, + { name: 'approved', type: 'bool' }, + ], + name: 'setApprovalForAll', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ name: 'interfaceId', type: 'bytes4' }], + name: 'supportsInterface', + outputs: [{ name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'symbol', + outputs: [{ name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ name: 'tokenId', type: 'uint256' }], + name: 'tokenURI', + outputs: [{ name: '', type: 'string' }], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'totalSupply', + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + ], + name: 'transferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + ], +} as const diff --git a/playgrounds/sveltekit/src/routes/index.css b/playgrounds/sveltekit/src/routes/index.css new file mode 100644 index 0000000000..0733a7ee6b --- /dev/null +++ b/playgrounds/sveltekit/src/routes/index.css @@ -0,0 +1,21 @@ +:root { + background-color: #181818; + color: rgba(255, 255, 255, 0.87); + color-scheme: light dark; + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + font-synthesis: none; + font-weight: 400; + line-height: 1.5; + text-rendering: optimizeLegibility; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +@media (prefers-color-scheme: light) { + :root { + background-color: #f8f8f8; + color: #181818; + } +} diff --git a/playgrounds/sveltekit/static/favicon.png b/playgrounds/sveltekit/static/favicon.png new file mode 100644 index 0000000000..825b9e65af Binary files /dev/null and b/playgrounds/sveltekit/static/favicon.png differ diff --git a/playgrounds/sveltekit/svelte.config.js b/playgrounds/sveltekit/svelte.config.js new file mode 100644 index 0000000000..e92eb5e127 --- /dev/null +++ b/playgrounds/sveltekit/svelte.config.js @@ -0,0 +1,18 @@ +import adapter from '@sveltejs/adapter-auto' +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + // Consult https://svelte.dev/docs/kit/integrations + // for more information about preprocessors + preprocess: vitePreprocess(), + + kit: { + // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://svelte.dev/docs/kit/adapters for more information about adapters. + adapter: adapter(), + }, +} + +export default config diff --git a/playgrounds/sveltekit/tsconfig.json b/playgrounds/sveltekit/tsconfig.json new file mode 100644 index 0000000000..f4d0a0e167 --- /dev/null +++ b/playgrounds/sveltekit/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files + // + // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes + // from the referenced tsconfig.json - TypeScript does not merge them in +} diff --git a/playgrounds/sveltekit/vite.config.ts b/playgrounds/sveltekit/vite.config.ts new file mode 100644 index 0000000000..dd1b8b7fa1 --- /dev/null +++ b/playgrounds/sveltekit/vite.config.ts @@ -0,0 +1,6 @@ +import { sveltekit } from '@sveltejs/kit/vite' +import { defineConfig } from 'vite' + +export default defineConfig({ + plugins: [sveltekit()], +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 96f33a1e9f..e460dcd2b6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -58,7 +58,7 @@ importers: version: 1.1.10 '@vitest/coverage-v8': specifier: ^2.1.1 - version: 2.1.1(vitest@2.1.1(@types/node@20.12.10)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(msw@2.4.9(typescript@5.5.4))(terser@5.31.0)) + version: 2.1.1(vitest@2.1.1) '@vitest/ui': specifier: ^2.1.1 version: 2.1.1(vitest@2.1.1) @@ -327,11 +327,63 @@ importers: specifier: 'catalog:' version: 3.4.27(typescript@5.5.4) + packages/svelte: + dependencies: + '@wagmi/connectors': + specifier: workspace:* + version: link:../connectors + '@wagmi/core': + specifier: workspace:* + version: link:../core + devDependencies: + '@sveltejs/adapter-auto': + specifier: ^3.0.0 + version: 3.3.0(@sveltejs/kit@2.7.2(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0)))(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0))) + '@sveltejs/kit': + specifier: ^2.0.0 + version: 2.7.2(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0)))(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0)) + '@sveltejs/package': + specifier: ^2.0.0 + version: 2.3.6(svelte@5.0.5)(typescript@5.5.4) + '@sveltejs/vite-plugin-svelte': + specifier: ^4.0.0 + version: 4.0.0(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0)) + '@tanstack/svelte-query': + specifier: https://pkg.pr.new/@tanstack/svelte-query@ccce0b8 + version: https://pkg.pr.new/@tanstack/svelte-query@ccce0b8(svelte@5.0.5) + '@wagmi/test': + specifier: workspace:* + version: link:../test + globals: + specifier: ^15.0.0 + version: 15.11.0 + publint: + specifier: ^0.2.0 + version: 0.2.11 + svelte: + specifier: ^5.0.0 + version: 5.0.5 + svelte-check: + specifier: ^4.0.0 + version: 4.0.5(picomatch@4.0.2)(svelte@5.0.5)(typescript@5.5.4) + typescript: + specifier: ^5.0.0 + version: 5.5.4 + vite: + specifier: ^5.0.11 + version: 5.2.11(@types/node@20.12.10)(terser@5.31.0) + vitest: + specifier: ^2.0.4 + version: 2.1.1(@types/node@20.12.10)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(msw@2.4.9(typescript@5.5.4))(terser@5.31.0) + packages/test: devDependencies: '@tanstack/react-query': specifier: 'catalog:' version: 5.49.2(react@18.3.1) + '@tanstack/svelte-query': + specifier: ^5.59.20 + version: 5.59.20(svelte@5.0.5) '@tanstack/vue-query': specifier: 'catalog:' version: 5.49.1(vue@3.4.27(typescript@5.5.4)) @@ -464,6 +516,40 @@ importers: specifier: ^4.3.2 version: 4.3.2(vue@3.4.27(typescript@5.5.4)) + playgrounds/sveltekit: + dependencies: + '@tanstack/svelte-query': + specifier: https://pkg.pr.new/@tanstack/svelte-query@ccce0b8 + version: https://pkg.pr.new/@tanstack/svelte-query@ccce0b8(svelte@5.0.5) + '@wagmi/svelte': + specifier: workspace:* + version: link:../../packages/svelte + viem: + specifier: ^2.21.44 + version: 2.21.44(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4) + devDependencies: + '@sveltejs/adapter-auto': + specifier: ^3.0.0 + version: 3.3.0(@sveltejs/kit@2.7.2(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0)))(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0))) + '@sveltejs/kit': + specifier: ^2.0.0 + version: 2.7.2(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0)))(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0)) + '@sveltejs/vite-plugin-svelte': + specifier: ^4.0.0 + version: 4.0.0(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0)) + svelte: + specifier: ^5.0.0 + version: 5.0.5 + svelte-check: + specifier: ^4.0.0 + version: 4.0.5(picomatch@4.0.2)(svelte@5.0.5)(typescript@5.5.4) + typescript: + specifier: ^5.0.0 + version: 5.5.4 + vite: + specifier: ^5.0.3 + version: 5.2.11(@types/node@20.12.10)(terser@5.31.0) + playgrounds/vite-core: dependencies: '@wagmi/connectors': @@ -595,6 +681,9 @@ importers: '@wagmi/core': specifier: workspace:* version: link:../packages/core + '@wagmi/svelte': + specifier: workspace:* + version: link:../packages/svelte '@wagmi/vue': specifier: workspace:* version: link:../packages/vue @@ -1912,9 +2001,6 @@ packages: '@noble/curves@1.2.0': resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} - '@noble/curves@1.4.0': - resolution: {integrity: sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==} - '@noble/curves@1.6.0': resolution: {integrity: sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==} engines: {node: ^14.21.3 || >=16} @@ -1930,10 +2016,6 @@ packages: resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} engines: {node: '>= 16'} - '@noble/hashes@1.4.0': - resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} - engines: {node: '>= 16'} - '@noble/hashes@1.5.0': resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==} engines: {node: ^14.21.3 || >=16} @@ -2640,9 +2722,6 @@ packages: '@scure/base@1.1.3': resolution: {integrity: sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q==} - '@scure/base@1.1.7': - resolution: {integrity: sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g==} - '@scure/base@1.1.9': resolution: {integrity: sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==} @@ -2655,9 +2734,6 @@ packages: '@scure/bip32@1.3.2': resolution: {integrity: sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==} - '@scure/bip32@1.4.0': - resolution: {integrity: sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==} - '@scure/bip32@1.5.0': resolution: {integrity: sha512-8EnFYkqEQdnkuGBVpCzKxyIwDCBLDVj3oiX0EKUFre/tOjL/Hqba1D6n/8RcmaQy4f95qQFrO2A8Sr6ybh4NRw==} @@ -2667,9 +2743,6 @@ packages: '@scure/bip39@1.2.1': resolution: {integrity: sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==} - '@scure/bip39@1.3.0': - resolution: {integrity: sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==} - '@scure/bip39@1.4.0': resolution: {integrity: sha512-BEEm6p8IueV/ZTfQLp/0vhw4NPnT9oWf5+28nvmeUICjP99f4vr2d+qc7AVGDDtwRep6ifR43Yed9ERVmiITzw==} @@ -2829,6 +2902,42 @@ packages: '@stablelib/x25519@1.0.3': resolution: {integrity: sha512-KnTbKmUhPhHavzobclVJQG5kuivH+qDLpe84iRqX3CLrKp881cF160JvXJ+hjn1aMyCwYOKeIZefIH/P5cJoRw==} + '@sveltejs/adapter-auto@3.3.0': + resolution: {integrity: sha512-EJZqY7eMM+bdbR898Xt9ufawUHLPJu7w3wPr4Cc+T1iIDf3fufVLWg4C71OluIqsdJqv85E4biKuHo3XXIY0PQ==} + peerDependencies: + '@sveltejs/kit': ^2.0.0 + + '@sveltejs/kit@2.7.2': + resolution: {integrity: sha512-bFwrl+0bNr0/DHQZM0INwwSPNYqDjfsKRhUoa6rj9d8tDZzszBrJ3La6/HVFxWGONEigtG+SzHXa1BEa1BLdwA==} + engines: {node: '>=18.13'} + hasBin: true + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 + svelte: ^4.0.0 || ^5.0.0-next.0 + vite: ^5.0.3 + + '@sveltejs/package@2.3.6': + resolution: {integrity: sha512-XzbXWXrdeGbiPj3xICtmh66XrLXApoB/s17LIf0X25bEowAWjEnmukzHVJXaMeSuaFukggdFYoxqcfy4SxucbA==} + engines: {node: ^16.14 || >=18} + hasBin: true + peerDependencies: + svelte: ^3.44.0 || ^4.0.0 || ^5.0.0-next.1 + + '@sveltejs/vite-plugin-svelte-inspector@3.0.0': + resolution: {integrity: sha512-hBxSYW/66989cq9dN248omD/ziskSdIV1NqfuueuAI1z6jGcg14k9Zd98pDIEnoA6wC9kWUGuQ6adzBbWwQyRg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^4.0.0-next.0||^4.0.0 + svelte: ^5.0.0-next.96 || ^5.0.0 + vite: ^5.0.0 + + '@sveltejs/vite-plugin-svelte@4.0.0': + resolution: {integrity: sha512-kpVJwF+gNiMEsoHaw+FJL76IYiwBikkxYU83+BpqQLdVMff19KeRKLd2wisS8niNBMJ2omv5gG+iGDDwd8jzag==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + svelte: ^5.0.0-next.96 || ^5.0.0 + vite: ^5.0.0 + '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} @@ -2845,6 +2954,13 @@ packages: '@tanstack/query-core@5.49.1': resolution: {integrity: sha512-JnC9ndmD1KKS01Rt/ovRUB1tmwO7zkyXAyIxN9mznuJrcNtOrkmOnQqdJF2ib9oHzc2VxHomnEG7xyfo54Npkw==} + '@tanstack/query-core@5.59.20': + resolution: {integrity: sha512-e8vw0lf7KwfGe1if4uPFhvZRWULqHjFcz3K8AebtieXvnMOz5FSzlZe3mTLlPuUBcydCnBRqYs2YJ5ys68wwLg==} + + '@tanstack/query-core@https://pkg.pr.new/TanStack/query/@tanstack/query-core@ccce0b8': + resolution: {tarball: https://pkg.pr.new/TanStack/query/@tanstack/query-core@ccce0b8} + version: 5.56.2 + '@tanstack/query-devtools@5.0.5': resolution: {integrity: sha512-xjuOhOrrO50sPoJ4WG9yPe3imQ0Ds/nutnmwdTqjM2ZTIkflh//p7q2iB6IxFBY9sB106h+PULlma8sgTuOKAQ==} @@ -2873,6 +2989,17 @@ packages: peerDependencies: react: ^18.0.0 + '@tanstack/svelte-query@5.59.20': + resolution: {integrity: sha512-DFRTz9i6OXIF+o4GFDRF4g3Q6BSBKWxoahZPcPbCAdXAS4NRhTnVFR4HgEtzlhfoQx8yMJsLXMhDiGUw365HsA==} + peerDependencies: + svelte: ^3.54.0 || ^4.0.0 || ^5.0.0-next.0 + + '@tanstack/svelte-query@https://pkg.pr.new/@tanstack/svelte-query@ccce0b8': + resolution: {tarball: https://pkg.pr.new/@tanstack/svelte-query@ccce0b8} + version: 5.56.2 + peerDependencies: + svelte: ^5.0.0-next.259 + '@tanstack/vue-query@5.49.1': resolution: {integrity: sha512-/nTqP8PNCRzMcqTGEuFQE3ntUD3A+K05r6Dw/0hNwNS3PLEaKUKlxytmAhIoaoloQwbbAghjLyKRQZ+CMWv90A==} peerDependencies: @@ -3606,17 +3733,6 @@ packages: zod: optional: true - abitype@1.0.5: - resolution: {integrity: sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw==} - peerDependencies: - typescript: '>=5.0.4' - zod: ^3 >=3.22.0 - peerDependenciesMeta: - typescript: - optional: true - zod: - optional: true - abitype@1.0.6: resolution: {integrity: sha512-MMSqYh4+C/aVqI2RQaWqbvI4Kxo5cQV40WQ4QFtDnNzCkqChm8MuENhElmynZlO0qUy/ObkEUaXtKqYnx1Kp3A==} peerDependencies: @@ -3637,6 +3753,11 @@ packages: peerDependencies: acorn: ^8 + acorn-typescript@1.4.13: + resolution: {integrity: sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==} + peerDependencies: + acorn: '>=8.9.0' + acorn-walk@8.3.2: resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} engines: {node: '>=0.4.0'} @@ -3651,6 +3772,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.13.0: + resolution: {integrity: sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==} + engines: {node: '>=0.4.0'} + hasBin: true + adm-zip@0.4.16: resolution: {integrity: sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==} engines: {node: '>=0.3.0'} @@ -3749,6 +3875,10 @@ packages: aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + array-union@1.0.2: resolution: {integrity: sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==} engines: {node: '>=0.10.0'} @@ -3801,6 +3931,10 @@ packages: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + b4a@1.6.6: resolution: {integrity: sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==} @@ -4198,6 +4332,10 @@ packages: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} + cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + copy-anything@3.0.5: resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} engines: {node: '>=12.13'} @@ -4378,6 +4516,9 @@ packages: resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} engines: {node: '>=0.10'} + dedent-js@1.0.1: + resolution: {integrity: sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==} + dedent@0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} @@ -4452,6 +4593,9 @@ packages: devalue@4.3.3: resolution: {integrity: sha512-UH8EL6H2ifcY8TbD2QsxwCC/pr5xSwPvv85LrLXVihmHVC3T3YqTCIwnR5ak0yO1KYqlxrPVOA/JVZJYPy2ATg==} + devalue@5.1.1: + resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==} + devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -4640,11 +4784,17 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} + esm-env@1.0.0: + resolution: {integrity: sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==} + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true + esrap@1.2.2: + resolution: {integrity: sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==} + estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} @@ -4984,6 +5134,13 @@ packages: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} + globals@15.11.0: + resolution: {integrity: sha512-yeyNSjdbyVaWurlwCpcA6XNBrHTMIeDdj0/hnvX/OLJ9ekOXYbLsLinH/MucQyGvNnXhidTdNhTtJaffL2sMfw==} + engines: {node: '>=18'} + + globalyzer@0.1.0: + resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==} + globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} @@ -4996,6 +5153,9 @@ packages: resolution: {integrity: sha512-yANWAN2DUcBtuus5Cpd+SKROzXHs2iVXFZt/Ykrfz6SAXqacLX25NZpltE+39ceMexYF4TtEadjuSTw8+3wX4g==} engines: {node: '>=4'} + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} @@ -5200,6 +5360,9 @@ packages: immutable@4.3.5: resolution: {integrity: sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==} + import-meta-resolve@4.1.0: + resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -5334,6 +5497,9 @@ packages: is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + is-reference@3.0.2: + resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} + is-ssh@1.4.0: resolution: {integrity: sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==} @@ -5520,6 +5686,10 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + klona@2.0.6: resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} engines: {node: '>= 8'} @@ -5574,6 +5744,9 @@ packages: resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} engines: {node: '>=14'} + locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + locate-path@2.0.0: resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} engines: {node: '>=4'} @@ -5638,6 +5811,9 @@ packages: loupe@3.1.1: resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} + lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + lru-cache@10.2.2: resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} engines: {node: 14 || >=16.14} @@ -6073,6 +6249,9 @@ packages: xml2js: optional: true + no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + node-addon-api@2.0.2: resolution: {integrity: sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==} @@ -6308,6 +6487,14 @@ packages: outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + ox@0.1.2: + resolution: {integrity: sha512-ak/8K0Rtphg9vnRJlbOdaX9R7cmxD2MiSthjWGaQdMk3D7hrAlDoM+6Lxn7hN52Za3vrXfZ7enfke/5WjolDww==} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + p-filter@2.1.0: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} engines: {node: '>=8'} @@ -6398,6 +6585,9 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + pascal-case@3.1.2: + resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} + path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -7070,6 +7260,9 @@ packages: set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} @@ -7168,6 +7361,10 @@ packages: resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} engines: {node: '>= 10'} + sirv@3.0.0: + resolution: {integrity: sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==} + engines: {node: '>=18'} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -7442,6 +7639,24 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svelte-check@4.0.5: + resolution: {integrity: sha512-icBTBZ3ibBaywbXUat3cK6hB5Du+Kq9Z8CRuyLmm64XIe2/r+lQcbuBx/IQgsbrC+kT2jQ0weVpZSSRIPwB6jQ==} + engines: {node: '>= 18.0.0'} + hasBin: true + peerDependencies: + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: '>=5.0.0' + + svelte2tsx@0.7.22: + resolution: {integrity: sha512-hf55ujq17ufVpDQlJzaQfRr9EjlLIwGmFlpKq4uYrQAQFw/99q1OcVYyBT6568iJySgBUY9PdccURrORmfetmQ==} + peerDependencies: + svelte: ^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0 + typescript: ^4.9.4 || ^5.0.0 + + svelte@5.0.5: + resolution: {integrity: sha512-f4WBlP5g8W6pEoDfx741lewMlemy+LIGpEqjGPWqnHVP92wqlQXl87U5O5Bi2tkSUrO95OxOoqwU8qlqiHmFKA==} + engines: {node: '>=18'} + svg-tags@1.0.0: resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} @@ -7503,6 +7718,9 @@ packages: thread-stream@0.15.2: resolution: {integrity: sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA==} + tiny-glob@0.2.9: + resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} + tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -7864,16 +8082,16 @@ packages: typescript: optional: true - viem@2.17.0: - resolution: {integrity: sha512-+gaVlsfDsHL1oYdjpatdRxW1WK/slLYVvpOws3fEdLfQFUToezKI6YLC9l1g2uKm4Hg3OdGX1KQy/G7/58tTKQ==} + viem@2.21.28: + resolution: {integrity: sha512-CbS2Ldq+SdZYYSG+P4oNLi1s6xq7JnZoJsIkMhFcZUMRz3w2J1OvC1izUp6E1/Zp/XXo3wt6g/Ae+f3SGzup2A==} peerDependencies: typescript: '>=5.0.4' peerDependenciesMeta: typescript: optional: true - viem@2.21.28: - resolution: {integrity: sha512-CbS2Ldq+SdZYYSG+P4oNLi1s6xq7JnZoJsIkMhFcZUMRz3w2J1OvC1izUp6E1/Zp/XXo3wt6g/Ae+f3SGzup2A==} + viem@2.21.44: + resolution: {integrity: sha512-oyLTCt7OQUetQN2m9KPNgSA//MzpnQLABAyglPKh+fAypU8cTT/hC5UyLQvaYt4WPg6dkbKOxfsahV4739pu9w==} peerDependencies: typescript: '>=5.0.4' peerDependenciesMeta: @@ -8000,6 +8218,14 @@ packages: terser: optional: true + vitefu@1.0.3: + resolution: {integrity: sha512-iKKfOMBHob2WxEJbqbJjHAkmYgvFDPhuqrO82om83S8RLk+17FtyMBfcyeH8GqD0ihShtkMW/zzJgiA51hCNCQ==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0-beta.0 + peerDependenciesMeta: + vite: + optional: true + vitepress@1.5.0: resolution: {integrity: sha512-q4Q/G2zjvynvizdB3/bupdYkCJe2umSAMv9Ju4d92E6/NXJ59z70xB0q5p/4lpRyAwflDsbwy1mLV9Q5+nlB+g==} hasBin: true @@ -8353,6 +8579,9 @@ packages: zhead@2.2.4: resolution: {integrity: sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag==} + zimmerframe@1.1.2: + resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} + zip-stream@6.0.1: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} @@ -9860,10 +10089,6 @@ snapshots: dependencies: '@noble/hashes': 1.3.2 - '@noble/curves@1.4.0': - dependencies: - '@noble/hashes': 1.4.0 - '@noble/curves@1.6.0': dependencies: '@noble/hashes': 1.5.0 @@ -9874,8 +10099,6 @@ snapshots: '@noble/hashes@1.3.2': {} - '@noble/hashes@1.4.0': {} - '@noble/hashes@1.5.0': {} '@noble/secp256k1@1.7.1': {} @@ -10771,7 +10994,7 @@ snapshots: '@safe-global/safe-apps-sdk@9.1.0(bufferutil@4.0.8)(encoding@0.1.13)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4)': dependencies: '@safe-global/safe-gateway-typescript-sdk': 3.8.0(encoding@0.1.13) - viem: 2.17.0(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4) + viem: 2.21.28(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4) transitivePeerDependencies: - bufferutil - encoding @@ -10787,8 +11010,6 @@ snapshots: '@scure/base@1.1.3': {} - '@scure/base@1.1.7': {} - '@scure/base@1.1.9': {} '@scure/bip32@1.1.5': @@ -10801,7 +11022,7 @@ snapshots: dependencies: '@noble/curves': 1.1.0 '@noble/hashes': 1.3.2 - '@scure/base': 1.1.7 + '@scure/base': 1.1.9 '@scure/bip32@1.3.2': dependencies: @@ -10809,17 +11030,11 @@ snapshots: '@noble/hashes': 1.3.2 '@scure/base': 1.1.3 - '@scure/bip32@1.4.0': - dependencies: - '@noble/curves': 1.4.0 - '@noble/hashes': 1.4.0 - '@scure/base': 1.1.7 - '@scure/bip32@1.5.0': dependencies: '@noble/curves': 1.6.0 '@noble/hashes': 1.5.0 - '@scure/base': 1.1.7 + '@scure/base': 1.1.9 '@scure/bip39@1.1.1': dependencies: @@ -10831,11 +11046,6 @@ snapshots: '@noble/hashes': 1.3.2 '@scure/base': 1.1.3 - '@scure/bip39@1.3.0': - dependencies: - '@noble/hashes': 1.4.0 - '@scure/base': 1.1.7 - '@scure/bip39@1.4.0': dependencies: '@noble/hashes': 1.5.0 @@ -11076,6 +11286,62 @@ snapshots: '@stablelib/random': 1.0.2 '@stablelib/wipe': 1.0.1 + '@sveltejs/adapter-auto@3.3.0(@sveltejs/kit@2.7.2(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0)))(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0)))': + dependencies: + '@sveltejs/kit': 2.7.2(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0)))(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0)) + import-meta-resolve: 4.1.0 + + '@sveltejs/kit@2.7.2(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0)))(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0))': + dependencies: + '@sveltejs/vite-plugin-svelte': 4.0.0(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0)) + '@types/cookie': 0.6.0 + cookie: 0.6.0 + devalue: 5.1.1 + esm-env: 1.0.0 + import-meta-resolve: 4.1.0 + kleur: 4.1.5 + magic-string: 0.30.11 + mrmime: 2.0.0 + sade: 1.8.1 + set-cookie-parser: 2.7.1 + sirv: 3.0.0 + svelte: 5.0.5 + tiny-glob: 0.2.9 + vite: 5.2.11(@types/node@20.12.10)(terser@5.31.0) + + '@sveltejs/package@2.3.6(svelte@5.0.5)(typescript@5.5.4)': + dependencies: + chokidar: 4.0.1 + kleur: 4.1.5 + sade: 1.8.1 + semver: 7.6.2 + svelte: 5.0.5 + svelte2tsx: 0.7.22(svelte@5.0.5)(typescript@5.5.4) + transitivePeerDependencies: + - typescript + + '@sveltejs/vite-plugin-svelte-inspector@3.0.0(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0)))(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0))': + dependencies: + '@sveltejs/vite-plugin-svelte': 4.0.0(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0)) + debug: 4.3.7 + svelte: 5.0.5 + vite: 5.2.11(@types/node@20.12.10)(terser@5.31.0) + transitivePeerDependencies: + - supports-color + + '@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0))': + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 3.0.0(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0)))(svelte@5.0.5)(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0)) + debug: 4.3.7 + deepmerge: 4.3.1 + kleur: 4.1.5 + magic-string: 0.30.12 + svelte: 5.0.5 + vite: 5.2.11(@types/node@20.12.10)(terser@5.31.0) + vitefu: 1.0.3(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0)) + transitivePeerDependencies: + - supports-color + '@swc/counter@0.1.3': {} '@swc/helpers@0.5.5': @@ -11091,6 +11357,10 @@ snapshots: '@tanstack/query-core@5.49.1': {} + '@tanstack/query-core@5.59.20': {} + + '@tanstack/query-core@https://pkg.pr.new/TanStack/query/@tanstack/query-core@ccce0b8': {} + '@tanstack/query-devtools@5.0.5': {} '@tanstack/query-persist-client-core@5.0.5': @@ -11121,6 +11391,16 @@ snapshots: '@tanstack/query-core': 5.49.1 react: 18.3.1 + '@tanstack/svelte-query@5.59.20(svelte@5.0.5)': + dependencies: + '@tanstack/query-core': 5.59.20 + svelte: 5.0.5 + + '@tanstack/svelte-query@https://pkg.pr.new/@tanstack/svelte-query@ccce0b8(svelte@5.0.5)': + dependencies: + '@tanstack/query-core': https://pkg.pr.new/TanStack/query/@tanstack/query-core@ccce0b8 + svelte: 5.0.5 + '@tanstack/vue-query@5.49.1(vue@3.4.27(typescript@5.5.4))': dependencies: '@tanstack/match-sorter-utils': 8.15.1 @@ -11574,7 +11854,7 @@ snapshots: vite: 5.4.10(@types/node@20.12.10)(terser@5.31.0) vue: 3.5.12(typescript@5.6.1-rc) - '@vitest/coverage-v8@2.1.1(vitest@2.1.1(@types/node@20.12.10)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(msw@2.4.9(typescript@5.5.4))(terser@5.31.0))': + '@vitest/coverage-v8@2.1.1(vitest@2.1.1)': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -12524,11 +12804,6 @@ snapshots: typescript: 5.5.4 zod: 3.22.2 - abitype@1.0.5(typescript@5.5.4)(zod@3.22.4): - optionalDependencies: - typescript: 5.5.4 - zod: 3.22.4 - abitype@1.0.6(typescript@5.5.4)(zod@3.22.4): optionalDependencies: typescript: 5.5.4 @@ -12542,12 +12817,18 @@ snapshots: dependencies: acorn: 8.11.3 + acorn-typescript@1.4.13(acorn@8.13.0): + dependencies: + acorn: 8.13.0 + acorn-walk@8.3.2: {} acorn@8.10.0: {} acorn@8.11.3: {} + acorn@8.13.0: {} + adm-zip@0.4.16: {} agent-base@6.0.2: @@ -12661,6 +12942,8 @@ snapshots: dependencies: dequal: 2.0.3 + aria-query@5.3.2: {} + array-union@1.0.2: dependencies: array-uniq: 1.0.3 @@ -12713,6 +12996,8 @@ snapshots: available-typed-arrays@1.0.5: {} + axobject-query@4.1.0: {} + b4a@1.6.6: {} balanced-match@1.0.2: {} @@ -13124,6 +13409,8 @@ snapshots: cookie@0.5.0: {} + cookie@0.6.0: {} + copy-anything@3.0.5: dependencies: is-what: 4.1.16 @@ -13302,6 +13589,8 @@ snapshots: decode-uri-component@0.2.2: {} + dedent-js@1.0.1: {} + dedent@0.7.0: {} deep-eql@5.0.2: {} @@ -13347,6 +13636,8 @@ snapshots: devalue@4.3.3: {} + devalue@5.1.1: {} + devlop@1.1.0: dependencies: dequal: 2.0.3 @@ -13598,8 +13889,15 @@ snapshots: escape-string-regexp@5.0.0: {} + esm-env@1.0.0: {} + esprima@4.0.1: {} + esrap@1.2.2: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + '@types/estree': 1.0.5 + estree-walker@2.0.2: {} estree-walker@3.0.3: @@ -14052,6 +14350,10 @@ snapshots: globals@11.12.0: {} + globals@15.11.0: {} + + globalyzer@0.1.0: {} + globby@11.1.0: dependencies: array-union: 2.1.0 @@ -14079,6 +14381,8 @@ snapshots: pify: 3.0.0 slash: 1.0.0 + globrex@0.1.2: {} + gopd@1.0.1: dependencies: get-intrinsic: 1.2.1 @@ -14331,6 +14635,8 @@ snapshots: immutable@4.3.5: {} + import-meta-resolve@4.1.0: {} + imurmurhash@0.1.4: {} indent-string@4.0.0: {} @@ -14441,6 +14747,10 @@ snapshots: dependencies: '@types/estree': 1.0.5 + is-reference@3.0.2: + dependencies: + '@types/estree': 1.0.5 + is-ssh@1.4.0: dependencies: protocols: 2.0.1 @@ -14491,10 +14801,6 @@ snapshots: dependencies: ws: 8.13.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) - isows@1.0.4(ws@8.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)): - dependencies: - ws: 8.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - isows@1.0.6(ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)): dependencies: ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -14604,6 +14910,8 @@ snapshots: kleur@3.0.3: {} + kleur@4.1.5: {} + klona@2.0.6: {} knip@5.30.6(@types/node@20.12.10)(typescript@5.5.4): @@ -14690,6 +14998,8 @@ snapshots: mlly: 1.4.2 pkg-types: 1.0.3 + locate-character@3.0.0: {} + locate-path@2.0.0: dependencies: p-locate: 2.0.0 @@ -14747,6 +15057,10 @@ snapshots: dependencies: get-func-name: 2.0.2 + lower-case@2.0.2: + dependencies: + tslib: 2.5.0 + lru-cache@10.2.2: {} lru-cache@10.4.3: {} @@ -15436,6 +15750,11 @@ snapshots: - supports-color - uWebSockets.js + no-case@3.0.4: + dependencies: + lower-case: 2.0.2 + tslib: 2.5.0 + node-addon-api@2.0.2: {} node-addon-api@7.0.0: {} @@ -15916,6 +16235,20 @@ snapshots: outvariant@1.4.3: {} + ox@0.1.2(typescript@5.5.4)(zod@3.22.4): + dependencies: + '@adraffy/ens-normalize': 1.11.0 + '@noble/curves': 1.6.0 + '@noble/hashes': 1.5.0 + '@scure/bip32': 1.5.0 + '@scure/bip39': 1.4.0 + abitype: 1.0.6(typescript@5.5.4)(zod@3.22.4) + eventemitter3: 5.0.1 + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - zod + p-filter@2.1.0: dependencies: p-map: 2.1.0 @@ -16014,6 +16347,11 @@ snapshots: parseurl@1.3.3: {} + pascal-case@3.1.2: + dependencies: + no-case: 3.0.4 + tslib: 2.5.0 + path-browserify@1.0.1: {} path-exists@3.0.0: {} @@ -16704,6 +17042,8 @@ snapshots: set-blocking@2.0.0: {} + set-cookie-parser@2.7.1: {} + setimmediate@1.0.5: {} setprototypeof@1.2.0: {} @@ -16806,6 +17146,12 @@ snapshots: mrmime: 2.0.0 totalist: 3.0.1 + sirv@3.0.0: + dependencies: + '@polka/url': 1.0.0-next.25 + mrmime: 2.0.0 + totalist: 3.0.1 + sisteransi@1.0.5: {} skin-tone@2.0.0: @@ -17057,6 +17403,41 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svelte-check@4.0.5(picomatch@4.0.2)(svelte@5.0.5)(typescript@5.5.4): + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + chokidar: 4.0.1 + fdir: 6.3.0(picomatch@4.0.2) + picocolors: 1.1.0 + sade: 1.8.1 + svelte: 5.0.5 + typescript: 5.5.4 + transitivePeerDependencies: + - picomatch + + svelte2tsx@0.7.22(svelte@5.0.5)(typescript@5.5.4): + dependencies: + dedent-js: 1.0.1 + pascal-case: 3.1.2 + svelte: 5.0.5 + typescript: 5.5.4 + + svelte@5.0.5: + dependencies: + '@ampproject/remapping': 2.3.0 + '@jridgewell/sourcemap-codec': 1.5.0 + '@types/estree': 1.0.5 + acorn: 8.13.0 + acorn-typescript: 1.4.13(acorn@8.13.0) + aria-query: 5.3.2 + axobject-query: 4.1.0 + esm-env: 1.0.0 + esrap: 1.2.2 + is-reference: 3.0.2 + locate-character: 3.0.0 + magic-string: 0.30.11 + zimmerframe: 1.1.2 + svg-tags@1.0.0: {} svgo@3.3.2: @@ -17133,6 +17514,11 @@ snapshots: dependencies: real-require: 0.1.0 + tiny-glob@0.2.9: + dependencies: + globalyzer: 0.1.0 + globrex: 0.1.2 + tiny-invariant@1.3.3: {} tinybench@2.9.0: {} @@ -17597,16 +17983,17 @@ snapshots: - utf-8-validate - zod - viem@2.17.0(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4): + viem@2.21.28(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4): dependencies: - '@adraffy/ens-normalize': 1.10.0 - '@noble/curves': 1.4.0 - '@noble/hashes': 1.4.0 - '@scure/bip32': 1.4.0 - '@scure/bip39': 1.3.0 - abitype: 1.0.5(typescript@5.5.4)(zod@3.22.4) - isows: 1.0.4(ws@8.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - ws: 8.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@adraffy/ens-normalize': 1.11.0 + '@noble/curves': 1.6.0 + '@noble/hashes': 1.5.0 + '@scure/bip32': 1.5.0 + '@scure/bip39': 1.4.0 + abitype: 1.0.6(typescript@5.5.4)(zod@3.22.4) + isows: 1.0.6(ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + webauthn-p256: 0.0.10 + ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: @@ -17614,15 +18001,15 @@ snapshots: - utf-8-validate - zod - viem@2.21.28(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4): + viem@2.21.44(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4): dependencies: - '@adraffy/ens-normalize': 1.11.0 '@noble/curves': 1.6.0 '@noble/hashes': 1.5.0 '@scure/bip32': 1.5.0 '@scure/bip39': 1.4.0 abitype: 1.0.6(typescript@5.5.4)(zod@3.22.4) isows: 1.0.6(ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + ox: 0.1.2(typescript@5.5.4)(zod@3.22.4) webauthn-p256: 0.0.10 ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) optionalDependencies: @@ -17639,7 +18026,7 @@ snapshots: vite-node@1.6.0(@types/node@20.12.10)(terser@5.31.0): dependencies: cac: 6.7.14 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.7 pathe: 1.1.2 picocolors: 1.1.0 vite: 5.2.11(@types/node@20.12.10)(terser@5.31.0) @@ -17766,6 +18153,10 @@ snapshots: fsevents: 2.3.3 terser: 5.31.0 + vitefu@1.0.3(vite@5.2.11(@types/node@20.12.10)(terser@5.31.0)): + optionalDependencies: + vite: 5.2.11(@types/node@20.12.10)(terser@5.31.0) + vitepress@1.5.0(@algolia/client-search@5.12.0)(@types/node@20.12.10)(@types/react@18.3.1)(change-case@5.4.4)(idb-keyval@6.2.1)(postcss@8.4.47)(qrcode@1.5.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(terser@5.31.0)(typescript@5.6.1-rc): dependencies: '@docsearch/css': 3.6.3 @@ -18218,6 +18609,8 @@ snapshots: zhead@2.2.4: {} + zimmerframe@1.1.2: {} + zip-stream@6.0.1: dependencies: archiver-utils: 5.0.2 diff --git a/site/.vitepress/config.ts b/site/.vitepress/config.ts index 77f242cb09..088c193243 100644 --- a/site/.vitepress/config.ts +++ b/site/.vitepress/config.ts @@ -80,6 +80,7 @@ export default defineConfig({ nav: [ { text: 'React', link: '/react/getting-started' }, { text: 'Vue', link: '/vue/getting-started' }, + { text: 'Svelte', link: '/svelte/getting-started' }, { text: 'Core', link: '/core/getting-started' }, { text: 'CLI', link: '/cli/getting-started' }, // { text: 'Examples', link: '/examples/connect-wallet' }, diff --git a/site/.vitepress/sidebar.ts b/site/.vitepress/sidebar.ts index da4d4bf5e2..f3ce18f8d4 100644 --- a/site/.vitepress/sidebar.ts +++ b/site/.vitepress/sidebar.ts @@ -629,6 +629,15 @@ export function getSidebar() { ], }, ], + '/svelte': [ + { + text: 'Introduction', + items: [ + { text: 'Getting Started', link: '/svelte/getting-started' }, + { text: 'Reactivity', link: '/svelte/reactivity' }, + ], + }, + ], '/core': [ { text: 'Introduction', diff --git a/site/package.json b/site/package.json index f683675231..89f384fc1f 100644 --- a/site/package.json +++ b/site/package.json @@ -15,6 +15,7 @@ "@types/react": "catalog:", "@wagmi/connectors": "workspace:*", "@wagmi/core": "workspace:*", + "@wagmi/svelte": "workspace:*", "@wagmi/vue": "workspace:*", "abitype": "*", "nuxt": "^3.11.2", diff --git a/site/snippets/svelte/config.ts b/site/snippets/svelte/config.ts new file mode 100644 index 0000000000..8777811c8a --- /dev/null +++ b/site/snippets/svelte/config.ts @@ -0,0 +1,10 @@ +import { http, createConfig } from '@wagmi/svelte' +import { mainnet, sepolia } from '@wagmi/svelte/chains' + +export const config = createConfig({ + chains: [mainnet, sepolia], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) diff --git a/site/svelte/getting-started.md b/site/svelte/getting-started.md new file mode 100644 index 0000000000..dd61841f00 --- /dev/null +++ b/site/svelte/getting-started.md @@ -0,0 +1,139 @@ + + +# Getting Started + +## Overview + +Wagmi is a Svelte library for Ethereum. + +## Manual Installation + +To manually add Wagmi to your project, install the required packages. + +::: code-group +```bash-vue [pnpm] +pnpm add @wagmi/svelte viem@{{viemVersion}} {{tanstackQuery}} +``` + +```bash-vue [npm] +npm install @wagmi/svelte viem@{{viemVersion}} {{tanstackQuery}} +``` + +```bash-vue [yarn] +yarn add @wagmi/svelte viem@{{viemVersion}} {{tanstackQuery}} +``` + +```bash-vue [bun] +bun add @wagmi/svelte viem@{{viemVersion}} {{tanstackQuery}} +``` +::: + +- [Viem](https://viem.sh) is a TypeScript interface for Ethereum that performs blockchain operations. +- [TanStack Query](https://tanstack.com/query/v5) is an async state manager that handles requests, caching, and more. + +### Create Config + +Create and export a new Wagmi config using `createConfig`. + +::: code-group +<<< @/snippets/svelte/config.ts[config.ts] +::: + +### Wrap App in Context Provider + +Wrap your app in the `WagmiProvider` component and pass the `config` you created earlier to the `config` property. You may do this in the `+layout.svelte` file, or in another component for more fine-grained control. + +::: code-group +```svelte [+layout.svelte] + + + // [!code focus] + {@render children()} // [!code focus] + // [!code focus] +``` +<<< @/snippets/svelte/config.ts[config.ts] +::: + +### Setup TanStack Query + +Inside the `WagmiProvider`, wrap your app in a TanStack Query Context Provider, e.g. `QueryClientProvider`, and pass a new `QueryClient` instance to the `client` property. + +::: code-group +```svelte [+layout.svelte] + + + + // [!code focus] + {@render children()} // [!code focus] + // [!code focus] + +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +Check out the [TanStack Query docs](https://tanstack.com/query/latest/docs/framework/svelte) to learn about the library, APIs, and more. + +### Use Wagmi + +Now that everything is set up, every component inside the Wagmi and TanStack Query Providers can use Wagmi React Hooks. + +::: code-group +```svelte [+page.svelte] + + +{#if ensName.status === 'loading'} +
Loading ENS name
+{:else if ensName.status === 'error'} +
Error fetching ENS name: {ensName.error.message}
+{:else} +
ENS name: {ensName.data}
+{/if} +``` + +```svelte [+layout.svelte] + + + + + {@render children()} + + +``` +<<< @/snippets/svelte/config.ts[config.ts] +::: + +::: danger +Reactivity in Svelte is different than other frameworks! I would highly suggest you read the page on [Reactivity](./reactivity.md) +::: diff --git a/site/svelte/reactivity.md b/site/svelte/reactivity.md new file mode 100644 index 0000000000..0ff86c6bb0 --- /dev/null +++ b/site/svelte/reactivity.md @@ -0,0 +1,33 @@ +# Reactivity + +To ensure Svelte Runes are reactive, we have opted to return a function from each hook. This pairs very well with the [`$derived.by` rune](https://svelte.dev/docs/svelte/$derived#$derived.by): + +```svelte + + +

Your address: {account.address}

+``` + +::: tip +If you don't need the return value (such as for `useWatchBlockNumber()`), there is no need to wrap the function in `$derived.by`. +::: + +Similarly, if you simply pass an object into the function, it will not be reactive. We have chosen to use Solid-style parameters, meaning you pass arguments as functions: + +```svelte + + +

Vitalik's ENS: {vitalikEns}

+``` diff --git a/vitest.workspace.ts b/vitest.workspace.ts index 0c187c0401..c68e09ea8b 100644 --- a/vitest.workspace.ts +++ b/vitest.workspace.ts @@ -9,6 +9,7 @@ const alias = { '@wagmi/core': path.resolve(__dirname, './packages/core/src/exports'), '@wagmi/test': path.resolve(__dirname, './packages/test/src/exports'), '@wagmi/vue': path.resolve(__dirname, './packages/vue/src/exports'), + '@wagmi/svelte': path.resolve(__dirname, './packages/svelte/src/exports'), wagmi: path.resolve(__dirname, './packages/react/src/exports'), } @@ -68,6 +69,7 @@ export default defineWorkspace([ }, resolve: { alias }, }, + './packages/svelte', { test: { name: 'react-register',