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

@uniformlabs/multiliquid-svm-sdk

v0.3.2

Published

Multiliquid SVM SDK

Readme

@uniformlabs/multiliquid-svm-sdk

TypeScript SDK for swapping against and managing Multiliquid vaults on Solana. Handles pair discovery, NAV oracle resolution, client-side quoting with exact on-chain math replication, simulation-based quoting, and instruction/transaction building for swaps and LP-admin flows.

Current package version in this repository: 0.3.0.

Program IDs

| Network | Program ID | | ------------ | ---------------------------------------------- | | Devnet | HaWDr94LKJQT2fXuHJGsSGeQf6M7S68FXpEQLcE5RYs6 | | Mainnet-Beta | HaWDr94LKJQT2fXuHJGsSGeQf6M7S68FXpEQLcE5RYs6 |

Installation

npm install @uniformlabs/multiliquid-svm-sdk

Runtime dependencies are declared by the package, including @solana/web3.js ^1.98.4, @coral-xyz/anchor ^0.32.1, and @solana/spl-token ^0.4.14.

Quick Start

import { Connection, Keypair, PublicKey } from "@solana/web3.js";
import { BN } from "@coral-xyz/anchor";
import { MultiliquidClient, SwapDirection, SwapType } from "@uniformlabs/multiliquid-svm-sdk";

const connection = new Connection("https://api.mainnet-beta.solana.com");
const client = new MultiliquidClient({
  connection,
  cluster: "mainnet-beta",
});

const wallet = Keypair.fromSecretKey(/* ... */);
const USDC = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
const USTB = new PublicKey("CCz3SGVziFeLYk2xfEstkiqJfYkjaSWb2GCABYsVcjo2");
const LP = new PublicKey("C8Mi6kn7ajFWuNe4ZmsR9A6fdqRYhzXFoqVBGMsdJ2Uf");

// Buy USTB with 100 USDC
const { transaction } = await client.buildSwapTransaction({
  user: wallet.publicKey,
  liquidityProvider: LP,
  stableMint: USDC,
  assetMint: USTB,
  amount: new BN(100_000_000), // 100 USDC (6 decimals)
  swapDirection: SwapDirection.StableToAsset,
  swapType: SwapType.ExactIn,
  minAmountOut: new BN(41_000_000_000), // slippage protection
});

transaction.sign([wallet]);
const sig = await connection.sendTransaction(transaction);

API

Client

Stateless client that holds connection config and delegates to standalone functions.

const client = new MultiliquidClient({
  connection, // Solana Connection
  cluster: "mainnet-beta", // "devnet" | "mainnet-beta" (default: "devnet")
  commitment: "confirmed", // Commitment level for all RPC calls (default: "confirmed")
  programId: undefined, // Override program ID (defaults per cluster)
  registryApiUrl: undefined, // Override pair registry API base URL (defaults to Multiliquid production API)
  registryFetchTimeoutMs: 10_000, // Optional API registry timeout; set 0 to disable timeout
  onRegistryError: (error, context) => {
    console.warn("Using static pair registry fallback", context, error);
  },
});

Pair Discovery

// API-backed registry lookup. Falls back to the static registry if unavailable.
const apiPairs = await client.getPairs({ stableMint, assetMint, liquidityProvider });

// Per-call cancellation is supported.
const abortablePairs = await client.getPairs(undefined, { signal: abortController.signal });

// Static registry lookup (no HTTP/RPC)
const offlinePairs = client.getOfflinePairs({ stableMint, assetMint, liquidityProvider });

// On-chain discovery via getProgramAccounts
const discoveredPairs = await client.discoverPairs({
  stableMint,
  assetMint,
  liquidityProvider,
});

All filters are optional and AND-combined.

Multiple LPs can list the same stableMint / assetMint pair. Include liquidityProvider when you need a specific pool rather than the first matching mint pair.

getPairs() is stateless and does not cache API results. Memoize or cache results in UI/server code when rendering pair lists repeatedly. If the API returns new mints that are not bundled in the static registry, the client uses its Solana connection to fetch mint decimals before returning the pair.

The default registry API base URL is https://api.multiliquid.xyz. registryApiUrl may be either that style of base URL or a full /v1/info/svm endpoint. API-backed entries may include vaultAuthority; static entries do not.

Quoting

// Client-side quote — replicates on-chain math exactly via BigInt
const quote = await client.getQuote({
  user: wallet.publicKey,
  liquidityProvider: LP,
  stableMint: USDC,
  assetMint: RWA,
  amount: new BN(100_000_000),
  swapDirection: SwapDirection.StableToAsset,
  swapType: SwapType.ExactIn,
});
// => { amountIn, amountOut, protocolFees, discountAmount, amountInForVault, assetNav, stableNav }

// Simulation-based quote — simulates the swap on-chain without executing
const simQuote = await client.getQuoteViaSimulation(params);
// => { amountIn, amountOut, protocolFee, discountBps, pair, swapDirection, swapType, computeUnitsConsumed }

getQuoteViaSimulation also returns computeUnitsConsumed, useful for setting a tight compute budget.

Instruction & Transaction Building

// Instruction-first: returns TransactionInstruction for composability
const { instruction, setupInstructions, accounts } = await client.buildSwapInstruction(params);

// Transaction convenience: returns unsigned VersionedTransaction
const { transaction, accounts } = await client.buildSwapTransaction(params);
  • setupInstructions contains ATA creation instructions when autoCreateAta: true (default).
  • No ComputeBudgetProgram instructions are included — set compute units and priority fees yourself.
  • Set autoCreateAta: false if you manage ATA creation externally.
  • Swap builders automatically resolve oracle accounts and any Token-2022 transfer-hook accounts needed for the actual token transfers.
  • Some mainnet RWA transfer hooks derive extra accounts from token account data; for those mints, pre-create the relevant user token account or pass an existing token account override before building the swap.
  • Known mainnet RWA transfer hooks are exported as MAINNET_RWA_TRANSFER_HOOKS; hook-bearing assets are ACRED, BENJI, WTGXX, and VBILL. USTB and USCC do not currently have transfer hooks.
  • remainingAccounts is only for additional/manual accounts beyond what the SDK derives automatically.

LP Admin & Liquidity

// Open a pair. liquidityProvider signs and pays; admin is fetched from global config if omitted.
const { instruction: initPairIx } = await client.buildInitPairInstruction({
  liquidityProvider: wallet.publicKey,
  stableMint: USDC,
  assetMint: USTB,
  redemptionFeeBps: 10,
  discountRateBps: 15,
});

// Update pair fees and pause state
const { instruction: updatePairIx } = await client.buildUpdatePairInstruction({
  liquidityProvider: wallet.publicKey,
  stableMint: USDC,
  assetMint: USTB,
  redemptionFeeBps: 10,
  discountRateBps: 15,
  paused: false,
});

// Deposit liquidity, deriving the LP ATA and vault ATA unless overridden
const { instruction: addLiquidityIx, setupInstructions } = await client.buildAddLiquidityInstruction({
  liquidityProvider: wallet.publicKey,
  mint: USDC,
  amount: new BN(1_000_000_000),
});

// Withdraw liquidity with an unsigned VersionedTransaction helper
const { transaction: removeLiquidityTx } = await client.buildRemoveLiquidityTransaction({
  liquidityProvider: wallet.publicKey,
  mint: USTB,
  amount: new BN(500_000_000),
});

// Close a pair and return any remaining vault balances to LP token accounts
const { transaction: closePairTx } = await client.buildClosePairTransaction({
  liquidityProvider: wallet.publicKey,
  stableMint: USDC,
  assetMint: USTB,
});
  • buildInitPairInstruction / buildInitPairTransaction wrap init_pair with the LP as the signer/payer. admin is a non-signing account checked by global config and is fetched automatically unless provided.
  • Init-pair builders pre-validate the derived asset configs and reject swapped stable/RWA mints before building the instruction.
  • buildUpdatePairInstruction / buildUpdatePairTransaction wrap the on-chain update_pair instruction.
  • buildAddLiquidityInstruction returns setupInstructions when the LP ATA or LP vault ATA needs to be created.
  • buildRemoveLiquidityInstruction returns setupInstructions when the LP ATA needs to be created.
  • buildClosePairInstruction returns setupInstructions when LP recipient ATAs for the stable or asset mint need to be created, except when a Token-2022 transfer-hook mint will transfer during close; in that case the recipient account must already exist on-chain before hook accounts can be resolved.
  • Override lpTokenAccount if the LP uses a non-ATA token account.
  • Override lpStableTokenAccount / lpAssetTokenAccount if close-pair proceeds should be returned to non-ATA token accounts.
  • Add-liquidity vault ATAs are owned by the SDK-derived LP vault authority PDA and paid for by the liquidity provider. Set autoCreateAta: false if the app creates both the LP ATA and vault ATA itself.
  • Add/remove liquidity and close-pair builders also auto-resolve Token-2022 transfer-hook accounts.
  • Close-pair only resolves vault balances and transfer-hook accounts for a mint when that mint's LP UserVaultInfo.used is 1, meaning the on-chain close will transfer and close that vault. Recipient token accounts are still required by the on-chain account constraints.
  • For close-pair on a Token-2022 transfer-hook mint that will transfer, pre-create the LP recipient token account or pass lpStableTokenAccount / lpAssetTokenAccount; transfer-hook meta resolution cannot see token accounts that would only be created by the same transaction's setup instructions.
  • Close-pair transfer-hook account resolution uses the vault balance observed at build time, while the on-chain instruction transfers the vault balance at execution time. Rebuild close-pair transactions shortly before sending if a hook derives accounts from transfer amount or the vault may change concurrently.
  • Close-pair accounts.remainingAccounts preserves the full AccountMeta[] flags returned to Anchor.
  • For Token-2022 mints whose transfer hooks require destination token-account data, the add-liquidity vault ATA must already exist on-chain before hook accounts can be resolved. In that case, pre-create the vault ATA before building the add-liquidity instruction.
  • remainingAccounts remains available for extra/manual accounts beyond the SDK-derived set.

Swap Parameters

interface SwapParams {
  user: PublicKey;
  liquidityProvider: PublicKey;
  stableMint: PublicKey;
  assetMint: PublicKey;
  amount: BN;
  swapDirection: SwapDirection; // StableToAsset | AssetToStable
  swapType: SwapType; // ExactIn | ExactOut
  minAmountOut?: BN; // Slippage protection (ExactIn)
  maxAmountIn?: BN; // Slippage protection (ExactOut)
  userStableTokenAccount?: PublicKey; // Override ATA derivation
  userAssetTokenAccount?: PublicKey; // Override ATA derivation
  autoCreateAta?: boolean; // Default: true
  remainingAccounts?: AccountMeta[]; // Optional extra accounts to append after SDK-derived accounts
}

Account Fetching

const globalConfig = await client.fetchGlobalConfig();
const pair = await client.fetchPair(pairAddress);
const assetConfig = await client.fetchAssetConfig(configAddress);
const lpStableConfig = await client.fetchLpStableConfig(configAddress);

// Batch fetch all 5 accounts needed for a swap in a single RPC call
const state = await client.fetchSwapState(stableMint, assetMint, lp);

// Check pause status across all levels
const status = await client.checkPauseStatus(stableMint, assetMint, lp);
// => { globalPaused, pairPaused, rwaPaused, stablePaused, lpStablePaused, anyPaused, pauseReasons }

PDA Derivation

All return [PublicKey, number] (address + bump).

client.deriveGlobalConfig();
client.deriveAssetConfig(mint);
client.derivePair(lp, stableMint, assetMint);
client.deriveVault(mint, lp, tokenProgram); // LP vault ATA; tokenProgram is optional and defaults to SPL Token
client.deriveVaultAuthority(lp);
client.deriveProgramAuthority();
client.deriveFeeVault(stableMint, tokenProgram); // fee vault ATA; tokenProgram is optional and defaults to SPL Token
client.deriveLpStableConfig(stableMint, lp);

When deriving vault addresses manually for Token-2022 mints, pass the Token-2022 program ID as tokenProgram. Swap and liquidity builders detect the mint owner and derive the correct vault ATAs automatically.

Event Parsing

// From transaction logs
const events = client.parseSwapEventsFromLogs(logs);

// From a transaction signature
const events = await client.parseSwapEventsFromTransaction(signature);
// => SwapExecutedEvent[]
// { requestor, amountIn, protocolFeeAmount, discountBps, amountOut, pair, swapDirection, swapType }

Error Handling

try {
  await connection.sendTransaction(transaction);
} catch (err) {
  const parsed = client.parseSwapError(err);
  if (parsed) {
    console.error(`[${parsed.code}] ${parsed.name}: ${parsed.message}`);
    // parsed.category: "slippage" | "paused" | "oracle" | "input_validation" | "math" | "liquidity" | "user_funds"
  }
}

| Category | Meaning | Recommended Action | | ------------------ | ----------------------------------- | ------------------------------- | | slippage | Output below min or input above max | Re-quote with fresh state | | paused | Program, pair, or asset is paused | Skip route until state changes | | oracle | NAV returned 0 or oracle error | Skip route | | input_validation | Invalid parameters (amount=0, etc.) | Fix input | | math | Overflow/underflow or fee config | Trade may be too small | | liquidity | Vault can't fill the swap | Try different LP or reduce size | | user_funds | User balance or token account issue | Fund or create the account |

Amount Formatting

import { toHumanReadable, toNativeAmount } from "@uniformlabs/multiliquid-svm-sdk";

toHumanReadable(100_000_000n, 6); // "100"
toHumanReadable(99_800_000_000n, 9); // "99.8"

toNativeAmount("100", 6); // 100_000_000n
toNativeAmount("99.8", 9); // 99_800_000_000n

Standalone Functions

Every MultiliquidClient method is also available as a standalone function:

import {
  buildSwapInstruction,
  buildSwapTransaction,
  buildUpdatePairInstruction,
  buildUpdatePairTransaction,
  buildAddLiquidityInstruction,
  buildAddLiquidityTransaction,
  buildRemoveLiquidityInstruction,
  buildRemoveLiquidityTransaction,
  getQuote,
  getQuoteViaSimulation,
  getPairs,
  getOfflinePairs,
  discoverPairs,
  fetchSwapState,
  checkPauseStatus,
  calculateSwapResults,
  calculateNav,
  resolveOracleAccounts,
  parseSwapError,
  parseSwapEventsFromLogs,
  parseSwapEventsFromTransaction,
  decodeSwapExecutedEvent,
  detectTokenProgram,
  ensureAtaInstructions,
  toHumanReadable,
  toNativeAmount,
  deriveGlobalConfig,
  deriveAssetConfig,
  derivePair,
  deriveVault,
  deriveVaultAuthority,
  deriveProgramAuthority,
  deriveFeeVault,
  deriveLpStableConfig,
  PROGRAM_ID_DEVNET,
  PROGRAM_ID_MAINNET,
  DEFAULT_REGISTRY_API_URL,
  DEFAULT_REGISTRY_FETCH_TIMEOUT_MS,
  MAINNET_RWA_TRANSFER_HOOKS,
  SWAP_COMPUTE_UNITS,
  SWAP_ERROR_MAP,
} from "@uniformlabs/multiliquid-svm-sdk";

Standalone getPairs() takes the cluster explicitly and accepts the same registry options used by the client:

const apiPairs = await getPairs("mainnet-beta", { stableMint }, { connection });
const offlinePairs = getOfflinePairs("mainnet-beta", { stableMint });

Design Principles

  • Instruction-first — Returns TransactionInstruction for composability. Compute budget is the consumer's responsibility.
  • Exact math — Client-side quoting replicates on-chain Rust math with BigInt to produce identical results.
  • Stateless — No caching beyond a single method call.
  • Commitment-consistent — All RPC calls within a single operation use the same commitment level.
  • Pass-through errors — Anchor errors propagate directly. parseSwapError is an optional utility.

License

ISC