@n1xyz/nord-ts 0.1.0 → 0.1.2

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.
@@ -84,8 +84,12 @@ export interface paths {
84
84
  get: {
85
85
  parameters: {
86
86
  query: {
87
+ /** @description Start action ID (exclusive) */
87
88
  from: number;
89
+ /** @description End action ID (inclusive) */
88
90
  to: number;
91
+ /** @description Optionally provided binary version of client, so nord server can ensure all actions provided are executable by client. */
92
+ clientVersion?: components["schemas"]["ExecutableVersion"] | null;
89
93
  };
90
94
  header?: never;
91
95
  path?: never;
@@ -109,6 +113,14 @@ export interface paths {
109
113
  "application/json": components["schemas"]["RangeTooLarge"];
110
114
  };
111
115
  };
116
+ 501: {
117
+ headers: {
118
+ [name: string]: unknown;
119
+ };
120
+ content: {
121
+ "application/json": components["schemas"]["NotImplemented"];
122
+ };
123
+ };
112
124
  };
113
125
  };
114
126
  put?: never;
@@ -137,6 +149,14 @@ export interface paths {
137
149
  "application/octet-stream": unknown;
138
150
  };
139
151
  };
152
+ 413: {
153
+ headers: {
154
+ [name: string]: unknown;
155
+ };
156
+ content: {
157
+ "application/json": components["schemas"]["PayloadTooLarge"];
158
+ };
159
+ };
140
160
  415: {
141
161
  headers: {
142
162
  /**
@@ -779,7 +799,7 @@ export interface paths {
779
799
  [name: string]: unknown;
780
800
  };
781
801
  content: {
782
- "application/json": components["schemas"]["TriggerInfo"][] | null;
802
+ "application/json": components["schemas"]["AccountTriggerInfo"][] | null;
783
803
  };
784
804
  };
785
805
  };
@@ -976,6 +996,79 @@ export interface paths {
976
996
  patch?: never;
977
997
  trace?: never;
978
998
  };
999
+ "/state/download": {
1000
+ parameters: {
1001
+ query?: never;
1002
+ header?: never;
1003
+ path?: never;
1004
+ cookie?: never;
1005
+ };
1006
+ /** @description Returns state download link and metadata, as defined by nearest(up to equality) snapshot `time` or `action_id`` or `timestamp`. If not specified, returns latest snapshot. Example: /state/download?actionId=1 /state/download?time&2025-09-22T12:34:56Z /state/download?timestamp&1695380000 - Does not implemented yet. To get the latest snap use: /state/download - w/o any params.
1007
+ *
1008
+ * The link from responce can be inserted to search bar in browser to download. */
1009
+ get: {
1010
+ parameters: {
1011
+ query?: never;
1012
+ header?: never;
1013
+ path?: never;
1014
+ cookie?: never;
1015
+ };
1016
+ requestBody?: never;
1017
+ responses: {
1018
+ 200: {
1019
+ headers: {
1020
+ [name: string]: unknown;
1021
+ };
1022
+ content: {
1023
+ "application/json": components["schemas"]["StateDownloadMeta"];
1024
+ };
1025
+ };
1026
+ 501: {
1027
+ headers: {
1028
+ [name: string]: unknown;
1029
+ };
1030
+ content: {
1031
+ "application/json": components["schemas"]["NotImplemented"];
1032
+ };
1033
+ };
1034
+ };
1035
+ };
1036
+ put?: never;
1037
+ post?: never;
1038
+ delete?: never;
1039
+ options?: never;
1040
+ head?: never;
1041
+ patch?: never;
1042
+ trace?: never;
1043
+ };
1044
+ "/state/download/{snapshot_id}": {
1045
+ parameters: {
1046
+ query?: never;
1047
+ header?: never;
1048
+ path?: never;
1049
+ cookie?: never;
1050
+ };
1051
+ /** @description Typical HTTP file download endpoint. Actually same as provided by S3 storages. Means supports Range header, Content-Length, Content-Type, ETag and so on, and relevant headers. */
1052
+ get: {
1053
+ parameters: {
1054
+ query?: never;
1055
+ header?: never;
1056
+ path: {
1057
+ snapshot_id: string;
1058
+ };
1059
+ cookie?: never;
1060
+ };
1061
+ requestBody?: never;
1062
+ responses: never;
1063
+ };
1064
+ put?: never;
1065
+ post?: never;
1066
+ delete?: never;
1067
+ options?: never;
1068
+ head?: never;
1069
+ patch?: never;
1070
+ trace?: never;
1071
+ };
979
1072
  "/tv": {
980
1073
  parameters: {
981
1074
  query?: never;
@@ -1911,12 +2004,25 @@ export interface components {
1911
2004
  AcceptedMediaType: {
1912
2005
  expected: string;
1913
2006
  };
2007
+ PayloadTooLarge: {
2008
+ /** Format: uint */
2009
+ limit: number;
2010
+ };
1914
2011
  ActionsQuery: {
1915
- /** Format: uint64 */
2012
+ /**
2013
+ * Format: uint64
2014
+ * @description Start action ID (exclusive)
2015
+ */
1916
2016
  from: number;
1917
- /** Format: uint64 */
2017
+ /**
2018
+ * Format: uint64
2019
+ * @description End action ID (inclusive)
2020
+ */
1918
2021
  to: number;
2022
+ /** @description Optionally provided binary version of client, so nord server can ensure all actions provided are executable by client. */
2023
+ clientVersion?: components["schemas"]["ExecutableVersion"] | null;
1919
2024
  };
2025
+ ExecutableVersion: string;
1920
2026
  ActionsItem: {
1921
2027
  /** Format: uint64 */
1922
2028
  actionId: number;
@@ -1929,6 +2035,10 @@ export interface components {
1929
2035
  /** Format: uint16 */
1930
2036
  maximal: number;
1931
2037
  };
2038
+ NotImplemented: {
2039
+ /** @description Human readable message describing what to expect next. */
2040
+ message: string;
2041
+ };
1932
2042
  ActionNotFound: null;
1933
2043
  /** @description Returns fee parts per market per balance change per action per account. Fee is taken from user without return. Please note that some operations need some deposit which will be returned - these are not part of fees. */
1934
2044
  FillRole: "maker" | "taker";
@@ -2259,14 +2369,23 @@ export interface components {
2259
2369
  /** @enum {string} */
2260
2370
  TriggerKind: "stopLoss" | "takeProfit";
2261
2371
  /** @enum {string} */
2262
- TriggerStatus: "active" | "success" | "cancel" | "fail" | "remove";
2263
- TriggerInfo: {
2372
+ TriggerStatus: "Active" | "Success" | "Removed" | "Canceled";
2373
+ /** @description Trigger into per account. */
2374
+ AccountTriggerInfo: {
2264
2375
  /** Format: uint32 */
2265
2376
  marketId: number;
2377
+ key: components["schemas"]["TriggerKey"];
2378
+ triggerPrices: components["schemas"]["TriggerPrice"];
2379
+ /** Format: uint64 */
2380
+ actionId: number;
2381
+ };
2382
+ TriggerKey: {
2266
2383
  side: components["schemas"]["Side"];
2267
2384
  kind: components["schemas"]["TriggerKind"];
2268
- triggerPrice: components["schemas"]["PositivePriceMantissa"];
2269
- limitPrice?: components["schemas"]["PositivePriceMantissa"] | null;
2385
+ };
2386
+ TriggerPrice: {
2387
+ trigger: components["schemas"]["PositivePriceMantissa"];
2388
+ settlement?: components["schemas"]["PositivePriceMantissa"] | null;
2270
2389
  };
2271
2390
  /**
2272
2391
  * Format: uint64
@@ -2334,6 +2453,29 @@ export interface components {
2334
2453
  */
2335
2454
  pageSize: number | null;
2336
2455
  };
2456
+ DownloadFilter: components["schemas"]["Op_for_DataDateTime"] | components["schemas"]["Op_for_uint64"] | components["schemas"]["Op_for_uint64"] | null;
2457
+ /** @description Parses tag (anycase), and value in round braces using `from_str`. */
2458
+ Op_for_DataDateTime: {
2459
+ le: components["schemas"]["DataDateTime"];
2460
+ };
2461
+ DataDateTime: string;
2462
+ /** @description Parses tag (anycase), and value in round braces using `from_str`. */
2463
+ Op_for_uint64: {
2464
+ /** Format: uint64 */
2465
+ le: number;
2466
+ };
2467
+ StateDownloadMeta: {
2468
+ /** @description Hash of the state file. Used as ETag. */
2469
+ hash: number[];
2470
+ /**
2471
+ * Format: uri
2472
+ * @description Link to download the state file.
2473
+ */
2474
+ link: string;
2475
+ /** @description Version of binary which produced this state snapshot. So can decide whether it is compatible with client. todo(repl): After upgrade release make it non-optional. */
2476
+ version?: components["schemas"]["BinaryId"] | null;
2477
+ };
2478
+ BinaryId: string;
2337
2479
  /** @description TV config query response https://www.tradingview.com/charting-library-docs/latest/connecting_data/UDF/#data-feed-configuration-data */
2338
2480
  TvConfigResponse: {
2339
2481
  supported_resolutions: components["schemas"]["Resolution"][];
@@ -1,6 +1,6 @@
1
1
  import Decimal from "decimal.js";
2
2
  import * as proto from "../../gen/nord_pb";
3
- import { paths, components } from "../../gen/openapi";
3
+ import { paths } from "../../gen/openapi";
4
4
  import createClient from "openapi-fetch";
5
5
 
6
6
  import { create } from "@bufbuild/protobuf";
@@ -10,11 +10,11 @@ import {
10
10
  KeyType,
11
11
  Side,
12
12
  QuoteSize,
13
+ TriggerKind,
13
14
  } from "../../types";
14
15
  import {
15
16
  assert,
16
17
  BigIntValue,
17
- checkedFetch,
18
18
  checkPubKeyLength,
19
19
  decodeLengthDelimited,
20
20
  SESSION_TTL,
@@ -55,7 +55,6 @@ async function sendAction(
55
55
  serverUrl: string,
56
56
  makeSignedMessage: (message: Uint8Array) => Promise<Uint8Array>,
57
57
  action: proto.Action,
58
- actionErrorDesc: string,
59
58
  ): Promise<proto.Receipt> {
60
59
  const body = await prepareAction(action, makeSignedMessage);
61
60
  // NOTE: restructure and reuse client as it is in Nord.ts
@@ -67,13 +66,19 @@ async function sendAction(
67
66
  },
68
67
  },
69
68
  body: body,
69
+ // NOTE: openapi-fetch ignores headers and types/const headers in schema, and always assume all things are JSON
70
+ // to handle multi type bodies, need these overrides and later adhoc parsing
71
+ bodySerializer: (body) => body,
72
+ parseAs: "stream",
70
73
  });
71
74
 
72
75
  if (response.error) {
73
- throw new Error(`Failed to ${actionErrorDesc}, HTTP status ${response}`);
76
+ throw new Error(
77
+ `Failed to ${action.kind.case}, HTTP status ${JSON.stringify(response.error)}`,
78
+ );
74
79
  }
75
80
 
76
- const rawResp = new Uint8Array(await response.response.arrayBuffer());
81
+ const rawResp = new Uint8Array(await response.response.bytes());
77
82
 
78
83
  const resp: proto.Receipt = decodeLengthDelimited(
79
84
  rawResp,
@@ -82,7 +87,7 @@ async function sendAction(
82
87
 
83
88
  if (resp.kind?.case === "err") {
84
89
  throw new Error(
85
- `Could not ${actionErrorDesc}, reason: ${proto.Error[resp.kind.value]}`,
90
+ `Could not execute ${action.kind.case}, reason: ${proto.Error[resp.kind.value]}`,
86
91
  );
87
92
  }
88
93
 
@@ -96,14 +101,21 @@ export async function prepareAction(
96
101
  makeSignedMessage: (message: Uint8Array) => Promise<Uint8Array>,
97
102
  ) {
98
103
  const encoded = sizeDelimitedEncode(proto.ActionSchema, action);
99
- // NOTE(agent): keep in sync with MAX_HTTP_REQUEST_BODY_SIZE in rust code
100
- const MAX_HTTP_REQUEST_BODY_SIZE = 1024;
101
- if (encoded.byteLength > MAX_HTTP_REQUEST_BODY_SIZE) {
104
+ // NOTE(agent): keep in sync with MAX_ENCODED_ACTION_SIZE in Rust code
105
+ const MAX_ENCODED_ACTION_SIZE = 1024;
106
+ if (encoded.byteLength > MAX_ENCODED_ACTION_SIZE) {
107
+ console.warn("Encoded message:", encoded);
102
108
  throw new Error(
103
- `Encoded message size (${encoded.byteLength} bytes) is greater than max payload size (${MAX_HTTP_REQUEST_BODY_SIZE} bytes).`,
109
+ `Encoded message size (${encoded.byteLength} bytes) is greater than max payload size (${MAX_ENCODED_ACTION_SIZE} bytes).`,
104
110
  );
105
111
  }
106
112
  const body = await makeSignedMessage(encoded);
113
+ if (body.byteLength > MAX_ENCODED_ACTION_SIZE) {
114
+ console.warn("Encoded length:", encoded.byteLength);
115
+ throw new Error(
116
+ `Signed message size (${body.byteLength} bytes) is greater than max payload size (${MAX_ENCODED_ACTION_SIZE} bytes).`,
117
+ );
118
+ }
107
119
  return body;
108
120
  }
109
121
 
@@ -147,7 +159,6 @@ export async function createSession(
147
159
  serverUrl,
148
160
  (m) => walletSign(walletSignFn, m),
149
161
  action,
150
- "create a new session",
151
162
  );
152
163
 
153
164
  if (resp.kind?.case === "createSessionResult") {
@@ -180,7 +191,6 @@ export async function revokeSession(
180
191
  serverUrl,
181
192
  (m) => walletSign(walletSignFn, m),
182
193
  action,
183
- "revoke session",
184
194
  );
185
195
 
186
196
  return { actionId: resp.actionId };
@@ -217,7 +227,6 @@ export async function withdraw(
217
227
  serverUrl,
218
228
  (m) => sessionSign(signFn, m),
219
229
  action,
220
- "withdraw",
221
230
  );
222
231
 
223
232
  if (resp.kind?.case === "withdrawResult") {
@@ -257,7 +266,7 @@ export async function placeOrder(
257
266
  const size = toScaledU64(params.size ?? 0, params.sizeDecimals);
258
267
 
259
268
  const scaledQuote = params.quoteSize
260
- ? params.quoteSize.toScaledU64(params.priceDecimals, params.sizeDecimals)
269
+ ? params.quoteSize.toWire(params.priceDecimals, params.sizeDecimals)
261
270
  : undefined;
262
271
 
263
272
  assert(
@@ -295,7 +304,6 @@ export async function placeOrder(
295
304
  serverUrl,
296
305
  (m) => sessionSign(signFn, m),
297
306
  action,
298
- "place order",
299
307
  );
300
308
 
301
309
  if (resp.kind?.case === "placeOrderResult") {
@@ -339,7 +347,6 @@ export async function cancelOrder(
339
347
  serverUrl,
340
348
  (m) => sessionSign(signFn, m),
341
349
  action,
342
- "cancel order",
343
350
  );
344
351
 
345
352
  if (resp.kind?.case === "cancelOrderResult") {
@@ -389,7 +396,6 @@ export async function transfer(
389
396
  serverUrl,
390
397
  (m) => sessionSign(signFn, m),
391
398
  action,
392
- "transfer",
393
399
  );
394
400
 
395
401
  if (resp.kind?.case === "transferred") {
@@ -406,6 +412,103 @@ export async function transfer(
406
412
  }
407
413
  }
408
414
 
415
+ export async function addTrigger(
416
+ serverUrl: string,
417
+ signFn: (message: Uint8Array) => Promise<Uint8Array>,
418
+ currentTimestamp: bigint,
419
+ nonce: number,
420
+ params: {
421
+ sessionId: BigIntValue;
422
+ marketId: number;
423
+ side: Side;
424
+ kind: TriggerKind;
425
+ priceDecimals: number;
426
+ triggerPrice: Decimal.Value;
427
+ limitPrice?: Decimal.Value;
428
+ accountId?: number;
429
+ },
430
+ ): Promise<{ actionId: bigint }> {
431
+ const triggerPrice = toScaledU64(params.triggerPrice, params.priceDecimals);
432
+ assert(triggerPrice > 0n, "Trigger price must be positive");
433
+ const limitPrice =
434
+ params.limitPrice === undefined
435
+ ? undefined
436
+ : toScaledU64(params.limitPrice, params.priceDecimals);
437
+ if (limitPrice !== undefined) {
438
+ assert(limitPrice > 0n, "Limit price must be positive");
439
+ }
440
+ const key = create(proto.TriggerKeySchema, {
441
+ kind:
442
+ params.kind === TriggerKind.StopLoss
443
+ ? proto.TriggerKind.STOP_LOSS
444
+ : proto.TriggerKind.TAKE_PROFIT,
445
+ side: params.side === Side.Bid ? proto.Side.BID : proto.Side.ASK,
446
+ });
447
+ const prices = create(proto.Action_TriggerPricesSchema, {
448
+ triggerPrice,
449
+ limitPrice,
450
+ });
451
+ const action = createAction(currentTimestamp, nonce, {
452
+ case: "addTrigger",
453
+ value: create(proto.Action_AddTriggerSchema, {
454
+ sessionId: BigInt(params.sessionId),
455
+ marketId: params.marketId,
456
+ key,
457
+ prices,
458
+ accountId: params.accountId,
459
+ }),
460
+ });
461
+ const resp = await sendAction(
462
+ serverUrl,
463
+ (m) => sessionSign(signFn, m),
464
+ action,
465
+ );
466
+ if (resp.kind?.case === "triggerAdded") {
467
+ return { actionId: resp.actionId };
468
+ }
469
+ throw new Error(`Unexpected receipt kind ${resp.kind?.case}`);
470
+ }
471
+
472
+ export async function removeTrigger(
473
+ serverUrl: string,
474
+ signFn: (message: Uint8Array) => Promise<Uint8Array>,
475
+ currentTimestamp: bigint,
476
+ nonce: number,
477
+ params: {
478
+ sessionId: BigIntValue;
479
+ marketId: number;
480
+ side: Side;
481
+ kind: TriggerKind;
482
+ accountId?: number;
483
+ },
484
+ ): Promise<{ actionId: bigint }> {
485
+ const key = create(proto.TriggerKeySchema, {
486
+ kind:
487
+ params.kind === TriggerKind.StopLoss
488
+ ? proto.TriggerKind.STOP_LOSS
489
+ : proto.TriggerKind.TAKE_PROFIT,
490
+ side: params.side === Side.Bid ? proto.Side.BID : proto.Side.ASK,
491
+ });
492
+ const action = createAction(currentTimestamp, nonce, {
493
+ case: "removeTrigger",
494
+ value: create(proto.Action_RemoveTriggerSchema, {
495
+ sessionId: BigInt(params.sessionId),
496
+ marketId: params.marketId,
497
+ key,
498
+ accountId: params.accountId,
499
+ }),
500
+ });
501
+ const resp = await sendAction(
502
+ serverUrl,
503
+ (m) => sessionSign(signFn, m),
504
+ action,
505
+ );
506
+ if (resp.kind?.case === "triggerRemoved") {
507
+ return { actionId: resp.actionId };
508
+ }
509
+ throw new Error(`Unexpected receipt kind ${resp.kind?.case}`);
510
+ }
511
+
409
512
  export type AtomicSubaction =
410
513
  | {
411
514
  kind: "place";
@@ -452,7 +555,7 @@ export async function atomic(
452
555
  const price = toScaledU64(a.price ?? 0, a.priceDecimals);
453
556
  const size = toScaledU64(a.size ?? 0, a.sizeDecimals);
454
557
  const scaledQuote = a.quoteSize
455
- ? a.quoteSize.toScaledU64(a.priceDecimals, a.sizeDecimals)
558
+ ? a.quoteSize.toWire(a.priceDecimals, a.sizeDecimals)
456
559
  : undefined;
457
560
 
458
561
  // Require at least one limit to be set (non-zero size, non-zero price, or quoteSize)
@@ -510,7 +613,6 @@ export async function atomic(
510
613
  serverUrl,
511
614
  (m) => sessionSign(signFn, m),
512
615
  action,
513
- "execute atomic action",
514
616
  );
515
617
  if (resp.kind?.case === "atomic") {
516
618
  return {
@@ -0,0 +1,57 @@
1
+ import createClient from "openapi-fetch";
2
+
3
+ import { paths, components } from "../../gen/openapi";
4
+
5
+ type AccountTriggerInfo = components["schemas"]["AccountTriggerInfo"];
6
+ type TriggerHistoryPage =
7
+ components["schemas"]["PageResult_for_uint64_and_HistoryTriggerInfo"];
8
+ type HistoryTriggerQuery = components["schemas"]["AccountTriggersQuery"];
9
+
10
+ export type { AccountTriggerInfo, TriggerHistoryPage, HistoryTriggerQuery };
11
+
12
+ export async function getAccountTriggers(
13
+ serverUrl: string,
14
+ accountId: number,
15
+ ): Promise<AccountTriggerInfo[]> {
16
+ const client = createClient<paths>({ baseUrl: serverUrl });
17
+ const response = await client.GET("/account/{account_id}/triggers", {
18
+ params: {
19
+ path: { account_id: accountId },
20
+ },
21
+ });
22
+
23
+ if (response.data === undefined) {
24
+ throw new Error(
25
+ `Failed to fetch triggers for account ${accountId}: HTTP ${response.response.status}`,
26
+ );
27
+ }
28
+
29
+ return response.data ?? [];
30
+ }
31
+
32
+ export async function getAccountTriggerHistory(
33
+ serverUrl: string,
34
+ accountId: number,
35
+ options: HistoryTriggerQuery,
36
+ ): Promise<TriggerHistoryPage> {
37
+ const client = createClient<paths>({ baseUrl: serverUrl });
38
+ const response = await client.GET("/account/{account_id}/triggers/history", {
39
+ params: {
40
+ path: { account_id: accountId },
41
+ query: {
42
+ since: options.since,
43
+ until: options.until,
44
+ pageSize: options.pageSize,
45
+ startInclusive: options.startInclusive,
46
+ },
47
+ },
48
+ });
49
+
50
+ if (!response.data) {
51
+ throw new Error(
52
+ `Failed to fetch trigger history for account ${accountId}: HTTP ${response.response.status}`,
53
+ );
54
+ }
55
+
56
+ return response.data;
57
+ }
@@ -6,6 +6,8 @@ import * as proto from "../../gen/nord_pb";
6
6
  import type { paths } from "../../gen/openapi.ts";
7
7
  import {
8
8
  Account,
9
+ AccountPnlPage,
10
+ AccountPnlQuery,
9
11
  ActionResponse,
10
12
  AggregateMetrics,
11
13
  Info,
@@ -636,6 +638,31 @@ export class Nord {
636
638
  });
637
639
  }
638
640
 
641
+ /**
642
+ * Get profit and loss history for an account
643
+ *
644
+ * @param accountId - Account ID to query
645
+ * @param query - Optional time and pagination filters
646
+ * @returns Page of PnL entries ordered from latest to oldest
647
+ * @throws {NordError} If the request fails
648
+ */
649
+ public async getAccountPnl(
650
+ accountId: number,
651
+ query?: Partial<AccountPnlQuery>,
652
+ ): Promise<AccountPnlPage> {
653
+ return await this.GET("/account/{account_id}/pnl", {
654
+ params: {
655
+ path: { account_id: accountId },
656
+ query: {
657
+ since: query?.since,
658
+ until: query?.until,
659
+ startInclusive: query?.startInclusive,
660
+ pageSize: query?.pageSize,
661
+ },
662
+ },
663
+ });
664
+ }
665
+
639
666
  /**
640
667
  * Get market statistics (alias for marketsStats for backward compatibility)
641
668
  *