@@ -2788,6 +2788,44 @@ where
27882788 f ( Box :: new ( cache_db) , block_info)
27892789 }
27902790
2791+ /// Returns the ERC20/TIP20 token balance for an account.
2792+ ///
2793+ /// Calls `balanceOf(address)` on the token contract. Returns `U256::ZERO` if
2794+ /// the call fails (e.g. the token contract doesn't exist).
2795+ pub async fn get_fee_token_balance (
2796+ & self ,
2797+ token : Address ,
2798+ account : Address ,
2799+ ) -> Result < U256 , BlockchainError > {
2800+ // balanceOf(address) selector: 0x70a08231
2801+ let mut calldata = vec ! [ 0x70 , 0xa0 , 0x82 , 0x31 ] ;
2802+ // ABI-encode the address (left-padded to 32 bytes)
2803+ calldata. extend_from_slice ( & [ 0u8 ; 12 ] ) ;
2804+ calldata. extend_from_slice ( account. as_slice ( ) ) ;
2805+
2806+ let request = WithOtherFields :: new ( TransactionRequest {
2807+ from : Some ( Address :: ZERO ) ,
2808+ to : Some ( TxKind :: Call ( token) ) ,
2809+ input : calldata. into ( ) ,
2810+ ..Default :: default ( )
2811+ } ) ;
2812+
2813+ let fee_details = FeeDetails :: zero ( ) ;
2814+ let ( exit, out, _, _) = self . call ( request, fee_details, None , Default :: default ( ) ) . await ?;
2815+
2816+ // Check if call succeeded
2817+ if exit != InstructionResult :: Return && exit != InstructionResult :: Stop {
2818+ // Return zero balance if call failed (token might not exist)
2819+ return Ok ( U256 :: ZERO ) ;
2820+ }
2821+
2822+ // Decode U256 from output
2823+ match out {
2824+ Some ( Output :: Call ( data) ) if data. len ( ) >= 32 => Ok ( U256 :: from_be_slice ( & data[ ..32 ] ) ) ,
2825+ _ => Ok ( U256 :: ZERO ) ,
2826+ }
2827+ }
2828+
27912829 /// Executes the [TransactionRequest] without writing to the DB
27922830 ///
27932831 /// # Errors
@@ -4049,7 +4087,60 @@ impl TransactionValidator<FoundryTxEnvelope> for Backend<FoundryNetwork> {
40494087 ) -> Result < ( ) , BlockchainError > {
40504088 let address = * tx. sender ( ) ;
40514089 let account = self . get_account ( address) . await ?;
4052- Ok ( self . validate_pool_transaction_for ( tx, & account, & self . next_evm_env ( ) ) ?)
4090+ let evm_env = self . next_evm_env ( ) ;
4091+
4092+ // Tempo AA: validate time bounds and fee token balance (async checks)
4093+ if let FoundryTxEnvelope :: Tempo ( aa_tx) = tx. transaction . as_ref ( ) {
4094+ let tempo_tx = aa_tx. tx ( ) ;
4095+ let current_time = evm_env. block_env . timestamp . saturating_to :: < u64 > ( ) ;
4096+
4097+ // Reject if valid_before is expired or too close to current time (< 3 seconds)
4098+ const AA_VALID_BEFORE_MIN_SECS : u64 = 3 ;
4099+ if let Some ( valid_before) = tempo_tx. valid_before {
4100+ let min_allowed = current_time. saturating_add ( AA_VALID_BEFORE_MIN_SECS ) ;
4101+ if valid_before <= min_allowed {
4102+ return Err ( InvalidTransactionError :: TempoValidBeforeExpired {
4103+ valid_before,
4104+ min_allowed,
4105+ }
4106+ . into ( ) ) ;
4107+ }
4108+ }
4109+
4110+ // Reject if valid_after is too far in the future (> 1 hour)
4111+ const AA_VALID_AFTER_MAX_SECS : u64 = 3600 ;
4112+ if let Some ( valid_after) = tempo_tx. valid_after {
4113+ let max_allowed = current_time. saturating_add ( AA_VALID_AFTER_MAX_SECS ) ;
4114+ if valid_after > max_allowed {
4115+ return Err ( InvalidTransactionError :: TempoValidAfterTooFar {
4116+ valid_after,
4117+ max_allowed,
4118+ }
4119+ . into ( ) ) ;
4120+ }
4121+ }
4122+
4123+ // Fee token balance check
4124+ let fee_payer = tempo_tx. recover_fee_payer ( address) . unwrap_or ( address) ;
4125+ let fee_token =
4126+ tempo_tx. fee_token . unwrap_or ( foundry_evm:: core:: tempo:: PATH_USD_ADDRESS ) ;
4127+
4128+ // gas_limit * max_fee_per_gas in wei, scaled to 6-decimal token units
4129+ let required_wei =
4130+ U256 :: from ( tempo_tx. gas_limit ) . saturating_mul ( U256 :: from ( tempo_tx. max_fee_per_gas ) ) ;
4131+ let required = required_wei / U256 :: from ( 10u64 . pow ( 12 ) ) ;
4132+
4133+ let balance = self . get_fee_token_balance ( fee_token, fee_payer) . await ?;
4134+ if balance < required {
4135+ return Err ( InvalidTransactionError :: TempoInsufficientFeeTokenBalance {
4136+ balance,
4137+ required,
4138+ }
4139+ . into ( ) ) ;
4140+ }
4141+ }
4142+
4143+ Ok ( self . validate_pool_transaction_for ( tx, & account, & evm_env) ?)
40534144 }
40544145
40554146 fn validate_pool_transaction_for (
0 commit comments