diff --git a/.changeset/funding-receipt-field-types.md b/.changeset/funding-receipt-field-types.md new file mode 100644 index 0000000..bec9b08 --- /dev/null +++ b/.changeset/funding-receipt-field-types.md @@ -0,0 +1,5 @@ +--- +"@hypercerts-org/lexicon": minor +--- + +Normalize funding receipt fields: `from`/`to` as union of DID ref + strongRef, `from` optional, `for` changed to strongRef diff --git a/SCHEMAS.md b/SCHEMAS.md index b433ccf..ceddfcb 100644 --- a/SCHEMAS.md +++ b/SCHEMAS.md @@ -250,19 +250,19 @@ Hypercerts-specific lexicons for tracking impact work and claims. #### Properties -| Property | Type | Required | Description | Comments | -| ---------------- | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `from` | `ref` | ❌ | DID of the sender who transferred the funds. This field is optional, and can be left undefined to represent anonymity. | | -| `to` | `string` | ✅ | The recipient of the funds. Can be identified by DID or a clear-text name. | maxLength: 2048 | -| `amount` | `string` | ✅ | Amount of funding received as a numeric string (e.g. '1000.50'). | maxLength: 50 | -| `currency` | `string` | ✅ | Currency of the payment (e.g. EUR, USD, ETH). | maxLength: 10 | -| `paymentRail` | `string` | ❌ | How the funds were transferred (e.g. bank_transfer, credit_card, onchain, cash, check, payment_processor). | maxLength: 50 | -| `paymentNetwork` | `string` | ❌ | Optional network within the payment rail (e.g. arbitrum, ethereum, sepa, visa, paypal). | maxLength: 50 | -| `transactionId` | `string` | ❌ | Identifier of the underlying payment transaction (e.g. bank reference, onchain transaction hash, or processor-specific ID). Use paymentNetwork to specify the network where applicable. | maxLength: 256 | -| `for` | `string` | ❌ | Optional reference to the activity, project, or organization this funding relates to. | | -| `notes` | `string` | ❌ | Optional notes or additional context for this funding receipt. | maxLength: 500 | -| `occurredAt` | `string` | ❌ | Timestamp when the payment occurred. | | -| `createdAt` | `string` | ✅ | Client-declared timestamp when this receipt record was created. | | +| Property | Type | Required | Description | Comments | +| ---------------- | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `from` | `union` | ❌ | The sender of the funds (either an account DID or a strong reference to a record). Optional — omit to represent anonymity. | | +| `to` | `union` | ✅ | The recipient of the funds (either an account DID or a strong reference to a record). | | +| `amount` | `string` | ✅ | Amount of funding received as a numeric string (e.g. '1000.50'). | maxLength: 50 | +| `currency` | `string` | ✅ | Currency of the payment (e.g. EUR, USD, ETH). | maxLength: 10 | +| `paymentRail` | `string` | ❌ | How the funds were transferred (e.g. bank_transfer, credit_card, onchain, cash, check, payment_processor). | maxLength: 50 | +| `paymentNetwork` | `string` | ❌ | Optional network within the payment rail (e.g. arbitrum, ethereum, sepa, visa, paypal). | maxLength: 50 | +| `transactionId` | `string` | ❌ | Identifier of the underlying payment transaction (e.g. bank reference, onchain transaction hash, or processor-specific ID). Use paymentNetwork to specify the network where applicable. | maxLength: 256 | +| `for` | `ref` | ❌ | Optional strong reference to the activity, project, or organization this funding relates to. | | +| `notes` | `string` | ❌ | Optional notes or additional context for this funding receipt. | maxLength: 500 | +| `occurredAt` | `string` | ❌ | Timestamp when the payment occurred. | | +| `createdAt` | `string` | ✅ | Client-declared timestamp when this receipt record was created. | | --- diff --git a/lexicons/org/hypercerts/funding/receipt.json b/lexicons/org/hypercerts/funding/receipt.json index 6f48445..523ef03 100644 --- a/lexicons/org/hypercerts/funding/receipt.json +++ b/lexicons/org/hypercerts/funding/receipt.json @@ -11,14 +11,14 @@ "required": ["to", "amount", "currency", "createdAt"], "properties": { "from": { - "type": "ref", - "ref": "app.certified.defs#did", - "description": "DID of the sender who transferred the funds. This field is optional, and can be left undefined to represent anonymity." + "type": "union", + "description": "The sender of the funds (either an account DID or a strong reference to a record). Optional — omit to represent anonymity.", + "refs": ["app.certified.defs#did", "com.atproto.repo.strongRef"] }, "to": { - "type": "string", - "description": "The recipient of the funds. Can be identified by DID or a clear-text name.", - "maxLength": 2048 + "type": "union", + "description": "The recipient of the funds (either an account DID or a strong reference to a record).", + "refs": ["app.certified.defs#did", "com.atproto.repo.strongRef"] }, "amount": { "type": "string", @@ -46,9 +46,9 @@ "maxLength": 256 }, "for": { - "type": "string", - "format": "at-uri", - "description": "Optional reference to the activity, project, or organization this funding relates to." + "type": "ref", + "ref": "com.atproto.repo.strongRef", + "description": "Optional strong reference to the activity, project, or organization this funding relates to." }, "notes": { "type": "string", diff --git a/tests/validate-funding-receipt.test.ts b/tests/validate-funding-receipt.test.ts index 3159f9e..602e8ed 100644 --- a/tests/validate-funding-receipt.test.ts +++ b/tests/validate-funding-receipt.test.ts @@ -3,7 +3,7 @@ import { validate, ids } from "../generated/lexicons.js"; import * as FundingReceipt from "../generated/types/org/hypercerts/funding/receipt.js"; const validBase = { - to: "Alice", + to: { $type: "app.certified.defs#did", did: "did:plc:recipient123" }, amount: "1000.00", currency: "USD", createdAt: "2024-01-01T00:00:00Z", @@ -17,7 +17,7 @@ describe("org.hypercerts.funding.receipt", () => { }); expect(result.success).toBe(true); if (result.success) { - expect(result.value.to).toBe("Alice"); + expect(result.value.to).toMatchObject({ did: "did:plc:recipient123" }); expect(result.value.amount).toBe("1000.00"); expect(result.value.currency).toBe("USD"); } @@ -34,11 +34,11 @@ describe("org.hypercerts.funding.receipt", () => { } }); - it("should accept a valid record with the optional 'from' field present", () => { + it("should accept a valid record with 'from' as a DID", () => { const result = FundingReceipt.validateMain({ $type: ids.OrgHypercertsFundingReceipt, ...validBase, - from: { did: "did:plc:abc123" }, + from: { $type: "app.certified.defs#did", did: "did:plc:abc123" }, }); expect(result.success).toBe(true); if (result.success) { @@ -46,15 +46,46 @@ describe("org.hypercerts.funding.receipt", () => { } }); + it("should accept 'to' as a strongRef", () => { + const result = FundingReceipt.validateMain({ + $type: ids.OrgHypercertsFundingReceipt, + to: { + $type: "com.atproto.repo.strongRef", + uri: "at://did:plc:recipient123/org.hypercerts.contributor.information/tid456", + cid: "bafyreie5737gdxlw5i64vngml6xvqeatqy3a4erphoqtso54z2eooh4zae", + }, + amount: "1000.00", + currency: "USD", + createdAt: "2024-01-01T00:00:00Z", + }); + expect(result.success).toBe(true); + }); + + it("should accept 'from' as a strongRef", () => { + const result = FundingReceipt.validateMain({ + $type: ids.OrgHypercertsFundingReceipt, + ...validBase, + from: { + $type: "com.atproto.repo.strongRef", + uri: "at://did:plc:abc123/org.hypercerts.contributor.information/tid789", + cid: "bafyreie5737gdxlw5i64vngml6xvqeatqy3a4erphoqtso54z2eooh4zae", + }, + }); + expect(result.success).toBe(true); + }); + it("should accept a valid record with all optional fields present", () => { const result = FundingReceipt.validateMain({ $type: ids.OrgHypercertsFundingReceipt, ...validBase, - from: { did: "did:plc:abc123" }, + from: { $type: "app.certified.defs#did", did: "did:plc:abc123" }, paymentRail: "onchain", paymentNetwork: "ethereum", transactionId: "0xabc123", - for: "at://did:plc:abc123/org.hypercerts.claim.activity/tid123", + for: { + uri: "at://did:plc:abc123/org.hypercerts.claim.activity/tid123", + cid: "bafyreie5737gdxlw5i64vngml6xvqeatqy3a4erphoqtso54z2eooh4zae", + }, notes: "Quarterly donation", occurredAt: "2024-01-01T00:00:00Z", }); @@ -73,7 +104,11 @@ describe("org.hypercerts.funding.receipt", () => { it("should reject a record missing the required 'amount' field", () => { const result = validate( - { to: "Alice", currency: "EUR", createdAt: "2024-01-01T00:00:00Z" }, + { + to: { $type: "app.certified.defs#did", did: "did:plc:recipient123" }, + currency: "EUR", + createdAt: "2024-01-01T00:00:00Z", + }, ids.OrgHypercertsFundingReceipt, "main", false, @@ -83,7 +118,11 @@ describe("org.hypercerts.funding.receipt", () => { it("should reject a record missing the required 'currency' field", () => { const result = validate( - { to: "Alice", amount: "500", createdAt: "2024-01-01T00:00:00Z" }, + { + to: { $type: "app.certified.defs#did", did: "did:plc:recipient123" }, + amount: "500", + createdAt: "2024-01-01T00:00:00Z", + }, ids.OrgHypercertsFundingReceipt, "main", false, @@ -93,7 +132,11 @@ describe("org.hypercerts.funding.receipt", () => { it("should reject a record missing the required 'createdAt' field", () => { const result = validate( - { to: "Alice", amount: "500", currency: "USD" }, + { + to: { $type: "app.certified.defs#did", did: "did:plc:recipient123" }, + amount: "500", + currency: "USD", + }, ids.OrgHypercertsFundingReceipt, "main", false,