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.