Variance is a comprehensive toolkit for Ethereum Account Abstraction development, streamlining the creation and management of smart accounts and their interactions with Entrypoints. Built on top of the Web3dart library, it provides a robust foundation for blockchain development.
- Smart Contract Integration: Seamlessly handle ABI encoding and decoding for smooth interaction with Solidity smart contracts and the Entrypoint.
- Streamlined Operations: Build and execute UserOperations with minimal complexity
- Token Management: Comprehensive support for ERC20 and ERC721 token operations, from transfers to approvals
- RPC Connectivity: Direct interface with Ethereum networks and bundler services
- Advanced Authentication: Enable secure transaction signing using Passkeys.
open your terminal and run the following command:
flutter pub add variance_dart
flutter pub add web3_signers
flutter pub add web3dart
// Import the packages
import 'package:web3_signers/web3_signers.dart';
import 'package:variance_dart/variance_dart.dart';
import 'package:web3dart/web3dart.dart';
const bundler = "https://api.pimlico.io/v2/84532/rpc?apikey=API_KEY";
final Chain chain = Chain(
bundlerUrl: bundler,
paymasterUrl: bundler,
testnet: true,
chainId: 84532,
jsonRpcUrl: "https://sepolia.base.org",
accountFactory: Addresses.safeProxyFactoryAddress,
explorer: "https://base-sepolia.blockscout.com/",
entrypoint: EntryPointAddress.v07);
When creating Safe Accounts, specify Addresses.safeProxyFactoryAddress
as the account factory address.
The SDK supports multiple networks including:
- Mainnets: Ethereum, Polygon, Optimism, Base, Arbitrum, Linea, Fuse, and Scroll
- Testnets: Sepolia and Base Testnet
Paymaster Configuration:
- By default, no paymaster is configured (null)
- To enable paymaster functionality, provide the paymaster's RPC endpoint when setting up your smart wallet
- Additional paymaster settings can be configured after wallet creation:
wallet.paymasterAddress = EthereumAddress.fromHex("0x");
wallet.paymasterContext = {'key': 'value'};
Creating a smart wallet client requires configuring a signer to validate useroperation hashes on-chain. The signer must be from the web3signers package.
Each account type requires a specific signer configuration:
PrivateKeys
- Compatible with all account typesPasskey
- Specifically for Safe Passkey and modular accountsEOA Wallet (seed phrases)
- Compatible with all account types
Visit the web3signers documentation for complete implementation details.
Before creating a smart wallet instance, initialize a SmartWalletFactory using your chain configuration and signer (configured in the steps above).
final SmartWalletFactory smartWalletFactory = SmartWalletFactory(chain, signer);
When using an Alchemy Light Account, you must configure your signer with a signature prefix. Add a Uint8
prefix value when initializing your web3_signer - this prefix will be automatically included in all signatures generated for your smart wallet.
Example:
// prefix is required for alchemy light accounts
final salt = Uint256.zero;
const prefix = const SignatureOptions(prefix: [0])
final signer = EOAWallet.createWallet(WordLength.word_12, prefix);
final smartWalletFactory = SmartWalletFactory(chain, signer);
final Smartwallet wallet = await smartWalletFactory.createAlchemyLightAccount(salt);
print("light account wallet address: ${wallet.address.hex}");
To create a Safe Smart Account
final salt = Uint256.zero;
final signer = EOAWallet.createWallet();
final smartWalletFactory = SmartWalletFactory(chain, signer);
final Smartwallet wallet = await smartWalletFactory.createSafeAccount(salt);
print("safe wallet address: ${wallet.address.hex}");
The
safeSingleton
address can be customized during account creation. If not specified, it defaults toSafeSingletonAddress.l1
for mainnet orSafeSingletonAddress.l2
for L2 chains.
To create a Safe Smart Account with Passkey
final salt = Uint256.zero;
final options = PassKeysOptions(
name: "domain",
namespace: "domain.com",
authenticatorAttachment: "cross-platform",
sharedWebauthnSigner: Addresses.sharedSignerAddress,
);
final signer = PassKeySigner(options: options);
final smartWalletFactory = SmartWalletFactory(chain, signer);
final keypair = await signer.register(name, displayName); // email can be used in place of name
final Smartwallet wallet = await smartWalletFactory.createSafeAccountWithPasskey(
keypair, salt);
print("p256 wallet address: ${wallet.address.hex}");
The
PassKeyPair
object, obtained during registration with yourPasskeySigner
, is required for this operation. You can serialize and persist the keypair for later use in your application. Additional modules (validator
,hooks
,executors
, orfallback
) can be initialized during account creation. For Passkey-enabled accounts, theWebAuthnValidator
module must be included during initialization. ThesafeSingleton
address can be customized during account creation. If not specified, it defaults toSafeSingletonAddress.l1
for mainnet orSafeSingletonAddress.l2
for L2 chains.
To create a Modular Safe Smart Account
please refer to ERC7579 for more information.
final salt = Uint256.zero;
final launchpad =
EthereumAddress.fromHex("0x7579011aB74c46090561ea277Ba79D510c6C00ff");
final attester =
EthereumAddress.fromHex("0x000000333034E9f539ce08819E12c1b8Cb29084d");
final signer = EOAWallet.createWallet();
final smartWalletFactory = SmartWalletFactory(chain, signer);
final Smartwallet wallet = await smartWalletFactory.createSafe7579Account(salt, launchpad,
attesters: [attester], attestersThreshold: 1);
print("safe wallet address: ${wallet.address.hex}");
Additional modules (
validator
,hooks
,executors
, orfallback
) can be initialized during account creation. For Passkey-enabled accounts, theWebAuthnValidator
module must be included during initialization. ThesafeSingleton
address can be customized during account creation. If not specified, it defaults toSafeSingletonAddress.l1
for mainnet orSafeSingletonAddress.l2
for L2 chains.
To create a Modular Safe Smart Account with Passkey
For more details about the technical specifications and implementation, visit ERC7579 and Rhinestone.
Note that you must initialize the WebAuthnValidator
module when creating the safe account.
To access all available modules, install the variance_modules
package by running:
import 'package:variance_modules/modules.dart';
final salt = Uint256.zero;
final options = PassKeysOptions(
name: "domain",
namespace: "domain.com",
authenticatorAttachment: "cross-platform",
sharedWebauthnSigner: Addresses.sharedSignerAddress,
);
final signer = PassKeySigner(options: options);
final smartWalletFactory = SmartWalletFactory(chain, signer);
final keypair = await signer.register(name, displayName);
final launchpad =
EthereumAddress.fromHex("0x7579011aB74c46090561ea277Ba79D510c6C00ff");
final attester =
EthereumAddress.fromHex("0x000000333034E9f539ce08819E12c1b8Cb29084d");
final signer = EOAWallet.createWallet();
final smartWalletFactory = SmartWalletFactory(chain, signer);
Smartwallet wallet = await smartWalletFactory.createSafe7579AccountWithPasskey(
keypair, salt, launchpad,
attesters: [attester],
attestersThreshold: 1,
validators: List.from([
ModuleInit(WebauthnValidator.getAddress(),
WebauthnValidator.parseInitData(BigInt.one, {keypair}))
]));
final validator = WebauthnValidator(wallet, BigInt.one, {keyPair});
// replace the wallet to allow for validator to trigger at least for initial transaction
wallet = validator.wallet;
print("safe wallet address: ${wallet.address.hex}");
Additional modules (
validator
,hooks
,executors
, orfallback
) can be initialized during account creation. For Passkey-enabled accounts, theWebAuthnValidator
module must be included during initialization. ThesafeSingleton
address can be customized during account creation. If not specified, it defaults toSafeSingletonAddress.l1
for mainnet orSafeSingletonAddress.l2
for L2 chains. To set up multi-signature functionality (threshold > 1), specify the threshold in theModuleInit
object. Note that all requiredkeypairs
must be available to sign user operations when using multiple signatures.
!!! CAVEATS
When working with Safe 7579 accounts and WebAuthn validation:
- You must initialize the
WebAuthnValidator
module during account creation - For the first transaction, use the validator's extended wallet instance instead of the base wallet. This is required because:
- Safe 7579 accounts need an initial transaction to complete setup
- The Shared Signer cannot be used until after this setup
// retrieve the balance of a smart wallet
final EtherAmount balance = await wallet.balance;
print("account balance: ${balance.getInWei}");
// retrive the account nonce
Uint256 nonce = await wallet.getNonce();
// you can also retrieve a nonce with key
nonce = await wallet.getNonce(key: '<Uint256 instance>');
print("account nonce: ${nonce.toInt()}");
// check if a smart wallet has been deployed
final bool deployed = await wallet.deployed;
print("account deployed: $deployed");
// get the init code of the smart wallet
final String initCode = wallet.initCode;
print("account init code: $initCode");
// perform a simple transaction (send ether to another account)
// account must be prefunded with native token. paymaster is not yet implemented
await wallet.send(
EthereumAddress.fromHex(
"0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"), // receive address
EtherAmount.fromInt(EtherUnit.ether, 0.7142), // 0.7142 ether
);
all modules can be installed from
variance_modules
package. simply runflutter pub add variance_modules
to add the package to your project. Additionally, you must already have an instance of the smart wallet.
import 'package:variance_modules/modules.dart';
// required constructor parameters
final threshold = BigInt.two;
final guardian1 = PrivateKeySigner.createRandom("guardian1 password");
final guardian2 = PrivateKeySigner.createRandom("guardian2 password");
// create a new instance of the module
final module = SocialRecovery(smartWallet, threshold, [guardian1.address, guardian2.address]);
// get the module address
final address = module.address;
// get the module init data
final initData = module.initData;
// get the module name
final name = module.name;
// get the module version
final version = module.version;
// get the module type
final type = module.type;
// install the module
final receipt = await smartWallet.installModule(
module.type,
module.address,
module.getInitData(),
);
// uninstall the module
final receipt = await smartWallet.uninstallModule(
module.type,
module.address,
await module.getDeInitData(), // you can pass additional data needed for deInitialization
);
each module contains its own specialized functionality and methods that are specific to that module's purpose. For validator modules, transactions requiring validation must be initiated through the validator instance rather than directly from the smart wallet. An example implementation is shown below.
// This example demonstrates how to add WebAuthn validation capabilities to an existing modular account.
// The account must support module installation (be modular).
// Note: A PassKey signer is required - create one if the account doesn't already use PassKey authentication.
final signer = PassKeySigner(options: PassKeysOptions(
name: "domain",
namespace: "domain.com",
authenticatorAttachment: "cross-platform",
sharedWebauthnSigner: Addresses.sharedSignerAddress,
));
final keypair = await signer.register(name, displayName);
final validator = WebauthnValidator(account, BigInt.one, {keyPair}, signer);
// we need to install it first from the smartwallet.
await account.installModule(
validator.type,
validator.address,
validator.getInitData(), // you can pass additional data needed for deInitialization
);
// mint an nft using a passkey signature
final nft = EthereumAddress.fromHex("0x"); // add nft contract address
final mintAbi = ContractAbis.get("ERC721_SafeMint");
final mintCall = Contract.encodeFunctionCall("safeMint", nft, mintAbi);
// this transaction must be sent directly from the `validator.wallet` instead.
// validators are responsible for validating userOperations and managing signature generation internally
final tx = await validator.wallet.sendTransaction(nft, mintCall);
final receipt = await tx.wait();
Note: When using the
WebauthnValidator
module, your account must either already use a passkey signer or you'll need to create one. Unlike validators, executors and hooks do not need to be used to process transactions. Use the defaultSmartWallet
instance to send transactions.
For detailed usage and examples, refer to the documentation. Additional refer to the example for use in a flutter app.
Detailed API reference and examples can be found in the API reference.
We are committed to maintaining variance as an open source sdk, take a look at existing issues, open a pull request etc.
This project is licensed under the BSD-3-Clause - see the LICENSE file for details.