@injectivelabs/wallet-core 1.15.0 → 1.15.2

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/dist/cjs/broadcaster/MsgBroadcaster.d.ts +132 -0
  2. package/dist/cjs/broadcaster/MsgBroadcaster.js +782 -0
  3. package/dist/cjs/broadcaster/Web3Broadcaster.d.ts +29 -0
  4. package/dist/cjs/broadcaster/Web3Broadcaster.js +31 -0
  5. package/dist/cjs/broadcaster/index.d.ts +3 -0
  6. package/dist/cjs/broadcaster/index.js +19 -0
  7. package/dist/cjs/broadcaster/types.d.ts +32 -0
  8. package/dist/cjs/broadcaster/types.js +2 -0
  9. package/dist/cjs/index.d.ts +3 -0
  10. package/dist/cjs/index.js +19 -0
  11. package/dist/cjs/package.json +3 -0
  12. package/dist/cjs/strategy/BaseWalletStrategy.d.ts +44 -0
  13. package/dist/cjs/strategy/BaseWalletStrategy.js +127 -0
  14. package/dist/cjs/strategy/index.d.ts +2 -0
  15. package/dist/cjs/strategy/index.js +8 -0
  16. package/dist/cjs/utils/index.d.ts +1 -0
  17. package/dist/cjs/utils/index.js +17 -0
  18. package/dist/cjs/utils/tx.d.ts +1 -0
  19. package/dist/cjs/utils/tx.js +11 -0
  20. package/dist/esm/broadcaster/MsgBroadcaster.d.ts +132 -0
  21. package/dist/esm/broadcaster/MsgBroadcaster.js +778 -0
  22. package/dist/esm/broadcaster/Web3Broadcaster.d.ts +29 -0
  23. package/dist/esm/broadcaster/Web3Broadcaster.js +27 -0
  24. package/dist/esm/broadcaster/index.d.ts +3 -0
  25. package/dist/esm/broadcaster/index.js +3 -0
  26. package/dist/esm/broadcaster/types.d.ts +32 -0
  27. package/dist/esm/broadcaster/types.js +1 -0
  28. package/dist/esm/index.d.ts +3 -0
  29. package/dist/esm/index.js +3 -0
  30. package/dist/esm/package.json +3 -0
  31. package/dist/esm/strategy/BaseWalletStrategy.d.ts +44 -0
  32. package/dist/esm/strategy/BaseWalletStrategy.js +124 -0
  33. package/dist/esm/strategy/index.d.ts +2 -0
  34. package/dist/esm/strategy/index.js +2 -0
  35. package/dist/esm/utils/index.d.ts +1 -0
  36. package/dist/esm/utils/index.js +1 -0
  37. package/dist/esm/utils/tx.d.ts +1 -0
  38. package/dist/esm/utils/tx.js +7 -0
  39. package/package.json +8 -8
@@ -0,0 +1,778 @@
1
+ import { TxGrpcApi, hexToBuff, PublicKey, SIGN_DIRECT, hexToBase64, ofacWallets, SIGN_EIP712_V2, SIGN_EIP712, ChainGrpcAuthApi, createTxRawEIP712, createTransaction, getAminoStdSignDoc, getEip712TypedData, createWeb3Extension, getEip712TypedDataV2, IndexerGrpcWeb3GwApi, ChainGrpcTendermintApi, createTransactionWithSigners, createTxRawFromSigResponse, getGasPriceBasedOnMessage, recoverTypedSignaturePubKey, } from '@injectivelabs/sdk-ts';
2
+ import { sleep, getStdFee, BigNumberInBase, DEFAULT_BLOCK_TIMEOUT_HEIGHT, } from '@injectivelabs/utils';
3
+ import { GeneralException, isThrownException, UnspecifiedErrorCode, ChainCosmosErrorCode, TransactionException, TransactionChainErrorModule, } from '@injectivelabs/exceptions';
4
+ import { getNetworkInfo, getNetworkEndpoints, } from '@injectivelabs/networks';
5
+ import { checkIfTxRunOutOfGas } from './../utils/tx.js';
6
+ import { Wallet, isCosmosWallet, WalletDeviceType, isEip712V2OnlyWallet, createEip712StdSignDoc, isCosmosAminoOnlyWallet, getEthereumSignerAddress, getInjectiveSignerAddress, } from '@injectivelabs/wallet-base';
7
+ const getEthereumWalletPubKey = ({ pubKey, eip712TypedData, signature, }) => {
8
+ if (pubKey) {
9
+ return pubKey;
10
+ }
11
+ return hexToBase64(recoverTypedSignaturePubKey(eip712TypedData, signature));
12
+ };
13
+ const defaultRetriesConfig = () => ({
14
+ [`${TransactionChainErrorModule.CosmosSdk}-${ChainCosmosErrorCode.ErrMempoolIsFull}`]: {
15
+ retries: 0,
16
+ maxRetries: 10,
17
+ timeout: 1000,
18
+ },
19
+ });
20
+ /**
21
+ * This class is used to broadcast transactions
22
+ * using the WalletStrategy as a handler
23
+ * for the sign/broadcast flow of the transactions
24
+ *
25
+ * Mainly used for building UI products
26
+ */
27
+ export class MsgBroadcaster {
28
+ options;
29
+ walletStrategy;
30
+ endpoints;
31
+ chainId;
32
+ txTimeout = DEFAULT_BLOCK_TIMEOUT_HEIGHT;
33
+ simulateTx = true;
34
+ txTimeoutOnFeeDelegation = false;
35
+ ethereumChainId;
36
+ gasBufferCoefficient = 1.2;
37
+ retriesOnError = defaultRetriesConfig();
38
+ constructor(options) {
39
+ const networkInfo = getNetworkInfo(options.network);
40
+ this.options = options;
41
+ this.simulateTx =
42
+ options.simulateTx !== undefined ? options.simulateTx : true;
43
+ this.txTimeout = options.txTimeout || DEFAULT_BLOCK_TIMEOUT_HEIGHT;
44
+ this.txTimeoutOnFeeDelegation =
45
+ options.txTimeoutOnFeeDelegation !== undefined
46
+ ? options.txTimeoutOnFeeDelegation
47
+ : true;
48
+ this.gasBufferCoefficient = options.gasBufferCoefficient || 1.2;
49
+ this.chainId = options.chainId || networkInfo.chainId;
50
+ this.ethereumChainId =
51
+ options.ethereumChainId || networkInfo.ethereumChainId;
52
+ this.endpoints = options.endpoints || getNetworkEndpoints(options.network);
53
+ this.walletStrategy = options.walletStrategy;
54
+ }
55
+ setOptions(options) {
56
+ this.simulateTx = options.simulateTx || this.simulateTx;
57
+ this.txTimeout = options.txTimeout || this.txTimeout;
58
+ this.txTimeoutOnFeeDelegation =
59
+ options.txTimeoutOnFeeDelegation || this.txTimeoutOnFeeDelegation;
60
+ }
61
+ /**
62
+ * Broadcasting the transaction using the client
63
+ * side approach for both cosmos and ethereum native wallets
64
+ *
65
+ * @param tx
66
+ * @returns {string} transaction hash
67
+ */
68
+ async broadcast(tx) {
69
+ const { walletStrategy } = this;
70
+ const txWithAddresses = {
71
+ ...tx,
72
+ ethereumAddress: getEthereumSignerAddress(tx.injectiveAddress),
73
+ injectiveAddress: getInjectiveSignerAddress(tx.injectiveAddress),
74
+ };
75
+ if (ofacWallets.includes(txWithAddresses.ethereumAddress)) {
76
+ throw new GeneralException(new Error('You cannot execute this transaction'));
77
+ }
78
+ try {
79
+ return isCosmosWallet(walletStrategy.wallet)
80
+ ? await this.broadcastDirectSign(txWithAddresses)
81
+ : isEip712V2OnlyWallet(walletStrategy.wallet)
82
+ ? await this.broadcastEip712V2(txWithAddresses)
83
+ : await this.broadcastEip712(txWithAddresses);
84
+ }
85
+ catch (e) {
86
+ const error = e;
87
+ if (isThrownException(error)) {
88
+ throw error;
89
+ }
90
+ throw new TransactionException(new Error(error));
91
+ }
92
+ }
93
+ /**
94
+ * Broadcasting the transaction using the client
95
+ * side approach for both cosmos and ethereum native wallets
96
+ * Note: using EIP712_V2 for Ethereum wallets
97
+ *
98
+ * @param tx
99
+ * @returns {string} transaction hash
100
+ */
101
+ async broadcastV2(tx) {
102
+ const { walletStrategy } = this;
103
+ const txWithAddresses = {
104
+ ...tx,
105
+ ethereumAddress: getEthereumSignerAddress(tx.injectiveAddress),
106
+ injectiveAddress: getInjectiveSignerAddress(tx.injectiveAddress),
107
+ };
108
+ if (ofacWallets.includes(txWithAddresses.ethereumAddress)) {
109
+ throw new GeneralException(new Error('You cannot execute this transaction'));
110
+ }
111
+ try {
112
+ return isCosmosWallet(walletStrategy.wallet)
113
+ ? await this.broadcastDirectSign(txWithAddresses)
114
+ : await this.broadcastEip712V2(txWithAddresses);
115
+ }
116
+ catch (e) {
117
+ const error = e;
118
+ if (isThrownException(error)) {
119
+ throw error;
120
+ }
121
+ throw new TransactionException(new Error(error));
122
+ }
123
+ }
124
+ /**
125
+ * Broadcasting the transaction using the feeDelegation
126
+ * support approach for both cosmos and ethereum native wallets
127
+ *
128
+ * @param tx
129
+ * @returns {string} transaction hash
130
+ */
131
+ async broadcastWithFeeDelegation(tx) {
132
+ const { walletStrategy } = this;
133
+ const txWithAddresses = {
134
+ ...tx,
135
+ ethereumAddress: getEthereumSignerAddress(tx.injectiveAddress),
136
+ injectiveAddress: getInjectiveSignerAddress(tx.injectiveAddress),
137
+ };
138
+ if (ofacWallets.includes(txWithAddresses.ethereumAddress)) {
139
+ throw new GeneralException(new Error('You cannot execute this transaction'));
140
+ }
141
+ try {
142
+ return isCosmosWallet(walletStrategy.wallet)
143
+ ? await this.broadcastDirectSignWithFeeDelegation(txWithAddresses)
144
+ : await this.broadcastEip712WithFeeDelegation(txWithAddresses);
145
+ }
146
+ catch (e) {
147
+ const error = e;
148
+ if (isThrownException(error)) {
149
+ throw error;
150
+ }
151
+ throw new TransactionException(new Error(error));
152
+ }
153
+ }
154
+ /**
155
+ * Prepare/sign/broadcast transaction using
156
+ * Ethereum native wallets on the client side.
157
+ *
158
+ * Note: Gas estimation not available
159
+ *
160
+ * @param tx The transaction that needs to be broadcasted
161
+ * @returns transaction hash
162
+ */
163
+ async broadcastEip712(tx) {
164
+ const { chainId, txTimeout, endpoints, ethereumChainId, walletStrategy } = this;
165
+ const msgs = Array.isArray(tx.msgs) ? tx.msgs : [tx.msgs];
166
+ if (!ethereumChainId) {
167
+ throw new GeneralException(new Error('Please provide ethereumChainId'));
168
+ }
169
+ /** Account Details * */
170
+ const accountDetails = await new ChainGrpcAuthApi(endpoints.grpc).fetchAccount(tx.injectiveAddress);
171
+ const { baseAccount } = accountDetails;
172
+ /** Block Details */
173
+ const latestBlock = await new ChainGrpcTendermintApi(endpoints.grpc).fetchLatestBlock();
174
+ const latestHeight = latestBlock.header.height;
175
+ const timeoutHeight = new BigNumberInBase(latestHeight).plus(txTimeout);
176
+ const gas = (tx.gas?.gas || getGasPriceBasedOnMessage(msgs)).toString();
177
+ let stdFee = getStdFee({ ...tx.gas, gas });
178
+ /**
179
+ * Account has been created on chain
180
+ * and we can simulate the transaction
181
+ * to estimate the gas
182
+ **/
183
+ if (baseAccount.pubKey) {
184
+ const { stdFee: simulatedStdFee } = await this.getTxWithSignersAndStdFee({
185
+ chainId,
186
+ signMode: SIGN_EIP712,
187
+ memo: tx.memo,
188
+ message: msgs,
189
+ timeoutHeight: timeoutHeight.toNumber(),
190
+ signers: {
191
+ pubKey: baseAccount.pubKey.key,
192
+ accountNumber: baseAccount.accountNumber,
193
+ sequence: baseAccount.sequence,
194
+ },
195
+ fee: stdFee,
196
+ });
197
+ stdFee = simulatedStdFee;
198
+ }
199
+ /** EIP712 for signing on Ethereum wallets */
200
+ const eip712TypedData = getEip712TypedData({
201
+ msgs,
202
+ fee: stdFee,
203
+ tx: {
204
+ memo: tx.memo,
205
+ accountNumber: baseAccount.accountNumber.toString(),
206
+ sequence: baseAccount.sequence.toString(),
207
+ timeoutHeight: timeoutHeight.toFixed(),
208
+ chainId,
209
+ },
210
+ ethereumChainId,
211
+ });
212
+ /** Signing on Ethereum */
213
+ const signature = await walletStrategy.signEip712TypedData(JSON.stringify(eip712TypedData), tx.ethereumAddress);
214
+ const pubKeyOrSignatureDerivedPubKey = getEthereumWalletPubKey({
215
+ pubKey: baseAccount.pubKey?.key,
216
+ eip712TypedData,
217
+ signature,
218
+ });
219
+ /** Preparing the transaction for client broadcasting */
220
+ const { txRaw } = createTransaction({
221
+ message: msgs,
222
+ memo: tx.memo,
223
+ signMode: SIGN_EIP712,
224
+ fee: stdFee,
225
+ pubKey: pubKeyOrSignatureDerivedPubKey,
226
+ sequence: baseAccount.sequence,
227
+ timeoutHeight: timeoutHeight.toNumber(),
228
+ accountNumber: baseAccount.accountNumber,
229
+ chainId,
230
+ });
231
+ const web3Extension = createWeb3Extension({
232
+ ethereumChainId,
233
+ });
234
+ const txRawEip712 = createTxRawEIP712(txRaw, web3Extension);
235
+ /** Append Signatures */
236
+ txRawEip712.signatures = [hexToBuff(signature)];
237
+ return walletStrategy.sendTransaction(txRawEip712, {
238
+ chainId,
239
+ endpoints,
240
+ txTimeout,
241
+ address: tx.injectiveAddress,
242
+ });
243
+ }
244
+ /**
245
+ * Prepare/sign/broadcast transaction using
246
+ * Ethereum native wallets on the client side.
247
+ *
248
+ * Note: Gas estimation not available
249
+ *
250
+ * @param tx The transaction that needs to be broadcasted
251
+ * @returns transaction hash
252
+ */
253
+ async broadcastEip712V2(tx) {
254
+ const { walletStrategy, chainId, txTimeout, endpoints, ethereumChainId } = this;
255
+ const msgs = Array.isArray(tx.msgs) ? tx.msgs : [tx.msgs];
256
+ if (!ethereumChainId) {
257
+ throw new GeneralException(new Error('Please provide ethereumChainId'));
258
+ }
259
+ /** Account Details * */
260
+ const accountDetails = await new ChainGrpcAuthApi(endpoints.grpc).fetchAccount(tx.injectiveAddress);
261
+ const { baseAccount } = accountDetails;
262
+ /** Block Details */
263
+ const latestBlock = await new ChainGrpcTendermintApi(endpoints.grpc).fetchLatestBlock();
264
+ const latestHeight = latestBlock.header.height;
265
+ const timeoutHeight = new BigNumberInBase(latestHeight).plus(txTimeout);
266
+ const gas = (tx.gas?.gas || getGasPriceBasedOnMessage(msgs)).toString();
267
+ let stdFee = getStdFee({ ...tx.gas, gas });
268
+ /**
269
+ * Account has been created on chain
270
+ * and we can simulate the transaction
271
+ * to estimate the gas
272
+ **/
273
+ if (baseAccount.pubKey) {
274
+ const { stdFee: simulatedStdFee } = await this.getTxWithSignersAndStdFee({
275
+ chainId,
276
+ signMode: SIGN_EIP712_V2,
277
+ memo: tx.memo,
278
+ message: msgs,
279
+ timeoutHeight: timeoutHeight.toNumber(),
280
+ signers: {
281
+ pubKey: baseAccount.pubKey.key,
282
+ accountNumber: baseAccount.accountNumber,
283
+ sequence: baseAccount.sequence,
284
+ },
285
+ fee: stdFee,
286
+ });
287
+ stdFee = simulatedStdFee;
288
+ }
289
+ /** EIP712 for signing on Ethereum wallets */
290
+ const eip712TypedData = getEip712TypedDataV2({
291
+ msgs,
292
+ fee: stdFee,
293
+ tx: {
294
+ memo: tx.memo,
295
+ accountNumber: baseAccount.accountNumber.toString(),
296
+ sequence: baseAccount.sequence.toString(),
297
+ timeoutHeight: timeoutHeight.toFixed(),
298
+ chainId,
299
+ },
300
+ ethereumChainId,
301
+ });
302
+ /** Signing on Ethereum */
303
+ const signature = await walletStrategy.signEip712TypedData(JSON.stringify(eip712TypedData), tx.ethereumAddress);
304
+ const pubKeyOrSignatureDerivedPubKey = getEthereumWalletPubKey({
305
+ pubKey: baseAccount.pubKey?.key,
306
+ eip712TypedData,
307
+ signature,
308
+ });
309
+ const { txRaw } = createTransaction({
310
+ message: msgs,
311
+ memo: tx.memo,
312
+ signMode: SIGN_EIP712_V2,
313
+ fee: stdFee,
314
+ pubKey: pubKeyOrSignatureDerivedPubKey,
315
+ sequence: baseAccount.sequence,
316
+ timeoutHeight: timeoutHeight.toNumber(),
317
+ accountNumber: baseAccount.accountNumber,
318
+ chainId,
319
+ });
320
+ const web3Extension = createWeb3Extension({
321
+ ethereumChainId,
322
+ });
323
+ const txRawEip712 = createTxRawEIP712(txRaw, web3Extension);
324
+ /** Append Signatures */
325
+ txRawEip712.signatures = [hexToBuff(signature)];
326
+ return walletStrategy.sendTransaction(txRawEip712, {
327
+ chainId,
328
+ endpoints,
329
+ txTimeout,
330
+ address: tx.injectiveAddress,
331
+ });
332
+ }
333
+ /**
334
+ * Prepare/sign/broadcast transaction using
335
+ * Ethereum native wallets using the Web3Gateway.
336
+ *
337
+ * @param tx The transaction that needs to be broadcasted
338
+ * @returns transaction hash
339
+ */
340
+ async broadcastEip712WithFeeDelegation(tx) {
341
+ const { txTimeout, endpoints, simulateTx, walletStrategy, ethereumChainId, txTimeoutOnFeeDelegation, } = this;
342
+ const msgs = Array.isArray(tx.msgs) ? tx.msgs : [tx.msgs];
343
+ const web3Msgs = msgs.map((msg) => msg.toWeb3());
344
+ if (!ethereumChainId) {
345
+ throw new GeneralException(new Error('Please provide ethereumChainId'));
346
+ }
347
+ const transactionApi = new IndexerGrpcWeb3GwApi(endpoints.web3gw || endpoints.indexer);
348
+ let timeoutHeight = undefined;
349
+ if (txTimeoutOnFeeDelegation) {
350
+ const latestBlock = await new ChainGrpcTendermintApi(endpoints.grpc).fetchLatestBlock();
351
+ const latestHeight = latestBlock.header.height;
352
+ timeoutHeight = new BigNumberInBase(latestHeight)
353
+ .plus(txTimeout)
354
+ .toNumber();
355
+ }
356
+ const prepareTxResponse = await transactionApi.prepareTxRequest({
357
+ timeoutHeight,
358
+ memo: tx.memo,
359
+ message: web3Msgs,
360
+ address: tx.ethereumAddress,
361
+ chainId: ethereumChainId,
362
+ gasLimit: getGasPriceBasedOnMessage(msgs),
363
+ estimateGas: simulateTx,
364
+ });
365
+ const signature = await walletStrategy.signEip712TypedData(prepareTxResponse.data, tx.ethereumAddress);
366
+ const broadcast = async () => await transactionApi.broadcastTxRequest({
367
+ signature,
368
+ message: web3Msgs,
369
+ txResponse: prepareTxResponse,
370
+ chainId: ethereumChainId,
371
+ });
372
+ try {
373
+ const response = await broadcast();
374
+ return await new TxGrpcApi(endpoints.grpc).fetchTxPoll(response.txHash);
375
+ }
376
+ catch (e) {
377
+ const error = e;
378
+ if (isThrownException(error)) {
379
+ const exception = error;
380
+ /**
381
+ * First MsgExec transaction with a PrivateKey wallet
382
+ * always runs out of gas for some reason, temporary solution
383
+ * to just broadcast the transaction twice
384
+ **/
385
+ if (walletStrategy.wallet === Wallet.PrivateKey &&
386
+ checkIfTxRunOutOfGas(exception)) {
387
+ /** Account Details * */
388
+ const accountDetails = await new ChainGrpcAuthApi(endpoints.grpc).fetchAccount(tx.injectiveAddress);
389
+ const { baseAccount } = accountDetails;
390
+ /** We only do it on the first account tx fail */
391
+ if (baseAccount.sequence > 1) {
392
+ throw e;
393
+ }
394
+ return await this.broadcastEip712WithFeeDelegation(tx);
395
+ }
396
+ return await this.retryOnException(exception, broadcast);
397
+ }
398
+ throw e;
399
+ }
400
+ }
401
+ /**
402
+ * Prepare/sign/broadcast transaction using
403
+ * Cosmos native wallets on the client side.
404
+ *
405
+ * @param tx The transaction that needs to be broadcasted
406
+ * @returns transaction hash
407
+ */
408
+ async broadcastDirectSign(tx) {
409
+ const { walletStrategy, txTimeout, endpoints, chainId } = this;
410
+ const msgs = Array.isArray(tx.msgs) ? tx.msgs : [tx.msgs];
411
+ /**
412
+ * When using Ledger with Keplr/Leap we have
413
+ * to send EIP712 to sign on Keplr/Leap
414
+ */
415
+ if ([Wallet.Keplr, Wallet.Leap].includes(walletStrategy.getWallet())) {
416
+ const walletDeviceType = await walletStrategy.getWalletDeviceType();
417
+ const isLedgerConnected = walletDeviceType === WalletDeviceType.Hardware;
418
+ if (isLedgerConnected) {
419
+ return this.experimentalBroadcastWalletThroughLedger(tx);
420
+ }
421
+ }
422
+ /** Account Details * */
423
+ const accountDetails = await new ChainGrpcAuthApi(endpoints.grpc).fetchAccount(tx.injectiveAddress);
424
+ const { baseAccount } = accountDetails;
425
+ /** Block Details */
426
+ const latestBlock = await new ChainGrpcTendermintApi(endpoints.grpc).fetchLatestBlock();
427
+ const latestHeight = latestBlock.header.height;
428
+ const timeoutHeight = new BigNumberInBase(latestHeight).plus(txTimeout);
429
+ const signMode = isCosmosAminoOnlyWallet(walletStrategy.wallet)
430
+ ? SIGN_EIP712
431
+ : SIGN_DIRECT;
432
+ const pubKey = await walletStrategy.getPubKey(tx.injectiveAddress);
433
+ const gas = (tx.gas?.gas || getGasPriceBasedOnMessage(msgs)).toString();
434
+ /** Prepare the Transaction * */
435
+ const { txRaw } = await this.getTxWithSignersAndStdFee({
436
+ chainId,
437
+ signMode,
438
+ memo: tx.memo,
439
+ message: msgs,
440
+ timeoutHeight: timeoutHeight.toNumber(),
441
+ signers: {
442
+ pubKey,
443
+ accountNumber: baseAccount.accountNumber,
444
+ sequence: baseAccount.sequence,
445
+ },
446
+ fee: getStdFee({ ...tx.gas, gas }),
447
+ });
448
+ /** Ledger using Cosmos app only allows signing amino docs */
449
+ if (isCosmosAminoOnlyWallet(walletStrategy.wallet)) {
450
+ const aminoSignDoc = getAminoStdSignDoc({
451
+ ...tx,
452
+ ...baseAccount,
453
+ msgs,
454
+ chainId,
455
+ gas: gas || tx.gas?.gas?.toString(),
456
+ timeoutHeight: timeoutHeight.toFixed(),
457
+ });
458
+ const signResponse = await walletStrategy.signAminoCosmosTransaction({
459
+ signDoc: aminoSignDoc,
460
+ address: tx.injectiveAddress,
461
+ });
462
+ txRaw.signatures = [
463
+ Buffer.from(signResponse.signature.signature, 'base64'),
464
+ ];
465
+ return walletStrategy.sendTransaction(txRaw, {
466
+ chainId,
467
+ endpoints,
468
+ txTimeout,
469
+ address: tx.injectiveAddress,
470
+ });
471
+ }
472
+ const directSignResponse = (await walletStrategy.signCosmosTransaction({
473
+ txRaw,
474
+ chainId,
475
+ address: tx.injectiveAddress,
476
+ accountNumber: baseAccount.accountNumber,
477
+ }));
478
+ return walletStrategy.sendTransaction(directSignResponse, {
479
+ chainId,
480
+ endpoints,
481
+ txTimeout,
482
+ address: tx.injectiveAddress,
483
+ });
484
+ }
485
+ /**
486
+ * We use this method only when we want to broadcast a transaction using Ledger on Keplr/Leap for Injective
487
+ *
488
+ * Note: Gas estimation not available
489
+ * @param tx the transaction that needs to be broadcasted
490
+ */
491
+ async experimentalBroadcastWalletThroughLedger(tx) {
492
+ const { chainId, txTimeout, endpoints, simulateTx, walletStrategy, ethereumChainId, } = this;
493
+ const msgs = Array.isArray(tx.msgs) ? tx.msgs : [tx.msgs];
494
+ /**
495
+ * We can only use this method
496
+ * when Ledger is connected through Keplr
497
+ */
498
+ if ([Wallet.Keplr, Wallet.Leap].includes(walletStrategy.getWallet())) {
499
+ const walletDeviceType = await walletStrategy.getWalletDeviceType();
500
+ const isLedgerConnected = walletDeviceType === WalletDeviceType.Hardware;
501
+ if (!isLedgerConnected) {
502
+ throw new GeneralException(new Error(`This method can only be used when Ledger is connected through ${walletStrategy.getWallet()}`));
503
+ }
504
+ }
505
+ if (!ethereumChainId) {
506
+ throw new GeneralException(new Error('Please provide ethereumChainId'));
507
+ }
508
+ const cosmosWallet = walletStrategy.getCosmosWallet(chainId);
509
+ /** Account Details * */
510
+ const accountDetails = await new ChainGrpcAuthApi(endpoints.grpc).fetchAccount(tx.injectiveAddress);
511
+ const { baseAccount } = accountDetails;
512
+ /** Block Details */
513
+ const latestBlock = await new ChainGrpcTendermintApi(endpoints.grpc).fetchLatestBlock();
514
+ const latestHeight = latestBlock.header.height;
515
+ const timeoutHeight = new BigNumberInBase(latestHeight).plus(txTimeout);
516
+ const pubKey = await walletStrategy.getPubKey();
517
+ const gas = (tx.gas?.gas || getGasPriceBasedOnMessage(msgs)).toString();
518
+ /** EIP712 for signing on Ethereum wallets */
519
+ const eip712TypedData = getEip712TypedData({
520
+ msgs,
521
+ fee: getStdFee({ ...tx.gas, gas }),
522
+ tx: {
523
+ memo: tx.memo,
524
+ accountNumber: baseAccount.accountNumber.toString(),
525
+ sequence: baseAccount.sequence.toString(),
526
+ timeoutHeight: timeoutHeight.toFixed(),
527
+ chainId,
528
+ },
529
+ ethereumChainId,
530
+ });
531
+ const aminoSignResponse = await cosmosWallet.signEIP712CosmosTx({
532
+ eip712: eip712TypedData,
533
+ signDoc: createEip712StdSignDoc({
534
+ ...tx,
535
+ ...baseAccount,
536
+ msgs,
537
+ chainId,
538
+ gas: gas || tx.gas?.gas?.toString(),
539
+ timeoutHeight: timeoutHeight.toFixed(),
540
+ }),
541
+ });
542
+ /**
543
+ * Create TxRaw from the signed tx that we
544
+ * get as a response in case the user changed the fee/memo
545
+ * on the Keplr popup
546
+ */
547
+ const { txRaw } = createTransaction({
548
+ pubKey,
549
+ message: msgs,
550
+ memo: aminoSignResponse.signed.memo,
551
+ signMode: SIGN_EIP712,
552
+ fee: aminoSignResponse.signed.fee,
553
+ sequence: parseInt(aminoSignResponse.signed.sequence, 10),
554
+ timeoutHeight: parseInt(aminoSignResponse.signed.timeout_height, 10),
555
+ accountNumber: parseInt(aminoSignResponse.signed.account_number, 10),
556
+ chainId,
557
+ });
558
+ /** Preparing the transaction for client broadcasting */
559
+ const web3Extension = createWeb3Extension({
560
+ ethereumChainId,
561
+ });
562
+ const txRawEip712 = createTxRawEIP712(txRaw, web3Extension);
563
+ if (simulateTx) {
564
+ await this.simulateTxRaw(txRawEip712);
565
+ }
566
+ /** Append Signatures */
567
+ const signatureBuff = Buffer.from(aminoSignResponse.signature.signature, 'base64');
568
+ txRawEip712.signatures = [signatureBuff];
569
+ /** Broadcast the transaction */
570
+ const response = await new TxGrpcApi(endpoints.grpc).broadcast(txRawEip712, { txTimeout });
571
+ if (response.code !== 0) {
572
+ throw new TransactionException(new Error(response.rawLog), {
573
+ code: UnspecifiedErrorCode,
574
+ contextCode: response.code,
575
+ contextModule: response.codespace,
576
+ });
577
+ }
578
+ return response;
579
+ }
580
+ /**
581
+ * Prepare/sign/broadcast transaction using
582
+ * Cosmos native wallets using the Web3Gateway.
583
+ *
584
+ * @param tx The transaction that needs to be broadcasted
585
+ * @returns transaction hash
586
+ */
587
+ async broadcastDirectSignWithFeeDelegation(tx) {
588
+ const { options, chainId, txTimeout, endpoints, walletStrategy, txTimeoutOnFeeDelegation, } = this;
589
+ const msgs = Array.isArray(tx.msgs) ? tx.msgs : [tx.msgs];
590
+ /**
591
+ * We can only use this method when Keplr is connected
592
+ * with ledger
593
+ */
594
+ if (walletStrategy.getWallet() === Wallet.Keplr) {
595
+ const walletDeviceType = await walletStrategy.getWalletDeviceType();
596
+ const isLedgerConnectedOnKeplr = walletDeviceType === WalletDeviceType.Hardware;
597
+ if (isLedgerConnectedOnKeplr) {
598
+ throw new GeneralException(new Error('Keplr + Ledger is not available with fee delegation. Connect with Ledger directly.'));
599
+ }
600
+ }
601
+ const cosmosWallet = walletStrategy.getCosmosWallet(chainId);
602
+ const canDisableCosmosGasCheck = [Wallet.Keplr, Wallet.OWallet].includes(walletStrategy.wallet);
603
+ const feePayerPubKey = await this.fetchFeePayerPubKey(options.feePayerPubKey);
604
+ const feePayerPublicKey = PublicKey.fromBase64(feePayerPubKey);
605
+ const feePayer = feePayerPublicKey.toAddress().address;
606
+ /** Account Details * */
607
+ const chainGrpcAuthApi = new ChainGrpcAuthApi(endpoints.grpc);
608
+ const accountDetails = await chainGrpcAuthApi.fetchAccount(tx.injectiveAddress);
609
+ const feePayerAccountDetails = await chainGrpcAuthApi.fetchAccount(feePayer);
610
+ const { baseAccount } = accountDetails;
611
+ const { baseAccount: feePayerBaseAccount } = feePayerAccountDetails;
612
+ /** Block Details */
613
+ const latestBlock = await new ChainGrpcTendermintApi(endpoints.grpc).fetchLatestBlock();
614
+ const latestHeight = latestBlock.header.height;
615
+ const timeoutHeight = new BigNumberInBase(latestHeight).plus(txTimeoutOnFeeDelegation ? txTimeout : DEFAULT_BLOCK_TIMEOUT_HEIGHT);
616
+ const pubKey = await walletStrategy.getPubKey();
617
+ const gas = (tx.gas?.gas || getGasPriceBasedOnMessage(msgs)).toString();
618
+ /** Prepare the Transaction * */
619
+ const { txRaw } = await this.getTxWithSignersAndStdFee({
620
+ chainId,
621
+ memo: tx.memo,
622
+ message: msgs,
623
+ timeoutHeight: timeoutHeight.toNumber(),
624
+ signers: [
625
+ {
626
+ pubKey,
627
+ accountNumber: baseAccount.accountNumber,
628
+ sequence: baseAccount.sequence,
629
+ },
630
+ {
631
+ pubKey: feePayerPublicKey.toBase64(),
632
+ accountNumber: feePayerBaseAccount.accountNumber,
633
+ sequence: feePayerBaseAccount.sequence,
634
+ },
635
+ ],
636
+ fee: getStdFee({ ...tx.gas, gas, payer: feePayer }),
637
+ });
638
+ // Temporary remove tx gas check because Keplr doesn't recognize feePayer
639
+ if (canDisableCosmosGasCheck && cosmosWallet.disableGasCheck) {
640
+ cosmosWallet.disableGasCheck(chainId);
641
+ }
642
+ const directSignResponse = (await walletStrategy.signCosmosTransaction({
643
+ txRaw,
644
+ chainId,
645
+ address: tx.injectiveAddress,
646
+ accountNumber: baseAccount.accountNumber,
647
+ }));
648
+ const transactionApi = new IndexerGrpcWeb3GwApi(endpoints.web3gw || endpoints.indexer);
649
+ const broadcast = async () => await transactionApi.broadcastCosmosTxRequest({
650
+ address: tx.injectiveAddress,
651
+ txRaw: createTxRawFromSigResponse(directSignResponse),
652
+ signature: directSignResponse.signature.signature,
653
+ pubKey: directSignResponse.signature.pub_key || {
654
+ value: pubKey,
655
+ type: '/injective.crypto.v1beta1.ethsecp256k1.PubKey',
656
+ },
657
+ });
658
+ try {
659
+ const response = await broadcast();
660
+ // Re-enable tx gas check removed above
661
+ if (canDisableCosmosGasCheck && cosmosWallet.enableGasCheck) {
662
+ cosmosWallet.enableGasCheck(chainId);
663
+ }
664
+ return await new TxGrpcApi(endpoints.grpc).fetchTxPoll(response.txHash);
665
+ }
666
+ catch (e) {
667
+ const error = e;
668
+ if (isThrownException(error)) {
669
+ const exception = error;
670
+ return await this.retryOnException(exception, broadcast);
671
+ }
672
+ throw e;
673
+ }
674
+ }
675
+ /**
676
+ * Fetch the fee payer's pub key from the web3 gateway
677
+ *
678
+ * Returns a base64 version of it
679
+ */
680
+ async fetchFeePayerPubKey(existingFeePayerPubKey) {
681
+ if (existingFeePayerPubKey) {
682
+ return existingFeePayerPubKey;
683
+ }
684
+ const { endpoints } = this;
685
+ const transactionApi = new IndexerGrpcWeb3GwApi(endpoints.web3gw || endpoints.indexer);
686
+ const response = await transactionApi.fetchFeePayer();
687
+ if (!response.feePayerPubKey) {
688
+ throw new GeneralException(new Error('Please provide a feePayerPubKey'));
689
+ }
690
+ if (response.feePayerPubKey.key.startsWith('0x') ||
691
+ response.feePayerPubKey.key.length === 66) {
692
+ return Buffer.from(response.feePayerPubKey.key, 'hex').toString('base64');
693
+ }
694
+ return response.feePayerPubKey.key;
695
+ }
696
+ /**
697
+ * In case we don't want to simulate the transaction
698
+ * we get the gas limit based on the message type.
699
+ *
700
+ * If we want to simulate the transaction we set the
701
+ * gas limit based on the simulation and add a small multiplier
702
+ * to be safe (factor of 1.2 as default)
703
+ */
704
+ async getTxWithSignersAndStdFee(args) {
705
+ const { simulateTx } = this;
706
+ if (!simulateTx) {
707
+ return {
708
+ ...createTransactionWithSigners(args),
709
+ stdFee: getStdFee(args.fee),
710
+ };
711
+ }
712
+ const result = await this.simulateTxWithSigners(args);
713
+ if (!result.gasInfo?.gasUsed) {
714
+ return {
715
+ ...createTransactionWithSigners(args),
716
+ stdFee: getStdFee(args.fee),
717
+ };
718
+ }
719
+ const stdGasFee = {
720
+ ...getStdFee({
721
+ ...getStdFee(args.fee),
722
+ gas: new BigNumberInBase(result.gasInfo.gasUsed)
723
+ .times(this.gasBufferCoefficient)
724
+ .toFixed(),
725
+ }),
726
+ };
727
+ return {
728
+ ...createTransactionWithSigners({
729
+ ...args,
730
+ fee: stdGasFee,
731
+ }),
732
+ stdFee: stdGasFee,
733
+ };
734
+ }
735
+ /**
736
+ * Create TxRaw and simulate it
737
+ */
738
+ async simulateTxRaw(txRaw) {
739
+ const { endpoints } = this;
740
+ txRaw.signatures = [new Uint8Array(0)];
741
+ const simulationResponse = await new TxGrpcApi(endpoints.grpc).simulate(txRaw);
742
+ return simulationResponse;
743
+ }
744
+ /**
745
+ * Create TxRaw and simulate it
746
+ */
747
+ async simulateTxWithSigners(args) {
748
+ const { endpoints } = this;
749
+ const { txRaw } = createTransactionWithSigners(args);
750
+ txRaw.signatures = Array(Array.isArray(args.signers) ? args.signers.length : 1).fill(new Uint8Array(0));
751
+ const simulationResponse = await new TxGrpcApi(endpoints.grpc).simulate(txRaw);
752
+ return simulationResponse;
753
+ }
754
+ async retryOnException(exception, retryLogic) {
755
+ const errorsToRetry = Object.keys(this.retriesOnError);
756
+ const errorKey = `${exception.contextModule}-${exception.contextCode}`;
757
+ if (!errorsToRetry.includes(errorKey)) {
758
+ throw exception;
759
+ }
760
+ const retryConfig = this.retriesOnError[errorKey];
761
+ if (retryConfig.retries >= retryConfig.maxRetries) {
762
+ this.retriesOnError = defaultRetriesConfig();
763
+ throw exception;
764
+ }
765
+ await sleep(retryConfig.timeout);
766
+ try {
767
+ retryConfig.retries += 1;
768
+ return await retryLogic();
769
+ }
770
+ catch (e) {
771
+ const error = e;
772
+ if (isThrownException(error)) {
773
+ return this.retryOnException(error, retryLogic);
774
+ }
775
+ throw e;
776
+ }
777
+ }
778
+ }