11import { encodeSecp256k1Pubkey , makeSignDoc as makeSignDocAmino } from "@cosmjs/amino" ;
22import { sha256 } from "@cosmjs/crypto" ;
33import { fromBase64 , toHex , toUtf8 } from "@cosmjs/encoding" ;
4- import { Int53 , Uint53 } from "@cosmjs/math" ;
4+ import { Decimal , Int53 , Uint53 } from "@cosmjs/math" ;
55import {
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
196200export 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