Skip to main content
Esta página foi traduzida automaticamente por IA. A versão em inglês é a fonte oficial.Ver versão em inglês →
PDAs (endereços derivados de programa) e CPIs (invocação entre programas) são os dois primitivos que tornam Raydium possível. PDAs permitem que um programa “possua” endereços determinísticos sem chaves privadas — é assim que as autoridades de pool e vaults funcionam. CPIs permitem que um programa chame outro — é assim que Raydium troca tokens via o programa SPL Token e como integradores compõem Raydium em seus próprios fluxos. Ambos valem a pena entender antes de ler o código-fonte de Raydium.

PDAs: endereços sem chaves

Um Endereço Derivado de Programa é uma chave pública que:
  • Não está na curva ed25519 (nenhuma chave privada existe para ela).
  • É derivada deterministicamente de um ID de programa e um conjunto de seeds.
  • Pode ser assinada apenas pelo programa de derivação, via invoke_signed.
Toda autoridade de pool Raydium, todo estado de pool, todo vault, todo estado de farm — todos são PDAs.

Derivação

Um PDA é calculado fazendo hash do ID do programa com as seeds, depois encontrando um byte “bump” que força o resultado para fora da curva. O primeiro bump (tipicamente começando de 255 e decrementando) que produz um endereço fora da curva vence; este é o bump canônico.
As seeds podem ser qualquer coisa — strings, outras pubkeys, valores u64 como bytes little-endian. A convenção de Raydium é um prefixo legível por humanos seguido de identificadores únicos.

Padrões de PDA em Raydium

PDAs comuns nos programas de Raydium: Usuários e integradores podem calcular esses PDAs sem buscar nada — dados os inputs públicos (ID do pool, ID do farm, chave do usuário), o PDA é determinístico.

Bump canônico

Embora em princípio possa haver múltiplos bumps produzindo endereços fora da curva, os programas de Raydium sempre usam o bump canônico (encontrado decrementando de 255). Isso é armazenado nos dados da conta PDA para que transações subsequentes possam passá-lo e pular o loop de derivação (caro):
(O PoolState de CLMM armazena bump: [u8; 1] em vez disso, então verifique a struct do programa específico em vez de assumir uma forma.) Em transações subsequentes, o bump é lido do estado do pool em vez de ser recalculado.

CPIs: chamando outros programas

Invocação Entre Programas permite que um programa invoque as instruções de outro programa inline dentro de uma única transação. Raydium usa CPIs extensivamente:
  • Instruções de swap chamam o programa SPL Token para mover tokens.
  • CLMM chama Metaplex para cunhar o NFT de posição.
  • Criação de pool chama System Program para alocar contas.
  • Farm v6 chama SPL Token para transferir recompensas.
Integradores também usam CPIs para chamar para dentro de Raydium — é assim que estratégias de vault, protocolos de LP alavancados e auto-compostos funcionam. Veja integration-guides/cpi-integration.

invoke vs invoke_signed

O runtime de Solana oferece dois primitivos de CPI:
  • invoke: chama outro programa; o programa chamado herda os signatários da transação externa.
  • invoke_signed: chama outro programa em nome de um PDA; o runtime verifica as seeds do PDA e autoriza a assinatura.
invoke_signed é a mágica que permite que programas mantenham autoridade sobre contas sem gerenciar chaves privadas.

Exemplo: Raydium transferindo de um vault de pool

Um vault de pool é uma Conta de Token cuja autoridade é um PDA do programa de pool. Para transferir tokens durante um swap, o programa de pool deve assinar como esse PDA:
O runtime vê que invoke_signed é chamado pelo programa CPMM, verifica que vault_and_lp_mint_auth_seed + bump deriva para o endereço de pool_authority quando feito hash com o ID do programa CPMM, e permite a assinatura de autoridade na transferência de token. Nenhuma chave privada envolvida.

Exemplo: integrador chamando CPMM de Raydium

Um programa integrador (por exemplo, um escrow) pode invocar swap_base_input de Raydium via CPI:
Este é o padrão de integração canônico — veja integration-guides/cpi-integration para o exemplo completo de escrow.

Limite de profundidade de CPI

Solana limita a profundidade de CPI a 4 níveis. A instrução de nível superior de uma transação conta como profundidade 0; cada invocação de CPI incrementa a profundidade. Implicação prática: o próprio swap de Raydium já usa 1-2 níveis de CPI (Raydium → SPL Token). Um integrador chamando Raydium usa 2. Se esse integrador é chamado por outro integrador, é 3. O 4º nível é o limite. A maioria das composições fica bem abaixo disso, mas aninhamento profundo (agregador → roteador → Raydium → hook) pode atingir. Projete de forma plana em vez de profunda.

Contas restantes

Quando uma instrução de Raydium precisa de um número variável de contas (por exemplo, swap de CLMM cruzando um número desconhecido de arrays de tick), as contas extras são passadas como contas restantes — anexadas à lista de contas fixas, interpretadas por posição. O SwapV2 de CPMM usa contas restantes para contas extras necessárias dos programas de transfer-hook. Clientes buscam as contas necessárias e as anexam:
No nível de CPI, integradores devem encaminhar contas restantes através de sua própria instrução:

Armadilhas de PDA

Seeds erradas → endereço errado

Um bug onde as seeds estão na ordem errada, codificação errada, ou incluem/excluem um byte extra produz silenciosamente um PDA diferente. A transação falha de forma ambígua (o programa tenta ler uma conta que não existe). Sempre faça testes unitários de derivação de seed contra valores golden conhecidos.

Não armazenar bump

Se você re-derivar o bump em cada transação, você paga computação pelo loop de derivação. Armazene o bump canônico nos dados do PDA e leia-o de lá.

Confundindo bump canônico vs não-canônico

Bumps não-canônicos (se alguém encontrar um que produza fora da curva) são permitidos por invoke_signed mas rejeitados pelos programas de Raydium via assert_eq!(bump, canonical_bump). Se alguém tentar reivindicar um PDA com um bump não-canônico, a tx falha.

Passando um PDA como signatário quando você não é o programa proprietário

Apenas o programa cujo ID está na derivação do PDA pode invoke_signed com suas seeds. Se você tentar, o runtime rejeita.

Armadilhas de CPI

Esquecendo de encaminhar remaining_accounts

Se sua instrução externa passa contas de transfer-hook em remaining_accounts mas o CPI para Raydium não as encaminha, Raydium falha porque não consegue encontrar as contas de hook. Sempre inclua with_remaining_accounts em CPIs que precisam delas.

Incompatibilidade de flags graváveis

Uma conta que a instrução externa marca como gravável também deve ser gravável na chamada de CPI se o programa chamado pretende escrevê-la. Incompatibilidade → rejeição do runtime.

Não contabilizar aluguel

CPI para um programa que cria uma conta (por exemplo, criação de ATA) requer que o pagador tenha SOL suficiente para aluguel. Verificações de aluguel falhadas aparecem como erros obscuros.

Exemplo trabalhado: computando PDAs de CPMM de Raydium

Isso é exatamente o que o SDK de Raydium faz sob o capô quando você chama getPoolInfoFromRpc({ poolId }) — ele deriva os PDAs associados sem uma viagem de ida e volta.

Referências

Fontes: