@solana-compass/sdk
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.
Install
npm install @solana-compass/sdk @solana/kit
Optional peer dependencies for DeFi protocol support:
npm install @kamino-finance/klend-sdk @coral-xyz/anchor
npm install @mrgnlabs/marginfi-client-v2
npm install @drift-labs/sdk
If these aren't installed, those providers are silently skipped.
Quick Start
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',
signer: {
getSigner: async (id) => walletAdapter,
getAddress: async (id) => walletAdapter.address,
},
});
await registerDefaultProviders(sdk);
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);
const result = await sdk.swap.executeSwap('SOL', 'USDC', 1.0, 'main');
const report = await sdk.portfolio.get([
{ name: 'main', address: 'YourWa11etAddress...' },
]);
Architecture
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 │
└─────────────────────────────────────┘
SolSdk Interface
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;
}
DI Interfaces
SolSdkOptions
interface SolSdkOptions {
rpc: Rpc<SolanaRpcApi>;
rpcUrl?: string;
logger?: Logger;
config?: ConfigProvider;
cache?: CacheProvider;
txLogger?: TransactionLogger;
signer: SignerProvider;
analyticsInstruction?: () => Instruction | null;
}
Logger
interface Logger {
verbose(msg: string): void;
warn(msg: string): void;
}
ConfigProvider
interface ConfigProvider {
get(key: string): unknown;
}
Config keys used by the SDK:
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 |
CacheProvider
interface CacheProvider {
getLatestPrice(mint: string): PriceCacheEntry | undefined;
insertPrice(mint: string, priceUsd: number, source: string): void;
getTokenByMint(mint: string): TokenCacheEntry | undefined;
getTokenBySymbol(symbol: string): TokenCacheEntry[];
isTokenCacheStale(mint: string, ttlHours?: number): boolean;
upsertTokenBatch(tokens: TokenUpsert[]): void;
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;
createSnapshot?(label?: string): number;
insertSnapshotEntry?(entry: SnapshotEntryRow): void;
getSnapshotEntries?(snapshotId: number): SnapshotEntryRow[];
getSnapshot?(id: number): SnapshotRow | undefined;
getLatestSnapshot?(): SnapshotRow | undefined;
listSnapshots?(limit: number): SnapshotRow[];
}
SignerProvider
interface SignerProvider {
getSigner(identifier: string): Promise<TransactionSigner>;
getAddress(identifier: string): Promise<string>;
getRawBytes?(identifier: string): Uint8Array;
}
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.
TransactionLogger
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;
}
Default Implementations
The SDK ships with zero-dependency defaults so you can get started without implementing every interface:
import {
NoopLogger,
NoopTransactionLogger,
InMemoryConfig,
InMemoryCache,
} from '@solana-compass/sdk';
const config = new InMemoryConfig({
'api.jupiterBaseUrl': 'https://mysite.com/api/jupiter',
'defaults.slippage': 100,
});
config.get('api.jupiterBaseUrl');
config.get('defaults.slippage');
Service Reference
Price
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);
Token
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;
uiBalance: number;
}
Token Registry
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);
console.log(sol?.decimals);
Token Lists
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.
Swap
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.
const quote = await sdk.swap.getQuote('SOL', 'USDC', 1.0);
console.log(`${quote.inputUiAmount} ${quote.inputSymbol} → ${quote.outputUiAmount} ${quote.outputSymbol}`);
const result = await sdk.swap.executeSwap('SOL', 'USDC', 1.0, 'main');
console.log(result.explorerUrl);
Stake
sdk.stake.getStakeAccounts(walletAddress: string): Promise<StakeAccountInfo[]>
sdk.stake.createAndDelegateStake(
walletName: string,
amountSol: number,
validatorVote?: string,
): Promise<CreateStakeResult>
sdk.stake.withdrawStake(
walletName: string,
stakeAccountAddress: string,
amountSol?: number,
force?: boolean,
): Promise<WithdrawStakeResult>
sdk.stake.claimMev(
walletName: string,
walletAddress: string,
stakeAccountAddress?: string,
withdrawOnly?: boolean,
): Promise<ClaimMevResult[]>
Native Solana staking. Creates stake account, funds it, and delegates — all in a single transaction.
Lend
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;
}
Earn
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.
Orders (DCA & Limit)
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.
Predict (Prediction Markets)
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
Portfolio
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 };
}
X402 (Pay-Per-Request)
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.
Onramp
sdk.onramp.getUrl(params: {
walletAddress: string;
amount?: number;
currency?: string;
provider?: string;
}): string
sdk.onramp.listProviders(): string[]
Generates fiat onramp URLs for purchasing SOL.
Transaction
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);
injectSigners(instructions, [signer]);
Utilities
Pure functions re-exported from the SDK:
import {
isValidAddress, lamportsToSol, solToLamports, uiToTokenAmount,
tokenAmountToUi, shortenAddress, explorerUrl, SOL_MINT, SOL_DECIMALS,
WELL_KNOWN_TOKENS, getWellKnownBySymbol, getWellKnownByMint,
fmtPrice, timed,
withRetry, RateLimiter, isRetryableHttpError,
SOLANA_COMPASS_VOTE,
PROTOCOL_NAMES,
EARN_PROTOCOL_NAMES,
PREDICT_CATEGORIES,
parseInterval,
getKaminoRpc, kAddress, kSigner, getCurrentSlot,
getMarginFiV1Connection, MarginFiDummyWallet,
getDriftV1Connection, DriftDummyWallet,
} from '@solana-compass/sdk';
API Proxy Support
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.
Custom Providers
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());
License
MIT