Skip to content

Commit fe524db

Browse files
committed
add dynamic gasfee pricing as an option through feemarket and osmosis eip1559
1 parent d314927 commit fe524db

File tree

6 files changed

+1306
-22
lines changed

6 files changed

+1306
-22
lines changed

packages/cosmwasm/src/signingcosmwasmclient.ts

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { encodeSecp256k1Pubkey, makeSignDoc as makeSignDocAmino } from "@cosmjs/amino";
22
import { sha256 } from "@cosmjs/crypto";
33
import { fromBase64, toHex, toUtf8 } from "@cosmjs/encoding";
4-
import { Int53, Uint53 } from "@cosmjs/math";
4+
import { Decimal, Int53, Uint53 } from "@cosmjs/math";
55
import {
66
EncodeObject,
77
encodePubkey,
@@ -20,6 +20,7 @@ import {
2020
createDefaultAminoConverters,
2121
defaultRegistryTypes as defaultStargateTypes,
2222
DeliverTxResponse,
23+
DynamicGasPriceConfig,
2324
Event,
2425
GasPrice,
2526
isDeliverTxFailure,
@@ -28,6 +29,8 @@ import {
2829
MsgSendEncodeObject,
2930
MsgUndelegateEncodeObject,
3031
MsgWithdrawDelegatorRewardEncodeObject,
32+
multiplyDecimalByNumber,
33+
queryDynamicGasPrice,
3134
SignerData,
3235
StdFee,
3336
} from "@cosmjs/stargate";
@@ -190,7 +193,8 @@ export interface SigningCosmWasmClientOptions {
190193
readonly aminoTypes?: AminoTypes;
191194
readonly broadcastTimeoutMs?: number;
192195
readonly broadcastPollIntervalMs?: number;
193-
readonly gasPrice?: GasPrice;
196+
/** Gas price configuration. Can be a static GasPrice or a DynamicGasPriceConfig for dynamic pricing. */
197+
readonly gasPrice?: GasPrice | DynamicGasPriceConfig;
194198
}
195199

196200
export class SigningCosmWasmClient extends CosmWasmClient {
@@ -200,10 +204,12 @@ export class SigningCosmWasmClient extends CosmWasmClient {
200204

201205
private readonly signer: OfflineSigner;
202206
private readonly aminoTypes: AminoTypes;
203-
private readonly gasPrice: GasPrice | undefined;
207+
private readonly gasPrice: GasPrice | DynamicGasPriceConfig | undefined;
204208
// Starting with Cosmos SDK 0.47, we see many cases in which 1.3 is not enough anymore
205209
// E.g. https://github.com/cosmos/cosmos-sdk/issues/16020
206210
private readonly defaultGasMultiplier = 1.4;
211+
// Default multiplier for dynamic gas price (applied on top of queried price)
212+
private readonly defaultDynamicGasMultiplier = 1.3;
207213

208214
/**
209215
* Creates an instance by connecting to the given CometBFT RPC endpoint.
@@ -615,10 +621,7 @@ export class SigningCosmWasmClient extends CosmWasmClient {
615621
): Promise<DeliverTxResponse> {
616622
let usedFee: StdFee;
617623
if (fee == "auto" || typeof fee === "number") {
618-
assertDefined(this.gasPrice, "Gas price must be set in the client options when auto gas is used.");
619-
const gasEstimation = await this.simulate(signerAddress, messages, memo);
620-
const multiplier = typeof fee === "number" ? fee : this.defaultGasMultiplier;
621-
usedFee = calculateFee(Math.round(gasEstimation * multiplier), this.gasPrice);
624+
usedFee = await this.calculateFeeForTransaction(signerAddress, messages, memo, fee);
622625
} else {
623626
usedFee = fee;
624627
}
@@ -651,10 +654,7 @@ export class SigningCosmWasmClient extends CosmWasmClient {
651654
): Promise<string> {
652655
let usedFee: StdFee;
653656
if (fee == "auto" || typeof fee === "number") {
654-
assertDefined(this.gasPrice, "Gas price must be set in the client options when auto gas is used.");
655-
const gasEstimation = await this.simulate(signerAddress, messages, memo);
656-
const multiplier = typeof fee === "number" ? fee : this.defaultGasMultiplier;
657-
usedFee = calculateFee(Math.round(gasEstimation * multiplier), this.gasPrice);
657+
usedFee = await this.calculateFeeForTransaction(signerAddress, messages, memo, fee);
658658
} else {
659659
usedFee = fee;
660660
}
@@ -663,6 +663,75 @@ export class SigningCosmWasmClient extends CosmWasmClient {
663663
return this.broadcastTxSync(txBytes);
664664
}
665665

666+
private async calculateFeeForTransaction(
667+
signerAddress: string,
668+
messages: readonly EncodeObject[],
669+
memo: string,
670+
fee: "auto" | number,
671+
): Promise<StdFee> {
672+
const gasEstimation = await this.simulate(signerAddress, messages, memo);
673+
const multiplier = typeof fee === "number" ? fee : this.defaultGasMultiplier;
674+
675+
const gasPriceConfig = this.gasPrice;
676+
677+
// Check if gasPrice is dynamic config or static GasPrice
678+
if (gasPriceConfig && "minGasPrice" in gasPriceConfig) {
679+
// Dynamic gas price config
680+
const dynamicGasConfig = gasPriceConfig;
681+
const multiplierValue = dynamicGasConfig.multiplier ?? this.defaultDynamicGasMultiplier;
682+
const minGasPrice = dynamicGasConfig.minGasPrice;
683+
const maxGasPrice = dynamicGasConfig.maxGasPrice;
684+
685+
try {
686+
const chainId = await this.getChainId();
687+
const queryClient = this.forceGetQueryClient();
688+
const dynamicGasPriceDecimal = await queryDynamicGasPrice(
689+
queryClient,
690+
dynamicGasConfig.denom,
691+
chainId,
692+
);
693+
694+
// Multiply by multiplier 18 fractional digits for Dec type
695+
const fractionalDigits = minGasPrice.amount.fractionalDigits;
696+
const adjustedGasPrice = multiplyDecimalByNumber(
697+
dynamicGasPriceDecimal,
698+
multiplierValue,
699+
fractionalDigits,
700+
);
701+
702+
// Apply min and max constraints using comparison methods
703+
let finalGasPrice = adjustedGasPrice.isGreaterThan(minGasPrice.amount)
704+
? adjustedGasPrice
705+
: minGasPrice.amount;
706+
if (maxGasPrice) {
707+
// Normalize maxGasPrice to same fractional digits if needed (user might create it differently)
708+
const normalizedMaxGasPrice =
709+
maxGasPrice.amount.fractionalDigits === fractionalDigits
710+
? maxGasPrice.amount
711+
: Decimal.fromUserInput(maxGasPrice.amount.toString(), fractionalDigits);
712+
finalGasPrice = finalGasPrice.isLessThan(normalizedMaxGasPrice)
713+
? finalGasPrice
714+
: normalizedMaxGasPrice;
715+
}
716+
const dynamicGasPriceObj = new GasPrice(finalGasPrice, dynamicGasConfig.denom);
717+
return calculateFee(Math.round(gasEstimation * multiplier), dynamicGasPriceObj);
718+
} catch (error) {
719+
// Fallback to minGasPrice if query fails
720+
return calculateFee(Math.round(gasEstimation * multiplier), minGasPrice);
721+
}
722+
} else {
723+
// Static gas price
724+
if (!gasPriceConfig) {
725+
throw new Error("Gas price must be set in the client options when auto gas is used.");
726+
}
727+
if (!(gasPriceConfig instanceof GasPrice) && !("amount" in gasPriceConfig && "denom" in gasPriceConfig)) {
728+
throw new Error("Gas price must be a GasPrice instance when using static pricing.");
729+
}
730+
const staticGasPrice = gasPriceConfig as GasPrice;
731+
return calculateFee(Math.round(gasEstimation * multiplier), staticGasPrice);
732+
}
733+
}
734+
666735
public async sign(
667736
signerAddress: string,
668737
messages: readonly EncodeObject[],

0 commit comments

Comments
 (0)