@n1xyz/nord-ts 0.1.5 → 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 (47) 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/nord_pb.d.ts +233 -7
  12. package/dist/gen/nord_pb.js +34 -8
  13. package/dist/gen/openapi.d.ts +40 -0
  14. package/dist/index.d.ts +6 -1
  15. package/dist/index.js +29 -1
  16. package/dist/nord/client/Nord.d.ts +1 -1
  17. package/dist/nord/client/Nord.js +2 -2
  18. package/dist/nord/client/NordAdmin.d.ts +24 -1
  19. package/dist/nord/client/NordAdmin.js +63 -5
  20. package/dist/nord/index.d.ts +1 -1
  21. package/dist/nord/index.js +2 -1
  22. package/dist/types.d.ts +6 -50
  23. package/dist/types.js +1 -24
  24. package/dist/utils.d.ts +8 -11
  25. package/dist/utils.js +54 -41
  26. package/dist/websocket/Subscriber.d.ts +37 -0
  27. package/dist/websocket/Subscriber.js +25 -0
  28. package/dist/websocket/index.d.ts +19 -2
  29. package/dist/websocket/index.js +82 -2
  30. package/package.json +1 -1
  31. package/src/actions.ts +333 -0
  32. package/src/{nord/client → client}/Nord.ts +209 -211
  33. package/src/{nord/client → client}/NordAdmin.ts +196 -153
  34. package/src/{nord/client → client}/NordUser.ts +216 -305
  35. package/src/gen/nord_pb.ts +271 -9
  36. package/src/gen/openapi.ts +40 -0
  37. package/src/index.ts +7 -1
  38. package/src/types.ts +7 -54
  39. package/src/utils.ts +44 -47
  40. package/src/{nord/models → websocket}/Subscriber.ts +2 -2
  41. package/src/websocket/index.ts +105 -2
  42. package/src/nord/api/actions.ts +0 -648
  43. package/src/nord/api/core.ts +0 -96
  44. package/src/nord/api/metrics.ts +0 -269
  45. package/src/nord/client/NordClient.ts +0 -79
  46. package/src/nord/index.ts +0 -25
  47. /package/src/{nord/utils/NordError.ts → error.ts} +0 -0
@@ -1,81 +1,19 @@
1
1
  import { create } from "@bufbuild/protobuf";
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";
6
- import { NordError } from "../utils/NordError";
2
+ import { PublicKey, Transaction } from "@solana/web3.js";
3
+ import * as proto from "../gen/nord_pb";
4
+ import { decodeHex, signUserPayload } from "../utils";
5
+ import { createAction, sendAction, expectReceiptKind } from "../actions";
6
+ import { NordError } from "../error";
7
7
  import { Nord } from "./Nord";
8
- import { FeeTierConfig } from "../../gen/nord_pb";
8
+ import { FeeTierConfig } from "../gen/nord_pb";
9
9
 
10
- /**
11
- * Parameters required to register a new token via the admin API.
12
- */
13
- export interface CreateTokenParams {
14
- tokenDecimals: number;
15
- weightBps: number;
16
- viewSymbol: string;
17
- oracleSymbol: string;
18
- mintAddr: PublicKey;
19
- }
20
-
21
- /**
22
- * Parameters used when creating a new market.
23
- */
24
- export interface CreateMarketParams {
25
- sizeDecimals: number;
26
- priceDecimals: number;
27
- imfBps: number;
28
- cmfBps: number;
29
- mmfBps: number;
30
- marketType: proto.MarketType;
31
- viewSymbol: string;
32
- oracleSymbol: string;
33
- baseTokenId: number;
34
- }
35
-
36
- /**
37
- * Configuration for updating the Wormhole guardian set on the oracle.
38
- */
39
- export interface PythSetWormholeGuardiansParams {
40
- guardianSetIndex: number;
41
- addresses: string[];
42
- }
43
-
44
- /**
45
- * Parameters required to link an oracle symbol to a Pyth price feed.
46
- */
47
- export interface PythSetSymbolFeedParams {
48
- oracleSymbol: string;
49
- priceFeedId: string;
50
- }
51
-
52
- /**
53
- * Identifies a market that should be frozen.
54
- */
55
- export interface FreezeMarketParams {
56
- marketId: number;
57
- }
58
-
59
- /**
60
- * Identifies a market that should be unfrozen.
61
- */
62
- export interface UnfreezeMarketParams {
63
- marketId: number;
64
- }
65
-
66
- /**
67
- * Parameters for adding a new fee tier.
68
- */
69
- export interface AddFeeTierParams {
70
- config: FeeTierConfig;
71
- }
72
-
73
- /**
74
- * Parameters for updating an existing fee tier.
75
- */
76
- export interface UpdateFeeTierParams {
77
- tierId: number;
78
- config: FeeTierConfig;
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,
79
17
  }
80
18
 
81
19
  /**
@@ -83,50 +21,41 @@ export interface UpdateFeeTierParams {
83
21
  */
84
22
  export class NordAdmin {
85
23
  private readonly nord: Nord;
86
- private readonly signFn: (x: Uint8Array) => Promise<Uint8Array>;
24
+ private readonly admin: PublicKey;
25
+ private readonly signFn: (_: Transaction) => Promise<Transaction>;
87
26
 
88
27
  private constructor({
89
28
  nord,
29
+ admin,
90
30
  signFn,
91
31
  }: {
92
32
  nord: Nord;
93
- signFn: (x: Uint8Array) => Promise<Uint8Array>;
33
+ admin: PublicKey;
34
+ signFn: (x: Transaction) => Promise<Transaction>;
94
35
  }) {
95
36
  this.nord = nord;
37
+ this.admin = admin;
96
38
  this.signFn = signFn;
97
39
  }
98
40
 
99
41
  /** Create a new admin client.
100
42
  *
101
43
  * @param nord - Nord instance
44
+ * @param admin - The user that will be signing actions.
102
45
  * @param signFn - Function to sign messages with the admin's wallet.
103
- *
104
- * `signFn` must sign the _hex-encoded_ message, not the raw message itself, for
105
- * the purpose of being compatible with Solana wallets.
106
- *
107
- * In practice, you will do something along the lines of:
108
- *
109
- * ```typescript
110
- * (x) => wallet.signMessage(new TextEncoder().encode(x.toHex()));
111
- * ```
112
- *
113
- * For a software signing key, this might look more like:
114
- *
115
- * ```typescript
116
- * (x) => nacl.sign.detached(new TextEncoder().encode(x.toHex()), sk);
117
- * ``
118
- *
119
- * where `nacl` is the tweetnacl library.
120
46
  */
121
47
  public static new({
122
48
  nord,
49
+ admin,
123
50
  signFn,
124
51
  }: Readonly<{
125
52
  nord: Nord;
126
- signFn: (m: Uint8Array) => Promise<Uint8Array>;
53
+ admin: PublicKey;
54
+ signFn: (m: Transaction) => Promise<Transaction>;
127
55
  }>): NordAdmin {
128
56
  return new NordAdmin({
129
57
  nord,
58
+ admin,
130
59
  signFn,
131
60
  });
132
61
  }
@@ -143,22 +72,72 @@ export class NordAdmin {
143
72
  const timestamp = await this.nord.getTimestamp();
144
73
  const action = createAction(timestamp, 0, kind);
145
74
  return sendAction(
146
- this.nord.webServerUrl,
75
+ this.nord.httpClient,
147
76
  async (xs: Uint8Array) => {
148
- const signature = await this.signFn(xs);
149
- if (signature.length !== 64) {
150
- throw new NordError("invalid signature length; must be 64 bytes");
151
- }
77
+ const signature = await signUserPayload({
78
+ payload: xs,
79
+ user: this.admin,
80
+ signTransaction: this.signFn,
81
+ });
152
82
  return Uint8Array.from([...xs, ...signature]);
153
83
  },
154
84
  action,
155
85
  );
156
86
  }
157
87
 
88
+ /** Set acl permissions for a given user.
89
+ *
90
+ * If all roles are removed, the user is removed from the acl.
91
+ *
92
+ * @param target - User to update.
93
+ * @param addRoles - Roles to add to the user.
94
+ * @param removeRoles - Reles to remove from the user.
95
+ */
96
+ async updateAcl({
97
+ target,
98
+ addRoles,
99
+ removeRoles,
100
+ }: Readonly<{
101
+ target: PublicKey;
102
+ addRoles: AclRole[];
103
+ removeRoles: AclRole[];
104
+ }>): Promise<{ actionId: bigint } & proto.Receipt_AclUpdated> {
105
+ const allRoles = addRoles.concat(removeRoles);
106
+ if (allRoles.length !== new Set(allRoles).size) {
107
+ throw new NordError("duplicate roles in acl update; must be unique");
108
+ }
109
+
110
+ let mask = 0;
111
+ let values = 0;
112
+ for (const role of allRoles) {
113
+ mask |= role;
114
+ }
115
+ for (const role of addRoles) {
116
+ values |= role;
117
+ }
118
+
119
+ const receipt = await this.submitAction({
120
+ case: "updateAcl",
121
+ value: create(proto.Action_UpdateAclSchema, {
122
+ aclPubkey: this.admin.toBytes(),
123
+ targetPubkey: target.toBytes(),
124
+ rolesValue: values,
125
+ rolesMask: mask,
126
+ }),
127
+ });
128
+ expectReceiptKind(receipt, "aclUpdated", "update acl");
129
+
130
+ return { ...receipt.kind.value, actionId: receipt.actionId };
131
+ }
132
+
158
133
  /**
159
134
  * Register a new token that can be listed on Nord.
160
135
  *
161
- * @param params - Token configuration values
136
+ * @param tokenDecimals - Decimal shift used when parsing deposits/withdrawals
137
+ * @param weightBps - Risk weight in basis points applied in account value calculations
138
+ * @param viewSymbol - Symbol surfaced to Nord clients
139
+ * @param oracleSymbol - Symbol resolved by the oracle adapter
140
+ * @param mintAddr - Solana mint backing this token
162
141
  * @returns Action identifier and resulting token metadata
163
142
  * @throws {NordError} If the action submission fails
164
143
  */
@@ -168,12 +147,17 @@ export class NordAdmin {
168
147
  viewSymbol,
169
148
  oracleSymbol,
170
149
  mintAddr,
171
- }: CreateTokenParams): Promise<
172
- { actionId: bigint } & proto.Receipt_InsertTokenResult
173
- > {
150
+ }: Readonly<{
151
+ tokenDecimals: number;
152
+ weightBps: number;
153
+ viewSymbol: string;
154
+ oracleSymbol: string;
155
+ mintAddr: PublicKey;
156
+ }>): Promise<{ actionId: bigint } & proto.Receipt_InsertTokenResult> {
174
157
  const receipt = await this.submitAction({
175
158
  case: "createToken",
176
159
  value: create(proto.Action_CreateTokenSchema, {
160
+ aclPubkey: this.admin.toBytes(),
177
161
  tokenDecimals,
178
162
  weightBps,
179
163
  viewSymbol,
@@ -188,25 +172,52 @@ export class NordAdmin {
188
172
  /**
189
173
  * Open a new market with the provided trading parameters.
190
174
  *
191
- * @param params - Market configuration to apply
175
+ * @param sizeDecimals - Decimal shift for contract sizes
176
+ * @param priceDecimals - Decimal shift for price ticks
177
+ * @param imfBps - Base initial margin fraction (IMF) in basis points, see docs/MARKETS.md
178
+ * @param cmfBps - Cancel margin fraction (CMF) in basis points, see docs/MARKETS.md
179
+ * @param mmfBps - Maintenance margin fraction (MMF) in basis points, see docs/MARKETS.md
180
+ * @param marketType - Spot or perpetual market type
181
+ * @param viewSymbol - Symbol exposed to Nord clients
182
+ * @param oracleSymbol - Symbol resolved by the oracle adapter
183
+ * @param baseTokenId - Registered base token backing this market
192
184
  * @returns Action identifier and resulting market metadata
193
185
  * @throws {NordError} If the action submission fails
194
186
  */
195
- async createMarket(
196
- params: CreateMarketParams,
197
- ): Promise<{ actionId: bigint } & proto.Receipt_InsertMarketResult> {
187
+ async createMarket({
188
+ sizeDecimals,
189
+ priceDecimals,
190
+ imfBps,
191
+ cmfBps,
192
+ mmfBps,
193
+ marketType,
194
+ viewSymbol,
195
+ oracleSymbol,
196
+ baseTokenId,
197
+ }: Readonly<{
198
+ sizeDecimals: number;
199
+ priceDecimals: number;
200
+ imfBps: number;
201
+ cmfBps: number;
202
+ mmfBps: number;
203
+ marketType: proto.MarketType;
204
+ viewSymbol: string;
205
+ oracleSymbol: string;
206
+ baseTokenId: number;
207
+ }>): Promise<{ actionId: bigint } & proto.Receipt_InsertMarketResult> {
198
208
  const receipt = await this.submitAction({
199
209
  case: "createMarket",
200
210
  value: create(proto.Action_CreateMarketSchema, {
201
- sizeDecimals: params.sizeDecimals,
202
- priceDecimals: params.priceDecimals,
203
- imfBps: params.imfBps,
204
- cmfBps: params.cmfBps,
205
- mmfBps: params.mmfBps,
206
- marketType: params.marketType,
207
- viewSymbol: params.viewSymbol,
208
- oracleSymbol: params.oracleSymbol,
209
- baseTokenId: params.baseTokenId,
211
+ aclPubkey: this.admin.toBytes(),
212
+ sizeDecimals,
213
+ priceDecimals,
214
+ imfBps,
215
+ cmfBps,
216
+ mmfBps,
217
+ marketType,
218
+ viewSymbol,
219
+ oracleSymbol,
220
+ baseTokenId,
210
221
  }),
211
222
  });
212
223
  expectReceiptKind(receipt, "insertMarketResult", "create market");
@@ -220,14 +231,19 @@ export class NordAdmin {
220
231
  * leading `0x` prefix). The engine validates the supplied guardian set index
221
232
  * before applying the update.
222
233
  *
223
- * @param params - Guardian set index and guardian addresses
234
+ * @param guardianSetIndex - Wormhole guardian set index that must already exist
235
+ * @param addresses - 20-byte hex-encoded guardian addresses
224
236
  * @returns Action identifier and guardian update receipt
225
237
  * @throws {NordError} If the action submission fails
226
238
  */
227
- async pythSetWormholeGuardians(
228
- params: PythSetWormholeGuardiansParams,
229
- ): Promise<{ actionId: bigint } & proto.Receipt_UpdateGuardianSetResult> {
230
- const addresses = params.addresses.map((address) => {
239
+ async pythSetWormholeGuardians({
240
+ guardianSetIndex,
241
+ addresses,
242
+ }: Readonly<{
243
+ guardianSetIndex: number;
244
+ addresses: readonly string[];
245
+ }>): Promise<{ actionId: bigint } & proto.Receipt_UpdateGuardianSetResult> {
246
+ const parsedAddresses = addresses.map((address) => {
231
247
  try {
232
248
  const decoded = decodeHex(address);
233
249
  if (decoded.length !== 20) {
@@ -245,8 +261,9 @@ export class NordAdmin {
245
261
  const receipt = await this.submitAction({
246
262
  case: "pythSetWormholeGuardians",
247
263
  value: create(proto.Action_PythSetWormholeGuardiansSchema, {
248
- guardianSetIndex: params.guardianSetIndex,
249
- addresses,
264
+ aclPubkey: this.admin.toBytes(),
265
+ guardianSetIndex,
266
+ addresses: parsedAddresses,
250
267
  }),
251
268
  });
252
269
  expectReceiptKind(
@@ -264,16 +281,21 @@ export class NordAdmin {
264
281
  * leading `0x` prefix). Use this call to create or update the mapping used
265
282
  * by the oracle integration.
266
283
  *
267
- * @param params - Oracle symbol and price feed identifier
284
+ * @param oracleSymbol - Symbol resolved by the oracle adapter
285
+ * @param priceFeedId - 32-byte hex-encoded Pyth price feed identifier
268
286
  * @returns Action identifier and symbol feed receipt
269
287
  * @throws {NordError} If the action submission fails
270
288
  */
271
- async pythSetSymbolFeed(
272
- params: PythSetSymbolFeedParams,
273
- ): Promise<{ actionId: bigint } & proto.Receipt_OracleSymbolFeedResult> {
289
+ async pythSetSymbolFeed({
290
+ oracleSymbol,
291
+ priceFeedId: priceFeedIdHex,
292
+ }: Readonly<{
293
+ oracleSymbol: string;
294
+ priceFeedId: string;
295
+ }>): Promise<{ actionId: bigint } & proto.Receipt_OracleSymbolFeedResult> {
274
296
  let priceFeedId: Uint8Array;
275
297
  try {
276
- priceFeedId = decodeHex(params.priceFeedId);
298
+ priceFeedId = decodeHex(priceFeedIdHex);
277
299
  if (priceFeedId.length !== 32) {
278
300
  throw new Error("price feed id must be 32 bytes");
279
301
  }
@@ -286,7 +308,8 @@ export class NordAdmin {
286
308
  const receipt = await this.submitAction({
287
309
  case: "pythSetSymbolFeed",
288
310
  value: create(proto.Action_PythSetSymbolFeedSchema, {
289
- oracleSymbol: params.oracleSymbol,
311
+ aclPubkey: this.admin.toBytes(),
312
+ oracleSymbol,
290
313
  priceFeedId,
291
314
  }),
292
315
  });
@@ -303,7 +326,9 @@ export class NordAdmin {
303
326
  async pause(): Promise<{ actionId: bigint }> {
304
327
  const receipt = await this.submitAction({
305
328
  case: "pause",
306
- value: create(proto.Action_PauseSchema, {}),
329
+ value: create(proto.Action_PauseSchema, {
330
+ aclPubkey: this.admin.toBytes(),
331
+ }),
307
332
  });
308
333
  expectReceiptKind(receipt, "paused", "pause");
309
334
  return { actionId: receipt.actionId };
@@ -318,7 +343,9 @@ export class NordAdmin {
318
343
  async unpause(): Promise<{ actionId: bigint }> {
319
344
  const receipt = await this.submitAction({
320
345
  case: "unpause",
321
- value: create(proto.Action_UnpauseSchema, {}),
346
+ value: create(proto.Action_UnpauseSchema, {
347
+ aclPubkey: this.admin.toBytes(),
348
+ }),
322
349
  });
323
350
  expectReceiptKind(receipt, "unpaused", "unpause");
324
351
  return { actionId: receipt.actionId };
@@ -327,17 +354,20 @@ export class NordAdmin {
327
354
  /**
328
355
  * Freeze an individual market, preventing new trades and orders.
329
356
  *
330
- * @param params - Target market identifier
357
+ * @param marketId - Target market identifier
331
358
  * @returns Action identifier and freeze receipt
332
359
  * @throws {NordError} If the action submission fails
333
360
  */
334
- async freezeMarket(
335
- params: FreezeMarketParams,
336
- ): Promise<{ actionId: bigint } & proto.Receipt_MarketFreezeUpdated> {
361
+ async freezeMarket({
362
+ marketId,
363
+ }: Readonly<{
364
+ marketId: number;
365
+ }>): Promise<{ actionId: bigint } & proto.Receipt_MarketFreezeUpdated> {
337
366
  const receipt = await this.submitAction({
338
367
  case: "freezeMarket",
339
368
  value: create(proto.Action_FreezeMarketSchema, {
340
- marketId: params.marketId,
369
+ marketId,
370
+ aclPubkey: this.admin.toBytes(),
341
371
  }),
342
372
  });
343
373
  expectReceiptKind(receipt, "marketFreezeUpdated", "freeze market");
@@ -347,17 +377,20 @@ export class NordAdmin {
347
377
  /**
348
378
  * Unfreeze a market that was previously halted.
349
379
  *
350
- * @param params - Target market identifier
380
+ * @param marketId - Target market identifier
351
381
  * @returns Action identifier and freeze receipt
352
382
  * @throws {NordError} If the action submission fails
353
383
  */
354
- async unfreezeMarket(
355
- params: UnfreezeMarketParams,
356
- ): Promise<{ actionId: bigint } & proto.Receipt_MarketFreezeUpdated> {
384
+ async unfreezeMarket({
385
+ marketId,
386
+ }: Readonly<{
387
+ marketId: number;
388
+ }>): Promise<{ actionId: bigint } & proto.Receipt_MarketFreezeUpdated> {
357
389
  const receipt = await this.submitAction({
358
390
  case: "unfreezeMarket",
359
391
  value: create(proto.Action_UnfreezeMarketSchema, {
360
- marketId: params.marketId,
392
+ marketId,
393
+ aclPubkey: this.admin.toBytes(),
361
394
  }),
362
395
  });
363
396
  expectReceiptKind(receipt, "marketFreezeUpdated", "unfreeze market");
@@ -371,17 +404,20 @@ export class NordAdmin {
371
404
  * the default Nord fees; use `updateFeeTier` if you need to change it.
372
405
  * - The first appended tier receives id 1, and subsequent tiers increment the id.
373
406
  *
374
- * @param params - Fee tier configuration to insert
407
+ * @param config - Fee tier configuration to insert
375
408
  * @returns Action identifier and fee tier addition receipt
376
409
  * @throws {NordError} If the action submission fails or the new tier exceeds the maximum range (0-15).
377
410
  */
378
- async addFeeTier(
379
- params: AddFeeTierParams,
380
- ): Promise<{ actionId: bigint } & proto.Receipt_FeeTierAdded> {
411
+ async addFeeTier({
412
+ config,
413
+ }: Readonly<{
414
+ config: FeeTierConfig;
415
+ }>): Promise<{ actionId: bigint } & proto.Receipt_FeeTierAdded> {
381
416
  const receipt = await this.submitAction({
382
417
  case: "addFeeTier",
383
418
  value: create(proto.Action_AddFeeTierSchema, {
384
- config: create(proto.FeeTierConfigSchema, params.config),
419
+ aclPubkey: this.admin.toBytes(),
420
+ config: create(proto.FeeTierConfigSchema, config),
385
421
  }),
386
422
  });
387
423
  expectReceiptKind(receipt, "feeTierAdded", "add fee tier");
@@ -394,18 +430,24 @@ export class NordAdmin {
394
430
  * Tier identifiers must already exist; attempting to update a missing tier
395
431
  * causes the action to fail.
396
432
  *
397
- * @param params - Fee tier identifier and updated configuration
433
+ * @param tierId - Existing fee tier identifier to update
434
+ * @param config - Replacement configuration for the tier
398
435
  * @returns Action identifier and fee tier update receipt
399
436
  * @throws {NordError} If the action submission fails or the tier ID exceeds the configured range.
400
437
  */
401
- async updateFeeTier(
402
- params: UpdateFeeTierParams,
403
- ): Promise<{ actionId: bigint } & proto.Receipt_FeeTierUpdated> {
438
+ async updateFeeTier({
439
+ tierId,
440
+ config,
441
+ }: Readonly<{
442
+ tierId: number;
443
+ config: FeeTierConfig;
444
+ }>): Promise<{ actionId: bigint } & proto.Receipt_FeeTierUpdated> {
404
445
  const receipt = await this.submitAction({
405
446
  case: "updateFeeTier",
406
447
  value: create(proto.Action_UpdateFeeTierSchema, {
407
- id: params.tierId,
408
- config: create(proto.FeeTierConfigSchema, params.config),
448
+ aclPubkey: this.admin.toBytes(),
449
+ id: tierId,
450
+ config: create(proto.FeeTierConfigSchema, config),
409
451
  }),
410
452
  });
411
453
  expectReceiptKind(receipt, "feeTierUpdated", "update fee tier");
@@ -431,6 +473,7 @@ export class NordAdmin {
431
473
  const receipt = await this.submitAction({
432
474
  case: "updateAccountsTier",
433
475
  value: create(proto.Action_UpdateAccountsTierSchema, {
476
+ aclPubkey: this.admin.toBytes(),
434
477
  accounts,
435
478
  tierId,
436
479
  }),