TypeScript SDK

What this page is for: the canonical reference for @rflowdapp/rflow — how to install it, configure a client, and invoke every flow with full TypeScript types.

Installation#

The SDK has three peer dependencies. Install everything together:

Terminal
$npm install @rflowdapp/rflow @coral-xyz/anchor @solana/web3.js @solana/spl-token
Peer dependency versions
@coral-xyz/anchor ^0.32.0 · @solana/web3.js ^1.95.0 · @solana/spl-token ^0.4.0

Quick start#

Read-only client

The fastest way to start: a read-only client. No wallet, no signing — just on-chain reads.

read-only.ts
1import { RFlowClient, formatAmount } from "@rflowdapp/rflow";
2import { Connection } from "@solana/web3.js";
3
4const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed");
5const client = RFlowClient.readOnly(connection);
6
7const config = await client.getConfig();
8console.log("Protocol fee:", (config?.feeBps ?? 0) / 100, "%");
9
10const deals = await client.yieldDeals.getAvailableDeals();
11deals.forEach((d) => {
12 console.log(`#${d.dealId} · ${formatAmount(d.sellingPrice, 6)} USDC`);
13});

Client with a wallet

For write operations, pass a wallet that satisfies Anchor's Wallet interface (publicKey, signTransaction, signAllTransactions):

with-wallet.ts
1import { RFlowClient } from "@rflowdapp/rflow";
2import { Connection } from "@solana/web3.js";
3import { Wallet } from "@coral-xyz/anchor";
4
5// Option A — server / script with a Keypair
6import { Keypair } from "@solana/web3.js";
7const wallet = new Wallet(Keypair.fromSecretKey(/* your key */ new Uint8Array()));
8
9// Option B — browser with wallet-adapter
10// const { publicKey, signTransaction, signAllTransactions } = useWallet();
11// const wallet = { publicKey, signTransaction, signAllTransactions };
12
13const client = new RFlowClient({
14 connection: new Connection("https://api.mainnet-beta.solana.com", "confirmed"),
15 wallet,
16});

Yield Deals#

The client.yieldDeals sub-client covers every flow for receipt-token deals (mSOL, jitoSOL, bSOL, kUSDC, cUSDC).

Reads

typescript
1// By deal id
2const deal = await client.yieldDeals.getDeal(42);
3
4// By PDA
5const dealByPda = await client.yieldDeals.getDealByPda(somePda);
6
7// All deals, optionally filtered
8import { DealStatus, SourceProtocol } from "@rflowdapp/rflow";
9
10const all = await client.yieldDeals.getAllDeals({
11 status: DealStatus.Active,
12 sourceProtocol: SourceProtocol.Marinade,
13});
14
15// Convenience queries
16const open = await client.yieldDeals.getAvailableDeals();
17const mySells = await client.yieldDeals.getDealsBySeller(myPubkey);
18const myBuys = await client.yieldDeals.getDealsByBuyer(myPubkey);

Create a yield deal

create-yield-deal.ts
1import {
2 SourceProtocol,
3 KNOWN_MINTS,
4 VALID_DURATIONS,
5} from "@rflowdapp/rflow";
6import { Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
7
8// Sell 10 mSOL of future appreciation for 90 days
9const ixs = await client.yieldDeals.createDeal({
10 receiptTokenMint: KNOWN_MINTS.MSOL,
11 receiptTokensAmount: 10_000_000_000, // 10 mSOL (9 decimals)
12 principalValueAtLock: 1_180_000_000, // ~$1,180 (6 decimals)
13 expectedYield: 17_000_000, // ~$17 expected
14 sellingPrice: 14_000_000, // List for $14
15 durationDays: 90, // see VALID_DURATIONS
16 sourceProtocol: SourceProtocol.Marinade,
17 exchangeRateAtLock: 1_180_000, // 1.18 scaled 1e6
18 // Mainnet only — Pyth price update PDA for the receipt mint
19 // priceUpdate: msolUsdPriceUpdatePubkey,
20});
21
22const tx = new Transaction().add(...ixs);
23const sig = await sendAndConfirmTransaction(connection, tx, [keypair]);
VALID_DURATIONS
30 | 60 | 90 | 180 | 365. The SDK throws InvalidDurationError for anything else.

Buy a yield deal

typescript
1import { getAssociatedTokenAddress } from "@solana/spl-token";
2
3const dealId = 42;
4const deal = await client.yieldDeals.getDeal(dealId);
5const config = await client.getConfig();
6
7const sellerPaymentAccount = await getAssociatedTokenAddress(
8 deal!.paymentMint,
9 deal!.seller
10);
11const treasuryAccount = await getAssociatedTokenAddress(
12 deal!.paymentMint,
13 config!.treasury
14);
15
16const ixs = await client.yieldDeals.buyDeal(
17 dealId,
18 sellerPaymentAccount,
19 treasuryAccount
20);
21
22const tx = new Transaction().add(...ixs);
23await sendAndConfirmTransaction(connection, tx, [keypair]);

Cancel · Settle · Buyback

typescript
1// Cancel — seller only, before fill
2const cancelIxs = await client.yieldDeals.cancelDeal(dealId);
3
4// Settle — permissionless after ends_at
5// currentTokenValue is the current USD value of the locked tokens.
6// Mainnet LSTs: pass a Pyth priceUpdate too; oracle takes precedence.
7const settleIxs = await client.yieldDeals.settleDeal(
8 dealId,
9 currentTokenValue,
10 /* priceUpdate? */ undefined
11);
12
13// Buyback — seller-only early exit with penalty
14const buybackIxs = await client.yieldDeals.buybackDeal(
15 dealId,
16 currentTokenValue,
17 /* priceUpdate? */ undefined
18);

Meteora LP Deals#

The client.meteoraDeals sub-client mirrors the yield deal API for Meteora DAMM v2 positions. Sellers lock a Position NFT; buyers claim pool fees during the active deal.

Create a Meteora LP deal

create-meteora-deal.ts
1const ixs = await client.meteoraDeals.createDeal({
2 // Position
3 positionNftMint: positionNftMintPubkey,
4 positionAccount: meteoraPositionPda,
5 pool: meteoraPoolPda,
6 tokenAMint: usdcMint,
7 tokenBMint: solMint,
8 // Fee snapshots at lock — read these from the position state
9 feeAAtLock: 0n,
10 feeBAtLock: 0n,
11 // Projections — your call, used for UI / discount math
12 expectedFeeA: 500_000_000n,
13 expectedFeeB: 1_000_000_000n,
14 expectedFeeValueUsdc: 650_000_000n,
15 sellingPrice: 500_000_000n,
16 durationDays: 90,
17});

Claim fees during the deal

Only the buyer can claim. The SDK builds the CPI into Meteora; you supply the pool-side accounts.

typescript
1import { PublicKey } from "@solana/web3.js";
2
3const METEORA_PROGRAM = new PublicKey(
4 "cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG"
5);
6
7const claimIxs = await client.meteoraDeals.claimFees({
8 dealId,
9 meteoraProgram: METEORA_PROGRAM,
10 meteoraPosition: positionPda,
11 meteoraPool: poolPda,
12 poolTokenAVault: tokenAVault,
13 poolTokenBVault: tokenBVault,
14});

Settle · Cancel · Split · Withdraw

typescript
1// Settle after expiry — permissionless. Returns the NFT to the seller.
2const settleIxs = await client.meteoraDeals.settleDeal(dealId);
3
4// Cancel before fill — seller only
5const cancelIxs = await client.meteoraDeals.cancelDeal(dealId);
6
7// Split a position before listing (so you sell only a portion of it)
8const splitIxs = await client.meteoraDeals.splitPosition({
9 sourceNftMint: sourceNftMint,
10 sourcePosition: sourcePositionPda,
11 targetNftMint: newPositionNftMint,
12 targetPosition: newPositionPda,
13 meteoraPool: poolPda,
14 meteoraProgram: METEORA_PROGRAM,
15 eventAuthority: meteoraEventAuthority,
16 unlockedLiquidityPercentage: 50,
17 permanentLockedLiquidityPercentage: 50,
18 feeAPercentage: 50,
19 feeBPercentage: 50,
20 reward0Percentage: 50,
21 reward1Percentage: 50,
22});
23
24// Withdraw liquidity after settlement — seller only
25const withdrawIxs = await client.meteoraDeals.withdrawLiquidity({
26 dealId,
27 meteoraProgram: METEORA_PROGRAM,
28 meteoraPosition: positionPda,
29 meteoraPool: poolPda,
30 poolTokenAVault: tokenAVault,
31 poolTokenBVault: tokenBVault,
32 poolAuthority: poolAuthorityPda,
33 eventAuthority: meteoraEventAuthority,
34 tokenAProgram: TOKEN_PROGRAM_ID,
35 tokenBProgram: TOKEN_PROGRAM_ID,
36 tokenAAmountThreshold: 0n,
37 tokenBAmountThreshold: 0n,
38});

PDA Helpers#

Every PDA the program uses is exposed as a pure helper. They work without a connection or wallet.

typescript
1import {
2 findProtocolConfigPDA,
3 findYieldDealPDA,
4 findVaultPDA,
5 findMeteoraLpDealPDA,
6 findMeteoraVaultPDA,
7 PROGRAM_ID,
8} from "@rflowdapp/rflow";
9
10const [configPda] = findProtocolConfigPDA(PROGRAM_ID);
11const [dealPda] = findYieldDealPDA(dealId, PROGRAM_ID);
12const [vaultPda] = findVaultPDA(dealPda, PROGRAM_ID);
13
14const [meteoraDealPda] = findMeteoraLpDealPDA(dealId, PROGRAM_ID);
15const [nftVaultPda] = findMeteoraVaultPDA(meteoraDealPda, PROGRAM_ID);
16
17// Or via the client (no need to pass PROGRAM_ID)
18const [pda] = client.yieldDeals.findDealPda(dealId);
19const [vault] = client.yieldDeals.findVaultPda(pda);

Error handling#

The SDK throws typed errors for input mistakes and provides helpers for parsing Anchor errors that come back from the chain.

typescript
1import {
2 RFlowError,
3 FetchError,
4 DealNotFoundError,
5 InvalidInputError,
6 InvalidDurationError,
7 ProtocolPausedError,
8 parseAnchorError,
9 isAccountNotFoundError,
10} from "@rflowdapp/rflow";
11
12// Input validation throws synchronously
13try {
14 await client.yieldDeals.createDeal({ /* bad input */ } as any);
15} catch (err) {
16 if (err instanceof InvalidDurationError) {
17 console.error("durationDays must be one of", err.validDurations);
18 } else if (err instanceof InvalidInputError) {
19 console.error("Bad input:", err.message);
20 }
21}
22
23// Read errors
24try {
25 const deal = await client.yieldDeals.getDeal(999);
26} catch (err) {
27 if (err instanceof DealNotFoundError) {
28 console.warn("No deal at id", err.dealId);
29 } else if (isAccountNotFoundError(err)) {
30 console.warn("Account does not exist on chain");
31 }
32}
33
34// On-chain errors come back wrapped — parse them
35try {
36 await sendAndConfirmTransaction(connection, tx, [keypair]);
37} catch (err) {
38 const parsed = parseAnchorError(err);
39 if (parsed) {
40 console.error("Program error", parsed.code, parsed.message);
41 }
42}

Types#

All public types are re-exported from the package root. Everything is strictly typed — there are no any escape hatches in the public surface.

typescript
1import type {
2 // Deal types
3 YieldDeal,
4 MeteoraLpDeal,
5 ProtocolConfig,
6 // Input types
7 CreateYieldDealInput,
8 CreateMeteoraLpDealInput,
9 ClaimMeteoraFeesInput,
10 WithdrawMeteoraLiquidityInput,
11 SplitMeteoraPositionInput,
12 DealFilters,
13 // Enums
14 DealStatus,
15 SourceProtocol,
16 DealDuration,
17} from "@rflowdapp/rflow";
18
19// Status & protocol enums match the program IDL
20enum DealStatus { Created, Active, Settled, Cancelled, BoughtBack }
21enum SourceProtocol {
22 Kamino, Solend, Save,
23 Marinade, Jito, Blaze, Sanctum,
24 RaydiumLp, MeteoraLp, OrcaLp,
25 FeeStream,
26}
27
28// Durations are a union — the compiler rejects anything else
29type DealDuration = 30 | 60 | 90 | 180 | 365;
Tips for production
Use a dedicated RPC (Helius, Triton, QuickNode) for connection. Batch your write transactions with createSignedVersionedTransaction from web3.js if you need priority fees, and keep one RFlowClient instance per wallet — it caches the Anchor Program.