@n1xyz/nord-ts 0.1.4 → 0.1.6

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.
@@ -1,20 +1,35 @@
1
- import * as proto from "../../gen/nord_pb";
2
1
  import { create } from "@bufbuild/protobuf";
3
- import { checkPubKeyLength } from "../../utils";
4
- import { KeyType } from "../../types";
2
+ import { PublicKey } from "@solana/web3.js";
3
+ import * as proto from "../../gen/nord_pb";
4
+ import { decodeHex } from "../../utils";
5
+ import { createAction, sendAction, expectReceiptKind } from "../api/actions";
5
6
  import { NordError } from "../utils/NordError";
6
- import { NordClient } from "./NordClient";
7
- import type { NordClientParams } from "./NordClient";
8
- import type { NordUser } from "./NordUser";
7
+ import { Nord } from "./Nord";
8
+ import { FeeTierConfig } from "../../gen/nord_pb";
9
+
10
+ // NOTE: keep in sync with `acl.rs`.
11
+ // NOTE: don't forget `number` as int is internally a u32.
12
+ export enum AclRole {
13
+ FEE_MANAGER = 1 << 0,
14
+ MARKET_MANAGER = 1 << 1,
15
+ // TODO: unsure about this?
16
+ ADMIN = 1 << 31,
17
+ }
9
18
 
19
+ /**
20
+ * Parameters required to register a new token via the admin API.
21
+ */
10
22
  export interface CreateTokenParams {
11
23
  tokenDecimals: number;
12
24
  weightBps: number;
13
25
  viewSymbol: string;
14
26
  oracleSymbol: string;
15
- solAddr: Uint8Array;
27
+ mintAddr: PublicKey;
16
28
  }
17
29
 
30
+ /**
31
+ * Parameters used when creating a new market.
32
+ */
18
33
  export interface CreateMarketParams {
19
34
  sizeDecimals: number;
20
35
  priceDecimals: number;
@@ -27,114 +42,224 @@ export interface CreateMarketParams {
27
42
  baseTokenId: number;
28
43
  }
29
44
 
45
+ /**
46
+ * Configuration for updating the Wormhole guardian set on the oracle.
47
+ */
30
48
  export interface PythSetWormholeGuardiansParams {
31
49
  guardianSetIndex: number;
32
- addresses: Uint8Array[];
50
+ addresses: string[];
33
51
  }
34
52
 
53
+ /**
54
+ * Parameters required to link an oracle symbol to a Pyth price feed.
55
+ */
35
56
  export interface PythSetSymbolFeedParams {
36
57
  oracleSymbol: string;
37
- priceFeedId: Uint8Array;
58
+ priceFeedId: string;
38
59
  }
39
60
 
61
+ /**
62
+ * Identifies a market that should be frozen.
63
+ */
40
64
  export interface FreezeMarketParams {
41
65
  marketId: number;
42
66
  }
43
67
 
68
+ /**
69
+ * Identifies a market that should be unfrozen.
70
+ */
44
71
  export interface UnfreezeMarketParams {
45
72
  marketId: number;
46
73
  }
47
74
 
48
- export interface NordAdminParams extends NordClientParams {
49
- signFn: (message: Uint8Array) => Promise<Uint8Array>;
75
+ /**
76
+ * Parameters for adding a new fee tier.
77
+ */
78
+ export interface AddFeeTierParams {
79
+ config: FeeTierConfig;
50
80
  }
51
81
 
52
- export class NordAdmin extends NordClient {
53
- private readonly signFn: (message: Uint8Array) => Promise<Uint8Array>;
82
+ /**
83
+ * Parameters for updating an existing fee tier.
84
+ */
85
+ export interface UpdateFeeTierParams {
86
+ tierId: number;
87
+ config: FeeTierConfig;
88
+ }
54
89
 
55
- constructor(params: NordAdminParams) {
56
- const { signFn: adminSignFn, ...clientParams } = params;
57
- super(clientParams);
58
- this.signFn = adminSignFn;
59
- }
90
+ /**
91
+ * Administrative client capable of submitting privileged configuration actions.
92
+ */
93
+ export class NordAdmin {
94
+ private readonly nord: Nord;
95
+ private readonly admin: PublicKey;
96
+ private readonly signFn: (x: Uint8Array) => Promise<Uint8Array>;
60
97
 
61
- clone(): NordAdmin {
62
- const copy = new NordAdmin({
63
- nord: this.nord,
64
- address: this.address,
65
- walletSignFn: this.walletSignFn,
66
- sessionSignFn: this.sessionSignFn,
67
- transactionSignFn: this.transactionSignFn,
68
- connection: this.connection,
69
- sessionId: this.sessionId,
70
- sessionPubKey: new Uint8Array(this.sessionPubKey),
71
- publicKey: this.publicKey,
72
- signFn: this.signFn,
73
- });
74
- this.cloneClientState(copy);
75
- return copy;
98
+ private constructor({
99
+ nord,
100
+ admin,
101
+ signFn,
102
+ }: {
103
+ nord: Nord;
104
+ admin: PublicKey;
105
+ signFn: (x: Uint8Array) => Promise<Uint8Array>;
106
+ }) {
107
+ this.nord = nord;
108
+ this.admin = admin;
109
+ this.signFn = signFn;
76
110
  }
77
111
 
78
- static fromUser(
79
- user: NordUser,
80
- adminSignFn: (message: Uint8Array) => Promise<Uint8Array>,
81
- ): NordAdmin {
112
+ /** Create a new admin client.
113
+ *
114
+ * @param nord - Nord instance
115
+ * @param admin - The user that will be signing actions.
116
+ * @param signFn - Function to sign messages with the admin's wallet.
117
+ *
118
+ * `signFn` must sign the _hex-encoded_ message, not the raw message itself, for
119
+ * the purpose of being compatible with Solana wallets.
120
+ *
121
+ * In practice, you will do something along the lines of:
122
+ *
123
+ * ```typescript
124
+ * (x) => wallet.signMessage(new TextEncoder().encode(x.toHex()));
125
+ * ```
126
+ *
127
+ * For a software signing key, this might look more like:
128
+ *
129
+ * ```typescript
130
+ * (x) => nacl.sign.detached(new TextEncoder().encode(x.toHex()), sk);
131
+ * ``
132
+ *
133
+ * where `nacl` is the tweetnacl library.
134
+ */
135
+ public static new({
136
+ nord,
137
+ admin,
138
+ signFn,
139
+ }: Readonly<{
140
+ nord: Nord;
141
+ admin: PublicKey;
142
+ signFn: (m: Uint8Array) => Promise<Uint8Array>;
143
+ }>): NordAdmin {
82
144
  return new NordAdmin({
83
- nord: user.nord,
84
- address: user.address,
85
- walletSignFn: user.walletSignFn,
86
- sessionSignFn: user.sessionSignFn,
87
- transactionSignFn: user.transactionSignFn,
88
- connection: user.connection,
89
- sessionId: user.sessionId,
90
- sessionPubKey: new Uint8Array(user.sessionPubKey),
91
- publicKey: user.publicKey,
92
- signFn: adminSignFn,
145
+ nord,
146
+ admin,
147
+ signFn,
93
148
  });
94
149
  }
95
150
 
151
+ /**
152
+ * Submit an action and append the admin signature before sending it to Nord.
153
+ *
154
+ * @param kind - Action payload describing the admin request
155
+ * @throws {NordError} If signing or submission fails
156
+ */
96
157
  private async submitAction(
97
158
  kind: proto.Action["kind"],
98
159
  ): Promise<proto.Receipt> {
99
- try {
100
- return await this.submitSignedAction(kind, async (message) => {
101
- const signature = await this.signFn(message);
102
- const signed = new Uint8Array(message.length + signature.length);
103
- signed.set(message);
104
- signed.set(signature, message.length);
105
- return signed;
106
- });
107
- } catch (error) {
108
- throw new NordError(`Admin action ${kind.case} failed`, {
109
- cause: error,
110
- });
160
+ const timestamp = await this.nord.getTimestamp();
161
+ const action = createAction(timestamp, 0, kind);
162
+ return sendAction(
163
+ this.nord.webServerUrl,
164
+ async (xs: Uint8Array) => {
165
+ const signature = await this.signFn(xs);
166
+ if (signature.length !== 64) {
167
+ throw new NordError("invalid signature length; must be 64 bytes");
168
+ }
169
+ return Uint8Array.from([...xs, ...signature]);
170
+ },
171
+ action,
172
+ );
173
+ }
174
+
175
+ /** Set acl permissions for a given user.
176
+ *
177
+ * If all roles are removed, the user is removed from the acl.
178
+ *
179
+ * @param target - User to update.
180
+ * @param addRoles - Roles to add to the user.
181
+ * @param removeRoles - Reles to remove from the user.
182
+ */
183
+ async updateAcl({
184
+ target,
185
+ addRoles,
186
+ removeRoles,
187
+ }: Readonly<{
188
+ target: PublicKey;
189
+ addRoles: AclRole[];
190
+ removeRoles: AclRole[];
191
+ }>): Promise<{ actionId: bigint } & proto.Receipt_AclUpdated> {
192
+ const allRoles = addRoles.concat(removeRoles);
193
+ if (allRoles.length !== new Set(allRoles).size) {
194
+ throw new NordError("duplicate roles in acl update; must be unique");
111
195
  }
196
+
197
+ let mask = 0;
198
+ let values = 0;
199
+ for (const role of allRoles) {
200
+ mask |= role;
201
+ }
202
+ for (const role of addRoles) {
203
+ values |= role;
204
+ }
205
+
206
+ const receipt = await this.submitAction({
207
+ case: "updateAcl",
208
+ value: create(proto.Action_UpdateAclSchema, {
209
+ aclPubkey: this.admin.toBytes(),
210
+ targetPubkey: target.toBytes(),
211
+ }),
212
+ });
213
+ expectReceiptKind(receipt, "aclUpdated", "update acl");
214
+
215
+ return { ...receipt.kind.value, actionId: receipt.actionId };
112
216
  }
113
217
 
114
- async createToken(
115
- params: CreateTokenParams,
116
- ): Promise<{ actionId: bigint } & proto.Receipt_InsertTokenResult> {
117
- checkPubKeyLength(KeyType.Ed25519, params.solAddr.length);
218
+ /**
219
+ * Register a new token that can be listed on Nord.
220
+ *
221
+ * @param params - Token configuration values
222
+ * @returns Action identifier and resulting token metadata
223
+ * @throws {NordError} If the action submission fails
224
+ */
225
+ async createToken({
226
+ tokenDecimals,
227
+ weightBps,
228
+ viewSymbol,
229
+ oracleSymbol,
230
+ mintAddr,
231
+ }: CreateTokenParams): Promise<
232
+ { actionId: bigint } & proto.Receipt_InsertTokenResult
233
+ > {
118
234
  const receipt = await this.submitAction({
119
235
  case: "createToken",
120
236
  value: create(proto.Action_CreateTokenSchema, {
121
- tokenDecimals: params.tokenDecimals,
122
- weightBps: params.weightBps,
123
- viewSymbol: params.viewSymbol,
124
- oracleSymbol: params.oracleSymbol,
125
- solAddr: params.solAddr,
237
+ aclPubkey: this.admin.toBytes(),
238
+ tokenDecimals,
239
+ weightBps,
240
+ viewSymbol,
241
+ oracleSymbol,
242
+ solAddr: mintAddr.toBytes(),
126
243
  }),
127
244
  });
128
- this.expectReceiptKind(receipt, "insertTokenResult", "create token");
245
+ expectReceiptKind(receipt, "insertTokenResult", "create token");
129
246
  return { actionId: receipt.actionId, ...receipt.kind.value };
130
247
  }
131
248
 
249
+ /**
250
+ * Open a new market with the provided trading parameters.
251
+ *
252
+ * @param params - Market configuration to apply
253
+ * @returns Action identifier and resulting market metadata
254
+ * @throws {NordError} If the action submission fails
255
+ */
132
256
  async createMarket(
133
257
  params: CreateMarketParams,
134
258
  ): Promise<{ actionId: bigint } & proto.Receipt_InsertMarketResult> {
135
259
  const receipt = await this.submitAction({
136
260
  case: "createMarket",
137
261
  value: create(proto.Action_CreateMarketSchema, {
262
+ aclPubkey: this.admin.toBytes(),
138
263
  sizeDecimals: params.sizeDecimals,
139
264
  priceDecimals: params.priceDecimals,
140
265
  imfBps: params.imfBps,
@@ -146,21 +271,48 @@ export class NordAdmin extends NordClient {
146
271
  baseTokenId: params.baseTokenId,
147
272
  }),
148
273
  });
149
- this.expectReceiptKind(receipt, "insertMarketResult", "create market");
274
+ expectReceiptKind(receipt, "insertMarketResult", "create market");
150
275
  return { actionId: receipt.actionId, ...receipt.kind.value };
151
276
  }
152
277
 
278
+ /**
279
+ * Update the Pyth guardian set used for verifying Wormhole messages.
280
+ *
281
+ * Each address must decode from a 20-byte hex string (with or without a
282
+ * leading `0x` prefix). The engine validates the supplied guardian set index
283
+ * before applying the update.
284
+ *
285
+ * @param params - Guardian set index and guardian addresses
286
+ * @returns Action identifier and guardian update receipt
287
+ * @throws {NordError} If the action submission fails
288
+ */
153
289
  async pythSetWormholeGuardians(
154
290
  params: PythSetWormholeGuardiansParams,
155
291
  ): Promise<{ actionId: bigint } & proto.Receipt_UpdateGuardianSetResult> {
292
+ const addresses = params.addresses.map((address) => {
293
+ try {
294
+ const decoded = decodeHex(address);
295
+ if (decoded.length !== 20) {
296
+ throw new Error("guardian address must be 20 bytes");
297
+ }
298
+ return decoded;
299
+ } catch (e) {
300
+ throw new NordError(
301
+ "invalid guardian address; must be a 20 byte hex address",
302
+ { cause: e },
303
+ );
304
+ }
305
+ });
306
+
156
307
  const receipt = await this.submitAction({
157
308
  case: "pythSetWormholeGuardians",
158
309
  value: create(proto.Action_PythSetWormholeGuardiansSchema, {
310
+ aclPubkey: this.admin.toBytes(),
159
311
  guardianSetIndex: params.guardianSetIndex,
160
- addresses: params.addresses,
312
+ addresses,
161
313
  }),
162
314
  });
163
- this.expectReceiptKind(
315
+ expectReceiptKind(
164
316
  receipt,
165
317
  "updateGuardianSetResult",
166
318
  "update wormhole guardians",
@@ -168,42 +320,85 @@ export class NordAdmin extends NordClient {
168
320
  return { actionId: receipt.actionId, ...receipt.kind.value };
169
321
  }
170
322
 
323
+ /**
324
+ * Link an oracle symbol to a specific Pyth price feed.
325
+ *
326
+ * The price feed identifier must decode to 32 bytes (with or without a
327
+ * leading `0x` prefix). Use this call to create or update the mapping used
328
+ * by the oracle integration.
329
+ *
330
+ * @param params - Oracle symbol and price feed identifier
331
+ * @returns Action identifier and symbol feed receipt
332
+ * @throws {NordError} If the action submission fails
333
+ */
171
334
  async pythSetSymbolFeed(
172
335
  params: PythSetSymbolFeedParams,
173
336
  ): Promise<{ actionId: bigint } & proto.Receipt_OracleSymbolFeedResult> {
337
+ let priceFeedId: Uint8Array;
338
+ try {
339
+ priceFeedId = decodeHex(params.priceFeedId);
340
+ if (priceFeedId.length !== 32) {
341
+ throw new Error("price feed id must be 32 bytes");
342
+ }
343
+ } catch (e) {
344
+ throw new NordError("invalid price feed id; must be a 32 byte hex id", {
345
+ cause: e,
346
+ });
347
+ }
348
+
174
349
  const receipt = await this.submitAction({
175
350
  case: "pythSetSymbolFeed",
176
351
  value: create(proto.Action_PythSetSymbolFeedSchema, {
352
+ aclPubkey: this.admin.toBytes(),
177
353
  oracleSymbol: params.oracleSymbol,
178
- priceFeedId: params.priceFeedId,
354
+ priceFeedId,
179
355
  }),
180
356
  });
181
- this.expectReceiptKind(
182
- receipt,
183
- "oracleSymbolFeedResult",
184
- "set symbol feed",
185
- );
357
+ expectReceiptKind(receipt, "oracleSymbolFeedResult", "set symbol feed");
186
358
  return { actionId: receipt.actionId, ...receipt.kind.value };
187
359
  }
188
360
 
361
+ /**
362
+ * Pause all trading activity on the exchange.
363
+ *
364
+ * @returns Action identifier confirming the pause
365
+ * @throws {NordError} If the action submission fails
366
+ */
189
367
  async pause(): Promise<{ actionId: bigint }> {
190
368
  const receipt = await this.submitAction({
191
369
  case: "pause",
192
- value: create(proto.Action_PauseSchema, {}),
370
+ value: create(proto.Action_PauseSchema, {
371
+ aclPubkey: this.admin.toBytes(),
372
+ }),
193
373
  });
194
- this.expectReceiptKind(receipt, "paused", "pause");
374
+ expectReceiptKind(receipt, "paused", "pause");
195
375
  return { actionId: receipt.actionId };
196
376
  }
197
377
 
378
+ /**
379
+ * Resume trading activity after a pause.
380
+ *
381
+ * @returns Action identifier confirming the unpause
382
+ * @throws {NordError} If the action submission fails
383
+ */
198
384
  async unpause(): Promise<{ actionId: bigint }> {
199
385
  const receipt = await this.submitAction({
200
386
  case: "unpause",
201
- value: create(proto.Action_UnpauseSchema, {}),
387
+ value: create(proto.Action_UnpauseSchema, {
388
+ aclPubkey: this.admin.toBytes(),
389
+ }),
202
390
  });
203
- this.expectReceiptKind(receipt, "unpaused", "unpause");
391
+ expectReceiptKind(receipt, "unpaused", "unpause");
204
392
  return { actionId: receipt.actionId };
205
393
  }
206
394
 
395
+ /**
396
+ * Freeze an individual market, preventing new trades and orders.
397
+ *
398
+ * @param params - Target market identifier
399
+ * @returns Action identifier and freeze receipt
400
+ * @throws {NordError} If the action submission fails
401
+ */
207
402
  async freezeMarket(
208
403
  params: FreezeMarketParams,
209
404
  ): Promise<{ actionId: bigint } & proto.Receipt_MarketFreezeUpdated> {
@@ -211,12 +406,20 @@ export class NordAdmin extends NordClient {
211
406
  case: "freezeMarket",
212
407
  value: create(proto.Action_FreezeMarketSchema, {
213
408
  marketId: params.marketId,
409
+ aclPubkey: this.admin.toBytes(),
214
410
  }),
215
411
  });
216
- this.expectReceiptKind(receipt, "marketFreezeUpdated", "freeze market");
412
+ expectReceiptKind(receipt, "marketFreezeUpdated", "freeze market");
217
413
  return { actionId: receipt.actionId, ...receipt.kind.value };
218
414
  }
219
415
 
416
+ /**
417
+ * Unfreeze a market that was previously halted.
418
+ *
419
+ * @param params - Target market identifier
420
+ * @returns Action identifier and freeze receipt
421
+ * @throws {NordError} If the action submission fails
422
+ */
220
423
  async unfreezeMarket(
221
424
  params: UnfreezeMarketParams,
222
425
  ): Promise<{ actionId: bigint } & proto.Receipt_MarketFreezeUpdated> {
@@ -224,9 +427,88 @@ export class NordAdmin extends NordClient {
224
427
  case: "unfreezeMarket",
225
428
  value: create(proto.Action_UnfreezeMarketSchema, {
226
429
  marketId: params.marketId,
430
+ aclPubkey: this.admin.toBytes(),
431
+ }),
432
+ });
433
+ expectReceiptKind(receipt, "marketFreezeUpdated", "unfreeze market");
434
+ return { actionId: receipt.actionId, ...receipt.kind.value };
435
+ }
436
+
437
+ /**
438
+ * Append a new fee tier to the account bracket configuration.
439
+ *
440
+ * - The engine supports at most 16 tiers (ids 0–15). Tier 0 is reserved for
441
+ * the default Nord fees; use `updateFeeTier` if you need to change it.
442
+ * - The first appended tier receives id 1, and subsequent tiers increment the id.
443
+ *
444
+ * @param params - Fee tier configuration to insert
445
+ * @returns Action identifier and fee tier addition receipt
446
+ * @throws {NordError} If the action submission fails or the new tier exceeds the maximum range (0-15).
447
+ */
448
+ async addFeeTier(
449
+ params: AddFeeTierParams,
450
+ ): Promise<{ actionId: bigint } & proto.Receipt_FeeTierAdded> {
451
+ const receipt = await this.submitAction({
452
+ case: "addFeeTier",
453
+ value: create(proto.Action_AddFeeTierSchema, {
454
+ aclPubkey: this.admin.toBytes(),
455
+ config: create(proto.FeeTierConfigSchema, params.config),
456
+ }),
457
+ });
458
+ expectReceiptKind(receipt, "feeTierAdded", "add fee tier");
459
+ return { actionId: receipt.actionId, ...receipt.kind.value };
460
+ }
461
+
462
+ /**
463
+ * Update an existing fee tier with new maker/taker rates.
464
+ *
465
+ * Tier identifiers must already exist; attempting to update a missing tier
466
+ * causes the action to fail.
467
+ *
468
+ * @param params - Fee tier identifier and updated configuration
469
+ * @returns Action identifier and fee tier update receipt
470
+ * @throws {NordError} If the action submission fails or the tier ID exceeds the configured range.
471
+ */
472
+ async updateFeeTier(
473
+ params: UpdateFeeTierParams,
474
+ ): Promise<{ actionId: bigint } & proto.Receipt_FeeTierUpdated> {
475
+ const receipt = await this.submitAction({
476
+ case: "updateFeeTier",
477
+ value: create(proto.Action_UpdateFeeTierSchema, {
478
+ aclPubkey: this.admin.toBytes(),
479
+ id: params.tierId,
480
+ config: create(proto.FeeTierConfigSchema, params.config),
481
+ }),
482
+ });
483
+ expectReceiptKind(receipt, "feeTierUpdated", "update fee tier");
484
+ return { actionId: receipt.actionId, ...receipt.kind.value };
485
+ }
486
+
487
+ /**
488
+ * Assign a fee tier to one or more accounts.
489
+ *
490
+ * The tier id must be within the configured range (0–15). Every account starts
491
+ * on tier 0; assigning it to another tier requires that tier to exist already.
492
+ * Invalid account ids or tier ids cause the action to fail.
493
+ *
494
+ * @param accounts - Account IDs to update
495
+ * @param tierId - Target fee tier identifier
496
+ * @returns Action identifier and accounts-tier receipt
497
+ * @throws {NordError} If the tier id exceeds the configured range or an account id is invalid.
498
+ */
499
+ async updateAccountsTier(
500
+ accounts: number[],
501
+ tierId: number,
502
+ ): Promise<{ actionId: bigint } & proto.Receipt_AccountsTierUpdated> {
503
+ const receipt = await this.submitAction({
504
+ case: "updateAccountsTier",
505
+ value: create(proto.Action_UpdateAccountsTierSchema, {
506
+ aclPubkey: this.admin.toBytes(),
507
+ accounts,
508
+ tierId,
227
509
  }),
228
510
  });
229
- this.expectReceiptKind(receipt, "marketFreezeUpdated", "unfreeze market");
511
+ expectReceiptKind(receipt, "accountsTierUpdated", "update accounts tier");
230
512
  return { actionId: receipt.actionId, ...receipt.kind.value };
231
513
  }
232
514
  }
@@ -2,15 +2,8 @@ import { Connection, PublicKey } from "@solana/web3.js";
2
2
  import type { Transaction } from "@solana/web3.js";
3
3
  import * as proto from "../../gen/nord_pb";
4
4
  import { createAction, sendAction } from "../api/actions";
5
- import { NordError } from "../utils/NordError";
6
5
  import { Nord } from "./Nord";
7
6
 
8
- type ReceiptKind = NonNullable<proto.Receipt["kind"]>;
9
- type ExtractReceiptKind<K extends ReceiptKind["case"]> = Extract<
10
- ReceiptKind,
11
- { case: K }
12
- >;
13
-
14
7
  export interface NordClientParams {
15
8
  nord: Nord;
16
9
  address: PublicKey;
@@ -83,23 +76,4 @@ export abstract class NordClient {
83
76
  getSolanaPublicKey(): PublicKey {
84
77
  return this.address;
85
78
  }
86
-
87
- protected expectReceiptKind<K extends ReceiptKind["case"]>(
88
- receipt: proto.Receipt,
89
- expected: K,
90
- action: string,
91
- ): asserts receipt is proto.Receipt & { kind: ExtractReceiptKind<K> } {
92
- if (receipt.kind?.case !== expected) {
93
- const label = this.formatReceiptError(receipt);
94
- throw new NordError(`Failed to ${action}: ${label}`);
95
- }
96
- }
97
-
98
- protected formatReceiptError(receipt: proto.Receipt): string {
99
- if (receipt.kind?.case === "err") {
100
- const err = receipt.kind.value;
101
- return proto.Error[err] ?? err.toString();
102
- }
103
- return receipt.kind?.case ?? "unknown";
104
- }
105
79
  }
@@ -39,6 +39,7 @@ import {
39
39
  createSession,
40
40
  revokeSession,
41
41
  atomic,
42
+ expectReceiptKind,
42
43
  AtomicSubaction,
43
44
  } from "../api/actions";
44
45
  import { NordError } from "../utils/NordError";
@@ -737,7 +738,7 @@ export class NordUser extends NordClient {
737
738
  amount: scaledAmount,
738
739
  }),
739
740
  });
740
- this.expectReceiptKind(receipt, "withdrawResult", "withdraw");
741
+ expectReceiptKind(receipt, "withdrawResult", "withdraw");
741
742
  return { actionId: receipt.actionId };
742
743
  } catch (error) {
743
744
  throw new NordError(
@@ -800,7 +801,7 @@ export class NordUser extends NordClient {
800
801
  : BigInt(params.clientOrderId),
801
802
  }),
802
803
  });
803
- this.expectReceiptKind(receipt, "placeOrderResult", "place order");
804
+ expectReceiptKind(receipt, "placeOrderResult", "place order");
804
805
  const result = receipt.kind.value;
805
806
  return {
806
807
  actionId: receipt.actionId,
@@ -840,7 +841,7 @@ export class NordUser extends NordClient {
840
841
  senderAccountId: accountId,
841
842
  }),
842
843
  });
843
- this.expectReceiptKind(receipt, "cancelOrderResult", "cancel order");
844
+ expectReceiptKind(receipt, "cancelOrderResult", "cancel order");
844
845
  return {
845
846
  actionId: receipt.actionId,
846
847
  orderId: receipt.kind.value.orderId,
@@ -900,7 +901,7 @@ export class NordUser extends NordClient {
900
901
  accountId: params.accountId,
901
902
  }),
902
903
  });
903
- this.expectReceiptKind(receipt, "triggerAdded", "add trigger");
904
+ expectReceiptKind(receipt, "triggerAdded", "add trigger");
904
905
  return { actionId: receipt.actionId };
905
906
  } catch (error) {
906
907
  throw new NordError("Failed to add trigger", { cause: error });
@@ -939,7 +940,7 @@ export class NordUser extends NordClient {
939
940
  accountId: params.accountId,
940
941
  }),
941
942
  });
942
- this.expectReceiptKind(receipt, "triggerRemoved", "remove trigger");
943
+ expectReceiptKind(receipt, "triggerRemoved", "remove trigger");
943
944
  return { actionId: receipt.actionId };
944
945
  } catch (error) {
945
946
  throw new NordError("Failed to remove trigger", { cause: error });
@@ -972,7 +973,7 @@ export class NordUser extends NordClient {
972
973
  amount,
973
974
  }),
974
975
  });
975
- this.expectReceiptKind(receipt, "transferred", "transfer tokens");
976
+ expectReceiptKind(receipt, "transferred", "transfer tokens");
976
977
  } catch (error) {
977
978
  throw new NordError("Failed to transfer tokens", { cause: error });
978
979
  }