La Guía Completa para el Desarrollo de Pila Completa de Solana

Lorena Fabris
18 min readFeb 14, 2022

--

con React, Anchor, Rust y Phantom

Por Nader Dabit, traducción del artículo “The Complete GUide to Fill Stack Solana Development with React, Anchor, Rust and Phantom” por Lorena Fabris

#webdev #react #blockchain #solana

Construyendo dapps Full Stack con React, Solana, Anchor, y Phantom wallet.

En The Complete Guide to Full Stack Ethereum Development hice una inmersión profunda en cómo construir una dapp de pila completa en Ethereum, que también se puede aplicar a otras cadenas compatibles con EVM como Polygon, Avalanche, y Ethereum Layers 2 como Arbitrum.

En esta guía, quiero sumergirme en Solana para mostrarte cómo construir una dapp de pila completa. También quiero presentarte el ecosistema y las herramientas para desarrolladores para ayudarte a construir tus propias ideas y aplicaciones en el futuro.

  • El código del proyecto se encuentra aquí

Visión general del desarrollador de Solana

Como alguien que acaba de empezar a aprender Solidity y su ecosistema hace unos 6 meses, supuse que no podía ser mucho más difícil de poner en marcha. Estaba equivocado.

Algunas partes de las herramientas para desarrolladores son realmente buenas y pulidas (la CLI de Solana y Anchor), mientras que el resto del ecosistema, e incluso la documentación de Anchor (que para ser justos, es muy nueva), deja bastante que desear.

Dicho esto, una vez que te haces con el control de todo, rápidamente se hace mucho más fácil entender cómo empezar a implementar tus propias ideas y empezar a experimentar.

Una de las claves para encontrar respuestas es estar atento a buscar en todo Google, Github, y especialmente en los diversos servidores de Discord para Anchor y Solana. Los desarrolladores en esos canales han sido extremadamente útiles, especialmente Armani Ferrante, que creó el marco de Anchor. Familiarízate con la función de búsqueda, a menudo puedes encontrar respuestas a tus preguntas en discusiones anteriores en Discord.

Resumen del proyecto

Las herramientas que usaremos hoy incluyen:

Solana Tool Suite — Esto incluye una CLI realmente pulida y bien documentada para interactuar con la red Solana.

Anchor Framework — Anchor es realmente un salvavidas para mí, y estoy casi seguro de que no habría sido capaz de superar el obstáculo de la construcción de cualquier cosa sin él. Es el Hardhat del desarrollo de Solana y más, y me encanta. También ofrece un DSL en la parte superior de Rust para que no necesites una profunda comprensión del lenguaje para empezar, aunque todavía estoy tratando de aprender Rust, ya que probablemente será útil para construir cualquier cosa no trivial, incluso con el DSL. Un buen sitio gratuito para aprender Rust es The Rust Book.

solana/web3.js — Una versión de Solana de web3.js que parece funcionar bastante bien, pero la documentación era casi inutilizable para mí

React — El framework del lado del cliente

Dejaré de lado todos los detalles en profundidad sobre cómo funciona Solana, ya que otras personas pueden cubrir esto mejor que yo. En su lugar, trataré de centrarme en la construcción de algo y compartir los detalles que necesitas saber para lograr esto, junto con las cosas que creo que son de suma importancia.

Si quieres aprender más sobre Solana y cómo funciona, aquí hay algunas buenas piezas:

En esta guía nos centraremos principalmente en la configuración del proyecto, las pruebas y la integración con el cliente del front-end para la construcción de un par de tipos de aplicaciones, principalmente centradas en las operaciones CRUD (sin la eliminación, por supuesto), que me pareció algo indocumentado (integración con las aplicaciones del cliente).

También aprenderemos a enviar tokens a nuestras propias cuentas de desarrollo usando la CLI de Solana, y a desplegar nuestras aplicaciones tanto en una red local como en una red de prueba.

No nos centraremos en los NFTs en esta guía, pero quizás me centre en ello en una futura guía. Por ahora, si estás interesado en construir un marketplace de NFT en Solana, te recomiendo revisar Metaplex.

Requisitos previos

Este tutorial cubre cómo construir una aplicación de pila completa en Solana, pero no entra en cómo instalar todas las dependencias individuales.

En su lugar, enumeraré las dependencias y enlazaré con la documentación para instalarlas, ya que cada proyecto podrá explicar y documentar estas cosas mejor de lo que yo podría, además de mantenerlas actualizadas.

1.- Node.js — Recomiendo instalar Node usando nvm o fnm

2.- Solana Tool Suite — Puedes ver las instrucciones de instalación aquí. nota — Si tienes algún problema para instalar Solana en un Mac M1, intenta construir desde el código fuente y consulta esta guía.

3.- Anchor (incluyendo la instalación de Mocha) — La instalación de Anchor fue bastante sencilla para mí. Puedes encontrar las instrucciones de instalación aquí.

4.- Solana browser wallet — Recomiendo Phantom, que es con lo que he probado esta aplicación.

Comenzar

Antes de empezar a construir, echemos un vistazo a la CLI de Solana.

Solana CLI

Las principales cosas que haremos con la CLI de Solana serán configurar nuestra red (entre localhost y una red de prueba para desarrolladores) así como enviar tokens en nuestras billeteras, prácticamente todo lo demás lo haremos con la CLI de Anchor.

Por ejemplo, podemos comprobar la configuración actual de la red (y otras) con este comando:

solana config get# output
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
  • Si no tienes una Keypair path, configura una siguiendo las instrucciones aquí

Podemos cambiar la red así:

# set to localhost
solana config set --url localhost
# set to devnet
solana config set --url devnet

Esto es importante ya que necesitarás saber qué red estás usando mientras construyes, pruebas y despliegas tus programas. También debes asegurarte de que tu wallet está utilizando la misma red que tu entorno local está utilizando cuando se está probando, algo que voy a cubrir.

Comenzaremos desarrollando en una red localhost, y luego cambiaremos a la reddevnet .

También podemos usar la CLI para ver la dirección de nuestra wallet local actual:

solana address

Y luego obtener los detalles completos de una cuenta:

solana account <address from above>

A continuación, hagamos airdrop de algunos tokens. Para ello, primero cambia a la red local, ya que es donde vamos a trabajar para empezar:

solana config set --url localhost

A continuación, inicia la red local. Este va a ser un nodo local de Solana en el que podemos desplegar para hacer pruebas:

solana-test-validator

Una vez que la red local esté funcionando, puedes (airdrop) enviar tokens a tu cuenta. Con la red en funcionamiento, abre una ventana aparte y ejecuta el siguiente comando:

solana airdrop 100

Puedes comprobar el saldo de tu wallet:

solana balance# orsolana balance <address>

Ahora deberías tener un saldo de 100 SOL en tu wallet. Con eso, podemos empezar a construir.

Vamos a empezar a construir

Para empezar, inicializa un nuevo proyecto anchor y cambia al nuevo directorio:

anchor init mysolanaapp --javascript

cd mysolanaapp
  • Asegúrate de utilizar la versión 0.16.0 o posterior de Anchor.

En este proyecto, verás cuatro carpetas principales (además de node_modules):

app — Donde irá nuestro código frontend

programs — Aquí es donde vive el código Rust para el programa Solana

test — Donde viven las pruebas de JavaScript para el programa

migrations — Un script de despliegue básico

Veamos el programa que se creó para nosotros.

Anchor utiliza, y nos permite escribir, un eDSL (DSL embebido) que abstrae muchas de las operaciones de bajo nivel más complejas que típicamente necesitarías hacer si estuvieras usando Solana y Rust sin él, haciéndolo más accesible para mí.

// 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 {}

Este es probablemente el programa más básico que puedes escribir. Lo único que sucede aquí es que estamos definiendo una función llamada initialize, que cuando se invoca simplemente sale del programa con éxito. No hay ninguna manipulación de datos.

La estructura Initialize define el contexto como vacío de cualquier argumento. Aprenderemos más sobre el contexto de la función más adelante.

Para compilar este programa, podemos ejecutar el comando Anchor build :

anchor build

Una vez que se completa la compilación, deberías ver una nueva carpeta llamada target.

Uno de los artefactos creados es un IDL ubicado en target/idl/mysolanaapp.json.

Los IDLs son muy similares a un ABI en Solidity (o a una definición de consulta en GraphQL), y los usaremos de manera similar en nuestras pruebas y frontends de JavaScript para comunicarnos con nuestro programa Solana vía RPC.

También podemos probar nuestro programa. Si abres tests/mysolanaapp.js, veras que hay un test escrito en JavaScript que nos permite probar el programa.

El test debería tener el siguiente aspecto:

const anchor = require('@project-serum/anchor');describe('mysolanaapp', () => {
// Configure the client to use the local cluster.
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);
});
});

Hay un par de cosas que aprender de esta prueba que son importantes y que usaremos en el futuro, tanto en nuestras pruebas como en los clientes front-end de JavaScript.

Para llamar a un programa de Solana usando Anchor, típicamente necesitamos dos cosas principales:

  1. Provider - El Provider es una abstracción de una conexión a la red Solana, que suele consistir en una Connection, wallet y un compromiso de verificación previa.

En la prueba, el framework Anchor creará el provider por nosotros basándose en el entorno (anchor.Provider.env()), pero en el cliente tendremos que construir el Provider nosotros mismos usando la wallet Solana del usuario.

2. program - El program es una abstracción que combina el Provider, idl, y el programID (que se genera cuando se construye el programa) y nos permite llamar a métodos RPC contra nuestro programa.

De nuevo, como con el Provider, Anchor ofrece una forma conveniente de acceder al program, pero cuando construyamos el front-end tendremos que construir este provider nosotros mismos.

Una vez que tenemos estas dos cosas, podemos empezar a llamar a funciones en nuestro programa. Por ejemplo, en nuestro programa tenemos una función de initialize. En nuestra prueba, verás que podemos invocar esa función directamente usando program.rpc.functionName:

const tx = await program.rpc.initialize();

Este es un patrón muy común que usarás mucho cuando trabajes con Anchor, y una vez que entiendas cómo funciona, hace que sea realmente fácil conectarse e interactuar con un programa de Solana.

Ahora podemos probar el programa ejecutando el testscript:

anchor test

Construyendo Hello World

Ahora que tenemos nuestro proyecto configurado, vamos a crear algo un poco más interesante.

Sé que, como desarrollador de pila completa, la mayor parte del tiempo me pregunto cómo hacer operaciones de tipo CRUD, así que eso es lo que veremos a continuación.

El primer programa que crearemos nos permitirá crear un contador que se incremente cada vez que lo llamemos desde una aplicación cliente.

Lo primero que debemos hacer es abrir programs/mysolanaapp/src/lib.rs y actualizarlo con el siguiente 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(())
}
}

// Transaction instructions
#[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>,
}

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

// An account that goes inside a transaction instruction
#[account]
pub struct BaseAccount {
pub count: u64,
}

En este programa tenemos dos funciones - create y increment. Estas dos funciones son los handlers de peticiones RPC que podremos llamar desde una aplicación cliente para interactuar con el programa.

El primer parámetro de un handler RPC es la estructura Context, que describe el contexto que se pasará cuando se llame a la función y cómo manejarlo. En el caso de Create, esperamos tres parámetros: base_account, user, y system_program.

Los atributos #[account(...)]definen las restricciones e instrucciones que están relacionadas con la cuenta de origen donde se declara. Si alguna de estas restricciones no se mantiene, entonces la instrucción nunca se ejecutará.

Cualquier cliente que llame a este programa con la base_accountadecuada puede llamar a estos métodos RPC.

La forma en que Solana maneja los datos es muy diferente a cualquier cosa con la que haya trabajado. No hay estado persistente dentro del programa, todo se adjunta a lo que se conoce como cuentas. Una cuenta contiene esencialmente todo el estado de un programa. Debido a esto, todos los datos se pasan por referencia desde el exterior.

Tampoco hay operaciones de lectura. Esto se debe a que todo lo que necesitas hacer para leer el contenido de un programa es solicitar la cuenta, a partir de ahí eres capaz de ver todo el estado del programa. Para leer más sobre el funcionamiento de las cuentas, consulta este post.

Para construir el programa:

anchor build

A continuación, vamos a escribir un test que utilice este programa de contadores. Para ello, abre tests/mysolanaapp.js y actualízalo con el siguiente 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 () => {
/* Call the create function 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],
});
/* Fetch the account and check the value of count */
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);
});
});

Antes de continuar probando y desplegando el programa, queremos obtener el ID del programa generado dinámicamente por la compilación. Necesitamos este ID para usarlo en el programa de Rust para reemplazar el ID del marcador de posición que establecimos cuando creamos el proyecto. Para obtener este ID, podemos ejecutar el siguiente comando:

solana address -k target/deploy/mysolanaapp-keypair.json

Ahora podemos actualizar los IDs del programa en lib.rs:

// mysolanaapp/src/lib.rs

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

Y en Anchor.toml:

# Anchor.toml
[programs.localnet]
mysolanaapp = "your-program-id"

A continuación, ejecuta la prueba:

anchor test

Una vez que pasa la prueba, ahora podemos desplegar.

Ahora podemos desplegar el programa. Asegúrate de que solana-test-validator está ejecutando:

anchor deploy

También puedes ver el registro del validador abriendo una ventana aparte y ejecutando solana logs

Ahora estamos listos para construir el front-end.

Construir la aplicación React

En la raíz del proyecto Anchor, crea una nueva aplicación React para sobrescribir el directorio de aplicaciones existente:

npx create-react-app app

A continuación, instala las dependencias que necesitaremos para Anchor y Solana Web3:

cd app

npm install @project-serum/anchor @solana/web3.js

También usaremos Solana Wallet Adapter para manejar la conexión de la wallet Solana del usuario. Vamos a instalar esas dependencias también:

npm install @solana/wallet-adapter-react \
@solana/wallet-adapter-react-ui @solana/wallet-adapter-wallets \
@solana/wallet-adapter-base

A continuación, en el directorio src, crea un nuevo archivo llamado idl.json. Aquí, copia el IDL JSON que fue creado para ti en la carpeta principal del proyecto, ubicado en target/idl/mysolanaapp.json.

Sería bueno si pudiéramos copiar este archivo idl automáticamente a nuestra carpeta src de la aplicación cliente, pero hasta ahora no he encontrado una manera de hacer esto de forma nativa. Por supuesto, puedes crear tu propio script que haga esto si lo deseas, o bien necesitas copiar y pegar sobre el IDL después de cada cambio en tu programa principal.

Si quieres un script como este, puedes hacerlo en sólo un par de líneas 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));

A continuación, abre app/src/App.js y actualízalo con lo siguiente:

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 = [
/* view list of available wallets at https://github.com/solana-labs/wallet-adapter#wallets */
new PhantomWalletAdapter()
]
const { SystemProgram, Keypair } = web3;
/* create an account */
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() {
/* 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 createCounter() {
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.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) {
/* If the user's wallet is not connected, display connect wallet button. */
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>
);
}
}
/* wallet configuration as specified here: 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;

Cambiando la red de tu wallet

Antes de poder interactuar con un programa en la red localhost, debemos cambiar nuestra wallet Phantom a la red adecuada.

Para ello, abre tu wallet Phantom y haz clic en el botón de configuración. A continuación, desplázate hacia abajo para cambiar la red (Change Network):

A continuación, elije Localhost:

Ahora tenemos que airdropear tokens a esta cartera. En la parte superior de la interfaz de la wallet, haz clic en tu dirección para copiarla en tu portapapeles.

A continuación, abre tu terminal y ejecuta este comando (asegúrate de que solana-test-validatorse está ejecutando):

solana airdrop 10 <address>

Ahora deberías tener 10 tokens en tu wallet. Ahora podemos ejecutar y probar la aplicación!.

Entra en el directorio de la aplicación y ejecuta el siguiente comando:

npm start

Deberías poder conectar tu wallet, crear un contador e incrementarlo.

Notarás que cuando refrescas, pierdes el estado del programa. Esto es porque estamos generando dinámicamente la cuenta base cuando se carga el programa. Si quieres leer e interactuar con los datos del programa a través de varios clientes, tendrías que crear y almacenar el Keypair en algún lugar de tu proyecto. He reunido una gist de un enfoque ingenuo de cómo podría verse esto.

Hola Mundo parte 2

Vamos a crear una variación de este programa que, en lugar de tratar con un contador, nos permita crear un mensaje y llevar la cuenta de todos los mensajes creados anteriormente.

Para ello, actualicemos nuestro programa de Rust para que tenga el siguiente aspecto:

/* 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>,
}

En este programa tenemos dos piezas principales de datos de las que hacemos un seguimiento, un String llamado data y un Vector que contiene una lista de todos los datos añadidos al programa llamado data_list.

Notarás que la asignación de memoria aquí es más alta (128 + 128) que en el programa anterior para tener en cuenta el Vector. No sé cuántas actualizaciones podrías almacenar en este programa tal como está, pero puede ser algo para investigar más o experimentar, ya que este ejemplo en sí mismo es experimental y sólo para darte una idea de cómo funcionan las cosas.

A continuación, podemos actualizar el test para este nuevo 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);
});
});

Para probarlo:

anchor test

Si la prueba falla, prueba a desactivar el validador y a ejecutarlo de nuevo.

A continuación, vamos a actualizar el 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;

A continuación, construye y despliega el programa (asegurate que solana-test-validator is running):

anchor buildanchor deploy

Con la nueva compilación tendrás un nuevo IDL que tendrás que actualizar para tu cliente. Copia el nuevo IDL en app/src/idl.json o ejecuta tu script copyIdl.js.

Prueba del programa

Cuando pruebes el nuevo programa, asegúrate de actualizar el archivo idl.json creado por la compilación.

Cambia al directorio de la aplicación y ejecuta el comando start :

npm start

Despliegue a Devnet

El despliegue a una red en vivo es bastante sencillo a partir de aquí. Las principales cosas que tenemos que hacer son:

1. Actualizar la CLI de Solana para usar devnet:

solana config set --url devnet

2. Actualizar Phantom wallet para que utilice devnet

3. Abrir Anchor.toml y actualizar el cluster de localnet a devnet.

4. Reconstruir el programa. Asegúrate de que el ID del programa en Anchor.toml coincida con el ID del programa actual.

5. Desplegar el programa de nuevo, esta vez se desplegará en devnet

6. En app/src/App.js, necesitamos también actualizar la red, esta vez usando el clusterApiUrl de @solana/web3, así:

/* before */
<ConnectionProvider endpoint="http://127.0.0.1:8899">
/* after */
import {
...,
clusterApiUrl
} from '@solana/web3.js';
const network = clusterApiUrl('devnet');<ConnectionProvider endpoint={network}>

A partir de aquí, deberías poder desplegar y probar como hemos hecho los pasos anteriores.

  • El código de este proyecto se encuentra aquí

Próximos pasos

Otro tutorial en profundidad que sugeriría revisar a continuación es Create a Solana dApp from scratch que implementa una versión simplificada de Twitter como una dapp de Solana.

Si estás interesado en trabajar con tecnología como esta a tiempo completo, únete a mí y a mi equipo en Edge & Node, ¡estamos contratando!

--

--

Lorena Fabris
Lorena Fabris

Written by Lorena Fabris

Lawyer, Political Scientist, Blockchain Enthusiast

No responses yet