Quick Start

What this page is for: get a TypeScript app talking to rFlow on mainnet — read the protocol config, list a yield deal, and fill one — in under five minutes.

What you’ll need
A funded Solana wallet (mainnet SOL for fees), Node 18+, and a couple of mainnet RPC calls to spare. The SDK works against any RPC; a dedicated provider (Helius, Triton, QuickNode) is recommended for production.

Prerequisites#

  • Node.js 18+ (or Bun 1.0+)
  • A Solana wallet — for write operations you need a Keypair or wallet-adapter wallet
  • Some USDC for buying deals, or a receipt token (mSOL, jitoSOL, bSOL, kUSDC, cUSDC) to list one
  • Familiarity with TypeScript and Anchor is helpful but not required — the SDK hides the IDL

1. Install the SDK#

The SDK ships as @rflowdapp/rflow with three peer dependencies. Install everything in one go:

Terminal
$npm install @rflowdapp/rflow @coral-xyz/anchor @solana/web3.js @solana/spl-token

2. Read the protocol#

You can start with a read-only client — no wallet required. It exposes the on-chain config and every deal.

read-config.ts
1import { RFlowClient } 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();
8if (!config) throw new Error("Protocol not initialized");
9
10console.log({
11 feeBps: config.feeBps, // 200 = 2% protocol fee
12 basePenaltyBps: config.basePenaltyBps, // 300 = 3% early-exit penalty
13 minDurationDays: config.minDurationDays,
14 maxDurationDays: config.maxDurationDays,
15 isPaused: config.isPaused,
16 totalDeals: config.dealCounter.toString(),
17});

If isPaused is true, the authority has frozen new deal creation — existing deals can still settle. Always check before submitting writes.

3. Browse open deals#

Available deals are deals waiting for a buyer (status = Created). The SDK filters for you:

list-deals.ts
1import { RFlowClient, formatAmount, SourceProtocol } from "@rflowdapp/rflow";
2import { Connection } from "@solana/web3.js";
3
4const client = RFlowClient.readOnly(
5 new Connection("https://api.mainnet-beta.solana.com")
6);
7
8// All deals waiting for a buyer (status === Created)
9const available = await client.yieldDeals.getAvailableDeals();
10
11for (const deal of available) {
12 console.log({
13 dealId: deal.dealId,
14 seller: deal.seller.toBase58(),
15 sellingPrice: formatAmount(deal.sellingPrice, 6) + " USDC",
16 expectedYield: formatAmount(deal.expectedYield, 6) + " USDC",
17 durationDays: deal.durationDays,
18 protocol: SourceProtocol[deal.sourceProtocol as number],
19 });
20}
21
22// Meteora LP deals are exposed under a separate sub-client
23const meteora = await client.meteoraDeals.getAvailableDeals();
24console.log(`Meteora LP deals open: ${meteora.length}`);

4. List your first deal#

To list a deal you need a signing wallet. The example below uses a generated Keypair for clarity — in a real app use @solana/wallet-adapter or your own key management.

One-time whitelist check
The receipt mint must already be in config.allowedMints. If you want a new mint listed, see the integration guide.
create-deal.ts
1import {
2 RFlowClient,
3 SourceProtocol,
4 KNOWN_MINTS,
5} from "@rflowdapp/rflow";
6import { Wallet } from "@coral-xyz/anchor";
7import {
8 Connection,
9 Keypair,
10 Transaction,
11 sendAndConfirmTransaction,
12} from "@solana/web3.js";
13
14const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed");
15const keypair = Keypair.fromSecretKey(/* your secret key */ new Uint8Array());
16const wallet = new Wallet(keypair);
17
18const client = new RFlowClient({ connection, wallet });
19
20// Sell 10 mSOL of future appreciation for 90 days
21const ixs = await client.yieldDeals.createDeal({
22 receiptTokenMint: KNOWN_MINTS.MSOL, // mSoLzYC...m7So
23 receiptTokensAmount: 10_000_000_000, // 10 mSOL (9 decimals)
24 principalValueAtLock: 1_180_000_000, // ~$1,180 USDC value at lock
25 expectedYield: 17_000_000, // ~$17 expected yield (90d @ ~6%)
26 sellingPrice: 14_000_000, // List for $14 — buyer’s discount
27 durationDays: 90, // 30 / 60 / 90 / 180 / 365
28 sourceProtocol: SourceProtocol.Marinade,
29 exchangeRateAtLock: 1_180_000, // 1.18 mSOL/SOL scaled 1e6
30 // On mainnet for LSTs, pass the Pyth price update account:
31 // priceUpdate: msolUsdPriceUpdatePubkey,
32});
33
34const tx = new Transaction().add(...ixs);
35const sig = await sendAndConfirmTransaction(connection, tx, [keypair]);
36console.log("Deal listed:", sig);
37
38const config = await client.getConfig();
39const dealId = (config!.dealCounter as unknown as number) - 1;
40const created = await client.yieldDeals.getDeal(dealId);
41console.log("Deal PDA:", created?.pda.toBase58());
Where exchangeRateAtLock comes from
For LSTs it is principal_value_at_lock / receipt_tokens_amount scaled by 1e6. The program snapshots this so settlement can detect oracle manipulation. The SDK validates the math; it does not fetch the rate for you.

5. Fill a deal as a buyer#

Filling a deal is a single instruction. The SDK fetches the seller and payment mint from the on-chain account for you — you only need the seller's USDC ATA and the protocol treasury USDC ATA:

buy-deal.ts
1import { RFlowClient } from "@rflowdapp/rflow";
2import { getAssociatedTokenAddress } from "@solana/spl-token";
3import { Connection, Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
4
5// Assume `wallet`, `keypair`, and `client` are already set up as above
6const dealId = 42;
7const deal = await client.yieldDeals.getDeal(dealId);
8if (!deal) throw new Error("Deal not found");
9
10const sellerPaymentAccount = await getAssociatedTokenAddress(
11 deal.paymentMint,
12 deal.seller
13);
14
15const config = await client.getConfig();
16const treasuryAccount = await getAssociatedTokenAddress(
17 deal.paymentMint,
18 config!.treasury
19);
20
21const ixs = await client.yieldDeals.buyDeal(
22 dealId,
23 sellerPaymentAccount,
24 treasuryAccount
25);
26
27const tx = new Transaction().add(...ixs);
28const sig = await sendAndConfirmTransaction(connection, tx, [keypair]);
29console.log("Deal filled:", sig);
That’s the full loop
Read → list → fill. Settlement after ends_at is permissionless — anyone (including a keeper) can call settle_deal. The yield lands in the buyer's token account; the principal returns to the seller.

Next steps#