返回提交历史
Modified
src/controllers/api/inventoryController.ts
+9
-2
Modified
src/controllers/api/tradingController.ts
+102
-118
Modified
src/controllers/custom/deleteAccountController.ts
+3
-0
Modified
src/models/inventoryModels/inventoryModel.ts
+0
-2
Added
src/models/tradingModel.ts
+21
-0
Added
src/services/tradingService.ts
+16
-0
Modified
src/types/inventoryTypes/inventoryTypes.ts
+3
-24
Added
src/types/tradingTypes.ts
+40
-0
XFEstudio/SpaceNinjaServer
chore: use mongodb for pending trades (#3908)
Closes #3892 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/3908 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
3caf9e3a
代码差异
8 个文件
+194
-146
@@ -66,6 +66,8 @@ import { generateRewardSeed } from "../../services/rngService.ts";
66
66
import { getInvasionByOid, getWorldState } from "../../services/worldStateService.ts";
67
67
import { createMessage } from "../../services/inboxService.ts";
68
68
import gameToBuildVersion from "../../constants/gameToBuildVersion.ts";
69
import { PendingTrade } from "../../models/tradingModel.ts";
70
import { exportTrade } from "../../services/tradingService.ts";
69
71
70
72
export const inventoryController: RequestHandler = async (request, response) => {
71
73
const account = await getAccountForRequest(request);
@@ -361,13 +363,18 @@ export const getInventoryResponse = async (
361
363
buildLabel: string | undefined,
362
364
forWebui: boolean = false
363
365
): Promise<IInventoryClient> => {
364
const [inventoryWithLoadOutPresets, ships, latestMessage] = await Promise.all([
366
const [inventoryWithLoadOutPresets, ships, latestMessage, pendingTrades] = await Promise.all([
365
367
inventory.populate<{ LoadOutPresets: ILoadoutDatabase }>("LoadOutPresets"),
366
368
Ship.find({ ShipOwnerId: inventory.accountOwnerId }),
367
Inbox.findOne({ ownerId: inventory.accountOwnerId }, "_id").sort({ date: -1 })
369
Inbox.findOne({ ownerId: inventory.accountOwnerId }, "_id").sort({ date: -1 }),
370
PendingTrade.find({ $or: [{ a: inventory.accountOwnerId }, { b: inventory.accountOwnerId }] })
368
371
]);
369
372
const inventoryResponse = inventoryWithLoadOutPresets.toJSON<IInventoryClient>();
370
373
inventoryResponse.Ships = ships.map(x => x.toJSON<IShipInventory>());
374
inventoryResponse.PendingTrades = pendingTrades.map(trade => {
375
const us = trade.a.equals(inventory.accountOwnerId) ? "a" : "b";
376
return exportTrade(trade, us);
377
});
371
378
372
379
if (!forWebui) {
373
380
for (const key of accountCheatBooleans) {
@@ -11,7 +11,7 @@ import {
11
11
import { getAccountForRequest } from "../../services/loginService.ts";
12
12
import { GuildPermission } from "../../types/guildTypes.ts";
13
13
import type { RequestHandler } from "express";
14
import type { IPendingTrade, ITradeOffer } from "../../types/inventoryTypes/inventoryTypes.ts";
14
import type { IPendingTradeDatabase, ITradeOffer } from "../../types/tradingTypes.ts";
15
15
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
16
16
import { logger } from "../../utils/logger.ts";
17
17
import { fromMongoDate, version_compare } from "../../helpers/inventoryHelpers.ts";
@@ -21,112 +21,100 @@ import { importUpgrade } from "../../services/importService.ts";
21
21
import { Guild } from "../../models/guildModel.ts";
22
22
import { ExportArcanes, ExportResources, ExportUpgrades, type TRarity } from "warframe-public-export-plus";
23
23
import { getInfNodes, getNemesisManifest } from "../../helpers/nemesisHelpers.ts";
24
import { PendingTrade } from "../../models/tradingModel.ts";
25
import type { HydratedDocument, QueryFilter } from "mongoose";
26
import { exportTrade } from "../../services/tradingService.ts";
24
27
25
28
export const tradingController: RequestHandler = async (req, res) => {
26
29
const account = await getAccountForRequest(req);
27
30
const accountId = account._id.toString();
28
const op = req.query.op as string;
29
if (op == "0" || op == "1") {
31
const op = req.query.op as string as TradingOperation;
32
if (op == TradingOperation.InitialOffer) {
30
33
const buddyId = req.query.buddyId as string;
31
const tradeId = getTradeId(accountId, buddyId);
32
34
const offer = getJSONfromString<ITradeOffer>(String(req.body));
33
pendingTrades[tradeId] ??= {
34
trader: accountId,
35
tradee: buddyId,
36
traderOffer: { _SlotOrderInfo: ["", "", "", "", "", ""] },
37
tradeeOffer: { _SlotOrderInfo: ["", "", "", "", "", ""] },
38
traderReady: false,
39
tradeeReady: false,
40
traderAccepted: false,
41
tradeeAccepted: false,
42
revision: -1,
43
clanTax: (req.query.guildId ? await Guild.findById(req.query.guildId as string) : undefined)?.TradeTax ?? 10 // When trading in Maroo's Bazaar, a 10% "Hub Tax" applies.
44
};
45
const us = accountId == pendingTrades[tradeId].trader ? "trader" : "tradee";
46
logger.debug(`${us} offer:`, offer);
47
{
48
const tax = calcuateTax(offer);
49
const clanTaxRate = pendingTrades[tradeId].clanTax ? 1 / pendingTrades[tradeId].clanTax : 0;
50
const clanTax = clanTaxRate * tax;
51
logger.debug(`calculated tax: ${tax} + ${clanTax}`);
52
}
53
pendingTrades[tradeId][`${us}Offer`] = offer;
54
pendingTrades[tradeId].traderReady = false;
55
pendingTrades[tradeId].tradeeReady = false;
56
pendingTrades[tradeId].traderAccepted = false;
57
pendingTrades[tradeId].tradeeAccepted = false;
58
pendingTrades[tradeId].revision += 1;
35
const us = getSide(accountId, buddyId);
36
const them = us == "a" ? "b" : "a";
37
const trade = await PendingTrade.create({
38
[us]: accountId,
39
[them]: buddyId,
40
[`${us}Offer`]: offer,
41
clanTax:
42
(req.query.guildId ? await Guild.findById(req.query.guildId as string, "TradeTax") : undefined)
43
?.TradeTax ?? 10 // When trading in Maroo's Bazaar, a 10% "Hub Tax" applies.
44
});
59
45
res.json({
60
PendingTrades: [exportTrade(pendingTrades[tradeId], us)]
46
PendingTrades: [exportTrade(trade, us)]
61
47
});
62
} else if (op == "2") {
48
} else if (op == TradingOperation.CounterOffer) {
63
49
const buddyId = req.query.buddyId as string;
64
const tradeId = getTradeId(accountId, buddyId);
65
if (parseInt(req.query.revision as string) != pendingTrades[tradeId].revision) {
50
const offer = getJSONfromString<ITradeOffer>(String(req.body));
51
const us = getSide(accountId, buddyId);
52
const trade = (await PendingTrade.findOneAndUpdate(
53
getTradeFilter(accountId, buddyId),
54
{
55
$set: {
56
[`${us}Offer`]: offer
57
},
58
$inc: {
59
revision: 1
60
}
61
},
62
{ returnDocument: "after" }
63
))!;
64
res.json({
65
PendingTrades: [exportTrade(trade, us)]
66
});
67
} else if (op == TradingOperation.AcceptTrade) {
68
const buddyId = req.query.buddyId as string;
69
const trade = (await getPendingTrade(accountId, buddyId))!;
70
if (parseInt(req.query.revision as string) != trade.revision) {
66
71
throw new Error(`can't accept trade because it was unexpectedly revised`);
67
72
}
68
const us = accountId == pendingTrades[tradeId].trader ? "trader" : "tradee";
69
pendingTrades[tradeId][`${us}Accepted`] = true;
70
const them = us == "trader" ? "tradee" : "trader";
71
if (pendingTrades[tradeId][`${them}Accepted`]) {
72
const [traderInventory, tradeeInventory] = await Promise.all([
73
getInventory(pendingTrades[tradeId].trader),
74
getInventory(pendingTrades[tradeId].tradee)
75
]);
73
const us = trade.a.equals(accountId) ? "a" : "b";
74
trade[`${us}Accepted`] = true;
75
const them = us == "a" ? "b" : "a";
76
if (trade[`${them}Accepted`]) {
77
const [aInventory, bInventory] = await Promise.all([getInventory(trade.a), getInventory(trade.b)]);
76
78
77
if (!traderInventory.tradesDontTouchInventory) {
78
applyOfferToInventory(traderInventory, pendingTrades[tradeId].traderOffer, -1); // Traders gives up their items
79
applyOfferToInventory(traderInventory, pendingTrades[tradeId].tradeeOffer, +1); // to get what tradee offered
80
// and pays tax for it
81
await chargeTax(
82
traderInventory,
83
pendingTrades[tradeId].tradeeOffer,
84
pendingTrades[tradeId].clanTax,
85
req.query.guildId as string | undefined
86
);
87
if (!traderInventory.infiniteTrades) {
88
traderInventory.TradesRemaining -= 1;
79
if (!aInventory.tradesDontTouchInventory) {
80
applyOfferToInventory(aInventory, trade.aOffer, -1); // A gives up their items
81
applyOfferToInventory(aInventory, trade.bOffer, +1); // to get what B offered
82
await chargeTax(aInventory, trade.bOffer, trade.clanTax, req.query.guildId as string | undefined); // and pays tax for it
83
if (!aInventory.infiniteTrades) {
84
aInventory.TradesRemaining -= 1;
89
85
}
90
86
}
91
if (!tradeeInventory.tradesDontTouchInventory) {
92
applyOfferToInventory(tradeeInventory, pendingTrades[tradeId].tradeeOffer, -1); // Conversely, tradee gives up their items
93
applyOfferToInventory(tradeeInventory, pendingTrades[tradeId].traderOffer, +1); // to get what trader offered
94
// and pays tax for it
95
await chargeTax(
96
tradeeInventory,
97
pendingTrades[tradeId].traderOffer,
98
pendingTrades[tradeId].clanTax,
99
req.query.guildId as string | undefined
100
);
101
if (!tradeeInventory.infiniteTrades) {
102
tradeeInventory.TradesRemaining -= 1;
87
if (!bInventory.tradesDontTouchInventory) {
88
applyOfferToInventory(bInventory, trade.bOffer, -1); // Conversely, B gives up their items
89
applyOfferToInventory(bInventory, trade.aOffer, +1); // to get what A offered
90
await chargeTax(bInventory, trade.aOffer, trade.clanTax, req.query.guildId as string | undefined); // and pays tax for it
91
if (!bInventory.infiniteTrades) {
92
bInventory.TradesRemaining -= 1;
103
93
}
104
94
}
105
95
106
await Promise.all([traderInventory.save(), tradeeInventory.save()]);
107
delete pendingTrades[tradeId];
96
await Promise.all([aInventory.save(), bInventory.save(), trade.deleteOne()]);
108
97
res.send(
109
98
account.BuildLabel && version_compare(account.BuildLabel, gameToBuildVersion["40.0.0"]) < 0 ? "9" : "12"
110
99
).end();
111
100
} else {
101
await trade.save();
112
102
res.send(
113
103
account.BuildLabel && version_compare(account.BuildLabel, gameToBuildVersion["40.0.0"]) < 0 ? "8" : "11"
114
104
).end();
115
105
}
116
} else if (op == "3") {
106
} else if (op == TradingOperation.CancelTrade) {
117
107
const buddyId = req.query.buddyId as string;
118
const tradeId = getTradeId(accountId, buddyId);
119
delete pendingTrades[tradeId];
120
res.json({
121
pendingTrades: []
122
});
123
} else if (op == "4") {
124
const tradeId = getTradeId(accountId, req.query.buddyId as string);
125
const us = accountId == pendingTrades[tradeId].trader ? "trader" : "tradee";
108
await PendingTrade.deleteOne(getTradeFilter(accountId, buddyId));
109
res.end();
110
} else if (op == TradingOperation.RefreshTradeInfo) {
111
const buddyId = req.query.buddyId as string;
112
const trade = (await getPendingTrade(accountId, buddyId))!;
113
const us = trade.a.equals(accountId) ? "a" : "b";
126
114
res.json({
127
PendingTrades: [exportTrade(pendingTrades[tradeId], us)]
115
PendingTrades: [exportTrade(trade, us)]
128
116
});
129
} else if (op == "5") {
117
} else if (op == TradingOperation.SetClanTax) {
130
118
const inventory = await getInventory(accountId, "GuildId LevelKeys");
131
119
const guild = await getGuildForRequestEx(req, inventory);
132
120
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Treasurer))) {
@@ -136,25 +124,27 @@ export const tradingController: RequestHandler = async (req, res) => {
136
124
guild.TradeTax = parseInt(req.query.tax as string);
137
125
await guild.save();
138
126
res.send(guild.TradeTax).end();
139
} else if (op == "6") {
127
} else if (op == TradingOperation.SetReady) {
140
128
const buddyId = req.query.buddyId as string;
141
const tradeId = getTradeId(accountId, buddyId);
142
const us = accountId == pendingTrades[tradeId].trader ? "trader" : "tradee";
143
if (parseInt(req.query.revision as string) == pendingTrades[tradeId].revision) {
144
pendingTrades[tradeId][`${us}Ready`] = true;
129
const us = getSide(accountId, buddyId);
130
const trade = (await getPendingTrade(accountId, buddyId))!;
131
if (trade.revision == parseInt(req.query.revision as string)) {
132
trade[`${us}Ready`] = true;
133
await trade.save();
145
134
}
146
135
res.json({
147
PendingTrades: [exportTrade(pendingTrades[tradeId], us)]
136
PendingTrades: [exportTrade(trade, us)]
148
137
});
149
} else if (op == "7") {
138
} else if (op == TradingOperation.UnsetReady) {
150
139
const buddyId = req.query.buddyId as string;
151
const tradeId = getTradeId(accountId, buddyId);
152
const us = accountId == pendingTrades[tradeId].trader ? "trader" : "tradee";
153
pendingTrades[tradeId][`${us}Ready`] = false;
154
pendingTrades[tradeId].tradeeAccepted = false;
155
pendingTrades[tradeId].traderAccepted = false;
140
const us = getSide(accountId, buddyId);
141
const trade = (await PendingTrade.findOneAndUpdate(
142
getTradeFilter(accountId, buddyId),
143
{ [`${us}Ready`]: false, aAccepted: false, bAccepted: false },
144
{ returnDocument: "after" }
145
))!;
156
146
res.json({
157
PendingTrades: [exportTrade(pendingTrades[tradeId], us)]
147
PendingTrades: [exportTrade(trade, us)]
158
148
});
159
149
} else {
160
150
if (req.body) {
@@ -164,37 +154,31 @@ export const tradingController: RequestHandler = async (req, res) => {
164
154
}
165
155
};
166
156
167
interface IPendingTradeMem {
168
trader: string;
169
tradee: string;
170
traderOffer: ITradeOffer;
171
tradeeOffer: ITradeOffer;
172
traderReady: boolean;
173
tradeeReady: boolean;
174
traderAccepted: boolean;
175
tradeeAccepted: boolean;
176
revision: number;
177
clanTax: number;
157
enum TradingOperation {
158
InitialOffer = "0",
159
CounterOffer = "1",
160
AcceptTrade = "2",
161
CancelTrade = "3",
162
RefreshTradeInfo = "4",
163
SetClanTax = "5",
164
SetReady = "6",
165
UnsetReady = "7",
166
NeverSayNever = "01189998819991197253"
178
167
}
179
168
180
const pendingTrades: Record<string, IPendingTradeMem> = {};
169
const getSide = (accountId: string, buddyId: string): "a" | "b" => {
170
return accountId.localeCompare(buddyId) < 0 ? "a" : "b";
171
};
181
172
182
const getTradeId = (accountId: string, buddyId: string): string => {
183
return accountId.localeCompare(buddyId) < 0 ? accountId + buddyId : buddyId + accountId;
173
const getTradeFilter = (accountId: string, buddyId: string): QueryFilter<IPendingTradeDatabase> => {
174
return accountId.localeCompare(buddyId) < 0 ? { a: accountId, b: buddyId } : { a: buddyId, b: accountId };
184
175
};
185
176
186
const exportTrade = (mem: IPendingTradeMem, us: "trader" | "tradee"): IPendingTrade => {
187
const them = us == "trader" ? "tradee" : "trader";
188
return {
189
ItemId: { $oid: mem[them] },
190
Giving: mem[`${us}Offer`],
191
Getting: mem[`${them}Offer`],
192
SelfReady: mem[`${us}Ready`],
193
BuddyReady: mem[`${them}Ready`],
194
State: mem[`${us}Accepted`] ? (mem[`${them}Accepted`] ? 4 : 3) : 2,
195
Revision: mem.revision,
196
ClanTax: mem.clanTax
197
};
177
const getPendingTrade = (
178
accountId: string,
179
buddyId: string
180
): Promise<HydratedDocument<IPendingTradeDatabase> | null> => {
181
return PendingTrade.findOne(getTradeFilter(accountId, buddyId));
198
182
};
199
183
200
184
const applyOfferToInventory = (inventory: TInventoryDatabaseDocument, offer: ITradeOffer, factor: 1 | -1): void => {
@@ -14,6 +14,7 @@ import { Friendship } from "../../models/friendModel.ts";
14
14
import { sendWsBroadcastTo } from "../../services/wsService.ts";
15
15
import { config } from "../../services/configService.ts";
16
16
import { saveConfig } from "../../services/configWriterService.ts";
17
import { PendingTrade } from "../../models/tradingModel.ts";
17
18
18
19
export const deleteAccountController: RequestHandler = async (req, res) => {
19
20
const account = await getAccountForRequest(req);
@@ -44,6 +45,8 @@ export const deleteAccountController: RequestHandler = async (req, res) => {
44
45
Inventory.deleteOne({ accountOwnerId: account._id }),
45
46
Leaderboard.deleteMany({ ownerId: account._id }),
46
47
Loadout.deleteOne({ loadoutOwnerId: account._id }),
48
PendingTrade.deleteMany({ a: account._id }),
49
PendingTrade.deleteMany({ b: account._id }),
47
50
PersonalRooms.deleteOne({ personalRoomsOwnerId: account._id }),
48
51
Ship.deleteMany({ ShipOwnerId: account._id }),
49
52
Stats.deleteOne({ accountOwnerId: account._id })
@@ -1671,8 +1671,6 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
1671
1671
TradesRemaining: { type: Number, default: 0 },
1672
1672
//How many gifts do you have left
1673
1673
GiftsRemaining: { type: Number, default: 8 },
1674
//Current trade info Giving or Getting items
1675
//PendingTrades: [Schema.Types.Mixed],
1676
1674
1677
1675
//Syndicate currently being pledged to.
1678
1676
SupportedSyndicate: String,
@@ -0,0 +1,21 @@
1
import { model, Schema, Types } from "mongoose";
2
import type { IPendingTradeDatabase, ITradeOffer } from "../types/tradingTypes.ts";
3
4
const defaultTradeOffer: ITradeOffer = { _SlotOrderInfo: ["", "", "", "", "", ""] };
5
6
const pendingTradeSchema = new Schema<IPendingTradeDatabase>({
7
a: { type: Types.ObjectId, required: true },
8
b: { type: Types.ObjectId, required: true },
9
aOffer: { type: Schema.Types.Mixed, default: defaultTradeOffer },
10
bOffer: { type: Schema.Types.Mixed, default: defaultTradeOffer },
11
aReady: { type: Boolean, default: false },
12
bReady: { type: Boolean, default: false },
13
aAccepted: { type: Boolean, default: false },
14
bAccepted: { type: Boolean, default: false },
15
revision: { type: Number, default: 0 },
16
clanTax: { type: Number, required: true }
17
});
18
19
pendingTradeSchema.index({ a: 1, b: 1 }, { unique: true });
20
21
export const PendingTrade = model<IPendingTradeDatabase>("PendingTrade", pendingTradeSchema);
@@ -0,0 +1,16 @@
1
import { toOid } from "../helpers/inventoryHelpers.ts";
2
import type { IPendingTradeClient, IPendingTradeDatabase } from "../types/tradingTypes.ts";
3
4
export const exportTrade = (trade: IPendingTradeDatabase, us: "a" | "b"): IPendingTradeClient => {
5
const them = us == "a" ? "b" : "a";
6
return {
7
ItemId: toOid(trade[them]),
8
Giving: trade[`${us}Offer`],
9
Getting: trade[`${them}Offer`],
10
SelfReady: trade[`${us}Ready`],
11
BuddyReady: trade[`${them}Ready`],
12
State: trade[`${us}Accepted`] ? (trade[`${them}Accepted`] ? 4 : 3) : 2,
13
Revision: trade.revision,
14
ClanTax: trade.clanTax
15
};
16
};
@@ -28,6 +28,7 @@ import type {
28
28
import type { ILoadoutConfigClientLegacy, ILoadOutPresets } from "../saveLoadoutTypes.ts";
29
29
import type { CalendarSeasonType } from "../worldStateTypes.ts";
30
30
import type { SlotNames } from "../purchaseTypes.ts";
31
import type { IPendingTradeClient } from "../tradingTypes.ts";
31
32
32
33
export type InventoryDatabaseEquipment = {
33
34
[_ in TEquipmentKey]: IEquipmentDatabase[];
@@ -154,6 +155,7 @@ export interface IInventoryDatabase
154
155
| "ChallengeInstanceStates"
155
156
| "PeriodicMissionCompletions"
156
157
| "FusionTreasures"
158
| "PendingTrades"
157
159
| TEquipmentKey
158
160
>,
159
161
InventoryDatabaseEquipment,
@@ -432,7 +434,7 @@ export interface IInventoryClient
432
434
KubrowPetEggs?: IKubrowPetEgg[];
433
435
LoreFragmentScans: ILoreFragmentScan[];
434
436
EquippedEmotes: string[];
435
//PendingTrades: IPendingTrade[];
437
PendingTrades: IPendingTradeClient[];
436
438
Boosters: IBooster[];
437
439
ActiveDojoColorResearch: string;
438
440
//SentientSpawnChanceBoosters: ISentientSpawnChanceBoosters;
@@ -1017,29 +1019,6 @@ export interface IAccolades {
1017
1019
Heirloom?: boolean;
1018
1020
}
1019
1021
1020
export interface IPendingTrade {
1021
State: number;
1022
SelfReady: boolean;
1023
BuddyReady: boolean;
1024
Giving?: ITradeOffer;
1025
Revision: number;
1026
Getting: ITradeOffer;
1027
ItemId: IOid;
1028
ClanTax?: number;
1029
}
1030
1031
export interface ITradeOffer {
1032
RandomUpgrades?: IUpgradeClient[];
1033
Upgrades?: IUpgradeClient[];
1034
RawUpgrades?: IRawUpgrade[];
1035
MiscItems?: IMiscItem[];
1036
Recipes?: ITypeCount[];
1037
FusionTreasures?: IFusionTreasure[];
1038
NemesisHistory?: INemesisClient;
1039
PremiumCredits?: number;
1040
_SlotOrderInfo: string[];
1041
}
1042
1043
1022
export interface IInnateDamageFingerprint {
1044
1023
compat: string;
1045
1024
buffs: IFingerprintStat[];
@@ -0,0 +1,40 @@
1
import type { Types } from "mongoose";
2
import type { IOid, ITypeCount } from "./commonTypes.ts";
3
import type { IFusionTreasure, INemesisClient, IRawUpgrade, IUpgradeClient } from "./inventoryTypes/inventoryTypes.ts";
4
5
export interface ITradeOffer {
6
RandomUpgrades?: IUpgradeClient[];
7
Upgrades?: IUpgradeClient[];
8
RawUpgrades?: IRawUpgrade[];
9
MiscItems?: ITypeCount[];
10
Recipes?: ITypeCount[];
11
FusionTreasures?: IFusionTreasure[];
12
NemesisHistory?: INemesisClient;
13
PremiumCredits?: number;
14
_SlotOrderInfo: string[];
15
}
16
17
export interface IPendingTradeClient {
18
State: number;
19
SelfReady: boolean;
20
BuddyReady: boolean;
21
Giving?: ITradeOffer;
22
Revision: number;
23
Getting: ITradeOffer;
24
ItemId: IOid;
25
ClanTax?: number;
26
}
27
28
export interface IPendingTradeDatabase {
29
a: Types.ObjectId;
30
b: Types.ObjectId;
31
aOffer: ITradeOffer;
32
bOffer: ITradeOffer;
33
aReady: boolean;
34
bReady: boolean;
35
aAccepted: boolean;
36
bAccepted: boolean;
37
revision: number;
38
clanTax: number;
39
_id: Types.ObjectId;
40
}