WEB3DEV

Cover image for Usando o Flutter para construir um Aplicativo Descentralizado (Dapp) para Solana
Adriano P. Araujo
Adriano P. Araujo

Posted on

Usando o Flutter para construir um Aplicativo Descentralizado (Dapp) para Solana

Image description

O Flutter tem ganhado popularidade há algum tempo devido à sua facilidade de uso e funcionalidade fantástica. O mesmo acontece com a Solana, uma blockchain popular por sua velocidade supersônica e taxas de gás de rede incrivelmente baixas.

Então, hoje, vamos falar sobre como trabalhar com a Solana em um aplicativo Flutter.

Introdução

A equipe de desenvolvimento móvel da Solana criou o Solana Mobile Stack para Android e React Native (a inclusão do iOS está prevista de acordo com seu documento de especificação). Uma parte deste stack é o protocolo Mobile Wallet Adapter. Atualmente, esse protocolo suporta apenas o sistema operacional Android. A Espresso Cash está desenvolvendo a versão Flutter do Solana Mobile Stack com base em todo esse stack.

Este blog é um tutorial para aprender a usar a biblioteca Flutter deles em um aplicativo Flutter. A biblioteca Flutter da Espresso Cash é totalmente de código aberto e você pode conferir aqui (você pode encontrar as bibliotecas relacionadas na subpasta “packages” (pacotes).

Os pacotes Flutter (com links) que usaremos para este projeto são: solana e solana_mobile_client

Pré-requisitos:

Para este blog, é bom se o leitor já souber sobre os seguintes conceitos:

  • Conhecimento do Flutter - como um aplicativo Flutter e widgets funcionam, como usar pacotes e integrá-los, gerenciamento de estado.

  • Conhecimento da Solana - carteiras, transações, contas, tokens SPL, airdrops, redes principais e redes de teste.

  • Configuração do Flutter - Documentação Oficial do Flutter

Repositório GitHub para este blog:

https://github.com/ritvij14/solana-flutter-sample?source=post_page-----ab9f2b6cd9dc--------------------------------

Funcionalidades que abordaremos:

Três coisas:

  1. Conectar o Dapp à carteira;

  2. Distribuir alguns SOL para a carteira conectada;

  3. Gerar e executar uma transação por meio da carteira conectada.

Vamos realizar essas funcionalidades na Devnet.

Como será a aparência do aplicativo?

Aqui está um vídeo mostrando o funcionamento do aplicativo móvel:

Design

Manteremos o design simples porque nossa ênfase é na funcionalidade. Teremos uma Appbar e uma coluna com três botões para as funcionalidades mencionadas anteriormente.


import 'package:flutter/material.dart';



void main() {

  runApp(const MainApp());

}



class MainApp extends StatefulWidget {

  const MainApp({super.key});



  @override

  State<MainApp> createState() => _MainAppState();

}



class _MainAppState extends State<MainApp> {



  @override

  void initState() {

    super.initState();

  }



  @override

  Widget build(BuildContext context) {

    return MaterialApp(

      home: Scaffold(

        appBar: AppBar(

          title: const Text("Exemplo Flutter da Solana"),

          centerTitle: true,

        ),

        body: Center(

          child: Column(

            mainAxisSize: MainAxisSize.max,

            mainAxisAlignment: MainAxisAlignment.center,

            children: [

              ElevatedButton(

                onPressed: () {},

                child: const Text("Autorizar"),

              ),

              ElevatedButton(

                onPressed: () {},

                child: const Text("Solicitar Airdrop"),

              ),

              ElevatedButton(

                onPressed: () {},

                child: const Text("Gerar e Assinar Transações"),

              ),

            ],

          ),

        ),

      ),

    );

  }

}




Enter fullscreen mode Exit fullscreen mode

Vamos começar.

Primeiro, vamos instalar nossas bibliotecas a partir do nosso terminal:


flutter pub add solana solana_mobile_client

Enter fullscreen mode Exit fullscreen mode

O Mobile Wallet Adapter utiliza um Scenario (Cenário) para gerenciar as conexões entre o cliente e os aplicativos de carteira. Em nosso exemplo, teremos o cliente (o Dapp) e a carteira (por exemplo, Phantom, Solflare) no mesmo dispositivo, então usaremos uma subclasse de Scenario chamada LocalAssociationScenario.

Como isso funciona em um Dapp é assim: 

  1. Primeiro, criamos um objeto LocalAssociationScenario.

  2. Usando o objeto acima, despachamos um Intent usando startActivity().

  3. Junto com isso, também usamos o comando para iniciar o  Scenario, que nos fornece um objeto MobileWalletAdapterClient.

  4. Usando esse objeto retornado, podemos utilizar as capacidades de conexão e assinatura do Protocolo Mobile Wallet Adapter.

  5. Após uma conexão ou assinatura específica ser concluída e recebida pelo Dapp, ele deve chamar a função close() do cenário para retornar da carteira para o Dapp.

Observação: o último passo de chamar a função close() traz o usuário de volta para o Dapp e também destrói o objeto que criamos na etapa 1. É por isso que precisamos repetir as etapas em cada função que envolve o Mobile Wallet Adapter (MWA).

A função start() do Cenário inicia a conexão WebSocket para trabalhar com a carteira. Chamar a função close() fecha essa conexão.

Ok, chega de teoria - é hora de ir para o código. Você verá que seguimos precisamente todas as etapas acima para fazer nossas operações.

1. Conectando a carteira

Para se conectar à carteira, adicionaremos outra variável para salvar o resultado da autorização. Utilizaremos o primeiro botão e em seu onPressed, adicionaremos o seguinte código:


late AuthorizationResult? _result;



// ... initState e o início da função build com widgets iniciais



ElevatedButton(

  onPressed: () async {

    /// Passo 1

    final localScenario = await LocalAssociationScenario.create();



    /// Passo 2

    localScenario.startActivityForResult(null).ignore();



    /// Passo 3

    final client = await localScenario.start();



    /// Passo 4

    final result = await client.authorize(

      identityUri: Uri.parse('https://solana.com'), // substitua com sua URL

      iconUri: Uri.parse('favicon.ico'), // substitua com seu ícone (isso deve ser relativo à URL acima)

      identityName: 'Solana', // substitua com o nome do seu Dapp

      cluster: 'devnet',

    );



    /// Passo 5

    localScenario.close();



    setState(() {

      _result = result;

    });

  },

  child: const Text("Autorizar"),

),



// ... restante do código

Enter fullscreen mode Exit fullscreen mode

Observação: o resultado que você obtém do Mobile Wallet Adapter possui muitas propriedades, incluindo o endereço da carteira conectada e um token de autenticação. Podemos armazenar o token de autenticação no armazenamento local ou temporário do dispositivo. Quando precisamos reutilizar o token de autenticação, podemos chamar a função reauthorize (mostrada na terceira funcionalidade abaixo). Se o resultado disso não for bem-sucedido, sabemos que precisamos realizar uma chamada completa de autorização(authorize) manual.

2. Airdrop

Para isso, utilizaremos a biblioteca solana criada pela Espresso Cash. Primeiramente, criamos um objeto SolanaClient. Em seguida, chamamos a função requestAirdrop no onPressed do segundo botão. Para esta função, enviamos a chave pública da carteira conectada e a quantidade de SOL desejada.


late AuthorizationResult? _result;

final solanaClient = SolanaClient(

  rpcUrl: Uri.parse("https://api.devnet.solana.com"),

  websocketUrl: Uri.parse("wss://api.devnet.solana.com"),

);

final int lamportsPerSol = 1000000000;



// ... initState e o início da função build com widgets iniciais



ElevatedButton(

  onPressed: () async {

    try {

      await solanaClient.requestAirdrop(

        /// Ed25519HDPublicKey é a classe principal que representa a chave pública 

        /// na biblioteca Solana Dart

        address: Ed25519HDPublicKey(

          _result!.publicKey.toList(),

        ),

        lamports: 1 * lamportsPerSol,

      );

    } catch (e) {

      print("$e");

    }

  },

  child: const Text("Solicitar Airdrop"),

),

Enter fullscreen mode Exit fullscreen mode

3. Gerar e Assinar Transação

Certo, agora chegamos à parte empolgante. Fique comigo, pois esta será uma função extensa. Manterei todo o código com etapas comentadas no bloco onPressed do terceiro botão.

No MWA, é essencial reautorizar o usuário com a carteira antes de executar uma transação. Essa etapa garante que a autenticação esteja ativa e que o Dapp possa concluir as transações.

Então, primeiramente, faremos um client.reauthorize para cuidar disso. Após isso, podemos criar uma transação usando a biblioteca solana e chamar client.signAndSendTransaction para assinar e enviar a transação.

Vamos dar uma olhada no código:


// ... initState e início da função build com widgets



ElevatedButton(

  onPressed: () async {

    final localScenario = await LocalAssociationScenario.create();

    localScenario.startActivityForResult(null).ignore();

    final client = await localScenario.start();

    final reAuth = await client.reauthorize(

      identityUri: Uri.parse('https://solana.com'),

      iconUri: Uri.parse('favicon.ico'),

      identityName: 'Solana',

      authToken: _result!.authToken,

    );



    if (reAuth != null) {

      /// Passo 1: criar instrução do Programa Memo

      final instruction = MemoInstruction(signers: [

        Ed25519HDPublicKey(

          _result!.publicKey.toList(),

        ),

      ], memo: 'Exemplo de memorando');



      /// Passo 2: criar uma assinatura vazia e obter o último blockhash

      final signature = Signature(

        List.filled(64, 0),

        publicKey: Ed25519HDPublicKey(

          _result!.publicKey.toList(),

        ),

      );



      final blockhash = await solanaClient.rpcClient

          .getLatestBlockhash()

          .then((it) => it.value.blockhash);



      /// Passo 3: criar uma transação com a assinatura vazia

      final txn = SignedTx(

        signatures: [signature],

        compiledMessage: Message.only(instruction).compile(

          recentBlockhash: blockhash,

          feePayer: Ed25519HDPublicKey(

            _result!.publicKey.toList(),

          ),

        ),

      );



      /// Passo 4: assinar e enviar a transação

      final result = await client.signAndSendTransactions(

        transactions: [

          Uint8List.fromList(txn.toByteArray().toList())

        ],

      );



      await localScenario.close();



      print(

        "TRANSACTION SIGNATURE: https://solscan.io/tx/${base58encode(result.signatures[0])}?cluster=devnet",

      );

    }

  },

  child: const Text("Gerar e Assinar Transações"),

),

Enter fullscreen mode Exit fullscreen mode

Portanto, após re-autenticar, verificamos se isso foi válido ou não e, ao confirmar, começamos com nossa funcionalidade de transação. Aqui está um resumo detalhado das etapas mencionadas nos comentários acima:

  1. Criamos uma instrução para o Programa Memo, que valida uma string codificada em UTF-8.

  2. Criamos uma assinatura vazia e obtemos o último blockhash. Uma assinatura vazia é necessária porque uma transação sempre consiste em uma assinatura. Uma transação não assinada consiste em uma assinatura vazia. (No caso do Flutter MWA, criamos uma lista de 64 0's para representar uma assinatura vazia). O último blockhash é necessário para evitar repetição.

  3. Criamos uma transação com as instruções e a assinatura vazia. Para adicionar a instrução, nós a adicionamos na forma de uma mensagem. Isso usa a chave pública do signatário (usuário) e o último blockhash que obtivemos no Passo 2.

  4. client.signAndSendTransaction requer a transação em forma de byte, então convertemos isso em Uint8List. Você também pode ver que isso é enviado como uma Lista porque esta função pode executar várias transações em uma chamada.

A última instrução de impressão é opcional, mas quando você a adiciona e obtém o link nos logs do aplicativo, você pode ver a transação verificada no Solscan!

Bônus

Verifique se uma carteira compatível com o MWA está disponível ou não

Isso é extremamente simples. A biblioteca que estamos usando possui uma função estática chamada isAvailable na classe LocalAssociationScenario. Podemos usar isso diretamente para verificar se o dispositivo possui uma carteira compatível.


@override

void initState() {

  super.initState();

  (() async {

    if (!await LocalAssociationScenario.isAvailable()) {

      print("Nenhuma carteira compatível com o MWA disponível; por favor, instale uma carteira");

    } else {

      print("CARTEIRA MWA ENCONTRADA");

    }

  })();

}

Enter fullscreen mode Exit fullscreen mode

Dúvidas?

Você pode me enviar uma mensagem direta no Twitter (ritvij14_) se tiver alguma dúvida. Além disso, se quiser participar da Comunidade Espresso Cash, pode participar pelo servidor Discord através deste link.

Comunidade Oficial Solana Mobile: link

O dReader é um projeto Solana de código aberto popular que usa Flutter para seu aplicativo móvel. Junte-se ao Discord deles! Eles têm pessoas que trabalharam bastante com Solana Mobile e Flutter.

Com isso, espero que você tenha entendido a funcionalidade do MWA em Dapps  Flutter e possa construir alguns aplicativos Flutter fantásticos no Ecossistema Solana.

Image description


Este artigo foi escrito por Ritvij Kumar Sharma e traduzido por Adriano P. de Araujo. O original em inglês pode ser encontrado aqui.

Latest comments (0)