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.
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:
| Parameter | Type | Required | Description |
|---|---|---|---|
| network | Network | ✅ | Blockchain network |
| private_key | str | ✅ | Wallet private key (with 0x prefix) |
| rpq_api_key | str | ✅ | RPQ Service API key |
| user_email | str | ❌ | Email for Swarm authentication (optional) |
| rpc_url | str | ❌ | Custom RPC endpoint |
Understanding offers
| Term | Meaning |
|---|---|
| Deposit asset | What the maker deposited — what you receive when you take the offer |
| Withdrawal asset | What the maker wants — what you pay when you take the offer |
| Partial offer | Can be taken in multiple smaller fills |
| Block offer | Must be taken all at once |
| Fixed pricing | Price set at offer creation, does not change |
| Dynamic pricing | Price 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_amountortarget_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:
- Queries RPQ Service for best offers
- Approves tokens for the contract
- Detects fixed vs. dynamic pricing and calls the correct contract function
- Waits for on-chain confirmation
- 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
| Exception | When it occurs |
|---|---|
| NoOffersAvailableException | No offers exist for this token pair |
| QuoteUnavailableException | Quote cannot be calculated |
| OfferNotFoundError | Offer ID doesn't exist on-chain |
| OfferInactiveError | Offer is cancelled or fully filled |
| InsufficientOfferBalanceError | Maker has insufficient token balance |
| OfferExpiredError | Offer has passed its expiration |
| UnauthorizedError | Caller is not the offer creator |
| RPQServiceException | RPQ API error |
| MarketMakerWeb3Exception | Smart contract execution error |
Supported networks
| Network | Chain ID | Supported |
|---|---|---|
| Polygon | 137 | ✅ |
| Ethereum | 1 | ✅ |
| Arbitrum | 42161 | ✅ |
| Base | 8453 | ✅ |
| Optimism | 10 | ✅ |
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")
.png)