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

# Farm / Staking 코드 데모

> Raydium의 farm v6 프로그램을 대상으로 팜 생성, LP 스테이킹, 보상 수확, 보상 예산 충전을 위한 TypeScript 예제

<Info>
  **이 페이지는 AI 자동 번역입니다. 모든 내용은 영문판을 기준으로 합니다.**

  [영문판 보기 →](/products/farm-staking/code-demos)
</Info>

<Info>
  **버전 정보.** 모든 데모는 Solana mainnet-beta에 대해 `@raydium-io/raydium-sdk-v2@0.2.42-alpha`를 대상으로 하며 2026년 4월에 검증되었습니다. SDK는 팜의 프로그램 오너를 기반으로 내부적으로 v3 / v5 / v6을 디스패치합니다. 아래 예제는 v6 팜을 가정합니다. 세 프로그램 ID는 [`reference/program-addresses`](/ko/reference/program-addresses)를 참조하세요.
</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 팜 프로그램은 프론트엔드 및 SDK 소스와 함께 게시된 Anchor 크레이트(`raydium_farm_v6`)와 함께 제공됩니다. 최소 `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`](/ko/products/farm-staking/instructions) — 기본 명령 참조.
* [`products/clmm/fees`](/ko/products/clmm/fees) — CLMM의 기본 보상 스트림과 비교.
* [`user-flows/migrate-amm-v4-to-cpmm`](/ko/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/`에 번들로 제공됩니다.
