@upstake/sdk
v0.13.1
Published
TypeScript SDK for upstake.fun Solana program - marketplace for selling future staking rewards
Maintainers
Readme
@upstake/sdk
TypeScript SDK for interacting with the upstake.fun Solana program - a marketplace for selling future staking rewards.
Version
Current: v0.4.0
Program ID: H4c5Qr5rYQU7JJfPgiQFNKfMCod8Fzb3nHb8nBHQRmaw
Network: Solana Testnet
Installation
npm install @upstake/sdk @coral-xyz/anchor @solana/web3.jsQuick Start
With Phantom/Wallet Adapter (Recommended for dApps)
Perfect for React apps using Phantom, Solflare, Backpack, etc.
import { UpstakeClient } from '@upstake/sdk';
import { useConnection, useWallet } from '@solana/wallet-adapter-react';
import { useMemo } from 'react';
import { BN, LAMPORTS_PER_SOL, Keypair } from '@solana/web3.js';
function MyComponent() {
const { connection } = useConnection();
const wallet = useWallet();
// Create client - it handles all Program setup internally!
const client = useMemo(() => {
if (!wallet.publicKey) return null;
return UpstakeClient.fromWallet(connection, wallet);
}, [connection, wallet]);
const handleAcceptOffer = async () => {
if (!client) return;
// 1. Browse available offers
const offers = await client.getActiveOffers();
// 2. Generate new stake account
const stakeAccount = Keypair.generate();
// 3. Accept offer and receive instant payout
const signature = await client.acceptOffer(
offers[0].account.validator,
offers[0].account.validatorVoteAccount,
stakeAccount,
new BN(1 * LAMPORTS_PER_SOL), // 1 SOL principal
new BN(60 * 60 * 24 * 30 * 6) // 6 months lock
);
console.log('Success!', signature);
};
return <button onClick={handleAcceptOffer}>Stake & Get Instant Payout</button>;
}See FRONTEND_INTEGRATION.md for complete wallet-adapter setup.
With Keypair (Node.js Scripts/Bots)
For backend services, automation, or testing.
import { UpstakeClient, IDL, PROGRAM_ID } from '@upstake/sdk';
import { AnchorProvider, Program, BN } from "@coral-xyz/anchor";
import { Connection, Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js";
// Setup
const connection = new Connection("https://api.testnet.solana.com");
const wallet = Keypair.generate(); // or Keypair.fromSecretKey(...)
// Create provider & program
const provider = new AnchorProvider(connection, { publicKey: wallet.publicKey } as any, {});
const program = new Program(IDL as any, PROGRAM_ID, provider);
// Create client
const client = new UpstakeClient(connection, program, wallet);
// Use client
const offers = await client.getActiveOffers();
const stakeAccount = Keypair.generate();
const signature = await client.acceptOffer(
offers[0].account.validator,
offers[0].account.validatorVoteAccount,
stakeAccount,
new BN(1 * LAMPORTS_PER_SOL),
new BN(60 * 60 * 24 * 30 * 3) // 3 months
);Features
- ✅ Type-safe - Full TypeScript support with auto-generated types from IDL
- ✅ Simple API - Clean, intuitive function signatures
- ✅ PDA helpers - Automatic PDA derivation for all accounts
- ✅ Account fetchers - Easy querying of all account types
- ✅ Comprehensive - All 14 instructions + account fetching utilities
- ✅ Standing Offer Model - Instant liquidity from validator pools
API Reference
Platform Admin Instructions
initializePlatform(program, admin, treasury)
Initialize the platform configuration (one-time setup by admin).
import { initializePlatform } from "@upstake/sdk";
const signature = await initializePlatform(
program,
adminKeypair
);pausePlatform(program, admin)
Emergency pause all new acceptances (admin only).
import { pausePlatform } from "@upstake/sdk";
await pausePlatform(program, adminKeypair);unpausePlatform(program, admin)
Resume platform operations (admin only).
import { unpausePlatform } from "@upstake/sdk";
await unpausePlatform(program, adminKeypair);updatePlatformConfig(program, admin, options)
Update platform fees and limits with trust-minimized caps (admin only).
import { updatePlatformConfig } from "@upstake/sdk";
await updatePlatformConfig(
program,
adminKeypair,
{
payoutFeeBps: 30, // 0.3% upfront payout fee (max 5%)
rewardFeeBps: 80, // 0.8% (max 10%)
keeperRewardBps: 20, // 0.2% (max 1%)
minPrincipal: new BN(0.05 * LAMPORTS_PER_SOL), // 0.05 SOL minimum
minLockDuration: new BN(7 * 24 * 60 * 60), // 1 week minimum
// maxPrincipal: keep current
// maxLockDuration: keep current
}
);Validator Pool Instructions
createStandingOffer(program, validator, validatorVoteAccount, params)
Create a standing offer with liquidity pool.
import { createStandingOffer } from "@upstake/sdk";
const signature = await createStandingOffer(
program,
validatorKeypair,
validatorVoteAccount,
500, // 5% APR
new BN(0.1 * LAMPORTS_PER_SOL), // min 0.1 SOL
new BN(10 * LAMPORTS_PER_SOL), // max 10 SOL
new BN(60 * 60 * 24 * 30 * 6), // min 6 months
new BN(60 * 60 * 24 * 365), // max 1 year
new BN(5 * LAMPORTS_PER_SOL) // initial deposit (escrow PDA created automatically)
);depositToPool(program, validator, amount)
Add liquidity to validator's pool.
import { depositToPool } from "@upstake/sdk";
await depositToPool(
program,
validatorKeypair,
new BN(2 * LAMPORTS_PER_SOL)
);withdrawFromPool(program, validator, amount)
Withdraw available (non-staked) funds from pool.
import { withdrawFromPool } from "@upstake/sdk";
await withdrawFromPool(
program,
validatorKeypair,
new BN(1 * LAMPORTS_PER_SOL)
);updateStandingOffer(program, validator, params)
Update standing offer parameters.
import { updateStandingOffer } from "@upstake/sdk";
await updateStandingOffer(
program,
validatorKeypair,
600, // new APR: 6%
null, // keep min principal
new BN(15 * LAMPORTS_PER_SOL), // new max principal
null, // keep min lock duration
null // keep max lock duration
);pauseStandingOffer(program, validator)
Pause standing offer (prevent new acceptances).
import { pauseStandingOffer } from "@upstake/sdk";
await pauseStandingOffer(program, validatorKeypair);unpauseStandingOffer(program, validator)
Resume standing offer (allow new acceptances).
import { unpauseStandingOffer } from "@upstake/sdk";
await unpauseStandingOffer(program, validatorKeypair);closeStandingOffer(program, validator)
Close a standing offer after all stakes are settled and liquidity is withdrawn.
import { closeStandingOffer } from "@upstake/sdk";
await closeStandingOffer(program, validatorKeypair);User & Settlement Instructions
acceptStandingOffer(program, user, validator, voteAccount, stakeAccount, principal, lockDuration)
User accepts standing offer and receives instant upfront payment.
import { acceptStandingOffer } from "@upstake/sdk";
const stakeAccount = Keypair.generate();
await acceptStandingOffer(
program,
userKeypair,
validatorPublicKey,
validatorVoteAccount,
stakeAccount,
new BN(0.5 * LAMPORTS_PER_SOL), // 0.5 SOL principal
new BN(60 * 60 * 24 * 30 * 11) // 11 months
);deactivateStake(program, userStake, stakeAccount)
Deactivate stake after lock period (permissionless).
import { deactivateStake } from "@upstake/sdk";
import { getUserStakePDA } from "@upstake/sdk";
const [userStakePDA] = getUserStakePDA(userPublicKey, stakeAccountPublicKey);
await deactivateStake(
program,
userStakePDA,
stakeAccountPublicKey
);settle(program, userStake, user, validator, settler, stakeAccount)
Settle stake and distribute rewards (permissionless - settler receives 0.25% reward).
import { settle } from "@upstake/sdk";
await settle(
program,
userStakePDA,
userPublicKey,
validatorPublicKey,
settlerKeypair,
stakeAccountPublicKey
);Account Fetchers
fetchPlatformConfig(program)
Fetch the platform configuration.
import { fetchPlatformConfig } from "@upstake/sdk";
const config = await fetchPlatformConfig(program);
console.log('Payout fee:', config.payoutFeeBps, 'bps');
console.log('Reward fee:', config.rewardFeeBps, 'bps');
console.log('Keeper reward:', config.keeperRewardBps, 'bps');
console.log('Paused:', config.paused);
console.log('Min principal:', config.minPrincipal.toString(), 'lamports');
console.log('Min lock duration:', config.minLockDuration.toString(), 'seconds');fetchStandingOffer(program, validator)
Fetch a specific standing offer.
import { fetchStandingOffer } from "@upstake/sdk";
const offer = await fetchStandingOffer(program, validatorPublicKey);
if (offer) {
console.log('APR:', offer.aprBps, 'bps');
console.log('Available liquidity:', offer.available.toString());
console.log('Active:', offer.isActive);
}fetchAllStandingOffers(program)
Fetch all standing offers (marketplace view).
import { fetchAllStandingOffers } from "@upstake/sdk";
const offers = await fetchAllStandingOffers(program);
console.log('Found', offers.length, 'standing offers');
// Filter active offers
const activeOffers = offers.filter(o => o.account.isActive);fetchActiveStandingOffers(program)
Fetch only active standing offers.
import { fetchActiveStandingOffers } from "@upstake/sdk";
const offers = await fetchActiveStandingOffers(program);
offers.forEach(({ publicKey, account }) => {
console.log('Validator:', account.validator.toBase58());
console.log('APR:', account.aprBps, 'bps (', account.aprBps / 100, '%)');
console.log('Available:', account.available.toString(), 'lamports');
});fetchUserStake(program, user, stakeAccount)
Fetch a specific user stake.
import { fetchUserStake } from "@upstake/sdk";
const userStake = await fetchUserStake(program, userPublicKey, stakeAccountPublicKey);
if (userStake) {
console.log('Principal:', userStake.principal.toString());
console.log('Lock duration:', userStake.lockDuration.toString());
console.log('Instant payout:', userStake.instantPayout.toString());
console.log('Status:', userStake.status);
}fetchUserStakesByUser(program, user)
Fetch all stakes for a user.
import { fetchUserStakesByUser } from "@upstake/sdk";
const stakes = await fetchUserStakesByUser(program, userPublicKey);
stakes.forEach(({ publicKey, account }) => {
console.log('Stake:', publicKey.toBase58());
console.log('Principal:', account.principal.toString());
console.log('Status:', account.status);
});fetchActiveUserStakes(program, user)
Fetch only active stakes for a user.
import { fetchActiveUserStakes } from "@upstake/sdk";
const activeStakes = await fetchActiveUserStakes(program, userPublicKey);
console.log('User has', activeStakes.length, 'active stakes');PDA Helpers
All PDA derivation functions:
import {
getPlatformConfigPDA,
getPlatformPDA,
getTreasuryPDA,
getStandingOfferPDA,
getPoolEscrowPDA,
getUserStakePDA,
} from "@upstake/sdk";
// Platform PDAs
const [platformConfig] = getPlatformConfigPDA();
const [platform] = getPlatformPDA();
const [treasury] = getTreasuryPDA();
// Standing Offer & Pool PDAs
const [standingOffer] = getStandingOfferPDA(validatorPublicKey);
const [poolEscrow] = getPoolEscrowPDA(standingOffer);
// User Stake PDA
const [userStake] = getUserStakePDA(userPublicKey, stakeAccountPublicKey);Constants
import { PROGRAM_ID, ENDPOINTS } from "@upstake/sdk";
console.log('Program ID:', PROGRAM_ID.toBase58());
console.log('Testnet:', ENDPOINTS.TESTNET);
console.log('Mainnet:', ENDPOINTS.MAINNET_BETA);Complete Example: User Flow
import { AnchorProvider, Program, BN } from "@coral-xyz/anchor";
import { Connection, Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js";
import {
IDL,
PROGRAM_ID,
fetchActiveStandingOffers,
acceptStandingOffer,
} from "@upstake/sdk";
// Setup
const connection = new Connection("https://api.testnet.solana.com");
const wallet = // ... your wallet
const provider = new AnchorProvider(connection, wallet, {});
const program = new Program(IDL, PROGRAM_ID, provider);
// 1. Browse active standing offers
const offers = await fetchActiveStandingOffers(program);
console.log('Found', offers.length, 'active offers');
// 2. Find best offer (highest rate with sufficient liquidity)
const desiredPrincipal = new BN(1 * LAMPORTS_PER_SOL);
const desiredDuration = new BN(60 * 60 * 24 * 30 * 11); // 11 months
const suitableOffers = offers.filter(({ account }) => {
const meetsMinimums =
account.minPrincipal.lte(desiredPrincipal) &&
account.maxPrincipal.gte(desiredPrincipal) &&
account.minLockDuration.lte(desiredDuration) &&
account.maxLockDuration.gte(desiredDuration);
const SECONDS_PER_YEAR = 365.25 * 24 * 60 * 60;
const instantPayout = desiredPrincipal
.mul(desiredDuration)
.mul(new BN(account.aprBps))
.div(new BN(SECONDS_PER_YEAR * 10000));
const hasLiquidity = account.available.gte(instantPayout);
return meetsMinimums && hasLiquidity;
});
const bestOffer = suitableOffers.sort((a, b) =>
b.account.aprBps - a.account.aprBps
)[0];
console.log('Best offer:', bestOffer.account.aprBps / 100, '% APR from',
bestOffer.account.validator.toBase58());
// 3. Accept best offer
const stakeAccount = Keypair.generate();
const acceptSig = await acceptStandingOffer(
program,
wallet, // user keypair
bestOffer.account.validator,
bestOffer.account.validatorVoteAccount,
stakeAccount,
desiredPrincipal,
desiredDuration
);
console.log('Accepted! Transaction:', acceptSig);Complete Example: Validator Flow
import {
createStandingOffer,
depositToPool,
fetchStandingOffer,
} from "@upstake/sdk";
// 1. Create standing offer with initial liquidity
const sig = await createStandingOffer(
program,
validatorKeypair,
validatorVoteAccount,
500, // 5% APR
new BN(0.1 * LAMPORTS_PER_SOL), // min 0.1 SOL
new BN(10 * LAMPORTS_PER_SOL), // max 10 SOL
new BN(60 * 60 * 24 * 30 * 6), // min 6 months
new BN(60 * 60 * 24 * 365), // max 1 year
new BN(5 * LAMPORTS_PER_SOL) // initial 5 SOL deposit
);
console.log('Standing offer created:', sig);
// 2. Monitor and add more liquidity as needed
const offer = await fetchStandingOffer(program, validatorKeypair.publicKey);
console.log('Current liquidity:', offer.available.toString(), 'lamports');
console.log('Active stakes:', offer.activeStakes.toString(), 'lamports');
// 3. Add more liquidity if pool is running low
if (offer.available.lt(new BN(1 * LAMPORTS_PER_SOL))) {
console.log('Pool low, adding 3 SOL...');
await depositToPool(
program,
validatorKeypair,
new BN(3 * LAMPORTS_PER_SOL)
);
}Complete Example: Settler Bot
import {
fetchUserStakesByValidator,
deactivateStake,
settle,
} from "@upstake/sdk";
// Settler bot that earns 0.25% keeper rewards
// 1. Find all stakes for a validator
const stakes = await fetchUserStakesByValidator(program, validatorPublicKey);
// 2. Find stakes ready to deactivate
const now = Date.now() / 1000;
const readyToDeactivate = stakes.filter(({ account }) =>
account.status.active && account.unlockTimestamp.toNumber() <= now
);
// 3. Deactivate unlocked stakes
for (const { account } of readyToDeactivate) {
try {
const [userStakePDA] = getUserStakePDA(account.user, account.stakeAccount);
await deactivateStake(program, userStakePDA, account.stakeAccount);
console.log('Deactivated:', account.stakeAccount.toBase58());
} catch (err) {
console.error('Deactivation failed:', err);
}
}
// 4. Find deactivating stakes ready to settle (after cooldown)
const readyToSettle = stakes.filter(({ account }) =>
account.status.deactivating // Check if cooldown complete (use stake account data)
);
// 5. Settle and earn keeper rewards (0.25% of rewards)
for (const { account } of readyToSettle) {
try {
const [userStakePDA] = getUserStakePDA(account.user, account.stakeAccount);
await settle(
program,
userStakePDA,
account.user,
account.validator,
settlerKeypair,
account.stakeAccount
);
console.log('Settled and earned keeper reward!');
} catch (err) {
console.error('Settlement failed:', err);
}
}Validator Verification
The SDK provides utilities to verify if an address is a validator. Validators in Solana have both an identity account (keypair) and one or more vote accounts (where votes are recorded). The SDK handles both cases automatically.
isValidator(connection, address) - Recommended
Smart function that checks if an address is a validator (handles both identity and vote accounts).
import { isValidator } from "@upstake/sdk";
import { Connection, PublicKey } from "@solana/web3.js";
const connection = new Connection("https://api.testnet.solana.com");
const address = new PublicKey("..."); // Could be identity OR vote account
const result = await isValidator(connection, address);
if (result.isValidator) {
console.log("✅ Valid validator!");
console.log("Type:", result.type); // 'vote_account' or 'validator_identity'
console.log("Vote accounts:", result.voteAccounts);
// Use the first vote account for creating standing offer
const voteAccount = result.voteAccounts[0];
await createStandingOffer(program, validator, voteAccount, ...);
} else {
console.error("❌", result.error);
}isValidatorVoteAccount(connection, address)
Check if a specific address is a valid vote account (owned by Vote program).
import { isValidatorVoteAccount } from "@upstake/sdk";
const voteAccount = new PublicKey("BwcLcLFz8nfuT8Q7p4WicS3Y5TMB9MJRWR54BydKRQmb");
const result = await isValidatorVoteAccount(connection, voteAccount);
if (result.isValidator) {
console.log("✅ Valid vote account");
} else {
console.error("❌", result.error);
}findVoteAccountsForValidator(connection, validatorIdentity)
Find all vote accounts associated with a validator identity.
import { findVoteAccountsForValidator } from "@upstake/sdk";
const validatorIdentity = new PublicKey("...");
const voteAccounts = await findVoteAccountsForValidator(connection, validatorIdentity);
if (voteAccounts.length > 0) {
console.log(`Found ${voteAccounts.length} vote account(s):`);
voteAccounts.forEach(va => console.log(" ", va.toBase58()));
} else {
console.log("No vote accounts found for this identity");
}checkMultipleValidators(connection, addresses)
Batch check multiple vote accounts in parallel for better performance.
import { checkMultipleValidators } from "@upstake/sdk";
const addresses = [
new PublicKey("vote1..."),
new PublicKey("vote2..."),
new PublicKey("vote3..."),
];
const results = await checkMultipleValidators(connection, addresses);
results.forEach((result, addressStr) => {
console.log(addressStr, result.isValidator ? "✅" : "❌", result.error || "");
});Validation Checks
Vote Account Validation:
- Exists on-chain
- Owned by Vote program (
Vote111111111111111111111111111111111111111) - Has valid vote account data (minimum size check)
Validator Identity → Vote Account:
- Queries cluster for all vote accounts
- Matches by node_pubkey (validator identity)
- Returns all associated vote accounts
Example Use Cases:
- Frontend form validation (accept both identity and vote account inputs)
- Pre-flight checks in validator onboarding flows
- Bulk validator verification for admin dashboards
- Validator discovery and listing
TypeScript Types
All types are automatically generated from the program IDL:
import type {
PlatformConfig,
StandingOffer,
UserStake,
StakeStatus,
} from "@upstake/sdk";
// Stake status enum
type Status = StakeStatus; // Active | Deactivating | Settled
// Platform Configuration
const config: PlatformConfig = {
admin: PublicKey,
paused: boolean,
payoutFeeBps: number, // e.g., 50 = 0.5%
rewardFeeBps: number, // e.g., 100 = 1%
keeperRewardBps: number, // e.g., 25 = 0.25%
minPrincipal: BN, // lamports
minLockDuration: BN, // seconds
maxPrincipal: BN, // lamports
maxLockDuration: BN, // seconds
bump: number,
};
// Standing Offer (Validator Pool)
const offer: StandingOffer = {
validator: PublicKey,
validatorVoteAccount: PublicKey,
aprBps: number, // e.g., 500 = 5% APR
minPrincipal: BN,
maxPrincipal: BN,
minLockDuration: BN,
maxLockDuration: BN,
totalDeposited: BN,
activeStakes: BN,
available: BN,
isActive: boolean,
createdAt: BN,
bump: number,
};
// User Stake
const userStake: UserStake = {
user: PublicKey,
stakeAccount: PublicKey,
standingOffer: PublicKey,
principal: BN,
lockDuration: BN,
instantPayout: BN,
createdAt: BN,
unlockTimestamp: BN,
status: StakeStatus,
validator: PublicKey,
validatorVoteAccount: PublicKey,
bump: number,
};Network Configuration
import { ENDPOINTS } from "@upstake/sdk";
// Testnet (current deployment)
const connection = new Connection(ENDPOINTS.TESTNET);
// Mainnet (when deployed)
const connection = new Connection(ENDPOINTS.MAINNET_BETA);Program Information
- Program ID (Testnet):
H4c5Qr5rYQU7JJfPgiQFNKfMCod8Fzb3nHb8nBHQRmaw - Anchor Version: 0.31.1
- Solana Version: Agave 3.0.5
- Network: Solana Testnet
- Model: Standing Offer Pool (v0.2.0+)
Development
# Install dependencies
cd sdk
npm install
# Build
npm run build
# Clean
npm run cleanPublishing
cd sdk
npm version patch/minor/major
npm publishChangelog
v0.11.0 (Current)
BREAKING: Wallet-Adapter Support 🎉
This release fixes the systematic design issue with wallet-adapter integration and makes the SDK properly work with Phantom, Solflare, and all browser wallets!
What's New:
- ✅
UpstakeClient.fromWallet(connection, wallet)- New factory method for wallet-adapter- Automatically creates AnchorProvider and Program
- Works with any wallet-adapter compatible wallet
- No manual Program setup required!
- See examples in Quick Start section
- ✅ Fixed documentation - FRONTEND_INTEGRATION.md now shows correct usage
- ✅ Updated examples - All examples now show both wallet-adapter and Keypair patterns
Migration Guide:
Old (incorrect):
const client = new UpstakeClient(connection, wallet); // ❌ WRONGNew (correct):
// Option 1: Use factory method (recommended for dApps)
const client = UpstakeClient.fromWallet(connection, wallet); // ✅
// Option 2: Manual setup (for advanced use cases)
const provider = new AnchorProvider(connection, wallet, {});
const program = new Program(IDL, PROGRAM_ID, provider);
const client = new UpstakeClient(connection, program, wallet); // ✅Why This Matters:
- Frontend developers no longer need to understand Anchor internals
- One-line setup for wallet-adapter integration
- Proper transaction signing with browser wallets
- The SDK now follows Solana dApp best practices
v0.10.0
New Feature: Comprehensive validator verification utilities
What's New:
- ✅
isValidator(connection, address)- Smart function that handles both validator identity accounts and vote accounts- Automatically detects if address is a vote account or validator identity
- Returns all associated vote accounts
- Returns type:
'vote_account'or'validator_identity'
- ✅
isValidatorVoteAccount(connection, address)- Check if address is a valid vote account- Verifies ownership by Vote program
- Validates account data size
- Returns detailed error messages
- ✅
findVoteAccountsForValidator(connection, validatorIdentity)- Find vote accounts for validator identity- Queries cluster for all vote accounts
- Filters by node_pubkey (validator identity)
- Returns array of vote account PublicKeys
- ✅
checkMultipleValidators(connection, addresses[])- Batch verification- Parallel checking for multiple addresses
- Efficient using
getMultipleAccountsInfo - Returns Map of results
Use Cases:
- Frontend form validation (accept both identity and vote account inputs)
- Validator onboarding pre-flight checks
- Admin dashboards with bulk validator verification
- Validator discovery and listing
Documentation:
- Full API documentation in README
- Usage examples for all functions
- Type definitions with detailed comments
v0.9.0
Contract Update: Synced with Standing Offer Pool model changes
Changes:
- Updated field names to match contract v0.1.0
- Refreshed IDL with latest contract changes
- Documentation updates for terminology consistency
v0.6.0 (Frontend Ready)
Major SDK Enhancement: Complete frontend and indexing support
New Modules (7 new files, ~2000 lines of utility code):
Event Parsing (
events.ts) - Webhook payload parsing- Parse all 13 Upstake events (12 existing + 4 new admin events)
- Automatic event type detection
- Support for both camelCase and snake_case formats
- Helper functions:
parseEventFromWebhook(),getEventType() - Event categorization:
isValidatorPoolEvent(),isUserStakeEvent(),isAdminEvent()
Formatting (
formatting.ts) - Display utilities- SOL formatting:
formatSOL(),lamportsToSOL(),solToLamports() - Rate formatting:
formatAPR(),formatBPS() - Time formatting:
formatDuration(),formatTimestamp(),formatTimeRemaining(),formatRelativeTime() - Progress:
calculateProgress(),formatProgress() - Numbers:
formatNumber(),formatTVL()
- SOL formatting:
Calculations (
calculations.ts) - Math utilities- Instant payout:
calculateInstantPayout(),calculateNetInstantPayout() - Rewards:
calculateExpectedRewards(),calculateEffectiveAPR(),calculateAPY() - ROI analysis:
calculateEffectiveReturn(),calculateBreakeven() - Pool metrics:
calculatePoolUtilization(),calculateAvailableLiquidity() - Fees:
calculatePlatformFee(),calculateRewardFee(),calculateKeeperReward() - Validation:
canOfferFulfill(),calculateMaxPrincipal()
- Instant payout:
Simulation (
simulation.ts) - Transaction previewsimulateAcceptOffer()- Preview exact amounts before signingpreviewFees()- Detailed fee breakdowncheckEligibility()- Pre-flight validation- Returns: gross/net payouts, unlock dates, estimated rewards, validation errors
Aggregators (
aggregators.ts) - Batch fetchersfetchOfferWithBalance()- Offer + pool balance in one callfetchUserPortfolio()- Complete user dashboard datafetchMarketOverview()- Platform-wide stats (TVL, offers, APR, etc.)fetchValidatorDashboard()- Validator complete state
Error Handling (
errors.ts) - User-friendly errorsparseTransactionError()- Convert Anchor errors to readable messages- 30+ error codes mapped to user-friendly messages
retryWithBackoff()- Resilient RPC callsisUpstakeError(),getUserErrorMessage()
Display Helpers (
display.ts) - UI utilities- Status badges:
getStakeStatusBadge(),getOfferStatusBadge() - Risk assessment:
getValidatorRiskLevel() - Sorting:
sortOffersByBestValue(),sortStakesByUnlockTime() - Filtering:
filterOffersForUser() - Color helpers:
getAPRColor(),getUtilizationColor()
- Status badges:
Contract Updates:
- ✅ Added 4 new admin events:
PlatformConfigUpdated,PlatformPaused,PlatformUnpaused,AdminTransferred - ✅ Updated all admin instructions to emit events
Benefits:
- ✅ Complete frontend integration support
- ✅ Webhook indexing ready
- ✅ Transaction preview before signing
- ✅ User-friendly error messages
- ✅ Comprehensive display utilities
- ✅ Performance optimizations (batch fetching)
v0.5.0
New Features:
- ✅ UpstakeClient Class - New unified client wrapper for all operations
- ✅ Query Utilities - Added queries.ts with helper functions for fetching data
- ✅ Type Guards - Added typeGuards.ts for runtime type checking
- ✅ Validators - Added validators.ts for input validation
Bug Fixes:
- ✅ Fixed Parameter Names - Corrected client.ts method parameters to match instruction wrappers
upfrontFeeBps→payoutFeeBpsrateBps→aprBpsnewRateBps→newAprBps
Documentation Updates:
- ✅ Terminology Sync - All field names now match contract v0.1.0
- ✅ Updated Examples - Fixed instant payout calculations with time factor
- ✅ Type Documentation - Updated PlatformConfig, StandingOffer, UserStake types
Breaking Changes:
- ⚠️ Client Method Signatures - Parameter names changed in UpstakeClient methods (see bug fixes above)
v0.4.0 (Documentation Update)
Documentation Fixes:
- ✅ Fixed Program ID - Updated to correct testnet ID:
H4c5Qr5rYQU7JJfPgiQFNKfMCod8Fzb3nHb8nBHQRmaw - ✅ Removed Outdated Examples - Removed all Intent/Offer model examples
- ✅ Added Standing Offer Examples - Complete user, validator, and settler flows
- ✅ Updated API Reference - All 14 instructions documented with correct signatures
- ✅ Improved Type Documentation - Added comprehensive type examples
No Breaking Changes - This is a documentation-only update. The SDK code remains unchanged from v0.3.1.
v0.3.1
Build Fix:
- Fixed npm package to exclude old ValidatorStats compiled files from dist/
v0.3.0 (Breaking Changes)
Architecture Optimization:
- ✅ Removed ValidatorStats Account - All validator statistics now tracked off-chain via event indexing
- Lower gas costs (~5,000 CU savings per transaction)
- No rent burden for validator stats accounts
- More flexible analytics capabilities
- Same data available through event logs
Breaking Changes:
⚠️ Removed on-chain accounts:
ValidatorStatsaccount removedgetValidatorStatsPDA()function removedfetchValidatorStats()function removedfetchAllValidatorStats()function removed
⚠️ Instruction changes:
accept_standing_offer: no longer requiresvalidator_statsaccountsettle: no longer requiresvalidator_statsaccount
Migration Guide:
Validator statistics should now be computed from indexed events using Helius/QuickNode + Supabase.
See docs/WEBHOOK_SUPABASE.md for event indexing setup.
v0.2.0 (Breaking Changes - Standing Offer Model)
Major Architectural Change:
- ✅ Standing Offer Pool Model - Replaced Intent/Offer bidding with validator liquidity pools
- Instant upfront payments (no waiting for offers)
- Validators create standing offers with deposited liquidity
- Users accept offers immediately if pool has funds
- ✅ Staking Power Model - Flexible minimum based on
principal × lock_duration- Minimum: 1 SOL-month (2,592,000,000,000,000 lamport-seconds)
- Examples: 1 SOL × 30 days, 2 SOL × 15 days, 0.5 SOL × 60 days
- ✅ Security Enhancements:
- Vote account validation
- Rent-exempt protection
- Fee caps (5%/10%/1% max)
- ✅ Keeper Rewards - 0.25% of rewards for settlement
Breaking Changes:
- ⚠️ Program ID changed - New program at
H4c5Qr5rYQU7JJfPgiQFNKfMCod8Fzb3nHb8nBHQRmaw - ⚠️ Complete model change:
- Removed:
Intent,Offeraccounts - Added:
StandingOffer,UserStakeaccounts - Removed:
createIntent,makeOffer,acceptOffer,cancelIntent,cancelOffer,updateOffer - Added:
createStandingOffer,acceptStandingOffer,depositToPool,withdrawFromPool, etc.
- Removed:
v0.1.0
- Initial release with Intent/Offer model
License
MIT
