> ## 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.

# PDAs và CPIs

> Program-derived addresses và cross-program invocation — hai primitives mà Raydium sử dụng để quản lý trạng thái một cách xác định và kết hợp với các chương trình khác. Seeds, bumps, invoke vs invoke_signed, và các mẫu mã mà Raydium tuân theo.

<Info>
  **Trang này được dịch tự động bằng AI. Phiên bản tiếng Anh là bản chính thức.**

  [Xem bản tiếng Anh →](/solana-fundamentals/pdas-and-cpis)
</Info>

<Info>
  **PDAs** (program-derived addresses) và **CPIs** (cross-program invocation) là hai primitives làm cho Raydium hoạt động được. PDAs cho phép một chương trình "sở hữu" các địa chỉ xác định mà không cần khóa riêng — đó là cách hoạt động của pool authorities và vaults. CPIs cho phép một chương trình gọi chương trình khác — đó là cách Raydium hoán đổi tokens thông qua chương trình SPL Token và cách các integrators kết hợp Raydium vào luồng công việc của riêng họ. Cả hai đều đáng hiểu trước khi đọc mã nguồn của Raydium.
</Info>

## PDAs: địa chỉ không có khóa

**Program-Derived Address** là một public key mà:

* Không nằm trên đường cong ed25519 (không tồn tại khóa riêng cho nó).
* Được dẫn xuất một cách xác định từ một program ID và một tập hợp seeds.
* Có thể được ký bởi *chỉ* chương trình dẫn xuất, thông qua `invoke_signed`.

Mỗi pool authority của Raydium, mỗi pool state account, mỗi vault, mỗi farm state — tất cả đều là PDAs.

### Dẫn xuất

Một PDA được tính bằng cách băm program ID với các seeds, sau đó tìm một byte "bump" để buộc kết quả nằm ngoài đường cong. Byte bump đầu tiên (thường bắt đầu từ 255 và giảm dần) tạo ra một địa chỉ nằm ngoài đường cong sẽ thắng; đây là **canonical bump**.

```ts theme={null}
import { PublicKey } from "@solana/web3.js";

const [poolAuthority, bump] = PublicKey.findProgramAddressSync(
  [Buffer.from("authority"), poolId.toBuffer()],
  CPMM_PROGRAM_ID,
);
```

Các seeds có thể là bất cứ thứ gì — chuỗi ký tự, các pubkeys khác, giá trị `u64` dưới dạng bytes little-endian. Quy ước của Raydium là một tiền tố có thể đọc được của con người theo sau bởi các định danh duy nhất.

### Mẫu PDA của Raydium

PDAs phổ biến trong các chương trình của Raydium:

| PDA                      | Seeds                                     | Program |
| ------------------------ | ----------------------------------------- | ------- |
| AMM authority (AMM v4)   | `[b"amm authority"]` + bump               | AMM 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 |

Người dùng và integrators có thể tính toán những PDAs này mà không cần tìm nạp bất cứ thứ gì — với các input công khai (pool ID, farm ID, user key), PDA là xác định được.

### Canonical bump

Mặc dù về nguyên tắc có thể tồn tại nhiều bumps tạo ra các địa chỉ nằm ngoài đường cong, các chương trình của Raydium luôn sử dụng **canonical bump** (được tìm thấy bằng cách giảm dần từ 255). Điều này được lưu trữ trong dữ liệu account của PDA để các giao dịch tiếp theo có thể chuyển nó vào và bỏ qua vòng lặp dẫn xuất (chi phí compute lớn):

```rust theme={null}
#[account]
pub struct PoolState {
    pub bump: [u8; 1],
    // ... rest of pool state
}
```

Trong các giao dịch tiếp theo, bump được đọc từ pool state thay vì được tính toán lại.

## CPIs: gọi các chương trình khác

**Cross-Program Invocation** cho phép một chương trình gọi các instructions của chương trình khác theo cách inline trong một giao dịch duy nhất. Raydium sử dụng CPIs rộng rãi:

* Các swap instructions gọi chương trình SPL Token để di chuyển tokens.
* CLMM gọi Metaplex để mint position NFT.
* Pool creation gọi System Program để cấp phát accounts.
* Farm v6 gọi SPL Token để chuyển phần thưởng.

Integrators cũng sử dụng CPIs để gọi *vào* Raydium — đó là cách các vault strategies, leveraged-LP protocols, và auto-compounders hoạt động. Xem [`integration-guides/cpi-integration`](/vi/integration-guides/cpi-integration).

### invoke vs invoke\_signed

Solana runtime cung cấp hai CPI primitives:

* **`invoke`**: gọi chương trình khác; chương trình được gọi sẽ kế thừa các signers từ giao dịch bên ngoài.
* **`invoke_signed`**: gọi chương trình khác *thay mặt cho một PDA*; runtime xác minh các seeds của PDA và ủy quyền cho chữ ký.

`invoke_signed` là phép thuật cho phép các chương trình giữ quyền kiểm soát các accounts mà không cần quản lý các khóa riêng.

### Ví dụ: Raydium chuyển từ một pool vault

Một pool vault là một Token Account có authority là một PDA của pool program. Để chuyển tokens ra ngoài trong suốt một swap, pool program phải ký tên như PDA đó:

```rust theme={null}
// 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)?;
```

Runtime thấy `invoke_signed` được gọi bởi CPMM program, xác minh rằng `vault_and_lp_mint_auth_seed + bump` dẫn xuất tới địa chỉ của `pool_authority` khi được băm với program ID của CPMM, và cho phép chữ ký của authority trên token transfer. Không có khóa riêng nào liên quan.

### Ví dụ: integrator gọi Raydium CPMM

Một integrator program (ví dụ: một escrow) có thể gọi `swap_base_input` của Raydium thông qua CPI:

```rust theme={null}
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)?;
```

Đây là mẫu tích hợp chính thức — xem [`integration-guides/cpi-integration`](/vi/integration-guides/cpi-integration) để xem ví dụ escrow đầy đủ.

## Giới hạn độ sâu CPI

Solana giới hạn độ sâu CPI ở **4 level**. Instruction cấp cao nhất của một giao dịch được tính là depth 0; mỗi CPI invocation tăng depth thêm một.

Ý nghĩa thực tế: swap của chính Raydium đã sử dụng 1-2 levels của CPI (Raydium → SPL Token). Một integrator gọi Raydium sử dụng 2. Nếu integrator đó được gọi bởi một integrator khác, đó là 3. Level thứ 4 là giới hạn.

Hầu hết các compositions vẫn ở dưới giới hạn này một cách dễ dàng, nhưng nesting sâu (aggregator → router → Raydium → hook) có thể vượt qua nó. Hãy thiết kế flat thay vì deep.

## Remaining accounts

Khi một Raydium instruction cần một số lượng accounts có thể thay đổi (ví dụ: CLMM swap đi qua một số lượng tick arrays không xác định), các accounts bổ sung được chuyển qua như **remaining accounts** — được thêm vào danh sách fixed-account, được diễn giải theo vị trí.

`SwapV2` của CPMM sử dụng remaining accounts cho các transfer-hook programs' extra required accounts. Clients tìm nạp các accounts cần thiết và thêm chúng:

```ts theme={null}
const swapIx = await raydium.cpmm.swap({
  /* ... */
  // SDK handles remaining accounts automatically
});
```

Ở mức CPI, integrators phải chuyển tiếp remaining accounts thông qua instruction riêng của họ:

```rust theme={null}
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,
)?;
```

## Những cạm bẫy của PDA

### Seeds sai → địa chỉ sai

Một lỗi trong đó các seeds ở thứ tự sai, mã hóa sai, hoặc bao gồm/loại trừ một byte thêm sẽ im lặng tạo ra một PDA khác. Giao dịch sẽ thất bại một cách mơ hồ (chương trình cố gắng đọc một account không tồn tại). Luôn test đơn vị dẫn xuất seed so với các giá trị golden đã biết.

### Không lưu trữ bump

Nếu bạn tái dẫn xuất bump trên mỗi giao dịch, bạn sẽ phải trả compute cho vòng lặp dẫn xuất. Lưu trữ canonical bump trong dữ liệu của PDA và đọc nó từ đó.

### Nhầm lẫn canonical vs non-canonical bump

Non-canonical bumps (nếu ai đó tìm thấy một cái tạo ra off-curve) được cho phép bởi `invoke_signed` nhưng bị từ chối bởi các chương trình của Raydium thông qua `assert_eq!(bump, canonical_bump)`. Nếu ai đó cố gắng khẳng định một PDA với non-canonical bump, tx sẽ thất bại.

### Chuyển một PDA dưới dạng signer khi bạn không phải là chương trình sở hữu

Chỉ chương trình có ID trong dẫn xuất PDA của PDA có thể `invoke_signed` với các seeds của nó. Nếu bạn cố gắng, runtime sẽ từ chối.

## Những cạm bẫy của CPI

### Quên chuyển tiếp `remaining_accounts`

Nếu instruction bên ngoài của bạn chuyển transfer-hook accounts trong `remaining_accounts` nhưng CPI vào Raydium không chuyển tiếp chúng, Raydium sẽ thất bại vì nó không thể tìm thấy các hook accounts. Luôn bao gồm `with_remaining_accounts` trong các CPIs cần chúng.

### Writable flags mismatch

Một account mà instruction bên ngoài đánh dấu writable cũng phải writable trong lệnh gọi CPI nếu chương trình được gọi dự định viết vào nó. Mismatch → runtime rejection.

### Không tính đến rent

CPI tới một chương trình tạo một account (ví dụ: ATA creation) yêu cầu payer có đủ SOL cho rent. Failed rent checks xuất hiện dưới dạng các lỗi mơ hồ.

## Ví dụ đã làm việc: tính toán PDAs CPMM của Raydium

```ts theme={null}
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 };
}
```

Đây chính xác là những gì Raydium SDK làm dưới nắp khi bạn gọi `getPoolInfoFromRpc({ poolId })` — nó dẫn xuất các PDAs liên quan mà không cần một round-trip.

## Các con trỏ

* [`solana-fundamentals/account-model`](/vi/solana-fundamentals/account-model) — cách PDAs vừa vặn trong mô hình account.
* [`solana-fundamentals/programs-and-anchor`](/vi/solana-fundamentals/programs-and-anchor) — các trợ giúp của Anchor để khai báo PDAs.
* [`integration-guides/cpi-integration`](/vi/integration-guides/cpi-integration) — xây dựng các tích hợp mà CPI vào Raydium.
* [`sdk-api/rust-cpi`](/vi/sdk-api/rust-cpi) — các loại Rust CPI của Raydium.

Nguồn:

* [Solana PDA docs](https://docs.solana.com/developing/programming-model/calling-between-programs).
* [Anchor PDA & CPI docs](https://www.anchor-lang.com/docs/pdas-and-cpis).
