Programas Nativos em Detalhe
Um programa nativo em Solana é um crate Rust que depende de solana-program, visa SBF como cdylib e expõe um entrypoint que recebe um program id, um slice de AccountInfos e instruction data. Não há macro de struct de contas, geração automática de constraints nem IDL built-in. Você possui o caminho completo de bytes de entrada até mudanças de state na saída.
Esta página é o mapa da seção. Use-a para posicionar dispatch de entrypoint, validação manual, (de)serialização, read/write, criação de conta via CPI, logging e trade-offs de framework em um continuum antes de seguir as páginas de receita focadas.
Resumo
Programas nativos Solana são módulos SBF comuns. O runtime chama process_instruction uma vez por instruction, entrega apenas o que a transação declarou e espera Ok(()) ou um ProgramError.
Sem Anchor, três trabalhos caem no seu código. Parse de instruction data em um layout estável de enum ou opcode. Valide cada conta que o handler usa: signers, writability, owners, igualdade de keys, seeds de PDA e relacionamentos de negócio. Mute bytes de conta owned pelo programa sob regras de borrow do Solana, incluindo CPI do System Program quando uma nova conta deve existir.
Esses trabalhos também são a principal superfície de risco. Um check is_signer faltando, suposição errada de owner ou caminho de reinit em conta existente é bug crítico. Frameworks codificam as mesmas regras em macros; código nativo as torna visíveis e fáceis de omitir.
Escolha nativo quando precisar de controle explícito, binários menores, CU baseline menor, layouts incomuns ou checks auditáveis visíveis. Escolha Anchor 0.32.1 (ou wrapper fino como Pinocchio) quando velocidade, geração de IDL/client e constraints declarativas importarem mais. Muitos times prototipam em Anchor e depois reescrevem hot paths como programas nativos compostos via CPI.
Fundamentos
O que "nativo" significa aqui
Nativo significa programar contra a API solana-program sem a camada #[program] / #[account] do Anchor. Você ainda implanta um .so, paga fees em lamports, declara metas de conta de clients como @solana/kit 7.0.0 e constrói com tooling SBF Agave 4.1.1 / Solana CLI 3.0.10. Nativo é uma camada de aplicação mais fina, não uma chain diferente.
Os três argumentos de cada instruction
| Input | Papel |
|---|---|
program_id | Public key do seu programa; checks de owner e derivação de PDA |
accounts: &[AccountInfo] | Contas listadas pelo client, com flags signer/writable verificadas pelo runtime |
instruction_data: &[u8] | Bytes opacos que você decodifica em enum de instruction ou opcode |
O runtime não re-deriva intenção dos seus tipos Rust. Conta errada no slot 2, data curto ou layout inválido deve falhar fechado no seu handler.
AccountInfo é uma capability, não um recurso tipado
AccountInfo expõe key, lamports, data, owner, executable, is_signer e is_writable. Essas flags refletem o que a transação declarou e o runtime verificou para assinaturas. Elas não provam "esta é a vault deste usuário" ou "este é state seguro do programa". Essa política é sua: comparações, checks de PDA e inspeção de campos dentro de data.
Onde frameworks ficam na mesma fundação
| Camada | O que adiciona | O que não remove |
|---|---|---|
| Raw nativo | Controle total de validação, layout, CPI | Checks corretos e disciplina de schema de client |
| Pinocchio / estilo Steel | Menos boilerplate, CU quase nativo | Revisão de segurança dos checks |
| Anchor 0.32.1 | Constraints, IDL, ergonomia de client | Necessidade de entender o modelo subjacente |
Literacia nativa torna toda opção mais segura, mesmo quando você entrega Anchor para a maior parte da superfície do produto.
Forma mínima do crate
Layout típico: cdylib, entrypoint!(process_instruction), módulos para instruction, processor, state e error, mais solana-program e um serializer (frequentemente Borsh). Um entrypoint por binário; múltiplos tipos de instruction vivem atrás de um match em data parseado. Noções Básicas de Programas Nativos percorre o esqueleto com exemplos.
Mecânica
Uma instruction é um pipeline fixo: entrar, decodificar, validar, agir (read/write e CPI opcional), logar com cuidado, retornar.
Pipeline de instruction ponta a ponta
Client (@solana/kit / CLI / CPI caller)
| AccountMeta list + instruction_data
| sign (or invoke_signed from caller)
v
Runtime: load accounts, check sigs & writable metas
|
v
entrypoint! -> process_instruction(program_id, accounts, data)
|
| 1. deserialize instruction_data
| 2. next_account_info / fixed account order
| 3. validate signers, owners, PDAs, relationships
| 4. borrow/mutate data, or CPI System Program
| 5. optional msg! / sol_log_data
v
Ok(()) commits | Err(ProgramError) aborts instruction
Valide antes de efeitos colaterais sempre que possível. Uma instruction (ou transação) que falha não deixa efeitos parciais do programa nesse caminho de falha.
1. Entrypoint e dispatch
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResultDeserialize uma vez no topo, depois match para handlers. Passe program_id em caminhos de PDA e owner. Mapeie erros de domínio para códigos estáveis ProgramError::Custom para clients.
2. Instruction data como contrato externo
Instruction data é o formato wire entre builders off-chain e código on-chain. Um padrão comum é enum Borsh (primeiro byte índice de variante, depois campos):
#[derive(BorshSerialize, BorshDeserialize)]
pub enum VaultInstruction {
Initialize { bump: u8 },
Deposit { amount: u64 },
}Não reordene variantes em produção. Limite strings e vecs ilimitados. Adicione byte de versão quando precisar quebrar layout. Espelhe o mesmo encoding em TypeScript com @solana/kit 7.0.0 ou pipeline Codama/schema compartilhado para que clients não driftem silenciosamente. Veja Dados de Instruction (de)Serialização.
3. Validação manual de contas
Segurança nativa é checklist explícito em cada caminho:
| Check | Padrão |
|---|---|
| Signer | account.is_signer |
| Writable | account.is_writable |
| Owner é seu programa | account.owner == program_id |
| Owner é System/Token/etc. | igualdade no program id esperado |
| Identidade PDA | find_program_address + bump armazenado |
| Relacionamentos | igualdade de key; campos mint/authority em data |
| Sysvars | Sysvar::get() ou keys conhecidas, não fakes spoofáveis |
Execute checks baratos de flag antes de CPI ou deserialize pesado. Documente ordem de contas; consuma com next_account_info. Para contas de token, o owner é o programa Token; authority e mint vivem dentro de data.
if !payer.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
if vault.owner != program_id {
return Err(ProgramError::IncorrectProgramId);
}
let (expected, bump) = Pubkey::find_program_address(
&[b"vault", payer.key.as_ref()],
program_id,
);
if expected != *vault.key || bump != stored_bump {
return Err(ProgramError::InvalidSeeds);
}Validação Manual de Contas cobre ataques de substituição e checklists reutilizáveis.
4. Lendo e escrevendo dados de conta
State owned pelo programa é um buffer de bytes. Borrow, parse, mute, escreva de volta dentro do comprimento da conta.
- Prefira
try_borrow_data/try_borrow_mut_data. - Libere borrows antes de CPI na mesma conta.
- Guarde comprimento; mapeie falhas de serialize claramente (por exemplo conta pequena demais).
- Versione layouts; planeje migrações.
- Apenas o programa owner pode escrever data (ownership definido em create/assign).
let mut data = account.try_borrow_mut_data()?;
let mut state = State::deserialize(&mut &data[..])
.map_err(|_| ProgramError::InvalidAccountData)?;
state.count = state.count.checked_add(1).ok_or(ProgramError::InvalidArgument)?;
state.serialize(&mut &mut data[..])
.map_err(|_| ProgramError::AccountDataTooSmall)?;Detalhe: Lendo e Escrevendo Dados de Conta.
5. Criando contas via CPI
Programas criam contas por CPI ao System Program (create_account ou allocate/assign relacionados). O payer financia lamports isentos de rent; space dimensiona o buffer; owner vira seu program_id para state do programa.
PDAs não têm private key. Use invoke_signed com seeds e bump exatos:
let rent = Rent::get()?;
let lamports = rent.minimum_balance(State::LEN);
invoke_signed(
&system_instruction::create_account(
payer.key, pda.key, lamports, State::LEN as u64, program_id,
),
&[payer.clone(), pda.clone(), system_program.clone()],
&[&[b"vault", owner.key.as_ref(), &[bump]]],
)?;
// initialize data after createValide o id do System Program, suposições de conta vazia/nova e match de seeds. Caminhos de reinit e "conta já existe" são classes clássicas de exploit. Fluxo completo: Criando Contas via CPI.
6. Logging
msg! e helpers relacionados escrevem logs de transação para simulação, explorers e indexers. Custo escala com volume e tamanho de string. Use breadcrumbs estruturados em desenvolvimento e logs esparsos em produção. Prefira erros customizados tipados para falhas esperadas. Veja Emitindo Logs.
7. Clients e testes
Nativo ainda precisa de história de client: builders manuais, crate compartilhado ou tooling Codama/IDL. Testes devem assertar falhas de validação (owner errado, signer faltando, PDA ruim), não apenas happy paths. Testes unitários estilo LiteSVM e validators locais sob Solana CLI 3.0.10 cobrem velocidade de lógica versus loops completos de deploy RPC.
Avançado
Trade-offs versus frameworks
| Fator | Nativo | Anchor 0.32.1 | Nativo fino (ex. Pinocchio) |
|---|---|---|---|
| Velocidade | Mais lento | Mais rápido para apps | Meio |
| Trilhos de segurança | Checklists manuais | Constraints declarativos | Helpers, ainda explícito |
| CU / tamanho de binário | Melhor controle | Baseline maior | Quase nativo |
| IDL / clients | Manual ou externo | Built-in | Varia |
| Visibilidade de auditoria | Linha a linha | Expansão de macro | Superfície fina |
| Melhor fit | Hot paths, primitivos, layouts incomuns | Maioria dos programas de app | Estilo nativo sensível a CU |
Nativo não é automaticamente mais seguro. É mais transparente. Transparência só ajuda se o checklist for aplicado sempre.
Quando nativo é a escolha certa de produto
Prefira nativo quando pelo menos dois se aplicam: CU de instruction é crítico para o negócio; layout briga com contas Anchor; tamanho de binário é restrito; auditores exigem validação totalmente explícita; ou você entrega primitivo que outros programas farão CPI.
Não escolha nativo só porque "Anchor é lento" sem perfilar. Contenção, RPC e algoritmos frequentemente dominam overhead de macro.
Arquiteturas híbridas
Forma comum: Anchor para admin e instructions de baixa frequência; nativo para o loop apertado (match step, game tick, settlement). Componha com CPI e layouts compartilhados. Documente qual program id possui qual state.
Lente de revisão de segurança
Para cada instruction pergunte: quem deve assinar; quais contas são escritas e com qual owner; um PDA, mint ou conta de token diferente pode ser substituído; initialize é re-chamável em data não vazio; seeds/bumps são canônicos; borrows são liberados antes de CPI; layout de instruction corresponde a clients publicados?
Disciplina operacional
Fixe Agave, CLI e solana-program como deps de aplicação. Documente códigos de erro customizados para consumidores @solana/kit. Trate mudanças de layout como migrações com version gates.
Equívocos Comuns
Equívoco: Nativo pula segurança porque o runtime já verificou contas.
O runtime verifica assinaturas e metas writable para contas listadas. Ele não conhece suas seeds de vault, vínculos de mint ou política de admin.
Equívoco: is_writable significa que a conta é segura para tratar como seu state.
Writable só significa que a transação permitiu mutação. Ainda verifique owner, identidade e discriminators.
Equívoco: Owner igual ao Token Program é suficiente para "a conta de token do usuário".
Authority, mint e amount vivem em data. Valide campos (e frequentemente derivação ATA) para a ação econômica.
Equívoco: Enums de instruction podem ser reordenados se Rust ainda compilar.
Índices de variante são formato wire. Reordenar quebra clients antigos e pode mapear opcodes antigos para handlers novos.
Equívoco: Criar PDA é create_account simples sem seeds.
Ações autorizadas por PDA precisam de invoke_signed com as seeds que o runtime espera.
Equívoco: Logging é observabilidade grátis.
Logs custam CU e incham meta. Não substituem erros ou métricas off-chain.
Equívoco: Nativo é automaticamente mais seguro que Anchor.
Ambos falham com validação errada. Nativo move o default de "macro pode omitir" para "autor pode omitir".
Equívoco: Programas nativos não precisam de IDL ou schema de client.
Sem encoding publicado, builders divergem. Entregue schema, crate compartilhado ou pipeline Codama.
FAQs
O que é um programa nativo Solana em uma frase?
Um crate SBF solana-program com entrypoint que manualmente faz parse de instruction data, valida AccountInfos e atualiza state owned pelo programa (frequentemente com CPI do System Program), sem macros Anchor.
Programas nativos usam runtime diferente de programas Anchor?
Não. Ambos são SBF sob o mesmo runtime Agave. Anchor gera mais glue; a chain vê um program id e bytecode de qualquer forma.
O que o entrypoint recebe?
program_id, um slice de AccountInfo para contas listadas e um byte slice de instruction data.
Por que ordem de contas faz parte da API?
Clients constroem uma lista AccountMeta paralela. Handlers tipicamente consomem contas em ordem fixa com next_account_info. Documente essa ordem ou gere de uma fonte de verdade.
O que devo validar que o runtime não valida?
Identidade de negócio: qual PDA, mint e authority; se initialize já rodou; relacionamentos entre contas; e política de owner além da presença de assinatura.
Como estruturar instruction data?
Prefira layout versionado e documentado (comumente enum Borsh). Mantenha variantes estáveis, limite tamanhos dinâmicos e espelhe o layout em clients.
Como criar conta owned pelo programa a partir de código nativo?
CPI system_instruction::create_account com lamports isentos de rent, espaço desejado e owner = program_id. Use invoke_signed quando o novo endereço for PDA.
Quando preciso de `invoke_signed`?
Sempre que um PDA deve autorizar uma ação porque não existe private key Ed25519 para esse endereço.
Como ler e escrever state com segurança?
Verifique owner e comprimento, faça borrow de data, deserialize ou use view segura, mute com aritmética checked, serialize dentro da capacidade e encerre borrows antes de CPI na mesma conta.
Logs `msg!` são obrigatórios?
Não. Use para debug e breadcrumbs esparsos em produção. Prefira valores ProgramError claros para falhas esperadas.
Como clients falam com programas nativos?
Construam transações que listam as contas corretas e serializam os mesmos bytes de instruction que o programa espera, usando @solana/kit 7.0.0 ou outros SDKs.
Nativo é sempre menor CU que Anchor?
Nativo tem piso menor e mais controle, mas CU real depende de checks, logs, serialização e CPI. Meça instructions hot.
Posso misturar Anchor e nativo?
Sim. Programas separados compostos com CPI é comum: Anchor para superfícies admin, nativo para lógica core sensível a performance.
Qual o maior erro de segurança em programas nativos?
Validação incompleta: signer faltando, owner errado, substituição de PDA ou reinitialize em conta existente. Trate cada instruction como input hostil.
Onde ir a seguir nesta seção?
Comece com Noções Básicas de Programas Nativos, depois aprofunde validação, instruction data, CPI create, I/O de conta e logs via Relacionado abaixo.
Relacionado
- Noções Básicas de Programas Nativos - layout de crate, esqueleto de entrypoint e exemplos iniciais
- Validação Manual de Contas - signers, owners, PDAs e defesas contra substituição
- Dados de Instruction (de)Serialização - enums Borsh, opcodes e paridade com client
- Lendo e Escrevendo Dados de Conta - borrows, loops de serialize e cuidado com layout
- Criando Contas via CPI - create do System Program e PDA
invoke_signed - Emitindo Logs -
msg!, custo de CU e disciplina de logging em produção
Versões do stack: Esta página foi escrita para Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, Rust 1.91.1 e @solana/kit 7.0.0.