voidly-pay 2.0.2__tar.gz → 3.0.0__tar.gz

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.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voidly-pay
3
- Version: 2.0.2
4
- Summary: Voidly Pay SDK — open marketplace for AI-agent payments. claim() + request_with_pay() handle x402 end-to-end. USDC-backed Sourcify-verified vault on Base mainnet.
3
+ Version: 3.0.0
4
+ Summary: voidpay SDK — zero-value internal test-credit tools for agent-to-agent transfers and x402 integration. Credits are not backed or redeemable.
5
5
  Author-email: Voidly <team@voidly.ai>
6
6
  License: MIT
7
7
  Keywords: voidly,payments,ai-agents,x402,did,ed25519
@@ -35,7 +35,7 @@ pay = VoidlyPay() # mints + persists keypair
35
35
  print("DID:", pay.did) # did:voidly:...
36
36
  pay.faucet() # 10 free credits
37
37
 
38
- # Browse the marketplace — 17 paid endpoints + N third-party listings
38
+ # Browse the marketplace — 12 paid endpoints + N third-party listings
39
39
  mp = pay.request("GET", "/v1/pay/marketplace").json()
40
40
  for item in mp["items"][:5]:
41
41
  print(f"{item['name']:40s} ${item['pricing']['amount_usdc']}")
@@ -123,11 +123,17 @@ def expensive():
123
123
  | **Streams (per-token billing)** | `pay.open_stream(...)`, `pay.meter_stream(...)`, `pay.finalize_stream(...)` |
124
124
  | **Subscriptions** | `pay.subscribe(...)`, `pay.cancel_subscription(...)` |
125
125
  | **x402 server-side quote** | `pay.create_quote(resource, amount)` |
126
- | **x402 server-side verify** | `pay.verify_payment(quote_id, transfer_id)` |
126
+ | **x402 server-side verify** | `pay.verify_payment(payment_header=header, payer_proof=proof, expected_resource=resource, expected_method=method, min_amount=price, expected_metadata=metadata)` |
127
127
  | **Webhooks** | `pay.subscribe_webhook(url, events=[...])` |
128
128
  | **Trust check** | `pay.health_check()` (6-check report incl. on-chain vault read) |
129
129
 
130
- ## What's in the marketplace today (Voidly's 17 paid endpoints)
130
+ ## What's in the marketplace today (Voidly's 12 paid endpoints)
131
+
132
+ Voidly does not sell its censorship data. The dataset is free and CC BY 4.0.
133
+ Every endpoint below charges for compute, or for an Ed25519 signature over data
134
+ that is itself free — never for the observatory. Five SKUs that failed that test
135
+ were retired on 2026-08-04 and now return HTTP 410 naming their free
136
+ replacement; see [voidly.ai/pay/changelog](https://voidly.ai/pay/changelog).
131
137
 
132
138
  | Endpoint | Price | What it does |
133
139
  |---|---|---|
@@ -141,13 +147,19 @@ def expensive():
141
147
  | `voidly_meta` | $0.001 | URL metadata (og + title + canonical) |
142
148
  | `voidly_extract` | $0.01 | PDF/document → plain text |
143
149
  | `voidly_scrape` | $0.01 | Fetch any URL + Voidly-signed receipt |
144
- | `voidly_fetch` | $0.05 | Country-pinned fetch via 37+ probe network |
145
- | `probe_attest` | $0.005 | Multi-vantage signed reachability proof |
146
- | `forecast_pro` | $0.01 | 30-day country-shutdown risk forecast |
147
- | `claim_verify_pro` | $0.005 | Evidence-backed verification of claims |
148
- | `incident_summary_pro` | $0.005 | Plain-English summary of an incident |
149
- | `agent_discover_pro` | $0.005 | Premium ranked agent search |
150
- | `incidents_export_pro` | $0.05 | Bulk export (high limit, no rate cap) |
150
+ | `probe_attest` | $0.005 | Ed25519 attestation over probe reachability data. The data is free at `/v1/probe/domain/{host}`; the charge is the signature. |
151
+ | `agent_discover_pro` | $0.005 | Ranked search over the voidpay agent registry (not observatory data). Free 20-result search at `/v1/agent/discover`. |
152
+
153
+ Retired 2026-08-04 — these now return **HTTP 410 Gone** with the free
154
+ replacement URL in the body. Do not route to them:
155
+
156
+ | Retired | Free replacement |
157
+ |---|---|
158
+ | `voidly_fetch` / `POST /v1/pay/fetch` | `voidly_scrape` — the country-pinning was never implemented, so every call was already a Cloudflare-edge fetch |
159
+ | `forecast_pro` | `GET /v1/forecast/{cc}/multi-horizon` (free, genuinely modelled) |
160
+ | `claim_verify_pro` | `POST /verify-claim` (free) |
161
+ | `incident_summary_pro` | `GET /data/incidents/{id}/report?format=markdown` (free) |
162
+ | `incidents_export_pro` | `GET /data/incidents/export?format=csv` (free, uncapped) |
151
163
 
152
164
  Plus N third-party listings registered self-serve at [voidly.ai/pay/list-your-service](https://voidly.ai/pay/list-your-service).
153
165
 
@@ -178,17 +190,17 @@ ok = verify_webhook_signature(
178
190
 
179
191
  ## Why agents use this
180
192
 
181
- | Problem | Voidly Pay solves it |
193
+ | Problem | voidpay solves it |
182
194
  |---|---|
183
195
  | **Need to add payment to your agent service** | x402 middleware ships for FastAPI, Flask, any web-fetch handler |
184
- | **Need to discover paid services** | One install → 17 endpoints + open marketplace listings |
196
+ | **Need to discover paid services** | One install → 12 endpoints + open marketplace listings |
185
197
  | **Don't want to manage 10 API keys** | One Ed25519 keypair, one wallet, every paid endpoint works |
186
198
  | **Don't trust the agent's payment claims** | Every receipt is Ed25519-signed by Voidly. Verifiable offline. |
187
- | **Need country-attested fetch** | 37+ probe network, signed (URL, country, ASN, probe-DID) |
199
+ | **Need censorship data** | It is free. `GET /data/incidents/export?format=csv`, CC BY 4.0, no wallet, no key. Voidly does not sell it. |
188
200
 
189
201
  ## Honest disclosure
190
202
 
191
- The Voidly Pay vault on Base mainnet (`0xd25d3c6f32886b65356cc5c700382a8a02d84df5`, [Sourcify-verified](https://repo.sourcify.dev/contracts/full_match/8453/0xd25d3c6f32886b65356cc5c700382a8a02d84df5/)) currently holds **$4 USDC**. We have approximately zero sustained external paying users yet. Live reserves at [voidly.ai/pay/proof](https://voidly.ai/pay/proof).
203
+ **Stage 2 is being retired** (decided 2026-08-04). Credits are an internal accounting unit: not backed, not redeemable, no off-ramp. The vault on Base mainnet (`0xd25d3c6f32886b65356cc5c700382a8a02d84df5`, [Sourcify-verified](https://repo.sourcify.dev/contracts/full_match/8453/0xd25d3c6f32886b65356cc5c700382a8a02d84df5/)) holds 4.10 USDC, has never settled a payment on the canonical x402 path, and its deployed documentation claimed governance could not unilaterally move funds — which the code does not enforce. It stays on-chain as a public record at [voidly.ai/pay/proof](https://voidly.ai/pay/proof). We have approximately zero sustained external paying users.
192
204
 
193
205
  We opened the marketplace before the demand exists because we believe agent adoption is gated on discoverability, not on payment-rail UX.
194
206
 
@@ -207,9 +219,9 @@ We opened the marketplace before the demand exists because we believe agent adop
207
219
  - [/pay/marketplace](https://voidly.ai/pay/marketplace) — visual browse
208
220
  - [/pay/list-your-service](https://voidly.ai/pay/list-your-service) — list in 60s
209
221
  - [/pay/claim](https://voidly.ai/pay/claim) — free 10-credit faucet
210
- - [/pay/proof](https://voidly.ai/pay/proof) — live reserves dashboard
222
+ - [/pay/proof](https://voidly.ai/pay/proof) — vault record (not a reserve; Stage 2 is being retired)
211
223
  - [/pay/for-builders](https://voidly.ai/pay/for-builders) — every middleware + adapter
212
- - [Voidly Pay landing](https://voidly.ai/pay)
224
+ - [voidpay landing](https://voidly.ai/pay)
213
225
 
214
226
  ## Keywords
215
227
 
@@ -19,7 +19,7 @@ pay = VoidlyPay() # mints + persists keypair
19
19
  print("DID:", pay.did) # did:voidly:...
20
20
  pay.faucet() # 10 free credits
21
21
 
22
- # Browse the marketplace — 17 paid endpoints + N third-party listings
22
+ # Browse the marketplace — 12 paid endpoints + N third-party listings
23
23
  mp = pay.request("GET", "/v1/pay/marketplace").json()
24
24
  for item in mp["items"][:5]:
25
25
  print(f"{item['name']:40s} ${item['pricing']['amount_usdc']}")
@@ -107,11 +107,17 @@ def expensive():
107
107
  | **Streams (per-token billing)** | `pay.open_stream(...)`, `pay.meter_stream(...)`, `pay.finalize_stream(...)` |
108
108
  | **Subscriptions** | `pay.subscribe(...)`, `pay.cancel_subscription(...)` |
109
109
  | **x402 server-side quote** | `pay.create_quote(resource, amount)` |
110
- | **x402 server-side verify** | `pay.verify_payment(quote_id, transfer_id)` |
110
+ | **x402 server-side verify** | `pay.verify_payment(payment_header=header, payer_proof=proof, expected_resource=resource, expected_method=method, min_amount=price, expected_metadata=metadata)` |
111
111
  | **Webhooks** | `pay.subscribe_webhook(url, events=[...])` |
112
112
  | **Trust check** | `pay.health_check()` (6-check report incl. on-chain vault read) |
113
113
 
114
- ## What's in the marketplace today (Voidly's 17 paid endpoints)
114
+ ## What's in the marketplace today (Voidly's 12 paid endpoints)
115
+
116
+ Voidly does not sell its censorship data. The dataset is free and CC BY 4.0.
117
+ Every endpoint below charges for compute, or for an Ed25519 signature over data
118
+ that is itself free — never for the observatory. Five SKUs that failed that test
119
+ were retired on 2026-08-04 and now return HTTP 410 naming their free
120
+ replacement; see [voidly.ai/pay/changelog](https://voidly.ai/pay/changelog).
115
121
 
116
122
  | Endpoint | Price | What it does |
117
123
  |---|---|---|
@@ -125,13 +131,19 @@ def expensive():
125
131
  | `voidly_meta` | $0.001 | URL metadata (og + title + canonical) |
126
132
  | `voidly_extract` | $0.01 | PDF/document → plain text |
127
133
  | `voidly_scrape` | $0.01 | Fetch any URL + Voidly-signed receipt |
128
- | `voidly_fetch` | $0.05 | Country-pinned fetch via 37+ probe network |
129
- | `probe_attest` | $0.005 | Multi-vantage signed reachability proof |
130
- | `forecast_pro` | $0.01 | 30-day country-shutdown risk forecast |
131
- | `claim_verify_pro` | $0.005 | Evidence-backed verification of claims |
132
- | `incident_summary_pro` | $0.005 | Plain-English summary of an incident |
133
- | `agent_discover_pro` | $0.005 | Premium ranked agent search |
134
- | `incidents_export_pro` | $0.05 | Bulk export (high limit, no rate cap) |
134
+ | `probe_attest` | $0.005 | Ed25519 attestation over probe reachability data. The data is free at `/v1/probe/domain/{host}`; the charge is the signature. |
135
+ | `agent_discover_pro` | $0.005 | Ranked search over the voidpay agent registry (not observatory data). Free 20-result search at `/v1/agent/discover`. |
136
+
137
+ Retired 2026-08-04 — these now return **HTTP 410 Gone** with the free
138
+ replacement URL in the body. Do not route to them:
139
+
140
+ | Retired | Free replacement |
141
+ |---|---|
142
+ | `voidly_fetch` / `POST /v1/pay/fetch` | `voidly_scrape` — the country-pinning was never implemented, so every call was already a Cloudflare-edge fetch |
143
+ | `forecast_pro` | `GET /v1/forecast/{cc}/multi-horizon` (free, genuinely modelled) |
144
+ | `claim_verify_pro` | `POST /verify-claim` (free) |
145
+ | `incident_summary_pro` | `GET /data/incidents/{id}/report?format=markdown` (free) |
146
+ | `incidents_export_pro` | `GET /data/incidents/export?format=csv` (free, uncapped) |
135
147
 
136
148
  Plus N third-party listings registered self-serve at [voidly.ai/pay/list-your-service](https://voidly.ai/pay/list-your-service).
137
149
 
@@ -162,17 +174,17 @@ ok = verify_webhook_signature(
162
174
 
163
175
  ## Why agents use this
164
176
 
165
- | Problem | Voidly Pay solves it |
177
+ | Problem | voidpay solves it |
166
178
  |---|---|
167
179
  | **Need to add payment to your agent service** | x402 middleware ships for FastAPI, Flask, any web-fetch handler |
168
- | **Need to discover paid services** | One install → 17 endpoints + open marketplace listings |
180
+ | **Need to discover paid services** | One install → 12 endpoints + open marketplace listings |
169
181
  | **Don't want to manage 10 API keys** | One Ed25519 keypair, one wallet, every paid endpoint works |
170
182
  | **Don't trust the agent's payment claims** | Every receipt is Ed25519-signed by Voidly. Verifiable offline. |
171
- | **Need country-attested fetch** | 37+ probe network, signed (URL, country, ASN, probe-DID) |
183
+ | **Need censorship data** | It is free. `GET /data/incidents/export?format=csv`, CC BY 4.0, no wallet, no key. Voidly does not sell it. |
172
184
 
173
185
  ## Honest disclosure
174
186
 
175
- The Voidly Pay vault on Base mainnet (`0xd25d3c6f32886b65356cc5c700382a8a02d84df5`, [Sourcify-verified](https://repo.sourcify.dev/contracts/full_match/8453/0xd25d3c6f32886b65356cc5c700382a8a02d84df5/)) currently holds **$4 USDC**. We have approximately zero sustained external paying users yet. Live reserves at [voidly.ai/pay/proof](https://voidly.ai/pay/proof).
187
+ **Stage 2 is being retired** (decided 2026-08-04). Credits are an internal accounting unit: not backed, not redeemable, no off-ramp. The vault on Base mainnet (`0xd25d3c6f32886b65356cc5c700382a8a02d84df5`, [Sourcify-verified](https://repo.sourcify.dev/contracts/full_match/8453/0xd25d3c6f32886b65356cc5c700382a8a02d84df5/)) holds 4.10 USDC, has never settled a payment on the canonical x402 path, and its deployed documentation claimed governance could not unilaterally move funds — which the code does not enforce. It stays on-chain as a public record at [voidly.ai/pay/proof](https://voidly.ai/pay/proof). We have approximately zero sustained external paying users.
176
188
 
177
189
  We opened the marketplace before the demand exists because we believe agent adoption is gated on discoverability, not on payment-rail UX.
178
190
 
@@ -191,9 +203,9 @@ We opened the marketplace before the demand exists because we believe agent adop
191
203
  - [/pay/marketplace](https://voidly.ai/pay/marketplace) — visual browse
192
204
  - [/pay/list-your-service](https://voidly.ai/pay/list-your-service) — list in 60s
193
205
  - [/pay/claim](https://voidly.ai/pay/claim) — free 10-credit faucet
194
- - [/pay/proof](https://voidly.ai/pay/proof) — live reserves dashboard
206
+ - [/pay/proof](https://voidly.ai/pay/proof) — vault record (not a reserve; Stage 2 is being retired)
195
207
  - [/pay/for-builders](https://voidly.ai/pay/for-builders) — every middleware + adapter
196
- - [Voidly Pay landing](https://voidly.ai/pay)
208
+ - [voidpay landing](https://voidly.ai/pay)
197
209
 
198
210
  ## Keywords
199
211
 
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "voidly-pay"
3
- version = "2.0.2"
4
- description = "Voidly Pay SDK — open marketplace for AI-agent payments. claim() + request_with_pay() handle x402 end-to-end. USDC-backed Sourcify-verified vault on Base mainnet."
3
+ version = "3.0.0"
4
+ description = "voidpay SDK — zero-value internal test-credit tools for agent-to-agent transfers and x402 integration. Credits are not backed or redeemable."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
7
7
  license = { text = "MIT" }
@@ -20,4 +20,4 @@ requires = ["setuptools>=61"]
20
20
  build-backend = "setuptools.build_meta"
21
21
 
22
22
  [tool.setuptools]
23
- packages = ["voidly_pay"]
23
+ packages = ["voidly_pay", "voidly_pay.middleware"]
@@ -143,6 +143,102 @@ class TransferTests(unittest.TestCase):
143
143
  self.assertEqual(ctx.exception.code, "insufficient_balance")
144
144
 
145
145
 
146
+ class X402Tests(unittest.TestCase):
147
+ def test_verify_payment_signs_every_merchant_expectation(self):
148
+ sess, captured = fake_session({
149
+ "POST /v1/pay/x402/verify": resp_json({"ok": True, "quote_id": "q-1", "transfer_id": "t-1"}),
150
+ })
151
+ pay = VoidlyPay(api_url="http://x.example", secret_key=bytes(nacl.signing.SigningKey.generate()._signing_key), session=sess)
152
+ pay.verify_payment(
153
+ payment_header="voidly-credit transfer_id=t-1; quote_id=q-1",
154
+ payer_proof="signed-payer-proof",
155
+ expected_resource=["service:read", "service:read:v2"],
156
+ expected_method="POST",
157
+ min_amount=0.004,
158
+ expected_recipient="did:voidly:merchant-payee",
159
+ expected_currency="voidly-credit",
160
+ expected_metadata={"order_id": "order-a", "sku": "report"},
161
+ )
162
+ call = next(c for c in captured if c["url"].endswith("/v1/pay/x402/verify"))
163
+ env = call["json_body"]["envelope"]
164
+ self.assertEqual(env["payer_proof"], "signed-payer-proof")
165
+ self.assertEqual(env["expected_resource"], ["service:read", "service:read:v2"])
166
+ self.assertEqual(env["expected_method"], "POST")
167
+ self.assertEqual(env["expected_recipient_did"], "did:voidly:merchant-payee")
168
+ self.assertEqual(env["min_amount_micro"], 4_000)
169
+ self.assertEqual(env["expected_currency"], "voidly-credit")
170
+ self.assertEqual(env["expected_metadata"], {"order_id": "order-a", "sku": "report"})
171
+
172
+ def test_verify_payment_returns_structured_4xx_refusal(self):
173
+ sess, _ = fake_session({
174
+ "POST /v1/pay/x402/verify": resp_json(
175
+ {"ok": False, "reason": "quote_already_consumed"}, status=409,
176
+ ),
177
+ })
178
+ pay = VoidlyPay(api_url="http://x.example", secret_key=bytes(nacl.signing.SigningKey.generate()._signing_key), session=sess)
179
+ result = pay.verify_payment(
180
+ payment_header="voidly-credit transfer_id=t-1; quote_id=q-1",
181
+ payer_proof="signed-payer-proof",
182
+ expected_resource="/goods",
183
+ expected_method=None,
184
+ min_amount=0.001,
185
+ expected_metadata={},
186
+ )
187
+ self.assertEqual(result, {"ok": False, "reason": "quote_already_consumed"})
188
+
189
+ def test_verify_payment_rejects_hollow_terms_without_request(self):
190
+ sess, captured = fake_session({})
191
+ pay = VoidlyPay(api_url="http://x.example", secret_key=bytes(nacl.signing.SigningKey.generate()._signing_key), session=sess)
192
+ with self.assertRaises(VoidlyPayError):
193
+ pay.verify_payment(
194
+ payment_header="x", payer_proof="proof", expected_resource="", expected_method=None, min_amount=0.001, expected_metadata={},
195
+ )
196
+ with self.assertRaises(VoidlyPayError):
197
+ pay.verify_payment(
198
+ payment_header="x", payer_proof="proof", expected_resource="/r", expected_method=None, min_amount=0.0000001, expected_metadata={},
199
+ )
200
+ self.assertEqual(captured, [])
201
+
202
+ def test_request_with_pay_relays_a_bound_payer_proof(self):
203
+ quote = {
204
+ "x402": {"accepts": [{
205
+ "scheme": "voidly-credit", "quote_id": "q-1", "recipient_did": "did:voidly:merchant",
206
+ "resource": "/goods", "amount_micro": 1_000,
207
+ }]},
208
+ }
209
+ first = resp_json(quote, status=402)
210
+ second = resp_json({"goods": True})
211
+ transfer = resp_json({"transfer_id": "t-1", "status": "settled"})
212
+ sess, captured = fake_session({
213
+ "GET /goods": first,
214
+ "POST /v1/pay/wallet": resp_json({"wallet": {}}),
215
+ "POST /v1/pay/transfer": transfer,
216
+ })
217
+ calls = {"goods": 0}
218
+
219
+ def route(method, url, **kwargs):
220
+ from urllib.parse import urlparse
221
+ if method.upper() == "GET" and urlparse(url).path == "/goods":
222
+ captured.append({"method": "GET", "url": url, "json_body": None, "headers": kwargs.get("headers", {})})
223
+ calls["goods"] += 1
224
+ return first if calls["goods"] == 1 else second
225
+ return fake_session({
226
+ "POST /v1/pay/wallet": resp_json({"wallet": {}}),
227
+ "POST /v1/pay/transfer": transfer,
228
+ })[0].request(method, url, **kwargs)
229
+
230
+ sess.request = mock.Mock(side_effect=route)
231
+ pay = VoidlyPay(api_url="http://x.example", secret_key=bytes(nacl.signing.SigningKey.generate()._signing_key), session=sess)
232
+ result = pay.request_with_pay("GET", "http://merchant.example/goods")
233
+ self.assertEqual(result.status_code, 200)
234
+ retry = [c for c in captured if c["method"] == "GET"][-1]
235
+ self.assertIn("X-Payment", retry["headers"])
236
+ proof = retry["headers"]["X-Voidly-Payer-Proof"]
237
+ decoded = json.loads(base64.b64decode(proof).decode())
238
+ self.assertEqual(decoded["envelope"]["quote_id"], "q-1")
239
+ self.assertEqual(decoded["envelope"]["transfer_id"], "t-1")
240
+
241
+
146
242
  class WebhookSignatureTests(unittest.TestCase):
147
243
  def test_valid_signature(self):
148
244
  secret = "deadbeef" * 8
@@ -1,4 +1,4 @@
1
- """Voidly Pay — Python SDK for agent-to-agent payments.
1
+ """voidpay — Python SDK for agent-to-agent payments.
2
2
 
3
3
  Quick start:
4
4
 
@@ -21,6 +21,7 @@ import base64
21
21
  import hashlib
22
22
  import hmac
23
23
  import json
24
+ import math
24
25
  import os
25
26
  import secrets
26
27
  import time
@@ -32,7 +33,7 @@ from typing import Any, Iterable, Mapping, Optional
32
33
  import nacl.signing
33
34
  import requests
34
35
 
35
- __version__ = "0.1.0"
36
+ __version__ = "3.0.0"
36
37
 
37
38
  DEFAULT_API = "https://api.voidly.ai"
38
39
  MICRO_PER_CREDIT = 1_000_000
@@ -193,8 +194,33 @@ class VoidlyPay:
193
194
 
194
195
  # ─── HTTP ──────────────────────────────────────────────────────────────
195
196
 
196
- def _req(self, method: str, path: str, body: Any = None, idempotency: str | None = None) -> dict:
197
+ def proof_header(self, did: str | None = None) -> dict:
198
+ """Mint a short-lived ownership proof for ``did`` (default: our own).
199
+
200
+ voidpay redacts relationship metadata -- counterparty DIDs, memos,
201
+ task payloads and row ids -- from every unauthenticated read, because
202
+ those endpoints are public and a stranger could otherwise rebuild who
203
+ deals with whom. This header proves you are a party to the rows you
204
+ are asking for, and the server then returns the full view.
205
+
206
+ A 60-second Ed25519 signature over a caller-chosen window: no new key
207
+ material, no round-trip, nothing to store.
208
+ """
209
+ env = {
210
+ "schema": "voidly-pay-history-read/v1",
211
+ "did": did or self.did,
212
+ "nonce": uuid.uuid4().hex,
213
+ "issued_at": self._now_iso(),
214
+ "expires_at": self._expires(60_000),
215
+ }
216
+ blob = json.dumps({"envelope": env, "signature": self._sign(env)}, separators=(",", ":"))
217
+ return {"x-voidly-proof": base64.b64encode(blob.encode()).decode()}
218
+
219
+ def _req(self, method: str, path: str, body: Any = None, idempotency: str | None = None,
220
+ headers_extra: dict | None = None) -> dict:
197
221
  headers = {"accept": "application/json"}
222
+ if headers_extra:
223
+ headers.update(headers_extra)
198
224
  if body is not None:
199
225
  headers["content-type"] = "application/json"
200
226
  if idempotency:
@@ -231,10 +257,28 @@ class VoidlyPay:
231
257
  Idempotent: re-calling for an already-registered DID is safe.
232
258
  Uses the test-mode endpoint which auto-grants a starter balance
233
259
  (Stage 1) for instant onboarding.
260
+
261
+ The route used to accept a bare {did, signing_public_key} from any
262
+ caller. Since it mints 1,000 credits, that let anyone print against any
263
+ DID they cared to name. It now requires a signed proof of control,
264
+ which costs this SDK nothing — it already holds the key the DID is
265
+ derived from.
234
266
  """
235
- return self._req("POST", "/v1/pay/test/wallet/create", {
267
+ envelope = {
268
+ "schema": "voidly-pay-test-wallet-create/v1",
236
269
  "did": self.did,
237
270
  "signing_public_key": self.public_key,
271
+ "nonce": os.urandom(16).hex(),
272
+ "issued_at": self._now_iso(),
273
+ # The server caps the signed window at 10 minutes; the SDK-wide
274
+ # default expiry is 30, so this one is set explicitly rather than
275
+ # inherited — an inherited 30 would be rejected as
276
+ # envelope_window_too_long.
277
+ "expires_at": self._expires(5 * 60_000),
278
+ }
279
+ return self._req("POST", "/v1/pay/test/wallet/create", {
280
+ "envelope": envelope,
281
+ "signature": self._sign(envelope),
238
282
  })
239
283
 
240
284
  def faucet(self) -> dict:
@@ -337,7 +381,9 @@ class VoidlyPay:
337
381
  return r
338
382
 
339
383
  def get_transfer(self, transfer_id: str) -> dict:
340
- return self._req("GET", f"/v1/pay/transfer/{transfer_id}").get("transfer", {})
384
+ # Counterparties and memo are withheld from non-parties; prove we are one.
385
+ return self._req("GET", f"/v1/pay/transfer/{transfer_id}",
386
+ headers_extra=self.proof_header()).get("transfer", {})
341
387
 
342
388
  def history(self, did: str | None = None, limit: int | None = None, before: str | None = None) -> dict:
343
389
  params = []
@@ -346,7 +392,9 @@ class VoidlyPay:
346
392
  if before:
347
393
  params.append(f"before={requests.utils.quote(before)}")
348
394
  qs = ("?" + "&".join(params)) if params else ""
349
- return self._req("GET", f"/v1/pay/history/{did or self.did}{qs}")
395
+ target = did or self.did
396
+ return self._req("GET", f"/v1/pay/history/{target}{qs}",
397
+ headers_extra=self.proof_header(target))
350
398
 
351
399
  # ─── Escrow ────────────────────────────────────────────────────────────
352
400
 
@@ -417,18 +465,106 @@ class VoidlyPay:
417
465
  }
418
466
  return self._req("POST", "/v1/pay/x402/quote", {"envelope": env, "signature": self._sign(env)})
419
467
 
420
- def verify_payment(self, quote_id: str, transfer_id: str | None = None, payment_header: str | None = None) -> dict:
468
+ def verify_payment(
469
+ self,
470
+ quote_id: str = "",
471
+ transfer_id: str | None = None,
472
+ payment_header: str | None = None,
473
+ *,
474
+ payer_proof: str | None,
475
+ expected_resource: str | list[str],
476
+ expected_method: str | None,
477
+ min_amount: float,
478
+ expected_metadata: dict[str, Any],
479
+ expected_recipient: str | None = None,
480
+ expected_currency: str = "voidly-credit",
481
+ ) -> dict:
482
+ """One-shot verify a customer's payment against this merchant's terms.
483
+
484
+ The payer proof and every authorization fact are required keyword
485
+ arguments. Older call shapes therefore fail locally instead of asking
486
+ the facilitator whether an attacker-selected quote was merely
487
+ internally consistent.
488
+ """
489
+ if isinstance(min_amount, bool) or not isinstance(min_amount, (int, float)) or not math.isfinite(min_amount):
490
+ min_amount_micro = 0
491
+ else:
492
+ min_amount_micro = round(min_amount * MICRO_PER_CREDIT)
493
+ if min_amount_micro <= 0 or min_amount_micro > 2**53 - 1:
494
+ raise VoidlyPayError(
495
+ code="x402_expected_amount_invalid",
496
+ message="verify_payment min_amount must resolve to at least one safe micro-credit",
497
+ status=400,
498
+ )
499
+ resources = [expected_resource] if isinstance(expected_resource, str) else expected_resource
500
+ if (
501
+ not isinstance(resources, list)
502
+ or not 1 <= len(resources) <= 16
503
+ or any(not isinstance(resource, str) or not 1 <= len(resource) <= 512 for resource in resources)
504
+ ):
505
+ raise VoidlyPayError(
506
+ code="x402_expected_resource_invalid",
507
+ message="verify_payment expected_resource must name one to sixteen non-empty resources",
508
+ status=400,
509
+ )
510
+ if not isinstance(expected_metadata, dict):
511
+ raise VoidlyPayError(
512
+ code="x402_expected_metadata_invalid",
513
+ message="verify_payment expected_metadata must be a mapping",
514
+ status=400,
515
+ )
516
+ if expected_method is not None and (
517
+ not isinstance(expected_method, str) or not 1 <= len(expected_method) <= 16
518
+ ):
519
+ raise VoidlyPayError(
520
+ code="x402_expected_method_invalid",
521
+ message="verify_payment expected_method must be a non-empty HTTP method or None for a methodless quote",
522
+ status=400,
523
+ )
421
524
  env = {
422
525
  "schema": "voidly-x402-verify/v1",
423
526
  "server_did": self.did,
424
527
  "quote_id": quote_id,
425
528
  "transfer_id": transfer_id,
426
529
  "payment_header": payment_header,
530
+ "payer_proof": payer_proof,
531
+ "expected_resource": expected_resource,
532
+ "expected_method": expected_method,
533
+ "expected_recipient_did": self.did if expected_recipient is None else expected_recipient,
534
+ "min_amount_micro": min_amount_micro,
535
+ "expected_currency": expected_currency,
536
+ "expected_metadata": expected_metadata,
427
537
  "nonce": uuid.uuid4().hex,
428
538
  "issued_at": self._now_iso(),
429
539
  "expires_at": self._expires(),
430
540
  }
431
- return self._req("POST", "/v1/pay/x402/verify", {"envelope": env, "signature": self._sign(env)})
541
+ try:
542
+ return self._req("POST", "/v1/pay/x402/verify", {"envelope": env, "signature": self._sign(env)})
543
+ except VoidlyPayError as error:
544
+ # A verifier refusal is a normal result for server middleware. Do
545
+ # not turn an invalid/replayed payment into an application 500.
546
+ # Infrastructure failures remain exceptions.
547
+ if 400 <= error.status < 500:
548
+ raw = error.raw if isinstance(error.raw, dict) else {}
549
+ raw_error = raw.get("error") if isinstance(raw.get("error"), dict) else {}
550
+ reason = raw.get("reason") or raw_error.get("code") or error.code
551
+ return {"ok": False, "reason": reason}
552
+ raise
553
+
554
+ def payer_proof(self, quote_id: str, transfer_id: str, ttl_ms: int = 120_000) -> str:
555
+ """Mint the single-use ``x-voidly-payer-proof`` value for one pair."""
556
+ bounded_ttl_ms = max(1, min(int(ttl_ms), 120_000))
557
+ env = {
558
+ "schema": "voidly-pay-payer-binding/v1",
559
+ "did": self.did,
560
+ "quote_id": quote_id,
561
+ "transfer_id": transfer_id,
562
+ "nonce": uuid.uuid4().hex,
563
+ "issued_at": self._now_iso(),
564
+ "expires_at": self._expires(bounded_ttl_ms),
565
+ }
566
+ blob = json.dumps({"envelope": env, "signature": self._sign(env)}, separators=(",", ":"))
567
+ return base64.b64encode(blob.encode("utf-8")).decode("ascii")
432
568
 
433
569
  # ─── x402 (client-side: pay-on-402) ────────────────────────────────────
434
570
 
@@ -462,6 +598,7 @@ class VoidlyPay:
462
598
  t = self.transfer(to=accept["recipient_did"], amount=credits, memo=f"x402: {accept['resource']}")
463
599
  headers = dict(kwargs.pop("headers", {}) or {})
464
600
  headers["X-Payment"] = f"voidly-credit transfer_id={t['transfer_id']}; quote_id={accept['quote_id']}"
601
+ headers["X-Voidly-Payer-Proof"] = self.payer_proof(accept["quote_id"], t["transfer_id"])
465
602
  return self.session.request(method, url, headers=headers, **kwargs)
466
603
 
467
604
  # ─── Webhooks ──────────────────────────────────────────────────────────
@@ -576,17 +713,25 @@ class VoidlyPay:
576
713
  return self._req("POST", "/v1/pay/hire", {"envelope": env, "signature": self._sign(env)})
577
714
 
578
715
  def hire_get(self, hire_id: str) -> dict:
579
- return self._req("GET", f"/v1/pay/hire/{hire_id}")
716
+ # A hire row names who bought what and carries the task payload, so it
717
+ # is redacted for anyone who is not the requester or the provider.
718
+ return self._req("GET", f"/v1/pay/hire/{hire_id}", headers_extra=self.proof_header())
580
719
 
581
720
  def hires_incoming(self, did: str | None = None, state: str | None = None, limit: int = 50) -> dict:
582
721
  target = did or self.did
583
722
  qs = f"?limit={limit}" + (f"&state={requests.utils.quote(state)}" if state else "")
584
- return self._req("GET", f"/v1/pay/hire/incoming/{target}{qs}")
723
+ # A provider polling for work MUST send the proof: without it the list
724
+ # comes back with no hire ids and no inputs. That is deliberate —
725
+ # naming a DID is not the same as controlling it, and /v1/agent/discover
726
+ # hands out every DID for free.
727
+ return self._req("GET", f"/v1/pay/hire/incoming/{target}{qs}",
728
+ headers_extra=self.proof_header(target))
585
729
 
586
730
  def hires_outgoing(self, did: str | None = None, state: str | None = None, limit: int = 50) -> dict:
587
731
  target = did or self.did
588
732
  qs = f"?limit={limit}" + (f"&state={requests.utils.quote(state)}" if state else "")
589
- return self._req("GET", f"/v1/pay/hire/outgoing/{target}{qs}")
733
+ return self._req("GET", f"/v1/pay/hire/outgoing/{target}{qs}",
734
+ headers_extra=self.proof_header(target))
590
735
 
591
736
  def hire_and_wait(
592
737
  self,
@@ -0,0 +1,140 @@
1
+ """voidpay middleware for popular Python web frameworks.
2
+
3
+ Each helper turns a plain handler into an x402-protected paywall.
4
+ Pay-by-call: the function-level decorator (FastAPI dependency / Flask
5
+ decorator) is what most users want.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from typing import Any, Awaitable, Callable
11
+
12
+ from ..__init__ import VoidlyPay
13
+
14
+
15
+ def fastapi_x402(pay: VoidlyPay, amount: float, *, resource: str | None = None,
16
+ recipient: str | None = None, description: str | None = None,
17
+ metadata: dict[str, Any] | None = None,
18
+ ttl_seconds: int | None = None):
19
+ """FastAPI dependency. Returns a callable to use as a Depends().
20
+
21
+ Usage::
22
+
23
+ from fastapi import Depends, FastAPI
24
+ from voidly_pay import VoidlyPay
25
+ from voidly_pay.middleware import fastapi_x402
26
+
27
+ app = FastAPI()
28
+ pay = VoidlyPay()
29
+ require_payment = fastapi_x402(pay, amount=0.01)
30
+
31
+ @app.get("/expensive")
32
+ def expensive(payment = Depends(require_payment)):
33
+ return {"data": "the goods", "payer": payment["payer_did"]}
34
+ """
35
+ from fastapi import Header, HTTPException, Request, Response
36
+
37
+ async def _dep(
38
+ request: Request,
39
+ x_payment: str | None = Header(None),
40
+ x_voidly_payer_proof: str | None = Header(None),
41
+ ):
42
+ if x_payment is None:
43
+ quote = pay.create_quote(
44
+ resource=resource or request.url.path,
45
+ amount=amount,
46
+ recipient=recipient,
47
+ method=request.method,
48
+ description=description,
49
+ metadata=metadata,
50
+ ttl_seconds=ttl_seconds,
51
+ )
52
+ raise HTTPException(
53
+ status_code=402,
54
+ detail=quote["payment_required_response"],
55
+ headers={"WWW-Authenticate": "x402"},
56
+ )
57
+ v = pay.verify_payment(
58
+ payment_header=x_payment,
59
+ payer_proof=x_voidly_payer_proof,
60
+ expected_resource=resource or request.url.path,
61
+ expected_method=request.method,
62
+ min_amount=amount,
63
+ expected_recipient=recipient or pay.did,
64
+ expected_currency="voidly-credit",
65
+ expected_metadata=metadata or {},
66
+ )
67
+ if not v.get("ok"):
68
+ raise HTTPException(
69
+ status_code=402,
70
+ detail={"error": {"code": "x402_verify_failed", "message": v.get("reason")}},
71
+ headers={"WWW-Authenticate": "x402"},
72
+ )
73
+ return v
74
+ return _dep
75
+
76
+
77
+ def flask_x402(pay: VoidlyPay, amount: float, *, resource: str | None = None,
78
+ recipient: str | None = None, description: str | None = None,
79
+ metadata: dict[str, Any] | None = None,
80
+ ttl_seconds: int | None = None):
81
+ """Flask decorator factory. Wrap a view function.
82
+
83
+ Usage::
84
+
85
+ from flask import Flask
86
+ from voidly_pay import VoidlyPay
87
+ from voidly_pay.middleware import flask_x402
88
+
89
+ app = Flask(__name__)
90
+ pay = VoidlyPay()
91
+ require_payment = flask_x402(pay, amount=0.01)
92
+
93
+ @app.get("/expensive")
94
+ @require_payment
95
+ def expensive():
96
+ return {"data": "the goods"}
97
+ """
98
+ from functools import wraps
99
+ from flask import jsonify, request
100
+
101
+ def decorator(view: Callable):
102
+ @wraps(view)
103
+ def _wrap(*args, **kwargs):
104
+ payment_header = request.headers.get("X-Payment")
105
+ if not payment_header:
106
+ quote = pay.create_quote(
107
+ resource=resource or request.path,
108
+ amount=amount,
109
+ recipient=recipient,
110
+ method=request.method,
111
+ description=description,
112
+ metadata=metadata,
113
+ ttl_seconds=ttl_seconds,
114
+ )
115
+ resp = jsonify(quote["payment_required_response"])
116
+ resp.status_code = 402
117
+ resp.headers["WWW-Authenticate"] = "x402"
118
+ return resp
119
+ v = pay.verify_payment(
120
+ payment_header=payment_header,
121
+ payer_proof=request.headers.get("X-Voidly-Payer-Proof"),
122
+ expected_resource=resource or request.path,
123
+ expected_method=request.method,
124
+ min_amount=amount,
125
+ expected_recipient=recipient or pay.did,
126
+ expected_currency="voidly-credit",
127
+ expected_metadata=metadata or {},
128
+ )
129
+ if not v.get("ok"):
130
+ resp = jsonify({"error": {"code": "x402_verify_failed", "message": v.get("reason")}})
131
+ resp.status_code = 402
132
+ resp.headers["WWW-Authenticate"] = "x402"
133
+ return resp
134
+ request.voidly_payment = v # type: ignore[attr-defined]
135
+ return view(*args, **kwargs)
136
+ return _wrap
137
+ return decorator
138
+
139
+
140
+ __all__ = ["fastapi_x402", "flask_x402"]
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voidly-pay
3
- Version: 2.0.2
4
- Summary: Voidly Pay SDK — open marketplace for AI-agent payments. claim() + request_with_pay() handle x402 end-to-end. USDC-backed Sourcify-verified vault on Base mainnet.
3
+ Version: 3.0.0
4
+ Summary: voidpay SDK — zero-value internal test-credit tools for agent-to-agent transfers and x402 integration. Credits are not backed or redeemable.
5
5
  Author-email: Voidly <team@voidly.ai>
6
6
  License: MIT
7
7
  Keywords: voidly,payments,ai-agents,x402,did,ed25519
@@ -35,7 +35,7 @@ pay = VoidlyPay() # mints + persists keypair
35
35
  print("DID:", pay.did) # did:voidly:...
36
36
  pay.faucet() # 10 free credits
37
37
 
38
- # Browse the marketplace — 17 paid endpoints + N third-party listings
38
+ # Browse the marketplace — 12 paid endpoints + N third-party listings
39
39
  mp = pay.request("GET", "/v1/pay/marketplace").json()
40
40
  for item in mp["items"][:5]:
41
41
  print(f"{item['name']:40s} ${item['pricing']['amount_usdc']}")
@@ -123,11 +123,17 @@ def expensive():
123
123
  | **Streams (per-token billing)** | `pay.open_stream(...)`, `pay.meter_stream(...)`, `pay.finalize_stream(...)` |
124
124
  | **Subscriptions** | `pay.subscribe(...)`, `pay.cancel_subscription(...)` |
125
125
  | **x402 server-side quote** | `pay.create_quote(resource, amount)` |
126
- | **x402 server-side verify** | `pay.verify_payment(quote_id, transfer_id)` |
126
+ | **x402 server-side verify** | `pay.verify_payment(payment_header=header, payer_proof=proof, expected_resource=resource, expected_method=method, min_amount=price, expected_metadata=metadata)` |
127
127
  | **Webhooks** | `pay.subscribe_webhook(url, events=[...])` |
128
128
  | **Trust check** | `pay.health_check()` (6-check report incl. on-chain vault read) |
129
129
 
130
- ## What's in the marketplace today (Voidly's 17 paid endpoints)
130
+ ## What's in the marketplace today (Voidly's 12 paid endpoints)
131
+
132
+ Voidly does not sell its censorship data. The dataset is free and CC BY 4.0.
133
+ Every endpoint below charges for compute, or for an Ed25519 signature over data
134
+ that is itself free — never for the observatory. Five SKUs that failed that test
135
+ were retired on 2026-08-04 and now return HTTP 410 naming their free
136
+ replacement; see [voidly.ai/pay/changelog](https://voidly.ai/pay/changelog).
131
137
 
132
138
  | Endpoint | Price | What it does |
133
139
  |---|---|---|
@@ -141,13 +147,19 @@ def expensive():
141
147
  | `voidly_meta` | $0.001 | URL metadata (og + title + canonical) |
142
148
  | `voidly_extract` | $0.01 | PDF/document → plain text |
143
149
  | `voidly_scrape` | $0.01 | Fetch any URL + Voidly-signed receipt |
144
- | `voidly_fetch` | $0.05 | Country-pinned fetch via 37+ probe network |
145
- | `probe_attest` | $0.005 | Multi-vantage signed reachability proof |
146
- | `forecast_pro` | $0.01 | 30-day country-shutdown risk forecast |
147
- | `claim_verify_pro` | $0.005 | Evidence-backed verification of claims |
148
- | `incident_summary_pro` | $0.005 | Plain-English summary of an incident |
149
- | `agent_discover_pro` | $0.005 | Premium ranked agent search |
150
- | `incidents_export_pro` | $0.05 | Bulk export (high limit, no rate cap) |
150
+ | `probe_attest` | $0.005 | Ed25519 attestation over probe reachability data. The data is free at `/v1/probe/domain/{host}`; the charge is the signature. |
151
+ | `agent_discover_pro` | $0.005 | Ranked search over the voidpay agent registry (not observatory data). Free 20-result search at `/v1/agent/discover`. |
152
+
153
+ Retired 2026-08-04 — these now return **HTTP 410 Gone** with the free
154
+ replacement URL in the body. Do not route to them:
155
+
156
+ | Retired | Free replacement |
157
+ |---|---|
158
+ | `voidly_fetch` / `POST /v1/pay/fetch` | `voidly_scrape` — the country-pinning was never implemented, so every call was already a Cloudflare-edge fetch |
159
+ | `forecast_pro` | `GET /v1/forecast/{cc}/multi-horizon` (free, genuinely modelled) |
160
+ | `claim_verify_pro` | `POST /verify-claim` (free) |
161
+ | `incident_summary_pro` | `GET /data/incidents/{id}/report?format=markdown` (free) |
162
+ | `incidents_export_pro` | `GET /data/incidents/export?format=csv` (free, uncapped) |
151
163
 
152
164
  Plus N third-party listings registered self-serve at [voidly.ai/pay/list-your-service](https://voidly.ai/pay/list-your-service).
153
165
 
@@ -178,17 +190,17 @@ ok = verify_webhook_signature(
178
190
 
179
191
  ## Why agents use this
180
192
 
181
- | Problem | Voidly Pay solves it |
193
+ | Problem | voidpay solves it |
182
194
  |---|---|
183
195
  | **Need to add payment to your agent service** | x402 middleware ships for FastAPI, Flask, any web-fetch handler |
184
- | **Need to discover paid services** | One install → 17 endpoints + open marketplace listings |
196
+ | **Need to discover paid services** | One install → 12 endpoints + open marketplace listings |
185
197
  | **Don't want to manage 10 API keys** | One Ed25519 keypair, one wallet, every paid endpoint works |
186
198
  | **Don't trust the agent's payment claims** | Every receipt is Ed25519-signed by Voidly. Verifiable offline. |
187
- | **Need country-attested fetch** | 37+ probe network, signed (URL, country, ASN, probe-DID) |
199
+ | **Need censorship data** | It is free. `GET /data/incidents/export?format=csv`, CC BY 4.0, no wallet, no key. Voidly does not sell it. |
188
200
 
189
201
  ## Honest disclosure
190
202
 
191
- The Voidly Pay vault on Base mainnet (`0xd25d3c6f32886b65356cc5c700382a8a02d84df5`, [Sourcify-verified](https://repo.sourcify.dev/contracts/full_match/8453/0xd25d3c6f32886b65356cc5c700382a8a02d84df5/)) currently holds **$4 USDC**. We have approximately zero sustained external paying users yet. Live reserves at [voidly.ai/pay/proof](https://voidly.ai/pay/proof).
203
+ **Stage 2 is being retired** (decided 2026-08-04). Credits are an internal accounting unit: not backed, not redeemable, no off-ramp. The vault on Base mainnet (`0xd25d3c6f32886b65356cc5c700382a8a02d84df5`, [Sourcify-verified](https://repo.sourcify.dev/contracts/full_match/8453/0xd25d3c6f32886b65356cc5c700382a8a02d84df5/)) holds 4.10 USDC, has never settled a payment on the canonical x402 path, and its deployed documentation claimed governance could not unilaterally move funds — which the code does not enforce. It stays on-chain as a public record at [voidly.ai/pay/proof](https://voidly.ai/pay/proof). We have approximately zero sustained external paying users.
192
204
 
193
205
  We opened the marketplace before the demand exists because we believe agent adoption is gated on discoverability, not on payment-rail UX.
194
206
 
@@ -207,9 +219,9 @@ We opened the marketplace before the demand exists because we believe agent adop
207
219
  - [/pay/marketplace](https://voidly.ai/pay/marketplace) — visual browse
208
220
  - [/pay/list-your-service](https://voidly.ai/pay/list-your-service) — list in 60s
209
221
  - [/pay/claim](https://voidly.ai/pay/claim) — free 10-credit faucet
210
- - [/pay/proof](https://voidly.ai/pay/proof) — live reserves dashboard
222
+ - [/pay/proof](https://voidly.ai/pay/proof) — vault record (not a reserve; Stage 2 is being retired)
211
223
  - [/pay/for-builders](https://voidly.ai/pay/for-builders) — every middleware + adapter
212
- - [Voidly Pay landing](https://voidly.ai/pay)
224
+ - [voidpay landing](https://voidly.ai/pay)
213
225
 
214
226
  ## Keywords
215
227
 
@@ -6,4 +6,5 @@ voidly_pay.egg-info/PKG-INFO
6
6
  voidly_pay.egg-info/SOURCES.txt
7
7
  voidly_pay.egg-info/dependency_links.txt
8
8
  voidly_pay.egg-info/requires.txt
9
- voidly_pay.egg-info/top_level.txt
9
+ voidly_pay.egg-info/top_level.txt
10
+ voidly_pay/middleware/__init__.py
File without changes