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

# 農場／質押代碼示例

> 針對 Raydium 農場 v6 程式的 TypeScript 示例，涵蓋建立農場、質押 LP、收穫獎勵和補充獎勵預算。

<Info>
  **本頁內容由 AI 自動翻譯，所有內容以英文版本為準。**

  [查看英文版 →](/products/farm-staking/code-demos)
</Info>

<Info>
  **版本說明。** 所有示例針對 `@raydium-io/raydium-sdk-v2@0.2.42-alpha` 運行於 Solana mainnet-beta，已於 2026 年 4 月驗證。SDK 會根據農場的程式所有者內部分配 v3 / v5 / v6；下述示例假設為 v6 農場。請參見 [`reference/program-addresses`](/zh-Hant/reference/program-addresses) 以取得三個程式 ID。
</Info>

## 設定

此處的示例鏡像 [`raydium-sdk-V2-demo/src/farm`](https://github.com/raydium-io/raydium-sdk-V2-demo/tree/master/src/farm) 中的檔案。初始化按照示例存儲庫的 [`config.ts.template`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/config.ts.template) 進行：

```ts theme={null}
import { Connection, Keypair, clusterApiUrl, PublicKey } from "@solana/web3.js";
import { Raydium, TxVersion } from "@raydium-io/raydium-sdk-v2";
import BN from "bn.js";
import fs from "node:fs";

const connection = new Connection(process.env.RPC_URL ?? clusterApiUrl("mainnet-beta"));
const owner = Keypair.fromSecretKey(
  new Uint8Array(JSON.parse(fs.readFileSync(process.env.KEYPAIR!, "utf8"))),
);
const raydium = await Raydium.load({
  owner,
  connection,
  cluster: "mainnet",
  disableFeatureCheck: true,
  blockhashCommitment: "finalized",
});
export const txVersion = TxVersion.V0;
```

## 按 ID 取得農場

```ts theme={null}
const farmId = new PublicKey("<FARM_ID>");

const farm = await raydium.farm.getFarmById({ farmId });
console.log("Staking mint:", farm.symbolMint.toBase58());
console.log("Total staked:", farm.totalStaked.toString());
console.log("Rewards:");
for (const r of farm.rewardInfos) {
  console.log(
    "  -", r.mint.toBase58(),
    "rate(1e-" + r.decimals + "/s):", r.perSecond.toString(),
    "open:", new Date(r.openTime * 1000).toISOString(),
    "end: ", new Date(r.endTime * 1000).toISOString(),
  );
}
```

`getFarmById` 從鏈外取得 `FarmState`，根據程式版本解碼，並將固定點發行率規範化為每秒的純 `Decimal` 值。

## 質押 LP 代幣

來源：[`src/farm/stake.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/farm/stake.ts)

```ts theme={null}
const amount = new BN(1_000_000_000);   // 1 LP (assuming 9 decimals)

const { execute } = await raydium.farm.deposit({
  farmInfo: farm,
  amount,
  txVersion: TxVersion.V0,
});

const { txId } = await execute({ sendAndConfirm: true });
console.log("Deposit tx:", txId);
```

SDK 會自動結算任何未決獎勵，因此如果此錢包在此農場中已有質押，該指令將在同一交易中向用戶的 ATA 支付累積的獎勵。

## 僅領取（收穫）

來源：[`src/farm/harvest.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/farm/harvest.ts)

```ts theme={null}
const { execute } = await raydium.farm.harvestAllRewards({
  farmInfoList: [farm],
  txVersion: TxVersion.V0,
});

await execute({ sendAndConfirm: true });
```

`harvestAllRewards` 接受列表 — 對於顯示投資組合視圖的 UI，批量調用。每個農場在一個交易中以單獨的指令領取（受 1232 字節大小限制；對於 >\~6 個農場，分成多個交易）。

對於 v6 上的單個農場，你也可以使用顯式的 `Harvest` 路徑：

```ts theme={null}
const { execute } = await raydium.farm.withdraw({
  farmInfo: farm,
  amount: new BN(0),        // 0 = harvest-only path, even on v6
  txVersion: TxVersion.V0,
});
await execute({ sendAndConfirm: true });
```

在 v3 和 v5 上，`amount: 0` 習慣用法是必需的；SDK 會正確分配它。

## 取消質押

來源：[`src/farm/unstake.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/farm/unstake.ts)

```ts theme={null}
const amount = new BN(500_000_000);   // 0.5 LP

const { execute } = await raydium.farm.withdraw({
  farmInfo: farm,
  amount,
  txVersion: TxVersion.V0,
});

await execute({ sendAndConfirm: true });
```

## 建立 v6 農場

來源：[`src/farm/createAmmFarm.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/farm/createAmmFarm.ts) 和 [`editAmmFarm.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/farm/editAmmFarm.ts)

```ts theme={null}
import { CurveCalculator } from "@raydium-io/raydium-sdk-v2"; // optional, for APR planning

const stakingMint = new PublicKey("<LP_MINT>");       // e.g. a CPMM LP mint
const rewardMint  = new PublicKey("<REWARD_MINT>");   // e.g. your project's token

const now      = Math.floor(Date.now() / 1000);
const openTime = now + 60 * 60;                       // start in 1h
const duration = 60 * 60 * 24 * 30;                   // 30 days
const endTime  = openTime + duration;

const totalBudget = new BN(1_000_000).mul(new BN(10).pow(new BN(9))); // 1M reward tokens (9 dec)
const perSecond   = totalBudget.div(new BN(duration));                // integer, SDK lifts to Q64.64

const { execute, extInfo } = await raydium.farm.create({
  programId: /* v6 program ID, from reference/program-addresses */,
  poolInfo: { lpMint: { address: stakingMint, decimals: 9, programId: /* SPL Token */ } as any },
  rewardInfos: [
    {
      mint:           rewardMint,
      openTime:       new BN(openTime),
      endTime:        new BN(endTime),
      perSecond,
      rewardSender:   owner.publicKey,    // who pays the initial budget
      mintProgramId:  /* SPL Token or Token-2022 */,
    },
  ],
  txVersion: TxVersion.V0,
});

const { txId } = await execute({ sendAndConfirm: true });
console.log("Farm:", extInfo.farmId.toBase58(), "createTx:", txId);
```

關鍵點：

* `perSecond` 是每秒的整數發行率。SDK 在發送前將其打包為 Q64.64。對於分數率，縮放並調整時間。
* 完整預算（`perSecond × duration`）必須存在於你的獎勵 ATA 中 — `create` 會原子性地將其移入獎勵金庫。
* 你可以在一個 `create` 調用中設置最多 5 個獎勵。帳戶列表每增加一個獎勵流會增長 `(reward_mint, reward_vault, sender_ata, token_program)`；注意 1232 字節交易大小限制。對於 4+ 個獎勵，先用 1-2 個創建，然後在後續交易中使用 `AddReward`。

## 補充現有獎勵流

```ts theme={null}
const additionalDays = 30;
const extraSeconds   = 60 * 60 * 24 * additionalDays;
const extraBudget    = perSecond.mul(new BN(extraSeconds));

const { execute } = await raydium.farm.setRewards({
  farmInfo: farm,
  rewardInfos: [
    {
      rewardMint:  rewardMint,
      newEndTime:  new BN(farm.rewardInfos[0].endTime + extraSeconds),
      newPerSecond: perSecond,                   // keep same rate
      payer:        owner.publicKey,
    },
  ],
  txVersion: TxVersion.V0,
});

await execute({ sendAndConfirm: true });
```

`setRewards` 延長 `end_time` 並轉移差額預算。該指令無法縮短流、無法降低活躍流的 `per_second`，也無法變更獎勵代幣。若要交換代幣，請等待 `end_time` 並在釋放的插槽（如有）上使用 `AddReward`，或建立新農場。

## 重啟已結束的流

```ts theme={null}
const { execute } = await raydium.farm.restartRewards({
  farmInfo: farm,
  newRewardInfo: {
    rewardMint:   rewardMint,
    openTime:     new BN(Math.floor(Date.now() / 1000) + 60 * 60),
    endTime:      new BN(Math.floor(Date.now() / 1000) + 60 * 60 + 60 * 60 * 24 * 14),
    perSecond:    new BN("1000000000"),
    payer:        owner.publicKey,
  },
  txVersion: TxVersion.V0,
});

await execute({ sendAndConfirm: true });
```

僅在目標插槽的 `reward_state == 2`（已結束）時有效。呼叫者必須是該插槽的 `reward_sender`（v6）或農場所有者（v5）。

## Rust CPI

與 AMM v4 不同，v6 農場程式隨附一個 Anchor crate（`raydium_farm_v6`），與前端和 SDK 源一起發佈。最小 `Deposit` 草圖：

```rust theme={null}
use anchor_lang::prelude::*;
use raydium_farm_v6::{self, cpi::accounts::Deposit as RaydiumDeposit};

#[derive(Accounts)]
pub struct ProxyFarmDeposit<'info> {
    /// CHECK:
    pub farm_program: UncheckedAccount<'info>,
    #[account(mut)] pub user: Signer<'info>,
    #[account(mut)] /// CHECK:
    pub user_ledger: UncheckedAccount<'info>,
    #[account(mut)] /// CHECK:
    pub farm_state: UncheckedAccount<'info>,
    /// CHECK:
    pub farm_authority: UncheckedAccount<'info>,
    #[account(mut)] /// CHECK:
    pub staking_vault: UncheckedAccount<'info>,
    #[account(mut)] /// CHECK:
    pub user_staking_ata: UncheckedAccount<'info>,
    // Followed in remaining_accounts by (reward_vault_i, user_reward_ata_i) pairs
    // for each live reward stream on the farm.
    pub token_program: UncheckedAccount<'info>,
    pub system_program: Program<'info, System>,
}

pub fn proxy_deposit<'info>(
    ctx: Context<'_, '_, '_, 'info, ProxyFarmDeposit<'info>>,
    amount: u64,
) -> Result<()> {
    let cpi_ctx = CpiContext::new(
        ctx.accounts.farm_program.to_account_info(),
        RaydiumDeposit {
            user:             ctx.accounts.user.to_account_info(),
            user_ledger:      ctx.accounts.user_ledger.to_account_info(),
            farm_state:       ctx.accounts.farm_state.to_account_info(),
            farm_authority:   ctx.accounts.farm_authority.to_account_info(),
            staking_vault:    ctx.accounts.staking_vault.to_account_info(),
            user_staking_ata: ctx.accounts.user_staking_ata.to_account_info(),
            token_program:    ctx.accounts.token_program.to_account_info(),
            system_program:   ctx.accounts.system_program.to_account_info(),
        },
    ).with_remaining_accounts(ctx.remaining_accounts.to_vec());

    raydium_farm_v6::cpi::deposit(cpi_ctx, amount)
}
```

`remaining_accounts` 切片必須與農場的活躍獎勵插槽 1-對-1 相符（按索引順序的 `reward_vault_i`、`user_reward_ata_i` 對）。遺漏或排列錯誤會導致無聲的誤算 — 程式會轉移錯誤的金額。

## 常見陷阱

* **提現前忘記領取。** 無害 — `Withdraw` 會先結算未決獎勵。但如果你的 UI 將「領取」與「提現」分開顯示，用戶可能認為 `Withdraw` 後還有東西要領取。沒有；所有累積到那時的獎勵都已支出。
* **發行期間 `total_staked = 0`。** 在沒有質押時累積的發行會被沒收（`reward_per_share` 更新公式除以 0，程式跳過更新）。對於有計畫 `open_time` 的程式，在 open\_time 進行「種子質押」以避免此問題。
* **Token-2022 轉帳費用。** 在帶有 Token-2022 獎勵代幣的 v6 農場上，轉帳費用在發行時適用（金庫 → 用戶）。將其納入 APR 引用。
* **v5 上的小 `per_second`。** v5 的 `u64` 率意味著任何 `per_second < 1` 代幣單位/秒（對於小數點≥9 位的代幣，這通常是期望的率）無法表示 — 流率四捨五入到 0，農場發行無效。使用 v6。

## 接下來的方向

* [`products/farm-staking/instructions`](/zh-Hant/products/farm-staking/instructions) — 基礎指令參考。
* [`products/clmm/fees`](/zh-Hant/products/clmm/fees) — 與 CLMM 的原生獎勵流比較。
* [`user-flows/migrate-amm-v4-to-cpmm`](/zh-Hant/user-flows/migrate-amm-v4-to-cpmm) — 通常與建立新 CPMM 農場配對。

來源：

* [Raydium SDK v2](https://github.com/raydium-io/raydium-sdk-V2)
* Farm v6 IDL 包含在 [`raydium-io/raydium-sdk-V2`](https://github.com/raydium-io/raydium-sdk-V2) 的 `src/raydium/farm/` 中。
