WEB3DEV

Cover image for O Guia Completo para Desenvolvimento Full Stack Solana com React, Anchor, Rust e Phantom
Banana Labs
Banana Labs

Posted on • Atualizado em

O Guia Completo para Desenvolvimento Full Stack Solana com React, Anchor, Rust e Phantom

Construindo dapps Full Stack com React, Solana, Anchor e a carteira Phantom.

No Guia Completo para Desenvolvimento Full Stack em Ethereum, aprofundei-me em como construir um dapp full stack na Ethereum, que também pode ser aplicado a outras blockchains compatíveis com EVM, como Polygon, Avalanche e Ethereum Layer 2, como Arbitrum.

Neste guia, quero mergulhar em Solana para mostrar como construir um dapp full stack. Também quero apresentar o ecossistema e as ferramentas do desenvolvedor para você, espero ajudá-lo a começar a construir suas próprias ideias e aplicativos daqui para frente.

O código do projeto está localizado aqui

Visão geral do desenvolvedor Solana

Como alguém que começou a aprender Solidity e seu ecossistema há cerca de 6 meses, eu meio que assumi que não poderia ser tão difícil começar a trabalhar com isso. Eu estava errado.

Partes das ferramentas do desenvolvedor são muito boas e polidas (o Solana CLI e o Anchor), enquanto o resto do ecossistema, e até a documentação do Anchor (que para ser justo, é muito nova), deixa uma boa parte a desejar.

Dito isto, uma vez que você pega o jeito de tudo, rapidamente se torna muito mais fácil entender como começar a implementar suas próprias ideias e começar a experimentar.

Uma das coisas mais importantes para encontrar respostas é estar atento ao pesquisar em todo o Google, Github e especialmente nos vários servidores Discord para Anchor e Solana. Os desenvolvedores desses canais foram extremamente prestativos, especialmente Armani Ferrante, que criou o framework Anchor. Familiarize-se com o recurso de pesquisa, muitas vezes você pode encontrar respostas para suas perguntas em discussões anteriores no Discord.

Visão Geral do Projeto

As ferramentas que usaremos hoje incluem:

Solana Tool Suite - Isso inclui uma CLI (Interface de Linha de Comando) realmente polida e bem documentada para interagir com a rede Solana.

Anchor Framework - O Anchor é na verdade um salva-vidas para mim, e tenho quase certeza de que não teria conseguido superar a dificuldade de construir nada sem ele. Ele é o Hardhat para desenvolvimento em Solana e muito mais, e eu adoro isso. Ele também oferece um DSL em cima do Rust para que você não precise de um profundo conhecimento da linguagem para começar, embora eu ainda esteja tentando aprender o Rust, pois provavelmente será útil para construir qualquer coisa não trivial, mesmo com o DSL. Um bom lugar gratuito para aprender Rust é o The Rust Book.

solana/web3.js - Uma versão Solana do web3.js que parece funcionar muito bem, mas a documentação era quase inutilizável para mim.

React - o framework do lado do cliente.

Vou deixar de fora todos os detalhes profundos sobre como Solana funciona, pois outras pessoas podem cobrir isso melhor do que eu. Em vez disso, tentarei me concentrar apenas em construir algo e compartilhar os detalhes que você precisa saber para conseguir isso, junto com coisas que considero de extrema importância.

Se você quiser saber mais sobre Solana e como ela funciona, aqui estão algumas boas peças:

Neste guia, focaremos principalmente na configuração do projeto, teste e integração do cliente frontend para criar alguns tipos de aplicativos, principalmente focados em operações CRUD (sem o delete, é claro), que achei um pouco não documentado (integração com aplicativos clientes).

Também aprenderemos como fazer o airdrop de tokens para nossas próprias contas de desenvolvimento usando a Solana CLI e implantar nossos aplicativos em uma rede local e em uma rede de teste real.

Não vamos nos concentrar em NFTs neste guia, mas talvez eu me concentre em fazer isso em um guia futuro. Por enquanto, se você estiver interessado em construir um mercado NFT em Solana, recomendo dar uma olhada no Metaplex.

Pré-requisitos

Este tutorial aborda como construir um aplicativo full stack no Solana, mas não explica como instalar todas as dependências individuais.

Em vez disso, vou listar as dependências e vincular à documentação de como instalá-las, pois cada projeto poderá explicar e documentar essas coisas melhor do que eu jamais poderia, além de mantê-las atualizadas.

  1. Node.js - Eu recomendo instalar o Node usando nvm ou fnm

  2. Solana Tool Suite - Você pode ver as instruções de instalação aqui. observe - Se você tiver algum problema ao instalar o Solana em um Mac M1, tente compilar a partir do código fonte e confira este guia.

  3. Anchor (incluindo a instalação do Mocha) - A instalação do Anchor foi bastante simples para mim. Você pode encontrar as instruções de instalação aqui.

  4. Carteira do navegador Solana - eu recomendo Phantom, que é o que eu testei com este aplicativo.

Primeiros Passos

Antes de começarmos a construir, vamos dar uma olhada na Solana CLI.

Solana CLI

As principais coisas que faremos com a Solana CLI serão a configuração de nossa rede (entre localhost e uma rede de teste do desenvolvedor), bem como o lançamento de tokens em nossas carteiras, praticamente tudo isso vai ser feito com a Anchor CLI.

Por exemplo, podemos verificar a configuração atual da rede (e outras) com este comando:

solana config get

# Saída
Config File: /Users/user/.config/solana/cli/config.yml
RPC URL: https://api.devnet.solana.com
WebSocket URL: wss://api.devnet.solana.com/ (computed)
Keypair Path: /Users/user/.config/solana/id.json
Commitment: confirmed

Enter fullscreen mode Exit fullscreen mode

Se você não tiver um Keypair path, configure um seguindo as instruções aqui

Podemos alterar a rede assim:

# configurando localhost
solana config set --url localhost

# configurando devnet
solana config set --url devnet

Enter fullscreen mode Exit fullscreen mode

Isso é importante, pois você precisará estar ciente de qual rede está usando ao construir, testar e implantar seus programas. Você também precisa ter certeza de que sua carteira está usando a mesma rede que seu ambiente local está usando quando se está testando, isso é algo que abordarei.

Começaremos desenvolvendo em uma rede localhost e, em seguida, alternando para a rede devnet.

Também podemos usar a CLI para ver nosso endereço de carteira local atual:

solana address
Enter fullscreen mode Exit fullscreen mode

E, em seguida, obtenha os detalhes completos sobre uma conta:

solana account <address from above>
Enter fullscreen mode Exit fullscreen mode

Em seguida, vamos fazer um airdrop de alguns tokens. Para fazer isso, primeiro mude para a rede local, pois é aqui que trabalharemos para começar:

solana config set --url localhost
Enter fullscreen mode Exit fullscreen mode

Em seguida, inicie a rede local. Esta será um nó Solana local no qual podemos implantar para teste:

solana-test-validator
Enter fullscreen mode Exit fullscreen mode

Depois que a rede local estiver em execução, você poderá enviar tokens para sua conta. Com a rede em execução, abra uma janela separada e execute o seguinte comando:

solana airdrop 100
Enter fullscreen mode Exit fullscreen mode

Você pode verificar o saldo da sua carteira:

solana balance

# ou

solana balance <address>
Enter fullscreen mode Exit fullscreen mode

Agora você deve ter um saldo de 100 SOL em sua carteira. Com isso, podemos começar a construir.

Vamos começar a construir

Para começar, inicialize um novo projeto com Anchor e vá para o novo diretório:

anchor init mysolanaapp --javascript

cd mysolanaapp
Enter fullscreen mode Exit fullscreen mode

Certifique-se de usar o Anchor na versão 0.16.0 ou posterior.

Neste projeto, você verá quatro pastas principais (além do node_modules):

app - Onde vai nosso código frontend

programs - É aqui que fica o código Rust para o programa Solana

test - Onde os testes de JavaScript para o programa ficam

migrations - Um script de implantação básico

Vamos dar uma olhada no programa que foi criado para nós.

O Anchor usa, e nos permite escrever, um eDSL (DSL embutido) que abstrai muitas das operações de baixo nível mais complexas que você normalmente precisaria fazer se estivesse usando Solana & Rust sem ele, tornando-o mais acessível para mim.

// programs/src/lib.rs
use anchor_lang::prelude::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

#[program]
pub mod mysolanaapp {
    use super::*;
    pub fn initialize(ctx: Context<Initialize>) -> ProgramResult {
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize {}
Enter fullscreen mode Exit fullscreen mode

Este é provavelmente o programa mais básico que você pode escrever. A única coisa que acontece aqui é que estamos definindo uma função chamada initialize, que quando invocada apenas sai do programa com sucesso. Não há nenhuma manipulação de dados.

A struct Initialize define o contexto como vazio de quaisquer argumentos. Aprenderemos mais sobre o contexto da função mais tarde.

Para compilar este programa, podemos executar o comando build do Anchor:

anchor build
Enter fullscreen mode Exit fullscreen mode

Depois que uma compilação for concluída, você deverá ver uma nova pasta chamada target.

Um dos artefatos criados é uma IDL localizada em target/idl/mysolanaapp.json.

IDLs são muito semelhantes a uma ABI no Solidity (ou uma definição de consulta no GraphQL), e nós as usaremos de maneira semelhante em nossos testes e frontends JavaScript para nos comunicarmos com nosso programa Solana via RPC.

Também podemos testar nosso programa. Se você abrir tests/mysolanaapp.js, verá que existe um teste escrito em JavaScript que nos permite testar o programa.

O teste deve ficar assim:

const anchor = require('@project-serum/anchor');

describe('mysolanaapp', () => {
  // Configure o cliente para usar o cluster local.
  anchor.setProvider(anchor.Provider.env());

  it('Is initialized!', async () => {
    const program = anchor.workspace.Mysolanaapp;
    const tx = await program.rpc.initialize();
    console.log("Your transaction signature", tx);
  });
});

Enter fullscreen mode Exit fullscreen mode

Há algumas coisas a aprender com este teste que são importantes e usaremos no futuro, tanto em nossos testes quanto nos clientes JavaScript frontend.

Para chamar um programa Solana usando o Anchor, normalmente precisamos de duas coisas principais:

1. Provider - O Provider é uma abstração de uma conexão com a rede Solana, normalmente consistindo de uma Conexão, Carteira e um compromisso de comprovação.

No teste, o framework Anchor criará o provider para nós com base no ambiente (anchor.Provider.env()), mas no cliente precisaremos construir o provider nós mesmos usando a carteira Solana do usuário.

2. program - O program é uma abstração que combina o Provider, idl e o programID (que é gerado quando o programa é compilado) e nos permite chamar métodos RPC em relação ao nosso programa.

Novamente, do mesmo jeito do Provider, o Anchor oferece uma maneira conveniente de acessar o programa, mas ao construir o frontend, precisaremos construir esse provider nós mesmos.

Assim que tivermos essas duas coisas, podemos começar a chamar funções em nosso programa. Por exemplo, em nosso programa temos uma função initialize. Em nosso teste, você verá que podemos invocar essa função diretamente usando program.rpc.functionName:

const tx = await program.rpc.initialize();
Enter fullscreen mode Exit fullscreen mode

Este é um padrão muito comum que você usará muito ao trabalhar com o Anchor, e uma vez que você entenda como ele funciona, fica muito fácil conectar e interagir com um programa Solana.

Agora podemos testar o programa executando o script de teste:

anchor test
Enter fullscreen mode Exit fullscreen mode

Construindo o Hello World

Agora que temos o nosso projeto configurado, vamos criar algo um pouco mais interessante.

Eu sei que, como desenvolvedor full stack, na maioria das vezes fico imaginando como fazer tipos de operações CRUD, então é isso que veremos a seguir.

O primeiro programa que criaremos nos permitirá criar um contador que aumenta toda vez que o chamamos a partir de um aplicativo cliente.

A primeira coisa que precisamos fazer é abrir programs/mysolanaapp/src/lib.rs e atualizá-lo com o seguinte código:

use anchor_lang::prelude::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

#[program]
mod mysolanaapp {
    use super::*;
    pub fn create(ctx: Context<Create>) -> ProgramResult {
        let base_account = &mut ctx.accounts.base_account;
        base_account.count = 0;
        Ok(())
    }

    pub fn increment(ctx: Context<Increment>) -> ProgramResult {
        let base_account = &mut ctx.accounts.base_account;
        base_account.count += 1;
        Ok(())
    }
}

// Instruções de Transação
#[derive(Accounts)]
pub struct Create<'info> {
    #[account(init, payer = user, space = 16 + 16)]
    pub base_account: Account<'info, BaseAccount>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program <'info, System>,
}

// Instruções de Transação
#[derive(Accounts)]
pub struct Increment<'info> {
    #[account(mut)]
    pub base_account: Account<'info, BaseAccount>,
}

// Uma conta que vai dentro de uma instrução de transação
#[account]
pub struct BaseAccount {
    pub count: u64,
}

Enter fullscreen mode Exit fullscreen mode

Neste programa temos duas funções - create e increment. Essas duas funções são os manipuladores de solicitação RPC que poderemos chamar de um aplicativo cliente para interagir com o programa.

O primeiro parâmetro de um manipulador RPC é o Context struct, que descreve o contexto que será passado quando a função for chamada e como tratá-lo. No caso de Create, esperamos três parâmetros: base_account, user e system_program.

Os atributos #[account(...)] definem restrições e instruções que estão relacionadas à conta de processo onde declarada. Se alguma dessas restrições não for mantida, a instrução nunca será executada.

Qualquer cliente que chame este programa com a base_account adequada pode chamar esses métodos RPC.

A maneira como Solana lida com dados é muito diferente de tudo com que já trabalhei. Não há estado persistente dentro do programa, tudo está anexado ao que é conhecido como contas. Uma conta contém essencialmente todo o estado de um programa. Por causa disso, todos os dados são passados por referência de fora.

Também não há operações de leitura. Isso porque tudo o que você precisa fazer para ler o conteúdo de um programa é solicitar a conta, a partir daí é possível visualizar todo o estado do programa. Para ler mais sobre como as contas funcionam, confira este post.

Para construir o programa:

anchor build
Enter fullscreen mode Exit fullscreen mode

Em seguida, vamos escrever um teste que use esse programa contador. Para isso, abra tests/mysolanaapp.js e atualize com o seguinte código:

const assert = require("assert");
const anchor = require("@project-serum/anchor");
const { SystemProgram } = anchor.web3;
describe("mysolanaapp", () => {
  /* create and set a Provider */
  const provider = anchor.Provider.env();
  anchor.setProvider(provider);
  const program = anchor.workspace.Mysolanaapp;
  it("Creates a counter)", async () => {
    /* Chame a função create via RPC */
    const baseAccount = anchor.web3.Keypair.generate();
    await program.rpc.create({
      accounts: {
        baseAccount: baseAccount.publicKey,
        user: provider.wallet.publicKey,
        systemProgram: SystemProgram.programId,
      },
      signers: [baseAccount],
    });

    /* Busque a conta e verifique o valor da contagem */
    const account = await program.account.baseAccount.fetch(baseAccount.publicKey);
    console.log('Count 0: ', account.count.toString())
    assert.ok(account.count.toString() == 0);
    _baseAccount = baseAccount;

  });

  it("Increments the counter", async () => {
    const baseAccount = _baseAccount;

    await program.rpc.increment({
      accounts: {
        baseAccount: baseAccount.publicKey,
      },
    });

    const account = await program.account.baseAccount.fetch(baseAccount.publicKey);
    console.log('Count 1: ', account.count.toString())
    assert.ok(account.count.toString() == 1);
  });
});
Enter fullscreen mode Exit fullscreen mode

Antes de continuarmos a testar e implantar o programa, queremos obter o ID do programa gerado dinamicamente que foi gerado pelo build. Precisamos desse ID para usar no programa Rust para substituir o ID de espaço reservado que configuramos quando criamos o projeto. Para obter esse ID, podemos executar o seguinte comando:

solana address -k target/deploy/mysolanaapp-keypair.json
Enter fullscreen mode Exit fullscreen mode

Agora podemos atualizar os IDs do programa em lib.rs:

// mysolanaapp/src/lib.rs

declare_id!("your-program-id");
Enter fullscreen mode Exit fullscreen mode

E também em Anchor.toml:

# Anchor.toml
[programs.localnet]
mysolanaapp = "your-program-id"
Enter fullscreen mode Exit fullscreen mode

A seguir, execute o teste:

anchor test
Enter fullscreen mode Exit fullscreen mode

Depois que o teste for aprovado, agora podemos implantar.

Agora podemos implantar o programa. Certifique-se de que solana-test-validator esteja em execução:

anchor deploy
Enter fullscreen mode Exit fullscreen mode

Você também pode visualizar o log do validador abrindo uma janela separada e executando os solana logs

Agora estamos prontos para construir o frontend.

Construindo o aplicativo React

Na raiz do projeto Anchor, crie um novo aplicativo react para substituir o diretório app existente:

npx create-react-app app
Enter fullscreen mode Exit fullscreen mode

Em seguida, instale as dependências que precisaremos para Anchor e Solana Web3:

cd app

npm install @project-serum/anchor @solana/web3.js
Enter fullscreen mode Exit fullscreen mode

Também usaremos o Solana Wallet Adapter para lidar com a conexão da carteira Solana do usuário. Vamos instalar essas dependências também:

npm install @solana/wallet-adapter-react \
@solana/wallet-adapter-react-ui @solana/wallet-adapter-wallets \
@solana/wallet-adapter-base
Enter fullscreen mode Exit fullscreen mode

Em seguida, no diretório src, crie um novo arquivo chamado idl.json. Aqui, copie o IDL JSON que foi criado para você na pasta principal do projeto, localizada em target/idl/mysolanaapp.json.

Seria bom se pudéssemos copiar esse arquivo idl automaticamente para a pasta src do aplicativo cliente, mas até agora não encontrei uma maneira de fazer isso nativamente. É claro que você pode criar seu próprio script que faça isso se desejar, ou então você precisa copiar e colar sobre o IDL após cada alteração no seu programa principal.

Se você deseja um script como este, pode fazê-lo em apenas algumas linhas de código:

// copyIdl.js
const fs = require('fs');
const idl = require('./target/idl/mysolanaapp.json');

fs.writeFileSync('./app/src/idl.json', JSON.stringify(idl));
Enter fullscreen mode Exit fullscreen mode

Em seguida, abra app/src/App.js e atualize-o com o seguinte:

import './App.css';
import { useState } from 'react';
import { Connection, PublicKey } from '@solana/web3.js';
import {
  Program, Provider, web3
} from '@project-serum/anchor';
import idl from './idl.json';

import { PhantomWalletAdapter } from '@solana/wallet-adapter-wallets';
import { useWallet, WalletProvider, ConnectionProvider } from '@solana/wallet-adapter-react';
import { WalletModalProvider, WalletMultiButton } from '@solana/wallet-adapter-react-ui';
require('@solana/wallet-adapter-react-ui/styles.css');

const wallets = [
  /* veja a lista de carteiras disponíveis em https://github.com/solana-labs/wallet-adapter#wallets */
  new PhantomWalletAdapter()
]

const { SystemProgram, Keypair } = web3;
/* criar uma conta  */
const baseAccount = Keypair.generate();
const opts = {
  preflightCommitment: "processed"
}
const programID = new PublicKey(idl.metadata.address);

function App() {
  const [value, setValue] = useState(null);
  const wallet = useWallet();

  async function getProvider() {
    /* criar o provider e devolvê-lo ao chamador */
    /* rede definida como rede local por enquanto */
    const network = "http://127.0.0.1:8899";
    const connection = new Connection(network, opts.preflightCommitment);

    const provider = new Provider(
      connection, wallet, opts.preflightCommitment,
    );
    return provider;
  }

  async function createCounter() {    
    const provider = await getProvider()
    /* crie a interface do programa combinando o idl, ID do programa, e provider */
    const program = new Program(idl, programID, provider);
    try {
      /* interagir com o programa via rpc */
      await program.rpc.create({
        accounts: {
          baseAccount: baseAccount.publicKey,
          user: provider.wallet.publicKey,
          systemProgram: SystemProgram.programId,
        },
        signers: [baseAccount]
      });

      const account = await program.account.baseAccount.fetch(baseAccount.publicKey);
      console.log('account: ', account);
      setValue(account.count.toString());
    } catch (err) {
      console.log("Transaction error: ", err);
    }
  }

  async function increment() {
    const provider = await getProvider();
    const program = new Program(idl, programID, provider);
    await program.rpc.increment({
      accounts: {
        baseAccount: baseAccount.publicKey
      }
    });

    const account = await program.account.baseAccount.fetch(baseAccount.publicKey);
    console.log('account: ', account);
    setValue(account.count.toString());
  }

  if (!wallet.connected) {
    /* Se a carteira do usuário não estiver conectada, exiba o botão conectar carteira. */
    return (
      <div style={{ display: 'flex', justifyContent: 'center', marginTop:'100px' }}>
        <WalletMultiButton />
      </div>
    )
  } else {
    return (
      <div className="App">
        <div>
          {
            !value && (<button onClick={createCounter}>Create counter</button>)
          }
          {
            value && <button onClick={increment}>Increment counter</button>
          }

          {
            value && value >= Number(0) ? (
              <h2>{value}</h2>
            ) : (
              <h3>Please create the counter.</h3>
            )
          }
        </div>
      </div>
    );
  }
}

/* configuração de carteira conforme especificado aqui: https://github.com/solana-labs/wallet-adapter#setup */
const AppWithProvider = () => (
  <ConnectionProvider endpoint="http://127.0.0.1:8899">
    <WalletProvider wallets={wallets} autoConnect>
      <WalletModalProvider>
        <App />
      </WalletModalProvider>
    </WalletProvider>
  </ConnectionProvider>
)

export default AppWithProvider;

Enter fullscreen mode Exit fullscreen mode

Mudando a rede de sua carteira

Antes de podermos interagir com um programa na rede localhost, devemos mudar nossa carteira Phantom para a rede adequada.

Para fazer isso, abra sua carteira Phantom e clique no botão de configurações. Em seguida, role para baixo até Alterar Rede:

Alterar Rede

Em seguida, escolha Localhost:

Localhost

Agora precisamos lançar tokens para esta carteira. Na parte superior da interface da carteira, clique no seu endereço para copiá-lo para a área de transferência.

Carteira

Em seguida, abra seu terminal e execute este comando (certifique-se de que o solana-test-validator esteja em execução):

solana airdrop 10 <address>
Enter fullscreen mode Exit fullscreen mode

Agora você deve ter 10 tokens em sua carteira. Agora, podemos executar e testar o aplicativo!

Mude para o diretório app e execute o seguinte comando:

npm start
Enter fullscreen mode Exit fullscreen mode

Você deve ser capaz de conectar sua carteira, criar um contador e incrementá-lo.

Você notará que ao atualizar, você perde o estado do programa. Isso ocorre porque estamos gerando dinamicamente a conta base quando o programa é carregado. Se você quisesse ler e interagir com os dados do programa em vários clientes, precisaria criar e armazenar o Keypair em algum lugar do seu projeto. Eu reuni um gist de uma abordagem ingênua de como isso pode parecer.

Hello World parte 2

Vamos criar uma variação deste programa que, ao invés de lidar com um contador, nos permite criar uma mensagem e acompanhar todas as mensagens criadas anteriormente.

Para fazer isso, vamos atualizar nosso programa Rust para ficar assim:

/* programs/mysolanaapp/src/lib.rs */
use anchor_lang::prelude::*;

declare_id!("your-program-id");

#[program]
mod mysolanaapp {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>, data: String) -> ProgramResult {
        let base_account = &mut ctx.accounts.base_account;
        let copy = data.clone();
        base_account.data = data;
        base_account.data_list.push(copy);
        Ok(())
    }

    pub fn update(ctx: Context<Update>, data: String) -> ProgramResult {
        let base_account = &mut ctx.accounts.base_account;
        let copy = data.clone();
        base_account.data = data;
        base_account.data_list.push(copy);
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = user, space = 64 + 64)]
    pub base_account: Account<'info, BaseAccount>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Update<'info> {
    #[account(mut)]
    pub base_account: Account<'info, BaseAccount>,
}

#[account]
pub struct BaseAccount {
    pub data: String,
    pub data_list: Vec<String>,
}
Enter fullscreen mode Exit fullscreen mode

Neste programa, temos duas partes principais de dados que estamos acompanhando, uma String chamada data e um Vector que contém uma lista de todos os dados já adicionados ao programa chamado data_list.

Você notará que a alocação de memória aqui é maior (128 + 128) do que no programa anterior para levar em conta o Vector. Não sei quantas atualizações você poderia armazenar neste programa como está, mas pode ser algo para investigar mais ou experimentar, já que este exemplo por si só é experimental e apenas para lhe dar uma compreensão de como as coisas funcionam.

Em seguida, podemos atualizar o teste para este novo programa:

const assert = require("assert");
const anchor = require("@project-serum/anchor");
const { SystemProgram } = anchor.web3;

describe("Mysolanaapp", () => {
  const provider = anchor.Provider.env();
  anchor.setProvider(provider);
  const program = anchor.workspace.Mysolanaapp;
  it("It initializes the account", async () => {
    const baseAccount = anchor.web3.Keypair.generate();
    await program.rpc.initialize("Hello World", {
      accounts: {
        baseAccount: baseAccount.publicKey,
        user: provider.wallet.publicKey,
        systemProgram: SystemProgram.programId,
      },
      signers: [baseAccount],
    });

    const account = await program.account.baseAccount.fetch(baseAccount.publicKey);
    console.log('Data: ', account.data);
    assert.ok(account.data === "Hello World");
    _baseAccount = baseAccount;

  });

  it("Updates a previously created account", async () => {
    const baseAccount = _baseAccount;

    await program.rpc.update("Some new data", {
      accounts: {
        baseAccount: baseAccount.publicKey,
      },
    });

    const account = await program.account.baseAccount.fetch(baseAccount.publicKey);
    console.log('Updated data: ', account.data)
    assert.ok(account.data === "Some new data");
    console.log('all account data:', account)
    console.log('All data: ', account.dataList);
    assert.ok(account.dataList.length === 2);
  });
});
Enter fullscreen mode Exit fullscreen mode

Para testar:

anchor test
Enter fullscreen mode Exit fullscreen mode

Se o teste falhar, tente desligar o validador e execute novamente.

Em seguida, vamos atualizar o cliente.

/* app/src/App.js */
import './App.css';
import { useState } from 'react';
import { Connection, PublicKey } from '@solana/web3.js';
import { Program, Provider, web3 } from '@project-serum/anchor';
import idl from './idl.json';

import { PhantomWalletAdapter } from '@solana/wallet-adapter-wallets';
import { useWallet, WalletProvider, ConnectionProvider } from '@solana/wallet-adapter-react';
import { WalletModalProvider, WalletMultiButton } from '@solana/wallet-adapter-react-ui';
require('@solana/wallet-adapter-react-ui/styles.css');

const wallets = [ new PhantomWalletAdapter() ]

const { SystemProgram, Keypair } = web3;
const baseAccount = Keypair.generate();
const opts = {
  preflightCommitment: "processed"
}
const programID = new PublicKey(idl.metadata.address);

function App() {
  const [value, setValue] = useState('');
  const [dataList, setDataList] = useState([]);
  const [input, setInput] = useState('');
  const wallet = useWallet()

  async function getProvider() {
    /* create the provider and return it to the caller */
    /* network set to local network for now */
    const network = "http://127.0.0.1:8899";
    const connection = new Connection(network, opts.preflightCommitment);

    const provider = new Provider(
      connection, wallet, opts.preflightCommitment,
    );
    return provider;
  }

  async function initialize() {    
    const provider = await getProvider();
    /* create the program interface combining the idl, program ID, and provider */
    const program = new Program(idl, programID, provider);
    try {
      /* interact with the program via rpc */
      await program.rpc.initialize("Hello World", {
        accounts: {
          baseAccount: baseAccount.publicKey,
          user: provider.wallet.publicKey,
          systemProgram: SystemProgram.programId,
        },
        signers: [baseAccount]
      });

      const account = await program.account.baseAccount.fetch(baseAccount.publicKey);
      console.log('account: ', account);
      setValue(account.data.toString());
      setDataList(account.dataList);
    } catch (err) {
      console.log("Transaction error: ", err);
    }
  }

  async function update() {
    if (!input) return
    const provider = await getProvider();
    const program = new Program(idl, programID, provider);
    await program.rpc.update(input, {
      accounts: {
        baseAccount: baseAccount.publicKey
      }
    });

    const account = await program.account.baseAccount.fetch(baseAccount.publicKey);
    console.log('account: ', account);
    setValue(account.data.toString());
    setDataList(account.dataList);
    setInput('');
  }

  if (!wallet.connected) {
    return (
      <div style={{ display: 'flex', justifyContent: 'center', marginTop:'100px' }}>
        <WalletMultiButton />
      </div>
    )
  } else {
    return (
      <div className="App">
        <div>
          {
            !value && (<button onClick={initialize}>Initialize</button>)
          }

          {
            value ? (
              <div>
                <h2>Current value: {value}</h2>
                <input
                  placeholder="Add new data"
                  onChange={e => setInput(e.target.value)}
                  value={input}
                />
                <button onClick={update}>Add data</button>
              </div>
            ) : (
              <h3>Please Inialize.</h3>
            )
          }
          {
            dataList.map((d, i) => <h4 key={i}>{d}</h4>)
          }
        </div>
      </div>
    );
  }
}

const AppWithProvider = () => (
  <ConnectionProvider endpoint="http://127.0.0.1:8899">
    <WalletProvider wallets={wallets} autoConnect>
      <WalletModalProvider>
        <App />
      </WalletModalProvider>
    </WalletProvider>
  </ConnectionProvider>
)

export default AppWithProvider;
Enter fullscreen mode Exit fullscreen mode

Em seguida, compile e implante o programa (certifique-se de que solana-test-validator esteja em execução):

anchor build

anchor deploy
Enter fullscreen mode Exit fullscreen mode

Com a nova compilação, você terá um novo IDL que precisará atualizar para seu cliente. Copie o novo IDL para app/src/idl.json ou execute o script copyIdl.js

Testando

Ao testar o novo programa, certifique-se de atualizar o arquivo idl.json que foi criado pela compilação.

Mude para o diretório app e execute o comando start:

npm start
Enter fullscreen mode Exit fullscreen mode

Implantando na Devnet

A implantação em uma rede ativa é bastante simples a partir daqui. As principais coisas que precisamos fazer são:

1. Atualize a CLI do Solana para usar a devnet:

solana config set --url devnet
Enter fullscreen mode Exit fullscreen mode

2. Atualize a carteira Phantom para usar a devnet

3. Abra o Anchor.toml e atualize o cluster de localnet para devnet.

4. Faça o rebuild do programa. Certifique-se de que o ID do programa em Anchor.toml corresponda ao ID do programa atual.

5. Implante o programa novamente, desta vez ele será implantado no devnet

6. Em app/src/App.js, precisamos atualizar também a rede, desta vez usando o clusterApiUrl de @solana/web3, assim:

/* antes */
<ConnectionProvider endpoint="http://127.0.0.1:8899">

/* depois */
import {
  ...,
  clusterApiUrl
} from '@solana/web3.js';

const network = clusterApiUrl('devnet');

<ConnectionProvider endpoint={network}>
Enter fullscreen mode Exit fullscreen mode

A partir daqui, você poderá implantar e testar como fizemos nas etapas anteriores.

O código para este projeto está localizado aqui

Próximos passos

Outro tutorial aprofundado que sugiro verificar a seguir é Criar um dApp Solana do zero, que implementa uma versão simplificada do Twitter como um dapp Solana.

Se você tem interesse em trabalhar com tecnologia como essa em tempo integral, venha se juntar a mim e ao meu time na Edge & Node, estamos contratando!


Esse artigo é uma tradução de Nader Dabit feita por @bananlabs. Você pode encontrar o artigo original aqui


Image description

Top comments (0)