Transferências SPL movem saldos de tokens entre contas de token da mesma mint. Delegados permitem que terceiros transfiram até um valor aprovado em nome do proprietário – o padrão por trás de roteadores DEX e programas de garantia.
# Quantidades da CLI são quantidades de UI (tokens inteiros), nunca unidades base brutasspl-token transfer <MINT> <UI_AMOUNT> <RECIPIENT> --fund-recipient# approve recebe a CONTA DE TOKEN DE ORIGEM, não a mintspl-token approve <TOKEN_ACCOUNT> <UI_AMOUNT> <DELEGATE_PUBKEY>
use anchor_spl::token::{self, TransferChecked, Approve};token::transfer_checked(cpi_ctx, amount, decimals)?;token::approve(cpi_ctx, amount)?;
Quando usar isso:
Pagamentos de usuário para usuário em tokens fungíveis
Aprovar um programa de swap para retirar tokens
Construir garantias que movem fundos na liquidação
# Alice envia 2.5 tokens - a CLI pega a quantidade de UI e escala pelos decimais da mintspl-token transfer "$MINT" 2.5 <BOB_WALLET> --fund-recipient# Alice aprova o roteador para gastar até 1 token de sua conta de token.# O argumento 1 é a conta de token que está sendo delegada, não a mint.ALICE_ATA=$(spl-token address --token "$MINT" --verbose | awk '/Associated token address/{print $NF}')spl-token approve "$ALICE_ATA" 1 <ROUTER_DELEGATE_PUBKEY>
use anchor_lang::prelude::*;use anchor_spl::token::{self, Approve, Mint, Token, TokenAccount, TransferChecked};#[derive(Accounts)]pub struct TransferPayment<'info> { pub mint: Account<'info, Mint>, #[account(mut, token::mint = mint, token::authority = authority)] pub from: Account<'info, TokenAccount>, #[account(mut, token::mint = mint)] pub to: Account<'info, TokenAccount>, pub authority: Signer<'info>, pub token_program: Program<'info, Token>,}pub fn transfer_payment(ctx: Context<TransferPayment>, amount: u64) -> Result<()> { let decimals = ctx.accounts.mint.decimals; let cpi = CpiContext::new( ctx.accounts.token_program.to_account_info(), TransferChecked { mint: ctx.accounts.mint.to_account_info(), from: ctx.accounts.from.to_account_info(), to: ctx.accounts.to.to_account_info(), authority: ctx.accounts.authority.to_account_info(), }, ); token::transfer_checked(cpi, amount, decimals)?; Ok(())}#[derive(Accounts)]pub struct ApproveRouter<'info> { #[account(mut, token::authority = owner)] pub owner_token: Account<'info, TokenAccount>, /// CHECK: a chave pública que está recebendo direitos de delegado; o programa Token a armazena literalmente. pub delegate: UncheckedAccount<'info>, pub owner: Signer<'info>, pub token_program: Program<'info, Token>,}pub fn approve_router(ctx: Context<ApproveRouter>, amount: u64) -> Result<()> { let cpi = CpiContext::new( ctx.accounts.token_program.to_account_info(), Approve { to: ctx.accounts.owner_token.to_account_info(), delegate: ctx.accounts.delegate.to_account_info(), authority: ctx.accounts.owner.to_account_info(), }, ); token::approve(cpi, amount)?; Ok(())}
O que isso demonstra:
token::mint = mint fixa ambos os lados no mesmo ativo antes de qualquer CPI
transfer_checked passa a mint e afirma os decimais na chain; transfer simples está obsoleto
O delegado é uma conta real passada na instrução - nunca reutilize outra conta para esse slot
Proprietário assina a transferência; o delegado pode assinar após approve; revoke redefine a quantidade delegada para zero
import { createSolanaRpc, address } from "@solana/kit";const rpc = createSolanaRpc("https://api.devnet.solana.com");// Construa instruções de transferência via helpers do @solana-program/token// Sempre derive o ATA do destinatário; financie se ausente
Incompatibilidade de mint - a transferência entre mints diferentes falha. Correção: valide from.mint == to.mint na chain e nos clientes.
Quantidade delegada insuficiente - a transferência do delegado excede a aprovação. Correção: reaprove ou divida entre transações.
Aprovações ilimitadas obsoletas - risco de segurança em DeFi. Correção: aprove o gasto exato; revogue após a troca.
Transferência para endereço não ATA - funciona, mas confunde os usuários. Correção: derive e exiba os endereços ATA.
Conta de origem congelada - todas as transferências são bloqueadas. Correção: verifique o campo state antes das promessas de UX.
transfer obsoleto não verificado - spl_token::instruction::transfer e anchor_spl::token::{transfer, Transfer} estão obsoletos, e Token-2022 rejeita a variante não verificada em mints com taxa de transferência. Correção: use transfer_checked / TransferChecked em todos os lugares.
Quantidades da CLI lidas como unidades brutas - spl-token transfer <MINT> 2500000 envia 2.500.000 tokens, não 2.5. Correção: a CLI aceita quantidades de UI; apenas os dados de instrução aceitam unidades base brutas.
approve / revoke recebem a mint - ambos recebem o endereço da conta de token, não a mint. Correção: derive o ATA primeiro (spl-token address --token <MINT>).