@n1xyz/nord-ts 0.1.7 → 0.1.8

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 (84) hide show
  1. package/dist/actions.js +39 -82
  2. package/dist/bundle.js +79181 -0
  3. package/dist/client/Nord.d.ts +2 -2
  4. package/dist/client/Nord.js +46 -78
  5. package/dist/client/NordAdmin.d.ts +2 -2
  6. package/dist/client/NordAdmin.js +57 -89
  7. package/dist/client/NordUser.js +118 -147
  8. package/dist/const.js +5 -8
  9. package/dist/error.js +7 -5
  10. package/dist/gen/nord_pb.js +88 -92
  11. package/dist/gen/openapi.d.ts +5 -6
  12. package/dist/gen/openapi.js +1 -2
  13. package/dist/index.js +10 -49
  14. package/dist/types.d.ts +1 -0
  15. package/dist/types.js +21 -60
  16. package/dist/utils.js +38 -86
  17. package/dist/websocket/NordWebSocketClient.js +12 -17
  18. package/dist/websocket/Subscriber.js +6 -7
  19. package/dist/websocket/events.js +1 -2
  20. package/dist/websocket/index.js +10 -15
  21. package/package.json +2 -3
  22. package/dist/api/client.d.ts +0 -14
  23. package/dist/api/client.js +0 -45
  24. package/dist/bridge/client.d.ts +0 -151
  25. package/dist/bridge/client.js +0 -434
  26. package/dist/bridge/const.d.ts +0 -23
  27. package/dist/bridge/const.js +0 -47
  28. package/dist/bridge/index.d.ts +0 -4
  29. package/dist/bridge/index.js +0 -23
  30. package/dist/bridge/types.d.ts +0 -120
  31. package/dist/bridge/types.js +0 -18
  32. package/dist/bridge/utils.d.ts +0 -64
  33. package/dist/bridge/utils.js +0 -131
  34. package/dist/gen/common.d.ts +0 -68
  35. package/dist/gen/common.js +0 -215
  36. package/dist/gen/nord.d.ts +0 -882
  37. package/dist/gen/nord.js +0 -6520
  38. package/dist/idl/bridge.d.ts +0 -569
  39. package/dist/idl/bridge.js +0 -8
  40. package/dist/idl/bridge.json +0 -1506
  41. package/dist/idl/index.d.ts +0 -607
  42. package/dist/idl/index.js +0 -8
  43. package/dist/nord/api/actions.d.ts +0 -126
  44. package/dist/nord/api/actions.js +0 -397
  45. package/dist/nord/api/core.d.ts +0 -16
  46. package/dist/nord/api/core.js +0 -81
  47. package/dist/nord/api/market.d.ts +0 -36
  48. package/dist/nord/api/market.js +0 -96
  49. package/dist/nord/api/metrics.d.ts +0 -67
  50. package/dist/nord/api/metrics.js +0 -229
  51. package/dist/nord/api/queries.d.ts +0 -46
  52. package/dist/nord/api/queries.js +0 -109
  53. package/dist/nord/api/triggers.d.ts +0 -7
  54. package/dist/nord/api/triggers.js +0 -38
  55. package/dist/nord/client/Nord.d.ts +0 -396
  56. package/dist/nord/client/Nord.js +0 -747
  57. package/dist/nord/client/NordAdmin.d.ts +0 -259
  58. package/dist/nord/client/NordAdmin.js +0 -395
  59. package/dist/nord/client/NordClient.d.ts +0 -33
  60. package/dist/nord/client/NordClient.js +0 -45
  61. package/dist/nord/client/NordUser.d.ts +0 -362
  62. package/dist/nord/client/NordUser.js +0 -781
  63. package/dist/nord/index.d.ts +0 -11
  64. package/dist/nord/index.js +0 -36
  65. package/dist/nord/models/Subscriber.d.ts +0 -37
  66. package/dist/nord/models/Subscriber.js +0 -25
  67. package/dist/nord/utils/NordError.d.ts +0 -35
  68. package/dist/nord/utils/NordError.js +0 -49
  69. package/src/actions.ts +0 -333
  70. package/src/client/Nord.ts +0 -934
  71. package/src/client/NordAdmin.ts +0 -484
  72. package/src/client/NordUser.ts +0 -1122
  73. package/src/const.ts +0 -34
  74. package/src/error.ts +0 -76
  75. package/src/gen/.gitkeep +0 -0
  76. package/src/gen/nord_pb.ts +0 -5053
  77. package/src/gen/openapi.ts +0 -2904
  78. package/src/index.ts +0 -11
  79. package/src/types.ts +0 -327
  80. package/src/utils.ts +0 -266
  81. package/src/websocket/NordWebSocketClient.ts +0 -316
  82. package/src/websocket/Subscriber.ts +0 -56
  83. package/src/websocket/events.ts +0 -31
  84. package/src/websocket/index.ts +0 -105
@@ -1,781 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.NordUser = void 0;
37
- const spl_token_1 = require("@solana/spl-token");
38
- const web3_js_1 = require("@solana/web3.js");
39
- const ed = __importStar(require("@noble/ed25519"));
40
- const sha512_1 = require("@noble/hashes/sha512");
41
- ed.etc.sha512Sync = sha512_1.sha512;
42
- const proton_1 = require("@n1xyz/proton");
43
- const types_1 = require("../../types");
44
- const proto = __importStar(require("../../gen/nord_pb"));
45
- const utils_1 = require("../../utils");
46
- const protobuf_1 = require("@bufbuild/protobuf");
47
- const actions_1 = require("../api/actions");
48
- const NordError_1 = require("../utils/NordError");
49
- const NordClient_1 = require("./NordClient");
50
- /**
51
- * User class for interacting with the Nord protocol
52
- */
53
- class NordUser extends NordClient_1.NordClient {
54
- /**
55
- * Create a new NordUser instance
56
- *
57
- * @param params - Parameters for creating a NordUser
58
- * @throws {NordError} If required parameters are missing
59
- */
60
- constructor({ address, nord, publicKey, sessionPubKey, sessionSignFn, transactionSignFn, walletSignFn, connection, sessionId, }) {
61
- if (!walletSignFn) {
62
- throw new NordError_1.NordError("Wallet sign function is required");
63
- }
64
- if (!sessionSignFn) {
65
- throw new NordError_1.NordError("Session sign function is required");
66
- }
67
- if (!sessionPubKey) {
68
- throw new NordError_1.NordError("Session public key is required");
69
- }
70
- let parsedAddress;
71
- try {
72
- parsedAddress = new web3_js_1.PublicKey(address);
73
- }
74
- catch (error) {
75
- throw new NordError_1.NordError("Invalid Solana address", { cause: error });
76
- }
77
- super({
78
- nord,
79
- address: parsedAddress,
80
- walletSignFn,
81
- sessionSignFn,
82
- transactionSignFn,
83
- connection,
84
- sessionId,
85
- sessionPubKey,
86
- publicKey,
87
- });
88
- /** User balances by token symbol */
89
- this.balances = {};
90
- /** User positions by account ID */
91
- this.positions = {};
92
- /** User margins by account ID */
93
- this.margins = {};
94
- /** SPL token information */
95
- this.splTokenInfos = [];
96
- // Convert tokens from info endpoint to SPLTokenInfo
97
- if (this.nord.tokens && this.nord.tokens.length > 0) {
98
- this.splTokenInfos = this.nord.tokens.map((token) => ({
99
- mint: token.mintAddr, // Use mintAddr as mint
100
- precision: token.decimals,
101
- tokenId: token.tokenId,
102
- name: token.symbol,
103
- }));
104
- }
105
- }
106
- /**
107
- * Create a clone of this NordUser instance
108
- *
109
- * @returns A new NordUser instance with the same properties
110
- */
111
- clone() {
112
- const cloned = new NordUser({
113
- nord: this.nord,
114
- address: this.address.toBase58(),
115
- walletSignFn: this.walletSignFn,
116
- sessionSignFn: this.sessionSignFn,
117
- transactionSignFn: this.transactionSignFn,
118
- connection: this.connection,
119
- sessionPubKey: new Uint8Array(this.sessionPubKey),
120
- publicKey: this.publicKey,
121
- sessionId: this.sessionId,
122
- });
123
- // Copy other properties
124
- cloned.balances = { ...this.balances };
125
- cloned.positions = { ...this.positions };
126
- cloned.margins = { ...this.margins };
127
- cloned.accountIds = this.accountIds ? [...this.accountIds] : undefined;
128
- cloned.splTokenInfos = [...this.splTokenInfos];
129
- this.cloneClientState(cloned);
130
- return cloned;
131
- }
132
- /**
133
- * Create a NordUser from a private key
134
- *
135
- * @param nord - Nord instance
136
- * @param privateKey - Private key as string or Uint8Array
137
- * @param connection - Solana connection (optional)
138
- * @returns NordUser instance
139
- * @throws {NordError} If the private key is invalid
140
- */
141
- static fromPrivateKey(nord, privateKey, connection) {
142
- try {
143
- const keypair = (0, utils_1.keypairFromPrivateKey)(privateKey);
144
- const publicKey = keypair.publicKey;
145
- // Create a signing function that uses the keypair but doesn't expose it
146
- const walletSignFn = async (message) => {
147
- function toHex(buffer) {
148
- return Array.from(buffer)
149
- .map((byte) => byte.toString(16).padStart(2, "0"))
150
- .join("");
151
- }
152
- const messageBuffer = new TextEncoder().encode(toHex(message));
153
- // Use ed25519 to sign the message
154
- const signature = ed.sign(messageBuffer, keypair.secretKey.slice(0, 32));
155
- return signature;
156
- };
157
- const sessionSignFn = async (message) => {
158
- // Use ed25519 to sign the message
159
- return ed.sign(message, keypair.secretKey.slice(0, 32));
160
- };
161
- // Create a transaction signing function
162
- const transactionSignFn = async (transaction) => {
163
- // This is a basic implementation - actual implementation would depend on the transaction type
164
- if (transaction.sign) {
165
- // Solana transaction
166
- transaction.sign(keypair);
167
- return transaction;
168
- }
169
- // For other transaction types, would need specific implementation
170
- throw new NordError_1.NordError("Unsupported transaction type for signing");
171
- };
172
- return new NordUser({
173
- nord,
174
- address: publicKey.toBase58(),
175
- walletSignFn,
176
- sessionSignFn,
177
- transactionSignFn,
178
- connection,
179
- publicKey,
180
- sessionPubKey: publicKey.toBytes(), // Use the public key derived from the private key as the session public key
181
- });
182
- }
183
- catch (error) {
184
- throw new NordError_1.NordError("Failed to create NordUser from private key", {
185
- cause: error,
186
- });
187
- }
188
- }
189
- /**
190
- * Get the associated token account for a token mint
191
- *
192
- * @param mint - Token mint address
193
- * @returns Associated token account address
194
- * @throws {NordError} If required parameters are missing or operation fails
195
- */
196
- async getAssociatedTokenAccount(mint) {
197
- if (!this.getSolanaPublicKey()) {
198
- throw new NordError_1.NordError("Solana public key is required to get associated token account");
199
- }
200
- try {
201
- // Get the token program ID from the mint account
202
- const mintAccount = await this.connection.getAccountInfo(mint);
203
- if (!mintAccount) {
204
- throw new NordError_1.NordError("Mint account not found");
205
- }
206
- const tokenProgramId = mintAccount.owner;
207
- // Validate that the mint is owned by a supported SPL token program
208
- if (!tokenProgramId.equals(spl_token_1.TOKEN_PROGRAM_ID) &&
209
- !tokenProgramId.equals(spl_token_1.TOKEN_2022_PROGRAM_ID)) {
210
- throw new NordError_1.NordError("Mint Account is not owned by a supported SPL token program");
211
- }
212
- const associatedTokenAddress = await (0, spl_token_1.getAssociatedTokenAddress)(mint, this.getSolanaPublicKey(), false, tokenProgramId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
213
- return associatedTokenAddress;
214
- }
215
- catch (error) {
216
- throw new NordError_1.NordError("Failed to get associated token account", {
217
- cause: error,
218
- });
219
- }
220
- }
221
- /**
222
- * Deposit SPL tokens to the app
223
- *
224
- * @param amount - Amount to deposit
225
- * @param tokenId - Token ID
226
- * @param recipient - Recipient address; defaults to the user's address
227
- * @returns Transaction signature
228
- * @deprecated Use deposit instead
229
- * @throws {NordError} If required parameters are missing or operation fails
230
- */
231
- async depositSpl(amount, tokenId, recipient) {
232
- return this.deposit({ amount, tokenId, recipient });
233
- }
234
- /**
235
- * Deposit SPL tokens to the app
236
- *
237
- * @param amount - Amount to deposit
238
- * @param tokenId - Token ID
239
- * @param recipient - Recipient address; defaults to the user's address
240
- * @param sendOptions - Send options for .sendTransaction
241
- * @returns Transaction signature
242
- * @throws {NordError} If required parameters are missing or operation fails
243
- */
244
- async deposit({ amount, tokenId, recipient, sendOptions, }) {
245
- try {
246
- // Find the token info
247
- const tokenInfo = this.splTokenInfos.find((t) => t.tokenId === tokenId);
248
- if (!tokenInfo) {
249
- throw new NordError_1.NordError(`Token with ID ${tokenId} not found`);
250
- }
251
- const mint = new web3_js_1.PublicKey(tokenInfo.mint);
252
- const fromAccount = await this.getAssociatedTokenAccount(mint);
253
- const payer = this.getSolanaPublicKey();
254
- const { ix, extraSigner } = await this.nord.protonClient.buildDepositIx({
255
- payer,
256
- recipient: recipient ?? payer,
257
- quantAmount: (0, proton_1.floatToScaledBigIntLossy)(amount, tokenInfo.precision),
258
- mint,
259
- sourceTokenAccount: fromAccount,
260
- });
261
- const { blockhash } = await this.connection.getLatestBlockhash();
262
- const tx = new web3_js_1.Transaction();
263
- tx.add(ix);
264
- tx.recentBlockhash = blockhash;
265
- tx.feePayer = payer;
266
- const signedTx = await this.transactionSignFn(tx);
267
- signedTx.partialSign(extraSigner);
268
- const signature = await this.connection.sendRawTransaction(signedTx.serialize(), sendOptions);
269
- return signature;
270
- }
271
- catch (error) {
272
- throw new NordError_1.NordError(`Failed to deposit ${amount} of token ID ${tokenId}`, { cause: error });
273
- }
274
- }
275
- /**
276
- * Get a new nonce for actions
277
- *
278
- * @returns Nonce as number
279
- */
280
- getNonce() {
281
- return this.nextActionNonce();
282
- }
283
- async submitSessionAction(kind) {
284
- return this.submitSignedAction(kind, async (message) => {
285
- const signature = await this.sessionSignFn(message);
286
- const signed = new Uint8Array(message.length + signature.length);
287
- signed.set(message);
288
- signed.set(signature, message.length);
289
- return signed;
290
- });
291
- }
292
- /**
293
- * Update account IDs for this user
294
- *
295
- * @throws {NordError} If the operation fails
296
- */
297
- async updateAccountId() {
298
- try {
299
- if (!this.publicKey) {
300
- throw new NordError_1.NordError("Public key is required to update account ID");
301
- }
302
- const resp = await this.nord.getUser({
303
- pubkey: this.publicKey.toBase58(),
304
- });
305
- if (!resp) {
306
- throw new NordError_1.NordError(`User ${this.publicKey.toBase58()} not found`);
307
- }
308
- this.accountIds = resp.accountIds;
309
- }
310
- catch (error) {
311
- throw new NordError_1.NordError("Failed to update account ID", { cause: error });
312
- }
313
- }
314
- /**
315
- * Fetch user information including balances and orders
316
- *
317
- * @throws {NordError} If the operation fails
318
- */
319
- async fetchInfo() {
320
- if (this.accountIds !== undefined) {
321
- const accountsData = await Promise.all(this.accountIds.map(async (accountId) => {
322
- const response = await (0, utils_1.checkedFetch)(`${this.nord.webServerUrl}/account/${accountId}`);
323
- const accountData = (await response.json());
324
- // Ensure we have the correct accountId
325
- return {
326
- ...accountData,
327
- accountId,
328
- };
329
- }));
330
- for (const accountData of accountsData) {
331
- // Process balances
332
- this.balances[accountData.accountId] = [];
333
- for (const balance of accountData.balances) {
334
- this.balances[accountData.accountId].push({
335
- accountId: accountData.accountId,
336
- balance: balance.amount,
337
- symbol: balance.token,
338
- });
339
- }
340
- // Process positions
341
- this.positions[accountData.accountId] = accountData.positions;
342
- // Process margins
343
- this.margins[accountData.accountId] = accountData.margins;
344
- }
345
- }
346
- }
347
- /**
348
- * Refresh the user's session
349
- *
350
- * @throws {NordError} If the operation fails
351
- */
352
- async refreshSession() {
353
- const result = await (0, actions_1.createSession)(this.nord.webServerUrl, this.walletSignFn, await this.nord.getTimestamp(), this.getNonce(), {
354
- userPubkey: (0, utils_1.optExpect)(this.publicKey.toBytes(), "No user's public key"),
355
- sessionPubkey: this.sessionPubKey,
356
- });
357
- this.sessionId = result.sessionId;
358
- }
359
- /**
360
- * Revoke a session
361
- *
362
- * @param sessionId - Session ID to revoke
363
- * @throws {NordError} If the operation fails
364
- */
365
- async revokeSession(sessionId) {
366
- try {
367
- await (0, actions_1.revokeSession)(this.nord.webServerUrl, this.walletSignFn, await this.nord.getTimestamp(), this.getNonce(), {
368
- sessionId,
369
- });
370
- }
371
- catch (error) {
372
- throw new NordError_1.NordError(`Failed to revoke session ${sessionId}`, {
373
- cause: error,
374
- });
375
- }
376
- }
377
- /**
378
- * Checks if the session is valid
379
- * @private
380
- * @throws {NordError} If the session is not valid
381
- */
382
- checkSessionValidity() {
383
- if (this.sessionId === undefined || this.sessionId === BigInt(0)) {
384
- throw new NordError_1.NordError("Invalid or empty session ID. Please create or refresh your session.");
385
- }
386
- }
387
- /**
388
- * Withdraw tokens from the exchange
389
- *
390
- * @param tokenId - Token ID to withdraw
391
- * @param amount - Amount to withdraw
392
- * @throws {NordError} If the operation fails
393
- */
394
- async withdraw({ amount, tokenId, }) {
395
- try {
396
- this.checkSessionValidity();
397
- const token = (0, utils_1.findToken)(this.nord.tokens, tokenId);
398
- const scaledAmount = (0, utils_1.toScaledU64)(amount, token.decimals);
399
- if (scaledAmount <= 0n) {
400
- throw new NordError_1.NordError("Withdraw amount must be positive");
401
- }
402
- const receipt = await this.submitSessionAction({
403
- case: "withdraw",
404
- value: (0, protobuf_1.create)(proto.Action_WithdrawSchema, {
405
- sessionId: BigInt((0, utils_1.optExpect)(this.sessionId, "No session")),
406
- tokenId,
407
- amount: scaledAmount,
408
- }),
409
- });
410
- (0, actions_1.expectReceiptKind)(receipt, "withdrawResult", "withdraw");
411
- return { actionId: receipt.actionId };
412
- }
413
- catch (error) {
414
- throw new NordError_1.NordError(`Failed to withdraw ${amount} of token ID ${tokenId}`, { cause: error });
415
- }
416
- }
417
- /**
418
- * Place an order on the exchange
419
- *
420
- * @param params - Order parameters
421
- * @returns Object containing actionId, orderId (if posted), fills, and clientOrderId
422
- * @throws {NordError} If the operation fails
423
- */
424
- async placeOrder(params) {
425
- try {
426
- this.checkSessionValidity();
427
- const market = (0, utils_1.findMarket)(this.nord.markets, params.marketId);
428
- if (!market) {
429
- throw new NordError_1.NordError(`Market with ID ${params.marketId} not found`);
430
- }
431
- const sessionId = (0, utils_1.optExpect)(this.sessionId, "No session");
432
- const price = (0, utils_1.toScaledU64)(params.price ?? 0, market.priceDecimals);
433
- const size = (0, utils_1.toScaledU64)(params.size ?? 0, market.sizeDecimals);
434
- const scaledQuote = params.quoteSize
435
- ? params.quoteSize.toWire(market.priceDecimals, market.sizeDecimals)
436
- : undefined;
437
- (0, utils_1.assert)(price > 0n || size > 0n || scaledQuote !== undefined, "OrderLimit must include at least one of: size, price, or quoteSize");
438
- const receipt = await this.submitSessionAction({
439
- case: "placeOrder",
440
- value: (0, protobuf_1.create)(proto.Action_PlaceOrderSchema, {
441
- sessionId: BigInt(sessionId),
442
- senderAccountId: params.accountId,
443
- marketId: params.marketId,
444
- side: params.side === types_1.Side.Bid ? proto.Side.BID : proto.Side.ASK,
445
- fillMode: (0, types_1.fillModeToProtoFillMode)(params.fillMode),
446
- isReduceOnly: params.isReduceOnly,
447
- price,
448
- size,
449
- quoteSize: scaledQuote === undefined
450
- ? undefined
451
- : (0, protobuf_1.create)(proto.QuoteSizeSchema, {
452
- size: scaledQuote.size,
453
- price: scaledQuote.price,
454
- }),
455
- clientOrderId: params.clientOrderId === undefined
456
- ? undefined
457
- : BigInt(params.clientOrderId),
458
- }),
459
- });
460
- (0, actions_1.expectReceiptKind)(receipt, "placeOrderResult", "place order");
461
- const result = receipt.kind.value;
462
- return {
463
- actionId: receipt.actionId,
464
- orderId: result.posted?.orderId,
465
- fills: result.fills,
466
- };
467
- }
468
- catch (error) {
469
- throw new NordError_1.NordError("Failed to place order", { cause: error });
470
- }
471
- }
472
- /**
473
- * Cancel an order
474
- *
475
- * @param orderId - Order ID to cancel
476
- * @param providedAccountId - Account ID that placed the order
477
- * @returns Object containing actionId, cancelled orderId, and accountId
478
- * @throws {NordError} If the operation fails
479
- */
480
- async cancelOrder(orderId, providedAccountId) {
481
- const accountId = providedAccountId != null ? providedAccountId : this.accountIds?.[0];
482
- try {
483
- this.checkSessionValidity();
484
- const receipt = await this.submitSessionAction({
485
- case: "cancelOrderById",
486
- value: (0, protobuf_1.create)(proto.Action_CancelOrderByIdSchema, {
487
- orderId: BigInt(orderId),
488
- sessionId: BigInt((0, utils_1.optExpect)(this.sessionId, "No session")),
489
- senderAccountId: accountId,
490
- }),
491
- });
492
- (0, actions_1.expectReceiptKind)(receipt, "cancelOrderResult", "cancel order");
493
- return {
494
- actionId: receipt.actionId,
495
- orderId: receipt.kind.value.orderId,
496
- accountId: receipt.kind.value.accountId,
497
- };
498
- }
499
- catch (error) {
500
- throw new NordError_1.NordError(`Failed to cancel order ${orderId}`, {
501
- cause: error,
502
- });
503
- }
504
- }
505
- /**
506
- * Add a trigger for the current session
507
- *
508
- * @param params - Trigger parameters including market, side, and prices
509
- * @returns Object containing the actionId of the submitted trigger
510
- * @throws {NordError} If the operation fails
511
- */
512
- async addTrigger(params) {
513
- try {
514
- this.checkSessionValidity();
515
- const market = (0, utils_1.findMarket)(this.nord.markets, params.marketId);
516
- if (!market) {
517
- throw new NordError_1.NordError(`Market with ID ${params.marketId} not found`);
518
- }
519
- const triggerPrice = (0, utils_1.toScaledU64)(params.triggerPrice, market.priceDecimals);
520
- (0, utils_1.assert)(triggerPrice > 0n, "Trigger price must be positive");
521
- const limitPrice = params.limitPrice === undefined
522
- ? undefined
523
- : (0, utils_1.toScaledU64)(params.limitPrice, market.priceDecimals);
524
- if (limitPrice !== undefined) {
525
- (0, utils_1.assert)(limitPrice > 0n, "Limit price must be positive");
526
- }
527
- const key = (0, protobuf_1.create)(proto.TriggerKeySchema, {
528
- kind: params.kind === types_1.TriggerKind.StopLoss
529
- ? proto.TriggerKind.STOP_LOSS
530
- : proto.TriggerKind.TAKE_PROFIT,
531
- side: params.side === types_1.Side.Bid ? proto.Side.BID : proto.Side.ASK,
532
- });
533
- const prices = (0, protobuf_1.create)(proto.Action_TriggerPricesSchema, {
534
- triggerPrice,
535
- limitPrice,
536
- });
537
- const receipt = await this.submitSessionAction({
538
- case: "addTrigger",
539
- value: (0, protobuf_1.create)(proto.Action_AddTriggerSchema, {
540
- sessionId: BigInt((0, utils_1.optExpect)(this.sessionId, "No session")),
541
- marketId: params.marketId,
542
- key,
543
- prices,
544
- accountId: params.accountId,
545
- }),
546
- });
547
- (0, actions_1.expectReceiptKind)(receipt, "triggerAdded", "add trigger");
548
- return { actionId: receipt.actionId };
549
- }
550
- catch (error) {
551
- throw new NordError_1.NordError("Failed to add trigger", { cause: error });
552
- }
553
- }
554
- /**
555
- * Remove a trigger for the current session
556
- *
557
- * @param params - Trigger parameters identifying the trigger to remove
558
- * @returns Object containing the actionId of the removal action
559
- * @throws {NordError} If the operation fails
560
- */
561
- async removeTrigger(params) {
562
- try {
563
- this.checkSessionValidity();
564
- const market = (0, utils_1.findMarket)(this.nord.markets, params.marketId);
565
- if (!market) {
566
- throw new NordError_1.NordError(`Market with ID ${params.marketId} not found`);
567
- }
568
- const key = (0, protobuf_1.create)(proto.TriggerKeySchema, {
569
- kind: params.kind === types_1.TriggerKind.StopLoss
570
- ? proto.TriggerKind.STOP_LOSS
571
- : proto.TriggerKind.TAKE_PROFIT,
572
- side: params.side === types_1.Side.Bid ? proto.Side.BID : proto.Side.ASK,
573
- });
574
- const receipt = await this.submitSessionAction({
575
- case: "removeTrigger",
576
- value: (0, protobuf_1.create)(proto.Action_RemoveTriggerSchema, {
577
- sessionId: BigInt((0, utils_1.optExpect)(this.sessionId, "No session")),
578
- marketId: params.marketId,
579
- key,
580
- accountId: params.accountId,
581
- }),
582
- });
583
- (0, actions_1.expectReceiptKind)(receipt, "triggerRemoved", "remove trigger");
584
- return { actionId: receipt.actionId };
585
- }
586
- catch (error) {
587
- throw new NordError_1.NordError("Failed to remove trigger", { cause: error });
588
- }
589
- }
590
- /**
591
- * Transfer tokens to another account
592
- *
593
- * @param params - Transfer parameters
594
- * @throws {NordError} If the operation fails
595
- */
596
- async transferToAccount(params) {
597
- try {
598
- this.checkSessionValidity();
599
- const token = (0, utils_1.findToken)(this.nord.tokens, params.tokenId);
600
- const amount = (0, utils_1.toScaledU64)(params.amount, token.decimals);
601
- if (amount <= 0n) {
602
- throw new NordError_1.NordError("Transfer amount must be positive");
603
- }
604
- const receipt = await this.submitSessionAction({
605
- case: "transfer",
606
- value: (0, protobuf_1.create)(proto.Action_TransferSchema, {
607
- sessionId: BigInt((0, utils_1.optExpect)(this.sessionId, "No session")),
608
- fromAccountId: (0, utils_1.optExpect)(params.fromAccountId, "No source account"),
609
- toAccountId: (0, utils_1.optExpect)(params.toAccountId, "No target account"),
610
- tokenId: params.tokenId,
611
- amount,
612
- }),
613
- });
614
- (0, actions_1.expectReceiptKind)(receipt, "transferred", "transfer tokens");
615
- }
616
- catch (error) {
617
- throw new NordError_1.NordError("Failed to transfer tokens", { cause: error });
618
- }
619
- }
620
- /**
621
- * Execute up to four place/cancel operations atomically.
622
- * Per Market:
623
- * 1. cancels can only be in the start (one cannot predict future order ids)
624
- * 2. intermediate trades can trade only
625
- * 3. placements go last
626
- *
627
- * Across Markets, order action can be any
628
- *
629
- * @param userActions array of user-friendly subactions
630
- * @param providedAccountId optional account performing the action (defaults to first account)
631
- */
632
- async atomic(userActions, providedAccountId) {
633
- try {
634
- this.checkSessionValidity();
635
- const accountId = providedAccountId != null ? providedAccountId : this.accountIds?.[0];
636
- if (accountId == null) {
637
- throw new NordError_1.NordError("Account ID is undefined. Make sure to call updateAccountId() before atomic operations.");
638
- }
639
- const apiActions = userActions.map((act) => {
640
- if (act.kind === "place") {
641
- const market = (0, utils_1.findMarket)(this.nord.markets, act.marketId);
642
- if (!market) {
643
- throw new NordError_1.NordError(`Market ${act.marketId} not found`);
644
- }
645
- return {
646
- kind: "place",
647
- marketId: act.marketId,
648
- side: act.side,
649
- fillMode: act.fillMode,
650
- isReduceOnly: act.isReduceOnly,
651
- sizeDecimals: market.sizeDecimals,
652
- priceDecimals: market.priceDecimals,
653
- size: act.size,
654
- price: act.price,
655
- quoteSize: act.quoteSize,
656
- clientOrderId: act.clientOrderId,
657
- };
658
- }
659
- return {
660
- kind: "cancel",
661
- orderId: act.orderId,
662
- };
663
- });
664
- const result = await (0, actions_1.atomic)(this.nord.webServerUrl, this.sessionSignFn, await this.nord.getTimestamp(), this.getNonce(), {
665
- sessionId: (0, utils_1.optExpect)(this.sessionId, "No session"),
666
- accountId: accountId,
667
- actions: apiActions,
668
- });
669
- return result;
670
- }
671
- catch (error) {
672
- throw new NordError_1.NordError("Atomic operation failed", { cause: error });
673
- }
674
- }
675
- /**
676
- * Helper function to retry a promise with exponential backoff
677
- *
678
- * @param fn - Function to retry
679
- * @param maxRetries - Maximum number of retries
680
- * @param initialDelay - Initial delay in milliseconds
681
- * @returns Promise result
682
- */
683
- async retryWithBackoff(fn, maxRetries = 3, initialDelay = 500) {
684
- let retries = 0;
685
- let delay = initialDelay;
686
- while (true) {
687
- try {
688
- return await fn();
689
- }
690
- catch (error) {
691
- if (retries >= maxRetries) {
692
- throw error;
693
- }
694
- // Check if error is rate limiting related
695
- const isRateLimitError = error instanceof Error &&
696
- (error.message.includes("rate limit") ||
697
- error.message.includes("429") ||
698
- error.message.includes("too many requests"));
699
- if (!isRateLimitError) {
700
- throw error;
701
- }
702
- retries++;
703
- await new Promise((resolve) => setTimeout(resolve, delay));
704
- delay *= 2; // Exponential backoff
705
- }
706
- }
707
- }
708
- /**
709
- * Get user's token balances on Solana chain using mintAddr
710
- *
711
- * @param options - Optional parameters
712
- * @param options.includeZeroBalances - Whether to include tokens with zero balance (default: true)
713
- * @param options.includeTokenAccounts - Whether to include token account addresses in the result (default: false)
714
- * @param options.maxConcurrent - Maximum number of concurrent requests (default: 5)
715
- * @param options.maxRetries - Maximum number of retries for rate-limited requests (default: 3)
716
- * @returns Object with token balances and optional token account addresses
717
- * @throws {NordError} If required parameters are missing or operation fails
718
- */
719
- async getSolanaBalances(options = {}) {
720
- const { includeZeroBalances = true, includeTokenAccounts = false, maxConcurrent = 5, maxRetries = 3, } = options;
721
- if (!this.connection || !this.getSolanaPublicKey()) {
722
- throw new NordError_1.NordError("Connection and Solana public key are required to get Solana balances");
723
- }
724
- const balances = {};
725
- const tokenAccounts = {};
726
- try {
727
- // Get SOL balance (native token)
728
- const solBalance = await this.retryWithBackoff(() => this.connection.getBalance(this.getSolanaPublicKey()), maxRetries);
729
- balances["SOL"] = solBalance / 1e9; // Convert lamports to SOL
730
- if (includeTokenAccounts) {
731
- tokenAccounts["SOL"] = this.getSolanaPublicKey().toString();
732
- }
733
- // Get SPL token balances using mintAddr from Nord tokens
734
- if (this.nord.tokens && this.nord.tokens.length > 0) {
735
- const tokens = this.nord.tokens.filter((token) => !!token.mintAddr);
736
- // Process tokens in batches to avoid rate limiting
737
- for (let i = 0; i < tokens.length; i += maxConcurrent) {
738
- const batch = tokens.slice(i, i + maxConcurrent);
739
- // Process batch in parallel
740
- const batchPromises = batch.map(async (token) => {
741
- try {
742
- const mint = new web3_js_1.PublicKey(token.mintAddr);
743
- const associatedTokenAddress = await this.retryWithBackoff(() => (0, spl_token_1.getAssociatedTokenAddress)(mint, this.getSolanaPublicKey()), maxRetries);
744
- if (includeTokenAccounts) {
745
- tokenAccounts[token.symbol] = associatedTokenAddress.toString();
746
- }
747
- try {
748
- const tokenBalance = await this.retryWithBackoff(() => this.connection.getTokenAccountBalance(associatedTokenAddress), maxRetries);
749
- const balance = Number(tokenBalance.value.uiAmount);
750
- if (balance > 0 || includeZeroBalances) {
751
- balances[token.symbol] = balance;
752
- }
753
- }
754
- catch {
755
- // Token account might not exist yet, set balance to 0
756
- if (includeZeroBalances) {
757
- balances[token.symbol] = 0;
758
- }
759
- }
760
- }
761
- catch (error) {
762
- console.error(`Error getting balance for token ${token.symbol}:`, error);
763
- if (includeZeroBalances) {
764
- balances[token.symbol] = 0;
765
- }
766
- }
767
- });
768
- // Wait for current batch to complete before processing next batch
769
- await Promise.all(batchPromises);
770
- }
771
- }
772
- return includeTokenAccounts ? { balances, tokenAccounts } : { balances };
773
- }
774
- catch (error) {
775
- throw new NordError_1.NordError("Failed to get Solana token balances", {
776
- cause: error,
777
- });
778
- }
779
- }
780
- }
781
- exports.NordUser = NordUser;