Create a Chain Agnostic Web3 wallet in Flutter
In this guide, we'll talk about how we can use Web3Auth to build your chain-agnostic Web3 wallet in Flutter. The wallet will support the Ethereum and Solana ecosystem.
As an overview, the app is quite simple, with functionality to log in, display user details, and perform blockchain interactions. The signing of the blockchain transactions is done through the Web3Auth embedded wallet. You can check out the infrastructure docs, "Web3Auth Wallet Management Infrastructure" for a high-level overview of the Web3Auth architecture and implementation. For those who want to skip straight to the code, you can find it on GitHub.
Here are few screenshots of the application.
How to set up Web3Auth Dashboard
If you haven't already, sign up on the Web3Auth platform. It is free and gives you access to the Web3Auth's base plan. After the basic setup, explore other features and functionalities offered by the Web3Auth Dashboard. It includes custom verifiers, whitelabeling, analytics, and more. Head to Web3Auth's documentation page for detailed instructions on setting up the Web3Auth Dashboard.
Integrating Web3Auth in Flutter
Once, you have set up the Web3Auth Dashboard, and created a new project, it's time to integrate Web3Auth in your Flutter application. For the implementation, we'll use the "web3auth_flutter package". This package facilitates integration with Web3Auth. This way you can easily manage embedded wallet in your Flutter application.
Installation
To install the web3auth_flutter package, you have two options. You can either manually add the
package in the pubspec.yaml
file, or you can use the flutter pub add
command.
- Console
- Pubspec
Add web3auth_flutter
using flutter pub add
command.
flutter pub add web3auth_flutter
Add web3auth_flutter
as a dependency to your pubspec.yaml
.
dependencies:
web3auth_flutter: ^5.0.4
Initialization
After successfully installing the package, the next step is to initialize Web3Auth in your Flutter app. This sets up the necessary configurations using Client Id and prepares Web3Auth. Learn more about Web3Auth Initialization.
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Addditional code
final Uri redirectUrl;
if (Platform.isAndroid) {
redirectUrl =
Uri.parse('w3aexample://com.example.flutter_solana_example/auth');
} else {
redirectUrl = Uri.parse('com.web3auth.fluttersolanasample://auth');
}
await Web3AuthFlutter.init(
Web3AuthOptions(
clientId: "YOUR_WEB3AUTH_CLIENT_ID",
network: Network.sapphire_mainnet,
redirectUrl: redirectUrl,
),
);
await Web3AuthFlutter.initialize();
runApp(const MainApp());
}
Session Management
To check whether the user is authenticated, you can use the getPrivateKey
or getEd25519PrivKey
method. For a user already authenticated, the result would be a non-empty String
. You can navigate
to different views based on the result. If the user is already authenticated, we'll navigate them to
HomeScreen
. In case of no active session, we'll navigate to LoginScreen
to authenticate again.
Learn more about Web3Auth session management.
class MainApp extends StatefulWidget {
const MainApp({super.key});
State<MainApp> createState() => _MainAppState();
}
class _MainAppState extends State<MainApp> {
late final Future<String> privateKeyFuture;
void initState() {
super.initState();
privateKeyFuture = Web3AuthFlutter.getEd25519PrivKey();
}
Widget build(BuildContext context) {
return MaterialApp(
home: FutureBuilder<String>(
future: privateKeyFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
if (snapshot.data!.isNotEmpty) {
return const HomeScreen();
}
}
return const LoginScreen();
}
return const Center(
child: CircularProgressIndicator.adaptive(),
);
},
),
);
}
}
Authentication
If the user is not authenticated, you should utilize the login
method. For the Wallet, we will add
two login options, Google, and Email Passwordless login. In Web3Auth, you can choose between a
Single Page Authentication flow or a Regular Web Application flow. For this guide, we'll be using a
Single Page Authentication flow. We'll create a helper function, _login
inside LoginScreen
. The
login method is pretty straightforward in Web3Auth and takes LoginParams
as input. After
successfully logging in, we'll navigate the user to HomeScreen
.
Learn more about Web3Auth LoginParams.
class _LoginScreenState extends State<LoginScreen> with WidgetsBindingObserver {
// Additional Code
void didChangeAppLifecycleState(final AppLifecycleState state) {
// This is important to trigger the user cancellation on Android.
if (state == AppLifecycleState.resumed) {
Web3AuthFlutter.setCustomTabsClosed();
}
}
Widget build(BuildContext context) {
// Login View
}
Future<void> _login(BuildContext context) async {
try {
// Validate the form, and TextField. In case of invalide
// form state, return back.
if (!formKey.currentState!.validate()) {
return;
}
// It can be used to set the OAuth login options for corresponding
// loginProvider. For instance, you'll need to pass user's email address as
// login_hint when the Provider is email_passwordless.
await Web3AuthFlutter.login(
LoginParams(
loginProvider: Provider.email_passwordless,
mfaLevel: MFALevel.DEFAULT,
extraLoginOptions: ExtraLoginOptions(
login_hint: emailController.text,
),
),
);
// If login is successful, navigate user to HomeScreen.
if (context.mounted) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) {
return const HomeScreen();
}),
);
}
} catch (e, _) {
if (context.mounted) {
showInfoDialog(context, e.toString());
}
}
}
}
Set up Blockchain Providers
Once we have successfully authenticated the user, the next step would be to fetch the user details, retrieve wallet address and prepare blockchain providers for interactions. For this guide, we are supporting only Ethereum and Solana ecosystem, but the general idea can be used for any blockchain ecosystem.
Given that the project follows clean architecture and Test-Driven Development (TDD) principles, we'll want to create an abstract layer to interact with the Blockchain providers. This abstraction will help us easily expand the blockchain support while isolate it from the rest of the application.
For interacting ethereum chains, we'll use the web3dart
package. Similary for solana, we'll use the solana package. To
install the packages, you have two options. You can either manually add the packages in the
pubspec.yaml
file, or you can use the flutter pub add
command.
- Console
- Pubspec
Add web3dart
and solana
using flutter pub add
command.
flutter pub add web3dart
flutter pub add solana
Add web3dart
and solana
as a dependency to your pubspec.yaml
.
dependencies:
web3dart: ^2.7.3
solana: ^0.30.4
After successfully installing both packages, it's time to set up our Blockchain provider. First,
we'll create a new class, ChainProvider
, which will used as a base class for EthereumProvider
and SolanaProvider
. If you wish to support any additional ecosystem, you can extend the
ChainProvider
and implement the methods.
If you want to learn, how you can integrate different blockchain with Web3Auth, you can checkout our Connect Blockchain resources.
abstract class ChainProvider {
Future<String> getBalance(String address);
Future<String> sendTransaction(String to, double amount);
Future<String> signMessage(String messsage);
Future<dynamic> readContract(
String address,
String function,
List<dynamic> params,
);
Future<dynamic> writeContract(
String address,
String function,
List<dynamic> params,
);
}
Generally, for any blockchain provider, you'll only require the getBalance
, sendTransaction
, and
signMessage
. The readContract
and writeContract
can be used to interact with SmartContract.
Ethereum Provider
Once we have our base class, we'll create EthereumProvider
and implement the methods. To create
the Web3Client
instance, you'll require the rpcTarget URL. If you are using public RPCs, you can
face some network congestion. It's ideal to use paid RPCs for production.
The readContract
, and writeContract
methods are used to interact with smart contracts on
Ethereum ecosystem. The readContract
is used to read the data from the smart contracts, where as
the writeContract
is used to write data on smart contract.
class EthereumProvider extends ChainProvider {
final Web3Client web3client;
EthereumProvider({required String rpcTarget})
: web3client = Web3Client(
rpcTarget,
Client(),
);
Future<String> getBalance(String address) async {
final balance = await web3client.getBalance(
EthereumAddress.fromHex(address),
);
// The result we from Web3Client is in wei, the smallest value. To convert
// the value to ether, you can divide it with 10^18, where 18 denotes the
// decimals for wei.
//
// For the sample, we'll use a helper function from web3dart package which
// has the same implementation.
return balance.getValueInUnit(EtherUnit.ether).toStringAsFixed(4);
}
Future<String> sendTransaction(String to, double amount) async {
final Credentials credentials = await _prepareCredentials();
final amountInWei = amount * pow(10, 18);
final Transaction transaction = Transaction(
to: EthereumAddress.fromHex(to),
value: EtherAmount.fromBigInt(
EtherUnit.wei,
BigInt.from(amountInWei),
),
);
final hash = await web3client.sendTransaction(
credentials,
transaction,
chainId: null,
fetchChainIdFromNetworkId: true,
);
return hash;
}
Future<String> signMessage(String messsage) async {
final Credentials credentials = await _prepareCredentials();
final signBytes = credentials.signPersonalMessageToUint8List(
Uint8List.fromList(messsage.codeUnits),
);
return bytesToHex(signBytes);
}
// Prepares the Credentials used for signing the message,
// and transaction on EVM chains. EVM ecosystem uses the
// scep2561k curve. You can use the Web3AuthFlutter.getPrivKey
// to retrieve the scep2561K compatible private key.
Future<Credentials> _prepareCredentials() async {
final privateKey = await Web3AuthFlutter.getPrivKey();
final Credentials credentials = EthPrivateKey.fromHex(privateKey);
return credentials;
}
Future<dynamic> readContract(
String address,
String function,
List<dynamic> params,
) async {
// For this sample, we are using the ERC 20 Contract. The same can be
// used for any of the EVM smart contract.
final contract = DeployedContract(
ContractAbi.fromJson(erc20Abi, 'Contract'),
EthereumAddress.fromHex(address),
);
final readFunction = contract.function(function);
final result = await web3client.call(
contract: contract,
function: readFunction,
params: params,
);
return result;
}
Future writeContract(String address, String function, List params) async {
// For this sample, we are using the ERC 20 Contract. The same can be
// used for any of the EVM smart contract.
final contract = DeployedContract(
ContractAbi.fromJson(erc20Abi, 'Contract'),
EthereumAddress.fromHex(address),
);
final writeFunction = contract.function(function);
final Credentials credentials = await _prepareCredentials();
final result = await web3client.sendTransaction(
credentials,
Transaction.callContract(
contract: contract,
function: writeFunction,
parameters: params,
),
chainId: null,
fetchChainIdFromNetworkId: true,
);
return result;
}
}
Solana Provider
After EthereumProvider
, it's time to extend ChainProvider
and create SolanaProvider
. For
SolanaProvider
, we'll only implement the getBalance
, sendTransaction
, and signMessage
. We'll
also add _generateKeyPair()
, a helper method to create Ed25519HDKeyPair
. It's used to sign the
transactions and messages on Solana ecosystem. Since, Solana uses ed25519
curve, we can utilize
the Web3AuthFlutter.getEd25519PrivKey
.
class SolanaProvider extends ChainProvider {
final SolanaClient solanaClient;
SolanaProvider({required String rpcTarget, required String wss})
: solanaClient = SolanaClient(
rpcUrl: Uri.parse(rpcTarget),
websocketUrl: Uri.parse(wss),
);
Future<String> getBalance(String address) async {
final balanceResponse = await solanaClient.rpcClient.getBalance(
address,
);
/// We are dividing the balance by 10^9, because Solana's
/// token decimals is set to be 9;
return (balanceResponse.value / pow(10, 9)).toString();
}
Future<String> sendTransaction(String to, double amount) async {
final Ed25519HDKeyPair ed25519hdKeyPair = await _generateKeyPair();
/// Converting user input to the lamports, which are smallest value
/// in Solana.
final num lamports = amount * pow(10, 9);
final transactionHash = await solanaClient.transferLamports(
source: ed25519hdKeyPair,
destination: Ed25519HDPublicKey.fromBase58(to),
lamports: lamports.toInt(),
);
return transactionHash;
}
Future<String> signMessage(String messsage) async {
final Ed25519HDKeyPair ed25519hdKeyPair = await _generateKeyPair();
final signatrure = await ed25519hdKeyPair.sign(
ByteArray.fromString(messsage),
);
return signatrure.toBase58();
}
Future<Ed25519HDKeyPair> _generateKeyPair() async {
final privateKey = await Web3AuthFlutter.getEd25519PrivKey();
return await Ed25519HDKeyPair.fromPrivateKeyBytes(
privateKey: privateKey.hexToBytes.take(32).toList(),
);
}
Future<dynamic> readContract(
String address,
String function,
List<dynamic> params,
) {
// TODO: implement readContract
throw UnimplementedError();
}
Future writeContract(String address, String function, List params) {
// TODO: implement writeContract
throw UnimplementedError();
}
}
Set up Supported Chains
After having our blockchain proivders in place, the next step on the list to define the supported
chains. To keep things simple, we'll simply a create a new file chain_configs
with list of Map to
define the supported chains.
For the guide, we have added the support for Ethereum Sepolia, Ethereum Mainnet, Polygon Mainnet, Polygon Amoy, and Solana devnet. If you wish to support more chains in your wallet, you can simply add the config with the required details in the list below.
import 'package:web3auth_flutter/enums.dart';
final chainConfigs = [
{
"chainNamespace": ChainNamespace.eip155.name,
"chainId": "0xaa36a7",
"displayName": "Ethereum Sepolia",
"ticker": "ETH",
"rpcTarget": "https://rpc.ankr.com/eth_sepolia",
"blockExplorerUrl": "https://sepolia.etherscan.io",
"logo": "https://web3auth.io/images/web3authlog.png",
"wss": '',
},
{
"chainNamespace": ChainNamespace.eip155.name,
"chainId": "0x1",
"displayName": "Ethereum Mainnet",
"rpcTarget": "https://rpc.ankr.com/eth",
"blockExplorerUrl": "https://etherscan.io",
"ticker": "ETH",
"logo": "https://web3auth.io/images/web3authlog.png",
"wss": '',
},
{
"chainNamespace": ChainNamespace.eip155.name,
"chainId": "0x89",
"rpcTarget": "https://rpc.ankr.com/polygon",
"displayName": "Polygon Mainnet",
"blockExplorerUrl": "https://polygonscan.com",
"ticker": "POL",
"logo": "https://web3auth.io/images/web3authlog.png",
"wss": '',
},
{
"chainNamespace": ChainNamespace.eip155.name,
"chainId": "80002",
"rpcTarget": "https://rpc-amoy.polygon.technology",
"displayName": "Polygon Amoy Testnet",
"blockExplorerUrl": "https://www.oklink.com/amoy",
"ticker": "POL",
"logo": "https://web3auth.io/images/web3authlog.png",
"wss": '',
},
{
"chainNamespace": ChainNamespace.solana.name,
"chainId": "devnet",
"rpcTarget": "https://api.devnet.solana.com",
"displayName": "Solana Devnet",
"blockExplorerUrl": "https://explorer.solana.com/?cluster=devnet/",
"ticker": "SOL",
"logo": "https://web3auth.io/images/web3authlog.png",
"wss": "ws://api.devnet.solana.com"
},
];
Once, we have defined the supported chains, create a new model ChainConfig
, to represent the Dart
object for the above chain config map. We'll use the ChainConfig
model for UI purposes and chain
interaction.
In the ChainConfig,
we'll also add a isEVM
parameter to help us differentiate the selected chain
ecosystem. If isEVM
is true for the selected chain, we can use EthereumProvider
for chain
interactions, or else we can use the SolanaProvider
.
import 'package:flutter_playground/features/home/domain/entities/chain_config.dart';
import 'package:web3auth_flutter/enums.dart';
class ChainConfigModel extends ChainConfig {
ChainConfigModel({
required super.chainNamespace,
required super.displayName,
required super.ticker,
required super.rpcTarget,
required super.logo,
required super.blockExplorerUrl,
required super.chainId,
required super.isEVMChain,
required super.wss,
});
factory ChainConfigModel.fromJson(Map<String, String> json) {
final nameSpace = ChainNamespace.values.byName(json['chainNamespace']!);
final isEVM = nameSpace == ChainNamespace.eip155;
return ChainConfigModel(
isEVMChain: isEVM,
chainNamespace: nameSpace,
displayName: json['displayName']!,
ticker: json['ticker']!,
rpcTarget: json['rpcTarget']!,
logo: json['logo'],
blockExplorerUrl: json['blockExplorerUrl']!,
chainId: json['chainId']!,
wss: json['wss']!,
);
}
}
Wallet Implementation
Once, we have set up the providers, and supported chains, it's time to integrate and plug them into
the wallet. For this guide, we are using the get_it
package for service locator abilities. It will
help us with the dependency injection.
Service Locator
Let's create a new ServiceLocator
class, and set up the ChainConfigDataSource
and
ChainConfigRepository
. The ChainConfigRepository
is responsible for converting the list of chain
configs map we defined earlier into a list of ChainConfig
models and inject into UI. As said
earlier, for simplicity we are maintaining the list of chain configs on the frontend, but using
ChainConfigRepository
you can get the list from the server as well.
Checkout the implementation of ChainConfigDataSource
and ChainConfigRepository
for more details.
class ServiceLocator {
ServiceLocator._();
static GetIt get getIt => GetIt.instance;
static void setUp() {
getIt.registerLazySingleton<ChainConfigDataSource>(
() => ChainConfigDataSourceImpl(chainConfigs: chainConfigs),
);
getIt.registerLazySingleton<ChainConfigRepository>(
() => ChainConfigRepositoryImp(getIt()),
);
}
}
After successfully setting up the ServiceLocator
, initialize it in the main
function above
Web3AuthFlutter
initiliation.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
ServiceLocator.setUp();
// Additional Web3AuthFlutter initiliation code.
}
Set up Home Provider
Once we have set up service locator, the next on list is to create a ChangeNotifier
to help us
maange the state of the wallet. The notifier will help us manage the state of currently selected
chain, and access the respective chain provider. For the state management, we will be using the
provider
package, so make sure to add provider
as a dependency.
class HomeProvider with ChangeNotifier {
late ChainConfig _selectedChain;
late List<ChainConfig> _chains;
late String _chainAddress;
ChainConfig get selectedChain => _selectedChain;
List<ChainConfig> get chains => _chains;
String get chainAddress => _chainAddress;
HomeProvider(List<ChainConfig> chains) {
_selectedChain = chains.first;
_chains = List.from(chains);
}
/// Update the selected chain
void updateSelectedChain(ChainConfig chain) {
_selectedChain = chain;
notifyListeners();
}
/// Update the chain address for corresponding
/// selected chain.
void updateChainAddress(String address) {
_chainAddress = address;
}
/// Add a new custom EVM chain on runtime.
void addNewChain(ChainConfig newChain) {
_chains.add(newChain);
notifyListeners();
}
}
To access the blockchain provider for currently selected chain, we will create a new extension on
ChainConfig
.
extension ChainConfigExtension on ChainConfig {
ChainProvider prepareChainProvider() {
if (isEVMChain) {
return EthereumProvider(rpcTarget: rpcTarget);
} else {
return SolanaProvider(rpcTarget: rpcTarget, wss: wss);
}
}
}
Setting up Home screen
Once, we have our provider ready, we create a new HomeScreen
widget to show user details as email
address, wallet address, user's balance for selectedChain, and blockchain interaction methods. We'll
retrieve the ChainConfigRepository
using ServiceLocator
, and initialize our HomeProvider
.
To get the user's balance, we'll use prepareAccount
method from the ChainConfigRepository
. The
method internally uses ChainProvider
to retrieve user's wallet address, and fetch the wallet
balance for the address. Checkout ChainConfigRepository
implementation for more details. The
methods returns Account
object which has the above details.
Checkout Account
data model below.
class Account {
final Ed25519HDKeyPair? solanaKeyPair;
final Credentials? ethereumKeyPair;
final String balance;
final String publicAddress;
Account({
this.solanaKeyPair,
this.ethereumKeyPair,
required this.balance,
required this.publicAddress,
});
}
Once, we have retrieve the ChainConfigRepository
in init
method of HomeScren
, we'll invoke the
prepareAccount
, and pass the Account
instance to StreamController
which is used for data flow
in the application.
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
late final ChainConfigRepository chainConfigRepository;
late final TorusUserInfo userInfo;
late final StreamController<Account> streamController;
late final HomeProvider homeProvider;
void initState() {
super.initState();
chainConfigRepository = ServiceLocator.getIt<ChainConfigRepository>();
streamController = StreamController<Account>();
homeProvider = Provider.of<HomeProvider>(
context,
listen: false,
);
loadAccount(false);
}
void dispose() {
super.dispose();
}
// loadAccount function is used to fetch the account
// details such as balance, user address, and private key
// for currently selected chain.
Future<void> loadAccount(bool isReload) async {
if (!isReload) {
userInfo = await Web3AuthFlutter.getUserInfo();
}
final account = await chainConfigRepository.prepareAccount(
homeProvider.selectedChain,
);
homeProvider.updateChainAddress(account.publicAddress);
// We streamController to control data flow in the application.
streamController.add(account);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(StringConstants.appBarTitle),
),
drawer: const SideDrawer(),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16),
child: StreamBuilder<Account>(
stream: streamController.stream,
builder: (context, snapShot) {
// Check if the AsyncSnapshot is in active connection,
// and if it's true, build the UI.
if (snapShot.connectionState == ConnectionState.active) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 24),
const HomeHeader(),
const SizedBox(height: 12),
// Helps users to switch chain in the wallet.
ChainSwitchTile(
onSelect: (chainConfig) {
homeProvider.updateSelectedChain(chainConfig);
loadAccount(true);
},
),
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 16),
// Displays user details, such as email,
// user name, and logo.
AccountDetails(
userInfo: userInfo,
account: snapShot.requireData,
),
const SizedBox(height: 24),
Consumer<HomeProvider>(builder: (
_,
homeProvider,
__,
) {
final chain = homeProvider.selectedChain;
// Displays user balance.
return BalanceWidget(
balance: snapShot.data!.balance,
ticker: chain.ticker,
chainId: chain.chainId,
);
}),
const SizedBox(height: 16),
Consumer<HomeProvider>(builder: (_, __, ___) {
return Column(
children: [
CustomTextButton(
onTap: () {
_navigationToScreen(
context,
const TransactionsScreen(),
);
},
text: 'Transaction',
),
// Disable the SmartContractInteractionScreen for
// non evm chains.
if (homeProvider.selectedChain.isEVMChain) ...[
const SizedBox(height: 16),
CustomTextButton(
onTap: () {
_navigationToScreen(
context,
const SmartContractInteractionScreen(),
);
},
text:
StringConstants.smartContractInteractionsText,
),
]
],
);
}),
],
),
);
}
return const Center(child: CircularProgressIndicator.adaptive());
},
),
),
);
}
// Helper function to navigate to different screens.
void _navigationToScreen(BuildContext context, Widget screen) {
Navigator.of(context).push(MaterialPageRoute(builder: (_) {
return screen;
}));
}
}
In HomeScreen
we'll also give users an option to logout from the wallet in navigation drawer. To
do so, we'll utilize the Web3AuthFlutter.logout
. Upon success, we'll navigate users back to
LoginScreen
. Checkout SideDrawer
widget for navigation drawer implementation.
Chain Interactions
Once we have setup HomeScreen
, the next step is to setup chain interactions for signing message,
signing transaction, reading from contracts, and writing on contracts. For signing message and
transaction, we'll create a new TransactionsScreen
widget and utilize signMessage
and
sendTransaction
from ChainProvider
for respective functionality.
To retrieve currently selected chain, and respective provider we'll use the HomeProvider
.
class TransactionsScreen extends StatefulWidget {
const TransactionsScreen({super.key});
State<TransactionsScreen> createState() => _TransactionsScreenState();
}
class _TransactionsScreenState extends State<TransactionsScreen> {
// Additional variable initiliation
void initState() {
super.initState();
selectedChain = context.read<HomeProvider>().selectedChain;
chainProvider = selectedChain.prepareChainProvider();
// Additional code
}
Widget build(BuildContext context) {
// Additiona UI code.
// Checkout GitHub repo for full code.
}
Future<void> _signMessage(BuildContext context) async {
try {
showLoader(context);
final signature = await chainProvider.signMessage(
signMessageTextController.text,
);
if (context.mounted) {
removeDialog(context);
showInfoDialog(context, signature);
}
} catch (e, _) {
log(e.toString(), stackTrace: _);
if (context.mounted) {
removeDialog(context);
showInfoDialog(context, e.toString());
}
}
}
Future<void> _sendTransaction(BuildContext context) async {
try {
showLoader(context);
final amount = double.parse(amountTextController.text);
final hash = await chainProvider.sendTransaction(
destinationTextController.text,
amount,
);
if (context.mounted) {
removeDialog(context);
showInfoDialog(context, hash);
}
} catch (e, _) {
log(e.toString(), stackTrace: _);
if (context.mounted) {
removeDialog(context);
showInfoDialog(context, e.toString());
}
}
}
}
Conclusion
Voila, you have build a chain agnostic Web3 wallet. This guide only gives you an overview of how to create your wallet with EVM and Solana ecosystem support. The general idea of the guide can be used for any of the blockchain ecosystem.
If you are interested in learning more about Web3Auth, please checkout our documentation for Flutter. You can find the code used for the guide on our examples repo.