@hypercerts-org/lexicon 0.10.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/CHANGELOG.md +218 -0
  2. package/README.md +392 -393
  3. package/SCHEMAS.md +539 -81
  4. package/dist/exports.d.ts +900 -10
  5. package/dist/exports.d.ts.map +1 -1
  6. package/dist/generated/exports.d.ts +900 -10
  7. package/dist/generated/exports.d.ts.map +1 -1
  8. package/dist/generated/lexicons.d.ts +2684 -1094
  9. package/dist/generated/lexicons.d.ts.map +1 -1
  10. package/dist/generated/types/app/bsky/richtext/facet.d.ts +44 -7
  11. package/dist/generated/types/app/bsky/richtext/facet.d.ts.map +1 -1
  12. package/dist/generated/types/app/certified/badge/award.d.ts +2 -3
  13. package/dist/generated/types/app/certified/badge/award.d.ts.map +1 -1
  14. package/dist/generated/types/app/certified/badge/definition.d.ts +3 -3
  15. package/dist/generated/types/app/certified/badge/definition.d.ts.map +1 -1
  16. package/dist/generated/types/app/certified/badge/response.d.ts +2 -2
  17. package/dist/generated/types/app/certified/badge/response.d.ts.map +1 -1
  18. package/dist/generated/types/app/certified/link/evm.d.ts +45 -0
  19. package/dist/generated/types/app/certified/link/evm.d.ts.map +1 -0
  20. package/dist/generated/types/org/hypercerts/claim/activity.d.ts +6 -3
  21. package/dist/generated/types/org/hypercerts/claim/activity.d.ts.map +1 -1
  22. package/dist/generated/types/org/hypercerts/collection.d.ts +11 -5
  23. package/dist/generated/types/org/hypercerts/collection.d.ts.map +1 -1
  24. package/dist/generated/types/org/hypercerts/context/attachment.d.ts +5 -3
  25. package/dist/generated/types/org/hypercerts/context/attachment.d.ts.map +1 -1
  26. package/dist/generated/types/org/hypercerts/context/evaluation.d.ts +6 -6
  27. package/dist/generated/types/org/hypercerts/context/evaluation.d.ts.map +1 -1
  28. package/dist/generated/types/org/hypercerts/defs.d.ts +11 -0
  29. package/dist/generated/types/org/hypercerts/defs.d.ts.map +1 -1
  30. package/dist/generated/types/org/hypercerts/funding/receipt.d.ts +17 -5
  31. package/dist/generated/types/org/hypercerts/funding/receipt.d.ts.map +1 -1
  32. package/dist/generated/types/pub/leaflet/blocks/blockquote.d.ts +13 -0
  33. package/dist/generated/types/pub/leaflet/blocks/blockquote.d.ts.map +1 -0
  34. package/dist/generated/types/pub/leaflet/blocks/bskyPost.d.ts +13 -0
  35. package/dist/generated/types/pub/leaflet/blocks/bskyPost.d.ts.map +1 -0
  36. package/dist/generated/types/pub/leaflet/blocks/button.d.ts +12 -0
  37. package/dist/generated/types/pub/leaflet/blocks/button.d.ts.map +1 -0
  38. package/dist/generated/types/pub/leaflet/blocks/code.d.ts +13 -0
  39. package/dist/generated/types/pub/leaflet/blocks/code.d.ts.map +1 -0
  40. package/dist/generated/types/pub/leaflet/blocks/header.d.ts +14 -0
  41. package/dist/generated/types/pub/leaflet/blocks/header.d.ts.map +1 -0
  42. package/dist/generated/types/pub/leaflet/blocks/horizontalRule.d.ts +10 -0
  43. package/dist/generated/types/pub/leaflet/blocks/horizontalRule.d.ts.map +1 -0
  44. package/dist/generated/types/pub/leaflet/blocks/iframe.d.ts +12 -0
  45. package/dist/generated/types/pub/leaflet/blocks/iframe.d.ts.map +1 -0
  46. package/dist/generated/types/pub/leaflet/blocks/image.d.ts +21 -0
  47. package/dist/generated/types/pub/leaflet/blocks/image.d.ts.map +1 -0
  48. package/dist/generated/types/pub/leaflet/blocks/math.d.ts +11 -0
  49. package/dist/generated/types/pub/leaflet/blocks/math.d.ts.map +1 -0
  50. package/dist/generated/types/pub/leaflet/blocks/orderedList.d.ts +31 -0
  51. package/dist/generated/types/pub/leaflet/blocks/orderedList.d.ts.map +1 -0
  52. package/dist/generated/types/pub/leaflet/blocks/page.d.ts +11 -0
  53. package/dist/generated/types/pub/leaflet/blocks/page.d.ts.map +1 -0
  54. package/dist/generated/types/pub/leaflet/blocks/poll.d.ts +12 -0
  55. package/dist/generated/types/pub/leaflet/blocks/poll.d.ts.map +1 -0
  56. package/dist/generated/types/pub/leaflet/blocks/text.d.ts +14 -0
  57. package/dist/generated/types/pub/leaflet/blocks/text.d.ts.map +1 -0
  58. package/dist/generated/types/pub/leaflet/blocks/unorderedList.d.ts +29 -0
  59. package/dist/generated/types/pub/leaflet/blocks/unorderedList.d.ts.map +1 -0
  60. package/dist/generated/types/pub/leaflet/blocks/website.d.ts +14 -0
  61. package/dist/generated/types/pub/leaflet/blocks/website.d.ts.map +1 -0
  62. package/dist/generated/types/pub/leaflet/pages/linearDocument.d.ts +54 -1
  63. package/dist/generated/types/pub/leaflet/pages/linearDocument.d.ts.map +1 -1
  64. package/dist/generated/types/pub/leaflet/richtext/facet.d.ts +97 -0
  65. package/dist/generated/types/pub/leaflet/richtext/facet.d.ts.map +1 -0
  66. package/dist/index.cjs +4487 -1808
  67. package/dist/index.cjs.map +1 -1
  68. package/dist/index.mjs +4408 -1805
  69. package/dist/index.mjs.map +1 -1
  70. package/dist/lexicons.cjs +928 -36
  71. package/dist/lexicons.cjs.map +1 -1
  72. package/dist/lexicons.d.ts +2684 -1094
  73. package/dist/lexicons.d.ts.map +1 -1
  74. package/dist/lexicons.mjs +928 -36
  75. package/dist/lexicons.mjs.map +1 -1
  76. package/dist/types/app/bsky/richtext/facet.d.ts +44 -7
  77. package/dist/types/app/bsky/richtext/facet.d.ts.map +1 -1
  78. package/dist/types/app/certified/badge/award.d.ts +2 -3
  79. package/dist/types/app/certified/badge/award.d.ts.map +1 -1
  80. package/dist/types/app/certified/badge/definition.d.ts +3 -3
  81. package/dist/types/app/certified/badge/definition.d.ts.map +1 -1
  82. package/dist/types/app/certified/badge/response.d.ts +2 -2
  83. package/dist/types/app/certified/badge/response.d.ts.map +1 -1
  84. package/dist/types/app/certified/link/evm.d.ts +45 -0
  85. package/dist/types/app/certified/link/evm.d.ts.map +1 -0
  86. package/dist/types/org/hypercerts/claim/activity.d.ts +6 -3
  87. package/dist/types/org/hypercerts/claim/activity.d.ts.map +1 -1
  88. package/dist/types/org/hypercerts/collection.d.ts +11 -5
  89. package/dist/types/org/hypercerts/collection.d.ts.map +1 -1
  90. package/dist/types/org/hypercerts/context/attachment.d.ts +5 -3
  91. package/dist/types/org/hypercerts/context/attachment.d.ts.map +1 -1
  92. package/dist/types/org/hypercerts/context/evaluation.d.ts +6 -6
  93. package/dist/types/org/hypercerts/context/evaluation.d.ts.map +1 -1
  94. package/dist/types/org/hypercerts/defs.d.ts +11 -0
  95. package/dist/types/org/hypercerts/defs.d.ts.map +1 -1
  96. package/dist/types/org/hypercerts/funding/receipt.d.ts +17 -5
  97. package/dist/types/org/hypercerts/funding/receipt.d.ts.map +1 -1
  98. package/dist/types/pub/leaflet/blocks/blockquote.d.ts +13 -0
  99. package/dist/types/pub/leaflet/blocks/blockquote.d.ts.map +1 -0
  100. package/dist/types/pub/leaflet/blocks/bskyPost.d.ts +13 -0
  101. package/dist/types/pub/leaflet/blocks/bskyPost.d.ts.map +1 -0
  102. package/dist/types/pub/leaflet/blocks/button.d.ts +12 -0
  103. package/dist/types/pub/leaflet/blocks/button.d.ts.map +1 -0
  104. package/dist/types/pub/leaflet/blocks/code.d.ts +13 -0
  105. package/dist/types/pub/leaflet/blocks/code.d.ts.map +1 -0
  106. package/dist/types/pub/leaflet/blocks/header.d.ts +14 -0
  107. package/dist/types/pub/leaflet/blocks/header.d.ts.map +1 -0
  108. package/dist/types/pub/leaflet/blocks/horizontalRule.d.ts +10 -0
  109. package/dist/types/pub/leaflet/blocks/horizontalRule.d.ts.map +1 -0
  110. package/dist/types/pub/leaflet/blocks/iframe.d.ts +12 -0
  111. package/dist/types/pub/leaflet/blocks/iframe.d.ts.map +1 -0
  112. package/dist/types/pub/leaflet/blocks/image.d.ts +21 -0
  113. package/dist/types/pub/leaflet/blocks/image.d.ts.map +1 -0
  114. package/dist/types/pub/leaflet/blocks/math.d.ts +11 -0
  115. package/dist/types/pub/leaflet/blocks/math.d.ts.map +1 -0
  116. package/dist/types/pub/leaflet/blocks/orderedList.d.ts +31 -0
  117. package/dist/types/pub/leaflet/blocks/orderedList.d.ts.map +1 -0
  118. package/dist/types/pub/leaflet/blocks/page.d.ts +11 -0
  119. package/dist/types/pub/leaflet/blocks/page.d.ts.map +1 -0
  120. package/dist/types/pub/leaflet/blocks/poll.d.ts +12 -0
  121. package/dist/types/pub/leaflet/blocks/poll.d.ts.map +1 -0
  122. package/dist/types/pub/leaflet/blocks/text.d.ts +14 -0
  123. package/dist/types/pub/leaflet/blocks/text.d.ts.map +1 -0
  124. package/dist/types/pub/leaflet/blocks/unorderedList.d.ts +29 -0
  125. package/dist/types/pub/leaflet/blocks/unorderedList.d.ts.map +1 -0
  126. package/dist/types/pub/leaflet/blocks/website.d.ts +14 -0
  127. package/dist/types/pub/leaflet/blocks/website.d.ts.map +1 -0
  128. package/dist/types/pub/leaflet/pages/linearDocument.d.ts +54 -1
  129. package/dist/types/pub/leaflet/pages/linearDocument.d.ts.map +1 -1
  130. package/dist/types/pub/leaflet/richtext/facet.d.ts +97 -0
  131. package/dist/types/pub/leaflet/richtext/facet.d.ts.map +1 -0
  132. package/lexicons/app/bsky/richtext/facet.json +51 -0
  133. package/lexicons/app/certified/badge/award.json +2 -2
  134. package/lexicons/app/certified/badge/definition.json +10 -2
  135. package/lexicons/app/certified/badge/response.json +2 -2
  136. package/lexicons/app/certified/link/evm.json +88 -0
  137. package/lexicons/org/hyperboards/board.json +1 -1
  138. package/lexicons/org/hypercerts/claim/activity.json +8 -4
  139. package/lexicons/org/hypercerts/collection.json +19 -5
  140. package/lexicons/org/hypercerts/context/attachment.json +16 -5
  141. package/lexicons/org/hypercerts/context/evaluation.json +9 -6
  142. package/lexicons/org/hypercerts/defs.json +21 -0
  143. package/lexicons/org/hypercerts/funding/receipt.json +30 -10
  144. package/lexicons/pub/leaflet/blocks/blockquote.json +22 -0
  145. package/lexicons/pub/leaflet/blocks/bskyPost.json +19 -0
  146. package/lexicons/pub/leaflet/blocks/button.json +19 -0
  147. package/lexicons/pub/leaflet/blocks/code.json +21 -0
  148. package/lexicons/pub/leaflet/blocks/header.json +27 -0
  149. package/lexicons/pub/leaflet/blocks/horizontalRule.json +11 -0
  150. package/lexicons/pub/leaflet/blocks/iframe.json +21 -0
  151. package/lexicons/pub/leaflet/blocks/image.json +37 -0
  152. package/lexicons/pub/leaflet/blocks/math.json +15 -0
  153. package/lexicons/pub/leaflet/blocks/orderedList.json +54 -0
  154. package/lexicons/pub/leaflet/blocks/page.json +15 -0
  155. package/lexicons/pub/leaflet/blocks/poll.json +16 -0
  156. package/lexicons/pub/leaflet/blocks/text.json +26 -0
  157. package/lexicons/pub/leaflet/blocks/unorderedList.json +50 -0
  158. package/lexicons/pub/leaflet/blocks/website.json +27 -0
  159. package/lexicons/pub/leaflet/pages/linearDocument.json +98 -0
  160. package/lexicons/pub/leaflet/richtext/facet.json +149 -0
  161. package/package.json +5 -5
package/README.md CHANGED
@@ -1,39 +1,181 @@
1
- # Hypercerts Lexicon Documentation
2
-
3
- This repository contains ATProto lexicon definitions for the
4
- Hypercerts protocol. Each lexicon defines a record type that can be
5
- stored on the ATProto network.
6
-
7
- ## Entity Relationship Diagram
1
+ # @hypercerts-org/lexicon
2
+
3
+ ATProto lexicon definitions and TypeScript types for the
4
+ [Hypercerts](https://hypercerts.org) protocol a system for tracking,
5
+ evaluating, and funding impact work on the
6
+ [AT Protocol](https://atproto.com) network.
7
+
8
+ ## Lexicon Map
9
+
10
+ ```text
11
+ CLAIMS ─ the core impact record and its parts
12
+ ──────────────────────────────────────────────────────────────────────
13
+ activity ──────────┬──► collection ◄──┐ (recursive nesting)
14
+ (the hypercert) │ │ │
15
+ │ ▼ │
16
+ ├──► contribution (role, timeframe)
17
+ ├──► contributorInformation (identity, image)
18
+ ├──► rights (licensing terms)
19
+ └──► workScope
20
+ ├── cel ───► tag (CEL expression referencing tags)
21
+ └── string (free-form scope)
22
+
23
+ CONTEXT ─ evidence, data, and social verification
24
+ ──────────────────────────────────────────────────────────────────────
25
+ attachment ─────────────► any record (activity, evaluation, …)
26
+ measurement ────────────► any record (activity, …)
27
+ evaluation ─────────────► any record (activity, measurement, …)
28
+ └──────► measurement
29
+ acknowledgement ────────► any record (bidirectional link)
30
+
31
+ FUNDING ─ payment records
32
+ ──────────────────────────────────────────────────────────────────────
33
+ receipt ────────────────► activity (from funder → to recipient)
34
+
35
+ HYPERBOARDS ─ visual display layer (hyperboards.org)
36
+ ──────────────────────────────────────────────────────────────────────
37
+ board ──────────────────► activity / collection
38
+ └── contributorConfig ► contributorInformation
39
+ displayProfile (per-user visual defaults)
40
+
41
+ CERTIFIED ─ shared lexicons (certified.app)
42
+ ──────────────────────────────────────────────────────────────────────
43
+ location (geo coordinates, GeoJSON, H3, …)
44
+ link/evm (ATProto DID ↔ EVM wallet link)
45
+ actor/profile (user profile)
46
+ actor/organization (org metadata)
47
+ badge/response ──► badge/award ──► badge/definition
48
+ ```
8
49
 
9
- The following diagrams show the relationship between:
50
+ Every arrow (`►`) is a `strongRef` or union reference stored on the
51
+ AT Protocol network. Full field-level documentation is in
52
+ [SCHEMAS.md](SCHEMAS.md).
10
53
 
11
- - data classes represented by ATProto lexicons, which model the data
12
- sets relating to hypercerts
54
+ ## Consuming These Lexicons
13
55
 
14
- - contributors to activity records (modelled/identified by ATProto
15
- DIDs rather than lexicons)
56
+ If you are building a downstream application on top of these lexicons,
57
+ we strongly recommend **NOT** reading from `main` or other development
58
+ branches of the repository, but instead via the following published
59
+ releases:
16
60
 
17
- - hypercerts protocol tokens which are onchain representations of
18
- activity records in ATProto
61
+ - **For TypeScript / JavaScript code** use [the npm package
62
+ `@hypercerts-org/lexicon`](https://www.npmjs.com/package/@hypercerts-org/lexicon),
63
+ which includes generated types, validation helpers, and schema
64
+ constants.
65
+ - **For other languages** — use the [tagged
66
+ releases](https://github.com/hypercerts-org/hypercerts-lexicon/releases)
67
+ published in this GitHub repository.
19
68
 
20
- Note that contributors and tokens do not require lexicons.
69
+ Both npm releases and git tags follow [SemVer](https://semver.org/).
70
+ For npm, you can depend on a version range to receive compatible
71
+ updates automatically. For GitHub releases/tags, pin a specific tag
72
+ or upgrade manually to a newer compatible SemVer release.
21
73
 
22
- To distinguish these in the diagrams, each class has one of the
23
- following icons:
74
+ The raw lexicons published on ATProto can also be used, but they are
75
+ (unavoidably) missing useful context such as full documentation
76
+ (including changelogs), TypeScript type definitions, SemVer
77
+ guarantees, git history, and other tooling provided by the packaged
78
+ releases.
24
79
 
25
- - "D" means "data class"
26
- - "E" means "entity"
27
- - "P" means "protocol"
80
+ ### AI Agent Skill
28
81
 
29
- ![Hypercert ERD](ERD.svg)
82
+ If you use AI coding assistants (e.g. Claude Code, OpenCode), you can
83
+ install a skill that teaches your agent how to build with these
84
+ lexicons:
30
85
 
31
- <details>
32
- <summary>View ERD with field details</summary>
86
+ ```bash
87
+ npx skills add hypercerts-org/hypercerts-lexicon
88
+ ```
33
89
 
34
- ![Hypercert ERD with fields](ERD-with-fields.svg)
90
+ This installs the
91
+ [`building-with-hypercerts-lexicons`](.agents/skills/building-with-hypercerts-lexicons/SKILL.md)
92
+ skill, which provides your agent with guidance on package entry points,
93
+ TypeScript types, validation, all lexicon schemas, code examples, and
94
+ AT Protocol conventions.
95
+
96
+ ## Maintenance and publishing releases
97
+
98
+ Clearly stability and predictability for users and developers are
99
+ essential.
100
+
101
+ Unfortunately AT Protocol doesn't support any kind of native
102
+ versioning or migrations which could support lexicon schema changes.
103
+ Instead, the AT Protocol community recommends minimising changes to
104
+ lexicons in general, and to avoid breaking changes wherever possible:
105
+
106
+ - https://atproto.com/guides/lexicon-style-guide
107
+ - https://www.pfrazee.com/blog/lexicon-guidance
108
+
109
+ This project intends to follow that guidance as much as possible
110
+ whilst retaining a pragmatic approach. In practice that means:
111
+
112
+ - Changes to other tooling within this repository which _do not touch
113
+ lexicons_ may be made at any time as long as they follow
114
+ [SemVer](https://semver.org/) to avoid negative impact on
115
+ developers.
116
+
117
+ - Non-breaking changes to lexicons, such as adding an optional
118
+ property or updating a `description`, may be made sparingly. While
119
+ these changes are backwards-compatible at the protocol level, they
120
+ may still require consuming applications and indexers to update
121
+ their schemas for consistent UX.
122
+
123
+ - Breaking changes to lexicons will only be made in exceptional
124
+ circumstances. Specifically, a breaking change will only proceed
125
+ **if and only if**:
126
+ - the broader community — not just the Hypercerts core team —
127
+ agrees that the benefits clearly outweigh the cost of the
128
+ breakage, **and**
129
+ - full consideration is given to all affected parties across the
130
+ community and wider ecosystem, not only those involved in the
131
+ decision, **and**
132
+ - no viable alternative exists, such as releasing a new `.v2`
133
+ version of the lexicon or introducing a `v2` field.
134
+
135
+ To date, breaking changes have only occurred during the early
136
+ stages of launching Hypercerts on AT Protocol, before external
137
+ consumers were building against the lexicons. We intend to keep
138
+ it that way.
139
+
140
+ It is also worth noting that members of the ATProto community have
141
+ been working on tooling to make these problems easier to deal with in
142
+ future, e.g. see https://panproto.dev/
143
+
144
+ ## Use of branches
145
+
146
+ `main` is the only evergreen branch and the default branch on GitHub.
147
+ We aim to minimise deviations between `main` and versions published on
148
+ npm and ATProto. However the publishing processes involve several
149
+ moving parts (including third-party systems), and it is technically
150
+ impossible to update all three at the same time. So **please do not
151
+ assume they will always be perfectly in sync**.
152
+
153
+ See [docs/PUBLISHING.md](docs/PUBLISHING.md) for the full release workflow.
154
+
155
+ > If you see a `develop` branch, it is a stale leftover from a
156
+ > previous workflow and is no longer used; do not open pull requests
157
+ > against it.
158
+
159
+ ## Contributing / development
160
+
161
+ Please see [CONTRIBUTING.md](CONTRIBUTING.md).
162
+
163
+ ### Project Structure
164
+
165
+ ```text
166
+ lexicons/ Source of truth (committed)
167
+ org/hypercerts/ Hypercerts protocol lexicons
168
+ org/hyperboards/ Hyperboards visual layer lexicons
169
+ app/certified/ Shared/certified lexicons
170
+ com/atproto/ ATProto external references
171
+
172
+ generated/ Auto-generated TypeScript (gitignored)
173
+ dist/ Built bundles (gitignored)
174
+ scripts/ Build and codegen scripts
175
+ ```
35
176
 
36
- </details>
177
+ > **Never edit `generated/` or `dist/` directly** — they are
178
+ > regenerated from lexicon JSON files.
37
179
 
38
180
  ## Installation
39
181
 
@@ -41,9 +183,7 @@ following icons:
41
183
  npm install @hypercerts-org/lexicon
42
184
  ```
43
185
 
44
- ## Usage
45
-
46
- ### Basic Import
186
+ ## Quick Start
47
187
 
48
188
  ```typescript
49
189
  import {
@@ -51,8 +191,6 @@ import {
51
191
  ACTIVITY_NSID,
52
192
  validate,
53
193
  } from "@hypercerts-org/lexicon";
54
-
55
- // Use with AT Protocol Agent
56
194
  import { Agent } from "@atproto/api";
57
195
 
58
196
  const agent = new Agent({ service: "https://bsky.social" });
@@ -60,222 +198,155 @@ const agent = new Agent({ service: "https://bsky.social" });
60
198
  // Register lexicons with the agent
61
199
  agent.api.lex.add(...HYPERCERTS_SCHEMAS);
62
200
 
63
- // Create a record
64
- const activityRecord = {
201
+ // Build a record
202
+ const record = {
65
203
  $type: ACTIVITY_NSID,
66
- title: "My Impact Work",
67
- shortDescription: "Description here",
68
- // workScope can be a CEL expression (structured, machine-evaluable):
69
- workScope: {
70
- $type: "org.hypercerts.workscope.cel",
71
- expression:
72
- "scope.hasAll(['mangrove_restoration', 'environmental_education']) && location.country == 'KE'",
73
- usedTags: [
74
- {
75
- uri: "at://did:plc:alice/org.hypercerts.workscope.tag/3k2abc",
76
- cid: "...",
77
- },
78
- {
79
- uri: "at://did:plc:alice/org.hypercerts.workscope.tag/7x9def",
80
- cid: "...",
81
- },
82
- ],
83
- version: "v1",
84
- createdAt: new Date().toISOString(),
85
- },
86
- // OR a strongRef to a single work scope tag:
87
- // workScope: { uri: "at://did:plc:alice/org.hypercerts.workscope.tag/abc123", cid: "..." },
88
- // OR a simple string: workScope: { $type: "org.hypercerts.claim.activity#workScopeString", scope: "Environmental conservation" },
89
- startDate: "2023-01-01T00:00:00Z",
90
- endDate: "2023-12-31T23:59:59Z",
204
+ title: "Reforestation in Amazon Basin 2024",
205
+ shortDescription: "Planted 5,000 native trees across 12 hectares",
91
206
  createdAt: new Date().toISOString(),
92
207
  };
93
208
 
94
- // Validate before creating
95
- const validation = validate(ACTIVITY_NSID, activityRecord);
96
- if (!validation.valid) {
97
- console.error("Validation failed:", validation.errors);
98
- }
209
+ // Validate before writing
210
+ const result = validate(record, ACTIVITY_NSID, "main");
211
+ if (!result.success) throw new Error(String(result.error));
99
212
 
213
+ // Write to the network
100
214
  await agent.api.com.atproto.repo.createRecord({
101
215
  repo: agent.session?.did,
102
216
  collection: ACTIVITY_NSID,
103
- record: activityRecord,
217
+ record,
104
218
  });
105
219
  ```
106
220
 
107
- ### Creating Location Records
221
+ ## Lexicon Reference
108
222
 
109
- Location records (`app.certified.location`) specify where work was performed
110
- using geographic coordinates or other location formats. They can be referenced
111
- by activities, collections, attachments, measurements, and evaluations.
223
+ ### Claims (`org.hypercerts.claim.*`)
112
224
 
113
- ```typescript
114
- import { LOCATION_NSID } from "@hypercerts-org/lexicon";
225
+ | Lexicon | NSID | Description |
226
+ | --------------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
227
+ | **Activity** | `org.hypercerts.claim.activity` | The main hypercert record — describes impact work with title, description, contributors, work scope, timeframe, locations, and rights. |
228
+ | **Contribution** | `org.hypercerts.claim.contribution` | Details about a specific contribution: role, description, and timeframe. |
229
+ | **Contributor Information** | `org.hypercerts.claim.contributorInformation` | Identity record for a contributor: identifier (DID or URI), display name, and image. |
230
+ | **Rights** | `org.hypercerts.claim.rights` | Licensing and rights terms (e.g. "CC BY-SA 4.0") attached to an activity. |
115
231
 
116
- const locationRecord = {
117
- $type: LOCATION_NSID,
118
- lpVersion: "1.0", // Location Protocol version
119
- srs: "http://www.opengis.net/def/crs/OGC/1.3/CRS84", // Spatial Reference System
120
- locationType: "coordinate-decimal", // or "geojson-point", "geojson", "h3", "geohash", "wkt", "address", etc.
121
- location: {
122
- uri: "https://example.com/location-data.geojson",
123
- },
124
- // Optional fields
125
- name: "Project Site A",
126
- description: "Primary research facility in the Amazon rainforest",
127
- createdAt: new Date().toISOString(),
128
- };
129
- ```
232
+ ### Collections (`org.hypercerts.*`)
130
233
 
131
- - `lpVersion` (required): Version of the Location Protocol specification
132
- - `srs` (required): Spatial Reference System URI defining the coordinate system
133
- - `locationType` (required): Format identifier (e.g., "coordinate-decimal", "geojson-point", "geojson", "h3", "geohash", "wkt", "address", "scaledCoordinates"). See the [Location Protocol spec](https://spec.decentralizedgeo.org/specification/location-types/#location-type-registry) for the full registry.
134
- - `location` (required): Location data as URI, blob, or string
135
- - `name` (optional): Human-readable name for the location
136
- - `description` (optional): Additional context about the location
137
- - `createdAt` (required): Timestamp when the record was created
234
+ | Lexicon | NSID | Description |
235
+ | -------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
236
+ | **Collection** | `org.hypercerts.collection` | A named, weighted group of activities and/or other collections. Supports recursive nesting. Used for projects, portfolios, favourites, funding rounds, etc. |
138
237
 
139
- **Location data formats:**
238
+ ### Context (`org.hypercerts.context.*`)
140
239
 
141
- The `location` field accepts three formats:
240
+ | Lexicon | NSID | Description |
241
+ | ------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------- |
242
+ | **Attachment** | `org.hypercerts.context.attachment` | Documents, reports, evidence, or other files linked to a record. |
243
+ | **Measurement** | `org.hypercerts.context.measurement` | Quantitative data point (metric + unit + value) linked to one or more records. |
244
+ | **Evaluation** | `org.hypercerts.context.evaluation` | An assessment of a record with evaluators, summary, score, and supporting measurements. |
245
+ | **Acknowledgement** | `org.hypercerts.context.acknowledgement` | Bidirectional link: confirms or rejects inclusion of a record in another context. |
142
246
 
143
- 1. **URI reference**: `{ uri: "https://..." }` - Link to external location data
144
- 2. **Small blob**: Embedded location data (up to 10MB)
145
- 3. **Location string**: Inline string wrapped in an object, containing coordinates or GeoJSON
247
+ ### Work Scope (`org.hypercerts.workscope.*`)
146
248
 
147
- ```typescript
148
- // Example with embedded blob
149
- const locationWithBlob = {
150
- $type: LOCATION_NSID,
151
- lpVersion: "1.0",
152
- srs: "http://www.opengis.net/def/crs/OGC/1.3/CRS84",
153
- locationType: "geojson-point",
154
- location: {
155
- blob: {
156
- $type: "blob",
157
- ref: {
158
- $link: "bafyrei...", // CID of the uploaded blob
159
- },
160
- mimeType: "application/geo+json",
161
- size: 123,
162
- },
163
- },
164
- name: "Amazon Research Station",
165
- createdAt: new Date().toISOString(),
166
- };
249
+ | Lexicon | NSID | Description |
250
+ | ------------------ | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
251
+ | **Tag** | `org.hypercerts.workscope.tag` | Reusable scope atom (topic, domain, method, …) with taxonomy support, aliases, and linked ontologies. |
252
+ | **CEL Expression** | `org.hypercerts.workscope.cel` | Structured work scope using [CEL](https://github.com/google/cel-spec) expressions over tags. Embedded inline in activity records. |
167
253
 
168
- // Example with inline string (coordinates)
169
- const locationWithCoordinates = {
170
- $type: LOCATION_NSID,
171
- lpVersion: "1.0",
172
- srs: "http://www.opengis.net/def/crs/OGC/1.3/CRS84",
173
- locationType: "coordinate-decimal",
174
- location: {
175
- string: "-3.4653, -62.2159", // lat, lon
176
- },
177
- name: "Amazon Research Site",
178
- description: "Field station coordinates",
179
- createdAt: new Date().toISOString(),
180
- };
254
+ ### Funding (`org.hypercerts.funding.*`)
181
255
 
182
- // Example with inline GeoJSON string
183
- const locationWithGeoJSON = {
184
- $type: LOCATION_NSID,
185
- lpVersion: "1.0",
186
- srs: "http://www.opengis.net/def/crs/OGC/1.3/CRS84",
187
- locationType: "geojson-point",
188
- location: {
189
- string: '{"type":"Point","coordinates":[-62.2159,-3.4653]}',
190
- },
191
- name: "Research Station Alpha",
192
- createdAt: new Date().toISOString(),
193
- };
194
- ```
256
+ | Lexicon | NSID | Description |
257
+ | ----------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
258
+ | **Receipt** | `org.hypercerts.funding.receipt` | Records a payment to a recipient, with amount, currency, payment rail, and optional transaction ID. The sender (`from`) is optional to support anonymous funders. |
195
259
 
196
- ### Accessing NSIDs (Lexicon IDs)
260
+ ### Hyperboards (`org.hyperboards.*`)
197
261
 
198
- **Recommended**: Use individual NSID constants for cleaner, more readable code:
262
+ | Lexicon | NSID | Description |
263
+ | ------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
264
+ | **Board** | `org.hyperboards.board` | Visual presentation layer wrapping an activity or collection with background, colors, aspect ratio, and per-contributor styling. |
265
+ | **Display Profile** | `org.hyperboards.displayProfile` | Per-user visual defaults (avatar, hover image, video, click-through URL) reusable across boards. Singleton record (`literal:self`). |
199
266
 
200
- ```typescript
201
- import { ACTIVITY_NSID, COLLECTION_NSID } from "@hypercerts-org/lexicon";
267
+ ### Certified (`app.certified.*`)
202
268
 
203
- // Clean and explicit
204
- const record = {
205
- $type: ACTIVITY_NSID,
206
- // ...
207
- };
269
+ | Lexicon | NSID | Description |
270
+ | -------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
271
+ | **Location** | `app.certified.location` | Geographic reference using the [Location Protocol](https://spec.decentralizedgeo.org) (coordinates, GeoJSON, H3, WKT, etc.). |
272
+ | **Profile** | `app.certified.actor.profile` | User account profile with display name, bio, avatar, and banner. |
273
+ | **Organization** | `app.certified.actor.organization` | Organization metadata: legal structure, URLs, location, founding date. |
274
+ | **Badge Definition** | `app.certified.badge.definition` | Defines a badge with type, title, icon, and optional issuer allowlist. |
275
+ | **Badge Award** | `app.certified.badge.award` | Awards a badge to a user, project, or activity. |
276
+ | **Badge Response** | `app.certified.badge.response` | Recipient accepts or rejects a badge award. |
277
+ | **EVM Link** | `app.certified.link.evm` | Verifiable ATProto DID ↔ EVM wallet link via EIP-712 signature. Extensible for future proof methods (e.g. ERC-1271, ERC-6492). |
278
+
279
+ > **Full property tables** → [SCHEMAS.md](SCHEMAS.md)
280
+
281
+ ## Entity Relationship Diagram
282
+
283
+ ![Hypercert ERD](ERD.svg)
284
+
285
+ <details>
286
+ <summary>View ERD with field details</summary>
287
+
288
+ ![Hypercert ERD with fields](ERD-with-fields.svg)
289
+
290
+ </details>
291
+
292
+ ## Usage
293
+
294
+ ### Accessing NSIDs
295
+
296
+ Individual constants (recommended):
297
+
298
+ ```typescript
299
+ import {
300
+ ACTIVITY_NSID,
301
+ HYPERCERTS_COLLECTION_NSID,
302
+ } from "@hypercerts-org/lexicon";
208
303
  ```
209
304
 
210
- **Alternative**: Use the semantic NSID object when you need multiple NSIDs:
305
+ Semantic object:
211
306
 
212
307
  ```typescript
213
308
  import { HYPERCERTS_NSIDS } from "@hypercerts-org/lexicon";
214
309
 
215
- // Access via semantic keys
216
- const activityId = HYPERCERTS_NSIDS.ACTIVITY;
217
- const collectionId = HYPERCERTS_NSIDS.COLLECTION;
218
- const rightsId = HYPERCERTS_NSIDS.RIGHTS;
310
+ const id = HYPERCERTS_NSIDS.ACTIVITY;
219
311
  ```
220
312
 
221
- **Type-based mapping**: If you need to map TypeScript type namespaces to NSIDs:
313
+ Type-based mapping:
222
314
 
223
315
  ```typescript
224
316
  import { HYPERCERTS_NSIDS_BY_TYPE } from "@hypercerts-org/lexicon";
225
317
 
226
- // Access via type namespace names
227
- const activityId = HYPERCERTS_NSIDS_BY_TYPE.OrgHypercertsClaimActivity;
228
- const collectionId = HYPERCERTS_NSIDS_BY_TYPE.OrgHypercertsCollection;
318
+ const id = HYPERCERTS_NSIDS_BY_TYPE.OrgHypercertsClaimActivity;
229
319
  ```
230
320
 
231
- **Lightweight Bundle**: Import from `/lexicons` for runtime validation without TypeScript types (smaller bundle size):
321
+ Lightweight bundle (no TypeScript types, smaller bundle):
232
322
 
233
323
  ```typescript
234
324
  import { schemas, validate, ids } from "@hypercerts-org/lexicon/lexicons";
235
-
236
- // Lighter bundle, type-based namespace access
237
- const result = validate(ids.OrgHypercertsClaimActivity, record);
238
325
  ```
239
326
 
240
- **Note**: Individual constants (e.g., `ACTIVITY_NSID`) are the recommended approach for most use cases as they provide the best developer experience with clear, concise naming.
241
-
242
327
  ### TypeScript Types
243
328
 
244
- All lexicon types are exported as namespaces:
245
-
246
329
  ```typescript
247
330
  import { OrgHypercertsClaimActivity } from "@hypercerts-org/lexicon";
248
331
 
249
- // Use the Main type
250
332
  const activity: OrgHypercertsClaimActivity.Main = {
251
333
  $type: "org.hypercerts.claim.activity",
252
334
  title: "My Impact Work",
253
- // ... other fields
335
+ shortDescription: "...",
336
+ createdAt: new Date().toISOString(),
254
337
  };
255
338
  ```
256
339
 
257
- ### Individual Lexicon Imports
258
-
259
- Each lexicon is available in two forms as individual constants:
340
+ ### Lexicon Documents
260
341
 
261
342
  ```typescript
262
343
  import {
263
- // Raw JSON (untyped) - direct import from JSON files
264
- ACTIVITY_LEXICON_JSON,
265
- RIGHTS_LEXICON_JSON,
266
-
267
- // Typed LexiconDoc - from lexicons.get() at module initialization
268
- ACTIVITY_LEXICON_DOC,
269
- RIGHTS_LEXICON_DOC,
344
+ ACTIVITY_LEXICON_JSON, // raw JSON (untyped)
345
+ ACTIVITY_LEXICON_DOC, // LexiconDoc (typed)
270
346
  } from "@hypercerts-org/lexicon";
271
347
  ```
272
348
 
273
- | Suffix | Type | Source | Use Case |
274
- | ------- | -------------------- | ------------------------- | ------------------------------ |
275
- | `_JSON` | Untyped JSON | Direct JSON import | Raw schema data |
276
- | `_DOC` | `LexiconDoc` (typed) | `lexicons.get()` instance | Type-safe lexicon manipulation |
277
-
278
- Or access all lexicons via semantic mapping objects:
349
+ Or via semantic mapping objects:
279
350
 
280
351
  ```typescript
281
352
  import {
@@ -283,289 +354,217 @@ import {
283
354
  HYPERCERTS_LEXICON_DOC,
284
355
  } from "@hypercerts-org/lexicon";
285
356
 
286
- // Access via semantic keys (same keys as HYPERCERTS_NSIDS)
287
- const activityJSON = HYPERCERTS_LEXICON_JSON.ACTIVITY;
288
- const activityDoc = HYPERCERTS_LEXICON_DOC.ACTIVITY;
289
- const rightsJSON = HYPERCERTS_LEXICON_JSON.RIGHTS;
290
- const rightsDoc = HYPERCERTS_LEXICON_DOC.RIGHTS;
357
+ const doc = HYPERCERTS_LEXICON_DOC.ACTIVITY;
291
358
  ```
292
359
 
293
- ## Schema Documentation
294
-
295
- For complete schema documentation with all lexicon definitions and
296
- property tables, see [SCHEMAS.md](SCHEMAS.md).
297
-
298
360
  ## Examples
299
361
 
300
- ### Collections
301
-
302
- Collections (`org.hypercerts.collection`) are named sets of references to
303
- other records, for any purpose the creator chooses. They live at the
304
- top-level namespace (not under `claim`) because they can contain more than
305
- just claims.
306
-
307
- #### Use Cases
308
-
309
- - Defining which activity claims belong to a project
310
- - Collections of projects
311
- - Favourites lists
312
- - Items associated with a particular funding round or funder
313
- - Portfolios of work by a contributor or organization
314
- - Thematic groupings (by work scope/topic)
315
- - Curated showcases for display (e.g. a hyperboard)
316
- - Milestone groupings (activities in a sprint/cycle)
317
- - Geographic groupings (projects or locations in a region)
318
- - Collections for reporting (e.g. all claims in a grant report)
319
-
320
- **Note**: Hyperboards are a separate concern — they are visualisations
321
- built on top of collections, not collections themselves.
322
-
323
- #### Creating a Collection with Nested Items
362
+ ### Creating Activities with Work Scope
324
363
 
325
364
  ```typescript
326
- import { TID } from "@atproto/common";
365
+ import { ACTIVITY_NSID } from "@hypercerts-org/lexicon";
327
366
 
328
- const collectionRecord = {
329
- $type: "org.hypercerts.collection",
330
- title: "Climate Action Projects",
331
- shortDescription:
332
- "A collection of climate-related activities and sub-collections",
333
- items: [
334
- // Reference to an activity
335
- {
336
- uri: "at://did:plc:alice/org.hypercerts.claim.activity/3k2abc",
337
- cid: "...",
338
- },
339
- // Reference to another activity
340
- {
341
- uri: "at://did:plc:bob/org.hypercerts.claim.activity/7x9def",
342
- cid: "...",
343
- },
344
- // Reference to another collection (recursive!)
345
- {
346
- uri: "at://did:plc:carol/org.hypercerts.collection/4m5ghi",
347
- cid: "...",
348
- },
349
- ],
367
+ const activity = {
368
+ $type: ACTIVITY_NSID,
369
+ title: "Mangrove Restoration in Mombasa",
370
+ shortDescription: "Restored 3 hectares of mangrove forest",
371
+ // Structured work scope via CEL expression:
372
+ workScope: {
373
+ $type: "org.hypercerts.workscope.cel",
374
+ expression:
375
+ "scope.hasAll(['mangrove_restoration']) && location.country == 'KE'",
376
+ usedTags: [
377
+ {
378
+ uri: "at://did:plc:alice/org.hypercerts.workscope.tag/3k2abc",
379
+ cid: "...",
380
+ },
381
+ ],
382
+ version: "v1",
383
+ createdAt: new Date().toISOString(),
384
+ },
385
+ startDate: "2024-01-01T00:00:00Z",
386
+ endDate: "2024-12-31T23:59:59Z",
350
387
  createdAt: new Date().toISOString(),
351
388
  };
352
389
  ```
353
390
 
354
- ### Creating a Project
355
-
356
- Projects are collections with a `type` field set to "project" and can
357
- include rich-text descriptions:
391
+ ### Creating Collections (Projects, Portfolios, etc.)
358
392
 
359
393
  ```typescript
360
- const projectRecord = {
361
- $type: "org.hypercerts.collection",
394
+ import { HYPERCERTS_COLLECTION_NSID } from "@hypercerts-org/lexicon";
395
+
396
+ const project = {
397
+ $type: HYPERCERTS_COLLECTION_NSID,
362
398
  type: "project",
363
399
  title: "Carbon Offset Initiative",
364
- shortDescription: "A project focused on carbon reduction and reforestation",
365
- description: {
366
- uri: "at://did:plc:alice/pub.leaflet.pages.linearDocument/abc123",
367
- cid: "...",
368
- },
400
+ shortDescription: "Activities focused on carbon reduction and reforestation",
369
401
  items: [
370
402
  {
371
- uri: "at://did:plc:alice/org.hypercerts.claim.activity/3k2abc",
372
- cid: "...",
403
+ itemIdentifier: {
404
+ uri: "at://did:plc:alice/org.hypercerts.claim.activity/3k2abc",
405
+ cid: "...",
406
+ },
373
407
  },
374
408
  {
375
- uri: "at://did:plc:bob/org.hypercerts.claim.activity/7x9def",
376
- cid: "...",
409
+ itemIdentifier: {
410
+ uri: "at://did:plc:bob/org.hypercerts.claim.activity/7x9def",
411
+ cid: "...",
412
+ },
413
+ },
414
+ // Collections can contain other collections (recursive nesting):
415
+ {
416
+ itemIdentifier: {
417
+ uri: "at://did:plc:carol/org.hypercerts.collection/4m5ghi",
418
+ cid: "...",
419
+ },
377
420
  },
378
421
  ],
379
422
  createdAt: new Date().toISOString(),
380
423
  };
381
424
  ```
382
425
 
383
- **Note**: The `type` field is optional and can be set to "project",
384
- "favorites", or any other collection type. The `description` field
385
- supports rich-text via Leaflet linear documents.
386
-
387
- ### Adding Visual Representation to Collections
388
-
389
- Collections can include `avatar` and `banner` fields for visual representation:
426
+ ### Creating Location Records
390
427
 
391
428
  ```typescript
392
- import { COLLECTION_NSID } from "@hypercerts-org/lexicon";
429
+ import { LOCATION_NSID } from "@hypercerts-org/lexicon";
393
430
 
394
- const collectionRecord = {
395
- $type: COLLECTION_NSID,
396
- title: "Climate Action Projects",
397
- avatar: {
398
- image: blobRef, // or { uri: "https://..." }
431
+ // Decimal coordinates
432
+ const location = {
433
+ $type: LOCATION_NSID,
434
+ lpVersion: "1.0",
435
+ srs: "http://www.opengis.net/def/crs/OGC/1.3/CRS84",
436
+ locationType: "coordinate-decimal",
437
+ location: {
438
+ $type: "app.certified.location#string",
439
+ string: "-3.4653, -62.2159",
399
440
  },
400
- banner: {
401
- image: largeBlobRef, // or { uri: "https://..." }
441
+ name: "Amazon Research Station",
442
+ createdAt: new Date().toISOString(),
443
+ };
444
+
445
+ // GeoJSON
446
+ const geoLocation = {
447
+ $type: LOCATION_NSID,
448
+ lpVersion: "1.0",
449
+ srs: "http://www.opengis.net/def/crs/OGC/1.3/CRS84",
450
+ locationType: "geojson-point",
451
+ location: {
452
+ $type: "app.certified.location#string",
453
+ string: '{"type":"Point","coordinates":[-62.2159,-3.4653]}',
402
454
  },
403
- items: [
404
- // ... collection items
405
- ],
455
+ name: "Research Station Alpha",
406
456
  createdAt: new Date().toISOString(),
407
457
  };
408
458
  ```
409
459
 
410
- **Note**: Both `avatar` (up to 5MB) and `banner` (up to 10MB) fields
411
- are optional and support either embedded image blobs or URI references to
412
- external images.
413
-
414
460
  ### Acknowledging Inclusion
415
461
 
416
- The `org.hypercerts.context.acknowledgement` record enables bidirectional
417
- linking between records that live in different PDS repositories. When
418
- one user includes another user's record (e.g. adding an activity to a
419
- collection), the owner of the included record can create an
420
- acknowledgement to confirm or reject the inclusion. This forms a
421
- two-way link that an AppView can verify.
422
-
423
- Each acknowledgement uses `com.atproto.repo.strongRef` fields to
424
- reference both the **subject** (the record being included) and the
425
- **context** (the record it's being included in).
426
-
427
- See [SCHEMAS.md](SCHEMAS.md) for the full property reference.
428
-
429
- #### Use Case: Activity Included in a Collection
430
-
431
- A project organizer (Alice) creates a collection and adds Bob's
432
- activity to it via a `strongRef` in the collection's `items[]` array.
433
- Bob then creates an acknowledgement in his own repo to confirm:
462
+ When one user includes another's record (e.g. adding an activity to a
463
+ collection), the owner can confirm or reject with an acknowledgement:
434
464
 
435
465
  ```typescript
436
- import { ACKNOWLEDGEMENT_NSID } from "@hypercerts-org/lexicon";
466
+ import { CONTEXT_ACKNOWLEDGEMENT_NSID } from "@hypercerts-org/lexicon";
437
467
 
438
- // Bob acknowledges that his activity is included in Alice's collection
439
468
  const ack = {
440
- $type: ACKNOWLEDGEMENT_NSID,
469
+ $type: CONTEXT_ACKNOWLEDGEMENT_NSID,
441
470
  subject: {
442
471
  uri: "at://did:plc:bob/org.hypercerts.claim.activity/3k2abc",
443
472
  cid: "bafy...",
444
473
  },
474
+ // context is a union — use $type to specify the variant
445
475
  context: {
476
+ $type: "com.atproto.repo.strongRef",
446
477
  uri: "at://did:plc:alice/org.hypercerts.collection/7x9def",
447
478
  cid: "bafy...",
448
479
  },
449
- acknowledged: true,
450
- createdAt: new Date().toISOString(),
451
- };
452
- ```
453
-
454
- #### Use Case: Contributor Included in an Activity
455
-
456
- Alice creates an activity that lists Bob as a contributor. Bob creates
457
- an acknowledgement in his own repo to confirm his participation:
458
-
459
- ```typescript
460
- const ack = {
461
- $type: ACKNOWLEDGEMENT_NSID,
462
- subject: {
463
- // Bob's contributor information record
464
- uri: "at://did:plc:bob/org.hypercerts.claim.contributorInformation/abc123",
465
- cid: "bafy...",
466
- },
467
- context: {
468
- // Alice's activity that lists Bob as contributor
469
- uri: "at://did:plc:alice/org.hypercerts.claim.activity/3k2abc",
470
- cid: "bafy...",
471
- },
472
- acknowledged: true,
473
- comment: "Confirming my contribution to this reforestation project",
474
- createdAt: new Date().toISOString(),
475
- };
476
- ```
477
-
478
- Setting `acknowledged: false` explicitly rejects inclusion, which an
479
- AppView can use to flag disputed associations.
480
-
481
- ### Adding Locations to Activities
482
-
483
- The `locations` field in activity records is an array of strong references
484
- (`com.atproto.repo.strongRef`) pointing to `app.certified.location` records.
485
- Each strong reference contains two required fields:
486
-
487
- - `uri`: The ATProto URI of the location record (e.g., `at://did:plc:alice/app.certified.location/abc123`)
488
- - `cid`: The content identifier (CID) of the location record, ensuring referential integrity
489
-
490
- **Validation and Expectations**:
491
-
492
- - All location records referenced in the `locations` array must conform to the
493
- `app.certified.location` lexicon schema
494
- - The `uri` field must be a valid ATProto URI pointing to an existing location record
495
- - The `cid` field must match the current CID of the referenced location record
496
- - The `locations` field is optional; activities can be created without location data
497
-
498
- ### Adding Location to Collections
499
-
500
- Collections can include an optional `location` field to specify where the collection's activities were performed:
501
-
502
- ```typescript
503
- const collectionRecord = {
504
- $type: "org.hypercerts.collection",
505
- title: "Climate Action Projects",
506
- shortDescription: "A collection of climate-related activities",
507
- location: {
508
- uri: "at://did:plc:alice/app.certified.location/xyz789",
509
- cid: "...",
510
- },
511
- items: [
512
- // ... collection items
513
- ],
480
+ acknowledged: true, // false to reject
514
481
  createdAt: new Date().toISOString(),
515
482
  };
516
483
  ```
517
484
 
518
- The `location` field is a strong reference to an `app.certified.location` record containing the same `uri` and `cid` fields as described above for activities.
519
-
520
485
  ### Creating Attachments
521
486
 
522
- Attachments provide commentary, context, evidence, or documentary material
523
- related to hypercert records. They can be linked to activities, evaluations,
524
- measurements, or even other attachments:
525
-
526
487
  ```typescript
527
- import { ATTACHMENT_NSID } from "@hypercerts-org/lexicon";
488
+ import { CONTEXT_ATTACHMENT_NSID } from "@hypercerts-org/lexicon";
528
489
 
529
- const attachmentRecord = {
530
- $type: ATTACHMENT_NSID,
490
+ const attachment = {
491
+ $type: CONTEXT_ATTACHMENT_NSID,
531
492
  title: "Field Survey Report",
493
+ contentType: "report",
532
494
  subjects: [
533
495
  {
534
496
  uri: "at://did:plc:alice/org.hypercerts.claim.activity/abc123",
535
497
  cid: "...",
536
498
  },
537
499
  ],
538
- contentType: "report",
500
+ // content items are a union — use $type to specify the variant
539
501
  content: [
540
- { uri: "https://example.com/reports/survey-2024.pdf" },
541
- { uri: "ipfs://Qm..." },
502
+ {
503
+ $type: "org.hypercerts.defs#uri",
504
+ uri: "https://example.com/reports/survey-2024.pdf",
505
+ },
506
+ { $type: "org.hypercerts.defs#uri", uri: "ipfs://Qm..." },
542
507
  ],
543
508
  shortDescription: "Quarterly field survey documenting project progress",
544
509
  createdAt: new Date().toISOString(),
545
510
  };
546
511
  ```
547
512
 
548
- **Key fields:**
513
+ ## Development
549
514
 
550
- - `title` (required): String title for the attachment
551
- - `shortDescription`/`description`: Support rich text via facet annotations
552
- - `subjects` (optional): Array of strong references to records this attachment relates to
553
- - `contentType` (optional): Type descriptor (e.g., "report", "audit", "evidence", "testimonial")
554
- - `content` (required): Array of URIs or blobs containing the attachment files
555
- - `location` (optional): Strong reference to an `app.certified.location` record
556
- - `createdAt` (required): Timestamp when the attachment was created
515
+ ### Commands
557
516
 
558
- **Adding Location to Attachments:**
517
+ ```bash
518
+ npm run gen-api # Regenerate TypeScript types from lexicons
519
+ npm run build # Build distributable bundles (ESM, CJS, types)
520
+ npm run check # Validate + typecheck + build (run before committing)
521
+ npm run lint # Check formatting (Prettier + ESLint)
522
+ npm run format # Auto-fix formatting
523
+ npm run gen-schemas-md # Regenerate SCHEMAS.md
524
+ npm run test # Run tests
525
+ ```
526
+
527
+ ### Linking ATProto Identity to EVM Wallets
528
+
529
+ The `app.certified.link.evm` record enables verifiable linking between
530
+ an ATProto DID and an EVM wallet address. The link is proven via a
531
+ cryptographic signature, allowing any verifier to confirm that the
532
+ wallet owner authorized the binding. Currently supports EOA wallets
533
+ via EIP-712 typed data signatures; the `proof` field is an open union
534
+ to allow future signature methods (e.g. ERC-1271, ERC-6492).
559
535
 
560
536
  ```typescript
561
- const attachmentWithLocation = {
562
- $type: ATTACHMENT_NSID,
563
- title: "Site Inspection Photos",
564
- content: [{ uri: "https://..." }],
565
- location: {
566
- uri: "at://did:plc:alice/app.certified.location/loc123",
567
- cid: "...",
537
+ import { LINK_EVM_NSID } from "@hypercerts-org/lexicon";
538
+
539
+ const evmLinkRecord = {
540
+ $type: LINK_EVM_NSID,
541
+ address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
542
+ proof: {
543
+ $type: "app.certified.link.evm#eip712Proof",
544
+ signature: "0xabc123...", // truncated for readability; real signatures are 130-132 hex chars
545
+ message: {
546
+ $type: "app.certified.link.evm#eip712Message",
547
+ did: "did:plc:alice",
548
+ evmAddress: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
549
+ chainId: "1",
550
+ timestamp: "1709500000",
551
+ nonce: "0",
552
+ },
568
553
  },
569
554
  createdAt: new Date().toISOString(),
570
555
  };
571
556
  ```
557
+
558
+ **Key fields:**
559
+
560
+ - `address` (required): 0x-prefixed EVM wallet address (EIP-55
561
+ checksummed, 42 chars)
562
+ - `proof` (required): Open union containing the cryptographic proof of
563
+ wallet ownership. Each variant bundles its signature with the
564
+ corresponding message format. Currently the only variant is
565
+ `#eip712Proof` for EOA wallets.
566
+ - `createdAt` (required): Timestamp when the record was created
567
+
568
+ ## License
569
+
570
+ MIT