@elemental-stv-core/sdk
v0.9.3
Published
TypeScript SDK for Elemental Vaults — p-STV Core, Elemental Lend, JLPD Strategy
Maintainers
Readme
@elemental-stv-core/sdk
TypeScript SDK for the Elemental Vaults on-chain stack. Wraps three Solana programs through one consistent surface so frontends, indexers, and back-end services don't need to hand-roll discriminators, PDA derivation, or account layouts.
@elemental-stv-core/sdk
├── /common shared types, buffer helpers, connection type, discriminators
├── /p-stv-core vault management — deposits, withdraws, epochs, fees (level N)
├── /elemental-lend idle-base lending sweep into Kamino + Jupiter Lend Earn (level N − 1)
└── /jlpd-strategy JLP-Deconstructed yield strategy + lend-adapter wiring (level N + 1 / N + 2)At a glance
| Module | Wraps program | What it gives you |
| ---------------------------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| @elemental-stv-core/sdk/p-stv-core | PSTVH77GiPqA3msXmpjAyUXdh3MytK37GCPbPzWu3Rc | STV / GlobalConfig / WithdrawRequest / ManagerRole accounts; all 14 instruction builders (createDepositIx, createClaimWithdrawIx, createProcessEpochIx, createMigrateLendIx, …); event decoders (18 event types); the sendSmartTx smart sender (auto CU sim, priority fee, LUT merge); wSOL wrap/unwrap helpers; USD price fetcher |
| @elemental-stv-core/sdk/elemental-lend | EHaGVh7p6xSxZsq4CcEEZhnV5adDvWg1gS1Rc5rLiBpC | StrategyState / StvPosition / ManagerRole accounts; CPI + protocol instruction builders; Kamino kVault and Jupiter Lend Earn account-resolution helpers; auto-route / auto-unroute account builders for sweep flows |
| @elemental-stv-core/sdk/jlpd-strategy | GXqt4ZH2UUBsLWwMNJiZMXza3q7xEGChfW8XjVRjLxr5 | JlpdConfig / per-asset StrategyState / StvPosition; CPI + rebalance instruction builders (createSwapJlpIx, createSettleYieldIx, createDepositToAdapterIx, …); Jupiter swap quote / instruction fetchers; on-chain JLP price + custody readers |
| @elemental-stv-core/sdk/common | (no program) | Buffer reads with bounds checks, ATA derivation, the canonical SolanaConnection structural type, and the StrategyStateHeader / StvPosition interfaces shared across the two strategy programs |
Module layout
Every program-specific module follows the same file shape, so you only have to learn the pattern once:
<module>/
├── constants.ts program ID, seeds, instruction discriminators, sizes, flags
├── pda.ts findXxxPda() helpers — pure synchronous PDA derivation
├── types.ts account interfaces + event types
├── accounts.ts deserializeXxx() + fetchXxx() readers + GPA queries
├── instructions.ts createXxxIx() instruction builders
├── events.ts event decoders (p-stv-core: 1-byte disc; Anchor: 8-byte)
└── (module-specific files)
Module-specific files:
p-stv-core: remaining-accounts.ts, send-tx.ts, sol-wrap.ts, prices.ts
elemental-lend: kamino-vault.ts, jupiter-lend.ts, protocol-actions.ts
jlpd-strategy: swap-jlp.ts, settle-yield.ts, jlp-data.ts,
jlp-borrow.ts, jupusd-earn.ts, adapter.tsNaming conventions hold across modules:
| Pattern | Returns | Side effects |
| --------------- | ------------------------- | ------------ |
| findXxxPda | [PublicKey, number] | none |
| deserializeXxx| typed account | none |
| fetchXxx | typed account | one RPC call |
| createXxxIx | TransactionInstruction | none |
| buildXxx* | helper objects | none |
build* is reserved for helpers that produce ephemeral data (remaining accounts, batched instruction lists). Anything that returns a wire-ready instruction is named createXxxIx.
Naming exceptions worth knowing
Each program has its own ManagerRole PDA scoped to a different anchor account. To prevent same-named imports from shadowing each other, the helpers carry a per-program prefix:
| Program | Function | PDA seeds |
| --------------- | ------------------------- | ---------------------------------------- |
| p-stv-core | findStvManagerRolePda | ["manager", stv, manager] |
| elemental-lend | findLendManagerRolePda | ["manager", strategy_state, manager] |
| jlpd-strategy | findJlpdManagerRolePda | ["manager", config, manager] |
Cross-cutting design
SolanaConnection (structural type)
Every helper that needs RPC accepts SolanaConnection, defined in common/connection.ts as a Pick<Connection, ...> of just the methods we use. This means callers can pass any version of @solana/web3.js without nominal type mismatches across SDK / consumer versions. Narrower call sites use a further Pick (e.g. ProtocolActionConnection in elemental-lend/protocol-actions.ts is Pick<SolanaConnection, "getAccountInfo" | "getMultipleAccountsInfo">).
Numeric convention (BN vs number)
Documented at the top of p-stv-core/types.ts:
- u64 fields are deserialized as
BN(bn.js) - u32 / u16 / u8 fields are deserialized as plain JavaScript
number
Mixing the two does not auto-convert and silently truncates — when adding new fields to any account interface, follow the rule.
Discriminators
- p-stv-core (Pinocchio) uses 1-byte instruction discriminators (0x00–0x0C, 13 instructions). Account discriminators are still 8 bytes for Anchor compatibility.
- elemental-lend and jlpd-strategy are Anchor 0.32.1 programs and use 8-byte instruction discriminators throughout.
- Account discriminators that are truly shared across programs (
ManagerRole,StrategyState,StvPosition) live once incommon/constants.tsand are re-exported by each module.
Each constant is documented inline with the source — for example IX_ADD_MANAGER in elemental-lend has a // sha256("global:add_manager")[..8] comment so an auditor can re-derive it.
Buffer reads
common/buffer.ts provides bounds-checked readers (readPubkey, readU64, readU32, readU16, readU8) and the corresponding writers / optional-encoders. Each read helper validates offset + width <= data.length and throws a descriptive RangeError rather than silently reading garbage when the offset is wrong.
Send-tx layer
p-stv-core/send-tx.ts exposes sendSmartTx(connection, instructions, payer, signTransaction, options?):
- Pre-checks the legacy serialized size to decide if a versioned tx is needed (no double-sign).
- Fetches blockhash + every default LUT (lend / stv / jlpd) + Helius priority-fee in parallel.
- Simulates with an inflated CU limit, takes the actual
unitsConsumed, applies a 5% buffer, and prepends the rightComputeBudgetPrograminstructions. - Falls back from legacy → versioned automatically when CU instructions push the size over
MAX_LEGACY_SIZE. - Confirms via
lastValidBlockHeight.
The signTransaction callback is generic over Transaction | VersionedTransaction so wallets pass the same callback the SDK uses on either path. The optional rpcUrl field in SmartTxOptions is the only way the SDK reaches a Helius endpoint — it never reads private fields off the connection object.
Address Lookup Tables
p-stv-core/send-tx.ts exports DEFAULT_LUT_ADDRESSES:
{
lend: PublicKey, // Elemental Lend infrastructure
stv: PublicKey, // p-STV Core vaults / evMints / vault ATAs
jlpd: PublicKey, // JLPD Strategy state / positions / oracles
}sendSmartTx always fetches all three. Consumers building transactions outside sendSmartTx should import these and merge them into their own LUT list. Server-side jobs (jlpd-server/src/jobs/ltv-rebalance.ts) use this same export — the SDK is the single source of truth.
Installation
npm install @elemental-stv-core/sdk
# or
yarn add @elemental-stv-core/sdk
# or
pnpm add @elemental-stv-core/sdkPeer dependencies must be installed in the consumer:
npm install @solana/web3.js@^1.95.0 @solana/spl-token@^0.4.0Quick examples
Deposit base into a vault from a frontend
import {
findConfigPda,
findStvPda,
findEvMintPda,
createDepositIx,
buildDepositContext,
sendSmartTx,
} from "@elemental-stv-core/sdk/p-stv-core";
const [config] = findConfigPda();
const [stv] = findStvPda(vaultId);
const [evMint] = findEvMintPda(vaultId);
const ctx = await buildDepositContext(connection, vault);
const ix = createDepositIx({
user, config, stv, evMint, vaultAta, baseMint, userBaseAta, userEvAta,
baseAmount, autoRouteCount: ctx.autoRouteCount, ...
});
const sig = await sendSmartTx(
connection,
[...ctx.preInstructions, ix],
user,
signTransaction,
);Read a JLPD strategy state from a server job
import { fetchJlpStrategyState } from "@elemental-stv-core/sdk/jlpd-strategy";
const state = await fetchJlpStrategyState(connection, baseMint);
console.log(`PPS: ${state.pps.toString()}`);
console.log(`base_loaned: ${state.baseLoaned.toString()}`);Build a manager-only swap_jlp rebalance from base → JLP
import { buildSwapJlpTransaction } from "@elemental-stv-core/sdk/jlpd-strategy";
const result = await buildSwapJlpTransaction({
connection,
manager,
baseMint,
vaultId,
direction: "BaseToJlp",
amount: 1_000_000n,
slippageBps: 30,
});
const sig = await sendSmartTx(
connection,
[...result.preInstructions, ...result.instructions],
manager,
signTransaction,
{ additionalLuts: result.addressLookupTables },
);Quality bar
This SDK is the artifact submitted for external audit. The following invariants are enforced:
tscstrict mode withnoUnusedLocalsandnoUnusedParameters. Zero suppressions in source.- No
anyin source code. All wallet / connection seams use proper structural types. - No
@deprecatedexports. Deprecated aliases were removed during the audit cleanup; if you find one, it's a bug. - Buffer reads are bounds-checked. Every helper in
common/buffer.tsthrows on out-of-range offsets. - Discriminators are documented inline with their derivation (
// sha256("global:deposit")[..8]). - Each module follows the same file shape so the audit pattern is the same per program.
Project structure
sdk/
├── README.md (this file)
├── package.json
├── tsconfig.json strict + noUnusedLocals + noUnusedParameters
├── src/
│ ├── index.ts namespace re-exports
│ ├── common/
│ │ ├── index.ts
│ │ ├── constants.ts shared discriminators, PPS_DECIMALS, BPS, staleness
│ │ ├── connection.ts SolanaConnection structural type
│ │ ├── buffer.ts bounds-checked readers / writers
│ │ ├── ata.ts findAta()
│ │ └── strategy-interface.ts StrategyStateHeader + StvPosition + decoders
│ ├── p-stv-core/ vault management — Pinocchio program (13 instructions, 0x00–0x0C)
│ │ ├── constants.ts, pda.ts, types.ts, accounts.ts, instructions.ts, events.ts
│ │ ├── remaining-accounts.ts context builders for deposit/claim/epoch/strategy ops
│ │ ├── send-tx.ts smart tx: simulate CU → priority fee → LUT → sign → confirm
│ │ ├── sol-wrap.ts native SOL wrap/unwrap for SOL-denominated vaults
│ │ └── prices.ts Jupiter Quote API USD price fetcher
│ ├── elemental-lend/ idle-base lending — Anchor 0.32.1 program
│ │ ├── constants.ts, pda.ts, types.ts, accounts.ts, instructions.ts
│ │ ├── kamino-vault.ts Kamino kVault account derivation + CPI + staleness check
│ │ ├── jupiter-lend.ts Jupiter Lend pool configs + auto-route account builders
│ │ └── protocol-actions.ts high-level protocol action tx builders
│ └── jlpd-strategy/ JLP yield strategy — Anchor 0.32.1 program
│ ├── constants.ts, pda.ts, types.ts, accounts.ts, instructions.ts
│ ├── swap-jlp.ts Jupiter swap tx builders (BaseToJlp / JlpToBase)
│ ├── settle-yield.ts settle_yield instruction builder
│ ├── jlp-data.ts JLP pool data reader (custody weights, AUM, prices)
│ ├── jlp-borrow.ts Jupiter Lend Borrow constants + position reading
│ ├── jupusd-earn.ts Jupiter Lend Earn position + APR helpers
│ └── adapter.ts JLPD Lend Adapter instruction builders
├── dist/ tsc output (published to npm; also committed for internal `file:` link consumers)
└── scripts/
└── create-luts.ts admin tool: create / extend address lookup tables