> ## Documentation Index
> Fetch the complete documentation index at: https://docs.multiliquid.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Integration Guide

> Developer guide for integrating with the Multiliquid Protocol on Solana using the TypeScript SDK

## 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 liquidity-provider administration flows.

<CardGroup cols={2}>
  <Card title="SDK Installation" icon="download" href="#installation">
    Install and configure the TypeScript SDK
  </Card>

  <Card title="Pair Discovery" icon="magnifying-glass" href="#pair-discovery">
    Find available trading pairs
  </Card>

  <Card title="Quoting" icon="calculator" href="#quoting">
    Get swap quotes using client-side math or simulation
  </Card>

  <Card title="Executing Swaps" icon="arrows-rotate" href="#executing-swaps">
    Build and submit swap transactions
  </Card>

  <Card title="LP Admin" icon="gear" href="#lp-admin-and-liquidity">
    Create, update, and close pairs and manage liquidity
  </Card>

  <Card title="Ladder Pricing Model" icon="chart-line" href="/svm/guides/ladder-pricing-model">
    Implement a rolling 24-hour laddered pricing model as an LP
  </Card>
</CardGroup>

## Installation

```bash theme={null}
npm install @uniformlabs/multiliquid-svm-sdk@0.3.2
```

The current SDK package is `0.3.2`. The package declares runtime dependencies on:

* `@solana/web3.js ^1.98.4`
* `@coral-xyz/anchor ^0.32.1`
* `@solana/spl-token ^0.4.14`

Install `@solana/web3.js` and `@coral-xyz/anchor` directly in your application when importing them in integration code:

```bash theme={null}
npm install @solana/web3.js@^1.98.4 @coral-xyz/anchor@^0.32.1
```

## Client Initialization

The SDK provides a `MultiliquidClient` class that wraps all functionality:

```typescript theme={null}
import { Connection, PublicKey } from "@solana/web3.js";
import { MultiliquidClient } from "@uniformlabs/multiliquid-svm-sdk";

const connection = new Connection("https://api.mainnet-beta.solana.com");

const client = new MultiliquidClient({
  connection,
  cluster: "mainnet-beta",   // "devnet" | "mainnet-beta"
  commitment: "confirmed",   // optional, default: "confirmed"
});
```

The `cluster` parameter determines which built-in pair registry is used. Both devnet and mainnet use the same program ID: `HaWDr94LKJQT2fXuHJGsSGeQf6M7S68FXpEQLcE5RYs6`.

## Pair Discovery

### Built-In Registry (No RPC)

The SDK ships with a hardcoded registry of known pairs for instant lookup:

```typescript theme={null}
const pairs = client.getPairs();
// Returns all registered pairs for the configured cluster

// Filter by asset
const usdcPairs = client.getPairs({
  stableMint: new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"),
});
```

Each returned entry includes the pair PDA, both mints, the liquidity provider, and token decimals:

```typescript theme={null}
for (const pair of pairs) {
  console.log(pair.label);                 // e.g. "USDC / USTB"
  console.log(pair.pair.toBase58());
  console.log(pair.stableMint.toBase58());
  console.log(pair.assetMint.toBase58());
  console.log(pair.liquidityProvider.toBase58());
  console.log(pair.stableDecimals);        // e.g. 6
  console.log(pair.assetDecimals);         // e.g. 6 for USTB
}

// Use the entry directly in swap params
const pair = pairs[0];
const quote = await client.getQuote({
  user: wallet.publicKey,
  liquidityProvider: pair.liquidityProvider,
  stableMint: pair.stableMint,
  assetMint: pair.assetMint,
  amount: new BN(1_000_000_000),
  swapDirection: SwapDirection.StableToAsset,
  swapType: SwapType.ExactIn,
});
```

### On-Chain Discovery (RPC)

For dynamically discovering pairs not in the registry:

```typescript theme={null}
const pairs = await client.discoverPairs({
  stableMint: USDC_MINT,
});
// Returns the same PairRegistryEntry format as getPairs()
```

## Checking Pair Status

Before executing a swap, verify the pair is active:

```typescript theme={null}
const status = await client.checkPauseStatus(stableMint, assetMint, lp);

if (status.anyPaused) {
  console.log("Swap blocked:", status.pauseReasons);
  // e.g. ["ProgramPaused"], ["PairPaused"], ["RwaPaused"], ["StablePaused"], ["LpStablePaused"]
}
```

The protocol has five independent pause levels: global config, RWA asset config, stablecoin asset config, LP stablecoin config, and pair config. All must be unpaused for swaps to execute.

## Quoting

The SDK offers two quoting methods: client-side math replication and on-chain simulation.

### Client-Side Quote

Replicates the on-chain Rust math exactly using BigInt. Fetches current NAV prices from oracles and computes the swap result:

```typescript theme={null}
import { PublicKey } from "@solana/web3.js";
import { BN } from "@coral-xyz/anchor";
import { SwapDirection, SwapType } from "@uniformlabs/multiliquid-svm-sdk";

const USDC = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
const USTB = new PublicKey("CCz3SGVziFeLYk2xfEstkiqJfYkjaSWb2GCABYsVcjo2");
const LP = new PublicKey("C8Mi6kn7ajFWuNe4ZmsR9A6fdqRYhzXFoqVBGMsdJ2Uf");

const quote = await client.getQuote({
  user: wallet.publicKey,
  liquidityProvider: LP,
  stableMint: USDC,
  assetMint: USTB,
  amount: new BN(1_000_000_000),  // 1000 USDC (6 decimals)
  swapDirection: SwapDirection.StableToAsset,
  swapType: SwapType.ExactIn,
});

console.log("Output:", MultiliquidClient.toHumanReadable(quote.amountOut, 6));
console.log("Protocol fees:", quote.protocolFees.toString());
console.log("Asset NAV:", quote.assetNav.toString());
console.log("Stable NAV:", quote.stableNav.toString());
```

The `SwapQuote` includes:

| Field            | Description                            |
| :--------------- | :------------------------------------- |
| `amountIn`       | Total input amount                     |
| `amountOut`      | Output amount received                 |
| `protocolFees`   | Protocol fee collected                 |
| `discountAmount` | LP fee amount (redemption or discount) |
| `assetNav`       | RWA NAV price (9 decimals)             |
| `stableNav`      | Stablecoin NAV price (9 decimals)      |

### Simulation Quote

Runs the swap instruction against the validator via `simulateTransaction` and parses the emitted event:

```typescript theme={null}
const simQuote = await client.getQuoteViaSimulation({
  user: wallet.publicKey,
  liquidityProvider: LP,
  stableMint: USDC,
  assetMint: USTB,
  amount: new BN(1_000_000_000),
  swapDirection: SwapDirection.StableToAsset,
  swapType: SwapType.ExactIn,
});

console.log("Output:", simQuote.amountOut.toString());
console.log("Compute units:", simQuote.computeUnitsConsumed);
```

The simulation quote also returns `computeUnitsConsumed`, which is useful for setting compute budget instructions.

## Executing Swaps

The SDK is **instruction-first**: the primary API returns `TransactionInstruction` objects for maximum composability. A convenience method for building full transactions is also available.

### Building a Swap Transaction

```typescript theme={null}
const { transaction, accounts } = await client.buildSwapTransaction({
  user: wallet.publicKey,
  liquidityProvider: LP,
  stableMint: USDC,
  assetMint: USTB,
  amount: new BN(1_000_000_000),  // 1000 USDC
  swapDirection: SwapDirection.StableToAsset,
  swapType: SwapType.ExactIn,
  minAmountOut: new BN(990_000_000),  // slippage protection
});

// Sign and send
transaction.sign([wallet]);
const signature = await connection.sendTransaction(transaction);
await connection.confirmTransaction(signature, "confirmed");
```

### Building Individual Instructions

For more control, build the swap instruction separately and compose it with other instructions (e.g., compute budget):

```typescript theme={null}
import { ComputeBudgetProgram } from "@solana/web3.js";

const { instruction, setupInstructions } = await client.buildSwapInstruction({
  user: wallet.publicKey,
  liquidityProvider: LP,
  stableMint: USDC,
  assetMint: USTB,
  amount: new BN(1_000_000_000),
  swapDirection: SwapDirection.StableToAsset,
  swapType: SwapType.ExactIn,
  minAmountOut: new BN(990_000_000),
});

// setupInstructions contains ATA creation if needed (autoCreateAta defaults to true).
// The builder also resolves Token-2022 transfer-hook accounts for known hook mints.
// Compose with compute budget:
const instructions = [
  ComputeBudgetProgram.setComputeUnitLimit({ units: 150_000 }),
  ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 50_000 }),
  ...setupInstructions,
  instruction,
];
```

<Note>
  The SDK does not include `ComputeBudgetProgram` instructions automatically. Set compute unit limits and priority fees based on your requirements. Use `getQuoteViaSimulation()` to measure actual compute units consumed.
</Note>

### Swap Examples

**Buy RWA with USDC (ExactIn)**

Swap exactly 1000 USDC for USTB, accepting at minimum 990 USTB:

```typescript theme={null}
const { transaction } = await client.buildSwapTransaction({
  user: wallet.publicKey,
  liquidityProvider: LP,
  stableMint: USDC,
  assetMint: USTB,
  amount: new BN(1_000_000_000),      // 1000 USDC (6 decimals)
  swapDirection: SwapDirection.StableToAsset,
  swapType: SwapType.ExactIn,
  minAmountOut: new BN(990_000_000),  // min 990 USTB (6 decimals)
});
```

**Sell RWA for USDC (ExactIn)**

Swap exactly 100 USTB for USDC, accepting at minimum 95 USDC:

```typescript theme={null}
const { transaction } = await client.buildSwapTransaction({
  user: wallet.publicKey,
  liquidityProvider: LP,
  stableMint: USDC,
  assetMint: USTB,
  amount: new BN(100_000_000),        // 100 USTB (6 decimals)
  swapDirection: SwapDirection.AssetToStable,
  swapType: SwapType.ExactIn,
  minAmountOut: new BN(95_000_000),   // min 95 USDC (6 decimals)
});
```

**Buy Exact RWA Amount (ExactOut)**

Receive exactly 100 USTB, spending at most 105 USDC:

```typescript theme={null}
const { transaction } = await client.buildSwapTransaction({
  user: wallet.publicKey,
  liquidityProvider: LP,
  stableMint: USDC,
  assetMint: USTB,
  amount: new BN(100_000_000),        // exact 100 USTB out (6 decimals)
  swapDirection: SwapDirection.StableToAsset,
  swapType: SwapType.ExactOut,
  maxAmountIn: new BN(105_000_000),   // max 105 USDC (6 decimals)
});
```

**Sell RWA for Exact USDC (ExactOut)**

Receive exactly 1000 USDC, spending at most 1010 USTB:

```typescript theme={null}
const { transaction } = await client.buildSwapTransaction({
  user: wallet.publicKey,
  liquidityProvider: LP,
  stableMint: USDC,
  assetMint: USTB,
  amount: new BN(1_000_000_000),      // exact 1000 USDC out (6 decimals)
  swapDirection: SwapDirection.AssetToStable,
  swapType: SwapType.ExactOut,
  maxAmountIn: new BN(1_010_000_000),  // max 1010 USTB (6 decimals)
});
```

### Swap Parameters Reference

| Parameter                | Type                       | Description                                                                   |
| :----------------------- | :------------------------- | :---------------------------------------------------------------------------- |
| `user`                   | `PublicKey`                | Transaction signer                                                            |
| `liquidityProvider`      | `PublicKey`                | LP that owns the pair                                                         |
| `stableMint`             | `PublicKey`                | Stablecoin token mint                                                         |
| `assetMint`              | `PublicKey`                | RWA token mint                                                                |
| `amount`                 | `BN`                       | Primary amount in native token units (input for ExactIn, output for ExactOut) |
| `swapDirection`          | `SwapDirection`            | `StableToAsset` or `AssetToStable`                                            |
| `swapType`               | `SwapType`                 | `ExactIn` or `ExactOut`                                                       |
| `minAmountOut`           | `BN` (optional)            | Minimum output for ExactIn swaps                                              |
| `maxAmountIn`            | `BN` (optional)            | Maximum input for ExactOut swaps                                              |
| `userStableTokenAccount` | `PublicKey` (optional)     | Override the user's stablecoin ATA                                            |
| `userAssetTokenAccount`  | `PublicKey` (optional)     | Override the user's asset ATA                                                 |
| `autoCreateAta`          | `boolean` (optional)       | Auto-create ATAs if missing (default: `true`)                                 |
| `remainingAccounts`      | `AccountMeta[]` (optional) | Extra accounts appended after SDK-derived oracle and transfer-hook accounts   |

## LP Admin and Liquidity

Liquidity 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 LP signs every builder below; the global-config `admin` is referenced as a non-signing account (the SDK fetches it from `GlobalConfig` when not supplied via `admin`).

### Permissioned Token Whitelisting

For every permissioned mint used by an LP, the token issuer must separately whitelist (allowlist) both of these addresses:

1. **LP admin address** — the `liquidityProvider` wallet that owns and authorizes the LP's source and recipient token accounts.
2. **Vault authority PDA** — the LP-specific PDA derived from `["vault_authority", liquidityProvider]` that owns the program's vault ATAs.

These are different addresses with independent issuer permissions. Whitelisting the LP admin address does **not** whitelist the vault authority PDA, and whitelisting the vault authority PDA does **not** whitelist the LP admin address.

After the relevant token accounts exist, confirm that the issuer has thawed both the LP-owned 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:

```typescript theme={null}
const lpAdmin = wallet.publicKey;
const [vaultAuthority] = client.deriveVaultAuthority(lpAdmin);

console.log("LP admin to whitelist:", lpAdmin.toBase58());
console.log("Vault authority PDA to whitelist:", vaultAuthority.toBase58());
```

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 LP-signed: the liquidity provider pays for the Pair PDA, LP-stable config, vault ATAs, and UserVaultInfo accounts. The builder pre-validates that the stable mint's asset config has type `Stable` and the asset mint's asset config has type `Rwa`.

```typescript theme={null}
const { instruction: initPairIx } = await client.buildInitPairInstruction({
  liquidityProvider: wallet.publicKey,
  stableMint: USDC,
  assetMint: USTB,
  redemptionFeeBps: 50, // 0.5% Stable → RWA fee
  discountRateBps: 25,  // 0.25% RWA → Stable fee
  // admin: optional cached admin pubkey; fetched from GlobalConfig if omitted
});

// Or get a signed-ready VersionedTransaction directly:
const { transaction: initPairTx } = await client.buildInitPairTransaction({
  liquidityProvider: wallet.publicKey,
  stableMint: USDC,
  assetMint: USTB,
  redemptionFeeBps: 50,
  discountRateBps: 25,
});
```

### Update Pair Configuration

```typescript theme={null}
const { instruction: updatePairIx } = await client.buildUpdatePairInstruction({
  liquidityProvider: wallet.publicKey,
  stableMint: USDC,
  assetMint: USTB,
  redemptionFeeBps: 10,
  discountRateBps: 15,
  paused: false,
});
```

### Add and Remove Liquidity

```typescript theme={null}
const { instruction: addLiquidityIx, setupInstructions } =
  await client.buildAddLiquidityInstruction({
    liquidityProvider: wallet.publicKey,
    mint: USDC,
    amount: new BN(1_000_000_000),
  });

const { transaction: removeLiquidityTx } =
  await client.buildRemoveLiquidityTransaction({
    liquidityProvider: wallet.publicKey,
    mint: USTB,
    amount: new BN(500_000_000),
  });
```

Use `lpTokenAccount` when the liquidity 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.

### Close a Pair

`close_pair` is LP-signed 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 LP'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.

```typescript theme={null}
const { instruction: closePairIx, setupInstructions } =
  await client.buildClosePairInstruction({
    liquidityProvider: wallet.publicKey,
    stableMint: USDC,
    assetMint: USTB,
    // Optional overrides:
    // lpStableTokenAccount, lpAssetTokenAccount — non-ATA recipient accounts
    // autoCreateAta: false                       — manage recipient ATAs externally
    // admin                                      — cached GlobalConfig admin
  });

const { transaction: closePairTx } = await client.buildClosePairTransaction({
  liquidityProvider: wallet.publicKey,
  stableMint: USDC,
  assetMint: USTB,
});
```

<Warning>
  For Token-2022 mints with a transfer hook, the LP 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 `lpStableTokenAccount` / `lpAssetTokenAccount`.

  Hook account resolution uses a build-time snapshot of the vault balance, while the program transfers the execution-time balance during `close_pair`.
</Warning>

## Event Parsing

Parse `SwapExecuted` events from transaction logs:

```typescript theme={null}
// From a transaction signature
const events = await client.parseSwapEventsFromTransaction(signature);

for (const event of events) {
  console.log("Amount in:", event.amountIn.toString());
  console.log("Amount out:", event.amountOut.toString());
  console.log("Protocol fee:", event.protocolFeeAmount.toString());
  console.log("Direction:", event.swapDirection);
}
```

```typescript theme={null}
// From raw logs (e.g., from websocket subscription)
const events = client.parseSwapEventsFromLogs(logs);
```

## Error Handling

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

```typescript theme={null}
try {
  const signature = await connection.sendTransaction(transaction);
  await connection.confirmTransaction(signature);
} catch (error) {
  const parsed = client.parseSwapError(error);

  if (parsed) {
    console.log("Error:", parsed.name, "-", parsed.message);
    console.log("Category:", parsed.category);

    switch (parsed.category) {
      case "slippage":
        // AmountOutTooLow or AmountInTooHigh — re-quote with fresh state
        break;
      case "paused":
        // ProgramPaused, PairPaused, RwaPaused, StablePaused — wait or skip
        break;
      case "oracle":
        // InvalidNav — NAV source unavailable or divergent
        break;
      case "liquidity":
        // InsufficientLiquidity — try smaller amount or different pair
        break;
      case "input_validation":
        // Fix input parameters
        break;
      case "math":
        // MathOverflow/Underflow — trade may be too small or too large
        break;
    }
  }
}
```

## Amount Formatting

Convert between native token amounts (with decimals) and human-readable strings:

```typescript theme={null}
// Native to human-readable
MultiliquidClient.toHumanReadable(100_000_000n, 6);   // "100"
MultiliquidClient.toHumanReadable(99_800_000_000n, 9); // "99.8"

// Human-readable to native
MultiliquidClient.toNativeAmount("100", 6);            // 100_000_000n
MultiliquidClient.toNativeAmount("99.8", 9);           // 99_800_000_000n
```

## Reading On-Chain State

### Fetch All Swap State (Single RPC Call)

```typescript theme={null}
const state = await client.fetchSwapState(stableMint, assetMint, lp);

console.log("Protocol fees:", state.globalConfig.protocolFeesBps, "bps");
console.log("Redemption fee:", state.pair.redemptionFeeBps, "bps");
console.log("Discount rate:", state.pair.discountRateBps, "bps");
console.log("Pair paused:", state.pair.paused);
console.log("LP stable paused:", state.lpStableConfig.paused);
console.log("Program paused:", state.globalConfig.paused);
```

### Fetch Individual Accounts

```typescript theme={null}
const globalConfig = await client.fetchGlobalConfig();
const pair = await client.fetchPair(pairAddress);
const assetConfig = await client.fetchAssetConfig(configAddress);
const lpStableConfig = await client.fetchLpStableConfig(lpStableConfigAddress);
```

## PDA Derivation

All program accounts are deterministic PDAs. The SDK provides derivation helpers:

```typescript theme={null}
const [globalConfig] = client.deriveGlobalConfig();
const [assetConfig] = client.deriveAssetConfig(mint);
const [pair] = client.derivePair(lp, stableMint, assetMint);
const [vaultAuthority] = client.deriveVaultAuthority(lp);
const [vault] = client.deriveVault(mint, lp);
const [feeVault] = client.deriveFeeVault(stableMint);
const [lpStableConfig] = client.deriveLpStableConfig(stableMint, lp);
const [programAuthority] = client.deriveProgramAuthority();
```

LP vault token accounts are ATAs for `(mint, vaultAuthority)`, where `vaultAuthority` is the LP-specific PDA derived from `["vault_authority", lp]`. Fee vault token accounts are ATAs for `(stableMint, 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 Built-In Registry**: `getPairs()` returns instantly without RPC calls

### Operational

1. **Priority Fees**: Set appropriate priority fees via `ComputeBudgetProgram.setComputeUnitPrice()` 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:

```typescript theme={null}
const connection = new Connection("https://api.devnet.solana.com");
const client = new MultiliquidClient({
  connection,
  cluster: "devnet",
});

// Devnet pairs use mock tokens — see SDK registry for addresses
const devnetPairs = client.getPairs();
```

### Mainnet

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

## Support and Resources

* **Protocol Website**: [https://www.multiliquid.xyz/](https://www.multiliquid.xyz/)
* **Program IDL**: See [Program IDL](/svm/idl) page
* **Deployment Addresses**: See [Deployments](/svm/deployments) page
