@solana/breeze-sdk
v2.1.0
Published
TypeScript SDK for Breeze API reads and Solana transaction construction, with an optional Solana Kit client plugin.
Maintainers
Readme
@solana/breeze-sdk
TypeScript SDK for the Breeze API — user yield tracking, balance queries, and deposit, withdraw, and close-account transaction building on Solana.
- Dependency-free core SDK — uses the built-in
fetch. - Optional trusted-backend Solana Kit plugin for planning, signing, sending, and confirming Breeze transactions.
- Dual ESM + CommonJS builds with bundled type definitions.
- Fully typed requests and responses.
Installation
npm install @solana/breeze-sdkThe root entry requires Node.js 18+ (for global fetch). It can be bundled for
modern browsers, but authenticated Breeze API calls belong on a trusted backend
so the API key is never shipped to users.
For the optional Solana Kit transaction integration:
npm install @solana/breeze-sdk @solana/kit \
@solana/kit-plugin-rpc @solana/kit-plugin-signerThe @solana/breeze-sdk/kit entry follows Kit 7's Node.js requirement
(20.18+). The dependency-free root entry remains compatible with Node.js 18+.
Quick start
import { BreezeSDK } from '@solana/breeze-sdk';
const apiKey = process.env.BREEZE_API_KEY;
if (!apiKey) throw new Error('BREEZE_API_KEY is required');
const sdk = new BreezeSDK({
apiKey,
baseUrl: 'https://api.breeze.baby/', // optional (this is the default)
timeout: 30000, // optional, milliseconds (default: 30000)
});
const userYield = await sdk.getUserYield({
userId: '7EcSQsLNbkorQr3igFzfEwFJoPEUgB3NfmDTAigEcoSY',
});
for (const entry of userYield.data) {
console.log(`${entry.fund_name}: earned ${entry.yield_earned} (APY ${entry.apy})`);
}Choose a transaction integration
Keep the Breeze API key on a trusted server. The two supported transaction patterns have different trust boundaries:
- Trusted backend or custodial signer: install the Kit plugin and finish a
client.breeze.instructions.<op>()builder with.planTransaction()or.sendTransaction(). The same process can access the Breeze API key and an HSM, custody adapter, or other backend signer. - Browser or mobile wallet: have your backend call the core SDK instruction methods, then return the unsigned instruction payload to the wallet application. The browser builds and presents the transaction for approval; neither the Breeze API key nor signing-key material belongs in browser code.
Trusted backend: Solana Kit
Install the Breeze plugin after the signer and RPC plugins. When one backend
signer is both the transaction identity and fee payer, Kit's signer() plugin
installs both capabilities:
import { createClient } from '@solana/kit';
import { solanaMainnetRpc } from '@solana/kit-plugin-rpc';
import { signer } from '@solana/kit-plugin-signer';
import { breeze } from '@solana/breeze-sdk/kit';
const apiKey = process.env.BREEZE_API_KEY;
const rpcUrl = process.env.SOLANA_RPC_URL;
if (!apiKey || !rpcUrl) {
throw new Error('BREEZE_API_KEY and SOLANA_RPC_URL are required');
}
// With solanaMainnetRpc, use a Kit TransactionPartialSigner or
// TransactionModifyingSigner supplied by trusted key management.
const client = createClient()
.use(signer(backendSigner))
.use(solanaMainnetRpc({ rpcUrl }))
.use(breeze({ apiKey }));
const result = await client.breeze.instructions
.deposit({
strategyId: 'your-strategy-id',
baseAsset: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
amount: 100, // asset's smallest unit
})
.sendTransaction();
console.log('Confirmed signature:', result.context.signature);instructions.deposit(...).sendTransaction() derives userKey and payerKey,
fetches and validates Breeze instructions, fetches the required lookup table,
converts eligible account metas to lookup-table metas, lets the planner size and
build a version-0 message, then delegates signing, submission, and confirmation
to the installed executor. instructions.withdraw(...) follows the same flow;
close-account planning does not require a lookup table.
With the official solanaMainnetRpc executor, identity and payer must implement
either Kit's TransactionPartialSigner (signTransactions) or
TransactionModifyingSigner (modifyAndSignTransactions). A sending-only
TransactionSigner is insufficient because the executor signs the planned
message with signTransactionMessageWithSigners. A legacy @solana/web3.js
Keypair or wallet-adapter object is not accepted directly; adapt it to a
compatible Kit signer first. The generic Breeze plugin can also compose with a
custom planner/executor that supports its installed signer capabilities. When
fee payer and transaction identity differ, install them explicitly:
import { identity, payer } from '@solana/kit-plugin-signer';
const client = createClient()
.use(payer(feePayerSigner))
.use(identity(identitySigner))
.use(solanaMainnetRpc({ rpcUrl }))
.use(breeze({ apiKey }));The plugin matches required signer account metas by address against the installed identity and payer. It fails before submission when a required signer is unavailable.
Browser wallet: backend instruction handoff
The browser sends public addresses and the requested operation to your backend. The backend authenticates the request, calls Breeze with its server-side API key, and returns the unsigned instruction response:
import { BreezeSDK } from '@solana/breeze-sdk';
const apiKey = process.env.BREEZE_API_KEY;
if (!apiKey) throw new Error('BREEZE_API_KEY is required');
const sdk = new BreezeSDK({ apiKey });
const instructionResponse = await sdk.getDepositInstructions({
strategyId,
baseAsset,
amount,
userKey: walletAddress,
payerKey: walletAddress,
});
// Return only the unsigned instruction response to the wallet application.
return instructionResponse;The wallet application converts the byte-array instruction payload, resolves
the returned address lookup table, builds a version-0 transaction, and asks the
connected wallet to review and sign it. The prebuilt
createDepositTransaction/createWithdrawTransaction methods are also
available when a serialized unsigned transaction is a better handoff format.
Configuration
new BreezeSDK(config) accepts a BreezeSDKConfig:
| Field | Type | Required | Default |
|---|---|---|---|
| apiKey | string | ✅ | — |
| baseUrl | string | | https://api.breeze.baby/ |
| timeout | number (ms) | | 30000 |
breeze(config) accepts the same fields. API request timeouts use timeout;
the optional { abortSignal } argument on Kit .planTransaction() and
.sendTransaction() terminals is forwarded to lookup-table fetching, planning,
sending, and confirmation.
client.breeze methods
| Method | Options | Result |
|---|---|---|
| getUserYield | { userId, fundId?, page?, limit? } | Paginated yield records |
| getUserBalances | { userId, asset?, sortBy?, sortOrder?, page?, limit? } | Paginated wallet balances |
| getBreezeBalances | { userId, strategyId, asset?, sortBy?, sortOrder?, page?, limit? } | Strategy-scoped balances |
| getStrategyInfo | strategyId | Strategy assets and APY data |
| getHealth | none | API health response |
| updateApiKey | apiKey | Replaces the API key used by later requests |
| instructions.deposit(options) | { strategyId, baseAsset, userTokenAccount?, amount } or { ..., all: true } | Pending plan → .planTransaction(config?) / .sendTransaction(config?) |
| instructions.withdraw(options) | Deposit fields plus createWsolAta?, unwrapWsolAta?, detectWsolAta?, excludeFees? | Pending plan → .planTransaction(config?) / .sendTransaction(config?) |
| instructions.closeUserAccount(options) | { userAccount, fundsRecipient?, userTokenAccount? } or { strategyId, mint, fundsRecipient?, userTokenAccount? } | Pending plan → .planTransaction(config?) / .sendTransaction(config?) |
Each instructions.<op>() builder resolves lazily on the terminal call:
.planTransaction(config?) returns the planned Kit message and
.sendTransaction(config?) returns Kit's confirmed transaction-plan result;
read its signature from result.context.signature. Both terminals accept an
optional { abortSignal }. amount is denominated in the asset's smallest unit
and is mutually exclusive with all: true. Identity and payer addresses are
always derived from the Kit client. For seed-based account closure, the identity
address is also used as userPubkey. Deposit and withdrawal enforce a version-0
message because their address lookup-table compression is not compatible with
legacy or version-1 messages; close-account uses the planner's selected version.
API reference
Reads
getUserYield(options) — paginated yield data.
const res = await sdk.getUserYield({
userId: '7EcSQsLNbkorQr3igFzfEwFJoPEUgB3NfmDTAigEcoSY',
fundId: '8pfa...', // optional filter
page: 1, // optional
limit: 10, // optional
});
// res.data: UserYieldEntry[] res.meta: PaginationMetagetUserBalances(options) — token balances with optional filtering/sorting.
const res = await sdk.getUserBalances({
userId: '7EcSQsLNbkorQr3igFzfEwFJoPEUgB3NfmDTAigEcoSY',
asset: 'USDC', // optional
sortBy: 'balance', // optional
sortOrder: 'desc', // optional
page: 1, // optional
limit: 10, // optional
});
// Each entry has `yield_balance: YieldBalance | null` (null when there is no yield position).getBreezeBalances(options) — strategy-scoped balances (strategyId required).
const res = await sdk.getBreezeBalances({
userId: 'your-user-id',
strategyId: 'your-strategy-id', // required
asset: 'USDC', // optional
sortBy: 'balance', // optional
sortOrder: 'desc', // optional
page: 1, // optional
limit: 10, // optional
});getStrategyInfo(strategyId) — supported assets and APY data.
const info = await sdk.getStrategyInfo('your-strategy-id');
console.log(info.strategy_name, info.apy, info.apy_per_asset);Transactions
createDepositTransaction and createWithdrawTransaction resolve to a
base64-encoded serialized transaction string. Identify the position with
strategyId + baseAsset (mint address). Failed requests throw
BreezeApiError; fundId is retained in the TypeScript type for compatibility
but is rejected by the current API. For new signing and sending integrations,
prefer the Kit plugin only in a trusted signing service. These methods remain
available for browser-wallet and other unsigned-transaction handoffs.
const depositTx = await sdk.createDepositTransaction({
strategyId: 'your-strategy-id',
baseAsset: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // token mint (e.g. USDC)
amount: 100,
userKey: '7EcSQsLNbkorQr3igFzfEwFJoPEUgB3NfmDTAigEcoSY',
payerKey: '...', // optional
});
const withdrawTx = await sdk.createWithdrawTransaction({
strategyId: 'your-strategy-id',
baseAsset: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
amount: 50,
userKey: '7EcSQsLNbkorQr3igFzfEwFJoPEUgB3NfmDTAigEcoSY',
// withdraw-only options: createWsolAta, unwrapWsolAta, detectWsolAta, excludeFees
});Instructions
Prefer instructions when composing your own transaction.
const deposit = await sdk.getDepositInstructions({ /* same options as deposit tx */ });
// deposit.deposit_instructions: SerializedInstruction[] deposit.lookup_table?: string
const withdraw = await sdk.getWithdrawInstructions({ /* same options as withdraw tx */ });
// withdraw.withdraw_instructions: SerializedInstruction[] withdraw.lookup_table?: stringClose user account
Identify the account with userPubkey + strategyId + mint (or a resolved
userAccount).
const closeTx = await sdk.createCloseUserAccountTransaction({
userPubkey: 'your-user-pubkey',
strategyId: 'your-strategy-id',
mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
payer: '...', // optional
fundsRecipient: '...', // optional
userTokenAccount: '...',// optional
});
const closeIx = await sdk.getCloseUserAccountInstructions({
userPubkey: 'your-user-pubkey',
strategyId: 'your-strategy-id',
mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
});
// closeIx.close_user_fund_instructions: SerializedInstruction[]Misc
await sdk.getHealth(); // "OK"
sdk.updateApiKey(rotatedApiKey); // rotate to a value loaded from trusted config
sdk.getApiClient(); // underlying ApiClient (advanced use)Planning before sending
Finish an instructions.deposit, instructions.withdraw, or
instructions.closeUserAccount builder with .planTransaction() when an
application needs to inspect or further compose the Kit transaction message:
const message = await client.breeze.instructions
.deposit({
strategyId: 'your-strategy-id',
baseAsset: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
amount: 100,
})
.planTransaction();
// The message contains live signer objects plus Breeze lookup-table references.
// Keep it in this trusted process and send through the same Kit client.
const result = await client.sendTransaction(message);
console.log('Confirmed signature:', result.context.signature);Planned messages are not a browser-wallet serialization format: they retain the installed backend signer objects. Use the backend instruction handoff above for user-controlled wallets.
The RPC plugin controls compute estimation, priority fees, preflight, and
confirmation. Configure those concerns in
solanaMainnetRpc({ transactionConfig, ... }); the Breeze plugin does not add a
competing transaction policy.
Error handling
Every failed request throws a BreezeApiError:
import { BreezeApiError } from '@solana/breeze-sdk';
try {
await sdk.getStrategyInfo('bad-id');
} catch (err) {
if (err instanceof BreezeApiError) {
console.error(err.message, err.status, err.code, err.response);
}
}| Field | Type | Notes |
|---|---|---|
| message | string | Human-readable message (from the API when available). |
| status | number \| undefined | HTTP status code. |
| code | string \| undefined | TIMEOUT, NETWORK_ERROR, PARSE_ERROR, or an API code. |
| response | unknown | Parsed error body, or raw text when it was not JSON. |
Breeze-specific instruction and precondition failures throw BreezeKitError
from @solana/breeze-sdk/kit with one of four codes:
| Code | Meaning |
|---|---|
| INVALID_SERIALIZED_INSTRUCTION | The API instruction did not contain valid byte arrays, account metadata, or an address. |
| MISSING_ADDRESS_LOOKUP_TABLE | A deposit or withdrawal instruction response omitted its required lookup table. |
| MISSING_TRANSACTION_SIGNER | An account meta requires a signer whose address does not match the installed identity or payer. |
| UNSUPPORTED_TRANSACTION_VERSION | The configured planner produced a non-version-0 message for a deposit or withdrawal, which requires version 0 for lookup-table compression. |
API failures still surface as BreezeApiError. Errors raised by Kit's address
parsing, lookup-table RPC, transaction planner, sender, or confirmation logic
remain their native Kit/RPC errors; the Breeze plugin does not wrap them.
Low-level Kit exports
The @solana/breeze-sdk/kit entry also exposes the primitives behind the fluent
client for custom Kit composition:
| Export | Purpose |
|---|---|
| breeze(config) | Installs the typed client.breeze namespace. |
| getKitInstruction(serializedInstruction, signers) | Validates a Breeze byte-array instruction, converts it to a Kit instruction, and attaches matching TransactionSigner objects. |
| BreezeKitError | Identifies Breeze-specific conversion and planning precondition failures through its typed code. |
getKitInstruction does not fetch lookup tables, plan, sign, or submit a
transaction. It throws BreezeKitError when the wire instruction is malformed or
a required signer is absent. Prefer the fluent builders unless you are assembling
a custom Kit instruction plan.
Named Kit types include BreezeKitErrorCode, BreezePluginConfig,
BreezePluginRequirements, BreezeKitClient, BreezeInstructionBuilders,
BreezeInstructionPlan, BreezeTransactionConfig, and the deposit, withdrawal,
and close-account option types.
Low-level API
The ApiClient and each endpoint function are exported for advanced use. The
client holds the API key and attaches the x-api-key header automatically; every
endpoint function takes (client, options):
import { ApiClient, getUserYield, getTransactionForDeposit } from '@solana/breeze-sdk';
const apiKey = process.env.BREEZE_API_KEY;
if (!apiKey) throw new Error('BREEZE_API_KEY is required');
const client = new ApiClient({
apiKey,
baseUrl: 'https://api.breeze.baby/', // optional
});
const userYield = await getUserYield(client, { userId: 'user_id' });
const depositTx = await getTransactionForDeposit(client, {
strategyId: 'strategy_id',
baseAsset: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
amount: 100,
userKey: 'user_key',
});Exported functions: getUserYield, getUserBalances, getBreezeBalances,
getStrategyInfo, getTransactionForDeposit, getInstructionsForDeposit,
getTransactionForWithdraw, getInstructionsForWithdraw,
getTransactionForCloseUserAccount, getInstructionsForCloseUserAccount,
getHealth.
Types
All request options and responses are exported as named types, e.g. UserYield,
UserBalances, BreezeBalancesResponse, BreezeBalance, StrategyInfo,
DepositOptions, WithdrawOptions, CloseUserAccountOptions,
InstructionsForDeposit, InstructionsForWithdraw, SerializedTransaction, and
SerializedInstruction. SerializedInstructionData documents the API's
byte-array program_id, account metadata, and instruction data, while
SerializedInstruction retains its legacy opaque-record contract; the Kit
entry requires, validates, and converts the wire fields before planning.
CloseUserAccountOptions requires exactly one identity form: userAccount, or
the complete userPubkey + strategyId + mint tuple used to derive it.
API endpoints
| Method | Endpoint | SDK method |
|---|---|---|
| GET | /user-yield/{user_id} | getUserYield |
| GET | /user-balances/{user_id} | getUserBalances |
| GET | /breeze-balances/{user_id} | getBreezeBalances |
| GET | /strategy-info/{strategy_id} | getStrategyInfo |
| POST | /deposit/tx | createDepositTransaction |
| POST | /deposit/ix | getDepositInstructions |
| POST | /withdraw/tx | createWithdrawTransaction |
| POST | /withdraw/ix | getWithdrawInstructions |
| POST | /close-user-account/tx | createCloseUserAccountTransaction |
| POST | /close-user-account/ix | getCloseUserAccountInstructions |
| GET | /health | getHealth |
Development
This package lives in an npm-workspaces monorepo; run commands from
projects/sdk (the npm workspace root), from packages/sdk, or with
--workspace @solana/breeze-sdk.
npm install # install all workspaces
npm run build # bundle ESM + CJS + type declarations (tsup)
npm run typecheck # tsc --noEmit
npm run typecheck:examples # compile examples without running them
npm run typecheck:integration # compile live tests without calling the API
npm run test # offline unit tests
npm run verify:publish # all typechecks + unit tests + build (also runs before publish)
npm run test:integration # live tests against a real API (see below)Project structure
src/
├── index.ts # public exports
├── client.ts # ApiClient + BreezeApiError
├── sdk.ts # BreezeSDK class
├── types.ts # request/response types
├── kit/ # optional Kit plugin, adapter, errors, and tests
└── endpoints/ # one module per endpoint groupTests
- Unit tests (
src/**/*.test.ts) run fully offline with a mockedfetch. - Integration tests (
tests-integration/) hit a real API — configure the documented environment variables, target guard, and fixture data intests-integration/README.mdfirst. Runnpm run typecheck:integrationto compile them without making API calls.
Kit instruction conversion, signer matching, lookup-table planning, and sending are covered by offline tests. The suite does not submit a live mainnet Kit transaction; do not treat mocked executor coverage as end-to-end confirmation.
Migrating from @solana/breeze-sdk 1.x
Version 2.0.0 makes the close-account identity contract explicit at compile
time. CloseUserAccountOptions now accepts exactly one of:
{ userAccount: 'resolved-user-fund-account' }
// or
{
userPubkey: 'user-wallet',
strategyId: 'strategy-id',
mint: 'asset-mint',
}Remove empty, partial, or mixed identity objects before upgrading. The optional
payer, fundsRecipient, and userTokenAccount overrides remain available
with either identity form.
Migrating from @breezebaby/breeze-sdk v2
The new @solana/breeze-sdk package starts at 1.0.0. The BreezeSDK class API
is unchanged from the legacy package except for one rename; the remaining
breaking changes are in the low-level API:
BreezeSDK.getWithdrawInstruction(...)→getWithdrawInstructions(...)(plural, to match its array payload and the deposit/close siblings). The low-levelgetInstructionForWithdrawis likewise nowgetInstructionsForWithdraw.new ApiClient(baseUrl, timeout)→new ApiClient({ apiKey, baseUrl?, timeout? }). The client now stores the API key and sends thex-api-keyheader itself.- Endpoint functions take
(client, options)instead of long positional argument lists — e.g.getUserYield(client, { userId }),getTransactionForDeposit(client, { strategyId, baseAsset, amount, userKey }). - The internal
BodyFor*request types were removed; use the*Optionstypes. - The root import remains dependency-free. Install
@solana/kitand import@solana/breeze-sdk/kitfor trusted-backend composable transaction execution; browser wallets use the backend handoff described above.
