Skip to main content
The eth_signUserOperation method allows you to sign an ERC-4337 UserOperation using your client’s MPC key without broadcasting it to the network. The signed UserOperation can then be submitted through a separate backend such as Pimlico or a custom bundler endpoint.
This method requires your client to be created with Account Abstraction enabled (isAccountAbstracted: true). Non-AA clients will receive a METHOD_UNSUPPORTED error.

Prerequisites

Before using eth_signUserOperation, ensure that:
  1. Your organization has Account Abstraction enabled. See the Account Abstraction guide for setup instructions.
  2. Your client was created with isAccountAbstracted: true via the Create a new client endpoint.
  3. Your client has a wallet created and backed up.

Signing a UserOperation

Use the request method with the eth_signUserOperation RPC method. The params array takes a single object with standard Ethereum transaction fields.
import 'package:portal_flutter/portal_flutter.dart';

final portal = Portal();

// Sign a UserOperation on Sepolia testnet
final result = await portal.request(
  chainId: 'eip155:11155111',
  method: 'eth_signUserOperation',
  params: [
    {
      'to': '0xRecipientAddress',
      'value': '0x0',
      'data': '0x',
    }
  ],
);

if (result.error != null) {
  print('Error: ${result.error}');
} else {
  print('Signed UserOp: ${result.result}');
  // result.result contains a hex-encoded signed UserOperation JSON
}
The chainId must be a CAIP-2 compliant Chain ID for a supported Account Abstraction network.

Transaction Parameters

The params object supports the following fields:
ParameterTypeRequiredDescription
toStringYesThe recipient or contract address
valueStringNoValue in wei (hex-encoded)
dataStringNoCalldata (hex-encoded)
gasStringNoGas limit (hex-encoded)
maxFeePerGasStringNoMax fee per gas for EIP-1559 (hex-encoded)
maxPriorityFeePerGasStringNoMax priority fee for EIP-1559 (hex-encoded)
nonceStringNoTransaction nonce (hex-encoded)

Response

The method returns a PortalProviderResponse where result is a hex-encoded JSON string containing the signed UserOperation. You can decode this result and submit it to a bundler of your choice.
import 'package:portal_flutter/portal_flutter.dart';

final portal = Portal();

final result = await portal.request(
  chainId: 'eip155:11155111',
  method: 'eth_signUserOperation',
  params: [
    {
      'to': '0xRecipientAddress',
      'value': '0xDE0B6B3A7640000', // 1 ETH in wei
      'data': '0x',
    }
  ],
);

if (result.error != null) {
  throw Exception('Failed to sign UserOp: ${result.error}');
}

// The signed UserOperation is a hex-encoded JSON string
final signedUserOp = result.result;
print('Signed UserOperation: $signedUserOp');

// Decode the hex string and submit to your bundler
// e.g., send signedUserOp to Pimlico or your custom bundler endpoint

EIP-1559 Gas Parameters

You can specify EIP-1559 gas parameters for finer control over fees:
final result = await portal.request(
  chainId: 'eip155:11155111',
  method: 'eth_signUserOperation',
  params: [
    {
      'to': '0xRecipientAddress',
      'value': '0x0',
      'data': '0x',
      'maxFeePerGas': '0x3B9ACA00',        // 1 Gwei
      'maxPriorityFeePerGas': '0x3B9ACA00', // 1 Gwei
    }
  ],
);

Error Handling

ErrorDescription
METHOD_UNSUPPORTEDThe client does not have Account Abstraction enabled
NOT_INITIALIZEDPortal was not initialized
RPC_ERRORThe RPC call returned an error
try {
  final result = await portal.request(
    chainId: 'eip155:11155111',
    method: 'eth_signUserOperation',
    params: [
      {
        'to': '0xRecipientAddress',
        'value': '0x0',
        'data': '0x',
      }
    ],
  );

  if (result.error != null) {
    print('Provider error: ${result.error}');
    return;
  }

  print('Signed UserOp: ${result.result}');
} on PortalException catch (e) {
  print('Portal error: ${e.message}');
}
Related Documentation