Skip to content
LogoLogo

wallet_sendCalls

Broadcast bundle of calls to the network.

Authentication Required: No

Request

await jaw.provider.request({
  method: 'wallet_sendCalls',
  params: [
    {
      calls: [
        {
          to: '0xContractA...',
          value: '0x0',
          data: '0x...',
        },
        {
          to: '0xContractB...',
          value: '0x0',
          data: '0x...',
        },
      ],
    },
  ],
});

Parameters

NameTypeRequiredDescription
version'1.0' | '2.0.0'NoEIP-5792 envelope version, defaults to '1.0'
from0x${string}NoAccount address to execute from
callsarrayYesArray of call objects (must be non-empty)
calls[].to0x${string}YesRecipient address
calls[].value0x${string}NoValue in wei (hex), default "0x0"
calls[].data0x${string}NoCall data (hex), default "0x"
chainId0x${string}NoTarget chain ID (defaults to connected chain)
atomicRequiredbooleanNoAlways satisfied — an ERC-4337 batch is one UserOperation
capabilitiesobjectNoAdditional capabilities (see below)

Envelope versions

Both EIP-5792 request envelopes are accepted and behave identically:

  • 2.0.0 — what viem's sendCalls() (and therefore wagmi) sends by default: hex chainId, atomicRequired, capabilities.
  • 1.0 — the original envelope. Also assumed when version is omitted.

Any other version is rejected with -32602 and a message naming the supported versions, so a dapp can tell a capability gap apart from a rejected batch. Stock viem/wagmi therefore works with no configuration:

import { useSendCalls } from 'wagmi';
 
// viem sends { version: '2.0.0', chainId: '0x66eee', atomicRequired: false, ... }
sendCalls({
  calls: [
    { to: '0xTokenA...', data: '0x...' },
    { to: '0xRouter...', data: '0x...' },
  ],
});

Capabilities

NameTypeDescription
capabilities.permissionsobjectPermission capability for delegated execution
capabilities.permissions.id0x${string}ID of the permission to use for execution
capabilities.paymasterServiceobjectPaymaster service for per-call gas sponsorship
capabilities.paymasterService.urlstringPaymaster service URL (ERC-7677 compliant)
capabilities.paymasterService.contextobjectOptional context (e.g., { sponsorshipPolicyId: '...' })

Response

Returns a batch call identifier.

Example

{
  "id": "0x123abc..."
}

Behavior

  • All calls succeed atomically or all fail together
  • Uses currently connected chain if chainId not specified
  • Returns batch ID immediately for status tracking
  • Background task monitors completion
  • Gas is sponsored by paymaster if configured
  • Users offered native ERC-20 paymaster option for gas fees (no configuration needed)

Errors

CodeDescription
4001User rejected the request
4100Unauthorized (not authenticated)
5700A requested capability isn't implemented and wasn't marked optional
5710The chain isn't configured (testnets need preference.showTestnets)
-32602Invalid params — unsupported version, empty calls, or a malformed field

Validation happens in the SDK before any wallet UI opens, so these errors reach the dapp immediately rather than after a dialog. viem maps 5700/5710 to typed errors (UnsupportedNonOptionalCapabilityError, UnsupportedChainIdError).

Example

Transfer Multiple ERC-20 Tokens

import { encodeFunctionData } from 'viem';
 
const transferAbi = [
  {
    name: 'transfer',
    type: 'function',
    inputs: [
      { name: 'to', type: 'address' },
      { name: 'amount', type: 'uint256' },
    ],
  },
];
 
const account = '0x1234...';
const recipient = '0x5678...';
 
// Encode both transfers
const usdcTransfer = encodeFunctionData({
  abi: transferAbi,
  functionName: 'transfer',
  args: [recipient, 1000000n],
});
 
const uniTransfer = encodeFunctionData({
  abi: transferAbi,
  functionName: 'transfer',
  args: [recipient, 1000000000000000000n],
});
 
// Send both transfers atomically
const result = await jaw.provider.request({
  method: 'wallet_sendCalls',
  params: [
    {
      calls: [
        {
          to: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
          data: usdcTransfer,
        },
        {
          to: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
          data: uniTransfer,
        },
      ],
    },
  ],
});
 
console.log('Batch ID:', result.id);

Check Status

// Get status of the batch
const status = await jaw.provider.request({
  method: 'wallet_getCallsStatus',
  params: [result.id],
});
 
console.log('Status:', status.status);

Execute with Permission (Delegated Execution)

When a permission has been granted via wallet_grantPermissions, you can execute calls using that permission. This allows the spender to execute transactions on behalf of the account within the permission's constraints.

import { encodeFunctionData } from 'viem';
 
// First, get the permission ID from a previous wallet_grantPermissions call
const permissionId = '0x1234...'; // The permission ID returned from grantPermissions
 
const transferData = encodeFunctionData({
  abi: erc20Abi,
  functionName: 'transfer',
  args: ['0x5678...', 1000000n],
});
 
// Execute the transfer using the permission
const result = await jaw.provider.request({
  method: 'wallet_sendCalls',
  params: [
    {
      calls: [
        {
          to: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
          data: transferData,
        },
      ],
      capabilities: {
        permissions: {
          id: permissionId,
        },
      },
    },
  ],
});
 
console.log('Batch ID:', result.id);

When using the permissions capability:

  • The calls are executed through the JustaPermissionManager contract
  • The permission's call and spend limits are enforced
  • The spender must have been granted appropriate permissions for the calls being made