@riocrypto/common-server 1.0.2843 → 1.0.2845

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.
@@ -91,6 +91,8 @@ declare class BitsoClient {
91
91
  createCryptoWithdrawal(amount: number, crypto: Crypto, address: string): Promise<Object>;
92
92
  getDepositByHash(hash: string): Promise<Object>;
93
93
  getDepositByConcepto(concepto: string): Promise<Object>;
94
+ listFundings(limit?: number): Promise<any[]>;
95
+ listWithdrawals(limit?: number): Promise<any[]>;
94
96
  signRequest(requestConfig: AxiosRequestConfig): AxiosRequestConfig;
95
97
  }
96
98
  export declare const buildBitsoClient: () => Promise<BitsoClient>;
@@ -482,6 +482,44 @@ class BitsoClient {
482
482
  return data.payload.find((deposit) => deposit.details.concepto === concepto);
483
483
  });
484
484
  }
485
+ // List recent fundings (deposits) so callers can import exchange-native
486
+ // deposits done directly on Bitso. Bitso caps `limit` at 100 per page.
487
+ listFundings(limit = 100) {
488
+ return __awaiter(this, void 0, void 0, function* () {
489
+ const requestConfig = {
490
+ method: "GET",
491
+ url: `${this.baseUrl}/api/v3/fundings/?limit=${limit}`,
492
+ headers: {
493
+ "Content-Type": "application/json",
494
+ },
495
+ };
496
+ const signedRequestConfig = this.signRequest(requestConfig);
497
+ const { data } = yield this.axiosClient(signedRequestConfig);
498
+ if (!data.success) {
499
+ throw new Error("Error listing Bitso fundings");
500
+ }
501
+ return data.payload;
502
+ });
503
+ }
504
+ // List recent withdrawals so callers can import exchange-native withdrawals
505
+ // done directly on Bitso. Bitso caps `limit` at 100 per page.
506
+ listWithdrawals(limit = 100) {
507
+ return __awaiter(this, void 0, void 0, function* () {
508
+ const requestConfig = {
509
+ method: "GET",
510
+ url: `${this.baseUrl}/api/v3/withdrawals/?limit=${limit}`,
511
+ headers: {
512
+ "Content-Type": "application/json",
513
+ },
514
+ };
515
+ const signedRequestConfig = this.signRequest(requestConfig);
516
+ const { data } = yield this.axiosClient(signedRequestConfig);
517
+ if (!data.success) {
518
+ throw new Error("Error listing Bitso withdrawals");
519
+ }
520
+ return data.payload;
521
+ });
522
+ }
485
523
  signRequest(requestConfig) {
486
524
  var _a;
487
525
  const url = new URL(requestConfig.url || "");
@@ -57,6 +57,9 @@ class CoincoverClient {
57
57
  [common_1.Crypto.SOL]: "solana",
58
58
  [common_1.Crypto.XLM]: "stellar",
59
59
  [common_1.Crypto.POL]: "matic-network",
60
+ // Synthetic catch-all for unmapped assets seen on imported treasury
61
+ // movements; it has no CoinGecko id and is never priced.
62
+ [common_1.Crypto.Other]: "",
60
63
  };
61
64
  this.axiosClient = (0, axios_with_logging_1.buildAxiosWithLogging)();
62
65
  }
@@ -51,6 +51,12 @@ declare class FireblocksClient {
51
51
  getSingularExchangeCryptoBalance(id: string, usdType: "usdc" | "usdt"): Promise<number>;
52
52
  getExchangeFiatBalance(): Promise<number>;
53
53
  getTransactions(vaultId: string, crypto: Crypto, nextPage?: string): Promise<any>;
54
+ listVaultTransactions(params: {
55
+ vaultId: string;
56
+ direction: "incoming" | "outgoing";
57
+ afterMs: number;
58
+ status?: string;
59
+ }): Promise<TransactionResponse[]>;
54
60
  getDepositAddress(vaultId: string, crypto: Crypto): Promise<string>;
55
61
  createVault(userId: string, name: string, type: "transactional", number?: number): Promise<string>;
56
62
  createWallet(vaultId: string, crypto: Crypto): Promise<{
@@ -356,6 +356,45 @@ class FireblocksClient {
356
356
  return transactionData;
357
357
  });
358
358
  }
359
+ // Lists a vault's transactions in one direction (incoming = deposits to the
360
+ // vault, outgoing = withdrawals from the vault) created at/after `afterMs`
361
+ // (unix ms). Pages through all results. Used by the treasury import cron to
362
+ // detect movements performed directly in Fireblocks (not initiated by Rio).
363
+ //
364
+ // Note the SDK paging quirk (fireblocks-sdk-js#268): when a page filter is
365
+ // provided it takes priority over the next-page path, so we pass the filter
366
+ // only on the first request and the path on subsequent requests.
367
+ listVaultTransactions(params) {
368
+ var _a;
369
+ return __awaiter(this, void 0, void 0, function* () {
370
+ const { vaultId, direction, afterMs, status } = params;
371
+ const filter = {
372
+ after: String(Math.floor(afterMs)),
373
+ orderBy: "createdAt",
374
+ limit: 200,
375
+ };
376
+ if (direction === "incoming") {
377
+ filter.destId = vaultId;
378
+ }
379
+ else {
380
+ filter.sourceId = vaultId;
381
+ }
382
+ if (status) {
383
+ filter.status = status;
384
+ }
385
+ const results = [];
386
+ let nextPage;
387
+ let pagesFetched = 0;
388
+ const MAX_PAGES = 20;
389
+ do {
390
+ const response = yield this.fireblocks.getTransactionsWithPageInfo(nextPage ? undefined : filter, nextPage);
391
+ results.push(...(response.transactions || []));
392
+ nextPage = ((_a = response.pageDetails) === null || _a === void 0 ? void 0 : _a.nextPage) || undefined;
393
+ pagesFetched += 1;
394
+ } while (nextPage && pagesFetched < MAX_PAGES);
395
+ return results;
396
+ });
397
+ }
359
398
  getDepositAddress(vaultId, crypto) {
360
399
  return __awaiter(this, void 0, void 0, function* () {
361
400
  let addresses = yield this.fireblocks.getDepositAddresses(vaultId, crypto);
@@ -33,18 +33,31 @@ export interface KrakenWithdrawInfo {
33
33
  amount: string;
34
34
  fee: string;
35
35
  }
36
+ export interface KrakenLedgerEntry {
37
+ refid: string;
38
+ time: number;
39
+ type: string;
40
+ subtype: string;
41
+ aclass: string;
42
+ asset: string;
43
+ amount: string;
44
+ fee: string;
45
+ balance: string;
46
+ }
36
47
  declare class KrakenClient {
37
48
  private apiKey;
38
49
  private env;
39
50
  private axiosClient;
40
51
  private decodedSecret;
41
52
  private lastNonce;
53
+ private requestQueue;
42
54
  private restBaseUrl;
43
55
  constructor(apiKey: string, apiSecret: string, env: RioEnv);
44
56
  private assertFundingAllowed;
45
57
  private nextNonce;
46
58
  private sign;
47
59
  private privatePost;
60
+ private executePrivatePost;
48
61
  getBalance(asset: string): Promise<number>;
49
62
  getAllBalances(assets: string[]): Promise<Record<string, number>>;
50
63
  getDepositMethods(asset: string): Promise<KrakenDepositMethod[]>;
@@ -55,6 +68,11 @@ declare class KrakenClient {
55
68
  getWithdrawStatus(asset: string, method?: string): Promise<KrakenFundingStatus[]>;
56
69
  getWithdrawStatusByRefid(asset: string, refid: string): Promise<KrakenFundingStatus | undefined>;
57
70
  getDepositByTxId(asset: string, txid: string): Promise<KrakenFundingStatus | undefined>;
71
+ getLedgers(params?: {
72
+ type?: string;
73
+ asset?: string;
74
+ start?: number;
75
+ }): Promise<Record<string, KrakenLedgerEntry>>;
58
76
  }
59
77
  export declare const buildKrakenClient: () => Promise<KrakenClient>;
60
78
  export type { KrakenClient };
@@ -35,6 +35,12 @@ class KrakenClient {
35
35
  // Kraken nonces must strictly increase per key. We keep a monotonic floor so
36
36
  // two calls in the same millisecond can't produce a decreasing nonce.
37
37
  this.lastNonce = 0;
38
+ // Serialization queue for private requests. Even with strictly increasing
39
+ // nonces, concurrent private requests can arrive at Kraken out of order,
40
+ // which Kraken rejects as `EAPI:Invalid nonce`. Chaining every private
41
+ // request through this promise guarantees one in-flight request at a time so
42
+ // nonces always arrive in order.
43
+ this.requestQueue = Promise.resolve();
38
44
  this.decodedSecret = Buffer.from(apiSecret, "base64");
39
45
  this.axiosClient = (0, axios_with_logging_1.buildAxiosWithLogging)();
40
46
  this.restBaseUrl = getKrakenRestBaseUrl(env);
@@ -68,6 +74,17 @@ class KrakenClient {
68
74
  .digest("base64");
69
75
  }
70
76
  privatePost(method, params = {}) {
77
+ return __awaiter(this, void 0, void 0, function* () {
78
+ // Queue behind any in-flight private request so nonces reach Kraken in
79
+ // strictly increasing order. The queue must survive individual failures,
80
+ // hence the `.catch` on the stored promise (the caller still sees the
81
+ // original rejection via `run`).
82
+ const run = this.requestQueue.then(() => this.executePrivatePost(method, params));
83
+ this.requestQueue = run.catch(() => undefined);
84
+ return run;
85
+ });
86
+ }
87
+ executePrivatePost(method, params = {}) {
71
88
  return __awaiter(this, void 0, void 0, function* () {
72
89
  const path = `/0/private/${method}`;
73
90
  const nonce = this.nextNonce();
@@ -184,6 +201,23 @@ class KrakenClient {
184
201
  return statuses.find((s) => s.txid === txid);
185
202
  });
186
203
  }
204
+ // ----- Ledgers -----
205
+ // Returns the ledger entries (keyed by ledger id) for the given filters.
206
+ // This is the authoritative source for fiat deposits/withdrawals, which do
207
+ // not appear in DepositStatus/WithdrawStatus. `start` is a unix timestamp in
208
+ // seconds; Kraken returns the most recent entries (max 50 per page) at or
209
+ // after it.
210
+ getLedgers(params = {}) {
211
+ var _a;
212
+ return __awaiter(this, void 0, void 0, function* () {
213
+ const result = yield this.privatePost("Ledgers", {
214
+ type: params.type,
215
+ asset: params.asset,
216
+ start: params.start,
217
+ });
218
+ return (_a = result === null || result === void 0 ? void 0 : result.ledger) !== null && _a !== void 0 ? _a : {};
219
+ });
220
+ }
187
221
  }
188
222
  // Cache a single client per process so the monotonic nonce floor is effective
189
223
  // process-wide; building a fresh client per call would reset it and risk
@@ -1,5 +1,5 @@
1
1
  import mongoose, { HydratedDocument } from "mongoose";
2
- import { InternalTransferType, TreasuryProvider, Fiat, Crypto, InternalTransferOriginStatus, InternalTransferDestinationStatus } from "@riocrypto/common";
2
+ import { InternalTransferType, TreasuryProvider, Fiat, Crypto, InternalTransferOriginStatus, InternalTransferDestinationStatus, OtherProviderMetadata } from "@riocrypto/common";
3
3
  interface InternalTransferAttrs {
4
4
  createdAt: Date;
5
5
  adminId?: string;
@@ -8,6 +8,8 @@ interface InternalTransferAttrs {
8
8
  transferType: InternalTransferType;
9
9
  amount: number;
10
10
  currency: Fiat | Crypto;
11
+ otherCurrencyCode?: string;
12
+ isImported?: boolean;
11
13
  blockchainTxId?: string;
12
14
  reference?: string;
13
15
  SPEITrackingNumber?: string;
@@ -17,14 +19,18 @@ interface InternalTransferAttrs {
17
19
  providerId?: string;
18
20
  amountSent?: number;
19
21
  status: InternalTransferOriginStatus;
22
+ otherStatus?: string;
23
+ otherProviderMetadata?: OtherProviderMetadata;
20
24
  };
21
25
  destination: {
22
26
  provider: TreasuryProvider;
23
27
  providerId?: string;
24
28
  amountReceived?: number;
25
29
  status: InternalTransferDestinationStatus;
30
+ otherStatus?: string;
26
31
  notifyWhenCredited?: boolean;
27
32
  notifyWhenCompleted?: boolean;
33
+ otherProviderMetadata?: OtherProviderMetadata;
28
34
  };
29
35
  }
30
36
  interface InternalTransferDoc extends mongoose.Document {
@@ -34,6 +40,8 @@ interface InternalTransferDoc extends mongoose.Document {
34
40
  liquiditySourcingPlanId?: string;
35
41
  createdAt: Date;
36
42
  transferType: InternalTransferType;
43
+ otherCurrencyCode?: string;
44
+ isImported?: boolean;
37
45
  blockchainTxId?: string;
38
46
  reference?: string;
39
47
  SPEITrackingNumber?: string;
@@ -43,14 +51,18 @@ interface InternalTransferDoc extends mongoose.Document {
43
51
  providerId?: string;
44
52
  amountSent?: number;
45
53
  status: InternalTransferOriginStatus;
54
+ otherStatus?: string;
55
+ otherProviderMetadata?: OtherProviderMetadata;
46
56
  };
47
57
  destination: {
48
58
  provider: TreasuryProvider;
49
59
  providerId?: string;
50
60
  amountReceived?: number;
51
61
  status: InternalTransferDestinationStatus;
62
+ otherStatus?: string;
52
63
  notifyWhenCredited?: boolean;
53
64
  notifyWhenCompleted?: boolean;
65
+ otherProviderMetadata?: OtherProviderMetadata;
54
66
  };
55
67
  amount: number;
56
68
  currency: Fiat | Crypto;
@@ -26,6 +26,13 @@ const buildInternalTransfer = (mongoose) => {
26
26
  enum: Object.values(common_1.InternalTransferType),
27
27
  required: true,
28
28
  },
29
+ otherCurrencyCode: {
30
+ type: String,
31
+ },
32
+ isImported: {
33
+ type: Boolean,
34
+ default: false,
35
+ },
29
36
  blockchainTxId: {
30
37
  type: String,
31
38
  },
@@ -55,6 +62,16 @@ const buildInternalTransfer = (mongoose) => {
55
62
  enum: Object.values(common_1.InternalTransferOriginStatus),
56
63
  required: true,
57
64
  },
65
+ otherStatus: {
66
+ type: String,
67
+ },
68
+ otherProviderMetadata: {
69
+ blockchainAddress: { type: String },
70
+ memo: { type: String },
71
+ bankAccountInfo: { type: String },
72
+ bankName: { type: String },
73
+ label: { type: String },
74
+ },
58
75
  },
59
76
  destination: {
60
77
  provider: {
@@ -73,6 +90,9 @@ const buildInternalTransfer = (mongoose) => {
73
90
  enum: Object.values(common_1.InternalTransferDestinationStatus),
74
91
  required: true,
75
92
  },
93
+ otherStatus: {
94
+ type: String,
95
+ },
76
96
  notifyWhenCredited: {
77
97
  type: Boolean,
78
98
  default: false,
@@ -81,6 +101,13 @@ const buildInternalTransfer = (mongoose) => {
81
101
  type: Boolean,
82
102
  default: false,
83
103
  },
104
+ otherProviderMetadata: {
105
+ blockchainAddress: { type: String },
106
+ memo: { type: String },
107
+ bankAccountInfo: { type: String },
108
+ bankName: { type: String },
109
+ label: { type: String },
110
+ },
84
111
  },
85
112
  amount: {
86
113
  type: Number,
@@ -99,6 +126,16 @@ const buildInternalTransfer = (mongoose) => {
99
126
  },
100
127
  },
101
128
  });
129
+ // Indexes to support dedup lookups when importing exchange-native activity.
130
+ InternalTransferSchema.index({
131
+ "origin.provider": 1,
132
+ "origin.providerId": 1,
133
+ });
134
+ InternalTransferSchema.index({
135
+ "destination.provider": 1,
136
+ "destination.providerId": 1,
137
+ });
138
+ InternalTransferSchema.index({ blockchainTxId: 1 });
102
139
  InternalTransferSchema.statics.build = (attrs) => {
103
140
  return new InternalTransfer(attrs);
104
141
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riocrypto/common-server",
3
- "version": "1.0.2843",
3
+ "version": "1.0.2845",
4
4
  "description": "",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
@@ -28,7 +28,7 @@
28
28
  "@google-cloud/secret-manager": "^5.6.0",
29
29
  "@google-cloud/storage": "^7.19.0",
30
30
  "@hyperdx/node-opentelemetry": "^0.10.3",
31
- "@riocrypto/common": "1.0.2647",
31
+ "@riocrypto/common": "1.0.2650",
32
32
  "@slack/web-api": "^7.15.0",
33
33
  "@types/express": "^4.17.25",
34
34
  "axios": "1.16.0",