> For the complete documentation index, see [llms.txt](/llms.txt).

# Create a chain-agnostic web3 wallet in Flutter

`embedded wallets` `flutter` `android` `ios` `evm` `solana` `web3auth`MetaMask Developer Relations | April 22, 2024

Open in Claude

In this guide, we'll use the MetaMask Embedded Wallets SDK (formerly Web3Auth) to build your chain-agnostic web3 wallet in Flutter. The wallet will support the Ethereum and Solana ecosystems.

You'll create a demo app which supports user login, displays user details, and performs blockchain interactions. Blockchain transaction signing is done through the Embedded Wallets SDK.

tip

See the [Embedded Wallets management infrastructure](/embedded-wallets/infrastructure/) for a high-level overview of the Embedded Wallets management infrastructure, architecture, and implementation. For those who want to skip straight to the code, find it on [GitHub](https://github.com/Web3Auth/web3auth-flutter-examples/tree/main/flutter-playground).

This is what your application will look like:

![Flutter Wallet Screenshots](/assets/images/flutter-wallet-preview-9e063bd9d84b30b3a89d6087038e68e4.png) 

## Step 1: Set up the Embedded Wallets dashboard[​](#step-1-set-up-the-embedded-wallets-dashboard "Direct link to Step 1: Set up the Embedded Wallets dashboard")

Sign up on the [Embedded Wallets dashboard](https://developer.metamask.io/). It's free and gives you access to the base plan.

tip

Consider exploring other features and functionalities offered by the Embedded Wallets dashboard. It includes custom verifiers, whitelabeling, analytics, and more. Head to the [Embedded Wallet documentation](/embedded-wallets/dashboard/) page for detailed instructions on setting up the dashboard.

## Step 2: Integrate Embedded Wallets in Flutter[​](#step-2-integrate-embedded-wallets-in-flutter "Direct link to Step 2: Integrate Embedded Wallets in Flutter")

Once you have set up your Embedded Wallets dashboard, and created a new project, it's time to integrate Web3Auth SDK in your Flutter application. For the implementation, we'll use the "web3auth_flutter package" to manage an Embedded Wallet in your Flutter application.

### 2.1 Installation[​](#21-installation "Direct link to 2.1 Installation")

To install the web3auth_flutter package, you have two options. Either manually add the package in the `pubspec.yaml` file, or 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: ^6.1.2

```

### 2.2 Initialization[​](#22-initialization "Direct link to 2.2 Initialization")

Initialize Web3Auth in your Flutter app. This sets up the necessary configurations using Client ID and prepares Web3Auth.

```
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Additional 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", // Pass your Web3Auth Client ID, ideally using an environment variable
      network: Network.sapphire_mainnet,
      redirectUrl: redirectUrl,
    ),
  );

  await Web3AuthFlutter.initialize();

  runApp(const MainApp());
}

```

tip

Learn more about [Web3Auth initialization](/embedded-wallets/sdk/flutter/#initialize-web3auth).

### 2.3 Session management[​](#23-session-management "Direct link to 2.3 Session management")

To check whether the user is authenticated, you can use the `getPrivateKey` or `getEd25519PrivKey` method. For an authenticated user, 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.

tip

[Learn more about Web3Auth session management](/embedded-wallets/features/session-management/).

```
class MainApp extends StatefulWidget {
  const MainApp({super.key});

  @override
  State<MainApp> createState() => _MainAppState();
}

class _MainAppState extends State<MainApp> {
  late final Future<String> privateKeyFuture;
  @override
  void initState() {
    super.initState();
    privateKeyFuture = Web3AuthFlutter.getEd25519PrivKey();
  }

  @override
  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(),
          );
        },
      ),
    );
  }
}

```

### 2.4 Authentication[​](#24-authentication "Direct link to 2.4 Authentication")

If the user isn't authenticated, call the `login` method. Provide two sign-in options for the wallet: Google and passwordless email login. Embedded Wallets supports two authentication flows: single page authentication, and regular web application. In this guide, you use the single-page authentication flow. We'll create a helper function, `_login` inside `LoginScreen`. The login method `LoginParams` as input. After a successful sign-in, navigate the user to `HomeScreen`.

tip

Learn more about [Embedded Wallets' LoginParams](/embedded-wallets/sdk/flutter/usage/login/#parameters).

```
class _LoginScreenState extends State<LoginScreen> with WidgetsBindingObserver {
  // Additional Code

  @override
  void didChangeAppLifecycleState(final AppLifecycleState state) {
    // This is important to trigger the user cancellation on Android.
    if (state == AppLifecycleState.resumed) {
      Web3AuthFlutter.setCustomTabsClosed();
    }
  }

  @override
  Widget build(BuildContext context) {
    // Login View
  }

  Future<void> _login(BuildContext context) async {
    try {
      // Validate the form, and TextField. In case of invalid
      // 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());
      }
    }
  }
}

```

## Step 3: Set up blockchain providers[​](#step-3-set-up-blockchain-providers "Direct link to Step 3: Set up blockchain providers")

Fetch the user details, retrieve the wallet address, and prepare providers for onchain interactions. This guide covers Ethereum and Solana, but you can apply the same approach to other ecosystems.

Because the project follows clean architecture and test-driven development (TDD) principles, create an abstract layer for interacting with blockchain providers. This abstraction isolates provider logic from the rest of the app and makes it easier to add support for additional blockchains.

For interacting with Ethereum chains, use the [web3dart](https://pub.dev/packages/web3dart) package. For Solana, use the [Solana](https://pub.dev/packages/solana) package.

3.1 To install the packages, you have two options. Either manually add the packages in the `pubspec.yaml` file, or 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

```

3.2 Set up your blockchain provider. Create a base class named `ChainProvider` for `EthereumProvider` and `SolanaProvider`. To support additional ecosystems, extend `ChainProvider` and implement the required methods.

note

Learn how to integrate different blockchains with Embedded Wallets from the [Connect Blockchain resources](/embedded-wallets/connect-blockchain/).

```
abstract class ChainProvider {
  Future<String> getBalance(String address);
  Future<String> sendTransaction(String to, double amount);
  Future<String> signMessage(String message);
  Future<dynamic> readContract(
    String address,
    String function,
    List<dynamic> params,
  );

  Future<dynamic> writeContract(
    String address,
    String function,
    List<dynamic> params,
  );
}

```

Typically, when interacting with blockchain providers, you'll only require the `getBalance`, `sendTransaction`, and `signMessage`.

### Ethereum provider[​](#ethereum-provider "Direct link to Ethereum provider")

With our base class in place, we'll create `EthereumProvider` and implement the methods. To create the `Web3Client` instance, you'll require the RPC target URL.

note

If you are using public RPCs, you could face some network congestion. Ideally, use [paid RPCs](/services/) for production.

The `readContract`, and `writeContract` methods are used to interact with smart contracts on Ethereum ecosystem. As the names suggest, `readContract` is used to read the data from the smart contracts, while 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(),
        );

  @override
  Future<String> getBalance(String address) async {
    final balance = await web3client.getBalance(
      EthereumAddress.fromHex(address),
    );

    // The result 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);
  }

  @override
  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;
  }

  @override
  Future<String> signMessage(String message) async {
    final Credentials credentials = await _prepareCredentials();
    final signBytes = credentials.signPersonalMessageToUint8List(
      Uint8List.fromList(message.codeUnits),
    );

    return bytesToHex(signBytes);
  }

  // Prepares the Credentials used for signing the message,
  // and transaction on EVM chains. EVM ecosystem uses the
  // secp256k1 curve. You can use the Web3AuthFlutter.getPrivKey
  // to retrieve the secp256k1 compatible private key.
  Future<Credentials> _prepareCredentials() async {
    final privateKey = await Web3AuthFlutter.getPrivKey();
    final Credentials credentials = EthPrivateKey.fromHex(privateKey);
    return credentials;
  }

  @override
  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;
  }

  @override
  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[​](#solana-provider "Direct link to Solana provider")

Extend `ChainProvider` and create `SolanaProvider`. In `SolanaProvider`, only implement the `getBalance`, `sendTransaction`, and `signMessage` methods. Add `_generateKeyPair()`to generate an `Ed25519HDKeyPair`which `SolanaProvider` uses to sign transactions and messages in the Solana ecosystem. Because Solana uses the `ed25519` curve, we can use `Web3AuthFlutter.getEd25519PrivKey` to retrieve the private key.

```
class SolanaProvider extends ChainProvider {
  final SolanaClient solanaClient;

  SolanaProvider({required String rpcTarget, required String wss})
      : solanaClient = SolanaClient(
          rpcUrl: Uri.parse(rpcTarget),
          websocketUrl: Uri.parse(wss),
        );

  @override
  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();
  }

  @override
  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;
  }

  @override
  Future<String> signMessage(String message) async {
    final Ed25519HDKeyPair ed25519hdKeyPair = await _generateKeyPair();

    final signature = await ed25519hdKeyPair.sign(
      ByteArray.fromString(message),
    );
    return signature.toBase58();
  }

  Future<Ed25519HDKeyPair> _generateKeyPair() async {
    final privateKey = await Web3AuthFlutter.getEd25519PrivKey();
    return await Ed25519HDKeyPair.fromPrivateKeyBytes(
      privateKey: privateKey.hexToBytes.take(32).toList(),
    );
  }

  @override
  Future<dynamic> readContract(
    String address,
    String function,
    List<dynamic> params,
  ) {
    // TODO: implement readContract
    throw UnimplementedError();
  }

  @override
  Future writeContract(String address, String function, List params) {
    // TODO: implement writeContract
    throw UnimplementedError();
  }
}

```

## Step 4: Set up supported chains[​](#step-4-set-up-supported-chains "Direct link to Step 4: Set up supported chains")

### 4.1 Define the supported chains[​](#41-define-the-supported-chains "Direct link to 4.1 Define the supported chains")

Define the supported chains with a `chain_configs` file that uses "list of map" to define the supported chains.

For this guide we will support Ethereum Sepolia, Ethereum Mainnet, Polygon Mainnet, Polygon Amoy, and Solana Devnet using the Ethereum and Solana providers. If you wish to support more EVM-compatible chains in your wallet, 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://ethereum-sepolia.publicnode.com",
    "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.ethereum.org",
    "blockExplorerUrl": "https://etherscan.io",
    "ticker": "ETH",
    "logo": "https://web3auth.io/images/web3authlog.png",
    "wss": '',
  },
  {
    "chainNamespace": ChainNamespace.eip155.name,
    "chainId": "0x89",
    "rpcTarget": "https://polygon-rpc.com",
    "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"
  },
];

```

### 4.2 Create a model to represent the Dart object[​](#42-create-a-model-to-represent-the-dart-object "Direct link to 4.2 Create a model to represent the Dart object")

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']!,
    );
  }
}

```

## Step 5: Wallet implementation[​](#step-5-wallet-implementation "Direct link to Step 5: Wallet implementation")

Integrate the providers and supported chains 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.

### 5.1 Service locator[​](#51-service-locator "Direct link to 5.1 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 configurations 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 configurations on the frontend, but using `ChainConfigRepository` you can get the list from the server as well.

See 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` initialization.

```
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  ServiceLocator.setUp();

  // Additional Web3AuthFlutter initiation code.
}

```

### 5.2 Set up the home provider[​](#52-set-up-the-home-provider "Direct link to 5.2 Set up the home provider")

Create a `ChangeNotifier` to help us manage 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);
    }
  }
}

```

### 5.3 Set up the home screen[​](#53-set-up-the-home-screen "Direct link to 5.3 Set up the home screen")

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`.

5.3.1 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.

See the `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,
  });
}

```

5.3.2 Once, we have retrieved the `ChainConfigRepository` in `init` method of `HomeScreen`, 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});

  @override
  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;

  @override
  void initState() {
    super.initState();
    chainConfigRepository = ServiceLocator.getIt<ChainConfigRepository>();

    streamController = StreamController<Account>();
    homeProvider = Provider.of<HomeProvider>(
      context,
      listen: false,
    );
    loadAccount(false);
  }

  @override
  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);
  }

  @override
  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;
    }));
  }
}

```

5.3.3 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.

### 5.4 Chain interactions[​](#54-chain-interactions "Direct link to 5.4 Chain interactions")

Set up chain interactions for signing message, signing transaction, reading from contracts, and writing on contracts. For signing messages and transactions, 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});

  @override
  State<TransactionsScreen> createState() => _TransactionsScreenState();
}

class _TransactionsScreenState extends State<TransactionsScreen> {
  // Additional variable initiation

  @override
  void initState() {
    super.initState();
    selectedChain = context.read<HomeProvider>().selectedChain;
    chainProvider = selectedChain.prepareChainProvider();
    // Additional code
  }

  @override
  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());
      }
    }
  }
}

```

Congratulations, you have built a chain agnostic web3 wallet. While this guide only gives you an overview of how to create your wallet with EVM and Solana ecosystem support, the general idea can be used for any blockchain ecosystem.

## Next steps[​](#next-steps "Direct link to Next steps")

- Learn more about Embedded Wallets, checkout our [documentation for Flutter](/embedded-wallets/sdk/flutter/).
- Find the code used for the guide on our [examples repo](https://github.com/Web3Auth/web3auth-flutter-examples/tree/main/flutter-playground).

[Share](https://www.facebook.com/sharer/sharer.php?https://metamask.io/tutorials/flutter-wallet)[Tweet](http://twitter.com/share?text=Checkout Create a chain-agnostic web3 wallet in Flutter published by @MetaMask&url=https://metamask.io/tutorials/flutter-wallet)Copy

On this page
- [Step 1: Set up the Embedded Wallets dashboard](#step-1-set-up-the-embedded-wallets-dashboard)
- [Step 2: Integrate Embedded Wallets in Flutter](#step-2-integrate-embedded-wallets-in-flutter)
  - [2.1 Installation](#21-installation)
  - [2.2 Initialization](#22-initialization)
  - [2.3 Session management](#23-session-management)
  - [2.4 Authentication](#24-authentication)
- [Step 3: Set up blockchain providers](#step-3-set-up-blockchain-providers)
  - [Ethereum provider](#ethereum-provider)
  - [Solana provider](#solana-provider)
- [Step 4: Set up supported chains](#step-4-set-up-supported-chains)
  - [4.1 Define the supported chains](#41-define-the-supported-chains)
  - [4.2 Create a model to represent the Dart object](#42-create-a-model-to-represent-the-dart-object)
- [Step 5: Wallet implementation](#step-5-wallet-implementation)
  - [5.1 Service locator](#51-service-locator)
  - [5.2 Set up the home provider](#52-set-up-the-home-provider)
  - [5.3 Set up the home screen](#53-set-up-the-home-screen)
  - [5.4 Chain interactions](#54-chain-interactions)
- [Next steps](#next-steps)
