feat(x402): add SKALE network settings to X402Settings#528
feat(x402): add SKALE network settings to X402Settings#528Subhajitdas99 wants to merge 3 commits into
Conversation
📝 WalkthroughWalkthroughAdds SKALE payment support to X402Settings: import updates, a supported-networks constant, five SKALE config fields, RPC URL entries for two SKALE networks, field validators for URL/network/token/name, and a post-model check ensuring RPC mappings exist. ChangesSKALE Payment Configuration
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
bindu/settings.py (1)
384-388: ⚡ Quick winAvoid duplicated network source of truth in validator.
supported_networksis hardcoded here and also represented inrpc_urls_by_network. This can drift and reject a valid mapped network later. Prefer deriving the allowed set from a single constant/shared source.♻️ Suggested refactor
class X402Settings(BaseSettings): @@ + SKALE_SUPPORTED_NETWORKS: frozenset[str] = frozenset( + {"eip155:2046399126", "skale-europa"} + ) @@ def validate_skale_network(cls, value: str) -> str: """Validate the SKALE network identifier against supported mappings.""" - supported_networks = {"eip155:2046399126", "skale-europa"} - if value not in supported_networks: + if value not in cls.SKALE_SUPPORTED_NETWORKS: raise ValueError( "skale_network must be one of: eip155:2046399126, skale-europa" )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@bindu/settings.py` around lines 384 - 388, The validator builds a local supported_networks set independently of rpc_urls_by_network which duplicates the source of truth and can drift; replace the hardcoded supported_networks check in the skale_network validation with a derived set from the keys of rpc_urls_by_network (or the shared constant that defines RPC mappings) so the validator uses the single source of truth (e.g., derive allowed = set(rpc_urls_by_network.keys()) and validate value against that), update the error message to list allowed = sorted(allowed) if you still want a message, and ensure any references to supported_networks are removed so only rpc_urls_by_network (or the shared constant) drives allowed networks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@bindu/settings.py`:
- Around line 384-388: The validator builds a local supported_networks set
independently of rpc_urls_by_network which duplicates the source of truth and
can drift; replace the hardcoded supported_networks check in the skale_network
validation with a derived set from the keys of rpc_urls_by_network (or the
shared constant that defines RPC mappings) so the validator uses the single
source of truth (e.g., derive allowed = set(rpc_urls_by_network.keys()) and
validate value against that), update the error message to list allowed =
sorted(allowed) if you still want a message, and ensure any references to
supported_networks are removed so only rpc_urls_by_network (or the shared
constant) drives allowed networks.
|
Addressed the nitpick by moving the supported SKALE network set to a shared class-level constant so the validator no longer keeps its own local copy. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
bindu/settings.py (1)
287-373: ⚡ Quick winKeep SKALE network allowlist and RPC mapping synchronized at load time.
Line 287 and Line 344 currently define SKALE network truth in two places. A small model-level consistency check would preserve the PR’s fail-fast goal if one side changes later.
♻️ Proposed guard to prevent config drift
from pydantic import Field, computed_field, BaseModel, HttpUrl, field_validator +from pydantic import model_validator @@ class X402Settings(BaseSettings): @@ rpc_urls_by_network: dict[str, list[str]] = { @@ } + + `@model_validator`(mode="after") + def validate_skale_network_mappings(self) -> "X402Settings": + missing = [ + network + for network in self.SKALE_SUPPORTED_NETWORKS + if network not in self.rpc_urls_by_network + or not self.rpc_urls_by_network[network] + ] + if missing: + raise ValueError( + f"Missing RPC URL mappings for SKALE networks: {', '.join(sorted(missing))}" + ) + return self🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@bindu/settings.py` around lines 287 - 373, Add a model-level validation on the Settings class that asserts SKALE_SUPPORTED_NETWORKS and the keys in rpc_urls_by_network remain in sync: check that every string in SKALE_SUPPORTED_NETWORKS is present as a key in rpc_urls_by_network and optionally that any rpc_urls_by_network key that represents a SKALE network appears in SKALE_SUPPORTED_NETWORKS; raise a clear ValueError if the sets differ. Implement this as a Pydantic class-level validator (root_validator/model_validator depending on your Pydantic version) inside the same class that defines SKALE_SUPPORTED_NETWORKS, rpc_urls_by_network, and model_config so the app fails fast at load time when the two definitions drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@bindu/settings.py`:
- Around line 287-373: Add a model-level validation on the Settings class that
asserts SKALE_SUPPORTED_NETWORKS and the keys in rpc_urls_by_network remain in
sync: check that every string in SKALE_SUPPORTED_NETWORKS is present as a key in
rpc_urls_by_network and optionally that any rpc_urls_by_network key that
represents a SKALE network appears in SKALE_SUPPORTED_NETWORKS; raise a clear
ValueError if the sets differ. Implement this as a Pydantic class-level
validator (root_validator/model_validator depending on your Pydantic version)
inside the same class that defines SKALE_SUPPORTED_NETWORKS,
rpc_urls_by_network, and model_config so the app fails fast at load time when
the two definitions drift.
|
The latest follow-up nitpicks have been addressed. I’m checking the remaining |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@bindu/settings.py`:
- Around line 391-401: In validate_skale_network, remove the unreachable regex
branch that checks re.fullmatch(...) (the inner if block) and instead make the
membership error message dynamic by deriving the allowed values string from
cls.SKALE_SUPPORTED_NETWORKS (e.g., join the set into a comma-separated string)
so the ValueError in validate_skale_network references the current
SKALE_SUPPORTED_NETWORKS; update the raise ValueError call in
validate_skale_network accordingly and keep the initial membership check as the
sole validation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
| @field_validator("skale_network") | ||
| @classmethod | ||
| def validate_skale_network(cls, value: str) -> str: | ||
| """Validate the SKALE network identifier against supported mappings.""" | ||
| if value not in cls.SKALE_SUPPORTED_NETWORKS: | ||
| raise ValueError( | ||
| "skale_network must be one of: eip155:2046399126, skale-europa" | ||
| ) | ||
| if value.startswith("eip155:") and not re.fullmatch(r"eip155:\d+", value): | ||
| raise ValueError("skale_network must match the format 'eip155:<numeric>'") | ||
| return value |
There was a problem hiding this comment.
validate_skale_network: unreachable regex branch + stale-on-extension error message
Two issues in this validator:
-
Lines 399–400 are dead code. The first guard (
value not in cls.SKALE_SUPPORTED_NETWORKS) already guarantees that any value reaching the secondifis either"eip155:2046399126"or"skale-europa"."eip155:2046399126"trivially satisfiesre.fullmatch(r"eip155:\d+", ...), so the innerValueErrorcan never be raised. Remove the block entirely. -
Hardcoded error message at line 397 will silently go stale the moment a new network is added to
SKALE_SUPPORTED_NETWORKS. Derive the string from the constant.
🐛 Proposed fix
`@field_validator`("skale_network")
`@classmethod`
def validate_skale_network(cls, value: str) -> str:
"""Validate the SKALE network identifier against supported mappings."""
if value not in cls.SKALE_SUPPORTED_NETWORKS:
raise ValueError(
- "skale_network must be one of: eip155:2046399126, skale-europa"
+ f"skale_network must be one of: {', '.join(sorted(cls.SKALE_SUPPORTED_NETWORKS))}"
)
- if value.startswith("eip155:") and not re.fullmatch(r"eip155:\d+", value):
- raise ValueError("skale_network must match the format 'eip155:<numeric>'")
return value🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@bindu/settings.py` around lines 391 - 401, In validate_skale_network, remove
the unreachable regex branch that checks re.fullmatch(...) (the inner if block)
and instead make the membership error message dynamic by deriving the allowed
values string from cls.SKALE_SUPPORTED_NETWORKS (e.g., join the set into a
comma-separated string) so the ValueError in validate_skale_network references
the current SKALE_SUPPORTED_NETWORKS; update the raise ValueError call in
validate_skale_network accordingly and keep the initial membership check as the
sole validation.
|
The remaining CI failure was coming from runtime test typing issues on updated |
|
Superseded by #534. Same goal achieved via extra_networks rather than hardcoded skale_* fields, so the pattern generalises to Polygon, Avalanche, etc. |
x402 v2 ships built-in pricing for Base mainnet and Base Sepolia only.
Reaching SKALE / Polygon / Avalanche / Ethereum mainnet etc. needs two
things to line up — a facilitator that supports the chain, and asset
metadata so the price parser maps `"0.01"` to the right ERC-20 contract.
This commit lands the second piece as an operator-extensible config
surface, and ships SKALE Europa as the worked example.
What's new
----------
* `bindu/settings.py::ExtraNetwork`
Pydantic model carrying CAIP-2 + asset address + EIP-712 domain bits
(`asset_name`, `asset_eip712_version`, `asset_decimals`). Field
validators reject malformed CAIP-2 / non-hex asset addresses at
settings-load time so misconfiguration fails early.
* `bindu/settings.py::X402Settings.extra_networks`
Dict keyed on friendly network slug ("skale-europa"). Default ships
one entry — SKALE Europa Hub bridged USDC — mirroring
facilitator.x402.fi's `/supported` metadata exactly. Operators add
more chains by extending the dict.
* `bindu/server/applications.py::_create_payment_requirements`
Plumbed the two ways `extra_networks` matters:
- The friendly → CAIP-2 normaliser now merges built-in mappings
(base, ethereum, …) with operator-supplied ones. Built-ins win
collisions, on purpose — operators can't accidentally re-route
`base-sepolia`.
- For each extra network, registers a money parser on
`ExactEvmServerScheme` via `register_money_parser`. The parser
claims requests for its specific CAIP-2 and returns `None` for
everything else, so the SDK's built-in Base parser still wins
for Base.
Why this shape (not the four open PRs that tried something else)
----------------------------------------------------------------
* GetBindu#486 documented SKALE as blocked by `x402.types.SupportedNetworks` —
a symbol that no longer exists after v2. The doc was a snapshot of a
problem v2 removed.
* GetBindu#507 added SKALE to `rpc_urls_by_network` — the dict the v2
migration removed because the facilitator owns RPC now.
* GetBindu#528 added hardcoded `skale_*` fields to `X402Settings`. It works
for one chain but doesn't generalise — and it re-introduced the dead
RPC dict. Plus settings alone don't help: `parse_price()` would still
raise on SKALE because the SDK has no built-in entry for chain
1187947933.
* GetBindu#496 wrapped a stubbed HTTP `GET /supported` health check around a
RAG agent and called it "SKALE integration". No payment actually
gated.
The right shape is operator-extensible networks + a money parser per
chain — same surface works for SKALE today and Polygon / Avalanche / any
EVM tomorrow with no code changes.
Live facilitator situation
--------------------------
Only one public facilitator advertises SKALE chains today:
`https://facilitator.x402.fi`. Coinbase's own facilitator at
`https://x402.org/facilitator` lists Base + non-EVM chains only.
`facilitator.x402.fi`'s cert is currently expired — documented in
`bugs/known-issues.md` as `skale-facilitator-cert-expired` (low/ops).
The shipped Bindu defaults still point at Coinbase; operators that want
SKALE set `X402__FACILITATOR_URL` themselves after reading the doc.
Tests
-----
* `tests/unit/server/middleware/x402/test_extra_networks.py` (11 tests)
Schema validation (CAIP-2 format, hex asset, decimal cap), money parser
registration, network-falls-through behavior, decimal-other-than-6
scaling (WETH 18-decimals case), independence of multiple parsers,
and confirmation that the shipped SKALE Europa default matches
facilitator.x402.fi's advertised asset.
* `tests/integration/x402/test_skale_facilitator_supported.py` (2 tests)
Live smoke against `facilitator.x402.fi/supported`. Opt-in
(`X402_NETWORK_TESTS=1`) so CI doesn't depend on a third-party with
an expired cert. Asserts (a) facilitator still advertises SKALE
Europa at the CAIP-2 we ship, (b) the asset metadata
(decimals/name/version) matches our `ExtraNetwork` default — if any
of those drift the test fires before agents start collecting
signature mismatches in prod.
* `pytest.ini`: new `network` marker for opt-in live-external tests.
Verified
--------
* 980 unit + integration tests pass (969 baseline + 11 new).
* ty: All checks passed (0 diagnostics).
* ruff clean.
* Pre-commit hooks all pass end-to-end (no --no-verify).
* Live smoke against facilitator.x402.fi confirms our shipped SKALE
Europa defaults match what the facilitator advertises.
Closes-out: GetBindu#486 (stale upstream-blocker note), GetBindu#507 (dead RPC dict
re-add), GetBindu#528 (hardcoded one-chain settings) — this PR is the
operator-extensible alternative all three were reaching toward.
Related: GetBindu#496 RAG router agent — the routing example is still useful,
but its SKALE story should be rewritten on top of this commit (drop
the stub health check, use the `X402Middleware` directly on the
premium endpoint).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Describe the problem and fix in 2–5 bullets:
X402Settingsdid not expose a clean, validated SKALE-specific configuration surface, and the earlier branch accumulated unrelated changes beyond the intended scope.bindu/settings.py, added validation for those fields, and added a secondary SKALE RPC endpoint so the existing fallback logic can actually fail over.Change Type (select all that apply)
Scope (select all touched areas)
Linked Issue/PR
User-Visible / Behavior Changes
Added new optional
X402Settingsfields:skale_facilitator_urlskale_networkskale_payment_tokenskale_payment_token_nameskale_default_amountAlso added SKALE RPC mappings for:
eip155:2046399126skale-europaInvalid SKALE settings values now fail at settings load instead of failing later at runtime.
Security Impact (required)
Yes/No) NoYes/No) NoYes/No) NoYes/No) NoYes/No) NoYes, explain risk + mitigation: NoneVerification
Environment
Steps to Test
mainand apply this branch.bindu/settings.pyloads with the new SKALE x402 fields present inX402Settings.rpc_urls_by_networkcontains both SKALE keys with fallback URLs.Expected Behavior
X402Settingsexposes the new SKALE configuration fields.Actual Behavior
bindu/settings.py.Evidence (attach at least one)
Human Verification (required)
What you personally verified (not just CI):
X402SettingsSKALE additions and validation logic inbindu/settings.py.Compatibility / Migration
Yes/No) YesYes/No) YesYes/No) NoX402Settingsfields.Failure Recovery (if this breaks)
bindu/settings.pyRisks and Mitigations
List only real risks for this PR. If none, write
None.Checklist
uv run pytest)uv run pre-commit run --all-files)Summary by CodeRabbit