@n1xyz/nord-ts 0.1.6 → 0.1.7

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.
Files changed (39) hide show
  1. package/dist/actions.d.ts +57 -0
  2. package/dist/actions.js +229 -0
  3. package/dist/client/Nord.d.ts +379 -0
  4. package/dist/client/Nord.js +718 -0
  5. package/dist/client/NordAdmin.d.ts +225 -0
  6. package/dist/client/NordAdmin.js +394 -0
  7. package/dist/client/NordUser.d.ts +350 -0
  8. package/dist/client/NordUser.js +743 -0
  9. package/dist/error.d.ts +35 -0
  10. package/dist/error.js +49 -0
  11. package/dist/gen/openapi.d.ts +40 -0
  12. package/dist/index.d.ts +6 -1
  13. package/dist/index.js +29 -1
  14. package/dist/nord/client/NordAdmin.js +2 -0
  15. package/dist/types.d.ts +4 -50
  16. package/dist/types.js +1 -24
  17. package/dist/utils.d.ts +8 -11
  18. package/dist/utils.js +54 -41
  19. package/dist/websocket/Subscriber.d.ts +37 -0
  20. package/dist/websocket/Subscriber.js +25 -0
  21. package/dist/websocket/index.d.ts +19 -2
  22. package/dist/websocket/index.js +82 -2
  23. package/package.json +1 -1
  24. package/src/actions.ts +333 -0
  25. package/src/{nord/client → client}/Nord.ts +207 -210
  26. package/src/{nord/client → client}/NordAdmin.ts +123 -153
  27. package/src/{nord/client → client}/NordUser.ts +216 -305
  28. package/src/gen/openapi.ts +40 -0
  29. package/src/index.ts +7 -1
  30. package/src/types.ts +4 -54
  31. package/src/utils.ts +44 -47
  32. package/src/{nord/models → websocket}/Subscriber.ts +2 -2
  33. package/src/websocket/index.ts +105 -2
  34. package/src/nord/api/actions.ts +0 -648
  35. package/src/nord/api/core.ts +0 -96
  36. package/src/nord/api/metrics.ts +0 -269
  37. package/src/nord/client/NordClient.ts +0 -79
  38. package/src/nord/index.ts +0 -25
  39. /package/src/{nord/utils/NordError.ts → error.ts} +0 -0
@@ -4,16 +4,9 @@ import {
4
4
  TOKEN_PROGRAM_ID,
5
5
  TOKEN_2022_PROGRAM_ID,
6
6
  } from "@solana/spl-token";
7
- import {
8
- Connection,
9
- PublicKey,
10
- Transaction,
11
- SendOptions,
12
- } from "@solana/web3.js";
7
+ import { PublicKey, Transaction, SendOptions } from "@solana/web3.js";
13
8
  import Decimal from "decimal.js";
14
9
  import * as ed from "@noble/ed25519";
15
- import { sha512 } from "@noble/hashes/sha512";
16
- ed.etc.sha512Sync = sha512;
17
10
  import { floatToScaledBigIntLossy } from "@n1xyz/proton";
18
11
  import {
19
12
  FillMode,
@@ -22,8 +15,8 @@ import {
22
15
  QuoteSize,
23
16
  TriggerKind,
24
17
  fillModeToProtoFillMode,
25
- } from "../../types";
26
- import * as proto from "../../gen/nord_pb";
18
+ } from "../types";
19
+ import * as proto from "../gen/nord_pb";
27
20
  import {
28
21
  BigIntValue,
29
22
  checkedFetch,
@@ -33,117 +26,19 @@ import {
33
26
  optExpect,
34
27
  keypairFromPrivateKey,
35
28
  toScaledU64,
36
- } from "../../utils";
29
+ } from "../utils";
37
30
  import { create } from "@bufbuild/protobuf";
38
31
  import {
39
32
  createSession,
40
33
  revokeSession,
41
34
  atomic,
42
35
  expectReceiptKind,
36
+ createAction,
37
+ sendAction,
43
38
  AtomicSubaction,
44
- } from "../api/actions";
45
- import { NordError } from "../utils/NordError";
39
+ } from "../actions";
40
+ import { NordError } from "../error";
46
41
  import { Nord } from "./Nord";
47
- import { NordClient } from "./NordClient";
48
-
49
- /**
50
- * Parameters for creating a NordUser instance
51
- */
52
- export interface NordUserParams {
53
- /** Nord client instance */
54
- nord: Nord;
55
-
56
- /** User's blockchain address */
57
- address: string;
58
-
59
- /** Function to sign messages with the user's wallet */
60
- walletSignFn: (message: Uint8Array | string) => Promise<Uint8Array>;
61
-
62
- /** Function to sign messages with the user's session key */
63
- sessionSignFn: (message: Uint8Array) => Promise<Uint8Array>;
64
-
65
- /** Function to sign transactions with the user's wallet (optional) */
66
- transactionSignFn: <T extends Transaction>(tx: T) => Promise<T>;
67
-
68
- /** Solana connection (optional) */
69
- connection?: Connection;
70
-
71
- /** Session ID (optional) */
72
- sessionId?: bigint;
73
-
74
- /** Session public key (required) */
75
- sessionPubKey: Uint8Array;
76
-
77
- /** Session public key (required) */
78
- publicKey: PublicKey;
79
- }
80
-
81
- /**
82
- * Parameters for placing an order
83
- */
84
- export interface PlaceOrderParams {
85
- /** Market ID */
86
- marketId: number;
87
-
88
- /** Order side (bid or ask) */
89
- side: Side;
90
-
91
- /** Fill mode (limit, market, etc.) */
92
- fillMode: FillMode;
93
-
94
- /** Whether the order is reduce-only */
95
- isReduceOnly: boolean;
96
-
97
- /** Order size */
98
- size?: Decimal.Value;
99
-
100
- /** Order price */
101
- price?: Decimal.Value;
102
-
103
- /** Quote size object (requires non-zero price and size) */
104
- quoteSize?: QuoteSize;
105
-
106
- /** Account ID to place the order from */
107
- accountId?: number;
108
-
109
- clientOrderId?: BigIntValue;
110
- }
111
-
112
- export interface AddTriggerParams {
113
- marketId: number;
114
- side: Side;
115
- kind: TriggerKind;
116
- triggerPrice: Decimal.Value;
117
- limitPrice?: Decimal.Value;
118
- accountId?: number;
119
- }
120
-
121
- export interface RemoveTriggerParams {
122
- marketId: number;
123
- side: Side;
124
- kind: TriggerKind;
125
- accountId?: number;
126
- }
127
-
128
- /**
129
- * Parameters for transferring tokens between accounts
130
- */
131
- export interface TransferParams {
132
- /** Recipient user */
133
- to: NordUser;
134
-
135
- /** Token ID to transfer */
136
- tokenId: number;
137
-
138
- /** Amount to transfer */
139
- amount: Decimal.Value;
140
-
141
- /** Source account ID */
142
- fromAccountId: number;
143
-
144
- /** Destination account ID */
145
- toAccountId: number;
146
- }
147
42
 
148
43
  /**
149
44
  * Parameters for individual atomic subactions (user-friendly version)
@@ -183,7 +78,17 @@ export interface UserAtomicSubaction {
183
78
  /**
184
79
  * User class for interacting with the Nord protocol
185
80
  */
186
- export class NordUser extends NordClient {
81
+ export class NordUser {
82
+ public readonly nord: Nord;
83
+ public readonly sessionSignFn: (message: Uint8Array) => Promise<Uint8Array>;
84
+ public readonly transactionSignFn: (tx: Transaction) => Promise<Transaction>;
85
+ public sessionId?: bigint;
86
+ public sessionPubKey: PublicKey;
87
+ public publicKey: PublicKey;
88
+ public lastTs = 0;
89
+
90
+ private nonce = 0;
91
+
187
92
  /** User balances by token symbol */
188
93
  public balances: {
189
94
  [key: string]: { accountId: number; balance: number; symbol: string }[];
@@ -229,48 +134,35 @@ export class NordUser extends NordClient {
229
134
  /**
230
135
  * Create a new NordUser instance
231
136
  *
232
- * @param params - Parameters for creating a NordUser
137
+ * @param nord - Nord client instance
138
+ * @param sessionSignFn - Function to sign messages with the user's session key
139
+ * @param transactionSignFn - Function to sign transactions with the user's wallet (optional)
140
+ * @param sessionId - Existing session identifier
141
+ * @param sessionPubKey - Session public key
142
+ * @param publicKey - Wallet public key
233
143
  * @throws {NordError} If required parameters are missing
234
144
  */
235
145
  constructor({
236
- address,
237
146
  nord,
238
- publicKey,
239
- sessionPubKey,
240
147
  sessionSignFn,
241
148
  transactionSignFn,
242
- walletSignFn,
243
- connection,
244
149
  sessionId,
245
- }: NordUserParams) {
246
- if (!walletSignFn) {
247
- throw new NordError("Wallet sign function is required");
248
- }
249
- if (!sessionSignFn) {
250
- throw new NordError("Session sign function is required");
251
- }
252
- if (!sessionPubKey) {
253
- throw new NordError("Session public key is required");
254
- }
255
-
256
- let parsedAddress: PublicKey;
257
- try {
258
- parsedAddress = new PublicKey(address);
259
- } catch (error) {
260
- throw new NordError("Invalid Solana address", { cause: error });
261
- }
262
-
263
- super({
264
- nord,
265
- address: parsedAddress,
266
- walletSignFn,
267
- sessionSignFn,
268
- transactionSignFn,
269
- connection,
270
- sessionId,
271
- sessionPubKey,
272
- publicKey,
273
- });
150
+ sessionPubKey,
151
+ publicKey,
152
+ }: Readonly<{
153
+ nord: Nord;
154
+ sessionSignFn: (message: Uint8Array) => Promise<Uint8Array>;
155
+ transactionSignFn: (tx: Transaction) => Promise<Transaction>;
156
+ sessionId?: bigint;
157
+ sessionPubKey: Uint8Array;
158
+ publicKey: PublicKey;
159
+ }>) {
160
+ this.nord = nord;
161
+ this.sessionSignFn = sessionSignFn;
162
+ this.transactionSignFn = transactionSignFn;
163
+ this.sessionId = sessionId;
164
+ this.sessionPubKey = new PublicKey(sessionPubKey);
165
+ this.publicKey = publicKey;
274
166
 
275
167
  // Convert tokens from info endpoint to SPLTokenInfo
276
168
  if (this.nord.tokens && this.nord.tokens.length > 0) {
@@ -283,74 +175,19 @@ export class NordUser extends NordClient {
283
175
  }
284
176
  }
285
177
 
286
- /**
287
- * Create a clone of this NordUser instance
288
- *
289
- * @returns A new NordUser instance with the same properties
290
- */
291
- clone(): NordUser {
292
- const cloned = new NordUser({
293
- nord: this.nord,
294
- address: this.address.toBase58(),
295
- walletSignFn: this.walletSignFn,
296
- sessionSignFn: this.sessionSignFn,
297
- transactionSignFn: this.transactionSignFn,
298
- connection: this.connection,
299
- sessionPubKey: new Uint8Array(this.sessionPubKey),
300
- publicKey: this.publicKey,
301
- sessionId: this.sessionId,
302
- });
303
-
304
- // Copy other properties
305
- cloned.balances = { ...this.balances };
306
- cloned.positions = { ...this.positions };
307
- cloned.margins = { ...this.margins };
308
- cloned.accountIds = this.accountIds ? [...this.accountIds] : undefined;
309
- cloned.splTokenInfos = [...this.splTokenInfos];
310
- this.cloneClientState(cloned);
311
-
312
- return cloned;
313
- }
314
-
315
178
  /**
316
179
  * Create a NordUser from a private key
317
180
  *
318
181
  * @param nord - Nord instance
319
182
  * @param privateKey - Private key as string or Uint8Array
320
- * @param connection - Solana connection (optional)
321
183
  * @returns NordUser instance
322
184
  * @throws {NordError} If the private key is invalid
323
185
  */
324
- static fromPrivateKey(
325
- nord: Nord,
326
- privateKey: string | Uint8Array,
327
- connection?: Connection,
328
- ): NordUser {
186
+ static fromPrivateKey(nord: Nord, privateKey: string | Uint8Array): NordUser {
329
187
  try {
330
188
  const keypair = keypairFromPrivateKey(privateKey);
331
189
  const publicKey = keypair.publicKey;
332
190
 
333
- // Create a signing function that uses the keypair but doesn't expose it
334
- const walletSignFn = async (
335
- message: Uint8Array | string,
336
- ): Promise<Uint8Array> => {
337
- function toHex(buffer: Uint8Array) {
338
- return Array.from(buffer)
339
- .map((byte) => byte.toString(16).padStart(2, "0"))
340
- .join("");
341
- }
342
- const messageBuffer = new TextEncoder().encode(
343
- toHex(message as Uint8Array),
344
- );
345
-
346
- // Use ed25519 to sign the message
347
- const signature = ed.sign(
348
- messageBuffer,
349
- keypair.secretKey.slice(0, 32),
350
- );
351
- return signature;
352
- };
353
-
354
191
  const sessionSignFn = async (
355
192
  message: Uint8Array,
356
193
  ): Promise<Uint8Array> => {
@@ -358,28 +195,19 @@ export class NordUser extends NordClient {
358
195
  return ed.sign(message, keypair.secretKey.slice(0, 32));
359
196
  };
360
197
 
361
- // Create a transaction signing function
362
- const transactionSignFn = async (transaction: any): Promise<any> => {
363
- // This is a basic implementation - actual implementation would depend on the transaction type
364
- if (transaction.sign) {
365
- // Solana transaction
366
- transaction.sign(keypair);
367
- return transaction;
368
- }
369
-
370
- // For other transaction types, would need specific implementation
371
- throw new NordError("Unsupported transaction type for signing");
198
+ const transactionSignFn = async (
199
+ tx: Transaction,
200
+ ): Promise<Transaction> => {
201
+ tx.sign(keypair);
202
+ return tx;
372
203
  };
373
204
 
374
205
  return new NordUser({
375
206
  nord,
376
- address: publicKey.toBase58(),
377
- walletSignFn,
378
207
  sessionSignFn,
379
208
  transactionSignFn,
380
- connection,
381
209
  publicKey,
382
- sessionPubKey: publicKey.toBytes(), // Use the public key derived from the private key as the session public key
210
+ sessionPubKey: publicKey.toBytes(),
383
211
  });
384
212
  } catch (error) {
385
213
  throw new NordError("Failed to create NordUser from private key", {
@@ -396,15 +224,9 @@ export class NordUser extends NordClient {
396
224
  * @throws {NordError} If required parameters are missing or operation fails
397
225
  */
398
226
  async getAssociatedTokenAccount(mint: PublicKey): Promise<PublicKey> {
399
- if (!this.getSolanaPublicKey()) {
400
- throw new NordError(
401
- "Solana public key is required to get associated token account",
402
- );
403
- }
404
-
405
227
  try {
406
228
  // Get the token program ID from the mint account
407
- const mintAccount = await this.connection.getAccountInfo(mint);
229
+ const mintAccount = await this.nord.solanaConnection.getAccountInfo(mint);
408
230
  if (!mintAccount) {
409
231
  throw new NordError("Mint account not found");
410
232
  }
@@ -422,7 +244,7 @@ export class NordUser extends NordClient {
422
244
 
423
245
  const associatedTokenAddress = await getAssociatedTokenAddress(
424
246
  mint,
425
- this.getSolanaPublicKey(),
247
+ this.publicKey,
426
248
  false,
427
249
  tokenProgramId,
428
250
  ASSOCIATED_TOKEN_PROGRAM_ID,
@@ -483,7 +305,7 @@ export class NordUser extends NordClient {
483
305
 
484
306
  const mint = new PublicKey(tokenInfo.mint);
485
307
  const fromAccount = await this.getAssociatedTokenAccount(mint);
486
- const payer = this.getSolanaPublicKey();
308
+ const payer = this.publicKey;
487
309
 
488
310
  const { ix, extraSigner } = await this.nord.protonClient.buildDepositIx({
489
311
  payer,
@@ -493,7 +315,8 @@ export class NordUser extends NordClient {
493
315
  sourceTokenAccount: fromAccount,
494
316
  });
495
317
 
496
- const { blockhash } = await this.connection.getLatestBlockhash();
318
+ const { blockhash } =
319
+ await this.nord.solanaConnection.getLatestBlockhash();
497
320
  const tx = new Transaction();
498
321
 
499
322
  tx.add(ix);
@@ -503,7 +326,7 @@ export class NordUser extends NordClient {
503
326
  const signedTx = await this.transactionSignFn(tx);
504
327
  signedTx.partialSign(extraSigner);
505
328
 
506
- const signature = await this.connection.sendRawTransaction(
329
+ const signature = await this.nord.solanaConnection.sendRawTransaction(
507
330
  signedTx.serialize(),
508
331
  sendOptions,
509
332
  );
@@ -523,7 +346,7 @@ export class NordUser extends NordClient {
523
346
  * @returns Nonce as number
524
347
  */
525
348
  getNonce(): number {
526
- return this.nextActionNonce();
349
+ return ++this.nonce;
527
350
  }
528
351
 
529
352
  private async submitSessionAction(
@@ -661,12 +484,12 @@ export class NordUser extends NordClient {
661
484
  */
662
485
  async refreshSession(): Promise<void> {
663
486
  const result = await createSession(
664
- this.nord.webServerUrl,
665
- this.walletSignFn,
487
+ this.nord.httpClient,
488
+ this.transactionSignFn,
666
489
  await this.nord.getTimestamp(),
667
490
  this.getNonce(),
668
491
  {
669
- userPubkey: optExpect(this.publicKey.toBytes(), "No user's public key"),
492
+ userPubkey: this.publicKey,
670
493
  sessionPubkey: this.sessionPubKey,
671
494
  },
672
495
  );
@@ -681,11 +504,12 @@ export class NordUser extends NordClient {
681
504
  async revokeSession(sessionId: BigIntValue): Promise<void> {
682
505
  try {
683
506
  await revokeSession(
684
- this.nord.webServerUrl,
685
- this.walletSignFn,
507
+ this.nord.httpClient,
508
+ this.transactionSignFn,
686
509
  await this.nord.getTimestamp(),
687
510
  this.getNonce(),
688
511
  {
512
+ userPubkey: this.publicKey,
689
513
  sessionId,
690
514
  },
691
515
  );
@@ -751,29 +575,57 @@ export class NordUser extends NordClient {
751
575
  /**
752
576
  * Place an order on the exchange
753
577
  *
754
- * @param params - Order parameters
578
+ * @param marketId - Target market identifier
579
+ * @param side - Order side
580
+ * @param fillMode - Fill mode (limit, market, etc.)
581
+ * @param isReduceOnly - Reduce-only flag
582
+ * @param size - Base size to place
583
+ * @param price - Limit price
584
+ * @param quoteSize - Quote-sized order representation
585
+ * @param accountId - Account executing the order
586
+ * @param clientOrderId - Optional client-specified identifier
755
587
  * @returns Object containing actionId, orderId (if posted), fills, and clientOrderId
756
588
  * @throws {NordError} If the operation fails
757
589
  */
758
- async placeOrder(params: PlaceOrderParams): Promise<{
590
+ async placeOrder({
591
+ marketId,
592
+ side,
593
+ fillMode,
594
+ isReduceOnly,
595
+ size,
596
+ price,
597
+ quoteSize,
598
+ accountId,
599
+ clientOrderId,
600
+ }: Readonly<{
601
+ marketId: number;
602
+ side: Side;
603
+ fillMode: FillMode;
604
+ isReduceOnly: boolean;
605
+ size?: Decimal.Value;
606
+ price?: Decimal.Value;
607
+ quoteSize?: QuoteSize;
608
+ accountId?: number;
609
+ clientOrderId?: BigIntValue;
610
+ }>): Promise<{
759
611
  actionId: bigint;
760
612
  orderId?: bigint;
761
613
  fills: proto.Receipt_Trade[];
762
614
  }> {
763
615
  try {
764
616
  this.checkSessionValidity();
765
- const market = findMarket(this.nord.markets, params.marketId);
617
+ const market = findMarket(this.nord.markets, marketId);
766
618
  if (!market) {
767
- throw new NordError(`Market with ID ${params.marketId} not found`);
619
+ throw new NordError(`Market with ID ${marketId} not found`);
768
620
  }
769
621
  const sessionId = optExpect(this.sessionId, "No session");
770
- const price = toScaledU64(params.price ?? 0, market.priceDecimals);
771
- const size = toScaledU64(params.size ?? 0, market.sizeDecimals);
772
- const scaledQuote = params.quoteSize
773
- ? params.quoteSize.toWire(market.priceDecimals, market.sizeDecimals)
622
+ const scaledPrice = toScaledU64(price ?? 0, market.priceDecimals);
623
+ const scaledSize = toScaledU64(size ?? 0, market.sizeDecimals);
624
+ const scaledQuote = quoteSize
625
+ ? quoteSize.toWire(market.priceDecimals, market.sizeDecimals)
774
626
  : undefined;
775
627
  assert(
776
- price > 0n || size > 0n || scaledQuote !== undefined,
628
+ scaledPrice > 0n || scaledSize > 0n || scaledQuote !== undefined,
777
629
  "OrderLimit must include at least one of: size, price, or quoteSize",
778
630
  );
779
631
 
@@ -781,13 +633,13 @@ export class NordUser extends NordClient {
781
633
  case: "placeOrder",
782
634
  value: create(proto.Action_PlaceOrderSchema, {
783
635
  sessionId: BigInt(sessionId),
784
- senderAccountId: params.accountId,
785
- marketId: params.marketId,
786
- side: params.side === Side.Bid ? proto.Side.BID : proto.Side.ASK,
787
- fillMode: fillModeToProtoFillMode(params.fillMode),
788
- isReduceOnly: params.isReduceOnly,
789
- price,
790
- size,
636
+ senderAccountId: accountId,
637
+ marketId,
638
+ side: side === Side.Bid ? proto.Side.BID : proto.Side.ASK,
639
+ fillMode: fillModeToProtoFillMode(fillMode),
640
+ isReduceOnly,
641
+ price: scaledPrice,
642
+ size: scaledSize,
791
643
  quoteSize:
792
644
  scaledQuote === undefined
793
645
  ? undefined
@@ -796,9 +648,7 @@ export class NordUser extends NordClient {
796
648
  price: scaledQuote.price,
797
649
  }),
798
650
  clientOrderId:
799
- params.clientOrderId === undefined
800
- ? undefined
801
- : BigInt(params.clientOrderId),
651
+ clientOrderId === undefined ? undefined : BigInt(clientOrderId),
802
652
  }),
803
653
  });
804
654
  expectReceiptKind(receipt, "placeOrderResult", "place order");
@@ -857,48 +707,67 @@ export class NordUser extends NordClient {
857
707
  /**
858
708
  * Add a trigger for the current session
859
709
  *
860
- * @param params - Trigger parameters including market, side, and prices
710
+ * @param marketId - Market to watch
711
+ * @param side - Order side for the trigger
712
+ * @param kind - Stop-loss or take-profit trigger type
713
+ * @param triggerPrice - Price that activates the trigger
714
+ * @param limitPrice - Limit price placed once the trigger fires
715
+ * @param accountId - Account executing the trigger
861
716
  * @returns Object containing the actionId of the submitted trigger
862
717
  * @throws {NordError} If the operation fails
863
718
  */
864
- async addTrigger(params: AddTriggerParams): Promise<{ actionId: bigint }> {
719
+ async addTrigger({
720
+ marketId,
721
+ side,
722
+ kind,
723
+ triggerPrice,
724
+ limitPrice,
725
+ accountId,
726
+ }: Readonly<{
727
+ marketId: number;
728
+ side: Side;
729
+ kind: TriggerKind;
730
+ triggerPrice: Decimal.Value;
731
+ limitPrice?: Decimal.Value;
732
+ accountId?: number;
733
+ }>): Promise<{ actionId: bigint }> {
865
734
  try {
866
735
  this.checkSessionValidity();
867
- const market = findMarket(this.nord.markets, params.marketId);
736
+ const market = findMarket(this.nord.markets, marketId);
868
737
  if (!market) {
869
- throw new NordError(`Market with ID ${params.marketId} not found`);
738
+ throw new NordError(`Market with ID ${marketId} not found`);
870
739
  }
871
- const triggerPrice = toScaledU64(
872
- params.triggerPrice,
740
+ const scaledTriggerPrice = toScaledU64(
741
+ triggerPrice,
873
742
  market.priceDecimals,
874
743
  );
875
- assert(triggerPrice > 0n, "Trigger price must be positive");
876
- const limitPrice =
877
- params.limitPrice === undefined
744
+ assert(scaledTriggerPrice > 0n, "Trigger price must be positive");
745
+ const scaledLimitPrice =
746
+ limitPrice === undefined
878
747
  ? undefined
879
- : toScaledU64(params.limitPrice, market.priceDecimals);
880
- if (limitPrice !== undefined) {
881
- assert(limitPrice > 0n, "Limit price must be positive");
748
+ : toScaledU64(limitPrice, market.priceDecimals);
749
+ if (scaledLimitPrice !== undefined) {
750
+ assert(scaledLimitPrice > 0n, "Limit price must be positive");
882
751
  }
883
752
  const key = create(proto.TriggerKeySchema, {
884
753
  kind:
885
- params.kind === TriggerKind.StopLoss
754
+ kind === TriggerKind.StopLoss
886
755
  ? proto.TriggerKind.STOP_LOSS
887
756
  : proto.TriggerKind.TAKE_PROFIT,
888
- side: params.side === Side.Bid ? proto.Side.BID : proto.Side.ASK,
757
+ side: side === Side.Bid ? proto.Side.BID : proto.Side.ASK,
889
758
  });
890
759
  const prices = create(proto.Action_TriggerPricesSchema, {
891
- triggerPrice,
892
- limitPrice,
760
+ triggerPrice: scaledTriggerPrice,
761
+ limitPrice: scaledLimitPrice,
893
762
  });
894
763
  const receipt = await this.submitSessionAction({
895
764
  case: "addTrigger",
896
765
  value: create(proto.Action_AddTriggerSchema, {
897
766
  sessionId: BigInt(optExpect(this.sessionId, "No session")),
898
- marketId: params.marketId,
767
+ marketId,
899
768
  key,
900
769
  prices,
901
- accountId: params.accountId,
770
+ accountId,
902
771
  }),
903
772
  });
904
773
  expectReceiptKind(receipt, "triggerAdded", "add trigger");
@@ -911,33 +780,44 @@ export class NordUser extends NordClient {
911
780
  /**
912
781
  * Remove a trigger for the current session
913
782
  *
914
- * @param params - Trigger parameters identifying the trigger to remove
783
+ * @param marketId - Market the trigger belongs to
784
+ * @param side - Order side for the trigger
785
+ * @param kind - Stop-loss or take-profit trigger type
786
+ * @param accountId - Account executing the trigger
915
787
  * @returns Object containing the actionId of the removal action
916
788
  * @throws {NordError} If the operation fails
917
789
  */
918
- async removeTrigger(
919
- params: RemoveTriggerParams,
920
- ): Promise<{ actionId: bigint }> {
790
+ async removeTrigger({
791
+ marketId,
792
+ side,
793
+ kind,
794
+ accountId,
795
+ }: Readonly<{
796
+ marketId: number;
797
+ side: Side;
798
+ kind: TriggerKind;
799
+ accountId?: number;
800
+ }>): Promise<{ actionId: bigint }> {
921
801
  try {
922
802
  this.checkSessionValidity();
923
- const market = findMarket(this.nord.markets, params.marketId);
803
+ const market = findMarket(this.nord.markets, marketId);
924
804
  if (!market) {
925
- throw new NordError(`Market with ID ${params.marketId} not found`);
805
+ throw new NordError(`Market with ID ${marketId} not found`);
926
806
  }
927
807
  const key = create(proto.TriggerKeySchema, {
928
808
  kind:
929
- params.kind === TriggerKind.StopLoss
809
+ kind === TriggerKind.StopLoss
930
810
  ? proto.TriggerKind.STOP_LOSS
931
811
  : proto.TriggerKind.TAKE_PROFIT,
932
- side: params.side === Side.Bid ? proto.Side.BID : proto.Side.ASK,
812
+ side: side === Side.Bid ? proto.Side.BID : proto.Side.ASK,
933
813
  });
934
814
  const receipt = await this.submitSessionAction({
935
815
  case: "removeTrigger",
936
816
  value: create(proto.Action_RemoveTriggerSchema, {
937
817
  sessionId: BigInt(optExpect(this.sessionId, "No session")),
938
- marketId: params.marketId,
818
+ marketId,
939
819
  key,
940
- accountId: params.accountId,
820
+ accountId,
941
821
  }),
942
822
  });
943
823
  expectReceiptKind(receipt, "triggerRemoved", "remove trigger");
@@ -950,16 +830,32 @@ export class NordUser extends NordClient {
950
830
  /**
951
831
  * Transfer tokens to another account
952
832
  *
953
- * @param params - Transfer parameters
833
+ * @param tokenId - Token identifier to move
834
+ * @param amount - Amount to transfer
835
+ * @param fromAccountId - Source account id
836
+ * @param toAccountId - Destination account id
954
837
  * @throws {NordError} If the operation fails
955
838
  */
956
- async transferToAccount(params: TransferParams): Promise<void> {
839
+ async transferToAccount({
840
+ tokenId,
841
+ amount,
842
+ fromAccountId,
843
+ toAccountId,
844
+ }: Readonly<{
845
+ tokenId: number;
846
+ amount: Decimal.Value;
847
+ fromAccountId?: number;
848
+ toAccountId?: number;
849
+ }>): Promise<{
850
+ actionId: bigint;
851
+ newAccountId?: number;
852
+ }> {
957
853
  try {
958
854
  this.checkSessionValidity();
959
- const token = findToken(this.nord.tokens, params.tokenId);
855
+ const token = findToken(this.nord.tokens, tokenId);
960
856
 
961
- const amount = toScaledU64(params.amount, token.decimals);
962
- if (amount <= 0n) {
857
+ const scaledAmount = toScaledU64(amount, token.decimals);
858
+ if (scaledAmount <= 0n) {
963
859
  throw new NordError("Transfer amount must be positive");
964
860
  }
965
861
 
@@ -967,13 +863,25 @@ export class NordUser extends NordClient {
967
863
  case: "transfer",
968
864
  value: create(proto.Action_TransferSchema, {
969
865
  sessionId: BigInt(optExpect(this.sessionId, "No session")),
970
- fromAccountId: optExpect(params.fromAccountId, "No source account"),
971
- toAccountId: optExpect(params.toAccountId, "No target account"),
972
- tokenId: params.tokenId,
973
- amount,
866
+ fromAccountId: optExpect(fromAccountId, "No source account"),
867
+ toAccountId: optExpect(toAccountId, "No target account"),
868
+ tokenId,
869
+ amount: scaledAmount,
974
870
  }),
975
871
  });
976
872
  expectReceiptKind(receipt, "transferred", "transfer tokens");
873
+ if (receipt.kind.value.accountCreated) {
874
+ assert(
875
+ receipt.kind.value.toUserAccount !== undefined,
876
+ `toAccount must be defined on new account on ${receipt.kind.value}`,
877
+ );
878
+ return {
879
+ actionId: receipt.actionId,
880
+ newAccountId: receipt.kind.value.toUserAccount,
881
+ };
882
+ } else {
883
+ return { actionId: receipt.actionId };
884
+ }
977
885
  } catch (error) {
978
886
  throw new NordError("Failed to transfer tokens", { cause: error });
979
887
  }
@@ -1037,7 +945,7 @@ export class NordUser extends NordClient {
1037
945
  });
1038
946
 
1039
947
  const result = await atomic(
1040
- this.nord.webServerUrl,
948
+ this.nord.httpClient,
1041
949
  this.sessionSignFn,
1042
950
  await this.nord.getTimestamp(),
1043
951
  this.getNonce(),
@@ -1124,24 +1032,18 @@ export class NordUser extends NordClient {
1124
1032
  maxRetries = 3,
1125
1033
  } = options;
1126
1034
 
1127
- if (!this.connection || !this.getSolanaPublicKey()) {
1128
- throw new NordError(
1129
- "Connection and Solana public key are required to get Solana balances",
1130
- );
1131
- }
1132
-
1133
1035
  const balances: { [symbol: string]: number } = {};
1134
1036
  const tokenAccounts: { [symbol: string]: string } = {};
1135
1037
 
1136
1038
  try {
1137
1039
  // Get SOL balance (native token)
1138
1040
  const solBalance = await this.retryWithBackoff(
1139
- () => this.connection!.getBalance(this.getSolanaPublicKey()),
1041
+ () => this.nord.solanaConnection.getBalance(this.publicKey),
1140
1042
  maxRetries,
1141
1043
  );
1142
1044
  balances["SOL"] = solBalance / 1e9; // Convert lamports to SOL
1143
1045
  if (includeTokenAccounts) {
1144
- tokenAccounts["SOL"] = this.getSolanaPublicKey().toString();
1046
+ tokenAccounts["SOL"] = this.publicKey.toString();
1145
1047
  }
1146
1048
 
1147
1049
  // Get SPL token balances using mintAddr from Nord tokens
@@ -1157,8 +1059,7 @@ export class NordUser extends NordClient {
1157
1059
  try {
1158
1060
  const mint = new PublicKey(token.mintAddr);
1159
1061
  const associatedTokenAddress = await this.retryWithBackoff(
1160
- () =>
1161
- getAssociatedTokenAddress(mint, this.getSolanaPublicKey()),
1062
+ () => getAssociatedTokenAddress(mint, this.publicKey),
1162
1063
  maxRetries,
1163
1064
  );
1164
1065
 
@@ -1169,7 +1070,7 @@ export class NordUser extends NordClient {
1169
1070
  try {
1170
1071
  const tokenBalance = await this.retryWithBackoff(
1171
1072
  () =>
1172
- this.connection.getTokenAccountBalance(
1073
+ this.nord.solanaConnection.getTokenAccountBalance(
1173
1074
  associatedTokenAddress,
1174
1075
  ),
1175
1076
  maxRetries,
@@ -1208,4 +1109,14 @@ export class NordUser extends NordClient {
1208
1109
  });
1209
1110
  }
1210
1111
  }
1112
+
1113
+ protected async submitSignedAction(
1114
+ kind: proto.Action["kind"],
1115
+ makeSignedMessage: (message: Uint8Array) => Promise<Uint8Array>,
1116
+ ): Promise<proto.Receipt> {
1117
+ const nonce = this.getNonce();
1118
+ const currentTimestamp = await this.nord.getTimestamp();
1119
+ const action = createAction(currentTimestamp, nonce, kind);
1120
+ return sendAction(this.nord.httpClient, makeSignedMessage, action);
1121
+ }
1211
1122
  }