The Flutter SDK can spin up an Alchemy smart account for any EVM wallet your user holds. The Para wallet signs; the smart account is what shows up on-chain. You get gas sponsorship through Alchemy’s Gas Manager and atomic batched calls through the Account Kit.
Only Alchemy (EIP-4337) is wired through the native bridge today. ZeroDev, Pimlico, and the other providers already available on web and React Native are on the roadmap for Flutter.
Setup
-
Create an Alchemy account, grab your API key, and create a Gas Manager policy for the chain you’re targeting. The policy ID is what tells the paymaster to sponsor gas.
-
Drop the values into your
.env file. flutter_dotenv is already wired into the Flutter example:
ALCHEMY_API_KEY=your_alchemy_api_key
ALCHEMY_GAS_POLICY_ID=your_gas_policy_id
You don’t need to add a pub.dev dependency. The Flutter SDK reaches @getpara/aa-alchemy through the bridge on the web side.
Usage
Create the smart account, then send a gasless transaction with it:
import 'package:para/para.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
// `para` is your authenticated Para instance
final info = await para.createSmartAccount(
apiKey: dotenv.env['ALCHEMY_API_KEY']!,
chainId: 11155111, // Sepolia
gasPolicyId: dotenv.env['ALCHEMY_GAS_POLICY_ID'],
);
print('Smart account: ${info.smartAccountAddress}');
// Zero-value tx to the burn address, paid for by the paymaster.
final receipt = await para.sendSmartAccountTransaction(
smartAccountAddress: info.smartAccountAddress,
chainId: 11155111,
to: '0x000000000000000000000000000000000000dEaD',
);
print('tx: ${receipt.transactionHash}, status: ${receipt.status}');
Heads up on timing: UserOp inclusion on a public chain usually lands in 30 to 90 seconds, and the call blocks until it does. ParaConfig.requestTimeout defaults to 120s for this reason. Override it when you build your ParaConfig if you’d rather bail earlier.
Batched transactions
One UserOp, multiple calls, one receipt. Use it for approve + swap, approve + transfer, or anything that should succeed or fail together:
final batchReceipt = await para.sendSmartAccountBatchTransaction(
smartAccountAddress: info.smartAccountAddress,
chainId: 11155111,
calls: [
SmartAccountCall(to: '0xRecipientA', value: '10000000000000000'), // 0.01 ETH
SmartAccountCall(to: '0xRecipientB', data: '0xEncodedCallData'),
],
);
Error handling
Provider failures come back with a SmartAccountErrorCode from @getpara/core-sdk. The ones you’re most likely to see:
PROVIDER_RATE_LIMITED: back off and retry.
SPONSORSHIP_DENIED: the gas policy rejected this UserOp. Check the Alchemy dashboard.
TRANSACTION_REVERTED: the target contract reverted at execution time.
MISSING_ACCOUNT_ADDRESS: you called sendSmartAccountTransaction before createSmartAccount for this chain + address.
Errors surface as ParaBridgeException and keep the provider’s original message.
What gets returned
createSmartAccount returns a SmartAccountInfo:
class SmartAccountInfo {
final String smartAccountAddress;
final String mode; // "4337"
final String provider; // "ALCHEMY"
final int chainId;
}
sendSmartAccountTransaction and sendSmartAccountBatchTransaction return an AATransactionReceipt:
class AATransactionReceipt {
final String transactionHash;
final String blockHash;
final String blockNumber; // uint256, decimal string
final String from;
final String? to;
final String status; // "success" or "reverted"
final String gasUsed;
final String effectiveGasPrice;
}
BigInt fields arrive as decimal strings so you don’t lose uint256 precision across the bridge.
Reference
Full working implementation: examples-hub/mobile/with-flutter/lib/features/smart_account/smart_account_screen.dart.