
Security News
White House Authorizes Private Companies to Conduct Offensive Cyber Operations
A new federal program will let vetted U.S. cybersecurity firms help investigate and disrupt foreign cybercrime groups under government direction.
@solana-compass/sdk
Advanced tools
Solana SDK for building apps with Jupiter, Kamino, MarginFi, Drift, Loopscale, and more
Solana SDK for building apps with Jupiter, Kamino, MarginFi, Drift, Loopscale, and more. Extracted from the Sol CLI so websites, bots, and other consumers can reuse the same integrations without dragging in Commander, SQLite, or filesystem dependencies.
npm install @solana-compass/sdk @solana/kit
Optional peer dependencies for DeFi protocol support:
# Kamino lending & earn vaults
npm install @kamino-finance/klend-sdk @coral-xyz/anchor
# MarginFi lending
npm install @mrgnlabs/marginfi-client-v2
# Drift lending
npm install @drift-labs/sdk
If these aren't installed, those providers are silently skipped.
import { createSolSdk, registerDefaultProviders, InMemoryConfig } from '@solana-compass/sdk';
import { createSolanaRpc } from '@solana/kit';
const rpc = createSolanaRpc('https://api.mainnet-beta.solana.com');
const sdk = createSolSdk({
rpc,
rpcUrl: 'https://api.mainnet-beta.solana.com', // needed for v1 compat providers
signer: {
getSigner: async (id) => walletAdapter, // your TransactionSigner
getAddress: async (id) => walletAdapter.address,
},
});
// Register DeFi providers (Kamino, MarginFi, Drift, Jupiter Lend, Loopscale)
await registerDefaultProviders(sdk);
// Read-only — works immediately
const prices = await sdk.price.getPrices(['So11111111111111111111111111111111']);
const rates = await sdk.lend.getRates(['SOL', 'USDC']);
const quote = await sdk.swap.getQuote('SOL', 'USDC', 1.0);
// Write — wallet signs the transaction
const result = await sdk.swap.executeSwap('SOL', 'USDC', 1.0, 'main');
// Portfolio
const report = await sdk.portfolio.get([
{ name: 'main', address: 'YourWa11etAddress...' },
]);
The SDK uses context-based dependency injection. Every service closes over a SolContext that carries pluggable implementations for logging, caching, config, signing, and transaction logging.
Your App Sol CLI
┌─────────────────────┐ ┌──────────────────────────┐
│ WalletAdapter signer │ │ FileSigner (key files) │
│ InMemoryCache │ │ SqliteCache (repos) │
│ NoopLogger │ │ CliLogger (verbose/warn) │
│ NoopTransactionLogger│ │ SqliteTxLogger (tx_log) │
└──────────┬──────────┘ │ TomlConfig (config.toml) │
│ └──────────┬───────────────┘
▼ ▼
┌─────────────────────────────────────┐
│ @solana-compass/sdk │
│ createSolSdk(opts) → SolSdk │
│ price, swap, stake, lend, earn, │
│ orders, predict, portfolio, x402 │
└─────────────────────────────────────┘
interface SolSdk {
price: PriceService;
token: TokenService;
registry: TokenRegistryService;
lists: TokenListsService;
swap: SwapService;
stake: StakeService;
lend: LendService;
earn: EarnService;
order: OrderService;
predict: PredictService;
portfolio: PortfolioService;
x402: X402Service;
onramp: OnrampService;
tx: TransactionService;
ctx: SolContext;
}
interface SolSdkOptions {
rpc: Rpc<SolanaRpcApi>;
rpcUrl?: string; // needed by v1 compat (MarginFi, Drift)
logger?: Logger; // defaults to NoopLogger
config?: ConfigProvider; // defaults to InMemoryConfig({})
cache?: CacheProvider; // defaults to InMemoryCache
txLogger?: TransactionLogger; // defaults to NoopTransactionLogger
signer: SignerProvider; // required
analyticsInstruction?: () => Instruction | null;
}
interface Logger {
verbose(msg: string): void;
warn(msg: string): void;
}
interface ConfigProvider {
get(key: string): unknown; // supports dot-notation: 'api.jupiterApiKey'
}
Config keys used by the SDK:
| Key | Type | Description |
|---|---|---|
api.jupiterApiKey | string | Jupiter API key (optional, falls back to lite API) |
api.jupiterBaseUrl | string | Proxy URL for Jupiter API calls |
api.dflowApiKey | string | DFlow router API key |
api.dflowBaseUrl | string | Proxy URL for DFlow API calls |
api.loopscaleBaseUrl | string | Proxy URL for Loopscale API calls |
api.coingeckoApiKey | string | CoinGecko API key (price fallback) |
defaults.router | string | Default swap router (best, jupiter, dflow) |
defaults.validator | string | Default staking validator vote account |
defaults.slippage | number | Default swap slippage in basis points |
lend.defaultProtocol | string | Default lending protocol |
earn.defaultProtocol | string | Default earn protocol |
predict.defaultProvider | string | Default prediction market provider |
interface CacheProvider {
// Price cache
getLatestPrice(mint: string): PriceCacheEntry | undefined;
insertPrice(mint: string, priceUsd: number, source: string): void;
// Token cache
getTokenByMint(mint: string): TokenCacheEntry | undefined;
getTokenBySymbol(symbol: string): TokenCacheEntry[];
isTokenCacheStale(mint: string, ttlHours?: number): boolean;
upsertTokenBatch(tokens: TokenUpsert[]): void;
// Optional — token lists
getTokenList?(category: string, interval: string | null): TokenListCacheRow[];
replaceTokenList?(category: string, interval: string | null, entries: TokenListCacheRow[]): void;
isTokenListStale?(category: string, interval: string | null, ttlMinutes: number): boolean;
// Optional — snapshots
createSnapshot?(label?: string): number;
insertSnapshotEntry?(entry: SnapshotEntryRow): void;
getSnapshotEntries?(snapshotId: number): SnapshotEntryRow[];
getSnapshot?(id: number): SnapshotRow | undefined;
getLatestSnapshot?(): SnapshotRow | undefined;
listSnapshots?(limit: number): SnapshotRow[];
}
interface SignerProvider {
getSigner(identifier: string): Promise<TransactionSigner>;
getAddress(identifier: string): Promise<string>;
getRawBytes?(identifier: string): Uint8Array; // v1 compat (MarginFi oracle crank)
}
The identifier is whatever your app uses to distinguish wallets. The CLI uses wallet names (e.g. "main"), but you can use addresses, indexes, or anything.
interface TransactionLogger {
log(entry: TxLogEntry): void;
updateStatus(signature: string, status: string, error?: string): void;
}
interface TxLogEntry {
signature: string;
type: string;
walletName?: string;
fromMint?: string;
toMint?: string;
fromAmount?: string;
toAmount?: string;
fromPriceUsd?: number;
toPriceUsd?: number;
status: string;
error?: string;
}
The SDK ships with zero-dependency defaults so you can get started without implementing every interface:
import {
NoopLogger, // verbose/warn are no-ops
NoopTransactionLogger, // log/updateStatus are no-ops
InMemoryConfig, // wraps a plain object, dot-notation get
InMemoryCache, // Map-based price + token cache
} from '@solana-compass/sdk';
// Pass a config object with dot-separated keys
const config = new InMemoryConfig({
'api.jupiterBaseUrl': 'https://mysite.com/api/jupiter',
'defaults.slippage': 100,
});
config.get('api.jupiterBaseUrl'); // 'https://mysite.com/api/jupiter'
config.get('defaults.slippage'); // 100
sdk.price.getPrices(mints: string[]): Promise<Map<string, PriceResult>>
sdk.price.getPrice(mint: string): Promise<PriceResult | undefined>
sdk.price.getCachedPrice(mint: string): PriceResult | undefined
Fetches USD prices via Jupiter Price API with CoinGecko fallback. Results are cached.
import { SOL_MINT } from '@solana-compass/sdk';
const prices = await sdk.price.getPrices([SOL_MINT]);
const solPrice = prices.get(SOL_MINT);
console.log(solPrice?.priceUsd); // 150.42
sdk.token.getSolBalance(addr: string): Promise<number>
sdk.token.getTokenBalances(walletAddress: string): Promise<TokenBalance[]>
sdk.token.getAllTokenAccounts(walletAddress: string): Promise<TokenAccountInfo[]>
interface TokenBalance {
mint: string;
symbol: string;
name: string;
decimals: number;
balance: string; // raw lamport/token amount
uiBalance: number; // human-readable
}
sdk.registry.resolveToken(symbolOrMint: string): Promise<TokenMetadata | undefined>
sdk.registry.resolveTokens(queries: string[]): Promise<Map<string, TokenMetadata>>
sdk.registry.syncTokenCache(): Promise<number>
Resolves token symbols (e.g. "SOL", "USDC") or mint addresses to full metadata. Uses a well-known token list first, then falls back to Jupiter's token API.
const sol = await sdk.registry.resolveToken('SOL');
console.log(sol?.mint); // 'So11111111111111111111111111111111'
console.log(sol?.decimals); // 9
sdk.lists.getCategories(): CategoryInfo[]
sdk.lists.browseTokens(category: string, opts?: {
interval?: string;
limit?: number;
}): Promise<TokenListEntry[]>
Browse curated token lists (trending, new, top volume, etc.) from the Jupiter Token API.
sdk.swap.getQuote(
inputSymbol: string,
outputSymbol: string,
amount: number,
opts?: { slippageBps?: number; router?: string },
): Promise<SwapQuote>
sdk.swap.executeSwap(
inputSymbol: string,
outputSymbol: string,
amount: number,
walletName: string,
opts?: { slippageBps?: number; skipPreflight?: boolean; router?: string },
): Promise<SwapResult>
Uses a pluggable router system. The default best router queries Jupiter and DFlow in parallel and picks the best quote. Force a specific router with opts.router.
// Quote only
const quote = await sdk.swap.getQuote('SOL', 'USDC', 1.0);
console.log(`${quote.inputUiAmount} ${quote.inputSymbol} → ${quote.outputUiAmount} ${quote.outputSymbol}`);
// Execute
const result = await sdk.swap.executeSwap('SOL', 'USDC', 1.0, 'main');
console.log(result.explorerUrl);
sdk.stake.getStakeAccounts(walletAddress: string): Promise<StakeAccountInfo[]>
sdk.stake.createAndDelegateStake(
walletName: string,
amountSol: number,
validatorVote?: string, // defaults to Solana Compass
): Promise<CreateStakeResult>
sdk.stake.withdrawStake(
walletName: string,
stakeAccountAddress: string,
amountSol?: number, // partial withdraw
force?: boolean, // deactivate if still active
): Promise<WithdrawStakeResult>
sdk.stake.claimMev(
walletName: string,
walletAddress: string,
stakeAccountAddress?: string, // specific account, or all
withdrawOnly?: boolean, // withdraw instead of compound
): Promise<ClaimMevResult[]>
Native Solana staking. Creates stake account, funds it, and delegates — all in a single transaction.
sdk.lend.getRates(tokens?: string[], protocol?: string): Promise<RatesResult>
sdk.lend.getPositions(walletAddress: string, protocol?: string): Promise<LendingPosition[]>
sdk.lend.deposit(walletName: string, token: string, amount: number, protocol?: string): Promise<LendWriteResult>
sdk.lend.withdraw(walletName: string, token: string, amount: number, protocol?: string): Promise<LendWriteResult>
sdk.lend.borrow(walletName: string, token: string, amount: number, collateral: string, protocol?: string): Promise<LendWriteResult>
sdk.lend.repay(walletName: string, token: string, amount: number, protocol?: string): Promise<LendWriteResult>
sdk.lend.registerProvider(provider: LendProvider): void
Multi-protocol lending across Kamino, MarginFi, Drift, Jupiter Lend, and Loopscale. Reads query all providers in parallel. Writes target a specific protocol (or the config default).
interface LendingRate {
protocol: string;
token: string;
mint: string;
depositApy: number;
borrowApy: number;
totalDeposited: number;
totalBorrowed: number;
utilizationPct: number;
}
interface LendingPosition {
protocol: string;
token: string;
mint: string;
type: 'deposit' | 'borrow';
amount: number;
valueUsd: number;
apy: number;
healthFactor?: number;
}
sdk.earn.getVaults(tokens?: string[], protocol?: string, sort?: 'apy' | 'tvl'): Promise<VaultsResult>
sdk.earn.getPositions(walletAddress: string, protocol?: string): Promise<EarnPosition[]>
sdk.earn.deposit(walletName: string, token: string, amount: number, protocol?: string, vaultId?: string): Promise<EarnWriteResult>
sdk.earn.withdraw(walletName: string, token: string, amount: number, protocol?: string): Promise<EarnWriteResult>
sdk.earn.registerProvider(provider: EarnProvider): void
Yield vaults across Kamino and Loopscale. Same multi-provider pattern as lend.
sdk.order.createDca(
totalAmount: number, inputSymbol: string, outputSymbol: string,
walletName: string, opts: { interval: string; count: number },
): Promise<DcaCreateResult>
sdk.order.listDca(walletAddress: string, opts?: { status?: string }): Promise<DcaOrder[]>
sdk.order.cancelDca(orderKey: string, walletName: string): Promise<string>
sdk.order.createLimit(
inputAmount: number, inputSymbol: string, outputSymbol: string,
walletName: string, opts: { targetPrice: number; slippageBps?: number; expiredAt?: number },
): Promise<LimitCreateResult>
sdk.order.listLimit(walletAddress: string, opts?: { status?: string }): Promise<LimitOrder[]>
sdk.order.cancelLimit(orderKey: string, walletName: string): Promise<string>
sdk.order.getOpenOrders(walletAddress: string): Promise<OpenOrderPosition[]>
DCA and limit orders via Jupiter's Recurring and Trigger APIs.
sdk.predict.listEvents(opts?): Promise<PredictionEvent[]>
sdk.predict.searchEvents(query: string, limit?: number): Promise<PredictionEvent[]>
sdk.predict.getEvent(eventId: string): Promise<PredictionEvent>
sdk.predict.getMarket(marketId: string): Promise<PredictionMarket>
sdk.predict.getOrderbook(marketId: string): Promise<PredictionOrderbook | null>
sdk.predict.buy(walletName: string, marketId: string, isYes: boolean, amountUsd: number, maxPrice?: number): Promise<PredictionOrderResult>
sdk.predict.sell(walletName: string, positionPubkey: string, minPrice?: number): Promise<PredictionCloseResult>
sdk.predict.claim(walletName: string, positionPubkey: string): Promise<PredictionClaimResult>
sdk.predict.getPositions(walletAddress: string): Promise<PredictionPosition[]>
sdk.predict.getHistory(walletAddress: string, limit?: number): Promise<PredictionHistoryEntry[]>
sdk.predict.registerProvider(provider: PredictProvider): void
sdk.portfolio.get(wallets: { name: string; address: string }[]): Promise<PortfolioReport>
sdk.portfolio.takeSnapshot(wallets: { name: string; address: string }[], label?: string): Promise<SnapshotResult>
sdk.portfolio.autoSnapshot(report: PortfolioReport): Promise<boolean>
sdk.portfolio.compareToSnapshot(wallets: { name: string; address: string }[], snapshotId?: number): Promise<CompareResult>
sdk.portfolio.getPnl(wallets: { name: string; address: string }[], sinceId?: number): Promise<CompareResult>
Aggregates positions across all services (tokens, staking, lending, earn, orders, prediction markets) into a unified view. Snapshots and P&L require the optional CacheProvider snapshot methods.
interface PortfolioReport {
wallets: { name: string; address: string }[];
positions: PortfolioPosition[];
allocation: AllocationEntry[];
totalValueUsd: number;
claimableMev: number;
lastSnapshot?: { id: number; label?: string; ago: string };
}
sdk.x402.fetch(url: string, opts?: X402FetchOptions): Promise<X402FetchResult>
HTTP client for the x402 payment protocol. Automatically detects 402 Payment Required responses, pays via USDC transfer, and retries with proof of payment.
sdk.onramp.getUrl(params: {
walletAddress: string;
amount?: number;
currency?: string;
provider?: string;
}): string
sdk.onramp.listProviders(): string[]
Generates fiat onramp URLs for purchasing SOL.
sdk.tx.buildAndSendTransaction(
instructions: Instruction[],
payer: TransactionSigner,
opts?: BuildAndSendOpts,
): Promise<SendResult>
sdk.tx.sendEncodedTransaction(
encodedTx: string,
opts?: SendEncodedOpts,
): Promise<SendResult>
Handles blockhash fetching, signing, encoding, sending, confirmation polling, and retry with error classification. All write services use this internally.
interface SendResult {
signature: string;
status: string;
attempts: number;
elapsed_ms: number;
explorerUrl: string;
}
Also exported as standalone utilities:
import { classifyError, ErrorClass, injectSigners } from '@solana-compass/sdk';
classifyError(err); // ErrorClass.RETRYABLE_TRANSIENT | TERMINAL_PROGRAM | etc.
injectSigners(instructions, [signer]); // attach signers to instruction accounts
Pure functions re-exported from the SDK:
import {
// Solana helpers
isValidAddress, lamportsToSol, solToLamports, uiToTokenAmount,
tokenAmountToUi, shortenAddress, explorerUrl, SOL_MINT, SOL_DECIMALS,
// Token list
WELL_KNOWN_TOKENS, getWellKnownBySymbol, getWellKnownByMint,
// Formatting
fmtPrice, timed,
// Retry / rate limiting
withRetry, RateLimiter, isRetryableHttpError,
// Constants
SOLANA_COMPASS_VOTE,
PROTOCOL_NAMES, // lend protocol names
EARN_PROTOCOL_NAMES, // earn protocol names
PREDICT_CATEGORIES, // prediction market categories
parseInterval, // 'day' → seconds
// Compat layers (for v1 SDK bridging)
getKaminoRpc, kAddress, kSigner, getCurrentSlot,
getMarginFiV1Connection, MarginFiDummyWallet,
getDriftV1Connection, DriftDummyWallet,
} from '@solana-compass/sdk';
For browser apps, you'll want to keep API keys server-side. The SDK supports URL overrides for all external APIs:
const sdk = createSolSdk({
rpc,
signer: { ... },
config: new InMemoryConfig({
'api.jupiterBaseUrl': 'https://mysite.com/api/jupiter',
'api.dflowBaseUrl': 'https://mysite.com/api/dflow',
'api.loopscaleBaseUrl': 'https://mysite.com/api/loopscale',
}),
});
When a base URL is set, all API calls for that service go through your proxy. Your proxy adds the API key server-side so it never appears in client code.
Register your own lend, earn, or predict providers:
import type { LendProvider, EarnProvider, PredictProvider } from '@solana-compass/sdk';
class MyLendProvider implements LendProvider {
name = 'my-protocol';
capabilities = { deposit: true, withdraw: true, borrow: false, repay: false };
async getRates(tokens?) { ... }
async getPositions(walletAddress) { ... }
async deposit(walletName, token, amount) { ... }
async withdraw(walletName, token, amount) { ... }
}
sdk.lend.registerProvider(new MyLendProvider());
MIT
FAQs
Solana SDK for building apps with Jupiter, Kamino, MarginFi, Drift, Loopscale, and more
We found that @solana-compass/sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
A new federal program will let vetted U.S. cybersecurity firms help investigate and disrupt foreign cybercrime groups under government direction.

Research
/Security News
The campaign amassed more than 75,000 installs by targeting Russian-speaking users seeking access to blocked services.

Company News
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.