Marketplace Module
The marketplace module provides an escrow-based marketplace for trading RWA tokens using SUI. It ensures secure, atomic swaps with automatic fee collection and change return.
Overview
The marketplace is a shared object that holds all listings and manages the escrow system. Sellers can list tokens for sale, update prices, cancel listings, and buyers can purchase tokens with automatic token and SUI transfers.
Key Structures
Marketplace
Central shared object holding all listings:
public struct Marketplace has key {
id: UID,
listings: Table<ID, Listing>, // Maps listing ID to listing
fee_basis_points: u64, // Fee as basis points (e.g., 50 = 0.5%)
total_volume: u64, // Cumulative trading volume (MIST)
}Listing
Individual token listing:
public struct Listing has store {
token: RwaToken, // The token being sold
seller: address, // Seller address
price: u64, // Price in MIST (1 SUI = 1,000,000,000 MIST)
created_at: u64, // Listing creation timestamp
}Main Functions
list()
List a token for sale (moves token to escrow):
public entry fun list(
marketplace: &mut Marketplace,
token: RwaToken,
price: u64,
ctx: &mut TxContext
): IDParameters:
marketplace: Mutable reference to Marketplacetoken: RWA token to listprice: Price in MISTctx: Transaction context
Returns: Listing ID
Effects:
- Moves token to marketplace escrow
- Creates listing record
- Emits
ListingCreatedevent
update_price()
Update listing price (seller only):
public entry fun update_price(
marketplace: &mut Marketplace,
listing_id: ID,
new_price: u64,
ctx: &TxContext
)Requirements:
- Caller must be the seller
- Listing must exist and be active
Effects:
- Updates listing price
- Emits
ListingUpdatedevent
cancel()
Cancel listing and return token to seller:
public entry fun cancel(
marketplace: &mut Marketplace,
listing_id: ID,
ctx: &TxContext
): RwaTokenRequirements:
- Caller must be the seller
- Listing must exist
Effects:
- Removes listing
- Returns token to seller
- Emits
ListingCancelledevent
buy()
Purchase listed token with SUI payment:
public entry fun buy(
marketplace: &mut Marketplace,
listing_id: ID,
payment: Coin<SUI>,
ctx: &mut TxContext
): (RwaToken, Coin<SUI>)Parameters:
marketplace: Mutable reference to Marketplacelisting_id: ID of listing to purchasepayment: Coin object containing SUI paymentctx: Transaction context
Returns:
RwaToken: The purchased tokenCoin<SUI>: Change (if payment exceeded price)
Effects:
- Transfers token to buyer
- Calculates fee and seller proceeds
- Transfers SUI to seller (minus fee)
- Returns change to buyer (if overpaid)
- Updates total volume
- Removes listing
- Emits
ListingPurchasedevent
Events
ListingCreated
public struct ListingCreated has copy, drop {
listing_id: ID,
token_id: ID,
seller: address,
price: u64,
timestamp: u64,
}ListingUpdated
public struct ListingUpdated has copy, drop {
listing_id: ID,
new_price: u64,
timestamp: u64,
}ListingCancelled
public struct ListingCancelled has copy, drop {
listing_id: ID,
seller: address,
timestamp: u64,
}ListingPurchased
public struct ListingPurchased has copy, drop {
listing_id: ID,
token_id: ID,
seller: address,
buyer: address,
price: u64,
fee: u64,
timestamp: u64,
}Fee System
Fees are calculated as a percentage of the sale price:
fee = (price * fee_basis_points) / 10000
seller_proceeds = price - feeExample:
- Price: 100 SUI (100,000,000,000 MIST)
- Fee: 0.5% (50 basis points)
- Fee Amount: 0.5 SUI (500,000,000 MIST)
- Seller Receives: 99.5 SUI
Usage Examples
List Token
sui client call \
--package <PACKAGE_ID> \
--module marketplace \
--function list \
--args <MARKETPLACE_ID> <TOKEN_ID> 1000000000 \
--gas-budget 10000000Update Price
sui client call \
--package <PACKAGE_ID> \
--module marketplace \
--function update_price \
--args <MARKETPLACE_ID> <LISTING_ID> 1500000000 \
--gas-budget 10000000Buy Token
sui client call \
--package <PACKAGE_ID> \
--module marketplace \
--function buy \
--args <MARKETPLACE_ID> <LISTING_ID> <COIN_ID> \
--gas-budget 10000000TypeScript Example
const tx = new TransactionBlock();
// Get payment coin
const payment = tx.splitCoins(tx.gas, [tx.pure.u64(priceInMist)]);
// Buy token
const [token, change] = tx.moveCall({
target: `${PACKAGE_ID}::marketplace::buy`,
arguments: [
tx.object(MARKETPLACE_ID),
tx.pure.id(listingId),
payment,
],
typeArguments: [],
});
tx.transferObjects([token, change], tx.pure.address(buyerAddress));
await suiClient.signAndExecuteTransactionBlock({
signer: keypair,
transactionBlock: tx,
});Features
Escrow Protection
- Tokens are locked in marketplace during listing
- Cannot be transferred or used elsewhere
- Seller can cancel to retrieve token
Automatic Change Return
- Buyers receive change if payment exceeds price
- Prevents overpayment losses
- Handled atomically in transaction
Overpayment Handling
If buyer pays more than listing price:
- Token transferred to buyer
- Full price (minus fee) sent to seller
- Change returned to buyer
Volume Tracking
- Cumulative trading volume stored on marketplace
- Can be queried for statistics
- Updated on each purchase
Security Considerations
Shared Object Safety
- Marketplace is a shared object (concurrent access safe)
- Table provides efficient key-value storage
- No race conditions in listing/buying
Price Validation
- Prices must be positive
- Payment must cover at least the listing price
- Change calculation prevents rounding errors
Access Control
- Only seller can update/cancel their listing
- Buyers must provide sufficient payment
- Fees are automatically collected
Integration with Document Access
When a token is purchased:
- Token ownership transfers to buyer
- Buyer becomes the new owner
- Access keys become available to new owner
- Previous owner loses access
This ensures document access follows token ownership.
Best Practices
- Price Setting: Set competitive but fair prices
- Gas Considerations: Include sufficient gas budget
- Cancellation: Cancel listings you no longer want to sell
- Change Handling: Check returned change coins
Next Steps
- Learn about Document Vault
- Understand Access Control
- Review Getting Started Guide