Skip to main content

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 / route fee 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 exactly amount; the vault debit (amount + fees) is server-computed.
  • mode="total_in" — amount is the total debited from the vault; the recipient gets that minus fees.

Safety caps

  • slippage_bps (swap / quote_swap, default 50 = 0.5%) — the slippage tolerance sent with the quote in basis points; the quote's guaranteed receive.min already reflects it.
  • min_receive (swap) — a human decimal in the receive asset. If the quote's guaranteed receive is below it, raises SlippageExceeded and signs nothing.
  • max_debit (swap / withdraw) — a human decimal ceiling on the total vault debit. Strongly recommended for unattended exact_out payouts, 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: int
  • Balance — asset, symbol, chain, decimals: int, amount_raw, usd: str | None
  • Amount — amount, asset
  • Receive — amount, min (guaranteed), asset
  • RouteFee — amount, estimate: bool (True = rate-derived cross-asset, False = exact same-asset spread)
  • Fees — asset, platform, network, route: RouteFee | None, total, usd: dict | None
  • Quote — 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 | None
  • Movement — 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)

Source distribution for paymos 0.1.4
File Size Uploaded
paymos-0.1.4.tar.gz 40.9 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for paymos 0.1.4
File
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 log

Release 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 log

Release 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 log

Release 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 log

Release 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 log

Release 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 log

Release 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

Release history Release notifications | RSS feed

This release

0.1.4 This release

7 release files

0.1.3

7 release files

0.1.2

3 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