Getting started with the SDK
@hobba-io/core is the Hobba SDK - the exact on-chain logic behind
app.hobba.io, published as an npm package. The app,
the embeddable widget and partner integrations all
run this same code, so an SDK integration behaves identically to Hobba’s own
UI: same lender routing, same account resolution, same math.
Use the SDK when you want Hobba under your own UI. If you just want a drop-in component, use the widget instead.
Install
Section titled “Install”npm install @hobba-io/core @coral-xyz/anchor @solana/web3.js @solana/spl-token bn.jsThe Solana stack is declared as peer dependencies so your bundle holds
exactly one copy of @solana/web3.js / bn.js (two copies break
instanceof PublicKey checks).
Bundler required. The package ships ESM targeted at bundler consumers (Next.js/webpack, Vite, esbuild/tsup). It is not consumable from bare Node ESM without a bundler or a loader such as
tsx.
Select the cluster
Section titled “Select the cluster”The program id baked into PROGRAM_ID is chosen at build time from env
variables your bundler must define:
| Env | Program |
|---|---|
NEXT_PUBLIC_MAINNET=true | Mainnet - Hobbakk1LmW2DhE4nAKnCeU1iK7V7pKHTq3ihaE2i8qG |
NEXT_PUBLIC_STAGING=true | Staging - soBBAHNhFyWF4QBCK9BJLVkX6d6NZt37ozNaPV5aq3H |
| (neither) | Local development |
Next.js inlines NEXT_PUBLIC_* automatically; with other bundlers, define
process.env.NEXT_PUBLIC_MAINNET yourself. For a production integration,
set NEXT_PUBLIC_MAINNET=true.
The three things every call takes
Section titled “The three things every call takes”Almost every SDK function takes the same trio:
import { Connection } from "@solana/web3.js";
const connection = new Connection(RPC_URL); // 1. your RPC connection
const signer = wallet; // 2. a HobbaSigner
const apiBaseUrl = "https://app.hobba.io"; // 3. the Hobba backend-
connection- aweb3.jsConnectionused for reads, simulation and submission. Use your own RPC provider. -
signer- anything matchingHobbaSigner:interface HobbaSigner {publicKey: PublicKey | null;signTransaction?: <T extends Transaction | VersionedTransaction>(tx: T) => Promise<T>;}Both wallet-adapter’s
WalletContextStateand a raw injected provider (window.solana,window.solflare) satisfy this structurally - no cast needed. -
apiBaseUrl- the Hobba backend serving helper routes the SDK calls (/api/juplend-operate,/api/lut,/api/rates, prices, …). Point it athttps://app.hobba.io. The same origin allowlist as the widget applies to browser calls - see Backend & CORS for getting your origin allowlisted or proxying.
Prerequisite: wallet allowlist
Section titled “Prerequisite: wallet allowlist”A wallet must be allowlisted once before it can transact - the same
referral-redeem step the widget needs. A
non-allowlisted wallet’s transactions fail; the SDK surfaces this as the
NOT_ALLOWLISTED error code (see below).
First transaction
Section titled “First transaction”executeDepositBorrow is the one-call onboarding flow - it initializes
first-time users, routes to the best lender, and executes the deposit and
borrow:
import { executeDepositBorrow, parseCollateral, parseUsdc, CBBTC_MINT,} from "@hobba-io/core";
const { depositSig, borrowSig, lender } = await executeDepositBorrow({ connection, signer, collateralMint: CBBTC_MINT, // or SOL_MINT depositAmount: parseCollateral("0.01", CBBTC_MINT), borrowAmount: parseUsdc("250"), apiBaseUrl: "https://app.hobba.io", onStep: (s) => console.log(s), // progress for your UI});See Deposit & borrow for the full breakdown, then the per-action pages - Deposit, Borrow, Repay, Withdraw - for managing the position.
Amounts are base units
Section titled “Amounts are base units”All amounts cross the SDK boundary as BN base units - never floats:
| Asset | Base unit | Helper |
|---|---|---|
| cbBTC | 1e8 (satoshis) | parseCollateral("0.01", CBBTC_MINT) |
| SOL | 1e9 (lamports) | parseCollateral("1.5", SOL_MINT) |
| USDC | 1e6 (micro-USDC) | parseUsdc("250") |
Formatting helpers (formatUsdcShort, formatCollateralShort, …) convert
back for display.
Error handling
Section titled “Error handling”The execute flows throw plain Errors; map them onto stable codes with
classifyError:
import { classifyError } from "@hobba-io/core";
try { await executeAction({ /* … */ });} catch (e) { const err = classifyError(e); switch (err.code) { case "WALLET_NOT_CONNECTED": /* prompt connect */ break; case "NOT_ALLOWLISTED": /* show allowlist instructions */ break; case "USER_REJECTED": /* user declined in wallet */ break; case "SIMULATION_FAILED": /* surface err.message */ break; case "SEND_FAILED": /* retryable network/blockhash issue */ break; default: /* UNKNOWN - surface err.message */ }}What’s in the box
Section titled “What’s in the box”| Function | Purpose |
|---|---|
executeDepositBorrow | One-call onboarding: init + deposit + borrow |
executeAction | Manage an existing position: deposit / borrow / repay / withdraw |
getPosition | Headless position read (collateral, debt, lender, vault) |
getPositionMetrics | Display-ready dashboard metrics - the exact values app.hobba.io renders |
getQuote | Projection math for calculators and confirm modals |
getLimits | Input bounds: max borrow / withdraw / repay, min deposit |
parseCollateral, parseUsdc, format* | Amount conversion |
classifyError | Stable error codes |
CBBTC_MINT, SOL_MINT, USDC_MINT, PROGRAM_ID, … | Constants |
Additional modules (PDAs, market config, IDL, oracle helpers) are importable
by subpath - e.g. @hobba-io/core/pdas, @hobba-io/core/idl. The
@hobba-io/core/rates subpath is server-only.