Python SDK for interacting with the 4Mica payment network.
Project description
4Mica Python SDK
Installation
pip install 'sdk-4mica[bls]'
Quick start (direct SDK)
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(core may reuse an active tab). The core response includesnext_req_id; with the SDK, compute it by 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.issue_payment_guarantee(...)with the claims + payer signature, thenawait recipient_client.remunerate(cert)to settle on chain.
pip install 'sdk-4mica[bls]'
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())
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.
Facilitator (x402)
The facilitator wraps the credit flow for x402 resource servers. Clients never call it directly; resource servers do. The canonical implementation and runnable examples live in https://github.com/4mica-Network/x402-4mica:
https://github.com/4mica-Network/x402-4mica/blob/main/examples/server/mock_paid_api.pyshows/tab,/verify, and/settlefrom a FastAPI resource server.https://github.com/4mica-Network/x402-4mica/blob/main/examples/python_client/client.pyshows a client signingX-PAYMENTheaders with the Python SDK.https://github.com/4mica-Network/x402-4mica/blob/main/README.mddocuments the full HTTP schema.
Endpoint shapes:
POST /tabs
{ "userAddress": "...", "recipientAddress": "...", "erc20Token": null, "ttlSeconds": 3600 }
POST /verify
POST /settle
{ "x402Version": 1, "paymentHeader": "<base64 X-PAYMENT>", "paymentRequirements": { ... } }
Quick integration (resource servers)
Use the facilitator (for example https://x402.4mica.xyz/) to open tabs and settle signed guarantees:
- Advertise
scheme = "4mica-credit"and a supportednetworkin your402 Payment Requiredresponses. Embed your POST tab endpoint inpaymentRequirements.extra.tabEndpoint, alongsidepayTo/asset/maxAmountRequired. - Implement the tab endpoint to accept
{ userAddress, paymentRequirements }, call the facilitator’sPOST /tabswith{ userAddress, recipientAddress = payTo, erc20Token = asset, ttlSeconds? }, and return the tab response (at leasttabId,userAddress, and the latestnextReqId/reqId). Cache tabs per(user, recipient, asset)to avoid extra calls; the facilitator will also reuse them. - When a retried request arrives with
X-PAYMENT, base64-decode it and send{ x402Version, paymentHeader, paymentRequirements }to/verify(optional preflight) and/settle(to obtain the certificate).
Quick integration (clients)
Install the SDK and sign the X-PAYMENT header with X402Flow (same flow as the TypeScript and Rust SDKs in ~/x402-4mica):
pip install sdk-4mica
import asyncio
from fourmica_sdk import Client, ConfigBuilder, PaymentRequirements, X402Flow
payer_key = "0x..." # wallet private key
user_address = "0x..." # address to embed in the claims
async def main():
cfg = ConfigBuilder().wallet_private_key(payer_key).rpc_url("https://api.4mica.xyz/").build()
client = await Client.new(cfg)
flow = X402Flow.from_client(client)
# Fetch the recipient's paymentRequirements (must include extra.tabEndpoint)
req_raw = fetch_requirements_somehow()[0]
requirements = PaymentRequirements.from_raw(req_raw)
payment = await flow.sign_payment(requirements, user_address)
headers = {"X-PAYMENT": payment.header} # base64 string to send with the retry
# Optional: settle immediately if the recipient delegates settlement to you
settlement = await flow.settle_payment(payment, requirements, facilitator_url="https://x402.4mica.xyz/")
print("Settlement:", settlement.settlement)
await client.aclose()
asyncio.run(main())
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.
Configuration
wallet_private_key(required)rpc_url(defaults tohttps://api.4mica.xyz/)ethereum_http_rpc_urlandcontract_addressare auto-fetched from the facilitator unless provided.
Environment variables mirror the Rust SDK:
4MICA_WALLET_PRIVATE_KEY
4MICA_RPC_URL
4MICA_ETHEREUM_HTTP_RPC_URL
4MICA_CONTRACT_ADDRESS
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.
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.4.3.tar.gz.
File metadata
- Download URL: sdk_4mica-0.4.3.tar.gz
- Upload date:
- Size: 32.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
091a70531423ed10d0d9651db60b51157ebfda60fc809aa6afd05a3350b57ab5
|
|
| MD5 |
a0a2eea70caa050529a27bc9362364c0
|
|
| BLAKE2b-256 |
f6674d13c55fe555d0ed98a5670326d3da8c736b4e7ff6b5dd8b198f159415e0
|
File details
Details for the file sdk_4mica-0.4.3-py3-none-any.whl.
File metadata
- Download URL: sdk_4mica-0.4.3-py3-none-any.whl
- Upload date:
- Size: 28.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6e6490716d1320e6c9be5b2bf51e0a0c3d2783912ea319c3dabd38b564c34586
|
|
| MD5 |
d7cc1f73e0ba533810ad2adb8400ec10
|
|
| BLAKE2b-256 |
63b99eca3963b355a7b6d5c7726b0eab3093fb8e173e261fa97413bbc9d47d8a
|