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.
Was dies bewirkt. Erstellt einen neuen CLMM-Pool in der Gebührenebene Ihrer Wahl und öffnet dann eine anfängliche konzentrierte Position. Zwei Transaktionen, ein Skript. Der Code stammt aus den offiziellen Demos in raydium-sdk-V2-demo/src/clmm und wurde auf eine einzelne Node-ausführbare Datei angepasst.
Setup
Stellen Sie sicher, dass Sie die Quick-start-Voraussetzungen gelesen haben und RPC_URL, KEYPAIR sowie die Abhängigkeiten installiert sind.
Die CLMM-Pool-Erstellung hat eine einmalige Gebühr plus Tick-Array-Miete für die anfängliche Position. Sie benötigen auch beide Seed-Mints in Ihrer Brieftasche — das Öffnen einer Position, wenn der Preis innerhalb des gewählten Bereichs liegt, erfordert Liquidität auf beiden Seiten.
Schritt 1 — config.ts
Speichern Sie als config.ts. Dies hat die gleiche Form wie das src/config.ts.template des Demo-Repos — disableFeatureCheck wird auf true gesetzt (empfohlen für jede nicht-triviale Integration, damit das SDK sich beim Start nicht auf dem Feature-Detect-Aufruf blockiert):
// 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],
},
});
};
Schritt 2 — createPool.ts
Speichern Sie neben config.ts. Quelle: 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();
Schritt 3 — createPosition.ts
Quelle: 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();
Schritt 4 — utils.ts
Quelle: 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);
Ausführung
# 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
Was ist gerade passiert
Transaktion 1 — raydium.clmm.createPool hat initialisiert:
- den Pool-Status auf der kanonischen PDA für
(mint1, mint2, ammConfig),
token_0_vault und token_1_vault (sortiert nach Mint-Byteordnung),
- den
observation-Ringpuffer,
- die Inline-Tick-Array-Bitmap,
und hat die anfängliche sqrt_price_x64 von Ihrer initialPrice gesetzt.
Transaktion 2 — raydium.clmm.openPositionFromBase öffnete eine konzentrierte Position:
- mintete ein Positions-NFT in Ihre Brieftasche (das NFT ist die Position; das Übertragen überträgt die Position),
- ordnete Tick-Arrays an den unteren und oberen Grenzen zu (einmalige Miete bei der ersten Position in diesen Bereichen; Tick-Arrays werden vom Programm nie geschlossen, daher zahlen nachfolgende Positionen in denselben Arrays keine zusätzliche Miete),
- zahlte
inputAmount von mint1 und den entsprechenden Pairbetrag von mint2 ein (berechnet durch PoolUtils.getLiquidityAmountOutFromAmountIn),
- gutschrieb die Position mit Liquidität proportional zur Bereichsbreite.
Je enger der Bereich, desto höher die Kapitaleffizienz pro Dollar TVL — und desto schmerzhafter der impermanente Verlust, wenn der Preis außerhalb des Bereichs abweicht. Der oben verwendete Bereich ([0.000001, 100000]) ist praktisch vollständig; enge ihn, um Gebühren nahe dem aktuellen Kurs zu konzentrieren.
Auswahl einer Gebührenebene
clmmConfigs[0] ist die niedrigste Gebührenebene. Der gesamte Satz wird unter GET https://api-v3.raydium.io/main/clmm-config veröffentlicht:
| Index | tradeFeeRate | Tick-Abstand | Verwenden wenn |
|---|
| 0 | 100 (1bp) | 1 | Stabil/stabil, sehr geringer impermanenter Verlust erwartet |
| 1 | 500 (5bp) | 10 | Hochkorrelierte Assets (z. B. liquid-gestaked gegenüber zugrunde liegend) |
| 2 | 2_500 (25bp) | 60 | Standard-Tokenpaar, Blue-Chip + Stabil |
| 3 | 10_000 (1,00%) | 120 | Volatiles oder dünnes Paar, bei dem IL-Risiko hoch ist |
Siehe user-flows/choosing-a-pool-type für eine vollständige Entscheidungsmatrix.
Häufige Fehler
Pool already exists for this config — Ein CLMM-Pool existiert bereits für dieses (mint1, mint2, ammConfig)-Tripel. Schlagen Sie die vorhandene Pool-ID nach und überspringen Sie Schritt 2.
Insufficient funds for amount B — Ihre Brieftasche hat den angeforderten Betrag von mintA, aber nicht den entsprechenden mintB. Das Öffnen einer Position, wenn der Preis innerhalb des Bereichs liegt, erfordert Liquidität auf beiden Seiten.
Tick out of range — Ihr lowerPrice oder upperPrice liegt außerhalb des darstellbaren Preisbereichs. Verwenden Sie einen realistischeren Bereich relativ zum aktuellen Preis.
- Veralteter Preis — Ein Angebot von der API kann 5–60 Sekunden veraltet sein. Wenn
executePosition bei Slippage fehlschlägt, entfernen Sie die Kommentierung des getRpcClmmPoolInfo-Blocks in createPosition.ts, um den Live-Preis unmittelbar vor dem Signieren erneut abzurufen.
Vorbehalte
- Positions-NFT ist Ihr einziger Handle. Verlieren Sie das NFT oder übertragen Sie es, verlieren Sie den Zugriff auf die Position. Behandeln Sie es wie einen Schlüssel.
- Außerhalb des Bereichs liegende Positionen verdienen keine Gebühren. Wenn der Preis außerhalb von
[lowerPrice, upperPrice] liegt, wird Ihre Position vollständig in einem Asset geparkt und verdient nichts, bis Sie sie neu ausbalancieren.
- Tick-Array-Miete ist einseitig. Die erste Position, die ein nie initialisiertes Tick-Array berührt, zahlt seine Miete; das Programm stellt keinen Weg zur Verfügung, um Tick-Arrays zu schließen, daher ist diese Miete dauerhaft. Nachfolgende Positionen im gleichen Array sind kostenlos.
Nächste Schritte
Quellen: