Python SDK for interacting with the 4Mica payment network.
Project description
4Mica Python SDK
The official Python SDK for interacting with the 4Mica payment network.
Overview
This SDK provides:
- User Client: deposit collateral, sign payments, and manage withdrawals in ETH or ERC20 tokens
- Recipient Client: create payment tabs, verify payment guarantees, and claim from user collateral
- X402 Flow Helper: generate X-PAYMENT headers for 402-protected HTTP resources via an X402-compatible service
- Admin RPCs: manage user suspension and admin API keys (when authorized)
Installation
pip install sdk-4mica
Install BLS support for on-chain remuneration:
pip install 'sdk-4mica[bls]'
Python 3.9+ is required.
Initialization and Configuration
The SDK requires a signing key and can use sensible defaults for the rest:
wallet_private_key(required): private key used for on-chain gateway operationsevm_signer(optional): custom signer implementingEvmSignerfor payment/auth signingrpc_url(optional): URL of the 4Mica core RPC server. Defaults tohttps://api.4mica.xyz/.ethereum_http_rpc_url(optional): Ethereum JSON-RPC endpoint; fetched from core if omittedcontract_address(optional): Core4Mica contract address; fetched from core if omittedbearer_token(optional): static bearer token for authauth_urlandauth_refresh_margin_secs(optional): SIWE auth config. Only used when auth is enabled viaenable_auth()or when set via env (defaults torpc_urland 60 seconds).
Note: wallet_private_key is currently required even when you supply evm_signer, because the
client always initializes the on-chain gateway.
1) Using ConfigBuilder
import asyncio
from fourmica_sdk import Client, ConfigBuilder
async def main() -> None:
cfg = (
ConfigBuilder()
.rpc_url("https://api.4mica.xyz/")
.wallet_private_key("0x...")
.build()
)
client = await Client.new(cfg)
try:
# use client.user, client.recipient, client.rpc
pass
finally:
await client.aclose()
if __name__ == "__main__":
asyncio.run(main())
2) Using Environment Variables
Set environment variables (example .env):
4MICA_WALLET_PRIVATE_KEY="0x..."
4MICA_RPC_URL="https://api.4mica.xyz/"
4MICA_ETHEREUM_HTTP_RPC_URL="http://localhost:8545"
4MICA_CONTRACT_ADDRESS="0x..."
4MICA_BEARER_TOKEN="Bearer <access_token>"
4MICA_AUTH_URL="https://api.4mica.xyz/"
4MICA_AUTH_REFRESH_MARGIN_SECS="60"
If you want to set them inline for a single command, use env since most shells do not allow
variable names that start with a digit:
env 4MICA_WALLET_PRIVATE_KEY="0x..." 4MICA_RPC_URL="https://api.4mica.xyz/" python app.py
Then in code:
from fourmica_sdk import Client, ConfigBuilder
cfg = ConfigBuilder().from_env().build()
client = await Client.new(cfg)
3) Using a Custom Signer
Provide a signer that implements EvmSigner (must expose address, sign_typed_data, and
sign_message). The private key is still required for on-chain operations.
from fourmica_sdk import Client, ConfigBuilder, LocalAccountSigner
signer = LocalAccountSigner("0x...")
cfg = ConfigBuilder().wallet_private_key("0x...").evm_signer(signer).build()
client = await Client.new(cfg)
SIWE Auth (Optional)
Enable automatic SIWE auth refresh, or pass a static bearer token:
from fourmica_sdk import Client, ConfigBuilder
cfg = (
ConfigBuilder()
.wallet_private_key("0x...")
.rpc_url("https://api.4mica.xyz/")
.enable_auth()
.build()
)
client = await Client.new(cfg)
await client.login() # optional: first RPC call also triggers auth
Or use a static token:
cfg = (
ConfigBuilder()
.wallet_private_key("0x...")
.bearer_token("Bearer <access_token>")
.build()
)
Env vars: 4MICA_BEARER_TOKEN, 4MICA_AUTH_URL, 4MICA_AUTH_REFRESH_MARGIN_SECS.
Usage
The SDK exposes three main entry points:
client.user: payer-side operations (collateral, signing, withdrawals)client.recipient: recipient-side operations (tabs, guarantees, remuneration)X402Flow: helper for 402-protected HTTP resources
Low-level admin RPCs are available under client.rpc (requires an admin API key):
client.rpc.with_admin_api_key("ak_...")
await client.rpc.update_user_suspension("0xUser", True)
Direct SDK Quick Start
This direct flow talks to 4mica core and shows the four core actions: deposit, open a tab, request
a guarantee, and settle (remunerate) it on-chain. The payer and recipient roles are separate in
production, so the SDK uses different keys. The snippets below are copied from the runnable scripts
in examples/recipient_quickstart.py and examples/payer_quickstart.py.
Key requirements:
- Payer flow uses the private key of the user
PAYER_KEYandRECIPIENT_ADDRESS.
Four-step direct flow:
- Deposit collateral (payer). For ETH, call
payer_client.user.deposit(amount). For ERC20, callpayer_client.user.approve_erc20(token, amount)first, thenpayer_client.user.deposit(amount, token).tokenis the contract address of the USDC/USDT. - Get
tab_idandreq_id(recipient). Callrecipient_client.recipient.create_tab(...)which hits core/core/payment-tabs. The SDK returnstab_id; compute the nextreq_idby callinglatest = await recipient_client.recipient.get_latest_guarantee(tab_id)and usingreq_id = 0 if latest is None else latest.req_id + 1. - Sign the guarantee (payer). Build
PaymentGuaranteeRequestClaimswithtab_id,req_id, amount, timestamp, and asset, then callsignature = await payer_client.user.sign_payment(claims, SigningScheme.EIP712). - Settle (recipient). Call
cert = await recipient_client.recipient.issue_payment_guarantee(...)with the claims + payer signature, thenawait recipient_client.recipient.remunerate(cert)to settle on chain.
Recipient (resource server) quick start
Recipient is the service provider that accepts a payer's credit. Each recipient can open a tab
with a user, a configurable line of credit that enables instant, trustless fair exchange between
payer and merchant. Run this script first to open a tab, compute the next REQ_ID, and print the
tab's asset address plus the amount the payer should sign.
export RECIPIENT_KEY=0x..
export USER_ADDRESS=0x.. # payer address
export AMOUNT_WEI=100000000000000000
import asyncio
import os
from eth_account import Account
from fourmica_sdk import Client, ConfigBuilder
RECIPIENT_KEY = os.environ["RECIPIENT_KEY"]
USER_ADDRESS = os.environ["USER_ADDRESS"]
AMOUNT_WEI = int(os.getenv("AMOUNT_WEI", "100000000000000000"), 0)
async def main() -> None:
recipient_cfg = ConfigBuilder().from_env().wallet_private_key(RECIPIENT_KEY).build()
recipient_client = await Client.new(recipient_cfg)
try:
recipient_address = Account.from_key(RECIPIENT_KEY).address
tab_id = await recipient_client.recipient.create_tab(
user_address=USER_ADDRESS,
recipient_address=recipient_address,
erc20_token=None,
ttl=None,
)
latest = await recipient_client.recipient.get_latest_guarantee(tab_id)
req_id = latest.req_id + 1 if latest else 0
tab = await recipient_client.recipient.get_tab(tab_id)
asset_address = tab.asset_address if tab else None
print("TAB_ID=", tab_id)
print("REQ_ID=", req_id)
print("AMOUNT_WEI=", AMOUNT_WEI)
print("ASSET_ADDRESS=", asset_address)
finally:
await recipient_client.aclose()
if __name__ == "__main__":
asyncio.run(main())
Payer (client) quick start
Run this after you have a TAB_ID and REQ_ID from the recipient. It signs the guarantee and
optionally issues a certificate if you also pass RECIPIENT_KEY.
export PAYER_KEY=0x..
export RECIPIENT_ADDRESS=0x..
export TAB_ID=<tab_id>
export REQ_ID=<req_id>
export AMOUNT_WEI=100000000000000000
export ASSET_ADDRESS=0x0000000000000000000000000000000000000000
export RECIPIENT_KEY=0x.. # optional
import asyncio
import json
import os
import time
from eth_account import Account
from fourmica_sdk import (
AssetBalanceInfo,
Client,
ConfigBuilder,
PaymentGuaranteeRequestClaims,
SigningScheme,
)
PAYER_KEY = os.environ["PAYER_KEY"]
RECIPIENT_ADDRESS = os.environ["RECIPIENT_ADDRESS"]
TAB_ID = int(os.environ["TAB_ID"], 0)
REQ_ID = int(os.environ["REQ_ID"], 0)
DEFAULT_ASSET_ADDRESS = "0x0000000000000000000000000000000000000000"
REQUESTED_AMOUNT_WEI = int(os.getenv("AMOUNT_WEI", "100000000000000000"), 0)
ASSET_ADDRESS = os.getenv("ASSET_ADDRESS") or DEFAULT_ASSET_ADDRESS
RECIPIENT_KEY = os.getenv("RECIPIENT_KEY")
async def main() -> None:
payer_cfg = ConfigBuilder().from_env().wallet_private_key(PAYER_KEY).build()
payer_client = await Client.new(payer_cfg)
recipient_client = None
try:
user_address = Account.from_key(PAYER_KEY).address
amount_wei = REQUESTED_AMOUNT_WEI
balance_raw = await payer_client.rpc.get_user_asset_balance(
user_address, ASSET_ADDRESS
)
if balance_raw:
balance = AssetBalanceInfo.from_rpc(balance_raw)
available = max(balance.total - balance.locked, 0)
print("COLLATERAL_TOTAL=", balance.total)
print("COLLATERAL_LOCKED=", balance.locked)
print("COLLATERAL_AVAILABLE=", available)
if available <= 0:
raise SystemExit("No available collateral for this asset.")
if amount_wei > available:
amount_wei = available
if amount_wei <= 0:
raise SystemExit("Requested amount exceeds available collateral.")
else:
print("COLLATERAL_TOTAL= <unknown>")
print("COLLATERAL_LOCKED= <unknown>")
print("COLLATERAL_AVAILABLE= <unknown>")
print("AMOUNT_WEI=", amount_wei)
claims = PaymentGuaranteeRequestClaims.new(
user_address=user_address,
recipient_address=RECIPIENT_ADDRESS,
tab_id=TAB_ID,
req_id=REQ_ID,
amount=amount_wei,
timestamp=int(time.time()),
erc20_token=ASSET_ADDRESS,
)
signature = await payer_client.user.sign_payment(claims, SigningScheme.EIP712)
print("PAYER_SIGNATURE=", signature.signature)
print(
"CLAIMS_JSON=",
json.dumps(
{
"user_address": claims.user_address,
"recipient_address": claims.recipient_address,
"tab_id": claims.tab_id,
"req_id": claims.req_id,
"amount": claims.amount,
"asset_address": claims.asset_address,
"timestamp": claims.timestamp,
}
),
)
if RECIPIENT_KEY:
recipient_cfg = (
ConfigBuilder().from_env().wallet_private_key(RECIPIENT_KEY).build()
)
recipient_client = await Client.new(recipient_cfg)
cert = await recipient_client.recipient.issue_payment_guarantee(
claims, signature.signature, SigningScheme.EIP712
)
print("CERT_CLAIMS=", cert.claims)
print("CERT_SIGNATURE=", cert.signature)
finally:
if recipient_client is not None:
await recipient_client.aclose()
await payer_client.aclose()
if __name__ == "__main__":
asyncio.run(main())
X402 Flow (HTTP 402)
The X402 helper turns paymentRequirements from a 402 Payment Required response into an
X-PAYMENT header (and optional /settle call) that the facilitator will accept.
What the SDK expects from paymentRequirements
At minimum you need:
schemeandnetwork(scheme must include4mica, e.g.4mica-credit)extra.tabEndpointfor tab resolution
X402Flow will refresh the tab by calling extra.tabEndpoint before signing.
X402 Version 1
Version 1 returns payment requirements in the JSON response body:
import asyncio
from fourmica_sdk import Client, ConfigBuilder, X402Flow
from fourmica_sdk import PaymentRequirementsV1
async def main() -> None:
cfg = ConfigBuilder().wallet_private_key("0x...").build()
client = await Client.new(cfg)
flow = X402Flow.from_client(client)
# 1) GET the protected endpoint and parse JSON body
res = fetch_resource_somehow()
requirements = PaymentRequirementsV1.from_raw(res["accepts"][0])
# 2) Build the X-PAYMENT header with the SDK
payment = await flow.sign_payment(requirements, "0xUser")
# 3) Call the protected resource with the header
headers = {"X-PAYMENT": payment.header}
await call_resource_somehow(headers)
await client.aclose()
if __name__ == "__main__":
asyncio.run(main())
X402 Version 2
Version 2 uses the payment-required header (base64-encoded) instead of a JSON response body:
import asyncio
import base64
import json
from fourmica_sdk import Client, ConfigBuilder, X402Flow
from fourmica_sdk import X402PaymentRequired, PaymentRequirementsV2
async def main() -> None:
cfg = ConfigBuilder().wallet_private_key("0x...").build()
client = await Client.new(cfg)
flow = X402Flow.from_client(client)
# 1) GET the protected endpoint and extract payment-required header
res = fetch_resource_somehow()
header = res.headers.get("payment-required")
if not header:
raise RuntimeError("Missing payment-required header")
# 2) Decode the header
decoded = base64.b64decode(header).decode("utf8")
payment_required = X402PaymentRequired.from_raw(json.loads(decoded))
# 3) Select a payment option
accepted = payment_required.accepts[0]
# 4) Build the PAYMENT-SIGNATURE header with the SDK
signed = await flow.sign_payment_v2(payment_required, accepted, "0xUser")
# 5) Call the protected resource with the header
headers = {"PAYMENT-SIGNATURE": signed.header}
await call_resource_somehow(headers)
await client.aclose()
if __name__ == "__main__":
asyncio.run(main())
Resource server / facilitator side
If your resource server proxies to the facilitator, you can reuse the SDK to settle after verifying:
from fourmica_sdk import Client, ConfigBuilder, X402Flow
from fourmica_sdk import PaymentRequirementsV1, X402SignedPayment
async def settle(
facilitator_url: str,
payment_requirements: PaymentRequirementsV1,
payment: X402SignedPayment,
) -> None:
core = await Client.new(
ConfigBuilder().wallet_private_key("0x...").build()
)
flow = X402Flow.from_client(core)
settled = await flow.settle_payment(payment, payment_requirements, facilitator_url)
print("settlement result:", settled.settlement)
await core.aclose()
Notes:
sign_paymentandsign_payment_v2always use EIP-712 signing and will error if the scheme is not 4mica.UserClient.sign_paymentsupportsSigningScheme.EIP712(default) andSigningScheme.EIP191.settle_paymentonly hits/settle; resource servers should still call/verifyfirst when enforcing access.
Concepts
- Tabs are per
(user, recipient, asset)credit ledgers. Core reuses an existing tab if it is still valid; otherwise it creates a new tab id using a SHA-256 hash of user, recipient, ttl, and a random UUID (treattab_idas opaque). - Tab lifecycle: tabs start
Pending; the first valid guarantee opens the tab and setsstart_timestampto the claim timestamp. Guarantees must be within[start_timestamp, start_timestamp + ttl]and are rejected if the tab is closed or expired. - Request ids:
req_idis per-tab and strictly sequential. The first guarantee usesreq_id = 0; each new guarantee must belast_req_id + 1. The facilitator returnsnextReqIdin/tabs. - Guarantee request claims (v1) are the signed payload:
{ user_address, recipient_address, tab_id, req_id, amount, asset_address, timestamp }.asset_addressis the zero address for ETH if omitted.timestampis seconds since epoch and is validated by core. - Guarantee certificates are BLS signatures over
PaymentGuaranteeClaims(core addsdomain,total_amountwhich is the running sum for the tab, andversion). The SDK models this asBLSCert { claims, signature }, whereclaimsis ABI-encoded hex.
X-PAYMENT header schema
X-PAYMENT is a base64-encoded JSON envelope:
{
"x402Version": 1,
"scheme": "4mica-credit",
"network": "polygon-amoy",
"payload": {
"claims": {
"user_address": "<0x-prefixed checksum string>",
"recipient_address": "<0x-prefixed checksum string>",
"tab_id": "<decimal or 0x value>",
"req_id": "<decimal or 0x value>",
"amount": "<decimal or 0x value>",
"asset_address": "<0x-prefixed checksum string>",
"timestamp": 1716500000,
"version": "v1"
},
"signature": "<0x-prefixed wallet signature>",
"scheme": "eip712"
}
}
Facilitators enforce that scheme / network match /supported, payTo matches
recipient_address in the claims, and asset / maxAmountRequired equal the signed amount.
End-to-end credit flow (x402)
- Resource sends
402 Payment Requiredwith(scheme, network)and a tab endpoint. - Client calls the tab endpoint with
{ userAddress, erc20Token?, ttlSeconds? }; the recipient calls/tabsand returns tab metadata. - Client signs a guarantee with the SDK and wraps it into
X-PAYMENT. - Client retries the protected call with
X-PAYMENT: <base64>. - Recipient optionally calls
/verifywith{ x402Version, paymentHeader, paymentRequirements }. - Recipient calls
/settleto obtain the certificate, then relays repayment details to the payer.
API Methods Summary
UserClient Methods
approve_erc20(token, amount)deposit(amount, erc20_token=None)get_user()get_tab_payment_status(tab_id)sign_payment(claims, scheme=SigningScheme.EIP712)pay_tab(tab_id, req_id, amount, recipient_address, erc20_token=None)request_withdrawal(amount, erc20_token=None)cancel_withdrawal(erc20_token=None)finalize_withdrawal(erc20_token=None)
RecipientClient Methods
create_tab(user_address, recipient_address, erc20_token, ttl)get_tab_payment_status(tab_id)issue_payment_guarantee(claims, signature, scheme)verify_payment_guarantee(cert)remunerate(cert)list_settled_tabs()list_pending_remunerations()get_tab(tab_id)list_recipient_tabs(settlement_statuses=None)get_tab_guarantees(tab_id)get_latest_guarantee(tab_id)get_guarantee(tab_id, req_id)list_recipient_payments()get_collateral_events_for_tab(tab_id)get_user_asset_balance(user_address, asset_address)
Admin / RPC Methods
Available under client.rpc (requires an admin API key):
update_user_suspension(user_address, suspended)create_admin_api_key({"name": ..., "scopes": [...]})list_admin_api_keys()revoke_admin_api_key(key_id)
Error Handling
All SDK errors extend FourMicaError. Common error types include ConfigError, RpcError,
ClientInitializationError, ContractError, SigningError, VerificationError, X402Error, and
AuthError.
Notes
- All methods are
async; useasyncio.runor your event loop of choice. - Remuneration requires
py-ecc(pip install 'sdk-4mica[bls]') to expand BLS signatures into the on-chain format. - Numeric values accept
intor hex/decimal strings and are serialized to0x-prefixed hex when sent to the facilitator.
License
MIT
Project details
Release history Release notifications | RSS feed
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 sdk_4mica-0.5.1.tar.gz.
File metadata
- Download URL: sdk_4mica-0.5.1.tar.gz
- Upload date:
- Size: 43.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
126554d616f906c6df3da076d7d195916b27093bae18d5730a1d31e81453df68
|
|
| MD5 |
d0f3c0233244f336d4498aea00838bc8
|
|
| BLAKE2b-256 |
c2a3801b886d2b06af72e1f6d299999e766e92cbc794fe1e2a1e639dfda2c9f6
|
File details
Details for the file sdk_4mica-0.5.1-py3-none-any.whl.
File metadata
- Download URL: sdk_4mica-0.5.1-py3-none-any.whl
- Upload date:
- Size: 35.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ddca042c65a9c9f3192615615d68053ecc69e248bbced52fd47ddc836b22d0b8
|
|
| MD5 |
10cbdb7a586f8448be63391e495e6243
|
|
| BLAKE2b-256 |
ee43ae38b98305c76128278b7876b872a044cb2743b632fe17c5fa77625b084d
|