Crypto Chief Python SDK - Crypto Processing API Client
Crypto Chief Python SDK is the official asyncio client library for the Crypto Chief crypto processing API - a unified crypto payment gateway for accepting crypto payments, sending crypto payouts (single and mass), signing on-chain transactions, managing wallets, and verifying webhooks across Ethereum, Tron, TON, Solana, Bitcoin and 20+ more blockchains.
Drop it into any async Python backend (FastAPI, aiohttp, Litestar, Django ASGI,
serverless ...) to add cryptocurrency payment processing - stablecoin
(USDT / USDC) payouts, pay-ins, swaps, and smart-contract calls - with typed
dataclass requests / responses, integer-precise amounts, and an except-friendly
error hierarchy.
- One-line setup; a reusable
CryptoChiefClientyouawait. - Typed dataclasses for every request and response - editor autocomplete and
attribute access (
est.amount_to_receive), no dict juggling. - Contract calls without hand-encoded calldata - Solidity ABI for EVM and TRON, Anchor + Borsh for Solana, Jetton / NFT / comment helpers for TON.
- Local RSA decryption of generated wallet private keys.
- Stable error codes via
APIError.code, automatic retry on transient failures. - Arbitrary-precision amounts via native
int- neverfloat. - Webhook verification + typed events, framework-agnostic.
await client.payouts.wait_for(uuid)polling that resolves when a payout / transaction / pay-in is final.
The wire format is snake_case and so is Python - the public API uses the same field names the REST API does, with no translation layer in between.
Install
pip install cryptochief-crypto-processing-python
import cryptochief
from cryptochief import CryptoChiefClient, Chain
Requires Python 3.10+.
Quick start
import asyncio
from cryptochief import CryptoChiefClient, Chain, EstimatePayoutRequest
async def main():
async with CryptoChiefClient(
merchant_id="YOUR_MERCHANT_ID",
api_key="YOUR_API_KEY", # signing secret - keep it server-side
) as client:
est = await client.payouts.estimate(EstimatePayoutRequest(
network=Chain.ETH_SEPOLIA,
coin="ETH",
amount="0.0001",
to_address="0xRecipient...",
))
print("amount to receive:", est.amount_to_receive)
asyncio.run(main())
Both credentials come from the Dashboard -> Project.
What you can do with it
| Domain | Service | Key methods |
|---|---|---|
| Single payout (incl. auto-convert swap) | client.payouts |
estimate, execute, info, history, wait_for |
| Mass payout (up to 50 items) | client.payouts |
batch_estimate, batch_execute |
| Two-phase sign / broadcast for arbitrary txs | client.transactions |
sign, execute, info, history, wait_for |
| EVM / TRON contract calls (incl. ERC-20 / TRC-20) | client.transactions |
sign_evm_call, sign_tron_call, erc20_transfer |
| Solana programs | client.transactions |
sign_anchor_call, sign_solana_call |
| TON contract calls (Jetton / NFT / text) | client.transactions |
jetton_transfer, nft_transfer, send_ton_comment, sign_ton_call |
| Accept incoming payments | client.pay_ins |
create, select_asset, reset_asset, cancel, info, history, wait_for |
| Wallet management + RSA decrypt | client.wallets |
generate, list, info, pay_in_history, freeze, rebind_master, set_callback_url, set_label, decrypt_private_key |
| Treasury sweeps | client.sweeps |
force, history, wallet_history, settings, update_settings |
| Withdrawals (read-only) | client.withdrawals |
info, history |
| Static-deposit history | client.static_deposits |
info, history |
| On-chain queries | client.blockchain |
supported_chains, contracts_available, contracts_list, wallet_balance, transaction_status |
| Fiat <-> crypto rate quote + what can be priced | client.currencies |
fiat_to_crypto, crypto_to_fiat, fiats, cryptos |
| Credits (billing) balance check and top-up - free of charge | client.credits |
balance, topup |
Accept a crypto payment (pay-in)
Create an invoice, send the customer to the hosted payment_link, then settle it
when the invoice.* webhook arrives (recommended) or by polling wait_for.
from cryptochief import CryptoChiefClient, CreatePayInRequest, PayInMode
async def accept():
async with CryptoChiefClient(merchant_id="M", api_key="K") as client:
invoice = await client.pay_ins.create(CreatePayInRequest(
order_id="invoice-1001", # your id - idempotency key, safe to retry
user_id="user-7",
mode=PayInMode.FIAT, # fix a fiat price; the customer pays the crypto equivalent
amount_fiat="49.99",
currency="USD",
url_callback="https://example.com/webhooks/crypto-chief",
url_success="https://example.com/thanks",
))
print("send the customer to:", invoice.payment_link)
final = await client.pay_ins.wait_for(invoice.uuid, timeout=1800)
print(final.status) # paid | expired | cancel
For a fixed-crypto invoice use mode=PayInMode.CRYPTO with amount_crypto and
asset=Asset(coin="USDT", network=Chain.TRON_MAINNET). For host-to-host flows
where the customer picks the coin in your own UI, create the order without a fixed
asset and commit the choice with client.pay_ins.select_asset(...).
Send a payout (with confirmation)
from cryptochief import (
CryptoChiefClient, Chain, APIError, ErrorCode, ExecutePayoutRequest,
)
async def pay():
async with CryptoChiefClient(merchant_id="M", api_key="K") as client:
try:
payout = await client.payouts.execute(ExecutePayoutRequest(
order_id="order-42", # idempotency key - safe to retry
user_id="user-7",
network=Chain.ETH_SEPOLIA,
coin="ETH",
amount="0.0001",
to_address="0xRecipient...",
url_callback="https://example.com/webhooks/crypto-chief",
))
final = await client.payouts.wait_for(payout.uuid, timeout=300)
print(final.status, final.txid)
except APIError as e:
if e.code == ErrorCode.INSUFFICIENT_FUNDS:
... # top up and retry
raise
Amounts: always integers, never floats
from cryptochief import human_to_base, base_to_human
human_to_base("1.5", 18) # 1500000000000000000
base_to_human(10_000, 8) # "0.0001"
int is arbitrary-precision in Python, so token values never overflow and
decimal strings round-trip exactly. Discover an asset's decimals with
client.blockchain.contracts_available().
Which chains and assets are there
Three calls answer that question live, and they answer different questions:
# The chains the platform's scanner is connected to right now - infrastructure,
# not entitlement. A bare array on the wire, so a plain list here.
for c in await client.blockchain.supported_chains():
print(c.name, c.type) # "ETH_MAINNET" "evm"
# What THIS project can be paid in right now - the list that governs orders,
# sweeps and payouts.
enabled = await client.blockchain.contracts_available()
# Every coin and token the platform supports anywhere, whether or not this
# project has it on: the "which assets could we turn on" picker.
catalogue = await client.blockchain.contracts_list()
on = {(a.network, a.coin) for a in enabled.items or []}
for a in catalogue.items or []:
kind = "token" if a.contract else "native" # contract is "" for a native coin
print(a.network, a.coin, kind, a.chain_family,
"test" if a.is_test else "live",
"enabled" if (a.network, a.coin) in on else "available")
Both asset calls return the same row type, so code that reads one reads the
other. supported_chains() and fiats() are the two bare-array endpoints, and
an empty answer arrives from them as a literal null rather than [] - both
decode to an empty list, so neither result needs a None guard before you
iterate it.
Two more lists say what the platform can put a price on, which is a different question:
# Every fiat code you can price an order in - a bare array on the wire, so a
# plain list here. These are the codes `currency` takes on a fiat-mode pay-in.
for f in await client.currencies.fiats():
print(f.code, f.name) # "SEK" "Swedish Krona"
rates = await client.currencies.cryptos()
print(rates.count, "tickers against", rates.quote) # "... against USDT"
print(list(rates.by_exchange or {})) # ["binance", "bybit", "exmo", "kucoin"]
cryptos() is rate availability, not payment availability. A ticker there
can be quoted; it does not follow that the platform takes deposits, sweeps or
payouts in it - count runs into the thousands, and contracts_available()
does not. Build a customer-facing asset picker from contracts_available() or
you will offer assets that orders then refuse.
Contract calls without hand-encoding
This snippet shows the encoder, not a complete swap. Uniswap's router moves your input token with
transferFrom, so it needs an ERC-20approve(address,uint256)on that token first, confirmed before the swap is signed — without it the swap reverts and burns the gas. And anamountOutMinof0accepts whatever the pool returns, which on a public mempool hands the trade to the first sandwich bot that sees it. The runnable version, with both, is inexamples/.
from cryptochief import EvmCallRequest, Erc20TransferRequest, Chain, human_to_base
# Any EVM/TRON method by Solidity signature - args are ABI-encoded for you.
await client.transactions.sign_evm_call(EvmCallRequest(
network=Chain.ETH_MAINNET,
from_address="0xYourWallet...",
contract="0xA0b8...", # Uniswap router, etc.
method="swapExactTokensForTokens(uint256,uint256,address[],address,uint256)",
args=[10**6, 0, ["0xTokenIn...", "0xTokenOut..."], "0xYourWallet...", 1750000000],
))
# ERC-20 / TRC-20 transfer in one line (TRON base58 addresses accepted):
await client.transactions.erc20_transfer(Erc20TransferRequest(
network=Chain.TRON_MAINNET,
from_address="TYour...",
token_contract="TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", # USDT
recipient="TRecipient...",
amount=human_to_base("12.5", 6),
))
TON Jetton transfers resolve the sender's Jetton wallet automatically and pick a sensible gas budget:
from cryptochief import JettonTransferRequest, Chain, human_to_base
await client.transactions.jetton_transfer(JettonTransferRequest(
network=Chain.TON_MAINNET,
from_address="UQYour...",
jetton_master="EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs", # USDT
recipient="UQRecipient...",
amount=human_to_base("5", 6),
memo="Order #4242",
))
Solana Anchor calls take explicitly-typed Borsh args:
from cryptochief import AnchorCallRequest, SolanaAccount, borsh_u64, borsh_string, Chain
await client.transactions.sign_anchor_call(AnchorCallRequest(
network=Chain.SOLANA_MAINNET,
from_address="YourPubkey...",
program="YourProgramId...",
method="initialize",
args=[borsh_u64(1_000), borsh_string("hello")],
accounts=[SolanaAccount(pubkey="...", is_signer=True, is_writable=True)],
))
Webhooks
verify_webhook_signature and parse_webhook_event are framework-agnostic - feed
them the raw request bytes and the Signature header. With FastAPI:
from fastapi import FastAPI, Request, HTTPException
from cryptochief import (
parse_webhook_event,
WebhookSignatureError,
PayInWebhookEvent,
PayoutWebhookEvent,
)
app = FastAPI()
API_KEY = "..."
@app.post("/webhooks/crypto-chief")
async def hook(request: Request):
raw = await request.body() # the EXACT bytes - do not re-encode
try:
event = parse_webhook_event(API_KEY, raw, request.headers.get("Signature"))
except WebhookSignatureError:
raise HTTPException(status_code=401, detail="bad signature")
if isinstance(event, PayInWebhookEvent):
if event.status == "paid":
... # invoice.paid -> fulfill the order for event.order_id
elif isinstance(event, PayoutWebhookEvent):
... # payout.paid / payout.system_fail -> reconcile your ledger
return {"ok": True}
parse_webhook_event returns a typed event (PayoutWebhookEvent,
TransactionWebhookEvent, PayInWebhookEvent, StaticDepositWebhookEvent) chosen
by the event-name prefix, or the raw dict for an unrecognized prefix. Whitelist
the sender IPs in WEBHOOK_SENDER_IPS at your edge for defense in depth.
Errors
Everything the SDK raises derives from CryptoChiefError. API failures are
APIError with a stable .code (plus .message, .http_status and the
untouched .raw body); branch on ErrorCode rather than parsing messages. Both
envelope shapes the gateway sends - its own refusals, which carry the code in
error, and refusals relayed from upstream as SERVICE_ERROR with the code in
msg - resolve to .code. 5xx and network errors are retried automatically;
4xx is raised immediately.
from cryptochief import APIError, ErrorCode
try:
await client.payouts.execute(req)
except APIError as e:
if e.code == ErrorCode.DEBT_LIMIT_EXCEEDED:
...
Wallet private-key decryption
Generated wallets return private_key_encrypted (RSA-OAEP / SHA-256, base64).
Configure your project's RSA private key to decrypt locally - it never touches
the network:
client = CryptoChiefClient(
merchant_id="M", api_key="K",
rsa_private_key=open("project_private_key.pem").read(),
)
wallet = await client.wallets.generate(...)
priv = client.wallets.decrypt_private_key(wallet.private_key_encrypted)
FAQ - common crypto-processing tasks in Python
-
How do I accept crypto payments in Python? Create a pay-in with
client.pay_ins.create(...), redirect the customer topay_in.payment_link, and confirm via webhook orclient.pay_ins.wait_for(uuid). -
How do I send a USDT payout?
client.payouts.execute(...)with the stablecoin'scoin/network; pollwait_for. -
How do I send many payouts at once?
client.payouts.batch_execute(...)- up to 50 items, funds locked sequentially. -
How do I do a crypto swap? A swap is a payout with
auto_convert=True. -
A payer says they sent funds and I only have the address.
client.wallets.pay_in_history(address)lists every pay-in that used that deposit address - the samePayInrecords andmetablock asclient.pay_ins.history, narrowed to one wallet, which matters because a deposit wallet can serve several orders over its lifetime. The address is matched case-insensitively, and one your project does not own yields an empty page rather than an error. -
Which fiat currencies can I price an order in?
client.currencies.fiats()- the ISO 4217 codes
currencyaccepts on a fiat-mode pay-in and on a rate quote. It answers a bare JSON array, so it returns a plainlist[FiatCurrency].
- the ISO 4217 codes
-
Which crypto tickers does the platform have a rate for?
client.currencies.cryptos()-tickersis the union,by_exchangesays which exchange carries which, quoted againstquote(USDT). That is rate availability, not payment availability: a ticker with a price is not necessarily an asset the platform takes deposits, sweeps or payouts in. Build an asset picker fromclient.blockchain.contracts_available()instead, or you will offer assets that orders then refuse. -
How do I call a smart contract?
client.transactions.sign_evm_call/sign_anchor_call/jetton_transfer, thentransactions.execute. -
How do I control when a deposit wallet is swept?
client.sweeps.settings(...)reads the policy in force for one wallet andclient.sweeps.update_settings(...)changes it - sweep on arrival (SweepPolicyMode.MOMENTUM), sweep once the balance reaches an amount (SweepPolicyMode.THRESHOLDplusthreshold_amount_usd), or never on its own (SweepPolicyMode.OFF, force still works). The read comes back in three layers - what will happen, what this wallet overrides, and what it inherits from the project - so a value of your own is distinguishable from an inherited one:s = await client.sweeps.update_settings( deposit_address, type_work=SweepPolicyMode.THRESHOLD, threshold_amount_usd="250", ) # s.effective is the resolved policy; s.effective.source names the layer it came from.
Inheritance is per field: overriding the mode leaves the fee mode inherited. To stop overriding a field, pass
CLEAR-Nonealready means "leave this field alone", so it cannot also mean "reset it". -
Am I paying for TRON energy without knowing it? Probably, yes.
gas_sourcedecides what is bought for a TRON sweep -SweepGasSource.NATIVEburns the wallet's own TRX,SweepGasSource.RENTEDhas the platform supply the energy and bill it to your API credits - and it is independent offee_mode, which decides who covers the network fees. Not setting it is not the same as settingnative. A wallet that never chose one gets the platform default, which isrented: energy is supplied and billed with nobody having switched it on. Send it explicitly to opt out:await client.sweeps.update_settings(deposit_address, gas_source=SweepGasSource.NATIVE) s = await client.sweeps.settings(address=deposit_address) s.effective.gas_source # what will actually happen - always concrete s.override.gas_source # None = this layer does not decide, NOT "off"
Passing
CLEARdrops the override and inherits again - which lands back on the default, not onnative. TRON only; the value is carried and ignored on every other chain. -
How do I find just the failed - or just the skipped - sweeps? Pass
statusonSweepHistoryQuery. Left unset it includes every status,SweepStatus.SKIPPEDamong them - those are the sweeps the platform decided against, almost always a balance below the wallet's threshold, and they are a normal outcome rather than a failure.searchis a substring match on the wallet address, the sweep or gas-pump transaction hash and thetask_id(client.sweeps.wallet_historyhas the wallet fixed already, so there it matches the hashes and thetask_id):from cryptochief import SweepHistoryQuery, SweepStatus await client.sweeps.history(SweepHistoryQuery(status=SweepStatus.FAILED.value)) await client.sweeps.history(SweepHistoryQuery(search=tx_hash))
-
How do I know a sweep actually settled?
statusisSweepStatus.COMPLETEDandsweep_confirmationsis above zero.SweepStatus.BROADCASTEDmeans the transaction is out and not yet confirmed, and earlier platform versions reportedcompletedat broadcast, so a sweep could read as settled while its transaction was still unconfirmed - the confirmation count separates the two. Do not readcompleted_atas settlement: it is stamped when the sweep reached a terminal outcome, failures included, so aSweepStatus.FAILEDsweep carries one too. The moment the chain was seen holding the funds arrives separately, asconfirmed_aton thesweep.confirmedwebhook. -
My deposits are settling on the wrong master wallet.
client.wallets.rebind_master(address, master_wallet_address)re-points a transit or static wallet at another master of the project - the link is otherwise decided at creation, falling back to the project's oldest master of that chain family when none was named. It moves no money: it changes where the next sweep settles, including sweeps already queued, and anything already swept sits on the previous master and has to be sent from there as an ordinary payout. It is idempotent, so re-running the same list is safe. -
A static address is announcing deposits to the wrong URL. Deposits go to the callback the address carries, fixed when it was minted - so an address you did not create through your own integration, or one minted before your endpoint moved, keeps notifying somewhere else.
client.wallets.set_callback_url(address, url)corrects it, from the next deposit on (one already announced is not re-announced). Pass""to clear it and stop the announcements - the SDK sends the empty string rather than dropping it the way it drops unset optional fields, and the wallet then reads backcallback_url=None. Static wallets only. -
How do I name a wallet? Pass
labelonclient.wallets.generate(GenerateWalletRequest(..., label="EU shop")). It applies to every wallet type, is up to 255 characters, and is yours alone - nothing on chain and nothing in routing depends on it. -
How do I rename a wallet I already have?
client.wallets.set_label(address, "EU shop")- every wallet type, master and transit included, unlike the deposit callback. Pass""to clear the name: as withset_callback_url, the empty string is sent rather than dropped, and the wallet then reads backlabel=None. The name comes back on every response that describes a wallet - generation,info,list, and the answers ofrebind_master/set_callback_url/set_labelitself - aswallet.label,Nonewhen the wallet is unnamed. -
How do I keep test payments off real chains? Set
environmentonCreatePayInRequesttoEnvironment.TESTNETorEnvironment.MAINNET. It constrains the asset the platform picks when you have not named a concrete network - fiat mode andANY- so an unconstrained pick cannot put a real payment on a test chain. Omit it to use the project's default.
Documentation
- SDK guide: https://docs-sdk.crypto-chief.com/processing/python
- REST API reference: https://docs-processing.crypto-chief.com
- Product: https://crypto-chief.com/processing/
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cryptochief_crypto_processing_python-0.7.0.tar.gz.
File metadata
- Download URL: cryptochief_crypto_processing_python-0.7.0.tar.gz
- Upload date:
- Size: 56.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
63ad74aded37a618b8e5a508b33982190bdee0c4c84f140a95b6c9514f0021bd
|
|
| MD5 |
fbb47bc38941aa0396ba85ff83195f5e
|
|
| BLAKE2b-256 |
24adead86074cd12ff3b7da671284ca012de8e344cb326046bcbd2e511387a59
|
Provenance
The following attestation bundles were made for cryptochief_crypto_processing_python-0.7.0.tar.gz:
Publisher:
ci.yml on crypto-chiefs/cryptochief-crypto-processing-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cryptochief_crypto_processing_python-0.7.0.tar.gz -
Subject digest:
63ad74aded37a618b8e5a508b33982190bdee0c4c84f140a95b6c9514f0021bd - Sigstore transparency entry: 2682924096
- Sigstore integration time:
-
Permalink:
crypto-chiefs/cryptochief-crypto-processing-python@af8889583529270209fae30dd1438b9e563ed91e -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/crypto-chiefs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@af8889583529270209fae30dd1438b9e563ed91e -
Trigger Event:
push
-
Statement type:
File details
Details for the file cryptochief_crypto_processing_python-0.7.0-py3-none-any.whl.
File metadata
- Download URL: cryptochief_crypto_processing_python-0.7.0-py3-none-any.whl
- Upload date:
- Size: 73.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
24539f35be65af05076c7089d10f4df7d75409b8cd4706f995d1a543081a7227
|
|
| MD5 |
9f2954d8cda8e65a799bc84127f05fce
|
|
| BLAKE2b-256 |
561ff8373097d4f507abd3508c2708e0be7ae7faec5d8953059e97b0781ed980
|
Provenance
The following attestation bundles were made for cryptochief_crypto_processing_python-0.7.0-py3-none-any.whl:
Publisher:
ci.yml on crypto-chiefs/cryptochief-crypto-processing-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cryptochief_crypto_processing_python-0.7.0-py3-none-any.whl -
Subject digest:
24539f35be65af05076c7089d10f4df7d75409b8cd4706f995d1a543081a7227 - Sigstore transparency entry: 2682924347
- Sigstore integration time:
-
Permalink:
crypto-chiefs/cryptochief-crypto-processing-python@af8889583529270209fae30dd1438b9e563ed91e -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/crypto-chiefs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@af8889583529270209fae30dd1438b9e563ed91e -
Trigger Event:
push
-
Statement type: