En La guía completa para el desarrollo Full Stack en Ethereum, realicé un análisis profundo sobre cómo construir una dApp Full Stack en Ethereum, que también puede aplicarse a otras cadenas compatibles con EVM como Polygon, Avalanche y las soluciones Layer 2 de Ethereum como Arbitrum.
En esta guía, quiero profundizar en Solana para mostrarte cómo construir una dApp Full Stack. También quiero presentarte el ecosistema y las herramientas de desarrollo para ayudarte a empezar a construir tus propias ideas y aplicaciones.
El código para el proyecto está ubicado aquí.
Como alguien que acaba de empezar a aprender Solidity y su ecosistema hace unos 6 meses, asumí que no sería mucho más difícil empezar con Solana. Me equivoqué.
Partes de las herramientas para desarrolladores son realmente agradables y pulidas (el 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 familiarizas con todo, rápidamente se vuelve mucho más fácil entender cómo empezar a implementar tus propias ideas y comenzar a experimentar.
Una de las claves para encontrar respuestas es ser vigilante en la búsqueda en 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, quien creó el marco Anchor. Familiarízate con la función de búsqueda; a menudo puedes encontrar respuestas a tus preguntas en discusiones anteriores en Discord.
Las herramientas que utilizaremos hoy incluyen:
Solana Tool Suite - Esto incluye un CLI realmente pulido y bien documentado para interactuar con la red de Solana.
Anchor Framework - Anchor es realmente un salvavidas para mí, y estoy casi seguro de que no habría podido superar las dificultades de construir nada sin él. Es el Hardhat del desarrollo en Solana y más, y me encanta. También ofrece un DSL sobre Rust para que no necesites un conocimiento profundo del lenguaje para comenzar, aunque todavía estoy tratando de aprender Rust ya que probablemente será útil para construir algo no trivial, incluso con el DSL. Un buen lugar 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 fue casi inutilizable para mí.
React - El marco del lado del cliente.
Omitiré todos los detalles en profundidad sobre cómo funciona Solana, ya que otras personas pueden cubrir esto mejor que yo. En cambio, intentaré centrarme en construir algo y compartir los detalles que necesitas conocer para lograr esto, junto con cosas que creo que son de suma importancia.
Si quieres aprender más sobre Solana y cómo funciona, aquí hay algunos buenos recursos:
En esta guía nos centraremos principalmente en la configuración del proyecto, pruebas e integración del cliente del lado del frontend para construir un par de tipos de aplicaciones, principalmente centradas en operaciones CRUD (sin la eliminación, por supuesto), que encontré algo no documentadas (integración con aplicaciones del cliente).
También aprenderemos cómo hacer airdrop de tokens a nuestras propias cuentas de desarrollo usando el CLI de Solana, y desplegar nuestras aplicaciones tanto en una red local como en una red de prueba en vivo.
No nos centraremos en los NFT en esta guía, pero tal vez me concentre en hacer eso en una guía futura. Por ahora, si estás interesado en construir un mercado de NFT en Solana, te recomiendo que revises Metaplex.
Este tutorial cubre cómo construir una aplicación Full Stack en Solana, pero no entra en cómo instalar todas las dependencias individuales.
En su lugar, enumeraré las dependencias y enlazaré a la documentación para su instalación, ya que cada proyecto podrá explicar y documentar estas cosas mejor de lo que yo jamás podría, además de mantenerlas actualizadas.
Antes de empezar a construir, echemos un vistazo al CLI de Solana.
Las principales cosas que haremos con el CLI de Solana serán configurar nuestra red (entre localhost y una red de prueba para desarrolladores) y hacer airdrop de tokens en nuestras billeteras. Prácticamente todo lo demás lo haremos con el CLI de Anchor.
Por ejemplo, podemos verificar la configuración actual de la red (y otras configuraciones) 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 ruta de Keypair, configúrala siguiendo las instrucciones aquí.
Podemos cambiar la red de la siguiente manera:
# establecer a localhost
solana config set --url localhost
# establecer a devnet
solana config set --url devnet
Esto es importante ya que necesitarás estar al tanto de qué red estás usando mientras construyes, pruebas y despliegas tus programas. También debes asegurarte de que tu billetera esté usando la misma red que tu entorno local cuando pruebes, algo que cubriré.
Comenzaremos desarrollando en una red localhost
, luego cambiaremos a la red devnet
.
También podemos usar el CLI para ver nuestra dirección de billetera local actual:
solana address
Y luego obtener los detalles completos sobre una cuenta:
solana account <dirección de arriba>
A continuación, hagamos airdrop de algunos tokens. Primero, cambia a la red local, ya que aquí es donde comenzaremos a trabajar:
solana config set --url localhost
Luego, inicia la red local. Esto va a ser un nodo local de Solana al que podemos desplegar para pruebas:
solana-test-validator
Una vez que la red local esté funcionando, puedes hacer airdrop de tokens a tu cuenta. Con la red en funcionamiento, abre una ventana separada y ejecuta el siguiente comando:
solana airdrop 100
Puedes verificar el saldo de tu billetera:
solana balance
# o
solana balance <dirección>
Ahora deberías tener un saldo de 100 SOL en tu billetera. Con eso, podemos empezar a construir.
Para empezar, inicializa un nuevo proyecto de Anchor y cambia al nuevo directorio:
anchor init mysolanaapp --javascript
cd mysolanaapp
Asegúrate de usar 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 de frontend.
programs - Aquí es donde vive el código de Rust para el programa de Solana.
test - Donde viven las pruebas de JavaScript para el programa.
migrations - Un script básico de despliegue.
Echemos un vistazo al programa que se creó para nosotros.
Anchor usa, y nos permite escribir, un eDSL que abstrae muchas de las operaciones de bajo nivel más complejas que normalmente tendrías que 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 manipulación de datos en absoluto.
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 build
de Anchor:
anchor build
Una vez que la compilación esté completa, deberías ver una nueva carpeta llamada target.
Uno de los artefactos creados es un IDL ubicado en target/idl/mysolanaapp.json.
Los IDL son muy similares a un ABI en Solidity (o una definición de consulta en GraphQL), y los usaremos de manera similar en nuestras pruebas de JavaScript y en los frontends para comunicarnos con nuestro programa de Solana a través de RPC.
También podemos probar nuestro programa. Si abres tests/mysolanaapp.js, verás que hay una prueba escrita en JavaScript que nos permite probar el programa.
La prueba debería verse así:
const anchor = require('@project-serum/anchor');
describe('mysolanaapp', () => {
// Configura el cliente para usar el clúster local.
anchor.setProvider(anchor.Provider.env());
it('¡Está inicializado!', async () => {
const program = anchor.workspace.Mysolanaapp;
const tx = await program.rpc.initialize();
console.log("Tu firma de transacción", tx);
});
});
Hay un par de cosas que aprender de esta prueba que son importantes y que utilizaremos en el futuro, tanto en nuestras pruebas como en los clientes de JavaScript del frontend.
Para llamar a un programa de Solana usando Anchor, normalmente necesitamos dos cosas principales:
1. Provider
- El Provider
es una abstracción de una conexión a la red de Solana, que generalmente consta de una Connection, una billetera y un preflight commitment.
En la prueba, el marco Anchor creará el proveedor para nosotros en función del entorno (anchor.Provider.env()
), pero en el cliente necesitaremos construir el Proveedor nosotros mismos usando la billetera de Solana del usuario.
2. program
- El program
es una abstracción que combina el Provider
, el 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, al igual que con el Provider
, Anchor ofrece una forma conveniente de acceder al program
, pero al construir el frontend necesitaremos construir este provider
nosotros mismos.
Una vez que tengamos estas dos cosas, podemos empezar a llamar a funciones en nuestro programa. Por ejemplo, en nuestro programa tenemos una función 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 script test
:
anchor test
Ahora que tenemos configurado nuestro proyecto, vamos a crear algo un poco más interesante.
Sé que, como desarrollador Full Stack, la mayoría del tiempo me pregunto cómo hacer tipos de operaciones CRUD, así que eso es lo que veremos a continuación.
El primer programa que crearemos nos permitirá crear un contador que incrementa cada vez que lo llamamos desde una aplicación cliente.
Lo primero que necesitamos 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(())
}
}
// Instrucciones de transacción
#[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>,
}
// Instrucciones de transacción
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut)]
pub base_account: Account<'info, BaseAccount>,
}
// Una cuenta que va dentro de una instrucción de transacción
#[account]
pub struct BaseAccount {
pub count: u64,
}
En este programa tenemos dos funciones - create
e increment
. Estas dos funciones son los manejadores de solicitudes RPC que podremos llamar desde una aplicación cliente para interactuar con el programa.
El primer parámetro de un manejador 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
, estamos esperando tres parámetros: base_account
, user
y system_program
.
Los atributos #[account(...)]
definen restricciones e instrucciones relacionadas con la cuenta que se declara a continuación. Si alguna de estas restricciones no se cumple, la instrucción nunca se ejecutará.
Cualquier cliente que llame a este programa con la base_account
adecuada 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 está adjunto a lo que se conoce como cuentas. Una cuenta esencialmente contiene 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í, puedes ver todo el estado del programa. Para leer más sobre cómo funcionan las cuentas, consulta este post.
Para compilar el programa:
anchor build
A continuación, escribamos una prueba que use este programa de contador. Para hacerlo, abre tests/mysolanaapp.js y actualiza con el siguiente código:
const assert = require("assert");
const anchor = require("@project-serum/anchor");
const { SystemProgram } = anchor.web3;
describe("mysolanaapp", () => {
/* crear y establecer un Proveedor */
const provider = anchor.Provider.env();
anchor.setProvider(provider);
const program = anchor.workspace.Mysolanaapp;
it("Crea un contador", async () => {
/* Llama a la función create mediante RPC */
const baseAccount = anchor.web3.Keypair.generate();
await program.rpc.create({
accounts: {
baseAccount: baseAccount.publicKey,
user: provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
},
signers: [baseAccount],
});
/* Obtiene la cuenta y verifica el valor del contador */
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("Incrementa el contador", 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 para probar y desplegar el programa, queremos obtener el ID del programa que se generó dinámicamente durante la compilación. Necesitamos este ID para usarlo en el programa de Rust y reemplazar el ID de marcador de posición que configuramos 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 la prueba pase, podemos desplegar.
Podemos desplegar el programa. Asegúrate de que solana-test-validator
esté ejecutándose:
anchor deploy
También puedes ver el registro del validador abriendo una ventana separada y ejecutando solana logs
.
Ahora estamos listos para construir el frontend.
En la raíz del proyecto Anchor, crea una nueva aplicación React para sobrescribir el directorio app 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 billetera de Solana del usuario. Instalemos 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 JSON de IDL que se creó 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 nuestro directorio de aplicaciones cliente src, pero hasta ahora no he encontrado una manera de hacer esto de forma nativa. Puedes, por supuesto, crear tu propio script que haga esto si lo deseas, o bien necesitas copiar y pegar el IDL después de cada cambio en tu programa principal.
Si quieres un script como este, puedes hacerlo en solo 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 = [
/* ver la lista de billeteras disponibles en https://github.com/solana-labs/wallet-adapter#wallets */
new PhantomWalletAdapter()
]
const { SystemProgram, Keypair } = web3;
/* crear una cuenta */
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() {
/* crear el proveedor y devolverlo al llamante */
/* red configurada para la red local por ahora */
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()
/* crear la interfaz del programa combinando el idl, program ID y el proveedor */
const program = new Program(idl, programID, provider);
try {
/* interactuar con el programa a través de 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("Error en la transacción: ", 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) {
/* Si la billetera del usuario no está conectada, muestra el botón de conectar billetera. */
return (
<div style={{ display: 'flex', justifyContent: 'center', marginTop:'100px' }}>
<WalletMultiButton />
</div>
)
} else {
return (
<div className="App">
<div>
{
!value && (<button onClick={createCounter}>Crear contador</button>)
}
{
value && <button onClick={increment}>Incrementar contador</button>
}
{
value && value >= Number(0) ? (
<h2>{value}</h2>
) : (
<h3>Por favor crea el contador.</h3>
)
}
</div>
</div>
);
}
}
/* configuración de la billetera según se especifica aquí: 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;
Antes de que podamos interactuar con un programa en la red localhost
, debemos cambiar nuestra billetera Phantom a la red adecuada.
Para hacerlo, abre tu billetera Phantom y haz clic en el botón de configuración. Luego desplázate hacia abajo hasta Cambiar Red:
A continuación, elige Localhost:
Ahora necesitamos hacer airdrop de tokens a esta billetera. En la parte superior de la interfaz de la billetera, haz clic en tu dirección para copiarla al portapapeles.
A continuación, abre tu terminal y ejecuta este comando (asegúrate de que solana-test-validator
esté ejecutándose):
solana airdrop 10 <address>
Ahora deberías tener 10 tokens en tu billetera. Ahora, ¡podemos ejecutar y probar la aplicación!
Cambia al directorio app y ejecuta el siguiente comando:
npm start
Deberías poder conectar tu billetera, crear un contador e incrementarlo.
Notarás que cuando actualizas, pierdes el estado del programa. Esto se debe a que estamos generando dinámicamente la cuenta base cuando se carga el programa. Si deseas leer e interactuar con los datos del programa en varios clientes, necesitarías crear y almacenar el Keypair en algún lugar de tu proyecto. He reunido un gist de un enfoque ingenuo de cómo podría verse esto.
Creemos una variación de este programa que, en lugar de tratar con un contador, nos permita crear un mensaje y hacer un seguimiento de todos los mensajes creados previamente.
Para hacerlo, actual
icemos nuestro programa de Rust para que se vea así:
/* 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 que estamos rastreando: una cadena llamada data
y un vector que contiene una lista de todos los datos agregados al programa llamado data_list
.
Notarás que la asignación de memoria aquí es mayor (128 + 128
) que el programa anterior para tener en cuenta el vector. No sé cuántas actualizaciones podrías almacenar en este programa tal como está, pero podría ser algo para investigar más o experimentar, ya que este ejemplo en sí mismo es experimental y solo para darte una comprensión de cómo funcionan las cosas.
A continuación, podemos actualizar la prueba 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("Inicializa la cuenta", 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("Actualiza una cuenta creada anteriormente", 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, intenta apagar el validador y luego ejecutarlo de nuevo.
A continuación, actualicemos 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() {
/* crear el proveedor y devolverlo al llamante */
/* red configurada para la red local por ahora */
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();
/* crear la interfaz del programa combinando el idl, program ID y el proveedor */
const program = new Program(idl, programID, provider);
try {
/* interactuar con el programa a través de 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("Error en la transacción: ", 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}>Inicializar</button>)
}
{
value ? (
<div>
<h2>Valor actual: {value}</h2>
<input
placeholder="Agregar nuevos datos"
onChange={e => setInput(e.target.value)}
value={input}
/>
<button onClick={update}>Agregar datos</button>
</div>
) : (
<h3>Por favor inicializa.</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, compila y despliega el programa (asegúrate de que solana-test-validator
esté ejecutándose):
anchor build
anchor deploy
Con la nueva compilación tendrás un nuevo IDL que necesitarás actualizar para tu cliente. Copia el nuevo IDL en app/src/idl.json o ejecuta tu script copyIdl.js.
Al probar el nuevo programa, asegúrate de actualizar el archivo idl.json que se creó durante la compilación.
Cambia al directorio app y ejecuta el comando start
:
npm start
Desplegar en una red en vivo es bastante sencillo desde aquí. Las principales cosas que necesitamos hacer son:
1. Actualizar el CLI de Solana para usar devnet
:
solana config set --url devnet
2. Actualizar la billetera Phantom para usar devnet
3. Abrir Anchor.toml y actualizar el clúster 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, también necesitamos actualizar la red, esta vez usando clusterApiUrl
de @solana/web3
, de esta manera:
/* antes */
<ConnectionProvider endpoint="http://127.0.0.1:8899">
/* después */
import {
...,
cluster
ApiUrl
} from '@solana/web3.js';
const network = clusterApiUrl('devnet');
<ConnectionProvider endpoint={network}>
Desde aquí, deberías poder desplegar y probar como hemos hecho en los pasos anteriores.
Jorge García
Fullstack developer