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.
Apa yang dilakukan halaman ini. Membuat pool CLMM baru pada tingkat biaya pilihan Anda, lalu membuka posisi terkonsentrasi awal. Dua transaksi, satu skrip. Kode diambil dari demo resmi di raydium-sdk-V2-demo/src/clmm dan disesuaikan menjadi satu file yang dapat dijalankan Node.
Setup
Pastikan Anda telah membaca Prasyarat Quick start dan memiliki RPC_URL, KEYPAIR, serta dependensi yang dipasang.
Pembuatan pool CLMM memiliki biaya sekali jalan ditambah sewa tick array per tick untuk posisi awal. Anda juga memerlukan kedua seed mint di dompet Anda — membuka posisi ketika harga berada dalam jangkauan yang dipilih memerlukan likuiditas di kedua sisi.
Langkah 1 — config.ts
Simpan sebagai config.ts. Bentuknya sama dengan src/config.ts.template repo demo — disableFeatureCheck dipaksa ke true (disarankan untuk integrasi apa pun yang tidak trivial sehingga SDK tidak memblokir panggilan deteksi fitur startupnya):
// config.ts
import { Raydium, TxVersion, parseTokenAccountResp } from "@raydium-io/raydium-sdk-v2";
import { Connection, Keypair, clusterApiUrl } from "@solana/web3.js";
import { TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";
import bs58 from "bs58";
import fs from "node:fs";
export const owner: Keypair = Keypair.fromSecretKey(
// accept either a JSON-array keypair file (same shape Solana CLI writes) or a bs58 secret in env
process.env.KEYPAIR_BS58
? bs58.decode(process.env.KEYPAIR_BS58)
: new Uint8Array(JSON.parse(fs.readFileSync(process.env.KEYPAIR!, "utf8"))),
);
export const connection = new Connection(
process.env.RPC_URL ?? clusterApiUrl("mainnet-beta"),
"confirmed",
);
export const txVersion = TxVersion.V0;
const cluster = "mainnet" as "mainnet" | "devnet";
let raydium: Raydium | undefined;
export const initSdk = async (params?: { loadToken?: boolean }) => {
if (raydium) return raydium;
raydium = await Raydium.load({
owner,
connection,
cluster,
disableFeatureCheck: true,
disableLoadToken: !params?.loadToken,
blockhashCommitment: "finalized",
});
return raydium;
};
export const fetchTokenAccountData = async () => {
const solAccountResp = await connection.getAccountInfo(owner.publicKey);
const tokenAccountResp = await connection.getTokenAccountsByOwner(owner.publicKey, {
programId: TOKEN_PROGRAM_ID,
});
const token2022Req = await connection.getTokenAccountsByOwner(owner.publicKey, {
programId: TOKEN_2022_PROGRAM_ID,
});
return parseTokenAccountResp({
owner: owner.publicKey,
solAccountResp,
tokenAccountResp: {
context: tokenAccountResp.context,
value: [...tokenAccountResp.value, ...token2022Req.value],
},
});
};
Langkah 2 — createPool.ts
Simpan di samping config.ts. Sumber: src/clmm/createPool.ts.
// createPool.ts
import { CLMM_PROGRAM_ID, DEVNET_PROGRAM_ID } from "@raydium-io/raydium-sdk-v2";
import { PublicKey } from "@solana/web3.js";
import Decimal from "decimal.js";
import { initSdk, txVersion } from "./config";
export const createPool = async () => {
const raydium = await initSdk({ loadToken: true });
// RAY
const mint1 = await raydium.token.getTokenInfo("4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R");
// USDT
const mint2 = await raydium.token.getTokenInfo("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB");
// Fee tiers come from the live API. On devnet the published `id` field is wrong;
// re-derive the PDA before passing it to the SDK.
const clmmConfigs = await raydium.api.getClmmConfigs();
const { execute } = await raydium.clmm.createPool({
programId: CLMM_PROGRAM_ID,
// programId: DEVNET_PROGRAM_ID.CLMM_PROGRAM_ID,
mint1,
mint2,
ammConfig: {
...clmmConfigs[0],
id: new PublicKey(clmmConfigs[0].id),
fundOwner: "",
description: "",
},
initialPrice: new Decimal(1),
txVersion,
// optional: set up priority fee here
// computeBudgetConfig: { units: 600000, microLamports: 46591500 },
});
const { txId } = await execute({ sendAndConfirm: true });
console.log("clmm pool created:", { txId: `https://explorer.solana.com/tx/${txId}` });
process.exit();
};
createPool();
Langkah 3 — createPosition.ts
Sumber: src/clmm/createPosition.ts.
// createPosition.ts
import {
ApiV3PoolInfoConcentratedItem,
TickUtils,
PoolUtils,
ClmmKeys,
} from "@raydium-io/raydium-sdk-v2";
import BN from "bn.js";
import Decimal from "decimal.js";
import { initSdk, txVersion } from "./config";
import { isValidClmm } from "./utils";
export const createPosition = async () => {
const raydium = await initSdk();
let poolInfo: ApiV3PoolInfoConcentratedItem;
// RAY-USDC pool
const poolId = "61R1ndXxvsWXXkWSyNkCxnzwd3zUNB8Q2ibmkiLPC8ht";
let poolKeys: ClmmKeys | undefined;
if (raydium.cluster === "mainnet") {
const data = await raydium.api.fetchPoolById({ ids: poolId });
poolInfo = data[0] as ApiV3PoolInfoConcentratedItem;
if (!isValidClmm(poolInfo.programId)) throw new Error("target pool is not CLMM pool");
} else {
const data = await raydium.clmm.getPoolInfoFromRpc(poolId);
poolInfo = data.poolInfo;
poolKeys = data.poolKeys;
}
// Optional: pull on-chain real-time price to avoid slippage errors from a stale API quote.
// const rpcData = await raydium.clmm.getRpcClmmPoolInfo({ poolId: poolInfo.id });
// poolInfo.price = rpcData.currentPrice;
const inputAmount = 0.000001; // RAY amount
const [startPrice, endPrice] = [0.000001, 100000];
const { tick: lowerTick } = TickUtils.getPriceAndTick({
poolInfo,
price: new Decimal(startPrice),
baseIn: true,
});
const { tick: upperTick } = TickUtils.getPriceAndTick({
poolInfo,
price: new Decimal(endPrice),
baseIn: true,
});
const epochInfo = await raydium.fetchEpochInfo();
const res = await PoolUtils.getLiquidityAmountOutFromAmountIn({
poolInfo,
slippage: 0,
inputA: true,
tickUpper: Math.max(lowerTick, upperTick),
tickLower: Math.min(lowerTick, upperTick),
amount: new BN(new Decimal(inputAmount || "0").mul(10 ** poolInfo.mintA.decimals).toFixed(0)),
add: true,
amountHasFee: true,
epochInfo,
});
const { execute, extInfo } = await raydium.clmm.openPositionFromBase({
poolInfo,
poolKeys,
tickUpper: Math.max(lowerTick, upperTick),
tickLower: Math.min(lowerTick, upperTick),
base: "MintA",
ownerInfo: { useSOLBalance: true },
baseAmount: new BN(new Decimal(inputAmount || "0").mul(10 ** poolInfo.mintA.decimals).toFixed(0)),
otherAmountMax: res.amountSlippageB.amount,
txVersion,
computeBudgetConfig: { units: 600000, microLamports: 100000 },
});
const { txId } = await execute({ sendAndConfirm: true });
console.log("clmm position opened:", { txId, nft: extInfo.nftMint.toBase58() });
process.exit();
};
createPosition();
Langkah 4 — utils.ts
Sumber: src/clmm/utils.ts.
// utils.ts
import { CLMM_PROGRAM_ID, DEVNET_PROGRAM_ID } from "@raydium-io/raydium-sdk-v2";
const VALID_PROGRAM_IDS = new Set<string>([
CLMM_PROGRAM_ID.toBase58(),
DEVNET_PROGRAM_ID.CLMM_PROGRAM_ID.toBase58(),
]);
export const isValidClmm = (programId: string) => VALID_PROGRAM_IDS.has(programId);
Jalankan
# create the pool first
RPC_URL="https://api.mainnet-beta.solana.com" \
KEYPAIR="$HOME/.config/solana/id.json" \
npx tsx createPool.ts
# then open an initial position against the new pool id
# (edit poolId at the top of createPosition.ts to point at your new pool)
RPC_URL="https://api.mainnet-beta.solana.com" \
KEYPAIR="$HOME/.config/solana/id.json" \
npx tsx createPosition.ts
Apa yang baru saja terjadi
Transaksi 1 — raydium.clmm.createPool menginisialisasi:
- state pool di PDA kanonik untuk
(mint1, mint2, ammConfig),
token_0_vault dan token_1_vault (diurutkan menurut urutan byte mint),
- ring buffer
observation,
- inline tick-array bitmap,
dan mengatur sqrt_price_x64 awal dari initialPrice Anda.
Transaksi 2 — raydium.clmm.openPositionFromBase membuka posisi terkonsentrasi:
- mencetak position NFT ke dompet Anda (NFT adalah posisi; mentransfernya mentransfer posisi),
- mengalokasikan tick array pada batas bawah dan atas (sewa sekali jalan jika posisi pertama di rentang tersebut; tick array tidak pernah ditutup oleh program, jadi posisi berikutnya di array yang sama tidak membayar sewa tambahan),
- mendeposit
inputAmount dari mint1 dan jumlah pasangan yang cocok dari mint2 (dihitung oleh PoolUtils.getLiquidityAmountOutFromAmountIn),
- mengkreditkan posisi dengan likuiditas proporsional dengan lebar rentang.
Semakin sempit rentangnya, semakin tinggi efisiensi modal per dolar TVL — dan semakin menyakitkan kerugian impermanent ketika harga bergeser keluar dari rentang. Rentang yang digunakan di atas ([0.000001, 100000]) secara efektif adalah full-range; kurangi untuk memusatkan biaya di dekat spot saat ini.
Memilih tingkat biaya
clmmConfigs[0] adalah tingkat biaya terendah. Set lengkapnya dipublikasikan di GET https://api-v3.raydium.io/main/clmm-config:
| Indeks | tradeFeeRate | Tick spacing | Digunakan ketika |
|---|
| 0 | 100 (1bp) | 1 | Stabil / stabil, kerugian impermanent sangat rendah diharapkan |
| 1 | 500 (5bp) | 10 | Aset yang sangat berkorelasi (misalnya liquid-staked vs underlying) |
| 2 | 2_500 (25bp) | 60 | Pasangan token standar, blue-chip + stabil |
| 3 | 10_000 (1.00%) | 120 | Pasangan yang volatile atau thin di mana risiko IL tinggi |
Lihat user-flows/choosing-a-pool-type untuk matriks keputusan lengkap.
Kesalahan umum
Pool already exists for this config — Pool CLMM sudah ada untuk triple (mint1, mint2, ammConfig) ini. Cari ID pool yang ada dan lewati Langkah 2.
Insufficient funds for amount B — Dompet Anda memiliki jumlah mintA yang diminta tetapi bukan mintB yang cocok. Membuka posisi ketika harga berada dalam rentang memerlukan likuiditas di kedua sisi.
Tick out of range — lowerPrice atau upperPrice Anda jatuh di luar jangkauan harga yang dapat direpresentasikan. Gunakan rentang yang lebih masuk akal relatif terhadap harga saat ini.
- Harga basi — Penawaran dari API dapat berusia 5–60 detik. Jika
executePosition gagal pada slippage, buka komentar blok getRpcClmmPoolInfo di createPosition.ts untuk mengambil kembali harga aktif tepat sebelum menandatangani.
Peringatan
- Position NFT adalah satu-satunya handle Anda. Kehilangan NFT atau mentransfernya, kehilangan akses ke posisi. Perlakukan seperti kunci.
- Posisi out-of-range tidak memperoleh biaya. Jika harga bergerak di luar
[lowerPrice, upperPrice], posisi Anda diparkir sepenuhnya dalam satu aset dan tidak memperoleh apa pun sampai Anda menyeimbangkan kembali.
- Sewa tick array adalah satu arah. Posisi pertama untuk menyentuh tick array yang tidak pernah diinisialisasi membayar sewa-nya; program tidak menampilkan jalan untuk menutup tick array, jadi sewa itu permanen. Posisi berikutnya di array yang sama gratis.
Selanjutnya
Sumber: