Metadata-Version: 2.4
Name: waveledger-sdk
Version: 0.2.0
Summary: Official Python client for the WaveLedger post-quantum testnet. Includes the Fourier compiler for local contract compilation.
Author: Fermi
License: MIT
Project-URL: Homepage, https://docs.fermi.world/sdk/python/
Project-URL: Documentation, https://docs.fermi.world
Project-URL: Source, https://github.com/DosseyRichards/Fermi-Mining-ASIC-Software
Keywords: waveledger,blockchain,post-quantum,fermi
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: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Distributed Computing
Classifier: License :: OSI Approved :: MIT License
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.28
Dynamic: license-file

# waveledger-sdk — Python client

Official Python SDK for the [WaveLedger](https://docs.fermi.world)
post-quantum chain. One `Client` class for every messenger surface
(auth, chat, wallet, explorer, playground, admin) plus an iterator
over the SSE event stream. Ships with the **Fourier compiler
vendored in-process** so contracts compile locally without a network
round-trip.

## Install

```bash
pip install waveledger-sdk
```

The PyPI distribution is `waveledger-sdk`; the importable module is
`waveledger`, so your code stays `from waveledger import Client`. The
only runtime dependency is `requests`.

From the WaveLedger source tree:

```bash
pip install -e clients/python
```

## Quickstart

```python
from waveledger import Client

c = Client("https://api.waveledger.net")

# Sign up with an invite (instant approval + 100 testnet WAVE)
c.signup("alice", invite_code="WAVE-ABC123")

# Post a chat message — real on-chain ML-DSA-87 tx
c.send_message("hello world")

# Send WAVE to another address
c.wallet_send(to="34378b1ba5be9d0999acd60be3a8a1f1", amount=1.0)

# Subscribe to every block as it lands
for ev in c.subscribe(types=["block"]):
    b = ev["block"]
    print(f"block {b['height']} from {b['miner'][:16]}")
```

## API surface

```python
# Auth / session
c.signup(name, invite_code=None)
c.login(name, token)
c.me()
c.logout()

# Chat
c.send_message(text)
c.messages(limit=50)

# Wallet
c.wallet()
c.wallet_send(to=..., amount=..., memo=None)
c.wallet_export(passphrase=...)
c.wallet_import(name=..., encrypted=..., passphrase=...)

# Explorer (public, no auth)
c.explorer.stats()
c.explorer.blocks(limit=25, offset=0)
c.explorer.block(height)
c.explorer.tx(tx_id)
c.explorer.address(address)

# Playground (Fourier)
c.playground.compile(source)         # server-side
c.playground.compile_local(source)   # in-process — no network call
c.playground.deploy(source)
c.playground.call(contract=..., method=..., args=[...])
c.playground.receipt(tx_id)
c.playground.contracts()

# SSE event stream — block / tx / message / receipt
for ev in c.subscribe(types=None, address=None):
    handle(ev)

# Admin (HTTP Basic — pass admin=(user, password) at construction)
ac = Client("https://api.waveledger.net",
            admin=("admin", "PASSWORD"))
ac.admin.pending()
ac.admin.approve(name)
ac.admin.block(name, reason=None)
ac.admin.unblock(name)
ac.admin.invite_create(max_uses=25)
ac.admin.invite_revoke(code)
ac.admin.invites_list()
ac.admin.token_create(label=..., name=..., scope="playground")
ac.admin.tokens_list()
ac.admin.token_revoke(token="wlg_...")
```

## API token auth

For CI pipelines and unattended use, an administrator mints a Bearer
token bound to an approved user. The token's user owns any contracts
deployed or called through it; that user's wallet pays the fees.

```python
# Operator (one-off): mint a token bound to ci-bot.
admin = Client("https://api.waveledger.net",
               admin=("admin", "PASSWORD"))
admin.admin.approve("ci-bot")
out = admin.admin.token_create(label="release-pipeline",
                                name="ci-bot",
                                scope="playground")
print(out["token"])     # wlg_… — save this once; not recoverable
```

```python
# Pipeline: use the token, no signup/login required.
c = Client("https://api.waveledger.net", api_token="wlg_…")
c.playground.deploy(source)
```

Tokens are stored as their SHA3-256 hash. The raw value is returned
exactly once; revocation is the only recovery for a lost or leaked
token.

## Local Fourier compile

The package vendors the Fourier compiler. `compile_local()` runs
in-process and returns the same shape as the server compile:

```python
out = c.playground.compile_local("""
    contract Counter {
        storage value: uint @ 0;
        pub fn inc() -> uint { value = value + 1; return value; }
    }
""")
print(out["bytecode_size"], out["abi"]["contract"])
```

The vendored compiler is pinned with the SDK release, so a given
`waveledger-sdk` version produces byte-identical bytecode regardless
of when or where it runs. Lex / parse / codegen / ABI errors raise
`ValidationError` with `payload={"phase": "compile", "error": ...}`,
matching the server's 400 response shape.

## Filtering the event stream

```python
# Block events only
for ev in c.subscribe(types=["block"]):
    print(ev["block"]["height"])

# Everything that touches one address
for ev in c.subscribe(address="34378b1ba5be9d0999acd60be3a8a1f1"):
    print(ev["type"], ev)

# Both filters AND'd
for ev in c.subscribe(types=["tx", "receipt"],
                       address="34378b1ba5be9d0999acd60be3a8a1f1"):
    print(ev)
```

Filtering is **server-side**, so the client pays no bandwidth or CPU
cost for events outside the filter.

## Errors

```python
from waveledger import (
    AuthError, NotFoundError, RateLimitedError,
    ValidationError, ServerError, WaveLedgerError,
)
```

| HTTP | Exception |
|---|---|
| 400 | `ValidationError` |
| 401 / 403 | `AuthError` |
| 404 | `NotFoundError` |
| 429 | `RateLimitedError` |
| 5xx | `ServerError` |
| anything else | `WaveLedgerError` |

Each exception carries `.status` (int) and `.payload` (decoded JSON if
any).

## Security notes

- The SDK emits a `UserWarning` at construction time if `api_token` or
  `admin=` is configured against a non-loopback `http://` URL.
  Loopback hosts (`localhost`, `127.0.0.1`, `::1`) stay quiet — that
  is normal local development.
- Bearer tokens and admin credentials are sent on a per-request basis;
  `requests` strips the `Authorization` header on cross-origin
  redirects so a redirect to a different host does not leak the
  credential.
- Wallet backups are AES-256-GCM with Argon2id KDF (server-side); the
  SDK never sees the user's passphrase except as a value passed
  through to `/api/wallet/export`.

## Self-hosted nodes

```python
c = Client("http://localhost:8081")
```

The messenger serves on `8081` by default (`node.py --messenger-port`).

## Tests

```bash
cd clients/python
python3 -m pytest tests/ -q
```

The suite uses a mock `requests.Session` plus the vendored Fourier
compiler — no network, no fixtures, no extra test deps.

## Versioning

Pre-1.0. Method names and response shapes track the REST API. Latest
release: [`waveledger-sdk 0.2.0`](https://pypi.org/project/waveledger-sdk/).
