Skip to content

Commit

Permalink
add zaps.fetchZappedReceipts + examples for receipts
Browse files Browse the repository at this point in the history
  • Loading branch information
frnandu committed Dec 22, 2024
1 parent 9ce578b commit 12245e9
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 34 deletions.
36 changes: 36 additions & 0 deletions packages/ndk/example/zaps/receipts_for_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// 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:
"30782a8323b7c98b172c5a2af7206bb8283c655be6ddce11133611a03d5f1177",
eventId:
"d7bc29fa3c55ac525a3d5f2021211edb672b58565225dec423479a0875feea9d");

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

// Sort profileReceipts 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",
);

// 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();
}
19 changes: 7 additions & 12 deletions packages/ndk/lib/domain_layer/entities/nip_01_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -131,17 +131,7 @@ class Nip01Event {

/// return first `e` tag found
String? getEId() {
for (var tag in tags) {
if (tag.length > 1) {
var key = tag[0];
var value = tag[1];

if (key == "e") {
return value;
}
}
}
return null;
return getFirstTag("e");

Check warning on line 134 in packages/ndk/lib/domain_layer/entities/nip_01_event.dart

View check run for this annotation

Codecov / codecov/patch

packages/ndk/lib/domain_layer/entities/nip_01_event.dart#L134

Added line #L134 was not covered by tests
}

/// return all tags that match given `tag`
Expand Down Expand Up @@ -189,12 +179,17 @@ class Nip01Event {

/// return first found `d` tag
String? getDtag() {
return getFirstTag("d");
}

/// Get first tag matching given name
String? getFirstTag(String name) {
for (var tag in tags) {
if (tag.length > 1) {
var key = tag[0];
var value = tag[1];

if (key == "d") {
if (key == name) {
return value;
}
}
Expand Down
42 changes: 42 additions & 0 deletions packages/ndk/lib/domain_layer/usecases/lnurl/lnurl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,46 @@ abstract class Lnurl {
await signer.sign(event);
return event;
}

/// extract amount from bolt11 in sats
static int getAmountFromBolt11(String bolt11) {
final numStr = subUntil(bolt11, "lnbc", "1p");
if (numStr.isNotEmpty) {
var numStrLength = numStr.length;
if (numStrLength > 1) {
var lastStr = numStr.substring(numStr.length - 1);
var pureNumStr = numStr.substring(0, numStr.length - 1);
var pureNum = int.tryParse(pureNumStr);

Check warning on line 154 in packages/ndk/lib/domain_layer/usecases/lnurl/lnurl.dart

View check run for this annotation

Codecov / codecov/patch

packages/ndk/lib/domain_layer/usecases/lnurl/lnurl.dart#L147-L154

Added lines #L147 - L154 were not covered by tests
if (pureNum != null) {
if (lastStr == "p") {
return (pureNum * 0.0001).round();
} else if (lastStr == "n") {
return (pureNum * 0.1).round();
} else if (lastStr == "u") {
return (pureNum * 100).round();
} else if (lastStr == "m") {
return (pureNum * 100000).round();

Check warning on line 163 in packages/ndk/lib/domain_layer/usecases/lnurl/lnurl.dart

View check run for this annotation

Codecov / codecov/patch

packages/ndk/lib/domain_layer/usecases/lnurl/lnurl.dart#L156-L163

Added lines #L156 - L163 were not covered by tests
}
}
}
}

return 0;
}

static String subUntil(String content, String before, String end) {
var beforeLength = before.length;
var index = content.indexOf(before);
if (index < 0) {

Check warning on line 175 in packages/ndk/lib/domain_layer/usecases/lnurl/lnurl.dart

View check run for this annotation

Codecov / codecov/patch

packages/ndk/lib/domain_layer/usecases/lnurl/lnurl.dart#L172-L175

Added lines #L172 - L175 were not covered by tests
return "";
}

var index2 = content.indexOf(end, index + beforeLength);
if (index2 <= 0) {

Check warning on line 180 in packages/ndk/lib/domain_layer/usecases/lnurl/lnurl.dart

View check run for this annotation

Codecov / codecov/patch

packages/ndk/lib/domain_layer/usecases/lnurl/lnurl.dart#L179-L180

Added lines #L179 - L180 were not covered by tests
return "";
}

return content.substring(index + beforeLength, index2);

Check warning on line 184 in packages/ndk/lib/domain_layer/usecases/lnurl/lnurl.dart

View check run for this annotation

Codecov / codecov/patch

packages/ndk/lib/domain_layer/usecases/lnurl/lnurl.dart#L184

Added line #L184 was not covered by tests
}

}
47 changes: 25 additions & 22 deletions packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart
Original file line number Diff line number Diff line change
@@ -1,33 +1,27 @@
import 'dart:convert';

import 'package:ndk/domain_layer/entities/nip_01_event.dart';
import 'package:ndk/domain_layer/usecases/lnurl/lnurl.dart';
import 'package:ndk/domain_layer/usecases/zaps/zap_request.dart';

import '../../../shared/logger/logger.dart';

class ZapReceipt {
static const KIND = 9735;

int? paidAt;
int? amountSats;
String? pubKey;
String? bolt11;
String? preimage;
String? zapRequestJson;
String? recipient;
String? eventId;
String? zapContent;
String? comment;
String? sender;
String? anon;

ZapReceipt(
this.paidAt,
this.pubKey,
this.bolt11,
this.preimage,
this.zapRequestJson,
this.recipient,
this.eventId,
this.zapContent,
this.sender);

ZapReceipt.fromEvent(Nip01Event event) {

Check warning on line 23 in packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart

View check run for this annotation

Codecov / codecov/patch

packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart#L23

Added line #L23 was not covered by tests
String? zapRequestJson;
if (event.kind == 9735) {
for (var tag in event.tags) {
if (tag[0] == 'bolt11') bolt11 = tag[1];
Expand All @@ -39,15 +33,25 @@ class ZapReceipt {
if (tag[0] == 'P') sender = tag[1];

Check warning on line 33 in packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart

View check run for this annotation

Codecov / codecov/patch

packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart#L25-L33

Added lines #L25 - L33 were not covered by tests
}
paidAt = event.createdAt;
if (zapRequestJson != null) {
try {
Map map = jsonDecode(zapRequestJson!);
zapContent = map['content'];
sender = map['pubkey'];
} catch (_) {
zapContent = '';
if (zapRequestJson != null && zapRequestJson.isNotEmpty) {
Nip01Event event = Nip01Event.fromJson(jsonDecode(zapRequestJson));
comment = event.content;
sender = event.pubKey;
String? amountString = event.getFirstTag('amount');
if (amountString != null && amountString.isNotEmpty) {

Check warning on line 41 in packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart

View check run for this annotation

Codecov / codecov/patch

packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart#L35-L41

Added lines #L35 - L41 were not covered by tests
try {
double? amount = double.tryParse(amountString);

Check warning on line 43 in packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart

View check run for this annotation

Codecov / codecov/patch

packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart#L43

Added line #L43 was not covered by tests
if (amount != null) {
amountSats = (amount / 1000).round();

Check warning on line 45 in packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart

View check run for this annotation

Codecov / codecov/patch

packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart#L45

Added line #L45 was not covered by tests
}
} catch (e) {
Logger.log.w(e);

Check warning on line 48 in packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart

View check run for this annotation

Codecov / codecov/patch

packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart#L48

Added line #L48 was not covered by tests
}
}
}
if (amountSats == null && bolt11 != null) {
amountSats = Lnurl.getAmountFromBolt11(bolt11!);

Check warning on line 53 in packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart

View check run for this annotation

Codecov / codecov/patch

packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart#L52-L53

Added lines #L52 - L53 were not covered by tests
}
List<String>? splitStrings = anon?.split('_');
if (splitStrings != null && splitStrings.length == 2) {

Check warning on line 56 in packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart

View check run for this annotation

Codecov / codecov/patch

packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart#L55-L56

Added lines #L55 - L56 were not covered by tests
// /// recipient decrypt
Expand Down Expand Up @@ -94,11 +98,10 @@ class ZapReceipt {
// - The zap receipt event's pubkey MUST be the same as the recipient's lnurl provider's nostrPubkey (retrieved in step 1 of the protocol flow).
// - The invoiceAmount contained in the bolt11 tag of the zap receipt MUST equal the amount tag of the zap request (if present).
// - The lnurl tag of the zap request (if present) SHOULD equal the recipient's lnurl.
// - SHA256(description) MUST match the description hash in the bolt11 invoice.
}

@override

Check warning on line 103 in packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart

View check run for this annotation

Codecov / codecov/patch

packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart#L103

Added line #L103 was not covered by tests
String toString() {
return 'ZapReceipt(paidAt: $paidAt, pubKey: $pubKey, bolt11: $bolt11, preimage: $preimage, description: $zapRequestJson, recipient: $recipient, eventId: $eventId, zapContent: $zapContent, sender: $sender, anon: $anon)';
return 'ZapReceipt(paidAt: $paidAt, pubKey: $pubKey, bolt11: $bolt11, preimage: $preimage, amount: $amountSats, recipient: $recipient, eventId: $eventId, comment: $comment, sender: $sender, anon: $anon)';

Check warning on line 105 in packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart

View check run for this annotation

Codecov / codecov/patch

packages/ndk/lib/domain_layer/usecases/zaps/zap_receipt.dart#L105

Added line #L105 was not covered by tests
}
}
2 changes: 2 additions & 0 deletions packages/ndk/lib/domain_layer/usecases/zaps/zap_request.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import 'package:ndk/domain_layer/entities/nip_01_event.dart';

/// Zap Request
class ZapRequest extends Nip01Event {
static const int KIND = 9734;

/// Zap Request
ZapRequest(
{required super.pubKey, required super.tags, required super.content})
: super(kind: KIND);
Expand Down
17 changes: 17 additions & 0 deletions packages/ndk/lib/domain_layer/usecases/zaps/zaps.dart
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,23 @@ class Zaps {
return ZapResponse(error: e.toString());

Check warning on line 105 in packages/ndk/lib/domain_layer/usecases/zaps/zaps.dart

View check run for this annotation

Codecov / codecov/patch

packages/ndk/lib/domain_layer/usecases/zaps/zaps.dart#L105

Added line #L105 was not covered by tests
}
}

/// fetch all zap receipts matching given pubKey and optional event id, in sats
Future<List<ZapReceipt>> fetchZappedReceipts({required String pubKey, String? eventId, Duration? timeout} ) async {
NdkResponse? response = _requests.query(filters: [

Check warning on line 111 in packages/ndk/lib/domain_layer/usecases/zaps/zaps.dart

View check run for this annotation

Codecov / codecov/patch

packages/ndk/lib/domain_layer/usecases/zaps/zaps.dart#L110-L111

Added lines #L110 - L111 were not covered by tests
eventId!=null?
Filter(
kinds: [ZapReceipt.KIND],
eTags: [eventId],
pTags: [pubKey])

Check warning on line 116 in packages/ndk/lib/domain_layer/usecases/zaps/zaps.dart

View check run for this annotation

Codecov / codecov/patch

packages/ndk/lib/domain_layer/usecases/zaps/zaps.dart#L113-L116

Added lines #L113 - L116 were not covered by tests
:
Filter(
kinds: [ZapReceipt.KIND],
pTags: [pubKey])
], timeout: timeout?? Duration(seconds: 20));
List<Nip01Event> events = await response.future;
return events.map((event) => ZapReceipt.fromEvent(event)).toList();

Check warning on line 123 in packages/ndk/lib/domain_layer/usecases/zaps/zaps.dart

View check run for this annotation

Codecov / codecov/patch

packages/ndk/lib/domain_layer/usecases/zaps/zaps.dart#L118-L123

Added lines #L118 - L123 were not covered by tests
}
}

/// zap response
Expand Down

0 comments on commit 12245e9

Please sign in to comment.