@rhea-finance/rnear-sdk
v0.1.1
Published
TypeScript SDK for rNEAR liquid staking (stake, unstake, withdraw) by Rhea Finance
Downloads
20
Readme
rnear-sdk
TypeScript SDK for rNEAR, the liquid staking token by Rhea Finance (formerly Ref Finance) on NEAR. Stake NEAR for rNEAR, unstake (delayed or instant), withdraw, and query protocol/account state — from any dapp, with any wallet.
- Zero runtime dependencies — plain
fetchJSON-RPC with endpoint failover and exact BigInt math. - Wallet-agnostic — transaction builders return plain JSON
transaction objects; sign them with NEAR Connect
(
@hot-labs/near-connect), near-api-js, the legacy wallet-selector, or anything else. - Works in browsers and Node.js (≥18), ESM and CJS.
How rNEAR works
Staking deposits NEAR into the rNEAR contract, which stakes it across a
validator pool and mints rNEAR shares. Rewards accrue into the share
price (ft_price), so rNEAR simply appreciates against NEAR. To exit:
| Path | What happens | Time |
| ------------------- | --------------------------------------------------- | ----- |
| Delayed unstake | Burns rNEAR at the current price, then withdraw | ~30h |
| Instant unstake | Swaps rNEAR to NEAR through the Rhea exchange | now |
Delayed unstake pays full price but waits ~4 epochs; instant unstake is immediate but pays swap fees/slippage.
Install
npm install @rhea-finance/rnear-sdkQuick start
import { RNearClient } from "@rhea-finance/rnear-sdk";
const client = new RNearClient(); // mainnet by default
const summary = await client.getSummary();
console.log(summary.ftPrice.amount); // "1.0567" NEAR per rNEAR
console.log(await client.getApy()); // "4.35" (percent)
const account = await client.getAccountDetails("you.near");
console.log(account.stakedNear.amount, account.pendingNear.amount);All amount inputs are human-readable decimal strings ("1.5").
All amount outputs are TokenAmount objects with both forms:
{ raw: "1500000000000000000000000", amount: "1.5" }.
Stake
A NEAR Connect wallet (or a
legacy wallet-selector Wallet) satisfies the SDK's TransactionSender
interface, so the convenience methods work directly:
import { NearConnector } from "@hot-labs/near-connect";
const connector = new NearConnector({ network: "mainnet" });
await connector.connect();
const wallet = await connector.wallet();
// stake 10 NEAR, receive rNEAR at the current price
await client.stake({ sender: wallet, accountId, nearAmount: "10" });Unstake & withdraw (delayed, full price)
A delayed unstake burns rNEAR at the current price immediately; the NEAR unlocks after ~4 epochs (~30h) and is then withdrawn explicitly:
// 1. start unstaking: 5 rNEAR, or the whole position
await client.unstake({ sender: wallet, accountId, rNearAmount: "5" });
await client.unstake({ sender: wallet, accountId, all: true });
// 2. wait out the unbonding period, checking progress
const details = await client.getAccountDetails(accountId);
console.log(details.pendingNear.amount); // NEAR waiting to unlock
console.log(details.canWithdraw); // true once unlocked
// estimated unlock time as Unix ms — format it however your app likes
if (details.estimatedUnlockAtMs !== null) {
console.log(new Date(details.estimatedUnlockAtMs).toLocaleString());
}
// 3. withdraw the unlocked NEAR to the wallet
if (details.canWithdraw) {
await client.withdraw({ sender: wallet, accountId });
}Instant unstake (immediate, small swap cost)
Skips the unbonding period by swapping rNEAR to NEAR on the Rhea exchange — no withdraw step needed:
const quote = await client.buildInstantUnstakeTransaction({
accountId: "you.near",
rNearAmount: "5",
slippage: 0.001, // 0.1% (default)
});
console.log(quote.expectedNear.amount, quote.minimumNear.amount);
await wallet.signAndSendTransactions({ transactions: [quote.transaction] });The route is fetched from the Rhea smart router and executed on the Rhea exchange; the output is unwrapped to native NEAR automatically. Mainnet only (testnet has no rNEAR liquidity pools).
Using any other signer
Every convenience method has a build*Transaction counterpart that just
returns the transaction, so you can sign it however you like (see
examples/node for a near-api-js adapter):
const tx = await client.buildStakeTransaction({
accountId: "you.near",
nearAmount: "10",
});
// tx = { signerId, receiverId, actions: [{ type: "FunctionCall", params: {...} }] }API
new RNearClient(options?)
| Option | Description |
| --------- | -------------------------------------------------------------- |
| network | "mainnet" (default) or "testnet" |
| config | Partial override of contract ids, RPC urls, indexer/router urls |
Views (no wallet needed)
| Method | Returns |
| ---------------------------------------- | -------------------------------------------------------------- |
| getSummary() | rNEAR price, total staked, validator count |
| getApy() | Staking APY percentage string, e.g. "4.35" |
| getAccountDetails(accountId) | Staked value, pending unstake, withdraw status, unlock time (Unix ms) |
| getRNearBalance(accountId) | rNEAR token balance |
| getNearBalance(accountId) | Native NEAR balance |
| isRegistered(accountId) | Whether storage is paid on the rNEAR token |
| canWithdraw(accountId, nearAmount) | Whether that much pending NEAR is unlocked |
| convertToNear(rNearAmount, ftPriceRaw?) | NEAR value of an rNEAR amount |
| convertToRNear(nearAmount, ftPriceRaw?) | rNEAR amount worth a NEAR amount |
Transaction builders
| Method | Contract call |
| ------------------------------------- | ---------------------------------------------------- |
| buildStakeTransaction(...) | deposit_and_stake (+ storage_deposit first time) |
| buildUnstakeTransaction(...) | unstake / unstake_all |
| buildInstantUnstakeTransaction(...) | ft_transfer_call swap via the Rhea exchange |
| buildWithdrawTransaction(...) | withdraw_all / withdraw |
Each has a matching convenience method (stake, unstake,
instantUnstake, withdraw) that also submits through a
TransactionSender.
Utilities
parseNearAmount("1.5") ⇄ formatNearAmount("15...0") convert between
decimal strings and raw yocto units (both NEAR and rNEAR use 24
decimals). NearRpcProvider is exported for custom view calls.
Contracts & endpoints
| Network | rNEAR contract | Exchange |
| ------- | -------------------- | --------------------- |
| mainnet | lst.rhealab.near | v2.ref-finance.near |
| testnet | lst.ref-dev.testnet| ref-finance-101.testnet |
Default RPC endpoints are public free tiers — override
config.rpcUrls with your own endpoints for production traffic.
Examples
examples/node— CLI scripts signing with near-api-js: summary, account, stake, unstake (delayed/instant/all), withdraw.examples/web— minimal Vite dapp using NEAR Connect for wallet connection. Live demo (mainnet): https://rhea-finance.github.io/rnear-sdk/
Development
pnpm install # installs the SDK and both examples (workspace)
pnpm run build # bundle to dist/ (ESM + CJS + d.ts)
pnpm test # unit tests (vitest)
pnpm run typecheckNotes
- Staking from an unregistered account automatically prepends the
one-time
storage_deposit(0.00125 NEAR). - When staking a wallet's full balance, leave ~0.2 NEAR for gas and storage.
getAccountDetailsreports pending balances below 0.00001 NEAR as zero: share rounding at stake time leaves yocto dust in the contract'sunstaked_balancefor nearly every staker.
