mojowallet
Python SDK for MojoWallet — digital wallet operations, sessions, balance management, and financial ledger tools.
Installation
pip install mojowallet
Requirements: Python 3.10+ | Dependencies: requests, pyobjict
Quick Start
import mojowallet
client = mojowallet.Client("your-api-key", base_url="https://api.example.com")
wallet = client.Wallet.get(42)
wallet.add_funds(1000, "SC_REAL", source="CREDIT_CARD")
print(wallet.balance("SC_REAL"))
Each Client carries its own credentials, so one process can serve multiple
tenants by holding one Client per group.
Since 2.4.0 the client keeps per-thread pooled keep-alive connections and
splits timeouts: Client(..., connect_timeout=3, timeout=30) (connect / read,
sent as timeout=(connect, read)). Connect-phase failures are retried exactly
once for GET/DELETE and for POSTs whose payload carries a reference_id.
Set the MOJOWALLET_DISABLE_POOL env var to revert to per-call requests, and
pass on_request_metrics=callable to observe one event per call.
Since 2.6.0 that event carries a stable op label and path_template, both
timing views (elapsed_ms = final attempt, total_ms = caller-perceived,
including a failed connect), attempts, session_new, the terminal
exception's class name, and the server's own Server-Timing breakdown — so
one log line on your side splits a slow call into network vs. server work.
Wrap calls in mojowallet.correlation(request_id) to tie both halves
together. See Request Timing.
Since 2.5.0 a bet/game spend is one call — the server resolves the customer's wallet, checks funds and your cap against the same holdings it draws from, and returns the complete post-spend family snapshot:
data = client.Wallet.spend_by_customer(
"cust-abc123", 10000, "SC", "bet-round-42", cap_units=25000)
data.replayed, data.split, data.balances
Retrying a committed reference_id returns the original result with
replayed=True and moves no money. See Wallet Operations.
All responses support both dict-style (resp["id"]) and attribute-style (resp.id) access.
Core Concepts
| Concept | Description |
|---|---|
| Wallet | OO wrapper — get a wallet, call actions on it |
| Sessions | Concurrency control for withdrawals (context manager) |
| Error Handling | Exception hierarchy with domain-specific errors |
Usage Examples
# Funds (wallet obtained via client.Wallet.get / get_by_customer)
wallet.add_funds(1000, "SC_REAL", source="CREDIT_CARD", category="deposited")
wallet.purchase(500, "SC_REAL", merchant="Coffee Shop")
wallet.cashout(800, "SC_REAL", reference_id="cashout-001")
# Sessions (context manager for safety)
with wallet.session("game-xyz", expires_in_seconds=3600) as session:
session.withdraw(500, "SC_REAL", reference_id="bet-001")
session.extend(1800)
# Balances
balance = wallet.balance("SC_REAL")
summary = wallet.balance_summary()
# Reserve flow
wallet.reserve(2500, "SC_REAL", "SC_HOLD", reference_id="payout-001")
wallet.confirm_reservation("payout-001")
# Lock/unlock
wallet.lock(reason="Chargeback")
wallet.unlock(reason="Investigation cleared")
Spending across a currency family
# Walk the SC family in priority order and debit across holdings as needed.
wallet.spend(750, root_code="SC", reference_id="game-bet-001", merchant="Casino")
Balance reads
balance_summary() returns balances grouped by root currency family
(root_code), with per-currency totals exposed separately:
s = wallet.balance_summary()
s.balances.SC.available # 200000
s.balances.SC.by_code.SC_REAL # {quantity: 150000, formatted: "$1500.00"}
s.total_by_currency.SC_REAL # per-currency rollup, unchanged from 2.2.x
For per-holding detail or a single family lookup:
holdings = wallet.get_holdings(root_code="SC")
sc_balance = wallet.get_root_balance("SC")
⚠ Breaking change in 2.3.0: balance_summary().balances is now keyed by
root_code (was currency_code). Callers reading s.balances["SC_REAL"]
must switch to s.total_by_currency["SC_REAL"] or
s.balances["SC"].by_code["SC_REAL"].
Error Handling
from mojowallet.exceptions import InsufficientBalanceError, SessionConflictError
try:
wallet.cashout(10000, "SC_REAL", reference_id="cashout-002")
except InsufficientBalanceError as e:
print(f"Not enough funds: {e.message}")
except SessionConflictError:
print("Another session is already active")
Error codes
Wallet errors carry a numeric code (5000-block) and an HTTP status from
the table below. The SDK raises a matching subclass whenever the response
body includes a code in this range — the dispatch is authoritative
regardless of HTTP status.
| Code | HTTP | Class |
|---|---|---|
| 5001 | 500 | WalletInvariantError |
| 5002 | 402 | InsufficientBalanceError |
| 5003 | 423 | WalletLockedError |
| 5004 | 423 | WalletSuspendedError |
| 5005 | 423 | WalletInactiveError |
| 5006 | 400 | InvalidReferenceError |
| 5007 | 409 | IdempotentReplayError |
| 5008 | 422 | SpendCapExceededError |
WalletInvariantError deliberately surfaces only a generic "please retry"
message — never the server's diff text — so safety-critical balance-check
failures don't leak internal state.
SpendCapExceededError (2.5.0) is not a subclass of
InsufficientBalanceError — the wallet could afford the spend and the
caller's own cap_units rejected it, so the two need different handling.
Documentation
Development
pip install poetry
poetry install --with dev
# Run all tests (unit tests only — no API key needed)
pytest -v
# With sandbox credentials
cp .env.example .env
# Edit .env with your sandbox API key
pytest -v
License
MIT
Release files for mojowallet 2.7.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mojowallet-2.7.0.tar.gz | 22.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mojowallet-2.7.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 47.9 kB
Release files / mojowallet-2.7.0.tar.gz
| Download URL | mojowallet-2.7.0.tar.gz |
|---|---|
| Size | 22.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b6db6cbc1c956c2b7edc39dd87c1942e3817196484f24313b2d8f5d582b24afe
|
|
BLAKE2b-256 checksum How to use checksums |
a4b3874c9788dd7a91979ca8c7b79b54f962cbeb34df49bf8afb1ece10cda3e0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / mojowallet-2.7.0-py3-none-any.whl
| Download URL | mojowallet-2.7.0-py3-none-any.whl |
|---|---|
| Size | 25.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
29d39dfa1f1f4548c8fd2673e916bac87c394bf899f07bb4b67f1981fc9409f2
|
|
BLAKE2b-256 checksum How to use checksums |
6ca0086f98f9e1a0fa052bafca1a9114a54a4f565f0ac39f55a14aff942d6394
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|