guveno 1.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. guveno-1.3.0/.gitignore +7 -0
  2. guveno-1.3.0/PKG-INFO +235 -0
  3. guveno-1.3.0/README.md +204 -0
  4. guveno-1.3.0/guveno/__init__.py +160 -0
  5. guveno-1.3.0/guveno/api/__init__.py +4 -0
  6. guveno-1.3.0/guveno/api/client.py +533 -0
  7. guveno-1.3.0/guveno/api/models.py +275 -0
  8. guveno-1.3.0/guveno/chains.py +110 -0
  9. guveno-1.3.0/guveno/config/__init__.py +15 -0
  10. guveno-1.3.0/guveno/config/file_store.py +187 -0
  11. guveno-1.3.0/guveno/constants.py +37 -0
  12. guveno-1.3.0/guveno/crypto/__init__.py +0 -0
  13. guveno-1.3.0/guveno/crypto/sealed_box.py +93 -0
  14. guveno-1.3.0/guveno/crypto/user_keys.py +56 -0
  15. guveno-1.3.0/guveno/derivation.py +226 -0
  16. guveno-1.3.0/guveno/encryption_session.py +77 -0
  17. guveno-1.3.0/guveno/errors.py +124 -0
  18. guveno-1.3.0/guveno/guveno.py +283 -0
  19. guveno-1.3.0/guveno/hot_wallet.py +308 -0
  20. guveno-1.3.0/guveno/keyprovider/__init__.py +5 -0
  21. guveno-1.3.0/guveno/keyprovider/base.py +20 -0
  22. guveno-1.3.0/guveno/keyprovider/file_provider.py +60 -0
  23. guveno-1.3.0/guveno/keyprovider/kms_provider.py +46 -0
  24. guveno-1.3.0/guveno/mnemonic.py +58 -0
  25. guveno-1.3.0/guveno/py.typed +0 -0
  26. guveno-1.3.0/guveno/signing/__init__.py +40 -0
  27. guveno-1.3.0/guveno/signing/sign_bitcoin.py +180 -0
  28. guveno-1.3.0/guveno/signing/sign_ethereum.py +88 -0
  29. guveno-1.3.0/guveno/signing/sign_polkadot.py +160 -0
  30. guveno-1.3.0/guveno/signing/sign_tron.py +78 -0
  31. guveno-1.3.0/guveno/signing/sign_withdrawal.py +68 -0
  32. guveno-1.3.0/guveno/signing/sign_xrp.py +49 -0
  33. guveno-1.3.0/guveno/signing/types.py +140 -0
  34. guveno-1.3.0/guveno/tron.py +226 -0
  35. guveno-1.3.0/guveno/wallet.py +156 -0
  36. guveno-1.3.0/guveno/wallet_service.py +206 -0
  37. guveno-1.3.0/pyproject.toml +65 -0
@@ -0,0 +1,7 @@
1
+ .venv/
2
+ dist/
3
+ build/
4
+ *.egg-info/
5
+ __pycache__/
6
+ *.pyc
7
+ .pytest_cache/
guveno-1.3.0/PKG-INFO ADDED
@@ -0,0 +1,235 @@
1
+ Metadata-Version: 2.4
2
+ Name: guveno
3
+ Version: 1.3.0
4
+ 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.
5
+ Project-URL: Homepage, https://guveno.com
6
+ Project-URL: Documentation, https://guveno.com/docs
7
+ Author: Guveno LLC
8
+ License-Expression: MIT
9
+ Keywords: bitcoin,custody,ethereum,guveno,hd-wallet,polkadot,sdk,tron,wallet,xrp
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Office/Business :: Financial
17
+ Classifier: Topic :: Security :: Cryptography
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: cryptography>=42
20
+ Requires-Dist: embit>=0.8
21
+ Requires-Dist: eth-account>=0.11
22
+ Requires-Dist: httpx>=0.27
23
+ Requires-Dist: mnemonic>=0.21
24
+ Requires-Dist: pydantic>=2.7
25
+ Requires-Dist: substrate-interface>=1.7
26
+ Requires-Dist: xrpl-py>=3.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=8; extra == 'dev'
29
+ Requires-Dist: respx>=0.21; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # Guveno Wallet SDK for Python
33
+
34
+ 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.
35
+
36
+ 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.
37
+
38
+ > Prefer the terminal? The [`@guveno/cli`](https://www.npmjs.com/package/@guveno/cli) package wraps the same API with the same features.
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ pip install guveno
44
+ ```
45
+
46
+ Requires Python 3.10+.
47
+
48
+ ## How it works
49
+
50
+ There are two credentials, and they do different jobs:
51
+
52
+ - **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.
53
+ - **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**.
54
+
55
+ 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.
56
+
57
+ ## Quick start
58
+
59
+ ```python
60
+ import os
61
+ from guveno import Guveno
62
+
63
+ guveno = Guveno(api_key=os.environ["GUVENO_API_KEY"])
64
+
65
+ # Browse — metadata only, no password needed.
66
+ wallets = guveno.list_wallets()
67
+
68
+ # Load a wallet to sign with it: fetches the sealed secret and unlocks it locally.
69
+ wallet = guveno.load_wallet(wallets[0].id, os.environ["GUVENO_ENCRYPTION_PASSWORD"])
70
+
71
+ # Derive and register the next receive address (uses the server-tracked index).
72
+ result = wallet.derive_address(label="deposits")
73
+ print("Deposit to", result.address["address"])
74
+
75
+ withdrawal = wallet.withdraw(
76
+ # Omit address_id to auto-select the source: the server picks an address with
77
+ # enough balance — and for Bitcoin aggregates UTXOs across the wallet.
78
+ asset_id=1,
79
+ to_address="0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
80
+ amount="1.5",
81
+ )
82
+ print(withdrawal.status) # 'broadcast' — confirmation arrives via webhooks
83
+
84
+ wallet.lock() # wipe key material from memory when done
85
+ ```
86
+
87
+ `load_wallet()` accepts a numeric id, a wallet name, or a `{"name", "chain", "network"}` selector:
88
+
89
+ ```python
90
+ guveno.load_wallet(wallets[0].id, password) # by id
91
+ guveno.load_wallet("treasury", password) # by name
92
+ guveno.load_wallet({"name": "treasury", "chain": "bitcoin", "network": "mainnet"}, password)
93
+ ```
94
+
95
+ 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.
96
+
97
+ `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.
98
+
99
+ 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.
100
+
101
+ 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.
102
+
103
+ ## Creating and importing wallets
104
+
105
+ ```python
106
+ # Generate a new recovery phrase, derive the first address, seal it, and register it.
107
+ created = guveno.create_wallet(
108
+ name="treasury-btc",
109
+ chain="bitcoin",
110
+ network="mainnet", # mainnet | testnet (Bitcoin) | sepolia (Ethereum) | paseo (Polkadot)
111
+ words=24, # 12 or 24-word recovery phrase (defaults to 12)
112
+ encryption_password=os.environ["GUVENO_ENCRYPTION_PASSWORD"],
113
+ )
114
+ print(created.first_address["address"]) # bc1...
115
+ print("Back this up:", created.mnemonic) # shown once — store it safely
116
+
117
+ # Import an existing phrase as a new server-side wallet.
118
+ imported = guveno.import_wallet(
119
+ name="restored-xrp",
120
+ chain="xrp",
121
+ network="mainnet",
122
+ mnemonic="test test test test test test test test test test test junk",
123
+ encryption_password=os.environ["GUVENO_ENCRYPTION_PASSWORD"],
124
+ )
125
+ ```
126
+
127
+ Both return a `Wallet` that's already loaded and ready to use.
128
+
129
+ ## Using a loaded wallet
130
+
131
+ ```python
132
+ wallet = guveno.load_wallet(wallet_id, encryption_password)
133
+
134
+ wallet.withdraw(asset_id=..., to_address=..., amount=...) # auto-select source
135
+ wallet.withdraw(address_id=..., asset_id=..., to_address=..., amount=...) # specific source
136
+ next_addr = wallet.derive_address(label="deposits") # next address at the server index
137
+ page = wallet.list_addresses()
138
+ balances = wallet.get_balances() # per-address, per-asset
139
+ totals = wallet.get_totals() # aggregated per asset
140
+ stats = wallet.get_stats() # holdings, flows, top addresses
141
+ phrase = wallet.reveal_mnemonic() # audited server-side
142
+ status = wallet.get_withdrawal(withdrawal.id)
143
+
144
+ wallet.id; wallet.name; wallet.chain; wallet.network # metadata properties
145
+ ```
146
+
147
+ - **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.
148
+ - **Idempotency** — an `idempotency_key` is auto-generated per withdrawal; pass your own to make a retried `withdraw(...)` replay-safe.
149
+ - **All custodied chains are signable**; Polkadot extrinsics are built fully offline from metadata the server includes in the prepared payload.
150
+
151
+ ## Headless signing (KMS / HSM / file)
152
+
153
+ 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:
154
+
155
+ ```python
156
+ from guveno import Guveno, FileKeyProvider, KmsKeyProvider
157
+
158
+ guveno = Guveno(api_key=os.environ["GUVENO_API_KEY"])
159
+
160
+ # keys.json: { "<keyFingerprint>": "<bip39 mnemonic>", ... }
161
+ wallet = guveno.load_wallet(wallet_id, FileKeyProvider("/run/secrets/keys.json"))
162
+ wallet.withdraw(address_id=100, asset_id=1, to_address="0x70997...", amount="1.5")
163
+ ```
164
+
165
+ 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:
166
+
167
+ ```python
168
+ def unwrap(ciphertext: str, key_fingerprint: str) -> str:
169
+ out = kms.decrypt(CiphertextBlob=base64.b64decode(ciphertext))
170
+ return out["Plaintext"].decode("utf-8") # the mnemonic
171
+
172
+ keys = KmsKeyProvider(entries={"<keyFingerprint>": "<base64 KMS ciphertext>"}, decrypt=unwrap)
173
+ wallet = guveno.load_wallet(wallet_id, keys)
174
+ ```
175
+
176
+ Key-provider wallets can `withdraw()` and `list_addresses()`; `derive_address()` and `reveal_mnemonic()` need the encryption-password path (they seal/unseal against the server).
177
+
178
+ ## Balances
179
+
180
+ 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:
181
+
182
+ ```python
183
+ # Per-address, per-asset balances — one entry per address.
184
+ for addr in wallet.get_balances():
185
+ for b in addr.balances:
186
+ print(addr.address, b.asset.symbol, b.total)
187
+
188
+ # Aggregated per-asset totals across the wallet's addresses.
189
+ for t in wallet.get_totals():
190
+ print(t.asset.symbol, t.total)
191
+
192
+ # Holdings, lifetime in/out flows, and the top-holding addresses.
193
+ stats = wallet.get_stats()
194
+ print(stats.address_count, stats.transaction_count)
195
+ ```
196
+
197
+ `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.
198
+
199
+ Without a loaded wallet, call the client directly — and roll up the whole company with `get_company_balance_summary`:
200
+
201
+ ```python
202
+ totals = guveno.client.get_wallet_balance_totals(wallet_id)
203
+
204
+ # Company-wide totals per asset, optionally scoped to a chain/network.
205
+ company = guveno.client.get_company_balance_summary()
206
+ eth_only = guveno.client.get_company_balance_summary(chain="ethereum", network="mainnet")
207
+ ```
208
+
209
+ ## Webhooks and low-level access
210
+
211
+ 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`):
212
+
213
+ ```python
214
+ webhook = guveno.client.create_webhook(
215
+ type="api",
216
+ config={"url": "https://example.com/webhooks/guveno"},
217
+ events=["deposit.confirmed", "withdrawal.confirmed"], # or ["*"] for all
218
+ )
219
+ print(webhook.signing_secret) # shown only here — store it to verify x-guveno-signature
220
+
221
+ deliveries = guveno.client.list_webhook_deliveries(webhook.id, limit=50)
222
+ ```
223
+
224
+ All event types are exported as `WEBHOOK_EVENT_TYPES`. Managing webhooks requires an API key with the `webhooks:manage` action.
225
+
226
+ ## Supported chains
227
+
228
+ | Chain | Address style |
229
+ | --- | --- |
230
+ | Bitcoin | native SegWit `bc1...` |
231
+ | Ethereum | `0x...` |
232
+ | XRP | classic `r...` |
233
+ | Polkadot | SS58 (`1...` mainnet) |
234
+
235
+ **We're adding new chains regularly** — these four 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).
guveno-1.3.0/README.md ADDED
@@ -0,0 +1,204 @@
1
+ # Guveno Wallet SDK for Python
2
+
3
+ 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.
4
+
5
+ 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.
6
+
7
+ > Prefer the terminal? The [`@guveno/cli`](https://www.npmjs.com/package/@guveno/cli) package wraps the same API with the same features.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pip install guveno
13
+ ```
14
+
15
+ Requires Python 3.10+.
16
+
17
+ ## How it works
18
+
19
+ There are two credentials, and they do different jobs:
20
+
21
+ - **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.
22
+ - **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**.
23
+
24
+ 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.
25
+
26
+ ## Quick start
27
+
28
+ ```python
29
+ import os
30
+ from guveno import Guveno
31
+
32
+ guveno = Guveno(api_key=os.environ["GUVENO_API_KEY"])
33
+
34
+ # Browse — metadata only, no password needed.
35
+ wallets = guveno.list_wallets()
36
+
37
+ # Load a wallet to sign with it: fetches the sealed secret and unlocks it locally.
38
+ wallet = guveno.load_wallet(wallets[0].id, os.environ["GUVENO_ENCRYPTION_PASSWORD"])
39
+
40
+ # Derive and register the next receive address (uses the server-tracked index).
41
+ result = wallet.derive_address(label="deposits")
42
+ print("Deposit to", result.address["address"])
43
+
44
+ withdrawal = wallet.withdraw(
45
+ # Omit address_id to auto-select the source: the server picks an address with
46
+ # enough balance — and for Bitcoin aggregates UTXOs across the wallet.
47
+ asset_id=1,
48
+ to_address="0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
49
+ amount="1.5",
50
+ )
51
+ print(withdrawal.status) # 'broadcast' — confirmation arrives via webhooks
52
+
53
+ wallet.lock() # wipe key material from memory when done
54
+ ```
55
+
56
+ `load_wallet()` accepts a numeric id, a wallet name, or a `{"name", "chain", "network"}` selector:
57
+
58
+ ```python
59
+ guveno.load_wallet(wallets[0].id, password) # by id
60
+ guveno.load_wallet("treasury", password) # by name
61
+ guveno.load_wallet({"name": "treasury", "chain": "bitcoin", "network": "mainnet"}, password)
62
+ ```
63
+
64
+ 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.
65
+
66
+ `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.
67
+
68
+ 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.
69
+
70
+ 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.
71
+
72
+ ## Creating and importing wallets
73
+
74
+ ```python
75
+ # Generate a new recovery phrase, derive the first address, seal it, and register it.
76
+ created = guveno.create_wallet(
77
+ name="treasury-btc",
78
+ chain="bitcoin",
79
+ network="mainnet", # mainnet | testnet (Bitcoin) | sepolia (Ethereum) | paseo (Polkadot)
80
+ words=24, # 12 or 24-word recovery phrase (defaults to 12)
81
+ encryption_password=os.environ["GUVENO_ENCRYPTION_PASSWORD"],
82
+ )
83
+ print(created.first_address["address"]) # bc1...
84
+ print("Back this up:", created.mnemonic) # shown once — store it safely
85
+
86
+ # Import an existing phrase as a new server-side wallet.
87
+ imported = guveno.import_wallet(
88
+ name="restored-xrp",
89
+ chain="xrp",
90
+ network="mainnet",
91
+ mnemonic="test test test test test test test test test test test junk",
92
+ encryption_password=os.environ["GUVENO_ENCRYPTION_PASSWORD"],
93
+ )
94
+ ```
95
+
96
+ Both return a `Wallet` that's already loaded and ready to use.
97
+
98
+ ## Using a loaded wallet
99
+
100
+ ```python
101
+ wallet = guveno.load_wallet(wallet_id, encryption_password)
102
+
103
+ wallet.withdraw(asset_id=..., to_address=..., amount=...) # auto-select source
104
+ wallet.withdraw(address_id=..., asset_id=..., to_address=..., amount=...) # specific source
105
+ next_addr = wallet.derive_address(label="deposits") # next address at the server index
106
+ page = wallet.list_addresses()
107
+ balances = wallet.get_balances() # per-address, per-asset
108
+ totals = wallet.get_totals() # aggregated per asset
109
+ stats = wallet.get_stats() # holdings, flows, top addresses
110
+ phrase = wallet.reveal_mnemonic() # audited server-side
111
+ status = wallet.get_withdrawal(withdrawal.id)
112
+
113
+ wallet.id; wallet.name; wallet.chain; wallet.network # metadata properties
114
+ ```
115
+
116
+ - **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.
117
+ - **Idempotency** — an `idempotency_key` is auto-generated per withdrawal; pass your own to make a retried `withdraw(...)` replay-safe.
118
+ - **All custodied chains are signable**; Polkadot extrinsics are built fully offline from metadata the server includes in the prepared payload.
119
+
120
+ ## Headless signing (KMS / HSM / file)
121
+
122
+ 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:
123
+
124
+ ```python
125
+ from guveno import Guveno, FileKeyProvider, KmsKeyProvider
126
+
127
+ guveno = Guveno(api_key=os.environ["GUVENO_API_KEY"])
128
+
129
+ # keys.json: { "<keyFingerprint>": "<bip39 mnemonic>", ... }
130
+ wallet = guveno.load_wallet(wallet_id, FileKeyProvider("/run/secrets/keys.json"))
131
+ wallet.withdraw(address_id=100, asset_id=1, to_address="0x70997...", amount="1.5")
132
+ ```
133
+
134
+ 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:
135
+
136
+ ```python
137
+ def unwrap(ciphertext: str, key_fingerprint: str) -> str:
138
+ out = kms.decrypt(CiphertextBlob=base64.b64decode(ciphertext))
139
+ return out["Plaintext"].decode("utf-8") # the mnemonic
140
+
141
+ keys = KmsKeyProvider(entries={"<keyFingerprint>": "<base64 KMS ciphertext>"}, decrypt=unwrap)
142
+ wallet = guveno.load_wallet(wallet_id, keys)
143
+ ```
144
+
145
+ Key-provider wallets can `withdraw()` and `list_addresses()`; `derive_address()` and `reveal_mnemonic()` need the encryption-password path (they seal/unseal against the server).
146
+
147
+ ## Balances
148
+
149
+ 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:
150
+
151
+ ```python
152
+ # Per-address, per-asset balances — one entry per address.
153
+ for addr in wallet.get_balances():
154
+ for b in addr.balances:
155
+ print(addr.address, b.asset.symbol, b.total)
156
+
157
+ # Aggregated per-asset totals across the wallet's addresses.
158
+ for t in wallet.get_totals():
159
+ print(t.asset.symbol, t.total)
160
+
161
+ # Holdings, lifetime in/out flows, and the top-holding addresses.
162
+ stats = wallet.get_stats()
163
+ print(stats.address_count, stats.transaction_count)
164
+ ```
165
+
166
+ `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.
167
+
168
+ Without a loaded wallet, call the client directly — and roll up the whole company with `get_company_balance_summary`:
169
+
170
+ ```python
171
+ totals = guveno.client.get_wallet_balance_totals(wallet_id)
172
+
173
+ # Company-wide totals per asset, optionally scoped to a chain/network.
174
+ company = guveno.client.get_company_balance_summary()
175
+ eth_only = guveno.client.get_company_balance_summary(chain="ethereum", network="mainnet")
176
+ ```
177
+
178
+ ## Webhooks and low-level access
179
+
180
+ 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`):
181
+
182
+ ```python
183
+ webhook = guveno.client.create_webhook(
184
+ type="api",
185
+ config={"url": "https://example.com/webhooks/guveno"},
186
+ events=["deposit.confirmed", "withdrawal.confirmed"], # or ["*"] for all
187
+ )
188
+ print(webhook.signing_secret) # shown only here — store it to verify x-guveno-signature
189
+
190
+ deliveries = guveno.client.list_webhook_deliveries(webhook.id, limit=50)
191
+ ```
192
+
193
+ All event types are exported as `WEBHOOK_EVENT_TYPES`. Managing webhooks requires an API key with the `webhooks:manage` action.
194
+
195
+ ## Supported chains
196
+
197
+ | Chain | Address style |
198
+ | --- | --- |
199
+ | Bitcoin | native SegWit `bc1...` |
200
+ | Ethereum | `0x...` |
201
+ | XRP | classic `r...` |
202
+ | Polkadot | SS58 (`1...` mainnet) |
203
+
204
+ **We're adding new chains regularly** — these four 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).
@@ -0,0 +1,160 @@
1
+ """Guveno Wallet SDK for Python.
2
+
3
+ Add secure crypto custody to your app in minutes — create and manage
4
+ multi-chain wallets, automate withdrawals, and receive real-time webhooks for
5
+ Bitcoin, Ethereum, XRP, and Polkadot.
6
+
7
+ Wire- and key-compatible with the Node.js SDK (``@guveno/wallet-sdk``): the same
8
+ recovery phrase derives the same addresses, and secrets sealed by one SDK open
9
+ in the other.
10
+ """
11
+
12
+ from .api.client import GuvenoApiClient
13
+ from .api.models import WEBHOOK_EVENT_TYPES
14
+ from .chains import (
15
+ CHAIN_DEFINITIONS,
16
+ DEFAULT_CHAIN,
17
+ POLKADOT_SS58_FORMAT,
18
+ SUPPORTED_CHAINS,
19
+ SUPPORTED_NETWORKS,
20
+ )
21
+ from .config.file_store import (
22
+ FileSystemConfigStore,
23
+ GuvenoConfig,
24
+ create_client_from_config,
25
+ init,
26
+ load_config,
27
+ )
28
+ from .constants import (
29
+ DEFAULT_API_BASE_URL,
30
+ DEFAULT_API_BASE_URL_ENV_VAR,
31
+ DEFAULT_API_KEY_ENV_VAR,
32
+ resolve_storage_dir,
33
+ )
34
+ from .crypto.sealed_box import SealedSecretJson, seal_secret, unseal_secret
35
+ from .crypto.user_keys import decrypt_user_private_key
36
+ from .derivation import (
37
+ DerivedAddressResult,
38
+ build_derivation_path,
39
+ derive_address,
40
+ derive_bitcoin_address,
41
+ derive_bsc_address,
42
+ derive_ethereum_address,
43
+ derive_polkadot_address,
44
+ derive_tron_address,
45
+ derive_xrp_address,
46
+ )
47
+ from .encryption_session import EncryptionSession
48
+ from .errors import (
49
+ ApiError,
50
+ AuthenticationError,
51
+ ConflictError,
52
+ EncryptionRequiredError,
53
+ KeyResolutionError,
54
+ NotFoundError,
55
+ RateLimitError,
56
+ SigningError,
57
+ StorageError,
58
+ UnsupportedSigningError,
59
+ ValidationError,
60
+ WalletSdkError,
61
+ WithdrawalExpiredError,
62
+ WrongPassphraseError,
63
+ WrongPasswordError,
64
+ )
65
+ from .guveno import Guveno
66
+ from .hot_wallet import HotWallet
67
+ from .keyprovider import FileKeyProvider, KeyProvider, KmsKeyProvider
68
+ from .mnemonic import (
69
+ assert_valid_mnemonic,
70
+ fingerprint_mnemonic,
71
+ generate_random_mnemonic,
72
+ get_mnemonic_word_count,
73
+ normalize_mnemonic,
74
+ )
75
+ from .signing import (
76
+ SIGNABLE_CHAINS,
77
+ EthereumGasOverrides,
78
+ SigningPayload,
79
+ can_sign_chain,
80
+ sign_bitcoin_withdrawal,
81
+ sign_ethereum_withdrawal,
82
+ sign_polkadot_withdrawal,
83
+ sign_tron_withdrawal,
84
+ sign_withdrawal,
85
+ sign_xrp_withdrawal,
86
+ )
87
+ from .wallet import Wallet
88
+ from .wallet_service import WalletService
89
+
90
+ __version__ = "1.3.0"
91
+
92
+ __all__ = [
93
+ "CHAIN_DEFINITIONS",
94
+ "DEFAULT_API_BASE_URL",
95
+ "DEFAULT_API_BASE_URL_ENV_VAR",
96
+ "DEFAULT_API_KEY_ENV_VAR",
97
+ "DEFAULT_CHAIN",
98
+ "POLKADOT_SS58_FORMAT",
99
+ "SIGNABLE_CHAINS",
100
+ "SUPPORTED_CHAINS",
101
+ "SUPPORTED_NETWORKS",
102
+ "WEBHOOK_EVENT_TYPES",
103
+ "ApiError",
104
+ "AuthenticationError",
105
+ "ConflictError",
106
+ "DerivedAddressResult",
107
+ "EncryptionRequiredError",
108
+ "EncryptionSession",
109
+ "EthereumGasOverrides",
110
+ "FileKeyProvider",
111
+ "FileSystemConfigStore",
112
+ "Guveno",
113
+ "GuvenoApiClient",
114
+ "GuvenoConfig",
115
+ "HotWallet",
116
+ "KeyProvider",
117
+ "KeyResolutionError",
118
+ "KmsKeyProvider",
119
+ "NotFoundError",
120
+ "RateLimitError",
121
+ "SealedSecretJson",
122
+ "SigningError",
123
+ "SigningPayload",
124
+ "StorageError",
125
+ "UnsupportedSigningError",
126
+ "ValidationError",
127
+ "Wallet",
128
+ "WalletSdkError",
129
+ "WalletService",
130
+ "WithdrawalExpiredError",
131
+ "WrongPassphraseError",
132
+ "WrongPasswordError",
133
+ "assert_valid_mnemonic",
134
+ "build_derivation_path",
135
+ "can_sign_chain",
136
+ "create_client_from_config",
137
+ "decrypt_user_private_key",
138
+ "derive_address",
139
+ "derive_bitcoin_address",
140
+ "derive_bsc_address",
141
+ "derive_ethereum_address",
142
+ "derive_polkadot_address",
143
+ "derive_tron_address",
144
+ "derive_xrp_address",
145
+ "fingerprint_mnemonic",
146
+ "generate_random_mnemonic",
147
+ "get_mnemonic_word_count",
148
+ "init",
149
+ "load_config",
150
+ "normalize_mnemonic",
151
+ "resolve_storage_dir",
152
+ "seal_secret",
153
+ "sign_bitcoin_withdrawal",
154
+ "sign_ethereum_withdrawal",
155
+ "sign_polkadot_withdrawal",
156
+ "sign_tron_withdrawal",
157
+ "sign_withdrawal",
158
+ "sign_xrp_withdrawal",
159
+ "unseal_secret",
160
+ ]
@@ -0,0 +1,4 @@
1
+ from .client import GuvenoApiClient
2
+ from .models import WEBHOOK_EVENT_TYPES
3
+
4
+ __all__ = ["GuvenoApiClient", "WEBHOOK_EVENT_TYPES"]