npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@flash_trade/flash-sdk-v2

v0.2.14

Published

v2 client for the Flash perpetuals program on Solana — MagicBlock Ephemeral Rollups (ER) LP + spot flows

Readme

flash-sdk-v2

v2 client for the Flash perpetuals program, built for the MagicBlock Ephemeral Rollups (ER) LP + spot flows. Mirrors the architecture of the magic-trade reference client (dual connection, one-file-per-instruction, centralized PDAs, copied ER wire/rpc plumbing), scoped to what the program currently supports on ER: the 9 liquidity/staking/spot flows plus delegation, ER admin, and keeper tooling. The leveraged trading/position engine is intentionally not included (it has no ER instructions yet).

The flow model (important)

Flash's ER user flows are program-driven, not SDK-driven. You send one base-layer *WithAction transaction; the MagicBlock validator then runs the _er step (in the ER) and the _settle/_revert step (back on base) on its own via queued post-delegation / post-undelegate actions.

your 1 tx ─► swap_with_action (base)         you build + send this
              └─ validator ─► swap_er (ER)    auto
                              └─ swap_settle / swap_revert (base)  auto

So the SDK builds only the *WithAction entry per user op and then polls the receipt for the outcome — it never builds _er/_settle/_revert.

const client = new FlashClient(provider, programId, { prioritizationFee: 5000 });
const pc = PoolConfig.fromIdsByName("Crypto.1", "mainnet-beta");

const { instructions, swapReceipt } = await client.swapWithAction(pc, {
  inSymbol: "USDC", outSymbol: "SOL",
  inAccount, outAccount, amountIn, minAmountOut,
});
await client.sendAndConfirmBase(instructions);
const outcome = await client.awaitOutcome("swapReceipt", swapReceipt);
// outcome.status === "settled" | "reverted" | "timeout"

Wait — swapWithAction returns the receipt PDA so you can awaitOutcome it.

Trade layer (basket-backed positions & orders)

A second architecture sits alongside the LP flows: per-user baskets hold positions + orders inline, funded through a per-mint trade vault + a user deposit ledger. Setup and trading:

// one-time setup (base layer)
await client.sendBase((await client.trade.initUserDepositLedgerEr(owner)).instructions);
await client.sendBase((await client.trade.initBasketEr(owner)).instructions);
await client.sendBase((await client.trade.depositDirectEr(owner, usdcMint, userUsdcAta, amount)).instructions);
await client.sendBase((await client.trade.delegateBasket(owner, 30_000, client.validatorKey())).instructions);

// trade — DIRECT-ER (basket is delegated): send to the ER RPC
const { instructions } = await client.trade.openPositionEr(pc, {
  market, targetSymbol: "SOL", lockSymbol: "USDC", receivingSymbol: "USDC",
  priceWithSlippage: { price, exponent }, collateralAmount, sizeAmount,
});
await client.sendEr(instructions, [sessionKeypair]);

Execution model by group:

| Group | Where | How to send | |---|---|---| | initTradeVaultEr, initBasketEr, initUserDepositLedgerEr, depositDirectEr, delegateBasket, migratePositionToBasket, migrateOrderToBasket | base | sendBase | | Positions: openPositionEr, closePositionEr, increasePositionSizeEr, decreasePositionSizeEr, addCollateralEr, removeCollateralEr, liquidatePositionEr | direct-ER (delegated basket) | sendEr | | Orders: placeLimitOrderEr/editLimitOrderEr/cancelLimitOrderEr/executeLimitOrderEr, placeTriggerOrderEr/editTriggerOrderEr/cancelTriggerOrderEr/cancelAllTriggerOrdersEr/executeTriggerOrderEr | direct-ER | sendEr | | Withdrawal: requestWithdrawalWithAction → (validator: process → execute) | base entry | sendBase, then client.awaitClosed(withdrawalEscrow) | | Custody settlement: requestCustodySettlementWithAction → (validator) | base entry | sendBase, then client.awaitClosed(settlementReceipt) |

Notes:

  • Referral / token-stake benefits (same as flash-sdk v1): the position ops (open/close/increase/decrease) and execute* orders append a 2-slot remaining-account tail [referral, tokenStake] gated by privilege (Privilege.None | Stake | Referral). For Stake pass the trader's tokenStakeAccount (= client.pdas.tokenStake(owner)); for Referral pass referralAccount (= client.pdas.referral(owner)) and the referrer's tokenStakeAccount. No referral / no token stake → just use Privilege.None (the default) and omit the accounts: the tail is skipped and the trade runs at full fee. A missing/invalid account never reverts (the program returns a benefit-status and charges full fee), but the SDK avoids sending phantom accounts. Built via the ported getReferralAccounts / buildReferralTail (utils/referral.ts), matching flash-sdk v1.
  • User position/order builders accept session keys: pass signer (a session key) + sessionToken; omit both to sign as owner.
  • execute* order builders and liquidatePositionEr are keeper ops (no session; pass the position owner to derive the basket).
  • Custody roles (target/lock/receiving/dispensing/reserve/receive) are given as token symbols and resolved to custody + oracle accounts via PoolConfig.
  • Withdrawal/custody-settlement are validator-driven 3-phase (request_* → process_*_er → execute_*_base_chain_er); the SDK builds only the base request_* entry and awaitClosed polls the escrow/receipt PDA (which the final execute step closes — these receipts have no processed field).

Accounts: fetch, wrappers, subscriptions

Core accounts come back as slim wrapper classes (accounts/wrappers.ts): a typed .data field (the decoded struct — no IDL field-mirroring, so it never drifts), the account publicKey, and helper methods. <X>Account.from(pubkey, decoded) is a pure wrap — fed identically by fetch, subscribe, or any decode.

const pool = await client.fetch.pool(poolPk);     // PoolAccount
pool.publicKey; pool.data.lpSupply;               // typed
pool.getCustodyId(custodyPk);

const ts = await client.fetch.tokenStake(owner);  // TokenStakeAccount | null
ts?.getWithdrawableAmount(); ts?.getLockStatus();

fetch.* returns wrappers for pool/poolByName, custody, market, position, order, tokenStake, govTokenVault. (Transient/aux accounts — receipts, basket, ledger, escrows, whitelist, flpStake — stay typed POJOs.)

Subscriptions (client.subscribe.*) use raw connection.onAccountChange + coder.decode + the same wrappers — not anchor's program.account.X.subscribe (which hides the subscription id, drops slot/raw-info, and is bound to one connection). Each returns an unsubscribe fn and can target base or ER:

const off = client.subscribe.pool(poolPk, (p, slot) => {
  console.log(slot, p.data.lpSupply.toString());
});
// delegated account → read from the ER:
client.subscribe.position(owner, market, (pos) => {...}, { er: true });
off(); // tear down

client.subscribe.raw("basket", pk, cb) is the escape hatch for any account.

Views / quotes (client.views.*)

On-chain view functions — the program computes and the SDK decodes the typed result (no TS re-implementation of pricing math). Mirrors magic-trade's ViewHelper: build the view ix → raw simulateTransaction (unbounded wire via buildUnboundedSimulateBytes, no signing) → decode the Program return: log with IdlCoder. Not anchor .view() — that uses a 2048-byte Message.serialize() buffer (breaks at ~55 keys) and can't target the ER aperture. Views route to the ER ViewHelper when the client has an ER endpoint (delegated pool/custody/market state lives there), else the base helper. Each resolves accounts + the verified remaining-account tail:

const fee = await client.views.getSwapAmountAndFees(pc, { receivingSymbol: "USDC", dispensingSymbol: "SOL", amountIn });
const lp  = await client.views.getLpTokenPrice(pc);                 // BN
const q   = await client.views.getOpenPositionQuote(pc, { market, targetSymbol:"SOL", collateralSymbol:"USDC", receivingSymbol:"USDC", amountIn, leverage });
const pnl = await client.views.getPnl(pc, { owner, market, targetSymbol:"SOL", collateralSymbol:"USDC" });

18 views: liquidity (getAdd/RemoveLiquidityAmountAndFee, getAdd/RemoveCompoundingLiquidityAmountAndFee, getCompoundingTokenData, getCompoundingTokenPrice, getLpTokenPrice), swap (getSwapAmountAndFees), and position quotes (getOpenPositionQuote, getEntryPriceAndFee, getClosePositionQuote, getExitPriceAndFee, getAddCollateralQuote, getRemoveCollateralQuote, getLiquidationPrice, getLiquidationState, getPnl, getPositionData). Position-bound quotes take owner + market (position PDA ["position", owner, market]).

Notes / verified contracts:

  • Liquidity/compounding/lp-price views send the AUM tail [custodies, oracles, markets] (IncludePnl); swap sends [custodies, oracles] (ExcludePnl); getOpenPositionQuote takes an optional existingPosition remaining account; the other position quotes take no tail.
  • Not included: get_assets_under_management and get_oracle_price return Rust tuples with no IDL returns (the IDL can't describe a tuple). Decode those via client.viewHelper.decodeReturnWithTypedef(simResult, typeDef) with a hand-built typedef if you need them — ViewHelper is exported.
  • Routing is automatic: with an erEndpoint set, views simulate against the ER (delegated state); otherwise against the base RPC. client.viewHelper / client.erViewHelper are exposed for custom simulate/decode.

Governance-token staking + referral / rebates (base layer)

Both are base-layer (sendBase). Verified against source — refresh_token_stake takes a batch of token_stake accounts as writable remaining accounts; reimburse takes the AUM tail (custodies+oracles+markets, IncludePnl).

// `stakingMint` = the governance/staking token mint; `stakerAta` = the user's
// associated token account for that mint, e.g.
//   getAssociatedTokenAddressSync(stakingMint, owner)
await client.tokenStake.depositTokenStake(stakingMint, stakerAta, amount);
await client.tokenStake.unstakeTokenRequest(amount);
await client.tokenStake.collectTokenReward(stakingMint, stakerAta); // rewards -> stakerAta
await client.tokenStake.refreshTokenStake([stakeA, stakeB]);        // keeper batch

// referral
await client.referral.createReferral(referrer);            // referrer = referrer's wallet
await client.referral.collectRebate(rebateMint, recipientAta);
await client.referral.settleRebates(pc);                   // keeper; uses pool.reward_custody

client.tokenStake.*: depositTokenStake, unstakeTokenRequest, cancelUnstakeTokenRequest, collectTokenReward, refreshTokenStake, plus admin distributeTokenReward / setTokenReward / setTokenStakeLevel / withdrawUnclaimedTokens. client.referral.*: createReferral, collectRebate, settleRebates, plus admin initRebateVault / reimburse. Fetchers: client.fetch.tokenStake(owner), client.fetch.referral(owner), client.fetch.govTokenVault(). The staking token mint is supplied by the caller (one singleton ["token_vault"]); tokenStake/referral/rebate_vault PDAs are derivable via client.pdas.*.

Note: settleRebates/createReferral use pool.reward_custody / the referrer's token stake; rewardSymbol defaults to "USDC" (override if your pool's reward custody differs).

Two interaction styles (LP)

| Style | Where | API | |---|---|---| | User flows (self-orchestrating) | base layer, 1 tx | client.swapWithAction, addLiquidityAndStakeWithAction, removeLiquidityWithAction, add/removeCompoundingLiquidityWithAction, collectStakeRewardWithAction, compoundFeesWithAction, migrateStakeWithAction, migrateFlpWithAction | | Admin delegation / init | base layer | client.admin.delegatePool / delegateCustody / delegateMarket / delegateInternalOracle / delegateReallocVault / initReallocVault / topUpReallocVault / initCustodyAccount / initMarketAccount (+ undelegate*) | | ER config / price setters (direct-ER) | ER RPC | client.admin.addCustodyEr / addMarketEr / setPoolConfigEr / setCustodyConfigEr / setMarketConfigEr / setCustodyTokenMultiplierEr / setInternalCurrentPriceEr / setInternalEmaPriceEr / setInternalLazerPriceEr — send via client.sendEr(...) | | Keeper | base + ER | client.keeper.batchDelegateFlpStake (base), undelegateFlpStake (base), refreshStakeEr (ER), fetchPoolFlpStakes |

The *Er config/price setters and refreshStakeEr mutate already-delegated accounts and must be sent to the ER RPC via client.sendEr(ixs, [keypair]) (legacy wire format; v0/ALT is not supported on the ER). Everything else goes to the base layer via client.sendBase / sendAndConfirmBase.

Layout

src/
  FlashClient.ts        dual-connection client; binds all builders
  constants.ts          program ids, seeds, delegation/magic ids, validator keys
  types.ts              Side enum + helpers (account types aliased — see note)
  PoolConfig.ts/.json   ported from v1
  OraclePrice.ts        ported from v1 (quoting helpers)
  utils/
    pda.ts              centralized PDA derivation (incl. delegation siblings)
    delegation.ts       #[delegate] injected-account fragments
    remainingAccounts.ts AUM + whitelist tail builder
    erWire.ts erRpc.ts  copied verbatim from magic-trade (ER wire quirks)
    rpc.ts math.ts
  instructions/
    user/*.ts           the 9 *WithAction builders
    admin/*.ts          delegation + ER config
    keeper/*.ts         batch delegate, refresh, sweep
  accounts/receipts.ts  receipt outcome poller

Types

Account/nested types are concrete generated interfaces in src/idl/generatedTypes.ts (produced by scripts/generateTypes.js from the IDL), re-exported through types.ts. They use anchor's runtime conventions (camelCase fields, camelCase enum variant tags, BN for 64/128-bit ints, PublicKey, Buffer for bytes). Plain interfaces are used instead of anchor's generic IdlAccounts<Perpetuals> / IdlTypes<Perpetuals> because the latter trip TS2589 ("type instantiation excessively deep") on this ~1MB IDL — for the same reason Program is used untyped (as magic-trade does). Regenerate after an IDL change:

npm run generate-types

Known follow-ups

  • awaitReceiptOutcome distinguishes settle vs revert by the receipt's output field; if the receipt is closed before a processed=1 read is observed it assumes settled. Decode the *Er/*Settle event logs if you need certainty.
  • Cluster: pass { cluster: "devnet" } in opts — the program id and validator key resolve automatically (the bundled IDL address is rewritten). An explicit programId constructor arg overrides the cluster default. PoolConfig is already cluster-keyed via PoolConfig.fromIdsByName(name, cluster). The validator key is otherwise optional: builders default validator to null and the on-chain program falls back to its compiled per-cluster validator.

Build

npm install
npm run build