Zum Hauptinhalt springen

Documentation Index

Fetch the complete documentation index at: https://docs.raydium.io/llms.txt

Use this file to discover all available pages before exploring further.

Diese Seite wurde mit KI automatisch übersetzt. Maßgeblich ist stets die englische Version.Englische Version ansehen →
PDAs (program-derived addresses) und CPIs (cross-program invocation) sind die zwei Primitiven, die Raydium ermöglichen. PDAs lassen ein Programm deterministische Adressen „besitzen”, ohne dass Privatschlüssel verwaltet werden — so funktionieren Pool-Authorities und Vaults. CPIs lassen ein Programm ein anderes aufrufen — so tauscht Raydium Token über das SPL Token Program aus und wie Integratoren Raydium in ihre eigenen Flows einbinden. Beide sind es wert, verstanden zu werden, bevor Sie Raydiums Source Code lesen.

PDAs: Adressen ohne Schlüssel

Eine Program-Derived Address ist ein Public Key, der:
  • Nicht auf der ed25519-Kurve liegt (es existiert kein Privatschlüssel dafür).
  • Deterministisch aus einer Program ID und einem Satz von Seeds abgeleitet wird.
  • Nur vom ableitenden Programm signiert werden kann, über invoke_signed.
Jede Raydium Pool Authority, jedes Pool-State-Account, jeder Vault, jeder Farm-State — sie sind alle PDAs.

Ableitung

Ein PDA wird berechnet, indem die Program ID mit den Seeds gehasht wird und dann ein „Bump”-Byte gefunden wird, das das Ergebnis von der Kurve drückt. Der erste Bump (typischerweise beginnend bei 255 und abnehmend), der eine off-curve Adresse produziert, gewinnt; dies ist der kanonische Bump.
import { PublicKey } from "@solana/web3.js";

const [poolAuthority, bump] = PublicKey.findProgramAddressSync(
  [Buffer.from("authority"), poolId.toBuffer()],
  CPMM_PROGRAM_ID,
);
Die Seeds können alles sein — Strings, andere Public Keys, u64-Werte als Little-Endian Bytes. Raydiums Konvention ist ein lesbares Präfix gefolgt von eindeutigen Identifiern.

Raydium PDA Muster

Häufige PDAs in Raydiums Programmen:
PDASeedsProgramm
AMM Authority (AMM v4)[b"amm authority"] + bumpAMM v4
Pool State (CPMM)[b"pool", amm_config, mint_a, mint_b]CPMM
Pool Vault (CPMM)[b"pool_vault", pool, mint]CPMM
Authority (CPMM)[b"vault_and_lp_mint_auth_seed"]CPMM
Pool State (CLMM)[b"pool", amm_config, mint_0, mint_1]CLMM
Tick Array (CLMM)[b"tick_array", pool, start_tick_index]CLMM
Observation (CLMM)[b"observation", pool]CLMM
Personal Position (CLMM)[b"position", position_nft_mint]CLMM
Farm State (Farm v6)[b"pool_farm_state", farm_id]Farm v6
User Ledger (Farm v6)[b"user_ledger", farm, user]Farm v6
Benutzer und Integratoren können diese berechnen, ohne etwas zu fetchen — anhand der öffentlichen Eingaben (Pool ID, Farm ID, Benutzer-Key) ist die PDA deterministisch.

Kanonischer Bump

Obwohl es prinzipiell mehrere Bumps geben kann, die Off-Curve-Adressen erzeugen, verwenden Raydiums Programme immer den kanonischen Bump (gefunden durch Dekrementierung von 255). Dieser wird in den Account-Daten der PDA gespeichert, sodass nachfolgende Transaktionen ihn übergeben und die (teure) Ableitungsschleife überspringen können:
#[account]
pub struct PoolState {
    pub bump: [u8; 1],
    // ... rest of pool state
}
Bei nachfolgenden Transaktionen wird der Bump aus dem Pool-State ausgelesen, anstatt neu berechnet zu werden.

CPIs: Andere Programme aufrufen

Cross-Program Invocation lässt ein Programm ein anderes Programms Instruktionen inline innerhalb einer einzelnen Transaktion aufrufen. Raydium nutzt CPIs umfangreich:
  • Swap-Instruktionen rufen SPL Token Program auf, um Token zu verschieben.
  • CLMM ruft Metaplex auf, um die Position NFT zu prägen.
  • Pool-Erstellung ruft System Program auf, um Accounts zuzuweisen.
  • Farm v6 ruft SPL Token auf, um Rewards zu übertragen.
Integratoren nutzen CPIs auch, um in Raydium aufzurufen — so funktionieren Vault-Strategien, gehebelte LP-Protokolle und Auto-Compounder. Siehe integration-guides/cpi-integration.

invoke vs invoke_signed

Die Solana Runtime bietet zwei CPI Primitiven:
  • invoke: ruft ein anderes Programm auf; das aufgerufene Programm erbt die Unterzeichner der äußeren Transaktion.
  • invoke_signed: ruft ein anderes Programm im Namen einer PDA auf; die Runtime verifiziert die PDA-Seeds und autorisiert die Signatur.
invoke_signed ist die Magie, die Programmen ermöglicht, die Autorität über Accounts zu halten, ohne Private Keys zu verwalten.

Beispiel: Raydium überträgt aus einem Pool Vault

Ein Pool Vault ist ein Token Account, dessen Authority eine PDA des Pool Programs ist. Um Token während eines Swaps zu übertragen, muss das Pool Program als diese PDA unterzeichnen:
// Seeds for the pool authority
let authority_seeds: &[&[u8]] = &[
    b"vault_and_lp_mint_auth_seed",
    &[authority_bump],
];
let signer_seeds: &[&[&[u8]]] = &[authority_seeds];

// Build CPI context
let cpi_accounts = Transfer {
    from:      input_vault.to_account_info(),
    to:        user_ata.to_account_info(),
    authority: pool_authority.to_account_info(),
};
let cpi_program = token_program.to_account_info();
let cpi_ctx     = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds);

// Execute
token::transfer(cpi_ctx, amount)?;
Die Runtime sieht, dass invoke_signed vom CPMM Programm aufgerufen wird, verifiziert, dass vault_and_lp_mint_auth_seed + bump zu pool_authoritys Adresse gehasht wird, wenn es mit der CPMM Program ID gehasht wird, und erlaubt die Authority-Signatur auf dem Token-Transfer. Kein Privatschlüssel beteiligt.

Beispiel: Integrator ruft Raydium CPMM auf

Ein Integrator-Programm (z. B. ein Escrow) kann Raydiums swap_base_input über CPI aufrufen:
use raydium_cpmm::cpi::{self, accounts::Swap};

let cpi_accounts = Swap {
    payer:                order.to_account_info(),      // PDA, will sign
    authority:            pool_authority_info,
    amm_config:           amm_config_info,
    pool_state:           pool_info,
    input_token_account:  order_input_ata,
    output_token_account: order_output_ata,
    input_vault:          input_vault_info,
    output_vault:         output_vault_info,
    input_token_program:  input_token_program_info,
    output_token_program: output_token_program_info,
    input_token_mint:     input_mint_info,
    output_token_mint:    output_mint_info,
    observation_state:    observation_info,
};

let seeds = &[b"order", user.key.as_ref(), &[order.bump]];
let cpi_ctx = CpiContext::new_with_signer(
    cpmm_program.to_account_info(),
    cpi_accounts,
    &[seeds],
);

cpi::swap_base_input(cpi_ctx, amount_in, min_out)?;
Dies ist das kanonische Integrationsmuster — siehe integration-guides/cpi-integration für das vollständige Escrow-Beispiel.

CPI Tiefenlimit

Solana begrenzt die CPI-Tiefe auf 4 Ebenen. Eine Top-Level-Instruktion einer Transaktion zählt als Tiefe 0; jeder CPI-Aufruf erhöht die Tiefe. Praktische Auswirkung: Raydiums eigener Swap nutzt bereits 1-2 Ebenen von CPIs (Raydium → SPL Token). Ein Integrator, der Raydium aufruft, nutzt 2. Wenn dieser Integrator von einem anderen Integrator aufgerufen wird, sind es 3. Die 4. Ebene ist das Limit. Die meisten Kompositionsszenarien bleiben darunter problemlos, aber tiefe Verschachtelung (Aggregator → Router → Raydium → Hook) kann dieses Limit erreichen. Entwerfen Sie flach statt tief.

Remaining Accounts

Wenn eine Raydium Instruktion eine variable Anzahl von Accounts benötigt (z. B. CLMM Swap, der eine unbekannte Anzahl von Tick Arrays kreuzt), werden die zusätzlichen Accounts als remaining accounts übergeben — angehängt an die Fest-Account-Liste, interpretiert nach Position. CPMMs SwapV2 nutzt remaining accounts für zusätzliche erforderliche Accounts von Transfer-Hook-Programmen. Clients fetchen die benötigten Accounts und hängen sie an:
const swapIx = await raydium.cpmm.swap({
  /* ... */
  // SDK handles remaining accounts automatically
});
Auf CPI-Ebene müssen Integratoren remaining accounts durch ihre eigene Instruktion weitergeben:
pub struct Swap<'info> {
    // ... fixed accounts
    // Plus Remaining accounts forwarded via ctx.remaining_accounts
}

// Forward remaining_accounts into the CPI
cpi::swap_base_input(
    cpi_ctx.with_remaining_accounts(ctx.remaining_accounts.to_vec()),
    amount_in,
    min_out,
)?;

PDA Fallstricke

Falsche Seeds → falsche Adresse

Ein Bug, bei dem Seeds in der falschen Reihenfolge, falscher Codierung sind oder ein zusätzliches Byte einschließen/ausschließen, erzeugt stillschweigend eine andere PDA. Die Transaktion schlägt mehrdeutig fehl (das Programm versucht, ein Account zu lesen, das nicht existiert). Testen Sie die Seed-Ableitung immer mit bekannten Referenzwerten.

Bump nicht speichern

Wenn Sie den Bump bei jeder Transaktion neu ableiten, zahlen Sie Compute für die Ableitungsschleife. Speichern Sie den kanonischen Bump in den Daten der PDA und lesen Sie ihn von dort aus.

Verwechslung zwischen kanonischem und nicht-kanonischem Bump

Nicht-kanonische Bumps (wenn jemand einen findet, der off-curve erzeugt) sind von invoke_signed erlaubt, aber von Raydiums Programmen durch assert_eq!(bump, canonical_bump) abgelehnt. Wenn jemand versucht, eine PDA mit einem nicht-kanonischen Bump zu beanspruchen, schlägt die Transaktion fehl.

PDA als Unterzeichner übergeben, wenn Sie nicht das besitzende Programm sind

Nur das Programm, dessen ID in der PDA-Ableitung enthalten ist, kann invoke_signed mit seinen Seeds aufrufen. Wenn Sie es versuchen, lehnt die Runtime es ab.

CPI Fallstricke

Vergessen, remaining_accounts weiterzugeben

Wenn Ihre äußere Instruktion Transfer-Hook-Accounts in remaining_accounts übergibt, aber der CPI in Raydium sie nicht weitergibt, schlägt Raydium fehl, da es die Hook-Accounts nicht finden kann. Schließen Sie immer with_remaining_accounts in CPIs ein, die sie benötigen.

Writable Flags stimmen nicht überein

Ein Account, den die äußere Instruktion als schreibbar kennzeichnet, muss auch in der CPI-Aufrufe schreibbar sein, wenn das aufgerufene Programm beabsichtigt, ihn zu schreiben. Nichtübereinstimmung → Runtime-Ablehnung.

Keine Berücksichtigung der Rent

CPI zu einem Programm, das ein Account erstellt (z. B. ATA-Erstellung), erfordert, dass der Zahler genügend SOL für Rent hat. Fehlgeschlagene Rent-Prüfungen erscheinen als undurchsichtige Fehler.

Praktisches Beispiel: Raydium CPMM PDAs berechnen

import { PublicKey } from "@solana/web3.js";

const CPMM_PROGRAM_ID = new PublicKey("CPMMoo8L3F4NbTegBCKVNunggL7H1Zpdmwpwh8KMoZ0F");

function computeCpmmPdas(ammConfig, mintA, mintB) {
  const [poolState, poolBump] = PublicKey.findProgramAddressSync(
    [
      Buffer.from("pool"),
      ammConfig.toBuffer(),
      mintA.toBuffer(),
      mintB.toBuffer(),
    ],
    CPMM_PROGRAM_ID,
  );

  const [authority] = PublicKey.findProgramAddressSync(
    [Buffer.from("vault_and_lp_mint_auth_seed")],
    CPMM_PROGRAM_ID,
  );

  const [vaultA] = PublicKey.findProgramAddressSync(
    [Buffer.from("pool_vault"), poolState.toBuffer(), mintA.toBuffer()],
    CPMM_PROGRAM_ID,
  );

  const [vaultB] = PublicKey.findProgramAddressSync(
    [Buffer.from("pool_vault"), poolState.toBuffer(), mintB.toBuffer()],
    CPMM_PROGRAM_ID,
  );

  const [observation] = PublicKey.findProgramAddressSync(
    [Buffer.from("observation"), poolState.toBuffer()],
    CPMM_PROGRAM_ID,
  );

  return { poolState, authority, vaultA, vaultB, observation, poolBump };
}
Dies ist exakt das, was Raydium SDK unter der Haube tut, wenn Sie getPoolInfoFromRpc({ poolId }) aufrufen — es leitet die zugehörigen PDAs ab, ohne einen Round-Trip zu benötigen.

Verweise

Quellen: