Uma invocação entre programas (CPI) é como um programa on-chain pede a outro programa para executar uma instrução dentro da mesma transação. Seu manipulador constrói uma Instruction (ID do programa, metadados de contas, dados) e passa uma fatia correspondente de valores AccountInfo, e o runtime carrega o chamado e o executa antes que o controle retorne para você.
As CPIs são o mecanismo por trás da composabilidade do Solana: cofres que movem SOL, garantias que transferem tokens SPL, roteadores que chamam DEXes e qualquer fluxo onde as regras de propriedade o forçam a passar por outro programa em vez de escrever suas contas diretamente.
Esta página é o guarda-chuva para Invocações entre Programas. Páginas irmãs aprofundam nos conceitos básicos, passagem de contas, invoke vs invoke_signed, alvos do System e Token, limites de profundidade, reentrância e padrões. Aqui você obtém um modelo coeso para que essas páginas se encaixem como zooms, não como histórias separadas.
Uma CPI é uma chamada aninhada para outro programa: o chamador fornece dados de instrução e uma lista de contas com privilégios restritos; o chamado executa sob os mesmos bloqueios de transação, assinaturas e orçamento de computação.
Por que Importa: Você não pode reescrever contas que pertencem a outro programa. Transferências, cunhagens, criação de contas e a maioria das integrações de protocolo exigem CPI para o programa proprietário. Contas erradas, caminho de assinatura errado ou ordenação insegura produzem transações falhas ou bugs de segurança.
Conceitos Chave:Instruction, AccountMeta / AccountInfo, propagação de privilégio, invoke, invoke_signed, sementes de PDA, profundidade de CPI, CPI do System Program, CPI do SPL Token, checks-effects-interactions (verificações-efeitos-interações), chamados em lista de permissões.
Quando Usar: Sempre que seu programa precisar criar contas, mover SOL ou tokens, cunhar/queimar sob uma autoridade de programa, ou compor com outro protocolo on-chain em uma única transação atômica.
Limitações / Trade-offs: A profundidade aninhada é finita, a CU (Compute Units) é compartilhada, toda conta deve ser listada antecipadamente, e IDs de programas chamados arbitrários são uma superfície de confiança. O sucesso atômico de múltiplos programas é poderoso; a falha aborta toda a transação.
Programas Solana são executáveis sem estado. Saldos duráveis e layouts vivem em contas pertencentes a programas específicos. A regra de "somente o proprietário escreve" é absoluta: somente o Token Program pode reescrever os dados de uma conta de token; somente o System Program executa o ciclo de vida usual de criação/atribuição/transferência para contas de propriedade do sistema; somente seu programa pode reescrever contas que ele possui.
Quando sua lógica precisa que o estado de outra pessoa seja alterado, você não mexe em bytes. Você faz CPI para o proprietário com a instrução que esse programa define.
No nível da API, uma CPI se parece com a construção da mesma forma que um cliente enviaria como uma instrução de nível superior:
Transação Externa +-- Instrução: SeuPrograma | contas: [usuario, cofre, token_origem, token_destino, programa_token, sistema, ...] | dados: seu discriminador + argumentos | | (dentro de SeuPrograma) | invoke / invoke_signed | | | v +-- Instrução Aninhada: TokenProgram (ou System, DEX, ...) contas: subconjunto da lista externa (ordem e flags corretas) dados: transferir / cunhar / criar_conta / ...
Três fatos decorrem dessa imagem:
Mesma transação, mesma atomicidade. Se o chamado falhar, sua instrução falha e toda a transação é revertida.
Sem descoberta ambiente. Toda conta que a chamada aninhada necessita deve já estar na lista de contas da transação externa (com privilégios de signatário/gravável utilizáveis). CPIs não buscam chaves não declaradas em tempo de execução.
Privilégios não crescem magicamente. As capacidades graváveis e de signatário na CPI são limitadas pelo que a mensagem externa já autorizou, mais assinaturas de PDA que seu programa pode adicionar com invoke_signed.
Clientes (incluindo aqueles construídos com @solana/kit 7.0.0) ainda montam a mensagem de nível superior: eles listam programas e contas, assinam com carteiras e definem o orçamento de computação. Seu programa então costura chamadas aninhadas a partir desse mundo fechado de contas.
Bytes opacos que o chamado desserializa (opcode + argumentos)
Você passa essa instrução para invoke ou invoke_signed com uma fatia de clones de AccountInfo cujos ordem e chaves correspondem aos metadados. Prefira helpers oficiais (system_instruction::transfer, spl_token::instruction::transfer) para que o layout dos dados e a ordem dos metadados permaneçam corretos.
Listas de contas para CPIs são uma superfície de segurança, não apenas contabilidade.
Ordem deve corresponder ao que o chamado espera (helpers codificam isso; programas customizados documentam).
Gravável deve ser o mínimo privilégio: marque como gravável apenas contas que o chamado precisa mutar.
Signatário em um metadado significa que a conta deve ser um signatário reconhecido para essa chamada aninhada: seja uma carteira que assinou a transação externa, ou um PDA que seu programa assina via sementes.
Contas de programa (Token, System, ou um programa parceiro) são geralmente entradas somente leitura para que o runtime possa carregar o chamado.
Bugs de escalonamento de privilégio parecem marcar uma conta como gravável ou signatária na CPI quando a transação externa nunca pretendeu essa capacidade. Valide flags da AccountInfo externa antes de promovê-las para metadados aninhados. Veja Passando Contas para CPIs para a lista de verificação operacional.
Adiciona "assinaturas" de PDA a partir de sementes
Não
Sim
Uso típico
Autoridade assinada pelo usuário já presente
PDA de cofre, PDA de autoridade de cunhagem, garantia de propriedade do programa
Modo de falha se mal utilizado
MissingRequiredSignature quando o PDA deve autorizar
Sementes/bump errados → falha na verificação de assinatura
// Carteira (ou outro signatário externo) já é is_signer na containvoke(&ix, &[from.clone(), to.clone(), authority.clone(), token_program.clone()])?;// Autoridade PDA: programa prova sementes + bump sob seu ID de programainvoke_signed( &ix, &[vault.clone(), to.clone(), system_program.clone()], &[&[b"vault", user.key.as_ref(), &[bump]]],)?;
invoke_signed não inventa assinaturas de carteira. Ele apenas permite que o programa atual autorize PDAs derivados de seu ID de programa e das fatias de sementes fornecidas. O design das sementes e a disciplina de bump pertencem aos PDAs; a escolha da CPI é simplesmente "o chamado precisa de um PDA como signatário?" Comparação completa: invoke vs invoke_signed.
O runtime limita a profundidade das CPIs aninhadas. No Solana atual (incluindo Agave 4.1.1), a profundidade máxima de CPI é 4. Árvores profundas (roteador → DEX → token → hook de transferência → …) atingem o limite rapidamente.
As unidades de computação são por transação. Chamadas aninhadas não recebem uma nova carteira de CU; chamados pesados e logs multiplicam o custo sob o orçamento padrão de ~1.4M CU (ajustável via instruções de Compute Budget dentro dos limites do protocolo). Projete de forma plana quando puder: uma instrução bem formatada com algumas CPIs é melhor que uma cadeia de chamadas profunda. Detalhes: Profundidade e Limites de CPI.
CPIs do System Program criam contas, alocam espaço, atribuem proprietários e transferem lamports. O fluxo típico para uma nova conta de propriedade do programa: mínimo isento de aluguel de Rent::get(), então create_account (ou padrões de alocação/atribuição) com o pagador como signatário. Movimentação de SOL financiada por PDA ou originada de PDA precisa de invoke_signed. Veja CPI para o System Program.
CPIs do SPL Token cunham, transferem, queimam, aprovam e operações relacionadas. Você nunca reescreve os bytes da conta de token diretamente; você constrói uma instrução com spl_token::instruction (ou equivalentes Token-2022) e invoke / invoke_signed com o ID correto do programa de token, cunhagem, autoridades e contas de token. Valide se a cunhagem corresponde, os decimais (valores brutos), o estado de congelamento e o ID do programa Token vs Token-2022. Autoridades de cunhagem ou congelamento de PDA usam invoke_signed. Veja CPI para SPL Token.
O runtime bloqueia reentrância clássica diretamente: invocar um programa que já está na pilha de instruções falha com ReentrancyNotAllowed, e a auto-recursão direta é a única exceção. Um hook de transferência, callback ou ID de programa malicioso fornecido pelo usuário, portanto, não pode reentrar em seu manipulador no meio do fluxo. O que você ainda precisa evitar é diferente: um chamado pode mutar contas que você já leu, então trate CPIs externas como efeitos colaterais não confiáveis.
Regras práticas:
Verificações → efeitos → interações: valide, atualize e serialize seu estado, então faça a CPI externa.
Flags idempotentes: uma vez que um saque ou liquidação é marcado como concluído, uma nova tentativa ou uma instrução irmã não pode pagar duas vezes.
Lista de permissões de chamados quando o ID do programa não é uma constante fixa (roteadores, plugins) - o risco é um chamado drenador, não um callback.
Releia após a CPI: qualquer coisa que o chamado tocou está desatualizada em suas cópias na memória (reload() em Anchor, unpack novamente nativamente).
Os limites de profundidade limitam a pilha; eles não tornam segura a ordenação insegura. Veja Reentrância e Segurança.
Composabilidade é o produto de CPIs feitas cuidadosamente: uma transação pode validar um usuário, atualizar um ledger, trocar em um DEX e depositar em um cofre, tudo ou nada.
Padrão
O que faz
Formato da CPI
Cuidado com
Escrow / cofre
Programa custodia ativos sob um PDA
invoke_signed para System ou Token
Sementes, bumps, campos de autoridade
Roteador / agregador
Uma instrução de entrada se ramifica para parceiros
Múltiplos invokes, IDs de programa em lista de permissões
Profundidade, CU, slippage, contas restantes
Adaptador
Normaliza um protocolo estrangeiro sob sua API
Wrapper de CPI fino + seu estado
Atualizações de parceiros e derivação do layout da conta
Multi-ix composta pelo cliente
Sem CPI aninhada; várias instruções de nível superior
Zero ou poucas CPIs em seu programa
Orquestração de programa único mais fraca; ainda atômica como uma transação
Contas restantes permitem que clientes passem caudas de contas extensíveis (pools extras, oráculos, programas de memo) sem codificar em hard cada slot em sua struct Accounts. Documente o layout por versão e valide cada chave e proprietário antes da CPI.
Address Lookup Tables (ALTs) abordam o tamanho da transação, não a profundidade da CPI. Elas ajudam quando as listas de contas explodem; elas não aumentam o limite de profundidade 4.
Anchor 0.32.1 gera helpers de CPI tipados (CpiContext, wrappers invoke_signed, anchor_spl) para que metadados e discriminadores permaneçam alinhados com os IDLs. O modelo de runtime é o mesmo: mesmos privilégios, mesma profundidade, mesma necessidade de ordenação CEI. Rust nativo com solana_program é a mesma superfície de syscall com mais fiação manual.
Para revisões de arquitetura, pergunte: qual programa possui cada conta que tocamos; quais signatários (carteira ou PDA) autorizam cada chamada aninhada; se os IDs dos programas chamados são fixos ou em lista de permissões; se o estado é finalizado antes da CPI externa; se a árvore de chamadas se encaixa nos limites de profundidade e CU sob simulação realista nas ferramentas Agave 4.1.1 / CLI 3.0.10.
"CPI é apenas uma chamada de API interna estilo HTTP." É uma instrução aninhada mediada pelo runtime sob os mesmos bloqueios, assinaturas e orçamento de CU, com regras rígidas de contas e profundidade.
"Meu programa pode escrever saldos de token se eu passar a conta de token." Somente o Token Program possui esses bytes. Você deve fazer CPI com uma instrução de transferência/cunhagem/queima válida e autoridade.
"invoke_signed me permite forjar qualquer assinatura." Ele apenas autoriza PDAs para seu ID de programa com as sementes que você fornece. Ele não cria assinaturas de carteira.
"Se eu conhecer uma chave pública, a CPI pode carregá-la." Contas não declaradas não estão disponíveis. Clientes devem incluir todas as contas que o caminho de chamada completo necessita.
"Programas aninhados recebem seus próprios orçamentos de computação." Todo o trabalho aninhado consome o pool de CU da transação.
"Um hook ou callback pode reentrar em meu programa como no EVM." Não pode: o runtime rejeita qualquer invocação de um programa já na pilha de instruções com ReentrancyNotAllowed (exceto auto-recursão direta). O perigo real é diferente: um chamado pode mutar contas que você já leu, então releia após a CPI e finalize o estado antes de chamar externamente, e coloque alvos não confiáveis em lista de permissões.
"Composição mais profunda é sempre melhor." Profundidade 4 e tetos de CU penalizam árvores profundas; designs mais planos e transações multi-instrução do lado do cliente são frequentemente mais saudáveis.
"Marcar tudo como gravável é mais seguro." Isso expande o raio de explosão se um chamado ou bug mutar mais do que o pretendido. Use o mínimo privilégio.
Uma invocação entre programas é seu programa executando a instrução de outro programa no meio do manipulador, dentro da mesma transação atômica, com uma lista de contas declarada e orçamento de computação compartilhado.
Por que não posso simplesmente editar os dados da conta de outro programa?
O runtime impõe a regra de "somente o proprietário escreve": somente o programa proprietário pode mutar os dados de uma conta. CPI é como você pede a esse proprietário para aplicar uma instrução suportada.
O que deve ser verdade sobre as contas que passo para uma CPI?
Elas devem já aparecer na transação externa, corresponder à ordem e papéis esperados pelo chamado, e carregar privilégios de signatário/gravável que a mensagem externa (mais quaisquer sinais de PDA) legitimamente permite.
Quando uso invoke vs invoke_signed?
Use invoke quando todos os signatários necessários já assinaram a transação. Use invoke_signed quando um PDA derivado do seu programa deve atuar como signatário (cofres, autoridades de cunhagem, garantias de propriedade do programa).
Qual é o limite de profundidade da CPI?
A profundidade máxima aninhada de CPI é 4 no Solana atual (Agave 4.1.1). Planeje os grafos de chamadas para que os programas de Token, hooks e parceiros não esgotem a pilha.
As CPIs recebem orçamentos de unidades de computação separados?
Não. A execução aninhada consome o mesmo pool de CU da transação. Simule caminhos com múltiplas CPIs e defina instruções de Compute Budget quando necessário.
Como as CPIs do System Program se encaixam no trabalho diário do programa?
Elas criam e financiam contas e movem SOL. create_account e transfer isentos de aluguel são os blocos de construção comuns; fontes de PDA precisam de invoke_signed.
Como as CPIs do SPL Token se encaixam no trabalho diário do programa?
Operações de cunhagem, transferência, queima e autoridade passam pelo Token ou Token-2022 via construtores de instrução e CPI. Valide o ID do programa, cunhagem, valores e autoridades antes de chamar.
Qual é o erro de segurança de CPI mais comum?
Atualizar saldos ou flags após uma CPI externa (ou chamar IDs de programa não listados em lista de permissões), o que permite que um chamado malicioso aja contra um estado inconsistente - e ler cópias de conta na memória desatualizadas após o retorno da CPI.
Um chamado pode chamar de volta para meu programa?
Não - não se seu programa já estiver na pilha; o runtime rejeita com ReentrancyNotAllowed. Apenas a auto-recursão direta é permitida. O perigo real é diferente: um chamado pode mutar contas que você já leu, então releia após a CPI e finalize o estado antes de chamar externamente.
O Anchor remove a necessidade de entender CPIs?
Não. Anchor 0.32.1 melhora a tipagem e a ergonomia; você ainda escolhe sementes invoke vs signed, passa contas corretamente, respeita profundidade/CU e ordena atualizações de estado de forma segura.
Como os clientes devem se preparar para uma instrução rica em CPI?
Inclua todas as contas que o caminho aninhado completo necessita (incluindo programas e sysvars), defina um orçamento de computação adequado e prefira ALTs quando a contagem de contas inflar o tamanho da mensagem.
A composição multi-instrução sem CPI é uma alternativa válida?
Sim. Várias instruções de nível superior em uma transação permanecem atômicas sem profundidade aninhada. Use isso quando você não precisar de lógica intermediária imposta pelo programa entre as etapas.