Skip to content

Add per-credential-type analytics counters to ProofRegistry - #444

Open
defimomof2 wants to merge 1 commit into
ToluLabs:mainfrom
defimomof2:contracts/397-analytics-counters
Open

Add per-credential-type analytics counters to ProofRegistry#444
defimomof2 wants to merge 1 commit into
ToluLabs:mainfrom
defimomof2:contracts/397-analytics-counters

Conversation

@defimomof2

@defimomof2 defimomof2 commented Aug 30, 2026

Copy link
Copy Markdown

@drips-wave

drips-wave Bot commented Aug 30, 2026

Copy link
Copy Markdown

@defimomof2 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Psalmuel01

Copy link
Copy Markdown
Collaborator

The #397 counters look good and CI is green. But this only implements #397. It does not address #386 (selective disclosure presets), #396 (delegated verification), or #398 (issuer reputation), and there is no code for them here. Please either implement those too, or change the description to Closes #397 only so they stay open. As-is, merging would wrongly auto-close three unbuilt features.

Alheriii added a commit to Alheriii/StellarCred that referenced this pull request Aug 31, 2026
…esets (ToluLabs#386)

Closes the remaining gap PR ToluLabs#444's description falsely claims: it
lists "Closes ToluLabs#397, ToluLabs#398, ToluLabs#396, ToluLabs#386" but only implements ToluLabs#397. This
PR already covers ToluLabs#398; this commit adds the other two.

## ToluLabs#396 — delegated verification (contracts/proof_registry)

grant_verification(holder, verifier, credential_type, expiry) lets a
holder authorize a specific verifier to read their is_verified result
via the new check_delegated_verification, until expiry.
revoke_verification lets the holder withdraw it early (a no-op if
none exists). Purely additive: is_verified itself is unchanged and
stays a public read for every caller, exactly as before.

One deliberate deviation from every other bump_*_ttl helper in this
contract: grant_verification passes MIN_TTL_EXTEND_TO as both the
threshold and extend-to value, not MIN_TTL_THRESHOLD. Every other
persistent key gets touched repeatedly over a dispute/claim's
lifetime, so an extend_ttl call that's a no-op on any single write
(because the entry's current TTL already exceeds MIN_TTL_THRESHOLD)
still gets refreshed eventually by a later call. A delegation has no
such second chance — it's either consumed or never touched again —
and a fresh persistent entry's ledger-assigned baseline TTL already
exceeds MIN_TTL_THRESHOLD, which would make an extend_ttl call using
that threshold a guaranteed no-op exactly when it needs to work
(caught by a first draft of grant_expires_correctly failing with
Error(Contract, ToluLabs#12) until the test's own out-of-range expiry was
fixed, then re-confirmed the extend itself takes effect via a direct
get_ttl assertion — same category of bug the ToluLabs#349 StellarCred PR
found in the indexer, different repo).

9 new contract tests: grant then check, no grant means false, scoped
to only the named verifier, expires correctly, revoke removes it (and
is a no-op without one), a live grant still reflects an independently
revoked underlying claim, an expiry in the past is rejected, and
re-granting overwrites the previous expiry. Full proof_registry suite
(32 tests) and the whole contracts workspace build pass.

## ToluLabs#386 — selective disclosure presets (frontend + sdk)

A holder-defined, named bundle of claim types (+ thresholds) — e.g.
"Investor onboarding" meaning kyc + accreditation + jurisdiction —
generated once and shared as a deep link a protocol verifies in one
call.

- frontend/lib/presets.ts: local CRUD (presets carry no secret
  material — just type names and public thresholds — so there's
  nothing here needing encryption beyond the same-origin isolation
  localStorage already provides) and encode/decode for the shareable
  `?c=kyc,age:21,...` link. Untrusted input (a hand-crafted or
  tampered link) degrades to "fewer claims checked" rather than
  throwing: unrecognised types and non-finite thresholds are silently
  dropped.
- sdk: verifyPreset(wallet, claims) — a thin wrapper over the
  existing hasClaims batched read, exported on the StellarCred
  namespace. Presets are typed against the SDK's own ClaimType
  (CLAIM_TYPES), not the app's wider CredentialType, since
  "employment" is a provable credential type the SDK's hasClaim/
  hasClaims don't yet know how to check — a preset naming it could
  never be verified.
- frontend/app/presets: define/list/delete presets from the claim
  types the holder currently holds, with a copyable share link.
  Linked from the holder page's header per the issue's "on the holder
  page" ask, rather than folded into that already-1900-line file.
- frontend/app/verify-preset: the consumption side — decodes the deep
  link, takes a wallet address, calls verifyPreset, and shows a
  per-claim pass/fail plus an overall result. A reference
  implementation of "a protocol can request and verify it end to
  end."
- vitest.config.ts: added a @stellarcred/sdk alias mirroring
  tsconfig.json's own path mapping — Next.js resolves it via
  tsconfig paths already, but vitest doesn't read those
  automatically, and presets.ts is the first lib module outside the
  sdk package's own tests to import from it.

16 new tests (10 for presets.ts's CRUD/encode-decode, 6 for
verifyPreset) plus the ClaimType-vs-CredentialType mismatch tsc
caught before any test even ran. Full frontend suite: 302/307 pass —
the 5 failures are credential-sync.test.ts's pre-existing, unrelated
`localStorage.clear is not a function` environment issue, confirmed
present on a clean main checkout before this change (this repo's
window.localStorage is non-functional in this test environment
entirely; presets.test.ts works around it by mocking safe-storage.ts
directly rather than depending on jsdom's broken localStorage).
tsc --noEmit is clean.

Fixes ToluLabs#396
Fixes ToluLabs#386
Psalmuel01 pushed a commit that referenced this pull request Aug 31, 2026
…sclosure presets (#483)

* Surface issuer reputation stats derived from indexed events

Holders and protocols choosing issuers had no signal about an
issuer beyond its address. Add per-issuer stats derived entirely
from indexed on-chain events: credentials issued, active vs
revoked, credential types covered, and first-seen date.

- services/indexer: new Db.issuerStats(issuer), implemented for
  both SQLite and Postgres, and a new GET /issuers/:issuer/stats
  endpoint. Unknown issuers get a zeroed row rather than a 404,
  matching claimsByWallet's "unknown = empty" contract. Postgres's
  MIN(verified_at) on a BIGINT column comes back as a string same
  as the recent #349 fix's finding, so first_seen is normalized to
  a number there too.
- frontend: new server-side GET /api/issuer-stats proxies to the
  indexer (INDEXER_URL, server-only — matches the api/issuers
  route's existing proxy-to-a-backend-service pattern rather than
  exposing the indexer URL to the client) and displays the stats
  inline on the issuer page next to whichever issuer is selected.

Tests: 4 new indexer API tests (zeroed row for an unindexed issuer,
aggregation across an issuer's claims including first_seen picking
the earliest verified_at, isolation between issuers, empty-path
rejection) and 5 new frontend route tests (missing issuer param,
default/overridden INDEXER_URL, issuer URL-encoding, upstream
non-2xx and network-error handling). Full indexer suite (23 tests)
and frontend suite (292 tests, minus 5 pre-existing unrelated
localStorage.clear failures confirmed on a clean main checkout)
pass; both services' tsc --noEmit are clean.

* Add delegated verification (#396) and selective disclosure presets (#386)

Closes the remaining gap PR #444's description falsely claims: it
lists "Closes #397, #398, #396, #386" but only implements #397. This
PR already covers #398; this commit adds the other two.

## #396 — delegated verification (contracts/proof_registry)

grant_verification(holder, verifier, credential_type, expiry) lets a
holder authorize a specific verifier to read their is_verified result
via the new check_delegated_verification, until expiry.
revoke_verification lets the holder withdraw it early (a no-op if
none exists). Purely additive: is_verified itself is unchanged and
stays a public read for every caller, exactly as before.

One deliberate deviation from every other bump_*_ttl helper in this
contract: grant_verification passes MIN_TTL_EXTEND_TO as both the
threshold and extend-to value, not MIN_TTL_THRESHOLD. Every other
persistent key gets touched repeatedly over a dispute/claim's
lifetime, so an extend_ttl call that's a no-op on any single write
(because the entry's current TTL already exceeds MIN_TTL_THRESHOLD)
still gets refreshed eventually by a later call. A delegation has no
such second chance — it's either consumed or never touched again —
and a fresh persistent entry's ledger-assigned baseline TTL already
exceeds MIN_TTL_THRESHOLD, which would make an extend_ttl call using
that threshold a guaranteed no-op exactly when it needs to work
(caught by a first draft of grant_expires_correctly failing with
Error(Contract, #12) until the test's own out-of-range expiry was
fixed, then re-confirmed the extend itself takes effect via a direct
get_ttl assertion — same category of bug the #349 StellarCred PR
found in the indexer, different repo).

9 new contract tests: grant then check, no grant means false, scoped
to only the named verifier, expires correctly, revoke removes it (and
is a no-op without one), a live grant still reflects an independently
revoked underlying claim, an expiry in the past is rejected, and
re-granting overwrites the previous expiry. Full proof_registry suite
(32 tests) and the whole contracts workspace build pass.

## #386 — selective disclosure presets (frontend + sdk)

A holder-defined, named bundle of claim types (+ thresholds) — e.g.
"Investor onboarding" meaning kyc + accreditation + jurisdiction —
generated once and shared as a deep link a protocol verifies in one
call.

- frontend/lib/presets.ts: local CRUD (presets carry no secret
  material — just type names and public thresholds — so there's
  nothing here needing encryption beyond the same-origin isolation
  localStorage already provides) and encode/decode for the shareable
  `?c=kyc,age:21,...` link. Untrusted input (a hand-crafted or
  tampered link) degrades to "fewer claims checked" rather than
  throwing: unrecognised types and non-finite thresholds are silently
  dropped.
- sdk: verifyPreset(wallet, claims) — a thin wrapper over the
  existing hasClaims batched read, exported on the StellarCred
  namespace. Presets are typed against the SDK's own ClaimType
  (CLAIM_TYPES), not the app's wider CredentialType, since
  "employment" is a provable credential type the SDK's hasClaim/
  hasClaims don't yet know how to check — a preset naming it could
  never be verified.
- frontend/app/presets: define/list/delete presets from the claim
  types the holder currently holds, with a copyable share link.
  Linked from the holder page's header per the issue's "on the holder
  page" ask, rather than folded into that already-1900-line file.
- frontend/app/verify-preset: the consumption side — decodes the deep
  link, takes a wallet address, calls verifyPreset, and shows a
  per-claim pass/fail plus an overall result. A reference
  implementation of "a protocol can request and verify it end to
  end."
- vitest.config.ts: added a @stellarcred/sdk alias mirroring
  tsconfig.json's own path mapping — Next.js resolves it via
  tsconfig paths already, but vitest doesn't read those
  automatically, and presets.ts is the first lib module outside the
  sdk package's own tests to import from it.

16 new tests (10 for presets.ts's CRUD/encode-decode, 6 for
verifyPreset) plus the ClaimType-vs-CredentialType mismatch tsc
caught before any test even ran. Full frontend suite: 302/307 pass —
the 5 failures are credential-sync.test.ts's pre-existing, unrelated
`localStorage.clear is not a function` environment issue, confirmed
present on a clean main checkout before this change (this repo's
window.localStorage is non-functional in this test environment
entirely; presets.test.ts works around it by mocking safe-storage.ts
directly rather than depending on jsdom's broken localStorage).
tsc --noEmit is clean.

Fixes #396
Fixes #386
freebuff-web Bot pushed a commit to Risktaker001/StellarCred that referenced this pull request Sep 1, 2026
…sclosure presets (ToluLabs#483)

* Surface issuer reputation stats derived from indexed events

Holders and protocols choosing issuers had no signal about an
issuer beyond its address. Add per-issuer stats derived entirely
from indexed on-chain events: credentials issued, active vs
revoked, credential types covered, and first-seen date.

- services/indexer: new Db.issuerStats(issuer), implemented for
  both SQLite and Postgres, and a new GET /issuers/:issuer/stats
  endpoint. Unknown issuers get a zeroed row rather than a 404,
  matching claimsByWallet's "unknown = empty" contract. Postgres's
  MIN(verified_at) on a BIGINT column comes back as a string same
  as the recent ToluLabs#349 fix's finding, so first_seen is normalized to
  a number there too.
- frontend: new server-side GET /api/issuer-stats proxies to the
  indexer (INDEXER_URL, server-only — matches the api/issuers
  route's existing proxy-to-a-backend-service pattern rather than
  exposing the indexer URL to the client) and displays the stats
  inline on the issuer page next to whichever issuer is selected.

Tests: 4 new indexer API tests (zeroed row for an unindexed issuer,
aggregation across an issuer's claims including first_seen picking
the earliest verified_at, isolation between issuers, empty-path
rejection) and 5 new frontend route tests (missing issuer param,
default/overridden INDEXER_URL, issuer URL-encoding, upstream
non-2xx and network-error handling). Full indexer suite (23 tests)
and frontend suite (292 tests, minus 5 pre-existing unrelated
localStorage.clear failures confirmed on a clean main checkout)
pass; both services' tsc --noEmit are clean.

* Add delegated verification (ToluLabs#396) and selective disclosure presets (ToluLabs#386)

Closes the remaining gap PR ToluLabs#444's description falsely claims: it
lists "Closes ToluLabs#397, ToluLabs#398, ToluLabs#396, ToluLabs#386" but only implements ToluLabs#397. This
PR already covers ToluLabs#398; this commit adds the other two.

## ToluLabs#396 — delegated verification (contracts/proof_registry)

grant_verification(holder, verifier, credential_type, expiry) lets a
holder authorize a specific verifier to read their is_verified result
via the new check_delegated_verification, until expiry.
revoke_verification lets the holder withdraw it early (a no-op if
none exists). Purely additive: is_verified itself is unchanged and
stays a public read for every caller, exactly as before.

One deliberate deviation from every other bump_*_ttl helper in this
contract: grant_verification passes MIN_TTL_EXTEND_TO as both the
threshold and extend-to value, not MIN_TTL_THRESHOLD. Every other
persistent key gets touched repeatedly over a dispute/claim's
lifetime, so an extend_ttl call that's a no-op on any single write
(because the entry's current TTL already exceeds MIN_TTL_THRESHOLD)
still gets refreshed eventually by a later call. A delegation has no
such second chance — it's either consumed or never touched again —
and a fresh persistent entry's ledger-assigned baseline TTL already
exceeds MIN_TTL_THRESHOLD, which would make an extend_ttl call using
that threshold a guaranteed no-op exactly when it needs to work
(caught by a first draft of grant_expires_correctly failing with
Error(Contract, ToluLabs#12) until the test's own out-of-range expiry was
fixed, then re-confirmed the extend itself takes effect via a direct
get_ttl assertion — same category of bug the ToluLabs#349 StellarCred PR
found in the indexer, different repo).

9 new contract tests: grant then check, no grant means false, scoped
to only the named verifier, expires correctly, revoke removes it (and
is a no-op without one), a live grant still reflects an independently
revoked underlying claim, an expiry in the past is rejected, and
re-granting overwrites the previous expiry. Full proof_registry suite
(32 tests) and the whole contracts workspace build pass.

## ToluLabs#386 — selective disclosure presets (frontend + sdk)

A holder-defined, named bundle of claim types (+ thresholds) — e.g.
"Investor onboarding" meaning kyc + accreditation + jurisdiction —
generated once and shared as a deep link a protocol verifies in one
call.

- frontend/lib/presets.ts: local CRUD (presets carry no secret
  material — just type names and public thresholds — so there's
  nothing here needing encryption beyond the same-origin isolation
  localStorage already provides) and encode/decode for the shareable
  `?c=kyc,age:21,...` link. Untrusted input (a hand-crafted or
  tampered link) degrades to "fewer claims checked" rather than
  throwing: unrecognised types and non-finite thresholds are silently
  dropped.
- sdk: verifyPreset(wallet, claims) — a thin wrapper over the
  existing hasClaims batched read, exported on the StellarCred
  namespace. Presets are typed against the SDK's own ClaimType
  (CLAIM_TYPES), not the app's wider CredentialType, since
  "employment" is a provable credential type the SDK's hasClaim/
  hasClaims don't yet know how to check — a preset naming it could
  never be verified.
- frontend/app/presets: define/list/delete presets from the claim
  types the holder currently holds, with a copyable share link.
  Linked from the holder page's header per the issue's "on the holder
  page" ask, rather than folded into that already-1900-line file.
- frontend/app/verify-preset: the consumption side — decodes the deep
  link, takes a wallet address, calls verifyPreset, and shows a
  per-claim pass/fail plus an overall result. A reference
  implementation of "a protocol can request and verify it end to
  end."
- vitest.config.ts: added a @stellarcred/sdk alias mirroring
  tsconfig.json's own path mapping — Next.js resolves it via
  tsconfig paths already, but vitest doesn't read those
  automatically, and presets.ts is the first lib module outside the
  sdk package's own tests to import from it.

16 new tests (10 for presets.ts's CRUD/encode-decode, 6 for
verifyPreset) plus the ClaimType-vs-CredentialType mismatch tsc
caught before any test even ran. Full frontend suite: 302/307 pass —
the 5 failures are credential-sync.test.ts's pre-existing, unrelated
`localStorage.clear is not a function` environment issue, confirmed
present on a clean main checkout before this change (this repo's
window.localStorage is non-functional in this test environment
entirely; presets.test.ts works around it by mocking safe-storage.ts
directly rather than depending on jsdom's broken localStorage).
tsc --noEmit is clean.

Fixes ToluLabs#396
Fixes ToluLabs#386
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment