11from abc import ABC , abstractmethod
22from decimal import Decimal
3- from typing import List , Optional
3+ from typing import Callable , List , Optional , Tuple
44
55import math
66from google .protobuf import any_pb2
99from pyinjective .async_client import AsyncClient
1010from pyinjective .composer import Composer
1111from pyinjective .constant import Network
12+ from pyinjective .proto .cosmos .authz .v1beta1 import tx_pb2 as cosmos_authz_tx_pb
1213
1314
1415class BroadcasterAccountConfig (ABC ):
@@ -86,6 +87,31 @@ def new_using_simulation(
8687 )
8788 return instance
8889
90+ @classmethod
91+ def new_without_simulation (
92+ cls ,
93+ network : Network ,
94+ private_key : str ,
95+ use_secure_connection : bool = True ,
96+ client : Optional [AsyncClient ] = None ,
97+ composer : Optional [Composer ] = None ,
98+ ):
99+ client = client or AsyncClient (network = network , insecure = not (use_secure_connection ))
100+ composer = composer or Composer (network = client .network .string ())
101+ account_config = StandardAccountBroadcasterConfig (private_key = private_key )
102+ fee_calculator = MessageBasedTransactionFeeCalculator (
103+ client = client ,
104+ composer = composer
105+ )
106+ instance = cls (
107+ network = network ,
108+ account_config = account_config ,
109+ client = client ,
110+ composer = composer ,
111+ fee_calculator = fee_calculator ,
112+ )
113+ return instance
114+
89115 @classmethod
90116 def new_for_grantee_account_using_simulation (
91117 cls ,
@@ -111,6 +137,31 @@ def new_for_grantee_account_using_simulation(
111137 )
112138 return instance
113139
140+ @classmethod
141+ def new_for_grantee_account_without_simulation (
142+ cls ,
143+ network : Network ,
144+ grantee_private_key : str ,
145+ use_secure_connection : bool = True ,
146+ client : Optional [AsyncClient ] = None ,
147+ composer : Optional [Composer ] = None ,
148+ ):
149+ client = client or AsyncClient (network = network , insecure = not (use_secure_connection ))
150+ composer = composer or Composer (network = client .network .string ())
151+ account_config = GranteeAccountBroadcasterConfig (grantee_private_key = grantee_private_key , composer = composer )
152+ fee_calculator = MessageBasedTransactionFeeCalculator (
153+ client = client ,
154+ composer = composer
155+ )
156+ instance = cls (
157+ network = network ,
158+ account_config = account_config ,
159+ client = client ,
160+ composer = composer ,
161+ fee_calculator = fee_calculator ,
162+ )
163+ return instance
164+
114165 async def broadcast (self , messages : List [any_pb2 .Any ]):
115166 await self ._client .sync_timeout_height ()
116167 await self ._client .get_account (self ._account_config .trading_injective_address )
@@ -196,10 +247,16 @@ def messages_prepared_for_transaction(self, messages: List[any_pb2.Any]) -> List
196247
197248class SimulatedTransactionFeeCalculator (TransactionFeeCalculator ):
198249
199- def __init__ (self , client : AsyncClient , composer : Composer , gas_price : Optional [int ] = None ):
250+ def __init__ (
251+ self ,
252+ client : AsyncClient ,
253+ composer : Composer ,
254+ gas_price : Optional [int ] = None ,
255+ gas_limit_adjustment_multiplier : Optional [Decimal ] = None ):
200256 self ._client = client
201257 self ._composer = composer
202258 self ._gas_price = gas_price or self .DEFAULT_GAS_PRICE
259+ self ._gas_limit_adjustment_multiplier = gas_limit_adjustment_multiplier or Decimal ("1.3" )
203260
204261 async def configure_gas_fee_for_transaction (
205262 self ,
@@ -216,7 +273,7 @@ async def configure_gas_fee_for_transaction(
216273 if not success :
217274 raise RuntimeError (f"Transaction simulation error: { sim_res } " )
218275
219- gas_limit = math .ceil (Decimal (str (sim_res .gas_info .gas_used )) * Decimal ( "1.1" ) )
276+ gas_limit = math .ceil (Decimal (str (sim_res .gas_info .gas_used )) * self . _gas_limit_adjustment_multiplier )
220277
221278 fee = [
222279 self ._composer .Coin (
@@ -227,3 +284,95 @@ async def configure_gas_fee_for_transaction(
227284
228285 transaction .with_gas (gas = gas_limit )
229286 transaction .with_fee (fee = fee )
287+
288+
289+ class MessageBasedTransactionFeeCalculator (TransactionFeeCalculator ):
290+ DEFAULT_GAS_LIMIT = 400_000
291+ DEFAULT_EXCHANGE_GAS_LIMIT = 200_000
292+
293+ def __init__ (
294+ self ,
295+ client : AsyncClient ,
296+ composer : Composer ,
297+ gas_price : Optional [int ] = None ,
298+ base_gas_limit : Optional [int ] = None ,
299+ base_exchange_gas_limit : Optional [int ] = None ):
300+ self ._client = client
301+ self ._composer = composer
302+ self ._gas_price = gas_price or self .DEFAULT_GAS_PRICE
303+ self ._base_gas_limit = base_gas_limit or self .DEFAULT_GAS_LIMIT
304+ self ._base_exchange_gas_limit = base_exchange_gas_limit or self .DEFAULT_EXCHANGE_GAS_LIMIT
305+
306+ self ._gas_limit_per_message_type_rules : List [Tuple [Callable , Decimal ]] = [
307+ (
308+ lambda message : "MsgPrivilegedExecuteContract" in self ._message_type (message = message ),
309+ Decimal ("6" ) * self ._base_gas_limit
310+ ),
311+ (
312+ lambda message : "MsgExecuteContract" in self ._message_type (message = message ),
313+ Decimal ("2.5" ) * self ._base_gas_limit
314+ ),
315+ (
316+ lambda message : "wasm" in self ._message_type (message = message ),
317+ Decimal ("1.5" ) * self ._base_gas_limit
318+ ),
319+ (
320+ lambda message : "exchange" in self ._message_type (message = message ),
321+ Decimal ("1" ) * self ._base_exchange_gas_limit
322+ ),
323+ (
324+ lambda message : self ._is_governance_message (message = message ),
325+ Decimal ("15" ) * self ._base_gas_limit
326+ ),
327+ ]
328+
329+ async def configure_gas_fee_for_transaction (
330+ self ,
331+ transaction : Transaction ,
332+ private_key : PrivateKey ,
333+ public_key : PublicKey ,
334+ ):
335+ transaction_gas_limit = math .ceil (self ._calculate_gas_limit (messages = transaction .msgs ))
336+
337+ fee = [
338+ self ._composer .Coin (
339+ amount = math .ceil (self ._gas_price * transaction_gas_limit ),
340+ denom = self ._client .network .fee_denom ,
341+ )
342+ ]
343+
344+ transaction .with_gas (gas = transaction_gas_limit )
345+ transaction .with_fee (fee = fee )
346+
347+ def _message_type (self , message : any_pb2 .Any ) -> str :
348+ if isinstance (message , any_pb2 .Any ):
349+ message_type = message .type_url
350+ else :
351+ message_type = message .DESCRIPTOR .full_name
352+ return message_type
353+
354+ def _is_governance_message (self , message : any_pb2 .Any ) -> bool :
355+ message_type = self ._message_type (message = message )
356+ return "gov" in message_type and ("MsgDeposit" in message_type or "MsgSubmitProposal" in message_type )
357+
358+ def _calculate_gas_limit (self , messages : List [any_pb2 .Any ]) -> int :
359+ total_gas_limit = Decimal ("0" )
360+
361+ for message in messages :
362+ applying_rule = next (
363+ (rule_tuple for rule_tuple in self ._gas_limit_per_message_type_rules
364+ if rule_tuple [0 ](message )),
365+ None
366+ )
367+
368+ if applying_rule is None :
369+ total_gas_limit += self ._base_gas_limit
370+ else :
371+ total_gas_limit += applying_rule [1 ]
372+
373+ if self ._message_type (message = message ).endswith ("MsgExec" ):
374+ exec_message = cosmos_authz_tx_pb .MsgExec .FromString (message .value )
375+ sub_messages_limit = self ._calculate_gas_limit (messages = exec_message .msgs )
376+ total_gas_limit += sub_messages_limit
377+
378+ return math .ceil (total_gas_limit )
0 commit comments