solana_wallet_adapter 0.1.1 copy "solana_wallet_adapter: ^0.1.1" to clipboard
solana_wallet_adapter: ^0.1.1 copied to clipboard

Dart implementation of Solana's Mobile Wallet Adapter Specification.

example/lib/main.dart

/// Imports
/// ------------------------------------------------------------------------------------------------

import 'package:flutter/material.dart';
import 'package:solana_wallet_adapter/solana_wallet_adapter.dart';
import 'package:solana_web3/programs.dart';
import 'package:solana_web3/solana_web3.dart';


/// Main
/// ------------------------------------------------------------------------------------------------

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const MaterialApp(
    home: Scaffold(
      body: ExampleApp(),
    ),
  ));
}


/// Transfer Data
/// ------------------------------------------------------------------------------------------------

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


/// Example App
/// ------------------------------------------------------------------------------------------------

class ExampleApp extends StatefulWidget {

  const ExampleApp({super.key});

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


/// Example App State
/// ------------------------------------------------------------------------------------------------

class _ExampleAppState extends State<ExampleApp> {

  /// Initialization future.
  late final Future<void> _future;

  /// NOTE: Your wallet application must be connected to the same cluster.
  static final Cluster cluster = Cluster.devnet;

  /// Request status.
  String? _status;
  
  /// Create an instance of the [SolanaWalletAdapter].
  final SolanaWalletAdapter adapter = SolanaWalletAdapter(
    AppIdentity(
      // uri: Uri.https('merigo.com'),   // YOUR_APP_DOMAIN.
      // icon: Uri.parse('favicon.png'), // YOUR_ICON_PATH relative to `uri`
      // name: 'Example App',            // YOUR_APP_NAME.
    ),
    cluster: cluster,                 // The cluster your wallet is connected to.
    hostAuthority: null,              // The server address that brokers a remote connection.
  );

  /// Load the adapter's stored state.
  @override
  void initState() {
    super.initState();
    _future = SolanaWalletAdapter.initialize();
  }

  /// Connects the application to a wallet running on the device.
  Future<void> _connect() async {
    if (!adapter.isAuthorized) {
      await adapter.authorize();
      setState(() {});
    }
  }

  /// Disconnects the application from a wallet running on the device.
  Future<void> _disconnect() async {
    if (adapter.isAuthorized) {
      await adapter.deauthorize();
      setState(() {});
    }
  }

  /// Requests an airdrop of 2 SOL for [wallet].
  Future<void> _airdrop(final Connection connection, final Pubkey wallet) async {
    if (cluster != 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, {
    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) _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"
      );
  }
  
  /// Signs [count] number of transactions (then sends them to the network for processing and 
  /// confirms the transaction results).
  void _signTransactions(final int count) async {
    final String description = "Sign Transactions ($count)";
    try {
      setState(() => _status = "Create $description...");
      final Connection connection = Connection(cluster);
      final List<TransferData> transfers = await _createTransfers(connection, count: count);

      setState(() => _status = "$description...");
      final SignTransactionsResult result = await adapter.signTransactions(
        transfers.map((transfer) => adapter.encodeTransaction(transfer.transaction)).toList(),
      );

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

      print('SIGN SIGNS ${signatures}');

      setState(() => _status = "Confirm $description...");
      await _confirmTransfers(
        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 int count) async {
    final String description = "Sign And Send Transactions ($count)";
    try {
      setState(() => _status = "Create $description...");
      final Connection connection = Connection(cluster);
      final List<TransferData> transfers = await _createTransfers(connection, count: count);

      setState(() => _status = "$description...");
      final SignAndSendTransactionsResult result = await adapter.signAndSendTransactions(
        transfers.map((transfer) => adapter.encodeTransaction(transfer.transaction)).toList(),
      );

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

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

  void _signMessages(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 adapter.signMessages(
        messages,
        addresses: [adapter.connectedAccount!.toBase58()],
      );

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

  Widget _builder(final BuildContext context, final AsyncSnapshot snapshot) {
    if (snapshot.connectionState != ConnectionState.done) {
      return const CircularProgressIndicator();
    }
    return ListView(
      shrinkWrap: true,
      padding: const EdgeInsets.all(24.0),
      children: [

        ElevatedButton(
          onPressed: _disconnect, 
          style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
          child: const Text('Disconnect'),
        ),
        ElevatedButton(
          onPressed: _connect, 
          child: const Text('Connect'),
        ),
        ElevatedButton(
          onPressed: () => _signTransactions(1), 
          child: const Text('Sign Transactions (1)'),
        ),
        ElevatedButton(
          onPressed: () => _signTransactions(3), 
          child: const Text('Sign Transactions (3)'),
        ),
        ElevatedButton(
          onPressed: () => _signAndSendTransactions(1), 
          child: const Text('Sign and Send Transactions (1)'),
        ),
        ElevatedButton(
          onPressed: () => _signAndSendTransactions(3), 
          child: const Text('Sign and Send Transactions (3)'),
        ),
        ElevatedButton(
          onPressed: () => _signMessages(1), 
          child: const Text('Sign Messages (1)'),
        ),
        ElevatedButton(
          onPressed: () => _signMessages(3), 
          child: const Text('Sign Messages (3)'),
        ),

        const SizedBox(
          height: 48.0,
        ),

        // Connect / Disconnect
        adapter.isAuthorized
          ? Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                Text(
                  '${adapter.connectedAccount?.toBase58()}',
                  textAlign: TextAlign.center,
                ),
                ElevatedButton(
                  onPressed: _disconnect, 
                  style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
                  child: const Text('Disconnect'),
                ),
              ],
            )
          : ElevatedButton(
              onPressed: _connect, 
              child: const Text('Connect'),
            ),
        const Divider(),
        // Sign Transactions
        Wrap(
          spacing: 24.0,
          runSpacing: 8.0,
          children: [
            ElevatedButton(
              onPressed: adapter.isAuthorized ? () => _signTransactions(1) : null, 
              child: const Text('Sign Transactions (1)'),
            ),
            ElevatedButton(
              onPressed: adapter.isAuthorized ? () => _signTransactions(3) : null, 
              child: const Text('Sign Transactions (3)'),
            ),
            ElevatedButton(
              onPressed: adapter.isAuthorized ? () => _signAndSendTransactions(1) : null, 
              child: const Text('Sign and Send Transactions (1)'),
            ),
            ElevatedButton(
              onPressed: adapter.isAuthorized ? () => _signAndSendTransactions(3) : null, 
              child: const Text('Sign and Send Transactions (3)'),
            ),
            ElevatedButton(
              onPressed: adapter.isAuthorized ? () => _signMessages(1) : null, 
              child: const Text('Sign Messages (1)'),
            ),
            ElevatedButton(
              onPressed: adapter.isAuthorized ? () => _signMessages(3) : null, 
              child: const Text('Sign Messages (3)'),
            ),
          ],
        ),
        Text(_status ?? ''),
      ],
    );
  }
  
  @override
  Widget build(final BuildContext context) {
    return Center(
      child: FutureBuilder(
        future: _future,
        builder: _builder,
      ),
    );
  }
}