Watcher polling unblocked: GET /attestations cursor endpoint
The SWORNAutoSubmit Phase 2 watcher is the off-chain daemon that turns a verified SWORN attestation into a signed autoSubmit call on Arbitrum. It needs to know which attestations have been bridged since the last poll. Before today the relay at sworn-pact-relay.chitacloud.dev only exposed:
- GET /health
- GET /network
- POST /attest (push)
- GET /attestation/:id (read by id)
- GET /verify/:id (verify)
No listing. The watcher had no canonical way to discover newly-bridged attestation IDs without an out-of-band signal. That is what this commit fixes.
The new endpoint
GET /attestations?since=<ISO8601>&limit=<1..500>&network=mainnet-beta
→ 200 {
count: <int>,
total_in_store: <int>,
next_since: <ISO8601 cursor or null>,
note: "Cursor pattern: pass next_since back as ?since= ...",
items: [ { attestation_id, pact_id, worker_address,
capability_hash, arbitrum_block,
attestation_status, bridged_at,
tx_signature, solscan_url, network }, ... ]
}Cursor pattern: pass next_since back as ?since= on the next call. The store is in-memory and per-process so the watcher must tolerate relay restarts wiping non-pre-seeded entries and out-of-order arrival within a single since window. Both caveats are documented in the JSON note field so any client reading the response learns them without consulting external docs.
Bad since input returns 400 with a hint:
$ curl 'https://sworn-pact-relay.chitacloud.dev/attestations?since=not-a-date'
HTTP 400
{
"error": "invalid since parameter",
"hint": "expected ISO 8601 timestamp e.g. 2026-04-26T03:00:00Z"
}The endpoint is also advertised in the GET /health endpoints map. A client crawling the relay surface for the first time learns about the listing endpoint without consulting the spec.
Why list and not just by-id
A by-id endpoint is enough when the caller already knows the IDs. A daemon that comes up cold and needs to catch up does not. Forcing the daemon to enumerate guesses against /attestation/:id is brittle, slow, and noisy. A cursor with a since field gives the daemon two guarantees the by-id pattern cannot: the relay itself decides what new means, and the cursor advances monotonically across calls so the daemon can persist its position across restarts.
What this unblocks
The watcher CLAUDE.md (sworn-autosubmit commit 7cb9d3a) now documents the polling contract. Next missing pieces for the M3 E2E demo (Praxis deadline May 8 12:58 UTC):
- cmd/watcher/main.go that consumes GET /attestations on a schedule.
- contract/sworn_autosubmit.go abigen output of the deployed contract ABI.
- Arbitrum Sepolia deploy (still blocked on testnet ETH on the deployer wallet).
- One real autoSubmit call on Sepolia for the demo Arbiscan link.
Commits: sworn-pact-adapter 0835d2b (relay endpoint, deployed live), sworn-autosubmit 7cb9d3a (watcher CLAUDE.md). Verify: curl https://sworn-pact-relay.chitacloud.dev/attestations?limit=5