Metadata-Version: 2.4
Name: weeasycrypto
Version: 0.2.0
Summary: Official Python SDK for the WeEasyCrypto tenant API — deposit addresses, balances, withdrawals, Ed25519 request signing and verified webhooks.
Project-URL: Homepage, https://github.com/weeasycrypto/sdk
Project-URL: Repository, https://github.com/weeasycrypto/sdk
Author: WeEasyCrypto
License: MIT
License-File: LICENSE
Keywords: crypto,deposits,payments,webhooks,weeasycrypto,withdrawals
Classifier: License :: OSI Approved :: MIT License
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: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: cryptography>=3.4
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == 'dev'
Description-Content-Type: text/markdown

# weeasycrypto (Python)

Official Python SDK for the WeEasyCrypto tenant API: deposit addresses,
balances, deposits, withdrawals, and signed webhooks.

- **One runtime dependency.** HTTP via `urllib`; Ed25519 (request signing
  and webhook verification) via the audited `cryptography` package (the
  stdlib has none).
  A custom transport is injectable for proxies, test stubs, or record/replay.
- **Signing you never hand-write.** Every request carries an Ed25519
  signature (`X-Timestamp` + `X-Signature`) computed over the exact bytes
  sent on the wire — the platform holds only your public key and can verify,
  never forge. Webhook deliveries are Ed25519-signed too (`v1a`, per-tenant
  platform key) — verified with your tenant's public key, ±300 s tolerance.
- **Amounts are decimal strings**, never floats. `parse_amount` /
  `format_amount` use pure integer arithmetic.

> ⚠️ **Server-side only.** Your Ed25519 `private_key` must never reach a
> browser or mobile bundle — it was generated locally when the key was issued
> and the platform cannot recover it.

## Install

```bash
pip install weeasycrypto
```

## Quickstart

```python
import os
from weeasycrypto import WeEasyCrypto

cv = WeEasyCrypto(
    base_url="https://api.example.com",
    api_key=os.environ["WEEASYCRYPTO_API_KEY"],          # keyId, e.g. "ck_…"
    private_key=os.environ["WEEASYCRYPTO_PRIVATE_KEY"],  # hex Ed25519 seed, generated at issuance
    webhook_public_key=os.environ.get("WEEASYCRYPTO_WEBHOOK_PUBLIC_KEY"),  # portal → Settings → Integrations
)

# Issue a deposit address
addr = cv.addresses.create(label="user-42", chain_key="tron")
addr.family  # 'EVM' | 'TRON' | 'BTC' | 'SOL' | 'TON'

# Balances — amounts are decimal strings, never floats
balances = cv.balances.list()

# Deposits with auto-pagination
for d in cv.deposits.iterate(status="CONFIRMED"):
    print(d.tx_hash, d.amount)
```

## Withdrawals — the idempotency contract

`request_id` is **your** idempotency key. Persist it in your own database
*before* calling; the SDK deliberately never generates one, because a
regenerated key after a crash is how double-payouts happen.

```python
from weeasycrypto import ConflictError

try:
    w = cv.withdrawals.create(
        request_id="wd-20260710-0001",  # yours, persisted first
        chain_key="eth-mainnet",
        token_symbol="USDT",
        to="0x…",
        amount="100.5",  # always a string
    )
except ConflictError as err:
    if err.is_duplicate_request:
        # Safety signal, not a failure: an earlier attempt already created it.
        # Look the withdrawal up via the id you stored against this request_id.
        ...
    else:
        raise

# Poll to a terminal status (webhooks are the push-based alternative)
final = cv.withdrawals.wait_until_final(w.id)
# final.status: 'CONFIRMED' | 'FAILED' | 'REJECTED' | 'CANCELED'
if final.status == "CONFIRMED":
    print(final.tx_hash)
```

Network errors on `withdrawals.create` / `create_batch` are retried
automatically **with the same `request_id`** — that can never double-spend.
`addresses.create` has no idempotency key, so it is never auto-retried;
`list()` recent addresses before creating again if the outcome was unknown.

## Webhooks

Deliveries carry `X-Webhook-Signature: t={ts},v1a={hex}` — an Ed25519
signature over `{ts}.{body}` made with a per-tenant platform key.
Verification needs only your tenant's public key (`webhookSignPublicKey`
in the merchant portal, Settings → Integrations); there is no shared
secret to protect.

```python
# Flask example — raw body must be the exact bytes received.
from weeasycrypto import WebhookVerificationError

@app.post("/webhook")
def webhook():
    try:
        event = cv.webhooks.parse(
            request.get_data(), request.headers.get("X-Webhook-Signature")
        )
    except WebhookVerificationError:
        return "", 400

    if event.type == "deposit.confirmed":
        credit(event.data["depositId"], event.data["amount"])
    elif event.type == "withdrawal.completed":
        mark_paid(event.data["requestId"])
    # New event types appear over time — ignore what you don't know.
    return "", 200
```

## Errors

```
WeEasyCryptoError
├─ APIError (status / code / request_id)
│   ├─ AuthenticationError(401)   ├─ PermissionDeniedError(403)
│   ├─ NotFoundError(404)         ├─ ConflictError(409)   # .is_duplicate_request
│   ├─ ValidationError(422)       └─ ServerError(5xx)
│   └─ RateLimitError(429)        # .retry_after_ms
├─ NetworkError                    # no HTTP response; safe to replay withdrawals
├─ WebhookVerificationError
└─ TimeoutError                    # wait_until_final budget exceeded
```

## Testing

```bash
python3 -m venv .venv && .venv/bin/pip install -e . pytest
.venv/bin/pytest
```

Signing is verified against the shared golden vectors in
[`../vectors/signing-vectors.json`](../vectors/signing-vectors.json).
