Metadata-Version: 2.5
Name: usdf
Version: 0.1.2
Summary: Client for the USDF metered inference gateway: run a prompt paid from a prepaid key or per request with x402 in USDF or USDG, OpenAI-compatible chat completions with receipts, batch jobs, quotes, balances, keys, links, agents, and a LangChain chat model.
Project-URL: Homepage, https://usdf.fi
License-Expression: MIT
License-File: LICENSE
Keywords: inference,langchain,openai-compatible,receipts,robinhood-chain,usdf,x402
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.9
Provides-Extra: httpx
Requires-Dist: httpx>=0.27; extra == 'httpx'
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.3; extra == 'langchain'
Provides-Extra: wallet
Requires-Dist: eth-account>=0.13; extra == 'wallet'
Description-Content-Type: text/markdown

# usdf

Client for the USDF metered inference gateway, and a LangChain chat model. No required dependencies: the standard library's `urllib`, or `httpx` when it is installed. Python 3.9+. Paying from a wallet uses `eth-account`; the LangChain model uses `langchain-core`.

1 USDF = $1 of compute, redeemable 1:1 for USDG at any time. Every request is held for its worst case before it runs, billed from the provider's reported usage at the published price sheet, and answered with a receipt.

```
pip install usdf
```

## Run a prompt

```python
from usdf import USDF

# Pay per request from a wallet, no account: an eth-account account (or a private key).
client = USDF(wallet=wallet)
result = client.run("deepseek-v3.2", "Hello")
print(result.text, result.receipt.paid.usdf, result.receipt.asset)

# Or charge a prepaid balance with an API key.
prepaid = USDF(api_key="sk_...")
r = prepaid.run("deepseek-v3.2", "Hello", max_tokens=256)
print(r.text, r.receipt.cost.usdf, "USDF", r.receipt.request_id)
```

`run(model, prompt_or_messages, max_tokens=, system=, temperature=, provider=, on_text=)` returns `.text`, `.receipt` (the gateway's receipt; the full body is `receipt.raw`), `.id`, `.completion` and, over x402, `.payment` (the decoded `PAYMENT-RESPONSE`). `on_text` streams the answer piece by piece.

Unset, `api_key`, `base_url` (the gateway origin, default `https://api.usdf.fi`), `session_token`, `rpc_url`, `max_amount` and `pay_to` come from `USDF_API_KEY`, `USDF_BASE_URL`, `USDF_SESSION_TOKEN`, `USDF_RPC_URL`, `USDF_MAX_AMOUNT` and `USDF_PAY_TO`. A wallet given explicitly is the payer; the environment's key is not read then.

### Paying from a wallet (x402)

The gateway answers an unpaid request with 402 and two accepted payments for the same amount: USDF through Permit2 and USDG through an EIP-3009 authorization. Neither costs the payer gas. The client reads the wallet's USDF allowance to Permit2 first, through `rpc_url` (or `USDF_RPC_URL`; unset, the chain's public RPC `https://rpc.mainnet.chain.robinhood.com`), which must answer for chain 4663: when it covers the amount it pays in USDF, otherwise in USDG, which needs no approval. A first-time payer whose USDF is not approved therefore pays USDG rather than a USDF payment the gateway would refuse. `asset="USDF"` or `"USDG"` fixes the choice; `approve=True` signs and sends the one approval a USDF payer ever needs, `approve(Permit2, max)` on USDF, through the same RPC. The receipt carries `paid` (what was charged, the quote), `usage_cost`, and the payment's `asset`, `method`, `payer` and `payment_tx`.

The 402 comes from whatever server answers at the base URL, so the client signs nothing on its word alone. It pays only: on Robinhood Chain (`eip155:4663`); USDF (`0x4A35a5eC2A50A4d2957B2F2366E017f526cAB318`) through Permit2 or USDG (`0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168`) through EIP-3009 under USDG's own domain (`Global Dollar`, version `1`); to the payee the gateway names: its own 402 when the base URL is the gateway (`https://api.usdf.fi`), else the address the gateway's `GET https://api.usdf.fi/v1/config` publishes (`x402.pay_to`), else the gateway's hot wallet (`0x13009Bd7116f8C955f92409467743ee67AB1990b`); `pay_to` (or `USDF_PAY_TO`, comma-separated) pins another payee, and then only that one; with a validity window of at most an hour; and at most `max_amount` base units per payment (`USDF_MAX_AMOUNT`; default 1000000, 1 USDF). Anything else raises `USDFError` with type `payment_refused` and a code (`untrusted_network`, `untrusted_asset`, `untrusted_domain`, `untrusted_payee`, `amount_over_limit`, `untrusted_amount`, `untrusted_timeout`, `wrong_chain`) before any approval, read or signature. The exact scheme charges the quote, the request's worst case at its `max_tokens`, so a lower `max_tokens` is the way to a smaller payment.

```python
from eth_account import Account
from usdf import USDF

client = USDF(wallet=Account.from_key(key), rpc_url="https://rpc.mainnet.chain.robinhood.com", approve=True)
```

`sign_exact_payment` (which applies the same checks), `check_accept`, `payment_limits`, `choose_accept`, `wallet_signer`, `read_permit2_allowance`, `approve_permit2`, the header codecs and the constants (`CHAIN_ID`, `USDF_ADDRESS`, `USDG_ADDRESS`, `GATEWAY_PAYEE`, `PUBLIC_RPC_URL`, `DEFAULT_MAX_AMOUNT`) are exported for other signers.

## LangChain

```python
from usdf.langchain import USDFChat

llm = USDFChat(model="deepseek-v3.2", wallet=wallet)   # or api_key="sk_..."
reply = llm.invoke("Hello")
print(reply.content, reply.response_metadata["receipt"]["request_id"], reply.usage_metadata)
```

`pip install usdf langchain` (or `usdf[langchain]` for `langchain-core` alone). `stream()` yields the answer as it arrives and ends with a chunk carrying the receipt. The settings are those of `USDF`: `api_key`, `wallet`, `base_url`, `asset`, `approve`, `rpc_url`, `max_amount`, `pay_to`, plus `max_tokens`, `temperature` and `provider` (privacy and routing asks). `api_key` is a secret (masked, left out of `model_dump`, named in `lc_secrets`) and `wallet` is left out of the model's repr and of the serialized form LangChain hands to callbacks and tracers.

## Chat completions

```python
client = USDF(api_key="sk_...")
c = client.chat.completions.create("deepseek-v3.2", [{"role": "user", "content": "Hello"}], max_tokens=256)
print(c.content, c.receipt.cost.usdf)

s = client.chat.completions.create("deepseek-v3.2", messages, max_tokens=256, stream=True)
for chunk in s:
    print((chunk["choices"][0]["delta"].get("content") if chunk["choices"] else "") or "", end="")
print(s.receipt.cost.usdf if s.receipt else "stopped early")
```

Keyword arguments are sent as given (`max_tokens`, `temperature`, `tools`, `provider`, ...). A stream exposes `request_id` from the first byte and `receipt` once read to the end; `interrupted` is set when the provider cut the answer (billed, receipt `partial`); a stream that produced nothing raises `USDFError` `upstream_unavailable` and nothing is charged. A request served on attested hardware carries `receipt.attestation`; `client.receipts.attestation(id)` returns the provider's signed receipt.

## Batch jobs

```python
job = client.batches.create(batch_model, messages, max_tokens=256)   # a model whose price carries batch_input
done = client.batches.wait(job["id"])                                 # the completion; done.receipt.batch is True
```

## Before and after a call

`quote(model, prompt=, max_tokens=)` (no key), `balance()`, `receipts.get(id)` (with `log`, the request's leaf in the usage log), `receipts.list(limit=, model=, status=)` (a key's own, or with only a session the account's), `models.list()`, `pricing()`, `embeddings.create(model, input)`. `usdf.estimate` reproduces the gateway's hold arithmetic.

## The account (session)

Everything a key can do (requests, quotes, the balance, receipts, tools) needs only the key: create one on the dashboard at usdf.fi. The account's own calls take a session: sign in at usdf.fi and pass its session token, `USDF(session_token=...)` or `USDF_SESSION_TOKEN` (it lasts about an hour). There is no email and password sign-in. `client.auth.info()` reads `GET /v1/auth` (the sign-in in force and every account path); `client.auth.sign_out()` ends the session at the gateway.

- `keys.create(label=, budget_units=, rate_limit_rpm=)`, `keys.list(revoked=)`, `keys.revoke(id)`, `keys.rotate(id)` (a new key with the old one's controls; the old one is revoked), `keys.limits(id)`, `keys.update(id, spend_limit_hour_units=, spend_limit_day_units=, spend_limit_month_units=, allowed_models=, paused=, no_logs=, private_tier=, webhook_url=)`, `keys.pause(id)`, `keys.resume(id)`.
- `account.get()`, `account.overview()`, `account.activity(model=, status=, from_=, to=, limit=)`, `account.activity_csv(...)`, `account.settings()`, `account.update_settings(...)`, `account.analytics(days=)`, `account.withdraw(units, chain_id=)` (to the linked wallet).
- `links.create(units, memo=, expires_in_days=)` (the token is returned once), `links.list()`, `links.get(token)`, `links.claim(token)`, `links.cancel(link_id)`.
- `agents.create(slug, name, **policy)`, `agents.list()`, `agents.get(slug)`, `agents.update(slug, **policy)`, `agents.fund(slug, units, direction=, idempotency_key=)`, `agents.pay(slug, to, units, memo=, idempotency_key=)`, `agents.payments(slug)`, `agents.rotate_key(slug)`.
- `zap.quote(token, amount, recipient=, slippage_bps=)`: ETH or a token with a liquidity pool against USDG, into USDF, with the transactions to send. No key.
- `pool.read()`: the USDF/USDG pool's price, tick, whether it sits in the seeded band, its liquidity and reserves. No key.

Money: base units (1 unit = $0.000001 = 0.000001 USDF); amounts are `Amount(units, usd, usdf)` or `{units, usd, usdf}`.

## Errors

Every refusal raises `USDFError` with the gateway's `status`, `type`, `code`, `message`, `request_id` and `retry_after`. Nothing is charged for a refusal.

## Changes

**0.1.2.** A wallet payment goes to the payee the gateway names, not to a hot wallet fixed in the package: the gateway rotated its custody hot wallet on 2026-10-26, and 0.1.1, which pays only the retired one (`0x52Fe...9F05`), refuses the new one's 402 with `untrusted_payee` before signing anything. The 402's payee is taken as it stands from `https://api.usdf.fi`; through any other base URL it must be the one `https://api.usdf.fi/v1/config` publishes, or the new hot wallet (`GATEWAY_PAYEE`). `pay_to` and `USDF_PAY_TO` still pin a payee.

**0.1.1.** The default base URL is `https://api.usdf.fi`, the gateway's own domain; 0.1.0 used the hosting provider's hostname for the same gateway, which still answers. `base_url` or `USDF_BASE_URL` still points the client elsewhere. No other change.
