@@ -284,6 +284,19 @@ def _nanopayment_network(self) -> str:
284284 )
285285 return network
286286
287+ @staticmethod
288+ def _route_value (route : Any ) -> str :
289+ return str (route .value if hasattr (route , "value" ) else route or "" ).strip ().lower ()
290+
291+ @classmethod
292+ def _route_uses_gateway_balance (cls , detected_route : Any , preferred_url_route : Any ) -> bool :
293+ preferred_route = cls ._route_value (preferred_url_route )
294+ if preferred_route in {"nanopayment" , "gateway" , "circle_gateway" }:
295+ return True
296+ if preferred_route == PaymentMethod .X402 .value :
297+ return False
298+ return cls ._route_value (detected_route ) == PaymentMethod .NANOPAYMENT .value
299+
287300 def _init_nanopayments (self ) -> None :
288301 """Initialize nanopayments components (direct private key only)."""
289302 if not self ._config .nanopayments_enabled :
@@ -975,9 +988,9 @@ async def pay(
975988 guards_passed : list [str ] = []
976989
977990 # Detect payment route early to know which balance to check
991+ source_network : Network | None = None
978992 try :
979993 # Try to get source network from Circle wallet first, then fall back to config default.
980- source_network = None
981994 try :
982995 wallet_info = self ._wallet_service .get_wallet (wallet_id )
983996 source_network = Network .from_string (wallet_info .blockchain )
@@ -1086,16 +1099,17 @@ async def pay(
10861099 if consume_intent_id :
10871100 await self ._reservation .release (consume_intent_id )
10881101
1089- # Get appropriate balance based on payment route
1090- # For X402/nanopayment routes → check Gateway balance
1091- # For Transfer/crosschain routes → check Circle wallet balance
1092- circle_balance = self ._wallet_service .get_usdc_balance_amount (wallet_id )
1102+ # Get appropriate balance based on payment route.
1103+ # Gateway nanopayments check Gateway balance. Direct x402 exact is
1104+ # delegated to the x402 adapter. Transfer/crosschain routes use Circle balance.
10931105 reserved_total = await self ._reservation .get_reserved_total (wallet_id )
1106+ preferred_url_route = kwargs .get ("preferred_url_route" )
1107+ required_amount = amount_decimal
10941108
1095- # Check if this is a Gateway-based route
1096- route_uses_gateway = detected_route in ( PaymentMethod . X402 , PaymentMethod . NANOPAYMENT )
1097-
1098- if route_uses_gateway and self . _nano_adapter :
1109+ if (
1110+ self . _route_uses_gateway_balance ( detected_route , preferred_url_route )
1111+ and self . _nano_adapter
1112+ ) :
10991113 try :
11001114 gateway_balance = await self .get_gateway_balance (wallet_id )
11011115 available = Decimal (str (gateway_balance .available_decimal ))
@@ -1106,20 +1120,24 @@ async def pay(
11061120 self ._logger .warning (f"Gateway balance check failed: { e } " )
11071121 available = Decimal ("0" )
11081122 balance_source = "Gateway available: (check failed)"
1123+ elif self ._route_value (detected_route ) == PaymentMethod .X402 .value :
1124+ available = amount_decimal
1125+ balance_source = "Direct x402: deferred to x402 adapter"
11091126 else :
1127+ circle_balance = self ._wallet_service .get_usdc_balance_amount (wallet_id )
11101128 available = circle_balance - reserved_total
11111129 balance_source = f"Circle: { available } "
11121130 if lock_lost_event .is_set ():
11131131 raise PaymentError ("Wallet lock lease was lost before execution could start." )
1114- if amount_decimal > available :
1115- error_msg = f"Insufficient available balance ({ balance_source } , Reserved: { reserved_total } , Required: { amount_decimal } )"
1132+ if required_amount > available :
1133+ error_msg = f"Insufficient available balance ({ balance_source } , Reserved: { reserved_total } , Required: { required_amount } )"
11161134 if guards_chain and reservation_tokens :
11171135 await guards_chain .release (reservation_tokens )
11181136 await self ._ledger .update_status (
11191137 ledger_entry .id , LedgerEntryStatus .FAILED , metadata_updates = {"error" : error_msg }
11201138 )
11211139 raise InsufficientBalanceError (
1122- error_msg , current_balance = available , required_amount = amount_decimal
1140+ error_msg , current_balance = available , required_amount = required_amount
11231141 )
11241142
11251143 # Resilience Shell
@@ -1359,15 +1377,18 @@ async def simulate(
13591377 amount_decimal = Decimal (str (amount ))
13601378
13611379 # Detect the actual route early so early-return reasons include it
1380+ source_network : Network | None = None
13621381 try :
1382+ source_network = Network .from_string (
1383+ self ._wallet_service .get_wallet (wallet_id ).blockchain
1384+ )
13631385 detected_route = (
13641386 self ._router .detect_method (
13651387 recipient ,
1366- source_network = Network .from_string (
1367- self ._wallet_service .get_wallet (wallet_id ).blockchain
1368- ),
1388+ source_network = source_network ,
13691389 destination_chain = kwargs .get ("destination_chain" ),
13701390 amount = amount_decimal ,
1391+ preferred_url_route = kwargs .get ("preferred_url_route" ),
13711392 )
13721393 or PaymentMethod .TRANSFER
13731394 )
@@ -1384,15 +1405,17 @@ async def simulate(
13841405 ),
13851406 )
13861407
1387- # Get appropriate balance based on payment route
1388- # For X402/nanopayment routes → check Gateway balance
1389- # For Transfer/crosschain routes → check Circle wallet balance
1390- circle_balance = self ._wallet_service .get_usdc_balance_amount (wallet_id )
1408+ # Get appropriate balance based on payment route.
1409+ # Gateway nanopayments check Gateway balance. Direct x402 exact is delegated
1410+ # to the x402 adapter. Transfer/crosschain routes use Circle balance.
13911411 reserved_total = await self ._reservation .get_reserved_total (wallet_id )
1412+ preferred_url_route = kwargs .get ("preferred_url_route" )
1413+ required_amount = amount_decimal
13921414
1393- route_uses_gateway = detected_route in (PaymentMethod .X402 , PaymentMethod .NANOPAYMENT )
1394-
1395- if route_uses_gateway and self ._nano_adapter :
1415+ if (
1416+ self ._route_uses_gateway_balance (detected_route , preferred_url_route )
1417+ and self ._nano_adapter
1418+ ):
13961419 try :
13971420 # Direct private key mode - use ON-CHAIN query
13981421 from omniclaw .protocols .nanopayments .wallet import GatewayWalletManager
@@ -1412,15 +1435,19 @@ async def simulate(
14121435 self ._logger .warning (f"Gateway balance check failed: { e } " )
14131436 available = 0
14141437 balance_source = "Gateway: (check failed)"
1438+ elif self ._route_value (detected_route ) == PaymentMethod .X402 .value :
1439+ available = amount_decimal
1440+ balance_source = "Direct x402: deferred to x402 adapter"
14151441 else :
1442+ circle_balance = self ._wallet_service .get_usdc_balance_amount (wallet_id )
14161443 available = circle_balance - reserved_total
14171444 balance_source = f"Circle: { available } "
14181445
1419- if amount_decimal > available :
1446+ if required_amount > available :
14201447 return SimulationResult (
14211448 would_succeed = False ,
14221449 route = detected_route ,
1423- reason = f"Insufficient available balance ({ balance_source } , Reserved: { reserved_total } , Required: { amount_decimal } )" ,
1450+ reason = f"Insufficient available balance ({ balance_source } , Reserved: { reserved_total } , Required: { required_amount } )" ,
14241451 )
14251452
14261453 # Check guards first
0 commit comments