Get $TOLL

Safety layer

Watch & alerts

Toll keeps reading a coin or a wallet after the trade and POSTs a signed event to your agent's https URL when something changes.

On this page

Coin watch

Watch a coin your agent bought. Toll re-reads it about every 10 minutes (with more watched coins it takes longer), and soon after a trade or a liquidity change on it. An event is sent when:

  • verdict_changed: the verdict word moved (CLEAR / CAREFUL / AGAINST / INCOMPLETE).
  • new_against: a finding against appeared that was not there before (dev sold, liquidity pulled, wallets dumping together).
  • dev_sold: the dev’s sold share rose 5 points or more since the last such alert.
  • cluster_sold: a launch wallet group (bundle wallets, snipers, early buyers) sold 5 points more of what it bought since the last alert for that group.

The first read of a new watch only records the state and sends nothing.

Wallet watch

Watch a wallet: your agent’s own, or one it trades with. Toll reads it about once a minute and sends an event when it:

  • approval: gives a new token approval: ERC-20, NFT approval-for-all or Permit2, with the spender and the amount ("unlimited" when it is).
  • delegation: signs a new EIP-7702 delegation (the delegate, and the one before it).
  • risky_token_in: receives a token whose Toll check is AGAINST (the token, amount, sender and the check’s link).

The first read records where the wallet is and sends nothing; nothing older is sent late. Several events from one read come in one POST. Revokes, a 7702 delegation being removed and single-NFT approvals send nothing.

Set it up

Watches belong to a wallet, so they need a Toll key: make one at /toll and send it as Authorization: Bearer <key> (or x-toll-pass). Over MCP (Use Toll in your AI):

watch_coin   { "address": "0xCoin",   "url": "https://your.app/toll-hook" }
watch_wallet { "address": "0xWallet", "url": "https://your.app/toll-hook" }
my_watches   {}                       // coin and wallet watches together, never the secret
unwatch_coin { "id": "<uuid>" }       unwatch_wallet { "id": "<uuid>" }

Or over REST:

# coin watch
curl -s https://t0ll.xyz/api/watch -H "authorization: Bearer $TOLL_KEY" \
  -H 'content-type: application/json' -d '{ "token": "0xCoin", "url": "https://your.app/toll-hook" }'

# wallet watch
curl -s https://t0ll.xyz/api/watch/wallet -H "authorization: Bearer $TOLL_KEY" \
  -H 'content-type: application/json' -d '{ "address": "0xWallet", "url": "https://your.app/toll-hook" }'

# list (GET) and remove (DELETE ?id=<uuid>) on the same two paths
curl -s https://t0ll.xyz/api/watch/wallet -H "authorization: Bearer $TOLL_KEY"
curl -s -X DELETE "https://t0ll.xyz/api/watch/wallet?id=<uuid>" -H "authorization: Bearer $TOLL_KEY"

The first add answers with the secret, once. Adding the same watch again resumes it if it was paused, and keeps the first secret.

{
  "id": "6f1c…",
  "address": "0xwallet…",
  "url": "https://your.app/toll-hook",
  "cap": 20,
  "events": ["approval", "delegation", "risky_token_in"],
  "signature": "x-toll-signature: sha256=<hex HMAC-SHA256(secret, raw body)>",
  "secret": "9b2e…",
  "note": "Keep the secret: it is shown once and signs every event."
}

Example events

Each POST carries content-type: application/json, x-toll-event (the first event) and x-toll-signature.

{
  "type": "toll.watch",
  "event": "verdict_changed",
  "events": ["verdict_changed", "new_against"],
  "watch_id": "1a2b…",
  "token": "0xcoin…",
  "chain": "rh",
  "verdict": { "before": "CAREFUL", "now": "AGAINST" },
  "new_against": ["The dev sold 41% of supply"],
  "check_url": "https://t0ll.xyz/check/0xcoin…",
  "at": "2026-11-08T14:02:11Z",
  "note": "Facts read from Robinhood Chain by Toll, not advice."
}
{
  "type": "toll.watch",
  "event": "dev_sold",
  "events": ["dev_sold", "cluster_sold"],
  "watch_id": "1a2b…",
  "token": "0xcoin…",
  "chain": "rh",
  "verdict": { "before": "CAREFUL", "now": "CAREFUL" },
  "sold": {
    "dev": { "before": 4, "now": 12, "of": "supply", "wallet": "0xdev…" },
    "groups": [{ "group": "exempt", "label": "bundle wallets", "before": 10, "now": 35 }]
  },
  "check_url": "https://t0ll.xyz/check/0xcoin…",
  "at": "2026-11-08T14:12:40Z"
}

In sold, of says what the share is of: supply, or bought (what the dev bought). Only the parts that fired are in it.

{
  "type": "toll.wallet_watch",
  "event": "approval",
  "events": [
    { "type": "approval", "kind": "erc20", "token": "0xtoken…", "spender": "0xspender…",
      "amount": "unlimited", "tx": "0x…", "block": 51234567 },
    { "type": "delegation", "delegate": "0xdelegate…", "previous": null, "block": 51234570 },
    { "type": "risky_token_in", "token": "0xcoin…", "amount": "1000000000000000000000", "from": "0xsender…",
      "verdict": "AGAINST", "tx": "0x…", "block": 51234569, "check_url": "https://t0ll.xyz/check/0xcoin…" }
  ],
  "more": 0,
  "watch_id": "6f1c…",
  "address": "0xwallet…",
  "chain": "rh",
  "blocks": { "from": 51234500, "to": 51234570 },
  "at": "2026-11-08T14:05:02Z",
  "note": "Facts read from Robinhood Chain by Toll, not advice."
}

An NFT approval-for-all has "amount": "all". Up to 20 events a POST; the rest are counted in more.

Verify the signature

x-toll-signature is sha256= followed by the hex HMAC-SHA256 of the raw body, keyed with the watch’s secret. Compute it over the bytes you received, before parsing, and compare in constant time. Drop anything that does not match.

import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody: the request body exactly as received (bytes), before any JSON.parse
function fromToll(rawBody, header, secret) {
  const want = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
  const got = String(header || "");
  return got.length === want.length && timingSafeEqual(Buffer.from(got), Buffer.from(want));
}
// fromToll(body, req.headers["x-toll-signature"], process.env.TOLL_WATCH_SECRET)
import hashlib, hmac

def from_toll(raw_body: bytes, header: str, secret: str) -> bool:
    want = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(want, header or "")

Caps and limits

  • The cap is your tier’s watch count, by $TOLL held: 3 / 20 / 50 / 100 (/toll). Wallet watches have their own cap of the same size, so a key can watch that many coins and that many wallets.
  • The URL must be public https on the default port; Toll checks the address it resolves to before each POST, and does not follow redirects.
  • Answer within 5 seconds with a 2xx. A failed delivery is sent again on the next read; 5 failures in a row pause the watch, and adding it again resumes it.
  • Robinhood Chain only. Facts read from the chain, not advice.

When an alert comes

  • An approval you did not mean: Approvals & revoke (MCP wallet_approvals) lists the wallet’s live approvals, each with a revoke transaction Toll has checked.
  • A coin that turned: open the event’s check_url for the full read.
  • Before the next signature: What Toll checks.
  • Tell your owner what the event says. Do not sell or sign on an alert alone.