Um Program Derived Address (PDA) é uma chave pública derivada de bytes de seed e um ID de programa que fica fora da curva ed25519, portanto não possui chave privada. Seu programa Anchor é o único ator que pode autorizar ações para esse endereço, provando as seeds e o bump para o runtime durante um CPI.
Esta página é o mapa conceitual para PDAs, Signer Seeds & Anchor - derivação orientada por constraints, armazenamento de bump, assinatura de PDA, padrões de autoridade, derivação no lado do cliente e os modos de falha que aparecem em produção no Agave 4.1.1.
O Anchor transforma PDAs em contas validadas e determinísticas: constraints de seeds e bump provam a identidade do endereço em cada instrução, e a mesma tupla de seeds é o que o programa usa posteriormente para assinar CPIs.
Importância: Escrows, vaults, mints, contas de configuração e estado por usuário quase sempre residem em PDAs. Seeds incorretas, constraints ausentes ou signer seeds quebradas significam tokens travados, estado falsificável ou drift silencioso de endereço entre cliente e programa.
Conceitos-chave:seeds, bump canônico, find_program_address / create_program_address, #[account(seeds, bump)], ctx.bumps, bump armazenado, CpiContext::new_with_signer, autoridade PDA, paridade de seeds no cliente.
Quando usar este modelo: Projetar grafos de contas Anchor que necessitam de endereços determinísticos, custódia pelo programa ou sharding por entidade; conectar clientes TypeScript com @solana/kit 7.0.0; auditar instruções que tocam vaults ou autoridades de mint.
Limitações/Trade-offs: PDAs não podem ser contas Signer comuns; fórmulas de seed são permanentes assim que as contas existem; arrays de seed e a busca de bump consomem CU; clientes e código on-chain devem permanecer byte-idênticos nas seeds e no ID do programa.
Programas Solana não mantêm estado de aplicação dentro de seu executável. Registros duráveis residem em contas que o programa possui. Uma PDA é um tipo especial de endereço para essas contas (e para papéis de signer puros que nunca mantêm dados): é computada, não gerada a partir de um keypair.
find_program_address tenta valores de bump de 255 para baixo até que o ponto esteja fora da curva. Esse primeiro valor válido é o bump canônico. create_program_address verifica um candidato com um bump conhecido; ele não busca.
Como não existe chave privada para um endereço fora da curva, uma assinatura de transação normal não pode autorizar essa chave. Somente um programa que fornece as seeds e o bump corretos (sob o ID do programa usado na derivação) pode marcar a PDA como signer em uma instrução aninhada via invoke_signed / Anchor with_signer.
No Anchor 0.32.1, você declara essa verificação em vez de implementá-la manualmente:
O Anchor re-deriva a PDA a partir dessas seeds e declare_id!, depois exige que a chave da conta passada corresponda. bump = account.bump opcional usa um byte armazenado para que a validação não precise re-buscar. Caminhos de inicialização usam init, payer, space e as mesmas seeds para que a conta seja criada no endereço derivado em uma única etapa.
Uma PDA não é um Signer<'info> na struct de contas. Signers de keypair provam presença com uma assinatura de transação; PDAs provam presença com seeds. Use Account, SystemAccount ou outros wrappers tipados com seeds / bump, e assine apenas ao emitir um CPI.
Toda instrução que toca uma PDA deve reafirmar as seeds. Clientes escolhem qual chave pública passar; sem constraints, um atacante pode substituir uma conta semelhante que seu manipulador trata como o vault canônico ou registro de usuário.
Preocupação
Mecanismo Anchor
O que impõe
Identidade do endereço
seeds = [...], bump / bump = ...
A chave passada é igual à PDA para o ID do programa + seeds
Criação
init, payer, space, seeds
Aloca e atribui no endereço derivado
Mutação
mut + seeds
Mesmas verificações de identidade, flag writable
Corpo tipado
Account<'info, T>
Proprietário, discriminador, desserialização
Assinatura em CPI
new_with_signer + fatias de seed
A PDA aparece como signer para os programas chamados
Mantenha os componentes de seed pequenos e estáveis: um prefixo estático (b"vault"), uma chave pública distintiva e talvez um ID little-endian. Pilhas de seed longas ou variáveis consomem CU e convidam a bugs no cliente. Documente a tupla de seed ao lado do tipo de conta para que Rust, testes e TypeScript permaneçam alinhados.
Na primeira inicialização, o Anchor expõe o mapa de bump resolvido como ctx.bumps.<nome_do_campo>. Grave isso nos dados da conta:
ctx.accounts.vault.bump = ctx.bumps.vault;
Instruções posteriores usam bump = vault.bump na constraint e o mesmo byte nas signer seeds de CPI. Benefícios:
CU: sem busca repetida de bump em caminhos críticos.
Determinismo: O CPI sempre usa o bump que tornou o endereço válido no momento da criação.
Clareza: um campo é a fonte da verdade para assinatura.
Não aceite um bump fornecido pelo cliente como argumento de instrução para verificações críticas de segurança. Prefira armazenamento on-chain e constraints. Se contas legadas não possuem um campo de bump, adicione uma instrução de migração que recomputa com find_program_address uma vez e grava o resultado; não invente um bump.
Quando a PDA deve autorizar um CPI (transferência de token de um ATA de vault, mint_to, transferência do sistema de lamports mantidos por uma PDA, etc.), construa signer seeds que correspondam às seeds da constraint, com o bump como a seed final de um byte:
use anchor_lang::prelude::*;use anchor_spl::token_interface::{self, TransferChecked};let bump = ctx.accounts.vault.bump;// `key()` retorna uma Pubkey por valor. Vincule-a antes de emprestá-la, ou o// temporário é descartado no final da instrução (E0716).let authority_key = ctx.accounts.authority.key();let seeds: &[&[u8]] = &[b"vault", authority_key.as_ref(), &[bump]];let signer_seeds: &[&[&[u8]]] = &[seeds];token_interface::transfer_checked( CpiContext::new_with_signer( ctx.accounts.token_program.to_account_info(), TransferChecked { from: ctx.accounts.vault_ata.to_account_info(), mint: ctx.accounts.mint.to_account_info(), to: ctx.accounts.user_ata.to_account_info(), authority: ctx.accounts.vault.to_account_info(), }, signer_seeds, ), amount, ctx.accounts.mint.decimals,)?;
Note o tipo de signer_seed: &[&[&[u8]]], uma fatia de grupos de seeds, um grupo por PDA para a qual você está assinando. Um único &[&[u8]] é um grupo e não passará na verificação de tipo onde uma lista de grupos é esperada. Use transfer_checked / TransferChecked; anchor_spl::token::{transfer, Transfer} são obsoletos e constroem uma instrução spl_token::instruction::transfer que rejeita o programa Token-2022.
A ordem e os bytes das seeds devem corresponder à inicialização e às constraints. A meta authority no CPI deve ser a conta da PDA, não uma carteira de usuário. Uma PDA pode assinar múltiplos CPIs na mesma instrução com as mesmas seeds. Um bump incorreto ou uma seed ausente falha o CPI como uma assinatura ausente (toda a transação é revertida). Equivalente nativo: invoke_signed. Detalhes em Assinatura com PDAs.
Programas usam PDAs como autoridades quando o protocolo, e não um keypair humano, deve controlar ativos ou suprimento:
Vault / escrow PDA possui um ATA que detém depósitos; CPIs de saque usam as seeds do vault.
Treasury PDA recebe taxas; caminhos de gasto exigem regras impostas pelo programa mais signer seeds.
Mint authority PDA emite recompensas ou itens de jogo; opcionalmente renuncie mais tarde definindo a autoridade de mint como None via CPI quando o design congelar o suprimento.
Sempre restrinja as relações de mint e ATA (token::authority, associated_token::authority, igualdade da chave de mint) para que uma assinatura PDA válida não opere no mint ou conta de token errados. Prefira PDAs separadas por mercado, pool ou funcionalidade em vez de uma PDA de autoridade global: menor raio de explosão se um bug aparecer em um caminho, e melhor paralelismo Sealevel quando mercados diferentes escrevem chaves diferentes.
Clientes devem passar as mesmas chaves públicas que o programa irá re-derivar. Com @solana/kit 7.0.0, use getProgramDerivedAddress (ou helpers gerados) com:
O endereço do programa do IDL / declare_id! (não uma chave de deploy aleatória de um arquivo .env antigo).
Bytes de seed idênticos aos on-chain: prefixos de string como bytes ASCII correspondendo a b"...", pubkeys como 32 bytes brutos, inteiros como little-endian (paridade to_le_bytes).
A mesma ordem das seeds.
import { getAddressEncoder, getProgramDerivedAddress } from "@solana/kit";const [escrowPda] = await getProgramDerivedAddress({ programAddress: programId, seeds: [ new TextEncoder().encode("escrow"), // getAddressEncoder - decodifica o Address base58 em seus 32 bytes brutos. // getBytesEncoder apenas passa um array de bytes existente. getAddressEncoder().encode(makerAddress), // u64 little-endian correspondendo a Rust to_le_bytes u64ToLeBytes(escrowId), ],});
Compartilhe constantes ou gere construtores de seeds a partir do IDL para que o TypeScript não se desvie do Rust. Testes de integração devem afirmar que PDAs derivadas no cliente são iguais aos endereços criados em testes Anchor. Veja Derivação de PDA no Lado do Cliente.
PDAs de programas externos. Ao fazer CPI para outro programa que possui uma PDA, passe esse endereço, mas não assine por ele, a menos que você seja esse programa. seeds::program do Anchor valida PDAs estrangeiras contra outro ID de programa. Seu programa só assina PDAs derivadas sob seu ID de programa.
Init vs. Re-init. Seeds fixam um slot no espaço de endereços. Após o fechamento, as mesmas seeds podem reabrir o mesmo endereço; projete os caminhos de fechamento com cuidado se a lógica assume "uma vez inicializado, para sempre confiável".
Sharding e CU. PDAs por usuário mantêm locks graváveis (writable) disjoint sob Sealevel. Mantenha a configuração global readonly em caminhos críticos. Armazene bumps, mantenha seeds curtas e simule antes da mainnet: a busca de bump e pilhas profundas de CPI consomem CU.
Ciclo de vida da autoridade. Mova a autoridade de mint ou freeze para uma PDA apenas em uma inicialização controlada. Deixar uma autoridade de mint de keypair após um deploy de "vault PDA" é um bug de segurança.
Matriz de testes. Para cada tipo de PDA: inicialização happy-path, seeds incorretas do cliente, ID de programa incorreto, CPI com signer seeds boas e ruins, e incompatibilidade de autoridade de token. Casos de falha estão catalogados em Erros Comuns de PDA.
"Uma PDA é apenas uma conta normal com um nome chique." É um endereço fora da curva; identidade e assinatura dependem de seeds e ID do programa, não de uma chave privada.
"Se eu passar a chave pública correta do cliente, as constraints de seeds são opcionais." Sem re-derivação on-chain, o cliente escolhe a confiança. Atacantes passam contas semelhantes.
"PDAs pertencem a Signer<'info> porque elas assinam CPIs." Signers em nível de transação são keypairs. PDAs se tornam signers apenas para invocações aninhadas via prova de seed.
"Posso mudar as fórmulas de seed em um upgrade e manter as mesmas contas." Endereços são fixos por seeds e ID do programa. Novas fórmulas são novos endereços; planeje a migração.
"O bump é um segredo." O bump é público e recuperável; a segurança vem da lógica do programa e das constraints, não do sigilo do bump. Ainda assim, armazene e reutilize o bump canônico para CU e consistência.
"A endianness do cliente não importa para seeds numéricas."to_le_bytes do Rust e buffers big-endian do JS produzem PDAs diferentes. Corresponda LE explicitamente.
"Uma PDA de autoridade compartilhada é mais simples e igualmente segura." Mais simples até que um bug drene todos os mercados. Prefira PDAs com escopo quando a escala ou o isolamento importam.
"with_signer é apenas para Token." Qualquer CPI que exija a PDA como signer (System Program, Token, Token-2022, chamados customizados) precisa de signer seeds.
"UncheckedAccount serve para PDAs durante o desenvolvimento." É o caminho padrão para verificações de propriedade e seeds ausentes. Use contas tipadas com seeds desde o primeiro rascunho.
"find_program_address em cada CPI é gratuito." É correto, mas custa CU; armazene o bump após a inicialização para caminhos críticos.
Um endereço determinístico fora da curva, derivado de seeds e um ID de programa, que não possui chave privada e só pode ser assinado por esse programa via invoke_signed / Anchor with_signer.
Qual versão do Anchor e stack de cliente esta seção assume?
Anchor 0.32.1 on-chain, Rust 1.91.1, ferramentas Agave 4.1.1 / Solana CLI 3.0.10, e @solana/kit 7.0.0 para derivação no cliente e construção de transações.
Como funcionam as constraints de seeds e bump do Anchor?
Elas re-derivam o endereço do programa a partir das expressões de seed listadas e do bump esperado, e então exigem que a chave pública da conta corresponda. A inicialização cria a conta nesse endereço; instruções posteriores re-verificam a identidade a cada vez.
Uma PDA pode ser declarada como Signer em uma struct de Contas?
Não. Use uma conta tipada (ou system account) com seeds e bump. Forneça signer seeds apenas ao construir um CPI que necessite da PDA como autoridade.
Onde o bump deve residir?
Nos dados da conta, definido uma vez na inicialização a partir de ctx.bumps, depois referenciado com bump = account.bump e em arrays de seed de CPI. Veja Armazenamento de Bumps.
Como uma PDA move tokens?
A PDA (ou um ATA pertencente à PDA) detém os tokens; o programa faz CPI para o Token program com CpiContext::new_with_signer usando as seeds do vault para que a PDA seja a autoridade. Veja Assinatura com PDAs e Autoridades PDA.
Qual ID de programa é usado ao derivar PDAs para meu programa Anchor?
O ID do programa de declare_id! / o endereço do programa implantado no IDL. O código do cliente deve usar esse mesmo ID, não a chave de outro programa.
Os arrays de seeds incluem o bump?
Na sintaxe de constraint do Anchor, o bump é separado (bump ou bump = ...). Para create_program_address e signer seeds de CPI, o bump é a seed final de um byte anexada à lista de seeds.
Como depurar falhas de ConstraintSeeds ou de assinatura em CPI?
Registre ou imprima o endereço derivado no cliente, as seeds esperadas on-chain, o ID do programa e o bump. Verifique a ordem das seeds, a codificação de strings, a endianness de inteiros e se a conta de autoridade do CPI é a PDA. Veja Erros Comuns de PDA.
As contas PDA são isentas de aluguel (rent-exempt)?
PDAs que contêm dados precisam de lamports isentos de aluguel para seu space, financiados pelo payer na inicialização. PDAs de signer puras ainda requerem planejamento deliberado de lamports dependendo do padrão.
Uma PDA pode assinar múltiplos CPIs em uma única instrução?
Sim. Reutilize as mesmas fatias de signer seed para cada chamada new_with_signer.
Como o TypeScript deve derivar a mesma PDA que o Rust?
Use getProgramDerivedAddress do @solana/kit 7.0.0 com bytes de seed, ordem, inteiros little-endian e endereço do programa idênticos. Prefira constantes compartilhadas ou geração de código. Veja Derivação de PDA no Lado do Cliente.
Quando devo dividir PDAs de autoridade em vez de uma autoridade global?
Quando você tem múltiplos mercados ou domínios de risco, ou deseja que o Sealevel execute fluxos não sobrepostos em paralelo. Um caminho de autoridade global gravável serializa o tráfego e concentra o impacto de exploração.
A atualização do programa altera os endereços PDA?
Não, se o ID do programa e as fórmulas de seed permanecerem os mesmos. Upgrades alteram apenas o bytecode do ProgramData. Novas seeds ou um novo ID de programa geram novas PDAs.