Skip to main content

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 description

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: the same account derives the same addresses, and secrets sealed by one SDK open in the other.

Prefer the terminal? The @guveno/cli package wraps the same API with the same features.

Install

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

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:

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

# 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

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:

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:

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:

# 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:

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):

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).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

guveno-1.3.1.tar.gz (43.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

guveno-1.3.1-py3-none-any.whl (58.5 kB view details)

Uploaded Python 3

File details

Details for the file guveno-1.3.1.tar.gz.

File metadata

  • Download URL: guveno-1.3.1.tar.gz
  • Upload date:
  • Size: 43.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.2

File hashes

Hashes for guveno-1.3.1.tar.gz
Algorithm Hash digest
SHA256 180830397d3161ed7213ee79a9c1012336144381bae426c04f115ef212783be7
MD5 dec50d2a914d372225ffa68414e4d66d
BLAKE2b-256 3679d9a39cb53b79933cd48c1e8f1803d1d666cad5117cf89b8021c35da5225c

See more details on using hashes here.

File details

Details for the file guveno-1.3.1-py3-none-any.whl.

File metadata

  • Download URL: guveno-1.3.1-py3-none-any.whl
  • Upload date:
  • Size: 58.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.2

File hashes

Hashes for guveno-1.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a897e99074d6f48937b82622ec91628ffdd8cc16459ceb21a6e0c9d6a4ec2181
MD5 f762a0b9a0299eec348bff19210bc95a
BLAKE2b-256 017a73daaf0e9ac3efb57a525665a84ee3ea224f7ae9eab2bb82f17415efda08

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page