@riocrypto/common-server 1.0.2842 → 1.0.2843

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.
@@ -180,7 +180,7 @@ declare class ClusterClient {
180
180
  stopExternalTradingAlgorithm(id: string): Promise<ExternalTradingAlgorithm>;
181
181
  getExternalTradingAlgorithm(id: string): Promise<ExternalTradingAlgorithm>;
182
182
  getExternalTrade(id: string): Promise<ExternalTrade>;
183
- placeExternalTrade({ provider, type, originCurrency, destinationCurrency, requestedOriginAmount, requestedDestinationAmount, limitPrice, twoWaySettlementDateOffset, arbitrageSessionId, fxTradeId, timeInForce, valueDate, }: {
183
+ placeExternalTrade({ provider, type, originCurrency, destinationCurrency, requestedOriginAmount, requestedDestinationAmount, limitPrice, twoWaySettlementDateOffset, arbitrageSessionId, fxTradeId, internalTradeId, timeInForce, valueDate, }: {
184
184
  provider: ExternalTradingProvider;
185
185
  type: ExternalTradeType;
186
186
  originCurrency: Crypto | Fiat;
@@ -191,6 +191,7 @@ declare class ClusterClient {
191
191
  twoWaySettlementDateOffset?: number;
192
192
  arbitrageSessionId?: string;
193
193
  fxTradeId?: string;
194
+ internalTradeId?: string;
194
195
  timeInForce?: "day" | "gtc" | "ioc" | "fok";
195
196
  valueDate?: string;
196
197
  }): Promise<ExternalTrade>;
@@ -654,7 +654,7 @@ class ClusterClient {
654
654
  return response.data;
655
655
  });
656
656
  }
657
- placeExternalTrade({ provider, type, originCurrency, destinationCurrency, requestedOriginAmount, requestedDestinationAmount, limitPrice, twoWaySettlementDateOffset, arbitrageSessionId, fxTradeId, timeInForce, valueDate, }) {
657
+ placeExternalTrade({ provider, type, originCurrency, destinationCurrency, requestedOriginAmount, requestedDestinationAmount, limitPrice, twoWaySettlementDateOffset, arbitrageSessionId, fxTradeId, internalTradeId, timeInForce, valueDate, }) {
658
658
  return __awaiter(this, void 0, void 0, function* () {
659
659
  const response = yield this.axios.post(`${this.baseUrl}/api/trading/external/trades`, {
660
660
  provider,
@@ -667,6 +667,7 @@ class ClusterClient {
667
667
  twoWaySettlementDateOffset,
668
668
  arbitrageSessionId,
669
669
  fxTradeId,
670
+ internalTradeId,
670
671
  timeInForce,
671
672
  valueDate,
672
673
  }, {
@@ -0,0 +1,60 @@
1
+ import { RioEnv } from "@riocrypto/common";
2
+ export declare const KRAKEN_ASSET_USD = "ZUSD";
3
+ export interface KrakenDepositMethod {
4
+ method: string;
5
+ limit: string | boolean;
6
+ fee?: string;
7
+ "gen-address"?: boolean;
8
+ "address-setup-fee"?: string;
9
+ }
10
+ export interface KrakenDepositAddress {
11
+ address: string;
12
+ expiretm: string;
13
+ new?: boolean;
14
+ tag?: string;
15
+ memo?: string;
16
+ }
17
+ export interface KrakenFundingStatus {
18
+ method: string;
19
+ aclass: string;
20
+ asset: string;
21
+ refid: string;
22
+ txid: string;
23
+ info: string;
24
+ amount: string;
25
+ fee: string;
26
+ time: number;
27
+ status: string;
28
+ "status-prop"?: string;
29
+ }
30
+ export interface KrakenWithdrawInfo {
31
+ method: string;
32
+ limit: string;
33
+ amount: string;
34
+ fee: string;
35
+ }
36
+ declare class KrakenClient {
37
+ private apiKey;
38
+ private env;
39
+ private axiosClient;
40
+ private decodedSecret;
41
+ private lastNonce;
42
+ private restBaseUrl;
43
+ constructor(apiKey: string, apiSecret: string, env: RioEnv);
44
+ private assertFundingAllowed;
45
+ private nextNonce;
46
+ private sign;
47
+ private privatePost;
48
+ getBalance(asset: string): Promise<number>;
49
+ getAllBalances(assets: string[]): Promise<Record<string, number>>;
50
+ getDepositMethods(asset: string): Promise<KrakenDepositMethod[]>;
51
+ getDepositAddresses(asset: string, method: string, isNew?: boolean): Promise<KrakenDepositAddress[]>;
52
+ getDepositStatus(asset: string, method?: string): Promise<KrakenFundingStatus[]>;
53
+ getWithdrawInfo(asset: string, key: string, amount: number): Promise<KrakenWithdrawInfo>;
54
+ withdraw(asset: string, key: string, amount: number): Promise<string>;
55
+ getWithdrawStatus(asset: string, method?: string): Promise<KrakenFundingStatus[]>;
56
+ getWithdrawStatusByRefid(asset: string, refid: string): Promise<KrakenFundingStatus | undefined>;
57
+ getDepositByTxId(asset: string, txid: string): Promise<KrakenFundingStatus | undefined>;
58
+ }
59
+ export declare const buildKrakenClient: () => Promise<KrakenClient>;
60
+ export type { KrakenClient };
@@ -0,0 +1,219 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.buildKrakenClient = exports.KRAKEN_ASSET_USD = void 0;
16
+ const crypto_1 = __importDefault(require("crypto"));
17
+ const common_1 = require("@riocrypto/common");
18
+ const axios_with_logging_1 = require("./axios-with-logging");
19
+ const secret_manager_client_1 = require("./secret-manager-client");
20
+ // Kraken REST hosts. There is no public Kraken spot testnet, but institutional
21
+ // clients get a separate UAT environment. The host is selected purely from
22
+ // RIO_ENV: only production talks to the live account; every other environment
23
+ // points at the UAT host so it can never move funds on the real account.
24
+ const KRAKEN_PROD_REST_BASE_URL = "https://api.kraken.com";
25
+ const KRAKEN_UAT_REST_BASE_URL = "https://api.uat.kraken.com";
26
+ const getKrakenRestBaseUrl = (env) => env === common_1.RioEnv.Production
27
+ ? KRAKEN_PROD_REST_BASE_URL
28
+ : KRAKEN_UAT_REST_BASE_URL;
29
+ // Kraken Balance keys. Fiat USD is `ZUSD`; the stablecoins use their ticker.
30
+ exports.KRAKEN_ASSET_USD = "ZUSD";
31
+ class KrakenClient {
32
+ constructor(apiKey, apiSecret, env) {
33
+ this.apiKey = apiKey;
34
+ this.env = env;
35
+ // Kraken nonces must strictly increase per key. We keep a monotonic floor so
36
+ // two calls in the same millisecond can't produce a decreasing nonce.
37
+ this.lastNonce = 0;
38
+ this.decodedSecret = Buffer.from(apiSecret, "base64");
39
+ this.axiosClient = (0, axios_with_logging_1.buildAxiosWithLogging)();
40
+ this.restBaseUrl = getKrakenRestBaseUrl(env);
41
+ }
42
+ // Fail-closed guard: a non-production environment must never move funds
43
+ // against the LIVE Kraken host. The host is derived from RIO_ENV, so this
44
+ // also catches an unknown/unset RIO_ENV resolving to the live host.
45
+ assertFundingAllowed() {
46
+ const isLiveHost = this.restBaseUrl === KRAKEN_PROD_REST_BASE_URL;
47
+ if (this.env !== common_1.RioEnv.Production && isLiveHost) {
48
+ throw new Error("Refusing to move Kraken funds: a non-production environment is pointed at the live Kraken host.");
49
+ }
50
+ }
51
+ nextNonce() {
52
+ const now = Date.now();
53
+ const nonce = now > this.lastNonce ? now : this.lastNonce + 1;
54
+ this.lastNonce = nonce;
55
+ return nonce;
56
+ }
57
+ sign(path, nonce, postData) {
58
+ // API-Sign = base64( HMAC-SHA512( path + SHA256(nonce + postData),
59
+ // base64decode(secret) ) )
60
+ const sha256 = crypto_1.default
61
+ .createHash("sha256")
62
+ .update(nonce + postData)
63
+ .digest();
64
+ const message = Buffer.concat([Buffer.from(path, "utf8"), sha256]);
65
+ return crypto_1.default
66
+ .createHmac("sha512", this.decodedSecret)
67
+ .update(message)
68
+ .digest("base64");
69
+ }
70
+ privatePost(method, params = {}) {
71
+ return __awaiter(this, void 0, void 0, function* () {
72
+ const path = `/0/private/${method}`;
73
+ const nonce = this.nextNonce();
74
+ const form = new URLSearchParams();
75
+ form.set("nonce", String(nonce));
76
+ for (const [key, value] of Object.entries(params)) {
77
+ if (value !== undefined && value !== null) {
78
+ form.set(key, String(value));
79
+ }
80
+ }
81
+ const postData = form.toString();
82
+ const signature = this.sign(path, nonce, postData);
83
+ const { data } = yield this.axiosClient.post(`${this.restBaseUrl}${path}`, postData, {
84
+ headers: {
85
+ "API-Key": this.apiKey,
86
+ "API-Sign": signature,
87
+ "Content-Type": "application/x-www-form-urlencoded",
88
+ },
89
+ });
90
+ // Kraken returns HTTP 200 with errors inside an `error` array.
91
+ if ((data === null || data === void 0 ? void 0 : data.error) && data.error.length > 0) {
92
+ throw new Error(`Kraken API error: ${data.error.join(", ")}`);
93
+ }
94
+ return data.result;
95
+ });
96
+ }
97
+ // ----- Balances -----
98
+ getBalance(asset) {
99
+ return __awaiter(this, void 0, void 0, function* () {
100
+ const balances = yield this.privatePost("Balance", {});
101
+ const value = balances[asset];
102
+ return value ? parseFloat(value) : 0;
103
+ });
104
+ }
105
+ getAllBalances(assets) {
106
+ return __awaiter(this, void 0, void 0, function* () {
107
+ const balances = yield this.privatePost("Balance", {});
108
+ const result = {};
109
+ for (const asset of assets) {
110
+ result[asset] = balances[asset] ? parseFloat(balances[asset]) : 0;
111
+ }
112
+ return result;
113
+ });
114
+ }
115
+ // ----- Deposits -----
116
+ getDepositMethods(asset) {
117
+ return __awaiter(this, void 0, void 0, function* () {
118
+ return this.privatePost("DepositMethods", { asset });
119
+ });
120
+ }
121
+ // Retrieve (or generate) deposit addresses for an asset + method. `method`
122
+ // must exactly match one of the values returned by DepositMethods.
123
+ getDepositAddresses(asset, method, isNew = false) {
124
+ return __awaiter(this, void 0, void 0, function* () {
125
+ return this.privatePost("DepositAddresses", {
126
+ asset,
127
+ method,
128
+ new: isNew ? true : undefined,
129
+ });
130
+ });
131
+ }
132
+ getDepositStatus(asset, method) {
133
+ return __awaiter(this, void 0, void 0, function* () {
134
+ return this.privatePost("DepositStatus", {
135
+ asset,
136
+ method,
137
+ });
138
+ });
139
+ }
140
+ // ----- Withdrawals -----
141
+ getWithdrawInfo(asset, key, amount) {
142
+ return __awaiter(this, void 0, void 0, function* () {
143
+ return this.privatePost("WithdrawInfo", {
144
+ asset,
145
+ key,
146
+ amount,
147
+ });
148
+ });
149
+ }
150
+ // Withdraw to a pre-configured, account-whitelisted address referenced by
151
+ // `key` (Kraken does not allow withdrawing to an arbitrary address via API).
152
+ // Returns Kraken's withdrawal reference id (refid).
153
+ withdraw(asset, key, amount) {
154
+ return __awaiter(this, void 0, void 0, function* () {
155
+ this.assertFundingAllowed();
156
+ const result = yield this.privatePost("Withdraw", {
157
+ asset,
158
+ key,
159
+ amount,
160
+ });
161
+ if (!(result === null || result === void 0 ? void 0 : result.refid)) {
162
+ throw new Error("Kraken Withdraw returned no refid");
163
+ }
164
+ return result.refid;
165
+ });
166
+ }
167
+ getWithdrawStatus(asset, method) {
168
+ return __awaiter(this, void 0, void 0, function* () {
169
+ return this.privatePost("WithdrawStatus", {
170
+ asset,
171
+ method,
172
+ });
173
+ });
174
+ }
175
+ getWithdrawStatusByRefid(asset, refid) {
176
+ return __awaiter(this, void 0, void 0, function* () {
177
+ const statuses = yield this.getWithdrawStatus(asset);
178
+ return statuses.find((s) => s.refid === refid);
179
+ });
180
+ }
181
+ getDepositByTxId(asset, txid) {
182
+ return __awaiter(this, void 0, void 0, function* () {
183
+ const statuses = yield this.getDepositStatus(asset);
184
+ return statuses.find((s) => s.txid === txid);
185
+ });
186
+ }
187
+ }
188
+ // Cache a single client per process so the monotonic nonce floor is effective
189
+ // process-wide; building a fresh client per call would reset it and risk
190
+ // `EAPI:Invalid nonce`.
191
+ let krakenClient = null;
192
+ let krakenClientPromise = null;
193
+ const buildKrakenClient = () => __awaiter(void 0, void 0, void 0, function* () {
194
+ if (krakenClient) {
195
+ return krakenClient;
196
+ }
197
+ if (!krakenClientPromise) {
198
+ krakenClientPromise = (() => __awaiter(void 0, void 0, void 0, function* () {
199
+ const KRAKEN_API_KEY = yield secret_manager_client_1.secretManagerClient.getSecretValue("KRAKEN_API_KEY");
200
+ if (!KRAKEN_API_KEY) {
201
+ throw new common_1.SecretManagerError();
202
+ }
203
+ const KRAKEN_API_SECRET = yield secret_manager_client_1.secretManagerClient.getSecretValue("KRAKEN_API_SECRET");
204
+ if (!KRAKEN_API_SECRET) {
205
+ throw new common_1.SecretManagerError();
206
+ }
207
+ krakenClient = new KrakenClient(KRAKEN_API_KEY, KRAKEN_API_SECRET, process.env.RIO_ENV);
208
+ return krakenClient;
209
+ }))();
210
+ }
211
+ try {
212
+ return yield krakenClientPromise;
213
+ }
214
+ catch (err) {
215
+ krakenClientPromise = null;
216
+ throw err;
217
+ }
218
+ });
219
+ exports.buildKrakenClient = buildKrakenClient;
package/build/index.d.ts CHANGED
@@ -148,6 +148,7 @@ export * from "./clients/circle-client";
148
148
  export * from "./clients/coincover-client";
149
149
  export * from "./clients/google-sheets-api-client";
150
150
  export * from "./clients/binance-client";
151
+ export * from "./clients/kraken-client";
151
152
  export * from "./helpers/get-user-helper";
152
153
  export * from "./helpers/get-processor-fees";
153
154
  export * from "./helpers/get-network-fees";
package/build/index.js CHANGED
@@ -164,6 +164,7 @@ __exportStar(require("./clients/circle-client"), exports);
164
164
  __exportStar(require("./clients/coincover-client"), exports);
165
165
  __exportStar(require("./clients/google-sheets-api-client"), exports);
166
166
  __exportStar(require("./clients/binance-client"), exports);
167
+ __exportStar(require("./clients/kraken-client"), exports);
167
168
  __exportStar(require("./helpers/get-user-helper"), exports);
168
169
  __exportStar(require("./helpers/get-processor-fees"), exports);
169
170
  __exportStar(require("./helpers/get-network-fees"), exports);
@@ -19,6 +19,8 @@ interface ExternalTradeAttrs {
19
19
  associatedMidmarketPrice?: number;
20
20
  status: ExternalTradeStatus;
21
21
  fxTradeId?: string;
22
+ internalTradeId?: string;
23
+ coinbaseConversionId?: string;
22
24
  providerOptions?: {
23
25
  emarkets?: {
24
26
  settlementType?: EmarketsSettlementType;
@@ -60,6 +62,8 @@ interface ExternalTradeDoc extends Document {
60
62
  associatedMidmarketPrice?: number;
61
63
  status: ExternalTradeStatus;
62
64
  fxTradeId?: string;
65
+ internalTradeId?: string;
66
+ coinbaseConversionId?: string;
63
67
  providerOptions?: {
64
68
  emarkets?: {
65
69
  settlementType?: EmarketsSettlementType;
@@ -32,6 +32,12 @@ const buildExternalTrade = (mongoose) => {
32
32
  fxTradeId: {
33
33
  type: String,
34
34
  },
35
+ internalTradeId: {
36
+ type: String,
37
+ },
38
+ coinbaseConversionId: {
39
+ type: String,
40
+ },
35
41
  provider: {
36
42
  type: String,
37
43
  required: true,
@@ -28,6 +28,9 @@ interface InternalBalancesDoc extends mongoose.Document {
28
28
  binance: {
29
29
  [key in Fiat & Crypto]: number;
30
30
  };
31
+ kraken: {
32
+ [key in Fiat & Crypto]: number;
33
+ };
31
34
  brex: {
32
35
  [key in Fiat & Crypto]: number;
33
36
  };
@@ -20,6 +20,9 @@ const buildInternalBalances = (mongoose) => {
20
20
  [common_1.TreasuryProvider.Binance]: {
21
21
  type: Object,
22
22
  },
23
+ [common_1.TreasuryProvider.Kraken]: {
24
+ type: Object,
25
+ },
23
26
  [common_1.TreasuryProvider.Brex]: {
24
27
  type: Object,
25
28
  },
@@ -19,6 +19,7 @@ interface InternalTradeAttrs {
19
19
  amountFilled?: number;
20
20
  limitPrice?: number;
21
21
  coinbaseConversionId?: string;
22
+ externalTradeId?: string;
22
23
  }
23
24
  interface InternalTradeDoc extends mongoose.Document {
24
25
  id: string;
@@ -40,6 +41,7 @@ interface InternalTradeDoc extends mongoose.Document {
40
41
  amountFilled?: number;
41
42
  limitPrice?: number;
42
43
  coinbaseConversionId?: string;
44
+ externalTradeId?: string;
43
45
  }
44
46
  interface InternalTradeModel extends mongoose.Model<InternalTradeDoc> {
45
47
  build(attrs: InternalTradeAttrs): HydratedDocument<InternalTradeDoc>;
@@ -67,6 +67,9 @@ const buildInternalTrade = (mongoose) => {
67
67
  coinbaseConversionId: {
68
68
  type: String,
69
69
  },
70
+ externalTradeId: {
71
+ type: String,
72
+ },
70
73
  }, {
71
74
  toJSON: {
72
75
  transform(doc, ret) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riocrypto/common-server",
3
- "version": "1.0.2842",
3
+ "version": "1.0.2843",
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.2643",
31
+ "@riocrypto/common": "1.0.2647",
32
32
  "@slack/web-api": "^7.15.0",
33
33
  "@types/express": "^4.17.25",
34
34
  "axios": "1.16.0",