Desenvolvimento Client Side
A maioria das dApps usa TypeScript para interagir com programas Solana implantados. Entender como integrar seu program no lado do cliente é essencial para construir aplicações funcionais.
Anchor Client SDK
O Anchor simplifica a interação do cliente com programas Solana através de um arquivo Interface Description Language (IDL) que espelha a estrutura do seu program.
Quando combinado com a biblioteca TypeScript do Anchor (@coral-xyz/anchor), o IDL fornece uma abordagem simplificada para construir instructions e transações.
Configuração
O pacote @coral-xyz/anchor é instalado automaticamente ao criar um programa Anchor. Após executar o anchor build, o Anchor gera:
Um IDL em
target/idl/<program-name>.jsonUm SDK TypeScript em
target/types/<program-name>.ts
Esses arquivos abstraem grande parte da complexidade subjacente. Transfira-os para seu cliente TypeScript usando esta estrutura:
src
├── anchor
│ ├── <program-name>.json
│ └── <program-name>.ts
└── integration.tsPara usar o wallet adapter com o SDK TypeScript do Anchor, crie um objeto Provider que combina a Connection (localhost, devnet ou mainnet) e a Wallet (o endereço que paga e assina as transações).
Configure a Wallet e a Connection:
import { useAnchorWallet, useConnection } from "@solana/wallet-adapter-react";
const { connection } = useConnection();
const wallet = useAnchorWallet();Crie o objeto Provider e defina-o como padrão:
import { AnchorProvider, setProvider } from "@coral-xyz/anchor";
const provider = new AnchorProvider(connection, wallet, {
commitment: "confirmed",
});
setProvider(provider);Program
O objeto Program do Anchor cria uma API personalizada para interagir com programas Solana. Esta API serve como a interface central para toda a comunicação com o program onchain:
Enviar transações,
Buscar accounts desserializadas,
Decodificar dados de instruction,
Inscrever-se para mudanças em accounts,
Ouvir eventos
Crie o objeto Program importando os tipos e o IDL:
import <program-name> from "./<program-name>.json";
import type { <Program-Type> } from "./<program-name>.ts";
import { Program, Idl } from "@coral-xyz/anchor";
const program = new Program(<program-name> as <Program-Type>);Se você não definiu um provider padrão, especifique-o explicitamente:
const program = new Program(<program-name> as <Program-Type>, provider);Uma vez configurado, use o Anchor Methods Builder para construir instructions e transações. O MethodsBuilder usa o IDL para fornecer um formato simplificado para construir transações que invocam instructions do program.
O padrão básico do MethodsBuilder:
await program.methods
.instructionName(instructionDataInputs)
.accounts({})
.signers([])
.rpc();Passe signers adicionais além do provider usando .signers().
Accounts
Use sintaxe de ponto para chamar .accounts no MethodsBuilder, passando um objeto com cada account que a instruction espera com base no IDL.
Transações
O método padrão para enviar transações através do Anchor é .rpc(), que envia a transação diretamente para a blockchain.
Para cenários que exigem assinatura no backend (como criar uma transação no frontend com a wallet do usuário e então assinar de forma segura com um keypair do backend), use .transaction():
const transaction = await program.methods
.instructionName(instructionDataInputs)
.accounts({})
.transaction();
//... Sign the transaction in the backend
// Send the transaction to the chain
await sendTransaction(transaction, connection);Para agrupar múltiplas instructions do Anchor, use .instruction() para obter objetos de instruction:
// Create first instruction
const instructionOne = await program.methods
.instructionOneName(instructionOneDataInputs)
.accounts({})
.instruction();
// Create second instruction
const instructionTwo = await program.methods
.instructionTwoName(instructionTwoDataInputs)
.accounts({})
.instruction();
// Add both instructions to one transaction
const transaction = new Transaction().add(instructionOne, instructionTwo);
// Send transaction
await sendTransaction(transaction, connection);Fetch and Filter Accounts
Quando seu program cria centenas de accounts, rastreá-las torna-se desafiador. O objeto Program fornece métodos para buscar e filtrar accounts do program de forma eficiente.
Busque todos os endereços de um tipo específico de account:
const accounts = await program.account.counter.all();Filtre accounts específicas usando a flag memcmp:
const accounts = await program.account.counter.all([
{
memcmp: {
offset: 8,
bytes: bs58.encode(new BN(0, "le").toArray()),
},
},
]);Para verificar se os dados da account mudaram, busque dados desserializados de uma account específica usando fetch:
const account = await program.account.counter.fetch(ACCOUNT_ADDRESS);Busque múltiplas accounts simultaneamente:
const accounts = await program.account.counter.fetchMultiple([
ACCOUNT_ADDRESS_ONE,
ACCOUNT_ADDRESS_TWO,
]);Events and Webhooks
Em vez de buscar dados onchain toda vez que os usuários conectam suas wallets, configure sistemas que ouvem a blockchain e armazenam dados relevantes em um banco de dados.
Existem duas abordagens principais para ouvir eventos onchain:
Polling: O cliente verifica repetidamente por novos dados em intervalos. O servidor responde com os dados mais recentes independentemente de mudanças, potencialmente retornando informações duplicadas.
Streaming: O servidor envia dados ao cliente apenas quando ocorrem atualizações. Isso proporciona transferência de dados mais eficiente e em tempo real, pois apenas mudanças relevantes são transmitidas.
Para fazer streaming de instructions do Anchor, use webhooks que ouvem eventos e os enviam para seu servidor quando ocorrem. Por exemplo, atualize uma entrada no banco de dados sempre que uma venda de NFT acontecer no seu marketplace.
O Anchor fornece duas macros para emitir eventos:
emit!(): Emite eventos diretamente para os logs do program usando a syscallsol_log_data(), codificando dados do evento como strings base64 prefixadas com "Program Data"emit_cpi!(): Emite eventos através de Cross Program Invocations (CPIs). Os dados do evento são codificados e incluídos nos dados da instruction da CPI em vez dos logs do program
Macro emit!()
Implementação no program:
use anchor_lang::prelude::*;
declare_id!("8T7MsCZyzxboviPJg5Rc7d8iqEcDReYR2pkQKrmbg7dy");
#[program]
pub mod event {
use super::*;
pub fn emit_event(_ctx: Context<EmitEvent>, input: String) -> Result<()> {
emit!(CustomEvent { message: input });
Ok(())
}
}
#[derive(Accounts)]
pub struct EmitEvent {}
#[event]
pub struct CustomEvent {
pub message: String,
}Escuta de eventos no lado do cliente com helpers do SDK do Anchor para decodificação base64:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { Event } from "../target/types/event";
describe("event", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Event as Program<Event>;
it("Emits custom event", async () => {
// Set up listener before sending transaction
const listenerId = program.addEventListener("customEvent", event => {
// Process the event data
console.log("Event Data:", event);
});
});
});Macro emit_cpi!()
Implementação no program:
use anchor_lang::prelude::*;
declare_id!("2cDQ2LxKwQ8fnFUz4LLrZ157QzBnhPNeQrTSmWcpVin1");
#[program]
pub mod event_cpi {
use super::*;
pub fn emit_event(ctx: Context<EmitEvent>, input: String) -> Result<()> {
emit_cpi!(CustomEvent { message: input });
Ok(())
}
}
#[event_cpi]
#[derive(Accounts)]
pub struct EmitEvent {}
#[event]
pub struct CustomEvent {
pub message: String,
}Decodificação de eventos no lado do cliente:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { EventCpi } from "../target/types/event_cpi";
describe("event-cpi", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.EventCpi as Program<EventCpi>;
it("Emits custom event", async () => {
// Fetch the transaction data
const transactionData = await program.provider.connection.getTransaction(
transactionSignature,
{ commitment: "confirmed" },
);
// Decode the event data from the CPI instruction data
const eventIx = transactionData.meta.innerInstructions[0].instructions[0];
const rawData = anchor.utils.bytes.bs58.decode(eventIx.data);
const base64Data = anchor.utils.bytes.base64.encode(rawData.subarray(8));
const event = program.coder.events.decode(base64Data);
console.log(event);
});
});