Python SDK

Download the single-file Python SDK (stdlib only, Python 3.9+): local keys, signing, transfer(), webhook verification.

# Python SDK

The Python SDK (version 0.2.0) is a **single file**, `rarebl.py`, for Python 3.9 or later. It uses the **standard library only** (pure-Python secp256k1, Keccak-256 and the address encodings), so there is nothing to install besides the file. The optional hosted-signing envelope needs the `cryptography` package for AES-GCM and raises a clear error without it; nothing else in the SDK does.

It builds, verifies and signs transactions **on your machine**. Your private key never leaves the process unless you call the hosted-signing helper yourself.

## Download

| File | Size | SHA-256 |
| --- | --- | --- |
| [rarebl.py](https://docs.rarebl.com/sdk/python/rarebl.py) | 90.4 KB | `3e8f65c2c926a00d7ff916caf406b993e92509708d0611b431a171085abb4235` |

Requires: Python 3.9 or later; standard library only. All checksums are in [SHA256SUMS](https://docs.rarebl.com/sdk/SHA256SUMS). Check the file before you use it:

~~~bash
curl -O https://docs.rarebl.com/sdk/python/rarebl.py
sha256sum rarebl.py
~~~

## Install

Copy `rarebl.py` next to your code (or onto your `PYTHONPATH`) and import it:

~~~python
import rarebl
~~~

Registry publishing (PyPI `rarebl`) is planned; today you download the file and check its SHA-256 above.

## Ten lines: send BTC

~~~python
import rarebl

k = rarebl.keys.generate("btc", "p2wpkh")            # local, CSPRNG: {privateKey, wif, publicKey, address}
print("deposit address:", k["address"])             # fund it, then:

client = rarebl.Rarebl()                             # https://api.rarebl.com
txid = client.transfer(chain="btc", from_=k["address"],
                       to="bc1qanfrvw5lunlemvm09dthnxmwknal0fw0q7jfnm",
                       amount="0.001", key=k["privateKey"], priority="standard")
print("broadcast:", txid)
~~~

## Send USDT on TRON

~~~python
txid = client.transfer(chain="tron", from_=addr, to=recipient, amount="25",
                       token="TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", key=tron_key, feeLimit=15_000_000)
~~~

## `transfer()`

`client.transfer(chain, from_, to, amount, key, fee_rate=None, priority=None, token=None, decimals=None, verify=True, broadcast=True, auto_watch=True, change=None, max_fee=None, **extra)`

It builds with the API, **verifies the reply against what you asked for**, signs locally, then assembles and broadcasts. It returns the txid (or the `signed` object when `broadcast=False`).

- `amount` is a decimal string in whole units, or `"max"` on UTXO chains.
- `max_fee` caps the fee in base units (sats, wei or sun).
- `auto_watch=True`: on BTC, LTC and BCH it starts a watch on `from_` and waits for it to become active.
- `decimals` is needed for a token the SDK does not know. `**extra` passes more build fields, for example `feeLimit` (TRON) or `gasLimit`.
- `verify=True` is the default and should stay on: see below.

## Lower level: build, sign, assemble

~~~python
build = client.build({"chain": "btc", "from": [addr], "to": [{"address": dest, "amount": "0.0015"}], "priority": "standard"})
expect = {"to": {"address": dest, "amount": "0.0015"}}
rarebl.verify_build(build, expect=expect)                   # recompute every digest, raises VerifyError
sigs = rarebl.sign_build(build, key, expect=expect)         # expect is required; verifies again, then signs: [{index, signature, publicKey}]
res = client.assemble(build["unsigned"], sigs, broadcast=False)
client.broadcast("btc", res["signed"]["hex"], idempotency_key="payout-0001")
~~~

| Function | What it does |
| --- | --- |
| `keys.generate(chain, type=None)` | New keypair from the OS CSPRNG. |
| `keys.fromPrivate(chain, key, type=None)` | Public key and address of a key (hex or WIF). |
| `address.derive(chain, public_key, type)`, `address.validate(chain, address)` | Derive and validate addresses. |
| `sign_digest(digest_hex, private_key, algorithm, sighash_type=None, chain=None)` | One signature: ECDSA (DER + sighash byte), Schnorr (BIP340) or recoverable ECDSA (`r‖s‖v`). |
| `verify_build(build, pubkeys=None, expect=None)` | Recompute every digest; return True or raise `VerifyError`. `expect` pins outputs, amounts and a fee cap. |
| `sign_build(build, keys, verify=True, expect=None, now=None, unsafe_no_expect=False, deterministic=False, prev_txs=None)` | Verify and sign every digest; returns signatures for `assemble`. Raises `InputError` without `expect` (it must contain `to`). |
| `Rarebl(base_url, timeout)` | Client: `build`, `assemble`, `broadcast`, `estimate`, `decode`, `watch`, `utxos`, `status`, `nonce`, `validate`, `price`, `signer_key`, `outspends`, `transfer`. |
| `envelope.encrypt(signer_key, payload)` | Builds an envelope v2 for [hosted signing](https://docs.rarebl.com/guides/hosted-signing) only: `payload` is `{"privateKeys": [...], "op": "sign" or "send", "chain": ..., "digests": [...] or "bind": ...}`. `signer_key` is the whole reply of `signer_key()`. |
| `envelope.bind_of(chain, to, token=None, decimals=None)`, `envelope.seal`, `envelope.decrypt` | `bind_of` converts recipients to the base-unit `bind` of an `op: "send"` envelope; `decrypt` opens the `encryptedPrivateKey` of an [address/new](https://docs.rarebl.com/api/address/new) reply. |
| `webhook.verify(secret, raw_body, header, tolerance_sec=300)` | Check `X-Rarebl-Signature`. |

`expect` pins who is paid: `to` (`{address, amount | amountRaw}` or a list), `change` (extra change addresses; the input addresses always qualify), `token`, `decimals`, `opReturn`, `from`, `chain` and the fee caps below. `amount: "max"` must leave no change output. `unsafe_no_expect=True` signs without pinning the recipients (the fee caps still apply), and `verify=False` also needs it. `transfer()` always pins what you passed.

## Fee caps and defaults

When `expect` gives no cap, the SDK applies one. Override it through `expect`:

| Chain | Default | Override |
| --- | --- | --- |
| BTC, LTC | 1000 sat/vB | `maxFeeRate`, or an absolute `maxFee` (whole units) or `maxFeeSats` |
| BCH, DASH | 50 sat/byte | the same |
| ETH | `maxFeePerGas` at most 500 gwei, total fee at most 0.05 ETH, tip at most max(5 gwei, `maxFeePerGas` / 2) | `maxFeePerGas`, `maxPriorityFeePerGas` (wei), `maxFee`, `maxFeeRaw` |
| TRON | `feeLimit` at most 150 TRX | `maxFeeLimit` (sun) |

A UTXO transaction with a version other than 1 or 2, a non-zero locktime (`allowLocktime: True` allows it), a repeated outpoint or a sighash type other than SIGHASH_ALL is refused (`0x41`, with FORKID, is accepted on BCH only).

Legacy p2pkh inputs (BTC, LTC, DASH) must carry `unsigned.inputs[i].prevTx`, which `tx/build` delivers (or pass `prev_txs={txid: raw_hex}`): the SDK checks that it hashes to the outpoint and takes the amount from it.

## Hosted signing

For [hosted signing](https://docs.rarebl.com/guides/hosted-signing) the SDK builds an envelope v2 that names the chain, an expiry, a one-time id and what may be signed. The request needs the header `X-Rarebl-Accept-Risk: hosted-signing`.

~~~python
signer = client.signer_key()                                              # {kid, publicKey, expiresAt}
digests = [t["digest"] for t in build["toSign"]]                          # after verify_build passed
for_sign = rarebl.envelope.encrypt(signer, {"privateKeys": [key], "op": "sign", "chain": "btc", "digests": digests})

bind = rarebl.envelope.bind_of("btc", {"address": dest, "amount": "0.0015"})   # recipients, as base units
for_send = rarebl.envelope.encrypt(signer, {"privateKeys": [key], "op": "send", "chain": "btc", "bind": bind})

client.post("/open/tx/send", {"chain": "btc", "from": [addr], "to": [{"address": dest, "amount": "0.0015"}],
                              "keys": {"encrypted": for_send}}, headers={"X-Rarebl-Accept-Risk": "hosted-signing"})
~~~

An envelope is valid for at most 300 seconds and opens once.

## Verify webhooks

~~~python
ok = rarebl.webhook.verify(SIGNING_SECRET, raw_body_bytes, request.headers["X-Rarebl-Signature"], 300)
~~~

Pass the **raw** body, not re-serialised JSON. See [Webhooks](https://docs.rarebl.com/guides/webhooks).

## Errors

API errors raise `rarebl.ApiError` with `code`, `message`, `reason`, `detail` and `http_status` (for example code `API422`, reason `node_rejected`). `VerifyError` means a digest or output did not match, `KeyMismatchError` means a key does not own an input, `InputError` means a missing or invalid argument (for example signing without `expect`), `EnvelopeError` covers the envelope helper. All derive from `RareblError`.

## Command line

`python3 rarebl.py` is also a CLI (and the [shell SDK](https://docs.rarebl.com/sdks/shell) builds on it): `keygen`, `address`, `sign-digest`, `verify-build`, `sign-build`, `transfer` and `verify-webhook`. `sign-build` needs `--expect-to ADDR --expect-amount N` (or `--unsafe-no-expect`). Keys come from `--key-file`, `--key-env` or `--key-stdin`, **never from an argument**.

## Security notes

- **Why local signing:** a key that never leaves your process cannot be intercepted, logged or leaked by a service you call. See [Send a transaction](https://docs.rarebl.com/guides/send-a-transaction).
- **What `verify` protects against:** a server (or anything between you and it) returning a transaction that pays someone else, or digests that do not match. The SDK recomputes every digest and checks outputs, amounts, change and the fee before it signs, and `expect` is required so that the recipient is always pinned. `verify=False` signs the server's digests blindly and needs `unsafe_no_expect=True`: do not use it unless you have verified them another way.
- **Pure-Python crypto is not constant-time.** It gives no protection against local side-channel attacks. Use it on a machine you control (your own server or laptop), not on a shared host.
- ECDSA signatures are RFC 6979 deterministic, low-S, and verified after signing. BIP340 (p2tr) signatures use fresh random auxiliary data; pass `deterministic=True` for reproducible output.
- **Hosted signing:** `envelope.encrypt` builds the envelope for [hosted signing](https://docs.rarebl.com/guides/hosted-signing). The SDK never sends a key by itself; only calling that helper and posting its output does.
- Never put keys in source control, logs or command lines. Test with small amounts first.

## Next

[Sign locally with the SDK](https://docs.rarebl.com/guides/sign-locally), [Send a transaction](https://docs.rarebl.com/guides/send-a-transaction), [Watch addresses, get UTXOs and balances](https://docs.rarebl.com/guides/watch-addresses), [Stuck transactions](https://docs.rarebl.com/guides/stuck-transactions).

Back to the Rarebl AI documentation index