Há algum tempo eu escrevi a primeira parte deste tutorial, sobre como fazer integração do seu site ou aplicação web com carteira cripto Solana, usando apenas JavaScript.
Isso é especialmente útil para quem estiver criando um dapp (aplicação distribuída), aplicação para web3 ou apenas querendo integrar carteiras Solana como meio de pagamento cripto.
No entanto, após a publicação da primeira parte, a dúvida número 1 da minha audiência foi: como lidar como outros tokens que não sejam o nativo da rede? Isso porque como conectamos na Solana, a criptomoeda dela é o SOL e qualquer saldo, transação, etc estará cotado nativamente nesta moeda.
Para entender melhor como fazer e consultar transações de outros tokens, valem algumas explicações antes.
Vamos lá!
#1 – Programas Solana
Quando o Bitcoin surgiu em 2008 para 2009 o seu foco, conforme consta no seu paper original, era ser um dinheiro eletrônico seguro e confiável P2P, ou seja, sem intermediários. O seu foco é e possivelmente sempre será ser “apenas” uma moeda digital, o que chamamos de criptomoeda 1.0.
Quando Vitalik Buterim criou o Ethereum em 2015 ele já pensava diferente. Ele viu na tecnologia blockchain a possibilidade de construir uma plataforma. Nesta plataforma, qualquer um poderia criar algoritmos que rodariam de maneira distribuída e descentralizada, incluindo novas criptomoedas, ou tokens, como passariam a ser chamadas as moedas não-nativas de uma blockchain. A esses algoritmos deu-se o nome de contratos inteligentes ou smart contracts.
Na prática, um contrato inteligente é como se fosse uma classe Java ou C#, mas que roda na blockchain, ou seja, sem um endereço central e específico, sem estar atrelado a um servidor. O programador do contrato inteligente faz deploy dele na blockchain e então o algoritmo passa a estar disponível publicamente, sendo executado por uma máquina virtual (EVM no caso da Etehereum) toda vez que requisitado e gravando os dados de suas transações na própria blockchain (o que incorre no famoso custo de gás). Isso é o que chamamos de criptomoeda 2.0 ou segunda geração.
A partir da terceira geração, com Cardano, Solana e outras moedas focadas em PoS (Proof of Stake), os smart contracts deram lugar aos programs, uma vez que sua utilidade ia muito além do que apenas acordos digitais.
Assim, contei toda essa história para lhe ajudar a entender que, qualquer que seja a atividade que você deseje fazer em uma blockchain 2.0 em diante que não seja consultar ou fazer transações com sua criptomoeda nativa, será feita através de contratos inteligentes ou programas. E isso responde, na teoria, a pergunta mais comum que recebi no tutorial anterior que é como consultar saldo e fazer transferência de outros tokens que o usuário possa ter na sua carteira Solana: através da interação com programas de tokens que existam na Solana.

#2 – SPL Token
A Solana Program Library (SPL) é uma coleção de programas de código-aberto que funcionam na blockchain da Solana, desenvolvidos e mantidos pela comunidade Solana e Solana Labs. Ela fornece um conjunto de componentes padronizados e pré-construídos que simplificam o desenvolvimento de aplicações descentralizadas (dApps) na blockchain Solana, funcionando como “blocos de construção” para os desenvolvedores.
Se você veio do mundo EVM, você pode criar uma associação com a biblioteca OpenZeppelin, porém é mais do que isso, já que como na Solana os programas são separados dos dados, diversos programas de SPL você pode utilizar diretamente chamando o executável do programa na Mainnet (deployado pela equipe da Solana), mas com o contexto (leia: accounts) do seu programa. Resumindo, é um OpenZeppelin com esteróides.
Um destes programas é o spl-token, que é o equivalente ao ERC-20 no mundo Solana. Ele define todo o comportamento padrão para novos tokens no ecossistema Solana incluindo questões de saldo, propriedade, transferência, mint, burn, freeze, etc. Além disso, ele faz a gestão das accounts necessárias para que um token funcione nessa rede e é o tipo de programa Solana mais comum.
E é isso que nós vamos aprender no tutorial de hoje: como interagir com programas de spl-tokens usando uma carteira Solana de browser. Antes de avançar, certifique-se de que você possui um spl-token deployado na blockchain, o que você pode aprender a fazer aqui ou que tenha o endereço de outro spl-token que você possa manipular.
Informação importante: vamos usar o padrão spl-token-2022 neste tutorial, que é o mais recente.
#3 – Ajustando o Projeto
Seguiremos aplicando os conhecimentos das bibliotecas @solana/wallet-adapter-* na mesma aplicação ReactJS que criamos na primeira parte. Não que isso faça grande diferença já que estamos usando JS puro, aplicável a qualquer outra tecnologia de frontend web como Angular e VueJS.
Vamos começar instalando uma dependência adicional que vamos precisar que é o da lib spl-token:
|
1 2 3 |
npm install @solana/spl-token buffer |
A dependência do buffer é interna e talvez você já tenha ela no projeto. Ela precisa ser configurada no index.html, da seguinte maneira, no lugar do carregamento do main.tsx:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<script type="module"> // Setup Buffer polyfill BEFORE importing app modules (async () => { const { Buffer } = await import('buffer'); globalThis.Buffer = Buffer; if (typeof window !== 'undefined') { window.Buffer = Buffer; } // Now load the main app await import('./src/main.tsx'); })(); </script> |
Com esse ajuste, o objeto Buffer será injetado na window da aplicação, pois via de regra, ele funciona apenas no backend. Mas para que isso funcione corretamente, ajuste o seu vite.config.js para:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' // https://vite.dev/config/ export default defineConfig({ plugins: [react()], define: { global: 'globalThis', }, resolve: { alias: { buffer: 'buffer', }, }, optimizeDeps: { include: ['buffer', '@solana/spl-token', '@solana/web3.js'], }, }) |
Nós vamos precisar também de um arquivo .env para definir algumas configurações importantíssimas. Crie como abaixo, na raiz do projeto de frontend:
|
1 2 3 4 5 |
VITE_SPL_TOKEN_MINT= VITE_TOKEN_DECIMALS= VITE_RPC_NODE= |
A saber:
- VITE_SPL_TOKEN_MINT: endereço da Token Mint Account na blockchain (account que contém os dados do token);
- VITE_TOKEN_DECIMALS: o número de casas decimais do seu token, sendo 9 o valor padrão;
- VITE_RPC_NODE: o nó RPC ao qual vamos nos conectar, sendo “devnet” o padrão;
Vamos começar também a reorganizar a nossa aplicação, criando um arquivo src/Web3Service.ts junto dos demais para que seja o nosso service, ou seja, a camada de comunicação da aplicação com recursos externos. Bem pra organização mesmo.
Por enquanto ele vai ter apenas as importações que vamos precisar e a inicialização de algumas variáveis.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import { Connection, PublicKey, Transaction } from '@solana/web3.js'; import { getAssociatedTokenAddress, createTransferInstruction, getAccount, createAssociatedTokenAccountInstruction, TOKEN_2022_PROGRAM_ID } from "@solana/spl-token"; import type { WalletContextState } from '@solana/wallet-adapter-react'; const TOKEN_MINT = new PublicKey(import.meta.env.VITE_SPL_TOKEN_MINT); const TOKEN_DECIMALS = parseInt(import.meta.env.VITE_TOKEN_DECIMALS || "9"); |
Agora nosso projeto está mais preparado para o que faremos a seguir que são as funções ligadas a tokens.
#4 – Saldo de Token
Vamos voltar ao nosso Web3Service.ts e vamos adicionar algumas novas funções, a começar pela getATA, que vai servir para retornar o endereço da Associated Token Account (ATA) de uma carteira Solana. É o ATA que guarda o saldo atual e para quem enviamos transferências também, vamos usar bastante esta função.
|
1 2 3 4 5 |
function getATA(publicKey: PublicKey) { return getAssociatedTokenAddress(TOKEN_MINT, publicKey, false, TOKEN_2022_PROGRAM_ID); } |
Atenção aqui ao fato de meu token usar o programa spl-token-2022, que permite extension metadata. Se o seu for um token padrão, mais antigo, o último parâmetro deve ser TOKEN_PROGRAM_ID apenas.
Também vamos precisar de uma helper function para formatar a escala de lamports para SOL, mas considerando o número de casas decimais do token em questão. Vou chamar essa função de formatTokenAmount:
|
1 2 3 4 5 |
function formatTokenAmount(amount: bigint | BigInt | number | string) { return Number(amount) / Math.pow(10, TOKEN_DECIMALS); } |
Agora com essas funções auxiliares prontas, vamos criar a getBalance, que nos traz o saldo de um token em uma carteira:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
export async function getBalance(connection: Connection, publicKey: PublicKey, callback: (balance: number) => void) { const ata = await getATA(publicKey); // Listen for changes to the token account connection.onAccountChange( ata, (updatedAccountInfo) => { // Parse the token account data to get the balance const tokenAccount = { amount: updatedAccountInfo.data.readBigUInt64LE(64) }; callback(formatTokenAmount(tokenAccount.amount)); }, "confirmed" ); try { const tokenAccount = await getAccount(connection, ata, "confirmed", TOKEN_2022_PROGRAM_ID); return formatTokenAmount(tokenAccount.amount); } catch (error) { console.log("Token account not found, balance is 0"); return 0; } } |
Aqui tenho várias coisas a comentar. Primeiro, começamos obtendo o ATA de onde pegaremos o saldo. Depois, eu configuro um listener para ser avisado toda vez que o saldo mudar, naquele ATA em questão. Quando isso acontecer, eu pego a nova amount e disparo o callback fornecido por parâmetro com a amount formatada. Esse callback vai ser configurado pelo nosso frontend, para alterar um state na UI.
Por fim, eu pego os detalhes da ATA com getAccount, formato a saída e retorno com o saldo atual. Em caso de erro, por exemplo porque a conta não existe, retorno zero.
Com essa função, podemos refatorar nossa UI para exibir o saldo de um token ao invés de SOL, no componente BalanceDisplay.tsx. Importe o getBalance no topo do componente e ajuste o useEffect dele para que use a referida função importada, não esquecendo de passar o setBalance como callback, para manter essa informação atualizada.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
useEffect(() => { const updateBalance = async () => { if (!connection || !publicKey) { console.error("Wallet not connected or connection unavailable"); return; } try { const balance = await getBalance(connection, publicKey, setBalance); setBalance(balance); } catch (error) { console.error("Failed to retrieve token account info:", error); } }; updateBalance(); }, [connection, publicKey]); |
Com isso, agora você terá o saldo do token que configurou na interface, veja.

#5 – Transferência de Token
Agora vamos fazer o mesmo trabalho mas para adaptar a transferência de tokens ao invés de SOL. Volte ao Web3Service.ts e vamos criar uma helper function para converter valores da escala de SOL para lamports, já que na UI o usuário vai colocar a quantia na maior escala.
|
1 2 3 4 5 |
function parseTokenAmount(amount: number | string) { return BigInt(Math.floor(Number(amount) * Math.pow(10, TOKEN_DECIMALS))); } |
Agora vamos criar a função sendTokens, que vai esperar a conexão com o nó Solana, o objeto de Wallet que recebemos da UI com a conexão à carteira de browser, o endereço da wallet para a qual vamos mandar tokens e a quantia de tokens, na escala de SOL.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
export async function sendTokens(connection: Connection, wallet: WalletContextState, toWallet: string, value: string) { const recipientPubKey = new PublicKey(toWallet); const transaction = new Transaction(); const senderATA = await getATA(wallet.publicKey!); const recipientATA = await getATA(recipientPubKey); try { console.log("Checking if recipient token account exists..."); await getAccount(connection, recipientATA, "confirmed", TOKEN_2022_PROGRAM_ID); } catch { console.log("Recipient token account not found, creating it..."); transaction.add( createAssociatedTokenAccountInstruction( wallet.publicKey!, recipientATA, recipientPubKey, TOKEN_MINT, TOKEN_2022_PROGRAM_ID ) ); } //Create transfer instruction const tokenAmount = parseTokenAmount(value); const transferInstruction = createTransferInstruction( senderATA, recipientATA, wallet.publicKey!, tokenAmount, undefined, TOKEN_2022_PROGRAM_ID ); transaction.add(transferInstruction); return wallet.sendTransaction(transaction, connection); } |
O primeiro passo é converter a string do endereço de destino para uma PublicKey, inicializar o objeto de Transaction e obter o endereço ATA das duas contas envolvidas na transferência: a que vai ter tokens deduzidos e a que vai receber a mesma quantia.
Aqui vai um detalhe importante: pode ser que o destinatário nunca tenho tido unidades daquele token e portanto ele não tem um ATA criado. Se tentar transferir tokens nestas condições você vai ter um erro. Para evitar isso, vamos tentar primeiro retornar o ATA na blockchain e, caso dê erro por não existir, a gente adiciona na transaction uma instrução para criação do ATA.
Agora com a garantia que o destinatário possui um ATA criado, a gente converte a quantia para o formato esperado e cria a instrução de transferência do senderATA para recipientATA, adicionando na transaction e enviando através da wallet.
Na UI do componente SendForm.tsx, pequenos ajustes são necessários, nas variáveis e states:
|
1 2 3 4 5 6 |
const wallet = useWallet(); const { connection } = useConnection(); const [value, setValue] = useState("0.5"); const [toWallet, setToWallet] = useState("84DAeL8XrucYZuNX5m5y8h5tVDUPgQkYVcqGk4F43VGk"); |
E na função sendToken (antiga sendSol), que vai ser chamada no click do botão:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
async function sendToken() { if (!wallet.publicKey) { console.error("Wallet not connected"); return; } try { const hash = await sendTokens(connection, wallet, toWallet, value); console.log(`Transaction signature: ${hash}`); alert("Transaction sent successfully! Tx Hash: " + hash); } catch (error) { console.error("Transaction failed", error); alert("Transaction failed: " + (error instanceof Error ? error.message : String(error))); } }; |
Com isso temos a funcionalidade de transferência de tokens perfeitamente funcional.
Agora se quiser aprender a integrar front React com outros programas Solana, leia este tutorial.
Até a próxima!
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.



