Skip to content

Fix Gateway balance parsing: select by domain instead of balances[0]#51

Merged
Abiorh001 merged 1 commit intoomnuron:mainfrom
ahmadghoniem:codex/fix-gateway-balance-domain
Apr 24, 2026
Merged

Fix Gateway balance parsing: select by domain instead of balances[0]#51
Abiorh001 merged 1 commit intoomnuron:mainfrom
ahmadghoniem:codex/fix-gateway-balance-domain

Conversation

@ahmadghoniem
Copy link
Copy Markdown
Contributor

@ahmadghoniem ahmadghoniem commented Apr 24, 2026

Summary

Fixes a real balance-parsing bug in NanopaymentClient.check_balance that can report 0 even when the funded domain has balance.

Problem

Circle POST /v1/balances returns an array across multiple domains. The previous logic effectively relied on the first row (balances[0]).

That is unsafe because row order is not guaranteed to match the requested network domain.

Observed case from live testing:

  • domain: 0 first row had balance: "0"
  • funded row was domain: 26 with non-zero balance

User-visible impact

This can cause false negatives in readiness and spending checks:

  • /api/v1/x402/inspect may return buyer_ready: false with "Gateway balance is below the required amount"
  • /api/v1/balance-detail can show gateway_balance: 0.00 while on-chain balance is non-zero
  • preflight checks may raise insufficient-balance errors unnecessarily

Fix

In check_balance:

  1. Map the requested CAIP-2 network to expected Circle domain.
  2. Filter rows by depositor.
  3. Prefer exact expected-domain match.
  4. Fallback to depositor match, then final fallback to first row.

This preserves backward compatibility while removing the incorrect index-0 assumption.

Why this is correct

The selected balance now aligns with the network being checked, which is the value downstream logic uses for inspect/preflight decisions.

@ahmadghoniem
Copy link
Copy Markdown
Contributor Author

Additional evidence and reproduction context from field testing:

  • Symptom:

    • Circle /v1/balances contained non-zero funds for the active domain, but OmniClaw could still report zero spendable balance.
    • This propagated to inspect/readiness responses (buyer_ready: false, low gateway balance reason).
  • Root cause:

    • Parsing logic previously depended on first row semantics in a multi-domain response.
    • Circle balance payload contains multiple domain rows and ordering is not a domain guarantee.
  • Concrete effect chain:

    • NanopaymentClient.check_balance(...) selected wrong row
    • OmniClaw.get_gateway_balance(...) returned wrong available amount
    • /api/v1/x402/inspect and /api/v1/balance-detail reflected false zero in some cases
  • Why this patch is targeted:

    • No behavior changes outside balance row selection
    • Selection is now deterministic and network-aligned
    • Existing fallback behavior remains when exact match is unavailable

@ahmadghoniem
Copy link
Copy Markdown
Contributor Author

Repro checklist (local)

  1. Configure OmniClaw to a network where your funded balance is not guaranteed to appear in the first /v1/balances row (for example Arc testnet domain 26).
  2. Fund Gateway for the same EOA.
  3. Query Circle balances directly:
    • POST https://gateway-api-testnet.circle.com/v1/balances
    • Body:
      {
        "token": "USDC",
        "sources": [
          { "network": "eip155:5042002", "depositor": "<EOA>" }
        ]
      }
    • Verify response has multiple balances[] rows, and non-zero balance is not in first row.
  4. Query OmniClaw inspect/balance endpoints:
    • POST /api/v1/x402/inspect
    • GET /api/v1/balance-detail
  5. Before this patch:
    • may show buyer_ready: false due to low gateway balance
    • may show gateway_balance: 0.00 despite funded domain row being non-zero
  6. After this patch:
    • inspect/readiness uses the row matching requested network domain
    • gateway_balance aligns with the funded domain value

Optional cross-check:

  • Compare against on-chain availableBalance(...) to distinguish indexing delay vs parsing bug.

1 similar comment
@ahmadghoniem
Copy link
Copy Markdown
Contributor Author

Repro checklist (local)

  1. Configure OmniClaw to a network where your funded balance is not guaranteed to appear in the first /v1/balances row (for example Arc testnet domain 26).
  2. Fund Gateway for the same EOA.
  3. Query Circle balances directly:
    • POST https://gateway-api-testnet.circle.com/v1/balances
    • Body:
      {
        "token": "USDC",
        "sources": [
          { "network": "eip155:5042002", "depositor": "<EOA>" }
        ]
      }
    • Verify response has multiple balances[] rows, and non-zero balance is not in first row.
  4. Query OmniClaw inspect/balance endpoints:
    • POST /api/v1/x402/inspect
    • GET /api/v1/balance-detail
  5. Before this patch:
    • may show buyer_ready: false due to low gateway balance
    • may show gateway_balance: 0.00 despite funded domain row being non-zero
  6. After this patch:
    • inspect/readiness uses the row matching requested network domain
    • gateway_balance aligns with the funded domain value

Optional cross-check:

  • Compare against on-chain availableBalance(...) to distinguish indexing delay vs parsing bug.

@Abiorh001 Abiorh001 self-requested a review April 24, 2026 20:43
@Abiorh001 Abiorh001 merged commit b0e344f into omnuron:main Apr 24, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants