Use this when reviewers score proposals, grants, milestones, maintainers, or deliverables and need protection from pressure by applicants, sponsors, or communities.
Fit
- Status: supported today.
- NIP-85 kind: 30383 for a proposal event, or 30384 for an addressable proposal record.
- Subject: proposal event id or NIP-33 address.
- Helpers:
aggregateEventContributionsoraggregateAddressableContributions. - Proof version: v2 recommended.
- Useful metrics:
rankas review score, confidence, or funding priority.
Subject design
- Use kind 30383 when the proposal is a single immutable event.
- Use kind 30384 when the proposal, milestone, or deliverable is an addressable record that can be referenced by
kind:pubkey:d-tag. - Score maintainers, proposals, milestones, and deliverables separately when the fund may take different actions on each.
- Decide whether revisions inherit review scores. Funding workflows usually need explicit supersession when applicants materially change a proposal.
What to publish
- A kind 30383 or 30384 assertion matching the proposal subject.
- A
rankprofile explaining whether the score represents eligibility, technical confidence, impact, delivery risk, final funding priority, or a veto signal. - Proof v2 tags from the reviewer circle, plus threshold, conflict rules, expiry, appeal path, and whether the score is advisory or automatic.
- Companion records for reviewer rationale, redacted comments, milestone evidence, conflict declarations, and final award decisions.
Implementation recipe
- Decide whether the subject is a proposal event, an addressable proposal record, a milestone, or a maintainer.
- Define whether
rankmeans eligibility, technical confidence, impact, delivery risk, or final funding priority. - Use proof v2 and verify the expected proposal subject, circle, threshold, and freshness.
- Keep reviewer comments, conflict declarations, and appeals in a companion grant process.
- Treat the aggregate as an auditable review signal unless the fund explicitly chooses automatic decisions.
Worked example
import {
NIP85_KINDS,
aggregateAddressableContributions,
contributeAddressableAssertion,
createTrustCircle,
} from 'nostr-veil'
import {
authorPubkey,
defaultMembers,
memberIndex,
proofVersion,
verifyUseCaseAssertion,
withCreatedAt,
} from './_shared.js'
const slug = 'grant-funding-proposal-review'
const proposalAddress = `30023:${authorPubkey}:grant-2026-q2`
const circle = createTrustCircle(defaultMembers.map(member => member.pub))
const contributions = defaultMembers.map((reviewer, index) =>
contributeAddressableAssertion(
circle,
proposalAddress,
{ rank: 68 + index * 5 },
reviewer.priv,
memberIndex(circle, reviewer.pub),
{ proofVersion },
),
)
export const assertion = withCreatedAt(aggregateAddressableContributions(
circle,
proposalAddress,
contributions,
{ proofVersion },
))
export const result = verifyUseCaseAssertion(slug, assertion, {
kind: NIP85_KINDS.ADDRESSABLE,
subject: proposalAddress,
subjectTag: 'a',
circleId: circle.circleId,
minDistinctSigners: 3,
freshAfter: assertion.created_at - 300,
})
What to verify
- Strict syntax and a valid proof v2.
- The assertion kind and subject tag match the proposal or milestone currently being evaluated.
- The reviewer circle is approved for this fund, topic, and conflict policy.
proof.distinctSignersmeets the funding threshold and therankmeaning is known before ranking proposals.- The score is current for the proposal revision and not superseded by appeals, conflicts, or milestone evidence.
What this proves
- Distinct grant reviewers contributed the aggregate score.
- The score is bound to a specific proposal subject.
- The reviewer set is public, but individual reviewers are not named.
What not to claim
- Do not claim the proof predicts delivery or guarantees impact.
- Do not claim hidden reviewers had no conflicts; conflict declarations and recusals must be enforced by the grant process.
- Do not claim a score is a fair final decision unless the fund has explicitly published that policy.
Failure handling
- Reject wrong-subject assertions, stale proposal scores, unknown reviewer circles, and scores without a published meaning.
- Pause automatic ranking when conflict reports, appeals, or material proposal revisions appear.
- Publish a new assertion for revised proposals or milestone outcomes instead of reinterpreting an older review.
- Show final decision notes separately so applicants know what to fix even when reviewer identities remain protected.
Operational requirements
| Risk to handle | Required control |
|---|---|
| The proof does not prove the proposal will succeed. | Use milestone tracking, deliverable checks, and post-award review to validate execution. |
| The proof does not show private reviewer rationale. | Publish redacted rationale, criteria scores, or decision notes when transparency is required. |
| Conflicts of interest are not eliminated by the proof. | Require conflict declarations, recusals, reviewer eligibility rules, and independent review circles for large awards. |
| A score alone may not be a fair decision. | Define whether the score is advisory, a ranking input, a veto, or an automatic threshold. |
Policy choices
- Does
rankmean funding priority, technical confidence, impact, or eligibility? - Should the circle include domain experts, funders, community members, or all three?
- Is the final decision automatic or advisory?
- How are appeals and corrections handled?
NIP-85 kind reference
NIP-85 defines the assertion kind by the subject being scored. The kind number is part of the proof v2 context, so deployments should verify both the number and the subject hint tag.
Nostr pubkey subjects. Subject hint: p.
Nostr event id subjects. Subject hint: e.
NIP-33 address subjects. Subject hint: a.
packages, relays, domains, vendors, and other identifiers. Subject hint: k.
provider metadata, not a score assertion. Subject hint: provider tags.
Spec: NIP-85 trusted assertions.
Live relay test
The opt-in relay test signs this canonical example as real Nostr event data, publishes it to wss://relay.trotters.cc, fetches it back by id, and re-runs the application, syntax, Nostr signature, canonical tag, and proof checks.
- Events
- 1/1 fetched from relay
- Proof
- 3/3 threshold from a 3-member ring
- Run
tf7bic-3b3a5ae6d6
- Canonical example passes locally
- Relay stored and returned every signed event
- Fetched Nostr event signatures are valid
- Fetched tags match the canonical example
- NIP-85 syntax validation passes
- nostr-veil proof verification passes
- Deployment profile verifier passes
875785599dc1...762e8647
Run the same check with npm run test:use-cases:relay -- --write docs/use-case-relay-checks.json.
Safety checks
Each canonical use-case example is also exercised by an adversarial test harness. These are the failure modes a production verifier should reject before acting on the score.
Published scores must still match the signed contribution aggregate.
The d tag and subject hint must stay bound to the signed v2 proof.
The assertion kind must match the profile and the signed v2 context.
New deployment profiles require proof v2.
Repeated key images must not increase the signer count.
Removing a signature must fail the profile threshold.
created_at must remain inside the freshness window.
The circle ID must be accepted by deployment policy.
verifyProductionDeployment() should require a signed deployment bundle from a trusted publisher.
Fetched event content and tags must match the Nostr signature.
Use validateUseCaseProfileDefinition() for custom profiles, then verifyProductionDeployment() with trusted bundle publishers, signed relay events, accepted circle manifests, expected subject, freshness, and threshold policy so these checks are not left to application glue. For application UI and audit logs, use verifyProductionDeploymentReport() or createProductionDecisionReport() so failures include issue codes, remediation text, a recommended action, pass/fail/not-checked status for the controls, and the profile's proofClaims, proofLimitations, requiredControls, and recommendedActions.