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

# Integrate Embedded Wallets with the Solana Blockchain in Flutter

While using the Embedded Wallets Flutter SDK (formerly Web3Auth), you can retrieve the Ed25519 private key upon successful authentication. This private key can be used to derive the user's public address and interact with the [Solana](https://solana.org/) chain. We've highlighted a few methods here to get you started quickly.

note

The SDKs are now branded as MetaMask Embedded Wallet SDKs (formerly Web3Auth Plug and Play SDKs). Package names and APIs remain Web3Auth (for example, Web3Auth React SDK), and code snippets may reference `web3auth` identifiers.

## Chain details for Solana[​](#chain-details-for-solana "Direct link to Chain details for Solana")

- Mainnet
- Testnet
- Devnet

- Chain Namespace: SOLANA
- Chain ID: 0x1
- Public RPC URL: `https://api.mainnet-beta.solana.com` (avoid public RPC in production; prefer managed services)
- Display Name: Solana Mainnet
- Block Explorer Link: `https://explorer.solana.com`
- Ticker: SOL
- Ticker Name: Solana
- Logo: <https://images.toruswallet.io/solana.svg>

- Chain Namespace: SOLANA
- Chain ID: 0x2
- Public RPC URL: `https://api.testnet.solana.com` (avoid public RPC in production; prefer managed services)
- Display Name: Solana Testnet
- Block Explorer Link: `https://explorer.solana.com`
- Ticker: SOL
- Ticker Name: Solana
- Logo: <https://images.toruswallet.io/solana.svg>

- Chain Namespace: SOLANA
- Chain ID: 0x3
- Public RPC URL: `https://api.devnet.solana.com` (avoid public RPC in production; prefer managed services)
- Display Name: Solana Devnet
- Block Explorer Link: `https://explorer.solana.com?cluster=devnet`
- Ticker: SOL
- Ticker Name: Solana
- Logo: <https://images.toruswallet.io/solana.svg>

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

To interact with the Solana blockchain in Flutter, you can use any Solana compatible package. Here, we're using [solana](https://pub.dev/packages/solana) to demonstrate how to interact with Solana chain using Web3Auth.

```
flutter pub add solana get_it hex

```

note

We will also be using `get_it` package for dependency injection, and `hex` package to perform Hex encoding and decoding.

## Initialize[​](#initialize "Direct link to Initialize")

To Initialize the `SolanaClient` we require `rpcUrl` and `websocketUrl`. The `rpcUrl` will provide a gateway and protocol to interact with Solana cluster while sending requests and receving response. For this example, we are using `rpcUrl` and `websocketUrl` for Devnet-beta. To interact with Testnet or Mainnet, change the `rpcUrl` and `websocketUrl`.

### Initializing the Solana SDK[​](#initializing-the-solana-sdk "Direct link to Initializing the Solana SDK")

In the following code block, we'll initialize the `SolanaClient` using the Devnet-beta RPC and websocket URLs. We'll also register the instance in GetIt for it to be accessed anywhere using service locator.

```
import 'package:flutter_solana_example/core/solana/solana_provider.dart';
import 'package:get_it/get_it.dart';
import 'package:solana/solana.dart';

class ServiceLocator {
  ServiceLocator._();

  static GetIt get getIt => GetIt.instance;

  static Future<void> init() async {
    final solanaClient = SolanaClient(
      rpcUrl: Uri.parse('https://api.devnet.solana.com'),
      websocketUrl: Uri.parse('ws://api.devnet.solana.com'),
    );

    // Register SolanaClient to be accessed using service locator
    getIt.registerLazySingleton<SolanaClient>(() => solanaClient);
  }
}

```

### Initializing Web3Auth[​](#initializing-web3auth "Direct link to Initializing Web3Auth")

In the following code block, we'll initialize the Web3Auth SDK and check whether the user has any Web3Auth session persisted or not. If the user is already authenticated, we can route them directly to `HomeScreen`, otherwise we can route them to `LoginScreen`.

By default, the session is persisted for 1 day. You can modify it using `sessionTime` parameter during initialization.

note

The session can be persisted for up to 30 days max.

```
// Additional imports
// ...
import 'package:web3auth_flutter/web3auth_flutter.dart';

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

  // Initialize ServiceLocator
  ServiceLocator.init();

  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:
          "BHgArYmWwSeq21czpcarYh0EVq2WWOzflX-NTK-tY1-1pauPzHKRRLgpABkmYiIV_og9jAvoIxQ8L3Smrwe04Lw",
      network: Network.sapphire_devnet,
      redirectUrl: redirectUrl,
    ),
  );


  runApp(const MainApp());
}

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) {
              // Check if user is already authenticated. If user is already
              // authenticated the snapshot.data will be non empty string
              // representing the private key used for Solana ecosystem.
              if (snapshot.data!.isNotEmpty) {
                return const HomeScreen();
              }
            }
            return const LoginScreen();
          }
          return const Center(
            child: CircularProgressIndicator.adaptive(),
          );
        },
      ),
    );
  }
}

```

## Get account and Balance[​](#get-account-and-balance "Direct link to Get account and Balance")

We can use `getEd25519PrivKey` method in Web3Auth to retrive the priavte key for the Solana ecosystem. In the following code block, we'll use the Ed25519 private key to retive user's public address, and Solana balance. We'll use `SolanaProvider` class to interact with Solana cluster and fetch user balance.

### Set up Solana provider[​](#set-up-solana-provider "Direct link to Set up Solana provider")

In the following code block, we'll create Solana provider to interact and perform Solana operations.

```
import 'dart:math';

import 'package:solana/dto.dart';
import 'package:solana/solana.dart';

const int tokenDecimals = 9;

class SolanaProvider {
  final SolanaClient solanaClient;

  SolanaProvider(this.solanaClient);

  Future<double> 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, tokenDecimals);
  }

   // ..
  // Additional methods
  // ..
}

```

Once we have setup the `SolanaProvider` we'll use it to fetch user balance in the `HomeScreen`.

```
// ..
// Additional code
// ..
class _HomeScreenState extends State<HomeScreen> {
  late final ValueNotifier<bool> isAccountLoaded;
  late final Ed25519HDKeyPair keyPair;
  late final SolanaProvider provider;
  late double balance;
  late final dynamic web3AuthInfo;

  @override
  void initState() {
    super.initState();
    isAccountLoaded = ValueNotifier<bool>(false);
    provider = ServiceLocator.getIt<SolanaProvider>();
    loadAccount(context);
  }

  Future<void> loadAccount(BuildContext context) async {
    try {
      final privateKey = await Web3AuthFlutter.getEd25519PrivKey();

      // ..
      // Additional code
      // ..

      /// The ED25519 PrivateKey returns a key pair from
      /// which we only require first 32 byte.
      keyPair = await Ed25519HDKeyPair.fromPrivateKeyBytes(
        privateKey: privateKey.hexToBytes.take(32).toList(),
      );
      balance = await provider.getBalance(keyPair.address);
      isAccountLoaded.value = true;
    } catch (e, _) {
      if (context.mounted) {
        showInfoDialog(context, e.toString());
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ValueListenableBuilder<bool>(
        valueListenable: isAccountLoaded,
        builder: (context, isLoaded, _) {
          if (isLoaded) {
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(
                    balance.toString(),
                    style: Theme.of(context).textTheme.displaySmall,
                  ),
                  // ..
                  // Additional code
                  // ..
                  Row(
                    children: [
                      const Spacer(),
                      Text(
                        keyPair.address.addressAbbreviation,
                        style: Theme.of(context).textTheme.bodyLarge,
                      ),
                      const SizedBox(
                        width: 4,
                      ),
                      IconButton(
                        onPressed: () {
                          copyContentToClipboard(context, keyPair.address);
                        },
                        icon: const Icon(Icons.copy),
                      ),
                      const Spacer(),
                    ],
                  ),
                  // ..
                  // Additional code
                  // ..
                ],
              ),
            );
          }
          return const Center(child: CircularProgressIndicator.adaptive());
        },
      ),
    );
  }
}

```

## Sign a transaction[​](#sign-a-transaction "Direct link to Sign a transaction")

Let's now go through how can we sign the transaction. In the following code block, we'll add a new method inside `SolanaProvider` we setup earlier to help us sign a transfer transaction. After successful implementation, we can use the method in `HomeScreen`.

```
class SolanaProvider {
  final SolanaClient solanaClient;

  SolanaProvider(this.solanaClient);

  Future<String> signSendTransaction({
    required Ed25519HDKeyPair keyPair,
    required String destination,
    required double value,
  }) async {
    /// Converting user input to the lamports, which are smallest value
    /// in Solana.
    final num lamports = value * pow(10, tokenDecimals);

    final message = Message(instructions: [
      SystemInstruction.transfer(
        fundingAccount: keyPair.publicKey,
        recipientAccount: Ed25519HDPublicKey.fromBase58(destination),
        lamports: lamports.toInt(),
      ),
    ]);

    final recentBlockHash = await getRecentBlockhash();

    final signedTx = await signTransaction(recentBlockHash, message, [keyPair]);
    return signedTx.signatures.first.toBase58();
  }

  Future<RecentBlockhash> getRecentBlockhash() async {
    return await solanaClient.rpcClient
        .getRecentBlockhash(commitment: Commitment.finalized)
        .value;
  }
}

// HomeScreen code

// ..
// Additional code
// ..
class _HomeScreenState extends State<HomeScreen> {

  late final SolanaProvider provider;

  @override
  void initState() {
    super.initState();
    isAccountLoaded = ValueNotifier<bool>(false);
    provider = ServiceLocator.getIt<SolanaProvider>();
    loadAccount(context);
  }

  // ..
  // Additional code
  // ..

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // ..
      body: ValueListenableBuilder<bool>(
        valueListenable: isAccountLoaded,
        builder: (context, isLoaded, _) {
          if (isLoaded) {
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  OutlinedButton(
                    onPressed: () {
                      signSelfTransfer(context);
                    },
                    child: const Text(
                      "Sign Self transfer 0.0001 Sol",
                    ),
                  ),
                ],
              ),
            );
          }
          return const Center(child: CircularProgressIndicator.adaptive());
        },
      ),
    );
  }

  Future<void> signSelfTransfer(BuildContext context) async {
    showLoader(context);
    try {
      final signedMessage = await provider.signSendTransaction(
        keyPair: keyPair,
        destination: keyPair.address,
        value: 0.0001,
      );
      if (context.mounted) {
        removeDialog(context);
        showInfoDialog(context, "Signed message\n$signedMessage");
      }
    } catch (e, _) {
      if (context.mounted) {
        removeDialog(context);
        showInfoDialog(context, e.toString());
      }
    }
  }
}


```

## Send transaction[​](#send-transaction "Direct link to Send transaction")

For the send transaction, we'll create a new method `sendSol` in the `SolanaProvider`.

```
class SolanaProvider {
  final SolanaClient solanaClient;

  SolanaProvider(this.solanaClient);

  Future<String> sendSol({
    required Ed25519HDKeyPair keyPair,
    required String destination,
    required double value,
  }) async {
    /// Converting user input to the lamports, which are smallest value
    /// in Solana.
    final num lamports = value * pow(10, tokenDecimals);
    final transactionHash = await solanaClient.transferLamports(
      source: keyPair,
      destination: Ed25519HDPublicKey.fromBase58(destination),
      lamports: lamports.toInt(),
    );

    return transactionHash;
  }
}

```

Once created, we can use the method in `HomeScreen` to transfer SOL. Upon successful transfer, we'll also refresh the balance of the user.

```
// ..

class _HomeScreenState extends State<HomeScreen> {
  late final ValueNotifier<bool> isAccountLoaded;
  late final Ed25519HDKeyPair keyPair;
  late final SolanaProvider provider;
  late double balance;
  late final dynamic web3AuthInfo;

  @override
  void initState() {
    super.initState();
    isAccountLoaded = ValueNotifier<bool>(false);
    provider = ServiceLocator.getIt<SolanaProvider>();
    loadAccount(context);
  }

  Future<void> refreshBalance(BuildContext context) async {
    try {
      isAccountLoaded.value = false;
      balance = await provider.getBalance(keyPair.address);
      isAccountLoaded.value = true;
    } catch (e, _) {
      if (context.mounted) {
        showInfoDialog(context, e.toString());
      }
    }
  }

  Widget get verticalGap => const SizedBox(height: 16);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: const Icon(Icons.logout),
          onPressed: () {
            logOut(context);
          },
        ),
      ),
      body: ValueListenableBuilder<bool>(
        valueListenable: isAccountLoaded,
        builder: (context, isLoaded, _) {
          if (isLoaded) {
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  // ..
                  verticalGap,
                  OutlinedButton(
                    onPressed: () {
                      selfTransfer(context);
                    },
                    child: const Text(
                      "Self transfer 0.0001 Sol",
                    ),
                  ),
                  // ..
                ],
              ),
            );
          }
          return const Center(child: CircularProgressIndicator.adaptive());
        },
      ),
    );
  }

  Future<void> selfTransfer(BuildContext context) async {
    showLoader(context);
    try {
      final hash = await provider.sendSol(
        destination: keyPair.address,
        keyPair: keyPair,
        value: 0.0001,
      );
      if (context.mounted) {
        removeDialog(context);
        showInfoDialog(context, "Success: $hash");
        refreshBalance(context);
      }
    } catch (e, _) {
      if (context.mounted) {
        removeDialog(context);
        showInfoDialog(context, e.toString());
      }
    }
  }
}

```
