solana_wallet_provider 0.1.9 copy "solana_wallet_provider: ^0.1.9" to clipboard
solana_wallet_provider: ^0.1.9 copied to clipboard

Provider widget for solana_web3 and solana_wallet_adapter packages.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:solana_wallet_provider/solana_wallet_provider.dart';

void main() {
  runApp(const MaterialApp(
    home: ExampleApp()),
  );
}

class ExampleApp extends StatefulWidget {

  const ExampleApp({
    super.key,
  });

  @override
  State<ExampleApp> createState() => _ExampleAppState();
}

class _ExampleAppState extends State<ExampleApp> {

  /// Request status.
  String? _status;

  @override
  Widget build(final BuildContext context) {
    // 1. Wrap application with SolanaWalletProvider.
    return SolanaWalletProvider.create(      
      httpCluster: Cluster.devnet,                     
      identity: AppIdentity(
        uri: Uri.parse('https://my_dapp.com'),
        icon: Uri.parse('favicon.png'),
        name: 'My Dapp'
      ),
      child: MaterialApp(
        home: Scaffold(
          body: FutureBuilder(
            // 2. Initialize SolanaWalletProvider before use.
            future: SolanaWalletProvider.initialize(),            
            builder: ((context, snapshot) {
              if (snapshot.connectionState != ConnectionState.done) {
                return const CircularProgressIndicator();
              }
              // 3. Access SolanaWalletProvider.
              final provider = SolanaWalletProvider.of(context);

              return Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Text('Wallet Button'),
                  SolanaWalletButton(),

                  const Divider(),

                  const Text('Wallet Methods'),
                  Wrap(
                    spacing: 24.0,
                    children: [
                      _textButton(
                        'Connect', 
                        enabled: !provider.adapter.isAuthorized, 
                        onPressed: () => _connect(context, provider),
                      ),
                      _textButton(
                        'Disconnect', 
                        enabled: provider.adapter.isAuthorized, 
                        onPressed: () => _disconnect(context, provider),
                      ),

                      _textButton(
                        'Sign Transactions (1)', 
                        enabled: provider.adapter.isAuthorized, 
                        onPressed: () => _signTransactions(context, provider, 1),
                      ),
                      _textButton(
                        'Sign Transactions (3)', 
                        enabled: provider.adapter.isAuthorized, 
                        onPressed: () => _signTransactions(context, provider, 3),
                      ),

                      _textButton(
                        'Sign And Send Transactions (1)', 
                        enabled: provider.adapter.isAuthorized, 
                        onPressed: () => _signAndSendTransactions(context, provider, 1),
                      ),
                      _textButton(
                        'Sign And Send Transactions (3)', 
                        enabled: provider.adapter.isAuthorized, 
                        onPressed: () => _signAndSendTransactions(context, provider, 3),
                      ),

                      _textButton(
                        'Sign Messages (1)', 
                        enabled: provider.adapter.isAuthorized, 
                        onPressed: () => _signMessages(context, provider, 1),
                      ),
                      _textButton(
                        'Sign Messages (3)', 
                        enabled: provider.adapter.isAuthorized, 
                        onPressed: () => _signMessages(context, provider, 3),
                      ),
                    ],
                  ),

                  const Divider(),

                  const Text('Output'),
                  Text(_status ?? '-'),
                ],
              ); 
            }),
          ),
        ),
      ),
    );
  }

  /// Connects the application to a wallet running on the device.
  Future<void> _connect(
    final BuildContext context, 
    final SolanaWalletProvider provider,
  ) async {
    if (!provider.adapter.isAuthorized) {
      await provider.connect(context);
      setState(() {});
    }
  }

  /// Disconnects the application from a wallet running on the device.
  Future<void> _disconnect(
    final BuildContext context, 
    final SolanaWalletProvider provider,
  ) async {
    if (provider.adapter.isAuthorized) {
      await provider.disconnect(context);
      setState(() {});
    }
  }

  /// Signs [count] number of transactions (then sends them to the network for processing and 
  /// confirms the transaction results).
  void _signTransactions(
    final BuildContext context, 
    final SolanaWalletProvider provider, 
    final int count,
  ) async {
    final String description = "Sign Transactions ($count)";
    try {
      setState(() => _status = "Create $description...");
      final List<TransferData> transfers = await _createTransfers(
        provider.connection, provider.adapter, count: count,
      );

      setState(() => _status = "$description...");
      final SignTransactionsResult result = await provider.signTransactions(
        context,
        transactions: transfers.map((transfer) => transfer.transaction).toList(),
      );

      setState(() => _status = "Broadcast $description...");
      final List<String?> signatures = await provider.connection.sendSignedTransactions(
        result.signedPayloads,
        eagerError: true,
      );

      setState(() => _status = "Confirm $description...");
      await _confirmTransfers(
        provider.connection, 
        signatures: signatures.map((e) => base58To64Encode(e!)).toList(), 
        transfers: transfers, 
      );

    } catch (error, stack) {
      print('$description Error: $error');
      print('$description Stack: $stack');
      setState(() => _status = error.toString());
    }
  }

  /// Signs and send [count] number of transactions to the network (then confirms the transaction 
  /// results).
  void _signAndSendTransactions(
    final BuildContext context, 
    final SolanaWalletProvider provider, 
    final int count,
  ) async {
    final String description = "Sign And Send Transactions ($count)";
    try {
      setState(() => _status = "Create $description...");
      final List<TransferData> transfers = await _createTransfers(
        provider.connection, provider.adapter, count: count,
      );

      setState(() => _status = "$description...");
      final SignAndSendTransactionsResult result = await provider.signAndSendTransactions(
        context,
        transactions: transfers.map((transfer) => transfer.transaction).toList(),
      );

      setState(() => _status = "Confirm $description...");
      await _confirmTransfers(
        provider.connection, 
        signatures: result.signatures, 
        transfers: transfers, 
      );

    } catch (error, stack) {
      print('$description Error: $error');
      print('$description Stack: $stack');
      setState(() => _status = error.toString());
    }
  }

  void _signMessages(
    final BuildContext context, 
    final SolanaWalletProvider provider, 
    final int count, 
  ) async {
    final String description = "Sign Messages ($count)";
    try {
      setState(() => _status = "Create $description...");
      final List<String> messages = List.generate(
        count, 
        (index) => 'Sign message $index'
      );

      setState(() => _status = "$description...");
      final SignMessagesResult result = await provider.signMessages(
        context,
        messages: messages,
        addresses: [provider.adapter.encodeAccount(provider.adapter.connectedAccount!)],
      );

      setState(() => _status = "Signed Messages ${result.signedPayloads.join('\n')}");
      
    } catch (error, stack) {
      print('$description Error: $error');
      print('$description Stack: $stack');
      setState(() => _status = error.toString());
    }
  }

  /// Requests an airdrop of 2 SOL for [wallet].
  Future<void> _airdrop(final Connection connection, final Pubkey wallet) async {
    if (connection.httpCluster != Cluster.mainnet) {
      setState(() => _status = "Requesting airdrop...");
      await connection.requestAndConfirmAirdrop(wallet, solToLamports(2).toInt());
    }
  }

  /// Creates [count] number of SOL transfer transactions.
  Future<List<TransferData>> _createTransfers(
    final Connection connection, 
    final SolanaWalletAdapter adapter, {
    required final int count,
  }) async {
  
    // Check connected wallet.
    setState(() => _status = "Pending...");
    final Pubkey? wallet = Pubkey.tryFromBase64(adapter.connectedAccount?.address);
    if (wallet == null) {
      throw 'Wallet not connected';
    }

    // Airdrop some SOL to the wallet account if required.
    setState(() => _status = "Checking balance...");
    final int balance = await connection.getBalance(wallet);
    if (balance < lamportsPerSol) await _airdrop(connection, wallet);

    // Create a SystemProgram instruction to transfer some SOL.
    setState(() => _status = "Creating transaction...");
    final latestBlockhash = await connection.getLatestBlockhash();
    final List<TransferData> txs = [];
    for (int i = 0; i < count; ++i) {
      final Keypair receiver = Keypair.generateSync();
      final BigInt lamports = solToLamports(0.1);
      final Transaction transaction = Transaction.v0(
        payer: wallet,
        recentBlockhash: latestBlockhash.blockhash,
        instructions: [
          SystemProgram.transfer(
            fromPubkey: wallet, 
            toPubkey: receiver.pubkey, 
            lamports: lamports,
          )
        ]
      );
      txs.add(TransferData(
        transaction: transaction, 
        receiver: receiver,
        lamports: lamports,
      ));
    }
    return txs;
  }

  /// Checks the results of [transfers].
  Future<void> _confirmTransfers(
    final Connection connection, {
    required final List<String?> signatures,
    required final List<TransferData> transfers, 
  }) async {

      // Wait for confirmations (**You need to convert the base-64 signatures to base-58!**).
      setState(() => _status = "Confirming transaction signature...");
      await Future.wait(
        [for (final sig in signatures) connection.confirmTransaction(base58To64Decode(sig!))], 
        eagerError: true,
      );

      // Get the receiver balances.
      setState(() => _status = "Checking balance...");
      final List<int> receiverBalances = await Future.wait(
        [for (final transfer in transfers) connection.getBalance(transfer.receiver.pubkey)],
        eagerError: true,
      );

      // Check the updated balances.
      final List<String> results = [];
      for (int i = 0; i < receiverBalances.length; ++i) {
        final TransferData transfer = transfers[i];
        final Pubkey pubkey = transfer.receiver.pubkey;
        final BigInt balance = receiverBalances[i].toBigInt();
        if (balance != transfer.lamports) throw Exception('Post transaction balance mismatch.');
        results.add("Transfer: Address $pubkey received $balance SOL");
      }

      // Output the result.
      setState(() => _status = "Success!\n\n"
        "Signatures: $signatures\n\n"
        "${results.join('\n')}"
        "\n"
      );
  }
  
  TextButton _textButton(
    final String text, {
    required final bool enabled,
    required final void Function() onPressed,
  }) => TextButton(
    onPressed: enabled ? onPressed : null, 
    child: Text(text), 
  );
}

class TransferData {
  const TransferData({
    required this.transaction,
    required this.receiver,
    required this.lamports,
  });
  final Transaction transaction;
  final Keypair receiver;
  final BigInt lamports;
}