paymos — Python SDK
Non-custodial, headless control of a wallet vault: read balances, preview fee-transparent
quotes, and move money with swap and withdraw — all over a co-signed 2-of-2 vault.
Your process holds one half of the signing key; the Paymos server holds the other. Neither side alone can move funds — every swap and withdrawal is co-signed, and your half never reaches the server.
- Non-custodial — a leaked API key can't sign; your co-signing share stays in your process.
- Fee-transparent — every quote is a dry preview with a full
platform / network / routefee breakdown you see before signing anything. - Human amounts, no floats — you pass decimal strings like
"100"/"0.5"; responses come back as raw integer strings. - No Rust toolchain — the native signing core ships compiled inside the wheel.
- Async-first — built on
httpx.AsyncClient, fully typed (py.typed).
The SDK talks in plain asset terms (USDC@base, ETH@arb) and hides the routing and the
2-of-2 signing behind a small, safe surface.
Install
pip install paymos
Requires Python ≥ 3.11. The only runtime dependency is httpx (installed automatically).
Prebuilt wheels cover Windows, macOS (Intel + Apple Silicon), and Linux (x86-64 and
ARM, glibc and musl). Each cp311-abi3 wheel serves CPython 3.11 / 3.12 / 3.13+, so a single
wheel per platform is all you need — and there's no compiler step on install.
Get access & your API key
The SDK controls a Paymos vault, which you own from the Paymos wallet. The wallet is a Telegram-only Mini App — there is no website login; you use it entirely inside Telegram. So first get into the wallet, then mint a key.
1. Open the wallet. In Telegram, open @Ox0000000000000000000Bot and tap Open Wallet. Access is invite-based — if you don't have access yet, you'll need an invite. Create your vault if you don't have one.
2. Mint an API key. In the Mini App go to Settings → Vault API and create one of:
| Key | Can do | Carries |
|---|---|---|
| read | assets, balances, quote_swap, quote_withdraw, movement, movements |
API key only |
| full | everything a read key does, plus swap and withdraw |
API key + your co-signing share |
The key is a vs_live_… string. A full key's co-signing share is decrypted on your device and
never leaves your process — there is no CLI provisioner. Treat a full secret like a private key.
3. Point the SDK at it with an environment variable:
export PAYMOS_VAULT_SECRET="vs_live_…"
# optional — defaults to https://wallet.paymos.io
# export PAYMOS_BASE_URL="https://wallet.paymos.io"
Quickstart
import asyncio
from paymos import Wallet
async def main():
async with Wallet.from_env() as w: # reads PAYMOS_VAULT_SECRET
# Balances: per-asset, raw amount strings + a nullable USD value.
for b in await w.balances():
print(b.asset, b.amount_raw, b.usd)
# Preview a withdraw — a dry, money-safe quote. Headline is the fee breakdown.
q = await w.quote_withdraw(asset="USDC@base", amount="25", to="0xRecipient…")
print(q.fees.platform, q.fees.network, q.fees.total)
# Move money (full key only). Amounts are human decimal strings.
mv = await w.swap(send="USDC@base", receive="ETH@arb", amount="100")
settled = await w.wait(mv.id) # poll to a terminal status
print(settled.status)
asyncio.run(main())
Wallet is an async context manager (aclose() releases the HTTP client). You can also build it
directly — Wallet("vs_live_…") — or from the environment with Wallet.from_env().
Core concepts
Amounts: human in, raw out
Amounts you pass are human decimal strings ("100", "0.5") — the server owns each asset's
decimals and scales them to raw. Amounts in responses (balances, quote legs, fees) come back
as raw integer strings. Never a float, in either direction.
Assets
Assets use white-label SYMBOL@chain ids (USDC@base, ETH@arb). assets() returns the catalog
(fetched once and cached per Wallet); the SDK uses each asset's decimals to validate the
amounts you pass — an unknown asset or an over-precise amount raises PaymosError locally, before
anything hits the wire.
Quotes and fees
quote_swap / quote_withdraw always return a dry Quote — a pure preview that persists
nothing and works for read and full keys alike. The headline is quote.fees:
q = await w.quote_swap(send="USDC@base", receive="ETH@arb", amount="100")
print(q.fees.platform) # raw string
print(q.fees.network) # raw string
print(q.fees.route) # RouteFee(amount, estimate) — or None when there's no route leg
print(q.fees.total) # raw string
print(q.receive.amount) # expected out
print(q.receive.min) # guaranteed out (your slippage floor)
Moving money
swap and withdraw (full key only) create a real movement and drive the 2-of-2 co-sign,
then return a Movement. Before any signature the SDK re-checks that the server's quote echoes
exactly the assets and amount you approved — a decimals bug, contract drift, or a lying server
raises and signs nothing.
# Swap, delivered back to your own vault.
mv = await w.swap(send="USDC@base", receive="ETH@arb", amount="100",
min_receive="0.03") # optional slippage floor
# Withdraw to an external chain address.
mv = await w.withdraw(asset="USDC@base", amount="25", to="0xRecipient…")
Withdraw modes
mode="exact_out"(default) — the recipient receives exactlyamount; the vault debit (amount+ fees) is server-computed.mode="total_in"—amountis the total debited from the vault; the recipient gets that minus fees.
Safety caps
slippage_bps(swap / quote_swap, default50= 0.5%) — the slippage tolerance sent with the quote in basis points; the quote's guaranteedreceive.minalready reflects it.min_receive(swap) — a human decimal in the receive asset. If the quote's guaranteed receive is below it, raisesSlippageExceededand signs nothing.max_debit(swap / withdraw) — a human decimal ceiling on the total vault debit. Strongly recommended for unattendedexact_outpayouts, whose input side is server-computed. Exceeded → raises, signs nothing.
Idempotency
Pass your own idempotency_key (e.g. a durable payout id) so a retry after an ambiguous failure
replays the same movement instead of paying twice; a retry that finds it already
signed/relayed converges on it rather than double-signing. Omit it and a fresh key is generated
per call.
mv = await w.withdraw(asset="USDC@base", amount="25", to="0xRecipient…",
idempotency_key="payout-8f21", max_debit="26")
Waiting and statuses
wait(id) polls a movement until it reaches a terminal status —
completed | failed | refunded | expired | cancelled — and raises PaymosError on timeout.
mv = await w.wait(mv.id, timeout=120, poll=2.0)
API reference
All Wallet methods are async.
| Method | Returns | Notes |
|---|---|---|
Wallet(secret, base_url=…, *, timeout=None) |
Wallet |
secret is a vs_live_… string |
Wallet.from_env(base_url=None, *, timeout=None) |
Wallet |
reads PAYMOS_VAULT_SECRET / PAYMOS_BASE_URL |
assets() |
list[Asset] |
curated catalog, cached |
balances() |
list[Balance] |
per-asset vault balances |
quote_swap(send, receive, amount, slippage_bps=50) |
Quote |
dry preview |
quote_withdraw(asset, amount, to, mode="exact_out") |
Quote |
dry preview |
swap(send, receive, amount, slippage_bps=50, min_receive=None, *, max_debit=None, idempotency_key=None) |
Movement |
full key |
withdraw(asset, amount, to, mode="exact_out", *, max_debit=None, idempotency_key=None) |
Movement |
full key |
movement(id) |
Movement |
one movement, with fee breakdown |
movements(limit=50, cursor=None) |
tuple[list[Movement], str | None] |
page + next cursor (newest first) |
wait(id, timeout=120, poll=2.0) |
Movement |
poll to a terminal status |
aclose() |
— | release the underlying HTTP client |
The default HTTP timeout is httpx.Timeout(60s, connect=10s) (the co-sign is multi-round);
override it with Wallet(..., timeout=…).
Data models
Every amount field is a raw integer string. All models are frozen dataclasses.
Asset—asset,symbol,chain,decimals: intBalance—asset,symbol,chain,decimals: int,amount_raw,usd: str | NoneAmount—amount,assetReceive—amount,min(guaranteed),assetRouteFee—amount,estimate: bool(True= rate-derived cross-asset,False= exact same-asset spread)Fees—asset,platform,network,route: RouteFee | None,total,usd: dict | NoneQuote—movement_id: str | None(None on a dry preview),mode,send: Amount,debit: Amount(full vault debit = send + fees),receive: Receive,fees: Fees,expires_at,sources: int | NoneMovement—id,type,status,send: Amount,receive: Receive,fees: Fees | None,dest_chain_tx_hash: str | None,dest_chain_explorer_url: str | None,created_at,completed_at: str | None
Timestamps stay raw ISO-8601 strings — the SDK does not parse them to datetime.
Errors
Every call raises a typed subclass of PaymosError, which carries message and status (the
HTTP status, or None for a transport-level failure). Branch on the type instead of
string-matching a message.
| Exception | When |
|---|---|
AuthError |
401 — API key missing, malformed, or unrecognized |
Forbidden |
403 — the key is valid but lacks the scope this call needs |
InsufficientFunds |
400 — the vault balance can't cover the amount (plus fees) |
QuoteExpired |
400 — the referenced quote expired; fetch a fresh one |
SlippageExceeded |
400 or local — delivered / guaranteed amount fell below your floor |
CrossAssetWithdrawNotAllowed |
400 — a withdraw changed the asset (that's a swap) |
RouteUnavailable |
400 — no route could be quoted right now; retry shortly |
RateLimited |
429 — carries .retry_after (seconds, or None) |
Conflict |
409 — idempotency key reused with a different request |
PaymosError |
base — 404, any other 4xx/5xx, and transport errors (status=None) |
import asyncio
from paymos import Wallet, InsufficientFunds, SlippageExceeded, RateLimited, PaymosError
try:
mv = await w.swap(send="USDC@base", receive="ETH@arb", amount="100", min_receive="0.03")
except SlippageExceeded:
... # guaranteed receive below your floor — nothing signed
except InsufficientFunds:
... # not enough balance for amount + fees
except RateLimited as e:
await asyncio.sleep(e.retry_after or 1)
except PaymosError as e:
print(e.status, e.message)
Calling swap / withdraw with a read-only key raises PaymosError before any movement is
created — a read key can never sign.
Security
- A leaked API key alone cannot move funds — no share, no signature (and the key can be revoked).
- A leaked share alone cannot move funds — no server co-sign.
- Both are required, on purpose. The SDK additionally verifies every message it co-signs, and that the server's quote matches exactly what you approved — so a compromised server can't redirect funds or inflate an amount past your caps.
Treat a full vs_live_… secret like a private key, and back up the share — losing it means losing
your ability to co-sign.
Examples
The source repository's examples/ folder has a clickable local web demo (FastAPI, prod-pointed)
that loads balances, shows the fee breakdown, and — behind a confirm box — runs a real swap, plus a
minimal demo.py CLI.
License
Proprietary. All rights reserved. Use of this SDK is subject to your agreement with Paymos.
Metadata
Release files for paymos 0.1.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| paymos-0.1.4.tar.gz | 40.9 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| paymos-0.1.4-cp311-abi3-win_amd64.whl | CPython 3.11 | abi3 | Windows x86-64 | Details |
| paymos-0.1.4-cp311-abi3-musllinux_1_2_x86_64.whl | CPython 3.11 | abi3 | Linux musl 1.2+ x86-64 | Details |
| paymos-0.1.4-cp311-abi3-musllinux_1_2_aarch64.whl | CPython 3.11 | abi3 | Linux musl 1.2+ ARM64 | Details |
| paymos-0.1.4-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.11 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| paymos-0.1.4-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.11 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| paymos-0.1.4-cp311-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl | CPython 3.11 | abi3 | macOS 10.12+ universal2 (ARM64, x86-64), macOS 11.0+ ARM64, macOS 10.12+ x86-64 | Details |
Total release size: 3.7 MB
Release files / paymos-0.1.4.tar.gz
| Download URL | paymos-0.1.4.tar.gz |
|---|---|
| Size | 40.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cea33c207da9c2c4b0685d1a6bb7c0d0f07aca761449b60960438496b050455a
|
|
BLAKE2b-256 checksum How to use checksums |
6382dd11fa1e5c200ce7c1517f9943ab563b5987a547f6ea75f4cd84495c1155
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 9, 2026.
Transparency logRelease files / paymos-0.1.4-cp311-abi3-win_amd64.whl
| Download URL | paymos-0.1.4-cp311-abi3-win_amd64.whl |
|---|---|
| Size | 363.5 kB |
| Tags | CPython 3.11 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
4d7d128f71e78267a63169d97b2a32c179f532d5117590d7b72a8250901f02c0
|
|
BLAKE2b-256 checksum How to use checksums |
39993e6f5f844cb05f30deee7dfed2384c6b2c5c31037903c03e28a7a5638cf0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 9, 2026.
Transparency logRelease files / paymos-0.1.4-cp311-abi3-musllinux_1_2_x86_64.whl
| Download URL | paymos-0.1.4-cp311-abi3-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 719.8 kB |
| Tags | CPython 3.11 Linux musl 1.2+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
e16341e651e8b0b8f333b5a34457d289616417f299660e5e87590fa5db9a1fdf
|
|
BLAKE2b-256 checksum How to use checksums |
f7d81b53cccfcb732d2cbc4e33423e13696e23d16f87f84a59989edf00f20655
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 9, 2026.
Transparency logRelease files / paymos-0.1.4-cp311-abi3-musllinux_1_2_aarch64.whl
| Download URL | paymos-0.1.4-cp311-abi3-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 670.1 kB |
| Tags | CPython 3.11 Linux musl 1.2+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
639d9ef7aaf45390417c5b8a6bc2147c72a577d3266063edcb04559052f1ff18
|
|
BLAKE2b-256 checksum How to use checksums |
9b00b1b90592031e3ac9e89e0cd1c9999ebdb3f890fb5747adf7c081264b0939
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 9, 2026.
Transparency logRelease files / paymos-0.1.4-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | paymos-0.1.4-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 512.1 kB |
| Tags | CPython 3.11 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
dce84de79f092c4c03010bfa5c30ef64998211715ad40f04e4289cdfb6d0b9d2
|
|
BLAKE2b-256 checksum How to use checksums |
cba9162534dc61543a95f97a4cfecb017ed3c6b0d2c6e692bbe9625a64682094
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 9, 2026.
Transparency logRelease files / paymos-0.1.4-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | paymos-0.1.4-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 494.8 kB |
| Tags | CPython 3.11 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
343f2025dc131b45e1937ee037ba305e80d7262acc56c7abbdbf5d3861bfc9a7
|
|
BLAKE2b-256 checksum How to use checksums |
891eb5afc6d4c8e1b05f211511c1626eeef8b39a760e9be839e2686b4bd4ff21
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 9, 2026.
Transparency logRelease files / paymos-0.1.4-cp311-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
| Download URL | paymos-0.1.4-cp311-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl |
|---|---|
| Size | 884.7 kB |
| Tags | CPython 3.11 abi3 macOS 10.12+ universal2 (ARM64, x86-64) macOS 10.12+ x86-64 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
4cbd91427144b1fd855903b6d0d6b4cac89447a081694446dad71a09a3263161
|
|
BLAKE2b-256 checksum How to use checksums |
f00c416592b34e808c61d5cd022a795cca2e7fe156b029ab8b236f86b601ba41
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 9, 2026.
Transparency log