@injectivelabs/wallet-core 1.16.25-alpha.1 → 1.16.26

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