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:
Funcionalidades que abordaremos:
Três coisas:
Conectar o Dapp à carteira;
Distribuir alguns SOL para a carteira conectada;
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"),
),
],
),
),
),
);
}
}
Vamos começar.
Primeiro, vamos instalar nossas bibliotecas a partir do nosso terminal:
flutter pub add solana solana_mobile_client
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:
Primeiro, criamos um objeto
LocalAssociationScenario
.Usando o objeto acima, despachamos um
Intent
usandostartActivity()
.Junto com isso, também usamos o comando para iniciar o
Scenario
, que nos fornece um objetoMobileWalletAdapterClient
.Usando esse objeto retornado, podemos utilizar as capacidades de conexão e assinatura do Protocolo Mobile Wallet Adapter.
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
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"),
),
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"),
),
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:
Criamos uma instrução para o Programa Memo, que valida uma string codificada em UTF-8.
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.
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.
client.signAndSendTransaction
requer a transação em forma de byte, então convertemos isso emUint8List
. 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");
}
})();
}
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.
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)