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-io/raydium-sdk-v2@0.2.42-alpha в сети Solana mainnet-beta, проверено в 04.2026. SDK внутренне работает с версиями v3 / v5 / v6 в зависимости от владельца программы фермы; примеры ниже предполагают ферму v6. Смотрите reference/program-addresses для трёх ID программ.
Настройка
Примеры здесь отражают файлы в raydium-sdk-V2-demo/src/farm. Инициализация следует config.ts.template из репозитория примеров:
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
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
const amount = new BN(1_000_000_000); // 1 LP (при условии 9 знаков после запятой)
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 пользователя в той же транзакции.
Только сбор наград (harvest)
Источник: src/farm/harvest.ts
const { execute } = await raydium.farm.harvestAllRewards({
farmInfoList: [farm],
txVersion: TxVersion.V0,
});
await execute({ sendAndConfirm: true });
harvestAllRewards принимает список — для пользовательских интерфейсов с портфолио группируйте вызовы. Каждая ферма собирается отдельной инструкцией в одну транзакцию (с учётом ограничения в 1232 байта; для более 6 ферм разделите на несколько транзакций).
Для одной фермы на v6 можно также использовать явный путь Harvest:
const { execute } = await raydium.farm.withdraw({
farmInfo: farm,
amount: new BN(0), // 0 = путь только для сбора, даже на v6
txVersion: TxVersion.V0,
});
await execute({ sendAndConfirm: true });
На v3 и v5 идиома amount: 0 обязательна; SDK правильно её обрабатывает.
Анстейкинг
Источник: src/farm/unstake.ts
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 и editAmmFarm.ts
import { CurveCalculator } from "@raydium-io/raydium-sdk-v2"; // опционально, для планирования APR
const stakingMint = new PublicKey("<LP_MINT>"); // например, LP-mint CPMM
const rewardMint = new PublicKey("<REWARD_MINT>"); // например, токен вашего проекта
const now = Math.floor(Date.now() / 1000);
const openTime = now + 60 * 60; // начало через 1 час
const duration = 60 * 60 * 24 * 30; // 30 дней
const endTime = openTime + duration;
const totalBudget = new BN(1_000_000).mul(new BN(10).pow(new BN(9))); // 1M токенов награды (9 дец.)
const perSecond = totalBudget.div(new BN(duration)); // целое число, SDK преобразует в Q64.64
const { execute, extInfo } = await raydium.farm.create({
programId: /* ID программы v6, из 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, // кто платит начальный бюджет
mintProgramId: /* SPL Token или 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 перемещает его в хранилище вознаграждений атомарно.
- Вы можете инициировать до 5 потоков вознаграждений в одном вызове
create. Список аккаунтов растёт на (reward_mint, reward_vault, sender_ata, token_program) за каждый дополнительный поток; имейте в виду ограничение размера транзакции в 1232 байта. Для 4+ потоков создавайте с 1–2 и используйте AddReward в последующих транзакциях.
Пополнение существующего потока вознаграждений
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, // сохраняем ту же ставку
payer: owner.publicKey,
},
],
txVersion: TxVersion.V0,
});
await execute({ sendAndConfirm: true });
setRewards продлевает end_time и переводит разницу бюджета. Инструкция не может сократить поток, не может снизить per_second на активном потоке и не может изменить mint вознаграждения. Для замены mint дождитесь end_time и используйте AddReward на освободившемся слоте (если есть), или создайте новую ферму.
Перезапуск завершённого потока
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 (raydium_farm_v6), опубликованным вместе с исходниками SDK и фронтенда. Минимальный пример Deposit:
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>,
// Далее в remaining_accounts идут пары (reward_vault_i, user_reward_ata_i)
// для каждого активного потока вознаграждения на ферме.
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 сначала выплачивает накопленные награды. Но если ваш интерфейс показывает “собрать” отдельно от “вывести”, пользователь может подумать, что после Withdraw есть ещё что-то для сбора. Нет; всё накопленное к этому моменту было выплачено.
total_staked = 0 во время эмиссии. Эмиссия, накопленная когда ничего не было застейкано, теряется (формула обновления reward_per_share делит на 0 и программа пропускает обновление). Для программ с запланированным open_time проведите “начальный стейк” в момент open_time, чтобы избежать этого.
- Комиссии передачи Token-2022. На фермах v6 с mint вознаграждений Token-2022 комиссия передачи применяется при эмиссии (хранилище → пользователь). Учитывайте это в расчётах APR.
- Малый
per_second на v5. u64 ставка v5 означает, что любой per_second < 1 единица токена в секунду (на mint с ≥9 дец., это часто желаемая ставка) не может быть выражена — ставка потока округляется до 0 и ферма ничего не выплачивает. Используйте v6.
Дальнейшие шаги
Источники: