From 842b06460e5a79acf9319e6de8be7e62d884ea00 Mon Sep 17 00:00:00 2001 From: ethicnology Date: Mon, 20 Jan 2025 14:31:31 -0500 Subject: [PATCH] feat: NIP21 --- README.md | 1 + lib/nostr.dart | 1 + lib/src/nips/nip_021.dart | 19 +++++++++++++++++++ test/nips/nip_021_test.dart | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+) create mode 100644 lib/src/nips/nip_021.dart create mode 100644 test/nips/nip_021_test.dart diff --git a/README.md b/README.md index 1115c88..fff9e14 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ flutter pub add nostr - [x] [NIP 15 End of Stored Events Notice](https://github.com/nostr-protocol/nips/blob/master/15.md) - [x] [NIP 19 bech32-encoded entities](https://github.com/nostr-protocol/nips/blob/master/19.md) - [x] [NIP 20 Command Results](https://github.com/nostr-protocol/nips/blob/master/20.md) +- [x] [NIP 21 nostr: URI scheme](https://github.com/nostr-protocol/nips/blob/master/21.md) - [x] [NIP 28 Public Chat](https://github.com/nostr-protocol/nips/blob/master/28.md) - [x] [NIP 50 Search Capability](https://github.com/nostr-protocol/nips/blob/master/50.md) - [x] [NIP 51 Lists](https://github.com/nostr-protocol/nips/blob/master/51.md) diff --git a/lib/nostr.dart b/lib/nostr.dart index 23c1587..c86e195 100644 --- a/lib/nostr.dart +++ b/lib/nostr.dart @@ -20,5 +20,6 @@ export 'src/nips/nip_010.dart'; export 'src/nips/nip_013.dart'; export 'src/nips/nip_019.dart'; export 'src/nips/nip_020.dart'; +export 'src/nips/nip_021.dart'; export 'src/nips/nip_028.dart'; export 'src/nips/nip_051.dart'; diff --git a/lib/src/nips/nip_021.dart b/lib/src/nips/nip_021.dart new file mode 100644 index 0000000..fe986bd --- /dev/null +++ b/lib/src/nips/nip_021.dart @@ -0,0 +1,19 @@ +/// A utility class to handle Nostr URIs according to NIP-21 specification. +/// Provides encode, decode functionalities for Nostr URIs. +class NIP21 { + static const String _prefix = 'nostr:'; + + /// Parses a `nostr:` URI and extracts the identifier. + /// + /// Throws an [Exception] if the prefix `nostr:` is missing + static String decode(String uri) { + if (!uri.startsWith(_prefix)) { + throw Exception('Invalid Nostr URI: must start with "nostr:"'); + } + + return uri.substring(_prefix.length); + } + + /// Generates a `nostr:` URI from a given content + static String encode(String content) => _prefix + content; +} diff --git a/test/nips/nip_021_test.dart b/test/nips/nip_021_test.dart new file mode 100644 index 0000000..8c90c72 --- /dev/null +++ b/test/nips/nip_021_test.dart @@ -0,0 +1,34 @@ +import 'package:nostr/nostr.dart'; +import 'package:test/test.dart'; + +void main() { + group('NIP21 URI Tests', () { + test('Parse valid npub URI', () { + expect( + NIP21.decode( + 'nostr:npub1sn0wdenkukak0d9dfczzeacvhkrgz92ak56egt7vdgzn8pv2wfqqhrjdv9'), + equals( + 'npub1sn0wdenkukak0d9dfczzeacvhkrgz92ak56egt7vdgzn8pv2wfqqhrjdv9')); + }); + + test('Parse valid nprofile URI', () { + expect( + NIP21.decode( + 'nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p'), + equals( + 'nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p')); + }); + + test('Generate npub URI', () { + expect( + NIP21.encode( + 'npub1sn0wdenkukak0d9dfczzeacvhkrgz92ak56egt7vdgzn8pv2wfqqhrjdv9'), + equals( + 'nostr:npub1sn0wdenkukak0d9dfczzeacvhkrgz92ak56egt7vdgzn8pv2wfqqhrjdv9')); + }); + + test('Invalid Nostr URI parsing', () { + expect(() => NIP21.decode('noprefix'), throwsA(isA())); + }); + }); +}