Skip to main content

Trading Grains

Grains that manage peer-to-peer trading with atomic item swaps.

TradeGrain

Manages a single trade session between two characters.

Key: Guid (tradeId)

State

public class TradeGrainState
{
public TradeSession? Session { get; set; }
}

TradeSession Structure

{
tradeId: string;
status: TradeStatus;
initiatorCharacterId: string;
targetCharacterId: string;
seasonId: string;
initiatorItems: string[]; // Item IDs offered by initiator
targetItems: string[]; // Item IDs offered by target
initiatorAccepted: boolean;
targetAccepted: boolean;
createdAt: string;
expiresAt: string;
}

Methods

MethodDescription
InitiateAsync()Start new trade
GetSessionAsync()Get trade state
AddItemAsync()Add item to offer
AddItemsAsync()Add multiple items
RemoveItemAsync()Remove from offer
RemoveItemsAsync()Remove multiple items
AcceptAsync()Accept current offer
CancelAsync()Cancel trade

Trade Lifecycle

Atomic Trade Execution

When both parties accept, items are swapped atomically:

Trade Rules

Trades are validated against a rule engine:

public interface IRule<TContext>
{
Task<RuleResult> EvaluateAsync(TContext context);
}

Implemented Rules:

RuleValidation
SameSeasonRuleBoth characters must be in the same season
SoloSelfFoundRuleSSF characters cannot participate in trades

Expiration

Trades have a configurable timeout (default: 5 minutes):

public class TradingOptions
{
public TimeSpan TradeTimeout { get; set; } = TimeSpan.FromMinutes(5);
}

A timer checks expiration periodically:

Orleans Streams

Trade events are published to Orleans Streams for real-time notifications:

private async Task PublishEventAsync(TradeEvent tradeEvent)
{
var stream = _streamProvider.GetStream<TradeEvent>(
TradeStreamConstants.Namespace,
this.GetPrimaryKey());
await stream.OnNextAsync(tradeEvent);
}

The TradeStreamSubscriber in the API layer subscribes to these events and broadcasts to SignalR groups.