@dydxprotocol/v4-client-js 1.17.0 → 1.19.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.
- package/CHANGELOG.md +2 -2
- package/build/examples/websocket_orderbook_example.d.ts +1 -0
- package/build/examples/websocket_orderbook_example.js +196 -0
- package/build/src/clients/modules/local-wallet.d.ts +4 -4
- package/build/src/clients/modules/local-wallet.js +1 -1
- package/build/src/clients/modules/signer.d.ts +3 -3
- package/build/src/clients/modules/signer.js +18 -8
- package/build/tsconfig.tsbuildinfo +1 -1
- package/examples/websocket_orderbook_example.ts +239 -0
- package/package.json +1 -1
- package/src/clients/modules/local-wallet.ts +4 -4
- package/src/clients/modules/signer.ts +33 -12
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
/* eslint-disable no-param-reassign */
|
|
3
|
+
import { Network } from '../src/clients/constants';
|
|
4
|
+
import { SocketClient } from '../src/clients/socket-client';
|
|
5
|
+
|
|
6
|
+
function test(): void {
|
|
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
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
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();
|
|
77
|
+
}
|
|
78
|
+
|
|
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
|
+
});
|
|
88
|
+
};
|
|
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
|
+
});
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const printOrderBook = (
|
|
101
|
+
orderBookBidList: [number, number, number][],
|
|
102
|
+
orderBookAskList: [number, number, number][],
|
|
103
|
+
): void => {
|
|
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('');
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
function createSpaces(count: number): string {
|
|
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;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const resolveCrossedOrderBook = (
|
|
134
|
+
orderBookBidList: [number, number, number][],
|
|
135
|
+
orderBookAskList: [number, number, number][],
|
|
136
|
+
): [[number, number, number][], [number, number, number][]] => {
|
|
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
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return [orderBookBidList, orderBookAskList];
|
|
157
|
+
};
|
|
158
|
+
const setInitialOrderBook = (
|
|
159
|
+
orderBookDataList: { bids: []; asks: [] },
|
|
160
|
+
orderBookBidList: [number, number, number][],
|
|
161
|
+
orderBookAskList: [number, number, number][],
|
|
162
|
+
messageId: number,
|
|
163
|
+
): void => {
|
|
164
|
+
orderBookDataList.bids.forEach((item: { price: string; size: string }) => {
|
|
165
|
+
orderBookBidList.push([Number(item.price), Number(item.size), messageId]);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
orderBookDataList.asks.forEach((item: { price: string; size: string }) => {
|
|
169
|
+
orderBookAskList.push([Number(item.price), Number(item.size), messageId]);
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const updateOrderBook = (
|
|
174
|
+
orderBookDataList: { bids: [[]]; asks: [[]] }[],
|
|
175
|
+
orderBookBidList: [number, number, number][],
|
|
176
|
+
orderBookAskList: [number, number, number][],
|
|
177
|
+
messageId: number,
|
|
178
|
+
): [[number, number, number][], [number, number, number][]] => {
|
|
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;
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
} else {
|
|
202
|
+
// Add new data to order book
|
|
203
|
+
orderBookBidList.push([entryBidPrice, entryBidSize, messageId]);
|
|
204
|
+
}
|
|
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;
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
} else {
|
|
229
|
+
// Add new data to order book
|
|
230
|
+
orderBookAskList.push([entryAskPrice, entryAskSize, messageId]);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
return [orderBookBidList, orderBookAskList];
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
test();
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
AccountData,
|
|
4
4
|
DirectSecp256k1HdWallet,
|
|
5
5
|
EncodeObject,
|
|
6
|
-
|
|
6
|
+
OfflineSigner,
|
|
7
7
|
} from '@cosmjs/proto-signing';
|
|
8
8
|
import { SigningStargateClient } from '@cosmjs/stargate';
|
|
9
9
|
import Long from 'long';
|
|
@@ -22,9 +22,9 @@ export default class LocalWallet {
|
|
|
22
22
|
address?: string;
|
|
23
23
|
pubKey?: Secp256k1Pubkey;
|
|
24
24
|
signer?: TransactionSigner;
|
|
25
|
-
offlineSigner?:
|
|
25
|
+
offlineSigner?: OfflineSigner;
|
|
26
26
|
|
|
27
|
-
static async fromOfflineSigner(signer:
|
|
27
|
+
static async fromOfflineSigner(signer: OfflineSigner): Promise<LocalWallet> {
|
|
28
28
|
const wallet = new LocalWallet();
|
|
29
29
|
await wallet.setSigner(signer);
|
|
30
30
|
return wallet;
|
|
@@ -36,7 +36,7 @@ export default class LocalWallet {
|
|
|
36
36
|
return wallet;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
async setSigner(signer:
|
|
39
|
+
async setSigner(signer: OfflineSigner): Promise<void> {
|
|
40
40
|
this.offlineSigner = signer;
|
|
41
41
|
const stargateClient = await SigningStargateClient.offline(signer, {
|
|
42
42
|
registry: generateRegistry(),
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { Secp256k1Pubkey } from '@cosmjs/amino';
|
|
2
2
|
import { fromBase64 } from '@cosmjs/encoding';
|
|
3
3
|
import { Int53 } from '@cosmjs/math';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
EncodeObject,
|
|
6
|
+
encodePubkey,
|
|
7
|
+
isOfflineDirectSigner,
|
|
8
|
+
makeAuthInfoBytes,
|
|
9
|
+
makeSignDoc,
|
|
10
|
+
OfflineSigner,
|
|
11
|
+
} from '@cosmjs/proto-signing';
|
|
5
12
|
import { SigningStargateClient, StdFee } from '@cosmjs/stargate';
|
|
6
13
|
import { TxExtension } from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/accountplus/tx';
|
|
7
14
|
import { TxBody, TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
|
|
@@ -22,12 +29,12 @@ protobuf.configure();
|
|
|
22
29
|
export class TransactionSigner {
|
|
23
30
|
readonly address: string;
|
|
24
31
|
readonly stargateSigningClient: SigningStargateClient;
|
|
25
|
-
readonly offlineSigner:
|
|
32
|
+
readonly offlineSigner: OfflineSigner;
|
|
26
33
|
|
|
27
34
|
constructor(
|
|
28
35
|
address: string,
|
|
29
36
|
stargateSigningClient: SigningStargateClient,
|
|
30
|
-
offlineSigner:
|
|
37
|
+
offlineSigner: OfflineSigner,
|
|
31
38
|
) {
|
|
32
39
|
this.address = address;
|
|
33
40
|
this.stargateSigningClient = stargateSigningClient;
|
|
@@ -105,14 +112,28 @@ export class TransactionSigner {
|
|
|
105
112
|
|
|
106
113
|
// Use OfflineSigner to sign the transaction
|
|
107
114
|
const signerAddress = this.address;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
if (isOfflineDirectSigner(this.offlineSigner)) {
|
|
116
|
+
const { signed, signature } = await this.offlineSigner.signDirect(signerAddress, signDoc);
|
|
117
|
+
|
|
118
|
+
const txRaw = TxRaw.fromPartial({
|
|
119
|
+
bodyBytes: signed.bodyBytes,
|
|
120
|
+
authInfoBytes: signed.authInfoBytes,
|
|
121
|
+
signatures: [fromBase64(signature.signature)],
|
|
122
|
+
});
|
|
123
|
+
return Uint8Array.from(TxRaw.encode(txRaw).finish());
|
|
124
|
+
} else {
|
|
125
|
+
const rawTx: TxRaw = await this.stargateSigningClient.sign(
|
|
126
|
+
this.address,
|
|
127
|
+
messages,
|
|
128
|
+
fee,
|
|
129
|
+
memo,
|
|
130
|
+
{
|
|
131
|
+
accountNumber: transactionOptions.accountNumber,
|
|
132
|
+
sequence: transactionOptions.sequence,
|
|
133
|
+
chainId: transactionOptions.chainId,
|
|
134
|
+
},
|
|
135
|
+
);
|
|
136
|
+
return Uint8Array.from(TxRaw.encode(rawTx).finish());
|
|
137
|
+
}
|
|
117
138
|
}
|
|
118
139
|
}
|