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
- Connect Wallet: User connects their wallet
- Select Assets: Choose RWA and stablecoin for swap
- Enter Amount: Specify swap amount
- Get Quote: UI calls contract view functions for exact pricing
- Review & Approve: User reviews swap details and exact amounts
- Execute Swap: Transaction submitted to blockchain
- 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
- Use Hardware Wallets: Never store private keys in code
- Validate Inputs: Sanitize all user-provided data
- Test on Testnet: Thoroughly test before mainnet deployment
- Monitor Transactions: Implement comprehensive logging
- Handle Reverts: Gracefully handle all error cases
- Batch Approvals: Approve large amounts to reduce transactions
- Gas Optimization: Use appropriate gas limits and prices
- Caching: Cache asset IDs and contract addresses
- Connection Pooling: Reuse provider connections
Compliance
- Frontend Whitelist Checks: Verify user eligibility before allowing swaps
- Metadata Usage: Pass compliance data in metadata fields
- Audit Trails: Log all operations for regulatory reporting
- Jurisdictional Checks: Implement geo-blocking if required
Operational
- Monitoring: Set up alerts for failed transactions
- Balance Tracking: Monitor custody address balances
- Price Alerts: Track significant price deviations
- Health Checks: Regularly verify contract availability
Testing
Testnet Deployment
The protocol is deployed on the following testnets:
- Sepolia (Ethereum testnet)
- Arbitrum Sepolia
Test Workflow
- Get Testnet Tokens: Use faucets to obtain testnet ETH
- Get Test RWA/Stablecoins: Contact Multiliquid team for test tokens
- Deploy Test Integration: Deploy your integration contracts to testnet
- Execute Test Swaps: Perform end-to-end swap testing
- Monitor Results: Verify all events and state changes
- 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