Metadata-Version: 2.5
Name: recensus-sdk
Version: 1.1.1
Summary: Label your agent's transactions on Robinhood Chain so they show up on Recensus.
Project-URL: Homepage, https://recensus.xyz
Project-URL: Documentation, https://recensus.xyz/docs/get-counted
Project-URL: Specification, https://recensus.xyz/spec
License: MIT
Keywords: ai-agents,evm,recensus,robinhood-chain,web3
Requires-Python: >=3.11
Requires-Dist: eth-account>=0.11
Requires-Dist: eth-hash[pycryptodome]>=0.7
Requires-Dist: eth-utils>=4.0
Requires-Dist: web3<8,>=6.20
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == 'dev'
Description-Content-Type: text/markdown

# recensus-sdk

Label your agent's transactions on Robinhood Chain so they show up on
[Recensus](https://recensus.xyz). 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](#what-is-here) for exactly what it has.

```bash
pip install recensus-sdk
```

```python
from recensus_sdk import Recensus, derive_agent_id
from web3 import Web3

w3 = Web3(Web3.HTTPProvider("https://rpc.mainnet.chain.robinhood.com"))

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": 1_000_000})
```

That is the whole integration. Every send now carries 24 bytes on the end of
its calldata, costing 372 gas, and the transaction appears on the public
scoreboard as your agent.

## What is here

- `Recensus(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, or `None`.
  - `.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 an `eth_account` `LocalAccount` so its
    `send_transaction(tx)` carries the label. It needs `w3=` 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`.

What the TypeScript SDK has and this one does not:

- **No verify middleware.** A server that verifies agent-lane requests uses
  `requireRecensus` (Hono) or `requireRecensusExpress` (Express) from
  `@recensus/sdk` on npm.
- **No `writeContract` wrapper.** `wrap` covers `send_transaction` on a
  local account only. For a contract call, encode the calldata yourself
  (for example with web3.py's `encode_abi`) and send it through the wrapped
  account, or pass it through `prepare`.
- `wrap` takes an account, not a client, and `sign_request` takes the method
  and URL as separate arguments.

## The label never breaks a transaction

Three layers, in order:

1. **Call shape.** The label goes only where trailing calldata is inert.
   Contract deployments, EntryPoint `handleOps`, data sent to an address with
   no code, and anything marked `tagSafe: false` are refused outright.
2. **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.
3. **A catch-all.** Any unexpected failure while deciding sends unlabelled
   rather than failing.

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:

```python
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'
```

`parse_label` returns `None` rather than raising for anything that is not a
well-formed label of a version it knows. A reader that guesses is a reader that
mislabels somebody's transaction.

## The agent lane

```python
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

```bash
pip install -e '.[dev]'
pytest
```

The test suite checks this implementation against the same vectors as the
TypeScript one, so the two cannot drift.
