@keplr-wallet/background 0.12.312 → 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.
- package/build/index.d.ts +1 -0
- package/build/index.js +7 -1
- package/build/index.js.map +1 -1
- package/build/keyring-cosmos/service.d.ts +10 -0
- package/build/keyring-cosmos/service.js +100 -0
- package/build/keyring-cosmos/service.js.map +1 -1
- package/build/keyring-ethereum/service.d.ts +5 -0
- package/build/keyring-ethereum/service.js +66 -0
- package/build/keyring-ethereum/service.js.map +1 -1
- package/build/recent-send-history/api.d.ts +31 -0
- package/build/recent-send-history/api.js +97 -0
- package/build/recent-send-history/api.js.map +1 -0
- package/build/recent-send-history/handler.js +36 -0
- package/build/recent-send-history/handler.js.map +1 -1
- package/build/recent-send-history/init.js +5 -0
- package/build/recent-send-history/init.js.map +1 -1
- package/build/recent-send-history/messages.d.ts +76 -1
- package/build/recent-send-history/messages.js +121 -1
- package/build/recent-send-history/messages.js.map +1 -1
- package/build/recent-send-history/service.d.ts +262 -9
- package/build/recent-send-history/service.js +2103 -812
- package/build/recent-send-history/service.js.map +1 -1
- package/build/recent-send-history/types.d.ts +214 -22
- package/build/recent-send-history/types.js +21 -0
- package/build/recent-send-history/types.js.map +1 -1
- package/build/tx/service.d.ts +2 -0
- package/build/tx/service.js +35 -0
- package/build/tx/service.js.map +1 -1
- package/build/tx-ethereum/service.d.ts +2 -0
- package/build/tx-ethereum/service.js +42 -0
- package/build/tx-ethereum/service.js.map +1 -1
- package/build/tx-executor/constants.d.ts +1 -0
- package/build/tx-executor/constants.js +5 -0
- package/build/tx-executor/constants.js.map +1 -0
- package/build/tx-executor/handler.d.ts +3 -0
- package/build/tx-executor/handler.js +45 -0
- package/build/tx-executor/handler.js.map +1 -0
- package/build/tx-executor/index.d.ts +3 -0
- package/build/tx-executor/index.js +20 -0
- package/build/tx-executor/index.js.map +1 -0
- package/build/tx-executor/init.d.ts +3 -0
- package/build/tx-executor/init.js +14 -0
- package/build/tx-executor/init.js.map +1 -0
- package/build/tx-executor/internal.d.ts +4 -0
- package/build/tx-executor/internal.js +24 -0
- package/build/tx-executor/internal.js.map +1 -0
- package/build/tx-executor/messages.d.ts +53 -0
- package/build/tx-executor/messages.js +116 -0
- package/build/tx-executor/messages.js.map +1 -0
- package/build/tx-executor/service.d.ts +67 -0
- package/build/tx-executor/service.js +715 -0
- package/build/tx-executor/service.js.map +1 -0
- package/build/tx-executor/types.d.ts +105 -0
- package/build/tx-executor/types.js +33 -0
- package/build/tx-executor/types.js.map +1 -0
- package/build/tx-executor/utils/cosmos.d.ts +59 -0
- package/build/tx-executor/utils/cosmos.js +526 -0
- package/build/tx-executor/utils/cosmos.js.map +1 -0
- package/build/tx-executor/utils/evm.d.ts +4 -0
- package/build/tx-executor/utils/evm.js +236 -0
- package/build/tx-executor/utils/evm.js.map +1 -0
- package/package.json +13 -13
- package/src/index.ts +24 -1
- package/src/keyring-cosmos/service.ts +151 -0
- package/src/keyring-ethereum/service.ts +103 -6
- package/src/recent-send-history/api.ts +119 -0
- package/src/recent-send-history/handler.ts +84 -0
- package/src/recent-send-history/init.ts +10 -0
- package/src/recent-send-history/messages.ts +163 -1
- package/src/recent-send-history/service.ts +3042 -1153
- package/src/recent-send-history/types.ts +268 -31
- package/src/tx/service.ts +41 -0
- package/src/tx-ethereum/service.ts +57 -0
- package/src/tx-executor/constants.ts +1 -0
- package/src/tx-executor/handler.ts +71 -0
- package/src/tx-executor/index.ts +3 -0
- package/src/tx-executor/init.ts +20 -0
- package/src/tx-executor/internal.ts +9 -0
- package/src/tx-executor/messages.ts +157 -0
- package/src/tx-executor/service.ts +1025 -0
- package/src/tx-executor/types.ts +161 -0
- package/src/tx-executor/utils/cosmos.ts +771 -0
- 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
|
+
}
|