@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.
- package/build/clients/bitso-client.d.ts +2 -0
- package/build/clients/bitso-client.js +38 -0
- package/build/clients/coincover-client.js +3 -0
- package/build/clients/fireblocks-client.d.ts +6 -0
- package/build/clients/fireblocks-client.js +39 -0
- package/build/clients/kraken-client.d.ts +18 -0
- package/build/clients/kraken-client.js +34 -0
- package/build/models/internal-transfer.d.ts +13 -1
- package/build/models/internal-transfer.js +37 -0
- package/package.json +2 -2
|
@@ -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.
|
|
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.
|
|
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",
|