smart402: Python SDK
Deterministic policy engine for AI agent payments via x402.
No LLM in the decision path. Every approve/deny traces to a rule your team configured (not a model's judgment call). A compromised agent cannot reason or prompt-inject its way past smart402.
smart402 currently supports USDC transactions on Base (eip155:8453). Other tokens and chains are on the roadmap.
Install
pip install smart402
For x402 integration extras:
pip install "smart402[x402]"
Python 3.10+ required.
Verify the service is reachable
curl https://api.smart402.com/health
# {"status":"healthy","version":"0.4.0","database":"connected","redis":"connected"}
Before you start
- Sign up at https://www.smart402.com
- Create an agent in the dashboard
- Configure at least one policy (e.g., daily budget of $10)
- Create an evaluate-scoped API key in Settings → API Keys
- Follow the Quick Start below.
Quick Start
import asyncio
import os
from smart402 import Smart402Client
async def main():
client = Smart402Client(
api_key=os.environ["SMART402_AGENT_KEY"],
agent_id="my-agent-001",
)
result = await client.evaluate_payment(
amount="100000", # Raw x402 token units as an integer string. "100000" = $0.10 USDC (6 decimals). Pass the value directly from the x402 PaymentRequirements object.
token="USDC",
network="eip155:8453", # Base mainnet (CAIP-2)
pay_to="0x9dBA414637c611a16BEa6f0796BFcbcBdc410df8",
)
print(result.decision) # "approve" or "deny"
print(result.triggered_rules) # [] or ["counterparty_not_on_allowlist", ...]
asyncio.run(main())
Synchronous usage: asyncio.run() wraps any async call.
x402 Integration
If your agent uses the x402 Python SDK, register smart402 as a lifecycle hook:
from smart402 import smart402_hook
from x402 import x402Client
from x402.mechanisms.evm.exact import register_exact_evm_client
from x402.mechanisms.evm.signers import EthAccountSigner
from eth_account import Account
account = Account.from_key(os.environ["EVM_PRIVATE_KEY"])
signer = EthAccountSigner(account)
client = x402Client()
register_exact_evm_client(client, signer)
# One line to add smart402 protection
client.on_before_payment_creation(
smart402_hook(
api_key=os.environ["SMART402_AGENT_KEY"],
agent_id="my-agent-001",
agent_wallet_address=signer.address,
)
)
# Every payment the x402 client makes is now evaluated first
The hook fires before each payment is signed. If smart402 denies the payment, AbortResult is returned and the payment is not made.
Smart402Guard
Smart402Guard is the class-based interface introduced in v0.2.0. It separates evaluation from signing: smart402 evaluates the payment; your wallet signs it. Pass wallet_address as a plain string — works with eth_account, CDP wallets (AgentKit), Privy, hardware wallets, or any signing mechanism.
from smart402 import Smart402Guard
guard = Smart402Guard(
api_key=os.environ["SMART402_AGENT_KEY"],
agent_id="my-agent-001",
wallet_address="0x...", # plain string — no signer object required
)
# Register with x402:
client.on_before_payment_creation(guard.as_hook())
CDP wallet example
from cdp import CdpClient
from smart402 import Smart402Guard
async with CdpClient() as cdp:
wallet = await cdp.evm.get_or_create_account(name="my-agent")
guard = Smart402Guard(
api_key=os.environ["SMART402_AGENT_KEY"],
agent_id="my-agent-001",
wallet_address=wallet.address, # CDP wallet address as string
wallet_provider="coinbase",
)
client.on_before_payment_creation(guard.as_hook())
Smart402Guard(api_key, agent_id, ...)
| Parameter | Default | Description |
|---|---|---|
api_key |
required | smart402 API key |
agent_id |
required | Agent identifier (from dashboard) |
wallet_address |
None |
Agent's EVM address as a plain string |
smart402_url |
https://api.smart402.com |
API base URL |
fail_mode |
"fail_open" |
Behavior when API is unreachable |
wallet_provider |
None |
e.g. "coinbase", "local_evm" |
agent_framework |
None |
e.g. "langchain", "langgraph" |
guard.as_hook() — returns the async callback for client.on_before_payment_creation(). Equivalent to calling smart402_hook() directly with the same parameters.
Advanced Usage
For full control over all request fields, use the Pydantic models directly:
from smart402 import Smart402Client
from smart402.models import EvaluateRequest, PaymentRequirementsPayload
client = Smart402Client(
api_key=os.environ["SMART402_AGENT_KEY"],
agent_id="my-agent-001",
)
result = await client.evaluate(
EvaluateRequest(
agent_id=client.agent_id, # set in the constructor above
agent_wallet_address="0x...",
payment_requirements=PaymentRequirementsPayload(
amount="0.10",
token="USDC",
network="eip155:8453",
pay_to="0x9dBA414637c611a16BEa6f0796BFcbcBdc410df8",
),
)
)
Configuration
Smart402Client(api_key, agent_id, ...)
| Parameter | Default | Description |
|---|---|---|
api_key |
required | smart402 API key |
agent_id |
required | Agent identifier (from dashboard) |
base_url |
https://api.smart402.com |
API base URL |
Amount format: Pass amount as raw x402 token units: an integer string such as "100000" for $0.10 USDC (6 decimals). The SDK converts to decimal automatically before sending to the API. Pass the value directly from the x402 PaymentRequirements object without conversion. Raises ValueError if the value is not a positive integer string.
Lifecycle: Smart402Client reuses a single HTTP connection across calls. Call await client.aclose() when done, or use it as an async context manager:
async with Smart402Client(api_key="...", agent_id="...") as client:
result = await client.evaluate_payment(...)
smart402_hook(api_key, agent_id, ...)
| Parameter | Default | Description |
|---|---|---|
api_key |
required | smart402 API key |
agent_id |
required | Agent identifier (from dashboard) |
smart402_url |
https://api.smart402.com |
API base URL |
fail_mode |
"fail_open" |
Behavior when API is unreachable |
agent_wallet_address |
None |
Agent's public EVM address |
wallet_provider |
None |
e.g. "coinbase", "local_evm" |
agent_framework |
None |
e.g. "langchain", "langgraph" |
Fail-Open vs Fail-Closed
# fail_open (default): if smart402 is unreachable, payment proceeds
smart402_hook(api_key="...", agent_id="...", fail_mode="fail_open")
# fail_closed: if smart402 is unreachable, payment is blocked
smart402_hook(api_key="...", agent_id="...", fail_mode="fail_closed")
| Mode | When API is unreachable |
|---|---|
fail_open (default) |
Warning logged, payment proceeds |
fail_closed |
AbortResult returned to x402; payment is not made |
Error Handling
result = await client.evaluate_payment(
amount="100000", token="USDC",
network="eip155:8453", pay_to="0x...",
)
if result.decision == "deny":
print("Blocked by:", result.triggered_rules)
print("Evaluation ID:", result.evaluation_id)
When using smart402_hook(), a denied payment returns AbortResult to the x402 client; the payment is not made and no exception is raised to your code. When calling Smart402Client.evaluate() directly, check result.decision; the client always returns the response, never raises on denial.
What data leaves your machine
The SDK sends to the smart402 API:
- amount, token, network, recipient address
- agent ID and wallet address (public, not private key)
The SDK never sends:
- Private keys, seed phrases, or wallet passwords
- Signed transactions or raw transaction data
- Wallet balances
One HTTPS call to POST /evaluate. No telemetry, no analytics, no side-channel requests.
Verify: the SDK is ~200 lines of code. Read it.
Read the full trust model: SECURITY.md
Limits
- Rate limit: 600 requests per minute per account
- Typical latency: 10–50ms (p50), under 200ms (p99)
- If the API is unreachable,
fail_open(default) lets the payment proceed.fail_closedblocks it. - The SDK does not retry on failure: it returns the error immediately, keeping latency predictable and letting you own retry logic.
- Default request timeout: 5 seconds
API Reference
Full endpoint documentation: API.md
License
Apache 2.0. See LICENSE
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file smart402-0.5.0.tar.gz.
File metadata
- Download URL: smart402-0.5.0.tar.gz
- Upload date:
- Size: 17.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ea5a20e01d5e4dfcd64bc8c5b5bbe724ac9c4f217b32aff181be26b0cf494312
|
|
| MD5 |
29437b3becdae7e4bfb39bc1d6dd7095
|
|
| BLAKE2b-256 |
eaea43af048ddc4aa3847b7acf6dc747ec4b1b0496f7227cb276388f320c14af
|
File details
Details for the file smart402-0.5.0-py3-none-any.whl.
File metadata
- Download URL: smart402-0.5.0-py3-none-any.whl
- Upload date:
- Size: 12.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
88c43555d428c903bcdb496165e93080787327e1739788feae4ce604b43d4bae
|
|
| MD5 |
8049b651484ee470254d6ea0bc4ca216
|
|
| BLAKE2b-256 |
4b43f1c2d9dfdff2e74fa4dc3ece31122e6d2db22433ea456d2fd6c4929520a6
|