Skip to content

Commit 5aaa785

Browse files
committed
Add feemarket work (by ertemann)
This is the diff from #1911
1 parent 0ec6cd7 commit 5aaa785

File tree

6 files changed

+1253
-22
lines changed

6 files changed

+1253
-22
lines changed

packages/cosmwasm/src/signingcosmwasmclient.ts

Lines changed: 84 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 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

Comments
 (0)