voidly-pay
Python APIs for searching Voidly's catalog, buying an exact seller listing over x402, and registering a seller with a pending listing.
Version 4.0.0 replaces the 3.x test-credit SDK, whose service has been retired.
It follows the buy, sell and catalog contract of the @voidly/agent-wallet npm
package; it is not a Python port of that package's wallet creation, backup, MCP
server or every CLI command.
Install
Python 3.10+ on Linux or macOS:
python -m pip install voidly-pay
voidly-pay "text extraction" --network base
The CLI only searches the public catalog. It does not load a wallet, sign or pay.
network is always explicit: base uses https://x402.voidly.ai and
base-sepolia uses https://x402-staging.voidly.ai. Both use Circle USDC with six
decimal places. Search is paginated; pass the returned nextCursor explicitly.
Search without a wallet
from voidly_pay import VoidlyPay
client = VoidlyPay(network="base")
page = client.catalog("text extraction", max_price_atomic=100_000, limit=20)
for item in page["items"]:
print(item["id"])
Catalog entries can include first-party services and sellers. Buying through this package requires an exact live seller POST listing ID and version. Catalog data does not by itself prove a successful purchase or delivery.
Buy with your own signer and caps
The application supplies an EOA signer. AccountSigner(account) adapts an
eth-account LocalAccount already held by your application; this package does
not discover key files, read environment secrets, create wallets, contact an RPC
or fund anything. An external Signer can implement address, sign_message
and sign_typed_data. Its returned signatures must recover to its address.
Smart-contract wallets, Permit2 and networks other than Base/Base Sepolia are
outside this first version.
from pathlib import Path
from voidly_pay import AccountSigner, SpendLimits, SpendStore, VoidlyPay
# `account` is supplied by your application through its own key-management flow.
signer = AccountSigner(account)
state = Path.home() / ".local" / "state" / "voidly-pay"
store = SpendStore(state)
# Initialize once. For a wallet restored onto a new host, use recovered=True;
# that new local ledger cannot spend until the next UTC day.
store.initialize(signer.address, "base", recovered=True)
client = VoidlyPay(
network="base",
signer=signer,
store=store,
limits=SpendLimits(per_call_usdc="0.10", daily_usdc="1.00"),
)
# Use a real ID and version selected from the catalog. This call can pay.
result = client.buy(
listing_id=selected_id,
version=selected_version,
input={"text": "hello"},
max_usdc="0.10",
)
Use recovered=False only when deliberately initializing the first ledger for
that wallet. Reopening an existing store preserves reservations; do not create
another store to reset a budget. Caps are enforced by this local ledger,
not by the token or the wallet globally. Other software, another host, another
ledger or restored old state can spend outside this budget. Keep the directory
private, preserve it across restarts, and coordinate one store per wallet/network.
Each buy:
- Checks the selected live listing, version, network, USDC asset and seller.
- Validates the x402 v2 quote, exact amount, request digest and maximum 120-second EIP-3009 authorization window.
- Commits the UTC-day reservation and original quote/payment identity before signing.
- Sends at most one paid retry to the original call URL with the original body.
- Verifies the gateway's Ed25519 receipt against its fixed-origin public key registry, the saved attempt and the exact delivered response bytes.
Reservations remain consumed if signing or delivery fails. A refund_owed
result is a signed obligation, not a completed refund. The library performs
no refund payment or on-chain transaction query.
Recover an uncertain response
from voidly_pay import PaymentMayHaveSettledError
try:
result = client.buy(selected_id, selected_version, {"text": "hello"}, max_usdc="0.10")
except PaymentMayHaveSettledError as error:
# Preserve error.quote_id. Do not run buy again for the same purchase.
result = client.recover(error.quote_id)
recover signs a readback request for the original stored quote. It never creates
a new payment. client.attempts() lists retained attempts after a restart.
If recovery fails, keep that identity and reconcile it; do not repay blindly.
archivePending=True means the signed outcome was verified but local receipt
storage failed. Preserve the returned receipt.
Register and create a pending seller listing
listing = {
"name": "Text summary",
"description": "Summarize a short text.",
"category": "text",
"upstreamUrl": "https://your-service.example/summarize",
"method": "POST",
"priceAtomic": 100_000,
"inputSchema": {
"type": "object",
"properties": {"text": {"type": "string", "maxLength": 2000}},
"required": ["text"], "additionalProperties": False, "maxProperties": 1,
},
"outputSchema": {
"type": "object",
"properties": {"summary": {"type": "string", "maxLength": 2000}},
"required": ["summary"], "additionalProperties": False, "maxProperties": 1,
},
}
result = client.sell(
listing,
secret_file=str(state / "seller-secrets" / "summary.json"),
)
print(result["status"]) # pending_activation
This validates and signs two fresh, short-lived, mutation-bound SIWE challenges: registration, then listing creation. The one-time HMAC health credential goes only into the new 0600 file under an owned 0700 directory. An existing file, relative path or symlinked directory is refused. The returned result contains its path, not the secret. Do not print or commit that file.
Install the saved credential on the upstream and complete the separate signed
activation health check. This package does not activate a listing, probe the
upstream, rotate secrets or promise automatic acceptance. The gateway remains
the authority for its schema and admission policy. The .example URL above is
a placeholder, not a live seller. If SellerCreationUncertainError occurs,
preserve the file and reconcile the original submission before retrying.
Voidly never holds your funds. Funding remains a transfer to your own wallet. The package never supplies a Voidly deposit or vault address.
Stop signing and mutations
Set client.enabled = False (or pass enabled=False at construction). That
blocks new buy, sell and recovery signing. Catalog and local attempt reads remain
available. It cannot revoke an authorization already signed or undo a submission
already sent.
Test and build
python -m pip install '.[test]'
python -m unittest discover -s tests -v
python -m build
Tests use mocked gateways, dummy signer output and dummy Ed25519 receipt fixtures. They do not call a gateway, RPC, upstream, wallet or payment endpoint.
The suite checks cap concurrency/rollover, quote tampering, persistence before signing, unknown paid outcomes, recovery, receipt signatures/body hashes, credential redaction and unsafe-path rejection.
Scope
Python canonical JSON uses RFC 8785 for JavaScript-compatible number rendering and UTF-16 key ordering.
Agent Home, board, mail, global wallet limits, backup/restore and automatic
seller activation are not implemented in this package. For those, use the hosted
MCP at https://api.voidly.ai/mcp or the @voidly/agent-wallet npm package.
License
Apache-2.0. Voidly-original data is CC BY 4.0; OONI-derived fields are CC BY-NC-SA 4.0.
Metadata
Release files for voidly-pay 4.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| voidly_pay-4.0.0.tar.gz | 43.6 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| voidly_pay-4.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 76.4 kB
Release files / voidly_pay-4.0.0.tar.gz
| Download URL | voidly_pay-4.0.0.tar.gz |
|---|---|
| Size | 43.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
84814148be375d7f0fe8fc3567a9a523c4c813408c48c02d5f1c5d9ff9ee015d
|
|
BLAKE2b-256 checksum How to use checksums |
0e2ea539dbaa9753977989a7efa07239b4383d591d976af1cebd464fecaf3e9a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.1
|
Release files / voidly_pay-4.0.0-py3-none-any.whl
| Download URL | voidly_pay-4.0.0-py3-none-any.whl |
|---|---|
| Size | 32.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
95fd07680f34af90bd9294d7e69dc191d295ad1362c86394dbc99fabc81d37ee
|
|
BLAKE2b-256 checksum How to use checksums |
4c7b64541fce381d7579ddd02b1babc4a50f1a4a9968bf7396b950f9752b9a77
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.1
|
Security for voidly-pay 4.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.