diff --git a/.changeset/nine-colts-clap.md b/.changeset/nine-colts-clap.md new file mode 100644 index 0000000000..7bd8afc229 --- /dev/null +++ b/.changeset/nine-colts-clap.md @@ -0,0 +1,6 @@ +--- +"wagmi": minor +"@wagmi/core": minor +--- + +Added experimental EIP-5792 Actions & Hooks. diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 9c18433eda..ffd47b0cdf 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -63,7 +63,7 @@ jobs: strategy: matrix: typescript-version: ['5.0.4', '5.1.6', '5.2.2', 'latest'] - viem-version: ['2.8.4', 'latest'] + viem-version: ['2.9.31', 'latest'] steps: - name: Clone repository diff --git a/.gitignore b/.gitignore index 48b7235821..0b914097b1 100644 --- a/.gitignore +++ b/.gitignore @@ -22,10 +22,12 @@ packages/cli/plugins packages/core/actions packages/core/chains packages/core/codegen +packages/core/experimental packages/core/internal packages/core/query packages/react/actions packages/react/chains packages/react/codegen packages/react/connectors +packages/react/experimental packages/react/query diff --git a/docs/.vitepress/sidebar.ts b/docs/.vitepress/sidebar.ts index 52144d7e96..aa41113d36 100644 --- a/docs/.vitepress/sidebar.ts +++ b/docs/.vitepress/sidebar.ts @@ -344,6 +344,31 @@ export function getSidebar() { }, ], }, + { + text: 'Experimental', + items: [ + { + text: 'useCallsStatus', + link: '/react/api/hooks/useCallsStatus', + }, + { + text: 'useCapabilities', + link: '/react/api/hooks/useCapabilities', + }, + { + text: 'useSendCalls', + link: '/react/api/hooks/useSendCalls', + }, + { + text: 'useShowCallsStatus', + link: '/react/api/hooks/useShowCallsStatus', + }, + { + text: 'useWriteContracts', + link: '/react/api/hooks/useWriteContracts', + }, + ], + }, ], '/core': [ { @@ -687,6 +712,31 @@ export function getSidebar() { }, ], }, + { + text: 'Experimental', + items: [ + { + text: 'getCallsStatus', + link: '/core/api/actions/getCallsStatus', + }, + { + text: 'getCapabilities', + link: '/core/api/actions/getCapabilities', + }, + { + text: 'sendCalls', + link: '/core/api/actions/sendCalls', + }, + { + text: 'showCallsStatus', + link: '/core/api/actions/showCallsStatus', + }, + { + text: 'writeContracts', + link: '/core/api/actions/writeContracts', + }, + ], + }, ], '/cli': [ { diff --git a/docs/core/api/actions/getCallsStatus.md b/docs/core/api/actions/getCallsStatus.md new file mode 100644 index 0000000000..e94cfb668f --- /dev/null +++ b/docs/core/api/actions/getCallsStatus.md @@ -0,0 +1,101 @@ + + +# getCallsStatus + +Action to fetch the status and receipts of a call batch that was sent via [`sendCalls`](/core/api/actions/sendCalls). + +[Read more.](https://github.com/ethereum/EIPs/blob/1663ea2e7a683285f977eda51c32cec86553f585/EIPS/eip-5792.md#wallet_getcallsstatus) + +::: warning +This is an experimental action that is not supported in most wallets. It is recommended to have a fallback mechanism if using this in production. +::: + +## Import + +```ts +import { getCallsStatus } from '@wagmi/core/experimental' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getCallsStatus } from '@wagmi/core/experimental' +import { config } from './config' + +const status = await getCallsStatus(config, { + id: '0x1234567890abcdef', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type GetCallsStatusParameters } from '@wagmi/core/experimental' +``` + +### connector + +`Connector | undefined` + +Connector to get call statuses with. + +::: code-group +```ts [index.ts] +import { getConnections, getCallsStatus } from '@wagmi/core/experimental' +import { config } from './config' + +const connections = getConnections(config) +const status = await getCallsStatus(config, { + connector: connections[0]?.connector, // [!code focus] + id: '0x1234567890abcdef', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### id + +`string` + +Identifier of the call batch. + +::: code-group +```ts [index.ts] +import { getCallsStatus } from '@wagmi/core/experimental' +import { config } from './config' + +const status = await getCallsStatus(config, { + id: '0x1234567890abcdef', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetCallsStatusReturnType } from '@wagmi/core/experimental' +``` + +`bigint` + +Most recent block number seen. + +## Error + +```ts +import { type GetCallsStatusErrorType } from '@wagmi/core/experimental' +``` + + + +## Viem + +- [`getCallsStatus`](https://viem.sh/experimental/eip5792/getCallsStatus) diff --git a/docs/core/api/actions/getCapabilities.md b/docs/core/api/actions/getCapabilities.md new file mode 100644 index 0000000000..6fe8cd99e9 --- /dev/null +++ b/docs/core/api/actions/getCapabilities.md @@ -0,0 +1,98 @@ + + +# getCapabilities + +Action to extract capabilities (grouped by chain ID) that a connected wallet supports (e.g. paymasters, session keys, etc). + +[Read more.](https://github.com/ethereum/EIPs/blob/815028dc634463e1716fc5ce44c019a6040f0bef/EIPS/eip-5792.md#wallet_getcapabilities) + +::: warning +This is an experimental action that is not supported in most wallets. It is recommended to have a fallback mechanism if using this in production. +::: + +## Import + +```ts +import { getCapabilities } from '@wagmi/core/experimental' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getCapabilities } from '@wagmi/core/experimental' +import { config } from './config' + +const capabilities = await getCapabilities(config) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type GetCapabilitiesParameters } from '@wagmi/core/experimental' +``` + +### account + +`Account | Address | undefined` + +Fetch capabilities for the provided account. + +::: code-group +```ts [index.ts] +import { getCapabilities } from '@wagmi/core/experimental' +import { config } from './config' + +const capabilities = await getCapabilities(config, { + account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +Connector to get capabilities from. + +::: code-group +```ts [index.ts] +import { getConnections, getCapabilities } from '@wagmi/core/experimental' +import { config } from './config' + +const connections = getConnections(config) +const capabilities = await getCapabilities(config, { + connector: connections[0]?.connector, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetCapabilitiesReturnType } from '@wagmi/core/experimental' +``` + +`bigint` + +Most recent block number seen. + +## Error + +```ts +import { type GetCapabilitiesErrorType } from '@wagmi/core/experimental' +``` + + + +## Viem + +- [`getCapabilities`](https://viem.sh/experimental/eip5792/getCapabilities) diff --git a/docs/core/api/actions/sendCalls.md b/docs/core/api/actions/sendCalls.md new file mode 100644 index 0000000000..92332bd99d --- /dev/null +++ b/docs/core/api/actions/sendCalls.md @@ -0,0 +1,223 @@ + + +# sendCalls + +Action that requests for the wallet to sign and broadcast a batch of calls (transactions) to the network. + +[Read more.](https://github.com/ethereum/EIPs/blob/815028dc634463e1716fc5ce44c019a6040f0bef/EIPS/eip-5792.md#wallet_sendcalls) + +::: warning +This is an experimental action that is not supported in most wallets. It is recommended to have a fallback mechanism if using this in production. +::: + +## Import + +```ts +import { sendCalls } from '@wagmi/core/experimental' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { parseEther } from 'viem' +import { sendCalls } from '@wagmi/core/experimental' +import { config } from './config' + +const id = await sendCalls(config, { + calls: [ + { + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1') + }, + { + data: '0xdeadbeef', + to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + }, + ] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type SendCallsParameters } from '@wagmi/core/experimental' +``` + +### account + +`Account | Address | undefined` + +Account to execute the calls. + +::: code-group +```ts [index.ts] +import { parseEther } from 'viem' +import { sendCalls } from '@wagmi/core/experimental' +import { config } from './config' + +const id = await sendCalls(config, { + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', // [!code focus] + calls: [ + { + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1') + }, + { + data: '0xdeadbeef', + to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + }, + ], +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### calls + +`{ to: Hex, data?: Hex, value?: bigint }[]` + +Calls to execute. + +::: code-group +```ts [index.ts] +import { parseEther } from 'viem' +import { sendCalls } from '@wagmi/core/experimental' +import { config } from './config' + +const id = await sendCalls(config, { + calls: [ // [!code focus] + { // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', // [!code focus] + value: parseEther('1') // [!code focus] + }, // [!code focus] + { // [!code focus] + data: '0xdeadbeef', // [!code focus] + to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', // [!code focus] + }, // [!code focus] + ], // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### capabilities + +`WalletCapabilities | undefined` + +Capability metadata for the calls (e.g. specifying a paymaster). + +::: code-group +```ts [index.ts] +import { parseEther } from 'viem' +import { sendCalls } from '@wagmi/core/experimental' +import { config } from './config' + +const id = await sendCalls(config, { + calls: [ + { + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1') + }, + { + data: '0xdeadbeef', + to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + }, + ], + capabilities: { // [!code focus] + paymasterService: { // [!code focus] + url: 'https://...' // [!code focus] + } // [!code focus] + } // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`number | undefined` + +The target chain ID to broadcast the calls. + +::: code-group +```ts [index.ts] +import { parseEther } from 'viem' +import { sendCalls } from '@wagmi/core/experimental' +import { config } from './config' + +const id = await sendCalls(config, { + calls: [ + { + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1') + }, + { + data: '0xdeadbeef', + to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + }, + ], + chainId: 10, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +Connector to get send the calls with. + +::: code-group +```ts [index.ts] +import { parseEther } from 'viem' +import { getConnections } from '@wagmi/core' +import { sendCalls } from '@wagmi/core/experimental' +import { config } from './config' + +const connections = getConnections(config) +const id = await sendCalls(config, { + calls: [ + { + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1') + }, + { + data: '0xdeadbeef', + to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + }, + ], + connector: connections[0]?.connector, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type SendCallsReturnType } from '@wagmi/core/experimental' +``` + +`bigint` + +Most recent block number seen. + +## Error + +```ts +import { type SendCallsErrorType } from '@wagmi/core/experimental' +``` + + + +## Viem + +- [`sendCalls`](https://viem.sh/experimental/eip5792/sendCalls) diff --git a/docs/core/api/actions/showCallsStatus.md b/docs/core/api/actions/showCallsStatus.md new file mode 100644 index 0000000000..dba02841ff --- /dev/null +++ b/docs/core/api/actions/showCallsStatus.md @@ -0,0 +1,101 @@ + + +# showCallsStatus + +Action to request for the wallet to show information about a call batch that was sent via `showCalls`. + +[Read more.](https://github.com/ethereum/EIPs/blob/1663ea2e7a683285f977eda51c32cec86553f585/EIPS/eip-5792.md#wallet_showcallsstatus) + +::: warning +This is an experimental action that is not supported in most wallets. It is recommended to have a fallback mechanism if using this in production. +::: + +## Import + +```ts +import { showCallsStatus } from '@wagmi/core/experimental' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { showCallsStatus } from '@wagmi/core/experimental' +import { config } from './config' + +await showCallsStatus(config, { + id: '0x1234567890abcdef', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type ShowCallsStatusParameters } from '@wagmi/core/experimental' +``` + +### connector + +`Connector | undefined` + +Connector to show call statuses with. + +::: code-group +```ts [index.ts] +import { getConnections, showCallsStatus } from '@wagmi/core/experimental' +import { config } from './config' + +const connections = getConnections(config) +await showCallsStatus(config, { + connector: connections[0]?.connector, // [!code focus] + id: '0x1234567890abcdef', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### id + +`string` + +Identifier of the call batch. + +::: code-group +```ts [index.ts] +import { showCallsStatus } from '@wagmi/core/experimental' +import { config } from './config' + +await showCallsStatus(config, { + id: '0x1234567890abcdef', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type ShowCallsStatusReturnType } from '@wagmi/core/experimental' +``` + +`bigint` + +Most recent block number seen. + +## Error + +```ts +import { type ShowCallsStatusErrorType } from '@wagmi/core/experimental' +``` + + + +## Viem + +- [`showCallsStatus`](https://viem.sh/experimental/eip5792/showCallsStatus) diff --git a/docs/core/api/actions/writeContracts.md b/docs/core/api/actions/writeContracts.md new file mode 100644 index 0000000000..c782330707 --- /dev/null +++ b/docs/core/api/actions/writeContracts.md @@ -0,0 +1,319 @@ + + +# writeContracts + +Action that requests for the wallet to sign and broadcast a batch of write contract calls (transactions) to the network. + +[Read more.](https://github.com/ethereum/EIPs/blob/815028dc634463e1716fc5ce44c019a6040f0bef/EIPS/eip-5792.md#wallet_sendcalls) + +::: warning +This is an experimental action that is not supported in most wallets. It is recommended to have a fallback mechanism if using this in production. +::: + +## Import + +```ts +import { writeContracts } from '@wagmi/core/experimental' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { parseAbi } from 'viem' +import { writeContracts } from '@wagmi/core/experimental' +import { config } from './config' + +const abi = parseAbi([ + 'function approve(address, uint256) returns (bool)', + 'function transferFrom(address, address, uint256) returns (bool)', +]) + +const id = await writeContracts(config, { + contracts: [ + { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'approve', + args: [ + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + 100n + ], + }, + { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'transferFrom', + args: [ + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + '0x0000000000000000000000000000000000000000', + 100n + ], + }, + ], +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type WriteContractsParameters } from '@wagmi/core/experimental' +``` + +### account + +`Account | Address | undefined` + +Account to execute the calls. + +::: code-group +```ts [index.ts] +import { parseAbi } from 'viem' +import { writeContracts } from '@wagmi/core/experimental' +import { config } from './config' + +const abi = parseAbi([ + 'function approve(address, uint256) returns (bool)', + 'function transferFrom(address, address, uint256) returns (bool)', +]) + +const id = await writeContracts(config, { + account: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', // [!code focus] + contracts: [ + { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'approve', + args: [ + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + 100n + ], + }, + { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'transferFrom', + args: [ + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + '0x0000000000000000000000000000000000000000', + 100n + ], + }, + ], +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### contracts + +`{ to: Hex, data?: Hex, value?: bigint }[]` + +Calls to execute. + +::: code-group +```ts [index.ts] +import { parseAbi } from 'viem' +import { writeContracts } from '@wagmi/core/experimental' +import { config } from './config' + +const abi = parseAbi([ + 'function approve(address, uint256) returns (bool)', + 'function transferFrom(address, address, uint256) returns (bool)', +]) + +const id = await writeContracts(config, { + contracts: [ // [!code focus] + { // [!code focus] + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', // [!code focus] + abi, // [!code focus] + functionName: 'approve', // [!code focus] + args: [ // [!code focus] + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', // [!code focus] + 100n // [!code focus] + ], // [!code focus] + }, // [!code focus] + { // [!code focus] + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', // [!code focus] + abi, // [!code focus] + functionName: 'transferFrom', // [!code focus] + args: [ // [!code focus] + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', // [!code focus] + '0x0000000000000000000000000000000000000000', // [!code focus] + 100n // [!code focus] + ], // [!code focus] + }, // [!code focus] + ], // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### capabilities + +`WalletCapabilities | undefined` + +Capability metadata for the calls (e.g. specifying a paymaster). + +::: code-group +```ts [index.ts] +import { parseAbi } from 'viem' +import { writeContracts } from '@wagmi/core/experimental' +import { config } from './config' + +const abi = parseAbi([ + 'function approve(address, uint256) returns (bool)', + 'function transferFrom(address, address, uint256) returns (bool)', +]) + +const id = await writeContracts(config, { + contracts: [ + { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'approve', + args: [ + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + 100n + ], + }, + { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'transferFrom', + args: [ + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + '0x0000000000000000000000000000000000000000', + 100n + ], + }, + ], + capabilities: { // [!code focus] + paymasterService: { // [!code focus] + url: 'https://...' // [!code focus] + } // [!code focus] + } // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`number | undefined` + +The target chain ID to broadcast the calls. + +::: code-group +```ts [index.ts] +import { parseAbi } from 'viem' +import { writeContracts } from '@wagmi/core/experimental' +import { config } from './config' + +const abi = parseAbi([ + 'function approve(address, uint256) returns (bool)', + 'function transferFrom(address, address, uint256) returns (bool)', +]) + +const id = await writeContracts(config, { + contracts: [ + { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'approve', + args: [ + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + 100n + ], + }, + { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'transferFrom', + args: [ + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + '0x0000000000000000000000000000000000000000', + 100n + ], + }, + ], + chainId: 10, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +Connector to get send the calls with. + +::: code-group +```ts [index.ts] +import { parseAbi } from 'viem' +import { getConnections } from '@wagmi/core' +import { writeContracts } from '@wagmi/core/experimental' +import { config } from './config' + +const abi = parseAbi([ + 'function approve(address, uint256) returns (bool)', + 'function transferFrom(address, address, uint256) returns (bool)', +]) + +const connections = getConnections(config) +const id = await writeContracts(config, { + contracts: [ + { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'approve', + args: [ + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + 100n + ], + }, + { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'transferFrom', + args: [ + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + '0x0000000000000000000000000000000000000000', + 100n + ], + }, + ], + connector: connections[0]?.connector, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type WriteContractsReturnType } from '@wagmi/core/experimental' +``` + +`bigint` + +Most recent block number seen. + +## Error + +```ts +import { type WriteContractsErrorType } from '@wagmi/core/experimental' +``` + + + +## Viem + +- [`writeContracts`](https://viem.sh/experimental/eip5792/writeContracts) diff --git a/docs/react/api/hooks/useCallsStatus.md b/docs/react/api/hooks/useCallsStatus.md new file mode 100644 index 0000000000..2d4acb7f63 --- /dev/null +++ b/docs/react/api/hooks/useCallsStatus.md @@ -0,0 +1,145 @@ +--- +title: useCallsStatus +description: Hook for fetching the number of the most recent block seen. +--- + + + +# useCallsStatus + +Hook to fetch the status and receipts of a call batch that was sent via [`useSendCalls`](/core/api/actions/useSendCalls). + +::: warning +This is an experimental action that is not supported in most wallets. It is recommended to have a fallback mechanism if using this in production. +::: + +## Import + +```ts +import { useCallsStatus } from 'wagmi/experimental' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useCallsStatus } from 'wagmi/experimental' + +function App() { + const result = useCallsStatus({ + id: '0x...', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseCallsStatusParameters } from 'wagmi/experimental' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the from nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useCallsStatus } from 'wagmi/experimental' +import { config } from './config' // [!code focus] + +function App() { + const result = useCallsStatus({ + config, // [!code focus] + id: '0x...', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +Connector to get call statuses with. + +::: code-group +```tsx [index.tsx] +import { useCallsStatus, useConnections } from 'wagmi/experimental' +import { config } from './config' // [!code focus] + +function App() { + const connections = useConnections() + const result = useCallsStatus({ + connector: connections[0]?.connector, // [!code focus] + id: '0x...', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### id + +`string` + +Identifier of the call batch. + +::: code-group +```ts [index.ts] +import { useCallsStatus } from '@wagmi/core' +import { config } from './config' + +const status = await useCallsStatus({ + id: '0x1234567890abcdef', // [!code focus] +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.tsx] +import { useCallsStatus } from 'wagmi/experimental' +import { config } from './config' // [!code focus] + +function App() { + const result = useCallsStatus({ + id: '0x...', + scopeKey: 'foo', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseCallsStatusReturnType } from 'wagmi/experimental' +``` + + + + + +## Action + +- [`getCallsStatus`](https://viem.sh/experimental/eip5792/getCallsStatus) \ No newline at end of file diff --git a/docs/react/api/hooks/useCapabilities.md b/docs/react/api/hooks/useCapabilities.md new file mode 100644 index 0000000000..e478cf000a --- /dev/null +++ b/docs/react/api/hooks/useCapabilities.md @@ -0,0 +1,142 @@ +--- +title: useCapabilities +description: Hook for fetching the number of the most recent block seen. +--- + + + +# useCapabilities + +Hook to extract capabilities (grouped by chain ID) that a connected wallet supports (e.g. paymasters, session keys, etc). + +[Read more.](https://github.com/ethereum/EIPs/blob/815028dc634463e1716fc5ce44c019a6040f0bef/EIPS/eip-5792.md#wallet_getcapabilities) + +::: warning +This is an experimental Hook that is not supported in most wallets. It is recommended to have a fallback mechanism if using this in production. +::: + +## Import + +```ts +import { useCapabilities } from 'wagmi/experimental' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useCapabilities } from 'wagmi/experimental' + +function App() { + const result = useCapabilities() +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseCapabilitiesParameters } from 'wagmi/experimental' +``` + +### account + +`Account | Address | undefined` + +Fetch capabilities for the provided account. + +::: code-group +```ts [index.ts] +import { useCapabilities } from '@wagmi/core' +import { config } from './config' + +const status = await useCapabilities({ + account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', // [!code focus] +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the from nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useCapabilities } from 'wagmi/experimental' +import { config } from './config' // [!code focus] + +function App() { + const result = useCapabilities({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +Connector to get call statuses with. + +::: code-group +```tsx [index.tsx] +import { useCapabilities, useConnections } from 'wagmi/experimental' +import { config } from './config' // [!code focus] + +function App() { + const connections = useConnections() + const result = useCapabilities({ + connector: connections[0]?.connector, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.tsx] +import { useCapabilities } from 'wagmi/experimental' +import { config } from './config' // [!code focus] + +function App() { + const result = useCapabilities({ + scopeKey: 'foo', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseCapabilitiesReturnType } from 'wagmi/experimental' +``` + + + + + +## Action + +- [`getCapabilities`](https://viem.sh/experimental/eip5792/getCapabilities) \ No newline at end of file diff --git a/docs/react/api/hooks/useSendCalls.md b/docs/react/api/hooks/useSendCalls.md new file mode 100644 index 0000000000..a85d13fea7 --- /dev/null +++ b/docs/react/api/hooks/useSendCalls.md @@ -0,0 +1,107 @@ +--- +title: useSendCalls +description: Hook that requests for the wallet to sign and broadcast a batch of calls (transactions) to the network. +--- + + + +# useSendCalls + +Hook that requests for the wallet to sign and broadcast a batch of calls (transactions) to the network. + +[Read more.](https://github.com/ethereum/EIPs/blob/815028dc634463e1716fc5ce44c019a6040f0bef/EIPS/eip-5792.md#wallet_sendcalls) + +::: warning +This is an experimental Hook that is not supported in most wallets. It is recommended to have a fallback mechanism if using this in production. +::: + +## Import + +```ts +import { useSendCalls } from 'wagmi/experimental' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useSendCalls } from 'wagmi/experimental' +import { parseEther } from 'viem' + +function App() { + const { sendCalls } = useSendCalls() + + return ( + + ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseSendCallsParameters } from 'wagmi/experimental' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the from nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useSendCalls } from 'wagmi/experimental' +import { config } from './config' // [!code focus] + +function App() { + const result = useSendCalls({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseSendCallsReturnType } from 'wagmi/experimental' +``` + + + + + +## Action + +- [`sendCalls`](/core/api/actions/sendCalls) diff --git a/docs/react/api/hooks/useShowCallsStatus.md b/docs/react/api/hooks/useShowCallsStatus.md new file mode 100644 index 0000000000..ee45927e29 --- /dev/null +++ b/docs/react/api/hooks/useShowCallsStatus.md @@ -0,0 +1,98 @@ +--- +title: useShowCallsStatus +description: Action to request for the wallet to show information about a call batch +--- + + + +# useShowCallsStatus + +Action to request for the wallet to show information about a call batch that was sent via `useShowCalls`. + +[Read more.](https://github.com/ethereum/EIPs/blob/1663ea2e7a683285f977eda51c32cec86553f585/EIPS/eip-5792.md#wallet_showcallsstatus) + +::: warning +This is an experimental action that is not supported in most wallets. It is recommended to have a fallback mechanism if using this in production. +::: + +## Import + +```ts +import { useShowCallsStatus } from 'wagmi/experimental' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useShowCallsStatus } from 'wagmi/experimental' +import { parseEther } from 'viem' + +function App() { + const { showCallsStatus } = useShowCallsStatus() + + return ( + + ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseShowCallsStatusParameters } from 'wagmi/experimental' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the from nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useShowCallsStatus } from 'wagmi/experimental' +import { config } from './config' // [!code focus] + +function App() { + const result = useShowCallsStatus({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseShowCallsStatusReturnType } from 'wagmi/experimental' +``` + + + + + +## Action + +- [`showCallsStatus`](/core/api/actions/showCallsStatus) diff --git a/docs/react/api/hooks/useWriteContracts.md b/docs/react/api/hooks/useWriteContracts.md new file mode 100644 index 0000000000..21f79e3dad --- /dev/null +++ b/docs/react/api/hooks/useWriteContracts.md @@ -0,0 +1,123 @@ +--- +title: useWriteContracts +description: Hook that requests for the wallet to sign and broadcast a batch of calls (transactions) to the network. +--- + + + +# useWriteContracts + +Hook that requests for the wallet to sign and broadcast a batch of write contract calls (transactions) to the network. + +[Read more.](https://github.com/ethereum/EIPs/blob/815028dc634463e1716fc5ce44c019a6040f0bef/EIPS/eip-5792.md#wallet_sendcalls) + +::: warning +This is an experimental Hook that is not supported in most wallets. It is recommended to have a fallback mechanism if using this in production. +::: + +## Import + +```ts +import { useWriteContracts } from 'wagmi/experimental' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useWriteContracts } from 'wagmi/experimental' +import { parseAbi } from 'viem' + +const abi = parseAbi([ + 'function approve(address, uint256) returns (bool)', + 'function transferFrom(address, address, uint256) returns (bool)', +]) + +function App() { + const { writeContracts } = useWriteContracts() + + return ( + + ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseWriteContractsParameters } from 'wagmi/experimental' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the from nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useWriteContracts } from 'wagmi/experimental' +import { config } from './config' // [!code focus] + +function App() { + const result = useWriteContracts({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseWriteContractsReturnType } from 'wagmi/experimental' +``` + + + + + +## Action + +- [`writeContracts`](/core/api/actions/writeContracts) diff --git a/package.json b/package.json index b2ff62dbb5..38e63ed592 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "sherif": "^0.8.1", "simple-git-hooks": "^2.9.0", "typescript": "5.4.2", - "viem": "2.8.4", + "viem": "2.9.31", "vitest": "^0.34.5" }, "packageManager": "pnpm@8.10.5", @@ -95,14 +95,14 @@ "ignoreDependencies": ["@walletconnect/modal"] }, "packages/core": { - "entry": "src/exports/{actions,chains,codegen,index,internal,query}.ts!", + "entry": "src/exports/{actions,chains,codegen,experimental,index,internal,query}.ts!", "ignoreDependencies": ["@tanstack/query-core"] }, "packages/create-wagmi": { "entry": "src/cli.ts!" }, "packages/react": { - "entry": "src/exports/{actions,chains,codegen,connectors,index,query}.ts!" + "entry": "src/exports/{actions,chains,codegen,connectors,experimental,index,query}.ts!" }, "packages/test": { "entry": [ diff --git a/packages/core/package.json b/packages/core/package.json index 47def7cac3..d4f63556c9 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -11,7 +11,7 @@ "scripts": { "build": "pnpm run clean && pnpm run build:esm+types", "build:esm+types": "tsc --project tsconfig.build.json --outDir ./dist/esm --declaration --declarationMap --declarationDir ./dist/types", - "clean": "rm -rf dist tsconfig.tsbuildinfo actions chains codegen internal query", + "clean": "rm -rf dist tsconfig.tsbuildinfo actions chains codegen experimental internal query", "test:build": "publint --strict", "typecheck": "tsc --noEmit" }, @@ -23,6 +23,7 @@ "!src/**/*.test-d.ts", "/actions", "/chains", + "/experimental", "/internal", "/query" ], @@ -48,6 +49,10 @@ "types": "./dist/types/exports/codegen.d.ts", "default": "./dist/esm/exports/codegen.js" }, + "./experimental": { + "types": "./dist/types/exports/experimental.d.ts", + "default": "./dist/esm/exports/experimental.js" + }, "./internal": { "types": "./dist/types/exports/internal.d.ts", "default": "./dist/esm/exports/internal.js" @@ -60,21 +65,12 @@ }, "typesVersions": { "*": { - "actions": [ - "./dist/types/exports/actions.d.ts" - ], - "chains": [ - "./dist/types/exports/chains.d.ts" - ], - "codegen": [ - "./dist/types/exports/codegen.d.ts" - ], - "internal": [ - "./dist/types/exports/internal.d.ts" - ], - "query": [ - "./dist/types/exports/query.d.ts" - ] + "actions": ["./dist/types/exports/actions.d.ts"], + "chains": ["./dist/types/exports/chains.d.ts"], + "codegen": ["./dist/types/exports/codegen.d.ts"], + "experimental": ["./dist/types/exports/experimental.d.ts"], + "internal": ["./dist/types/exports/internal.d.ts"], + "query": ["./dist/types/exports/query.d.ts"] } }, "peerDependencies": { @@ -98,17 +94,7 @@ "devDependencies": { "@tanstack/query-core": ">=5.0.0" }, - "contributors": [ - "awkweb.eth ", - "jxom.eth " - ], + "contributors": ["awkweb.eth ", "jxom.eth "], "funding": "https://github.com/sponsors/wevm", - "keywords": [ - "wagmi", - "eth", - "ethereum", - "dapps", - "wallet", - "web3" - ] -} \ No newline at end of file + "keywords": ["wagmi", "eth", "ethereum", "dapps", "wallet", "web3"] +} diff --git a/packages/core/src/actions/call.test.ts b/packages/core/src/actions/call.test.ts index 3e088095bd..25d6357cf1 100644 --- a/packages/core/src/actions/call.test.ts +++ b/packages/core/src/actions/call.test.ts @@ -74,7 +74,7 @@ test('nonce too low', async () => { nonce: 0 Details: nonce too low - Version: viem@2.8.4" + Version: viem@2.9.31" `) }) @@ -103,7 +103,7 @@ test('insufficient funds', async () => { value: 100000 ETH Details: Insufficient funds for gas * price + value - Version: viem@2.8.4" + Version: viem@2.9.31" `) }) @@ -126,7 +126,7 @@ test('maxFeePerGas less than maxPriorityFeePerGas', async () => { maxFeePerGas: 20 gwei maxPriorityFeePerGas: 22 gwei - Version: viem@2.8.4" + Version: viem@2.9.31" `) }) @@ -146,7 +146,7 @@ test('contract revert (contract error)', async () => { data: 0xa0712d6800000000000000000000000000000000000000000000000000000000000001a4 Details: execution reverted: revert: Token ID is taken - Version: viem@2.8.4" + Version: viem@2.9.31" `) }) @@ -166,6 +166,6 @@ test('contract revert (insufficient params)', async () => { data: 0xa0712d68 Details: execution reverted - Version: viem@2.8.4" + Version: viem@2.9.31" `) }) diff --git a/packages/core/src/actions/connect.test.ts b/packages/core/src/actions/connect.test.ts index e0f51b2d62..ab77ea9d74 100644 --- a/packages/core/src/actions/connect.test.ts +++ b/packages/core/src/actions/connect.test.ts @@ -57,7 +57,7 @@ test('behavior: user rejected request', async () => { [UserRejectedRequestError: User rejected the request. Details: Failed to connect. - Version: viem@2.8.4] + Version: viem@2.9.31] `) }) diff --git a/packages/core/src/actions/getToken.test.ts b/packages/core/src/actions/getToken.test.ts index 9e437f20ff..97834d47ed 100644 --- a/packages/core/src/actions/getToken.test.ts +++ b/packages/core/src/actions/getToken.test.ts @@ -79,6 +79,6 @@ test('behavior: bogus token', async () => { function: decimals() Docs: https://viem.sh/docs/contract/multicall - Version: viem@2.8.4" + Version: viem@2.9.31" `) }) diff --git a/packages/core/src/actions/prepareTransactionRequest.test.ts b/packages/core/src/actions/prepareTransactionRequest.test.ts index aeb9c2ca30..a62b964bd4 100644 --- a/packages/core/src/actions/prepareTransactionRequest.test.ts +++ b/packages/core/src/actions/prepareTransactionRequest.test.ts @@ -88,6 +88,7 @@ test('behavior: local account', async () => { { "account": { "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "experimental_signAuthMessage": [Function], "publicKey": "0x048318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed753547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5", "signMessage": [Function], "signTransaction": [Function], diff --git a/packages/core/src/actions/readContracts.test.ts b/packages/core/src/actions/readContracts.test.ts index ca3b898b86..34038084c5 100644 --- a/packages/core/src/actions/readContracts.test.ts +++ b/packages/core/src/actions/readContracts.test.ts @@ -397,7 +397,7 @@ test('throws if allowFailure=false & a contract method fails', async () => { args: (0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 69420) Docs: https://viem.sh/docs/contract/readContract - Version: viem@2.8.4" + Version: viem@2.9.31" `, ) }) @@ -450,7 +450,7 @@ test('allowFailure=true & a contract method fails', async () => { args: (0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 69420) Docs: https://viem.sh/docs/contract/readContract - Version: viem@2.8.4], + Version: viem@2.9.31], "result": undefined, "status": "failure", }, @@ -464,7 +464,7 @@ test('allowFailure=true & a contract method fails', async () => { args: (0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 69421) Docs: https://viem.sh/docs/contract/readContract - Version: viem@2.8.4], + Version: viem@2.9.31], "result": undefined, "status": "failure", }, @@ -502,7 +502,7 @@ test('throws if allowFailure=false & encoding contract function data fails', asy args: (1e+31) Docs: https://viem.sh/docs/contract/readContract - Version: viem@2.8.4" + Version: viem@2.9.31" `, ) }) @@ -561,7 +561,7 @@ test('allowFailure=true & encoding contract function data fails', async () => { args: (1e+31) Docs: https://viem.sh/docs/contract/readContract - Version: viem@2.8.4], + Version: viem@2.9.31], "result": undefined, "status": "failure", }, @@ -579,7 +579,7 @@ test('allowFailure=true & encoding contract function data fails', async () => { args: (1e+31) Docs: https://viem.sh/docs/contract/readContract - Version: viem@2.8.4], + Version: viem@2.9.31], "result": undefined, "status": "failure", }, @@ -617,7 +617,7 @@ test('should throw if allowFailure=false & a contract has no response', async () args: (0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC) Docs: https://viem.sh/docs/contract/readContract - Version: viem@2.8.4" + Version: viem@2.9.31" `, ) }) @@ -669,7 +669,7 @@ test('allowFailure=true & a contract has no response', async () => { args: (0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC) Docs: https://viem.sh/docs/contract/readContract - Version: viem@2.8.4], + Version: viem@2.9.31], "result": undefined, "status": "failure", }, diff --git a/packages/core/src/actions/sendTransaction.test.ts b/packages/core/src/actions/sendTransaction.test.ts index ccd25105a9..3309ed363b 100644 --- a/packages/core/src/actions/sendTransaction.test.ts +++ b/packages/core/src/actions/sendTransaction.test.ts @@ -77,7 +77,7 @@ test('behavior: value exceeds balance', async () => { value: 99999 ETH Details: Insufficient funds for gas * price + value - Version: viem@2.8.4" + Version: viem@2.9.31" `) await disconnect(config, { connector }) }) diff --git a/packages/core/src/actions/sendTransaction.ts b/packages/core/src/actions/sendTransaction.ts index 1ae61db29f..218fd69572 100644 --- a/packages/core/src/actions/sendTransaction.ts +++ b/packages/core/src/actions/sendTransaction.ts @@ -82,7 +82,7 @@ export async function sendTransaction< if (!('data' in parameters) || !parameters.data) return undefined // Skip gas estimation if connector supports simulation. - if (activeConnector?.supportsSimulation) return undefined + if ((connector ?? activeConnector)?.supportsSimulation) return undefined // Skip gas estimation if `null` is provided. if (gas_ === null) return undefined diff --git a/packages/core/src/actions/signMessage.test.ts b/packages/core/src/actions/signMessage.test.ts index 4be837a32c..f5d4917161 100644 --- a/packages/core/src/actions/signMessage.test.ts +++ b/packages/core/src/actions/signMessage.test.ts @@ -51,7 +51,7 @@ test('behavior: user rejected request', async () => { [UserRejectedRequestError: User rejected the request. Details: Failed to sign message. - Version: viem@2.8.4] + Version: viem@2.9.31] `) await disconnect(config, { connector: connector_ }) }) diff --git a/packages/core/src/actions/signTypedData.test.ts b/packages/core/src/actions/signTypedData.test.ts index d67f053c8c..6519be6901 100644 --- a/packages/core/src/actions/signTypedData.test.ts +++ b/packages/core/src/actions/signTypedData.test.ts @@ -47,7 +47,7 @@ test('behavior: user rejected request', async () => { [UserRejectedRequestError: User rejected the request. Details: Failed to sign typed data. - Version: viem@2.8.4] + Version: viem@2.9.31] `) await disconnect(config, { connector: connector_ }) }) diff --git a/packages/core/src/actions/switchChain.test.ts b/packages/core/src/actions/switchChain.test.ts index a2f2f0edd7..3e61852e36 100644 --- a/packages/core/src/actions/switchChain.test.ts +++ b/packages/core/src/actions/switchChain.test.ts @@ -43,7 +43,7 @@ test('behavior: user rejected request', async () => { [UserRejectedRequestError: User rejected the request. Details: Failed to switch chain. - Version: viem@2.8.4] + Version: viem@2.9.31] `) await disconnect(config, { connector: connector_ }) }) diff --git a/packages/core/src/connectors/mock.ts b/packages/core/src/connectors/mock.ts index df4df88879..067a0764cd 100644 --- a/packages/core/src/connectors/mock.ts +++ b/packages/core/src/connectors/mock.ts @@ -6,11 +6,14 @@ import { SwitchChainError, type Transport, UserRejectedRequestError, + type WalletCallReceipt, type WalletRpcSchema, custom, fromHex, getAddress, + keccak256, numberToHex, + stringToHex, } from 'viem' import { rpc } from 'viem/utils' @@ -35,6 +38,7 @@ export type MockParameters = { mock.type = 'mock' as const export function mock(parameters: MockParameters) { + const transactionCache = new Map() const features = parameters.features ?? {} type Provider = ReturnType< @@ -70,7 +74,10 @@ export function mock(parameters: MockParameters) { connected = true - return { accounts, chainId: currentChainId } + return { + accounts: accounts.map((x) => getAddress(x)), + chainId: currentChainId, + } }, async disconnect() { connected = false @@ -151,6 +158,86 @@ export function mock(parameters: MockParameters) { return } + if (method === 'wallet_getCapabilities') + return { + '0x2105': { + paymasterService: { + supported: + (params as [Hex])[0] === + '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + }, + sessionKeys: { + supported: true, + }, + }, + '0x14A34': { + paymasterService: { + supported: + (params as [Hex])[0] === + '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + }, + }, + } + + if (method === 'wallet_sendCalls') { + const hashes = [] + const calls = (params as any)[0].calls + for (const call of calls) { + const { result, error } = await rpc.http(url, { + body: { + method: 'eth_sendTransaction', + params: [call], + }, + }) + if (error) + throw new RpcRequestError({ + body: { method, params }, + error, + url, + }) + hashes.push(result) + } + const id = keccak256(stringToHex(JSON.stringify(calls))) + transactionCache.set(id, hashes) + return id + } + + if (method === 'wallet_getCallsStatus') { + const hashes = transactionCache.get((params as any)[0]) + if (!hashes) return null + const receipts = await Promise.all( + hashes.map(async (hash) => { + const { result, error } = await rpc.http(url, { + body: { + method: 'eth_getTransactionReceipt', + params: [hash], + id: 0, + }, + }) + if (error) + throw new RpcRequestError({ + body: { method, params }, + error, + url, + }) + if (!result) return null + return { + blockHash: result.blockHash, + blockNumber: result.blockNumber, + gasUsed: result.gasUsed, + logs: result.logs, + status: result.status, + transactionHash: result.transactionHash, + } satisfies WalletCallReceipt + }), + ) + if (receipts.some((x) => !x)) + return { status: 'PENDING', receipts: [] } + return { status: 'CONFIRMED', receipts } + } + + if (method === 'wallet_showCallsStatus') return + // other methods if (method === 'personal_sign') { if (features.signMessageError) { diff --git a/packages/core/src/experimental/actions/getCallsStatus.test.ts b/packages/core/src/experimental/actions/getCallsStatus.test.ts new file mode 100644 index 0000000000..b20886d530 --- /dev/null +++ b/packages/core/src/experimental/actions/getCallsStatus.test.ts @@ -0,0 +1,70 @@ +import { accounts, config, testClient } from '@wagmi/test' +import { parseEther } from 'viem' +import { expect, test } from 'vitest' + +import { connect } from '../../actions/connect.js' +import { disconnect } from '../../actions/disconnect.js' +import { getCallsStatus } from './getCallsStatus.js' +import { sendCalls } from './sendCalls.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + const id = await sendCalls(config, { + calls: [ + { + data: '0xdeadbeef', + to: accounts[1], + value: parseEther('1'), + }, + { + to: accounts[2], + value: parseEther('2'), + }, + { + to: accounts[3], + value: parseEther('3'), + }, + ], + }) + await testClient.mainnet.mine({ blocks: 1 }) + const { receipts, status } = await getCallsStatus(config, { + id, + }) + + expect(status).toBe('CONFIRMED') + expect( + receipts?.map((x) => ({ ...x, blockHash: undefined })), + ).toMatchInlineSnapshot( + ` + [ + { + "blockHash": undefined, + "blockNumber": 19258214n, + "gasUsed": 21064n, + "logs": [], + "status": "success", + "transactionHash": "0xa81a893ca5596aa105fc10206b9a8bd63df8bd526d8c0594496330f956515b4b", + }, + { + "blockHash": undefined, + "blockNumber": 19258214n, + "gasUsed": 21000n, + "logs": [], + "status": "success", + "transactionHash": "0x63624bbfc73f8bd7629ecb4e00ba521a9967ea9e9098ac17ae5bf500c904da1a", + }, + { + "blockHash": undefined, + "blockNumber": 19258214n, + "gasUsed": 21000n, + "logs": [], + "status": "success", + "transactionHash": "0xc3df1b2f2c76723846a964c0c4d6d11ba423df036e0e636d298416e0f94bda29", + }, + ] + `, + ) + await disconnect(config, { connector }) +}) diff --git a/packages/core/src/experimental/actions/getCallsStatus.ts b/packages/core/src/experimental/actions/getCallsStatus.ts new file mode 100644 index 0000000000..d1fa1de00c --- /dev/null +++ b/packages/core/src/experimental/actions/getCallsStatus.ts @@ -0,0 +1,27 @@ +import { + type GetCallsStatusErrorType as viem_GetCallsStatusErrorType, + type GetCallsStatusParameters as viem_GetCallsStatusParameters, + type GetCallsStatusReturnType as viem_GetCallsStatusReturnType, + getCallsStatus as viem_getCallsStatus, +} from 'viem/experimental' + +import { getConnectorClient } from '../../actions/getConnectorClient.js' +import { type Config } from '../../createConfig.js' +import type { ConnectorParameter } from '../../types/properties.js' + +export type GetCallsStatusParameters = viem_GetCallsStatusParameters & + ConnectorParameter + +export type GetCallsStatusReturnType = viem_GetCallsStatusReturnType + +export type GetCallsStatusErrorType = viem_GetCallsStatusErrorType + +/** https://wagmi.sh/core/api/actions/getCallsStatus */ +export async function getCallsStatus( + config: config, + parameters: GetCallsStatusParameters, +): Promise { + const { connector, id } = parameters + const client = await getConnectorClient(config, { connector }) + return viem_getCallsStatus(client, { id }) +} diff --git a/packages/core/src/experimental/actions/getCapabilities.test.ts b/packages/core/src/experimental/actions/getCapabilities.test.ts new file mode 100644 index 0000000000..2946e27a5c --- /dev/null +++ b/packages/core/src/experimental/actions/getCapabilities.test.ts @@ -0,0 +1,64 @@ +import { accounts, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { connect } from '../../actions/connect.js' +import { disconnect } from '../../actions/disconnect.js' +import { getCapabilities } from './getCapabilities.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + const capabilities = await getCapabilities(config) + expect(capabilities).toMatchInlineSnapshot(` + { + "8453": { + "paymasterService": { + "supported": true, + }, + "sessionKeys": { + "supported": true, + }, + }, + "84532": { + "paymasterService": { + "supported": true, + }, + }, + } + `) + await disconnect(config, { connector }) +}) + +test('args: account', async () => { + await connect(config, { connector }) + const capabilities = await getCapabilities(config, { + account: accounts[1], + }) + expect(capabilities).toMatchInlineSnapshot(` + { + "8453": { + "paymasterService": { + "supported": false, + }, + "sessionKeys": { + "supported": true, + }, + }, + "84532": { + "paymasterService": { + "supported": false, + }, + }, + } + `) + await disconnect(config, { connector }) +}) + +test('behavior: not connected', async () => { + await expect(getCapabilities(config)).rejects.toMatchInlineSnapshot(` + [ConnectorNotConnectedError: Connector not connected. + + Version: @wagmi/core@x.y.z] + `) +}) diff --git a/packages/core/src/experimental/actions/getCapabilities.ts b/packages/core/src/experimental/actions/getCapabilities.ts new file mode 100644 index 0000000000..b27c6ef745 --- /dev/null +++ b/packages/core/src/experimental/actions/getCapabilities.ts @@ -0,0 +1,28 @@ +import type { Account } from 'viem' +import { + type GetCapabilitiesErrorType as viem_GetCapabilitiesErrorType, + type GetCapabilitiesParameters as viem_GetCapabilitiesParameters, + type GetCapabilitiesReturnType as viem_GetCapabilitiesReturnType, + getCapabilities as viem_getCapabilities, +} from 'viem/experimental' + +import { getConnectorClient } from '../../actions/getConnectorClient.js' +import { type Config } from '../../createConfig.js' +import type { ConnectorParameter } from '../../types/properties.js' + +export type GetCapabilitiesParameters = + viem_GetCapabilitiesParameters & ConnectorParameter + +export type GetCapabilitiesReturnType = viem_GetCapabilitiesReturnType + +export type GetCapabilitiesErrorType = viem_GetCapabilitiesErrorType + +/** https://wagmi.sh/core/api/actions/getCapabilities */ +export async function getCapabilities( + config: config, + parameters: GetCapabilitiesParameters = {}, +): Promise { + const { account, connector } = parameters + const client = await getConnectorClient(config, { account, connector }) + return viem_getCapabilities(client as any, { account: account as Account }) +} diff --git a/packages/core/src/experimental/actions/sendCalls.test.ts b/packages/core/src/experimental/actions/sendCalls.test.ts new file mode 100644 index 0000000000..589bce6adf --- /dev/null +++ b/packages/core/src/experimental/actions/sendCalls.test.ts @@ -0,0 +1,88 @@ +import { accounts, config } from '@wagmi/test' +import { parseEther } from 'viem' +import { expect, test } from 'vitest' + +import { connect } from '../../actions/connect.js' +import { disconnect } from '../../actions/disconnect.js' +import { sendCalls } from './sendCalls.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + await expect( + sendCalls(config, { + calls: [ + { + data: '0xdeadbeef', + to: accounts[1], + value: parseEther('1'), + }, + { + to: accounts[2], + value: parseEther('2'), + }, + { + to: accounts[3], + value: parseEther('3'), + }, + ], + }), + ).resolves.toMatchInlineSnapshot( + '"0x5dedb5a4ff8968db37459b52b83cbdc92b01c9c709c9cff26e345ef5cf27f92e"', + ) + await disconnect(config, { connector }) +}) + +test('behavior: not connected', async () => { + await expect( + sendCalls(config, { + calls: [ + { + to: accounts[1], + value: parseEther('1'), + }, + { + to: accounts[2], + value: parseEther('2'), + }, + { + to: accounts[3], + value: parseEther('3'), + }, + ], + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + "Connector not connected. + + Version: @wagmi/core@x.y.z" + `) +}) + +test('behavior: account does not exist on connector', async () => { + await connect(config, { connector }) + await expect( + sendCalls(config, { + account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + calls: [ + { + to: accounts[1], + value: parseEther('1'), + }, + { + to: accounts[2], + value: parseEther('2'), + }, + { + to: accounts[3], + value: parseEther('3'), + }, + ], + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + "Account \\"0xA0Cf798816D4b9b9866b5330EEa46a18382f251e\\" not found for connector \\"Mock Connector\\". + + Version: @wagmi/core@x.y.z" + `) + await disconnect(config, { connector }) +}) diff --git a/packages/core/src/experimental/actions/sendCalls.ts b/packages/core/src/experimental/actions/sendCalls.ts new file mode 100644 index 0000000000..369300083e --- /dev/null +++ b/packages/core/src/experimental/actions/sendCalls.ts @@ -0,0 +1,68 @@ +import { type Account, type Chain } from 'viem' +import { + type SendCallsErrorType as viem_SendCallsErrorType, + type SendCallsParameters as viem_SendCallsParameters, + type SendCallsReturnType as viem_SendCallsReturnType, + sendCalls as viem_sendCalls, +} from 'viem/experimental' + +import { + type GetConnectorClientErrorType, + getConnectorClient, +} from '../../actions/getConnectorClient.js' +import { type Config } from '../../createConfig.js' +import type { BaseErrorType, ErrorType } from '../../errors/base.js' +import type { SelectChains } from '../../types/chain.js' +import type { + ChainIdParameter, + ConnectorParameter, +} from '../../types/properties.js' +import { type Evaluate } from '../../types/utils.js' + +export type SendCallsParameters< + config extends Config = Config, + chainId extends config['chains'][number]['id'] = config['chains'][number]['id'], + /// + chains extends readonly Chain[] = SelectChains, +> = { + [key in keyof chains]: Evaluate< + Omit, 'chain'> & + ChainIdParameter & + ConnectorParameter + > +}[number] + +export type SendCallsReturnType = viem_SendCallsReturnType + +export type SendCallsErrorType = + // getConnectorClient() + | GetConnectorClientErrorType + // base + | BaseErrorType + | ErrorType + // viem + | viem_SendCallsErrorType + +/** https://wagmi.sh/core/api/actions/sendCalls */ +export async function sendCalls< + config extends Config, + chainId extends config['chains'][number]['id'], +>( + config: config, + parameters: SendCallsParameters, +): Promise { + const { account, chainId, connector, calls, ...rest } = parameters + + const client = await getConnectorClient(config, { + account, + chainId, + connector, + }) + + return viem_sendCalls(client, { + ...(rest as any), + ...(account ? { account } : {}), + calls, + chain: chainId ? { id: chainId } : undefined, + }) +} diff --git a/packages/core/src/experimental/actions/showCallsStatus.test.ts b/packages/core/src/experimental/actions/showCallsStatus.test.ts new file mode 100644 index 0000000000..d397886e05 --- /dev/null +++ b/packages/core/src/experimental/actions/showCallsStatus.test.ts @@ -0,0 +1,36 @@ +import { accounts, config, testClient } from '@wagmi/test' +import { parseEther } from 'viem' +import { test } from 'vitest' + +import { connect } from '../../actions/connect.js' +import { disconnect } from '../../actions/disconnect.js' +import { sendCalls } from './sendCalls.js' +import { showCallsStatus } from './showCallsStatus.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + const id = await sendCalls(config, { + calls: [ + { + data: '0xdeadbeef', + to: accounts[1], + value: parseEther('1'), + }, + { + to: accounts[2], + value: parseEther('2'), + }, + { + to: accounts[3], + value: parseEther('3'), + }, + ], + }) + await testClient.mainnet.mine({ blocks: 1 }) + await showCallsStatus(config, { + id, + }) + await disconnect(config, { connector }) +}) diff --git a/packages/core/src/experimental/actions/showCallsStatus.ts b/packages/core/src/experimental/actions/showCallsStatus.ts new file mode 100644 index 0000000000..23be4f4ebd --- /dev/null +++ b/packages/core/src/experimental/actions/showCallsStatus.ts @@ -0,0 +1,27 @@ +import { + type ShowCallsStatusErrorType as viem_ShowCallsStatusErrorType, + type ShowCallsStatusParameters as viem_ShowCallsStatusParameters, + type ShowCallsStatusReturnType as viem_ShowCallsStatusReturnType, + showCallsStatus as viem_showCallsStatus, +} from 'viem/experimental' + +import { getConnectorClient } from '../../actions/getConnectorClient.js' +import { type Config } from '../../createConfig.js' +import type { ConnectorParameter } from '../../types/properties.js' + +export type ShowCallsStatusParameters = viem_ShowCallsStatusParameters & + ConnectorParameter + +export type ShowCallsStatusReturnType = viem_ShowCallsStatusReturnType + +export type ShowCallsStatusErrorType = viem_ShowCallsStatusErrorType + +/** https://wagmi.sh/core/api/actions/showCallsStatus */ +export async function showCallsStatus( + config: config, + parameters: ShowCallsStatusParameters, +): Promise { + const { connector, id } = parameters + const client = await getConnectorClient(config, { connector }) + return viem_showCallsStatus(client, { id }) +} diff --git a/packages/core/src/experimental/actions/writeContracts.test.ts b/packages/core/src/experimental/actions/writeContracts.test.ts new file mode 100644 index 0000000000..42df43c452 --- /dev/null +++ b/packages/core/src/experimental/actions/writeContracts.test.ts @@ -0,0 +1,95 @@ +import { abi, address, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { connect } from '../../actions/connect.js' +import { disconnect } from '../../actions/disconnect.js' +import { writeContracts } from './writeContracts.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + await expect( + writeContracts(config, { + contracts: [ + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + ], + }), + ).resolves.toMatchInlineSnapshot( + '"0x8913636bd97cf4bcc0a6343c730905a27ead0f7480ff82190072e916439eb212"', + ) + await disconnect(config, { connector }) +}) + +test('behavior: not connected', async () => { + await expect( + writeContracts(config, { + contracts: [ + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + ], + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + "Connector not connected. + + Version: @wagmi/core@x.y.z" +`) +}) + +test('behavior: account does not exist on connector', async () => { + await connect(config, { connector }) + await expect( + writeContracts(config, { + account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + contracts: [ + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + ], + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + "Account \\"0xA0Cf798816D4b9b9866b5330EEa46a18382f251e\\" not found for connector \\"Mock Connector\\". + + Version: @wagmi/core@x.y.z" + `) + await disconnect(config, { connector }) +}) diff --git a/packages/core/src/experimental/actions/writeContracts.ts b/packages/core/src/experimental/actions/writeContracts.ts new file mode 100644 index 0000000000..f1c63e1ba3 --- /dev/null +++ b/packages/core/src/experimental/actions/writeContracts.ts @@ -0,0 +1,77 @@ +import { type Account, type Chain, type ContractFunctionParameters } from 'viem' +import { + type WriteContractsErrorType as viem_WriteContractsErrorType, + type WriteContractsParameters as viem_WriteContractsParameters, + type WriteContractsReturnType as viem_WriteContractsReturnType, + writeContracts as viem_writeContracts, +} from 'viem/experimental' + +import { + type GetConnectorClientErrorType, + getConnectorClient, +} from '../../actions/getConnectorClient.js' +import { type Config } from '../../createConfig.js' +import type { BaseErrorType, ErrorType } from '../../errors/base.js' +import type { SelectChains } from '../../types/chain.js' +import type { + ChainIdParameter, + ConnectorParameter, +} from '../../types/properties.js' +import { type Evaluate } from '../../types/utils.js' + +export type WriteContractsParameters< + contracts extends readonly unknown[] = readonly ContractFunctionParameters[], + config extends Config = Config, + chainId extends config['chains'][number]['id'] = config['chains'][number]['id'], + /// + chains extends readonly Chain[] = SelectChains, +> = { + [key in keyof chains]: Evaluate< + Omit< + viem_WriteContractsParameters< + contracts, + chains[key], + Account, + chains[key] + >, + 'chain' + > & + ChainIdParameter & + ConnectorParameter + > +}[number] + +export type WriteContractsReturnType = viem_WriteContractsReturnType + +export type WriteContractsErrorType = + // getConnectorClient() + | GetConnectorClientErrorType + // base + | BaseErrorType + | ErrorType + // viem + | viem_WriteContractsErrorType + +/** https://wagmi.sh/core/api/actions/writeContracts */ +export async function writeContracts< + const contracts extends readonly unknown[], + config extends Config, + chainId extends config['chains'][number]['id'], +>( + config: config, + parameters: WriteContractsParameters, +): Promise { + const { account, chainId, connector, ...rest } = parameters + + const client = await getConnectorClient(config, { + account, + chainId, + connector, + }) + + return viem_writeContracts(client, { + ...(rest as any), + ...(account ? { account } : {}), + chain: chainId ? { id: chainId } : undefined, + }) +} diff --git a/packages/core/src/experimental/query/getCallsStatus.test.ts b/packages/core/src/experimental/query/getCallsStatus.test.ts new file mode 100644 index 0000000000..fe834ecc79 --- /dev/null +++ b/packages/core/src/experimental/query/getCallsStatus.test.ts @@ -0,0 +1,23 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getCallsStatusQueryOptions } from './getCallsStatus.js' + +test('default', () => { + expect( + getCallsStatusQueryOptions(config, { + id: '0x0000000000000000000000000000000000000000', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "callsStatus", + { + "id": "0x0000000000000000000000000000000000000000", + }, + ], + "retry": [Function], + } + `) +}) diff --git a/packages/core/src/experimental/query/getCallsStatus.ts b/packages/core/src/experimental/query/getCallsStatus.ts new file mode 100644 index 0000000000..1f8619b4ba --- /dev/null +++ b/packages/core/src/experimental/query/getCallsStatus.ts @@ -0,0 +1,50 @@ +import { type QueryOptions } from '@tanstack/query-core' + +import { type Config } from '../../createConfig.js' +import { ConnectorNotConnectedError } from '../../errors/config.js' +import { filterQueryOptions } from '../../query/utils.js' +import type { ScopeKeyParameter } from '../../types/properties.js' +import type { Evaluate } from '../../types/utils.js' +import { + type GetCallsStatusErrorType, + type GetCallsStatusParameters, + type GetCallsStatusReturnType, + getCallsStatus, +} from '../actions/getCallsStatus.js' + +export type GetCallsStatusOptions = Evaluate< + GetCallsStatusParameters & ScopeKeyParameter +> + +export function getCallsStatusQueryOptions( + config: config, + options: GetCallsStatusOptions, +) { + return { + async queryFn({ queryKey }) { + const { scopeKey: _, ...parameters } = queryKey[1] + const status = await getCallsStatus(config, parameters) + return status + }, + queryKey: getCallsStatusQueryKey(options), + retry(failureCount, error) { + if (error instanceof ConnectorNotConnectedError) return false + return failureCount < 3 + }, + } as const satisfies QueryOptions< + GetCallsStatusQueryFnData, + GetCallsStatusErrorType, + GetCallsStatusData, + GetCallsStatusQueryKey + > +} + +export type GetCallsStatusQueryFnData = GetCallsStatusReturnType + +export type GetCallsStatusData = GetCallsStatusQueryFnData + +export function getCallsStatusQueryKey(options: GetCallsStatusOptions) { + return ['callsStatus', filterQueryOptions(options)] as const +} + +export type GetCallsStatusQueryKey = ReturnType diff --git a/packages/core/src/experimental/query/getCapabilities.test.ts b/packages/core/src/experimental/query/getCapabilities.test.ts new file mode 100644 index 0000000000..942eb37e04 --- /dev/null +++ b/packages/core/src/experimental/query/getCapabilities.test.ts @@ -0,0 +1,36 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getCapabilitiesQueryOptions } from './getCapabilities.js' + +test('default', () => { + expect(getCapabilitiesQueryOptions(config)).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "capabilities", + {}, + ], + "retry": [Function], + } + `) +}) + +test('parameters: chainId', () => { + expect( + getCapabilitiesQueryOptions(config, { + account: '0x0000000000000000000000000000000000000000', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "capabilities", + { + "account": "0x0000000000000000000000000000000000000000", + }, + ], + "retry": [Function], + } + `) +}) diff --git a/packages/core/src/experimental/query/getCapabilities.ts b/packages/core/src/experimental/query/getCapabilities.ts new file mode 100644 index 0000000000..24946e65a0 --- /dev/null +++ b/packages/core/src/experimental/query/getCapabilities.ts @@ -0,0 +1,50 @@ +import { type QueryOptions } from '@tanstack/query-core' + +import { type Config } from '../../createConfig.js' +import { ConnectorNotConnectedError } from '../../errors/config.js' +import { filterQueryOptions } from '../../query/utils.js' +import type { ScopeKeyParameter } from '../../types/properties.js' +import type { Evaluate, ExactPartial } from '../../types/utils.js' +import { + type GetCapabilitiesErrorType, + type GetCapabilitiesParameters, + type GetCapabilitiesReturnType, + getCapabilities, +} from '../actions/getCapabilities.js' + +export type GetCapabilitiesOptions = Evaluate< + ExactPartial & ScopeKeyParameter +> + +export function getCapabilitiesQueryOptions( + config: config, + options: GetCapabilitiesOptions = {}, +) { + return { + async queryFn({ queryKey }) { + const { scopeKey: _, ...parameters } = queryKey[1] + const capabilities = await getCapabilities(config, parameters) + return capabilities + }, + queryKey: getCapabilitiesQueryKey(options), + retry(failureCount, error) { + if (error instanceof ConnectorNotConnectedError) return false + return failureCount < 3 + }, + } as const satisfies QueryOptions< + GetCapabilitiesQueryFnData, + GetCapabilitiesErrorType, + GetCapabilitiesData, + GetCapabilitiesQueryKey + > +} + +export type GetCapabilitiesQueryFnData = GetCapabilitiesReturnType + +export type GetCapabilitiesData = GetCapabilitiesQueryFnData + +export function getCapabilitiesQueryKey(options: GetCapabilitiesOptions = {}) { + return ['capabilities', filterQueryOptions(options)] as const +} + +export type GetCapabilitiesQueryKey = ReturnType diff --git a/packages/core/src/experimental/query/sendCalls.test.ts b/packages/core/src/experimental/query/sendCalls.test.ts new file mode 100644 index 0000000000..9892aa43e3 --- /dev/null +++ b/packages/core/src/experimental/query/sendCalls.test.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { sendCallsMutationOptions } from './sendCalls.js' + +test('default', () => { + expect(sendCallsMutationOptions(config)).toMatchInlineSnapshot(` + { + "mutationFn": [Function], + "mutationKey": [ + "sendCalls", + ], + } + `) +}) diff --git a/packages/core/src/experimental/query/sendCalls.ts b/packages/core/src/experimental/query/sendCalls.ts new file mode 100644 index 0000000000..7949c3b3e7 --- /dev/null +++ b/packages/core/src/experimental/query/sendCalls.ts @@ -0,0 +1,64 @@ +import type { MutateOptions, MutationOptions } from '@tanstack/query-core' + +import { type Config } from '../../createConfig.js' +import { type Evaluate } from '../../types/utils.js' +import { + type SendCallsErrorType, + type SendCallsParameters, + type SendCallsReturnType, + sendCalls, +} from '../actions/sendCalls.js' + +export function sendCallsMutationOptions( + config: config, +) { + return { + mutationFn(variables) { + return sendCalls(config, variables) + }, + mutationKey: ['sendCalls'], + } as const satisfies MutationOptions< + SendCallsData, + SendCallsErrorType, + SendCallsVariables + > +} + +export type SendCallsData = Evaluate + +export type SendCallsVariables< + config extends Config, + chainId extends config['chains'][number]['id'], +> = SendCallsParameters + +export type SendCallsMutate = < + chainId extends config['chains'][number]['id'], +>( + variables: SendCallsVariables, + options?: + | Evaluate< + MutateOptions< + SendCallsData, + SendCallsErrorType, + Evaluate>, + context + > + > + | undefined, +) => void + +export type SendCallsMutateAsync = < + chainId extends config['chains'][number]['id'], +>( + variables: SendCallsVariables, + options?: + | Evaluate< + MutateOptions< + SendCallsData, + SendCallsErrorType, + Evaluate>, + context + > + > + | undefined, +) => Promise diff --git a/packages/core/src/experimental/query/showCallsStatus.test.ts b/packages/core/src/experimental/query/showCallsStatus.test.ts new file mode 100644 index 0000000000..ad26ab6171 --- /dev/null +++ b/packages/core/src/experimental/query/showCallsStatus.test.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { showCallsStatusMutationOptions } from './showCallsStatus.js' + +test('default', () => { + expect(showCallsStatusMutationOptions(config)).toMatchInlineSnapshot(` + { + "mutationFn": [Function], + "mutationKey": [ + "showCallsStatus", + ], + } + `) +}) diff --git a/packages/core/src/experimental/query/showCallsStatus.ts b/packages/core/src/experimental/query/showCallsStatus.ts new file mode 100644 index 0000000000..4cc704a910 --- /dev/null +++ b/packages/core/src/experimental/query/showCallsStatus.ts @@ -0,0 +1,57 @@ +import type { MutateOptions, MutationOptions } from '@tanstack/query-core' + +import { type Config } from '../../createConfig.js' +import { type Evaluate } from '../../types/utils.js' +import { + type ShowCallsStatusErrorType, + type ShowCallsStatusParameters, + type ShowCallsStatusReturnType, + showCallsStatus, +} from '../actions/showCallsStatus.js' + +export function showCallsStatusMutationOptions( + config: config, +) { + return { + mutationFn(variables) { + return showCallsStatus(config, variables) + }, + mutationKey: ['showCallsStatus'], + } as const satisfies MutationOptions< + ShowCallsStatusData, + ShowCallsStatusErrorType, + ShowCallsStatusVariables + > +} + +export type ShowCallsStatusData = Evaluate + +export type ShowCallsStatusVariables = ShowCallsStatusParameters + +export type ShowCallsStatusMutate = ( + variables: ShowCallsStatusVariables, + options?: + | Evaluate< + MutateOptions< + ShowCallsStatusData, + ShowCallsStatusErrorType, + Evaluate, + context + > + > + | undefined, +) => void + +export type ShowCallsStatusMutateAsync = ( + variables: ShowCallsStatusVariables, + options?: + | Evaluate< + MutateOptions< + ShowCallsStatusData, + ShowCallsStatusErrorType, + Evaluate, + context + > + > + | undefined, +) => Promise diff --git a/packages/core/src/experimental/query/writeContracts.test.ts b/packages/core/src/experimental/query/writeContracts.test.ts new file mode 100644 index 0000000000..5f4a1f28dd --- /dev/null +++ b/packages/core/src/experimental/query/writeContracts.test.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { writeContractsMutationOptions } from './writeContracts.js' + +test('default', () => { + expect(writeContractsMutationOptions(config)).toMatchInlineSnapshot(` + { + "mutationFn": [Function], + "mutationKey": [ + "writeContracts", + ], + } + `) +}) diff --git a/packages/core/src/experimental/query/writeContracts.ts b/packages/core/src/experimental/query/writeContracts.ts new file mode 100644 index 0000000000..dfc9187ff8 --- /dev/null +++ b/packages/core/src/experimental/query/writeContracts.ts @@ -0,0 +1,70 @@ +import type { MutateOptions, MutationOptions } from '@tanstack/query-core' + +import { type Config } from '../../createConfig.js' +import { type Evaluate } from '../../types/utils.js' +import { + type WriteContractsErrorType, + type WriteContractsParameters, + type WriteContractsReturnType, + writeContracts, +} from '../actions/writeContracts.js' + +export function writeContractsMutationOptions< + const contracts extends readonly unknown[], + config extends Config, +>(config: config) { + return { + mutationFn(variables) { + return writeContracts(config, variables as any) as any + }, + mutationKey: ['writeContracts'], + } as const satisfies MutationOptions< + WriteContractsData, + WriteContractsErrorType, + WriteContractsVariables + > +} + +export type WriteContractsData = Evaluate + +export type WriteContractsVariables< + contracts extends readonly unknown[], + config extends Config, + chainId extends config['chains'][number]['id'], +> = WriteContractsParameters + +export type WriteContractsMutate< + contracts extends readonly unknown[], + config extends Config, + context = unknown, +> = ( + variables: WriteContractsVariables, + options?: + | Evaluate< + MutateOptions< + WriteContractsData, + WriteContractsErrorType, + Evaluate>, + context + > + > + | undefined, +) => void + +export type WriteContractsMutateAsync< + contracts extends readonly unknown[], + config extends Config, + context = unknown, +> = ( + variables: WriteContractsVariables, + options?: + | Evaluate< + MutateOptions< + WriteContractsData, + WriteContractsErrorType, + Evaluate>, + context + > + > + | undefined, +) => Promise diff --git a/packages/core/src/exports/experimental.ts b/packages/core/src/exports/experimental.ts new file mode 100644 index 0000000000..b552083378 --- /dev/null +++ b/packages/core/src/exports/experimental.ts @@ -0,0 +1,84 @@ +//////////////////////////////////////////////////////////////////////////////// +// Actions +//////////////////////////////////////////////////////////////////////////////// + +export { + type GetCallsStatusErrorType, + type GetCallsStatusParameters, + type GetCallsStatusReturnType, + getCallsStatus, +} from '../experimental/actions/getCallsStatus.js' + +export { + type GetCapabilitiesErrorType, + type GetCapabilitiesParameters, + type GetCapabilitiesReturnType, + getCapabilities, +} from '../experimental/actions/getCapabilities.js' + +export { + type SendCallsErrorType, + type SendCallsParameters, + type SendCallsReturnType, + sendCalls, +} from '../experimental/actions/sendCalls.js' + +export { + type ShowCallsStatusErrorType, + type ShowCallsStatusParameters, + type ShowCallsStatusReturnType, + showCallsStatus, +} from '../experimental/actions/showCallsStatus.js' + +export { + type WriteContractsErrorType, + type WriteContractsParameters, + type WriteContractsReturnType, + writeContracts, +} from '../experimental/actions/writeContracts.js' + +//////////////////////////////////////////////////////////////////////////////// +// Tanstack Query +//////////////////////////////////////////////////////////////////////////////// + +export { + type GetCallsStatusData, + type GetCallsStatusOptions, + type GetCallsStatusQueryFnData, + type GetCallsStatusQueryKey, + getCallsStatusQueryOptions, + getCallsStatusQueryKey, +} from '../experimental/query/getCallsStatus.js' + +export { + type GetCapabilitiesData, + type GetCapabilitiesOptions, + type GetCapabilitiesQueryFnData, + type GetCapabilitiesQueryKey, + getCapabilitiesQueryOptions, + getCapabilitiesQueryKey, +} from '../experimental/query/getCapabilities.js' + +export { + type SendCallsData, + type SendCallsMutate, + type SendCallsMutateAsync, + type SendCallsVariables, + sendCallsMutationOptions, +} from '../experimental/query/sendCalls.js' + +export { + type ShowCallsStatusData, + type ShowCallsStatusMutate, + type ShowCallsStatusMutateAsync, + type ShowCallsStatusVariables, + showCallsStatusMutationOptions, +} from '../experimental/query/showCallsStatus.js' + +export { + type WriteContractsData, + type WriteContractsMutate, + type WriteContractsMutateAsync, + type WriteContractsVariables, + writeContractsMutationOptions, +} from '../experimental/query/writeContracts.js' diff --git a/packages/core/src/query/prepareTransactionRequest.test.ts b/packages/core/src/query/prepareTransactionRequest.test.ts index 2bb0e23af1..6d81391c4f 100644 --- a/packages/core/src/query/prepareTransactionRequest.test.ts +++ b/packages/core/src/query/prepareTransactionRequest.test.ts @@ -44,6 +44,7 @@ test('parameters: account', () => { { "account": { "address": "0x14791697260E4c9A71f18484C9f997B308e59325", + "experimental_signAuthMessage": [Function], "publicKey": "0x046655feed4d214c261e0a6b554395596f1f1476a77d999560e5a8df9b8a1a3515217e88dd05e938efdd71b2cce322bf01da96cd42087b236e8f5043157a9c068e", "signMessage": [Function], "signTransaction": [Function], diff --git a/packages/react/package.json b/packages/react/package.json index 8156802f72..226d9e2cb5 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -11,7 +11,7 @@ "scripts": { "build": "pnpm run clean && pnpm run build:esm+types", "build:esm+types": "tsc --project tsconfig.build.json --outDir ./dist/esm --declaration --declarationMap --declarationDir ./dist/types", - "clean": "rm -rf dist tsconfig.tsbuildinfo actions chains codegen connectors query", + "clean": "rm -rf dist tsconfig.tsbuildinfo actions chains codegen connectors experimental query", "test:build": "publint --strict", "typecheck": "tsc --noEmit" }, @@ -25,6 +25,7 @@ "/chains", "/codegen", "/connectors", + "/experimental", "/query" ], "sideEffects": false, @@ -53,6 +54,10 @@ "types": "./dist/types/exports/connectors.d.ts", "default": "./dist/esm/exports/connectors.js" }, + "./experimental": { + "types": "./dist/types/exports/experimental.d.ts", + "default": "./dist/esm/exports/experimental.js" + }, "./query": { "types": "./dist/types/exports/query.d.ts", "default": "./dist/esm/exports/query.js" @@ -61,21 +66,12 @@ }, "typesVersions": { "*": { - "actions": [ - "./dist/types/exports/actions.d.ts" - ], - "chains": [ - "./dist/types/exports/chains.d.ts" - ], - "codegen": [ - "./dist/types/exports/codegen.d.ts" - ], - "connectors": [ - "./dist/types/exports/connectors.d.ts" - ], - "query": [ - "./dist/types/exports/query.d.ts" - ] + "actions": ["./dist/types/exports/actions.d.ts"], + "chains": ["./dist/types/exports/chains.d.ts"], + "codegen": ["./dist/types/exports/codegen.d.ts"], + "connectors": ["./dist/types/exports/connectors.d.ts"], + "experimental": ["./dist/types/exports/experimental.d.ts"], + "query": ["./dist/types/exports/query.d.ts"] } }, "peerDependencies": { @@ -103,10 +99,7 @@ "react": ">=18", "react-dom": ">=18" }, - "contributors": [ - "awkweb.eth ", - "jxom.eth " - ], + "contributors": ["awkweb.eth ", "jxom.eth "], "funding": "https://github.com/sponsors/wevm", "keywords": [ "wagmi", @@ -118,4 +111,4 @@ "wallet", "web3" ] -} \ No newline at end of file +} diff --git a/packages/react/src/experimental/hooks/useCallsStatus.test.ts b/packages/react/src/experimental/hooks/useCallsStatus.test.ts new file mode 100644 index 0000000000..98c202ee77 --- /dev/null +++ b/packages/react/src/experimental/hooks/useCallsStatus.test.ts @@ -0,0 +1,92 @@ +import { connect, disconnect } from '@wagmi/core' +import { accounts, config, testClient } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { parseEther } from 'viem' +import { expect, test } from 'vitest' + +import { useCallsStatus } from './useCallsStatus.js' +import { useSendCalls } from './useSendCalls.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => useSendCalls()) + + result.current.sendCalls({ + calls: [ + { + data: '0xdeadbeef', + to: accounts[1], + value: parseEther('1'), + }, + { + to: accounts[2], + value: parseEther('2'), + }, + { + to: accounts[3], + value: parseEther('3'), + }, + ], + }) + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { result: result_2 } = renderHook(() => + useCallsStatus({ id: result.current.data! }), + ) + await waitFor(() => expect(result_2.current.isSuccess).toBeTruthy()) + + expect(result_2.current.data).toMatchInlineSnapshot( + ` + { + "receipts": [], + "status": "PENDING", + } + `, + ) + + await testClient.mainnet.mine({ blocks: 1 }) + + const { result: result_3 } = renderHook(() => + useCallsStatus({ id: result.current.data! }), + ) + await waitFor(() => expect(result_3.current.isSuccess).toBeTruthy()) + + expect(result_3.current.data).toMatchInlineSnapshot( + ` + { + "receipts": [ + { + "blockHash": "0x759f30616acde00a66d2bd83cc0bd011662ff0ca945d599554818fa0f9fdd0d2", + "blockNumber": 19258214n, + "gasUsed": 21064n, + "logs": [], + "status": "success", + "transactionHash": "0xa81a893ca5596aa105fc10206b9a8bd63df8bd526d8c0594496330f956515b4b", + }, + { + "blockHash": "0x759f30616acde00a66d2bd83cc0bd011662ff0ca945d599554818fa0f9fdd0d2", + "blockNumber": 19258214n, + "gasUsed": 21000n, + "logs": [], + "status": "success", + "transactionHash": "0x63624bbfc73f8bd7629ecb4e00ba521a9967ea9e9098ac17ae5bf500c904da1a", + }, + { + "blockHash": "0x759f30616acde00a66d2bd83cc0bd011662ff0ca945d599554818fa0f9fdd0d2", + "blockNumber": 19258214n, + "gasUsed": 21000n, + "logs": [], + "status": "success", + "transactionHash": "0xc3df1b2f2c76723846a964c0c4d6d11ba423df036e0e636d298416e0f94bda29", + }, + ], + "status": "CONFIRMED", + } + `, + ) + + await disconnect(config, { connector }) +}) diff --git a/packages/react/src/experimental/hooks/useCallsStatus.ts b/packages/react/src/experimental/hooks/useCallsStatus.ts new file mode 100644 index 0000000000..f9ce545753 --- /dev/null +++ b/packages/react/src/experimental/hooks/useCallsStatus.ts @@ -0,0 +1,49 @@ +'use client' + +import { type Config, type ResolvedRegister } from '@wagmi/core' +import { + type GetCallsStatusData, + type GetCallsStatusErrorType, + type GetCallsStatusOptions, + type GetCallsStatusQueryFnData, + type GetCallsStatusQueryKey, + getCallsStatusQueryOptions, +} from '@wagmi/core/experimental' +import { type Evaluate } from '@wagmi/core/internal' + +import { useConfig } from '../../hooks/useConfig.js' +import type { ConfigParameter, QueryParameter } from '../../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../../utils/query.js' + +export type UseCallsStatusParameters< + config extends Config = Config, + selectData = GetCallsStatusData, +> = Evaluate< + GetCallsStatusOptions & + ConfigParameter & + QueryParameter< + GetCallsStatusQueryFnData, + GetCallsStatusErrorType, + selectData, + GetCallsStatusQueryKey + > +> + +export type UseCallsStatusReturnType = + UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useCallsStatus */ +export function useCallsStatus< + config extends Config = ResolvedRegister['config'], + selectData = GetCallsStatusData, +>( + parameters: UseCallsStatusParameters, +): UseCallsStatusReturnType { + const { query = {} } = parameters + + const config = useConfig(parameters) + + const options = getCallsStatusQueryOptions(config, parameters) + + return useQuery({ ...query, ...options }) +} diff --git a/packages/react/src/experimental/hooks/useCapabilities.test.ts b/packages/react/src/experimental/hooks/useCapabilities.test.ts new file mode 100644 index 0000000000..007b7172ce --- /dev/null +++ b/packages/react/src/experimental/hooks/useCapabilities.test.ts @@ -0,0 +1,171 @@ +import { connect, disconnect } from '@wagmi/core' +import { accounts, config } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useCapabilities } from './useCapabilities.js' + +const connector = config.connectors[0]! + +test('mounts', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => useCapabilities()) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": { + "8453": { + "paymasterService": { + "supported": true, + }, + "sessionKeys": { + "supported": true, + }, + }, + "84532": { + "paymasterService": { + "supported": true, + }, + }, + }, + "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": [ + "capabilities", + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + }, + ], + "refetch": [Function], + "status": "success", + } + `) + + await disconnect(config, { connector }) +}) + +test('args: account', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => useCapabilities({ account: accounts[1] })) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": { + "8453": { + "paymasterService": { + "supported": false, + }, + "sessionKeys": { + "supported": true, + }, + }, + "84532": { + "paymasterService": { + "supported": false, + }, + }, + }, + "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": [ + "capabilities", + { + "account": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + }, + ], + "refetch": [Function], + "status": "success", + } + `) + + await disconnect(config, { connector }) +}) + +test('behavior: not connected', async () => { + const { result } = renderHook(() => useCapabilities()) + + await waitFor(() => expect(result.current.isError).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": undefined, + "dataUpdatedAt": 0, + "error": [ConnectorNotConnectedError: Connector not connected. + + Version: @wagmi/core@2.8.1], + "errorUpdateCount": 2, + "errorUpdatedAt": 1675209600000, + "failureCount": 1, + "failureReason": [ConnectorNotConnectedError: Connector not connected. + + Version: @wagmi/core@2.8.1], + "fetchStatus": "idle", + "isError": true, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": true, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": false, + "queryKey": [ + "capabilities", + { + "account": undefined, + }, + ], + "refetch": [Function], + "status": "error", + } + `) +}) diff --git a/packages/react/src/experimental/hooks/useCapabilities.ts b/packages/react/src/experimental/hooks/useCapabilities.ts new file mode 100644 index 0000000000..2ee25935c7 --- /dev/null +++ b/packages/react/src/experimental/hooks/useCapabilities.ts @@ -0,0 +1,54 @@ +'use client' + +import { type Config, type ResolvedRegister } from '@wagmi/core' +import { + type GetCapabilitiesData, + type GetCapabilitiesErrorType, + type GetCapabilitiesOptions, + type GetCapabilitiesQueryFnData, + type GetCapabilitiesQueryKey, + getCapabilitiesQueryOptions, +} from '@wagmi/core/experimental' +import { type Evaluate } from '@wagmi/core/internal' + +import { useAccount } from '../../hooks/useAccount.js' +import { useConfig } from '../../hooks/useConfig.js' +import type { ConfigParameter, QueryParameter } from '../../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../../utils/query.js' + +export type UseCapabilitiesParameters< + config extends Config = Config, + selectData = GetCapabilitiesData, +> = Evaluate< + GetCapabilitiesOptions & + ConfigParameter & + QueryParameter< + GetCapabilitiesQueryFnData, + GetCapabilitiesErrorType, + selectData, + GetCapabilitiesQueryKey + > +> + +export type UseCapabilitiesReturnType = + UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useCapabilities */ +export function useCapabilities< + config extends Config = ResolvedRegister['config'], + selectData = GetCapabilitiesData, +>( + parameters: UseCapabilitiesParameters = {}, +): UseCapabilitiesReturnType { + const { account, query = {} } = parameters + + const { address } = useAccount() + const config = useConfig(parameters) + + const options = getCapabilitiesQueryOptions(config, { + ...parameters, + account: account ?? address, + }) + + return useQuery({ ...query, ...options }) +} diff --git a/packages/react/src/experimental/hooks/useSendCalls.test.ts b/packages/react/src/experimental/hooks/useSendCalls.test.ts new file mode 100644 index 0000000000..55771537dc --- /dev/null +++ b/packages/react/src/experimental/hooks/useSendCalls.test.ts @@ -0,0 +1,40 @@ +import { connect, disconnect } from '@wagmi/core' +import { accounts, config } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { parseEther } from 'viem' +import { expect, test } from 'vitest' + +import { useSendCalls } from './useSendCalls.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => useSendCalls()) + + result.current.sendCalls({ + calls: [ + { + data: '0xdeadbeef', + to: accounts[1], + value: parseEther('1'), + }, + { + to: accounts[2], + value: parseEther('2'), + }, + { + to: accounts[3], + value: parseEther('3'), + }, + ], + }) + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current.data).toMatchInlineSnapshot( + '"0x5dedb5a4ff8968db37459b52b83cbdc92b01c9c709c9cff26e345ef5cf27f92e"', + ) + + await disconnect(config, { connector }) +}) diff --git a/packages/react/src/experimental/hooks/useSendCalls.ts b/packages/react/src/experimental/hooks/useSendCalls.ts new file mode 100644 index 0000000000..1953aea3cd --- /dev/null +++ b/packages/react/src/experimental/hooks/useSendCalls.ts @@ -0,0 +1,76 @@ +'use client' + +import { useMutation } from '@tanstack/react-query' +import type { Config, ResolvedRegister } from '@wagmi/core' +import { + type SendCallsData, + type SendCallsErrorType, + type SendCallsMutate, + type SendCallsMutateAsync, + type SendCallsVariables, + sendCallsMutationOptions, +} from '@wagmi/core/experimental' +import type { Evaluate } from '@wagmi/core/internal' + +import { useConfig } from '../../hooks/useConfig.js' +import type { ConfigParameter } from '../../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../../utils/query.js' + +export type UseSendCallsParameters< + config extends Config = Config, + context = unknown, +> = Evaluate< + ConfigParameter & { + mutation?: + | UseMutationParameters< + SendCallsData, + SendCallsErrorType, + SendCallsVariables, + context + > + | undefined + } +> + +export type UseSendCallsReturnType< + config extends Config = Config, + context = unknown, +> = Evaluate< + UseMutationReturnType< + SendCallsData, + SendCallsErrorType, + SendCallsVariables, + context + > & { + sendCalls: SendCallsMutate + sendCallsAsync: SendCallsMutateAsync + } +> + +/** https://wagmi.sh/react/api/hooks/useSendCalls */ +export function useSendCalls< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseSendCallsParameters = {}, +): UseSendCallsReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = sendCallsMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + type Return = UseSendCallsReturnType + return { + ...result, + sendCalls: mutate as Return['sendCalls'], + sendCallsAsync: mutateAsync as Return['sendCallsAsync'], + } +} diff --git a/packages/react/src/experimental/hooks/useShowCallsStatus.ts b/packages/react/src/experimental/hooks/useShowCallsStatus.ts new file mode 100644 index 0000000000..774f3840bb --- /dev/null +++ b/packages/react/src/experimental/hooks/useShowCallsStatus.ts @@ -0,0 +1,73 @@ +'use client' + +import { useMutation } from '@tanstack/react-query' +import type { Config, ResolvedRegister } from '@wagmi/core' +import { + type ShowCallsStatusData, + type ShowCallsStatusErrorType, + type ShowCallsStatusMutate, + type ShowCallsStatusMutateAsync, + type ShowCallsStatusVariables, + showCallsStatusMutationOptions, +} from '@wagmi/core/experimental' +import type { Evaluate } from '@wagmi/core/internal' + +import { useConfig } from '../../hooks/useConfig.js' +import type { ConfigParameter } from '../../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../../utils/query.js' + +export type UseShowCallsStatusParameters< + config extends Config = Config, + context = unknown, +> = Evaluate< + ConfigParameter & { + mutation?: + | UseMutationParameters< + ShowCallsStatusData, + ShowCallsStatusErrorType, + ShowCallsStatusVariables, + context + > + | undefined + } +> + +export type UseShowCallsStatusReturnType = Evaluate< + UseMutationReturnType< + ShowCallsStatusData, + ShowCallsStatusErrorType, + ShowCallsStatusVariables, + context + > & { + showCallsStatus: ShowCallsStatusMutate + showCallsStatusAsync: ShowCallsStatusMutateAsync + } +> + +/** https://wagmi.sh/react/api/hooks/useShowCallsStatus */ +export function useShowCallsStatus< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseShowCallsStatusParameters = {}, +): UseShowCallsStatusReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = showCallsStatusMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + type Return = UseShowCallsStatusReturnType + return { + ...result, + showCallsStatus: mutate as Return['showCallsStatus'], + showCallsStatusAsync: mutateAsync as Return['showCallsStatusAsync'], + } +} diff --git a/packages/react/src/experimental/hooks/useWriteContracts.test.ts b/packages/react/src/experimental/hooks/useWriteContracts.test.ts new file mode 100644 index 0000000000..1522bfb5cb --- /dev/null +++ b/packages/react/src/experimental/hooks/useWriteContracts.test.ts @@ -0,0 +1,41 @@ +import { connect, disconnect } from '@wagmi/core' +import { abi, address, config } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useWriteContracts } from './useWriteContracts.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => useWriteContracts()) + + result.current.writeContracts({ + contracts: [ + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + ], + }) + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current.data).toMatchInlineSnapshot( + '"0x8913636bd97cf4bcc0a6343c730905a27ead0f7480ff82190072e916439eb212"', + ) + + await disconnect(config, { connector }) +}) diff --git a/packages/react/src/experimental/hooks/useWriteContracts.ts b/packages/react/src/experimental/hooks/useWriteContracts.ts new file mode 100644 index 0000000000..d64dc5875b --- /dev/null +++ b/packages/react/src/experimental/hooks/useWriteContracts.ts @@ -0,0 +1,84 @@ +'use client' + +import { useMutation } from '@tanstack/react-query' +import type { Config, ResolvedRegister } from '@wagmi/core' +import { + type WriteContractsData, + type WriteContractsErrorType, + type WriteContractsMutate, + type WriteContractsMutateAsync, + type WriteContractsVariables, + writeContractsMutationOptions, +} from '@wagmi/core/experimental' +import type { Evaluate } from '@wagmi/core/internal' +import type { ContractFunctionParameters } from 'viem' + +import { useConfig } from '../../hooks/useConfig.js' +import type { ConfigParameter } from '../../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../../utils/query.js' + +export type UseWriteContractsParameters< + contracts extends readonly unknown[] = readonly ContractFunctionParameters[], + config extends Config = Config, + context = unknown, +> = Evaluate< + ConfigParameter & { + mutation?: + | UseMutationParameters< + WriteContractsData, + WriteContractsErrorType, + WriteContractsVariables< + contracts, + config, + config['chains'][number]['id'] + >, + context + > + | undefined + } +> + +export type UseWriteContractsReturnType< + contracts extends readonly unknown[] = readonly ContractFunctionParameters[], + config extends Config = Config, + context = unknown, +> = Evaluate< + UseMutationReturnType< + WriteContractsData, + WriteContractsErrorType, + WriteContractsVariables, + context + > & { + writeContracts: WriteContractsMutate + writeContractsAsync: WriteContractsMutateAsync + } +> + +/** https://wagmi.sh/react/api/hooks/useWriteContracts */ +export function useWriteContracts< + const contracts extends readonly unknown[] = readonly ContractFunctionParameters[], + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseWriteContractsParameters = {}, +): UseWriteContractsReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = writeContractsMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + type Return = UseWriteContractsReturnType + return { + ...result, + writeContracts: mutate as Return['writeContracts'], + writeContractsAsync: mutateAsync as Return['writeContractsAsync'], + } +} diff --git a/packages/react/src/exports/experimental.ts b/packages/react/src/exports/experimental.ts new file mode 100644 index 0000000000..2051235bcd --- /dev/null +++ b/packages/react/src/exports/experimental.ts @@ -0,0 +1,33 @@ +//////////////////////////////////////////////////////////////////////////////// +// Hooks +//////////////////////////////////////////////////////////////////////////////// + +export { + type UseCallsStatusParameters, + type UseCallsStatusReturnType, + useCallsStatus, +} from '../experimental/hooks/useCallsStatus.js' + +export { + type UseCapabilitiesParameters, + type UseCapabilitiesReturnType, + useCapabilities, +} from '../experimental/hooks/useCapabilities.js' + +export { + type UseSendCallsParameters, + type UseSendCallsReturnType, + useSendCalls, +} from '../experimental/hooks/useSendCalls.js' + +export { + type UseShowCallsStatusParameters, + type UseShowCallsStatusReturnType, + useShowCallsStatus, +} from '../experimental/hooks/useShowCallsStatus.js' + +export { + type UseWriteContractsParameters, + type UseWriteContractsReturnType, + useWriteContracts, +} from '../experimental/hooks/useWriteContracts.js' diff --git a/packages/test/src/utils.ts b/packages/test/src/utils.ts index 9b26c5750a..fe430fe911 100644 --- a/packages/test/src/utils.ts +++ b/packages/test/src/utils.ts @@ -1,4 +1,4 @@ -import { pool } from './constants.js' +const pool = Number(process.env.VITEST_POOL_ID ?? 1) export function getRpcUrls({ port }: { port: number }) { return { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 15a3e8d388..c8095ca1bb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -63,8 +63,8 @@ importers: specifier: 5.4.2 version: 5.4.2 viem: - specifier: 2.8.4 - version: 2.8.4(typescript@5.4.2) + specifier: 2.9.31 + version: 2.9.31(typescript@5.4.2) vitest: specifier: ^0.34.5 version: 0.34.5(@vitest/ui@0.34.5)(happy-dom@12.2.1)(jsdom@20.0.3) @@ -11494,8 +11494,8 @@ packages: - zod dev: false - /viem@2.8.4(typescript@5.4.2): - resolution: {integrity: sha512-zBC8+YNKzo+XeUsCyXdrFzimH9Ei/nRfUKldPmVRoR/lR56/sqkDPyfCE1yvzwwmA9AJ9m9m2HtSPgl9NiTReA==} + /viem@2.9.31(typescript@5.4.2): + resolution: {integrity: sha512-8aJ8Dm/591Czwb/nRayo0z8Ls5KxqC4QYE33fmHwhx2tDUWC/hHcPZqjLRSTWFtAfi0aZKvP7BeB6UZ3ZkTRhQ==} peerDependencies: typescript: '>=5.0.4' peerDependenciesMeta: