Skip to content

Commit

Permalink
Merge pull request #76 from relaystr/lnurl-zaps
Browse files Browse the repository at this point in the history
nip-57 Zaps
  • Loading branch information
frnandu authored Dec 25, 2024
2 parents 85f4b0c + c84c4df commit 141cc69
Show file tree
Hide file tree
Showing 37 changed files with 1,405 additions and 89 deletions.
2 changes: 1 addition & 1 deletion packages/amber/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ android {
}

defaultConfig {
minSdkVersion 23
minSdkVersion 21
}

dependencies {
Expand Down
32 changes: 8 additions & 24 deletions packages/amber/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -238,14 +238,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.1"
flat_buffers:
dependency: transitive
description:
name: flat_buffers
sha256: "380bdcba5664a718bfd4ea20a45d39e13684f5318fcd8883066a55e21f37f4c3"
url: "https://pub.dev"
source: hosted
version: "23.5.26"
flutter:
dependency: "direct main"
description: flutter
Expand Down Expand Up @@ -470,22 +462,6 @@ packages:
relative: true
source: path
version: "0.2.0-dev005"
objectbox:
dependency: transitive
description:
name: objectbox
sha256: ea823f4bf1d0a636e7aa50b43daabb64dd0fbd80b85a033016ccc1bc4f76f432
url: "https://pub.dev"
source: hosted
version: "4.0.3"
objectbox_generator:
dependency: "direct dev"
description:
name: objectbox_generator
sha256: "96da521f2cef455cd524f8854e31d64495c50711ad5f1e2cf3142a8e527bc75f"
url: "https://pub.dev"
source: hosted
version: "4.0.3"
package_config:
dependency: transitive
description:
Expand Down Expand Up @@ -723,6 +699,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.1"
web_socket_client:
dependency: transitive
description:
name: web_socket_client
sha256: "0ec5230852349191188c013112e4d2be03e3fc83dbe80139ead9bf3a136e53b5"
url: "https://pub.dev"
source: hosted
version: "0.1.5"
webdriver:
dependency: transitive
description:
Expand Down
4 changes: 1 addition & 3 deletions packages/amber/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ homepage: https://github.com/relaystr/ndk

environment:
sdk: ">=3.2.1 <4.0.0"
flutter: ">=3.3.0"

platforms:
android:
Expand All @@ -19,7 +18,7 @@ dependencies:
ndk: ^0.2.0-dev002

#dependency_overrides:
# ndk:
# ndk:
# path: ../ndk

dev_dependencies:
Expand All @@ -30,7 +29,6 @@ dev_dependencies:
mockito: ^5.0.17
integration_test:
sdk: flutter
objectbox_generator: any

flutter:
plugin:
Expand Down
2 changes: 1 addition & 1 deletion packages/ndk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,9 @@ await for (final event in response.stream) {
- [x] Lists ([NIP-51](https://github.com/nostr-protocol/nips/blob/master/51.md))
- [x] Relay List Metadata ([NIP-65](https://github.com/nostr-protocol/nips/blob/master/65.md))
- [x] Wallet Connect API ([NIP-47](https://github.com/nostr-protocol/nips/blob/master/47.md))
- [X] Zaps ([NIP-57](https://github.com/nostr-protocol/nips/blob/master/57.md))
- [x] Authentication of clients to relays ([NIP-42](https://github.com/nostr-protocol/nips/blob/master/42.md))
- [ ] Bech Encoding support ([NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md))
- [ ] Zaps (private, public, anon, non-zap) ([NIP-57](https://github.com/nostr-protocol/nips/blob/master/57.md))
- [ ] Badges ([NIP-58](https://github.com/nostr-protocol/nips/blob/master/58.md))

## Performance
Expand Down
11 changes: 11 additions & 0 deletions packages/ndk/example/zaps/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# running the examples

For zapping example `zap.dart` you need a `nostr+walletconnect://...` uri from your NWC wallet service provider.

see https://github.com/getAlby/awesome-nwc for more info how to get a wallet supporting NWC

`NWC_URI=nostr+walletconnect://.... dart zap.dart`

for more logging

`NWC_URI=nostr+walletconnect://.... dart --enable-asserts zap.dart`
32 changes: 32 additions & 0 deletions packages/ndk/example/zaps/receipts_for_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// ignore_for_file: avoid_print

import 'package:ndk/ndk.dart';

void main() async {
final ndk = Ndk.defaultConfig();

print("fetching zap receipts for single event ");
final receipts = await ndk.zaps.fetchZappedReceipts(
pubKey:
"787338757fc25d65cd929394d5e7713cf43638e8d259e8dcf5c73b834eb851f2",
eventId:
"906a0c5920b59e5754d0df5164bfea2a8d48ce5d73beaa1e854b3e6725e3288a").toList();

// Sort eventReceipts by amountSats in descending order
receipts
.sort((a, b) => (b.amountSats ?? 0).compareTo(a.amountSats ?? 0));

int eventSum = 0;
for (var receipt in receipts) {
String? sender;
if (receipt.sender!=null) {
Metadata? metadata = await ndk.metadata.loadMetadata(receipt.sender!);
sender = metadata?.name;
}
print("${sender!=null?"from ${sender} ":""} ${receipt.amountSats} sats ${receipt.comment}");
eventSum += receipt.amountSats ?? 0;
}
print("${receipts.length} receipts, total of $eventSum sats");

await ndk.destroy();
}
30 changes: 30 additions & 0 deletions packages/ndk/example/zaps/receipts_for_profile.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// ignore_for_file: avoid_print

import 'package:ndk/ndk.dart';

void main() async {
final ndk = Ndk.defaultConfig();

print("fetching zap receipts for profile ");
final profileReceipts = await ndk.zaps.fetchZappedReceipts(
pubKey: "30782a8323b7c98b172c5a2af7206bb8283c655be6ddce11133611a03d5f1177",
).toList();

// Sort profileReceipts by amountSats in descending order
profileReceipts
.sort((a, b) => (b.amountSats ?? 0).compareTo(a.amountSats ?? 0));

int profileSum = 0;
for (var receipt in profileReceipts) {
String? sender;
if (receipt.sender!=null) {
Metadata? metadata = await ndk.metadata.loadMetadata(receipt.sender!);
sender = metadata?.name;
}
print("${sender!=null?"from ${sender} ":""} ${receipt.amountSats} sats ${receipt.comment}");
profileSum += receipt.amountSats ?? 0;
}
print("${profileReceipts.length} receipts, total of $profileSum sats");

await ndk.destroy();
}
53 changes: 53 additions & 0 deletions packages/ndk/example/zaps/zap.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// ignore_for_file: avoid_print

import 'dart:io';

import 'package:ndk/domain_layer/usecases/zaps/zap_receipt.dart';
import 'package:ndk/domain_layer/usecases/zaps/zaps.dart';
import 'package:ndk/ndk.dart';
import 'package:ndk/shared/nips/nip01/bip340.dart';
import 'package:ndk/shared/nips/nip01/key_pair.dart';

void main() async {
// We use an empty bootstrap relay list,
// since NWC will provide the relay we connect to so we don't need default relays
final ndk = Ndk(NdkConfig(
eventVerifier: Bip340EventVerifier(),
cache: MemCacheManager(),
logLevel: Logger.logLevels.trace));

// You need an NWC_URI env var or to replace with your NWC uri connection
final nwcUri = Platform.environment['NWC_URI']!;
final connection = await ndk.nwc.connect(nwcUri);
KeyPair key = Bip340.generatePrivateKey();
final amount = 21;
final lnurl = "opensats@vlt.ge";
final comment = "enjoy this zap from NDK";

ZapResponse response = await ndk.zaps.zap(
nwcConnection: connection,
lnurl: lnurl,
comment: comment,
amountSats: amount,
fetchZapReceipt: true,
signer: Bip340EventSigner(privateKey: key.privateKey, publicKey: key.publicKey),
relays: ["wss://relay.damus.io"],
pubKey: "787338757fc25d65cd929394d5e7713cf43638e8d259e8dcf5c73b834eb851f2",
eventId: "906a0c5920b59e5754d0df5164bfea2a8d48ce5d73beaa1e854b3e6725e3288a"
);

if (response.payInvoiceResponse!=null && response.payInvoiceResponse!.preimage.isNotEmpty) {
print("Payed $amount to $lnurl, preimage = ${response.payInvoiceResponse!
.preimage}");

print("Waiting for Zap Receipt...");
ZapReceipt? receipt = await response.zapReceipt;
if (receipt!=null) {
print("Receipt : $receipt");
} else {
print("No Receipt");
}
}

await ndk.destroy();
}
3 changes: 2 additions & 1 deletion packages/ndk/lib/data_layer/data_sources/http_request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ class HttpRequestDS {
/// make a get request to the given url
Future<Map<String, dynamic>> jsonRequest(String url) async {
http.Response response = await _client
.get(Uri.parse(url), headers: {"Accept": "application/json"});
.get(Uri.parse(url).replace(scheme: 'https'), headers: {"Accept": "application/json"});

print(response);
if (response.statusCode != 200) {
return throw Exception(
"error fetching STATUS: ${response.statusCode}, Link: $url");
Expand Down
36 changes: 36 additions & 0 deletions packages/ndk/lib/data_layer/repositories/lnurl_http_impl.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import 'package:ndk/domain_layer/repositories/lnurl_transport.dart';
import 'package:ndk/domain_layer/usecases/lnurl/lnurl_response.dart';

import '../../domain_layer/repositories/nip_05_repo.dart';
import '../../shared/logger/logger.dart';
import '../data_sources/http_request.dart';

/// implementation of the [Nip05Repository] interface with http
class LnurlTransportHttpImpl implements LnurlTransport {
final HttpRequestDS httpDS;

/// constructor
LnurlTransportHttpImpl(this.httpDS);

@override
Future<LnurlResponse?> requestLnurlResponse(String lnurl) async {
try {
final response = await httpDS.jsonRequest(lnurl);
return LnurlResponse.fromJson(response);
} catch (e) {
Logger.log.w(e);
return null;
}
}

@override
Future<Map<String,dynamic>?> fetchInvoice(String callbacklink) async {
try {
return await httpDS.jsonRequest(callbacklink);
} catch (e) {
Logger.log.d(e);
return null;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class WebSocketClientNostrTransport implements NostrTransport {
Completer completer = Completer();
ready = completer.future;
_websocketDS.ws.connection.listen((state) {
Logger.log.d("${_websocketDS.url} connection state changed to $state");
Logger.log.t("${_websocketDS.url} connection state changed to $state");
switch (state) {
case Connected() || Reconnected():
completer.complete();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import 'package:web_socket_client/web_socket_client.dart';

import '../../../domain_layer/repositories/nostr_transport.dart';
import '../../../shared/helpers/relay_helper.dart';
import '../../../shared/logger/logger.dart';
import '../../data_sources/websocket_client.dart';

class WebSocketClientNostrTransportFactory implements NostrTransportFactory {
Expand Down
4 changes: 2 additions & 2 deletions packages/ndk/lib/domain_layer/entities/relay_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class RelayInfo {
RelayInfo._(this.name, this.description, this.pubKey, this.contact, this.nips,
this.software, this.version, this.icon);

factory RelayInfo.fromJson(Map<dynamic, dynamic> json, String url) {
factory RelayInfo.fromJson(Map<String, dynamic> json, String url) {
final String name = json["name"] ?? '';
final String description = json["description"] ?? "";
final String pubKey = json["pubkey"] ?? "";
Expand All @@ -56,7 +56,7 @@ class RelayInfo {
headers: {'Accept': 'application/nostr+json'},
);
final decodedResponse =
jsonDecode(utf8.decode(response.bodyBytes)) as Map;
jsonDecode(utf8.decode(response.bodyBytes)) as Map<String,dynamic>;
return RelayInfo.fromJson(decodedResponse, uri.toString());
} catch (e) {
Logger.log.d(e);
Expand Down
11 changes: 11 additions & 0 deletions packages/ndk/lib/domain_layer/repositories/lnurl_transport.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:ndk/domain_layer/usecases/lnurl/lnurl_response.dart';
import 'package:ndk/ndk.dart';

/// transport to get the lnurl response
abstract class LnurlTransport {
/// network request to get theLnurl response and invoices
Future<LnurlResponse?> requestLnurlResponse(String lnurl);

/// fetch an invoice from lnurl callback endpoint
Future<Map<String,dynamic>?> fetchInvoice(String callbacklink);
}
55 changes: 28 additions & 27 deletions packages/ndk/lib/domain_layer/usecases/cache_read/cache_read.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,33 +31,33 @@ class CacheRead {
foundEvents.addAll(cached);
// WE CANNOT DO THIS, BECAUSE 1) kinds.length > 1, 2) only replaceable events have 1 event per pubKey+kind, normal events can have many per pubKey+kind
// TODO if kind.length == 1 and kind IS replaceable AND there is not limit/until/since AND it is NOT a subscription, then we can do some shit
//
// // remove found authors from unresolved filter if it's not a subscription
// if (!requestState.isSubscription && cached.isNotEmpty) {
// if (filter.limit == null) {
// // Keep track of whether we've kept one item
// bool keptOne = false;
// filter.authors!.removeWhere((author) {
// if (!keptOne &&
// foundEvents.any((event) => event.pubKey == author)) {
// keptOne = true;
// return false; // Keep the first matching item
// }
// return foundEvents.any((event) => event.pubKey == author);
// });
// } else if (foundEvents.length >= filter.limit!) {
// // Keep track of whether we've kept one item
// bool keptOne = false;
// filter.authors!.removeWhere((author) {
// if (!keptOne &&
// foundEvents.any((event) => event.pubKey == author)) {
// keptOne = true;
// return false; // Keep the first matching item
// }
// return foundEvents.any((event) => event.pubKey == author);
// });
// }
// }
//
// // remove found authors from unresolved filter if it's not a subscription
// if (!requestState.isSubscription && cached.isNotEmpty) {
// if (filter.limit == null) {
// // Keep track of whether we've kept one item
// bool keptOne = false;
// filter.authors!.removeWhere((author) {
// if (!keptOne &&
// foundEvents.any((event) => event.pubKey == author)) {
// keptOne = true;
// return false; // Keep the first matching item
// }
// return foundEvents.any((event) => event.pubKey == author);
// });
// } else if (foundEvents.length >= filter.limit!) {
// // Keep track of whether we've kept one item
// bool keptOne = false;
// filter.authors!.removeWhere((author) {
// if (!keptOne &&
// foundEvents.any((event) => event.pubKey == author)) {
// keptOne = true;
// return false; // Keep the first matching item
// }
// return foundEvents.any((event) => event.pubKey == author);
// });
// }
// }
}

if (filter.ids != null) {
Expand All @@ -74,6 +74,7 @@ class CacheRead {
return foundIdEvents.any((event) => event.id == id);
});


foundEvents.addAll(foundIdEvents);
if (filter.ids!.isEmpty) {
// if we have not more ids in filter, remove the filter entirely,
Expand Down
Loading

0 comments on commit 141cc69

Please sign in to comment.