solvapay 0.1.0.dev4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
solvapay/solvapay.py ADDED
@@ -0,0 +1,173 @@
1
+ """``create_solvapay()``: the SDK's entry point.
2
+
3
+ ```python
4
+ from solvapay import create_solvapay
5
+
6
+ solvapay = create_solvapay() # reads SOLVAPAY_SECRET_KEY
7
+
8
+ @solvapay.payable(product="prd_…")
9
+ async def summarise(text: str, *, customer_ref: str) -> str:
10
+ ...
11
+ ```
12
+
13
+ Configuration comes from arguments, else the environment, the same
14
+ variables the TypeScript SDK reads:
15
+
16
+ - ``SOLVAPAY_SECRET_KEY`` (required): the merchant's secret key.
17
+ - ``SOLVAPAY_API_BASE_URL``: defaults to ``https://api.solvapay.com``.
18
+ - ``SOLVAPAY_PRODUCT_REF``: the product a paid call is for, when not given.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import functools
24
+ import inspect
25
+ import os
26
+ from collections.abc import Awaitable, Callable
27
+ from typing import Any, ParamSpec, TypeVar
28
+
29
+ from ._http import DEFAULT_API_BASE_URL, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT_SECONDS
30
+ from .client import AsyncApiClient
31
+ from .errors import SolvaPayError
32
+ from .paywall.decide import (
33
+ DEFAULT_LIMITS_CACHE_TTL_SECONDS,
34
+ FreeLimit,
35
+ Paywall,
36
+ PaywallDecision,
37
+ PaywallError,
38
+ )
39
+
40
+ P = ParamSpec("P")
41
+ R = TypeVar("R")
42
+
43
+
44
+ class SolvaPay:
45
+ """The API client and the paywall for one merchant key."""
46
+
47
+ def __init__(self, *, api: AsyncApiClient, product_ref: str | None = None, **paywall: Any):
48
+ self.api = api
49
+ self.product_ref = product_ref
50
+ self.paywall = Paywall(api, **paywall)
51
+
52
+ def require_product(self, product: str | None) -> str:
53
+ """The product for a paid call: the one given, else ``SOLVAPAY_PRODUCT_REF``."""
54
+ resolved = product or self.product_ref
55
+ if not resolved:
56
+ raise SolvaPayError(
57
+ "No product: pass product=… or set SOLVAPAY_PRODUCT_REF.",
58
+ code="missing_product",
59
+ )
60
+ return resolved
61
+
62
+ async def gate(
63
+ self,
64
+ *,
65
+ customer_ref: str,
66
+ product: str | None = None,
67
+ meter: str | None = None,
68
+ free_limit: FreeLimit | None = None,
69
+ tool_name: str | None = None,
70
+ ) -> PaywallDecision:
71
+ """``allow`` or ``gate`` for one paid call, for a handler you run yourself.
72
+
73
+ After an ``allow``, record the outcome with ``paywall.run_allow`` (or
74
+ call ``paywall.track_usage`` yourself).
75
+ """
76
+ return await self.paywall.decide(
77
+ customer_ref=customer_ref,
78
+ product=self.require_product(product),
79
+ meter=meter,
80
+ free_limit=free_limit,
81
+ tool_name=tool_name,
82
+ )
83
+
84
+ def payable(
85
+ self,
86
+ *,
87
+ product: str | None = None,
88
+ meter: str | None = None,
89
+ free_limit: FreeLimit | None = None,
90
+ customer_ref_arg: str = "customer_ref",
91
+ ) -> Callable[[Callable[P, Awaitable[R]]], Callable[P, Awaitable[R]]]:
92
+ """Makes an async function paid. Raises ``PaywallError`` (with ``.gate``) when gated.
93
+
94
+ The customer is read from the keyword argument named ``customer_ref_arg``.
95
+ """
96
+ resolved_product = self.require_product(product)
97
+
98
+ def wrap(handler: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[R]]:
99
+ if not inspect.iscoroutinefunction(handler):
100
+ raise SolvaPayError("payable() wraps async functions", code="invalid_argument")
101
+
102
+ @functools.wraps(handler)
103
+ async def paid(*args: P.args, **kwargs: P.kwargs) -> R:
104
+ customer_ref = kwargs.get(customer_ref_arg)
105
+ if not isinstance(customer_ref, str) or not customer_ref:
106
+ raise SolvaPayError(
107
+ f"{handler.__name__}() was called without {customer_ref_arg}=…",
108
+ code="identity_required",
109
+ status=401,
110
+ )
111
+ decision = await self.paywall.decide(
112
+ customer_ref=customer_ref,
113
+ product=resolved_product,
114
+ meter=meter,
115
+ free_limit=free_limit,
116
+ tool_name=handler.__name__,
117
+ )
118
+ if decision.gate is not None:
119
+ raise PaywallError(decision.gate)
120
+ return await self.paywall.run_allow(
121
+ decision,
122
+ lambda: handler(*args, **kwargs),
123
+ product=resolved_product,
124
+ meter=meter,
125
+ free_limit=free_limit,
126
+ tool_name=handler.__name__,
127
+ )
128
+
129
+ return paid
130
+
131
+ return wrap
132
+
133
+ async def aclose(self) -> None:
134
+ await self.api.aclose()
135
+
136
+ async def __aenter__(self) -> SolvaPay:
137
+ return self
138
+
139
+ async def __aexit__(self, *_: object) -> None:
140
+ await self.aclose()
141
+
142
+
143
+ def create_solvapay(
144
+ *,
145
+ api_key: str | None = None,
146
+ api_base_url: str | None = None,
147
+ product_ref: str | None = None,
148
+ timeout: float = DEFAULT_TIMEOUT_SECONDS,
149
+ max_retries: int = DEFAULT_MAX_RETRIES,
150
+ limits_cache_ttl: float = DEFAULT_LIMITS_CACHE_TTL_SECONDS,
151
+ api: AsyncApiClient | None = None,
152
+ ) -> SolvaPay:
153
+ """A ``SolvaPay`` from arguments, else the environment. Fails without a secret key."""
154
+ if api is None:
155
+ key = api_key or os.environ.get("SOLVAPAY_SECRET_KEY")
156
+ if not key:
157
+ raise SolvaPayError(
158
+ "A SolvaPay secret key is required: pass api_key=… or set SOLVAPAY_SECRET_KEY.",
159
+ code="missing_secret",
160
+ )
161
+ api = AsyncApiClient(
162
+ api_key=key,
163
+ api_base_url=api_base_url
164
+ or os.environ.get("SOLVAPAY_API_BASE_URL")
165
+ or DEFAULT_API_BASE_URL,
166
+ timeout=timeout,
167
+ max_retries=max_retries,
168
+ )
169
+ return SolvaPay(
170
+ api=api,
171
+ product_ref=product_ref or os.environ.get("SOLVAPAY_PRODUCT_REF") or None,
172
+ limits_cache_ttl=limits_cache_ttl,
173
+ )
solvapay/webhooks.py ADDED
@@ -0,0 +1,93 @@
1
+ """Webhook signature verification, matching ``@solvapay/server``'s ``verifyWebhook``.
2
+
3
+ SolvaPay signs a delivery as HMAC-SHA256 over ``"{t}.{raw body}"``, keyed by
4
+ the whole signing secret (``whsec_…``, UTF-8), and sends it in the
5
+ ``SV-Signature`` header as ``t={unix seconds},v1={lowercase hex}``.
6
+
7
+ ```python
8
+ from solvapay import verify_webhook
9
+
10
+ event = verify_webhook(
11
+ body=request.body, # the raw bytes or text, as received
12
+ signature=request.headers["sv-signature"],
13
+ secret=os.environ["SOLVAPAY_WEBHOOK_SECRET"],
14
+ )
15
+ ```
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import hashlib
21
+ import hmac
22
+ import json
23
+ import re
24
+ import time
25
+ from datetime import datetime
26
+ from typing import Any, Final
27
+
28
+ from .errors import SolvaPayError
29
+
30
+ WEBHOOK_TOLERANCE_SECONDS: Final = 300
31
+
32
+ WEBHOOK_ERROR_CODES: Final = {
33
+ "missing": "webhook_signature_missing",
34
+ "malformed": "webhook_signature_malformed",
35
+ "timestamp": "webhook_timestamp_out_of_tolerance",
36
+ "invalid": "webhook_signature_invalid",
37
+ "payload": "webhook_payload_invalid",
38
+ }
39
+
40
+
41
+ def _reject_constant(name: str) -> Any:
42
+ # JSON has no NaN or Infinity; json.loads accepts them, JSON.parse does not.
43
+ raise ValueError(f"{name} is not valid JSON")
44
+
45
+
46
+ def verify_webhook(
47
+ *,
48
+ body: str | bytes,
49
+ signature: str | None,
50
+ secret: str,
51
+ now: datetime | None = None,
52
+ ) -> Any:
53
+ """Verifies a delivery and returns the parsed event.
54
+
55
+ Raises ``SolvaPayError`` whose ``code`` is one of ``WEBHOOK_ERROR_CODES``.
56
+ ``now`` is the time to check the signature's timestamp against
57
+ (default: the current time); a timestamp more than 300 seconds away,
58
+ either way, is rejected.
59
+ """
60
+ raw = body if isinstance(body, bytes) else body.encode("utf-8")
61
+ if not signature:
62
+ raise SolvaPayError("Missing webhook signature", code=WEBHOOK_ERROR_CODES["missing"])
63
+
64
+ parts = [part.strip() for part in signature.split(",")]
65
+ t_part = next((p for p in parts if p.startswith("t=")), None)
66
+ v1_part = next((p for p in parts if p.startswith("v1=")), None)
67
+ if t_part is None or v1_part is None:
68
+ raise SolvaPayError("Malformed webhook signature", code=WEBHOOK_ERROR_CODES["malformed"])
69
+ raw_timestamp, received = t_part[2:], v1_part[3:]
70
+ if not re.fullmatch(r"\d+", raw_timestamp, flags=re.ASCII) or not received:
71
+ raise SolvaPayError("Malformed webhook signature", code=WEBHOOK_ERROR_CODES["malformed"])
72
+
73
+ now_seconds = int(now.timestamp()) if now is not None else int(time.time())
74
+ if abs(now_seconds - int(raw_timestamp)) > WEBHOOK_TOLERANCE_SECONDS:
75
+ raise SolvaPayError(
76
+ "Webhook signature timestamp is more than "
77
+ f"{WEBHOOK_TOLERANCE_SECONDS} seconds from now",
78
+ code=WEBHOOK_ERROR_CODES["timestamp"],
79
+ )
80
+
81
+ expected = hmac.new(
82
+ secret.encode("utf-8"), raw_timestamp.encode("ascii") + b"." + raw, hashlib.sha256
83
+ ).hexdigest()
84
+ # compare_digest takes ASCII text only; anything else cannot be a hex digest.
85
+ if not received.isascii() or not hmac.compare_digest(expected, received):
86
+ raise SolvaPayError("Invalid webhook signature", code=WEBHOOK_ERROR_CODES["invalid"])
87
+
88
+ try:
89
+ return json.loads(raw, parse_constant=_reject_constant)
90
+ except ValueError as error:
91
+ raise SolvaPayError(
92
+ "Invalid webhook payload: body is not valid JSON", code=WEBHOOK_ERROR_CODES["payload"]
93
+ ) from error
@@ -0,0 +1,183 @@
1
+ Metadata-Version: 2.5
2
+ Name: solvapay
3
+ Version: 0.1.0.dev4
4
+ Summary: SolvaPay for Python: paywalls, usage billing, webhooks and paid MCP tools.
5
+ Project-URL: Homepage, https://solvapay.com
6
+ Project-URL: Documentation, https://docs.solvapay.com
7
+ Project-URL: Source, https://github.com/solvapay/solvapay-sdk/tree/main/python
8
+ Author: SolvaPay
9
+ License-Expression: MIT
10
+ Keywords: agents,billing,mcp,payments,paywall,solvapay
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: httpx<1,>=0.27
21
+ Requires-Dist: pydantic<3,>=2.7
22
+ Provides-Extra: agents
23
+ Requires-Dist: pyjwt[crypto]<3,>=2.9; extra == 'agents'
24
+ Provides-Extra: mcp
25
+ Requires-Dist: mcp<3,>=2.3; extra == 'mcp'
26
+ Requires-Dist: starlette>=0.40; extra == 'mcp'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # solvapay
30
+
31
+ SolvaPay for Python: paywalls, usage billing, webhooks and paid MCP tools.
32
+
33
+ ```bash
34
+ pip install solvapay # API client, paywall, webhooks
35
+ pip install 'solvapay[mcp]' # paid tools for the official MCP Python SDK
36
+ ```
37
+
38
+ Python 3.10+. Typed (`py.typed`), async-first, with blocking twins for the API client.
39
+
40
+ ## Configure
41
+
42
+ ```bash
43
+ export SOLVAPAY_SECRET_KEY=sk_sandbox_... # required
44
+ export SOLVAPAY_PRODUCT_REF=prd_... # the product paid calls bill against
45
+ export SOLVAPAY_API_BASE_URL=... # optional; defaults to https://api.solvapay.com
46
+ ```
47
+
48
+ Run `npx solvapay init` to sign in and write these to `.env`.
49
+
50
+ ## Make a function paid
51
+
52
+ ```python
53
+ from solvapay import PaywallError, create_solvapay
54
+
55
+ solvapay = create_solvapay()
56
+
57
+
58
+ @solvapay.payable()
59
+ async def summarise(text: str, *, customer_ref: str) -> str:
60
+ return text[:100]
61
+
62
+
63
+ try:
64
+ summary = await summarise("...", customer_ref="user_123")
65
+ except PaywallError as error:
66
+ print(error.gate["message"]) # what the customer should see, with a checkout link
67
+ ```
68
+
69
+ `customer_ref` is your own user id: SolvaPay creates the customer on first use.
70
+ Each call is checked against the customer's plan, and billed when it succeeds.
71
+
72
+ ## Paid MCP tools
73
+
74
+ ```python
75
+ from solvapay import create_solvapay
76
+ from solvapay.mcp import ResponseContext, create_solvapay_mcp_server
77
+
78
+ mcp = create_solvapay_mcp_server(
79
+ solvapay=create_solvapay(),
80
+ public_base_url="https://research.example.com",
81
+ )
82
+
83
+
84
+ async def quote(symbol: str, ctx: ResponseContext):
85
+ return ctx.respond({"symbol": symbol, "price": 101.5})
86
+
87
+
88
+ mcp.register_payable("quote", handler=quote, description="Latest price for a symbol.")
89
+
90
+ app = mcp.http_app() # serve with uvicorn; the MCP endpoint is /mcp
91
+ ```
92
+
93
+ Customers sign in with SolvaPay over OAuth, and every bearer token is verified
94
+ with SolvaPay. A call over the customer's limit returns the paywall: a message
95
+ naming the next step, with a checkout link. See
96
+ [`examples/python-stock-research-mcp`](../examples/python-stock-research-mcp).
97
+
98
+ The server also comes with SolvaPay's built-in tools, as the TypeScript server
99
+ does: `account` shows the customer's plan, balance and usage with a checkout,
100
+ top-up or account link (`view`: `checkout`, `account` or `topup`), and
101
+ `activate_plan` activates a plan by `planRef`. The `/upgrade`,
102
+ `/manage_account`, `/topup` and `/activate_plan` prompts and the
103
+ `docs://solvapay/overview.md` resource come with them. Pass `views=[...]` to
104
+ choose the views, or `views=[]` to leave `account` out.
105
+
106
+ ### The checkout widget
107
+
108
+ On hosts that support MCP Apps (ChatGPT, Claude), `account` also opens
109
+ SolvaPay's checkout and account widget in the chat, the same one the TypeScript
110
+ SDK serves. It ships in this package (`solvapay/mcp/mcp-app.html`) and is
111
+ served at `ui://<name>/mcp-app.html` (`resource_uri=` to change it). Text-only
112
+ hosts get the same text as before; `mode: "text"` asks for text on any host.
113
+
114
+ The widget pays, tops up, cancels and reactivates renewals through six tools
115
+ only it calls: `create_payment_intent`, `process_payment`,
116
+ `create_hosted_session`, `set_renewal`, `attach_business_details` and
117
+ `get_history`. They are marked for the app (`_meta.ui.visibility: ["app"]`), so
118
+ MCP Apps hosts keep them from the model. For a host that lists them anyway,
119
+ `hide_tools_by_audience=["ui"]` leaves them out of `tools/list`; ChatGPT still
120
+ gets them, since its widget can only call listed tools.
121
+
122
+ Its Content Security Policy allows Stripe and the SolvaPay API. Add domains with
123
+ `csp={"connectDomains": [...], "resourceDomains": [...], "frameDomains": [...]}`.
124
+
125
+ ### Paid by agents
126
+
127
+ `solvapay.agents.mcp.create_agent_mcp_route` serves an MCP server that SolvaPay
128
+ agents pay for per call: each paid tool call is authorized with SolvaPay before
129
+ it runs and completed at the cost the upstream reports. Install
130
+ `solvapay[agents,mcp]`.
131
+
132
+ ## Verify webhooks
133
+
134
+ ```python
135
+ from solvapay import SolvaPayError, verify_webhook
136
+
137
+ try:
138
+ event = verify_webhook(
139
+ body=raw_body, # bytes or str, exactly as received
140
+ signature=request.headers["sv-signature"],
141
+ secret=os.environ["SOLVAPAY_WEBHOOK_SECRET"],
142
+ )
143
+ except SolvaPayError as error:
144
+ ... # error.code: webhook_signature_invalid, webhook_timestamp_out_of_tolerance, ...
145
+ ```
146
+
147
+ ## The API
148
+
149
+ Every SolvaPay API operation, generated from the API spec:
150
+
151
+ ```python
152
+ from solvapay import AsyncApiClient
153
+
154
+ async with AsyncApiClient(api_key="sk_sandbox_...") as api:
155
+ customer = await api.get_customer(reference="cus_123")
156
+ balance = await api.get_customer_balance(reference="cus_123")
157
+ ```
158
+
159
+ `ApiClient` is the blocking twin. Errors are `SolvaPayError` with a stable `code`
160
+ and the HTTP `status`; branch on those, not on the message.
161
+
162
+ ## Same behaviour as the TypeScript SDK
163
+
164
+ The paywall, its messages, the `account` and `activate_plan` narration, what
165
+ the widget's tools answer and webhook verification are checked against the
166
+ TypeScript SDK on every change by a shared conformance suite
167
+ ([`conformance/`](../conformance)). The API client is generated from the same
168
+ spec as the TypeScript types ([`openapi/`](../openapi)).
169
+
170
+ ## Versions
171
+
172
+ `solvapay` has its own version line, starting at 0.1.0. Its numbers don't
173
+ follow the TypeScript packages: Python 0.3.0 and `@solvapay/server` 2.12.0 can
174
+ behave the same. What the two share is the conformance suite (the version this
175
+ package passes is pinned in [`conformance.lock`](conformance.lock)) and the API
176
+ spec.
177
+
178
+ Until 1.0.0, a minor release (0.x → 0.y) may change the API. Previews of the
179
+ next release are published from the `dev` branch as `<next>.devN`:
180
+
181
+ ```bash
182
+ pip install --pre solvapay
183
+ ```
@@ -0,0 +1,39 @@
1
+ solvapay/__init__.py,sha256=p39j5ZvKms5SRKhchZHusWaRaK2n-9SKHP1WUaCb-tc,755
2
+ solvapay/_currencies.py,sha256=GdDf4FG2s5_OEmd2Gv8hOmfbk_epImkBT4juaxkNAGc,3417
3
+ solvapay/_http.py,sha256=WK6OT90Mro6wpsKZlh8t75T9wQf4qgCqB2VaVaGJr0w,8404
4
+ solvapay/_js.py,sha256=u-C1Y5YqLDWR2iSnC0YyifYCUdqB6AjTRsKuirVH4Q0,3489
5
+ solvapay/_tax_ids.py,sha256=D0TVr3Gk4Qy6nNG-wAS7X-QFxZ6Cl20oJU6-uEJlXk8,11234
6
+ solvapay/business_details.py,sha256=qmRhTc_xwvT53ZXz-J6RNBI_s7Uj7mnXEkqvJtLwqNg,4343
7
+ solvapay/client.py,sha256=be8XfsHLRDo1YY4KLgvcbDVibTquZ0lCY4OgE1BhTKg,2753
8
+ solvapay/errors.py,sha256=QovGghgtkGs4s1ipFlpoD0lKZwnwKKkbXt0iF9h6L5A,821
9
+ solvapay/money.py,sha256=D9EIJfGUYj0w45wmkEeZadeBI8vQ2nY9lsSi2YQ4mmA,2157
10
+ solvapay/pricing.py,sha256=2EWL2HZe5ZUYvlaOO2onLwyuswYJUiFMLdsvY0sdkOw,10375
11
+ solvapay/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ solvapay/solvapay.py,sha256=OW5XNGBRfJOPy2nDADp-c7UBOx09UaHxoKUqjutsxbo,5867
13
+ solvapay/webhooks.py,sha256=uF1RCiNo0vlNHYTbm_6jEjWZd5J5echgu2Xgg8EGP3Y,3445
14
+ solvapay/_generated/__init__.py,sha256=ocuVKIf-7sVBn7gvvPlDy1_hkPNm8UKRSzbqOjE5y-Y,92
15
+ solvapay/_generated/models.py,sha256=7ISI0RixUpotXYO-rzUKdzHSANBuB3Noow8c3w82Y8s,108970
16
+ solvapay/_generated/operations.py,sha256=xZ5chS9Dr8GHfvdo-FtrWxxNmOBycnhULnrTflEkxhI,57992
17
+ solvapay/agents/__init__.py,sha256=7YB_yxDQOpoOoh6cgBzGjaVJzdeOGpveyCojnvCyHYU,1425
18
+ solvapay/agents/calls.py,sha256=mVfaTGilCCiyijR7AzcH5qaZf-VMFM_pNALGaQBPj3Q,11473
19
+ solvapay/agents/identity.py,sha256=TzSSuw2mynOPRk1fsvW4aDax5Cnj8TqUTv2UUCs1v_M,11090
20
+ solvapay/agents/mcp.py,sha256=2Duibu9OuKH3gmNbpcNd_JxpGEmll1EVZOYJTJDDLg8,23079
21
+ solvapay/agents/oauth.py,sha256=_dZObCLSMKDQ0b99lgewFnqbWVZZ-1oi49XKhvz4ywY,4280
22
+ solvapay/mcp/__init__.py,sha256=HP7jierJQsUgk5mZO_CpLF89n6BkeVEkNEpP9piajUc,2301
23
+ solvapay/mcp/app.py,sha256=mukkCleekLGhnB0VfBqwtRFr0-RU-IiwOI4JvwnJMow,9241
24
+ solvapay/mcp/auth.py,sha256=izhI8JEiZFFAb259AsiOZQk1PSMkMSwLxJnmbzTynEE,23479
25
+ solvapay/mcp/bootstrap.py,sha256=T_NkpcMejFy1oC5des_McghFS4Khyp05_vDQ0AVOtHU,18196
26
+ solvapay/mcp/intent.py,sha256=ccumggLaMOj4tAUZVraua5dlp56EU8qc3zYNqThF8-Q,19196
27
+ solvapay/mcp/mcp-app.html,sha256=4IbDS93ib2B7c-Ehym2god2ZvAIvq-U217cvPzxfj9o,848783
28
+ solvapay/mcp/narrate.py,sha256=kDrsqSqPxrybPGO9Pr0OBNt81rNbVYwhGWxyGS87mDs,36358
29
+ solvapay/mcp/response.py,sha256=kiSFWAa0IXlvd-EaUaCGBkE56fh9Di8smzLR5EK-1qA,7497
30
+ solvapay/mcp/server.py,sha256=LupoijalrC6Cy0JHeijeXWhUAUKyIWNdawMmP6XkKEc,12901
31
+ solvapay/mcp/tools.py,sha256=FBRtIcFXQvCB-wZLCxgn1ad9cxB0_4tacFsDcq0MDeM,9847
32
+ solvapay/mcp/transport.py,sha256=4XYyhqqboOfbHVVUPMoi_U1dSeMOJUKKHKr7TS4kDXM,21477
33
+ solvapay/paywall/__init__.py,sha256=aIxL6iS3DLkwOcewOu0XMIJM8DCzjQastIbmme2oH6g,591
34
+ solvapay/paywall/decide.py,sha256=mcEG563R621B63e4D9OnV4TQpwr79DN9JERWvE6elsE,16697
35
+ solvapay/paywall/gate.py,sha256=oFNFpOvhYg5m1gRgLsttqfdSV7YvEtOzvj3cSPzQk7E,6001
36
+ solvapay/paywall/state.py,sha256=xqwziDE9EisPpAcC9to0EjG4flE1ijXaC62KohdZGwA,14135
37
+ solvapay-0.1.0.dev4.dist-info/METADATA,sha256=kcm5FkyqbD3TQcmPPrRegQxC6syi0V2MtswVbk7U440,6738
38
+ solvapay-0.1.0.dev4.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
39
+ solvapay-0.1.0.dev4.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any