@riocrypto/common-server 1.0.2766 → 1.0.2768

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -241,14 +241,16 @@ declare class ClusterClient {
241
241
  quantity: number;
242
242
  valueDate?: string;
243
243
  fiat?: string;
244
+ tradeKey: string;
244
245
  }): Promise<StonexFXTrade>;
245
- takeStonexQuote(tradeId: string, side: "buy" | "sell"): Promise<StonexFXTrade>;
246
+ takeStonexQuote(tradeId: string, side: "buy" | "sell", tradeKey: string): Promise<StonexFXTrade>;
246
247
  cancelStonexTrade(tradeId: string): Promise<StonexFXTrade>;
247
248
  updateStonexOrder(tradeId: string, params: {
248
249
  quantity?: number;
249
250
  price?: number;
250
251
  stopPx?: number;
251
252
  timeInForce?: string;
253
+ tradeKey: string;
252
254
  }): Promise<StonexFXTrade>;
253
255
  placeStonexDirectOrder(params: {
254
256
  symbol: string;
@@ -261,8 +263,10 @@ declare class ClusterClient {
261
263
  stopPx?: number;
262
264
  valueDate?: string;
263
265
  execInst?: string;
266
+ tradeKey: string;
264
267
  }): Promise<StonexFXTrade>;
265
268
  getStonexTrade(tradeId: string): Promise<StonexFXTrade>;
269
+ getStonexTradeByKey(tradeKey: string): Promise<StonexFXTrade | null>;
266
270
  getStonexTrades(offset: number, limit: number): Promise<{
267
271
  data: StonexFXTrade[];
268
272
  hasMore: boolean;
@@ -794,13 +794,24 @@ class ClusterClient {
794
794
  // ─── StoneX FIX ───────────────────────────────────────────────────
795
795
  createStonexTrade(data) {
796
796
  return __awaiter(this, void 0, void 0, function* () {
797
- const response = yield this.axios.post(`${this.baseUrl}/api/stonex/create-quote-stream`, data, { headers: { "x-cluster-api-key": this.clusterApiKey } });
797
+ const { tradeKey } = data;
798
+ const response = yield this.axios.post(`${this.baseUrl}/api/stonex/create-quote-stream`, data, {
799
+ headers: {
800
+ "x-cluster-api-key": this.clusterApiKey,
801
+ "X-Trade-Key": tradeKey,
802
+ },
803
+ });
798
804
  return response.data;
799
805
  });
800
806
  }
801
- takeStonexQuote(tradeId, side) {
807
+ takeStonexQuote(tradeId, side, tradeKey) {
802
808
  return __awaiter(this, void 0, void 0, function* () {
803
- const response = yield this.axios.post(`${this.baseUrl}/api/stonex/take-quote/${tradeId}`, { side }, { headers: { "x-cluster-api-key": this.clusterApiKey } });
809
+ const response = yield this.axios.post(`${this.baseUrl}/api/stonex/take-quote/${tradeId}`, { side, tradeKey }, {
810
+ headers: {
811
+ "x-cluster-api-key": this.clusterApiKey,
812
+ "X-Trade-Key": tradeKey,
813
+ },
814
+ });
804
815
  return response.data;
805
816
  });
806
817
  }
@@ -812,13 +823,25 @@ class ClusterClient {
812
823
  }
813
824
  updateStonexOrder(tradeId, params) {
814
825
  return __awaiter(this, void 0, void 0, function* () {
815
- const response = yield this.axios.post(`${this.baseUrl}/api/stonex/update-order/${tradeId}`, params, { headers: { "x-cluster-api-key": this.clusterApiKey } });
826
+ const { tradeKey } = params;
827
+ const response = yield this.axios.post(`${this.baseUrl}/api/stonex/update-order/${tradeId}`, params, {
828
+ headers: {
829
+ "x-cluster-api-key": this.clusterApiKey,
830
+ "X-Trade-Key": tradeKey,
831
+ },
832
+ });
816
833
  return response.data;
817
834
  });
818
835
  }
819
836
  placeStonexDirectOrder(params) {
820
837
  return __awaiter(this, void 0, void 0, function* () {
821
- const response = yield this.axios.post(`${this.baseUrl}/api/stonex/place-order`, params, { headers: { "x-cluster-api-key": this.clusterApiKey } });
838
+ const { tradeKey } = params;
839
+ const response = yield this.axios.post(`${this.baseUrl}/api/stonex/place-order`, params, {
840
+ headers: {
841
+ "x-cluster-api-key": this.clusterApiKey,
842
+ "X-Trade-Key": tradeKey,
843
+ },
844
+ });
822
845
  return response.data;
823
846
  });
824
847
  }
@@ -828,6 +851,29 @@ class ClusterClient {
828
851
  return response.data;
829
852
  });
830
853
  }
854
+ // Reconcile helper: look up a Stonex trade by caller-supplied trade key.
855
+ // Returns null when the key has no matching trade (the server either
856
+ // never received the request or failed before persisting). External-trading
857
+ // calls this before stamping a local trade as Failed so a lost HTTP
858
+ // response does not desync the two systems.
859
+ getStonexTradeByKey(tradeKey) {
860
+ var _a;
861
+ return __awaiter(this, void 0, void 0, function* () {
862
+ try {
863
+ const response = yield this.axios.get(`${this.baseUrl}/api/stonex/trades/by-trade-key/${encodeURIComponent(tradeKey)}`, { headers: { "x-cluster-api-key": this.clusterApiKey } });
864
+ return response.data;
865
+ }
866
+ catch (err) {
867
+ if (typeof err === "object" &&
868
+ err !== null &&
869
+ "response" in err &&
870
+ ((_a = err.response) === null || _a === void 0 ? void 0 : _a.status) === 404) {
871
+ return null;
872
+ }
873
+ throw err;
874
+ }
875
+ });
876
+ }
831
877
  getStonexTrades(offset, limit) {
832
878
  return __awaiter(this, void 0, void 0, function* () {
833
879
  const response = yield this.axios.get(`${this.baseUrl}/api/stonex/trades?offset=${offset}&limit=${limit}`, { headers: { "x-cluster-api-key": this.clusterApiKey } });
@@ -3,6 +3,7 @@ import { Mongoose, Model, Document, HydratedDocument } from "mongoose";
3
3
  interface ExternalTradeAttrs {
4
4
  createdAt: Date;
5
5
  providerOrderId?: string;
6
+ tradeKey?: string;
6
7
  type: ExternalTradeType;
7
8
  side: Side;
8
9
  provider: ExternalTradingProvider;
@@ -31,6 +32,7 @@ interface ExternalTradeAttrs {
31
32
  ordType?: string;
32
33
  amountReceived?: number;
33
34
  stopPx?: number;
35
+ takeQuoteTradeKey?: string;
34
36
  };
35
37
  };
36
38
  externalTradingAlgorithmId?: string;
@@ -40,6 +42,7 @@ interface ExternalTradeAttrs {
40
42
  interface ExternalTradeDoc extends Document {
41
43
  createdAt: Date;
42
44
  providerOrderId?: string;
45
+ tradeKey?: string;
43
46
  type: ExternalTradeType;
44
47
  side: Side;
45
48
  amountType: ExternalTradeAmountType;
@@ -69,6 +72,7 @@ interface ExternalTradeDoc extends Document {
69
72
  ordType?: string;
70
73
  amountReceived?: number;
71
74
  stopPx?: number;
75
+ takeQuoteTradeKey?: string;
72
76
  };
73
77
  };
74
78
  externalTradingAlgorithmId?: string;
@@ -14,6 +14,9 @@ const buildExternalTrade = (mongoose) => {
14
14
  providerOrderId: {
15
15
  type: String,
16
16
  },
17
+ tradeKey: {
18
+ type: String,
19
+ },
17
20
  requestedAmountType: {
18
21
  type: String,
19
22
  required: true,
@@ -87,6 +90,18 @@ const buildExternalTrade = (mongoose) => {
87
90
  },
88
91
  },
89
92
  });
93
+ // Partial unique compound index: guarantees at most one ExternalTrade per
94
+ // (provider, tradeKey) pair. Legacy rows without tradeKey are
95
+ // excluded by the partial filter, so no backfill is required.
96
+ ExternalTradeSchema.index({ provider: 1, tradeKey: 1 }, {
97
+ unique: true,
98
+ partialFilterExpression: { tradeKey: { $type: "string" } },
99
+ });
100
+ // Defense-in-depth: at most one ExternalTrade per (provider, providerOrderId).
101
+ ExternalTradeSchema.index({ provider: 1, providerOrderId: 1 }, {
102
+ unique: true,
103
+ partialFilterExpression: { providerOrderId: { $type: "string" } },
104
+ });
90
105
  ExternalTradeSchema.statics.build = (attrs) => {
91
106
  return new ExternalTrade(attrs);
92
107
  };
@@ -30,6 +30,10 @@ interface StonexFXTradeAttrs {
30
30
  expireTime?: string;
31
31
  leavesQty?: number;
32
32
  avgPx?: number;
33
+ tradeKey?: string;
34
+ takeQuoteTradeKey?: string;
35
+ fixSubmitted?: boolean;
36
+ submitAttempts?: number;
33
37
  }
34
38
  interface StonexFXTradeDoc extends Document {
35
39
  id: string;
@@ -62,6 +66,10 @@ interface StonexFXTradeDoc extends Document {
62
66
  expireTime?: string;
63
67
  leavesQty?: number;
64
68
  avgPx?: number;
69
+ tradeKey?: string;
70
+ takeQuoteTradeKey?: string;
71
+ fixSubmitted?: boolean;
72
+ submitAttempts?: number;
65
73
  }
66
74
  interface StonexFXTradeModel extends Model<StonexFXTradeDoc> {
67
75
  build(attrs: StonexFXTradeAttrs): StonexFXTradeDoc;
@@ -99,6 +99,20 @@ const buildStonexFXTrade = (mongoose) => {
99
99
  avgPx: {
100
100
  type: Number,
101
101
  },
102
+ tradeKey: {
103
+ type: String,
104
+ },
105
+ takeQuoteTradeKey: {
106
+ type: String,
107
+ },
108
+ fixSubmitted: {
109
+ type: Boolean,
110
+ default: false,
111
+ },
112
+ submitAttempts: {
113
+ type: Number,
114
+ default: 0,
115
+ },
102
116
  metadata: {
103
117
  type: Map,
104
118
  of: mongoose.Schema.Types.Mixed,
@@ -112,6 +126,19 @@ const buildStonexFXTrade = (mongoose) => {
112
126
  },
113
127
  },
114
128
  });
129
+ // Partial unique index: only docs with a string tradeKey are indexed,
130
+ // so existing docs (no field) don't collide. Guarantees at most one StonexFXTrade
131
+ // per caller-supplied trade key.
132
+ StonexFXTradeSchema.index({ tradeKey: 1 }, {
133
+ unique: true,
134
+ partialFilterExpression: { tradeKey: { $type: "string" } },
135
+ });
136
+ StonexFXTradeSchema.index({ takeQuoteTradeKey: 1 }, {
137
+ unique: true,
138
+ partialFilterExpression: {
139
+ takeQuoteTradeKey: { $type: "string" },
140
+ },
141
+ });
115
142
  StonexFXTradeSchema.statics.build = (attrs) => {
116
143
  return new StonexFXTrade(attrs);
117
144
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riocrypto/common-server",
3
- "version": "1.0.2766",
3
+ "version": "1.0.2768",
4
4
  "description": "",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
@@ -24,7 +24,7 @@
24
24
  "@google-cloud/secret-manager": "^5.6.0",
25
25
  "@google-cloud/storage": "^7.19.0",
26
26
  "@hyperdx/node-opentelemetry": "^0.10.3",
27
- "@riocrypto/common": "1.0.2563",
27
+ "@riocrypto/common": "1.0.2565",
28
28
  "@slack/web-api": "^7.15.0",
29
29
  "@types/express": "^4.17.25",
30
30
  "axios": "1.13.6",