Skip to main content

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 {method, path_prefix, status_code, elapsed_ms, retried, pooled} per request.

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

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mojowallet 2.5.0
File Size Uploaded
mojowallet-2.5.0.tar.gz 18.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mojowallet 2.5.0
File Interpreter ABI Platform
mojowallet-2.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 39.4 kB

Release files / mojowallet-2.5.0.tar.gz

Download URL mojowallet-2.5.0.tar.gz
Size 18.7 kB
Tags Source
SHA-256 checksum
How to use checksums
cc6ea1b9987e350bc48e247f26010d98dd66cd0cef66b7d475f2ee4d6a57fea8
BLAKE2b-256 checksum
How to use checksums
ff4927dc59e8c53cc6cc1fc7a909d3f8f9f406a142a61f339401fc1e7c5deac1
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.5.0-py3-none-any.whl

Download URL mojowallet-2.5.0-py3-none-any.whl
Size 20.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4d42c10964e30ddc8e853043b21c237b360967e6cd4b58bdc0ef6d3e2ace7c0c
BLAKE2b-256 checksum
How to use checksums
5d222fe941da1ac53ae0597cfcc0fc4dd2d66814a4b78b74d8408d1b2a516350
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 history Release notifications | RSS feed

2.8.1

2 release files

2.8.0

2 release files

2.7.1

2 release files

2.7.0

2 release files

2.6.0

2 release files

This release

2.5.0 This release

2 release files

2.3.1

2 release files

2.2.1

2 release files

2.1.1

2 release files

2.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page