diff --git a/lib/src/enums/destination_type.dart b/lib/src/enums/destination_type.dart index 38bf878..4320841 100644 --- a/lib/src/enums/destination_type.dart +++ b/lib/src/enums/destination_type.dart @@ -5,7 +5,8 @@ enum DestinationType { lnUrl('ln_url'), tetherAddress('tether_address'), lnAddress('ln_address'), - arkAddress('ark_address'); + arkAddress('ark_address'), + silentPayment('silent_payment'); const DestinationType(this.jsonValue); final String jsonValue; diff --git a/lib/src/helpers/branta_extensions.dart b/lib/src/helpers/branta_extensions.dart index 1d1a414..82177f6 100644 --- a/lib/src/helpers/branta_extensions.dart +++ b/lib/src/helpers/branta_extensions.dart @@ -12,9 +12,15 @@ extension StringBrantaExtensions on String { bool isArk() => toLowerCase().startsWith('ark1'); + bool isSilentPayment() { + final lower = toLowerCase(); + return lower.startsWith('sp1') || lower.startsWith('tsp1'); + } + DestinationType? getHashZkType() { if (isBolt11()) return DestinationType.bolt11; if (isArk()) return DestinationType.arkAddress; + if (isSilentPayment()) return DestinationType.silentPayment; return null; } diff --git a/lib/src/v2/classes/qr_parser.dart b/lib/src/v2/classes/qr_parser.dart index adbceed..8aadd92 100644 --- a/lib/src/v2/classes/qr_parser.dart +++ b/lib/src/v2/classes/qr_parser.dart @@ -47,6 +47,11 @@ class QRParser { destinations.add(QrDestination(arkValue, _detectPlainTextType(arkValue))); } + final silentPaymentValue = queryParams['silent_payment']; + if (silentPaymentValue != null) { + destinations.add(QrDestination(silentPaymentValue, _detectPlainTextType(silentPaymentValue))); + } + return; } @@ -86,6 +91,7 @@ class QRParser { if (value.toLowerCase().startsWith('lno')) return DestinationType.bolt12; if (value.toLowerCase().startsWith('lnurl')) return DestinationType.lnUrl; if (value.isArk()) return DestinationType.arkAddress; + if (value.isSilentPayment()) return DestinationType.silentPayment; if (_isEthereumAddress(value)) return DestinationType.tetherAddress; if (_isTronAddress(value)) return DestinationType.tetherAddress; if (_isLnAddress(value)) return DestinationType.lnAddress; diff --git a/test/branta_test.dart b/test/branta_test.dart index 65e3fa9..a07e4d6 100644 --- a/test/branta_test.dart +++ b/test/branta_test.dart @@ -326,6 +326,23 @@ void main() { expect('bc1qabc'.isArk(), isFalse); }); + test('isSilentPayment returns true for sp1 prefix', () { + expect('sp1qqwl5p9jhz0000h5zkvlf9gfqv9dl9qjp5ggq5x3fw'.isSilentPayment(), isTrue); + }); + + test('isSilentPayment returns true for tsp1 prefix', () { + expect('tsp1qqwl5p9jhz0000h5zkvlf9gfqv9dl9qjp5ggq5x3fw'.isSilentPayment(), isTrue); + }); + + test('isSilentPayment is case-insensitive', () { + expect('SP1QQWL5P9JHZ'.isSilentPayment(), isTrue); + }); + + test('isSilentPayment returns false for non-sp values', () { + expect('bc1qabc'.isSilentPayment(), isFalse); + expect('ark1qqjqtest'.isSilentPayment(), isFalse); + }); + test('getHashZkType returns bolt11 for bolt11 invoice', () { expect(_bolt11Invoice.getHashZkType(), equals(DestinationType.bolt11)); }); @@ -334,6 +351,11 @@ void main() { expect(_arkAddress.getHashZkType(), equals(DestinationType.arkAddress)); }); + test('getHashZkType returns silentPayment for silent payment address', () { + expect('sp1qqwl5p9jhz0000h5zkvlf9gfqv9dl9qjp5ggq5x3fw'.getHashZkType(), + equals(DestinationType.silentPayment)); + }); + test('getHashZkType returns null for bitcoin address', () { expect(_bitcoinAddress.getHashZkType(), isNull); }); @@ -471,6 +493,20 @@ void main() { expect(result.destinationType, equals(DestinationType.arkAddress)); }); + test('silent payment address sets silentPayment type', () { + final result = QRParser('sp1qqwl5p9jhz0000h5zkvlf9gfqv9dl9qjp5ggq5x3fw'); + + expect(result.destination, equals('sp1qqwl5p9jhz0000h5zkvlf9gfqv9dl9qjp5ggq5x3fw')); + expect(result.destinationType, equals(DestinationType.silentPayment)); + }); + + test('testnet silent payment address sets silentPayment type', () { + final result = QRParser('tsp1qqwl5p9jhz0000h5zkvlf9gfqv9dl9qjp5ggq5x3fw'); + + expect(result.destination, equals('tsp1qqwl5p9jhz0000h5zkvlf9gfqv9dl9qjp5ggq5x3fw')); + expect(result.destinationType, equals(DestinationType.silentPayment)); + }); + test('unrecognized text sets null type', () { final result = QRParser('not-any-known-format'); @@ -512,6 +548,17 @@ void main() { expect(result.destinations[2].type, equals(DestinationType.arkAddress)); expect(result.isOnChainZk(), isFalse); }); + + test('bitcoin URI with silent_payment param adds silentPayment destination', () { + final result = QRParser( + 'bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?silent_payment=sp1qqwl5p9jhz0000h5zkvlf9gfqv9dl9qjp5ggq5x3fw'); + + expect(result.destinations.length, equals(2)); + expect(result.destination, equals('1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa')); + expect(result.destinationType, equals(DestinationType.bitcoinAddress)); + expect(result.destinations[1].value, equals('sp1qqwl5p9jhz0000h5zkvlf9gfqv9dl9qjp5ggq5x3fw')); + expect(result.destinations[1].type, equals(DestinationType.silentPayment)); + }); }); // ------------------------------------------------------------------------- @@ -599,6 +646,7 @@ void main() { (DestinationType.tetherAddress, 'tether_address'), (DestinationType.lnAddress, 'ln_address'), (DestinationType.arkAddress, 'ark_address'), + (DestinationType.silentPayment, 'silent_payment'), ]) { test('destinationType $type serializes to "$expected"', () { final dest = Destination(value: 'addr', type: type);