@uniformlabs/multiliquid-svm-sdk
v0.1.10
Published
Multiliquid SVM SDK
Downloads
1,045
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.1.9.
Program IDs
| Network | Program ID |
| ------------ | ---------------------------------------------- |
| Devnet | FKeT8H2RSgsamrABNNxwT5f9g3n9msfm6D5AvocjrJAD |
| Mainnet-Beta | FKeT8H2RSgsamrABNNxwT5f9g3n9msfm6D5AvocjrJAD |
Installation
npm install @uniformlabs/multiliquid-svm-sdkPeer dependencies: @solana/web3.js ^1.95, @coral-xyz/anchor ^0.32
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)
});Pair Discovery
// Hard-coded registry lookup (no RPC)
const pairs = client.getPairs({ stableMint, assetMint, liquidityProvider });
// On-chain discovery via getProgramAccounts
const pairs = await client.discoverPairs({
stableMint,
assetMint,
liquidityProvider,
});All filters are optional and AND-combined.
Multiple LPs can list the same stableMint / assetMint pair. On mainnet, for example, both USDC / WTGXX and USDC / WTGXX (Metalayer) use the same mint pair, so include liquidityProvider when you need a specific pool.
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);setupInstructionscontains ATA creation instructions whenautoCreateAta: true(default).- No
ComputeBudgetPrograminstructions are included — set compute units and priority fees yourself. - Set
autoCreateAta: falseif you manage ATA creation externally. - Swap builders automatically resolve oracle accounts and any Token-2022 transfer-hook accounts needed for the actual token transfers.
remainingAccountsis only for additional/manual accounts beyond what the SDK derives automatically.
LP Admin & Liquidity
// 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 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),
});buildUpdatePairInstruction/buildUpdatePairTransactionwrap the on-chainupdate_pairinstruction.buildAddLiquidityInstruction/buildRemoveLiquidityInstructionreturnsetupInstructionswhen the LP ATA needs to be created.- Override
lpTokenAccountif the LP uses a non-ATA token account. - Add/remove liquidity builders also auto-resolve Token-2022 transfer-hook accounts.
remainingAccountsremains 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);
client.deriveProgramAuthority();
client.deriveFeeVault(stableMint);
client.deriveLpStableConfig(stableMint, lp);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"
}
}| 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 |
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_000nStandalone Functions
Every MultiliquidClient method is also available as a standalone function:
import {
buildSwapInstruction,
buildSwapTransaction,
buildUpdatePairInstruction,
buildUpdatePairTransaction,
buildAddLiquidityInstruction,
buildAddLiquidityTransaction,
buildRemoveLiquidityInstruction,
buildRemoveLiquidityTransaction,
getQuote,
getQuoteViaSimulation,
getPairs,
discoverPairs,
fetchSwapState,
checkPauseStatus,
calculateSwapResults,
calculateNav,
resolveOracleAccounts,
parseSwapError,
parseSwapEventsFromLogs,
parseSwapEventsFromTransaction,
decodeSwapExecutedEvent,
detectTokenProgram,
ensureAtaInstructions,
toHumanReadable,
toNativeAmount,
deriveGlobalConfig,
deriveAssetConfig,
derivePair,
deriveVault,
deriveProgramAuthority,
deriveFeeVault,
deriveLpStableConfig,
PROGRAM_ID_DEVNET,
SWAP_COMPUTE_UNITS,
SWAP_ERROR_MAP,
} from "@uniformlabs/multiliquid-svm-sdk";Design Principles
- Instruction-first — Returns
TransactionInstructionfor 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.
parseSwapErroris an optional utility.
License
ISC
