diff --git a/apps/extension/src/pages/main/components/claim-all/index.tsx b/apps/extension/src/pages/main/components/claim-all/index.tsx index 33a66a5bdc..70dc0eae9d 100644 --- a/apps/extension/src/pages/main/components/claim-all/index.tsx +++ b/apps/extension/src/pages/main/components/claim-all/index.tsx @@ -19,6 +19,7 @@ import { CoinPretty, Dec, Int, PricePretty } from "@keplr-wallet/unit"; import { AminoSignResponse, BroadcastMode, + FeeCurrency, StdSignDoc, } from "@keplr-wallet/types"; import { InExtensionMessageRequester } from "@keplr-wallet/router-extension"; @@ -286,10 +287,14 @@ export const ClaimAll: FunctionComponent<{ isNotReady?: boolean }> = observer( account.cosmos.makeWithdrawDelegationRewardTx(validatorAddresses); (async () => { - let feeCurrency = chainInfo.feeCurrencies.find( - (cur) => - cur.coinMinimalDenom === chainInfo.stakeCurrency?.coinMinimalDenom - ); + // feemarket feature가 있는 경우 이후의 로직에서 사용할 수 있는 fee currency를 찾아야하기 때문에 undefined로 시작시킨다. + let feeCurrency = chainInfo.hasFeature("feemarket") + ? undefined + : chainInfo.feeCurrencies.find( + (cur) => + cur.coinMinimalDenom === + chainInfo.stakeCurrency?.coinMinimalDenom + ); if (chainInfo.hasFeature("osmosis-base-fee-beta") && feeCurrency) { const queryBaseFee = queriesStore.get(chainInfo.chainId).osmosis @@ -354,7 +359,107 @@ export const ClaimAll: FunctionComponent<{ isNotReady?: boolean }> = observer( } | undefined; - for (const chainFeeCurrency of chainInfo.feeCurrencies) { + const feeCurrencies = await (async () => { + if (chainInfo.hasFeature("feemarket")) { + const queryFeeMarketGasPrices = + queriesStore.get(chainId).cosmos.queryFeeMarketGasPrices; + await queryFeeMarketGasPrices.waitFreshResponse(); + + const result: FeeCurrency[] = []; + + for (const gasPrice of queryFeeMarketGasPrices.gasPrices) { + const currency = await chainInfo.findCurrencyAsync( + gasPrice.denom + ); + if (currency) { + let multiplication = { + low: 1.1, + average: 1.2, + high: 1.3, + }; + + const multificationConfig = + queriesStore.simpleQuery.queryGet<{ + [str: string]: + | { + low: number; + average: number; + high: number; + } + | undefined; + }>( + "https://gjsttg7mkgtqhjpt3mv5aeuszi0zblbb.lambda-url.us-west-2.on.aws", + "/feemarket/info.json" + ); + + if (multificationConfig.response) { + const _default = + multificationConfig.response.data["__default__"]; + if ( + _default && + _default.low != null && + typeof _default.low === "number" && + _default.average != null && + typeof _default.average === "number" && + _default.high != null && + typeof _default.high === "number" + ) { + multiplication = { + low: _default.low, + average: _default.average, + high: _default.high, + }; + } + const specific = + multificationConfig.response.data[ + chainInfo.chainIdentifier + ]; + if ( + specific && + specific.low != null && + typeof specific.low === "number" && + specific.average != null && + typeof specific.average === "number" && + specific.high != null && + typeof specific.high === "number" + ) { + multiplication = { + low: specific.low, + average: specific.average, + high: specific.high, + }; + } + } + + result.push({ + ...currency, + gasPriceStep: { + low: parseFloat( + new Dec(multiplication.low) + .mul(gasPrice.amount) + .toString() + ), + average: parseFloat( + new Dec(multiplication.average) + .mul(gasPrice.amount) + .toString() + ), + high: parseFloat( + new Dec(multiplication.high) + .mul(gasPrice.amount) + .toString() + ), + }, + }); + } + } + + return result; + } else { + return chainInfo.feeCurrencies; + } + })(); + for (const chainFeeCurrency of feeCurrencies) { const currency = await chainInfo.findCurrencyAsync( chainFeeCurrency.coinMinimalDenom ); @@ -369,7 +474,10 @@ export const ClaimAll: FunctionComponent<{ isNotReady?: boolean }> = observer( ); if (!prev) { - feeCurrency = currency; + feeCurrency = { + ...chainFeeCurrency, + ...currency, + }; prev = { balance: balance.balance, price, @@ -377,7 +485,10 @@ export const ClaimAll: FunctionComponent<{ isNotReady?: boolean }> = observer( } else { if (!prev.price) { if (prev.balance.toDec().lt(balance.balance.toDec())) { - feeCurrency = currency; + feeCurrency = { + ...chainFeeCurrency, + ...currency, + }; prev = { balance: balance.balance, price, @@ -385,7 +496,10 @@ export const ClaimAll: FunctionComponent<{ isNotReady?: boolean }> = observer( } } else if (price) { if (prev.price.toDec().lt(price.toDec())) { - feeCurrency = currency; + feeCurrency = { + ...chainFeeCurrency, + ...currency, + }; prev = { balance: balance.balance, price, diff --git a/packages/chain-validator/src/feature.ts b/packages/chain-validator/src/feature.ts index 5c258ed5eb..510f502d5b 100644 --- a/packages/chain-validator/src/feature.ts +++ b/packages/chain-validator/src/feature.ts @@ -22,6 +22,7 @@ export const SupportedChainFeatures = [ "ibc-pfm", "authz-msg-revoke-fixed", "osmosis-base-fee-beta", + "feemarket", ]; /** @@ -151,6 +152,18 @@ export const RecognizableChainFeaturesMethod: { return result.status === 400; }, }, + { + feature: "feemarket", + fetch: async (_features, _rpc, rest) => { + const result = await simpleFetch<{ + params: { + enabled: boolean; + }; + }>(rest, "/feemarket/v1/params"); + + return result.data.params.enabled; + }, + }, ]; /** diff --git a/packages/hooks/src/tx/fee.ts b/packages/hooks/src/tx/fee.ts index 7cacad222a..ee8d60d8e6 100644 --- a/packages/hooks/src/tx/fee.ts +++ b/packages/hooks/src/tx/fee.ts @@ -254,6 +254,35 @@ export class FeeConfig extends TxChainSetter implements IFeeConfig { return cur1.coinMinimalDenom < cur2.coinMinimalDenom ? -1 : 1; }); } + } else if (this.canFeeMarketTxFeesAndReady()) { + const queryCosmos = this.queriesStore.get(this.chainId).cosmos; + if (queryCosmos) { + const gasPrices = queryCosmos.queryFeeMarketGasPrices.gasPrices; + + const found: FeeCurrency[] = []; + for (const gasPrice of gasPrices) { + const cur = this.chainInfo.findCurrency(gasPrice.denom); + if (cur) { + found.push(cur); + } + } + + const firstFeeDenom = + this.chainInfo.feeCurrencies.length > 0 + ? this.chainInfo.feeCurrencies[0].coinMinimalDenom + : ""; + return found.sort((cur1, cur2) => { + // firstFeeDenom should be the first. + // others should be sorted in alphabetical order. + if (cur1.coinMinimalDenom === firstFeeDenom) { + return -1; + } + if (cur2.coinMinimalDenom === firstFeeDenom) { + return 1; + } + return cur1.coinDenom < cur2.coinDenom ? -1 : 1; + }); + } } const res: FeeCurrency[] = []; @@ -409,6 +438,38 @@ export class FeeConfig extends TxChainSetter implements IFeeConfig { return false; } + protected canFeeMarketTxFeesAndReady(): boolean { + if (this.chainInfo.hasFeature("feemarket")) { + const queries = this.queriesStore.get(this.chainId); + if (!queries.cosmos) { + console.log( + "Chain has feemarket feature. But no cosmos queries provided." + ); + return false; + } + + const queryFeeMarketGasPrices = queries.cosmos.queryFeeMarketGasPrices; + + if (queryFeeMarketGasPrices.gasPrices.length === 0) { + return false; + } + + for (let i = 0; i < queryFeeMarketGasPrices.gasPrices.length; i++) { + const gasPrice = queryFeeMarketGasPrices.gasPrices[i]; + // 일단 모든 currency에 대해서 find를 시도한다. + this.chainInfo.findCurrency(gasPrice.denom); + } + + return ( + queryFeeMarketGasPrices.gasPrices.find((gasPrice) => + this.chainInfo.findCurrency(gasPrice.denom) + ) != null + ); + } + + return false; + } + protected canEIP1559TxFeesAndReady(): boolean { if (this.chainInfo.evm && this.senderConfig.sender.startsWith("0x")) { const queries = this.queriesStore.get(this.chainId); @@ -548,6 +609,81 @@ export class FeeConfig extends TxChainSetter implements IFeeConfig { return this.populateGasPriceStep(baseFeeCurrency, feeType); } } + } else if (this.canFeeMarketTxFeesAndReady()) { + const queryCosmos = this.queriesStore.get(this.chainId).cosmos; + if (queryCosmos) { + const gasPrices = queryCosmos.queryFeeMarketGasPrices.gasPrices; + + const gasPrice = gasPrices.find( + (gasPrice) => gasPrice.denom === feeCurrency.coinMinimalDenom + ); + if (gasPrice) { + let multiplication = { + low: 1.1, + average: 1.2, + high: 1.3, + }; + + const multificationConfig = this.queriesStore.simpleQuery.queryGet<{ + [str: string]: + | { + low: number; + average: number; + high: number; + } + | undefined; + }>( + "https://gjsttg7mkgtqhjpt3mv5aeuszi0zblbb.lambda-url.us-west-2.on.aws", + "/feemarket/info.json" + ); + + if (multificationConfig.response) { + const _default = multificationConfig.response.data["__default__"]; + if ( + _default && + _default.low != null && + typeof _default.low === "number" && + _default.average != null && + typeof _default.average === "number" && + _default.high != null && + typeof _default.high === "number" + ) { + multiplication = { + low: _default.low, + average: _default.average, + high: _default.high, + }; + } + const specific = + multificationConfig.response.data[ + this.chainInfo.chainIdentifier + ]; + if ( + specific && + specific.low != null && + typeof specific.low === "number" && + specific.average != null && + typeof specific.average === "number" && + specific.high != null && + typeof specific.high === "number" + ) { + multiplication = { + low: specific.low, + average: specific.average, + high: specific.high, + }; + } + } + switch (feeType) { + case "low": + return new Dec(multiplication.low).mul(gasPrice.amount); + case "average": + return new Dec(multiplication.average).mul(gasPrice.amount); + case "high": + return new Dec(multiplication.high).mul(gasPrice.amount); + } + } + } } if (this.canEIP1559TxFeesAndReady()) { @@ -719,6 +855,26 @@ export class FeeConfig extends TxChainSetter implements IFeeConfig { }; } } + } else if (this.canFeeMarketTxFeesAndReady()) { + const queryCosmos = this.queriesStore.get(this.chainId).cosmos; + if (queryCosmos) { + const queryFeeMarketGasPrices = queryCosmos.queryFeeMarketGasPrices; + if (queryFeeMarketGasPrices.error) { + return { + warning: new Error("Failed to fetch gas prices"), + }; + } + if (!queryFeeMarketGasPrices.response) { + return { + loadingState: "loading-block", + }; + } + if (queryFeeMarketGasPrices.isFetching) { + return { + loadingState: "loading", + }; + } + } } if (this.canOsmosisTxFeesAndReady()) { diff --git a/packages/hooks/src/tx/internal.ts b/packages/hooks/src/tx/internal.ts index 89d0990362..abe2287b8e 100644 --- a/packages/hooks/src/tx/internal.ts +++ b/packages/hooks/src/tx/internal.ts @@ -9,7 +9,10 @@ import { EthereumQueries } from "@keplr-wallet/stores-eth"; export type QueriesStore = IQueriesStore< Partial & Partial & { - cosmos?: Pick; + cosmos?: Pick< + CosmosQueriesImpl, + "queryDelegations" | "queryFeeMarketGasPrices" + >; } & { keplrETC?: Pick< KeplrETCQueriesImpl, diff --git a/packages/stores/src/query/cosmos/feemarket/index.ts b/packages/stores/src/query/cosmos/feemarket/index.ts new file mode 100644 index 0000000000..eb28252c38 --- /dev/null +++ b/packages/stores/src/query/cosmos/feemarket/index.ts @@ -0,0 +1,34 @@ +import { ObservableChainQuery } from "../../chain-query"; +import { GasPrices } from "./types"; +import { QuerySharedContext } from "../../../common"; +import { ChainGetter } from "../../../chain"; +import { makeObservable } from "mobx"; +import { Dec } from "@keplr-wallet/unit"; + +export class ObservableQueryFeeMarketGasPrices extends ObservableChainQuery { + constructor( + sharedContext: QuerySharedContext, + chainId: string, + chainGetter: ChainGetter + ) { + super(sharedContext, chainId, chainGetter, "/feemarket/v1/gas_prices"); + + makeObservable(this); + } + + get gasPrices(): { + denom: string; + amount: Dec; + }[] { + if (!this.response || !this.response.data.prices) { + return []; + } + + return this.response.data.prices.map((price) => { + return { + denom: price.denom, + amount: new Dec(price.amount), + }; + }); + } +} diff --git a/packages/stores/src/query/cosmos/feemarket/types.ts b/packages/stores/src/query/cosmos/feemarket/types.ts new file mode 100644 index 0000000000..e7f24c0625 --- /dev/null +++ b/packages/stores/src/query/cosmos/feemarket/types.ts @@ -0,0 +1,6 @@ +export interface GasPrices { + prices: { + denom: string; + amount: string; + }[]; +} diff --git a/packages/stores/src/query/cosmos/queries.ts b/packages/stores/src/query/cosmos/queries.ts index fa5aef924e..30e823d495 100644 --- a/packages/stores/src/query/cosmos/queries.ts +++ b/packages/stores/src/query/cosmos/queries.ts @@ -23,6 +23,7 @@ import { ObservableQueryDistributionParams } from "./distribution"; import { ObservableQueryRPCStatus } from "./status"; import { ObservableQueryAuthZGranter } from "./authz"; import { QuerySharedContext } from "../../common"; +import { ObservableQueryFeeMarketGasPrices } from "./feemarket"; export interface CosmosQueries { cosmos: CosmosQueriesImpl; @@ -72,6 +73,8 @@ export class CosmosQueriesImpl { public readonly queryAuthZGranter: DeepReadonly; + public readonly queryFeeMarketGasPrices: DeepReadonly; + constructor( base: QueriesSetBase, sharedContext: QuerySharedContext, @@ -156,5 +159,11 @@ export class CosmosQueriesImpl { chainId, chainGetter ); + + this.queryFeeMarketGasPrices = new ObservableQueryFeeMarketGasPrices( + sharedContext, + chainId, + chainGetter + ); } }