From 627ec099c01c1256e2e40c7e3112e9a9e28ad3de Mon Sep 17 00:00:00 2001 From: Nas Kavian Date: Thu, 1 Oct 2026 16:02:24 -0700 Subject: [PATCH] feat: implement x402 seller workflows --- README.md | 121 + scripts/verify_distribution.py | 16 + src/inflowpay/x402/_seller.py | 172 + src/inflowpay/x402/facilitator.py | 146 + src/inflowpay/x402/seller.py | 256 ++ tests/fixtures/x402-seller.json | 5433 +++++++++++++++++++++++++++++ tests/test_x402_seller.py | 660 ++++ 7 files changed, 6804 insertions(+) create mode 100644 src/inflowpay/x402/_seller.py create mode 100644 src/inflowpay/x402/facilitator.py create mode 100644 src/inflowpay/x402/seller.py create mode 100644 tests/fixtures/x402-seller.json create mode 100644 tests/test_x402_seller.py diff --git a/README.md b/README.md index d5ef11c..f884b09 100644 --- a/README.md +++ b/README.md @@ -571,6 +571,127 @@ after the owner agrees: delegation persists even if the payment fails. The extension returns signed data; it does not broadcast a transaction. Availability depends on the InFlow environment's sponsorship endpoint and supported networks. +## Accept x402 payments with FastAPI + +Create an InFlow **Seller** account and an API key in its dashboard: +[Sandbox](https://sandbox.inflowpay.ai) for testing or +[Production](https://app.inflowpay.ai) for live payments. Use the matching +`environment` in `ClientOptions`. + +```shell +pip install 'inflowpay[x402,fastapi]' uvicorn +export INFLOW_API_KEY='your-seller-api-key' +``` + +`Seller` reads your configured wallets, assets, and payment methods and converts +prices into upstream x402 route options. `Facilitator` sends verification and +settlement requests to InFlow. The upstream FastAPI middleware challenges the +buyer, verifies the payment before calling your handler, and settles after a +successful handler response. + +```python +import asyncio +import os + +import uvicorn +from fastapi import FastAPI +from x402 import x402ResourceServer +from x402.http.middleware.fastapi import payment_middleware +from x402.http.types import RouteConfig + +from inflowpay import ClientOptions +from inflowpay.x402.facilitator import Facilitator +from inflowpay.x402.seller import Seller + + +async def serve() -> None: + options = ClientOptions(environment="sandbox", api_key=os.environ["INFLOW_API_KEY"]) + async with ( + await Seller.create(options) as seller, + await Facilitator.create(options) as facilitator, + ): + resource_server = x402ResourceServer(facilitator) + for registration in await seller.scheme_registrations(): + resource_server.register(registration["network"], registration["server"]) + + routes = { + "GET /report": RouteConfig(accepts=await seller.offers("$0.01")), + } + app = FastAPI() + app.middleware("http")(payment_middleware(routes, resource_server)) + + @app.get("/report") + async def report() -> dict[str, str]: + return {"report": "Your paid report"} + + await uvicorn.Server(uvicorn.Config(app, host="127.0.0.1", port=8000)).serve() + + +asyncio.run(serve()) +``` + +Both clients own their HTTP connections and close them when their context exits. +`Seller.create()` requires an API key and preloads configuration and capabilities. +Configuration reads are cached for one hour; `await seller.config(refresh=True)` +forces a refresh. `await seller.get_supported(refresh=True)` refreshes the Seller's +capability lookup, not a running Facilitator or resource server. Values returned +by these methods can be modified without changing the client's cache. + +### Prices, payment methods, and metering + +`seller.offers()` accepts `"$0.01"`, `"0.01 USDC"`, or `"0.01"` with +`currency="USDC"`. Dollar prices select all stablecoin currencies in your Seller +configuration. An explicit `currency` overrides the currency in the price string. +Conversion uses decimal digits rather than floating-point arithmetic and rejects +prices that cannot be represented in an asset's smallest unit. Price strings +allow up to eight fractional digits. + +Use `schemes=["balance"]` or `networks=["eip155:8453"]` to restrict offers. +Both filters apply when supplied together. Routes default to fixed-price offers +and a 300-second payment timeout; `max_timeout_seconds` changes the timeout. +An empty offers list means the selected configuration and filters produced no +payment option. Check it before exposing the route. + +Metered `upto` payments require `inflowpay[evm]` and explicit `schemes=["upto"]` +on both `seller.offers()` and `seller.scheme_registrations()`. Your environment +must advertise metered Permit2 support for the asset. The route price is the +maximum authorized charge. In your FastAPI handler, call the upstream helper +`set_settlement_overrides(response, {"amount": "250000"})` to settle the actual +amount in atomic asset units. The buyer's signed maximum remains unchanged. +Without an override, settlement uses the route's maximum amount. + +`await seller.route("0.01 USDC", schemes=["exact"], permit2=True)` selects +compatible Permit2 offers and adds a sponsorship declaration only when the token +and facilitator advertise support. It prefers EIP-2612 over InFlow EIP-7702. +Install `inflowpay[evm]` for EIP-2612 declarations. Balance offers are unaffected +by `permit2=True`; filter to `exact` for an on-chain-only route. InFlow-managed +buyers cannot sign Permit2 payments; these offers require external wallets. + +### Settlement and framework behavior + +The facilitator preserves a valid buyer payment identifier or derives one from +the signed payment material. Verification and settlement use the same identifier +without modifying the caller's payload. HTTP 412 `permit2_allowance_required` +is returned as an invalid verification result. Settlement retries only an HTTP +409 `idempotency_pending` response, at most five attempts and at most five seconds +between attempts. Other HTTP or transport failures are not retried automatically: +the payment may already have completed. Cancelling the task stops pending waits. + +The upstream FastAPI middleware buffers the handler's successful response before +settlement; it does not stream the response to the buyer during settlement. A +rejected verification prevents the handler from running. A handler error prevents +after-handler settlement; a settlement failure replaces the successful handler +response with a payment failure. Avoid irreversible business side effects in a +handler without your own reconciliation design. InFlow does not roll back the +handler's work. + +The clients are framework-independent and do not import FastAPI. Use them with +other upstream asynchronous adapters where appropriate. For anonymous external +on-chain facilitation, explicitly use +`await Facilitator.create(ClientOptions(environment="sandbox"), anonymous=True)`. +Anonymous setup rejects credentials; Seller configuration and InFlow balance +settlement require a Seller account. + ## x402 facilitator capabilities Facilitator capabilities describe the payment schemes, networks, and extensions diff --git a/scripts/verify_distribution.py b/scripts/verify_distribution.py index c103df0..51473e1 100644 --- a/scripts/verify_distribution.py +++ b/scripts/verify_distribution.py @@ -71,6 +71,8 @@ async def check_seller(): entry = payment_identifier_entry(declare_payment_identifier(), generate_payment_id()) assert entry is not None and entry['info']['required'] is False from inflowpay.x402.buyer import Buyer +from inflowpay.x402.facilitator import Facilitator +from inflowpay.x402.seller import Seller from inflowpay import ClientOptions import asyncio import httpx @@ -79,6 +81,20 @@ async def check_buyer(): async with await Buyer.create(ClientOptions(transport=transport)) as buyer: assert (await buyer.get_supported()).kinds == [] asyncio.run(check_buyer()) +async def check_seller(): + config = {'sellerId': 'seller', 'assets': [], 'wallets': [], + 'paymentMethods': [], 'supported': []} + transport = httpx.MockTransport(lambda request: httpx.Response(200, json= + config if request.url.path.endswith('/config') else {'kinds': []})) + options = ClientOptions(api_key='test-key', transport=transport) + async with await Seller.create(options) as seller: + assert await seller.offers('$1') == [] + assert await seller.scheme_registrations() == [] + transport = httpx.MockTransport(lambda request: httpx.Response(200, json={'kinds': []})) + options = ClientOptions(transport=transport) + async with await Facilitator.create(options, anonymous=True) as facilitator: + assert facilitator.get_supported().kinds == [] +asyncio.run(check_seller()) for name in ('mpp', 'mcp', 'web3', 'solana', 'fastapi', 'rfc8785'): assert util.find_spec(name) is None, name """ diff --git a/src/inflowpay/x402/_seller.py b/src/inflowpay/x402/_seller.py new file mode 100644 index 0000000..a541d8d --- /dev/null +++ b/src/inflowpay/x402/_seller.py @@ -0,0 +1,172 @@ +from __future__ import annotations + +import re +from collections.abc import Sequence +from copy import deepcopy +from typing import Any + +from pydantic import Field +from x402.http.types import PaymentOption +from x402.schemas import AssetAmount, SupportedKind +from x402.schemas.base import BaseX402Model + +PERMIT2_PROXY = "0x402085c248EeA27D92E8b30b2C58ed07f9E20001" + + +class Asset(BaseX402Model): + asset_id: str + asset_name: str + currency: str + blockchain: str + network: str + decimals: int = Field(ge=0) + asset_transfer_method: str | None = None + permit2_proxy: str | None = None + token_name: str | None = None + token_version: str | None = None + supports_eip2612: bool = False + supports_eip7702: bool = False + + +class Wallet(BaseX402Model): + address: str + blockchain: str + fee_payer: str | None = None + + +class PaymentMethod(BaseX402Model): + scheme: str + network: str + pay_to: str + decimals: int = Field(ge=0) + extra: dict[str, Any] = Field(default_factory=dict) + + +class SellerConfig(BaseX402Model): + seller_id: str + assets: list[Asset] + wallets: list[Wallet] + payment_methods: list[PaymentMethod] + supported: list[SupportedKind] + + +def supports_permit2(asset: Asset) -> bool: + return ( + asset.network.startswith("eip155:") + and (asset.permit2_proxy or "").lower() == PERMIT2_PROXY.lower() + ) + + +def upto_kind(config: SellerConfig, asset: Asset) -> SupportedKind | None: + if not asset.network.startswith("eip155:") or not asset.permit2_proxy: + return None + for kind in config.supported: + extra = kind.extra or {} + if ( + kind.x402_version == 2 + and kind.scheme == "upto" + and kind.network == asset.network + and extra.get("assetTransferMethod") == "permit2" + and isinstance(extra.get("facilitatorAddress"), str) + and extra["facilitatorAddress"] + and isinstance(extra.get("permit2Proxy"), str) + and extra["permit2Proxy"] + ): + return kind + return None + + +def _price(value: str, currency: str | None) -> tuple[str, str, str]: + matched = re.fullmatch(r"(\$?)([0-9]+)(?:\.([0-9]{1,8}))?(?:\s+([A-Z][A-Z0-9_]*))?", value) + if matched is None or (matched[1] and matched[4]): + raise ValueError("Price must be '$1.00', '1.00 USDC', or a plain amount with currency") + selected = currency if currency is not None else ("USD" if matched[1] else matched[4]) + if not selected: + raise ValueError("A currency is required for a plain amount") + return matched[2], matched[3] or "", selected + + +def _atomic(integer: str, fraction: str, decimals: int) -> str: + if fraction[decimals:].strip("0"): + raise ValueError("Price cannot be represented in the asset's decimal precision") + return (integer + fraction[:decimals].ljust(decimals, "0")).lstrip("0") or "0" + + +def build_offers( + config: SellerConfig, + price: str, + *, + currency: str | None = None, + schemes: Sequence[str] | None = None, + networks: Sequence[str] | None = None, + max_timeout_seconds: int = 300, + permit2: bool = False, +) -> list[PaymentOption]: + integer, fraction, selected = _price(price, currency) + + def include(scheme: str, network: str) -> bool: + return (schemes is None or scheme in schemes) and (networks is None or network in networks) + + result: list[PaymentOption] = [] + for wallet in config.wallets: + for asset in config.assets: + if asset.blockchain != wallet.blockchain or selected not in ("USD", asset.currency): + continue + methods: list[tuple[str, str | None, dict[str, Any]]] = [] + if not permit2 or supports_permit2(asset): + methods.append(("exact", "permit2" if permit2 else asset.asset_transfer_method, {})) + kind = upto_kind(config, asset) if schemes is not None and "upto" in schemes else None + if kind is not None: + methods.append(("upto", "permit2", kind.extra or {})) + for scheme, method, kind_extra in methods: + if not include(scheme, asset.network): + continue + extra: dict[str, Any] = {"assetName": asset.asset_name} + for key, value in ( + ("name", asset.token_name), + ("version", asset.token_version), + ("assetTransferMethod", method), + ("feePayer", wallet.fee_payer), + ): + if value is not None: + extra[key] = value + if method == "permit2": + if asset.permit2_proxy is not None: + extra["permit2Proxy"] = asset.permit2_proxy + if asset.supports_eip2612: + extra["supportsEip2612"] = True + if asset.supports_eip7702: + extra["supportsEip7702"] = True + result.append( + PaymentOption( + scheme=scheme, + network=asset.network, + pay_to=wallet.address, + price=AssetAmount( + asset=asset.asset_id, amount=_atomic(integer, fraction, asset.decimals) + ), + max_timeout_seconds=max_timeout_seconds, + extra={**extra, **deepcopy(kind_extra)}, + ) + ) + currencies = ( + list(dict.fromkeys(asset.currency for asset in config.assets)) + if selected == "USD" + else [selected] + ) + for method_info in config.payment_methods: + if include(method_info.scheme, method_info.network): + for item in currencies: + result.append( + PaymentOption( + scheme=method_info.scheme, + network=method_info.network, + pay_to=method_info.pay_to, + price=AssetAmount( + asset=item, amount=_atomic(integer, fraction, method_info.decimals) + ), + max_timeout_seconds=max_timeout_seconds, + extra={**deepcopy(method_info.extra), "assetName": item}, + ) + ) + return result diff --git a/src/inflowpay/x402/facilitator.py b/src/inflowpay/x402/facilitator.py new file mode 100644 index 0000000..4020fc7 --- /dev/null +++ b/src/inflowpay/x402/facilitator.py @@ -0,0 +1,146 @@ +from __future__ import annotations + +import asyncio +import hashlib +import json +import re +from types import TracebackType +from typing import Self + +from x402.schemas import ( + PaymentPayload, + PaymentPayloadV1, + PaymentRequirements, + PaymentRequirementsV1, + SettleResponse, + SupportedResponse, + VerifyResponse, +) + +from .._runtime import Client +from ..errors import InflowApiError +from ..options import ClientOptions +from ._core import ( + PAYMENT_IDENTIFIER, + declare_payment_identifier, + read_payment_identifier, + validate_payment_id, +) + + +def _request( + payload: PaymentPayload | PaymentPayloadV1, + requirements: PaymentRequirements | PaymentRequirementsV1, +) -> dict[str, object]: + if not isinstance(payload, PaymentPayload) or payload.x402_version != 2: + raise ValueError("InFlow facilitator requires x402 version 2") + if not isinstance(requirements, PaymentRequirements): + raise ValueError("InFlow facilitator requires version 2 requirements") + payment = payload.model_copy(deep=True) + entry = read_payment_identifier((payment.extensions or {}).get(PAYMENT_IDENTIFIER)) + if entry is None or not validate_payment_id(entry["info"].get("id")): + material = "payload:" + json.dumps( + payment.payload, separators=(",", ":"), ensure_ascii=False + ) + for key in ("transactionId", "transaction", "signature"): + value = payment.payload.get(key) + if isinstance(value, str) and value: + material = key + ":" + value + break + entry = declare_payment_identifier() + entry["info"]["id"] = "pay_" + hashlib.sha256(material.encode()).hexdigest()[:32] + payment.extensions = {**(payment.extensions or {}), PAYMENT_IDENTIFIER: entry} + return { + "x402Version": 2, + "paymentPayload": payment.model_dump(by_alias=True, exclude_none=True), + "paymentRequirements": requirements.model_dump(by_alias=True, exclude_none=True), + } + + +class Facilitator: + def __init__(self, client: Client, supported: SupportedResponse) -> None: + self._client = client + self._supported = supported.model_copy(deep=True) + + @classmethod + async def create(cls, options: ClientOptions, *, anonymous: bool = False) -> Self: + if anonymous: + if options.api_key is not None or options.access_token is not None: + raise ValueError("Anonymous facilitator must not receive credentials") + elif options.api_key is None: + raise ValueError("Facilitator setup requires a Seller API key or anonymous=True") + client = Client(options) + try: + supported = SupportedResponse.model_validate( + await client.request("GET", "/v1/x402/supported") + ) + return cls(client, supported) + except BaseException: + await client.aclose() + raise + + async def __aenter__(self) -> Self: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + traceback: TracebackType | None, + ) -> None: + await self.aclose() + + async def aclose(self) -> None: + await self._client.aclose() + + def get_supported(self) -> SupportedResponse: + # The upstream interface is synchronous. Read capabilities loaded during async + # setup; network refresh requires a new adapter and resource server. + return self._supported.model_copy(deep=True) + + async def verify( + self, + payload: PaymentPayload | PaymentPayloadV1, + requirements: PaymentRequirements | PaymentRequirementsV1, + ) -> VerifyResponse: + try: + response = await self._client.request( + "POST", "/v1/x402/verify", body=_request(payload, requirements) + ) + except InflowApiError as error: + body = error.body + if not ( + error.http_status == 412 + and isinstance(body, dict) + and body.get("isValid") is False + and body.get("invalidReason") == "permit2_allowance_required" + ): + raise + response = body + return VerifyResponse.model_validate(response) + + async def settle( + self, + payload: PaymentPayload | PaymentPayloadV1, + requirements: PaymentRequirements | PaymentRequirementsV1, + ) -> SettleResponse: + body = _request(payload, requirements) + attempt = 1 + while True: + try: + response = await self._client.request("POST", "/v1/x402/settle", body=body) + return SettleResponse.model_validate(response) + except InflowApiError as error: + if not ( + error.http_status == 409 + and isinstance(error.body, dict) + and error.body.get("errorReason") == "idempotency_pending" + and attempt < 5 + ): + raise + # Only an explicit pending response permits resubmission. Reuse the + # same request and identifier; a transport failure may follow payment. + value = error.headers.get("retry-after", "") + delay = min(float(value), 5) if re.fullmatch(r"[0-9]+", value) else 5 + await asyncio.sleep(delay) + attempt += 1 diff --git a/src/inflowpay/x402/seller.py b/src/inflowpay/x402/seller.py new file mode 100644 index 0000000..29c41c7 --- /dev/null +++ b/src/inflowpay/x402/seller.py @@ -0,0 +1,256 @@ +from __future__ import annotations + +import asyncio +from collections.abc import Callable, Mapping, Sequence +from copy import deepcopy +from types import TracebackType +from typing import Generic, Self, TypedDict, TypeVar, cast + +from pydantic import BaseModel +from x402.http.types import PaymentOption, RouteConfig +from x402.interfaces import PaymentFlowConfig, SchemeNetworkServer +from x402.schemas import AssetAmount, PaymentRequirements, Price, SupportedKind, SupportedResponse + +from .._runtime import Client +from ..options import ClientOptions +from ._core import INFLOW_EIP7702_GAS_SPONSORING, declare_sponsorship +from ._seller import PERMIT2_PROXY, SellerConfig, build_offers, supports_permit2, upto_kind + +__all__ = ["SchemeRegistration", "Seller", "SellerConfig"] + +_T = TypeVar("_T", bound=BaseModel) + + +class _Cache(Generic[_T]): + def __init__(self, client: Client, path: str, model: type[_T]) -> None: + self.client, self.path, self.model = client, path, model + self.value: _T | None = None + self.expires = 0.0 + self.task: asyncio.Task[None] | None = None + + async def get(self, refresh: bool = False) -> _T: + if refresh or self.value is None or asyncio.get_running_loop().time() >= self.expires: + if self.task is None: + self.task = asyncio.create_task(self._fetch()) + self.task.add_done_callback(self._finish) + await asyncio.shield(self.task) + assert self.value is not None + return self.value.model_copy(deep=True) + + async def _fetch(self) -> None: + value = self.model.model_validate(await self.client.request("GET", self.path)) + self.value = value + self.expires = asyncio.get_running_loop().time() + 3600 + + def _finish(self, task: asyncio.Task[None]) -> None: + if not task.cancelled(): + task.exception() + self.task = None + + async def close(self) -> None: + if self.task is not None: + self.task.cancel() + await asyncio.gather(self.task, return_exceptions=True) + + +class SchemeRegistration(TypedDict): + network: str + server: SchemeNetworkServer + + +class _Passthrough: + def __init__(self, scheme: str, methods: list[str]) -> None: + self.scheme = scheme + self.default_asset_transfer_method = methods[0] + self.payment_flows: Mapping[str, PaymentFlowConfig] = { + method: {"supported": ("authorization",), "default": "authorization"} + for method in methods + } + + def parse_price(self, price: Price, network: str) -> AssetAmount: + if not isinstance(price, AssetAmount): + raise ValueError("Use Seller.offers to provide a price in atomic asset units") + return price.model_copy(deep=True) + + def enhance_payment_requirements( + self, + requirements: PaymentRequirements, + supported_kind: SupportedKind, + extension_keys: list[str], + ) -> PaymentRequirements: + return requirements.model_copy(deep=True) + + +class Seller: + def __init__(self, client: Client) -> None: + self._client = client + self._config = _Cache(client, "/v1/x402/config", SellerConfig) + self._supported = _Cache(client, "/v1/x402/supported", SupportedResponse) + self._closed = False + + @classmethod + async def create(cls, options: ClientOptions) -> Self: + if options.api_key is None: + raise ValueError("Seller setup requires an InFlow Seller API key") + seller = cls(Client(options)) + try: + await asyncio.gather(seller.config(), seller.get_supported()) + return seller + except BaseException: + await seller.aclose() + raise + + async def __aenter__(self) -> Self: + self._check_open() + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + traceback: TracebackType | None, + ) -> None: + await self.aclose() + + async def aclose(self) -> None: + self._closed = True + await asyncio.gather(self._config.close(), self._supported.close()) + await self._client.aclose() + + def _check_open(self) -> None: + if self._closed: + raise RuntimeError("x402 Seller is closed") + + async def config(self, *, refresh: bool = False) -> SellerConfig: + self._check_open() + return await self._config.get(refresh) + + async def get_supported(self, *, refresh: bool = False) -> SupportedResponse: + self._check_open() + return await self._supported.get(refresh) + + async def get_signer_addresses(self, network: str) -> list[str]: + signers = (await self.get_supported()).signers + if network in signers: + return signers[network] + namespace, separator, _ = network.partition(":") + return signers.get(namespace + ":*", []) if namespace and separator else [] + + async def offers( + self, + price: str, + *, + currency: str | None = None, + schemes: Sequence[str] | None = None, + networks: Sequence[str] | None = None, + max_timeout_seconds: int = 300, + ) -> list[PaymentOption]: + return build_offers( + await self.config(), + price, + currency=currency, + schemes=schemes, + networks=networks, + max_timeout_seconds=max_timeout_seconds, + ) + + async def route( + self, + price: str, + *, + currency: str | None = None, + schemes: Sequence[str] | None = None, + networks: Sequence[str] | None = None, + max_timeout_seconds: int = 300, + permit2: bool = False, + ) -> RouteConfig: + offers = build_offers( + await self.config(), + price, + currency=currency, + schemes=schemes, + networks=networks, + max_timeout_seconds=max_timeout_seconds, + permit2=permit2, + ) + route = RouteConfig(accepts=offers) + candidates = [ + offer for offer in offers if (offer.extra or {}).get("assetTransferMethod") == "permit2" + ] + if not candidates or not all( + offer.scheme == "exact" + and offer.network.startswith("eip155:") + and str((offer.extra or {}).get("permit2Proxy", "")).lower() == PERMIT2_PROXY.lower() + for offer in candidates + ): + return route + eip2612 = all( + (offer.extra or {}).get("supportsEip2612") is True + and isinstance((offer.extra or {}).get("name"), str) + and (offer.extra or {})["name"] + and isinstance((offer.extra or {}).get("version"), str) + and (offer.extra or {})["version"] + for offer in candidates + ) + eip7702 = all((offer.extra or {}).get("supportsEip7702") is True for offer in candidates) + if not eip2612 and not eip7702: + return route + supported = await self.get_supported(refresh=True) + + def supports(require_eip7702: bool) -> bool: + return all( + any( + kind.x402_version == 2 + and kind.scheme == offer.scheme + and kind.network == offer.network + and (not require_eip7702 or (kind.extra or {}).get("supportsEip7702") is True) + for kind in supported.kinds + ) + for offer in candidates + ) + + if eip2612 and "eip2612GasSponsoring" in supported.extensions and supports(False): + from x402.extensions.eip2612_gas_sponsoring import ( + declare_eip2612_gas_sponsoring_extension, + ) + + route.extensions = deepcopy(declare_eip2612_gas_sponsoring_extension()) + elif eip7702 and INFLOW_EIP7702_GAS_SPONSORING in supported.extensions and supports(True): + route.extensions = declare_sponsorship() + return route + + async def scheme_registrations( + self, *, schemes: Sequence[str] | None = None + ) -> list[SchemeRegistration]: + config = await self.config() + groups: dict[tuple[str, str], list[str]] = {} + + def add(scheme: str, network: str, method: object) -> None: + if schemes is not None and scheme not in schemes: + return + selected = method if isinstance(method, str) else "default" + methods = groups.setdefault((scheme, network), []) + if selected not in methods: + methods.append(selected) + + for asset in config.assets: + add("exact", asset.network, asset.asset_transfer_method) + if supports_permit2(asset): + add("exact", asset.network, "permit2") + if schemes is not None and "upto" in schemes and upto_kind(config, asset) is not None: + add("upto", asset.network, "permit2") + for method in config.payment_methods: + add(method.scheme, method.network, method.extra.get("assetTransferMethod")) + result: list[SchemeRegistration] = [] + for (scheme, network), methods in groups.items(): + server: SchemeNetworkServer + if scheme == "upto": + from x402.mechanisms.evm.upto import UptoEvmServerScheme + + # Upstream 2.25.0 has an untyped constructor and an inferred flow-map + # type; the object implements SchemeNetworkServer at runtime. + server = cast(Callable[[], SchemeNetworkServer], UptoEvmServerScheme)() + else: + server = _Passthrough(scheme, methods) + result.append({"network": network, "server": server}) + return result diff --git a/tests/fixtures/x402-seller.json b/tests/fixtures/x402-seller.json new file mode 100644 index 0000000..6af5c4f --- /dev/null +++ b/tests/fixtures/x402-seller.json @@ -0,0 +1,5433 @@ +{ + "source": "inflow-specs/fixtures/x402.mjs", + "cases": [ + { + "id": "x402.seller.verify", + "suite": "x402-seller", + "operation": "x402.seller.verify", + "input": { + "api_key": "test-only-seller-key", + "payment_payload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "payment_requirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + }, + "expect": { + "result": { + "isValid": true, + "payer": "0x2222222222222222222222222222222222222222" + } + }, + "platform": { + "exchanges": [ + { + "request": { + "method": "POST", + "path": "/v1/x402/verify", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 200, + "json": { + "isValid": true, + "payer": "0x2222222222222222222222222222222222222222" + } + } + } + ] + } + }, + { + "id": "x402.seller.verify-rejected", + "suite": "x402-seller", + "operation": "x402.seller.verify", + "input": { + "api_key": "test-only-seller-key", + "payment_payload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "payment_requirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + }, + "expect": { + "result": { + "isValid": false, + "invalidReason": "invalid_payload" + } + }, + "platform": { + "exchanges": [ + { + "request": { + "method": "POST", + "path": "/v1/x402/verify", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 200, + "json": { + "isValid": false, + "invalidReason": "invalid_payload" + } + } + } + ] + } + }, + { + "id": "x402.seller.verify-allowance", + "suite": "x402-seller", + "operation": "x402.seller.verify", + "input": { + "api_key": "test-only-seller-key", + "payment_payload": { + "x402Version": 2, + "accepted": { + "scheme": "exact", + "network": "eip155:8453", + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000", + "payTo": "0x1111111111111111111111111111111111111111", + "maxTimeoutSeconds": 300, + "extra": { + "assetTransferMethod": "permit2", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001" + } + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "signature": "0xababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab", + "permit2Authorization": { + "permitted": { + "token": "0x3333333333333333333333333333333333333333", + "amount": "1000000" + }, + "from": "0x2222222222222222222222222222222222222222", + "spender": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "nonce": "1", + "deadline": "1700000300", + "witness": { + "to": "0x1111111111111111111111111111111111111111", + "validAfter": "1700000000", + "extra": "0x" + } + } + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "payment_requirements": { + "scheme": "exact", + "network": "eip155:8453", + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000", + "payTo": "0x1111111111111111111111111111111111111111", + "maxTimeoutSeconds": 300, + "extra": { + "assetTransferMethod": "permit2", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001" + } + } + }, + "expect": { + "result": { + "isValid": false, + "invalidReason": "permit2_allowance_required" + } + }, + "platform": { + "exchanges": [ + { + "request": { + "method": "POST", + "path": "/v1/x402/verify", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "exact", + "network": "eip155:8453", + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000", + "payTo": "0x1111111111111111111111111111111111111111", + "maxTimeoutSeconds": 300, + "extra": { + "assetTransferMethod": "permit2", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001" + } + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "signature": "0xababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab", + "permit2Authorization": { + "permitted": { + "token": "0x3333333333333333333333333333333333333333", + "amount": "1000000" + }, + "from": "0x2222222222222222222222222222222222222222", + "spender": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "nonce": "1", + "deadline": "1700000300", + "witness": { + "to": "0x1111111111111111111111111111111111111111", + "validAfter": "1700000000", + "extra": "0x" + } + } + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "exact", + "network": "eip155:8453", + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000", + "payTo": "0x1111111111111111111111111111111111111111", + "maxTimeoutSeconds": 300, + "extra": { + "assetTransferMethod": "permit2", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001" + } + } + } + }, + "response": { + "status": 412, + "json": { + "isValid": false, + "invalidReason": "permit2_allowance_required" + } + } + } + ] + } + }, + { + "id": "x402.seller.settle", + "suite": "x402-seller", + "operation": "x402.seller.settle", + "input": { + "api_key": "test-only-seller-key", + "payment_payload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "payment_requirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + }, + "expect": { + "result": { + "success": true, + "transaction": "22222222-2222-4222-8222-222222222222", + "network": "inflow:1", + "payer": "0x2222222222222222222222222222222222222222", + "amount": "100000000" + } + }, + "platform": { + "exchanges": [ + { + "request": { + "method": "POST", + "path": "/v1/x402/settle", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 200, + "json": { + "success": true, + "transaction": "22222222-2222-4222-8222-222222222222", + "network": "inflow:1", + "payer": "0x2222222222222222222222222222222222222222", + "amount": "100000000" + } + } + } + ] + } + }, + { + "id": "x402.seller.settle-rejected", + "suite": "x402-seller", + "operation": "x402.seller.settle", + "input": { + "api_key": "test-only-seller-key", + "payment_payload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "payment_requirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + }, + "expect": { + "result": { + "success": false, + "transaction": "", + "network": "inflow:1", + "errorReason": "settlement_failed" + } + }, + "platform": { + "exchanges": [ + { + "request": { + "method": "POST", + "path": "/v1/x402/settle", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 200, + "json": { + "success": false, + "transaction": "", + "network": "inflow:1", + "errorReason": "settlement_failed" + } + } + } + ] + } + }, + { + "id": "x402.seller.verify-http-failure", + "suite": "x402-seller", + "operation": "x402.seller.verify", + "input": { + "api_key": "test-only-seller-key", + "payment_payload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "payment_requirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + }, + "expect": { + "error": { + "code": "api-error", + "message": "InFlow API request failed.", + "details": { + "body": { + "code": "TEMPORARY_ERROR" + } + }, + "http_status": 503 + } + }, + "platform": { + "exchanges": [ + { + "request": { + "method": "POST", + "path": "/v1/x402/verify", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 503, + "json": { + "code": "TEMPORARY_ERROR" + } + } + } + ] + } + }, + { + "id": "x402.seller.settle-http-failure", + "suite": "x402-seller", + "operation": "x402.seller.settle", + "input": { + "api_key": "test-only-seller-key", + "payment_payload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "payment_requirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + }, + "expect": { + "error": { + "code": "api-error", + "message": "InFlow API request failed.", + "details": { + "body": { + "code": "TEMPORARY_ERROR" + } + }, + "http_status": 503 + } + }, + "platform": { + "exchanges": [ + { + "request": { + "method": "POST", + "path": "/v1/x402/settle", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 503, + "json": { + "code": "TEMPORARY_ERROR" + } + } + } + ] + } + }, + { + "id": "x402.seller.settle-conflict", + "suite": "x402-seller", + "operation": "x402.seller.settle", + "input": { + "api_key": "test-only-seller-key", + "payment_payload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "payment_requirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + }, + "expect": { + "error": { + "code": "api-error", + "message": "InFlow API request failed.", + "details": { + "body": { + "success": false, + "transaction": "", + "network": "inflow:1", + "errorReason": "idempotency_conflict" + } + }, + "http_status": 409 + } + }, + "platform": { + "exchanges": [ + { + "request": { + "method": "POST", + "path": "/v1/x402/settle", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 409, + "json": { + "success": false, + "transaction": "", + "network": "inflow:1", + "errorReason": "idempotency_conflict" + } + } + } + ] + } + }, + { + "id": "x402.seller.settle-unrelated-conflict", + "suite": "x402-seller", + "operation": "x402.seller.settle", + "input": { + "api_key": "test-only-seller-key", + "payment_payload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "payment_requirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + }, + "expect": { + "error": { + "code": "api-error", + "message": "InFlow API request failed.", + "details": { + "body": { + "code": "CONFLICT" + } + }, + "http_status": 409 + } + }, + "platform": { + "exchanges": [ + { + "request": { + "method": "POST", + "path": "/v1/x402/settle", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 409, + "json": { + "code": "CONFLICT" + } + } + } + ] + } + }, + { + "id": "x402.seller.verify-unrecognized-412", + "suite": "x402-seller", + "operation": "x402.seller.verify", + "input": { + "api_key": "test-only-seller-key", + "payment_payload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "payment_requirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + }, + "expect": { + "error": { + "code": "api-error", + "message": "InFlow API request failed.", + "details": { + "body": { + "code": "PRECONDITION_FAILED" + } + }, + "http_status": 412 + } + }, + "platform": { + "exchanges": [ + { + "request": { + "method": "POST", + "path": "/v1/x402/verify", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 412, + "json": { + "code": "PRECONDITION_FAILED" + } + } + } + ] + } + }, + { + "id": "x402.seller.verify-contradictory-412", + "suite": "x402-seller", + "operation": "x402.seller.verify", + "input": { + "api_key": "test-only-seller-key", + "payment_payload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "payment_requirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + }, + "expect": { + "error": { + "code": "api-error", + "message": "InFlow API request failed.", + "details": { + "body": { + "isValid": true, + "invalidReason": "permit2_allowance_required" + } + }, + "http_status": 412 + } + }, + "platform": { + "exchanges": [ + { + "request": { + "method": "POST", + "path": "/v1/x402/verify", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 412, + "json": { + "isValid": true, + "invalidReason": "permit2_allowance_required" + } + } + } + ] + } + }, + { + "id": "x402.seller.verify-string-validity-412", + "suite": "x402-seller", + "operation": "x402.seller.verify", + "input": { + "api_key": "test-only-seller-key", + "payment_payload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "payment_requirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + }, + "expect": { + "error": { + "code": "api-error", + "message": "InFlow API request failed.", + "details": { + "body": { + "isValid": "false", + "invalidReason": "permit2_allowance_required" + } + }, + "http_status": 412 + } + }, + "platform": { + "exchanges": [ + { + "request": { + "method": "POST", + "path": "/v1/x402/verify", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 412, + "json": { + "isValid": "false", + "invalidReason": "permit2_allowance_required" + } + } + } + ] + } + }, + { + "id": "x402.seller.verify-unrelated-reason-412", + "suite": "x402-seller", + "operation": "x402.seller.verify", + "input": { + "api_key": "test-only-seller-key", + "payment_payload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "payment_requirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + }, + "expect": { + "error": { + "code": "api-error", + "message": "InFlow API request failed.", + "details": { + "body": { + "isValid": false, + "invalidReason": "unrelated_precondition" + } + }, + "http_status": 412 + } + }, + "platform": { + "exchanges": [ + { + "request": { + "method": "POST", + "path": "/v1/x402/verify", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 412, + "json": { + "isValid": false, + "invalidReason": "unrelated_precondition" + } + } + } + ] + } + }, + { + "id": "x402.seller.settle-pending-resolved", + "suite": "x402-seller", + "operation": "x402.seller.settle", + "input": { + "api_key": "test-only-seller-key", + "payment_payload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "payment_requirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + }, + "expect": { + "result": { + "success": true, + "transaction": "22222222-2222-4222-8222-222222222222", + "network": "inflow:1", + "payer": "0x2222222222222222222222222222222222222222", + "amount": "100000000" + } + }, + "platform": { + "exchanges": [ + { + "request": { + "method": "POST", + "path": "/v1/x402/settle", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 409, + "json": { + "success": false, + "transaction": "", + "network": "inflow:1", + "errorReason": "idempotency_pending" + }, + "headers": { + "retry-after": "0" + } + } + }, + { + "request": { + "method": "POST", + "path": "/v1/x402/settle", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 409, + "json": { + "success": false, + "transaction": "", + "network": "inflow:1", + "errorReason": "idempotency_pending" + }, + "headers": { + "retry-after": "0" + } + } + }, + { + "request": { + "method": "POST", + "path": "/v1/x402/settle", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 200, + "json": { + "success": true, + "transaction": "22222222-2222-4222-8222-222222222222", + "network": "inflow:1", + "payer": "0x2222222222222222222222222222222222222222", + "amount": "100000000" + } + } + } + ] + } + }, + { + "id": "x402.seller.settle-pending-exhausted", + "suite": "x402-seller", + "operation": "x402.seller.settle", + "input": { + "api_key": "test-only-seller-key", + "payment_payload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "payment_requirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + }, + "expect": { + "error": { + "code": "api-error", + "message": "InFlow API request failed.", + "details": { + "body": { + "success": false, + "transaction": "", + "network": "inflow:1", + "errorReason": "idempotency_pending" + } + }, + "http_status": 409 + } + }, + "platform": { + "exchanges": [ + { + "request": { + "method": "POST", + "path": "/v1/x402/settle", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 409, + "json": { + "success": false, + "transaction": "", + "network": "inflow:1", + "errorReason": "idempotency_pending" + }, + "headers": { + "retry-after": "0" + } + } + }, + { + "request": { + "method": "POST", + "path": "/v1/x402/settle", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 409, + "json": { + "success": false, + "transaction": "", + "network": "inflow:1", + "errorReason": "idempotency_pending" + }, + "headers": { + "retry-after": "0" + } + } + }, + { + "request": { + "method": "POST", + "path": "/v1/x402/settle", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 409, + "json": { + "success": false, + "transaction": "", + "network": "inflow:1", + "errorReason": "idempotency_pending" + }, + "headers": { + "retry-after": "0" + } + } + }, + { + "request": { + "method": "POST", + "path": "/v1/x402/settle", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 409, + "json": { + "success": false, + "transaction": "", + "network": "inflow:1", + "errorReason": "idempotency_pending" + }, + "headers": { + "retry-after": "0" + } + } + }, + { + "request": { + "method": "POST", + "path": "/v1/x402/settle", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 409, + "json": { + "success": false, + "transaction": "", + "network": "inflow:1", + "errorReason": "idempotency_pending" + }, + "headers": { + "retry-after": "0" + } + } + } + ] + } + }, + { + "id": "x402.seller.identifier-transaction-id", + "suite": "x402-seller", + "operation": "x402.seller.verify-settle", + "input": { + "api_key": "test-only-seller-key", + "payment_payload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "test-extension": { + "info": { + "value": "preserve" + } + } + } + }, + "payment_requirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + }, + "expect": { + "result": { + "verification": { + "isValid": true, + "payer": "0x2222222222222222222222222222222222222222" + }, + "settlement": { + "success": true, + "transaction": "22222222-2222-4222-8222-222222222222", + "network": "inflow:1", + "payer": "0x2222222222222222222222222222222222222222", + "amount": "100000000" + } + } + }, + "platform": { + "exchanges": [ + { + "request": { + "method": "POST", + "path": "/v1/x402/verify", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "test-extension": { + "info": { + "value": "preserve" + } + }, + "payment-identifier": { + "info": { + "required": false, + "id": "pay_a0ec8b5b8677bdf441d36f1103a39516" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 200, + "json": { + "isValid": true, + "payer": "0x2222222222222222222222222222222222222222" + } + } + }, + { + "request": { + "method": "POST", + "path": "/v1/x402/settle", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "test-extension": { + "info": { + "value": "preserve" + } + }, + "payment-identifier": { + "info": { + "required": false, + "id": "pay_a0ec8b5b8677bdf441d36f1103a39516" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 200, + "json": { + "success": true, + "transaction": "22222222-2222-4222-8222-222222222222", + "network": "inflow:1", + "payer": "0x2222222222222222222222222222222222222222", + "amount": "100000000" + } + } + } + ] + } + }, + { + "id": "x402.seller.identifier-transaction", + "suite": "x402-seller", + "operation": "x402.seller.verify-settle", + "input": { + "api_key": "test-only-seller-key", + "payment_payload": { + "x402Version": 2, + "accepted": { + "scheme": "exact", + "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "amount": "1000000", + "payTo": "11111111111111111111111111111111", + "maxTimeoutSeconds": 300, + "extra": { + "assetTransferMethod": "solana" + } + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transaction": "test-only-signed-transaction" + }, + "extensions": { + "test-extension": { + "info": { + "value": "preserve" + } + } + } + }, + "payment_requirements": { + "scheme": "exact", + "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "amount": "1000000", + "payTo": "11111111111111111111111111111111", + "maxTimeoutSeconds": 300, + "extra": { + "assetTransferMethod": "solana" + } + } + }, + "expect": { + "result": { + "verification": { + "isValid": true, + "payer": "0x2222222222222222222222222222222222222222" + }, + "settlement": { + "success": true, + "transaction": "22222222-2222-4222-8222-222222222222", + "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "payer": "0x2222222222222222222222222222222222222222", + "amount": "1000000" + } + } + }, + "platform": { + "exchanges": [ + { + "request": { + "method": "POST", + "path": "/v1/x402/verify", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "exact", + "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "amount": "1000000", + "payTo": "11111111111111111111111111111111", + "maxTimeoutSeconds": 300, + "extra": { + "assetTransferMethod": "solana" + } + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transaction": "test-only-signed-transaction" + }, + "extensions": { + "test-extension": { + "info": { + "value": "preserve" + } + }, + "payment-identifier": { + "info": { + "required": false, + "id": "pay_14cd985cfeb3b033e37f9805bcc3c1e1" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "exact", + "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "amount": "1000000", + "payTo": "11111111111111111111111111111111", + "maxTimeoutSeconds": 300, + "extra": { + "assetTransferMethod": "solana" + } + } + } + }, + "response": { + "status": 200, + "json": { + "isValid": true, + "payer": "0x2222222222222222222222222222222222222222" + } + } + }, + { + "request": { + "method": "POST", + "path": "/v1/x402/settle", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "exact", + "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "amount": "1000000", + "payTo": "11111111111111111111111111111111", + "maxTimeoutSeconds": 300, + "extra": { + "assetTransferMethod": "solana" + } + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transaction": "test-only-signed-transaction" + }, + "extensions": { + "test-extension": { + "info": { + "value": "preserve" + } + }, + "payment-identifier": { + "info": { + "required": false, + "id": "pay_14cd985cfeb3b033e37f9805bcc3c1e1" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "exact", + "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "amount": "1000000", + "payTo": "11111111111111111111111111111111", + "maxTimeoutSeconds": 300, + "extra": { + "assetTransferMethod": "solana" + } + } + } + }, + "response": { + "status": 200, + "json": { + "success": true, + "transaction": "22222222-2222-4222-8222-222222222222", + "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "payer": "0x2222222222222222222222222222222222222222", + "amount": "1000000" + } + } + } + ] + } + }, + { + "id": "x402.seller.identifier-signature", + "suite": "x402-seller", + "operation": "x402.seller.verify-settle", + "input": { + "api_key": "test-only-seller-key", + "payment_payload": { + "x402Version": 2, + "accepted": { + "scheme": "exact", + "network": "eip155:8453", + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000", + "payTo": "0x1111111111111111111111111111111111111111", + "maxTimeoutSeconds": 300, + "extra": { + "assetTransferMethod": "eip3009", + "name": "USDC", + "version": "2" + } + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "signature": "0xababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab", + "authorization": { + "from": "0x2222222222222222222222222222222222222222", + "to": "0x1111111111111111111111111111111111111111", + "value": "1000000", + "validAfter": "1700000000", + "validBefore": "1700000300", + "nonce": "0x1212121212121212121212121212121212121212121212121212121212121212" + } + }, + "extensions": { + "test-extension": { + "info": { + "value": "preserve" + } + } + } + }, + "payment_requirements": { + "scheme": "exact", + "network": "eip155:8453", + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000", + "payTo": "0x1111111111111111111111111111111111111111", + "maxTimeoutSeconds": 300, + "extra": { + "assetTransferMethod": "eip3009", + "name": "USDC", + "version": "2" + } + } + }, + "expect": { + "result": { + "verification": { + "isValid": true, + "payer": "0x2222222222222222222222222222222222222222" + }, + "settlement": { + "success": true, + "transaction": "22222222-2222-4222-8222-222222222222", + "network": "eip155:8453", + "payer": "0x2222222222222222222222222222222222222222", + "amount": "1000000" + } + } + }, + "platform": { + "exchanges": [ + { + "request": { + "method": "POST", + "path": "/v1/x402/verify", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "exact", + "network": "eip155:8453", + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000", + "payTo": "0x1111111111111111111111111111111111111111", + "maxTimeoutSeconds": 300, + "extra": { + "assetTransferMethod": "eip3009", + "name": "USDC", + "version": "2" + } + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "signature": "0xababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab", + "authorization": { + "from": "0x2222222222222222222222222222222222222222", + "to": "0x1111111111111111111111111111111111111111", + "value": "1000000", + "validAfter": "1700000000", + "validBefore": "1700000300", + "nonce": "0x1212121212121212121212121212121212121212121212121212121212121212" + } + }, + "extensions": { + "test-extension": { + "info": { + "value": "preserve" + } + }, + "payment-identifier": { + "info": { + "required": false, + "id": "pay_f1cc9f8bff220dba70dc02262aaf1603" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "exact", + "network": "eip155:8453", + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000", + "payTo": "0x1111111111111111111111111111111111111111", + "maxTimeoutSeconds": 300, + "extra": { + "assetTransferMethod": "eip3009", + "name": "USDC", + "version": "2" + } + } + } + }, + "response": { + "status": 200, + "json": { + "isValid": true, + "payer": "0x2222222222222222222222222222222222222222" + } + } + }, + { + "request": { + "method": "POST", + "path": "/v1/x402/settle", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "exact", + "network": "eip155:8453", + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000", + "payTo": "0x1111111111111111111111111111111111111111", + "maxTimeoutSeconds": 300, + "extra": { + "assetTransferMethod": "eip3009", + "name": "USDC", + "version": "2" + } + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "signature": "0xababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab", + "authorization": { + "from": "0x2222222222222222222222222222222222222222", + "to": "0x1111111111111111111111111111111111111111", + "value": "1000000", + "validAfter": "1700000000", + "validBefore": "1700000300", + "nonce": "0x1212121212121212121212121212121212121212121212121212121212121212" + } + }, + "extensions": { + "test-extension": { + "info": { + "value": "preserve" + } + }, + "payment-identifier": { + "info": { + "required": false, + "id": "pay_f1cc9f8bff220dba70dc02262aaf1603" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "exact", + "network": "eip155:8453", + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000", + "payTo": "0x1111111111111111111111111111111111111111", + "maxTimeoutSeconds": 300, + "extra": { + "assetTransferMethod": "eip3009", + "name": "USDC", + "version": "2" + } + } + } + }, + "response": { + "status": 200, + "json": { + "success": true, + "transaction": "22222222-2222-4222-8222-222222222222", + "network": "eip155:8453", + "payer": "0x2222222222222222222222222222222222222222", + "amount": "1000000" + } + } + } + ] + } + }, + { + "id": "x402.seller.anonymous-verify", + "suite": "x402-seller", + "operation": "x402.seller.verify", + "input": { + "payment_payload": { + "x402Version": 2, + "accepted": { + "scheme": "exact", + "network": "eip155:8453", + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000", + "payTo": "0x1111111111111111111111111111111111111111", + "maxTimeoutSeconds": 300, + "extra": { + "assetTransferMethod": "eip3009", + "name": "USDC", + "version": "2" + } + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "signature": "0xababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab", + "authorization": { + "from": "0x2222222222222222222222222222222222222222", + "to": "0x1111111111111111111111111111111111111111", + "value": "1000000", + "validAfter": "1700000000", + "validBefore": "1700000300", + "nonce": "0x1212121212121212121212121212121212121212121212121212121212121212" + } + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "payment_requirements": { + "scheme": "exact", + "network": "eip155:8453", + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000", + "payTo": "0x1111111111111111111111111111111111111111", + "maxTimeoutSeconds": 300, + "extra": { + "assetTransferMethod": "eip3009", + "name": "USDC", + "version": "2" + } + } + }, + "expect": { + "result": { + "isValid": true, + "payer": "0x2222222222222222222222222222222222222222" + } + }, + "platform": { + "exchanges": [ + { + "request": { + "method": "POST", + "path": "/v1/x402/verify", + "headers": {}, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "exact", + "network": "eip155:8453", + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000", + "payTo": "0x1111111111111111111111111111111111111111", + "maxTimeoutSeconds": 300, + "extra": { + "assetTransferMethod": "eip3009", + "name": "USDC", + "version": "2" + } + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "signature": "0xababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab", + "authorization": { + "from": "0x2222222222222222222222222222222222222222", + "to": "0x1111111111111111111111111111111111111111", + "value": "1000000", + "validAfter": "1700000000", + "validBefore": "1700000300", + "nonce": "0x1212121212121212121212121212121212121212121212121212121212121212" + } + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "exact", + "network": "eip155:8453", + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000", + "payTo": "0x1111111111111111111111111111111111111111", + "maxTimeoutSeconds": 300, + "extra": { + "assetTransferMethod": "eip3009", + "name": "USDC", + "version": "2" + } + } + } + }, + "response": { + "status": 200, + "json": { + "isValid": true, + "payer": "0x2222222222222222222222222222222222222222" + } + } + } + ] + } + }, + { + "id": "x402.seller.offers-default", + "suite": "x402-seller", + "operation": "x402.seller.offers", + "input": { + "config": { + "sellerId": "11111111-1111-4111-8111-111111111111", + "assets": [ + { + "assetId": "0x3333333333333333333333333333333333333333", + "assetName": "USDC", + "currency": "USDC", + "blockchain": "BASE", + "network": "eip155:8453", + "decimals": 6, + "assetTransferMethod": "eip3009", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "tokenName": "USDC", + "tokenVersion": "2" + } + ], + "wallets": [ + { + "address": "0x1111111111111111111111111111111111111111", + "blockchain": "BASE", + "network": "eip155:8453" + } + ], + "paymentMethods": [ + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "decimals": 8 + } + ], + "supported": [ + { + "scheme": "balance", + "network": "inflow:1", + "x402Version": 2 + }, + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2 + } + ] + }, + "options": { + "price": "$1" + } + }, + "expect": { + "result": [ + { + "scheme": "exact", + "network": "eip155:8453", + "payTo": "0x1111111111111111111111111111111111111111", + "price": { + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000" + }, + "maxTimeoutSeconds": 300, + "extra": { + "assetName": "USDC", + "name": "USDC", + "version": "2", + "assetTransferMethod": "eip3009" + } + }, + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "price": { + "asset": "USDC", + "amount": "100000000" + }, + "maxTimeoutSeconds": 300, + "extra": { + "assetName": "USDC" + } + } + ] + } + }, + { + "id": "x402.seller.offers-currency", + "suite": "x402-seller", + "operation": "x402.seller.offers", + "input": { + "config": { + "sellerId": "11111111-1111-4111-8111-111111111111", + "assets": [ + { + "assetId": "0x3333333333333333333333333333333333333333", + "assetName": "USDC", + "currency": "USDC", + "blockchain": "BASE", + "network": "eip155:8453", + "decimals": 6, + "assetTransferMethod": "eip3009", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "tokenName": "USDC", + "tokenVersion": "2" + } + ], + "wallets": [ + { + "address": "0x1111111111111111111111111111111111111111", + "blockchain": "BASE", + "network": "eip155:8453" + } + ], + "paymentMethods": [ + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "decimals": 8 + } + ], + "supported": [ + { + "scheme": "balance", + "network": "inflow:1", + "x402Version": 2 + }, + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2 + } + ] + }, + "options": { + "price": "1 USDC" + } + }, + "expect": { + "result": [ + { + "scheme": "exact", + "network": "eip155:8453", + "payTo": "0x1111111111111111111111111111111111111111", + "price": { + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000" + }, + "maxTimeoutSeconds": 300, + "extra": { + "assetName": "USDC", + "name": "USDC", + "version": "2", + "assetTransferMethod": "eip3009" + } + }, + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "price": { + "asset": "USDC", + "amount": "100000000" + }, + "maxTimeoutSeconds": 300, + "extra": { + "assetName": "USDC" + } + } + ] + } + }, + { + "id": "x402.seller.offers-balance-only", + "suite": "x402-seller", + "operation": "x402.seller.offers", + "input": { + "config": { + "sellerId": "11111111-1111-4111-8111-111111111111", + "assets": [ + { + "assetId": "0x3333333333333333333333333333333333333333", + "assetName": "USDC", + "currency": "USDC", + "blockchain": "BASE", + "network": "eip155:8453", + "decimals": 6, + "assetTransferMethod": "eip3009", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "tokenName": "USDC", + "tokenVersion": "2" + } + ], + "wallets": [ + { + "address": "0x1111111111111111111111111111111111111111", + "blockchain": "BASE", + "network": "eip155:8453" + } + ], + "paymentMethods": [ + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "decimals": 8 + } + ], + "supported": [ + { + "scheme": "balance", + "network": "inflow:1", + "x402Version": 2 + }, + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2 + } + ] + }, + "options": { + "price": "$1", + "schemes": [ + "balance" + ] + } + }, + "expect": { + "result": [ + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "price": { + "asset": "USDC", + "amount": "100000000" + }, + "maxTimeoutSeconds": 300, + "extra": { + "assetName": "USDC" + } + } + ] + } + }, + { + "id": "x402.seller.offers-network-only", + "suite": "x402-seller", + "operation": "x402.seller.offers", + "input": { + "config": { + "sellerId": "11111111-1111-4111-8111-111111111111", + "assets": [ + { + "assetId": "0x3333333333333333333333333333333333333333", + "assetName": "USDC", + "currency": "USDC", + "blockchain": "BASE", + "network": "eip155:8453", + "decimals": 6, + "assetTransferMethod": "eip3009", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "tokenName": "USDC", + "tokenVersion": "2" + } + ], + "wallets": [ + { + "address": "0x1111111111111111111111111111111111111111", + "blockchain": "BASE", + "network": "eip155:8453" + } + ], + "paymentMethods": [ + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "decimals": 8 + } + ], + "supported": [ + { + "scheme": "balance", + "network": "inflow:1", + "x402Version": 2 + }, + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2 + } + ] + }, + "options": { + "price": "$1", + "networks": [ + "eip155:8453" + ] + } + }, + "expect": { + "result": [ + { + "scheme": "exact", + "network": "eip155:8453", + "payTo": "0x1111111111111111111111111111111111111111", + "price": { + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000" + }, + "maxTimeoutSeconds": 300, + "extra": { + "assetName": "USDC", + "name": "USDC", + "version": "2", + "assetTransferMethod": "eip3009" + } + } + ] + } + }, + { + "id": "x402.seller.offers-intersect-filters", + "suite": "x402-seller", + "operation": "x402.seller.offers", + "input": { + "config": { + "sellerId": "11111111-1111-4111-8111-111111111111", + "assets": [ + { + "assetId": "0x3333333333333333333333333333333333333333", + "assetName": "USDC", + "currency": "USDC", + "blockchain": "BASE", + "network": "eip155:8453", + "decimals": 6, + "assetTransferMethod": "eip3009", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "tokenName": "USDC", + "tokenVersion": "2" + } + ], + "wallets": [ + { + "address": "0x1111111111111111111111111111111111111111", + "blockchain": "BASE", + "network": "eip155:8453" + } + ], + "paymentMethods": [ + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "decimals": 8 + } + ], + "supported": [ + { + "scheme": "balance", + "network": "inflow:1", + "x402Version": 2 + }, + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2 + } + ] + }, + "options": { + "price": "$1", + "schemes": [ + "balance" + ], + "networks": [ + "eip155:8453" + ] + } + }, + "expect": { + "result": [] + } + }, + { + "id": "x402.seller.offers-large-amount", + "suite": "x402-seller", + "operation": "x402.seller.offers", + "input": { + "config": { + "sellerId": "11111111-1111-4111-8111-111111111111", + "assets": [ + { + "assetId": "0x3333333333333333333333333333333333333333", + "assetName": "USDC", + "currency": "USDC", + "blockchain": "BASE", + "network": "eip155:8453", + "decimals": 6, + "assetTransferMethod": "eip3009", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "tokenName": "USDC", + "tokenVersion": "2" + } + ], + "wallets": [ + { + "address": "0x1111111111111111111111111111111111111111", + "blockchain": "BASE", + "network": "eip155:8453" + } + ], + "paymentMethods": [ + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "decimals": 8 + } + ], + "supported": [ + { + "scheme": "balance", + "network": "inflow:1", + "x402Version": 2 + }, + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2 + } + ] + }, + "options": { + "price": "9007199254740993 USDC", + "schemes": [ + "exact" + ] + } + }, + "expect": { + "result": [ + { + "scheme": "exact", + "network": "eip155:8453", + "payTo": "0x1111111111111111111111111111111111111111", + "price": { + "asset": "0x3333333333333333333333333333333333333333", + "amount": "9007199254740993000000" + }, + "maxTimeoutSeconds": 300, + "extra": { + "assetName": "USDC", + "name": "USDC", + "version": "2", + "assetTransferMethod": "eip3009" + } + } + ] + } + }, + { + "id": "x402.seller.invalid-price-negative", + "suite": "x402-seller", + "operation": "x402.seller.offers", + "input": { + "config": { + "sellerId": "11111111-1111-4111-8111-111111111111", + "assets": [ + { + "assetId": "0x3333333333333333333333333333333333333333", + "assetName": "USDC", + "currency": "USDC", + "blockchain": "BASE", + "network": "eip155:8453", + "decimals": 6, + "assetTransferMethod": "eip3009", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "tokenName": "USDC", + "tokenVersion": "2" + } + ], + "wallets": [ + { + "address": "0x1111111111111111111111111111111111111111", + "blockchain": "BASE", + "network": "eip155:8453" + } + ], + "paymentMethods": [ + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "decimals": 8 + } + ], + "supported": [ + { + "scheme": "balance", + "network": "inflow:1", + "x402Version": 2 + }, + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2 + } + ] + }, + "options": { + "price": "-1 USDC" + } + }, + "expect": { + "error": { + "code": "invalid-input", + "message": "Invalid input." + } + } + }, + { + "id": "x402.seller.invalid-price-exponent", + "suite": "x402-seller", + "operation": "x402.seller.offers", + "input": { + "config": { + "sellerId": "11111111-1111-4111-8111-111111111111", + "assets": [ + { + "assetId": "0x3333333333333333333333333333333333333333", + "assetName": "USDC", + "currency": "USDC", + "blockchain": "BASE", + "network": "eip155:8453", + "decimals": 6, + "assetTransferMethod": "eip3009", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "tokenName": "USDC", + "tokenVersion": "2" + } + ], + "wallets": [ + { + "address": "0x1111111111111111111111111111111111111111", + "blockchain": "BASE", + "network": "eip155:8453" + } + ], + "paymentMethods": [ + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "decimals": 8 + } + ], + "supported": [ + { + "scheme": "balance", + "network": "inflow:1", + "x402Version": 2 + }, + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2 + } + ] + }, + "options": { + "price": "1e3 USDC" + } + }, + "expect": { + "error": { + "code": "invalid-input", + "message": "Invalid input." + } + } + }, + { + "id": "x402.seller.invalid-price-precision", + "suite": "x402-seller", + "operation": "x402.seller.offers", + "input": { + "config": { + "sellerId": "11111111-1111-4111-8111-111111111111", + "assets": [ + { + "assetId": "0x3333333333333333333333333333333333333333", + "assetName": "USDC", + "currency": "USDC", + "blockchain": "BASE", + "network": "eip155:8453", + "decimals": 6, + "assetTransferMethod": "eip3009", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "tokenName": "USDC", + "tokenVersion": "2" + } + ], + "wallets": [ + { + "address": "0x1111111111111111111111111111111111111111", + "blockchain": "BASE", + "network": "eip155:8453" + } + ], + "paymentMethods": [ + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "decimals": 8 + } + ], + "supported": [ + { + "scheme": "balance", + "network": "inflow:1", + "x402Version": 2 + }, + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2 + } + ] + }, + "options": { + "price": "1.0000001 USDC" + } + }, + "expect": { + "error": { + "code": "invalid-input", + "message": "Invalid input." + } + } + }, + { + "id": "x402.seller.invalid-price-currency-missing", + "suite": "x402-seller", + "operation": "x402.seller.offers", + "input": { + "config": { + "sellerId": "11111111-1111-4111-8111-111111111111", + "assets": [ + { + "assetId": "0x3333333333333333333333333333333333333333", + "assetName": "USDC", + "currency": "USDC", + "blockchain": "BASE", + "network": "eip155:8453", + "decimals": 6, + "assetTransferMethod": "eip3009", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "tokenName": "USDC", + "tokenVersion": "2" + } + ], + "wallets": [ + { + "address": "0x1111111111111111111111111111111111111111", + "blockchain": "BASE", + "network": "eip155:8453" + } + ], + "paymentMethods": [ + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "decimals": 8 + } + ], + "supported": [ + { + "scheme": "balance", + "network": "inflow:1", + "x402Version": 2 + }, + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2 + } + ] + }, + "options": { + "price": "1" + } + }, + "expect": { + "error": { + "code": "invalid-input", + "message": "Invalid input." + } + } + }, + { + "id": "x402.seller.metered-explicit", + "suite": "x402-seller", + "operation": "x402.seller.offers", + "input": { + "config": { + "sellerId": "11111111-1111-4111-8111-111111111111", + "assets": [ + { + "assetId": "0x3333333333333333333333333333333333333333", + "assetName": "USDC", + "currency": "USDC", + "blockchain": "BASE", + "network": "eip155:8453", + "decimals": 6, + "assetTransferMethod": "eip3009", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "tokenName": "USDC", + "tokenVersion": "2" + } + ], + "wallets": [ + { + "address": "0x1111111111111111111111111111111111111111", + "blockchain": "BASE", + "network": "eip155:8453" + } + ], + "paymentMethods": [ + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "decimals": 8 + } + ], + "supported": [ + { + "scheme": "balance", + "network": "inflow:1", + "x402Version": 2 + }, + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2 + }, + { + "scheme": "upto", + "network": "eip155:8453", + "x402Version": 2, + "extra": { + "assetTransferMethod": "permit2", + "permit2Proxy": "0x4444444444444444444444444444444444444444", + "facilitatorAddress": "0x2222222222222222222222222222222222222222" + } + } + ] + }, + "options": { + "price": "$1", + "schemes": [ + "upto" + ] + } + }, + "expect": { + "result": [ + { + "scheme": "upto", + "network": "eip155:8453", + "payTo": "0x1111111111111111111111111111111111111111", + "price": { + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000" + }, + "maxTimeoutSeconds": 300, + "extra": { + "assetName": "USDC", + "name": "USDC", + "version": "2", + "assetTransferMethod": "permit2", + "permit2Proxy": "0x4444444444444444444444444444444444444444", + "facilitatorAddress": "0x2222222222222222222222222222222222222222" + } + } + ] + } + }, + { + "id": "x402.seller.metered-not-default", + "suite": "x402-seller", + "operation": "x402.seller.offers", + "input": { + "config": { + "sellerId": "11111111-1111-4111-8111-111111111111", + "assets": [ + { + "assetId": "0x3333333333333333333333333333333333333333", + "assetName": "USDC", + "currency": "USDC", + "blockchain": "BASE", + "network": "eip155:8453", + "decimals": 6, + "assetTransferMethod": "eip3009", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "tokenName": "USDC", + "tokenVersion": "2" + } + ], + "wallets": [ + { + "address": "0x1111111111111111111111111111111111111111", + "blockchain": "BASE", + "network": "eip155:8453" + } + ], + "paymentMethods": [ + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "decimals": 8 + } + ], + "supported": [ + { + "scheme": "balance", + "network": "inflow:1", + "x402Version": 2 + }, + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2 + }, + { + "scheme": "upto", + "network": "eip155:8453", + "x402Version": 2, + "extra": { + "assetTransferMethod": "permit2", + "permit2Proxy": "0x4444444444444444444444444444444444444444", + "facilitatorAddress": "0x2222222222222222222222222222222222222222" + } + } + ] + }, + "options": { + "price": "$1" + } + }, + "expect": { + "result": [ + { + "scheme": "exact", + "network": "eip155:8453", + "payTo": "0x1111111111111111111111111111111111111111", + "price": { + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000" + }, + "maxTimeoutSeconds": 300, + "extra": { + "assetName": "USDC", + "name": "USDC", + "version": "2", + "assetTransferMethod": "eip3009" + } + }, + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "price": { + "asset": "USDC", + "amount": "100000000" + }, + "maxTimeoutSeconds": 300, + "extra": { + "assetName": "USDC" + } + } + ] + } + }, + { + "id": "x402.seller.metered-incomplete-config", + "suite": "x402-seller", + "operation": "x402.seller.offers", + "input": { + "config": { + "sellerId": "11111111-1111-4111-8111-111111111111", + "assets": [ + { + "assetId": "0x3333333333333333333333333333333333333333", + "assetName": "USDC", + "currency": "USDC", + "blockchain": "BASE", + "network": "eip155:8453", + "decimals": 6, + "assetTransferMethod": "eip3009", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "tokenName": "USDC", + "tokenVersion": "2" + } + ], + "wallets": [ + { + "address": "0x1111111111111111111111111111111111111111", + "blockchain": "BASE", + "network": "eip155:8453" + } + ], + "paymentMethods": [ + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "decimals": 8 + } + ], + "supported": [ + { + "scheme": "balance", + "network": "inflow:1", + "x402Version": 2 + }, + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2 + }, + { + "scheme": "upto", + "network": "eip155:8453", + "x402Version": 2, + "extra": { + "assetTransferMethod": "permit2", + "permit2Proxy": "0x4444444444444444444444444444444444444444" + } + } + ] + }, + "options": { + "price": "$1", + "schemes": [ + "upto" + ] + } + }, + "expect": { + "result": [] + } + }, + { + "id": "x402.seller.sponsorship-advertised", + "suite": "x402-seller", + "operation": "x402.seller.route", + "input": { + "config": { + "sellerId": "11111111-1111-4111-8111-111111111111", + "assets": [ + { + "assetId": "0x3333333333333333333333333333333333333333", + "assetName": "USDC", + "currency": "USDC", + "blockchain": "BASE", + "network": "eip155:8453", + "decimals": 6, + "assetTransferMethod": "eip3009", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "tokenName": "USDC", + "tokenVersion": "2", + "supportsEip7702": true + } + ], + "wallets": [ + { + "address": "0x1111111111111111111111111111111111111111", + "blockchain": "BASE", + "network": "eip155:8453" + } + ], + "paymentMethods": [ + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "decimals": 8 + } + ], + "supported": [ + { + "scheme": "balance", + "network": "inflow:1", + "x402Version": 2 + }, + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2 + } + ] + }, + "supported": { + "kinds": [ + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2, + "extra": { + "supportsEip7702": true + } + } + ], + "extensions": [ + "inflowEip7702GasSponsoring" + ] + }, + "options": { + "price": "$1", + "schemes": [ + "exact" + ], + "assetTransferMethod": "permit2" + } + }, + "expect": { + "result": { + "accepts": [ + { + "scheme": "exact", + "network": "eip155:8453", + "payTo": "0x1111111111111111111111111111111111111111", + "price": { + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000" + }, + "maxTimeoutSeconds": 300, + "extra": { + "assetName": "USDC", + "name": "USDC", + "version": "2", + "assetTransferMethod": "permit2", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "supportsEip7702": true + } + } + ], + "extensions": { + "inflowEip7702GasSponsoring": { + "info": { + "version": "1" + } + } + } + } + } + }, + { + "id": "x402.seller.sponsorship-token-unconfirmed", + "suite": "x402-seller", + "operation": "x402.seller.route", + "input": { + "config": { + "sellerId": "11111111-1111-4111-8111-111111111111", + "assets": [ + { + "assetId": "0x3333333333333333333333333333333333333333", + "assetName": "USDC", + "currency": "USDC", + "blockchain": "BASE", + "network": "eip155:8453", + "decimals": 6, + "assetTransferMethod": "eip3009", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "tokenName": "USDC", + "tokenVersion": "2" + } + ], + "wallets": [ + { + "address": "0x1111111111111111111111111111111111111111", + "blockchain": "BASE", + "network": "eip155:8453" + } + ], + "paymentMethods": [ + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "decimals": 8 + } + ], + "supported": [ + { + "scheme": "balance", + "network": "inflow:1", + "x402Version": 2 + }, + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2 + } + ] + }, + "supported": { + "kinds": [ + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2, + "extra": { + "supportsEip7702": true + } + } + ], + "extensions": [ + "inflowEip7702GasSponsoring" + ] + }, + "options": { + "price": "$1", + "schemes": [ + "exact" + ], + "assetTransferMethod": "permit2" + } + }, + "expect": { + "result": { + "accepts": [ + { + "scheme": "exact", + "network": "eip155:8453", + "payTo": "0x1111111111111111111111111111111111111111", + "price": { + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000" + }, + "maxTimeoutSeconds": 300, + "extra": { + "assetName": "USDC", + "name": "USDC", + "version": "2", + "assetTransferMethod": "permit2", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001" + } + } + ] + } + } + }, + { + "id": "x402.seller.sponsorship-facilitator-unconfirmed", + "suite": "x402-seller", + "operation": "x402.seller.route", + "input": { + "config": { + "sellerId": "11111111-1111-4111-8111-111111111111", + "assets": [ + { + "assetId": "0x3333333333333333333333333333333333333333", + "assetName": "USDC", + "currency": "USDC", + "blockchain": "BASE", + "network": "eip155:8453", + "decimals": 6, + "assetTransferMethod": "eip3009", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "tokenName": "USDC", + "tokenVersion": "2", + "supportsEip7702": true + } + ], + "wallets": [ + { + "address": "0x1111111111111111111111111111111111111111", + "blockchain": "BASE", + "network": "eip155:8453" + } + ], + "paymentMethods": [ + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "decimals": 8 + } + ], + "supported": [ + { + "scheme": "balance", + "network": "inflow:1", + "x402Version": 2 + }, + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2 + } + ] + }, + "supported": { + "kinds": [ + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2, + "extra": { + "supportsEip7702": true + } + } + ], + "extensions": [] + }, + "options": { + "price": "$1", + "schemes": [ + "exact" + ], + "assetTransferMethod": "permit2" + } + }, + "expect": { + "result": { + "accepts": [ + { + "scheme": "exact", + "network": "eip155:8453", + "payTo": "0x1111111111111111111111111111111111111111", + "price": { + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000" + }, + "maxTimeoutSeconds": 300, + "extra": { + "assetName": "USDC", + "name": "USDC", + "version": "2", + "assetTransferMethod": "permit2", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "supportsEip7702": true + } + } + ] + } + } + }, + { + "id": "x402.seller.sponsorship-network-unconfirmed", + "suite": "x402-seller", + "operation": "x402.seller.route", + "input": { + "config": { + "sellerId": "11111111-1111-4111-8111-111111111111", + "assets": [ + { + "assetId": "0x3333333333333333333333333333333333333333", + "assetName": "USDC", + "currency": "USDC", + "blockchain": "BASE", + "network": "eip155:8453", + "decimals": 6, + "assetTransferMethod": "eip3009", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "tokenName": "USDC", + "tokenVersion": "2", + "supportsEip7702": true + } + ], + "wallets": [ + { + "address": "0x1111111111111111111111111111111111111111", + "blockchain": "BASE", + "network": "eip155:8453" + } + ], + "paymentMethods": [ + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "decimals": 8 + } + ], + "supported": [ + { + "scheme": "balance", + "network": "inflow:1", + "x402Version": 2 + }, + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2 + } + ] + }, + "supported": { + "kinds": [], + "extensions": [ + "inflowEip7702GasSponsoring" + ] + }, + "options": { + "price": "$1", + "schemes": [ + "exact" + ], + "assetTransferMethod": "permit2" + } + }, + "expect": { + "result": { + "accepts": [ + { + "scheme": "exact", + "network": "eip155:8453", + "payTo": "0x1111111111111111111111111111111111111111", + "price": { + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000" + }, + "maxTimeoutSeconds": 300, + "extra": { + "assetName": "USDC", + "name": "USDC", + "version": "2", + "assetTransferMethod": "permit2", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "supportsEip7702": true + } + } + ] + } + } + }, + { + "id": "x402.seller.sponsorship-prefers-eip2612", + "suite": "x402-seller", + "operation": "x402.seller.route", + "input": { + "config": { + "sellerId": "11111111-1111-4111-8111-111111111111", + "assets": [ + { + "assetId": "0x3333333333333333333333333333333333333333", + "assetName": "USDC", + "currency": "USDC", + "blockchain": "BASE", + "network": "eip155:8453", + "decimals": 6, + "assetTransferMethod": "eip3009", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "tokenName": "USDC", + "tokenVersion": "2", + "supportsEip7702": true, + "supportsEip2612": true + } + ], + "wallets": [ + { + "address": "0x1111111111111111111111111111111111111111", + "blockchain": "BASE", + "network": "eip155:8453" + } + ], + "paymentMethods": [ + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "decimals": 8 + } + ], + "supported": [ + { + "scheme": "balance", + "network": "inflow:1", + "x402Version": 2 + }, + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2 + } + ] + }, + "supported": { + "kinds": [ + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2, + "extra": { + "supportsEip7702": true + } + } + ], + "extensions": [ + "eip2612GasSponsoring", + "inflowEip7702GasSponsoring" + ] + }, + "options": { + "price": "$1", + "schemes": [ + "exact" + ], + "assetTransferMethod": "permit2" + } + }, + "expect": { + "result": { + "accepts": [ + { + "scheme": "exact", + "network": "eip155:8453", + "payTo": "0x1111111111111111111111111111111111111111", + "price": { + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000" + }, + "maxTimeoutSeconds": 300, + "extra": { + "assetName": "USDC", + "name": "USDC", + "version": "2", + "assetTransferMethod": "permit2", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "supportsEip7702": true, + "supportsEip2612": true + } + } + ], + "extensions": { + "eip2612GasSponsoring": { + "info": { + "description": "The facilitator accepts EIP-2612 gasless Permit to `Permit2` canonical contract.", + "version": "1" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "from": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The address of the sender." + }, + "asset": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The address of the ERC-20 token contract." + }, + "spender": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "The address of the spender (Canonical Permit2)." + }, + "amount": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "The amount to approve (uint256). Typically MaxUint." + }, + "nonce": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "The current nonce of the sender." + }, + "deadline": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "The timestamp at which the signature expires." + }, + "signature": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "The 65-byte concatenated signature (r, s, v) as a hex string." + }, + "version": { + "type": "string", + "pattern": "^[0-9]+(\\.[0-9]+)*$", + "description": "Schema version identifier." + } + }, + "required": [ + "from", + "asset", + "spender", + "amount", + "nonce", + "deadline", + "signature", + "version" + ] + } + } + } + } + } + }, + { + "id": "x402.seller.sponsorship-eip2612-missing-domain", + "suite": "x402-seller", + "operation": "x402.seller.route", + "input": { + "config": { + "sellerId": "11111111-1111-4111-8111-111111111111", + "assets": [ + { + "assetId": "0x3333333333333333333333333333333333333333", + "assetName": "USDC", + "currency": "USDC", + "blockchain": "BASE", + "network": "eip155:8453", + "decimals": 6, + "assetTransferMethod": "eip3009", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "tokenName": "USDC", + "supportsEip7702": true, + "supportsEip2612": true + } + ], + "wallets": [ + { + "address": "0x1111111111111111111111111111111111111111", + "blockchain": "BASE", + "network": "eip155:8453" + } + ], + "paymentMethods": [ + { + "scheme": "balance", + "network": "inflow:1", + "payTo": "11111111-1111-4111-8111-111111111111", + "decimals": 8 + } + ], + "supported": [ + { + "scheme": "balance", + "network": "inflow:1", + "x402Version": 2 + }, + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2 + } + ] + }, + "supported": { + "kinds": [ + { + "scheme": "exact", + "network": "eip155:8453", + "x402Version": 2, + "extra": { + "supportsEip7702": true + } + } + ], + "extensions": [ + "eip2612GasSponsoring", + "inflowEip7702GasSponsoring" + ] + }, + "options": { + "price": "$1", + "schemes": [ + "exact" + ], + "assetTransferMethod": "permit2" + } + }, + "expect": { + "result": { + "accepts": [ + { + "scheme": "exact", + "network": "eip155:8453", + "payTo": "0x1111111111111111111111111111111111111111", + "price": { + "asset": "0x3333333333333333333333333333333333333333", + "amount": "1000000" + }, + "maxTimeoutSeconds": 300, + "extra": { + "assetName": "USDC", + "name": "USDC", + "assetTransferMethod": "permit2", + "permit2Proxy": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", + "supportsEip7702": true, + "supportsEip2612": true + } + } + ], + "extensions": { + "inflowEip7702GasSponsoring": { + "info": { + "version": "1" + } + } + } + } + } + }, + { + "id": "x402.seller.rejected-verification-no-settlement", + "suite": "x402-seller", + "operation": "x402.seller.verify-settle", + "input": { + "api_key": "test-only-seller-key", + "payment_payload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "payment_requirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + }, + "expect": { + "result": { + "verification": { + "isValid": false, + "invalidReason": "invalid_payload" + } + } + }, + "platform": { + "exchanges": [ + { + "request": { + "method": "POST", + "path": "/v1/x402/verify", + "headers": { + "x-api-key": "test-only-seller-key" + }, + "json": { + "x402Version": 2, + "paymentPayload": { + "x402Version": 2, + "accepted": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + }, + "resource": { + "url": "https://seller.example/data", + "mimeType": "application/json" + }, + "payload": { + "transactionId": "22222222-2222-4222-8222-222222222222" + }, + "extensions": { + "payment-identifier": { + "info": { + "required": false, + "id": "pay_0123456789abcdef0123456789abcdef" + }, + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 16, + "maxLength": 128, + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "required" + ] + } + } + } + }, + "paymentRequirements": { + "scheme": "balance", + "network": "inflow:1", + "asset": "USDC", + "amount": "100000000", + "payTo": "11111111-1111-4111-8111-111111111111", + "maxTimeoutSeconds": 300, + "extra": {} + } + } + }, + "response": { + "status": 200, + "json": { + "isValid": false, + "invalidReason": "invalid_payload" + } + } + } + ] + } + } + ] +} diff --git a/tests/test_x402_seller.py b/tests/test_x402_seller.py new file mode 100644 index 0000000..8d0905b --- /dev/null +++ b/tests/test_x402_seller.py @@ -0,0 +1,660 @@ +import asyncio +import base64 +import hashlib +import json +from copy import deepcopy +from pathlib import Path +from types import SimpleNamespace +from typing import Any + +import httpx +import pytest +from fastapi import FastAPI, Request +from pydantic import ValidationError +from starlette.responses import JSONResponse +from x402 import x402ResourceServer +from x402.http.middleware.fastapi import payment_middleware, set_settlement_overrides +from x402.http.types import PaymentOption, RouteConfig +from x402.schemas import AssetAmount, PaymentPayload, PaymentRequirements, SupportedKind +from x402.schemas.v1 import PaymentPayloadV1, PaymentRequirementsV1 + +from inflowpay import ClientOptions, InflowApiError +from inflowpay.x402._seller import SellerConfig, build_offers +from inflowpay.x402.facilitator import Facilitator +from inflowpay.x402.seller import Seller + +CASES: list[dict[str, Any]] = json.loads( + Path(__file__).with_name("fixtures").joinpath("x402-seller.json").read_text() +)["cases"] +CONFIG = next( + case["input"]["config"] for case in CASES if case["operation"] == "x402.seller.offers" +) +REQUIREMENTS = PaymentRequirements.model_validate(CASES[0]["input"]["payment_requirements"]) +PAYLOAD = PaymentPayload.model_validate(CASES[0]["input"]["payment_payload"]) +SUPPORTED = {"kinds": CONFIG["supported"], "signers": {"eip155:*": ["wildcard"], "inflow:1": []}} + + +class Platform(httpx.AsyncBaseTransport): + def __init__(self) -> None: + self.config: Any = deepcopy(CONFIG) + self.supported: Any = deepcopy(SUPPORTED) + self.verification: Any = {"isValid": True} + self.settlement: Any = {"success": True, "transaction": "tx", "network": "inflow:1"} + self.requests: list[httpx.Request] = [] + self.block = "" + self.started = asyncio.Event() + self.release = asyncio.Event() + self.closed = False + + async def handle_async_request(self, request: httpx.Request) -> httpx.Response: + self.requests.append(request) + operation = request.url.path.rsplit("/", 1)[-1] + if operation == self.block: + self.started.set() + await self.release.wait() + value = { + "config": self.config, + "supported": self.supported, + "verify": self.verification, + "settle": self.settlement, + }[operation] + if isinstance(value, Exception): + raise value + return value if isinstance(value, httpx.Response) else httpx.Response(200, json=value) + + async def aclose(self) -> None: + self.closed = True + + def options(self, *, anonymous: bool = False) -> ClientOptions: + return ClientOptions(api_key=None if anonymous else "seller-key", transport=self) + + +def wire_offer(offer: PaymentOption) -> dict[str, Any]: + assert isinstance(offer.price, AssetAmount) + return { + "scheme": offer.scheme, + "network": offer.network, + "payTo": offer.pay_to, + "price": offer.price.model_dump(exclude_none=True), + "maxTimeoutSeconds": offer.max_timeout_seconds, + "extra": offer.extra, + } + + +@pytest.mark.parametrize("case", CASES, ids=lambda case: case["id"]) +async def test_shared_seller_vectors(case: dict[str, Any]) -> None: + data = case["input"] + platform = Platform() + exchanges = deepcopy(case.get("platform", {}).get("exchanges", [])) + + async def transport(request: httpx.Request) -> httpx.Response: + if request.method == "GET": + return httpx.Response( + 200, + json=platform.config if request.url.path.endswith("config") else platform.supported, + ) + expected = exchanges.pop(0) + assert request.method == expected["request"]["method"] + assert request.url.path == expected["request"]["path"] + for name, value in expected["request"]["headers"].items(): + assert request.headers[name] == value + assert json.loads(request.content) == expected["request"]["json"] + response = expected["response"] + return httpx.Response( + response["status"], json=response["json"], headers=response.get("headers") + ) + + options = ClientOptions( + api_key=data.get("api_key", "seller-key"), transport=httpx.MockTransport(transport) + ) + + async def execute() -> Any: + operation = case["operation"].rsplit(".", 1)[-1] + if operation in ("offers", "route"): + platform.config = data["config"] + platform.supported = data.get("supported", SUPPORTED) + arguments = data["options"] + async with await Seller.create(options) as seller: + kwargs = { + key: arguments[key] for key in ("schemes", "networks") if key in arguments + } + if operation == "offers": + return [ + wire_offer(offer) + for offer in await seller.offers(arguments["price"], **kwargs) + ] + route = await seller.route( + arguments["price"], + permit2=arguments.get("assetTransferMethod") == "permit2", + **kwargs, + ) + assert isinstance(route.accepts, list) + result: dict[str, Any] = {"accepts": [wire_offer(offer) for offer in route.accepts]} + if route.extensions is not None: + result["extensions"] = route.extensions + return result + anonymous = "api_key" not in data + options_without_key = ClientOptions( + api_key=data.get("api_key"), transport=httpx.MockTransport(transport) + ) + async with await Facilitator.create( + options_without_key, anonymous=anonymous + ) as facilitator: + payment = PaymentPayload.model_validate(data["payment_payload"]) + required = PaymentRequirements.model_validate(data["payment_requirements"]) + if operation == "verify-settle": + verified = await facilitator.verify(payment, required) + results = {"verification": verified.model_dump(exclude_none=True)} + if verified.is_valid: + results["settlement"] = ( + await facilitator.settle(payment, required) + ).model_dump(exclude_none=True) + return results + response = await ( + facilitator.verify(payment, required) + if operation == "verify" + else facilitator.settle(payment, required) + ) + return response.model_dump(exclude_none=True) + + expected = case["expect"] + if "error" in expected: + with pytest.raises((InflowApiError, ValueError)) as caught: + await execute() + if isinstance(caught.value, InflowApiError): + assert caught.value.http_status == expected["error"]["http_status"] + else: + actual = await execute() + if case["id"] == "x402.seller.sponsorship-prefers-eip2612": + # Upstream Python and Node word schema descriptions differently; assert + # identical declaration fields and validation constraints, not copy text. + def constraints(value: Any) -> Any: + if isinstance(value, dict): + return { + key: constraints(item) + for key, item in value.items() + if key != "description" + } + if isinstance(value, list): + return [constraints(item) for item in value] + return value + + assert constraints(actual) == constraints(expected["result"]) + else: + assert actual == expected["result"] + assert not exchanges + + +@pytest.mark.parametrize("anonymous,key", [(False, None), (True, "key")]) +async def test_facilitator_requires_explicit_auth_mode(anonymous: bool, key: str | None) -> None: + with pytest.raises(ValueError): + await Facilitator.create(ClientOptions(api_key=key), anonymous=anonymous) + + +async def test_anonymous_rejects_token_provider() -> None: + async def token() -> str: + return "token" + + with pytest.raises(ValueError): + await Facilitator.create(ClientOptions(access_token=token), anonymous=True) + with pytest.raises(ValueError): + await Seller.create(ClientOptions()) + + +@pytest.mark.parametrize("kind", ["seller", "facilitator"]) +async def test_setup_failure_closes_transport(kind: str) -> None: + platform = Platform() + platform.supported = {"invalid": True} + with pytest.raises(ValidationError): + await ( + Seller.create(platform.options()) + if kind == "seller" + else Facilitator.create(platform.options()) + ) + assert platform.closed + + +async def test_seller_cache_refresh_isolation_and_signers() -> None: + platform = Platform() + async with await Seller.create(platform.options()) as seller: + config = await seller.config() + config.assets.clear() + assert (await seller.config()).assets + assert len(platform.requests) == 2 + assert await seller.get_signer_addresses("inflow:1") == [] + assert await seller.get_signer_addresses("eip155:8453") == ["wildcard"] + for network in ("unknown:1", "unknown", ":bad"): + assert await seller.get_signer_addresses(network) == [] + (await seller.get_supported()).kinds.clear() + assert (await seller.get_supported()).kinds + platform.config["assets"] = [] + assert (await seller.config(refresh=True)).assets == [] + seller._supported.expires = 0 + assert (await seller.get_supported()).kinds + with pytest.raises(RuntimeError): + await seller.config() + with pytest.raises(RuntimeError): + await seller.__aenter__() + + +async def test_refresh_concurrency_cancellation_and_failure() -> None: + platform = Platform() + seller = await Seller.create(platform.options()) + platform.block = "config" + first = asyncio.create_task(seller.config(refresh=True)) + second = asyncio.create_task(seller.config(refresh=True)) + await platform.started.wait() + first.cancel() + with pytest.raises(asyncio.CancelledError): + await first + platform.release.set() + await second + assert sum(r.url.path.endswith("config") for r in platform.requests) == 2 + platform.config = httpx.Response(503, json={"code": "unavailable"}) + with pytest.raises(InflowApiError): + await seller.config(refresh=True) + assert (await seller.config()).assets + platform.release.clear() + task = asyncio.create_task(seller.config(refresh=True)) + await asyncio.sleep(0) + await asyncio.sleep(0) + await seller.aclose() + with pytest.raises(asyncio.CancelledError): + await task + assert platform.closed + + +async def test_fixed_facilitator_snapshot_and_recreation() -> None: + platform = Platform() + async with await Facilitator.create(platform.options()) as facilitator: + assert facilitator.get_supported().kinds + facilitator.get_supported().kinds.clear() + platform.supported = {"kinds": []} + assert facilitator.get_supported().kinds + assert len(platform.requests) == 1 + async with await Facilitator.create(platform.options()) as replacement: + assert replacement.get_supported().kinds == [] + + +@pytest.mark.parametrize( + "inner", [{}, {"transactionId": ""}, {"transactionId": 4}, {"unknown": "é"}] +) +async def test_identifier_fallback_and_caller_isolation(inner: dict[str, object]) -> None: + platform = Platform() + payload = PAYLOAD.model_copy(deep=True) + payload.payload = inner + payload.extensions = {"other": {"info": {"value": "keep"}}} + before = payload.model_dump() + async with await Facilitator.create(platform.options()) as facilitator: + await facilitator.verify(payload, REQUIREMENTS) + await facilitator.settle(payload, REQUIREMENTS) + requests = [json.loads(r.content) for r in platform.requests if r.method == "POST"] + assert requests[0] == requests[1] + material = "payload:" + json.dumps(inner, separators=(",", ":"), ensure_ascii=False) + assert ( + requests[0]["paymentPayload"]["extensions"]["payment-identifier"]["info"]["id"] + == "pay_" + hashlib.sha256(material.encode()).hexdigest()[:32] + ) + assert payload.model_dump() == before + + +async def test_invalid_versions_are_rejected_before_http() -> None: + platform = Platform() + async with await Facilitator.create(platform.options()) as facilitator: + bad = PAYLOAD.model_copy(update={"x402_version": 1}) + with pytest.raises(ValueError): + await facilitator.verify(bad, REQUIREMENTS) + old = PaymentRequirementsV1( + scheme="exact", + network="base", + max_amount_required="1", + resource="https://test", + description="", + mime_type="", + pay_to="pay", + max_timeout_seconds=60, + asset="USDC", + ) + with pytest.raises(ValueError): + await facilitator.settle(PAYLOAD, old) + payload = PaymentPayloadV1(x402_version=1, scheme="exact", network="base", payload={}) + with pytest.raises(ValueError): + await facilitator.verify(payload, old) + assert len(platform.requests) == 1 + + +@pytest.mark.parametrize( + "header,expected", [(None, 5), ("invalid", 5), ("10", 5), ("2", 2), ("0", 0)] +) +async def test_pending_delay_and_cancellation( + monkeypatch: pytest.MonkeyPatch, header: str | None, expected: int +) -> None: + platform = Platform() + platform.settlement = httpx.Response( + 409, + json={"errorReason": "idempotency_pending"}, + headers={} if header is None else {"Retry-After": header}, + ) + async with await Facilitator.create(platform.options()) as facilitator: + real_sleep = asyncio.sleep + + async def sleep(delay: float) -> None: + if delay == 0 and expected != 0: + await real_sleep(0) + return + assert delay == expected + raise asyncio.CancelledError + + # Patch only facilitator sleep, leaving the transport's scheduling yield intact. + monkeypatch.setattr("inflowpay.x402.facilitator.asyncio", SimpleNamespace(sleep=sleep)) + with pytest.raises(asyncio.CancelledError): + await facilitator.settle(PAYLOAD, REQUIREMENTS) + assert len(platform.requests) == 2 + + +@pytest.mark.parametrize( + "failure", + [ + httpx.ConnectError("offline"), + httpx.Response(503), + httpx.Response(409, json={"errorReason": "conflict"}), + ], +) +async def test_settlement_never_retries_uncertain_failure(failure: object) -> None: + platform = Platform() + platform.settlement = failure + async with await Facilitator.create(platform.options()) as facilitator: + with pytest.raises(InflowApiError): + await facilitator.settle(PAYLOAD, REQUIREMENTS) + assert len(platform.requests) == 2 + + +async def test_missing_network_is_not_silently_repaired() -> None: + platform = Platform() + platform.settlement = {"success": False, "transaction": "", "errorReason": "settlement_failed"} + async with await Facilitator.create(platform.options()) as facilitator: + with pytest.raises(ValidationError): + await facilitator.settle(PAYLOAD, REQUIREMENTS) + + +@pytest.mark.parametrize( + "price,currency,amount", + [ + ("0", "USDC", "0"), + ("1.00000000", "USDC", "1000000"), + ("$1", "USDT", None), + ("1.00 USDC", "USDC", "1000000"), + ], +) +async def test_price_precision_and_explicit_currency( + price: str, currency: str, amount: str | None +) -> None: + async with await Seller.create(Platform().options()) as seller: + offers = await seller.offers(price, currency=currency, schemes=["exact"]) + if amount is None: + assert offers == [] + else: + assert wire_offer(offers[0])["price"]["amount"] == amount + + +@pytest.mark.parametrize( + "price", ["$1 USDC", "1.000000001 USDC", " 1 USDC", "1 USDC ", "NaN", "+1 USDC"] +) +async def test_malformed_price_is_not_coerced(price: str) -> None: + async with await Seller.create(Platform().options()) as seller: + with pytest.raises(ValueError): + await seller.offers(price) + + +async def test_mixed_assets_and_passthrough_registration() -> None: + platform = Platform() + platform.config["assets"].append(deepcopy(platform.config["assets"][0])) + platform.config["assets"][1].update( + blockchain="SOLANA", network="solana:test", assetTransferMethod="solana", permit2Proxy=None + ) + platform.config["assets"].append(deepcopy(platform.config["assets"][0])) + platform.config["wallets"].append( + {"blockchain": "SOLANA", "address": "solana-receiver", "feePayer": "payer"} + ) + async with await Seller.create(platform.options()) as seller: + offers = await seller.offers("$1") + assert len(offers) == 4 + assert offers[2].extra == { + "assetName": "USDC", + "name": "USDC", + "version": "2", + "assetTransferMethod": "solana", + "feePayer": "payer", + } + registrations = await seller.scheme_registrations(schemes=["balance"]) + assert len(registrations) == 1 + scheme = registrations[0]["server"] + assert scheme.default_asset_transfer_method == "default" + with pytest.raises(ValueError): + scheme.parse_price("$1", "inflow:1") + value = AssetAmount(asset="USDC", amount="1", extra={"nested": {"a": 1}}) + copied = scheme.parse_price(value, "inflow:1") + assert copied == value and copied is not value + all_schemes = await seller.scheme_registrations() + assert len(all_schemes) == 3 + assert set(all_schemes[0]["server"].payment_flows) == {"eip3009", "permit2"} + assert ( + scheme.enhance_payment_requirements( + REQUIREMENTS, + SupportedKind(scheme="balance", network="inflow:1", x402_version=2), + [], + ) + is not REQUIREMENTS + ) + assert await seller.scheme_registrations(schemes=[]) == [] + + +@pytest.mark.parametrize( + "update", [{"permit2Proxy": None}, {"network": "solana:test"}, {"permit2Proxy": "wrong"}] +) +async def test_permit2_only_does_not_emit_unsupported_onchain_offer( + update: dict[str, object], +) -> None: + platform = Platform() + platform.config["assets"][0].update(update) + async with await Seller.create(platform.options()) as seller: + route = await seller.route("$1", schemes=["exact"], permit2=True) + assert route.accepts == [] + assert route.extensions is None + assert await seller.offers("$1", schemes=["upto"]) == [] + + +async def test_declared_permit2_without_proxy_preserves_server_config() -> None: + config = SellerConfig.model_validate(CONFIG) + config.assets[0].asset_transfer_method = "permit2" + config.assets[0].permit2_proxy = None + offers = build_offers(config, "$1", schemes=["exact"]) + assert "permit2Proxy" not in (offers[0].extra or {}) + config.wallets[0].blockchain = "OTHER" + assert build_offers(config, "$1", schemes=["exact"]) == [] + + +async def test_sponsorship_never_declared_for_metered_routes() -> None: + case = next(case for case in CASES if case["id"] == "x402.seller.metered-explicit") + platform = Platform() + platform.config = deepcopy(case["input"]["config"]) + async with await Seller.create(platform.options()) as seller: + route = await seller.route("$1", schemes=["upto"]) + assert route.extensions is None + registrations = await seller.scheme_registrations(schemes=["upto"]) + assert len(registrations) == 1 + assert registrations[0]["server"].scheme == "upto" + + +@pytest.mark.parametrize( + "failure", ["missing-name", "missing-version", "wrong-kind", "wrong-proxy"] +) +async def test_sponsorship_requires_matching_domain_and_facilitator(failure: str) -> None: + case = next(case for case in CASES if case["id"] == "x402.seller.sponsorship-prefers-eip2612") + platform = Platform() + platform.config, platform.supported = ( + deepcopy(case["input"]["config"]), + deepcopy(case["input"]["supported"]), + ) + if failure == "missing-name": + platform.config["assets"][0]["tokenName"] = "" + elif failure == "missing-version": + platform.config["assets"][0]["tokenVersion"] = "" + elif failure == "wrong-kind": + platform.supported["kinds"][0]["x402Version"] = 1 + else: + platform.config["assets"][0]["permit2Proxy"] = "wrong" + platform.config["assets"][0]["supportsEip7702"] = False + async with await Seller.create(platform.options()) as seller: + assert (await seller.route("$1", schemes=["exact"], permit2=True)).extensions is None + + +async def test_sponsorship_schemas_are_independent_between_routes() -> None: + case = next(case for case in CASES if case["id"] == "x402.seller.sponsorship-prefers-eip2612") + platform = Platform() + platform.config = deepcopy(case["input"]["config"]) + platform.supported = deepcopy(case["input"]["supported"]) + async with await Seller.create(platform.options()) as seller: + first = await seller.route("$1", schemes=["exact"], permit2=True) + second = await seller.route("$1", schemes=["exact"], permit2=True) + assert first.extensions is not None + expected = deepcopy(second.extensions) + first.extensions["eip2612GasSponsoring"]["schema"]["properties"].clear() + assert second.extensions == expected + assert (await seller.route("$1", schemes=["exact"], permit2=True)).extensions == expected + + +async def test_setup_cancellation_closes_pending_requests() -> None: + platform = Platform() + platform.block = "config" + task = asyncio.create_task(Seller.create(platform.options())) + await platform.started.wait() + task.cancel() + with pytest.raises(asyncio.CancelledError): + await task + assert platform.closed + + +async def test_metered_fastapi_route_preserves_authorized_maximum() -> None: + case = next(case for case in CASES if case["id"] == "x402.seller.metered-explicit") + seller_platform, platform = Platform(), Platform() + seller_platform.config = deepcopy(case["input"]["config"]) + platform.supported = {"kinds": seller_platform.config["supported"]} + platform.settlement = { + "success": True, + "transaction": "tx", + "network": "eip155:8453", + "amount": "250000", + } + async with ( + await Seller.create(seller_platform.options()) as seller, + await Facilitator.create(platform.options()) as facilitator, + ): + resource = x402ResourceServer(facilitator) + for entry in await seller.scheme_registrations(schemes=["upto"]): + resource.register(entry["network"], entry["server"]) + route = await seller.route("$1", schemes=["upto"]) + app = FastAPI() + app.middleware("http")(payment_middleware({"GET /paid": route}, resource)) + + @app.get("/paid") + async def paid() -> JSONResponse: + response = JSONResponse({"used": 250000}) + set_settlement_overrides(response, {"amount": "250000"}) + return response + + async with httpx.AsyncClient( + transport=httpx.ASGITransport(app), base_url="https://seller.test" + ) as http: + challenge = await http.get("/paid") + assert challenge.status_code == 402 + required = json.loads(base64.b64decode(challenge.headers["payment-required"])) + payload = PaymentPayload( + accepted=PaymentRequirements.model_validate(required["accepts"][0]), + payload={"signature": "test-signature"}, + ) + response = await http.get( + "/paid", + headers={ + "payment-signature": base64.b64encode( + payload.model_dump_json(exclude_none=True).encode() + ).decode() + }, + ) + assert response.status_code == 200 + sent = [json.loads(r.content) for r in platform.requests if r.method == "POST"] + assert len(sent) == 2 + assert sent[0]["paymentRequirements"]["amount"] == "1000000" + assert sent[1]["paymentRequirements"]["amount"] == "250000" + assert sent[1]["paymentPayload"]["accepted"]["amount"] == "1000000" + assert sent[0]["paymentPayload"]["extensions"] == sent[1]["paymentPayload"]["extensions"] + assert not any("override" in name for name in response.headers) + + +@pytest.mark.parametrize( + "outcome", ["success", "verify-reject", "settle-reject", "handler-error", "handler-throws"] +) +async def test_actual_fastapi_payment_flow(outcome: str) -> None: + platform = Platform() + if outcome == "verify-reject": + platform.verification = {"isValid": False, "invalidReason": "invalid_payload"} + if outcome == "settle-reject": + platform.settlement = { + "success": False, + "errorReason": "settlement_failed", + "transaction": "", + "network": "inflow:1", + } + async with ( + await Seller.create(Platform().options()) as seller, + await Facilitator.create(platform.options()) as facilitator, + ): + resource = x402ResourceServer(facilitator) + for registration in await seller.scheme_registrations(): + resource.register(registration["network"], registration["server"]) + route = RouteConfig(accepts=await seller.offers("$1", schemes=["balance"])) + app = FastAPI() + app.middleware("http")(payment_middleware({"GET /paid": route}, resource)) + called = [] + + @app.get("/paid") + async def paid(request: Request) -> JSONResponse: + called.append(request.state.payment_requirements) + if outcome == "handler-throws": + raise ValueError("handler failed") + return JSONResponse( + {"paid": True}, status_code=500 if outcome == "handler-error" else 200 + ) + + async with httpx.AsyncClient( + transport=httpx.ASGITransport(app, raise_app_exceptions=False), + base_url="https://seller.example", + ) as http: + challenge = await http.get("/paid") + assert challenge.status_code == 402 + wire = json.loads(base64.b64decode(challenge.headers["payment-required"])) + selected = PaymentRequirements.model_validate(wire["accepts"][0]) + payload = PaymentPayload(accepted=selected, payload={"transactionId": "tx"}) + encoded = base64.b64encode( + json.dumps(payload.model_dump(exclude_none=True)).encode() + ).decode() + response = await http.get("/paid", headers={"payment-signature": encoded}) + assert ( + response.status_code + == { + "success": 200, + "verify-reject": 402, + "settle-reject": 402, + "handler-error": 500, + "handler-throws": 500, + }[outcome] + ) + assert len(called) == (0 if outcome == "verify-reject" else 1) + posts = [r.url.path for r in platform.requests if r.method == "POST"] + assert posts == ["/v1/x402/verify"] + ( + ["/v1/x402/settle"] if outcome in ("success", "settle-reject") else [] + ) + if outcome in ("success", "settle-reject"): + receipt = json.loads(base64.b64decode(response.headers["payment-response"])) + assert receipt["network"] == "inflow:1" + assert receipt["success"] is (outcome == "success")