commit d07ad67778825811eed62da5f2750e92205c2840
parent af75eed83a2a1dd0eb33a0a27ded71c9f44dacbd
Author: kernelkind <kernelkind@gmail.com>
Date: Thu, 18 Jan 2024 14:59:27 -0500
nip19: add bech32 TLV url parsing
Create shortened URLs for bech32 with TLV data strings. Additionally,
upon clicking on an nevent URL the user is directed to the note.
Lightning-url: LNURL1DP68GURN8GHJ7EM9W3SKCCNE9E3K7MF0D3H82UNVWQHKWUN9V4HXGCTHDC6RZVGR8SW3G
Signed-off-by: kernelkind <kernelkind@gmail.com>
Reviewed-by: William Casarin <jb55@jb55.com>
Signed-off-by: William Casarin <jb55@jb55.com>
Diffstat:
9 files changed, 285 insertions(+), 88 deletions(-)
diff --git a/damus/ContentView.swift b/damus/ContentView.swift
@@ -492,21 +492,15 @@ struct ContentView: View {
open_profile(pubkey: pubkey)
case .note(let noteId):
- guard let target = damus_state.events.lookup(noteId) else {
- return
- }
-
- switch local.type {
- case .dm:
- selected_timeline = .dms
- damus_state.dms.set_active_dm(target.pubkey)
- navigationCoordinator.push(route: Route.DMChat(dms: damus_state.dms.active_model))
- case .like, .zap, .mention, .repost:
- open_event(ev: target)
- case .profile_zap:
- // Handled separately above.
- break
- }
+ openEvent(noteId: noteId, notificationType: local.type)
+ case .nevent(let nevent):
+ openEvent(noteId: nevent.noteid, notificationType: local.type)
+ case .nprofile(let nprofile):
+ open_profile(pubkey: nprofile.author)
+ case .nrelay(_):
+ break
+ case .naddr(let naddr):
+ break
}
@@ -725,6 +719,22 @@ struct ContentView: View {
}
}
+ private func openEvent(noteId: NoteId, notificationType: LocalNotificationType) {
+ guard let target = damus_state.events.lookup(noteId) else {
+ return
+ }
+
+ switch notificationType {
+ case .dm:
+ selected_timeline = .dms
+ damus_state.dms.set_active_dm(target.pubkey)
+ navigationCoordinator.push(route: Route.DMChat(dms: damus_state.dms.active_model))
+ case .like, .zap, .mention, .repost:
+ open_event(ev: target)
+ case .profile_zap:
+ break
+ }
+ }
}
struct ContentView_Previews: PreviewProvider {
@@ -1082,6 +1092,8 @@ func on_open_url(state: DamusState, url: URL, result: @escaping (OpenResult?) ->
case .param, .quote:
// doesn't really make sense here
break
+ case .naddr(let naddr):
+ break // TODO: fix
}
case .filter(let filt):
result(.filter(filt))
diff --git a/damus/Models/Contacts+.swift b/damus/Models/Contacts+.swift
@@ -113,7 +113,7 @@ func is_already_following(contacts: NostrEvent, follow: FollowRef) -> Bool {
case let (.pubkey(pk), .pubkey(follow_pk)):
return pk == follow_pk
case (.hashtag, .pubkey), (.pubkey, .hashtag),
- (.event, _), (.quote, _), (.param, _):
+ (.event, _), (.quote, _), (.param, _), (.naddr, _):
return false
}
}
diff --git a/damus/Models/Mentions.swift b/damus/Models/Mentions.swift
@@ -10,6 +10,8 @@ import Foundation
enum MentionType: AsciiCharacter, TagKey {
case p
case e
+ case a
+ case r
var keychar: AsciiCharacter {
self.rawValue
@@ -17,21 +19,26 @@ enum MentionType: AsciiCharacter, TagKey {
}
enum MentionRef: TagKeys, TagConvertible, Equatable, Hashable {
- case pubkey(Pubkey) // TODO: handle nprofile
+ case pubkey(Pubkey)
case note(NoteId)
+ case nevent(NEvent)
+ case nprofile(NProfile)
+ case nrelay(String)
+ case naddr(NAddr)
var key: MentionType {
switch self {
case .pubkey: return .p
case .note: return .e
+ case .nevent: return .e
+ case .nprofile: return .p
+ case .nrelay: return .r
+ case .naddr: return .a
}
}
var bech32: String {
- switch self {
- case .pubkey(let pubkey): return bech32_pubkey(pubkey)
- case .note(let noteId): return bech32_note_id(noteId)
- }
+ return Bech32Object.encode(toBech32Object())
}
static func from_bech32(str: String) -> MentionRef? {
@@ -46,6 +53,10 @@ enum MentionRef: TagKeys, TagConvertible, Equatable, Hashable {
switch self {
case .pubkey(let pubkey): return pubkey
case .note: return nil
+ case .nevent(let nevent): return nevent.author
+ case .nprofile(let nprofile): return nprofile.author
+ case .nrelay: return nil
+ case .naddr: return nil
}
}
@@ -53,6 +64,10 @@ enum MentionRef: TagKeys, TagConvertible, Equatable, Hashable {
switch self {
case .pubkey(let pubkey): return ["p", pubkey.hex()]
case .note(let noteId): return ["e", noteId.hex()]
+ case .nevent(let nevent): return ["e", nevent.noteid.hex()]
+ case .nprofile(let nprofile): return ["p", nprofile.author.hex()]
+ case .nrelay(let url): return ["r", url]
+ case .naddr(let naddr): return ["a", naddr.kind.description + ":" + naddr.author.hex() + ":" + naddr.identifier.string()]
}
}
@@ -64,14 +79,45 @@ enum MentionRef: TagKeys, TagConvertible, Equatable, Hashable {
guard let t0 = i.next(),
let chr = t0.single_char,
let mention_type = MentionType(rawValue: chr),
- let id = i.next()?.id()
+ let element = i.next()
else {
return nil
}
switch mention_type {
- case .p: return .pubkey(Pubkey(id))
- case .e: return .note(NoteId(id))
+ case .p:
+ guard let data = element.id() else { return nil }
+ return .pubkey(Pubkey(data))
+ case .e:
+ guard let data = element.id() else { return nil }
+ return .note(NoteId(data))
+ case .a:
+ let str = element.string()
+ let data = str.split(separator: ":")
+ if(data.count != 3) { return nil }
+
+ guard let pubkey = Pubkey(hex: String(data[1])) else { return nil }
+ guard let kind = UInt32(data[0]) else { return nil }
+
+ return .naddr(NAddr(identifier: String(data[2]), author: pubkey, relays: [], kind: kind))
+ case .r: return .nrelay(element.string())
+ }
+ }
+
+ func toBech32Object() -> Bech32Object {
+ switch self {
+ case .pubkey(let pk):
+ return .npub(pk)
+ case .note(let noteid):
+ return .note(noteid)
+ case .naddr(let naddr):
+ return .naddr(naddr)
+ case .nevent(let nevent):
+ return .nevent(nevent)
+ case .nprofile(let nprofile):
+ return .nprofile(nprofile)
+ case .nrelay(let url):
+ return .nrelay(url)
}
}
}
@@ -251,4 +297,3 @@ func post_to_event(post: NostrPost, keypair: FullKeypair) -> NostrEvent? {
.joined(separator: "")
return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: post.kind.rawValue, tags: post_tags.tags)
}
-
diff --git a/damus/Models/NoteContent.swift b/damus/Models/NoteContent.swift
@@ -182,26 +182,31 @@ func attributed_string_attach_icon(_ astr: inout AttributedString, img: UIImage)
astr.append(wrapped)
}
-func mention_str(_ m: Mention<MentionRef>, profiles: Profiles) -> CompatibleText {
- switch m.ref {
- case .pubkey(let pk):
- let npub = bech32_pubkey(pk)
- let profile_txn = profiles.lookup(id: pk)
- let profile = profile_txn?.unsafeUnownedValue
- let disp = Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 50)
- var attributedString = AttributedString(stringLiteral: "@\(disp)")
- attributedString.link = URL(string: "damus:nostr:\(npub)")
- attributedString.foregroundColor = DamusColors.purple
-
- return CompatibleText(attributed: attributedString)
- case .note(let note_id):
- let bevid = bech32_note_id(note_id)
- var attributedString = AttributedString(stringLiteral: "@\(abbrev_pubkey(bevid))")
- attributedString.link = URL(string: "damus:nostr:\(bevid)")
- attributedString.foregroundColor = DamusColors.purple
+func getDisplayName(pk: Pubkey, profiles: Profiles) -> String {
+ let profile_txn = profiles.lookup(id: pk)
+ let profile = profile_txn?.unsafeUnownedValue
+ return Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 50)
+}
- return CompatibleText(attributed: attributedString)
- }
+func mention_str(_ m: Mention<MentionRef>, profiles: Profiles) -> CompatibleText {
+ let bech32String = Bech32Object.encode(m.ref.toBech32Object())
+
+ let attributedStringLiteral: String = {
+ switch m.ref {
+ case .pubkey(let pk): return getDisplayName(pk: pk, profiles: profiles)
+ case .note: return "@\(abbrev_pubkey(bech32String))"
+ case .nevent: return "@\(abbrev_pubkey(bech32String))"
+ case .nprofile(let nprofile): return getDisplayName(pk: nprofile.author, profiles: profiles)
+ case .nrelay(let url): return url
+ case .naddr: return "@\(abbrev_pubkey(bech32String))"
+ }
+ }()
+
+ var attributedString = AttributedString(stringLiteral: attributedStringLiteral)
+ attributedString.link = URL(string: "damus:nostr:\(bech32String)")
+ attributedString.foregroundColor = DamusColors.purple
+
+ return CompatibleText(attributed: attributedString)
}
// trim suffix whitespace and newlines
diff --git a/damus/Nostr/ReferencedId.swift b/damus/Nostr/ReferencedId.swift
@@ -121,7 +121,8 @@ enum RefId: TagConvertible, TagKeys, Equatable, Hashable {
case quote(QuoteId)
case hashtag(TagElem)
case param(TagElem)
-
+ case naddr(NAddr)
+
var key: RefKey {
switch self {
case .event: return .e
@@ -129,11 +130,12 @@ enum RefId: TagConvertible, TagKeys, Equatable, Hashable {
case .quote: return .q
case .hashtag: return .t
case .param: return .d
+ case .naddr: return .a
}
}
enum RefKey: AsciiCharacter, TagKey, CustomStringConvertible {
- case e, p, t, d, q
+ case e, p, t, d, q, a
var keychar: AsciiCharacter {
self.rawValue
@@ -155,6 +157,8 @@ enum RefId: TagConvertible, TagKeys, Equatable, Hashable {
case .quote(let quote): return quote.hex()
case .hashtag(let string): return string.string()
case .param(let string): return string.string()
+ case .naddr(let naddr):
+ return naddr.kind.description + ":" + naddr.author.hex() + ":" + naddr.identifier
}
}
@@ -174,6 +178,7 @@ enum RefId: TagConvertible, TagKeys, Equatable, Hashable {
case .q: return t1.id().map({ .quote(QuoteId($0)) })
case .t: return .hashtag(t1)
case .d: return .param(t1)
+ case .a: return .naddr(NAddr(identifier: "", author: Pubkey(Data()), relays: [], kind: 0))
}
}
}
diff --git a/damus/Types/Block.swift b/damus/Types/Block.swift
@@ -150,47 +150,18 @@ fileprivate extension Block {
self = .invoice(Invoice(description: description, amount: amount, string: invstr, expiry: b11.expiry, payment_hash: payment_hash, created_at: created_at))
}
}
+
fileprivate extension Block {
/// Failable initializer for the C-backed type `mention_bech32_block_t`. This initializer will inspect the
/// bech32 type code and build the appropriate enum type.
init?(bech32 b: mention_bech32_block_t) {
- switch b.bech32.type {
- case NOSTR_BECH32_NOTE:
- let note = b.bech32.data.note;
- let note_id = NoteId(Data(bytes: note.event_id, count: 32))
- self = .mention(.any(.note(note_id)))
- case NOSTR_BECH32_NEVENT:
- let nevent = b.bech32.data.nevent;
- let note_id = NoteId(Data(bytes: nevent.event_id, count: 32))
- self = .mention(.any(.note(note_id)))
- case NOSTR_BECH32_NPUB:
- let npub = b.bech32.data.npub
- let pubkey = Pubkey(Data(bytes: npub.pubkey, count: 32))
- self = .mention(.any(.pubkey(pubkey)))
- case NOSTR_BECH32_NSEC:
- let nsec = b.bech32.data.nsec
- let privkey = Privkey(Data(bytes: nsec.nsec, count: 32))
- guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil }
- self = .mention(.any(.pubkey(pubkey)))
- case NOSTR_BECH32_NPROFILE:
- let nprofile = b.bech32.data.nprofile
- let pubkey = Pubkey(Data(bytes: nprofile.pubkey, count: 32))
- self = .mention(.any(.pubkey(pubkey)))
- case NOSTR_BECH32_NRELAY:
- let nrelay = b.bech32.data.nrelay
- guard let relay_str = String(nrelay.relay) else {
- return nil
- }
- self = .relay(relay_str)
- case NOSTR_BECH32_NADDR:
- // TODO: wtf do I do with this
- guard let naddr = String(b.str) else {
- return nil
- }
- self = .text("nostr:" + naddr)
- default:
+ guard let decoded = decodeCBech32(b.bech32) else {
+ return nil
+ }
+ guard let ref = decoded.toMentionRef() else {
return nil
}
+ self = .mention(.any(ref))
}
}
extension Block {
@@ -201,10 +172,7 @@ extension Block {
return "#[\(idx)]"
}
- switch m.ref {
- case .pubkey(let pk): return "nostr:\(pk.npub)"
- case .note(let note_id): return "nostr:\(note_id.bech32)"
- }
+ return "nostr:" + Bech32Object.encode(m.ref.toBech32Object())
case .relay(let relay):
return relay
case .text(let txt):
diff --git a/damus/Util/Bech32Object.swift b/damus/Util/Bech32Object.swift
@@ -16,7 +16,7 @@ fileprivate extension String {
}
}
-struct NEvent : Equatable {
+struct NEvent : Equatable, Hashable {
let noteid: NoteId
let relays: [String]
let author: Pubkey?
@@ -49,12 +49,12 @@ struct NEvent : Equatable {
}
}
-struct NProfile : Equatable {
+struct NProfile : Equatable, Hashable {
let author: Pubkey
let relays: [String]
}
-struct NAddr : Equatable {
+struct NAddr : Equatable, Hashable {
let identifier: String
let author: Pubkey
let relays: [String]
@@ -107,6 +107,29 @@ enum Bech32Object : Equatable {
return bech32_encode(hrp: "nscript", data)
}
}
+
+ func toMentionRef() -> MentionRef? {
+ switch self {
+ case .nsec(let privkey):
+ guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil }
+ return .pubkey(pubkey)
+ case .npub(let pubkey):
+ return .pubkey(pubkey)
+ case .note(let noteid):
+ return .note(noteid)
+ case .nscript(_):
+ return nil
+ case .nevent(let nevent):
+ return .nevent(nevent)
+ case .nprofile(let nprofile):
+ return .nprofile(nprofile)
+ case .nrelay(let relayURL):
+ return .nrelay(relayURL)
+ case .naddr(let naddr):
+ return .naddr(naddr)
+ }
+ }
+
}
func decodeCBech32(_ b: nostr_bech32_t) -> Bech32Object? {
diff --git a/damusTests/NoteContentViewTests.swift b/damusTests/NoteContentViewTests.swift
@@ -6,6 +6,7 @@
//
import XCTest
+import SwiftUI
@testable import damus
class NoteContentViewTests: XCTestCase {
@@ -35,5 +36,101 @@ class NoteContentViewTests: XCTestCase {
XCTAssertTrue((parsed.blocks[0].asURL != nil), "NoteContentView does not correctly parse an image block when url in JSON content contains optional escaped slashes.")
}
+
+ func testMentionStr_Pubkey_ContainsAbbreviated() throws {
+ let compatibleText = createCompatibleText(test_pubkey.npub)
+
+ assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: "17ldvg64:nq5mhr77")
+ }
+
+ func testMentionStr_Pubkey_ContainsFullBech32() {
+ let compatableText = createCompatibleText(test_pubkey.npub)
+
+ assertCompatibleTextHasExpectedString(compatibleText: compatableText, expected: test_pubkey.npub)
+ }
+
+ func testMentionStr_Nprofile_ContainsAbbreviated() throws {
+ let compatibleText = createCompatibleText("nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p")
+
+ assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: "180cvv07:wsyjh6w6")
+ }
+
+ func testMentionStr_Nprofile_ContainsFullBech32() throws {
+ let bech = "nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p"
+ let compatibleText = createCompatibleText(bech)
+
+ assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: bech)
+ }
+
+ func testMentionStr_Note_ContainsAbbreviated() {
+ let compatibleText = createCompatibleText(test_note.id.bech32)
+
+ assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: "note1qqq:qqn2l0z3")
+ }
+
+ func testMentionStr_Note_ContainsFullBech32() {
+ let compatableText = createCompatibleText(test_note.id.bech32)
+
+ assertCompatibleTextHasExpectedString(compatibleText: compatableText, expected: test_note.id.bech32)
+ }
+
+ func testMentionStr_Nevent_ContainsAbbreviated() {
+ let bech = "nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ez6un9d3shjtn4de6x2argwghx6egpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5nxnepm"
+ let compatibleText = createCompatibleText(bech)
+
+ assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: "nevent1q:t5nxnepm")
+ }
+
+ func testMentionStr_Nevent_ContainsFullBech32() throws {
+ let bech = "nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ez6un9d3shjtn4de6x2argwghx6egpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5nxnepm"
+ let compatibleText = createCompatibleText(bech)
+
+ assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: bech)
+ }
+
+ func testMentionStr_Nrelay_ContainsAbbreviated() {
+ let bech = "nrelay1qqt8wumn8ghj7un9d3shjtnwdaehgu3wvfskueq4r295t"
+ let compatibleText = createCompatibleText(bech)
+
+ assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: "wss://relay.nostr.band")
+ }
+
+ func testMentionStr_Nrelay_ContainsFullBech32() {
+ let bech = "nrelay1qqt8wumn8ghj7un9d3shjtnwdaehgu3wvfskueq4r295t"
+ let compatibleText = createCompatibleText(bech)
+
+ assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: bech)
+ }
+
+ func testMentionStr_Naddr_ContainsAbbreviated() {
+ let bech = "naddr1qqxnzdesxqmnxvpexqunzvpcqyt8wumn8ghj7un9d3shjtnwdaehgu3wvfskueqzypve7elhmamff3sr5mgxxms4a0rppkmhmn7504h96pfcdkpplvl2jqcyqqq823cnmhuld"
+ let compatibleText = createCompatibleText(bech)
+
+ assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: "naddr1qq:3cnmhuld")
+ }
+
+ func testMentionStr_Naddr_ContainsFullBech32() {
+ let bech = "naddr1qqxnzdesxqmnxvpexqunzvpcqyt8wumn8ghj7un9d3shjtnwdaehgu3wvfskueqzypve7elhmamff3sr5mgxxms4a0rppkmhmn7504h96pfcdkpplvl2jqcyqqq823cnmhuld"
+ let compatibleText = createCompatibleText(bech)
+
+ assertCompatibleTextHasExpectedString(compatibleText: compatibleText, expected: bech)
+ }
+
+}
+private func assertCompatibleTextHasExpectedString(compatibleText: CompatibleText, expected: String) {
+ guard let hasExpected = compatibleText.items.first?.attributed_string()?.description.contains(expected) else {
+ XCTFail()
+ return
+ }
+
+ XCTAssertTrue(hasExpected)
+}
+
+private func createCompatibleText(_ bechString: String) -> CompatibleText {
+ guard let mentionRef = Bech32Object.parse(bechString)?.toMentionRef() else {
+ XCTFail("Failed to create MentionRef from Bech32 string")
+ return CompatibleText()
+ }
+ return mention_str(.any(mentionRef), profiles: test_damus_state.profiles)
}
diff --git a/damusTests/damusTests.swift b/damusTests/damusTests.swift
@@ -228,5 +228,47 @@ class damusTests: XCTestCase {
XCTAssertEqual(txt, "there is no mention here")
}
+
+ func testTagGeneration_Nevent_ContainsETag() {
+ let ev = createEventFromContentString("nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ez6un9d3shjtn4de6x2argwghx6egpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5nxnepm")
+
+ XCTAssertEqual(ev.tags.count, 1)
+ XCTAssertEqual(ev.tags[0][0].string(), "e")
+ XCTAssertEqual(ev.tags[0][1].string(), "b9f5441e45ca39179320e0031cfb18e34078673dcc3d3e3a3b3a981760aa5696")
+ }
+
+ func testTagGeneration_Nprofile_ContainsPTag() {
+ let ev = createEventFromContentString("nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p")
+
+ XCTAssertEqual(ev.tags.count, 1)
+ XCTAssertEqual(ev.tags[0][0].string(), "p")
+ XCTAssertEqual(ev.tags[0][1].string(), "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d")
+ }
+
+ func testTagGeneration_Nrelay_ContainsRTag() {
+ let ev = createEventFromContentString("nrelay1qqt8wumn8ghj7un9d3shjtnwdaehgu3wvfskueq4r295t")
+
+ XCTAssertEqual(ev.tags.count, 1)
+ XCTAssertEqual(ev.tags[0][0].string(), "r")
+ XCTAssertEqual(ev.tags[0][1].string(), "wss://relay.nostr.band")
+ }
+
+ func testTagGeneration_Naddr_ContainsATag(){
+ let ev = createEventFromContentString("naddr1qqxnzdesxqmnxvpexqunzvpcqyt8wumn8ghj7un9d3shjtnwdaehgu3wvfskueqzypve7elhmamff3sr5mgxxms4a0rppkmhmn7504h96pfcdkpplvl2jqcyqqq823cnmhuld")
+
+ XCTAssertEqual(ev.tags.count, 1)
+ XCTAssertEqual(ev.tags[0][0].string(), "a")
+ XCTAssertEqual(ev.tags[0][1].string(), "30023:599f67f7df7694c603a6d0636e15ebc610db77dcfd47d6e5d05386d821fb3ea9:1700730909108")
+ }
}
+
+private func createEventFromContentString(_ content: String) -> NostrEvent {
+ let post = NostrPost(content: content, references: [])
+ guard let ev = post_to_event(post: post, keypair: test_keypair_full) else {
+ XCTFail("Could not create event")
+ return test_note
+ }
+
+ return ev
+}