Skip to content

Commit 1f908c5

Browse files
authored
Merge pull request #49 from ahmadghoniem/fix/x402-onchain-gateway-fallback
x402 inspect: fallback to on-chain gateway balance when API balance is stale
2 parents 3227a0b + 6723a8c commit 1f908c5

2 files changed

Lines changed: 74 additions & 5 deletions

File tree

src/omniclaw/agent/routes.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,22 @@ async def _choose_x402_route(
9292
gateway_available_balance = balance.formatted_available
9393
required_atomic = int(selected_gateway_kind.amount_atomic)
9494
gateway_ready = balance.available >= required_atomic
95-
gateway_reason = (
96-
"Gateway balance is sufficient for GatewayWalletBatched"
97-
if gateway_ready
98-
else "Gateway balance is below the required amount"
99-
)
95+
if gateway_ready:
96+
gateway_reason = "Gateway balance is sufficient for GatewayWalletBatched"
97+
else:
98+
# Fallback to direct on-chain balance when API-reported balance is stale/lagging.
99+
try:
100+
onchain_balance = await client.get_gateway_onchain_balance(wallet_id)
101+
if onchain_balance.available >= required_atomic:
102+
gateway_available_balance = onchain_balance.formatted_available
103+
gateway_ready = True
104+
gateway_reason = (
105+
"Gateway on-chain balance is sufficient (API balance appears stale)"
106+
)
107+
else:
108+
gateway_reason = "Gateway balance is below the required amount"
109+
except Exception:
110+
gateway_reason = "Gateway balance is below the required amount"
100111
except Exception as exc:
101112
gateway_ready = False
102113
gateway_reason = f"Gateway balance check failed: {exc}"

tests/test_x402_sdk_adapter.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,12 @@ async def test_choose_x402_route_prefers_exact_when_gateway_is_unfunded():
346346
formatted_available="0.00",
347347
)
348348
),
349+
get_gateway_onchain_balance=AsyncMock(
350+
return_value=SimpleNamespace(
351+
available=0,
352+
formatted_available="0.00",
353+
)
354+
),
349355
)
350356
x402_adapter = SimpleNamespace(
351357
_resolve_agent_network=lambda wallet_id, destination_chain: "eip155:84532"
@@ -385,6 +391,12 @@ async def test_choose_x402_route_keeps_gateway_when_it_is_the_only_option():
385391
formatted_available="0.00",
386392
)
387393
),
394+
get_gateway_onchain_balance=AsyncMock(
395+
return_value=SimpleNamespace(
396+
available=0,
397+
formatted_available="0.00",
398+
)
399+
),
388400
)
389401
x402_adapter = SimpleNamespace(
390402
_resolve_agent_network=lambda wallet_id, destination_chain: "eip155:84532"
@@ -403,6 +415,52 @@ async def test_choose_x402_route_keeps_gateway_when_it_is_the_only_option():
403415
assert route["gateway_ready"] is False
404416

405417

418+
@pytest.mark.asyncio
419+
async def test_choose_x402_route_uses_onchain_fallback_when_api_balance_is_stale():
420+
gateway_kind = SimpleNamespace(
421+
amount_atomic=250000,
422+
is_gateway_batched=True,
423+
get_amount_usdc=lambda: Decimal("0.25"),
424+
)
425+
requirements = SimpleNamespace(
426+
select_preferred_kind=lambda *, prefer_gateway, source_network: (
427+
gateway_kind if prefer_gateway else None
428+
)
429+
)
430+
client = SimpleNamespace(
431+
_nano_adapter=object(),
432+
get_gateway_balance=AsyncMock(
433+
return_value=SimpleNamespace(
434+
available=0,
435+
formatted_available="0.00",
436+
)
437+
),
438+
get_gateway_onchain_balance=AsyncMock(
439+
return_value=SimpleNamespace(
440+
available=300000,
441+
formatted_available="0.30",
442+
)
443+
),
444+
)
445+
x402_adapter = SimpleNamespace(
446+
_resolve_agent_network=lambda wallet_id, destination_chain: "eip155:84532"
447+
)
448+
449+
route = await _choose_x402_route(
450+
client=client,
451+
wallet_id="buyer-wallet",
452+
x402_adapter=x402_adapter,
453+
requirements=requirements,
454+
)
455+
456+
assert route["selected_route"] == "nanopayment"
457+
assert route["payment_source"] == "gateway_balance"
458+
assert route["selected_kind"] is gateway_kind
459+
assert route["gateway_ready"] is True
460+
assert route["gateway_available_balance"] == "0.30"
461+
assert route["gateway_reason"] == "Gateway on-chain balance is sufficient (API balance appears stale)"
462+
463+
406464
@pytest.mark.asyncio
407465
async def test_pay_route_inspects_url_even_when_amount_is_supplied(
408466
monkeypatch: pytest.MonkeyPatch,

0 commit comments

Comments
 (0)