@keplr-wallet/background 0.12.313 → 0.13.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.
Files changed (83) hide show
  1. package/build/index.d.ts +1 -0
  2. package/build/index.js +7 -1
  3. package/build/index.js.map +1 -1
  4. package/build/keyring-cosmos/service.d.ts +10 -0
  5. package/build/keyring-cosmos/service.js +100 -0
  6. package/build/keyring-cosmos/service.js.map +1 -1
  7. package/build/keyring-ethereum/service.d.ts +5 -0
  8. package/build/keyring-ethereum/service.js +66 -0
  9. package/build/keyring-ethereum/service.js.map +1 -1
  10. package/build/recent-send-history/api.d.ts +31 -0
  11. package/build/recent-send-history/api.js +97 -0
  12. package/build/recent-send-history/api.js.map +1 -0
  13. package/build/recent-send-history/handler.js +36 -0
  14. package/build/recent-send-history/handler.js.map +1 -1
  15. package/build/recent-send-history/init.js +5 -0
  16. package/build/recent-send-history/init.js.map +1 -1
  17. package/build/recent-send-history/messages.d.ts +76 -1
  18. package/build/recent-send-history/messages.js +121 -1
  19. package/build/recent-send-history/messages.js.map +1 -1
  20. package/build/recent-send-history/service.d.ts +262 -9
  21. package/build/recent-send-history/service.js +2103 -812
  22. package/build/recent-send-history/service.js.map +1 -1
  23. package/build/recent-send-history/types.d.ts +214 -22
  24. package/build/recent-send-history/types.js +21 -0
  25. package/build/recent-send-history/types.js.map +1 -1
  26. package/build/tx/service.d.ts +2 -0
  27. package/build/tx/service.js +35 -0
  28. package/build/tx/service.js.map +1 -1
  29. package/build/tx-ethereum/service.d.ts +2 -0
  30. package/build/tx-ethereum/service.js +42 -0
  31. package/build/tx-ethereum/service.js.map +1 -1
  32. package/build/tx-executor/constants.d.ts +1 -0
  33. package/build/tx-executor/constants.js +5 -0
  34. package/build/tx-executor/constants.js.map +1 -0
  35. package/build/tx-executor/handler.d.ts +3 -0
  36. package/build/tx-executor/handler.js +45 -0
  37. package/build/tx-executor/handler.js.map +1 -0
  38. package/build/tx-executor/index.d.ts +3 -0
  39. package/build/tx-executor/index.js +20 -0
  40. package/build/tx-executor/index.js.map +1 -0
  41. package/build/tx-executor/init.d.ts +3 -0
  42. package/build/tx-executor/init.js +14 -0
  43. package/build/tx-executor/init.js.map +1 -0
  44. package/build/tx-executor/internal.d.ts +4 -0
  45. package/build/tx-executor/internal.js +24 -0
  46. package/build/tx-executor/internal.js.map +1 -0
  47. package/build/tx-executor/messages.d.ts +53 -0
  48. package/build/tx-executor/messages.js +116 -0
  49. package/build/tx-executor/messages.js.map +1 -0
  50. package/build/tx-executor/service.d.ts +67 -0
  51. package/build/tx-executor/service.js +715 -0
  52. package/build/tx-executor/service.js.map +1 -0
  53. package/build/tx-executor/types.d.ts +105 -0
  54. package/build/tx-executor/types.js +33 -0
  55. package/build/tx-executor/types.js.map +1 -0
  56. package/build/tx-executor/utils/cosmos.d.ts +59 -0
  57. package/build/tx-executor/utils/cosmos.js +526 -0
  58. package/build/tx-executor/utils/cosmos.js.map +1 -0
  59. package/build/tx-executor/utils/evm.d.ts +4 -0
  60. package/build/tx-executor/utils/evm.js +236 -0
  61. package/build/tx-executor/utils/evm.js.map +1 -0
  62. package/package.json +13 -13
  63. package/src/index.ts +24 -1
  64. package/src/keyring-cosmos/service.ts +151 -0
  65. package/src/keyring-ethereum/service.ts +103 -6
  66. package/src/recent-send-history/api.ts +119 -0
  67. package/src/recent-send-history/handler.ts +84 -0
  68. package/src/recent-send-history/init.ts +10 -0
  69. package/src/recent-send-history/messages.ts +163 -1
  70. package/src/recent-send-history/service.ts +3042 -1153
  71. package/src/recent-send-history/types.ts +268 -31
  72. package/src/tx/service.ts +41 -0
  73. package/src/tx-ethereum/service.ts +57 -0
  74. package/src/tx-executor/constants.ts +1 -0
  75. package/src/tx-executor/handler.ts +71 -0
  76. package/src/tx-executor/index.ts +3 -0
  77. package/src/tx-executor/init.ts +20 -0
  78. package/src/tx-executor/internal.ts +9 -0
  79. package/src/tx-executor/messages.ts +157 -0
  80. package/src/tx-executor/service.ts +1025 -0
  81. package/src/tx-executor/types.ts +161 -0
  82. package/src/tx-executor/utils/cosmos.ts +771 -0
  83. package/src/tx-executor/utils/evm.ts +310 -0
@@ -0,0 +1,771 @@
1
+ import { BaseAccount, EthermintChainIdHelper } from "@keplr-wallet/cosmos";
2
+ import { Any } from "@keplr-wallet/proto-types/google/protobuf/any";
3
+ import {
4
+ ChainInfo,
5
+ Msg,
6
+ AminoSignResponse,
7
+ StdSignDoc,
8
+ Coin,
9
+ StdFee,
10
+ FeeCurrency,
11
+ } from "@keplr-wallet/types";
12
+ import { Buffer } from "buffer/";
13
+ import { escapeHTML, sortObjectByKey } from "@keplr-wallet/common";
14
+ import { Mutable } from "utility-types";
15
+ import {
16
+ AuthInfo,
17
+ Fee,
18
+ SignDoc,
19
+ SignerInfo,
20
+ TxBody,
21
+ TxRaw,
22
+ } from "@keplr-wallet/proto-types/cosmos/tx/v1beta1/tx";
23
+ import { ExtensionOptionsWeb3Tx } from "@keplr-wallet/proto-types/ethermint/types/v1/web3";
24
+ import { PubKey } from "@keplr-wallet/proto-types/cosmos/crypto/secp256k1/keys";
25
+ import { SignMode } from "@keplr-wallet/proto-types/cosmos/tx/signing/v1beta1/signing";
26
+ import { simpleFetch } from "@keplr-wallet/simple-fetch";
27
+ import { Dec } from "@keplr-wallet/unit";
28
+ import { BackgroundTxFeeType } from "../types";
29
+
30
+ // TODO: move helper functions to proper packages
31
+
32
+ /**
33
+ * Build a signed transaction from an AminoSignResponse
34
+ */
35
+ export function buildSignedTxFromAminoSignResponse(params: {
36
+ protoMsgs: Any[];
37
+ signResponse: AminoSignResponse;
38
+ chainInfo: ChainInfo;
39
+ eip712Signing: boolean;
40
+ }): {
41
+ tx: Uint8Array;
42
+ signDoc: StdSignDoc;
43
+ } {
44
+ const { protoMsgs, signResponse, chainInfo, eip712Signing } = params;
45
+
46
+ const chainIsInjective = chainInfo.chainId.startsWith("injective");
47
+ const ethSignPlainJson: boolean =
48
+ chainInfo.features?.includes("evm-ledger-sign-plain-json") === true;
49
+
50
+ const pubKeyTypeUrl = getCosmosPubKeyTypeUrl(chainInfo);
51
+
52
+ return {
53
+ tx: TxRaw.encode({
54
+ bodyBytes: TxBody.encode(
55
+ TxBody.fromPartial({
56
+ messages: protoMsgs,
57
+ timeoutHeight: signResponse.signed.timeout_height,
58
+ memo: signResponse.signed.memo,
59
+ extensionOptions:
60
+ eip712Signing && !ethSignPlainJson
61
+ ? [
62
+ {
63
+ typeUrl: (() => {
64
+ if (
65
+ chainInfo.features?.includes(
66
+ "/cosmos.evm.types.v1.ExtensionOptionsWeb3Tx"
67
+ )
68
+ ) {
69
+ return "/cosmos.evm.types.v1.ExtensionOptionsWeb3Tx";
70
+ }
71
+
72
+ if (chainIsInjective) {
73
+ return "/injective.types.v1beta1.ExtensionOptionsWeb3Tx";
74
+ }
75
+
76
+ return "/ethermint.types.v1.ExtensionOptionsWeb3Tx";
77
+ })(),
78
+ value: ExtensionOptionsWeb3Tx.encode(
79
+ ExtensionOptionsWeb3Tx.fromPartial({
80
+ typedDataChainId: EthermintChainIdHelper.parse(
81
+ chainInfo.chainId
82
+ ).ethChainId.toString(),
83
+ feePayer: !chainIsInjective
84
+ ? signResponse.signed.fee.feePayer
85
+ : undefined,
86
+ feePayerSig: !chainIsInjective
87
+ ? Buffer.from(
88
+ signResponse.signature.signature,
89
+ "base64"
90
+ )
91
+ : undefined,
92
+ })
93
+ ).finish(),
94
+ },
95
+ ]
96
+ : undefined,
97
+ })
98
+ ).finish(),
99
+ authInfoBytes: AuthInfo.encode({
100
+ signerInfos: [
101
+ {
102
+ publicKey: {
103
+ typeUrl: pubKeyTypeUrl,
104
+ value: PubKey.encode({
105
+ key: Buffer.from(
106
+ signResponse.signature.pub_key.value,
107
+ "base64"
108
+ ),
109
+ }).finish(),
110
+ },
111
+ modeInfo: {
112
+ single: {
113
+ mode:
114
+ eip712Signing && ethSignPlainJson
115
+ ? SignMode.SIGN_MODE_EIP_191
116
+ : SignMode.SIGN_MODE_LEGACY_AMINO_JSON,
117
+ },
118
+ multi: undefined,
119
+ },
120
+ sequence: signResponse.signed.sequence,
121
+ },
122
+ ],
123
+ fee: Fee.fromPartial({
124
+ amount: signResponse.signed.fee.amount as Coin[],
125
+ gasLimit: signResponse.signed.fee.gas,
126
+ payer:
127
+ eip712Signing && !chainIsInjective && !ethSignPlainJson
128
+ ? // Fee delegation feature not yet supported. But, for eip712 ethermint signing, we must set fee payer.
129
+ signResponse.signed.fee.feePayer
130
+ : undefined,
131
+ }),
132
+ }).finish(),
133
+ signatures:
134
+ // Injective needs the signature in the signatures list even if eip712
135
+ !eip712Signing || chainIsInjective || ethSignPlainJson
136
+ ? [Buffer.from(signResponse.signature.signature, "base64")]
137
+ : [new Uint8Array(0)],
138
+ }).finish(),
139
+ signDoc: signResponse.signed,
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Prepare sign document for Cosmos transaction signing
145
+ */
146
+ export function prepareSignDocForAminoSigning(params: {
147
+ chainInfo: ChainInfo;
148
+ accountNumber: string;
149
+ sequence: string;
150
+ aminoMsgs: Msg[];
151
+ fee: StdFee;
152
+ memo: string;
153
+ eip712Signing: boolean;
154
+ signer: string;
155
+ }): StdSignDoc {
156
+ const {
157
+ chainInfo,
158
+ accountNumber,
159
+ sequence,
160
+ aminoMsgs,
161
+ memo,
162
+ eip712Signing,
163
+ signer,
164
+ fee,
165
+ } = params;
166
+
167
+ const chainIsInjective = chainInfo.chainId.startsWith("injective");
168
+ const ethSignPlainJson: boolean =
169
+ chainInfo.features?.includes("evm-ledger-sign-plain-json") === true;
170
+
171
+ const signDocRaw: StdSignDoc = {
172
+ chain_id: chainInfo.chainId,
173
+ account_number: accountNumber,
174
+ sequence,
175
+ fee,
176
+ msgs: aminoMsgs,
177
+ memo: escapeHTML(memo ?? ""),
178
+ };
179
+
180
+ if (eip712Signing) {
181
+ if (chainIsInjective) {
182
+ // Due to injective's problem, it should exist if injective with ledger.
183
+ // There is currently no effective way to handle this in keplr. Just set a very large number.
184
+ (signDocRaw as Mutable<StdSignDoc>).timeout_height =
185
+ Number.MAX_SAFE_INTEGER.toString();
186
+ } else {
187
+ // If not injective (evmos), they require fee payer.
188
+ // XXX: "feePayer" should be "payer". But, it maybe from ethermint team's mistake.
189
+ // That means this part is not standard.
190
+ (signDocRaw as Mutable<StdSignDoc>).fee = {
191
+ ...signDocRaw.fee,
192
+ ...(() => {
193
+ if (ethSignPlainJson) {
194
+ return {};
195
+ }
196
+ return {
197
+ feePayer: signer,
198
+ };
199
+ })(),
200
+ };
201
+ }
202
+ }
203
+
204
+ return sortObjectByKey(signDocRaw);
205
+ }
206
+
207
+ export function prepareSignDocForDirectSigning(params: {
208
+ chainInfo: ChainInfo;
209
+ accountNumber: string;
210
+ sequence: string;
211
+ protoMsgs: Any[];
212
+ fee: StdFee;
213
+ memo: string;
214
+ pubKey: Uint8Array;
215
+ }): {
216
+ signDoc: SignDoc;
217
+ bodyBytes: Uint8Array;
218
+ authInfoBytes: Uint8Array;
219
+ } {
220
+ const { chainInfo, accountNumber, sequence, protoMsgs, fee, memo, pubKey } =
221
+ params;
222
+
223
+ const bodyBytes = TxBody.encode(
224
+ TxBody.fromPartial({
225
+ messages: protoMsgs,
226
+ memo,
227
+ })
228
+ ).finish();
229
+
230
+ const pubKeyTypeUrl = getCosmosPubKeyTypeUrl(chainInfo);
231
+
232
+ const authInfoBytes = AuthInfo.encode({
233
+ signerInfos: [
234
+ SignerInfo.fromPartial({
235
+ publicKey: {
236
+ typeUrl: pubKeyTypeUrl,
237
+ value: PubKey.encode({
238
+ key: pubKey,
239
+ }).finish(),
240
+ },
241
+ modeInfo: {
242
+ single: {
243
+ mode: SignMode.SIGN_MODE_DIRECT,
244
+ },
245
+ },
246
+ sequence,
247
+ }),
248
+ ],
249
+ fee: Fee.fromPartial({
250
+ amount: fee.amount.map((amount) => ({
251
+ amount: amount.amount,
252
+ denom: amount.denom,
253
+ })),
254
+ gasLimit: fee.gas,
255
+ }),
256
+ }).finish();
257
+
258
+ const signDoc = SignDoc.fromPartial({
259
+ bodyBytes,
260
+ authInfoBytes,
261
+ chainId: chainInfo.chainId,
262
+ accountNumber,
263
+ });
264
+
265
+ return {
266
+ signDoc,
267
+ bodyBytes,
268
+ authInfoBytes,
269
+ };
270
+ }
271
+
272
+ /**
273
+ * Resolve the correct pub key typeUrl for a chain, accounting for ethermint/initia/injective variants.
274
+ */
275
+ export function getCosmosPubKeyTypeUrl(chainInfo: ChainInfo): string {
276
+ const useEthereumSign = chainInfo.features?.includes("eth-key-sign") === true;
277
+
278
+ if (!useEthereumSign) {
279
+ return "/cosmos.crypto.secp256k1.PubKey";
280
+ }
281
+
282
+ if (chainInfo.chainId.startsWith("injective")) {
283
+ return "/injective.crypto.v1beta1.ethsecp256k1.PubKey";
284
+ }
285
+
286
+ if (chainInfo.chainId.startsWith("stratos")) {
287
+ return "/stratos.crypto.v1.ethsecp256k1.PubKey";
288
+ }
289
+
290
+ if (chainInfo.features?.includes("eth-secp256k1-cosmos")) {
291
+ return "/cosmos.evm.crypto.v1.ethsecp256k1.PubKey";
292
+ }
293
+
294
+ if (chainInfo.features?.includes("eth-secp256k1-initia")) {
295
+ return "/initia.crypto.v1beta1.ethsecp256k1.PubKey";
296
+ }
297
+
298
+ return "/ethermint.crypto.v1.ethsecp256k1.PubKey";
299
+ }
300
+
301
+ export async function simulateCosmosTx(
302
+ signer: string,
303
+ chainInfo: ChainInfo,
304
+ msgs: Any[],
305
+ fee: Omit<StdFee, "gas">,
306
+ memo: string = ""
307
+ ): Promise<{
308
+ gasUsed: number;
309
+ }> {
310
+ const account = await BaseAccount.fetchFromRest(chainInfo.rest, signer, true);
311
+
312
+ const unsignedTx = TxRaw.encode({
313
+ bodyBytes: TxBody.encode(
314
+ TxBody.fromPartial({
315
+ messages: msgs,
316
+ memo: memo,
317
+ })
318
+ ).finish(),
319
+ authInfoBytes: AuthInfo.encode({
320
+ signerInfos: [
321
+ SignerInfo.fromPartial({
322
+ // Pub key is ignored.
323
+ // It is fine to ignore the pub key when simulating tx.
324
+ // However, the estimated gas would be slightly smaller because tx size doesn't include pub key.
325
+ modeInfo: {
326
+ single: {
327
+ mode: SignMode.SIGN_MODE_LEGACY_AMINO_JSON,
328
+ },
329
+ multi: undefined,
330
+ },
331
+ sequence: account.getSequence().toString(),
332
+ }),
333
+ ],
334
+ fee: Fee.fromPartial({
335
+ amount: fee.amount.map((amount) => {
336
+ return { amount: amount.amount, denom: amount.denom };
337
+ }),
338
+ }),
339
+ }).finish(),
340
+ // Because of the validation of tx itself, the signature must exist.
341
+ // However, since they do not actually verify the signature, it is okay to use any value.
342
+ signatures: [new Uint8Array(64)],
343
+ }).finish();
344
+
345
+ const result = await simpleFetch<{
346
+ gas_info: {
347
+ gas_used: string;
348
+ };
349
+ }>(chainInfo.rest, "/cosmos/tx/v1beta1/simulate", {
350
+ method: "POST",
351
+ headers: {
352
+ "content-type": "application/json",
353
+ },
354
+ body: JSON.stringify({
355
+ tx_bytes: Buffer.from(unsignedTx).toString("base64"),
356
+ }),
357
+ });
358
+
359
+ const gasUsed = parseInt(result.data.gas_info.gas_used);
360
+ if (Number.isNaN(gasUsed)) {
361
+ throw new Error(`Invalid integer gas: ${result.data.gas_info.gas_used}`);
362
+ }
363
+
364
+ return {
365
+ gasUsed: gasUsed,
366
+ };
367
+ }
368
+
369
+ export async function fetchCosmosSpendableBalances(
370
+ baseURL: string,
371
+ bech32Address: string,
372
+ limit = 1000
373
+ ): Promise<{ balances: Coin[] }> {
374
+ const { data } = await simpleFetch<{ balances: Coin[] }>(
375
+ baseURL,
376
+ `/cosmos/bank/v1beta1/spendable_balances/${bech32Address}?pagination.limit=${limit}`
377
+ );
378
+ return data;
379
+ }
380
+
381
+ // Default gas price steps
382
+ const DefaultGasPriceStep = {
383
+ low: 0.01,
384
+ average: 0.025,
385
+ high: 0.04,
386
+ };
387
+
388
+ // Default multiplication factors for fee market
389
+ const DefaultMultiplication = {
390
+ low: 1.1,
391
+ average: 1.2,
392
+ high: 1.3,
393
+ };
394
+
395
+ export async function getCosmosGasPrice(
396
+ chainInfo: ChainInfo,
397
+ feeType: BackgroundTxFeeType = "average",
398
+ feeCurrency?: FeeCurrency
399
+ ): Promise<{
400
+ gasPrice: Dec;
401
+ source:
402
+ | "osmosis-base-fee"
403
+ | "osmosis-txfees"
404
+ | "feemarket"
405
+ | "initia-dynamic"
406
+ | "eip1559"
407
+ | "default";
408
+ }> {
409
+ // Use first currency from chainInfo if feeCurrency is not provided
410
+ const currency = feeCurrency || chainInfo.feeCurrencies[0];
411
+ if (!currency) {
412
+ throw new Error("No fee currency is provided and not found for chain");
413
+ }
414
+
415
+ // 1. Try Osmosis base fee (for Osmosis with base-fee-beta feature)
416
+ if (chainInfo.features?.includes("osmosis-base-fee-beta")) {
417
+ try {
418
+ const osmosisResult = await getOsmosisBaseFeeCurrency(
419
+ chainInfo,
420
+ currency,
421
+ feeType
422
+ );
423
+ if (!osmosisResult) {
424
+ throw new Error("Failed to fetch Osmosis base fee currency");
425
+ }
426
+
427
+ if (chainInfo.features?.includes("osmosis-txfees")) {
428
+ const osmosisTxFeesResult = await getOsmosisTxFeesGasPrice(
429
+ chainInfo,
430
+ currency,
431
+ feeType
432
+ );
433
+ if (osmosisTxFeesResult) {
434
+ return {
435
+ gasPrice: osmosisTxFeesResult,
436
+ source: "osmosis-txfees",
437
+ };
438
+ }
439
+ }
440
+
441
+ // if osmosis-txfees is not enabled, use the base fee currency
442
+ return {
443
+ gasPrice: new Dec(osmosisResult.gasPriceStep![feeType]),
444
+ source: "osmosis-base-fee",
445
+ };
446
+ } catch (error) {
447
+ console.error("Failed to fetch Osmosis base fee:", error);
448
+ }
449
+ }
450
+
451
+ // 2. Try Initia Dynamic Fee
452
+ if (chainInfo.features?.includes("initia-dynamicfee")) {
453
+ try {
454
+ const initiaResult = await getInitiaDynamicFeeGasPrice(
455
+ chainInfo,
456
+ feeType
457
+ );
458
+ if (initiaResult) {
459
+ return { gasPrice: initiaResult, source: "initia-dynamic" };
460
+ }
461
+ } catch (error) {
462
+ console.error("Failed to fetch Initia dynamic fee:", error);
463
+ }
464
+ }
465
+
466
+ // 3. Try Fee Market (for chains with feemarket feature)
467
+ if (chainInfo.features?.includes("feemarket")) {
468
+ try {
469
+ const feeMarketResult = await getFeeMarketGasPrice(
470
+ chainInfo,
471
+ currency,
472
+ feeType
473
+ );
474
+ if (feeMarketResult) {
475
+ return { gasPrice: feeMarketResult, source: "feemarket" };
476
+ }
477
+ } catch (error) {
478
+ console.error("Failed to fetch fee market gas price:", error);
479
+ }
480
+ }
481
+
482
+ // 5. Try EIP-1559 (for EVM chains)
483
+ if (chainInfo.evm) {
484
+ try {
485
+ const eip1559Result = await getEIP1559GasPrice(chainInfo, feeType);
486
+ if (eip1559Result) {
487
+ return { gasPrice: eip1559Result, source: "eip1559" };
488
+ }
489
+ } catch (error) {
490
+ console.error("Failed to fetch EIP-1559 gas price:", error);
491
+ }
492
+ }
493
+
494
+ // 6. Fallback to default gas price step
495
+ const gasPrice = getDefaultGasPrice(currency, feeType);
496
+ return { gasPrice, source: "default" };
497
+ }
498
+
499
+ async function getOsmosisBaseFeeCurrency(
500
+ chainInfo: ChainInfo,
501
+ feeCurrency: FeeCurrency,
502
+ feeType: BackgroundTxFeeType
503
+ ): Promise<FeeCurrency | null> {
504
+ // Fetch base fee from Osmosis
505
+ const baseDenom = "uosmo";
506
+
507
+ if (feeCurrency.coinMinimalDenom !== baseDenom) {
508
+ return null;
509
+ }
510
+
511
+ // Fetch multiplication factors from remote config
512
+ const remoteConfig = await simpleFetch<{
513
+ low?: number;
514
+ average?: number;
515
+ high?: number;
516
+ }>(
517
+ "https://gjsttg7mkgtqhjpt3mv5aeuszi0zblbb.lambda-url.us-west-2.on.aws/osmosis/osmosis-base-fee-beta.json"
518
+ ).catch(() => ({ data: {} as Record<BackgroundTxFeeType, number> }));
519
+
520
+ const { data: baseFeeResponse } = await simpleFetch<{ base_fee: string }>(
521
+ chainInfo.rest,
522
+ "/osmosis/txfees/v1beta1/cur_eip_base_fee"
523
+ );
524
+
525
+ const multiplier =
526
+ remoteConfig.data[feeType] || DefaultMultiplication[feeType];
527
+ return {
528
+ ...feeCurrency,
529
+ gasPriceStep: {
530
+ low: parseFloat(baseFeeResponse.base_fee) * multiplier,
531
+ average: parseFloat(baseFeeResponse.base_fee) * multiplier,
532
+ high: parseFloat(baseFeeResponse.base_fee) * multiplier,
533
+ },
534
+ };
535
+ }
536
+
537
+ async function getOsmosisTxFeesGasPrice(
538
+ chainInfo: ChainInfo,
539
+ feeCurrency: FeeCurrency,
540
+ feeType: BackgroundTxFeeType
541
+ ): Promise<Dec | null> {
542
+ // Check if it's a fee token
543
+ const { data: feeTokensResponse } = await simpleFetch<{
544
+ fee_tokens: Array<{ denom: string; poolID: string }>;
545
+ }>(chainInfo.rest, "/osmosis/txfees/v1beta1/fee_tokens");
546
+
547
+ const isFeeToken = feeTokensResponse.fee_tokens.some(
548
+ (token) => token.denom === feeCurrency.coinMinimalDenom
549
+ );
550
+
551
+ if (!isFeeToken) {
552
+ return null;
553
+ }
554
+
555
+ // Get spot price
556
+ const { data: spotPriceResponse } = await simpleFetch<{ spot_price: string }>(
557
+ chainInfo.rest,
558
+ `/osmosis/txfees/v1beta1/spot_price_by_denom?denom=${feeCurrency.coinMinimalDenom}`
559
+ );
560
+
561
+ const spotPrice = new Dec(spotPriceResponse.spot_price);
562
+ if (spotPrice.lte(new Dec(0))) {
563
+ return null;
564
+ }
565
+
566
+ const baseGasPrice = getDefaultGasPrice(feeCurrency, feeType);
567
+ // Add 1% slippage protection
568
+ return baseGasPrice.quo(spotPrice).mul(new Dec(1.01));
569
+ }
570
+
571
+ async function getFeeMarketGasPrice(
572
+ chainInfo: ChainInfo,
573
+ feeCurrency: FeeCurrency,
574
+ feeType: BackgroundTxFeeType
575
+ ): Promise<Dec | null> {
576
+ try {
577
+ const gasPricesResponse = await simpleFetch<{
578
+ prices: Array<{ denom: string; amount: string }>;
579
+ }>(chainInfo.rest, "/feemarket/v1/gas_prices");
580
+
581
+ const gasPrice = gasPricesResponse.data.prices.find(
582
+ (price) => price.denom === feeCurrency.coinMinimalDenom
583
+ );
584
+
585
+ if (!gasPrice) {
586
+ return null;
587
+ }
588
+
589
+ // Fetch multiplication config
590
+ const multiplicationConfig = await simpleFetch<{
591
+ [chainId: string]: {
592
+ low: number;
593
+ average: number;
594
+ high: number;
595
+ };
596
+ }>(
597
+ "https://gjsttg7mkgtqhjpt3mv5aeuszi0zblbb.lambda-url.us-west-2.on.aws",
598
+ "/feemarket/info.json"
599
+ ).catch(() => ({
600
+ data: {} as Record<
601
+ string,
602
+ { low: number; average: number; high: number }
603
+ >,
604
+ }));
605
+
606
+ let multiplication = DefaultMultiplication;
607
+
608
+ // Apply default multiplication
609
+ const defaultConfig = multiplicationConfig.data["__default__"];
610
+ if (defaultConfig) {
611
+ multiplication = {
612
+ low: defaultConfig.low || multiplication.low,
613
+ average: defaultConfig.average || multiplication.average,
614
+ high: defaultConfig.high || multiplication.high,
615
+ };
616
+ }
617
+
618
+ // Apply chain-specific multiplication
619
+ const chainConfig = multiplicationConfig.data[chainInfo.chainId];
620
+ if (chainConfig) {
621
+ multiplication = {
622
+ low: chainConfig.low || multiplication.low,
623
+ average: chainConfig.average || multiplication.average,
624
+ high: chainConfig.high || multiplication.high,
625
+ };
626
+ }
627
+
628
+ const baseGasPrice = new Dec(gasPrice.amount);
629
+ return baseGasPrice.mul(new Dec(multiplication[feeType]));
630
+ } catch (error) {
631
+ return null;
632
+ }
633
+ }
634
+
635
+ async function getInitiaDynamicFeeGasPrice(
636
+ chainInfo: ChainInfo,
637
+ feeType: BackgroundTxFeeType
638
+ ): Promise<Dec | null> {
639
+ try {
640
+ const dynamicFeeResponse = await simpleFetch<{
641
+ params: {
642
+ base_gas_price: string;
643
+ };
644
+ }>(chainInfo.rest, "/initia/dynamicfee/v1/params");
645
+
646
+ if (!dynamicFeeResponse.data.params.base_gas_price) {
647
+ return null;
648
+ }
649
+
650
+ const baseGasPrice = new Dec(dynamicFeeResponse.data.params.base_gas_price);
651
+
652
+ // Fetch multiplication config
653
+ const multiplicationConfig = await simpleFetch<{
654
+ [str: string]: {
655
+ low: number;
656
+ average: number;
657
+ high: number;
658
+ };
659
+ }>(
660
+ "https://gjsttg7mkgtqhjpt3mv5aeuszi0zblbb.lambda-url.us-west-2.on.aws",
661
+ "/feemarket/info.json"
662
+ ).catch(() => ({
663
+ data: {} as Record<
664
+ string,
665
+ { low: number; average: number; high: number }
666
+ >,
667
+ }));
668
+
669
+ let multiplication = DefaultMultiplication;
670
+
671
+ // Apply default multiplication
672
+ const defaultConfig = multiplicationConfig.data["__default__"];
673
+ if (defaultConfig) {
674
+ multiplication = {
675
+ low: defaultConfig.low || multiplication.low,
676
+ average: defaultConfig.average || multiplication.average,
677
+ high: defaultConfig.high || multiplication.high,
678
+ };
679
+ }
680
+
681
+ // Apply chain-specific multiplication
682
+ const chainConfig = multiplicationConfig.data[chainInfo.chainId];
683
+ if (chainConfig) {
684
+ multiplication = {
685
+ low: chainConfig.low || multiplication.low,
686
+ average: chainConfig.average || multiplication.average,
687
+ high: chainConfig.high || multiplication.high,
688
+ };
689
+ }
690
+
691
+ return baseGasPrice.mul(new Dec(multiplication[feeType]));
692
+ } catch (error) {
693
+ return null;
694
+ }
695
+ }
696
+
697
+ async function getEIP1559GasPrice(
698
+ chainInfo: ChainInfo,
699
+ feeType: BackgroundTxFeeType
700
+ ): Promise<Dec | null> {
701
+ try {
702
+ // Get latest block for base fee
703
+ const blockResponse = await simpleFetch<{
704
+ result: {
705
+ baseFeePerGas: string;
706
+ };
707
+ }>(chainInfo.rpc, {
708
+ method: "POST",
709
+ headers: { "Content-Type": "application/json" },
710
+ body: JSON.stringify({
711
+ jsonrpc: "2.0",
712
+ method: "eth_getBlockByNumber",
713
+ params: ["latest", false],
714
+ id: 1,
715
+ }),
716
+ });
717
+
718
+ const baseFeePerGasHex = blockResponse.data.result.baseFeePerGas;
719
+ if (!baseFeePerGasHex) {
720
+ return null;
721
+ }
722
+
723
+ const baseFeePerGas = new Dec(parseInt(baseFeePerGasHex, 16));
724
+
725
+ // Calculate priority fee (simplified version)
726
+ const priorityFeeMultipliers: Record<BackgroundTxFeeType, number> = {
727
+ low: 1.1,
728
+ average: 1.25,
729
+ high: 1.5,
730
+ };
731
+
732
+ const maxPriorityFeePerGas = baseFeePerGas.mul(
733
+ new Dec(priorityFeeMultipliers[feeType] - 1)
734
+ );
735
+
736
+ return baseFeePerGas.add(maxPriorityFeePerGas);
737
+ } catch (error) {
738
+ return null;
739
+ }
740
+ }
741
+
742
+ export function getDefaultGasPrice(
743
+ feeCurrency: FeeCurrency,
744
+ feeType: BackgroundTxFeeType
745
+ ): Dec {
746
+ const gasPriceStep = feeCurrency.gasPriceStep || DefaultGasPriceStep;
747
+ return new Dec(gasPriceStep[feeType]);
748
+ }
749
+
750
+ export function calculateCosmosStdFee(
751
+ feeCurrency: FeeCurrency,
752
+ gasUsed: number,
753
+ gasPrice: Dec,
754
+ features: string[] | undefined
755
+ ): StdFee {
756
+ const gasAdjustment = features?.includes("feemarket") ? 1.6 : 1.4;
757
+
758
+ const adjustedGas = Math.floor(gasUsed * gasAdjustment);
759
+
760
+ const feeAmount = gasPrice.mul(new Dec(adjustedGas)).roundUp();
761
+
762
+ return {
763
+ amount: [
764
+ {
765
+ denom: feeCurrency.coinMinimalDenom,
766
+ amount: feeAmount.toString(),
767
+ },
768
+ ],
769
+ gas: adjustedGas.toString(),
770
+ };
771
+ }