Skip to main content

Overview

The MultiliquidSwapV2 contract is the core orchestrator of the Multiliquid Protocol, managing all atomic swap operations between Real World Asset (RWA) tokens and stablecoins. It coordinates with delegate contracts, price adapters, and implements comprehensive fee management and risk controls. Contract Location: src/v2/MultiliquidSwap.sol

Key Responsibilities

  • Orchestrate bidirectional swaps between RWAs and stablecoins
  • Calculate swap amounts using USD-denominated pricing
  • Enforce volume-based fee tiers
  • Manage RWA whitelists per stablecoin
  • Coordinate with asset-specific delegate contracts
  • Track and collect protocol fees

Contract Architecture

Inheritance

contract MultiliquidSwapV2 is
    Initializable,
    UUPSUpgradeable,
    ReentrancyGuardUpgradeable,
    AccessControlEnumerableUpgradeable,
    PausableUpgradeable,
    IMultiliquidSwapV2

Base Contracts

  • Initializable: Supports proxy initialization
  • UUPSUpgradeable: Enables secure contract upgrades
  • ReentrancyGuardUpgradeable: Reentrancy attack protection
  • AccessControlEnumerableUpgradeable: Role-based permission system with enumeration
  • PausableUpgradeable: Emergency pause capability
  • IMultiliquidSwapV2: Interface implementation

Core Functions

Swap Functions

NAV-Based Deterministic Pricing: All swap functions use deterministic NAV-based pricing. While parameters accept “minimum” or “maximum” amounts for safety bounds, integrators should use the exact amounts returned by the corresponding calculation functions (calculateStableAmt, calculateRWAAmt, etc.) rather than applying arbitrary slippage tolerances. The protocol pricing is not subject to market slippage like AMM DEXs.See the Integration Guide for code examples showing proper usage of calculation functions.

swapIntoStablecoin

Swap RWA tokens for stablecoins (ExactIn variant).
function swapIntoStablecoin(SwapIntoStablecoinInputs calldata inputs) external;
Parameters:
  • inputs.stablecoinID: ID of target stablecoin
  • inputs.stablecoinAmount: Minimum stablecoin amount to receive
  • inputs.rwaID: ID of source RWA token
  • inputs.rwaAmount: Exact RWA amount to spend
  • inputs.stablecoinMetadata: Metadata for stablecoin delegate
  • inputs.rwaMetadata: Metadata for RWA delegate
Access: Public, requires whitelisting (if enforced by delegates) Emits: SwapExecuted(user, rwaID, stablecoinID, rwaAmount, stablecoinAmount, fee) Requirements:
  • Contract not paused
  • RWA and stablecoin must be accepted
  • RWA must be whitelisted by stablecoin
  • User must have sufficient RWA balance
  • Calculated stablecoin output must meet minimum
  • RWA delegate validation must pass
Example:
// First, calculate exact output for 1000 ULTRA input
const [stableAmt, feeAmt] = await multiliquidSwap.calculateStableAmt(
    usdcID,
    ultraID,
    parseUnits("1000", 6)
);

// Use exact calculated amount (NAV-based pricing is deterministic)
await multiliquidSwap.swapIntoStablecoin({
    stablecoinID: usdcID,
    stablecoinAmount: stableAmt,             // Exact calculated output
    rwaID: ultraID,
    rwaAmount: parseUnits("1000", 6),        // Exact input
    stablecoinMetadata: "0x",
    rwaMetadata: "0x"
});

swapIntoStablecoinExactOut

Swap RWA tokens for stablecoins (ExactOut variant).
function swapIntoStablecoinExactOut(SwapIntoStablecoinInputs calldata inputs) external;
Parameters:
  • inputs.stablecoinID: ID of target stablecoin
  • inputs.stablecoinAmount: Exact stablecoin amount to receive
  • inputs.rwaID: ID of source RWA token
  • inputs.rwaAmount: Maximum RWA amount willing to spend
  • inputs.stablecoinMetadata: Metadata for stablecoin delegate
  • inputs.rwaMetadata: Metadata for RWA delegate
Example:
// First, calculate exact RWA required for 1000 USDC output
const [rwaRequired, feeAmt] = await multiliquidSwap.calculateRWARequired(
    usdcID,
    ultraID,
    parseUnits("1000", 6)
);

// Use exact calculated amount (NAV-based pricing is deterministic)
await multiliquidSwap.swapIntoStablecoinExactOut({
    stablecoinID: usdcID,
    stablecoinAmount: parseUnits("1000", 6),  // Exact output
    rwaID: ultraID,
    rwaAmount: rwaRequired,                   // Exact calculated input
    stablecoinMetadata: "0x",
    rwaMetadata: "0x"
});

swapIntoRWA

Swap stablecoins for RWA tokens (ExactIn variant).
function swapIntoRWA(SwapIntoRWAInputs calldata inputs) external;
Parameters:
  • inputs.rwaID: ID of target RWA token
  • inputs.rwaAmount: Minimum RWA amount to receive
  • inputs.stablecoinID: ID of source stablecoin
  • inputs.stablecoinAmount: Exact stablecoin amount to spend
  • inputs.stablecoinMetadata: Metadata for stablecoin delegate
  • inputs.rwaMetadata: Metadata for RWA delegate
Emits: SwapExecuted(user, stablecoinID, rwaID, stablecoinAmount, rwaAmount, fee) Example:
// First, calculate exact RWA output for 1000 USDC input
const [rwaAmt, feeAmt, redemptionFeeAmt] = await multiliquidSwap.calculateRWAAmt(
    ultraID,
    usdcID,
    parseUnits("1000", 6)
);

// Use exact calculated amount (NAV-based pricing is deterministic)
await multiliquidSwap.swapIntoRWA({
    rwaID: ultraID,
    rwaAmount: rwaAmt,                        // Exact calculated output
    stablecoinID: usdcID,
    stablecoinAmount: parseUnits("1000", 6),  // Exact input
    stablecoinMetadata: "0x",
    rwaMetadata: "0x"
});

swapIntoRWAExactOut

Swap stablecoins for RWA tokens (ExactOut variant).
function swapIntoRWAExactOut(SwapIntoRWAInputs calldata inputs) external;
Parameters:
  • inputs.rwaID: ID of target RWA token
  • inputs.rwaAmount: Exact RWA amount to receive
  • inputs.stablecoinID: ID of source stablecoin
  • inputs.stablecoinAmount: Maximum stablecoin amount willing to spend
  • inputs.stablecoinMetadata: Metadata for stablecoin delegate
  • inputs.rwaMetadata: Metadata for RWA delegate

swapRWAToRWA

Swap between two RWA tokens using a specific stablecoin delegate as the rebalanced portfolio. This will accrue double redemption fees.
function swapRWAToRWA(SwapRWAToRWAInputs calldata inputs) external;
Parameters:
  • inputs.exactOut: Boolean indicating exactOut (true) or exactIn (false)
  • inputs.stablecoinID: ID of stablecoin used for pricing
  • inputs.rwaInID: ID of source RWA
  • inputs.rwaInAmount: RWA input amount (exact if exactOut=false, max if exactOut=true)
  • inputs.rwaOutID: ID of target RWA
  • inputs.rwaOutAmount: RWA output amount (min if exactOut=false, exact if exactOut=true)
  • inputs.rwaInMetadata: Metadata for source RWA delegate
  • inputs.rwaOutMetadata: Metadata for target RWA delegate
Process:
  1. User provides rwaIn tokens
  2. Protocol calculates equivalent value
  3. Protocol calculates equivalent value in rwaOut tokens
  4. User receives rwaOut tokens
  5. Fees deducted from output
Use Case: Swap ULTRA → USTB

swapStablecoinToStablecoin

Swap between two stablecoins using a specific stablecoinDelegate based on useDelegateForStablecoinOut. The delegate chosen determines fees.
function swapStablecoinToStablecoin(SwapStablecoinToStablecoinInputs calldata inputs) external;
Parameters:
  • inputs.exactOut: Boolean indicating exactOut (true) or exactIn (false)
  • inputs.useDelegateForStablecoinOut: Route through stablecoinOut delegate (true) or stablecoinIn delegate (false)
  • inputs.stablecoinInID: ID of source stablecoin
  • inputs.stablecoinInAmount: Input amount
  • inputs.stablecoinOutID: ID of target stablecoin
  • inputs.stablecoinOutAmount: Output amount
  • inputs.stablecoinInMetadata: Metadata for source stablecoin delegate
  • inputs.stablecoinOutMetadata: Metadata for target stablecoin delegate
Use Case: Swap USDC → USDT

Calculation/View Functions

The contract provides view functions for off-chain quote generation, with one calculation function corresponding to each swap function.

calculateRWAAmt

Calculate RWA output for a given stablecoin input (for swapIntoRWA).
function calculateRWAAmt(
    bytes32 rwaID,
    bytes32 stablecoinID,
    uint256 stableAmt
) external view returns (
    uint256 rwaAmt,
    uint256 feeAmt,
    uint256 redemptionFeeAmt
);
Returns:
  • rwaAmt: RWA amount user will receive (after fees)
  • feeAmt: Protocol fee charged
  • redemptionFeeAmt: Redemption fee charged by stablecoin issuer

calculateStablecoinRequired

Calculate stablecoin input required for exact RWA output (for swapIntoRWAExactOut).
function calculateStablecoinRequired(
    bytes32 rwaID,
    bytes32 stablecoinID,
    uint256 exactRWAOut
) external view returns (
    uint256 stablecoinRequired,
    uint256 feeAmt,
    uint256 redemptionFeeAmt
);
Returns:
  • stablecoinRequired: Stablecoin amount needed to receive exact RWA output
  • feeAmt: Protocol fee charged
  • redemptionFeeAmt: Redemption fee charged by stablecoin issuer

calculateStableAmt

Calculate stablecoin output for a given RWA input (for swapIntoStablecoin).
function calculateStableAmt(
    bytes32 stablecoinID,
    bytes32 rwaID,
    uint256 rwaAmt
) external view returns (
    uint256 stableAmt,
    uint256 feeAmt
);
Returns:
  • stableAmt: Stablecoin amount user will receive (after fees)
  • feeAmt: Protocol fee charged

calculateRWARequired

Calculate RWA input required for exact stablecoin output (for swapIntoStablecoinExactOut).
function calculateRWARequired(
    bytes32 stablecoinID,
    bytes32 rwaID,
    uint256 exactStablecoinOut
) external view returns (
    uint256 rwaRequired,
    uint256 feeAmt
);
Returns:
  • rwaRequired: RWA amount needed to receive exact stablecoin output
  • feeAmt: Protocol fee charged

calculateRWAToRWA

Calculate amounts for RWA-to-RWA swap (for swapRWAToRWA).
function calculateRWAToRWA(
    bytes32 stablecoinID,
    bytes32 rwaInID,
    bytes32 rwaOutID,
    uint256 rwaInAmount,
    uint256 rwaOutAmount,
    bool exactOut
) external view returns (
    RWAToRWAReturnParams memory outputs
);
Parameters:
  • stablecoinID: ID of stablecoin used for pricing
  • rwaInID: ID of input RWA token
  • rwaOutID: ID of output RWA token
  • rwaInAmount: Input RWA amount (exact if exactOut=false, max if exactOut=true)
  • rwaOutAmount: Output RWA amount (min if exactOut=false, exact if exactOut=true)
  • exactOut: Boolean indicating exactOut (true) or exactIn (false)
Returns (RWAToRWAReturnParams):
  • rwaInAmount: RWA input amount required
  • rwaOutAmount: RWA output amount received
  • protocolFeeAmt: Protocol fee charged
  • redemptionFeeAmt: Total redemption fees (both RWAs)

calculateStablecoinToStablecoin

Calculate amounts for stablecoin-to-stablecoin swap (for swapStablecoinToStablecoin).
function calculateStablecoinToStablecoin(
    bool exactOut,
    bool useDelegateForStablecoinOut,
    bytes32 stablecoinInID,
    uint256 stablecoinInAmount,
    bytes32 stablecoinOutID,
    uint256 stablecoinOutAmount
) external view returns (
    StablecoinToStablecoinReturnParams memory outputs
);
Parameters:
  • exactOut: Boolean indicating exactOut (true) or exactIn (false)
  • useDelegateForStablecoinOut: Route through stablecoinOut delegate (true) or stablecoinIn delegate (false)
  • stablecoinInID: ID of input stablecoin
  • stablecoinInAmount: Input amount (exact if exactOut=false, max if exactOut=true)
  • stablecoinOutID: ID of output stablecoin
  • stablecoinOutAmount: Output amount (min if exactOut=false, exact if exactOut=true)
Returns (StablecoinToStablecoinReturnParams):
  • stablecoinInAmount: Stablecoin input amount required
  • stablecoinOutAmount: Stablecoin output amount received
  • protocolFeeAmt: Protocol fee charged
  • acceptanceFeeAmt: Acceptance fee charged by issuer
  • redemptionFeeAmt: Redemption fee charged by issuer

Administrative Functions

setRWAAcceptance

Register or update an RWA token in the protocol.
function setRWAAcceptance(
    bytes32 rwaID,
    address _delegate,
    address _assetAddress,
    bool _accepted
) external onlyRole(OPERATOR_ROLE);
Parameters:
  • rwaID: Unique identifier for the RWA (keccak256 of lowercase address)
  • _delegate: Address of RWA delegate contract (address(0) if no delegate)
  • _assetAddress: Address of the RWA token contract
  • _accepted: true to accept, false to remove
Access: OPERATOR_ROLE Events:
event RWAAcceptanceSet(
    bytes32 indexed rwaID,
    address indexed delegate,
    address indexed assetAddress,
    uint8 decimals,
    bool accepted
);

setStablecoinAcceptance

Register or update a stablecoin in the protocol.
function setStablecoinAcceptance(
    bytes32 stablecoinID,
    address _delegate,
    address _assetAddress,
    bool _accepted
) external onlyRole(OPERATOR_ROLE);
Parameters:
  • stablecoinID: Unique identifier for the stablecoin
  • _delegate: Address of stablecoin delegate contract
  • _assetAddress: Address of the stablecoin token contract
  • _accepted: true to accept, false to remove
Access: OPERATOR_ROLE Events:
event StablecoinAcceptanceSet(
    bytes32 indexed stablecoinID,
    address indexed delegate,
    address indexed assetAddress,
    uint8 decimals,
    bool accepted,
    bool externalSwapper
);

setStablecoinAcceptanceForSwap

Register a stablecoin for swap-only mode (without delegate).
function setStablecoinAcceptanceForSwap(
    bytes32 stablecoinID,
    address _assetAddress,
    bool _accepted
) external onlyRole(OPERATOR_ROLE);
Purpose: For stablecoins used only in stablecoin-to-stablecoin swaps Access: OPERATOR_ROLE

setRwaPriceAdapter

Set or update the price adapter for an RWA.
function setRwaPriceAdapter(bytes32 rwaID, address priceAdapter)
    external
    onlyRole(OPERATOR_ROLE);
Access: OPERATOR_ROLE Events:
event UpdateRWAUSDValue(bytes32 indexed rwaID, address priceAdapter);

setStablecoinUSDValue

Set the USD value for a stablecoin (usually 1e18 for $1.00 pegged stablecoins).
function setStablecoinUSDValue(bytes32 stablecoinID, uint256 value)
    external
    onlyRole(OPERATOR_ROLE);
Access: OPERATOR_ROLE Events:
event UpdateStablecoinUSDValue(bytes32 indexed stablecoinID, uint256 value);

setRWADiscountRate

Set discount rate for RWA when swapping from a specific stablecoin.
function setRWADiscountRate(
    bytes32 stablecoinID,
    bytes32 rwaID,
    uint256 rate
) external;
Access: Stablecoin delegate contract only Parameters:
  • stablecoinID: ID of the stablecoin
  • rwaID: ID of the RWA token
  • rate: Discount rate in WAD format (1e18 = 100%, must be less than 1e18)

setRWARedemptionFee

Set redemption fee for RWA when swapping into RWA from a stablecoin.
function setRWARedemptionFee(
    bytes32 stablecoinID,
    bytes32 rwaID,
    uint256 rate
) external;
Access: Stablecoin delegate contract only Parameters:
  • stablecoinID: ID of the stablecoin
  • rwaID: ID of the RWA token
  • rate: Redemption fee in WAD format (1e18 = 100%, must be less than 1e18)

setMultiliquidFees

Set the protocol fee tier structure. Fee tiers are volume-based, where larger swaps receive lower fee rates.
function setMultiliquidFees(FeeTier[] calldata _feeTiers) external onlyRole(OPERATOR_ROLE);
Parameters:
  • _feeTiers: Array of fee tiers, each containing maxVolume (WAD) and fee (WAD)
Access: OPERATOR_ROLE Events:
event MultiliquidFeeTiersSet(FeeTier[] feeTiers);
This function replaces the entire fee tier structure. There is no modification—only deletion and addition of the complete tier list.

setStablecoinAcceptanceFee

Set acceptance fee charged when a stablecoin delegate accepts another stablecoin as backing (used in stablecoin-to-stablecoin swaps).
function setStablecoinAcceptanceFee(
    bytes32 stablecoinOutID,
    address stablecoin,
    uint256 rate
) external;
Access: Stablecoin delegate contract only Parameters:
  • stablecoinOutID: ID of the stablecoin being issued/minted
  • stablecoin: Address of the stablecoin being accepted as backing
  • rate: Acceptance fee in WAD format (1e18 = 100%, must be less than 1e18)

setStablecoinRedemptionFee

Set redemption fee charged when a stablecoin delegate redeems/burns its stablecoin and gives another stablecoin from custody (used in stablecoin-to-stablecoin swaps).
function setStablecoinRedemptionFee(
    bytes32 stablecoinOutID,
    address stablecoin,
    uint256 rate
) external;
Access: Stablecoin delegate contract only Parameters:
  • stablecoinOutID: ID of the stablecoin being redeemed/burned
  • stablecoin: Address of the stablecoin being given out from custody
  • rate: Redemption fee in WAD format (1e18 = 100%, must be less than 1e18)

pause

Pauses the contract, disabling all swap functionality.
function pause() external onlyRole(OPERATOR_ROLE);
Access: OPERATOR_ROLE Emits: Paused(address account) from OpenZeppelin’s PausableUpgradeable

unpause

Unpauses the contract, re-enabling swap functionality.
function unpause() external onlyRole(OPERATOR_ROLE);
Access: OPERATOR_ROLE Emits: Unpaused(address account) from OpenZeppelin’s PausableUpgradeable

Events

Swap Events

event SwapIntoRWA(
    bytes32 indexed rwaID,
    bytes32 indexed stablecoinID,
    address indexed user,
    uint256 rwaAmt,
    uint256 stablecoinAmt,
    uint256 feeAmt,
    uint256 redemptionFeeAmt,
    bytes stablecoinMetadata,
    bytes rwaMetadata
);

event SwapIntoRWAExactOut(
    bytes32 indexed rwaID,
    bytes32 indexed stablecoinID,
    address indexed user,
    uint256 rwaAmt,
    uint256 stablecoinAmt,
    uint256 feeAmt,
    uint256 redemptionFeeAmt,
    bytes stablecoinMetadata,
    bytes rwaMetadata
);

event SwapIntoStablecoin(
    bytes32 indexed stablecoinID,
    bytes32 indexed rwaID,
    address indexed user,
    uint256 stablecoinAmt,
    uint256 rwaAmt,
    uint256 feeAmt,
    bytes stablecoinMetadata,
    bytes rwaMetadata
);

event SwapIntoStablecoinExactOut(
    bytes32 indexed stablecoinID,
    bytes32 indexed rwaID,
    address indexed user,
    uint256 stablecoinAmt,
    uint256 rwaAmt,
    uint256 feeAmt,
    bytes stablecoinMetadata,
    bytes rwaMetadata
);

event RWAToRWASwap(
    bytes32 indexed rwaInID,
    bytes32 indexed rwaOutID,
    bytes32 indexed stablecoinID,
    address user,
    uint256 rwaInAmount,
    uint256 rwaOutAmount,
    uint256 protocolFeeAmt,
    uint256 redemptionFeeAmt,
    bytes rwaInMetadata,
    bytes rwaOutMetadata
);

event StablecoinToStablecoinSwap(
    bytes32 indexed stablecoinInID,
    bytes32 indexed stablecoinOutID,
    address indexed user,
    uint256 stablecoinInAmount,
    uint256 stablecoinOutAmount,
    uint256 protocolFeeAmt,
    uint256 acceptanceFeeAmt,
    uint256 redemptionFeeAmt,
    bytes stablecoinInMetadata,
    bytes stablecoinOutMetadata
);

Admin Events

event RWAAcceptanceSet(
    bytes32 indexed rwaID,
    address indexed delegate,
    address indexed assetAddress,
    uint8 decimals,
    bool accepted
);

event StablecoinAcceptanceSet(
    bytes32 indexed stablecoinID,
    address indexed delegate,
    address indexed assetAddress,
    uint8 decimals,
    bool accepted,
    bool externalSwapper
);

event UpdateRWAUSDValue(bytes32 indexed rwaID, address priceAdapter);
event UpdateStablecoinUSDValue(bytes32 indexed stablecoinID, uint256 value);

event MultiliquidFeeTiersSet(FeeTier[] feeTiers);
event DiscountRateSet(bytes32 indexed stablecoinID, bytes32 indexed rwaID, uint256 rate);
event RedemptionFeeSet(bytes32 indexed stablecoinID, bytes32 indexed rwaID, uint256 rate);

event StablecoinAcceptanceFeeSet(
    bytes32 indexed stablecoinOutID, address indexed stablecoinIn, uint256 rate
);
event StablecoinRedemptionFeeSet(
    bytes32 indexed stablecoinOutID, address indexed stablecoinIn, uint256 rate
);

Access Control Roles

DEFAULT_ADMIN_ROLE: Can upgrade contract and manage all roles OPERATOR_ROLE:
  • Accept new RWAs and stablecoins
  • Set price adapters and USD values
  • Configure fee tiers
  • Pause/unpause the contract

Next: Stablecoin Delegate Contracts

Learn about asset-specific contracts handling stablecoin integration