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