diff --git a/ERD-with-fields.png b/ERD-with-fields.png
index 31388c3..46bb842 100644
Binary files a/ERD-with-fields.png and b/ERD-with-fields.png differ
diff --git a/ERD-with-fields.svg b/ERD-with-fields.svg
index 8d34c36..385df85 100644
--- a/ERD-with-fields.svg
+++ b/ERD-with-fields.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/ERD.png b/ERD.png
index 852a4ce..3f0b7dc 100644
Binary files a/ERD.png and b/ERD.png differ
diff --git a/ERD.puml b/ERD.puml
index 3343995..20c8fce 100644
--- a/ERD.puml
+++ b/ERD.puml
@@ -137,6 +137,36 @@ dataclass location {
!endif
}
+' Certified badges (data class + award + response)
+dataclass badgeDefinition {
+ !if (SHOW_FIELDS == "true")
+ badgeType
+ title
+ icon
+ description?
+ allowedIssuers[]?
+ createdAt
+ !endif
+}
+
+dataclass badgeAward {
+ !if (SHOW_FIELDS == "true")
+ badge
+ subject
+ note?
+ createdAt
+ !endif
+}
+
+dataclass badgeResponse {
+ !if (SHOW_FIELDS == "true")
+ badgeAward
+ response
+ weight?
+ createdAt
+ !endif
+}
+
' org.hypercerts.claim.rights
dataclass rights {
!if (SHOW_FIELDS == "true")
@@ -294,4 +324,11 @@ fundingReceipt::to --> contributor
fundingReceipt::for --> activity : funds
+badgeAward::badge --> badgeDefinition
+badgeResponse::badgeAward --> badgeAward
+badgeAward::subject --> contributor
+badgeAward::subject --> activity
+' This screws up the layout
+'badgeAward::subject --[norank]-> project
+
@enduml
diff --git a/ERD.svg b/ERD.svg
index 03ec4cb..e86ba08 100644
--- a/ERD.svg
+++ b/ERD.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 404a704..1aaf3b2 100644
--- a/README.md
+++ b/README.md
@@ -110,6 +110,53 @@ Certified lexicons are common/shared lexicons that can be used across multiple p
| `description` | `string` | ❌ | Optional description for this location | |
| `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created | |
+### Badges Lexicon
+
+**Lexicon IDs:** `app.certified.badge.definition`, `app.certified.badge.award`, `app.certified.badge.response`
+
+**Description:** Defines badge metadata, award records, and recipient responses for certified badges that can be used across protocols.
+
+#### Badge Definition
+
+**Lexicon ID:** `app.certified.badge.definition`
+
+**Key:** `tid`
+
+| Property | Type | Required | Description |
+| ---------------- | -------- | -------- | ---------------------------------------------------------------------- |
+| `badgeType` | `string` | ✅ | Category of the badge (e.g., endorsement, participation, affiliation). |
+| `title` | `string` | ✅ | Human-readable title of the badge. |
+| `icon` | `blob` | ✅ | Icon representing the badge (accepted `image/*` types, maxSize 1MB). |
+| `description` | `string` | ❌ | Optional short statement describing the badge. |
+| `allowedIssuers` | `array` | ❌ | Optional allowlist of DIDs allowed to issue this badge. |
+| `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created. |
+
+#### Badge Award
+
+**Lexicon ID:** `app.certified.badge.award`
+
+**Key:** `tid`
+
+| Property | Type | Required | Description |
+| ----------- | -------- | -------- | ------------------------------------------------------------------------------------ |
+| `badge` | `ref` | ✅ | Reference to the badge definition for this award (`app.certified.badge.definition`). |
+| `subject` | `union` | ✅ | Entity the badge award is for (either a DID or a specific AT Protocol record). |
+| `note` | `string` | ❌ | Optional explanation for the award. |
+| `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created. |
+
+#### Badge Response
+
+**Lexicon ID:** `app.certified.badge.response`
+
+**Key:** `tid`
+
+| Property | Type | Required | Description |
+| ------------ | -------- | -------- | ---------------------------------------------------------------------- |
+| `badgeAward` | `ref` | ✅ | Reference to the badge award (`app.certified.badge.award`). |
+| `response` | `string` | ✅ | Enum: `accepted` or `rejected`. |
+| `weight` | `string` | ❌ | Optional relative weight assigned by the recipient (stored as string). |
+| `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created. |
+
---
## Hypercerts Lexicons
diff --git a/lexicons/app/certified/badge/award.json b/lexicons/app/certified/badge/award.json
new file mode 100644
index 0000000..f02fce1
--- /dev/null
+++ b/lexicons/app/certified/badge/award.json
@@ -0,0 +1,36 @@
+{
+ "lexicon": 1,
+ "id": "app.certified.badge.award",
+ "defs": {
+ "main": {
+ "type": "record",
+ "description": "Records a badge award to a user, project, or activity claim.",
+ "key": "tid",
+ "record": {
+ "type": "object",
+ "required": ["badge", "subject", "createdAt"],
+ "properties": {
+ "badge": {
+ "type": "ref",
+ "ref": "app.certified.badge.definition",
+ "description": "Reference to the badge definition for this award."
+ },
+ "subject": {
+ "type": "union",
+ "description": "Entity the badge award is for (either an account DID or any specific AT Protocol record), e.g. a user, a project, or a specific activity claim.",
+ "refs": ["app.certified.defs#did", "com.atproto.repo.strongRef"]
+ },
+ "note": {
+ "type": "string",
+ "description": "Optional statement explaining the reason for this badge award."
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "datetime",
+ "description": "Client-declared timestamp when this record was originally created"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/lexicons/app/certified/badge/definition.json b/lexicons/app/certified/badge/definition.json
new file mode 100644
index 0000000..7729158
--- /dev/null
+++ b/lexicons/app/certified/badge/definition.json
@@ -0,0 +1,53 @@
+{
+ "lexicon": 1,
+ "id": "app.certified.badge.definition",
+ "defs": {
+ "main": {
+ "type": "record",
+ "description": "Defines a badge that can be awarded via badge award records to users, projects, or activity claims.",
+ "key": "tid",
+ "record": {
+ "type": "object",
+ "required": ["title", "badgeType", "icon", "createdAt"],
+ "properties": {
+ "badgeType": {
+ "type": "string",
+ "description": "Category of the badge (e.g. endorsement, participation, affiliation)."
+ },
+ "title": {
+ "type": "string",
+ "description": "Human-readable title of the badge."
+ },
+ "icon": {
+ "type": "blob",
+ "description": "Icon representing the badge, stored as a blob for compact visual display.",
+ "accept": [
+ "image/png",
+ "image/jpeg",
+ "image/webp",
+ "image/svg+xml"
+ ],
+ "maxSize": 1048576
+ },
+ "description": {
+ "type": "string",
+ "description": "Optional short statement describing what the badge represents."
+ },
+ "allowedIssuers": {
+ "type": "array",
+ "description": "Optional allowlist of DIDs allowed to issue this badge. If omitted, anyone may issue it.",
+ "items": {
+ "type": "ref",
+ "ref": "app.certified.defs#did"
+ }
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "datetime",
+ "description": "Client-declared timestamp when this record was originally created"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/lexicons/app/certified/badge/response.json b/lexicons/app/certified/badge/response.json
new file mode 100644
index 0000000..eba4e58
--- /dev/null
+++ b/lexicons/app/certified/badge/response.json
@@ -0,0 +1,36 @@
+{
+ "lexicon": 1,
+ "id": "app.certified.badge.response",
+ "defs": {
+ "main": {
+ "type": "record",
+ "description": "Recipient response to a badge award.",
+ "key": "tid",
+ "record": {
+ "type": "object",
+ "required": ["badgeAward", "response", "createdAt"],
+ "properties": {
+ "badgeAward": {
+ "type": "ref",
+ "ref": "app.certified.badge.award",
+ "description": "Reference to the badge award."
+ },
+ "response": {
+ "type": "string",
+ "enum": ["accepted", "rejected"],
+ "description": "The recipient’s response for the badge (accepted or rejected)."
+ },
+ "weight": {
+ "type": "string",
+ "description": "Optional relative weight for accepted badges, assigned by the recipient."
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "datetime",
+ "description": "Client-declared timestamp when this record was originally created"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/lexicons/app/certified/defs.json b/lexicons/app/certified/defs.json
new file mode 100644
index 0000000..81d3959
--- /dev/null
+++ b/lexicons/app/certified/defs.json
@@ -0,0 +1,12 @@
+{
+ "lexicon": 1,
+ "id": "app.certified.defs",
+ "description": "Common type definitions used across certified protocols.",
+ "defs": {
+ "did": {
+ "type": "string",
+ "format": "did",
+ "description": "A Decentralized Identifier (DID) string."
+ }
+ }
+}
diff --git a/lexicons/org/hypercerts/claim/evaluation.json b/lexicons/org/hypercerts/claim/evaluation.json
index 5f0c9b4..a6efbc4 100644
--- a/lexicons/org/hypercerts/claim/evaluation.json
+++ b/lexicons/org/hypercerts/claim/evaluation.json
@@ -38,8 +38,8 @@
"type": "array",
"description": "DIDs of the evaluators",
"items": {
- "type": "string",
- "format": "did"
+ "type": "ref",
+ "ref": "app.certified.defs#did"
},
"maxLength": 1000
},
diff --git a/lexicons/org/hypercerts/claim/measurement.json b/lexicons/org/hypercerts/claim/measurement.json
index a28f581..029eecf 100644
--- a/lexicons/org/hypercerts/claim/measurement.json
+++ b/lexicons/org/hypercerts/claim/measurement.json
@@ -19,8 +19,8 @@
"type": "array",
"description": "DIDs of the entity (or entities) that measured this data",
"items": {
- "type": "string",
- "format": "did"
+ "type": "ref",
+ "ref": "app.certified.defs#did"
},
"maxLength": 100
},
diff --git a/lexicons/org/hypercerts/funding/receipt.json b/lexicons/org/hypercerts/funding/receipt.json
index 4943fd3..3efe87c 100644
--- a/lexicons/org/hypercerts/funding/receipt.json
+++ b/lexicons/org/hypercerts/funding/receipt.json
@@ -11,8 +11,8 @@
"required": ["from", "to", "amount", "currency", "createdAt"],
"properties": {
"from": {
- "type": "string",
- "format": "did",
+ "type": "ref",
+ "ref": "app.certified.defs#did",
"description": "DID of the sender who transferred the funds. Leave empty if sender wants to stay anonymous."
},
"to": {