@dydxprotocol/v4-client-js 1.18.0 → 1.20.0

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.
@@ -47,31 +47,57 @@ async function test(): Promise<void> {
47
47
  await placeOrder(client, subaccount2, subaccount1, authenticatorID);
48
48
  }
49
49
 
50
- async function removeAuthenticator(client: CompositeClient,subaccount: SubaccountInfo, id: Long): Promise<void> {
50
+ async function removeAuthenticator(
51
+ client: CompositeClient,
52
+ subaccount: SubaccountInfo,
53
+ id: Long,
54
+ ): Promise<void> {
51
55
  await client.removeAuthenticator(subaccount, id);
52
56
  }
53
57
 
54
- async function addAuthenticator(client: CompositeClient, subaccount: SubaccountInfo, authedPubKey:string): Promise<void> {
55
- const subAuthenticators = [{
58
+ async function addAuthenticator(
59
+ client: CompositeClient,
60
+ subaccount: SubaccountInfo,
61
+ authedPubKey: string,
62
+ ): Promise<void> {
63
+ const subAuth = [ {
56
64
  type: AuthenticatorType.SIGNATURE_VERIFICATION,
57
65
  config: authedPubKey,
58
66
  },
59
67
  {
60
- type: AuthenticatorType.MESSAGE_FILTER,
61
- config: toBase64(new TextEncoder().encode("/dydxprotocol.clob.MsgPlaceOrder")),
62
- },
68
+ type: AuthenticatorType.ANY_OF,
69
+ config: [
70
+ {
71
+ type: AuthenticatorType.MESSAGE_FILTER,
72
+ config: toBase64(new TextEncoder().encode('/dydxprotocol.clob.MsgPlaceOrder')),
73
+ },
74
+ {
75
+ type: AuthenticatorType.MESSAGE_FILTER,
76
+ config: toBase64(new TextEncoder().encode('/dydxprotocol.clob.MsgPlaceOrder')),
77
+ },
78
+ ]
79
+ }
63
80
  ];
64
81
 
65
- const jsonString = JSON.stringify(subAuthenticators);
82
+ const jsonString = JSON.stringify(subAuth);
66
83
  const encodedData = new TextEncoder().encode(jsonString);
67
84
 
68
- await client.addAuthenticator(subaccount, AuthenticatorType.ALL_OF, encodedData);
85
+ try {
86
+ await client.addAuthenticator(subaccount, AuthenticatorType.ALL_OF, encodedData);
87
+ } catch (error) {
88
+ console.log(error.message);
89
+ }
69
90
  }
70
91
 
71
- async function placeOrder(client: CompositeClient, fromAccount: SubaccountInfo, forAccount: SubaccountInfo, authenticatorId: Long): Promise<void> {
92
+ async function placeOrder(
93
+ client: CompositeClient,
94
+ fromAccount: SubaccountInfo,
95
+ forAccount: SubaccountInfo,
96
+ authenticatorId: Long,
97
+ ): Promise<void> {
72
98
  try {
73
- const side = OrderSide.BUY
74
- const price = Number("1000");
99
+ const side = OrderSide.BUY;
100
+ const price = Number('1000');
75
101
  const currentBlock = await client.validatorClient.get.latestBlockHeight();
76
102
  const nextValidBlockHeight = currentBlock + 5;
77
103
  const goodTilBlock = nextValidBlockHeight + 10;
@@ -94,7 +120,7 @@ async function placeOrder(client: CompositeClient, fromAccount: SubaccountInfo,
94
120
  {
95
121
  authenticators: [authenticatorId],
96
122
  accountForOrder: forAccount,
97
- }
123
+ },
98
124
  );
99
125
  console.log('**Order Tx**');
100
126
  console.log(Buffer.from(tx.hash).toString('hex'));
@@ -1,252 +1,239 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* eslint-disable no-param-reassign */
1
3
  import { Network } from '../src/clients/constants';
2
4
  import { SocketClient } from '../src/clients/socket-client';
3
5
 
4
6
  function test(): void {
5
- let orderBookBidList: [number, number, number][] = [];
6
- let orderBookAskList: [number, number, number][] = [];
7
-
8
- const mySocket = new SocketClient(
9
- Network.mainnet().indexerConfig,
10
- () => {
11
- console.log('socket opened');
12
-
13
- mySocket.subscribeToOrderbook('ETH-USD');
14
- },
15
- () => {
16
- console.log('socket closed');
17
- },
18
- (message) => {
19
- try {
20
- if (typeof message.data === "string") {
21
- const jsonString = message.data;
22
- const parsingData = JSON.parse(jsonString);
23
- const messageId = parsingData.message_id;
24
- const orderBookDataList = parsingData.contents;
25
-
26
- if (orderBookDataList instanceof Array) {
27
- // common orderBook data;
28
- [orderBookBidList, orderBookAskList] = updateOrderBook(
29
- orderBookDataList,
30
- orderBookBidList,
31
- orderBookAskList,
32
- messageId,
33
- );
34
-
35
- // sort
36
- orderBookBidList = sortByNthElementDesc(
37
- orderBookBidList,
38
- 0,
39
- );
40
- orderBookAskList = sortByNthElementAsc(
41
- orderBookAskList,
42
- 0,
43
- );
44
-
45
- // resolving crossed orderBook
46
- if (
47
- orderBookBidList &&
48
- orderBookAskList &&
49
- orderBookBidList.length > 0 &&
50
- orderBookAskList.length > 0 &&
51
- orderBookBidList[0][0] >= orderBookAskList[0][0]
52
- ) {
53
- [orderBookBidList, orderBookAskList] =
54
- resolveCrossedOrderBook(
55
- orderBookBidList,
56
- orderBookAskList,
57
- );
58
- }
59
-
60
- printOrderBook(orderBookBidList, orderBookAskList);
61
-
62
- if (orderBookBidList.length > 300) {
63
- orderBookBidList.splice(50);
64
- }
65
-
66
- if (orderBookAskList.length > 300) {
67
- orderBookAskList.splice(50);
68
- }
69
- } else if (
70
- orderBookDataList !== null &&
71
- orderBookDataList !== undefined
72
- ) {
73
- // initial OrderBook data
74
- setInitialOrderBook(
75
- orderBookDataList,
76
- orderBookBidList,
77
- orderBookAskList,
78
- messageId,
79
- );
80
- }
81
- }
82
- } catch (e) {
83
- console.error('Error parsing JSON message:', e);
7
+ let orderBookBidList: [number, number, number][] = [];
8
+ let orderBookAskList: [number, number, number][] = [];
9
+
10
+ const mySocket = new SocketClient(
11
+ Network.mainnet().indexerConfig,
12
+ () => {
13
+ console.log('socket opened');
14
+
15
+ mySocket.subscribeToOrderbook('ETH-USD');
16
+ },
17
+ () => {
18
+ console.log('socket closed');
19
+ },
20
+ (message) => {
21
+ try {
22
+ if (typeof message.data === 'string') {
23
+ const jsonString = message.data;
24
+ const parsingData = JSON.parse(jsonString);
25
+ const messageId = parsingData.message_id;
26
+ const orderBookDataList = parsingData.contents;
27
+
28
+ if (orderBookDataList instanceof Array) {
29
+ // common orderBook data;
30
+ [orderBookBidList, orderBookAskList] = updateOrderBook(
31
+ orderBookDataList,
32
+ orderBookBidList,
33
+ orderBookAskList,
34
+ messageId,
35
+ );
36
+
37
+ // sort
38
+ orderBookBidList = sortByNthElementDesc(orderBookBidList, 0);
39
+ orderBookAskList = sortByNthElementAsc(orderBookAskList, 0);
40
+
41
+ // resolving crossed orderBook
42
+ if (
43
+ orderBookBidList.length > 0 &&
44
+ orderBookAskList.length > 0 &&
45
+ orderBookBidList[0][0] >= orderBookAskList[0][0]
46
+ ) {
47
+ [orderBookBidList, orderBookAskList] = resolveCrossedOrderBook(
48
+ orderBookBidList,
49
+ orderBookAskList,
50
+ );
84
51
  }
85
- },
86
- (event) => {
87
- console.error('Encountered error:', event.message);
88
- },
89
- );
90
52
 
91
- mySocket.connect();
53
+ printOrderBook(orderBookBidList, orderBookAskList);
54
+
55
+ if (orderBookBidList.length > 300) {
56
+ orderBookBidList.splice(50);
57
+ }
58
+
59
+ if (orderBookAskList.length > 300) {
60
+ orderBookAskList.splice(50);
61
+ }
62
+ } else if (orderBookDataList !== null && orderBookDataList !== undefined) {
63
+ // initial OrderBook data
64
+ setInitialOrderBook(orderBookDataList, orderBookBidList, orderBookAskList, messageId);
65
+ }
66
+ }
67
+ } catch (e) {
68
+ console.error('Error parsing JSON message:', e);
69
+ }
70
+ },
71
+ (event) => {
72
+ console.error('Encountered error:', event.message);
73
+ },
74
+ );
75
+
76
+ mySocket.connect();
92
77
  }
93
78
 
94
- const sortByNthElementAsc = (arr: [number, number, number][], n: number): [number, number, number][] => {
95
- return arr.sort((a, b) => {
96
- if (a[n] < b[n]) return -1;
97
- if (a[n] > b[n]) return 1;
98
- return 0;
99
- });
79
+ const sortByNthElementAsc = (
80
+ arr: [number, number, number][],
81
+ n: number,
82
+ ): [number, number, number][] => {
83
+ return arr.sort((a, b) => {
84
+ if (a[n] < b[n]) return -1;
85
+ if (a[n] > b[n]) return 1;
86
+ return 0;
87
+ });
100
88
  };
101
- const sortByNthElementDesc = (arr: [number, number, number][], n: number): [number, number, number][] => {
102
- return arr.sort((a, b) => {
103
- if (a[n] > b[n]) return -1;
104
- if (a[n] < b[n]) return 1;
105
- return 0;
106
- });
89
+ const sortByNthElementDesc = (
90
+ arr: [number, number, number][],
91
+ n: number,
92
+ ): [number, number, number][] => {
93
+ return arr.sort((a, b) => {
94
+ if (a[n] > b[n]) return -1;
95
+ if (a[n] < b[n]) return 1;
96
+ return 0;
97
+ });
107
98
  };
108
99
 
109
100
  const printOrderBook = (
110
- orderBookBidList: [number, number, number][],
111
- orderBookAskList: [number, number, number][],
101
+ orderBookBidList: [number, number, number][],
102
+ orderBookAskList: [number, number, number][],
112
103
  ): void => {
113
- // print
114
- console.log(`OrderBook for ETH-USD:`);
115
- console.log(`Price Qty`);
116
- for (let i = 4; i > -1; i--) {
117
- const priceStr = String(orderBookAskList[i][0]);
118
- const spaces = createSpaces(10 - priceStr.length);
119
- console.log(`${priceStr}${spaces}${orderBookAskList[i][1]}`);
120
- }
121
- console.log('---------------------');
122
- for (let i = 0; i < 5; i++) {
123
- const priceStr = String(orderBookBidList[i][0]);
124
- const spaces = createSpaces(10 - priceStr.length);
125
- console.log(`${priceStr}${spaces}${orderBookBidList[i][1]}`);
126
- }
127
- console.log('');
104
+ // print
105
+ console.log(`OrderBook for ETH-USD:`);
106
+ console.log(`Price Qty`);
107
+ for (let i = 4; i > -1; i--) {
108
+ const priceStr = String(orderBookAskList[i][0]);
109
+ const spaces = createSpaces(10 - priceStr.length);
110
+ console.log(`${priceStr}${spaces}${orderBookAskList[i][1]}`);
111
+ }
112
+ console.log('---------------------');
113
+ for (let i = 0; i < 5; i++) {
114
+ const priceStr = String(orderBookBidList[i][0]);
115
+ const spaces = createSpaces(10 - priceStr.length);
116
+ console.log(`${priceStr}${spaces}${orderBookBidList[i][1]}`);
117
+ }
118
+ console.log('');
128
119
  };
129
120
 
130
121
  function createSpaces(count: number): string {
131
- if(count <= 0 ){
132
- return "";
133
- }
134
-
135
- let spaces = "";
136
- for (let i = 0; i < count; i++) {
137
- spaces += " ";
138
- }
139
- return spaces;
122
+ if (count <= 0) {
123
+ return '';
124
+ }
125
+
126
+ let spaces = '';
127
+ for (let i = 0; i < count; i++) {
128
+ spaces += ' ';
129
+ }
130
+ return spaces;
140
131
  }
141
132
 
142
133
  const resolveCrossedOrderBook = (
143
- orderBookBidList: [number, number, number][],
144
- orderBookAskList: [number, number, number][],
134
+ orderBookBidList: [number, number, number][],
135
+ orderBookAskList: [number, number, number][],
145
136
  ): [[number, number, number][], [number, number, number][]] => {
146
- while (orderBookBidList[0][0] >= orderBookAskList[0][0]) {
147
- if (orderBookBidList[0][2] < orderBookAskList[0][2]) {
148
- orderBookBidList.shift();
149
- } else if (orderBookBidList[0][2] > orderBookAskList[0][2]) {
150
- orderBookAskList.shift();
151
- } else {
152
- if (orderBookBidList[0][1] > orderBookAskList[0][1]) {
153
- orderBookBidList[0][1] -= orderBookAskList[0][1];
154
- orderBookAskList.shift();
155
- } else if (orderBookBidList[0][1] < orderBookAskList[0][1]) {
156
- orderBookAskList[0][1] -= orderBookBidList[0][1];
157
- orderBookBidList.shift();
158
- } else {
159
- orderBookAskList.shift();
160
- orderBookBidList.shift();
161
- }
162
- }
137
+ while (orderBookBidList[0][0] >= orderBookAskList[0][0]) {
138
+ if (orderBookBidList[0][2] < orderBookAskList[0][2]) {
139
+ orderBookBidList.shift();
140
+ } else if (orderBookBidList[0][2] > orderBookAskList[0][2]) {
141
+ orderBookAskList.shift();
142
+ } else {
143
+ if (orderBookBidList[0][1] > orderBookAskList[0][1]) {
144
+ orderBookBidList[0][1] -= orderBookAskList[0][1];
145
+ orderBookAskList.shift();
146
+ } else if (orderBookBidList[0][1] < orderBookAskList[0][1]) {
147
+ orderBookAskList[0][1] -= orderBookBidList[0][1];
148
+ orderBookBidList.shift();
149
+ } else {
150
+ orderBookAskList.shift();
151
+ orderBookBidList.shift();
152
+ }
163
153
  }
154
+ }
164
155
 
165
- return [orderBookBidList, orderBookAskList];
156
+ return [orderBookBidList, orderBookAskList];
166
157
  };
167
158
  const setInitialOrderBook = (
168
- orderBookDataList: { bids: []; asks: [] },
169
- orderBookBidList: [number, number, number][],
170
- orderBookAskList: [number, number, number][],
171
- messageId: number,
159
+ orderBookDataList: { bids: []; asks: [] },
160
+ orderBookBidList: [number, number, number][],
161
+ orderBookAskList: [number, number, number][],
162
+ messageId: number,
172
163
  ): void => {
173
- orderBookDataList.bids.forEach((item: { price: string; size: string }) => {
174
- orderBookBidList.push([Number(item.price), Number(item.size), messageId]);
175
- });
164
+ orderBookDataList.bids.forEach((item: { price: string; size: string }) => {
165
+ orderBookBidList.push([Number(item.price), Number(item.size), messageId]);
166
+ });
176
167
 
177
- orderBookDataList.asks.forEach((item: { price: string; size: string }) => {
178
- orderBookAskList.push([Number(item.price), Number(item.size), messageId]);
179
- });
168
+ orderBookDataList.asks.forEach((item: { price: string; size: string }) => {
169
+ orderBookAskList.push([Number(item.price), Number(item.size), messageId]);
170
+ });
180
171
  };
181
172
 
182
173
  const updateOrderBook = (
183
- orderBookDataList: { bids: [[]]; asks: [[]] }[],
184
- orderBookBidList: [number, number, number][],
185
- orderBookAskList: [number, number, number][],
186
- messageId: number,
174
+ orderBookDataList: { bids: [[]]; asks: [[]] }[],
175
+ orderBookBidList: [number, number, number][],
176
+ orderBookAskList: [number, number, number][],
177
+ messageId: number,
187
178
  ): [[number, number, number][], [number, number, number][]] => {
188
- orderBookDataList.forEach((entry: { bids: [[]]; asks: [[]] }) => {
189
- if (entry.bids !== null && entry.bids !== undefined) {
190
- const flattened = entry.bids.reduce((acc: any[], val: any[]) => acc.concat(val), []);
191
- const entryBidPrice = Number(flattened[0]);
192
- const entryBidSize = Number(flattened[1]);
193
-
194
- // remove prices with zero Qty
195
- if (entryBidSize === 0) {
196
- for (let i = orderBookBidList.length - 1; i >= 0; i--) {
197
- if (orderBookBidList[i][0] === entryBidPrice) {
198
- orderBookBidList.splice(i, 1);
199
- }
200
- }
201
- } else {
202
- // The price that already exists in the order book is modified only Qty
203
- if (
204
- orderBookBidList.some((innerArray) => innerArray[0] === entryBidPrice)
205
- ) {
206
- orderBookBidList.forEach((item, index) => {
207
- if (item[0] === entryBidPrice) {
208
- orderBookBidList[index][1] = entryBidSize;
209
- orderBookBidList[index][2] = messageId;
210
- }
211
- });
212
- } else {
213
- // Add new data to order book
214
- orderBookBidList.push([entryBidPrice, entryBidSize, messageId]);
215
- }
179
+ orderBookDataList.forEach((entry: { bids: [[]]; asks: [[]] }) => {
180
+ if (entry.bids !== null && entry.bids !== undefined) {
181
+ const flattened = entry.bids.reduce((acc: any[], val: any[]) => acc.concat(val), []);
182
+ const entryBidPrice = Number(flattened[0]);
183
+ const entryBidSize = Number(flattened[1]);
184
+
185
+ // remove prices with zero Qty
186
+ if (entryBidSize === 0) {
187
+ for (let i = orderBookBidList.length - 1; i >= 0; i--) {
188
+ if (orderBookBidList[i][0] === entryBidPrice) {
189
+ orderBookBidList.splice(i, 1);
190
+ }
191
+ }
192
+ } else {
193
+ // The price that already exists in the order book is modified only Qty
194
+ if (orderBookBidList.some((innerArray) => innerArray[0] === entryBidPrice)) {
195
+ orderBookBidList.forEach((item, index) => {
196
+ if (item[0] === entryBidPrice) {
197
+ orderBookBidList[index][1] = entryBidSize;
198
+ orderBookBidList[index][2] = messageId;
216
199
  }
200
+ });
201
+ } else {
202
+ // Add new data to order book
203
+ orderBookBidList.push([entryBidPrice, entryBidSize, messageId]);
217
204
  }
218
- if (entry.asks !== null && entry.asks !== undefined) {
219
- const flattened = entry.asks.reduce((acc: any[], val: any[]) => acc.concat(val), []);
220
- const entryAskPrice = Number(flattened[0]);
221
- const entryAskSize = Number(flattened[1]);
222
-
223
- if (entryAskSize === 0) {
224
- // remove prices with zero Qty
225
- for (let i = orderBookAskList.length - 1; i >= 0; i--) {
226
- if (orderBookAskList[i][0] === entryAskPrice) {
227
- orderBookAskList.splice(i, 1);
228
- }
229
- }
230
- } else {
231
- // The price that already exists in the order book is modified only Qty
232
- if (
233
- orderBookAskList.some((innerArray) => innerArray[0] === entryAskPrice)
234
- ) {
235
- orderBookAskList.forEach((item, index) => {
236
- if (item[0] === entryAskPrice) {
237
- orderBookAskList[index][1] = entryAskSize;
238
- orderBookAskList[index][2] = messageId;
239
- }
240
- });
241
- } else {
242
- // Add new data to order book
243
- orderBookAskList.push([entryAskPrice, entryAskSize, messageId]);
244
- }
205
+ }
206
+ }
207
+ if (entry.asks !== null && entry.asks !== undefined) {
208
+ const flattened = entry.asks.reduce((acc: any[], val: any[]) => acc.concat(val), []);
209
+ const entryAskPrice = Number(flattened[0]);
210
+ const entryAskSize = Number(flattened[1]);
211
+
212
+ if (entryAskSize === 0) {
213
+ // remove prices with zero Qty
214
+ for (let i = orderBookAskList.length - 1; i >= 0; i--) {
215
+ if (orderBookAskList[i][0] === entryAskPrice) {
216
+ orderBookAskList.splice(i, 1);
217
+ }
218
+ }
219
+ } else {
220
+ // The price that already exists in the order book is modified only Qty
221
+ if (orderBookAskList.some((innerArray) => innerArray[0] === entryAskPrice)) {
222
+ orderBookAskList.forEach((item, index) => {
223
+ if (item[0] === entryAskPrice) {
224
+ orderBookAskList[index][1] = entryAskSize;
225
+ orderBookAskList[index][2] = messageId;
245
226
  }
227
+ });
228
+ } else {
229
+ // Add new data to order book
230
+ orderBookAskList.push([entryAskPrice, entryAskSize, messageId]);
246
231
  }
247
- });
232
+ }
233
+ }
234
+ });
248
235
 
249
- return [orderBookBidList, orderBookAskList];
236
+ return [orderBookBidList, orderBookAskList];
250
237
  };
251
238
 
252
- test();
239
+ test();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dydxprotocol/v4-client-js",
3
- "version": "1.18.0",
3
+ "version": "1.20.0",
4
4
  "description": "General client library for the new dYdX system (v4 decentralized)",
5
5
  "main": "build/src/index.js",
6
6
  "scripts": {
@@ -1,3 +1,5 @@
1
+ import { TextDecoder } from 'util';
2
+
1
3
  import { EncodeObject } from '@cosmjs/proto-signing';
2
4
  import { Account, GasPrice, IndexedTx, StdFee } from '@cosmjs/stargate';
3
5
  import { Method } from '@cosmjs/tendermint-rpc';
@@ -18,6 +20,7 @@ import { bigIntToBytes } from '../lib/helpers';
18
20
  import { isStatefulOrder, verifyOrderFlags } from '../lib/validation';
19
21
  import { GovAddNewMarketParams, OrderFlags } from '../types';
20
22
  import {
23
+ Authenticator,
21
24
  AuthenticatorType,
22
25
  Network,
23
26
  OrderExecution,
@@ -169,7 +172,7 @@ export class CompositeClient {
169
172
  broadcastMode,
170
173
  account,
171
174
  undefined,
172
- authenticators,
175
+ authenticators,
173
176
  );
174
177
  }
175
178
 
@@ -311,9 +314,10 @@ export class CompositeClient {
311
314
  ): Promise<BroadcastTxAsyncResponse | BroadcastTxSyncResponse | IndexedTx> {
312
315
  // For permissioned orders, use the permissioning account details instead of the subaccount
313
316
  // This allows placing orders on behalf of another account when using permissioned keys
314
- const accountForOrder = permissionedKeysAccountAuth ? permissionedKeysAccountAuth.accountForOrder : subaccount;
317
+ const accountForOrder = permissionedKeysAccountAuth
318
+ ? permissionedKeysAccountAuth.accountForOrder
319
+ : subaccount;
315
320
  const msgs: Promise<EncodeObject[]> = new Promise((resolve, reject) => {
316
-
317
321
  const msg = this.placeShortTermOrderMessage(
318
322
  accountForOrder,
319
323
  marketId,
@@ -1233,7 +1237,13 @@ export class CompositeClient {
1233
1237
  gasAdjustment?: number,
1234
1238
  memo?: string,
1235
1239
  ): Promise<BroadcastTxAsyncResponse | BroadcastTxSyncResponse | IndexedTx> {
1236
- return this.validatorClient.post.createMarketPermissionless(ticker, subaccount, broadcastMode, gasAdjustment, memo);
1240
+ return this.validatorClient.post.createMarketPermissionless(
1241
+ ticker,
1242
+ subaccount,
1243
+ broadcastMode,
1244
+ gasAdjustment,
1245
+ memo,
1246
+ );
1237
1247
  }
1238
1248
 
1239
1249
  async addAuthenticator(
@@ -1241,17 +1251,60 @@ export class CompositeClient {
1241
1251
  authenticatorType: AuthenticatorType,
1242
1252
  data: Uint8Array,
1243
1253
  ): Promise<BroadcastTxAsyncResponse | BroadcastTxSyncResponse | IndexedTx> {
1244
- return this.validatorClient.post.addAuthenticator(subaccount, authenticatorType, data)
1254
+ // Validate the provided authenticators before sending to the validator
1255
+ const authenticator: Authenticator = {
1256
+ type: authenticatorType,
1257
+ config: JSON.parse(new TextDecoder().decode(data)),
1258
+ };
1259
+ if (!this.validateAuthenticator(authenticator)) {
1260
+ throw new Error(
1261
+ 'Invalid authenticator, please ensure the authenticator permissions are correct',
1262
+ );
1263
+ }
1264
+
1265
+ return this.validatorClient.post.addAuthenticator(subaccount, authenticatorType, data);
1245
1266
  }
1246
1267
 
1247
1268
  async removeAuthenticator(
1248
1269
  subaccount: SubaccountInfo,
1249
1270
  id: Long,
1250
1271
  ): Promise<BroadcastTxAsyncResponse | BroadcastTxSyncResponse | IndexedTx> {
1251
- return this.validatorClient.post.removeAuthenticator(subaccount, id)
1272
+ return this.validatorClient.post.removeAuthenticator(subaccount, id);
1252
1273
  }
1253
1274
 
1254
- async getAuthenticators(address: string): Promise<GetAuthenticatorsResponse>{
1275
+ async getAuthenticators(address: string): Promise<GetAuthenticatorsResponse> {
1255
1276
  return this.validatorClient.get.getAuthenticators(address);
1256
1277
  }
1278
+
1279
+ validateAuthenticator(authenticator: Authenticator): boolean {
1280
+ function checkAuthenticator(auth: Authenticator): boolean {
1281
+ if (auth.type === AuthenticatorType.SIGNATURE_VERIFICATION) {
1282
+ return true; // A SignatureVerification authenticator is safe.
1283
+ }
1284
+
1285
+ if (!Array.isArray(auth.config)) {
1286
+ return false; // Unsafe case: a non-array config for a composite authenticator
1287
+ }
1288
+
1289
+ if (auth.type === AuthenticatorType.ANY_OF) {
1290
+ // ANY_OF is safe only if ALL sub-authenticators return true
1291
+ return auth.config.every((nestedAuth) => checkAuthenticator(nestedAuth));
1292
+ }
1293
+
1294
+ if (auth.type === AuthenticatorType.ALL_OF) {
1295
+ // ALL_OF is safe if at least one sub-authenticator returns true
1296
+ return auth.config.some((nestedAuth) => checkAuthenticator(nestedAuth));
1297
+ }
1298
+
1299
+ // If it's a base-case authenticator but not SignatureVerification, it's unsafe
1300
+ return false;
1301
+ }
1302
+
1303
+ // The top-level authenticator must pass validation
1304
+ if (!checkAuthenticator(authenticator)) {
1305
+ return false
1306
+ }
1307
+
1308
+ return true;
1309
+ }
1257
1310
  }
@@ -211,6 +211,11 @@ export enum AuthenticatorType {
211
211
  SUBACCOUNT_FILTER = 'SubaccountFilter',
212
212
  }
213
213
 
214
+ export interface Authenticator {
215
+ type: AuthenticatorType;
216
+ config: string | Authenticator[];
217
+ }
218
+
214
219
  export enum TradingRewardAggregationPeriod {
215
220
  DAILY = 'DAILY',
216
221
  WEEKLY = 'WEEKLY',