recensus-sdk
Label your agent's transactions on Arc, Circle's chain where gas is paid in
USDC, so they show up on Recensus. The Python counterpart of @recensus/sdk on
npm: the same label, the same safety rules and the same request signing, with
a smaller surface. See What is here for exactly what it has.
pip install recensus-sdk
from recensus_sdk import Recensus, derive_agent_id
from web3 import Web3
w3 = Web3(Web3.HTTPProvider("https://rpc.mainnet.arc.io"))
recensus = Recensus(
agent_id=derive_agent_id(operator_address, "price-watcher"),
autonomous=True, # no human approves each send
# test=True # in staging: excluded from every public number
w3=w3,
)
account = recensus.wrap(account)
account.send_transaction({"to": recipient, "value": 10**16}) # 0.01 USDC: native USDC has 18 decimals
That is the whole integration. Every send now carries 24 bytes on the end of its calldata, and the transaction appears on the public scoreboard as your agent. The label costs 372 gas on a contract call and 930 on a plain transfer (the EIP-7623 calldata floor); at Arc's 20 gwei base fee that is under 0.00002 USDC.
An ERC-8004 agent
If your agent is registered in Arc's ERC-8004 Identity Registry, label it with its registry token ID instead of a derived ID:
recensus = Recensus(erc8004_agent_id=207, autonomous=True, w3=w3)
The label then carries the token ID as a big-endian uint128 with the
erc8004 flag set (label version 2). Give exactly one of agent_id and
erc8004_agent_id.
What is here
Recensus(agent_id= | erc8004_agent_id=, framework=, autonomous=, test=, w3=, simulate_before_send=, denylist=, deny_selectors=, on_unlabelled=, logger=).tag(calldata)appends the label, with no checks..parse(calldata)reads a label back, orNone..decide(to, data)says whether a call's shape may carry the label..prepare(to, data, value, sender, gas)runs decide, simulate and fall back for one call and returns the calldata to send..wrap(account)wraps aneth_accountLocalAccountso itssend_transaction(tx)carries the label. It needsw3=on the constructor, because sending needs a provider..sign_request(method, url, account, body)returns the five agent-lane headers.
derive_agent_id(operator, name),has_label(calldata), and the label functions:build_label,parse_label,append_label,strip_label,erc8004_agent_id(token_id)anderc8004_token_id(label).
What the TypeScript SDK has and this one does not:
- No verify middleware. A server that verifies agent-lane requests uses
requireRecensus(Hono) orrequireRecensusExpress(Express) from@recensus/sdkon npm. - No
writeContractwrapper.wrapcoverssend_transactionon a local account only. For a contract call, encode the calldata yourself (for example with web3.py'sencode_abi) and send it through the wrapped account, or pass it throughprepare. wraptakes an account, not a client, andsign_requesttakes the method and URL as separate arguments.
The label never breaks a transaction
Three layers, in order:
- Call shape. The label goes only where trailing calldata is inert.
Contract deployments, EntryPoint
handleOps, data sent to an address with no code, a call with no data to a contract (even one markedtagSafe: true), and anything markedtagSafe: falseare refused outright. Calldata given asbytesorHexBytesis checked exactly as hex is. - Simulation. Before sending, the labelled call is simulated. If it would revert where the unlabelled one succeeds, the unlabelled call is sent and a warning is raised. This is on by default and the standard forbids shipping it off by default.
- A catch-all. Any unexpected failure while deciding sends unlabelled
rather than failing. So does
preparewith now3to simulate with, unless you turned simulation off.
If both the labelled and unlabelled calls revert, your own calldata is sent, so the error you see is yours and not ours.
Without web3
The label itself has no dependencies, so a reader or writer can live anywhere:
from recensus_sdk import build_label, parse_label
label = build_label("0x9f2a0c1e7b5d4a8f36c20e91d7b4a5c3", framework=0x0003)
data = existing_calldata + label[2:]
parse_label(data).agent_id # '0x9f2a0c1e7b5d4a8f36c20e91d7b4a5c3'
build_label writes version 2. parse_label reads versions 1 and 2, and
returns None rather than raising for anything that is not a well-formed
label of a version it knows, including a flag bit its version reserves. A reader that guesses is a reader that
mislabels somebody's transaction.
The agent lane
from recensus_sdk import canonical_body
url = "https://api.example.com/v1/thing"
body = {"hello": "world"}
headers = recensus.sign_request("POST", url, account, body=body)
# Send the exact bytes that were signed. `requests.post(url, json=body)`
# re-serialises with spaces after separators, so the server hashes different
# bytes and answers BAD_SIGNATURE.
requests.post(url, data=canonical_body(body),
headers={**headers, "content-type": "application/json"})
Five headers an app can verify with requireRecensus or
requireRecensusExpress from @recensus/sdk on npm, so a labelled agent can be given its own rate limits instead of being
throttled like a spam bot.
Development
pip install -e '.[dev]'
pytest
The test suite checks this implementation against the same vectors as the TypeScript one, so the two cannot drift.
The fork suite
tests/fork/test_conformance.py is the RECENSUS-1 §4.4 conformance test. For
each call it checks the SDK did not fall back to unlabelled, that the call
carries the label, and that the labelled and unlabelled calls end the same:
same status, logs, return data and balances, and a gas difference of exactly
the label's calldata cost.
An Anvil fork of Arc cannot run a USDC call that moves value: transfer,
transferFrom, and so a Gateway deposit, revert there even unlabelled, while
they succeed on the chain. So the suite runs in two places.
On an Anvil fork, sent through Recensus.wrap(account) and mined, each
case twice from one snapshot:
- a native USDC transfer to an EOA, with a derived ID and with an ERC-8004 ID (+930 gas);
- USDC
approve(it moves no value, so it runs on the fork); - Permit2
approve; - a plain value send to a contract (an EntryPoint), which must go out unlabelled and land.
On real Arc mainnet state, by debug_traceCall with the call tracer at one
block, with the labelled calldata from the SDK's own prepare(), whose
simulation runs against mainnet first:
- USDC
transferfrom a real holder (+372 gas; both USDC emitters log it); - USDC
approve; - USDC
transferFrom, sent as a real spender with a live allowance; - Circle Gateway
deposit(USDC, 1)from a real owner with a live allowance to the Gateway wallet.
The holders, spender and depositor are found when the suite runs, from recent
USDC Transfer and Approval logs.
pkill -f "anvil --fork-url"; FORK_RPC_URL=https://rpc.mainnet.arc.io bash scripts/dev/services.sh up
FORK_RPC=http://127.0.0.1:8546 pytest -m fork tests/fork
MAINNET_RPC (default https://rpc.mainnet.arc.io) serves the eth_calls and
log queries, and TRACE_RPC (default https://rpc.drpc.mainnet.arc.io, the
public endpoint that answers debug_traceCall) the traces. No DEX on Arc is
verified, so there are no swap cases.
Release files for recensus-sdk 2.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| recensus_sdk-2.0.0.tar.gz | 33.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| recensus_sdk-2.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 53.7 kB
Release files / recensus_sdk-2.0.0.tar.gz
| Download URL | recensus_sdk-2.0.0.tar.gz |
|---|---|
| Size | 33.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fdcf531e9b0463f375477017cc3b383c9d9c5bcb11be51d8520ef68f38577ab8
|
|
BLAKE2b-256 checksum How to use checksums |
f4780fa04bd4f067fbc4d5af0b78b2dcfb53b5228090f9b3db30dc59168e3074
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.7
|
Release files / recensus_sdk-2.0.0-py3-none-any.whl
| Download URL | recensus_sdk-2.0.0-py3-none-any.whl |
|---|---|
| Size | 20.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
71d8eb240f0361b993af297b8e71dcc1f95e00476ad85a3bc0f531c5678b60f4
|
|
BLAKE2b-256 checksum How to use checksums |
fe251072089d09337c3b34769f1b7458cb182884370110155f5420ffc37616c9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.7
|