> ## 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 포지션, 풀 점유, 팜 스테이킹, 대기 중인 보상을 지갑 UI에 표시 — 감지 패턴, 트랜잭션 미리보기 시뮬레이션, Token-2022 표시 사항.

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

  [영문판 보기 →](/integration-guides/wallet-integration)
</Info>

<Info>
  Raydium을 통합하는 지갑은 사용자마다 일반적으로 네 가지 질문에 답해야 합니다. 이 사용자가 LP로 보유 중인 풀은 어떤 것인가? 어떤 포지션(CLMM NFT)을 보유하고 있는가? 어느 팜에 스테이킹되어 있는가? 전체 가치는 얼마인가? 이 페이지에서는 각각을 설명합니다.
</Info>

## Raydium 포지션 감지

### Classic LP 토큰 (CPMM, AMM v4)

이들은 다른 SPL Token처럼 보입니다. 사용자의 ATA가 잔액을 보유합니다. 지갑은 기본적으로 이를 다른 토큰으로 표시합니다. Raydium LP 포지션으로 표시하려면:

1. 사용자의 토큰 계정을 나열합니다: `connection.getParsedTokenAccountsByOwner(user, { programId: TOKEN_PROGRAM_ID })`.
2. 각 민트에 대해 Raydium의 민트 목록을 확인합니다: `GET https://api-v3.raydium.io/pools/info/lps?lps=<LP_MINT>,...` (호출당 최대 약 50개 LP 민트까지 일괄 처리).
3. 민트가 일치하는 경우 API가 풀 참조를 반환합니다. 이를 사용하여 포지션의 토큰 단위 가치를 계산합니다:

```
token_a_owned = user_lp_balance * poolReserves.A / lpMint.supply
token_b_owned = user_lp_balance * poolReserves.B / lpMint.supply
usd_value     = token_a_owned * priceA_usd + token_b_owned * priceB_usd
```

LP 잔액과 래핑 해제된 금액을 모두 표시합니다. 사용자는 LP 단위가 아닌 기저 토큰으로 생각합니다.

### CLMM 포지션 NFT

CLMM 포지션은 NFT입니다. 각 포지션의 `PersonalPositionState` PDA는 NFT 민트에서 파생됩니다. 감지하려면:

1. 사용자의 NFT를 나열합니다. 레거시 Metaplex NFT의 경우: 토큰 계정을 공급량 1 및 소수 자릿수 0인 계정으로 필터링합니다.
2. 각 NFT 민트에 대해 PersonalPositionState PDA를 파생시키려고 합니다:

```ts theme={null}
import { CLMM_PROGRAM_ID } from "@raydium-io/raydium-sdk-v2";

const [positionPda] = PublicKey.findProgramAddressSync(
  [Buffer.from("position"), nftMint.toBuffer()],
  CLMM_PROGRAM_ID,
);

const accountInfo = await connection.getAccountInfo(positionPda);
if (!accountInfo || !accountInfo.owner.equals(CLMM_PROGRAM_ID)) return null;
// Raydium CLMM 포지션입니다 — 디코딩합니다.
```

3. `raydium.clmm.getPositionInfo({ positionPda })`를 통해 디코딩하여 다음을 얻습니다:
   * `poolId` → 풀을 가져와서 민트 해석
   * `tickLower`, `tickUpper` → 범위 표시
   * `liquidity`, `tokensOwedA/B` → 포지션 가치 + 대기 중인 수수료 계산
   * `rewardInfos` → 스트림당 대기 중인 보상

4. Token-2022 하에서 발급된 포지션 NFT의 경우 (`OpenPositionWithToken22Nft`), NFT 민트의 프로그램은 SPL Token이 아닌 Token-2022입니다. 스캔할 때 둘 다를 나열합니다.

### 팜 스테이크

Farm v3 / v5 / v6 각각에는 사용자별 원장 PDA가 있습니다. 파생:

```ts theme={null}
// 사용자의 각 잠재적 팜 상호작용에 대해 세 가지 팜 버전을 모두 시도합니다.
// 가장 저렴한 접근: 먼저 API에 문의합니다. API는 모든 사용자 포지션을 색인했습니다.

const r = await fetch(
  `https://api-v3.raydium.io/positions/staking?wallet=${user.toBase58()}`
).then(r => r.json());

for (const s of r.data.stakings) {
  // s.farmId, s.stakedAmount, s.pendingRewards[], s.poolApr, ...
}
```

온체인 감지를 선호하는 지갑의 경우: 선별된 "가능성 있는" 팜 ID 목록으로 사용자를 해시하여 가능한 `UserLedger` PDA를 반복합니다. 모든 팜 ID를 철저히 나열하는 것은 비현실적입니다(수천 개가 존재). API를 사용하세요.

## 포지션 가치 계산

### CPMM / AMM v4 LP

```ts theme={null}
const poolInfo = await raydium.<type>.getPoolInfoFromRpc({ poolId });
const myShare  = userLpBalance / poolInfo.lpMint.supply;
const tokensA  = BigInt(poolInfo.mintAmountA) * BigInt(userLpBalance)
                / BigInt(poolInfo.lpMint.supply);
const tokensB  = BigInt(poolInfo.mintAmountB) * BigInt(userLpBalance)
                / BigInt(poolInfo.lpMint.supply);
```

그런 다음 각각에 민트의 USD 가격을 곱합니다 (`raydium.token` 또는 가격 오라클에서).

### CLMM 포지션

```ts theme={null}
const position = await raydium.clmm.getPositionInfo({ positionPda });
const pool     = await raydium.clmm.getPoolInfoFromRpc({ poolId: position.poolId });

const { amountA, amountB } = PoolUtils.getAmountsFromLiquidity({
  sqrtPriceX64:  pool.sqrtPriceX64,
  tickLower:     position.tickLower,
  tickUpper:     position.tickUpper,
  liquidity:     position.liquidity,
  slippage:      0,  // 표시용, 슬리피지 없음
});

// 대기 중인 수수료 — "수집되지 않음"으로 별도로 표시합니다
const fees = {
  A: position.tokenFeesOwedA,
  B: position.tokenFeesOwedB,
};

// 대기 중인 보상 — CLMM 풀은 0–3개의 보상 스트림을 가집니다.
const rewards = position.rewardInfos.map(r => ({
  mint:          r.rewardMint,
  rewardAmount:  r.rewardAmountOwed,
}));
```

다음과 같이 렌더링합니다:

* 유동성 가치(현재 가격)
* 수집되지 않은 수수료
* 스트림당 대기 중인 보상
* 범위: `[tickLower_price, tickUpper_price]` 현재 가격이 범위 내인지 보여주는 시각적 바

### 팜 스테이크

```ts theme={null}
// API 응답에는 이미 대기 중인 보상이 포함되어 있습니다. 직접 사용하세요.
const apr       = stakingFarm.apr;               // %, 연간화
const staked    = stakingFarm.stakedAmount;      // 스테이킹 민트의 최소 단위
const rewards   = stakingFarm.pendingRewards;    // array of { mint, amount }
```

온체인 계산의 경우 팜의 회계를 반영합니다:

```
pending_reward_i = user.deposited
                 * farm.reward_per_share_x64[i] / 2^64
                 - user.reward_debts[i]
```

계산하기 전에 느슨한 업데이트 공식으로 `reward_per_share_x64`를 새로고침합니다(경과 시간 × 배출 속도 ÷ 총 스테이킹).

## 미리보기를 위한 트랜잭션 시뮬레이션

사용자가 서명하기 전에 지갑은 일반적으로 잔액 변경을 미리봅니다. `simulateTransaction`을 사용하세요:

```ts theme={null}
const sim = await connection.simulateTransaction(tx, {
  sigVerify: false,
  commitment: "confirmed",
  accounts: {
    encoding: "base64",
    addresses: [userTokenAtaA, userTokenAtaB, userLpAta].map(a => a.toBase58()),
  },
});

// 각 반환된 계정의 잔액을 디코딩하고 사전 트랜잭션 잔액과 비교합니다.
for (const [i, acctData] of sim.value.accounts!.entries()) {
  const newBalance = decodeTokenAccount(acctData!.data[0]).amount;
  // 사전 트랜잭션 잔액과 비교하고, Δ를 표시합니다
}
```

`accounts` 매개변수는 유효성 검사기에게 나열된 주소에 대한 시뮬레이션 후 계정 상태를 반환하도록 요청합니다. 지시문 형태만으로 잔액 변경을 예측하려는 것보다 훨씬 정확합니다.

### 시뮬레이션 함정

* **CLMM 스왑은 유효한 틱 배열이 필요합니다.** 사용자의 입력 크기가 초기화되지 않은 틱 배열로 이동하면 시뮬레이션이 실행과 마찬가지로 되돌립니다. UI에서 이를 명확하게 표시합니다.
* **우선 수수료.** 시뮬레이션은 적용된 컴퓨팅 예산 지시문 없이 실행됩니다. 기본값 200k CU를 초과하는 대규모 트랜잭션의 경우 시뮬레이션은 실패하지만 명시적 CU 한계로의 실제 실행은 성공합니다. 시뮬레이션된 트랜잭션에도 항상 CU 한계를 설정합니다.
* **새로운 블록해시.** 시뮬레이션은 현재 블록해시를 사용합니다. 서명이 60초 이상 걸리면 트랜잭션이 유효하지 않게 됩니다. 사용자가 주저하면 다시 시뮬레이션합니다.

## Token-2022 표시

Token-2022 프로그램 하의 토큰은 지갑의 토큰 목록에서 그렇게 표시되어야 합니다. 위험 표면이 다르기 때문입니다:

* **이전 수수료 민트**: 현재 `transferFeeBasisPoints`를 잔액 옆에 "이전 수수료: X%"로 표시합니다. 수신 시 경고합니다. 사용자는 발신자가 보낸 금액보다 적게 받을 것임을 깨닫지 못할 수 있습니다.
* **이전 훅 민트**: 훅 프로그램 ID를 표시합니다. 악의적인 훅은 아웃바운드 전송을 차단할 수 있습니다. 사용자는 훅이 예상한 것인지 확인해야 합니다.
* **이전 불가능 민트**: "이전 불가능"을 표시하고 스왑/전송을 비활성화합니다. 이들은 일반적으로 영혼 바인딩 토큰 또는 자격 증명입니다.
* **이자 베어링 민트**: `TokenAccount.amount`에서 파생된 UI 잔액은 누적된 이자를 반영하지 **않습니다**. 표시된 값에 `@solana/spl-token`의 `amountToUiAmount`(스케일링 계수를 적용함)를 사용합니다.

## 팜 APR 표시

사용자에게 표시되는 APR은 모든 라이브 보상 스트림을 결합하고, USD로 변환하고, 연간화해야 합니다:

```ts theme={null}
let apr = 0;
for (const r of farm.rewardInfos) {
  if (r.rewardState !== 1) continue;   // 실행되지 않음 건너뛰기
  const annualRewardTokens = Number(r.emissionPerSecond) * 86400 * 365 / 1e<decimals>;
  const annualRewardValue  = annualRewardTokens * priceUsd(r.rewardMint);
  const tvlValue           = Number(farm.totalStaked) * priceUsd(farm.stakingMint) / 1e<decimals>;
  apr += annualRewardValue / tvlValue;
}
```

`APR: X.Y%`로 표시합니다. 스테이킹 민트가 LP 토큰인 경우 기저 LP의 기본 수수료 APR도 계산하고 합계를 "총 APR" 또는 "APR + 수수료"로 표시합니다.

## 포인터

* [`products/clmm/ticks-and-positions`](/ko/products/clmm/ticks-and-positions) — 포지션 가치 파생.
* [`products/farm-staking/accounts`](/ko/products/farm-staking/accounts) — 팜 상태 필드.
* [`algorithms/token-2022-transfer-fees`](/ko/algorithms/token-2022-transfer-fees) — 이전 수수료 토큰의 표시 의미론.

출처:

* Raydium SDK v2 — 포지션/팜 도우미.
* `api-v3.raydium.io`의 사용자 포지션 엔드포인트.
