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
| Method | Description |
|---|---|
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:
| Rule | Validation |
|---|---|
SameSeasonRule | Both characters must be in the same season |
SoloSelfFoundRule | SSF 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.