Skip to main content

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:

  1. Checks the selected live listing, version, network, USDC asset and seller.
  2. Validates the x402 v2 quote, exact amount, request digest and maximum 120-second EIP-3009 authorization window.
  3. Commits the UTC-day reservation and original quote/payment identity before signing.
  4. Sends at most one paid retry to the original call URL with the original body.
  5. 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)

Source distribution for voidly-pay 4.0.0
File Size Uploaded Provenance
voidly_pay-4.0.0.tar.gz 43.6 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for voidly-pay 4.0.0
File Interpreter ABI Platform
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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page