Market Maker SDK

The Market Maker SDK enables decentralised, peer-to-peer OTC trading of Real World Assets directly on-chain via smart contracts. It is available 24/7, requires no KYC, and supports both taking existing offers and creating your own.

Use this when you want: P2P on-chain trading, 24/7 availability, the ability to create and manage liquidity offers, or to avoid centralised intermediaries.

→ API Reference


Key features

  • Trade 24/7 — no market hours restrictions
  • Fully decentralised execution via smart contracts
  • Fixed-price or dynamic (oracle-feed) pricing
  • Partial and block offer types
  • Become a liquidity provider by creating your own offers
  • No KYC required

Prerequisites

  • Python 3.8+
  • A wallet with a private key
  • Tokens to trade and gas tokens (MATIC, ETH, etc.)
  • An RPQ API Key — required for offer discovery (contact the Swarm team to request access)

Initialising the client

from swarm.market_maker_sdk import MarketMakerClient
from swarm.shared.models import Network

async with MarketMakerClient(
    network=Network.POLYGON,
    private_key="0x...",
    rpq_api_key="your_rpq_key",
) as client:
    # Ready to trade
    pass

Always use async with — it handles authentication and cleanup automatically.

Constructor parameters:

ParameterTypeRequiredDescription
networkNetworkBlockchain network
private_keystrWallet private key (with 0x prefix)
rpq_api_keystrRPQ Service API key
user_emailstrEmail for Swarm authentication (optional)
rpc_urlstrCustom RPC endpoint

Understanding offers

TermMeaning
Deposit assetWhat the maker deposited — what you receive when you take the offer
Withdrawal assetWhat the maker wants — what you pay when you take the offer
Partial offerCan be taken in multiple smaller fills
Block offerMust be taken all at once
Fixed pricingPrice set at offer creation, does not change
Dynamic pricingPrice updated in real-time from a live oracle feed

Browsing and quoting offers

Browse available offers

async with MarketMakerClient(...) as client:
    offers = await client.rpq_client.get_offers(
        buy_asset_address="0xRWA...",    # token you want to receive
        sell_asset_address="0xUSDC...",  # token you want to pay
        page=0,
        limit=10
    )

    for offer in offers:
        print(f"Offer {offer.id}: {offer.amount_in} {offer.deposit_asset.symbol}"
              f" for {offer.amount_out} {offer.withdrawal_asset.symbol}")

Find the best offer combination

# Spend 100 USDC across the best combination of offers
best = await client.rpq_client.get_best_offers(
    buy_asset_address="0xRWA...",
    sell_asset_address="0xUSDC...",
    target_sell_amount="100"       # or target_buy_amount="10" to specify received amount
)

print(f"Uses {len(best.result.selected_offers)} offer(s)")
print(f"Total paid: {best.result.total_withdrawal_amount_paid}")

Provide either target_sell_amount or target_buy_amount, not both.

Get a price quote

from decimal import Decimal

quote = await client.get_quote(
    from_token="0xUSDC...",
    to_token="0xRWA...",
    from_amount=Decimal("100")   # or to_amount=Decimal("10")
)

print(f"You'll receive: {quote.buy_amount} tokens at rate {quote.rate}")


Executing a trade

The trade() method handles offer selection, token approval, and on-chain execution automatically:

result = await client.trade(
    from_token="0xUSDC...",
    to_token="0xRWA...",
    from_amount=Decimal("100"),    # or to_amount=Decimal("10")
    affiliate=None                 # optional affiliate address
)

print(f"TX Hash:  {result.tx_hash}")
print(f"Offer ID: {result.order_id}")
print(f"Paid:     {result.sell_amount} USDC")
print(f"Received: {result.buy_amount} RWA")

What happens automatically:

  1. Queries RPQ Service for best offers
  2. Approves tokens for the contract
  3. Detects fixed vs. dynamic pricing and calls the correct contract function
  4. Waits for on-chain confirmation
  5. Returns TradeResult

Dynamic offers use depositToWithdrawalRate for slippage protection automatically — the same trade() call works for both pricing types.


Creating offers (becoming a liquidity provider)

Fixed-price offer

result = await client.make_offer(
    sell_token="0xRWA...",         # token you're offering
    sell_amount=Decimal("10"),     # amount you're selling
    buy_token="0xUSDC...",         # token you want to receive
    buy_amount=Decimal("1000"),    # amount you want to receive
    is_dynamic=False,
    expires_at=None                # or a Unix timestamp
)

print(f"Offer created: {result.order_id}")

Dynamic-price offer

# Check available price feeds
feeds = await client.rpq_client.get_price_feeds()

result = await client.make_offer(
    sell_token="0xRWA...",
    sell_amount=Decimal("10"),
    buy_token="0xUSDC...",
    buy_amount=Decimal("1000"),
    is_dynamic=True   # price adjusts in real-time via oracle
)

Setting an expiration

from datetime import datetime, timedelta

expires_at = int((datetime.now() + timedelta(days=7)).timestamp())

result = await client.make_offer(
    sell_token="0xRWA...",
    sell_amount=Decimal("10"),
    buy_token="0xUSDC...",
    buy_amount=Decimal("1000"),
    expires_at=expires_at
)

Your tokens are locked in the contract escrow until the offer is taken or you cancel it.


Cancelling offers

tx_hash = await client.cancel_offer(offer_id="12345")
print(f"Cancelled. TX: {tx_hash}")

Only the original offer creator can cancel. Tokens are returned immediately. Gas fees apply.

Partially-filled offers cannot be cancelled.


Error handling

from swarm.market_maker_sdk.rpq_service.exceptions import (
    NoOffersAvailableException,
    QuoteUnavailableException,
    RPQServiceException,
)
from swarm.market_maker_sdk.market_maker_web3.exceptions import (
    OfferNotFoundError,
    OfferInactiveError,
    InsufficientOfferBalanceError,
    OfferExpiredError,
    UnauthorizedError,
    MarketMakerWeb3Exception,
)

try:
    result = await client.trade(...)

except NoOffersAvailableException:
    pass  # No offers for this token pair — try a different pair or create one

except OfferNotFoundError:
    pass  # Offer was already taken or cancelled

except OfferInactiveError:
    pass  # Offer is no longer active

except InsufficientOfferBalanceError:
    pass  # Maker has insufficient balance — try a different offer

except OfferExpiredError:
    pass  # Offer has expired — find a newer one

except UnauthorizedError:
    pass  # Only the offer creator can perform this action

except MarketMakerWeb3Exception:
    pass  # On-chain error — check gas and balances

ExceptionWhen it occurs
NoOffersAvailableExceptionNo offers exist for this token pair
QuoteUnavailableExceptionQuote cannot be calculated
OfferNotFoundErrorOffer ID doesn't exist on-chain
OfferInactiveErrorOffer is cancelled or fully filled
InsufficientOfferBalanceErrorMaker has insufficient token balance
OfferExpiredErrorOffer has passed its expiration
UnauthorizedErrorCaller is not the offer creator
RPQServiceExceptionRPQ API error
MarketMakerWeb3ExceptionSmart contract execution error

Supported networks

NetworkChain IDSupported
Polygon137
Ethereum1
Arbitrum42161
Base8453
Optimism10

Contract addresses are loaded dynamically from remote config based on network and environment (SWARM_COLLECTION_MODE=dev|prod).


Complete example

import asyncio
from decimal import Decimal
from swarm.market_maker_sdk import MarketMakerClient
from swarm.shared.models import Network
from swarm.market_maker_sdk.rpq_service.exceptions import NoOffersAvailableException
from swarm.market_maker_sdk.market_maker_web3.exceptions import MarketMakerWeb3Exception

PRIVATE_KEY = "0x..."
RPQ_API_KEY = "your_key"
USDC = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"  # Polygon USDC
RWA  = "0x..."  # RWA token address

async def main():
    async with MarketMakerClient(
        network=Network.POLYGON,
        private_key=PRIVATE_KEY,
        rpq_api_key=RPQ_API_KEY,
    ) as client:

        try:
            # Get a quote
            quote = await client.get_quote(
                from_token=USDC,
                to_token=RWA,
                from_amount=Decimal("100")
            )
            print(f"Rate: {quote.rate} — receive {quote.buy_amount} tokens")

            # Execute trade
            result = await client.trade(
                from_token=USDC,
                to_token=RWA,
                from_amount=Decimal("100")
            )
            print(f"✅ TX: {result.tx_hash}")
            print(f"   Paid: {result.sell_amount} USDC")
            print(f"   Received: {result.buy_amount} RWA")

        except NoOffersAvailableException:
            print("No offers available — consider creating one")
        except MarketMakerWeb3Exception as e:
            print(f"Blockchain error: {e}")

asyncio.run(main())


Quick reference

# Imports
from swarm.market_maker_sdk import MarketMakerClient
from swarm.shared.models import Network
from decimal import Decimal

# Initialise
async with MarketMakerClient(
    network=Network.POLYGON, private_key="0x...", rpq_api_key="your_key"
) as client:

    # Get quote
    quote = await client.get_quote(from_token="0xUSDC...", to_token="0xRWA...", from_amount=Decimal("100"))

    # Trade
    result = await client.trade(from_token="0xUSDC...", to_token="0xRWA...", from_amount=Decimal("100"))

    # Create offer
    result = await client.make_offer(
        sell_token="0xRWA...", sell_amount=Decimal("10"),
        buy_token="0xUSDC...", buy_amount=Decimal("1000")
    )

    # Cancel offer
    tx_hash = await client.cancel_offer(offer_id="12345")

→ Full API Reference