@dydxprotocol/v4-client-js 2.4.0 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +2 -8
  2. package/build/cjs/examples/constants.js +4 -2
  3. package/build/cjs/examples/permissioned_keys_example.js +52 -29
  4. package/build/cjs/src/clients/composite-client.js +30 -20
  5. package/build/cjs/src/clients/constants.js +3 -2
  6. package/build/cjs/src/clients/indexer-client.js +5 -5
  7. package/build/cjs/src/clients/modules/rest.js +11 -5
  8. package/build/cjs/src/clients/socket-client.js +6 -2
  9. package/build/cjs/src/lib/utils.js +7 -1
  10. package/build/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  11. package/build/esm/examples/constants.d.ts +1 -0
  12. package/build/esm/examples/constants.d.ts.map +1 -1
  13. package/build/esm/examples/constants.js +3 -1
  14. package/build/esm/examples/permissioned_keys_example.js +53 -30
  15. package/build/esm/src/clients/composite-client.d.ts.map +1 -1
  16. package/build/esm/src/clients/composite-client.js +30 -20
  17. package/build/esm/src/clients/constants.d.ts +3 -1
  18. package/build/esm/src/clients/constants.d.ts.map +1 -1
  19. package/build/esm/src/clients/constants.js +3 -2
  20. package/build/esm/src/clients/indexer-client.js +5 -5
  21. package/build/esm/src/clients/modules/rest.d.ts +3 -1
  22. package/build/esm/src/clients/modules/rest.d.ts.map +1 -1
  23. package/build/esm/src/clients/modules/rest.js +8 -5
  24. package/build/esm/src/clients/socket-client.d.ts +1 -0
  25. package/build/esm/src/clients/socket-client.d.ts.map +1 -1
  26. package/build/esm/src/clients/socket-client.js +6 -2
  27. package/build/esm/src/lib/utils.d.ts +3 -0
  28. package/build/esm/src/lib/utils.d.ts.map +1 -1
  29. package/build/esm/src/lib/utils.js +6 -1
  30. package/build/esm/tsconfig.esm.tsbuildinfo +1 -1
  31. package/examples/constants.ts +3 -0
  32. package/examples/permissioned_keys_example.ts +58 -29
  33. package/package.json +2 -1
  34. package/src/clients/composite-client.ts +28 -21
  35. package/src/clients/constants.ts +4 -1
  36. package/src/clients/indexer-client.ts +4 -4
  37. package/src/clients/modules/rest.ts +10 -4
  38. package/src/clients/socket-client.ts +7 -1
  39. package/src/lib/utils.ts +8 -0
@@ -17,6 +17,9 @@ export const DYDX_LOCAL_MNEMONIC =
17
17
 
18
18
  export const DYDX_TEST_MNEMONIC_2 = 'movie yard still copper exile wear brisk chest ride dizzy novel future menu finish radar lunar claim hub middle force turtle mouse frequent embark';
19
19
 
20
+ // dydx1fxu2ndxwju2evh28js0cflkxn5wxzvlhw4gec5
21
+ export const DYDX_TEST_MNEMONIC_3 = 'crane poverty boil edit response weasel quick almost fringe able upon benefit boil boat narrow innocent enter cradle tongue toward luggage awful mention column';
22
+
20
23
  export const MARKET_BTC_USD: string = 'BTC-USD';
21
24
  export const PERPETUAL_PAIR_BTC_USD: number = 0;
22
25
 
@@ -8,11 +8,12 @@ import { CompositeClient } from '../src/clients/composite-client';
8
8
  import { AuthenticatorType, Network, OrderSide, SelectedGasDenom } from '../src/clients/constants';
9
9
  import LocalWallet from '../src/clients/modules/local-wallet';
10
10
  import { SubaccountInfo } from '../src/clients/subaccount';
11
- import { DYDX_TEST_MNEMONIC, DYDX_TEST_MNEMONIC_2 } from './constants';
11
+ import { DYDX_TEST_MNEMONIC, DYDX_TEST_MNEMONIC_2, DYDX_TEST_MNEMONIC_3 } from './constants';
12
12
 
13
13
  async function test(): Promise<void> {
14
14
  const wallet1 = await LocalWallet.fromMnemonic(DYDX_TEST_MNEMONIC, BECH32_PREFIX);
15
15
  const wallet2 = await LocalWallet.fromMnemonic(DYDX_TEST_MNEMONIC_2, BECH32_PREFIX);
16
+ const wallet3 = await LocalWallet.fromMnemonic(DYDX_TEST_MNEMONIC_3, BECH32_PREFIX);
16
17
 
17
18
  const network = Network.staging();
18
19
  const client = await CompositeClient.connect(network);
@@ -23,28 +24,53 @@ async function test(): Promise<void> {
23
24
 
24
25
  const subaccount1 = new SubaccountInfo(wallet1, 0);
25
26
  const subaccount2 = new SubaccountInfo(wallet2, 0);
27
+ const subaccount3 = new SubaccountInfo(wallet3, 0);
26
28
 
27
29
  // Change second wallet pubkey
28
30
  // Add an authenticator to allow wallet2 to place orders
29
31
  console.log("** Adding authenticator **");
30
- await addAuthenticator(client, subaccount1, wallet2.pubKey!.value);
31
-
32
- const authenticators = await client.getAuthenticators(wallet1.address!);
32
+ // Record authenticator count before adding
33
+ const authsBefore = await client.getAuthenticators(wallet1.address!);
34
+ const beforeCount = authsBefore.accountAuthenticators.length;
35
+ console.log(`Authenticators before: ${beforeCount}`);
36
+ await addTestAuthenticator(client, subaccount1, wallet2.pubKey!.value);
37
+
38
+ console.log("** Waiting 3 seconds for txn to be confirmed **");
39
+ await new Promise((resolve) => setTimeout(resolve, 3000));
40
+
41
+ const authsAfter = await client.getAuthenticators(wallet1.address!);
42
+ const afterCount = authsAfter.accountAuthenticators.length;
43
+ console.log(`Authenticators after: ${afterCount}`);
44
+ if (afterCount !== beforeCount + 1) {
45
+ console.error('Authenticator count did not increment by 1.');
46
+ process.exit(1);
47
+ } else {
48
+ console.log('Authenticator count incremented by 1 as expected.');
49
+ }
33
50
  // Last element in authenticators array is the most recently created
34
- const lastElement = authenticators.accountAuthenticators.length - 1;
35
- const authenticatorID = authenticators.accountAuthenticators[lastElement].id;
51
+ const lastElement = authsAfter.accountAuthenticators.length - 1;
52
+ const newAuthenticatorID = authsAfter.accountAuthenticators[lastElement].id;
53
+
54
+ console.log(`New authenticator ID: ${newAuthenticatorID}`);
36
55
 
37
56
  // Placing order using subaccount2 for subaccount1 succeeds
38
- console.log("** Placing order with authenticator **");
39
- await placeOrder(client, subaccount2, subaccount1, authenticatorID);
57
+ console.log("** Placing order for subaccount1 with subaccount2 + authenticator, should succeed **");
58
+ await placeOrder(client, subaccount2, subaccount1, newAuthenticatorID);
59
+
60
+ // Placing order using subaccount3 for subaccount1 should fail
61
+ console.log("** Placing order for subaccount1 with subaccount3 + authenticator, should fail **");
62
+ await placeOrder(client, subaccount3, subaccount1, newAuthenticatorID);
40
63
 
41
64
  // Remove authenticator
42
65
  console.log("** Removing authenticator **");
43
- await removeAuthenticator(client, subaccount1, authenticatorID);
66
+ await removeAuthenticator(client, subaccount1, newAuthenticatorID);
44
67
 
68
+ console.log("** Waiting 3 seconds for txn to be confirmed **");
69
+ await new Promise((resolve) => setTimeout(resolve, 3000));
70
+
45
71
  // Placing an order using subaccount2 will now fail
46
- console.log("** Placing order with invalid authenticator should fail **");
47
- await placeOrder(client, subaccount2, subaccount1, authenticatorID);
72
+ console.log("** Placing order with removed authenticator should fail **");
73
+ await placeOrder(client, subaccount2, subaccount1, newAuthenticatorID);
48
74
  }
49
75
 
50
76
  async function removeAuthenticator(
@@ -55,38 +81,41 @@ async function removeAuthenticator(
55
81
  await client.removeAuthenticator(subaccount, id);
56
82
  }
57
83
 
58
- async function addAuthenticator(
84
+ async function addTestAuthenticator(
59
85
  client: CompositeClient,
60
86
  subaccount: SubaccountInfo,
61
87
  authedPubKey: string,
62
88
  ): Promise<void> {
63
- const subAuth = [ {
64
- type: AuthenticatorType.SIGNATURE_VERIFICATION,
65
- config: authedPubKey,
66
- },
67
- {
68
- type: AuthenticatorType.ANY_OF,
69
- config: [
89
+ const msgType = (s: string): string => toBase64(new TextEncoder().encode(s));
90
+ const anyOfSubAuth = [
70
91
  {
71
92
  type: AuthenticatorType.MESSAGE_FILTER,
72
- config: toBase64(new TextEncoder().encode('/dydxprotocol.clob.MsgPlaceOrder')),
93
+ config: msgType('/dydxprotocol.clob.MsgPlaceOrder'),
73
94
  },
74
95
  {
75
96
  type: AuthenticatorType.MESSAGE_FILTER,
76
- config: toBase64(new TextEncoder().encode('/dydxprotocol.clob.MsgPlaceOrder')),
97
+ config: msgType('/dydxprotocol.sending.MsgCreateTransfer'),
77
98
  },
78
- ]
79
- }
80
- ];
99
+ ];
100
+
101
+ // Nested AnyOf config must be base64(JSON([...])) as on-chain expects []byte
102
+ const anyOfConfigB64 = toBase64(new TextEncoder().encode(JSON.stringify(anyOfSubAuth)));
103
+
104
+ const subAuth = [
105
+ {
106
+ type: AuthenticatorType.SIGNATURE_VERIFICATION,
107
+ config: authedPubKey,
108
+ },
109
+ {
110
+ type: AuthenticatorType.ANY_OF,
111
+ config: anyOfConfigB64,
112
+ },
113
+ ];
81
114
 
82
115
  const jsonString = JSON.stringify(subAuth);
83
116
  const encodedData = new TextEncoder().encode(jsonString);
84
117
 
85
- try {
86
- await client.addAuthenticator(subaccount, AuthenticatorType.ALL_OF, encodedData);
87
- } catch (error) {
88
- console.log(error.message);
89
- }
118
+ await client.addAuthenticator(subaccount, AuthenticatorType.ALL_OF, encodedData);
90
119
  }
91
120
 
92
121
  async function placeOrder(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dydxprotocol/v4-client-js",
3
- "version": "2.4.0",
3
+ "version": "2.5.1",
4
4
  "description": "General client library for the new dYdX system (v4 decentralized)",
5
5
  "main": "build/cjs/src/index.js",
6
6
  "module": "build/esm/src/index.js",
@@ -62,6 +62,7 @@
62
62
  "cosmjs-types": "^0.9.0",
63
63
  "ethereum-cryptography": "^2.0.0",
64
64
  "ethers": "^6.6.1",
65
+ "https-proxy-agent": "^7.0.6",
65
66
  "long": "^4.0.0",
66
67
  "protobufjs": ">=6.11.4",
67
68
  "ws": "^8.14.2"
@@ -1084,7 +1084,7 @@ export class CompositeClient {
1084
1084
  console.log(err);
1085
1085
  });
1086
1086
  });
1087
- const signature = await this.sign(wallet, () => msgs, true);
1087
+ const signature = await this.sign(subaccount.wallet, () => msgs, true);
1088
1088
 
1089
1089
  return Buffer.from(signature).toString('base64');
1090
1090
  }
@@ -1414,34 +1414,41 @@ export class CompositeClient {
1414
1414
  }
1415
1415
 
1416
1416
  validateAuthenticator(authenticator: Authenticator): boolean {
1417
- function checkAuthenticator(auth: Authenticator): boolean {
1418
- if (auth.type === AuthenticatorType.SIGNATURE_VERIFICATION) {
1419
- return true; // A SignatureVerification authenticator is safe.
1417
+ const decodeCompositeConfig = (config: unknown): Authenticator[] | null => {
1418
+ if (Array.isArray(config)) {
1419
+ return config as Authenticator[];
1420
1420
  }
1421
-
1422
- if (!Array.isArray(auth.config)) {
1423
- return false; // Unsafe case: a non-array config for a composite authenticator
1421
+ if (typeof config === 'string') {
1422
+ try {
1423
+ const decoded = Buffer.from(config, 'base64').toString('utf8');
1424
+ const parsed = JSON.parse(decoded);
1425
+ return Array.isArray(parsed) ? (parsed as Authenticator[]) : null;
1426
+ } catch {
1427
+ return null;
1428
+ }
1424
1429
  }
1430
+ return null;
1431
+ };
1425
1432
 
1426
- if (auth.type === AuthenticatorType.ANY_OF) {
1427
- // ANY_OF is safe only if ALL sub-authenticators return true
1428
- return auth.config.every((nestedAuth) => checkAuthenticator(nestedAuth));
1433
+ const checkAuthenticator = (auth: Authenticator): boolean => {
1434
+ if (auth.type === AuthenticatorType.SIGNATURE_VERIFICATION) {
1435
+ return true;
1429
1436
  }
1430
1437
 
1431
- if (auth.type === AuthenticatorType.ALL_OF) {
1432
- // ALL_OF is safe if at least one sub-authenticator returns true
1433
- return auth.config.some((nestedAuth) => checkAuthenticator(nestedAuth));
1438
+ if (auth.type === AuthenticatorType.ANY_OF || auth.type === AuthenticatorType.ALL_OF) {
1439
+ const subAuthenticators = decodeCompositeConfig(auth.config);
1440
+ if (subAuthenticators == null) {
1441
+ return false;
1442
+ }
1443
+ if (auth.type === AuthenticatorType.ANY_OF) {
1444
+ return subAuthenticators.every((nested) => checkAuthenticator(nested));
1445
+ }
1446
+ return subAuthenticators.some((nested) => checkAuthenticator(nested));
1434
1447
  }
1435
1448
 
1436
- // If it's a base-case authenticator but not SignatureVerification, it's unsafe
1437
- return false;
1438
- }
1439
-
1440
- // The top-level authenticator must pass validation
1441
- if (!checkAuthenticator(authenticator)) {
1442
1449
  return false;
1443
- }
1450
+ };
1444
1451
 
1445
- return true;
1452
+ return checkAuthenticator(authenticator);
1446
1453
  }
1447
1454
  }
@@ -1,4 +1,5 @@
1
1
  import { PageRequest } from '@dydxprotocol/v4-proto/src/codegen/cosmos/base/query/v1beta1/pagination';
2
+ import { AxiosProxyConfig } from 'axios';
2
3
  import Long from 'long';
3
4
 
4
5
  import { BroadcastOptions, DenomConfig } from './types';
@@ -243,10 +244,12 @@ export const PAGE_REQUEST: PageRequest = {
243
244
  export class IndexerConfig {
244
245
  public restEndpoint: string;
245
246
  public websocketEndpoint: string;
247
+ public proxy?: AxiosProxyConfig;
246
248
 
247
- constructor(restEndpoint: string, websocketEndpoint: string) {
249
+ constructor(restEndpoint: string, websocketEndpoint: string, proxy?: AxiosProxyConfig) {
248
250
  this.restEndpoint = restEndpoint;
249
251
  this.websocketEndpoint = websocketEndpoint;
252
+ this.proxy = proxy;
250
253
  }
251
254
  }
252
255
 
@@ -19,10 +19,10 @@ export class IndexerClient {
19
19
  this.config = config;
20
20
  this.apiTimeout = apiTimeout ?? DEFAULT_API_TIMEOUT;
21
21
 
22
- this._markets = new MarketsClient(config.restEndpoint);
23
- this._account = new AccountClient(config.restEndpoint);
24
- this._utility = new UtilityClient(config.restEndpoint);
25
- this._vault = new VaultClient(config.restEndpoint);
22
+ this._markets = new MarketsClient(config.restEndpoint, apiTimeout, config.proxy);
23
+ this._account = new AccountClient(config.restEndpoint, apiTimeout, config.proxy);
24
+ this._utility = new UtilityClient(config.restEndpoint, apiTimeout, config.proxy);
25
+ this._vault = new VaultClient(config.restEndpoint, apiTimeout, config.proxy);
26
26
  }
27
27
 
28
28
  /**
@@ -1,24 +1,30 @@
1
+ import axios, { AxiosInstance, AxiosProxyConfig } from 'axios';
2
+
1
3
  import { DEFAULT_API_TIMEOUT } from '../constants';
2
4
  import { generateQueryPath } from '../helpers/request-helpers';
3
- import { RequestMethod, Response, request } from '../lib/axios';
5
+ import { Response } from '../lib/axios';
4
6
  import { Data } from '../types';
5
7
 
6
8
  export default class RestClient {
7
9
  readonly host: string;
8
10
  readonly apiTimeout: Number;
11
+ readonly axiosInstance: AxiosInstance;
9
12
 
10
- constructor(host: string, apiTimeout?: Number | null) {
13
+ constructor(host: string, apiTimeout?: Number | null, proxy?: AxiosProxyConfig) {
11
14
  if (host.endsWith('/')) {
12
15
  this.host = host.slice(0, -1);
13
16
  } else {
14
17
  this.host = host;
15
18
  }
16
19
  this.apiTimeout = apiTimeout || DEFAULT_API_TIMEOUT;
20
+ this.axiosInstance = axios.create({
21
+ proxy,
22
+ });
17
23
  }
18
24
 
19
25
  async get(requestPath: string, params: {} = {}): Promise<Data> {
20
26
  const url = `${this.host}${generateQueryPath(requestPath, params)}`;
21
- const response = await request(url);
27
+ const response = await this.axiosInstance.get(url);
22
28
  return response.data;
23
29
  }
24
30
 
@@ -29,6 +35,6 @@ export default class RestClient {
29
35
  headers: {} = {},
30
36
  ): Promise<Response> {
31
37
  const url = `${this.host}${generateQueryPath(requestPath, params)}`;
32
- return request(url, RequestMethod.POST, body, headers);
38
+ return this.axiosInstance.post(url, body, { headers });
33
39
  }
34
40
  }
@@ -1,5 +1,7 @@
1
+ import { HttpsProxyAgent } from 'https-proxy-agent';
1
2
  import WebSocket, { ErrorEvent, MessageEvent } from 'ws';
2
3
 
4
+ import { getProxyAgent } from '../lib/utils';
3
5
  import { IndexerConfig } from './constants';
4
6
 
5
7
  enum OutgoingMessageTypes {
@@ -37,6 +39,7 @@ export enum CandlesResolution {
37
39
  export class SocketClient {
38
40
  private url: string;
39
41
  private ws?: WebSocket;
42
+ private proxyAgent?: HttpsProxyAgent<string>;
40
43
  private onOpenCallback?: () => void;
41
44
  private onCloseCallback?: () => void;
42
45
  private onMessageCallback?: (event: MessageEvent) => void;
@@ -51,6 +54,7 @@ export class SocketClient {
51
54
  onErrorCallback: (event: ErrorEvent) => void,
52
55
  ) {
53
56
  this.url = config.websocketEndpoint;
57
+ this.proxyAgent = config.proxy ? getProxyAgent(config.proxy) : undefined;
54
58
  this.onOpenCallback = onOpenCallback;
55
59
  this.onCloseCallback = onCloseCallback;
56
60
  this.onMessageCallback = onMessageCallback;
@@ -58,7 +62,9 @@ export class SocketClient {
58
62
  }
59
63
 
60
64
  connect(): void {
61
- this.ws = new WebSocket(this.url);
65
+ this.ws = new WebSocket(this.url, {
66
+ agent: this.proxyAgent,
67
+ });
62
68
  this.ws.addEventListener('open', this.handleOpen.bind(this));
63
69
  this.ws.addEventListener('close', this.handleClose.bind(this));
64
70
  this.ws.addEventListener('message', this.handleMessage.bind(this));
package/src/lib/utils.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { AxiosProxyConfig } from 'axios';
2
+ import { HttpsProxyAgent } from 'https-proxy-agent';
3
+
1
4
  import { MAX_UINT_32 } from '../clients/constants';
2
5
 
3
6
  /**
@@ -74,3 +77,8 @@ export function calculateClockOffsetFromFetchDateHeader(
74
77
 
75
78
  return Math.floor(offset);
76
79
  }
80
+
81
+ export function getProxyAgent(proxy: AxiosProxyConfig): HttpsProxyAgent<string> {
82
+ const auth = proxy.auth ? `${proxy.auth.username}:${proxy.auth.password}@` : '';
83
+ return new HttpsProxyAgent(`${proxy.protocol}://${auth}${proxy.host}:${proxy.port}`);
84
+ }