Skip to main content

Integration Overview

The Multiliquid Protocol offers multiple integration paths depending on your institution’s technical requirements and operational model. This guide covers the available integration options, technical requirements, and best practices for institutional developers.

Multiliquid UI Integration

Status: Coming Soon Website: https://www.multiliquid.xyz/ The Multiliquid UI provides a web interface for executing swaps through the protocol. The UI handles wallet integration, transaction signing, and real-time status updates.

Key Features

  • Wallet Integration: Support for MetaMask, WalletConnect, and hardware wallets
  • Real-Time Quotes: Deterministic NAV-based pricing
  • Transaction Simulation: Pre-flight checks before submission
  • Intuitive UX: Straightforward UX designed for ease of use
  • Compliance Hooks: Integration points for KYC/AML validation

User Workflow

  1. Connect Wallet: User connects their wallet
  2. Select Assets: Choose RWA and stablecoin for swap
  3. Enter Amount: Specify swap amount
  4. Get Quote: UI calls contract view functions for exact pricing
  5. Review & Approve: User reviews swap details and exact amounts
  6. Execute Swap: Transaction submitted to blockchain
  7. Confirmation: Real-time status updates until finalization
The Multiliquid UI is recommended for institutions that prefer a managed interface. It handles all technical complexity while maintaining non-custodial security.

Direct Contract Integration

For institutions requiring programmatic control, direct smart contract integration is available.

Prerequisites

  • Solidity 0.8.x knowledge
  • Web3 library (ethers.js, web3.js, viem)
  • Understanding of ERC-20 token approvals
  • Familiarity with transaction signing
NAV-Based Pricing - No Slippage Tolerance RequiredThe Multiliquid Protocol uses deterministic NAV-based pricing for all swaps. Unlike AMM-style DEXs (Uniswap, Curve, etc.), there is no price slippage based on liquidity depth or market impact.Critical Integration Rule: Always use the exact amounts returned by the calculation functions (calculateStableAmt, calculateRWAAmt, calculateStablecoinRequired, calculateRWARequired) without applying any slippage tolerance. These functions return the precise amounts that will be used in the swap transaction.Do not add slippage buffers or tolerances to the calculated amounts. The pricing is deterministic and based on real-time NAV values.

Integration Steps

1. Connect to Protocol Contracts

import { ethers } from 'ethers';

// Contract addresses (contact Multiliquid team for current addresses)
const MULTILIQUID_SWAP_ADDRESS = '0x...';
const RWA_TOKEN_ADDRESS = '0x...';
const STABLECOIN_ADDRESS = '0x...';

// ABIs (see #contract-abis section)
const MultiliquidSwapABI = [...];
const ERC20_ABI = [...];

// Initialize provider and signer
const provider = new ethers.JsonRpcProvider('https://...');
const signer = new ethers.Wallet(privateKey, provider);

// Initialize contracts
const multiliquidSwap = new ethers.Contract(
    MULTILIQUID_SWAP_ADDRESS,
    MultiliquidSwapABI,
    signer
);

const rwaToken = new ethers.Contract(
    RWA_TOKEN_ADDRESS,
    ERC20_ABI,
    signer
);

const stablecoin = new ethers.Contract(
    STABLECOIN_ADDRESS,
    ERC20_ABI,
    signer
);

2. Approve Token Spending

Before executing swaps, approve the MultiliquidSwap contract to spend your tokens:
/**
 * Approve RWA tokens for swapping to stablecoin
 */
async function approveRWA(amount) {
    const tx = await rwaToken.approve(
        MULTILIQUID_SWAP_ADDRESS,
        amount
    );
    await tx.wait();
    console.log('RWA approval confirmed');
}

/**
 * Approve stablecoin for swapping to RWA
 */
async function approveStablecoin(amount) {
    const tx = await stablecoin.approve(
        MULTILIQUID_SWAP_ADDRESS,
        amount
    );
    await tx.wait();
    console.log('Stablecoin approval confirmed');
}
For production systems, implement approval tracking to avoid redundant transactions. Consider using the ERC-20 allowance() function to check existing approvals before approving again.

3. Get Swap Quote

Use view functions to calculate swap amounts before execution:
/**
 * Get quote for RWA → Stablecoin swap
 */
async function getStablecoinQuote(rwaID, stablecoinID, rwaAmount) {
    const [stableAmt, feeAmt] = await multiliquidSwap.calculateStableAmt(
        stablecoinID,
        rwaID,
        rwaAmount
    );

    return {
        stablecoinAmount: stableAmt,
        fee: feeAmt
    };
}

/**
 * Get quote for Stablecoin → RWA swap
 */
async function getRWAQuote(stablecoinID, rwaID, stablecoinAmount) {
    const [rwaAmt, feeAmt, redemptionFeeAmt] = await multiliquidSwap.calculateRWAAmt(
        rwaID,
        stablecoinID,
        stablecoinAmount
    );

    return {
        rwaAmount: rwaAmt,
        protocolFee: feeAmt,
        redemptionFee: redemptionFeeAmt,
        totalFees: feeAmt + redemptionFeeAmt
    };
}

4. Execute Swap

NAV-Based Pricing: All swaps in the Multiliquid Protocol use deterministic NAV-based pricing. Unlike AMM-style DEXs, there is no price slippage or liquidity depth to account for. The calculation functions (calculateStableAmt, calculateRWAAmt, etc.) return exact amounts that will be received or required.You should use the calculated amounts directly without applying slippage tolerance. The amounts returned by the calculate functions are the exact amounts that will be used in the swap.
Example: RWA → Stablecoin (ExactIn)
/**
 * Swap exact RWA amount for stablecoin
 * Uses NAV-based pricing - no slippage tolerance needed
 */
async function swapRWAToStablecoin(
    rwaID,
    rwaAmount,
    stablecoinID
) {
    // Get exact expected output using calculate function
    const quote = await getStablecoinQuote(rwaID, stablecoinID, rwaAmount);

    // Prepare swap parameters
    // Use exact calculated amount - NAV-based pricing is deterministic
    const swapParams = {
        stablecoinID: stablecoinID,
        stablecoinAmount: quote.stablecoinAmount,  // Exact calculated output
        rwaID: rwaID,
        rwaAmount: rwaAmount,                      // Exact input
        stablecoinMetadata: '0x',                  // Optional metadata
        rwaMetadata: '0x'                          // Optional metadata
    };

    // Execute swap
    const tx = await multiliquidSwap.swapIntoStablecoin(swapParams);
    const receipt = await tx.wait();

    console.log('Swap confirmed:', receipt.transactionHash);
    return receipt;
}
Example: Stablecoin → RWA (ExactOut)
/**
 * Swap stablecoin for exact RWA amount
 * Uses NAV-based pricing - no slippage tolerance needed
 */
async function swapStablecoinToRWA(
    stablecoinID,
    rwaID,
    exactRWAAmount
) {
    // Calculate exact required input
    const [requiredStable, feeAmt, redemptionFeeAmt] =
        await multiliquidSwap.calculateStablecoinRequired(
            rwaID,
            stablecoinID,
            exactRWAAmount
        );

    // Prepare swap parameters
    // Use exact calculated amount - NAV-based pricing is deterministic
    const swapParams = {
        rwaID: rwaID,
        rwaAmount: exactRWAAmount,        // Exact output
        stablecoinID: stablecoinID,
        stablecoinAmount: requiredStable, // Exact calculated input
        stablecoinMetadata: '0x',
        rwaMetadata: '0x'
    };

    // Execute swap
    const tx = await multiliquidSwap.swapIntoRWAExactOut(swapParams);
    const receipt = await tx.wait();

    console.log('Swap confirmed:', receipt.transactionHash);
    return receipt;
}
Example: RWA → RWA (Direct Asset Swap)
/**
 * Swap one RWA directly for another RWA
 * Uses NAV-based pricing - no slippage tolerance needed
 * Pricing occurs via intermediate stablecoin pricing
 */
async function swapRWAToRWA(
    rwaInID,
    rwaInAmount,
    rwaOutID,
    stablecoinID
) {
    // Get exact expected output using calculate function
    const outputs = await multiliquidSwap.calculateRWAToRWA(
        stablecoinID,   // Pricing stablecoin (determines which issuer's rates apply)
        rwaInID,        // RWA to swap from
        rwaOutID,       // RWA to swap into
        rwaInAmount,    // Exact input amount
        0,              // Minimum output (0 for exactIn)
        false           // exactOut = false (this is exactIn)
    );

    // Prepare swap parameters
    // Use exact calculated amount - NAV-based pricing is deterministic
    const swapParams = {
        exactOut: false,
        stablecoinID: stablecoinID,              // Pricing stablecoin
        rwaInID: rwaInID,
        rwaInAmount: rwaInAmount,                // Exact input
        rwaOutID: rwaOutID,
        rwaOutAmount: outputs.rwaOutAmount,      // Exact calculated output
        rwaInMetadata: '0x',                     // Optional metadata
        rwaOutMetadata: '0x'                     // Optional metadata
    };

    // Execute swap
    const tx = await multiliquidSwap.swapRWAToRWA(swapParams);
    const receipt = await tx.wait();

    console.log('RWA-to-RWA Swap confirmed:', receipt.transactionHash);
    console.log('Protocol Fee:', outputs.protocolFeeAmt.toString());
    console.log('Redemption Fee:', outputs.redemptionFeeAmt.toString());
    return receipt;
}
Example: RWA → RWA (ExactOut)
/**
 * Swap RWA for exact amount of another RWA
 * Uses NAV-based pricing - no slippage tolerance needed
 */
async function swapRWAToRWAExactOut(
    rwaInID,
    rwaOutID,
    exactRWAOutAmount,
    stablecoinID
) {
    // Calculate exact required input
    const outputs = await multiliquidSwap.calculateRWAToRWA(
        stablecoinID,
        rwaInID,
        rwaOutID,
        0,                      // Maximum input (will be calculated)
        exactRWAOutAmount,      // Exact output desired
        true                    // exactOut = true
    );

    // Prepare swap parameters
    // Use exact calculated amount - NAV-based pricing is deterministic
    const swapParams = {
        exactOut: true,
        stablecoinID: stablecoinID,
        rwaInID: rwaInID,
        rwaInAmount: outputs.rwaInAmount,       // Exact calculated input required
        rwaOutID: rwaOutID,
        rwaOutAmount: exactRWAOutAmount,        // Exact output
        rwaInMetadata: '0x',
        rwaOutMetadata: '0x'
    };

    // Execute swap
    const tx = await multiliquidSwap.swapRWAToRWA(swapParams);
    const receipt = await tx.wait();

    console.log('RWA-to-RWA ExactOut Swap confirmed:', receipt.transactionHash);
    return receipt;
}
Example: Stablecoin → Stablecoin (Direct Swap)
/**
 * Swap one stablecoin directly for another stablecoin
 * Uses NAV-based pricing - no slippage tolerance needed
 * Useful for converting between different stablecoin issuers
 */
async function swapStablecoinToStablecoin(
    stablecoinInID,
    stablecoinInAmount,
    stablecoinOutID,
    useDelegateForStablecoinOut
) {
    // Get exact expected output using calculate function
    const outputs = await multiliquidSwap.calculateStablecoinToStablecoin(
        false,                          // exactOut = false (this is exactIn)
        useDelegateForStablecoinOut,    // Which delegate to use for the swap
        stablecoinInID,                 // Stablecoin to swap from
        stablecoinInAmount,             // Exact input amount
        stablecoinOutID,                // Stablecoin to swap into
        0                               // Minimum output (0 for exactIn)
    );

    // Prepare swap parameters
    // Use exact calculated amount - NAV-based pricing is deterministic
    const swapParams = {
        exactOut: false,
        useDelegateForStablecoinOut: useDelegateForStablecoinOut,
        stablecoinInID: stablecoinInID,
        stablecoinInAmount: stablecoinInAmount,             // Exact input
        stablecoinOutID: stablecoinOutID,
        stablecoinOutAmount: outputs.stablecoinOutAmount,   // Exact calculated output
        stablecoinInMetadata: '0x',                         // Optional metadata
        stablecoinOutMetadata: '0x'                         // Optional metadata
    };

    // Execute swap
    const tx = await multiliquidSwap.swapStablecoinToStablecoin(swapParams);
    const receipt = await tx.wait();

    console.log('Stablecoin-to-Stablecoin Swap confirmed:', receipt.transactionHash);
    console.log('Protocol Fee:', outputs.protocolFeeAmt.toString());
    console.log('Acceptance Fee:', outputs.acceptanceFeeAmt.toString());
    console.log('Redemption Fee:', outputs.redemptionFeeAmt.toString());
    return receipt;
}
Example: Stablecoin → Stablecoin (ExactOut)
/**
 * Swap stablecoin for exact amount of another stablecoin
 * Uses NAV-based pricing - no slippage tolerance needed
 */
async function swapStablecoinToStablecoinExactOut(
    stablecoinInID,
    stablecoinOutID,
    exactStablecoinOutAmount,
    useDelegateForStablecoinOut
) {
    // Calculate exact required input
    const outputs = await multiliquidSwap.calculateStablecoinToStablecoin(
        true,                           // exactOut = true
        useDelegateForStablecoinOut,
        stablecoinInID,
        0,                              // Maximum input (will be calculated)
        stablecoinOutID,
        exactStablecoinOutAmount        // Exact output desired
    );

    // Prepare swap parameters
    // Use exact calculated amount - NAV-based pricing is deterministic
    const swapParams = {
        exactOut: true,
        useDelegateForStablecoinOut: useDelegateForStablecoinOut,
        stablecoinInID: stablecoinInID,
        stablecoinInAmount: outputs.stablecoinInAmount,     // Exact calculated input required
        stablecoinOutID: stablecoinOutID,
        stablecoinOutAmount: exactStablecoinOutAmount,      // Exact output
        stablecoinInMetadata: '0x',
        stablecoinOutMetadata: '0x'
    };

    // Execute swap
    const tx = await multiliquidSwap.swapStablecoinToStablecoin(swapParams);
    const receipt = await tx.wait();

    console.log('Stablecoin-to-Stablecoin ExactOut Swap confirmed:', receipt.transactionHash);
    return receipt;
}
Understanding useDelegateForStablecoinOutThe useDelegateForStablecoinOut parameter determines which stablecoin’s delegate handles the swap:
  • true: Use the output stablecoin’s delegate (mint/burn or custody operations)
  • false: Use the input stablecoin’s delegate (mint/burn or custody operations)
One stablecoin must have a delegate (full protocol integration), while the other can be “swap-only” (accepted for swaps but no dedicated delegate). The delegate handles the actual token movements, fee collection, and compliance checks.

5. Monitor Swap Events

Listen for swap events to track all protocol activity:
/**
 * Monitor RWA → Stablecoin swaps
 */
function monitorRWAToStablecoinSwaps() {
    multiliquidSwap.on('SwapIntoStablecoin', (
        stablecoinID,
        rwaID,
        user,
        stablecoinAmt,
        rwaAmt,
        feeAmt,
        stablecoinMetadata,
        rwaMetadata,
        event
    ) => {
        console.log('RWA → Stablecoin Swap:', {
            user,
            stablecoinID,
            rwaID,
            stablecoinAmount: stablecoinAmt.toString(),
            rwaAmount: rwaAmt.toString(),
            fee: feeAmt.toString(),
            txHash: event.transactionHash
        });
    });
}

/**
 * Monitor Stablecoin → RWA swaps
 */
function monitorStablecoinToRWASwaps() {
    multiliquidSwap.on('SwapIntoRWA', (
        rwaID,
        stablecoinID,
        user,
        rwaAmt,
        stablecoinAmt,
        feeAmt,
        redemptionFeeAmt,
        stablecoinMetadata,
        rwaMetadata,
        event
    ) => {
        console.log('Stablecoin → RWA Swap:', {
            user,
            rwaID,
            stablecoinID,
            rwaAmount: rwaAmt.toString(),
            stablecoinAmount: stablecoinAmt.toString(),
            protocolFee: feeAmt.toString(),
            redemptionFee: redemptionFeeAmt.toString(),
            txHash: event.transactionHash
        });
    });
}

/**
 * Monitor RWA-to-RWA swaps
 */
function monitorRWAToRWASwaps() {
    multiliquidSwap.on('RWAToRWASwap', (
        rwaInID,
        rwaOutID,
        stablecoinID,
        user,
        rwaInAmount,
        rwaOutAmount,
        protocolFeeAmt,
        redemptionFeeAmt,
        rwaInMetadata,
        rwaOutMetadata,
        event
    ) => {
        console.log('RWA-to-RWA Swap:', {
            user,
            fromRWA: rwaInID,
            toRWA: rwaOutID,
            pricingStablecoin: stablecoinID,
            inputAmount: rwaInAmount.toString(),
            outputAmount: rwaOutAmount.toString(),
            txHash: event.transactionHash
        });
    });
}

Asset IDs

Assets in the protocol are identified by bytes32 IDs. These IDs are chosen on deployment and are not derived from token addresses.
// Asset IDs are defined on deployment - contact Multiliquid team for current IDs

// Ethereum Mainnet - RWA Asset IDs
const ULTRA_ID = '0x2e6e8e8a8e57b239de932d023d4b828ead7c5efab47641f4a9e63fe75a892fcd';
const JTRSY_ID = '0x6c960f5e3ca8b7bd2ea26403dff72e1031e729046442ac82781e5652f606cc22';
const WTGXX_ID = '0xd6f4e7d1065d311484763d3eff16f9b2fb4ddd23fc099d4210f64a09db73fc21';
const BENJI_ID = '0x509a71b6090f9efb1ef94c245f59c388968bbaf7c535a026bb5123c821bc547c';
const USTB_ID = '0x6f640388b2d2075b1c9f2f195fa3d1e4b26167f5e25357294d44c4b7c877da68';

// Ethereum Mainnet - Stablecoin Asset IDs
const USDC_ID = '0x58e27f2731863499e6a23a1662ebeed9041d6b40996ea9ca7f4ebc54201823b4';

// Arbitrum Sepolia - RWA Asset IDs (testnet)
const SEPOLIA_ULTRA_ID = '0x2d207df8a1ab65b6bf02932966ea1939e705ef3f77a2432636700d98c2796657';

// Arbitrum Sepolia - Stablecoin Asset IDs (testnet)
const SEPOLIA_USDC_ID = '0x58e27f2731863499e6a23a1662ebeed9041d6b40996ea9ca7f4ebc54201823b4';
Asset IDs are NOT derived from token addresses. They are chosen on deployment using keccak256 of a specific string selected by the protocol administrators. Contact the Multiliquid team for the correct Asset IDs for your network.

Error Handling

Implement comprehensive error handling for production systems:
async function executeSwapWithErrorHandling(swapFunction) {
    try {
        const receipt = await swapFunction();
        return { success: true, receipt };
    } catch (error) {
        // Parse revert reason
        if (error.reason) {
            console.error('Transaction reverted:', error.reason);

            // Handle specific errors
            if (error.reason.includes('Daily limit exceeded')) {
                return { success: false, error: 'VOLUME_LIMIT' };
            } else if (error.reason.includes('not whitelisted')) {
                return { success: false, error: 'NOT_WHITELISTED' };
            } else if (error.reason.includes('Insufficient')) {
                return { success: false, error: 'INSUFFICIENT_BALANCE' };
            }
        }

        // Network errors
        if (error.code === 'NETWORK_ERROR') {
            return { success: false, error: 'NETWORK_ERROR' };
        }

        // Generic error
        return { success: false, error: 'UNKNOWN', details: error.message };
    }
}

REST API Integration

Status: Future Development A comprehensive REST API is planned to enable programmatic access for trading systems, portfolio management platforms, and automated treasury operations.

Planned Features

  • Quote Endpoints: Get real-time swap quotes
  • Execution Endpoints: Submit swap transactions
  • Portfolio Endpoints: Query holdings and balances
  • History Endpoints: Retrieve transaction history
  • Webhook Support: Real-time notifications for swap completion
  • Authentication: API key-based access with rate limiting

Example API Usage (Planned)

# Get swap quote (exact NAV-based pricing)
curl -X POST https://api.multiliquid.xyz/v1/quotes \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "fromAsset": "ULTRA",
    "toAsset": "USDC",
    "amount": "1000"
  }'

# Execute swap
curl -X POST https://api.multiliquid.xyz/v1/swaps \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "quoteId": "quote_abc123",
    "fromAddress": "0x...",
    "signature": "0x..."
  }'
The REST API is currently under development. Contact the Multiliquid team to express interest in early access or to provide input on API design.

Contract ABIs

Application Binary Interfaces (ABIs) are available in the Multiliquid repository.

Retrieving ABIs

From Repository:
# Clone the repository
git clone https://github.com/uniformlabs/Multiliquid.git
cd Multiliquid

# Build contracts to generate ABIs
forge build

# ABIs are located in
ls -la out/MultiliquidSwap.sol/MultiliquidSwapV2.json
ls -la out/StablecoinDelegate.sol/StablecoinDelegateBase.json
ls -la out/RWADelegate.sol/RWADelegate.json
From Etherscan/Block Explorer:
  • Navigate to verified contract address
  • Click “Contract” tab
  • Scroll to “Contract ABI” section
  • Copy JSON ABI

Key Contract ABIs

MultiliquidSwapV2
  • All swap functions
  • Calculation/quote functions
  • Administrative functions
  • Event definitions
StablecoinDelegateBase
  • Stablecoin integration interface
  • Whitelist management
  • Fee configuration
RWADelegate
  • RWA risk management interface
  • Volume limit tracking
  • Compliance hooks
ERC-20 (Standard)
  • transfer(), transferFrom(), approve()
  • balanceOf(), allowance()
  • Standard token interface

ABI Usage in Code

// Load ABI from file
import MultiliquidSwapABI from './abis/MultiliquidSwapV2.json';

// Or define inline (excerpt)
const MultiliquidSwapABI = [
    {
        "inputs": [
            {
                "components": [
                    { "name": "stablecoinID", "type": "bytes32" },
                    { "name": "stablecoinAmount", "type": "uint256" },
                    { "name": "rwaID", "type": "bytes32" },
                    { "name": "rwaAmount", "type": "uint256" },
                    { "name": "stablecoinMetadata", "type": "bytes" },
                    { "name": "rwaMetadata", "type": "bytes" }
                ],
                "name": "inputs",
                "type": "tuple"
            }
        ],
        "name": "swapIntoStablecoin",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
    },
    // ... more functions
];

Integration Best Practices

Security

  1. Use Hardware Wallets: Never store private keys in code
  2. Validate Inputs: Sanitize all user-provided data
  3. Test on Testnet: Thoroughly test before mainnet deployment
  4. Monitor Transactions: Implement comprehensive logging
  5. Handle Reverts: Gracefully handle all error cases

Performance

  1. Batch Approvals: Approve large amounts to reduce transactions
  2. Gas Optimization: Use appropriate gas limits and prices
  3. Caching: Cache asset IDs and contract addresses
  4. Connection Pooling: Reuse provider connections

Compliance

  1. Frontend Whitelist Checks: Verify user eligibility before allowing swaps
  2. Metadata Usage: Pass compliance data in metadata fields
  3. Audit Trails: Log all operations for regulatory reporting
  4. Jurisdictional Checks: Implement geo-blocking if required

Operational

  1. Monitoring: Set up alerts for failed transactions
  2. Balance Tracking: Monitor custody address balances
  3. Price Alerts: Track significant price deviations
  4. Health Checks: Regularly verify contract availability

Testing

Testnet Deployment

The protocol is deployed on the following testnets:
  • Sepolia (Ethereum testnet)
  • Arbitrum Sepolia

Test Workflow

  1. Get Testnet Tokens: Use faucets to obtain testnet ETH
  2. Get Test RWA/Stablecoins: Contact Multiliquid team for test tokens
  3. Deploy Test Integration: Deploy your integration contracts to testnet
  4. Execute Test Swaps: Perform end-to-end swap testing
  5. Monitor Results: Verify all events and state changes
  6. Load Testing: Test with realistic volumes

Support and Resources

Developer Resources

Getting Help

For integration support:
  • Technical questions: Contact via website
  • Security concerns: Use secure channels (see Security)
  • Partnership inquiries: Contact Multiliquid team