Metadata-Version: 2.4
Name: guveno
Version: 1.3.1
Summary: Add secure crypto custody to your app in minutes — create and manage multi-chain wallets, automate withdrawals, and receive real-time webhooks for Bitcoin, Ethereum, XRP, and Polkadot. Built for exchanges, fintechs, and platforms.
Project-URL: Homepage, https://guveno.com
Project-URL: Documentation, https://guveno.com/docs
Author: Guveno LLC
License-Expression: MIT
Keywords: bitcoin,custody,ethereum,guveno,hd-wallet,polkadot,sdk,tron,wallet,xrp
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: Security :: Cryptography
Requires-Python: >=3.10
Requires-Dist: cryptography>=42
Requires-Dist: embit>=0.8
Requires-Dist: eth-account>=0.11
Requires-Dist: httpx>=0.27
Requires-Dist: mnemonic>=0.21
Requires-Dist: pydantic>=2.7
Requires-Dist: substrate-interface>=1.7
Requires-Dist: xrpl-py>=3.0
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Description-Content-Type: text/markdown

# Guveno Wallet SDK for Python

Add secure crypto custody to your platform — create and manage multi-chain wallets, automate withdrawals, and receive real-time webhooks for Bitcoin, Ethereum, XRP, Polkadot, and more. Built for exchanges, fintechs, and platforms.

A typed Python SDK with client-side key encryption and a fully offline signing path, so the server never holds plaintext key material. Fully wire- and key-compatible with the [Node.js SDK](https://www.npmjs.com/package/@guveno/wallet-sdk): the same account derives the same addresses, and secrets sealed by one SDK open in the other.

> Prefer the terminal? The [`@guveno/cli`](https://www.npmjs.com/package/@guveno/cli) package wraps the same API with the same features.

## Install

```bash
pip install guveno
```

Requires Python 3.10+.

## How it works

There are two credentials, and they do different jobs:

- **An API key (`gv_live_...`)** authenticates every request. Generate one in the Guveno dashboard. It carries your company and an explicit set of allowed actions, granted per wallet (plus a couple of global actions). A request for a wallet or action the key wasn't granted is rejected.
- **Your encryption password** decrypts your recovery phrases. The server only ever stores them *sealed* to your account's encryption key; the password (set during dashboard onboarding) unlocks that key locally and **never leaves your process**.

Wallets, addresses, and the sealed secret all live on the server. Listing and reading metadata needs only the API key. Signing — withdrawing, deriving addresses, revealing a phrase — additionally needs your encryption password, because that's what decrypts the key.

## Quick start

```python
import os
from guveno import Guveno

guveno = Guveno(api_key=os.environ["GUVENO_API_KEY"])

# Browse — metadata only, no password needed.
wallets = guveno.list_wallets()

# Load a wallet to sign with it: fetches the sealed secret and unlocks it locally.
wallet = guveno.load_wallet(wallets[0].id, os.environ["GUVENO_ENCRYPTION_PASSWORD"])

# Derive and register the next receive address (uses the server-tracked index).
result = wallet.derive_address(label="deposits")
print("Deposit to", result.address["address"])

withdrawal = wallet.withdraw(
    # Omit address_id to auto-select the source: the server picks an address with
    # enough balance — and for Bitcoin aggregates UTXOs across the wallet.
    asset_id=1,
    to_address="0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
    amount="1.5",
)
print(withdrawal.status)  # 'broadcast' — confirmation arrives via webhooks

wallet.lock()  # wipe key material from memory when done
```

`load_wallet()` accepts a numeric id, a wallet name, or a `{"name", "chain", "network"}` selector:

```python
guveno.load_wallet(wallets[0].id, password)   # by id
guveno.load_wallet("treasury", password)      # by name
guveno.load_wallet({"name": "treasury", "chain": "bitcoin", "network": "mainnet"}, password)
```

Names are only unique within a `(chain, network)`, so a bare name that matches wallets on more than one chain/network raises — pass the scoped selector or the id to disambiguate.

`withdraw()` runs the full `prepare → sign → broadcast` loop: the server returns an unsigned payload, the SDK signs it **in process**, and broadcasts the signed transaction. The recovery phrase never leaves your machine.

By default the source is **auto-selected**. Pass `address_id` to send from one specific address, or (Bitcoin only) `source_address_ids` to restrict UTXO aggregation to a chosen subset of the wallet's addresses. Account-based chains can't combine balances across addresses, so an auto withdrawal raises if no single address covers the amount.

Fees use the server's suggestion by default. Override per call with `ethereum_gas` (an `EthereumGasOverrides`) or `bitcoin_fee_rate` (sat/vB). Bitcoin inputs are fixed at prepare time, so a fee rate much higher than suggested can fail to fit the selected inputs.

## Creating and importing wallets

```python
# Generate a new recovery phrase, derive the first address, seal it, and register it.
created = guveno.create_wallet(
    name="treasury-btc",
    chain="bitcoin",
    network="mainnet",  # mainnet | testnet (Bitcoin) | sepolia (Ethereum) | paseo (Polkadot)
    words=24,           # 12 or 24-word recovery phrase (defaults to 12)
    encryption_password=os.environ["GUVENO_ENCRYPTION_PASSWORD"],
)
print(created.first_address["address"])   # bc1...
print("Back this up:", created.mnemonic)  # shown once — store it safely

# Import an existing phrase as a new server-side wallet.
imported = guveno.import_wallet(
    name="restored-xrp",
    chain="xrp",
    network="mainnet",
    mnemonic="test test test test test test test test test test test junk",
    encryption_password=os.environ["GUVENO_ENCRYPTION_PASSWORD"],
)
```

Both return a `Wallet` that's already loaded and ready to use.

## Using a loaded wallet

```python
wallet = guveno.load_wallet(wallet_id, encryption_password)

wallet.withdraw(asset_id=..., to_address=..., amount=...)                  # auto-select source
wallet.withdraw(address_id=..., asset_id=..., to_address=..., amount=...) # specific source
next_addr = wallet.derive_address(label="deposits")  # next address at the server index
page = wallet.list_addresses()
balances = wallet.get_balances()      # per-address, per-asset
totals = wallet.get_totals()          # aggregated per asset
stats = wallet.get_stats()            # holdings, flows, top addresses
phrase = wallet.reveal_mnemonic()     # audited server-side
status = wallet.get_withdrawal(withdrawal.id)

wallet.id; wallet.name; wallet.chain; wallet.network  # metadata properties
```

- **Per-source serialization** — withdrawals from the same source (a specific address, or a wallet when auto-selecting) are queued so concurrent sends never collide on a nonce or reuse a UTXO; different sources run in parallel.
- **Idempotency** — an `idempotency_key` is auto-generated per withdrawal; pass your own to make a retried `withdraw(...)` replay-safe.
- **All custodied chains are signable**; Polkadot extrinsics are built fully offline from metadata the server includes in the prepared payload.

## Headless signing (KMS / HSM / file)

For automated signers that hold their own key material, load a wallet with a key provider instead of a password — no encryption password, and the sealed secret is never fetched from the server:

```python
from guveno import Guveno, FileKeyProvider, KmsKeyProvider

guveno = Guveno(api_key=os.environ["GUVENO_API_KEY"])

# keys.json: { "<keyFingerprint>": "<bip39 mnemonic>", ... }
wallet = guveno.load_wallet(wallet_id, FileKeyProvider("/run/secrets/keys.json"))
wallet.withdraw(address_id=100, asset_id=1, to_address="0x70997...", amount="1.5")
```

Don't want a plaintext key file? Back it with AWS KMS, GCP KMS, Vault, or an HSM — the plaintext mnemonic exists only transiently in memory while a withdrawal is signed:

```python
def unwrap(ciphertext: str, key_fingerprint: str) -> str:
    out = kms.decrypt(CiphertextBlob=base64.b64decode(ciphertext))
    return out["Plaintext"].decode("utf-8")  # the mnemonic

keys = KmsKeyProvider(entries={"<keyFingerprint>": "<base64 KMS ciphertext>"}, decrypt=unwrap)
wallet = guveno.load_wallet(wallet_id, keys)
```

Key-provider wallets can `withdraw()` and `list_addresses()`; `derive_address()` and `reveal_mnemonic()` need the encryption-password path (they seal/unseal against the server).

## Balances

Reading balances needs only the API key — no encryption password or key provider. The quickest path is the getters on a loaded wallet, but `guveno.client` exposes the same endpoints if you only have a wallet id:

```python
# Per-address, per-asset balances — one entry per address.
for addr in wallet.get_balances():
    for b in addr.balances:
        print(addr.address, b.asset.symbol, b.total)

# Aggregated per-asset totals across the wallet's addresses.
for t in wallet.get_totals():
    print(t.asset.symbol, t.total)

# Holdings, lifetime in/out flows, and the top-holding addresses.
stats = wallet.get_stats()
print(stats.address_count, stats.transaction_count)
```

`total`/`amount` are decimal strings in the asset's main unit (e.g. `"1.5"` ETH), and each carries the full `asset` (symbol, decimals, contract address, …) so you don't need a separate asset lookup.

Without a loaded wallet, call the client directly — and roll up the whole company with `get_company_balance_summary`:

```python
totals = guveno.client.get_wallet_balance_totals(wallet_id)

# Company-wide totals per asset, optionally scoped to a chain/network.
company = guveno.client.get_company_balance_summary()
eth_only = guveno.client.get_company_balance_summary(chain="ethereum", network="mainnet")
```

## Webhooks and low-level access

The high-level facade covers wallet operations. For everything else — webhooks, renaming/deleting wallets, listing withdrawals — use the underlying client at `guveno.client` (a `GuvenoApiClient`):

```python
webhook = guveno.client.create_webhook(
    type="api",
    config={"url": "https://example.com/webhooks/guveno"},
    events=["deposit.confirmed", "withdrawal.confirmed"],  # or ["*"] for all
)
print(webhook.signing_secret)  # shown only here — store it to verify x-guveno-signature

deliveries = guveno.client.list_webhook_deliveries(webhook.id, limit=50)
```

All event types are exported as `WEBHOOK_EVENT_TYPES`. Managing webhooks requires an API key with the `webhooks:manage` action.

## Supported chains

| Chain | Address style |
| --- | --- |
| Bitcoin | native SegWit `bc1...` |
| Ethereum | `0x...` |
| BNB Smart Chain | `0x...` (same address as Ethereum) |
| XRP | classic `r...` |
| Polkadot | SS58 (`1...` mainnet) |
| TRON | Base58Check `T...` |

**We're adding new chains regularly** — these six are live today. All use standard HD derivation, so a recovery phrase restores the same accounts in any compatible wallet (Polkadot uses sr25519 substrate junctions, matching Polkadot-JS / Talisman; BSC shares Ethereum's derivation, matching MetaMask/Trust Wallet).
