Run this command:
With Flutter:
flutter pub add particle_aa
click here to get the demo source code
Particle AA service can't use individually.
Before using the SDK, you have to call init(Required)
(Optional) The biconomyApiKeys comes from Biconomy, visit Biconomy Dashboard to learn more.
Map<int, String> biconomyApiKeys = {
1: "your ethereum mainnet key",
5: "your ethereum goerli key",
137: "your polygon mainnet key",
80001: "your polygon testnet key"
};
// Support BICONOMY_V1 | BICONOMY_V2 | CYBERCONNECT | SIMPLE
ParticleAA.init(AccountName.BICONOMY_V1(), biconomyApiKeys);
Check is a smart account address has been deployed by eoa address.
static void isDeploy() async {
try {
final eoaAddress = await ParticleAuth.getAddress();
var isDeploy = await ParticleAA.isDeploy(eoaAddress);
print("isDeploy: $isDeploy");
} catch (error) {
print("isDeploy: $error");
}
}
var result = await ParticleAA.isAAModeEnable();
print(result);
ParticleAA.enableAAMode();
ParticleAA.disableAAMode();
Before send transaction, you should get your smart account address first.
static void getSmartAccountAddress() async {
if (account == null) {
print("not connect");
return;
}
try {
final eoaAddress = account!.publicAddress;
final smartAccountConfig = SmartAccountConfig.fromAccountName(AccountName.BICONOMY_V1(), eoaAddress);
List<dynamic> response = await EvmService.getSmartAccount(
<SmartAccountConfig>[smartAccountConfig]);
var smartAccountJson = response.firstOrNull;
if (smartAccountJson != null) {
final smartAccount = smartAccountJson as Map<String, dynamic>;
final smartAccountAddress =
smartAccount["smartAccountAddress"] as String;
AAConnectLogic.smartAccountAddress = smartAccountAddress;
print("getSmartAccount: $smartAccountAddress");
} else {
print('List is empty');
}
} catch (error) {
print("getSmartAccountAddress: $error");
}
}
You should use particle-auth/particle-connect to send transaction, both of them have a function called signAndSendTransaction
, a parameter called feeMode
is used with AA service.
feeMode
support native, gasless and token, just as its name implies, it tells how to pay gas fee.
Show how to send transaction with particle-connect, use native token to pay gas fee.
static void signAndSendTransactionWithNative() async {
if (account == null) {
print("not connect");
return;
}
if (smartAccountAddress == null) {
print("not get smartAccountAddress");
return;
}
try {
final transaction =
await TransactionMock.mockEvmSendNative(smartAccountAddress!);
// check if enough native for gas fee
var result = await ParticleAA.rpcGetFeeQuotes(
account!.publicAddress, [transaction]);
var verifyingPaymasterNative = result["verifyingPaymasterNative"];
var feeQuote = verifyingPaymasterNative["feeQuote"];
var fee = BigInt.parse(feeQuote["fee"], radix: 10);
var balance = BigInt.parse(feeQuote["balance"], radix: 10);
if (balance < fee) {
print("native balance if not enough for gas fee");
return;
}
// pass result from rpcGetFeeQuotes to send pay with native
final signature = await ParticleConnect.signAndSendTransaction(
WalletType.metaMask, account!.publicAddress, transaction,
feeMode: AAFeeMode.native(result));
print("signature $signature");
} catch (error) {
print("signAndSendTransactionWithNative: $error");
}
}
Show how to send transaction with particle-connect, gasless.
static void signAndSendTransactionWithGasless() async {
if (account == null) {
print("not connect");
return;
}
if (smartAccountAddress == null) {
print("not get smartAccountAddress");
return;
}
try {
final transaction =
await TransactionMock.mockEvmSendNative(smartAccountAddress!);
// check if gasless available
var result = await ParticleAA.rpcGetFeeQuotes(
account!.publicAddress, [transaction]);
var verifyingPaymasterGasless = result["verifyingPaymasterGasless"];
if (verifyingPaymasterGasless == null) {
print("gasless is not available");
return;
}
// pass result from rpcGetFeeQuotes to send gasless
final signature = await ParticleConnect.signAndSendTransaction(
WalletType.metaMask, account!.publicAddress, transaction,
feeMode: AAFeeMode.gasless(result));
print("signature $signature");
showToast("signature $signature");
} catch (error) {
print("signAndSendTransactionWithGasless: $error");
showToast("signAndSendTransactionWithGasless: $error");
}
}
Show how to send transaction with particle-connect, use token to pay gas fee.
static void signAndSendTransactionWithToken() async {
if (account == null) {
print("not connect");
return;
}
if (smartAccountAddress == null) {
print("not get smartAccountAddress");
return;
}
try {
final transaction =
await TransactionMock.mockEvmSendNative(smartAccountAddress!);
List<String> transactions = <String>[transaction];
var result = await ParticleAA.rpcGetFeeQuotes(
account!.publicAddress, transactions);
List<dynamic> feeQuotes = result["tokenPaymaster"]["feeQuotes"];
var overFeeQuotes = feeQuotes.where((element) {
var fee = BigInt.parse(element["fee"], radix: 10);
var balance = BigInt.parse(element["balance"], radix: 10);
return balance >= fee;
}).toList();
if (overFeeQuotes.isEmpty) {
print("no valid token for gas fee");
return;
}
var feeQuote = overFeeQuotes[0];
String tokenPaymasterAddress =
result["tokenPaymaster"]["tokenPaymasterAddress"];
print("feeQuote $feeQuote");
print("tokenPaymasterAddress $tokenPaymasterAddress");
final signature = await ParticleConnect.signAndSendTransaction(
WalletType.metaMask, account!.publicAddress, transaction,
feeMode: AAFeeMode.token(feeQuote, tokenPaymasterAddress));
print("signature $signature");
showToast("signature $signature");
} catch (error) {
print("signAndSendTransactionWithToken: $error");
showToast("signAndSendTransactionWithToken: $error");
}
}
{% hint style="info" %}
If you are using particle-auth to send transaction, the difference is which to call signAndSendTransaction
method.
{% endhint %}
Use with send transaction, fill parameter feeMode
.
static void rpcGetFeeQuotes() async {
if (account == null) {
print("not connect");
return;
}
if (smartAccountAddress == null) {
print("not get smartAccountAddress");
return;
}
try {
final transaction =
await TransactionMock.mockEvmSendNative(smartAccountAddress!);
List<String> transactions = <String>[transaction];
var result = await ParticleAA.rpcGetFeeQuotes(
account!.publicAddress, transactions);
print("rpcGetFeeQuotes: $result");
showToast("rpcGetFeeQuotes: $result");
} catch (error) {
print("rpcGetFeeQuotes: $error");
showToast("rpcGetFeeQuotes: $error");
}
}
Merge multiple transactions and send them together
static void batchSendTransactions() async {
if (account == null) {
print("not connect");
return;
}
if (smartAccountAddress == null) {
print("not get smartAccountAddress");
return;
}
try {
final transaction =
await TransactionMock.mockEvmSendNative(smartAccountAddress!);
List<String> transactions = <String>[transaction, transaction];
// check if enough native for gas fee
var result = await ParticleAA.rpcGetFeeQuotes(
account!.publicAddress, transactions);
var verifyingPaymasterNative = result["verifyingPaymasterNative"];
var feeQuote = verifyingPaymasterNative["feeQuote"];
var fee = BigInt.parse(feeQuote["fee"], radix: 10);
var balance = BigInt.parse(feeQuote["balance"], radix: 10);
if (balance < fee) {
print("native balance if not enough for gas fee");
return;
}
final signature = await ParticleConnect.batchSendTransactions(
WalletType.metaMask, account!.publicAddress, transactions,
feeMode: AAFeeMode.native(result));
print("signature $signature");
showToast("signature $signature");
} catch (error) {
print("batchSendTransactions: $error");
showToast("batchSendTransactions: $error");
}
}
{% hint style="info" %}
If you are using particle-auth to send transaction, the difference is which to call batchSendTransactions
method.
{% endhint %}