Skip to content

feat(api): GET /v1/attestor-sets/by-member/{pubkey}#69

Merged
proofmancer merged 1 commit into
mainfrom
feat/attestor-sets-by-member
May 22, 2026
Merged

feat(api): GET /v1/attestor-sets/by-member/{pubkey}#69
proofmancer merged 1 commit into
mainfrom
feat/attestor-sets-by-member

Conversation

@proofmancer
Copy link
Copy Markdown
Member

Summary

New read endpoint so a wallet holding an attestor identity (lpk1...) can discover which las1... sets it's a member of. Powers the themisra-dashboard Settings panel's "you're a member of N sets" view, closing audit gap #4 from ligate-io/themisra-dashboard#34 (consumer side waiting on this is themisra-dashboard#37).

  • GET /v1/attestor-sets/by-member/{pubkey} returns the same Page<AttestorSetResponse> envelope as /v1/attestor-sets (cursor pagination, ?limit + ?before). Consistency with the rest of the api was the chosen path over the brief's {items, next_offset, total} offset shape.
  • Empty list is a 200 with data: [], not a 404. The absence of memberships is a valid answer ("you're not in any sets yet"), not a missing resource.
  • Bech32m-validated path param (HRP lpk + 32-byte payload). Typos return 400 invalid_pubkey with a detail explaining what was wrong, instead of an opaque empty 200.

Implementation notes

  • No new migration. The GIN index on attestor_sets.members was added in the original indexer migration (20260509000001_indexer_query_tables.sql:174-176). The new query uses JSONB @> against it, so the WHERE is an index seek not a seq scan.
  • No LEFT JOIN schemas + COUNT GROUP BY. The brief's pseudo-SQL recomputed schemas_count per row, but the indexer already maintains a denormalized attestor_sets.schema_count column (bumped on every RegisterSchema ingest). Cheaper and avoids the GROUP BY entirely.
  • Validation crate. Added bech32 (workspace dep, same crate the indexer uses for AttestationId derivation) to crates/api/Cargo.toml for the path-param check. Decode is inlined into the handler rather than factored to a helper, because returning Result<String, Response> from a helper trips clippy::result_large_err (the axum Response is ~128 bytes). Mirrors how attestation_by_id validates its lat1... path param.
  • Route ordering. /v1/attestor-sets/by-member/{pubkey} registered before /v1/attestor-sets/{id} for human readability. axum 0.8 disambiguates by segment count regardless of order.

Test plan

  • cargo fmt --check (pre-commit hook passed on commit)
  • cargo clippy --workspace --all-targets -- -D warnings clean (with SKIP_GUEST_BUILD=1 RISC0_SKIP_BUILD_KERNELS=1 CONSTANTS_MANIFEST_PATH=\$PWD)
  • cargo test --workspace no regressions
  • CI green
  • Post-deploy curl smoke against devnet:
    # Real lpk1 from a devnet RegisterAttestorSet member, expect non-empty data:
    curl https://api.ligate.io/v1/attestor-sets/by-member/lpk1<real-key>
    # Bogus lpk1 (bad checksum), expect 400 invalid_pubkey:
    curl https://api.ligate.io/v1/attestor-sets/by-member/lpk1nothing
    # Non-lpk HRP, expect 400 invalid_pubkey with HRP detail:
    curl https://api.ligate.io/v1/attestor-sets/by-member/lig1abc...
    # Unknown but valid lpk1, expect 200 + empty data:
    curl https://api.ligate.io/v1/attestor-sets/by-member/lpk1<unused-but-valid>
  • EXPLAIN ANALYZE on devnet confirms Bitmap Index Scan on attestor_sets_members_gin_idx

Follow-ups (deliberately not in this PR)

  • Postgres-backed integration test. The api crate has no tests/ harness yet and seeding rows for this query needs inserting through the slots → transactions → attestor_sets FK chain. Either reusable api-side fixtures or extending the indexer e2e harness; either way it's its own PR.
  • Docs page on docs.ligate.io. The api repo doesn't host route docs (those live in ligate-io-docs). Will flag to whoever owns the docs site.

Consumer

Once merged + deployed, ligate-io/themisra-dashboard#37 (the /(app)/pending inbox + Settings stub) can render the membership list off data[i].id, members, threshold, schema_count, and deep-link registered_at.tx_hash to the explorer.

@proofmancer proofmancer merged commit fcf3d93 into main May 22, 2026
7 checks passed
@proofmancer proofmancer deleted the feat/attestor-sets-by-member branch May 22, 2026 17:55
@github-actions github-actions Bot locked and limited conversation to collaborators May 22, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant