/nip85-trusted-assertions
The NIP-85 trusted-assertions model in Quartz (`nip85TrustedAssertions/`) — kind 10040 trust-provider lists, kind 30382 contact cards / user assertions, 30383 event assertions, 30384 addressable assertions, 30385 external-id assertions. Use when building or parsing these events,
$ npx -y skills add vitorpamplona/amethyst --skill nip85-trusted-assertions --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/nip85-trusted-assertions
Context preview
The summary Claude sees to decide when to auto-load this skill.
The NIP-85 trusted-assertions model in Quartz (`nip85TrustedAssertions/`) — kind 10040 trust-provider lists, kind 30382 contact cards / user assertions, 30383 event assertions, 30384 addressable assertions, 30385 external-id assertions. Use when building or parsing these events,
SKILL.md
nip85-trusted-assertions.SKILL.mdname: nip85-trusted-assertions
description: The NIP-85 trusted-assertions model in Quartz (`nip85TrustedAssertions/`) — kind 10040 trust-provider lists, kind 30382 contact cards / user assertions, 30383 event assertions, 30384 addressable assertions, 30385 external-id assertions. Use when building or parsing these events, working with the typed tags (RankTag, HopsTag, FollowerCountTag, ServiceProviderTag/ServiceType, …), wiring a consumer that resolves a 10040 provider entry to the 30382s it signs, ranking on assertion values, or touching the GrapeRank publisher, contact-card nicknames, or the trust projection of an external store.
NIP-85 Trusted Assertions — the Quartz model
Package: `quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip85TrustedAssertions/`. NIP-85 is still an evolving spec; **this package is the operative definition** of what Amethyst-family software writes and reads. This skill states the model (who signs what about whom), the exact kind/d-tag/tag vocabulary, and what consumers may — and may not — assume.
The model in one paragraph
An **assertion is signed by the asserting party** (a trust provider service, or the user themself) **about a subject named in the d-tag**. All assertion kinds are addressable, so "latest card by provider P about subject S" is just the addressable coordinate `(kind, P, S)` and supersession is standard NIP-01 latest-wins. Discovery is the observer's **kind 10040 list**: each entry says *"for metric M on kind K, I trust provider P — fetch their assertions at relay R"*. Quartz enforces none of this cryptographically beyond normal event signatures; the 10040→assertion link is **consumer-side convention** (see "Authorization" below).
Kind map
| Kind | Class | Kind class | d-tag = the subject | Content | |---|---|---|---|---| | 10040 | `list/TrustProviderListEvent` | replaceable | *(none — always `""`)* | NIP-44 private provider entries (optional) | | 30382 | `users/ContactCardEvent` | addressable | **target user's pubkey** (hex) | NIP-44 private tags (petname/summary/emoji) | | 30383 | `events/EventAssertionEvent` | addressable | **target event id** (hex) | `""` | | 30384 | `addressables/AddressableAssertionEvent` | addressable | **target coordinate** `kind:pubkey:dtag` | `""` | | 30385 | `externalIds/ExternalIdAssertionEvent` | addressable | **external identifier** (e.g. `isbn:978-0-13-468599-1`) | `""` |
Addresses: `ContactCardEvent.createAddress(owner, target)` → `Address(30382, owner, target)` (owner = signer, target = subject). `TrustProviderListEvent.createAddress(pubKey)` uses `FIXED_D_TAG = ""`. `AssertionEventTest.eventKindsAreCorrect` pins all five numbers.
`ContactCardEvent` is also a `SearchableEvent` — it indexes only the **public** petname/summary tags plus topics; the encrypted card content is intentionally never indexed.
The 10040 provider entry (`ServiceProviderTag` / `ServiceType`)
There is **no fixed tag name**: `tag[0]` *is* the service string.
["30382:rank", "<provider pubkey, 64 hex>", "wss://nip85.brainstorm.world"]
- `ServiceType(kind, type)` parses/renders `"<kind>:<type>"` — kind must be an int, the first
`:` splits, colons in the remainder stay in `type`. `ServiceType.isOfKind` is the allocation-free prefix check.
- `ServiceProviderTag.parse` requires ≥3 elements, non-empty service, 64-char pubkey
(length-only check), and a **normalizable relay URL** (`RelayUrlNormalizer.normalizeOrNull`) — entries failing any check are silently dropped, which is what keeps foreign tags like `["client","nostria"]` out (regression-tested in `ServiceTypeParserTest`).
- Entries may be **public** (tag array) or **private** (NIP-44 content); `create`/`add` take
`isPrivate`. `remove` always needs decryption and strips from both sides by parsed-value equality.
- `object ProviderTypes` (`list/tags/ServiceType.kt`) enumerates the *known* service types —
`30382:rank`, `30382:followers`, `30382:first_created_at`, per-metric `30383:*`/`30384:*`/ `30385:*`, etc. It is an **open vocabulary**: real 10040s in the wild (see the fiatjaf → brainstorm fixture in `commonTest/.../nip85TrustedAssertions/ServiceParser.kt`) carry types Quartz doesn't enumerate (`30382:personalizedGrapeRank_influence`, `30382:hops`, `30382:verifiedFollowersCount`, …). Parse any `kind:type`; special-case only what you rank on.
Authorization — what a consumer may assume
- **A 30382 (or 30383/…) is meaningful to an observer only if its author is listed in the
observer's 10040 for a matching service type.** Quartz does not enforce this; the consuming code does. The in-repo pattern is `commons/.../model/nip85TrustedAssertions/UserCardsCache.kt`: `rankFlow(trustProviderList)` picks the received card whose **author pubkey equals the provider entry's pubkey** and reads `rank()` from it. Assertions from unlisted signers are simply ignored for trust purposes (they may still be stored; dropping them — as an external store's orphan sweep does — is a legitimate storage policy, not a protocol rule).
- What an entry authorizes is scoped by its `ServiceType`: `30382:rank` authorizes that
provider's user-rank cards, nothing else. Amethyst models this as one provider slot per metric (`liveUserRankProvider`, `liveUserFollowerCount` in `amethyst/.../model/trustedAssertions/TrustProviderListState.kt`).
- **Multi-provider combination is unprescribed.** When two listed providers assert different
ranks, there is no spec'd merge; Amethyst avoids the question by selecting one provider per metric slot. Consumers choose their own policy — document it.
- The relay URL in the entry is a **fetch hint, and it is honored**:
`amethyst/.../UserCardsSubAssembler.kt` subscribes for cards at the provider's declared relay (`kinds=[30382], authors=[provider], #d=[targets]`).
The dual use of kind 30382
The same kind serves two roles, distinguished **by author**:
1. **Provider WoT cards** — signed by a trust pro
Read more
name: nip85-trusted-assertions description: The NIP-85 trusted-assertions model in Quartz (`nip85TrustedAssertions/`) — kind 10040 trust-provider lists, kind 30382 contact cards / user assertions, 30383 event assertions, 30384 addressable assertions, 30385 external-id assertions. Use when building or parsing these events, working with the typed tags (RankTag, HopsTag, FollowerCountTag, ServiceProviderTag/ServiceType, …), wiring a consumer that resolves a 10040 provider entry to the 30382s it signs, ranking on assertion values, or touching the GrapeRank publisher, contact-card nicknames, or the trust projection of an external store.
NIP-85 Trusted Assertions — the Quartz model
Package: `quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip85TrustedAssertions/`. NIP-85 is still an evolving spec; **this package is the operative definition** of what Amethyst-family software writes and reads. This skill states the model (who signs what about whom), the exact kind/d-tag/tag vocabulary, and what consumers may — and may not — assume.
The model in one paragraph
An **assertion is signed by the asserting party** (a trust provider service, or the user themself) **about a subject named in the d-tag**. All assertion kinds are addressable, so "latest card by provider P about subject S" is just the addressable coordinate `(kind, P, S)` and supersession is standard NIP-01 latest-wins. Discovery is the observer's **kind 10040 list**: each entry says *"for metric M on kind K, I trust provider P — fetch their assertions at relay R"*. Quartz enforces none of this cryptographically beyond normal event signatures; the 10040→assertion link is **consumer-side convention** (see "Authorization" below).
Kind map
| Kind | Class | Kind class | d-tag = the subject | Content | |---|---|---|---|---| | 10040 | `list/TrustProviderListEvent` | replaceable | *(none — always `""`)* | NIP-44 private provider entries (optional) | | 30382 | `users/ContactCardEvent` | addressable | **target user's pubkey** (hex) | NIP-44 private tags (petname/summary/emoji) | | 30383 | `events/EventAssertionEvent` | addressable | **target event id** (hex) | `""` | | 30384 | `addressables/AddressableAssertionEvent` | addressable | **target coordinate** `kind:pubkey:dtag` | `""` | | 30385 | `externalIds/ExternalIdAssertionEvent` | addressable | **external identifier** (e.g. `isbn:978-0-13-468599-1`) | `""` |
Addresses: `ContactCardEvent.createAddress(owner, target)` → `Address(30382, owner, target)` (owner = signer, target = subject). `TrustProviderListEvent.createAddress(pubKey)` uses `FIXED_D_TAG = ""`. `AssertionEventTest.eventKindsAreCorrect` pins all five numbers.
`ContactCardEvent` is also a `SearchableEvent` — it indexes only the **public** petname/summary tags plus topics; the encrypted card content is intentionally never indexed.
The 10040 provider entry (`ServiceProviderTag` / `ServiceType`)
There is **no fixed tag name**: `tag[0]` *is* the service string.
["30382:rank", "<provider pubkey, 64 hex>", "wss://nip85.brainstorm.world"]
- `ServiceType(kind, type)` parses/renders `"<kind>:<type>"` — kind must be an int, the first
`:` splits, colons in the remainder stay in `type`. `ServiceType.isOfKind` is the allocation-free prefix check.
- `ServiceProviderTag.parse` requires ≥3 elements, non-empty service, 64-char pubkey
(length-only check), and a **normalizable relay URL** (`RelayUrlNormalizer.normalizeOrNull`) — entries failing any check are silently dropped, which is what keeps foreign tags like `["client","nostria"]` out (regression-tested in `ServiceTypeParserTest`).
- Entries may be **public** (tag array) or **private** (NIP-44 content); `create`/`add` take
`isPrivate`. `remove` always needs decryption and strips from both sides by parsed-value equality.
- `object ProviderTypes` (`list/tags/ServiceType.kt`) enumerates the *known* service types —
`30382:rank`, `30382:followers`, `30382:first_created_at`, per-metric `30383:*`/`30384:*`/ `30385:*`, etc. It is an **open vocabulary**: real 10040s in the wild (see the fiatjaf → brainstorm fixture in `commonTest/.../nip85TrustedAssertions/ServiceParser.kt`) carry types Quartz doesn't enumerate (`30382:personalizedGrapeRank_influence`, `30382:hops`, `30382:verifiedFollowersCount`, …). Parse any `kind:type`; special-case only what you rank on.
Authorization — what a consumer may assume
- **A 30382 (or 30383/…) is meaningful to an observer only if its author is listed in the
observer's 10040 for a matching service type.** Quartz does not enforce this; the consuming code does. The in-repo pattern is `commons/.../model/nip85TrustedAssertions/UserCardsCache.kt`: `rankFlow(trustProviderList)` picks the received card whose **author pubkey equals the provider entry's pubkey** and reads `rank()` from it. Assertions from unlisted signers are simply ignored for trust purposes (they may still be stored; dropping them — as an external store's orphan sweep does — is a legitimate storage policy, not a protocol rule).
- What an entry authorizes is scoped by its `ServiceType`: `30382:rank` authorizes that
provider's user-rank cards, nothing else. Amethyst models this as one provider slot per metric (`liveUserRankProvider`, `liveUserFollowerCount` in `amethyst/.../model/trustedAssertions/TrustProviderListState.kt`).
- **Multi-provider combination is unprescribed.** When two listed providers assert different
ranks, there is no spec'd merge; Amethyst avoids the question by selecting one provider per metric slot. Consumers choose their own policy — document it.
- The relay URL in the entry is a **fetch hint, and it is honored**:
`amethyst/.../UserCardsSubAssembler.kt` subscribes for cards at the provider's declared relay (`kinds=[30382], authors=[provider], #d=[targets]`).
The dual use of kind 30382
The same kind serves two roles, distinguished **by author**:
1. **Provider WoT cards** — signed by a trust pro
Other skills on amethyst.
- /account-state
Account state and in-memory event store patterns in Amethyst. Use when working with `Account.kt` (per-user state objects — `kind3FollowList`, `nip65RelayList`, `muteList`, `bookmarkState`, each exposing a `.flow` StateFlow), `LocalCache` (the object-level event store backed by
Open skill - /amy-expert
Patterns for extending `amy`, the Amethyst CLI in `cli/`. Use when adding an `amy <verb>` command, touching files under `cli/src/main/kotlin/…/cli/`, wiring a new subcommand into `Main.kt`, writing an interop test script that drives Amy, or extracting logic out of `amethyst/`
Open skill - /android-expert
Android platform patterns for the `amethyst/` module. Use when working with (1) Android navigation (Navigation Compose, type-safe routes, bottom nav), (2) runtime permissions (camera, notifications, biometrics), (3) platform APIs (Intent, Context, Activity, ContentResolver), (4)
Open skill - /auth-signers
Signer abstraction patterns in Amethyst. Use when working with event signing, choosing between a local keypair (`NostrSignerInternal`), a remote NIP-46 bunker signer (`NostrSignerRemote`), or a NIP-55 Android external-app signer (`NostrSignerExternal`). Covers the abstract
Open skill - /compose-expert
Advanced Compose Multiplatform UI patterns for shared composables. Use when working with visual UI components, state management patterns (remember, derivedStateOf, produceState), recomposition optimization (@Stable/@Immutable visual usage), Material3 theming, custom ImageVector
Open skill - /compose-modifier-and-layout-style
Use when writing or reviewing Jetpack Compose layout APIs, modifier parameters, modifier chain construction, hardcoded root layout decisions, or layout wrappers around a single conditional. Technique-layer skill — complements the codebase-specific compose-expert.
Open skill

