Skip to main content

Integration Overview

The Multiliquid Protocol on Solana can be integrated via the official TypeScript SDK, which provides a complete interface for quoting, building, and executing swaps against the on-chain program, along with capital provider administration flows. The SDK is built on Solana Kit (@solana/kit) and uses native bigint values for token amounts. There is no Anchor or @solana/web3.js runtime dependency.

SDK Installation

Install and configure the TypeScript SDK

Pair Discovery

Find available trading pairs

Quoting

Get swap quotes using client-side math or simulation

Executing Swaps

Build and submit swap transactions

Capital Provider Admin

Create, update, and close pairs and manage liquidity

Ladder Pricing Model

Implement a rolling 24-hour laddered pricing model as a capital provider

Installation

The package declares runtime dependencies on:
  • @solana/kit
  • @solana-program/token-2022
Install @solana/kit directly when importing Kit helpers such as address, createSolanaRpc, and signTransaction in application code. Node.js >=24 is required.

Client Initialization

The SDK provides a MultiliquidClient class that wraps all functionality. Addresses are Kit Address values (branded strings), created with address():
cluster selects the built-in pair registry and default program address. Both devnet and mainnet use the same swap program ID: HaWDr94LKJQT2fXuHJGsSGeQf6M7S68FXpEQLcE5RYs6. Pass programAddress to override that default. Optional registryApiUrl, registryFetchTimeoutMs, and onRegistryError configure metadata-API pair discovery.

Pair Discovery

Metadata Registry

getPairs() queries the Multiliquid metadata API and falls back to the bundled registry:
Each returned entry includes the pair PDA, both mints in the pair account’s stored quote/base order, the capital provider, and token decimals:
getOfflinePairs() returns the bundled registry without an HTTP call.

On-Chain Discovery (RPC)

For dynamically discovering pairs not in the registry:

Checking Pair Status

Before executing a swap, verify the pair is active. Callers pass the tokens being spent and received; the SDK resolves pair ordering:
Pause checks cover the global config, the pair, the input and output asset configs, the configured fee asset, and any capital provider stable configs on those sides. All must be unpaused for swaps to execute. Pass pairAddress when both on-chain pair orderings exist for the same capital provider and mint pair.

Quoting

The SDK offers two quoting methods: client-side math replication and on-chain simulation. Callers specify the tokens spent and received. The SDK checks both possible on-chain pair orderings and translates the result to the protocol’s quote-to-base or base-to-quote direction internally. If both orderings exist for the same capital provider and mint pair, pass pairAddress to disambiguate.

Client-Side Quote

Replicates the on-chain Rust math exactly using bigint. Fetches current NAV prices from oracles and computes the swap result:
protocolFees is denominated in feeTokenMint and is paid from the capital provider fee vault. It is not deducted from the trader’s input. The SwapQuote includes:

Simulation Quote

Builds a swap instruction with ATA creation disabled, simulates it against the validator, and parses the emitted event:
The simulation quote also returns computeUnitsConsumed as bigint, which is useful for setting compute-budget instructions.

Executing Swaps

The SDK is instruction-first: the primary API returns Kit Instruction objects for maximum composability. A convenience method for building unsigned Kit transactions is also available.

Building a Swap Transaction

The returned transaction is unsigned. A signer whose address matches user must sign before submission.

Building Individual Instructions

For more control, build the swap instruction separately and compose it with other instructions (for example, compute budget):
The SDK does not include compute-budget instructions automatically. Integrators that need priority fees or explicit compute limits can compose @solana-program/compute-budget instructions as shown above. Use getQuoteViaSimulation() to measure actual compute units consumed.
Convenience swap and liquidity builders automatically plan treasury CPI accounts, associated token accounts, and NAV and APY remaining accounts when the capital provider has treasury enabled. Transfer-hook accounts are resolved for supported routes. Treasury-enabled add/remove-liquidity builders reject transfer-hook mints; swap builders reject hooks on stable inputs that may be swept or stable outputs that may require Treasury redemption. Those routes require raw builders with explicitly resolved remaining accounts.

Swap Examples

Buy RWA with USDC (ExactIn) Swap exactly 1000 USDC for USTB, accepting at minimum 990 USTB:
Sell RWA for USDC (ExactIn) Swap exactly 100 USTB for USDC, accepting at minimum 95 USDC:
Buy Exact RWA Amount (ExactOut) Receive exactly 100 USTB, spending at most 105 USDC:
Sell RWA for Exact USDC (ExactOut) Receive exactly 1000 USDC, spending at most 1010 USTB:

Swap Parameters Reference

Swap instruction and transaction results expose the resolved onchainDirection (SwapDirection.QuoteToBase or SwapDirection.BaseToQuote).

Capital Provider Admin and Liquidity

Capital providers create, configure, and close their own pairs, and manage vault balances, using the same instruction-first API. The SDK derives PDAs, vault authorities, token program IDs, and Token-2022 transfer-hook accounts automatically. The capital provider signs every builder below. For close_pair, the global-config admin is referenced as a non-signing account; the SDK fetches it from GlobalConfig when it is not supplied via admin.

Permissioned Token Whitelisting

For every permissioned mint used by a capital provider, the token issuer must separately whitelist (allowlist) both of these addresses:
  1. Capital provider admin address — the liquidityProvider wallet that owns and authorizes the capital provider’s source and recipient token accounts.
  2. Vault authority PDA — the capital provider’s PDA derived from ["vault_authority", liquidityProvider] that owns the program’s vault ATAs.
These are different addresses with independent issuer permissions. Whitelisting the capital provider admin address does not whitelist the vault authority PDA, and whitelisting the vault authority PDA does not whitelist the capital provider admin address. After the relevant token accounts exist, confirm that the issuer has thawed both the capital provider’s token account and the PDA-owned vault ATA for each permissioned mint. init_pair can create the vault ATA, but the Multiliquid program and SDK cannot grant issuer approval or bypass a frozen-account restriction. add_liquidity, remove_liquidity, and swaps involving that vault will fail until the required accounts are authorized and thawed. Derive the address that must be submitted to the issuer before funding the pair. Kit PDA helpers are asynchronous:
Token-2022 program detection and transfer-hook account resolution are automatic, but issuer whitelisting and token-account thawing are external onboarding steps.

Create a Pair

init_pair is signed by the capital provider: the capital provider pays for the Pair PDA, capital provider stable configs, vault ATAs, and UserVaultInfo accounts. Callers pass unordered mintA and mintB values. The builder compares the decoded 32-byte public keys, derives the canonical quote/base PDA order, and places optional capital provider stable configs according to each side’s on-chain AssetType. Stable-on-base, stable-on-quote, and RWA-to-RWA pairs are supported. Fee combinations where redemptionFeeBps + discountRateBps >= 10_000 are rejected.

Update Pair Configuration

Update and close builders resolve both possible PDA mint orders and use the existing pair account’s stored quote/base layout. Pass pairAddress when both orders exist.

Add and Remove Liquidity

Use lpTokenAccount when the capital provider uses a non-ATA token account. Set autoCreateAta: false if token accounts are managed externally, and use remainingAccounts only for extra accounts beyond those derived by the SDK. For Treasury-backed removal, ExactOut specifies asset output and uses idle vault liquidity before redeeming the shortfall. ExactIn (the SDK default) specifies value in Treasury token units; yield accounting can make the actual receipt-token burn smaller. amountThreshold is a minimum asset output for ExactIn and a maximum receipt-token burn for ExactOut. Add-liquidity always uses ExactIn and treats the threshold as minimum receipt output. These optional bounds apply only when the corresponding Treasury CPI occurs; the SDK does not infer a bound when it is omitted. The capital provider enables routing with the swap program’s set_lp_treasury_enabled instruction, available through swapProgramClient. Enabling routing does not move existing balances. The Pair Management page describes sweep conditions and account ownership.

Close a Pair

close_pair is signed by the capital provider and reclaims rent for the Pair PDA, and — when the pair was the last consumer of a shared vault — the vault ATA and UserVaultInfo PDA. Any remaining vault balances are returned to the capital provider’s recipient token accounts. The SDK reads UserVaultInfo.used for both vaults and only resolves Token-2022 transfer-hook accounts when the shared-vault counter shows the close will transfer. The capital provider’s Treasury position must be empty before a pair can close, including after routing is disabled. After withdrawing and settling principal, yield, and pending deposits, the capital provider can use close_treasury_position to recover Treasury account rent and close_lp_fee_account to recover the fee account after closing its pairs.
For Token-2022 mints with a transfer hook, the capital provider recipient token account must already exist on-chain when the builder runs if the close will transfer vault balances. The builder resolves hook accounts from current on-chain data, so a recipient ATA that only exists as a setup instruction will be rejected. Pre-create the recipient account or pass it via lpMintATokenAccount / lpMintBTokenAccount.Hook account resolution uses a build-time snapshot of the vault balance, while the program transfers the execution-time balance during close_pair.

Event Parsing

Parse SwapExecuted events from transaction logs:

Error Handling

The SDK provides structured error parsing for on-chain program errors:

Amount Formatting

Convert between native token amounts (with decimals) and human-readable strings. Formatting uses decimal strings and bigint; no floating-point arithmetic is involved:

Reading On-Chain State

Fetch All Swap State

Fetch Individual Accounts

Treasury account reads and instruction builders are available through treasuryProgramClient. Its fetchUserPosition and getUserPositionDecoder include pending deposits beyond the initial 50 slots. The Treasury IDL defines the direct deposit, redeem, rate-posting, and administration interfaces.

PDA Derivation

All program accounts are deterministic PDAs. Kit PDA derivation is asynchronous; every helper returns Promise<ProgramDerivedAddress>, whose resolved value is [address, bump]:
Derive an existing pair with the registry entry’s stored quoteMint / baseMint. For a new pair, canonicalize the unordered mints first:
Capital provider vault token accounts are ATAs for (mint, vaultAuthority), where vaultAuthority is the capital provider’s PDA derived from ["vault_authority", lp]. Fee vault token accounts are ATAs for (feeMint, programAuthority), where programAuthority is the global PDA derived from ["program_authority"]. When deriving vault or fee-vault addresses manually for Token-2022 mints, pass the Token-2022 program ID as the optional tokenProgram argument. Swap and liquidity builders detect the mint owner and derive the correct ATAs automatically.

Integration Best Practices

Security

  1. Use Hardware Wallets: Never store private keys in code or environment variables for production
  2. Simulate First: Always call getQuote() or getQuoteViaSimulation() before executing
  3. Set Slippage: Always provide minAmountOut or maxAmountIn for production swaps
  4. Check Pause State: Call checkPauseStatus() before building transactions

Performance

  1. Use Client-Side Quotes: getQuote() is faster than getQuoteViaSimulation() for most use cases
  2. Set Compute Budget: Use computeUnitsConsumed from simulation to set appropriate compute limits
  3. Reuse Client: Create one MultiliquidClient instance and reuse across operations
  4. Use the Registry: getPairs() uses the metadata API with a bundled fallback; getOfflinePairs() avoids network calls

Operational

  1. Priority Fees: Set appropriate priority fees via compute-budget instructions for time-sensitive operations
  2. Confirmation: Wait for confirmed or finalized commitment before treating a swap as complete
  3. Event Monitoring: Use parseSwapEventsFromTransaction() to verify swap results after execution
  4. Error Recovery: Use parseSwapError() to categorize failures and implement appropriate retry logic

Testing

Devnet

Configure the client for devnet testing:
The Deployments page records the devnet program status. The SDK’s configured program address does not establish that the matching swap and Treasury interfaces are deployed on that cluster.

Mainnet

Support and Resources