Skip to main content

Trading System

The trading system enables secure item exchanges between players with support for batch operations, configurable limits, and automatic expiration.

Overview

  • TradeGrain manages each trade session
  • Orleans Streams push real-time events to SignalR clients
  • Configurable limits prevent abuse
  • Auto-expiration cleans up abandoned trades

Trade Flow

sequenceDiagram
participant A as User A (Initiator)
participant T as TradeGrain
participant B as User B (Target)

A->>T: InitiateAsync(userA, userB)
A->>T: AddItemsAsync(items)
B->>T: AddItemAsync(item)
A->>T: AcceptAsync()
B->>T: AcceptAsync()
T->>T: ExecuteTradeAsync()
T-->>A: Items transferred
T-->>B: Items transferred

Configuration

// appsettings.json
{
"Trading": {
"TradeTimeout": "00:15:00",
"ExpirationCheckInterval": "00:01:00",
"MaxItemsPerUser": 50
}
}
OptionDefaultDescription
TradeTimeout15 minAuto-expire pending trades after this duration
ExpirationCheckInterval1 minHow often to check for expired trades
MaxItemsPerUser50Max items per user per trade (0 = unlimited)

API

Initiating a Trade

// Client-side via TradeHub
var session = await connection.InvokeAsync<TradeSession>("InitiateTradeAsync", targetUserId);

Adding Items

// Client-side via TradeHub
await connection.InvokeAsync("AddItemAsync", itemId);

Removing Items

// Client-side via TradeHub
await connection.InvokeAsync("RemoveItemAsync", itemId);

Accepting and Completing

// Client-side via TradeHub
await connection.InvokeAsync("AcceptTradeAsync");
// Wait for "TradeCompleted" event

Cancelling

// Client-side via TradeHub
await connection.InvokeAsync("CancelTradeAsync");

Trade Status

StatusDescription
PendingTrade in progress, items can be added/removed
AcceptedOne party accepted (waiting for other)
CompletedBoth accepted, items transferred
CancelledTrade cancelled by a user
ExpiredTrade timed out
FailedItem transfer failed

Validation

The trade system validates:

  1. Ownership - User must own items they're adding
  2. Tradeability - Item type must have IsTradeable = true in registry
  3. Limits - Cannot exceed MaxItemsPerUser
  4. Status - Can only modify pending trades
  5. Rules - Custom game rules (e.g. SameSeason, SoloSelfFound) are validated via the IRule<T> engine.

Rule Engine

The trade system uses an extensible rule engine to enforce game-specific restrictions without modifying core trade logic.

Interface

Rules implement the IRule<T> interface:

public interface IRule<in TContext>
{
Task ValidateAsync(TContext context);
}

Built-in Rules

  • SameSeasonRule: Ensures both players are in the same season/league.
  • SoloSelfFoundRule: Blocks trading for characters marked as "Solo Self-Found" (SSF).

Registration

Rules are registered in DI and automatically injected into the TradeGrain:

// Program.cs
builder.Services.AddSingleton<IRule<TradeRequestContext>, SameSeasonRule>();
builder.Services.AddSingleton<IRule<TradeRequestContext>, SoloSelfFoundRule>();

Real-Time Events

Trade events are published via Orleans Streams:

EventTrigger
TradeStartedTrade initiated
ItemAddedItem added to trade
ItemRemovedItem removed from trade
TradeAcceptedOne party accepted
TradeCompletedBoth accepted, items transferred
TradeCancelledTrade cancelled
TradeExpiredTrade timed out

SignalR Integration

Clients can subscribe to trade updates:

// Join trade session
await connection.invoke("JoinTradeSession", tradeId);

// Receive updates
connection.on("TradeUpdate", (event) => {
console.log(event.EventType, event.Data);
});

// Leave session
await connection.invoke("LeaveTradeSession", tradeId);