Skip to content
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
36ec5fe
refactor: nip01 immutable
1-leo Dec 5, 2025
8811870
refactor: event service
1-leo Dec 5, 2025
b7a7d40
refactor: signer, amber
1-leo Dec 5, 2025
765ec56
refactor: entities
1-leo Dec 5, 2025
225f59f
refactor usecases
1-leo Dec 5, 2025
c9dfc97
refactor: other packages
1-leo Dec 5, 2025
bf19d54
refactor: valid sig
1-leo Dec 5, 2025
7c50a16
refactor: ndk tests
1-leo Dec 5, 2025
240faf8
refactor: immutable event sources
1-leo Dec 6, 2025
37866c6
refactor(fix): sign event in test
1-leo Dec 6, 2025
59f750f
refactor(fix): auth event id
1-leo Dec 6, 2025
0413531
refactor: zap request calc id
1-leo Dec 6, 2025
35d0cd8
refactor(fix): mock relay fix send encode
1-leo Dec 6, 2025
9ea161b
refactor(fix): brodcast detect need for signing
1-leo Dec 6, 2025
338e975
refactor(fix): use signed event in sets engine
1-leo Dec 6, 2025
6f66a4e
refactor(fix): encode gift wrap
1-leo Dec 6, 2025
e8c1933
refactor(fix): encode zapRequest
1-leo Dec 6, 2025
2fb7dd0
fix: add copy with to extended class
1-leo Dec 6, 2025
8b47398
refactor: nip07, amber, sembast
1-leo Dec 6, 2025
6a1d852
refactor: proof of work as instance
1-leo Dec 6, 2025
9b57773
fix: blossom signing
1-leo Dec 12, 2025
052b634
chore(fix) missing sign return value
1-leo Dec 13, 2025
2b469e9
nip-01 immutable light
frnandu Dec 22, 2025
c65eaaa
Merge pull request #348 from relaystr/fix/nip-01-immutable-light
frnandu Dec 22, 2025
72c2173
feat: isolate pool
1-leo Dec 24, 2025
1aa82c5
chore: set isolate pool size to 1
1-leo Dec 24, 2025
6fc4209
feat: eose extract request id
1-leo Dec 24, 2025
22f7e13
feat: preserve order decoding
1-leo Dec 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'dart:convert';

import 'package:ndk/ndk.dart';
import 'package:ndk/shared/nips/nip19/nip19.dart';

import '../../data_sources/amber_flutter.dart';

Expand All @@ -18,13 +17,15 @@ class AmberEventSigner implements EventSigner {
});

@override
Future<void> sign(Nip01Event event) async {
Future<Nip01Event> sign(Nip01Event event) async {
final npub = publicKey.startsWith('npub')
? publicKey
: Nip19.encodePubKey(publicKey);
Map<dynamic, dynamic> map = await amberFlutterDS.amber.signEvent(
currentUser: npub, eventJson: jsonEncode(event.toJson()), id: event.id);
event.sig = map['signature'];
currentUser: npub,
eventJson: Nip01EventModel.fromEntity(event).toJsonString(),
id: event.id);
return event.copyWith(sig: map['signature']);
}

@override
Expand Down
7 changes: 3 additions & 4 deletions packages/isar/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -356,10 +356,9 @@ packages:
ndk:
dependency: "direct main"
description:
name: ndk
sha256: "88c371ca73014fa41b8f711036fb64d01c99045e7ce7e9560fa49a0976c36e18"
url: "https://pub.dev"
source: hosted
path: "../ndk"
relative: true
source: path
version: "0.6.1-dev.1"
node_preamble:
dependency: transitive
Expand Down
2 changes: 1 addition & 1 deletion packages/ndk/example/accounts_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ void main() async {

// broadcast a new event using the logged in account with it's signer to sign
NdkBroadcastResponse response = ndk.broadcast.broadcast(
nostrEvent: Nip01Event(
nostrEvent: Nip01EventService.createEventCalculateId(
pubKey: key1.publicKey,
kind: Nip01Event.kTextNodeKind,
tags: [],
Expand Down
6 changes: 3 additions & 3 deletions packages/ndk/example/bunkers_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ Future<void> main() async {
log('Logged in as: ${ndk.accounts.getPublicKey()}');

// Test signing an event
final event = Nip01Event(
final event = Nip01EventService.createEventCalculateId(
pubKey: ndk.accounts.getPublicKey()!,
kind: 1,
content: 'Hello from NIP-46 with new simplified API!',
tags: [],
);

await ndk.accounts.sign(event);
final signedEvent = await ndk.accounts.sign(event);
log('Event signed successfully!');
log('Event ID: ${event.id}');
log('Event ID: ${signedEvent.id}');
} catch (e) {
log('Error: $e');
}
Expand Down
19 changes: 14 additions & 5 deletions packages/ndk/example/nip13_pow_example.dart
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
import 'dart:developer';

import 'package:ndk/domain_layer/entities/nip_01_event.dart';
import 'package:ndk/ndk.dart';
import 'package:ndk/shared/nips/nip01/bip340.dart';

void main() async {
final keyPair = Bip340.generatePrivateKey();

final minedEvent = Nip01Event(
final event = Nip01EventService.createEventCalculateId(
pubKey: keyPair.publicKey,
kind: 1,
tags: [],
content: 'message',
).minePoW(12);
);

log(minedEvent.id); // the id will start with "000"
/// your global NDK instance
final ndk = Ndk.emptyBootstrapRelaysConfig();

if (minedEvent.checkPoWDifficulty(10)) {
/// pass your event to the proof of work usecase
final minedEvent =
await ndk.proofOfWork.minePoW(event: event, targetDifficulty: 10);

/// the id will start with "000"
log(minedEvent.id);

/// check the difficulty
if (ndk.proofOfWork.checkPoWDifficulty(event: event, targetDifficulty: 10)) {
log('Event has difficulty >= 10');
}
}
6 changes: 3 additions & 3 deletions packages/ndk/example/nostrconnect_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@ Future<void> main() async {
log('Logged in as: ${ndk.accounts.getPublicKey()}');

// Test signing an event
final event = Nip01Event(
final event = Nip01EventService.createEventCalculateId(
pubKey: ndk.accounts.getPublicKey()!,
kind: 1,
content: 'Hello from NIP-46 with new simplified API!',
tags: [],
);

await ndk.accounts.sign(event);
final signedEvent = await ndk.accounts.sign(event);
log('Event signed successfully!');
log('Event ID: ${event.id}');
log('Event ID: ${signedEvent.id}');
} catch (e) {
log('Error: $e');
}
Expand Down
181 changes: 181 additions & 0 deletions packages/ndk/lib/data_layer/models/nip_01_event_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import 'dart:convert';

import '../../domain_layer/entities/nip_01_event.dart';
import '../../shared/helpers/list_casting.dart';
import '../../shared/nips/nip19/nip19.dart';

/// Data model for NIP-01 Event
/// Extends [Nip01Event] entity with serialization methods to/from JSON and other formats
class Nip01EventModel extends Nip01Event {
/// creates a new [Nip01EventModel] instance
Nip01EventModel({
required super.id,
required super.pubKey,
required super.createdAt,
required super.kind,
required super.tags,
required super.content,
required super.sig,
super.validSig,
super.sources = const [],
});

/// creates a copy of this event with the given fields replaced by the new values \
/// needed so other packages depending on the extened nip01_event.dart can use copyWith
@override
Nip01EventModel copyWith({
String? id,
String? pubKey,
int? createdAt,
int? kind,
List<List<String>>? tags,
String? content,
String? sig,
bool? validSig,
List<String>? sources,
}) {
return Nip01EventModel(
id: id ?? this.id,
pubKey: pubKey ?? this.pubKey,
createdAt: createdAt ?? this.createdAt,
kind: kind ?? this.kind,
tags: tags ?? this.tags,
content: content ?? this.content,
sig: sig ?? this.sig,
validSig: validSig ?? this.validSig,
sources: sources ?? this.sources);
}

/**
* encoding/decoding methods
*/

/// creates a new [Nip01EventModel] instance from a [Nip01Event] entity
factory Nip01EventModel.fromEntity(Nip01Event event) {
return Nip01EventModel(
id: event.id,
pubKey: event.pubKey,
createdAt: event.createdAt,
kind: event.kind,
tags: event.tags,
content: event.content,
sig: event.sig,
sources: event.sources,
validSig: event.validSig,
);
}

/// creates a new [Nip01EventModel] instance from a JSON object
factory Nip01EventModel.fromJson(Map<dynamic, dynamic> data) {
final id = data['id'] as String? ?? '';
final pubKey = data['pubkey'] as String? ?? '';
final createdAt = data['created_at'] as int;
final kind = data['kind'] as int;
final tags = castToListOfListOfString(data['tags']);
final content = data['content'] as String? ?? '';

/// '' to support rumor events
final sig = (data['sig'] as String?);

return Nip01EventModel(
id: id,
pubKey: pubKey,
createdAt: createdAt,
kind: kind,
tags: tags,
content: content,
sig: sig,
);
}

/// Returns the Event object as a JSON object
Map<String, dynamic> toJson() {
return {
'id': id,
'pubkey': pubKey,
'created_at': createdAt,
'kind': kind,
'tags': tags,
'content': content,
'sig': sig
};
}

/// Returns the Event object as a JSON string
String toJsonString() {
final jsonMap = toJson();
return json.encode(jsonMap);
}

/// Encode this event as a nevent (NIP-19 event reference)
///
/// Returns a bech32-encoded nevent string that includes:
/// - Event ID (required)
/// - Author pubkey (included)
/// - Kind (included)
/// - Relay hints from event.sources (if available)
///
/// Usage: `final nevent = event.nevent;`
String get nevent {
return Nip19.encodeNevent(
eventId: id,
author: pubKey,
kind: kind,
relays: sources.isEmpty ? null : sources,
);
}

/// Encode this event as an naddr (NIP-19 addressable event coordinate)
///
/// Only works for addressable/replaceable events (kind >= 10000 or kind 0, 3, 41)
/// Requires a "d" tag to identify the event.
///
/// Returns a bech32-encoded naddr string or null if:
/// - Event is not addressable/replaceable
/// - Event doesn't have a "d" tag
///
/// Usage: `final naddr = event.naddr;`
String? get naddr {
// Check if this is an addressable event
if (!_isAddressableKind(kind)) {
return null;
}

// Get the "d" tag identifier
final identifier = getDtag();
if (identifier == null) {
return null;
}

return Nip19.encodeNaddr(
identifier: identifier,
pubkey: pubKey,
kind: kind,
relays: sources.isEmpty ? null : sources,
);
}

/// Returns the Event object as a base64-encoded JSON string
String toBase64() {
return base64Encode(utf8.encode(json.encode(toJson())));
}

/// Check if an event kind is addressable/replaceable
///
/// According to NIP-01:
/// - Replaceable events: 0, 3, 41
/// - Parameterized replaceable events: 10000-19999, 30000-39999
bool _isAddressableKind(int kind) {
// Replaceable events
if (kind == 0 || kind == 3 || kind == 41) {
return true;
}

// Parameterized replaceable events
if ((kind >= 10000 && kind <= 19999) || (kind >= 30000 && kind <= 39999)) {
return true;
}

return false;
}
}
22 changes: 15 additions & 7 deletions packages/ndk/lib/data_layer/repositories/blossom/blossom_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import '../../../domain_layer/entities/nip_01_event.dart';
import '../../../domain_layer/entities/tuple.dart';
import '../../../domain_layer/repositories/blossom.dart';
import '../../data_sources/http_request.dart';
import '../../models/nip_01_event_model.dart';

class BlossomRepositoryImpl implements BlossomRepository {
final HttpRequestDS client;
Expand Down Expand Up @@ -169,7 +170,8 @@ class BlossomRepositoryImpl implements BlossomRepository {
headers: {
if (contentType != null) 'Content-Type': contentType,
if (authorization != null)
'Authorization': "Nostr ${authorization.toBase64()}",
'Authorization':
"Nostr ${Nip01EventModel.fromEntity(authorization).toBase64()}",
'Content-Length': '${data.length}',
},
);
Expand Down Expand Up @@ -212,7 +214,8 @@ class BlossomRepositoryImpl implements BlossomRepository {
url: Uri.parse('$serverUrl/mirror'),
body: myBody,
headers: {
'Authorization': "Nostr ${authorization.toBase64()}",
'Authorization':
"Nostr ${Nip01EventModel.fromEntity(authorization).toBase64()}",
'Content-Type': 'application/json',
},
);
Expand Down Expand Up @@ -259,7 +262,8 @@ class BlossomRepositoryImpl implements BlossomRepository {
}

if (authorization != null) {
headers['Authorization'] = "Nostr ${authorization.toBase64()}";
headers['Authorization'] =
"Nostr ${Nip01EventModel.fromEntity(authorization).toBase64()}";
}

final response = await client.get(
Expand Down Expand Up @@ -298,7 +302,8 @@ class BlossomRepositoryImpl implements BlossomRepository {
final headers = <String, String>{};

if (authorization != null) {
headers['Authorization'] = "Nostr ${authorization.toBase64()}";
headers['Authorization'] =
"Nostr ${Nip01EventModel.fromEntity(authorization).toBase64()}";
}

for (final url in serverUrls) {
Expand Down Expand Up @@ -420,7 +425,8 @@ class BlossomRepositoryImpl implements BlossomRepository {

final headers = <String, String>{};
if (authorization != null) {
headers['Authorization'] = "Nostr ${authorization.toBase64()}";
headers['Authorization'] =
"Nostr ${Nip01EventModel.fromEntity(authorization).toBase64()}";
}

final response = await client.get(
Expand Down Expand Up @@ -466,7 +472,8 @@ class BlossomRepositoryImpl implements BlossomRepository {
final response = await client.delete(
url: Uri.parse('$serverUrl/$sha256'),
headers: {
'Authorization': "Nostr ${authorization.toBase64()}",
'Authorization':
"Nostr ${Nip01EventModel.fromEntity(authorization).toBase64()}",
},
);

Expand Down Expand Up @@ -504,7 +511,8 @@ class BlossomRepositoryImpl implements BlossomRepository {
required String sha256,
required Nip01Event reportEvent,
}) async {
final String myBody = jsonEncode(reportEvent.toJson());
final String myBody =
jsonEncode(Nip01EventModel.fromEntity(reportEvent).toJson());

final response = await client.put(
url: Uri.parse('$serverUrl/report'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ class Bip340EventSigner implements EventSigner {
});

@override
Future<void> sign(Nip01Event event) async {
Future<Nip01Event> sign(Nip01Event event) async {
if (Helpers.isNotBlank(privateKey)) {
event.sig = Bip340.sign(event.id, privateKey!);
return event.copyWith(sig: Bip340.sign(event.id, privateKey!));
}
throw Exception('Private key is required for signing');
}

@override
Expand Down
Loading