# Integrate Embedded Wallets with the Solana Blockchain in Flutter

> Integrate Embedded Wallets with the Solana Blockchain in Flutter | Embedded Wallets

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

<Tabs
 defaultValue="mainnet"
  values={[
    { label: "Mainnet", value: "mainnet" },
    { label: "Testnet", value: "testnet" },
    { label: "Devnet", value: "devnet" },
  ]}
>
<TabItem value="mainnet">

- 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

</TabItem>

<TabItem value="testnet">

- 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

</TabItem>

<TabItem value="devnet">

- 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

</TabItem>
</Tabs>

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

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

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

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.

```dart

// focus-next-line

class ServiceLocator {
  ServiceLocator._();

  static GetIt get getIt => GetIt.instance;

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

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

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

:::

```dart
// Additional imports
// ...

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

  // focus-start
  await Web3AuthFlutter.init(
    Web3AuthOptions(
      clientId:
          "BHgArYmWwSeq21czpcarYh0EVq2WWOzflX-NTK-tY1-1pauPzHKRRLgpABkmYiIV_og9jAvoIxQ8L3Smrwe04Lw",
      network: Network.sapphire_devnet,
      redirectUrl: redirectUrl,
    ),
  );

  // focus-end

  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) {
            // focus-start
            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();
            // focus-end
          }
          return const Center(
            child: CircularProgressIndicator.adaptive(),
          );
        },
      ),
    );
  }
}
```

## 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

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

```dart

const int tokenDecimals = 9;

class SolanaProvider {
  final SolanaClient solanaClient;

  SolanaProvider(this.solanaClient);

  // focus-start
  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);
  }
  // focus-end

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

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

```dart
// ..
// 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);
    // focus-next-line
    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.
      // focus-start
      keyPair = await Ed25519HDKeyPair.fromPrivateKeyBytes(
        privateKey: privateKey.hexToBytes.take(32).toList(),
      );
      balance = await provider.getBalance(keyPair.address);
      // focus-end
      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: [
                  // focus-start
                  Text(
                    balance.toString(),
                    style: Theme.of(context).textTheme.displaySmall,
                  ),
                  // focus-end
                  // ..
                  // Additional code
                  // ..
                  // focus-start
                  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(),
                    ],
                  ),
                  // focus-end
                  // ..
                  // Additional code
                  // ..
                ],
              ),
            );
          }
          return const Center(child: CircularProgressIndicator.adaptive());
        },
      ),
    );
  }
}
```

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

```dart
class SolanaProvider {
  final SolanaClient solanaClient;

  SolanaProvider(this.solanaClient);

  // focus-start
  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();
  }
  // focus-end

  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);
    // focus-next-line
    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: [
                  // focus-start
                  OutlinedButton(
                    onPressed: () {
                      signSelfTransfer(context);
                    },
                    child: const Text(
                      "Sign Self transfer 0.0001 Sol",
                    ),
                  ),
                  // focus-end
                ],
              ),
            );
          }
          return const Center(child: CircularProgressIndicator.adaptive());
        },
      ),
    );
  }

  // focus-start
  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());
      }
    }
  }
  // focus-end
}

```

## Send transaction

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

```dart
class SolanaProvider {
  final SolanaClient solanaClient;

  SolanaProvider(this.solanaClient);

  // focus-start
  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;
  }
  // focus-end
}
```

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

```dart
// ..

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,
                  // focus-start
                  OutlinedButton(
                    onPressed: () {
                      selfTransfer(context);
                    },
                    child: const Text(
                      "Self transfer 0.0001 Sol",
                    ),
                  ),
                  // focus-end
                  // ..
                ],
              ),
            );
          }
          return const Center(child: CircularProgressIndicator.adaptive());
        },
      ),
    );
  }

  // focus-start
  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());
      }
    }
  }
  // focus-end
}
```
