A complete demonstration of bridging Para's embedded wallet with Variance SDK to enable ERC-4337 Account Abstraction (smart accounts).
This integration demonstrates how to use Para's embedded wallet as a signer for ERC-4337 smart accounts using the Variance SDK. It bridges Para's authentication and key management with Ethereum's Account Abstraction standard, enabling gasless transactions, batch operations, and enhanced security for your users.
- β Smart Account Creation: Deploy ERC-4337 Safe smart accounts for users
- β Gasless Transactions: Sponsor gas fees for your users
- β Batch Operations: Execute multiple transactions atomically
- β Social Recovery: Add guardians and recovery mechanisms
- β Session Keys: Delegate limited permissions without exposing main keys
- β Para Authentication: OAuth (Google, Apple) + Passkey security
dependencies:
flutter: sdk: flutter
para: ^latest_version
variance_dart: ^0.2.0
web3_signers: ^latest_version
web3dart: ^2.7.0
crypto: ^3.0.3
eth_sig_util: ^latest_version- Para Account: Sign up at Para Dashboard
- RPC Provider: Your target RPC
- Bundler: ERC-4337 bundler endpoint (Pimlico, Stackup, Gelato or run your own)
flutter pub add para variance_dart web3_signers web3dart crypto eth_sig_utilgit clone https://github.com/vaariance/para-demo.git
cd para-demoCreate a .env file:
PARA_API_KEY=your_para_api_key
PARA_ENVIRONMENT=beta # or production
VARIANCE_API_KEY=your_variance_api_key
RPC_URL=your_ethereum_rpc_url
BUNDLER_URL=your_bundler_url
ENTRYPOINT_ADDRESS= There are existing entrypoints address in the variance_dart package to utilizefinal para = Parra.fromConfig(
config: ParaConfig(
apiKey: dotenv.env['PARA_API_KEY'] ?? '',
environment: Environment.beta,
requestTimeout: const Duration(seconds: 60),
),
appScheme: 'parademo',
sessionPersistence: sessionPersistence,
);βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Your Flutter App β
ββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ParaSigner (MSI) β
β β’ Implements MultiSignerInterface β
β β’ Bridges Para β Variance β
β β’ Handles EIP-191 signing β
ββββββββββββββ¬βββββββββββββββββββββββββββββ¬ββββββββββββββββββββ
β β
β β
ββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββ
β Parra (Custom) β β Variance SDK β
β β’ signMessageRaw() β β β’ Smart Account Factory β
β β’ Base64 encoding β β β’ UserOp creation β
β β’ JS Bridge access β β β’ Bundler communication β
ββββββββββ¬ββββββββββββββββ ββββββββββββββββ¬ββββββββββββββββ
β β
β β
ββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββ
β Para SDK β β ERC-4337 Network β
β β’ Capsule MPC β β β’ EntryPoint Contract β
β β’ OAuth β β β’ Bundler β
β β’ Passkey Auth β β β’ Paymaster (optional) β
ββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββ
The Parra class extends Para's SDK to add raw byte signing capability, which is required for ERC-4337 but not supported out-of-the-box.
class Parra extends Para {
/// Signs raw bytes by directly accessing the Capsule JS bridge
/// This bypasses Para's string-only signMessage API
ParaFuture<SignatureResult> signMessageRaw({
required String walletId,
required String messageBase64, // Base64-encoded bytes
}) {
// Direct JS bridge call with proper keyshare loading
}
}Key Features:
- Direct access to Capsule's signing infrastructure
- Base64 encoding for byte transport
- Automatic transmission keyshare loading
- Proper error handling
Implements the MultiSignerInterface (MSI) from web3_signers, making Para compatible with Variance and other Ethereum libraries.
class ParaSigner extends MSI {
final Parra client;
final para.Wallet _wallet;
@override
Future<Uint8List> personalSign(Uint8List hash, {int? index}) async {
// EIP-191 implementation with Para signing
}
@override
Future<MsgSignature> signToEc(Uint8List hash, {int? index}) async {
// ECDSA signature for transaction signing
}
}Key Features:
- EIP-191 compliant personal message signing
- EIP-712 typed data signing support
- Signature verification
- V value normalization (0/1 β 27/28)
Creates and manages ERC-4337 Safe smart accounts.
static Future<SmartWallet> createSafeAccount(
Parra client,
para.Wallet wallet,
Chain chain,
Uint256 salt,
) async {
final signer = ParaSigner.fromWallet(client, wallet);
final factory = SmartWalletFactory(chain, signer);
return await factory.createSafeAccount(salt);
}import 'package:para/para.dart';
import 'parra.dart'; // Your custom extension
Future<Parra> initializePara() async {
final para = Parra(
environment: Environment.beta,
apiKey: 'your_api_key',
appScheme: 'myapp',
);
return para;
}Future<para.Wallet> authenticateUser(Parra client) async {
// OAuth authentication
final authResult = await client.verifyOAuth(
provider: OAuthProvider.google,
);
if (authResult.stage == AuthStage.login) {
// User authenticated, get wallet
final wallets = await client.getWallets();
return wallets.first;
}
throw Exception('Authentication failed');
}import 'package:variance_dart/variance_dart.dart';
import 'para_signer.dart';
Future<SmartWallet> createSmartAccount(
Parra paraClient,
para.Wallet paraWallet,
) async {
// Configure chain
final chain = Chain(
chainId: 84532, // Base Sepolia
rpcUrl: 'your_rpc_url',
bundlerUrl: 'your_bundler_url',
entrypoint: EthereumAddress.fromHex(
'0x0000000071727De22E5E9d8BAf0edAc6f37da032',
),
);
// Create smart account
final smartWallet = await ParaSigner.createSafeAccount(
paraClient,
paraWallet,
chain,
Uint256.zero, // Salt for deterministic address
);
print('Smart Account Address: ${smartWallet.address}');
return smartWallet;
}Future<String> sendTransaction(SmartWallet wallet) async {
// Create transaction
final tx = Contract.function(
'transfer',
EthereumAddress.fromHex('0xRecipient...'),
amount: BigInt.from(1000000), // 0.001 ETH
);
// Send via smart account
final userOpHash = await wallet.sendUserOperation(tx);
print('UserOp Hash: $userOpHash');
// Wait for confirmation
final receipt = await wallet.waitForUserOperationReceipt(userOpHash);
print('Transaction Hash: ${receipt.transactionHash}');
return receipt.transactionHash;
}// 1. Encode bytes as base64 (string representation of bytes)
String messageBase64 = base64Encode(hash); // "Op9f2b..."
// 2. Call Capsule JS bridge directly
final sig = await client.signMessageRaw(
walletId: wallet.id!,
messageBase64: messageBase64, // Para decodes back to bytes internally
);
// 3. Capsule signs the actual bytes (not string)
// Result: Valid Ethereum signature β
Future<Uint8List> personalSign(Uint8List message) async {
// 1. Add Ethereum message prefix
final prefix = '\u0019Ethereum Signed Message:\n${message.length}';
final prefixBytes = ascii.encode(prefix);
// 2. Concatenate prefix + message
final payload = prefixBytes.concat(message);
// 3. Hash the complete payload
final digest = keccak256(payload);
// 4. Sign via Para (as base64)
final sig = await client.signMessageRaw(
walletId: _wallet.id!,
messageBase64: base64Encode(digest),
);
// 5. Parse and normalize signature
final sigBytes = hexToBytes(sig.signedTransaction);
final r = sigBytes.sublist(0, 32);
final s = sigBytes.sublist(32, 64);
final v = 27 + sigBytes[64]; // Convert parity (0/1) to Ethereum v (27/28)
return Uint8List.fromList([...r, ...s, v]);
}https://github.com/user-attachments/assets/9fce562d-b8de-40eb-aec0-8d598ba81fc2