Metadata-Version: 2.4
Name: pynukez
Version: 4.0.5
Summary: Nukez SDK — agent-native storage with cryptographic verification
Author-email: Nukez <dev@nukez.xyz>
License: MIT
Project-URL: Homepage, https://github.com/Nukez-xyz/pynukez
Project-URL: Documentation, https://docs.nukez.xyz/sdk/python
Project-URL: Repository, https://github.com/Nukez-xyz/pynukez
Project-URL: Issues, https://github.com/Nukez-xyz/pynukez/issues
Keywords: ai,agent,autonomous,storage,solana,blockchain,cryptographic,verification,llm,tool-calling
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.24.0
Requires-Dist: pynacl>=1.5.0
Requires-Dist: base58>=2.1.0
Requires-Dist: eth-account>=0.10.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
Requires-Dist: pytest-mock>=3.10.0; extra == "dev"
Requires-Dist: black>=23.0.0; extra == "dev"
Requires-Dist: isort>=5.12.0; extra == "dev"
Requires-Dist: mypy>=1.0.0; extra == "dev"
Requires-Dist: types-requests>=2.28.0; extra == "dev"
Requires-Dist: python-dotenv>=1.0.0; extra == "dev"

# PyNukez

**Persistent storage for AI agents. Store anything, get a cryptographic receipt. pynukez does not move funds — you pay out-of-band and hand us the tx signature to confirm.**

[![PyPI](https://img.shields.io/pypi/v/pynukez.svg)](https://pypi.org/project/pynukez/)
[![Python](https://img.shields.io/pypi/pyversions/pynukez.svg)](https://pypi.org/project/pynukez/)
[![License](https://img.shields.io/pypi/l/pynukez.svg)](https://github.com/Nukez-xyz/pynukez/blob/main/LICENSE)

```bash
pip install pynukez
```

One command, one install target. Envelope signing works for both Solana-paid (Ed25519) and EVM-paid (secp256k1) lockers out of the box — no extras knob to get wrong.

Requires Python 3.9+.

## How it works

1. `request_storage()` asks the gateway for a quote. You receive payment instructions — address, amount, asset, chain.
2. **You execute the transfer yourself** — wallet, CLI, another tool, a hardware signer, whatever. pynukez does not touch your funds.
3. `confirm_storage(pay_req_id, tx_sig=<your_tx_sig>)` closes the loop and returns a receipt.
4. Use the receipt to provision a locker and upload / download / verify files.

The SDK signs API envelopes (Ed25519 for Solana-paid lockers, secp256k1 for EVM-paid lockers) so the gateway can prove requests came from the locker's owner or an authorized operator. That's all the cryptography it does.

## 30-Second Example

```python
from pynukez import Nukez

client = Nukez(keypair_path="~/.config/solana/id.json")

# 1. Ask for payment instructions
request = client.request_storage(units=1)
print(request.next_step)
# -> "Transfer 0.001 SOL to <addr> on solana-devnet,
#     then call confirm_storage(pay_req_id='...', tx_sig=<your_tx_signature>)"

# 2. Execute the transfer yourself (wallet, CLI, etc.), capture the tx signature.
tx_sig = "..."  # from your wallet / RPC / CLI

# 3. Close the loop with the gateway
receipt = client.confirm_storage(request.pay_req_id, tx_sig=tx_sig)

# 4. Use the receipt
client.provision_locker(receipt.id)
urls = client.create_file(receipt.id, "notes.txt")
client.upload_bytes(urls.upload_url, b"Hello!")
data = client.download_bytes(urls.download_url)  # b"Hello!"
```

### Async version

```python
from pynukez import AsyncNukez

async with AsyncNukez(keypair_path="~/.config/solana/id.json") as client:
    request = await client.request_storage(units=1)
    # ... execute the transfer externally ...
    receipt = await client.confirm_storage(request.pay_req_id, tx_sig=tx_sig)
    # ... same methods as sync, just awaited
```

---

## Quick Reference

| What you want | Code |
|--------------|------|
| Buy storage (quote) | `request = client.request_storage(units=1)` |
| Confirm payment | `receipt = client.confirm_storage(request.pay_req_id, tx_sig=<your_tx_sig>)` |
| Setup locker | `client.provision_locker(receipt.id)` |
| Store bytes | `urls = client.create_file(receipt.id, "file.txt")` then `client.upload_bytes(urls.upload_url, data)` |
| Store file | `client.upload_file_path(receipt.id, "/path/to/file.pdf")` |
| Batch upload | `client.bulk_upload_paths(receipt.id, [{"filepath": "a.pdf"}, {"filepath": "b.txt"}])` |
| Store directory | `client.upload_directory(receipt.id, "/path/to/dir", pattern="*.pdf", recursive=True)` |
| Confirm hash | `client.confirm_file(receipt.id, "file.txt", confirm_url=urls.confirm_url)` |
| Get data | `data = client.download_bytes(urls.download_url)` |
| List files | `files = client.list_files(receipt.id)` |
| Delete file | `client.delete_file(receipt.id, "file.txt")` |
| Receipt hash | `check = client.verify_receipt_hash(receipt.id)` |
| Verify | `result = client.verify_storage(receipt.id)` |
| Attest | `att = client.attest(receipt.id)` |
| Merkle proof | `proof = client.get_merkle_proof(receipt.id, "file.txt")` |
| Files manifest | `client.get_files_manifest(receipt.id)` |
| Locker record | `client.get_locker_record(receipt.id)` |
| Delegate | `client.add_operator(receipt.id, operator_pubkey)` |
| Viewer link | `client.get_owner_viewer_url(receipt.id)` |

---

## Sandboxed App Uploads

If your agent runs in a proxied app sandbox (for example, `/mnt/data` path restrictions), path uploads can fail even when locker auth is valid.

Use the sandbox ingest flow instead:

```python
job = client.sandbox_create_ingest_job(
    receipt_id=receipt.id,
    files=[{"filename": "image.png", "content_type": "image/png"}],
)

client.sandbox_append_ingest_part(
    receipt_id=receipt.id,
    job_id=job["job_id"],
    file_id=job["files"][0]["file_id"],
    part_no=0,
    payload_b64="<chunk-0-base64>",
    is_last=True,
)

result = client.sandbox_complete_ingest_job(
    receipt_id=receipt.id,
    job_id=job["job_id"],
)
```

Convenience helpers are available:
- `client.sandbox_upload_bytes(...)`
- `client.sandbox_upload_base64(...)`
- `client.sandbox_upload_file_path(...)`

Important: if a valid `receipt_id` already exists, reuse it. Do not purchase storage again unless explicitly requested.

---

## Important

**Save your `receipt.id`** — you need it for everything.

```python
# First time
receipt = client.confirm_storage(...)
print(receipt.id)  # Save this string somewhere!

# Later — fresh process, reconstructed client:
client.bind_receipt(receipt)          # or: bind_receipt(receipt_id=..., owner_identity=...)
files = client.list_files(receipt.id)
```

`confirm_storage()` primes per-receipt state automatically in the same
process. Across kernel restarts, subprocesses, or receipts loaded from
disk/DB, call `bind_receipt(receipt)` before owner-only ops
(`add_operator`, `remove_operator`) — on dual-key clients, the SDK
refuses to guess which signer to use and raises `ReceiptStateNotBoundError`
instead.

---

## Going to Production

Change one line:

```python
# Devnet (testing)
client = Nukez(keypair_path="~/.config/solana/id.json", network="devnet")

# Mainnet (production)
client = Nukez(keypair_path="~/.config/solana/id.json", network="mainnet-beta")
```

---

## Common Issues

| Problem | Fix |
|---------|-----|
| "Transaction not found" | The tx hasn't propagated yet. Wait a few seconds and retry `confirm_storage()` |
| "URL expired" | Call `client.get_file_urls(receipt_id, filename)` for fresh URLs |
| "File not found" | Check `client.list_files(receipt_id)` to see what exists |
| `ReceiptStateNotBoundError` | Call `client.bind_receipt(receipt)` before the op (cross-session / fresh-client flows) |
| `AuthenticationError: Envelope sig_alg '...' incompatible with ... network` | Dual-key client picked wrong signer — call `client.bind_receipt(receipt)` first |

---

## Links

- [Full SDK Reference](https://github.com/Nukez-xyz/pynukez/blob/main/docs/SDK_REFERENCE.md) — Every method, type, and error documented
- [Examples](https://github.com/Nukez-xyz/pynukez/tree/main/examples) — Working code you can copy
- [PyPI](https://pypi.org/project/pynukez/) — Published releases
- [GitHub](https://github.com/Nukez-xyz/pynukez) — Source code, issues, releases
- [Contributing](https://github.com/Nukez-xyz/pynukez/blob/main/CONTRIBUTING.md) — Dev setup and PR workflow

---

## License

MIT
