Metadata-Version: 2.5
Name: recensus-sdk
Version: 1.1.0
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 mirror of `@recensus/sdk` on npm —
the same surface.

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

## 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` 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.
