Overview and Goals

This plan outlines how to fork or adapt the Haveno decentralized exchange into being able to be used as a decentralized marketplace for physical and digital goods (e.g. shoes, clothing, electronics). The goal is to extend Haveno’s architecture, which is peer-to-peer, Tor-based, and arbitrated, to support listing and trading real-world products, without disrupting Haveno’s core Monero exchange functionality. We will reuse Haveno’s network design (no central server, all communication over Tor) and its non-custodial escrow and arbitration mechanisms , while introducing new gRPC APIs and data models for products and orders. Key objectives include:

  • Defining new protobuf messages (e.g. Product, Store, ShippingMethod, Order, OrderStatus) and corresponding gRPC services to handle marketplace operations.
  • Ensuring product listings propagate and persist similarly to Haveno trade offers (visible to the network only while a node is online, maintaining privacy and decentralization).
  • Leveraging Haveno’s existing multisig escrow and arbitrator dispute resolution for payments and item delivery conflicts , with minimal changes.
  • Keeping all changes modular and optional, so the marketplace features can be enabled or disabled without affecting core exchange logic.

By following Haveno’s established patterns and code structure, we can implement the marketplace as a clean extension. Below we detail the design, including proto modifications, data storage, arbitration approach, and example message definitions.

Reusing Haveno’s P2P Tor Architecture

Haveno is built as a decentralized P2P network over Tor, with no central server holding offers or data . All nodes (users) connect via onion addresses, and all communications are routed privately through the Tor network . We will reuse this network for the marketplace. In practice, product listings will be distributed across the same Haveno P2P network that currently disseminates trade offers. This means:

  • No centralized listing server: Product offers, like currency trade offers, are stored locally by the seller’s node and shared P2P with other nodes on the network . This ensures censorship-resistance and that listings exist only while the seller’s node is online, just as Haveno offers only exist while posted by an online peer.
  • Tor for anonymity: All marketplace messages (product listings, orders, chats) will be sent through Tor connections between nodes, preserving user privacy exactly as Haveno does for trades .
  • Peer discovery: We will utilize Haveno’s seed nodes and peer discovery mechanisms to advertise marketplace nodes and listings. Haveno currently uses seed nodes for bootstrapping; the marketplace feature will integrate with the same process so that nodes learn about product listings from peers or seed nodes (no new infrastructure needed).

Haveno’s non-custodial escrow architecture will also be repurposed. In Haveno, trades are secured by 2-of-3 multisignature escrow on the Monero blockchain involving buyer, seller, and arbitrator keys . For the marketplace, the buyer’s payment (in XMR) and both parties’ security deposits will be locked in a multisig wallet during the order. This approach ensures the marketplace remains non-custodial: even in a product sale, no third party ever holds funds outright, the funds are either in the multisig escrow or in users’ wallets . Arbitrators hold one key and can only co-sign transactions in case of disputes, never unilaterally moving funds .

Arbitration and dispute resolution will similarly mirror Haveno’s process. Haveno introduces arbitrators to resolve trade disputes if traders cannot resolve issues via chat . In a marketplace context, disputes might involve non-delivery or item quality issues, but the mechanism stays the same: an arbitrator can decide how to release the escrowed funds based on evidence, without ever taking custody. We will reuse Haveno’s dispute gRPC service (Disputes) which already supports opening a dispute, chatting, and resolving with a winner decision . Only minor extensions (like additional dispute reason codes) may be added to cover goods-specific scenarios.

By adhering to Haveno’s proven architecture, P2P/Tor networking, ephemeral distributed offers, multisig escrow, and optional arbitration, the marketplace can achieve the same privacy, security, and decentralization guarantees that Haveno provides for currency trades .

gRPC API Extensions for Marketplace Features

We will introduce new gRPC services and messages to handle marketplace functionality. Haveno’s API is defined via Protocol Buffers and exposed as a gRPC server, with distinct services for different domains (Account, Offers, Trades, Disputes, etc.) . We will add parallel services for Products and Orders, plus any supporting structures (e.g. shipping info). This keeps the API logically separated: core exchange RPCs remain unchanged, and marketplace RPCs are grouped in their own service. The new proto definitions will live alongside Haveno’s existing grpc.proto and pb.proto, possibly guarded by a feature flag to allow compiling without marketplace support if desired.

New Protobuf Messages: The following new message types (entities) are proposed, to be added in the Haveno .proto schema (likely in pb.proto for data structures and in grpc.proto for service RPCs):

  • Product, represents a product listing (analogous to an Offer for trades). It will include identifying and descriptive information and pricing:

    message Product {
        string id = 1;                        // Unique product ID (could be an UUID or similar).
        string title = 2;                     // Name or title of the item.
        string description = 3;               // Detailed description of the item.
        uint64 price = 4 [jstype = JS_STRING]; // Price in piconero (atomic XMR units) or minor currency units.
        string currency_code = 5;            // Currency of price (e.g. "XMR"). Likely "XMR" for all, but extensible.
        repeated ShippingMethod shipping_options = 6; // Available shipping methods for this product.
        string seller_node_address = 7;      // Tor node address of the seller offering this product.
        string store_id = 8;                 // Reference to a Store (seller’s store/profile) offering the item.
        bool active = 9;                     // Whether the listing is currently active.
        uint64 date_posted = 10;             // Timestamp of listing creation.
    }
    

    This structure is comparable to Haveno’s Offer/OfferInfo (which includes fields like price, currency, owner node, etc. ) but tailored to goods. We include the seller’s node address and a reference to a Store so that buyers know who the vendor is (similar to OfferInfo.owner_node_address in trades ). The price is stored in a 64-bit to accommodate atomic units (Haveno uses strings for big ints in JSON, hence jstype = JS_STRING as in OfferInfo.amount ). If needed, currency_code can allow fiat pricing, but initially this will likely always be "XMR" since payments will use Monero.

  • Store, represents a seller’s store or profile. Although each Haveno node is essentially a single user (with one wallet), introducing a Store entity allows a user to organize products or have a public profile for reputation:

    message Store {
        string id = 1;               // Unique store ID.
        string name = 2;             // Store name or alias.
        string description = 3;      // Optional description or profile info.
        string owner_node_address = 4; // The node (seller) that owns this store.
        // Additional fields like reputation, ratings could be added in future.
    }
    

    Each Product.store_id links to a Store, so buyers can see all products by a given seller and get info about them. A simple approach is one store per node (store ID could even be the node’s ID), but this structure allows flexibility (a user could operate multiple stores if desired, though not required).

  • ShippingMethod, describes a shipping or delivery option for physical goods:

    message ShippingMethod {
        string id = 1;           // Identifier for the shipping option (could be unique per product or a standard code).
        string name = 2;         // Name (e.g. "UPS Ground", "Local Pickup").
        string region = 3;       // Region or country this method applies to (e.g. "US", "Worldwide").
        uint64 cost = 4 [jstype = JS_STRING]; // Cost of shipping in piconero (0 if free).
        uint32 estimated_days = 5; // Estimated delivery time in days.
    }
    

    Each product can list multiple ShippingMethod options (with different costs or regions). Shipping costs would ultimately be added to the total price an order will escrow. (In implementation, we might store shipping costs separately or simply adjust the price during order placement, but listing them here allows the buyer’s software to choose an option.)

  • Order, represents a purchase order initiated by a buyer for a given product. This is analogous to a Trade in Haveno. It will capture the product, chosen shipping, and state of the order:

    message Order {
        string id = 1;                 // Unique order ID (e.g. trade ID, likely a UUID or hash).
        Product product = 2;           // Copy of the Product details at time of ordering (or at least product ID and title/price).
        uint64 quantity = 3;           // Quantity of the product being bought (for simplicity, could default to 1 if not supporting multiples).
        ShippingMethod shipping_method = 4; // Shipping option selected by buyer.
        string buyer_node_address = 5;  // Buyer's node address.
        string seller_node_address = 6; // Seller's node address (could derive from product, but stored for convenience).
        OrderStatus status = 7;         // Current status of the order.
        uint64 date_initiated = 8;      // Timestamp when order was placed.
        // Payment and escrow details:
        uint64 price_amount = 9 [jstype = JS_STRING];    // Total price in XMR (product price * quantity).
        uint64 shipping_cost = 10 [jstype = JS_STRING];  // Shipping cost in XMR.
        uint64 buyer_deposit = 11 [jstype = JS_STRING];  // Security deposit from buyer in XMR.
        uint64 seller_deposit = 12 [jstype = JS_STRING]; // Security deposit from seller in XMR.
        string arbitrator_node_address = 13;             // Arbitrator assigned (if any) for this order.
    }
    

    The Order includes both business info (which product, who buyer/seller are, what shipping) and financial info (amounts and deposits). We copy key product fields into the order (especially price) to have an immutable record even if the original listing changes or goes offline. The deposit fields correspond to the escrow: Haveno requires both parties to lock some XMR as collateral , which we’ll apply here as well for fairness and to discourage fraud. For example, buyer_deposit might be a percentage of the price (similar to Haveno’s trade security deposit), and seller_deposit could be a small percentage of the price as a guarantee of shipment.

  • OrderStatus, an enumeration for the stages of an order (similar to trade phases):

    enum OrderStatus {
        OS_PENDING = 0;     // Order placed, awaiting buyer to deposit payment (and deposits) into escrow.
        OS_FUNDS_LOCKED = 1; // Payment and deposits are escrowed (trade is in progress).
        OS_SHIPPED = 2;     // Seller marked item as shipped.
        OS_DELIVERED = 3;   // Buyer marked item as received (delivery confirmed).
        OS_COMPLETED = 4;   // Trade complete,  funds released to seller, deposits returned.
        OS_DISPUTED = 5;    // Dispute opened,  awaiting arbitrator resolution.
        OS_CANCELED = 6;    // Order canceled (before escrow funding or by mutual agreement, funds returned if any).
    }
    

    We may also include intermediate states (e.g. OS_PAYMENT_RELEASED or similar), but the above covers the primary lifecycle. Haveno’s Trade state is managed via multiple booleans and fields in TradeInfo (like is_payment_sent, is_payment_received, etc.) . Here we simplify by combining into a single status enum for clarity in the new API, though internally we might still track granular flags. The transitions roughly map to Haveno’s trade protocol:

    • Pending until escrow is set up,
    • Funds Locked when multisig escrow is funded (akin to both deposits locked and XMR payment in escrow),
    • Shipped when seller performs the equivalent of “payment sent” (in this context, “item sent”),
    • Delivered when buyer confirms receipt (analogous to “payment received” in Haveno’s flow),
    • Completed when the trade is finalized (which in Haveno triggers payout from multisig to seller and return of deposits ),
    • or Disputed/Canceled as alternate ends.

With these data structures in place, we will create new gRPC services in grpc.proto to allow clients (UI or CLI) to interact with the marketplace. We propose two main services:

  • Products service: for managing product listings (similar to Haveno’s Offers service ). This service will allow sellers to post and remove products, and allow any user to query available listings. For example:

    service Products {
        rpc PostProduct (PostProductRequest) returns (PostProductReply) {}
        rpc CancelProduct (CancelProductRequest) returns (CancelProductReply) {}
        rpc GetProduct (GetProductRequest) returns (GetProductReply) {}
        rpc GetProducts (GetProductsRequest) returns (GetProductsReply) {}
        rpc GetMyProducts (GetMyProductsRequest) returns (GetMyProductsReply) {}
    }
    message PostProductRequest { Product product = 1; /* product details to list; seller info inferred from auth context if available */ }
    message PostProductReply  { Product product = 1; }
    message CancelProductRequest { string product_id = 1; }
    message CancelProductReply {}
    message GetProductRequest { string product_id = 1; }
    message GetProductReply { Product product = 1; }
    message GetProductsRequest { string search_keyword = 1; /* optional filtering, e.g. by name or category */ }
    message GetProductsReply { repeated Product products = 1; }
    message GetMyProductsRequest {}  // perhaps filter by store or status if needed.
    message GetMyProductsReply { repeated Product products = 1; }
    

    This mirrors the pattern of Offers service (which has PostOffer, CancelOffer, GetOffer(s) etc. ). The PostProduct call allows a seller to create a listing; internally this will store the product in the local node’s inventory and broadcast it to the network (discussed in the next section). CancelProduct removes the listing (broadcasting a removal). GetProducts returns all current listings (with optional filters), analogous to how GetOffers returns current trade offers . GetMyProducts helps a seller see their own active listings. These RPCs ensure that from a UI perspective, interacting with products is as straightforward as with currency offers.

  • Orders service: for handling the purchase workflow and order state changes (similar to Haveno’s Trades service ). Proposed RPC methods:

    service Orders {
        rpc PlaceOrder (PlaceOrderRequest) returns (PlaceOrderReply) {}
        rpc CancelOrder (CancelOrderRequest) returns (CancelOrderReply) {}
        rpc ConfirmShipped (ConfirmShippedRequest) returns (ConfirmShippedReply) {}
        rpc ConfirmDelivered (ConfirmDeliveredRequest) returns (ConfirmDeliveredReply) {}
        rpc GetOrder (GetOrderRequest) returns (GetOrderReply) {}
        rpc GetOrders (GetOrdersRequest) returns (GetOrdersReply) {}
        rpc SendOrderMessage (SendOrderMessageRequest) returns (SendOrderMessageReply) {}
    }
    message PlaceOrderRequest { string product_id = 1; string shipping_method_id = 2; /* possibly quantity, etc. */ }
    message PlaceOrderReply { Order order = 1; AvailabilityResultWithDescription failure_reason = 2; }
    // (AvailabilityResultWithDescription exists in Haveno’s API for offers ,  we can reuse it to indicate if an order fails because product or seller is unavailable.)
    message CancelOrderRequest { string order_id = 1; }
    message CancelOrderReply {}
    message ConfirmShippedRequest { string order_id = 1; }
    message ConfirmShippedReply {}
    message ConfirmDeliveredRequest { string order_id = 1; }
    message ConfirmDeliveredReply {}
    message GetOrderRequest { string order_id = 1; }
    message GetOrderReply { Order order = 1; }
    message GetOrdersRequest { enum Category { OPEN = 0; CLOSED = 1; DISPUTED = 2; /* etc. */ } Category category = 1; }
    message GetOrdersReply { repeated Order orders = 1; }
    message SendOrderMessageRequest { string order_id = 1; string message = 2; repeated Attachment attachments = 3; }
    message SendOrderMessageReply {}
    

    These calls align with the trade flow:

    • PlaceOrder: Buyer initiates an order by specifying the product (and chosen shipping). This is analogous to TakeOffer in Haveno , it will trigger the creation of a multisig escrow with the buyer’s payment and both deposits, and transition the order to PENDING then FUNDS_LOCKED status once funds are reserved. The reply returns an Order object (similar to how TakeOfferReply.trade returns a TradeInfo ). If the order cannot be placed (e.g., product no longer available), failure_reason is provided.
    • CancelOrder: Allows cancellation before the trade is fully locked or if both parties agree to cancel (if Haveno permits cancellation at certain stages). If invoked while PENDING, it would abort the process and unlock any partial funds. (Cancellation after escrow funding would normally require both sides or arbitration, but we provide an RPC for the user to request it, which might raise a dispute or trigger mutual cancel logic if implemented).
    • ConfirmShipped: The seller calls this when they have shipped/sent the item. This is equivalent to confirming payment sent in a currency trade (which Haveno provides via ConfirmPaymentSent ). It updates the order status to SHIPPED and notifies the buyer.
    • ConfirmDelivered: The buyer calls this when the item is received and verified, equivalent to confirming payment received (ConfirmPaymentReceived in Haveno ). This will trigger the completion of the trade: the multisig payout transaction is signed by buyer and arbitrator (or automatically by buyer+seller if both agree) to release the XMR payment to the seller, and return deposits. The order status becomes COMPLETED.
    • We include GetOrder(s) to query the status of a single order or list of orders. GetOrders can list open/pending orders, completed ones, disputed ones, etc., similar to GetTradesRequest.Category which lists open/closed/failed trades .
    • SendOrderMessage: This is for in-trade communication (chat) between buyer and seller. Haveno’s Trades service has SendChatMessage and GetChatMessages for trade chat . We can reuse the same ChatMessage structure and attachments (images or proofs) for order communications. This is crucial for the buyer and seller to share shipping details (e.g. an address or tracking number) and attempt to resolve any issues directly. Attachments can be used for proof of shipment or condition if needed. The messages would be routed P2P through the same mechanism as trade chats (likely via the mailbox system over the Tor network used by Haveno).

    The Orders service thus parallels Haveno’s Trades service in functionality, adapted to physical goods. We will also ensure that disputes tie in: if an order is marked OS_DISPUTED, the existing Disputes service can be used. For example, an OpenDisputeRequest would reference the order_id (which corresponds to a trade_id in the escrow sense) to summon an arbitrator .

All new RPCs will be defined in the Haveno proto files. We will update the Java package haveno.proto.grpc accordingly and regenerate the gRPC interfaces. Following Haveno’s established coding pattern, we will implement these RPC handlers in the backend by adding new service classes (e.g. GrpcProductsService, GrpcOrdersService) and linking them into the main GrpcServer. Haveno’s developer guide suggests how new API calls flow: e.g. “GrpcServer -> GrpcOffersService -> CoreApi -> CoreOffersService -> OfferBookService” for getting offers . We will mirror this:

  • GrpcProductsService and GrpcOrdersService: new classes to implement the gRPC interfaces. These will parse incoming requests, call into the core logic, and build responses. They will be registered with GrpcServer so that the gRPC server exposes the new services (similar to how GrpcServer registers the existing Offers, Trades services, etc. ).
  • CoreMarketplaceApi (or integrating into existing CoreApi): A core API layer that orchestrates marketplace operations. We might extend CoreApi with methods like createProduct, listProducts, placeOrder, etc., or create a separate CoreMarketplaceService grouping this logic. Given modularity, a separate core service class is preferable (just as CoreOffersService and CoreTradesService exist). For instance, GrpcProductsService.postProduct() calls CoreMarketplaceService.postProduct().
  • ProductCatalog (OfferBook analogue): A new component to hold and manage product listings. In Haveno, the OfferBookService keeps track of active offers (it likely handles receiving new offers from the P2P network and storing them) . We will implement a similar ProductCatalogService (or ProductBookService) that maintains a list of current Product listings. It will interface with the P2P layer to broadcast new listings and listen for listings from other nodes. The CoreMarketplaceService.getProducts() will query this catalog to return available listings (similar to how OfferBookService.getOffers() is the final call in the chain for listing offers ).
  • OrderManager (TradeManager analogue): For handling the lifecycle of orders, including escrow setup, state transitions, and triggers for dispute/arbitration. Haveno has a TradeManager or similar classes in haveno.core.trade that coordinate taking an offer, creating the multisig escrow, and tracking the trade through completion. We will create an OrderManager that does the same for product orders. For example, when CoreMarketplaceService.placeOrder() is called, OrderManager will: reserve the product (prevent others from buying it concurrently, possibly by marking it as in-trade), initiate the Monero multisig escrow (reusing Haveno’s wallet/escrow utilities), deposit buyer’s payment and both deposits into the multisig, record the Order in a local DB or memory, and return the Order info. It will also assign an arbitrator Haveno has an internal method to pick an arbitrator or one from a pool).
  • We will reuse Notification and chat mechanisms as well. Haveno’s Notifications service can push events (like TRADE_UPDATE, CHAT_MESSAGE) to the UI . We will ensure that order updates (status changes) and new chat messages trigger notifications so the user interfaces can react in real time (for example, a buyer gets notified that the seller marked an item as shipped, etc.).

By extending the gRPC API in this modular fashion, we keep a clear separation: existing Haveno exchange RPCs are untouched (ensuring backward compatibility for exchange users), and the marketplace functionality is cleanly added as new services. Clients that do not need marketplace can simply not call those services. If needed, one could even compile Haveno without the marketplace by toggling those services off (since no core trade code is affected). This satisfies the requirement that the marketplace features be optionally disable-able and not interfere with core Monero trading.

Persistence of Product Listings (Offer Analogy)

In Haveno’s design, trade offers exist only while a node is online, there is no permanent global order book on a server; instead, each node shares its offers with peers when online, and an offer effectively vanishes if the maker goes offline (or cancels it). We will follow the same model for product listings:

  • When a seller uses PostProduct, the product is stored in the local ProductCatalog and then announced to the network. We will implement a P2P message (probably extending Haveno’s existing p2p protocol) for a “new product listing” broadcast, similar to how new offers are broadcast. Haveno has a custom P2P message why are stored on each nodes disk serialized as a binary protofubber for offers which gets relayed through peers (potentially using its seed node as an introduction point). We’ll add a new message type for products. For instance, if Haveno’s pb.proto defines an Offer message for P2P exchange, we will define a Product message (or reuse the same definition from the API) to propagate listings. Each listing message will contain the product details and the seller’s network address.
  • Other nodes receiving the product message will add it to their local ProductCatalog (provided it’s valid and the seller is reachable). In effect, each node maintains a distributed marketplace listing similar to a distributed order book. When a user calls GetProducts, their node returns the listings currently in its ProductCatalog (which includes items from all online sellers it’s connected to).
  • No long-term storage on others’ nodes: If a seller goes offline, we may choose to expire or remove their products from others’ ProductCatalog after a certain timeout of not hearing from that seller. Haveno offers likely work in a similar way (offers might be removed if not refreshed or if the peer is not seen). For simplicity, we can remove product listings once the originating node disconnects or after an inactivity period. This means product availability is ephemeral: a product listing “persists” only as long as the seller’s node remains online and broadcasting it, matching the question’s requirement. This design naturally prevents stale listings, if the seller is gone, the item is not for sale.
  • Local persistence for sellers: On the seller’s own node, we can store the product listings in a local database or file so that if the node restarts, it can re-post those products automatically. This would mirror how Haveno allows a user to restore their offers on restart (if Haveno doesn’t do this already, it would be a useful feature to implement in parallel). A simple approach is to save the list of active product listings in the user’s Haveno data directory (perhaps in the wallet or a separate file), and on startup, after connecting to the P2P network, automatically re-publish each product. This way, a seller doesn’t have to manually re-create listings every time they restart their node, the listings “persist” across sessions for the seller, but still only propagate when online. We will implement this by extending the account or application initialization part of Haveno: after the user opens their account, the ProductCatalogService can reload saved products and issue PostProduct broadcasts for each.
  • Consistency with offers: We will ensure the product propagation follows Haveno’s existing conventions. For instance, Haveno employs a gossip model & peer exchange model for offers: a newly online node might request all current offers from a seed or a peer (pull), and peers also broadcast changes (push). We can adopt the same for product listings. We might integrate product listings into Haveno’s seed node (if Haveno’s seed node caches offers, it could also cache products), or simply rely on peer gossip. This detail will be decided by reviewing Haveno’s P2P code (likely in the p2p or core.offer packages). The key is to minimize new complexity: reuse message handling and storage infrastructure already present for offers. If necessary, we add new message handlers for products in Haveno’s P2P layer, but reuse encryption/Tor routing as-is.
  • Product availability: Just as offers may have a state (AVAILABLE, RESERVED, COMPLETED) , we might track product availability state. For example, once an order is placed on a product, we might mark the product listing as temporarily unavailable or in a RESERVED state (to avoid multiple buyers double-spending a single inventory item). Haveno’s OfferInfo.state field serves a similar purpose (e.g. an offer might go into a reserved state when a trade is in progress) . We will add an active boolean or a status field in Product for this. The node will broadcast updates to the product listing when its status changes (e.g. “product reserved” or ultimately “product sold” when order completes). If the seller wants to allow multiple sales of the same item (quantity >1), we would need to manage inventory count; but initial implementation can assume one listing represents one item/unit for simplicity.

Backend storage: The ProductCatalogService will manage an in-memory list of current products (from the network) and also interface with a persistent store for local data. Haveno uses an embedded database (it might use H2 or a simple file-based store for offers/trades, we’d confirm by reviewing the code). For the marketplace, we can add a small table or JSON file for products. The data to store includes the seller’s own products (to reload on restart) and possibly cached external products. However, caching others’ products to disk is optional, since they are ephemeral, we might simply keep them in memory. To be safe, we could persist for quick startup (like a local snapshot of marketplace state), but this is not strictly necessary.

In summary, product listings will behave just like trade offers: distributed via the P2P network, not permanently recorded by any central server, and only guaranteed to be available while the lister is online. This satisfies the persistence requirement by mirroring Haveno’s offer propagation model. The use of an in-memory catalog and optional local persistence ensures that the marketplace scales similarly to Haveno’s exchange, without introducing centralized databases.

Order Execution and Escrow Mechanism

When a buyer decides to purchase a product (placing an order), the underlying process will utilize Haveno’s escrow and trade protocol under the hood. We will treat a product order as a special case of a Haveno trade where the asset on one side is a physical good delivered outside the system. Notably, this is directly analogous to Haveno’s core use-case: exchanging XMR for fiat involves XMR on one side and an outside-the-system asset (fiat cash, bank transfer, etc.) on the other side . In the marketplace, XMR is exchanged for a physical/digital item, which is delivered outside the system, conceptually the same pattern. The Haveno documentation explicitly notes: “The payment happens outside of Haveno, which is mostly used to hold the two traders' deposits in escrow and the amount of XMR to be exchanged.” . We will leverage this exact approach:

  • Escrow setup: When PlaceOrder is called by the buyer, the backend (through OrderManager) will initiate a 2-of-3 multisignature Monero wallet involving the buyer, seller, and an arbitrator (just as Haveno does for trades ). The buyer will be prompted (by the software) to fund the multisig with the total amount required: this includes the price of the item (plus shipping cost, if any) which will ultimately go to the seller, plus the buyer’s security deposit. At the same time, the seller is required to contribute their security deposit into the multisig. Haveno’s protocol already handles locking both parties’ deposits at trade start , we will use the same code path. In practice, Haveno’s TakeOffer sequence locks deposits and trade amount; in our case the “offer” is the product listing and “trade amount” is the product price. The output will be that the multisig escrow now holds: buyer_payment + buyer_deposit + seller_deposit in XMR. (If the product is digital and delivery is instant, we might allow 0 seller deposit, but for fairness we assume a small deposit from seller to incentivize honest fulfillment.)

  • Arbitrator assignment: Haveno usually assigns an arbitrator (from a set of registered arbitrators) when a trade is taken. We will do the same for orders, likely the same arbitrator selection method can be reused. The arbitrator’s Monero public key becomes part of the multisig (the 2-of-3 third key) . This is recorded in the Order.arbitrator_node_address field as well.

  • Locking period: In Haveno, once funds are deposited in multisig, there is a lock time and a wait (e.g. 20 minutes for confirmations) . We will follow the same waiting period to ensure the Monero transactions are confirmed on-chain. During this time, the order is in FUNDS_LOCKED status, and the seller should prepare to ship.

  • Shipping and confirmation: After escrow is confirmed, the seller ships the item to the buyer’s provided address (note: address exchange will happen via the trade chat, the buyer can send their shipping address privately through the built-in chat, which is end-to-end encrypted over Tor). Once shipped, the seller calls ConfirmShipped, which updates status to SHIPPED and optionally logs a shipping tracking code (the seller can send it via chat or we could allow an optional field in ConfirmShippedRequest for a tracking number). The buyer is notified of shipment (via a notification or by polling GetOrder).

    When the buyer receives the item (or the digital good is delivered), they call ConfirmDelivered. This moves status to DELIVERED and triggers trade completion: the buyer essentially signals they are satisfied, akin to “payment received” in Haveno . At this point, because both parties are in agreement, the 2-of-3 multisig can be spent by buyer and seller collaboratively (or by buyer and arbitrator, but if buyer confirms, presumably seller will also sign). Haveno automates the payout transaction: once the buyer confirms, the Haveno backend will create the payout transaction from the multisig. The transaction will send the price amount (plus shipping if included) to the seller’s Monero address and return each party’s deposit to their own addresses. Since the buyer’s and seller’s wallet info is known (Haveno requires each to have a Monero wallet integrated), the funds distribution can be done just as in a normal trade completion . The Order.status becomes COMPLETED. In TradeInfo terms, this corresponds to is_completed = true and having a payout_tx_id .

  • Failure cases and dispute: If something goes wrong, e.g., the seller never ships, or the buyer claims non-delivery, either party can open a dispute via the Disputes.OpenDispute RPC (or via the UI’s dispute button). This would set the order status to DISPUTED and notify the arbitrator. The arbitrator can then investigate (using chat messages, any provided evidence such as photos or tracking info). Haveno’s dispute resolution flow will be used: the arbitrator eventually calls ResolveDispute(trade_id, winner, reason, etc.) . We may extend DisputeResult.Reason to include marketplace-specific reasons like ITEM_NOT_DELIVERED or ITEM_NOT_AS_DESCRIBED, but this is a minor detail. The arbitrator’s decision will specify a winner (buyer or seller) and optionally a custom payout division . Then the arbitrator and winner can sign the multisig transaction to allocate funds accordingly. For example:

    • If buyer wins (seller failed to deliver or item was wrong), the arbitrator will have the multisig pay the buyer back their payment plus their deposit. The seller might forfeit their deposit (it could be awarded to buyer or just returned to seller depending on policies; likely at least buyer gets their deposit back since they were wronged, and possibly their payment; seller deposit might be given to buyer as compensation or split).
    • If seller wins (buyer received item but is falsely claiming an issue), the payout transaction will give the seller the price amount (they keep the payment) and likely also return seller’s deposit. The buyer could lose their deposit to compensate the seller’s trouble (perhaps arbitrator awards buyer’s deposit to seller or splits it). These rules can be configured similarly to how Haveno handles fiat trade disputes, typically the innocent party keeps both deposits to cover their loss.

    In any case, the existing multisig and arbitrator system can handle the outcome by constructing the appropriate payout transaction. The Order.status would then transition to a final state (we might still call it COMPLETED or perhaps DISPUTED_RESOLVED internally) once the dispute is resolved and funds are moved.

  • Edge cases: If the buyer doesn’t confirm delivery within a certain time (e.g. item is in transit for too long), the seller might be able to trigger a reminder or even auto-release after a timeout. Haveno has a concept of timeouts for crypto trades (e.g. if buyer fails to mark payment received within a period, they risk losing deposit). We can incorporate similar time-based logic: define a reasonable timeframe for delivery confirmation, after which the seller can escalate or auto-complete if the buyer is unresponsive (perhaps involving arbitrator if not automatic). These policies will be refined but are outside the core architecture (they’re business logic that can be adjusted without structural changes).

Feasibility: This order execution plan is feasible because it uses Haveno’s existing trade-handling components. The Monero wallet integration, multisig creation, deposit management, and arbitrator coordination are already implemented for currency trades . We will tap into those. For instance, Haveno’s TradeManager likely has methods to create multisig wallets and handle the sequence of deposit transactions and payout. We can call those methods from our OrderManager with minimal modifications: instead of referencing a currency offer and payment method, we reference a product and shipping info, but ultimately we still call “lock XMR amount and deposits in a 2-of-3 wallet and later release it”. We will ensure the proto Contract that Haveno uses for trades (if any) can accommodate our scenario. Haveno’s TradeInfo.contract contains details of the trade contract, including buyer/seller addresses and payment method payloads . For our orders, we might not need a complex contract beyond identifying buyer and seller and listing the item, but if needed we will create a lightweight ContractInfo for marketplace trades too (or reuse the existing one with possibly dummy payment fields). The escrow transaction flows remain the same as a trade for XMR, nothing new needs to be invented on the blockchain side.

In conclusion, the order/trade flow will operate within Haveno’s battle-tested escrow protocol . Buyers and sellers will experience it as a simple process (deposit XMR, then mark shipped/received), but underneath, the Monero network ensures funds are safe until both are satisfied or an arbitrator resolves a dispute.

Adapting Arbitration for Marketplace Disputes

Haveno’s arbitration system is already designed to be general: an arbitrator is an entity that can mediate any trade where two parties disagree. In Haveno, arbitrators never hold funds directly (they only have one key to co-sign transactions) and are only involved if a dispute is raised . We will reuse this system entirely, with slight adaptations for marketplace specifics:

  • Arbitrator role remains the same: If a dispute arises in a product order, the arbitrator’s role is to decide how the escrowed XMR should be distributed. They will likely communicate with both buyer and seller via the dispute chat (Haveno has SendDisputeChatMessage for arbitrator-trader communication ). The arbitrator can request evidence: for example, the buyer could upload a photo of a damaged item, or the seller could provide a shipment tracking log. The existing Attachment message type (used in disputes for things like images or files ) will be very useful here to handle evidence uploads. We do not need a new system for this, just an understanding that these attachments might contain things like photos of the goods or screenshots of delivery status.

  • Adapting dispute resolution outcomes: Haveno’s ResolveDisputeRequest allows specifying a winner (buyer or seller) and a reason, plus an optional custom payout amount . This is flexible enough to cover marketplace cases. We may expand the enum of reasons (DisputeResult.Reason) to include cases like NOT_DELIVERED or NOT_AS_DESCRIBED, but this is a minor change. The arbitrator, after reviewing, will decide the winner:

    • If buyer is the winner, arbitrator will co-sign a transaction returning the buyer’s XMR (price + deposit) and possibly also the buyer keeps their deposit (so effectively buyer is whole, seller loses their deposit).
    • If seller wins, the payout goes to seller (they get the price amount), and buyer likely forfeits their deposit (which could be awarded to seller).
    • There could be split decisions if partial refunds are needed (for instance, item was not as described but buyer is willing to keep it for a partial refund). In that case, the arbitrator could specify a custom payout amount in ResolveDisputeRequest.custom_payout_amount , e.g., refund half the XMR to buyer, give half to seller, and the deposits can be split or returned accordingly. Haveno’s flexibility here allows complex resolutions if needed.
  • Leverage existing dispute infrastructure: Haveno already has UI and API elements for disputes. We will not create a new dispute system; instead, we’ll make sure that each Order corresponds to an underlying Trade or escrow id that the dispute system recognizes. For example, we might internally tag marketplace trades with a special payment method type (like “External/PhysicalGood”) so the arbitrator knows the context, but ultimately they use the same screens or commands to resolve it. In the gRPC API, we don’t necessarily need a new OpenOrderDispute; the regular OpenDispute(trade_id) can be used by providing the order_id (which is effectively the trade_id for the escrow).

  • Arbitrator assignment and registration: Haveno requires arbitrators to register (likely via DisputeAgents.RegisterDisputeAgent which arbitrators use to announce themselves) . We will not change this, the same pool of arbitrators can serve both exchange trades and marketplace orders. We might want arbitrators to have some indication of the type of dispute when one comes in (so they know to ask for shipping info, etc.), but this can be deduced from the context (perhaps if payment method in the contract is a special “PHYSICAL_GOOD” type, or simply by the presence of product details in the contract).

  • Feasibility in code: Because we are not altering the fundamental dispute mechanism, we mainly need to ensure that when an order is created, it registers properly so that GetDisputes and other calls can retrieve it. Haveno’s Dispute object (likely defined in pb.proto) contains fields like trade ID, dispute state, etc. We will populate those accordingly for marketplace orders. The existing Disputes gRPC service already covers all needed functionality (fetching disputes, sending messages, resolving) . Therefore, adapting arbitration is more about policy (how to decide fairness in goods trade) than about coding new components. From an implementation standpoint, we validate that adding marketplace does not break arbitration by checking that the Trade (escrow) created for an order is indistinguishable from a normal trade to the arbitrator module. If Haveno’s code in DisputeManager or similar expects certain fields (like a payment account id or fiat transfer info), we will simply supply dummy or placeholder info if needed. For instance, arbitrators might normally see the fiat payment method details; in a goods trade, they might see none or a generic placeholder (“Physical delivery outside platform”). This is acceptable as long as they can still perform their duty using chat and manual evidence.

In summary, the existing arbitrator system is fully capable of handling marketplace disputes, since it was built to manage trades where one side of the deal occurs outside the blockchain (fiat transfer) . A product sale is just another case of that. By reusing the same Dispute RPCs and logic, we also keep the user experience unified, a dispute will work the same way whether it was over a currency trade or an item sale.

Data Storage and Backend Modifications

Implementing these features will require some additions to Haveno’s backend storage, but we aim to integrate cleanly:

  • Proto file changes: All new messages and services will be added to Haveno’s .proto definitions (most likely grpc.proto for the service definitions and request/response messages, and pb.proto for the reusable data structures like Product, Order, etc.). As per Haveno’s development process, after editing the proto, we will run the build to regenerate gRPC code . The addition of these messages should not conflict with existing ones; we will assign new field numbers and message IDs safely beyond the current ranges. For example, if pb.proto currently ends at a certain message, we append our new definitions afterwards. (The diff snippet from Haveno’s repo suggests message Offer exists around line 1450 in pb.proto, meaning we have room to add new definitions without clashing .)
  • Database schema: Haveno uses an embedded database for persistence the profobufer is transformed into binary format for effienct serialization. We will introduce new database tables or domain objects for Store, Product, and Order. This can be done similarly to how Haveno stores offers and trades. For example, Haveno’s OfferBookService might simply keep offers in memory (and rely on the wallet for persistent aspects like locked funds). However, trades definitely have persistence (so they survive restart and can be recovered). We will ensure that orders are persisted like trades: if the node restarts in the middle of an order, it should reload that order and resume state (especially important if waiting for delivery or in dispute). So we will add an OrderDAO or extend the existing trade persistence to include marketplace orders. If Haveno uses plain objects with serialization, we can add our objects to the serialization. If it uses a relational DB via ORM, we’ll create new entities for these.
  • Wallet integration: The Haveno core uses a Monero wallet RPC or library to manage multisig wallets and funds. We will reuse the same for handling escrow. The WalletService (if one exists for handling balances, addresses, etc.) will be invoked when creating orders to move XMR into escrow. We might need to create new payment account types for physical goods. Haveno’s design around PaymentAccount/PaymentMethod is geared to fiat/crypto payment methods (e.g., bank accounts, Revolut, PayPal, etc.) . For goods, there isn’t a “payment method” in the same sense, since payment is always XMR on-platform. We might treat the product sale as a special payment method internally. Alternatively, we bypass the PaymentAccount system altogether for marketplace trades, since no off-chain payment info is needed (the only off-chain detail is shipping address, which we handle via chat, not via PaymentAccount). Therefore, we can skip creating a PaymentAccount in TakeOfferRequest equivalent. Notice that TakeOfferRequest includes a payment_account_id for the fiat/altcoin side . In PlaceOrderRequest we omit such a field. Our code will instead directly handle the escrow funding without requiring a separate payment account (or we create a dummy payment account type like “PhysicalDelivery” just to satisfy the interface but it wouldn’t contain anything). This approach isolates marketplace logic so it doesn’t tangle with Haveno’s payment accounts.
  • Separation and modularity: We will keep all new code in separate packages as much as possible. For instance, new proto definitions use their own service names (Products, Orders). New Java classes might reside in haveno.core.market or haveno.core.marketplace package. We will likely create sub-packages for product and order management under the core module. This way, if one were to remove or disable marketplace, it could be done without affecting core classes. The registration of the marketplace gRPC services can be made conditional. Perhaps an entry in Haveno’s config (e.g., enableMarketplace=true/false) can control whether GrpcServer instantiates GrpcProductsService and GrpcOrdersService. Similarly, the P2P layer can ignore marketplace messages if disabled. By default, in our fork, we will enable it, but we highlight that it can be cleanly isolated.
  • Stats and monitoring: Haveno has a statsnode (based on repo structure) which might collect global stats of offers, etc. If the network were to have a stats node for marketplace (not required for function, but useful for UX), we could optionally extend it to count active products, etc. This is an auxiliary consideration and not critical to core functionality, so it can be deferred or handled outside the main implementation plan.

Throughout the development, we will validate feasibility by studying the actual Haveno source structure. For example, if Haveno’s Offer propagation is implemented in a class OfferManager or via the P2PService, we will ensure our ProductManager integrates similarly. Early inspection shows the Haveno code is structured with clear separation of concerns and uses gRPC for the API layer , which is encouraging for adding new features. Each step of adding the new proto messages and services, we will follow Haveno’s contributor guidelines (as indicated in their developer guide ) to implement and test incrementally:

  • First define proto and run make to generate code .
  • Implement service classes following patterns (we can literally mirror GrpcOffersService to create GrpcProductsService, etc., calling into new core methods) .
  • Use or extend OfferBookService into ProductCatalogService for offer distribution.
  • Write unit tests similar to Haveno’s (the developer guide references updating haveno.ts and its tests for new RPCs ; we would do analogous tests for our new RPCs).

Example Workflow Illustration

To cement the design, here’s a brief example of how everything ties together in practice, referencing the above components:

  1. Seller Listing a Product: Alice (running a Haveno marketplace node) wants to sell a pair of shoes. She uses the UI which calls PostProduct with details (title “Running Shoes”, description, price 0.5 XMR, shipping options UPS 0.05 XMR worldwide, etc). Her node’s GrpcProductsService receives this, invokes CoreMarketplaceService.postProduct(), which creates a Product object and stores it in Alice’s ProductCatalog. Alice’s node then broadcasts a NewProduct message over the P2P network. Peers (including any seed nodes) receive it and add that Product to their in-memory catalogs. Now Bob (a buyer) who is online can call GetProducts on his node and see Alice’s “Running Shoes” listing among the results . The listing includes Alice’s onion address and her specified price .

  2. Buyer Placing an Order: Bob decides to buy the shoes. His UI calls PlaceOrder with the product_id of Alice’s listing and selects UPS shipping. His GrpcOrdersService.placeOrder() triggers the order flow:

    • Bob’s node contacts Alice’s node (likely directly via a message, or possibly through the seed node as intermediary, similar to how offer taking works) to initiate the trade. Alice’s node sees the request and the product ID, confirms the item is still available. We mark the product as reserved (Alice’s ProductCatalogService updates the status and could broadcast an update or at least will not let others take it).
    • Both nodes collaboratively create a 2-of-3 multisig escrow on Monero: Bob’s wallet prepares to send 0.5 XMR price + say 0.1 XMR deposit; Alice’s wallet prepares 0.1 XMR deposit. The arbitrator’s key is also included. Bob’s node and Alice’s node use Haveno’s wallet API to create the multisig and exchange keys (this all happens in the background code). Eventually, Bob’s node publishes the funding transactions: 0.6 XMR from Bob (0.5 price + 0.1 deposit) and 0.1 XMR from Alice into the multisig address. After required confirmations, the escrow is active.
    • Bob’s PlaceOrder call returns an Order object to his UI, containing status FUNDS_LOCKED (once confirmed) and all details. Alice’s node also creates a corresponding Order in her system (she might get a notification or can check via GetOrders). Both see that an order with ID (say) ORDER12345 is now in progress for the shoes.
  3. Shipping and Confirmation: Once escrow is locked, Alice gets a signal (perhaps a notification on her UI via the Notifications.TRADE_UPDATE event with trade/order info ). She obtains Bob’s shipping address from the trade chat (Bob would have sent it either at order placement or when asked). Alice ships the shoes via UPS and then clicks “Mark as Shipped” (which calls ConfirmShipped). Her node updates the Order status to SHIPPED and sends a message to Bob’s node. Bob sees the status update (via notification or by polling GetOrder). They may exchange chat messages during this (e.g., Alice sends the tracking number through SendOrderMessage).
    Bob waits until the package arrives, then upon receiving and being satisfied, he clicks “Confirm Delivery” (ConfirmDelivered). His node updates status to DELIVERED and initiates payout. The Haveno backend now uses Bob’s and arbitrator’s keys to sign the payout transaction from the multisig: 0.5 XMR (price) goes to Alice’s wallet address, 0.1 XMR deposit returns to Bob’s, and 0.1 XMR deposit returns to Alice’s. This transaction is broadcast to the Monero network. Once confirmed, both wallets show the funds, and the order is marked COMPLETED. The listing for the shoes is automatically removed (Alice’s node cancels the product or marks it sold, perhaps CancelProduct is invoked internally or she had marked quantity 0 remaining).
    Both Alice and Bob can see the order in their history (GetOrders(category=CLOSED) would list it).

  4. Dispute scenario example: If Bob claimed the shoes were never delivered (say the package got lost) and opens a dispute, an arbitrator Charlie gets involved. Using Haveno’s dispute interface, Charlie sees Order ORDER12345, reads chat logs and tracking info (Alice provided tracking showing delivered, Bob insists nothing received). Charlie might determine that the postal service marked it delivered, so likely Bob received it (or at least Alice isn’t at fault). Charlie resolves the dispute in favor of Alice (seller). The ResolveDispute call from Charlie’s side results in a payout transaction where Alice gets the 0.5 XMR and possibly Bob’s 0.1 deposit (as compensation or to dissuade Bob from false claims). Bob gets back nothing except his remorse. The outcome is recorded and both Bob and Alice see the dispute resolution in the UI, and the order marked as closed with dispute outcome. If Bob was truthful and item truly lost, Charlie might decide in Bob’s favor instead, refunding Bob and penalizing Alice’s deposit. Either way, the process used the same code paths as Haveno dispute resolution, only the context differed (shipping vs fiat payment).

This workflow demonstrates that with our extensions, Haveno can seamlessly handle marketplace transactions using the same decentralized principles:

  • Privacy: All communications (listing broadcast, order negotiation, chat, dispute) went through Tor, preserving anonymity of Bob, Alice, and Charlie (arbitrator) .
  • Security: Funds were protected in multisig; no one could steal them without consensus. Even the arbitrator couldn’t take funds unilaterally .
  • Decentralization: The product listing was not on a server, Bob discovered it through the P2P network. The escrow was directly on Monero’s blockchain, and arbitration was a decentralized human process, not an automated centralized system.
  • Non-interference with core features: If a user only wants to use Haveno for XMR/BTC trading, they can do so and never invoke any of these marketplace RPCs. The marketplace messages might still flow in the network (which is fine, they’ll just be ignored by nodes that aren't interested), or those users could run with marketplace disabled. The core trading flows remain as they were.

Modular Design and Optionality

To ensure the marketplace can be cleanly separated or disabled, we will implement it in a modular fashion:

  • All new proto services (Products, Orders) can be placed in their own file (e.g., market.proto imported by grpc.proto) or at least clearly delineated in the proto with comments, so it’s easy to stub them out if needed. When compiling the project, one could choose not to include these services in the GrpcServer.
  • We can introduce a configuration flag in Haveno’s config (perhaps in haveno.properties or a startup parameter) like marketplace.enabled=true/false. On startup of the Haveno daemon, if this is false, the GrpcServer will simply not instantiate the GrpcProductsService and GrpcOrdersService. It will also not start the ProductCatalogService or related background tasks. The node will essentially behave like a normal Haveno exchange node (it might still hear product messages on the network, but it can ignore them).
  • The marketplace code will be organized such that its dependencies on core Haveno code are minimized. For example, the core exchange logic doesn’t need to know about Product or Order classes, only the marketplace manager deals with those. The only intersection is at the point of using the wallet and escrow functionalities, where we’ll ensure abstraction. If needed, we might add a couple of methods to existing core classes (e.g., allowing a trade to be initiated without a fiat payment account for marketplace use), but we will guard these additions so they don’t alter behavior for normal trades.
  • By keeping things separate, merging future upstream changes from Haveno (since it’s an active project) will be easier. Our fork’s differentiating code will be largely confined to specific sections, reducing merge conflicts.

Conclusion

This implementation plan has detailed how to fork and extend Haveno into a decentralized marketplace platform. We have proposed concrete proto definitions and new gRPC services for products and orders, demonstrating how they parallel Haveno’s offers and trades. We’ve addressed how product listings are propagated and stored in a peer-to-peer, ephemeral manner (only live when nodes are online, no central server) , and how to possibly persist them locally for convenience. The order handling and escrow make maximal reuse of Haveno’s Monero multisig escrow and arbitration scheme , ensuring that payments for goods are secured just like crypto trades.

Crucially, all these changes are designed to be additive and modular. The marketplace functionality can be developed as an optional module that does not interfere with Haveno’s primary purpose as a Monero exchange. By following Haveno’s existing code structure and conventions , we ensure the feasibility of the implementation: the new components slot into the framework where expected and leverage services (Tor networking, wallets, etc.) already present.

In essence, we are building on Haveno’s strengths, privacy, decentralization, security, and applying them to a broader use-case of buying and selling goods. The end result will be a decentralized marketplace with the anonymity of Monero and Tor, the safety of multisig escrow, and the community-driven trust of arbitrated dispute resolution, all achieved by forking and extending Haveno in a clean, maintainable way.