Compare commits
100
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f01fc66dcf | ||
|
|
9a14af33a2 | ||
|
|
770868a773 | ||
|
|
dc78f8958c | ||
|
|
03ba0e532f | ||
|
|
a6a4365a0d | ||
|
|
ac23574ae2 | ||
|
|
87f8e52036 | ||
|
|
eb9319fca0 | ||
|
|
a9e4272762 | ||
|
|
f66668f0ec | ||
|
|
100a4943eb | ||
|
|
5b1317c3e4 | ||
|
|
d85b6562dc | ||
|
|
56585319cc | ||
|
|
79ca30c5ed | ||
|
|
0bd988a7a6 | ||
|
|
218d798d2a | ||
|
|
e8d8789413 | ||
|
|
a2866d035a | ||
|
|
1a8e3e601b | ||
|
|
b3dde35141 | ||
|
|
8541222da6 | ||
|
|
d6085f7754 | ||
|
|
d2035ba416 | ||
|
|
42bab5d18a | ||
|
|
6c05785391 | ||
|
|
65224314e1 | ||
|
|
2534cf5d1e | ||
|
|
262b049c20 | ||
|
|
697f6fa555 | ||
|
|
02fec140e1 | ||
|
|
7cc92d6732 | ||
|
|
013560d25b | ||
|
|
d19a58df84 | ||
|
|
fd64d2f269 | ||
|
|
426b63456d | ||
|
|
4d2ad39323 | ||
|
|
060c6acd4e | ||
|
|
14da243b89 | ||
|
|
4f91a2be1c | ||
|
|
ab13e8f6c2 | ||
|
|
f431f825c5 | ||
|
|
b56aa5ce62 | ||
|
|
c69de2b41b | ||
|
|
e5285058a8 | ||
|
|
b01b658b93 | ||
|
|
6596aa963a | ||
|
|
ed309d18e8 | ||
|
|
1b30d9836b | ||
|
|
588a127f9a | ||
|
|
362e45e40f | ||
|
|
fae57cc46e | ||
|
|
80ab0fd9e1 | ||
|
|
29895f4079 | ||
|
|
41fa32d1b4 | ||
|
|
63ce12d745 | ||
|
|
7245dbb669 | ||
|
|
e124e0839a | ||
|
|
1a31b47c6b | ||
|
|
a5cd50af13 | ||
|
|
66119f6b56 | ||
|
|
484f73a33b | ||
|
|
67204a12f8 | ||
|
|
b4a62fe9e5 | ||
|
|
638b288b7e | ||
|
|
357cc6827f | ||
|
|
682ec4ba7d | ||
|
|
03cf1d2119 | ||
|
|
2309a2edda | ||
|
|
ac1ac11075 | ||
|
|
aafa9f97eb | ||
|
|
7d3fe5ac58 | ||
|
|
b9c01ce865 | ||
|
|
679aa6be5f | ||
|
|
c44a17efbd | ||
|
|
27a2baefcc | ||
|
|
e23c5748ce | ||
|
|
c9781b2ff2 | ||
|
|
b9485d3296 | ||
|
|
c222fff82e | ||
|
|
f839031340 | ||
|
|
41b8743b29 | ||
|
|
056f4c1286 | ||
|
|
cfed450521 | ||
|
|
438603c97b | ||
|
|
c92c438efe | ||
|
|
7d5efcfb75 | ||
|
|
bb942ef72c | ||
|
|
b28a529ffc | ||
|
|
1ff3a6ec16 | ||
|
|
f1b679d912 | ||
|
|
8b514337a6 | ||
|
|
d7f6803f1f | ||
|
|
d91a40c41d | ||
|
|
0ed49789d0 | ||
|
|
a41b99644c | ||
|
|
286eae3812 | ||
|
|
e1e8057be5 | ||
|
|
a1ce50bcd4 |
@@ -214,7 +214,7 @@ flutter build apk --release --target-platform android-arm64
|
||||
│ flutter_rust_bridge v2.11.1 (FFI) │
|
||||
├─────────────────────────────────────────┤
|
||||
│ elcaju_core (Rust interno) │
|
||||
│ CDK 0.15.1 + SQLite + BIP39 + Tokio │
|
||||
│ CDK 0.16.0 + SQLite + BIP39 + Tokio │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
@@ -256,7 +256,7 @@ elcaju/
|
||||
│ │ └── scanner/ # QR scanner widget
|
||||
│ └── l10n/ # 11 languages (354 translation keys)
|
||||
├── rust/
|
||||
│ ├── Cargo.toml # elcaju_core: CDK 0.15.1, flutter_rust_bridge 2.11.1
|
||||
│ ├── Cargo.toml # elcaju_core: CDK 0.16.0, flutter_rust_bridge 2.11.1
|
||||
│ └── src/api/ # wallet, token, keys, mint_info, error
|
||||
├── rust_builder/ # Cargokit integration (auto Rust compilation)
|
||||
├── android/ # Android config (NDK 27.0, NFC HCE service)
|
||||
|
||||
@@ -368,7 +368,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.elcaju;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.elcaju;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -384,7 +384,7 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.elcaju.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.elcaju.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
@@ -401,7 +401,7 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.elcaju.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.elcaju.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
@@ -416,7 +416,7 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.elcaju.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.elcaju.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
@@ -547,7 +547,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.elcaju;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.elcaju;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
@@ -569,7 +569,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.elcaju;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.elcaju;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
||||
@@ -22,6 +22,7 @@ class AppColors {
|
||||
// === ACCENT COLORS ===
|
||||
static const Color primaryAction = Color(0xFFE35D33); // Naranja vibrante
|
||||
static const Color secondaryAction = Color(0xFFFFB74D); // Amarillo pulpa/Bitcoin
|
||||
static const Color bitcoinOrange = Color(0xFFF7931A); // Bitcoin brand orange
|
||||
static const Color success = Color(0xFF00E676); // Verde neón
|
||||
static const Color error = Color(0xFFFF5252); // Rojo suave
|
||||
static const Color warning = Color(0xFFFFB74D); // Amarillo
|
||||
|
||||
@@ -473,11 +473,13 @@ class NfcService {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Check if a string looks like a Cashu token.
|
||||
/// Check if a string looks like a Cashu token or payment request.
|
||||
static bool _isCashuToken(String text) {
|
||||
final lower = text.toLowerCase().trim();
|
||||
return lower.startsWith('cashua') ||
|
||||
lower.startsWith('cashub') ||
|
||||
lower.startsWith('creqa');
|
||||
lower.startsWith('creqa') ||
|
||||
lower.startsWith('creqb') ||
|
||||
lower.startsWith('bitcoin:');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
/// Servicio para obtener precios de Bitcoin usando Yadio API
|
||||
@@ -86,4 +87,59 @@ class PriceService {
|
||||
// Retornar en centavos
|
||||
return BigInt.from((fiatAmount * 100).round());
|
||||
}
|
||||
|
||||
// --- Blink API (precios históricos) ---
|
||||
|
||||
static const _blinkUrl = 'https://api.blink.sv/graphql';
|
||||
|
||||
/// Obtiene precios históricos de BTC desde Blink API.
|
||||
/// range: ONE_DAY, ONE_WEEK, ONE_MONTH, ONE_YEAR, FIVE_YEARS
|
||||
static Future<List<PricePoint>> getHistoricalPrices({
|
||||
String range = 'ONE_DAY',
|
||||
}) async {
|
||||
try {
|
||||
final response = await http.post(
|
||||
Uri.parse(_blinkUrl),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode({
|
||||
'query':
|
||||
'query { btcPriceList(range: $range) { price { base offset } timestamp } }',
|
||||
}),
|
||||
).timeout(const Duration(seconds: 15));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Error HTTP: ${response.statusCode}');
|
||||
}
|
||||
|
||||
final json = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
final data = json['data'] as Map<String, dynamic>?;
|
||||
if (data == null) throw Exception('No data in response');
|
||||
|
||||
final list = data['btcPriceList'] as List?;
|
||||
if (list == null || list.isEmpty) {
|
||||
throw Exception('No historical prices in response');
|
||||
}
|
||||
|
||||
return list.map((point) {
|
||||
final price = point['price'] as Map<String, dynamic>;
|
||||
final base = (price['base'] as num).toDouble();
|
||||
final offset = (price['offset'] as num).toInt();
|
||||
// base / 10^offset = USD cents → /100 = USD
|
||||
final priceUsd = base / pow(10, offset) / 100;
|
||||
final timestamp = (point['timestamp'] as num).toInt();
|
||||
|
||||
return PricePoint(timestamp: timestamp, priceUsd: priceUsd);
|
||||
}).toList();
|
||||
} catch (e) {
|
||||
throw Exception('Error obteniendo precios históricos: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Punto de precio histórico de BTC
|
||||
class PricePoint {
|
||||
final int timestamp;
|
||||
final double priceUsd;
|
||||
|
||||
PricePoint({required this.timestamp, required this.priceUsd});
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class ProofService {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
final dbPath = '${dir.path}/elcaju_wallet.sqlite';
|
||||
|
||||
_db = await openDatabase(dbPath);
|
||||
_db = await openDatabase(dbPath, singleInstance: false);
|
||||
return _db!;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/// Builds a BIP-321 unified payment URI.
|
||||
///
|
||||
/// Format: BITCOIN:?LIGHTNING=LNBC...&CREQ=CREQB1...
|
||||
/// All uppercase for optimal QR encoding (alphanumeric mode).
|
||||
///
|
||||
/// Ref: NUT-26 "BIP-321 Integration" section.
|
||||
String buildUnifiedUri({
|
||||
required String creqB,
|
||||
String? bolt11,
|
||||
}) {
|
||||
final buffer = StringBuffer('bitcoin:?');
|
||||
if (bolt11 != null) {
|
||||
buffer.write('lightning=');
|
||||
buffer.write(bolt11.toUpperCase());
|
||||
buffer.write('&');
|
||||
}
|
||||
buffer.write('creq=');
|
||||
buffer.write(creqB.toUpperCase());
|
||||
return buffer.toString();
|
||||
}
|
||||
@@ -15,7 +15,7 @@ enum IncomingDataType {
|
||||
cashuToken, // cashuA... / cashuB...
|
||||
lightningInvoice, // lnbc... / lntb... / lnbcrt...
|
||||
mintUrl, // https://...
|
||||
paymentRequest, // creqA... (post-MVP, Cashu payment request)
|
||||
paymentRequest, // creqA/creqB/bitcoin:?creq= (NUT-18/26 payment request)
|
||||
unknown,
|
||||
}
|
||||
|
||||
@@ -103,8 +103,31 @@ class IncomingDataParser {
|
||||
);
|
||||
}
|
||||
|
||||
// Payment Request (creqA...)
|
||||
if (lower.startsWith('creqa')) {
|
||||
// BIP-321 unified URI: bitcoin:?lightning=...&creq=...
|
||||
// Detect as paymentRequest but also extract the lightning invoice
|
||||
if (lower.startsWith('bitcoin:')) {
|
||||
final bolt11 = _extractBip321Param(trimmed, 'lightning');
|
||||
final creq = _extractBip321Param(trimmed, 'creq');
|
||||
|
||||
if (creq != null) {
|
||||
return ParsedData(
|
||||
type: IncomingDataType.paymentRequest,
|
||||
raw: trimmed,
|
||||
invoiceBolt11: bolt11,
|
||||
);
|
||||
}
|
||||
// bitcoin: URI with only lightning= (no creq)
|
||||
if (bolt11 != null) {
|
||||
return ParsedData(
|
||||
type: IncomingDataType.lightningInvoice,
|
||||
raw: trimmed,
|
||||
invoiceBolt11: bolt11,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Payment Request: creqA (NUT-18), creqB/CREQB1 (NUT-26)
|
||||
if (lower.startsWith('creqa') || lower.startsWith('creqb')) {
|
||||
return ParsedData(
|
||||
type: IncomingDataType.paymentRequest,
|
||||
raw: trimmed,
|
||||
@@ -193,7 +216,35 @@ class IncomingDataParser {
|
||||
case ScanMode.cashuOnly:
|
||||
return data.type == IncomingDataType.cashuToken;
|
||||
case ScanMode.invoiceOnly:
|
||||
return data.type == IncomingDataType.lightningInvoice;
|
||||
// Accept pure invoices and BIP-321 URIs that contain a lightning invoice
|
||||
return data.type == IncomingDataType.lightningInvoice ||
|
||||
(data.type == IncomingDataType.paymentRequest &&
|
||||
data.invoiceBolt11 != null);
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract a query parameter value from a BIP-321 URI.
|
||||
/// Case-insensitive key matching, handles percent-encoding.
|
||||
static String? _extractBip321Param(String uri, String key) {
|
||||
final qIndex = uri.indexOf('?');
|
||||
if (qIndex < 0) return null;
|
||||
final query = uri.substring(qIndex + 1);
|
||||
for (final param in query.split('&')) {
|
||||
final eqIndex = param.indexOf('=');
|
||||
if (eqIndex < 0) continue;
|
||||
final k = param.substring(0, eqIndex);
|
||||
if (k.toLowerCase() == key.toLowerCase()) {
|
||||
final rawValue = param.substring(eqIndex + 1);
|
||||
if (rawValue.isEmpty) continue;
|
||||
try {
|
||||
final value = Uri.decodeComponent(rawValue);
|
||||
if (value.isEmpty) continue;
|
||||
return value;
|
||||
} on ArgumentError {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+50
-2
@@ -118,6 +118,7 @@
|
||||
"confirm": "Bestätigen",
|
||||
"cancel": "Abbrechen",
|
||||
"insufficientBalance": "Unzureichendes Guthaben",
|
||||
"feeExceedsAmount": "Die Gebühr übersteigt den Sendebetrag",
|
||||
"tokenCreationError": "Fehler beim Erstellen des Tokens: {error}",
|
||||
"@tokenCreationError": {
|
||||
"placeholders": {
|
||||
@@ -502,7 +503,22 @@
|
||||
"scanLightningInvoiceHint": "Scanne eine Lightning Rechnung (lnbc...)",
|
||||
"addMintQuestion": "Diesen Mint hinzufügen?",
|
||||
"cameraPermissionDenied": "Kamera-Berechtigung verweigert",
|
||||
"paymentRequestNotSupported": "Zahlungsanfragen werden noch nicht unterstützt",
|
||||
"paymentRequestTitle": "Zahlungsanfrage",
|
||||
"paymentRequestFrom": "Anfrage von",
|
||||
"paymentRequestAmount": "Angeforderter Betrag",
|
||||
"paymentRequestDescription": "Beschreibung",
|
||||
"paymentRequestMints": "Akzeptierte Mints",
|
||||
"paymentRequestAnyMint": "Jeder Mint",
|
||||
"paymentRequestPay": "Bezahlen",
|
||||
"paymentRequestPaying": "Bezahle...",
|
||||
"paymentRequestSuccess": "Zahlung erfolgreich gesendet",
|
||||
"paymentRequestNoTransport": "Diese Anfrage hat keine konfigurierte Zustellmethode",
|
||||
"paymentRequestTransport": "Transport",
|
||||
"paymentRequestMintNotAccepted": "Dein aktiver Mint ist nicht in der Liste der akzeptierten Mints",
|
||||
"paymentRequestUnitMismatch": "Inkompatible Einheit: Anfrage erfordert {unit}",
|
||||
"paymentRequestInsufficientBalance": "Unzureichendes Guthaben",
|
||||
"paymentRequestErrorParsing": "Fehler beim Lesen der Zahlungsanfrage",
|
||||
|
||||
"p2pkTitle": "P2PK-Schlüssel",
|
||||
"p2pkSettingsDescription": "Gesperrtes ecash empfangen",
|
||||
"p2pkExperimental": "P2PK ist experimentell. Mit Vorsicht verwenden.",
|
||||
@@ -534,5 +550,37 @@
|
||||
"p2pkErrorInvalidNsec": "Ungültiger nsec",
|
||||
"p2pkErrorKeyAlreadyExists": "Dieser Schlüssel existiert bereits",
|
||||
"p2pkErrorKeyNotFound": "Schlüssel nicht gefunden",
|
||||
"p2pkErrorCannotDeletePrimary": "Primärschlüssel kann nicht gelöscht werden"
|
||||
"p2pkErrorCannotDeletePrimary": "Primärschlüssel kann nicht gelöscht werden",
|
||||
|
||||
"request": "Anfordern",
|
||||
"requestPayment": "Zahlung anfordern",
|
||||
"requestPaymentDescription": "Einheitliche Zahlungsanforderung erstellen",
|
||||
"generateRequest": "Anforderung erstellen",
|
||||
"generatingRequest": "Anforderung wird erstellt...",
|
||||
"requestPaymentReceived": "Zahlung erhalten",
|
||||
"requestDescriptionHint": "Beschreibung (optional)",
|
||||
"universal": "Universal",
|
||||
"copiedToClipboard": "In die Zwischenablage kopiert",
|
||||
|
||||
"swap": "Tauschen",
|
||||
"swapDescription": "Zwischen Sats und USD wechseln",
|
||||
"swapFrom": "Von",
|
||||
"swapTo": "Nach",
|
||||
"swapAction": "Tauschen",
|
||||
"swapEstimatedFee": "Geschätzte Gebühr",
|
||||
"swapUseAll": "Alles verwenden",
|
||||
"swapMinimum": "Minimum: {amount}",
|
||||
"swapProcessing": "Swap wird verarbeitet...",
|
||||
"swapSuccess": "Swap abgeschlossen",
|
||||
"swapErrorInsufficient": "Unzureichendes Guthaben",
|
||||
"swapErrorExpired": "Angebot abgelaufen",
|
||||
"swapErrorGeneric": "Swap-Fehler: {error}",
|
||||
"swapChartUnavailable": "Preis nicht verfügbar · Tippen zum Wiederholen",
|
||||
"swapChartMinMax": "24h Min: {minPrice} — Max: {maxPrice}",
|
||||
"privacyPolicy": "Datenschutz",
|
||||
"privacyTitle": "WIR SAMMELN NICHTS",
|
||||
"privacyGoodbye": "TSCHÜSS",
|
||||
"privacyKeepReading": "(lies weiter, wenn du willst…)",
|
||||
"privacyBody": "Wir wissen nicht, wer du bist\nWir wissen nicht, wie viel du hast\nWir wissen nicht, was du tust",
|
||||
"privacyConclusion": "Der beste Weg, deine Daten zu schützen,\nist sie nicht zu haben"
|
||||
}
|
||||
|
||||
+54
-2
@@ -103,6 +103,7 @@
|
||||
"confirm": "Confirm",
|
||||
"cancel": "Cancel",
|
||||
"insufficientBalance": "Insufficient balance",
|
||||
"feeExceedsAmount": "Fee exceeds the amount to send",
|
||||
"tokenCreationError": "Error creating token: {error}",
|
||||
|
||||
"tokenCreated": "Token created",
|
||||
@@ -390,7 +391,26 @@
|
||||
"scanLightningInvoiceHint": "Scan a Lightning invoice (lnbc...)",
|
||||
"addMintQuestion": "Add this mint?",
|
||||
"cameraPermissionDenied": "Camera permission denied",
|
||||
"paymentRequestNotSupported": "Payment requests are not yet supported",
|
||||
"paymentRequestTitle": "Payment Request",
|
||||
"paymentRequestFrom": "Request from",
|
||||
"paymentRequestAmount": "Requested amount",
|
||||
"paymentRequestDescription": "Description",
|
||||
"paymentRequestMints": "Accepted mints",
|
||||
"paymentRequestAnyMint": "Any mint",
|
||||
"paymentRequestPay": "Pay",
|
||||
"paymentRequestPaying": "Paying...",
|
||||
"paymentRequestSuccess": "Payment sent successfully",
|
||||
"paymentRequestNoTransport": "This request has no delivery method configured",
|
||||
"paymentRequestTransport": "Transport",
|
||||
"paymentRequestMintNotAccepted": "Your active mint is not in the list of accepted mints",
|
||||
"paymentRequestUnitMismatch": "Incompatible unit: request requires {unit}",
|
||||
"@paymentRequestUnitMismatch": {
|
||||
"placeholders": {
|
||||
"unit": { "type": "String" }
|
||||
}
|
||||
},
|
||||
"paymentRequestInsufficientBalance": "Insufficient balance",
|
||||
"paymentRequestErrorParsing": "Error reading payment request",
|
||||
|
||||
"p2pkTitle": "P2PK Keys",
|
||||
"p2pkSettingsDescription": "Receive locked ecash",
|
||||
@@ -423,5 +443,37 @@
|
||||
"p2pkErrorInvalidNsec": "Invalid nsec",
|
||||
"p2pkErrorKeyAlreadyExists": "This key already exists",
|
||||
"p2pkErrorKeyNotFound": "Key not found",
|
||||
"p2pkErrorCannotDeletePrimary": "Cannot delete primary key"
|
||||
"p2pkErrorCannotDeletePrimary": "Cannot delete primary key",
|
||||
|
||||
"request": "Request",
|
||||
"requestPayment": "Request Payment",
|
||||
"requestPaymentDescription": "Generate unified payment request",
|
||||
"generateRequest": "Generate Request",
|
||||
"generatingRequest": "Generating request...",
|
||||
"requestPaymentReceived": "Payment received",
|
||||
"requestDescriptionHint": "Description (optional)",
|
||||
"universal": "Universal",
|
||||
"copiedToClipboard": "Copied to clipboard",
|
||||
|
||||
"swap": "Swap",
|
||||
"swapDescription": "Convert between sats and USD",
|
||||
"swapFrom": "From",
|
||||
"swapTo": "To",
|
||||
"swapAction": "Swap",
|
||||
"swapEstimatedFee": "Estimated fee",
|
||||
"swapUseAll": "Use all",
|
||||
"swapMinimum": "Minimum: {amount}",
|
||||
"swapProcessing": "Processing swap...",
|
||||
"swapSuccess": "Swap completed",
|
||||
"swapErrorInsufficient": "Insufficient balance",
|
||||
"swapErrorExpired": "Quote has expired",
|
||||
"swapErrorGeneric": "Swap error: {error}",
|
||||
"swapChartUnavailable": "Price unavailable · Tap to retry",
|
||||
"swapChartMinMax": "24h Min: {minPrice} — Max: {maxPrice}",
|
||||
"privacyPolicy": "Privacy policy",
|
||||
"privacyTitle": "WE COLLECT NOTHING",
|
||||
"privacyGoodbye": "BYE",
|
||||
"privacyKeepReading": "(keep reading if you want…)",
|
||||
"privacyBody": "We don't know who you are\nWe don't know how much you have\nWe don't know what you do",
|
||||
"privacyConclusion": "The best way to protect your data\nis not to have it"
|
||||
}
|
||||
|
||||
+70
-2
@@ -118,6 +118,7 @@
|
||||
"confirm": "Confirmar",
|
||||
"cancel": "Cancelar",
|
||||
"insufficientBalance": "Balance insuficiente",
|
||||
"feeExceedsAmount": "La comisión supera el monto a enviar",
|
||||
"tokenCreationError": "Error al crear token: {error}",
|
||||
"@tokenCreationError": {
|
||||
"placeholders": {
|
||||
@@ -510,7 +511,26 @@
|
||||
"scanLightningInvoiceHint": "Escanea un invoice Lightning (lnbc...)",
|
||||
"addMintQuestion": "¿Agregar este mint?",
|
||||
"cameraPermissionDenied": "Permiso de cámara denegado",
|
||||
"paymentRequestNotSupported": "Los payment requests aún no están soportados",
|
||||
"paymentRequestTitle": "Solicitud de pago",
|
||||
"paymentRequestFrom": "Solicitud de",
|
||||
"paymentRequestAmount": "Monto solicitado",
|
||||
"paymentRequestDescription": "Descripción",
|
||||
"paymentRequestMints": "Mints aceptados",
|
||||
"paymentRequestAnyMint": "Cualquier mint",
|
||||
"paymentRequestPay": "Pagar",
|
||||
"paymentRequestPaying": "Pagando...",
|
||||
"paymentRequestSuccess": "Pago enviado correctamente",
|
||||
"paymentRequestNoTransport": "Esta solicitud no tiene método de entrega configurado",
|
||||
"paymentRequestTransport": "Transporte",
|
||||
"paymentRequestMintNotAccepted": "Tu mint activo no está en la lista de mints aceptados",
|
||||
"paymentRequestUnitMismatch": "Unidad incompatible: la solicitud requiere {unit}",
|
||||
"@paymentRequestUnitMismatch": {
|
||||
"placeholders": {
|
||||
"unit": { "type": "String" }
|
||||
}
|
||||
},
|
||||
"paymentRequestInsufficientBalance": "Balance insuficiente",
|
||||
"paymentRequestErrorParsing": "Error al leer la solicitud de pago",
|
||||
|
||||
"p2pkTitle": "Claves P2PK",
|
||||
"p2pkSettingsDescription": "Recibir ecash bloqueado",
|
||||
@@ -543,5 +563,53 @@
|
||||
"p2pkErrorInvalidNsec": "nsec inválido",
|
||||
"p2pkErrorKeyAlreadyExists": "Esta clave ya existe",
|
||||
"p2pkErrorKeyNotFound": "Clave no encontrada",
|
||||
"p2pkErrorCannotDeletePrimary": "No se puede eliminar la clave principal"
|
||||
"p2pkErrorCannotDeletePrimary": "No se puede eliminar la clave principal",
|
||||
|
||||
"request": "Solicitar",
|
||||
"requestPayment": "Solicitar pago",
|
||||
"requestPaymentDescription": "Generar solicitud de pago unificada",
|
||||
"generateRequest": "Generar solicitud",
|
||||
"generatingRequest": "Generando solicitud...",
|
||||
"requestPaymentReceived": "Pago recibido",
|
||||
"requestDescriptionHint": "Descripción (opcional)",
|
||||
"universal": "Universal",
|
||||
"copiedToClipboard": "Copiado al portapapeles",
|
||||
|
||||
"swap": "Cambiar",
|
||||
"swapDescription": "Convertir entre sats y USD",
|
||||
"swapFrom": "De",
|
||||
"swapTo": "A",
|
||||
"swapAction": "Cambiar",
|
||||
"swapEstimatedFee": "Fee estimado",
|
||||
"swapUseAll": "Usar todo",
|
||||
"swapMinimum": "Mínimo: {amount}",
|
||||
"@swapMinimum": {
|
||||
"placeholders": {
|
||||
"amount": { "type": "String" }
|
||||
}
|
||||
},
|
||||
"swapProcessing": "Procesando swap...",
|
||||
"swapSuccess": "Swap completado",
|
||||
"swapErrorInsufficient": "Saldo insuficiente",
|
||||
"swapErrorExpired": "La cotización ha expirado",
|
||||
"swapErrorGeneric": "Error en el swap: {error}",
|
||||
"@swapErrorGeneric": {
|
||||
"placeholders": {
|
||||
"error": { "type": "String" }
|
||||
}
|
||||
},
|
||||
"swapChartUnavailable": "Precio no disponible · Toca para reintentar",
|
||||
"swapChartMinMax": "24h Mín: {minPrice} — Máx: {maxPrice}",
|
||||
"@swapChartMinMax": {
|
||||
"placeholders": {
|
||||
"minPrice": { "type": "String" },
|
||||
"maxPrice": { "type": "String" }
|
||||
}
|
||||
},
|
||||
"privacyPolicy": "Política de privacidad",
|
||||
"privacyTitle": "NO RECOPILAMOS NADA",
|
||||
"privacyGoodbye": "ADIÓS",
|
||||
"privacyKeepReading": "(sigue leyendo si quieres…)",
|
||||
"privacyBody": "No sabemos quién eres\nNo sabemos cuánto tienes\nNo sabemos qué haces",
|
||||
"privacyConclusion": "La mejor forma de proteger tus datos\nes no tenerlos"
|
||||
}
|
||||
|
||||
+50
-2
@@ -118,6 +118,7 @@
|
||||
"confirm": "Confirmer",
|
||||
"cancel": "Annuler",
|
||||
"insufficientBalance": "Solde insuffisant",
|
||||
"feeExceedsAmount": "Les frais dépassent le montant à envoyer",
|
||||
"tokenCreationError": "Erreur de création du token : {error}",
|
||||
"@tokenCreationError": {
|
||||
"placeholders": {
|
||||
@@ -502,7 +503,22 @@
|
||||
"scanLightningInvoiceHint": "Scannez une facture Lightning (lnbc...)",
|
||||
"addMintQuestion": "Ajouter ce mint ?",
|
||||
"cameraPermissionDenied": "Permission de la caméra refusée",
|
||||
"paymentRequestNotSupported": "Les demandes de paiement ne sont pas encore prises en charge",
|
||||
"paymentRequestTitle": "Demande de paiement",
|
||||
"paymentRequestFrom": "Demande de",
|
||||
"paymentRequestAmount": "Montant demandé",
|
||||
"paymentRequestDescription": "Description",
|
||||
"paymentRequestMints": "Mints acceptés",
|
||||
"paymentRequestAnyMint": "N'importe quel mint",
|
||||
"paymentRequestPay": "Payer",
|
||||
"paymentRequestPaying": "Paiement en cours...",
|
||||
"paymentRequestSuccess": "Paiement envoyé avec succès",
|
||||
"paymentRequestNoTransport": "Cette demande n'a pas de méthode de livraison configurée",
|
||||
"paymentRequestTransport": "Transport",
|
||||
"paymentRequestMintNotAccepted": "Votre mint actif n'est pas dans la liste des mints acceptés",
|
||||
"paymentRequestUnitMismatch": "Unité incompatible : la demande nécessite {unit}",
|
||||
"paymentRequestInsufficientBalance": "Solde insuffisant",
|
||||
"paymentRequestErrorParsing": "Erreur lors de la lecture de la demande de paiement",
|
||||
|
||||
"p2pkTitle": "Clés P2PK",
|
||||
"p2pkSettingsDescription": "Recevoir ecash verrouillé",
|
||||
"p2pkExperimental": "P2PK est expérimental. Utiliser avec prudence.",
|
||||
@@ -534,5 +550,37 @@
|
||||
"p2pkErrorInvalidNsec": "nsec invalide",
|
||||
"p2pkErrorKeyAlreadyExists": "Cette clé existe déjà",
|
||||
"p2pkErrorKeyNotFound": "Clé non trouvée",
|
||||
"p2pkErrorCannotDeletePrimary": "Impossible de supprimer la clé principale"
|
||||
"p2pkErrorCannotDeletePrimary": "Impossible de supprimer la clé principale",
|
||||
|
||||
"request": "Demander",
|
||||
"requestPayment": "Demander un paiement",
|
||||
"requestPaymentDescription": "Générer une demande de paiement unifiée",
|
||||
"generateRequest": "Générer la demande",
|
||||
"generatingRequest": "Génération en cours...",
|
||||
"requestPaymentReceived": "Paiement reçu",
|
||||
"requestDescriptionHint": "Description (facultatif)",
|
||||
"universal": "Universel",
|
||||
"copiedToClipboard": "Copié dans le presse-papiers",
|
||||
|
||||
"swap": "Échanger",
|
||||
"swapDescription": "Convertir entre sats et USD",
|
||||
"swapFrom": "De",
|
||||
"swapTo": "Vers",
|
||||
"swapAction": "Échanger",
|
||||
"swapEstimatedFee": "Frais estimés",
|
||||
"swapUseAll": "Tout utiliser",
|
||||
"swapMinimum": "Minimum : {amount}",
|
||||
"swapProcessing": "Swap en cours...",
|
||||
"swapSuccess": "Swap terminé",
|
||||
"swapErrorInsufficient": "Solde insuffisant",
|
||||
"swapErrorExpired": "Le devis a expiré",
|
||||
"swapErrorGeneric": "Erreur de swap : {error}",
|
||||
"swapChartUnavailable": "Prix indisponible · Appuyez pour réessayer",
|
||||
"swapChartMinMax": "24h Min : {minPrice} — Max : {maxPrice}",
|
||||
"privacyPolicy": "Politique de confidentialité",
|
||||
"privacyTitle": "NOUS NE COLLECTONS RIEN",
|
||||
"privacyGoodbye": "AU REVOIR",
|
||||
"privacyKeepReading": "(continue à lire si tu veux…)",
|
||||
"privacyBody": "Nous ne savons pas qui tu es\nNous ne savons pas combien tu as\nNous ne savons pas ce que tu fais",
|
||||
"privacyConclusion": "La meilleure façon de protéger tes données,\nc'est de ne pas les avoir"
|
||||
}
|
||||
|
||||
+50
-2
@@ -118,6 +118,7 @@
|
||||
"confirm": "Conferma",
|
||||
"cancel": "Annulla",
|
||||
"insufficientBalance": "Saldo insufficiente",
|
||||
"feeExceedsAmount": "La commissione supera l'importo da inviare",
|
||||
"tokenCreationError": "Errore creazione token: {error}",
|
||||
"@tokenCreationError": {
|
||||
"placeholders": {
|
||||
@@ -502,7 +503,22 @@
|
||||
"scanLightningInvoiceHint": "Scansiona una fattura Lightning (lnbc...)",
|
||||
"addMintQuestion": "Aggiungere questo mint?",
|
||||
"cameraPermissionDenied": "Permesso fotocamera negato",
|
||||
"paymentRequestNotSupported": "Le richieste di pagamento non sono ancora supportate",
|
||||
"paymentRequestTitle": "Richiesta di pagamento",
|
||||
"paymentRequestFrom": "Richiesta da",
|
||||
"paymentRequestAmount": "Importo richiesto",
|
||||
"paymentRequestDescription": "Descrizione",
|
||||
"paymentRequestMints": "Mint accettati",
|
||||
"paymentRequestAnyMint": "Qualsiasi mint",
|
||||
"paymentRequestPay": "Paga",
|
||||
"paymentRequestPaying": "Pagamento in corso...",
|
||||
"paymentRequestSuccess": "Pagamento inviato con successo",
|
||||
"paymentRequestNoTransport": "Questa richiesta non ha un metodo di consegna configurato",
|
||||
"paymentRequestTransport": "Trasporto",
|
||||
"paymentRequestMintNotAccepted": "Il tuo mint attivo non è nella lista dei mint accettati",
|
||||
"paymentRequestUnitMismatch": "Unità incompatibile: la richiesta richiede {unit}",
|
||||
"paymentRequestInsufficientBalance": "Saldo insufficiente",
|
||||
"paymentRequestErrorParsing": "Errore nella lettura della richiesta di pagamento",
|
||||
|
||||
"p2pkTitle": "Chiavi P2PK",
|
||||
"p2pkSettingsDescription": "Ricevi ecash bloccato",
|
||||
"p2pkExperimental": "P2PK è sperimentale. Usare con cautela.",
|
||||
@@ -534,5 +550,37 @@
|
||||
"p2pkErrorInvalidNsec": "nsec non valido",
|
||||
"p2pkErrorKeyAlreadyExists": "Questa chiave esiste già",
|
||||
"p2pkErrorKeyNotFound": "Chiave non trovata",
|
||||
"p2pkErrorCannotDeletePrimary": "Impossibile eliminare la chiave principale"
|
||||
"p2pkErrorCannotDeletePrimary": "Impossibile eliminare la chiave principale",
|
||||
|
||||
"request": "Richiedi",
|
||||
"requestPayment": "Richiedi pagamento",
|
||||
"requestPaymentDescription": "Genera richiesta di pagamento unificata",
|
||||
"generateRequest": "Genera richiesta",
|
||||
"generatingRequest": "Generazione in corso...",
|
||||
"requestPaymentReceived": "Pagamento ricevuto",
|
||||
"requestDescriptionHint": "Descrizione (opzionale)",
|
||||
"universal": "Universale",
|
||||
"copiedToClipboard": "Copiato negli appunti",
|
||||
|
||||
"swap": "Scambia",
|
||||
"swapDescription": "Converti tra sats e USD",
|
||||
"swapFrom": "Da",
|
||||
"swapTo": "A",
|
||||
"swapAction": "Scambia",
|
||||
"swapEstimatedFee": "Commissione stimata",
|
||||
"swapUseAll": "Usa tutto",
|
||||
"swapMinimum": "Minimo: {amount}",
|
||||
"swapProcessing": "Swap in corso...",
|
||||
"swapSuccess": "Swap completato",
|
||||
"swapErrorInsufficient": "Saldo insufficiente",
|
||||
"swapErrorExpired": "Il preventivo è scaduto",
|
||||
"swapErrorGeneric": "Errore swap: {error}",
|
||||
"swapChartUnavailable": "Prezzo non disponibile · Tocca per riprovare",
|
||||
"swapChartMinMax": "24h Min: {minPrice} — Max: {maxPrice}",
|
||||
"privacyPolicy": "Informativa sulla privacy",
|
||||
"privacyTitle": "NON RACCOGLIAMO NULLA",
|
||||
"privacyGoodbye": "CIAO",
|
||||
"privacyKeepReading": "(continua a leggere se vuoi…)",
|
||||
"privacyBody": "Non sappiamo chi sei\nNon sappiamo quanto hai\nNon sappiamo cosa fai",
|
||||
"privacyConclusion": "Il modo migliore per proteggere i tuoi dati\nè non averli"
|
||||
}
|
||||
|
||||
+50
-2
@@ -118,6 +118,7 @@
|
||||
"confirm": "確認",
|
||||
"cancel": "キャンセル",
|
||||
"insufficientBalance": "残高不足",
|
||||
"feeExceedsAmount": "手数料が送金額を超えています",
|
||||
"tokenCreationError": "トークン作成エラー:{error}",
|
||||
"@tokenCreationError": {
|
||||
"placeholders": {
|
||||
@@ -502,7 +503,22 @@
|
||||
"scanLightningInvoiceHint": "Lightningインボイスをスキャン(lnbc...)",
|
||||
"addMintQuestion": "このMintを追加しますか?",
|
||||
"cameraPermissionDenied": "カメラの許可が拒否されました",
|
||||
"paymentRequestNotSupported": "支払いリクエストはまだサポートされていません",
|
||||
"paymentRequestTitle": "支払いリクエスト",
|
||||
"paymentRequestFrom": "リクエスト元",
|
||||
"paymentRequestAmount": "リクエスト金額",
|
||||
"paymentRequestDescription": "説明",
|
||||
"paymentRequestMints": "対応ミント",
|
||||
"paymentRequestAnyMint": "すべてのミント",
|
||||
"paymentRequestPay": "支払う",
|
||||
"paymentRequestPaying": "支払い中...",
|
||||
"paymentRequestSuccess": "支払いが正常に送信されました",
|
||||
"paymentRequestNoTransport": "このリクエストには配信方法が設定されていません",
|
||||
"paymentRequestTransport": "トランスポート",
|
||||
"paymentRequestMintNotAccepted": "アクティブなミントは対応ミントリストにありません",
|
||||
"paymentRequestUnitMismatch": "単位が互換性がありません:リクエストには{unit}が必要です",
|
||||
"paymentRequestInsufficientBalance": "残高不足",
|
||||
"paymentRequestErrorParsing": "支払いリクエストの読み取りエラー",
|
||||
|
||||
"p2pkTitle": "P2PK鍵",
|
||||
"p2pkSettingsDescription": "ロックされたecashを受け取る",
|
||||
"p2pkExperimental": "P2PKは実験的機能です。注意してご使用ください。",
|
||||
@@ -534,5 +550,37 @@
|
||||
"p2pkErrorInvalidNsec": "無効なnsec",
|
||||
"p2pkErrorKeyAlreadyExists": "この鍵は既に存在します",
|
||||
"p2pkErrorKeyNotFound": "鍵が見つかりません",
|
||||
"p2pkErrorCannotDeletePrimary": "プライマリ鍵は削除できません"
|
||||
"p2pkErrorCannotDeletePrimary": "プライマリ鍵は削除できません",
|
||||
|
||||
"request": "リクエスト",
|
||||
"requestPayment": "支払いをリクエスト",
|
||||
"requestPaymentDescription": "統合支払いリクエストを生成",
|
||||
"generateRequest": "リクエストを生成",
|
||||
"generatingRequest": "生成中...",
|
||||
"requestPaymentReceived": "支払いを受け取りました",
|
||||
"requestDescriptionHint": "説明(任意)",
|
||||
"universal": "ユニバーサル",
|
||||
"copiedToClipboard": "クリップボードにコピーしました",
|
||||
|
||||
"swap": "交換",
|
||||
"swapDescription": "SatsとUSDを変換",
|
||||
"swapFrom": "送信元",
|
||||
"swapTo": "送信先",
|
||||
"swapAction": "交換",
|
||||
"swapEstimatedFee": "推定手数料",
|
||||
"swapUseAll": "全額使用",
|
||||
"swapMinimum": "最小: {amount}",
|
||||
"swapProcessing": "スワップ処理中...",
|
||||
"swapSuccess": "スワップ完了",
|
||||
"swapErrorInsufficient": "残高不足",
|
||||
"swapErrorExpired": "見積もりの有効期限切れ",
|
||||
"swapErrorGeneric": "スワップエラー: {error}",
|
||||
"swapChartUnavailable": "価格を取得できません・タップで再試行",
|
||||
"swapChartMinMax": "24h 安値: {minPrice} — 高値: {maxPrice}",
|
||||
"privacyPolicy": "プライバシーポリシー",
|
||||
"privacyTitle": "何も収集しません",
|
||||
"privacyGoodbye": "さようなら",
|
||||
"privacyKeepReading": "(読み続けたければどうぞ…)",
|
||||
"privacyBody": "あなたが誰か知りません\nいくら持っているか知りません\n何をしているか知りません",
|
||||
"privacyConclusion": "データを守る最善の方法は\nデータを持たないことです"
|
||||
}
|
||||
|
||||
+50
-2
@@ -118,6 +118,7 @@
|
||||
"confirm": "확인",
|
||||
"cancel": "취소",
|
||||
"insufficientBalance": "잔액 부족",
|
||||
"feeExceedsAmount": "수수료가 송금액을 초과합니다",
|
||||
"tokenCreationError": "토큰 생성 오류: {error}",
|
||||
"@tokenCreationError": {
|
||||
"placeholders": {
|
||||
@@ -502,7 +503,22 @@
|
||||
"scanLightningInvoiceHint": "Lightning 인보이스 스캔 (lnbc...)",
|
||||
"addMintQuestion": "이 mint를 추가하시겠습니까?",
|
||||
"cameraPermissionDenied": "카메라 권한이 거부되었습니다",
|
||||
"paymentRequestNotSupported": "결제 요청은 아직 지원되지 않습니다",
|
||||
"paymentRequestTitle": "결제 요청",
|
||||
"paymentRequestFrom": "요청자",
|
||||
"paymentRequestAmount": "요청 금액",
|
||||
"paymentRequestDescription": "설명",
|
||||
"paymentRequestMints": "허용된 민트",
|
||||
"paymentRequestAnyMint": "모든 민트",
|
||||
"paymentRequestPay": "결제",
|
||||
"paymentRequestPaying": "결제 중...",
|
||||
"paymentRequestSuccess": "결제가 성공적으로 전송되었습니다",
|
||||
"paymentRequestNoTransport": "이 요청에는 전달 방법이 설정되지 않았습니다",
|
||||
"paymentRequestTransport": "전송 방식",
|
||||
"paymentRequestMintNotAccepted": "활성 민트가 허용된 민트 목록에 없습니다",
|
||||
"paymentRequestUnitMismatch": "호환되지 않는 단위: 요청에 {unit}이(가) 필요합니다",
|
||||
"paymentRequestInsufficientBalance": "잔액 부족",
|
||||
"paymentRequestErrorParsing": "결제 요청을 읽는 중 오류 발생",
|
||||
|
||||
"p2pkTitle": "P2PK 키",
|
||||
"p2pkSettingsDescription": "잠긴 ecash 받기",
|
||||
"p2pkExperimental": "P2PK는 실험적 기능입니다. 주의하여 사용하세요.",
|
||||
@@ -534,5 +550,37 @@
|
||||
"p2pkErrorInvalidNsec": "유효하지 않은 nsec",
|
||||
"p2pkErrorKeyAlreadyExists": "이 키는 이미 존재합니다",
|
||||
"p2pkErrorKeyNotFound": "키를 찾을 수 없습니다",
|
||||
"p2pkErrorCannotDeletePrimary": "기본 키는 삭제할 수 없습니다"
|
||||
"p2pkErrorCannotDeletePrimary": "기본 키는 삭제할 수 없습니다",
|
||||
|
||||
"request": "요청",
|
||||
"requestPayment": "결제 요청",
|
||||
"requestPaymentDescription": "통합 결제 요청 생성",
|
||||
"generateRequest": "요청 생성",
|
||||
"generatingRequest": "생성 중...",
|
||||
"requestPaymentReceived": "결제가 수신되었습니다",
|
||||
"requestDescriptionHint": "설명 (선택사항)",
|
||||
"universal": "유니버설",
|
||||
"copiedToClipboard": "클립보드에 복사되었습니다",
|
||||
|
||||
"swap": "교환",
|
||||
"swapDescription": "Sats와 USD 간 변환",
|
||||
"swapFrom": "에서",
|
||||
"swapTo": "으로",
|
||||
"swapAction": "교환",
|
||||
"swapEstimatedFee": "예상 수수료",
|
||||
"swapUseAll": "전액 사용",
|
||||
"swapMinimum": "최소: {amount}",
|
||||
"swapProcessing": "교환 처리 중...",
|
||||
"swapSuccess": "교환 완료",
|
||||
"swapErrorInsufficient": "잔액 부족",
|
||||
"swapErrorExpired": "견적이 만료되었습니다",
|
||||
"swapErrorGeneric": "교환 오류: {error}",
|
||||
"swapChartUnavailable": "가격 불러오기 실패 · 탭하여 재시도",
|
||||
"swapChartMinMax": "24h 최저: {minPrice} — 최고: {maxPrice}",
|
||||
"privacyPolicy": "개인정보 처리방침",
|
||||
"privacyTitle": "우리는 아무것도 수집하지 않습니다",
|
||||
"privacyGoodbye": "안녕",
|
||||
"privacyKeepReading": "(계속 읽고 싶으면…)",
|
||||
"privacyBody": "당신이 누구인지 모릅니다\n얼마를 가지고 있는지 모릅니다\n무엇을 하는지 모릅니다",
|
||||
"privacyConclusion": "데이터를 보호하는 가장 좋은 방법은\n데이터를 갖지 않는 것입니다"
|
||||
}
|
||||
|
||||
@@ -661,6 +661,12 @@ abstract class L10n {
|
||||
/// **'Balance insuficiente'**
|
||||
String get insufficientBalance;
|
||||
|
||||
/// No description provided for @feeExceedsAmount.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'La comisión supera el monto a enviar'**
|
||||
String get feeExceedsAmount;
|
||||
|
||||
/// No description provided for @tokenCreationError.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
@@ -2017,11 +2023,95 @@ abstract class L10n {
|
||||
/// **'Permiso de cámara denegado'**
|
||||
String get cameraPermissionDenied;
|
||||
|
||||
/// No description provided for @paymentRequestNotSupported.
|
||||
/// No description provided for @paymentRequestTitle.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Los payment requests aún no están soportados'**
|
||||
String get paymentRequestNotSupported;
|
||||
/// **'Solicitud de pago'**
|
||||
String get paymentRequestTitle;
|
||||
|
||||
/// No description provided for @paymentRequestFrom.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Solicitud de'**
|
||||
String get paymentRequestFrom;
|
||||
|
||||
/// No description provided for @paymentRequestAmount.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Monto solicitado'**
|
||||
String get paymentRequestAmount;
|
||||
|
||||
/// No description provided for @paymentRequestDescription.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Descripción'**
|
||||
String get paymentRequestDescription;
|
||||
|
||||
/// No description provided for @paymentRequestMints.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Mints aceptados'**
|
||||
String get paymentRequestMints;
|
||||
|
||||
/// No description provided for @paymentRequestAnyMint.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Cualquier mint'**
|
||||
String get paymentRequestAnyMint;
|
||||
|
||||
/// No description provided for @paymentRequestPay.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Pagar'**
|
||||
String get paymentRequestPay;
|
||||
|
||||
/// No description provided for @paymentRequestPaying.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Pagando...'**
|
||||
String get paymentRequestPaying;
|
||||
|
||||
/// No description provided for @paymentRequestSuccess.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Pago enviado correctamente'**
|
||||
String get paymentRequestSuccess;
|
||||
|
||||
/// No description provided for @paymentRequestNoTransport.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Esta solicitud no tiene método de entrega configurado'**
|
||||
String get paymentRequestNoTransport;
|
||||
|
||||
/// No description provided for @paymentRequestTransport.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Transporte'**
|
||||
String get paymentRequestTransport;
|
||||
|
||||
/// No description provided for @paymentRequestMintNotAccepted.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Tu mint activo no está en la lista de mints aceptados'**
|
||||
String get paymentRequestMintNotAccepted;
|
||||
|
||||
/// No description provided for @paymentRequestUnitMismatch.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Unidad incompatible: la solicitud requiere {unit}'**
|
||||
String paymentRequestUnitMismatch(String unit);
|
||||
|
||||
/// No description provided for @paymentRequestInsufficientBalance.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Balance insuficiente'**
|
||||
String get paymentRequestInsufficientBalance;
|
||||
|
||||
/// No description provided for @paymentRequestErrorParsing.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Error al leer la solicitud de pago'**
|
||||
String get paymentRequestErrorParsing;
|
||||
|
||||
/// No description provided for @p2pkTitle.
|
||||
///
|
||||
@@ -2214,6 +2304,186 @@ abstract class L10n {
|
||||
/// In es, this message translates to:
|
||||
/// **'No se puede eliminar la clave principal'**
|
||||
String get p2pkErrorCannotDeletePrimary;
|
||||
|
||||
/// No description provided for @request.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Solicitar'**
|
||||
String get request;
|
||||
|
||||
/// No description provided for @requestPayment.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Solicitar pago'**
|
||||
String get requestPayment;
|
||||
|
||||
/// No description provided for @requestPaymentDescription.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Generar solicitud de pago unificada'**
|
||||
String get requestPaymentDescription;
|
||||
|
||||
/// No description provided for @generateRequest.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Generar solicitud'**
|
||||
String get generateRequest;
|
||||
|
||||
/// No description provided for @generatingRequest.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Generando solicitud...'**
|
||||
String get generatingRequest;
|
||||
|
||||
/// No description provided for @requestPaymentReceived.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Pago recibido'**
|
||||
String get requestPaymentReceived;
|
||||
|
||||
/// No description provided for @requestDescriptionHint.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Descripción (opcional)'**
|
||||
String get requestDescriptionHint;
|
||||
|
||||
/// No description provided for @universal.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Universal'**
|
||||
String get universal;
|
||||
|
||||
/// No description provided for @copiedToClipboard.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Copiado al portapapeles'**
|
||||
String get copiedToClipboard;
|
||||
|
||||
/// No description provided for @swap.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Cambiar'**
|
||||
String get swap;
|
||||
|
||||
/// No description provided for @swapDescription.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Convertir entre sats y USD'**
|
||||
String get swapDescription;
|
||||
|
||||
/// No description provided for @swapFrom.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'De'**
|
||||
String get swapFrom;
|
||||
|
||||
/// No description provided for @swapTo.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'A'**
|
||||
String get swapTo;
|
||||
|
||||
/// No description provided for @swapAction.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Cambiar'**
|
||||
String get swapAction;
|
||||
|
||||
/// No description provided for @swapEstimatedFee.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Fee estimado'**
|
||||
String get swapEstimatedFee;
|
||||
|
||||
/// No description provided for @swapUseAll.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Usar todo'**
|
||||
String get swapUseAll;
|
||||
|
||||
/// No description provided for @swapMinimum.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Mínimo: {amount}'**
|
||||
String swapMinimum(String amount);
|
||||
|
||||
/// No description provided for @swapProcessing.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Procesando swap...'**
|
||||
String get swapProcessing;
|
||||
|
||||
/// No description provided for @swapSuccess.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Swap completado'**
|
||||
String get swapSuccess;
|
||||
|
||||
/// No description provided for @swapErrorInsufficient.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Saldo insuficiente'**
|
||||
String get swapErrorInsufficient;
|
||||
|
||||
/// No description provided for @swapErrorExpired.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'La cotización ha expirado'**
|
||||
String get swapErrorExpired;
|
||||
|
||||
/// No description provided for @swapErrorGeneric.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Error en el swap: {error}'**
|
||||
String swapErrorGeneric(String error);
|
||||
|
||||
/// No description provided for @swapChartUnavailable.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Precio no disponible · Toca para reintentar'**
|
||||
String get swapChartUnavailable;
|
||||
|
||||
/// No description provided for @swapChartMinMax.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'24h Mín: {minPrice} — Máx: {maxPrice}'**
|
||||
String swapChartMinMax(String minPrice, String maxPrice);
|
||||
|
||||
/// No description provided for @privacyPolicy.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Política de privacidad'**
|
||||
String get privacyPolicy;
|
||||
|
||||
/// No description provided for @privacyTitle.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'NO RECOPILAMOS NADA'**
|
||||
String get privacyTitle;
|
||||
|
||||
/// No description provided for @privacyGoodbye.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'ADIÓS'**
|
||||
String get privacyGoodbye;
|
||||
|
||||
/// No description provided for @privacyKeepReading.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'(sigue leyendo si quieres…)'**
|
||||
String get privacyKeepReading;
|
||||
|
||||
/// No description provided for @privacyBody.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'No sabemos quién eres\nNo sabemos cuánto tienes\nNo sabemos qué haces'**
|
||||
String get privacyBody;
|
||||
|
||||
/// No description provided for @privacyConclusion.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'La mejor forma de proteger tus datos\nes no tenerlos'**
|
||||
String get privacyConclusion;
|
||||
}
|
||||
|
||||
class _L10nDelegate extends LocalizationsDelegate<L10n> {
|
||||
|
||||
@@ -295,6 +295,9 @@ class L10nDe extends L10n {
|
||||
@override
|
||||
String get insufficientBalance => 'Unzureichendes Guthaben';
|
||||
|
||||
@override
|
||||
String get feeExceedsAmount => 'Die Gebühr übersteigt den Sendebetrag';
|
||||
|
||||
@override
|
||||
String tokenCreationError(String error) {
|
||||
return 'Fehler beim Erstellen des Tokens: $error';
|
||||
@@ -1063,8 +1066,54 @@ class L10nDe extends L10n {
|
||||
String get cameraPermissionDenied => 'Kamera-Berechtigung verweigert';
|
||||
|
||||
@override
|
||||
String get paymentRequestNotSupported =>
|
||||
'Zahlungsanfragen werden noch nicht unterstützt';
|
||||
String get paymentRequestTitle => 'Zahlungsanfrage';
|
||||
|
||||
@override
|
||||
String get paymentRequestFrom => 'Anfrage von';
|
||||
|
||||
@override
|
||||
String get paymentRequestAmount => 'Angeforderter Betrag';
|
||||
|
||||
@override
|
||||
String get paymentRequestDescription => 'Beschreibung';
|
||||
|
||||
@override
|
||||
String get paymentRequestMints => 'Akzeptierte Mints';
|
||||
|
||||
@override
|
||||
String get paymentRequestAnyMint => 'Jeder Mint';
|
||||
|
||||
@override
|
||||
String get paymentRequestPay => 'Bezahlen';
|
||||
|
||||
@override
|
||||
String get paymentRequestPaying => 'Bezahle...';
|
||||
|
||||
@override
|
||||
String get paymentRequestSuccess => 'Zahlung erfolgreich gesendet';
|
||||
|
||||
@override
|
||||
String get paymentRequestNoTransport =>
|
||||
'Diese Anfrage hat keine konfigurierte Zustellmethode';
|
||||
|
||||
@override
|
||||
String get paymentRequestTransport => 'Transport';
|
||||
|
||||
@override
|
||||
String get paymentRequestMintNotAccepted =>
|
||||
'Dein aktiver Mint ist nicht in der Liste der akzeptierten Mints';
|
||||
|
||||
@override
|
||||
String paymentRequestUnitMismatch(String unit) {
|
||||
return 'Inkompatible Einheit: Anfrage erfordert $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get paymentRequestInsufficientBalance => 'Unzureichendes Guthaben';
|
||||
|
||||
@override
|
||||
String get paymentRequestErrorParsing =>
|
||||
'Fehler beim Lesen der Zahlungsanfrage';
|
||||
|
||||
@override
|
||||
String get p2pkTitle => 'P2PK-Schlüssel';
|
||||
@@ -1167,4 +1216,104 @@ class L10nDe extends L10n {
|
||||
@override
|
||||
String get p2pkErrorCannotDeletePrimary =>
|
||||
'Primärschlüssel kann nicht gelöscht werden';
|
||||
|
||||
@override
|
||||
String get request => 'Anfordern';
|
||||
|
||||
@override
|
||||
String get requestPayment => 'Zahlung anfordern';
|
||||
|
||||
@override
|
||||
String get requestPaymentDescription =>
|
||||
'Einheitliche Zahlungsanforderung erstellen';
|
||||
|
||||
@override
|
||||
String get generateRequest => 'Anforderung erstellen';
|
||||
|
||||
@override
|
||||
String get generatingRequest => 'Anforderung wird erstellt...';
|
||||
|
||||
@override
|
||||
String get requestPaymentReceived => 'Zahlung erhalten';
|
||||
|
||||
@override
|
||||
String get requestDescriptionHint => 'Beschreibung (optional)';
|
||||
|
||||
@override
|
||||
String get universal => 'Universal';
|
||||
|
||||
@override
|
||||
String get copiedToClipboard => 'In die Zwischenablage kopiert';
|
||||
|
||||
@override
|
||||
String get swap => 'Tauschen';
|
||||
|
||||
@override
|
||||
String get swapDescription => 'Zwischen Sats und USD wechseln';
|
||||
|
||||
@override
|
||||
String get swapFrom => 'Von';
|
||||
|
||||
@override
|
||||
String get swapTo => 'Nach';
|
||||
|
||||
@override
|
||||
String get swapAction => 'Tauschen';
|
||||
|
||||
@override
|
||||
String get swapEstimatedFee => 'Geschätzte Gebühr';
|
||||
|
||||
@override
|
||||
String get swapUseAll => 'Alles verwenden';
|
||||
|
||||
@override
|
||||
String swapMinimum(String amount) {
|
||||
return 'Minimum: $amount';
|
||||
}
|
||||
|
||||
@override
|
||||
String get swapProcessing => 'Swap wird verarbeitet...';
|
||||
|
||||
@override
|
||||
String get swapSuccess => 'Swap abgeschlossen';
|
||||
|
||||
@override
|
||||
String get swapErrorInsufficient => 'Unzureichendes Guthaben';
|
||||
|
||||
@override
|
||||
String get swapErrorExpired => 'Angebot abgelaufen';
|
||||
|
||||
@override
|
||||
String swapErrorGeneric(String error) {
|
||||
return 'Swap-Fehler: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get swapChartUnavailable =>
|
||||
'Preis nicht verfügbar · Tippen zum Wiederholen';
|
||||
|
||||
@override
|
||||
String swapChartMinMax(String minPrice, String maxPrice) {
|
||||
return '24h Min: $minPrice — Max: $maxPrice';
|
||||
}
|
||||
|
||||
@override
|
||||
String get privacyPolicy => 'Datenschutz';
|
||||
|
||||
@override
|
||||
String get privacyTitle => 'WIR SAMMELN NICHTS';
|
||||
|
||||
@override
|
||||
String get privacyGoodbye => 'TSCHÜSS';
|
||||
|
||||
@override
|
||||
String get privacyKeepReading => '(lies weiter, wenn du willst…)';
|
||||
|
||||
@override
|
||||
String get privacyBody =>
|
||||
'Wir wissen nicht, wer du bist\nWir wissen nicht, wie viel du hast\nWir wissen nicht, was du tust';
|
||||
|
||||
@override
|
||||
String get privacyConclusion =>
|
||||
'Der beste Weg, deine Daten zu schützen,\nist sie nicht zu haben';
|
||||
}
|
||||
|
||||
@@ -292,6 +292,9 @@ class L10nEn extends L10n {
|
||||
@override
|
||||
String get insufficientBalance => 'Insufficient balance';
|
||||
|
||||
@override
|
||||
String get feeExceedsAmount => 'Fee exceeds the amount to send';
|
||||
|
||||
@override
|
||||
String tokenCreationError(String error) {
|
||||
return 'Error creating token: $error';
|
||||
@@ -1050,8 +1053,53 @@ class L10nEn extends L10n {
|
||||
String get cameraPermissionDenied => 'Camera permission denied';
|
||||
|
||||
@override
|
||||
String get paymentRequestNotSupported =>
|
||||
'Payment requests are not yet supported';
|
||||
String get paymentRequestTitle => 'Payment Request';
|
||||
|
||||
@override
|
||||
String get paymentRequestFrom => 'Request from';
|
||||
|
||||
@override
|
||||
String get paymentRequestAmount => 'Requested amount';
|
||||
|
||||
@override
|
||||
String get paymentRequestDescription => 'Description';
|
||||
|
||||
@override
|
||||
String get paymentRequestMints => 'Accepted mints';
|
||||
|
||||
@override
|
||||
String get paymentRequestAnyMint => 'Any mint';
|
||||
|
||||
@override
|
||||
String get paymentRequestPay => 'Pay';
|
||||
|
||||
@override
|
||||
String get paymentRequestPaying => 'Paying...';
|
||||
|
||||
@override
|
||||
String get paymentRequestSuccess => 'Payment sent successfully';
|
||||
|
||||
@override
|
||||
String get paymentRequestNoTransport =>
|
||||
'This request has no delivery method configured';
|
||||
|
||||
@override
|
||||
String get paymentRequestTransport => 'Transport';
|
||||
|
||||
@override
|
||||
String get paymentRequestMintNotAccepted =>
|
||||
'Your active mint is not in the list of accepted mints';
|
||||
|
||||
@override
|
||||
String paymentRequestUnitMismatch(String unit) {
|
||||
return 'Incompatible unit: request requires $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get paymentRequestInsufficientBalance => 'Insufficient balance';
|
||||
|
||||
@override
|
||||
String get paymentRequestErrorParsing => 'Error reading payment request';
|
||||
|
||||
@override
|
||||
String get p2pkTitle => 'P2PK Keys';
|
||||
@@ -1150,4 +1198,102 @@ class L10nEn extends L10n {
|
||||
|
||||
@override
|
||||
String get p2pkErrorCannotDeletePrimary => 'Cannot delete primary key';
|
||||
|
||||
@override
|
||||
String get request => 'Request';
|
||||
|
||||
@override
|
||||
String get requestPayment => 'Request Payment';
|
||||
|
||||
@override
|
||||
String get requestPaymentDescription => 'Generate unified payment request';
|
||||
|
||||
@override
|
||||
String get generateRequest => 'Generate Request';
|
||||
|
||||
@override
|
||||
String get generatingRequest => 'Generating request...';
|
||||
|
||||
@override
|
||||
String get requestPaymentReceived => 'Payment received';
|
||||
|
||||
@override
|
||||
String get requestDescriptionHint => 'Description (optional)';
|
||||
|
||||
@override
|
||||
String get universal => 'Universal';
|
||||
|
||||
@override
|
||||
String get copiedToClipboard => 'Copied to clipboard';
|
||||
|
||||
@override
|
||||
String get swap => 'Swap';
|
||||
|
||||
@override
|
||||
String get swapDescription => 'Convert between sats and USD';
|
||||
|
||||
@override
|
||||
String get swapFrom => 'From';
|
||||
|
||||
@override
|
||||
String get swapTo => 'To';
|
||||
|
||||
@override
|
||||
String get swapAction => 'Swap';
|
||||
|
||||
@override
|
||||
String get swapEstimatedFee => 'Estimated fee';
|
||||
|
||||
@override
|
||||
String get swapUseAll => 'Use all';
|
||||
|
||||
@override
|
||||
String swapMinimum(String amount) {
|
||||
return 'Minimum: $amount';
|
||||
}
|
||||
|
||||
@override
|
||||
String get swapProcessing => 'Processing swap...';
|
||||
|
||||
@override
|
||||
String get swapSuccess => 'Swap completed';
|
||||
|
||||
@override
|
||||
String get swapErrorInsufficient => 'Insufficient balance';
|
||||
|
||||
@override
|
||||
String get swapErrorExpired => 'Quote has expired';
|
||||
|
||||
@override
|
||||
String swapErrorGeneric(String error) {
|
||||
return 'Swap error: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get swapChartUnavailable => 'Price unavailable · Tap to retry';
|
||||
|
||||
@override
|
||||
String swapChartMinMax(String minPrice, String maxPrice) {
|
||||
return '24h Min: $minPrice — Max: $maxPrice';
|
||||
}
|
||||
|
||||
@override
|
||||
String get privacyPolicy => 'Privacy policy';
|
||||
|
||||
@override
|
||||
String get privacyTitle => 'WE COLLECT NOTHING';
|
||||
|
||||
@override
|
||||
String get privacyGoodbye => 'BYE';
|
||||
|
||||
@override
|
||||
String get privacyKeepReading => '(keep reading if you want…)';
|
||||
|
||||
@override
|
||||
String get privacyBody =>
|
||||
'We don\'t know who you are\nWe don\'t know how much you have\nWe don\'t know what you do';
|
||||
|
||||
@override
|
||||
String get privacyConclusion =>
|
||||
'The best way to protect your data\nis not to have it';
|
||||
}
|
||||
|
||||
@@ -292,6 +292,9 @@ class L10nEs extends L10n {
|
||||
@override
|
||||
String get insufficientBalance => 'Balance insuficiente';
|
||||
|
||||
@override
|
||||
String get feeExceedsAmount => 'La comisión supera el monto a enviar';
|
||||
|
||||
@override
|
||||
String tokenCreationError(String error) {
|
||||
return 'Error al crear token: $error';
|
||||
@@ -1055,8 +1058,53 @@ class L10nEs extends L10n {
|
||||
String get cameraPermissionDenied => 'Permiso de cámara denegado';
|
||||
|
||||
@override
|
||||
String get paymentRequestNotSupported =>
|
||||
'Los payment requests aún no están soportados';
|
||||
String get paymentRequestTitle => 'Solicitud de pago';
|
||||
|
||||
@override
|
||||
String get paymentRequestFrom => 'Solicitud de';
|
||||
|
||||
@override
|
||||
String get paymentRequestAmount => 'Monto solicitado';
|
||||
|
||||
@override
|
||||
String get paymentRequestDescription => 'Descripción';
|
||||
|
||||
@override
|
||||
String get paymentRequestMints => 'Mints aceptados';
|
||||
|
||||
@override
|
||||
String get paymentRequestAnyMint => 'Cualquier mint';
|
||||
|
||||
@override
|
||||
String get paymentRequestPay => 'Pagar';
|
||||
|
||||
@override
|
||||
String get paymentRequestPaying => 'Pagando...';
|
||||
|
||||
@override
|
||||
String get paymentRequestSuccess => 'Pago enviado correctamente';
|
||||
|
||||
@override
|
||||
String get paymentRequestNoTransport =>
|
||||
'Esta solicitud no tiene método de entrega configurado';
|
||||
|
||||
@override
|
||||
String get paymentRequestTransport => 'Transporte';
|
||||
|
||||
@override
|
||||
String get paymentRequestMintNotAccepted =>
|
||||
'Tu mint activo no está en la lista de mints aceptados';
|
||||
|
||||
@override
|
||||
String paymentRequestUnitMismatch(String unit) {
|
||||
return 'Unidad incompatible: la solicitud requiere $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get paymentRequestInsufficientBalance => 'Balance insuficiente';
|
||||
|
||||
@override
|
||||
String get paymentRequestErrorParsing => 'Error al leer la solicitud de pago';
|
||||
|
||||
@override
|
||||
String get p2pkTitle => 'Claves P2PK';
|
||||
@@ -1158,4 +1206,103 @@ class L10nEs extends L10n {
|
||||
@override
|
||||
String get p2pkErrorCannotDeletePrimary =>
|
||||
'No se puede eliminar la clave principal';
|
||||
|
||||
@override
|
||||
String get request => 'Solicitar';
|
||||
|
||||
@override
|
||||
String get requestPayment => 'Solicitar pago';
|
||||
|
||||
@override
|
||||
String get requestPaymentDescription => 'Generar solicitud de pago unificada';
|
||||
|
||||
@override
|
||||
String get generateRequest => 'Generar solicitud';
|
||||
|
||||
@override
|
||||
String get generatingRequest => 'Generando solicitud...';
|
||||
|
||||
@override
|
||||
String get requestPaymentReceived => 'Pago recibido';
|
||||
|
||||
@override
|
||||
String get requestDescriptionHint => 'Descripción (opcional)';
|
||||
|
||||
@override
|
||||
String get universal => 'Universal';
|
||||
|
||||
@override
|
||||
String get copiedToClipboard => 'Copiado al portapapeles';
|
||||
|
||||
@override
|
||||
String get swap => 'Cambiar';
|
||||
|
||||
@override
|
||||
String get swapDescription => 'Convertir entre sats y USD';
|
||||
|
||||
@override
|
||||
String get swapFrom => 'De';
|
||||
|
||||
@override
|
||||
String get swapTo => 'A';
|
||||
|
||||
@override
|
||||
String get swapAction => 'Cambiar';
|
||||
|
||||
@override
|
||||
String get swapEstimatedFee => 'Fee estimado';
|
||||
|
||||
@override
|
||||
String get swapUseAll => 'Usar todo';
|
||||
|
||||
@override
|
||||
String swapMinimum(String amount) {
|
||||
return 'Mínimo: $amount';
|
||||
}
|
||||
|
||||
@override
|
||||
String get swapProcessing => 'Procesando swap...';
|
||||
|
||||
@override
|
||||
String get swapSuccess => 'Swap completado';
|
||||
|
||||
@override
|
||||
String get swapErrorInsufficient => 'Saldo insuficiente';
|
||||
|
||||
@override
|
||||
String get swapErrorExpired => 'La cotización ha expirado';
|
||||
|
||||
@override
|
||||
String swapErrorGeneric(String error) {
|
||||
return 'Error en el swap: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get swapChartUnavailable =>
|
||||
'Precio no disponible · Toca para reintentar';
|
||||
|
||||
@override
|
||||
String swapChartMinMax(String minPrice, String maxPrice) {
|
||||
return '24h Mín: $minPrice — Máx: $maxPrice';
|
||||
}
|
||||
|
||||
@override
|
||||
String get privacyPolicy => 'Política de privacidad';
|
||||
|
||||
@override
|
||||
String get privacyTitle => 'NO RECOPILAMOS NADA';
|
||||
|
||||
@override
|
||||
String get privacyGoodbye => 'ADIÓS';
|
||||
|
||||
@override
|
||||
String get privacyKeepReading => '(sigue leyendo si quieres…)';
|
||||
|
||||
@override
|
||||
String get privacyBody =>
|
||||
'No sabemos quién eres\nNo sabemos cuánto tienes\nNo sabemos qué haces';
|
||||
|
||||
@override
|
||||
String get privacyConclusion =>
|
||||
'La mejor forma de proteger tus datos\nes no tenerlos';
|
||||
}
|
||||
|
||||
@@ -296,6 +296,9 @@ class L10nFr extends L10n {
|
||||
@override
|
||||
String get insufficientBalance => 'Solde insuffisant';
|
||||
|
||||
@override
|
||||
String get feeExceedsAmount => 'Les frais dépassent le montant à envoyer';
|
||||
|
||||
@override
|
||||
String tokenCreationError(String error) {
|
||||
return 'Erreur de création du token : $error';
|
||||
@@ -1068,8 +1071,54 @@ class L10nFr extends L10n {
|
||||
String get cameraPermissionDenied => 'Permission de la caméra refusée';
|
||||
|
||||
@override
|
||||
String get paymentRequestNotSupported =>
|
||||
'Les demandes de paiement ne sont pas encore prises en charge';
|
||||
String get paymentRequestTitle => 'Demande de paiement';
|
||||
|
||||
@override
|
||||
String get paymentRequestFrom => 'Demande de';
|
||||
|
||||
@override
|
||||
String get paymentRequestAmount => 'Montant demandé';
|
||||
|
||||
@override
|
||||
String get paymentRequestDescription => 'Description';
|
||||
|
||||
@override
|
||||
String get paymentRequestMints => 'Mints acceptés';
|
||||
|
||||
@override
|
||||
String get paymentRequestAnyMint => 'N\'importe quel mint';
|
||||
|
||||
@override
|
||||
String get paymentRequestPay => 'Payer';
|
||||
|
||||
@override
|
||||
String get paymentRequestPaying => 'Paiement en cours...';
|
||||
|
||||
@override
|
||||
String get paymentRequestSuccess => 'Paiement envoyé avec succès';
|
||||
|
||||
@override
|
||||
String get paymentRequestNoTransport =>
|
||||
'Cette demande n\'a pas de méthode de livraison configurée';
|
||||
|
||||
@override
|
||||
String get paymentRequestTransport => 'Transport';
|
||||
|
||||
@override
|
||||
String get paymentRequestMintNotAccepted =>
|
||||
'Votre mint actif n\'est pas dans la liste des mints acceptés';
|
||||
|
||||
@override
|
||||
String paymentRequestUnitMismatch(String unit) {
|
||||
return 'Unité incompatible : la demande nécessite $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get paymentRequestInsufficientBalance => 'Solde insuffisant';
|
||||
|
||||
@override
|
||||
String get paymentRequestErrorParsing =>
|
||||
'Erreur lors de la lecture de la demande de paiement';
|
||||
|
||||
@override
|
||||
String get p2pkTitle => 'Clés P2PK';
|
||||
@@ -1172,4 +1221,104 @@ class L10nFr extends L10n {
|
||||
@override
|
||||
String get p2pkErrorCannotDeletePrimary =>
|
||||
'Impossible de supprimer la clé principale';
|
||||
|
||||
@override
|
||||
String get request => 'Demander';
|
||||
|
||||
@override
|
||||
String get requestPayment => 'Demander un paiement';
|
||||
|
||||
@override
|
||||
String get requestPaymentDescription =>
|
||||
'Générer une demande de paiement unifiée';
|
||||
|
||||
@override
|
||||
String get generateRequest => 'Générer la demande';
|
||||
|
||||
@override
|
||||
String get generatingRequest => 'Génération en cours...';
|
||||
|
||||
@override
|
||||
String get requestPaymentReceived => 'Paiement reçu';
|
||||
|
||||
@override
|
||||
String get requestDescriptionHint => 'Description (facultatif)';
|
||||
|
||||
@override
|
||||
String get universal => 'Universel';
|
||||
|
||||
@override
|
||||
String get copiedToClipboard => 'Copié dans le presse-papiers';
|
||||
|
||||
@override
|
||||
String get swap => 'Échanger';
|
||||
|
||||
@override
|
||||
String get swapDescription => 'Convertir entre sats et USD';
|
||||
|
||||
@override
|
||||
String get swapFrom => 'De';
|
||||
|
||||
@override
|
||||
String get swapTo => 'Vers';
|
||||
|
||||
@override
|
||||
String get swapAction => 'Échanger';
|
||||
|
||||
@override
|
||||
String get swapEstimatedFee => 'Frais estimés';
|
||||
|
||||
@override
|
||||
String get swapUseAll => 'Tout utiliser';
|
||||
|
||||
@override
|
||||
String swapMinimum(String amount) {
|
||||
return 'Minimum : $amount';
|
||||
}
|
||||
|
||||
@override
|
||||
String get swapProcessing => 'Swap en cours...';
|
||||
|
||||
@override
|
||||
String get swapSuccess => 'Swap terminé';
|
||||
|
||||
@override
|
||||
String get swapErrorInsufficient => 'Solde insuffisant';
|
||||
|
||||
@override
|
||||
String get swapErrorExpired => 'Le devis a expiré';
|
||||
|
||||
@override
|
||||
String swapErrorGeneric(String error) {
|
||||
return 'Erreur de swap : $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get swapChartUnavailable =>
|
||||
'Prix indisponible · Appuyez pour réessayer';
|
||||
|
||||
@override
|
||||
String swapChartMinMax(String minPrice, String maxPrice) {
|
||||
return '24h Min : $minPrice — Max : $maxPrice';
|
||||
}
|
||||
|
||||
@override
|
||||
String get privacyPolicy => 'Politique de confidentialité';
|
||||
|
||||
@override
|
||||
String get privacyTitle => 'NOUS NE COLLECTONS RIEN';
|
||||
|
||||
@override
|
||||
String get privacyGoodbye => 'AU REVOIR';
|
||||
|
||||
@override
|
||||
String get privacyKeepReading => '(continue à lire si tu veux…)';
|
||||
|
||||
@override
|
||||
String get privacyBody =>
|
||||
'Nous ne savons pas qui tu es\nNous ne savons pas combien tu as\nNous ne savons pas ce que tu fais';
|
||||
|
||||
@override
|
||||
String get privacyConclusion =>
|
||||
'La meilleure façon de protéger tes données,\nc\'est de ne pas les avoir';
|
||||
}
|
||||
|
||||
@@ -293,6 +293,9 @@ class L10nIt extends L10n {
|
||||
@override
|
||||
String get insufficientBalance => 'Saldo insufficiente';
|
||||
|
||||
@override
|
||||
String get feeExceedsAmount => 'La commissione supera l\'importo da inviare';
|
||||
|
||||
@override
|
||||
String tokenCreationError(String error) {
|
||||
return 'Errore creazione token: $error';
|
||||
@@ -1058,8 +1061,54 @@ class L10nIt extends L10n {
|
||||
String get cameraPermissionDenied => 'Permesso fotocamera negato';
|
||||
|
||||
@override
|
||||
String get paymentRequestNotSupported =>
|
||||
'Le richieste di pagamento non sono ancora supportate';
|
||||
String get paymentRequestTitle => 'Richiesta di pagamento';
|
||||
|
||||
@override
|
||||
String get paymentRequestFrom => 'Richiesta da';
|
||||
|
||||
@override
|
||||
String get paymentRequestAmount => 'Importo richiesto';
|
||||
|
||||
@override
|
||||
String get paymentRequestDescription => 'Descrizione';
|
||||
|
||||
@override
|
||||
String get paymentRequestMints => 'Mint accettati';
|
||||
|
||||
@override
|
||||
String get paymentRequestAnyMint => 'Qualsiasi mint';
|
||||
|
||||
@override
|
||||
String get paymentRequestPay => 'Paga';
|
||||
|
||||
@override
|
||||
String get paymentRequestPaying => 'Pagamento in corso...';
|
||||
|
||||
@override
|
||||
String get paymentRequestSuccess => 'Pagamento inviato con successo';
|
||||
|
||||
@override
|
||||
String get paymentRequestNoTransport =>
|
||||
'Questa richiesta non ha un metodo di consegna configurato';
|
||||
|
||||
@override
|
||||
String get paymentRequestTransport => 'Trasporto';
|
||||
|
||||
@override
|
||||
String get paymentRequestMintNotAccepted =>
|
||||
'Il tuo mint attivo non è nella lista dei mint accettati';
|
||||
|
||||
@override
|
||||
String paymentRequestUnitMismatch(String unit) {
|
||||
return 'Unità incompatibile: la richiesta richiede $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get paymentRequestInsufficientBalance => 'Saldo insufficiente';
|
||||
|
||||
@override
|
||||
String get paymentRequestErrorParsing =>
|
||||
'Errore nella lettura della richiesta di pagamento';
|
||||
|
||||
@override
|
||||
String get p2pkTitle => 'Chiavi P2PK';
|
||||
@@ -1160,4 +1209,104 @@ class L10nIt extends L10n {
|
||||
@override
|
||||
String get p2pkErrorCannotDeletePrimary =>
|
||||
'Impossibile eliminare la chiave principale';
|
||||
|
||||
@override
|
||||
String get request => 'Richiedi';
|
||||
|
||||
@override
|
||||
String get requestPayment => 'Richiedi pagamento';
|
||||
|
||||
@override
|
||||
String get requestPaymentDescription =>
|
||||
'Genera richiesta di pagamento unificata';
|
||||
|
||||
@override
|
||||
String get generateRequest => 'Genera richiesta';
|
||||
|
||||
@override
|
||||
String get generatingRequest => 'Generazione in corso...';
|
||||
|
||||
@override
|
||||
String get requestPaymentReceived => 'Pagamento ricevuto';
|
||||
|
||||
@override
|
||||
String get requestDescriptionHint => 'Descrizione (opzionale)';
|
||||
|
||||
@override
|
||||
String get universal => 'Universale';
|
||||
|
||||
@override
|
||||
String get copiedToClipboard => 'Copiato negli appunti';
|
||||
|
||||
@override
|
||||
String get swap => 'Scambia';
|
||||
|
||||
@override
|
||||
String get swapDescription => 'Converti tra sats e USD';
|
||||
|
||||
@override
|
||||
String get swapFrom => 'Da';
|
||||
|
||||
@override
|
||||
String get swapTo => 'A';
|
||||
|
||||
@override
|
||||
String get swapAction => 'Scambia';
|
||||
|
||||
@override
|
||||
String get swapEstimatedFee => 'Commissione stimata';
|
||||
|
||||
@override
|
||||
String get swapUseAll => 'Usa tutto';
|
||||
|
||||
@override
|
||||
String swapMinimum(String amount) {
|
||||
return 'Minimo: $amount';
|
||||
}
|
||||
|
||||
@override
|
||||
String get swapProcessing => 'Swap in corso...';
|
||||
|
||||
@override
|
||||
String get swapSuccess => 'Swap completato';
|
||||
|
||||
@override
|
||||
String get swapErrorInsufficient => 'Saldo insufficiente';
|
||||
|
||||
@override
|
||||
String get swapErrorExpired => 'Il preventivo è scaduto';
|
||||
|
||||
@override
|
||||
String swapErrorGeneric(String error) {
|
||||
return 'Errore swap: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get swapChartUnavailable =>
|
||||
'Prezzo non disponibile · Tocca per riprovare';
|
||||
|
||||
@override
|
||||
String swapChartMinMax(String minPrice, String maxPrice) {
|
||||
return '24h Min: $minPrice — Max: $maxPrice';
|
||||
}
|
||||
|
||||
@override
|
||||
String get privacyPolicy => 'Informativa sulla privacy';
|
||||
|
||||
@override
|
||||
String get privacyTitle => 'NON RACCOGLIAMO NULLA';
|
||||
|
||||
@override
|
||||
String get privacyGoodbye => 'CIAO';
|
||||
|
||||
@override
|
||||
String get privacyKeepReading => '(continua a leggere se vuoi…)';
|
||||
|
||||
@override
|
||||
String get privacyBody =>
|
||||
'Non sappiamo chi sei\nNon sappiamo quanto hai\nNon sappiamo cosa fai';
|
||||
|
||||
@override
|
||||
String get privacyConclusion =>
|
||||
'Il modo migliore per proteggere i tuoi dati\nè non averli';
|
||||
}
|
||||
|
||||
@@ -288,6 +288,9 @@ class L10nJa extends L10n {
|
||||
@override
|
||||
String get insufficientBalance => '残高不足';
|
||||
|
||||
@override
|
||||
String get feeExceedsAmount => '手数料が送金額を超えています';
|
||||
|
||||
@override
|
||||
String tokenCreationError(String error) {
|
||||
return 'トークン作成エラー:$error';
|
||||
@@ -1038,7 +1041,51 @@ class L10nJa extends L10n {
|
||||
String get cameraPermissionDenied => 'カメラの許可が拒否されました';
|
||||
|
||||
@override
|
||||
String get paymentRequestNotSupported => '支払いリクエストはまだサポートされていません';
|
||||
String get paymentRequestTitle => '支払いリクエスト';
|
||||
|
||||
@override
|
||||
String get paymentRequestFrom => 'リクエスト元';
|
||||
|
||||
@override
|
||||
String get paymentRequestAmount => 'リクエスト金額';
|
||||
|
||||
@override
|
||||
String get paymentRequestDescription => '説明';
|
||||
|
||||
@override
|
||||
String get paymentRequestMints => '対応ミント';
|
||||
|
||||
@override
|
||||
String get paymentRequestAnyMint => 'すべてのミント';
|
||||
|
||||
@override
|
||||
String get paymentRequestPay => '支払う';
|
||||
|
||||
@override
|
||||
String get paymentRequestPaying => '支払い中...';
|
||||
|
||||
@override
|
||||
String get paymentRequestSuccess => '支払いが正常に送信されました';
|
||||
|
||||
@override
|
||||
String get paymentRequestNoTransport => 'このリクエストには配信方法が設定されていません';
|
||||
|
||||
@override
|
||||
String get paymentRequestTransport => 'トランスポート';
|
||||
|
||||
@override
|
||||
String get paymentRequestMintNotAccepted => 'アクティブなミントは対応ミントリストにありません';
|
||||
|
||||
@override
|
||||
String paymentRequestUnitMismatch(String unit) {
|
||||
return '単位が互換性がありません:リクエストには$unitが必要です';
|
||||
}
|
||||
|
||||
@override
|
||||
String get paymentRequestInsufficientBalance => '残高不足';
|
||||
|
||||
@override
|
||||
String get paymentRequestErrorParsing => '支払いリクエストの読み取りエラー';
|
||||
|
||||
@override
|
||||
String get p2pkTitle => 'P2PK鍵';
|
||||
@@ -1136,4 +1183,100 @@ class L10nJa extends L10n {
|
||||
|
||||
@override
|
||||
String get p2pkErrorCannotDeletePrimary => 'プライマリ鍵は削除できません';
|
||||
|
||||
@override
|
||||
String get request => 'リクエスト';
|
||||
|
||||
@override
|
||||
String get requestPayment => '支払いをリクエスト';
|
||||
|
||||
@override
|
||||
String get requestPaymentDescription => '統合支払いリクエストを生成';
|
||||
|
||||
@override
|
||||
String get generateRequest => 'リクエストを生成';
|
||||
|
||||
@override
|
||||
String get generatingRequest => '生成中...';
|
||||
|
||||
@override
|
||||
String get requestPaymentReceived => '支払いを受け取りました';
|
||||
|
||||
@override
|
||||
String get requestDescriptionHint => '説明(任意)';
|
||||
|
||||
@override
|
||||
String get universal => 'ユニバーサル';
|
||||
|
||||
@override
|
||||
String get copiedToClipboard => 'クリップボードにコピーしました';
|
||||
|
||||
@override
|
||||
String get swap => '交換';
|
||||
|
||||
@override
|
||||
String get swapDescription => 'SatsとUSDを変換';
|
||||
|
||||
@override
|
||||
String get swapFrom => '送信元';
|
||||
|
||||
@override
|
||||
String get swapTo => '送信先';
|
||||
|
||||
@override
|
||||
String get swapAction => '交換';
|
||||
|
||||
@override
|
||||
String get swapEstimatedFee => '推定手数料';
|
||||
|
||||
@override
|
||||
String get swapUseAll => '全額使用';
|
||||
|
||||
@override
|
||||
String swapMinimum(String amount) {
|
||||
return '最小: $amount';
|
||||
}
|
||||
|
||||
@override
|
||||
String get swapProcessing => 'スワップ処理中...';
|
||||
|
||||
@override
|
||||
String get swapSuccess => 'スワップ完了';
|
||||
|
||||
@override
|
||||
String get swapErrorInsufficient => '残高不足';
|
||||
|
||||
@override
|
||||
String get swapErrorExpired => '見積もりの有効期限切れ';
|
||||
|
||||
@override
|
||||
String swapErrorGeneric(String error) {
|
||||
return 'スワップエラー: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get swapChartUnavailable => '価格を取得できません・タップで再試行';
|
||||
|
||||
@override
|
||||
String swapChartMinMax(String minPrice, String maxPrice) {
|
||||
return '24h 安値: $minPrice — 高値: $maxPrice';
|
||||
}
|
||||
|
||||
@override
|
||||
String get privacyPolicy => 'プライバシーポリシー';
|
||||
|
||||
@override
|
||||
String get privacyTitle => '何も収集しません';
|
||||
|
||||
@override
|
||||
String get privacyGoodbye => 'さようなら';
|
||||
|
||||
@override
|
||||
String get privacyKeepReading => '(読み続けたければどうぞ…)';
|
||||
|
||||
@override
|
||||
String get privacyBody => 'あなたが誰か知りません\nいくら持っているか知りません\n何をしているか知りません';
|
||||
|
||||
@override
|
||||
String get privacyConclusion => 'データを守る最善の方法は\nデータを持たないことです';
|
||||
}
|
||||
|
||||
@@ -290,6 +290,9 @@ class L10nKo extends L10n {
|
||||
@override
|
||||
String get insufficientBalance => '잔액 부족';
|
||||
|
||||
@override
|
||||
String get feeExceedsAmount => '수수료가 송금액을 초과합니다';
|
||||
|
||||
@override
|
||||
String tokenCreationError(String error) {
|
||||
return '토큰 생성 오류: $error';
|
||||
@@ -1040,7 +1043,51 @@ class L10nKo extends L10n {
|
||||
String get cameraPermissionDenied => '카메라 권한이 거부되었습니다';
|
||||
|
||||
@override
|
||||
String get paymentRequestNotSupported => '결제 요청은 아직 지원되지 않습니다';
|
||||
String get paymentRequestTitle => '결제 요청';
|
||||
|
||||
@override
|
||||
String get paymentRequestFrom => '요청자';
|
||||
|
||||
@override
|
||||
String get paymentRequestAmount => '요청 금액';
|
||||
|
||||
@override
|
||||
String get paymentRequestDescription => '설명';
|
||||
|
||||
@override
|
||||
String get paymentRequestMints => '허용된 민트';
|
||||
|
||||
@override
|
||||
String get paymentRequestAnyMint => '모든 민트';
|
||||
|
||||
@override
|
||||
String get paymentRequestPay => '결제';
|
||||
|
||||
@override
|
||||
String get paymentRequestPaying => '결제 중...';
|
||||
|
||||
@override
|
||||
String get paymentRequestSuccess => '결제가 성공적으로 전송되었습니다';
|
||||
|
||||
@override
|
||||
String get paymentRequestNoTransport => '이 요청에는 전달 방법이 설정되지 않았습니다';
|
||||
|
||||
@override
|
||||
String get paymentRequestTransport => '전송 방식';
|
||||
|
||||
@override
|
||||
String get paymentRequestMintNotAccepted => '활성 민트가 허용된 민트 목록에 없습니다';
|
||||
|
||||
@override
|
||||
String paymentRequestUnitMismatch(String unit) {
|
||||
return '호환되지 않는 단위: 요청에 $unit이(가) 필요합니다';
|
||||
}
|
||||
|
||||
@override
|
||||
String get paymentRequestInsufficientBalance => '잔액 부족';
|
||||
|
||||
@override
|
||||
String get paymentRequestErrorParsing => '결제 요청을 읽는 중 오류 발생';
|
||||
|
||||
@override
|
||||
String get p2pkTitle => 'P2PK 키';
|
||||
@@ -1138,4 +1185,100 @@ class L10nKo extends L10n {
|
||||
|
||||
@override
|
||||
String get p2pkErrorCannotDeletePrimary => '기본 키는 삭제할 수 없습니다';
|
||||
|
||||
@override
|
||||
String get request => '요청';
|
||||
|
||||
@override
|
||||
String get requestPayment => '결제 요청';
|
||||
|
||||
@override
|
||||
String get requestPaymentDescription => '통합 결제 요청 생성';
|
||||
|
||||
@override
|
||||
String get generateRequest => '요청 생성';
|
||||
|
||||
@override
|
||||
String get generatingRequest => '생성 중...';
|
||||
|
||||
@override
|
||||
String get requestPaymentReceived => '결제가 수신되었습니다';
|
||||
|
||||
@override
|
||||
String get requestDescriptionHint => '설명 (선택사항)';
|
||||
|
||||
@override
|
||||
String get universal => '유니버설';
|
||||
|
||||
@override
|
||||
String get copiedToClipboard => '클립보드에 복사되었습니다';
|
||||
|
||||
@override
|
||||
String get swap => '교환';
|
||||
|
||||
@override
|
||||
String get swapDescription => 'Sats와 USD 간 변환';
|
||||
|
||||
@override
|
||||
String get swapFrom => '에서';
|
||||
|
||||
@override
|
||||
String get swapTo => '으로';
|
||||
|
||||
@override
|
||||
String get swapAction => '교환';
|
||||
|
||||
@override
|
||||
String get swapEstimatedFee => '예상 수수료';
|
||||
|
||||
@override
|
||||
String get swapUseAll => '전액 사용';
|
||||
|
||||
@override
|
||||
String swapMinimum(String amount) {
|
||||
return '최소: $amount';
|
||||
}
|
||||
|
||||
@override
|
||||
String get swapProcessing => '교환 처리 중...';
|
||||
|
||||
@override
|
||||
String get swapSuccess => '교환 완료';
|
||||
|
||||
@override
|
||||
String get swapErrorInsufficient => '잔액 부족';
|
||||
|
||||
@override
|
||||
String get swapErrorExpired => '견적이 만료되었습니다';
|
||||
|
||||
@override
|
||||
String swapErrorGeneric(String error) {
|
||||
return '교환 오류: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get swapChartUnavailable => '가격 불러오기 실패 · 탭하여 재시도';
|
||||
|
||||
@override
|
||||
String swapChartMinMax(String minPrice, String maxPrice) {
|
||||
return '24h 최저: $minPrice — 최고: $maxPrice';
|
||||
}
|
||||
|
||||
@override
|
||||
String get privacyPolicy => '개인정보 처리방침';
|
||||
|
||||
@override
|
||||
String get privacyTitle => '우리는 아무것도 수집하지 않습니다';
|
||||
|
||||
@override
|
||||
String get privacyGoodbye => '안녕';
|
||||
|
||||
@override
|
||||
String get privacyKeepReading => '(계속 읽고 싶으면…)';
|
||||
|
||||
@override
|
||||
String get privacyBody => '당신이 누구인지 모릅니다\n얼마를 가지고 있는지 모릅니다\n무엇을 하는지 모릅니다';
|
||||
|
||||
@override
|
||||
String get privacyConclusion => '데이터를 보호하는 가장 좋은 방법은\n데이터를 갖지 않는 것입니다';
|
||||
}
|
||||
|
||||
@@ -293,6 +293,9 @@ class L10nPt extends L10n {
|
||||
@override
|
||||
String get insufficientBalance => 'Saldo insuficiente';
|
||||
|
||||
@override
|
||||
String get feeExceedsAmount => 'A taxa excede o valor a enviar';
|
||||
|
||||
@override
|
||||
String tokenCreationError(String error) {
|
||||
return 'Erro ao criar token: $error';
|
||||
@@ -1057,8 +1060,54 @@ class L10nPt extends L10n {
|
||||
String get cameraPermissionDenied => 'Permissão de câmera negada';
|
||||
|
||||
@override
|
||||
String get paymentRequestNotSupported =>
|
||||
'Solicitações de pagamento ainda não são suportadas';
|
||||
String get paymentRequestTitle => 'Solicitação de pagamento';
|
||||
|
||||
@override
|
||||
String get paymentRequestFrom => 'Solicitação de';
|
||||
|
||||
@override
|
||||
String get paymentRequestAmount => 'Valor solicitado';
|
||||
|
||||
@override
|
||||
String get paymentRequestDescription => 'Descrição';
|
||||
|
||||
@override
|
||||
String get paymentRequestMints => 'Mints aceitos';
|
||||
|
||||
@override
|
||||
String get paymentRequestAnyMint => 'Qualquer mint';
|
||||
|
||||
@override
|
||||
String get paymentRequestPay => 'Pagar';
|
||||
|
||||
@override
|
||||
String get paymentRequestPaying => 'Pagando...';
|
||||
|
||||
@override
|
||||
String get paymentRequestSuccess => 'Pagamento enviado com sucesso';
|
||||
|
||||
@override
|
||||
String get paymentRequestNoTransport =>
|
||||
'Esta solicitação não tem método de entrega configurado';
|
||||
|
||||
@override
|
||||
String get paymentRequestTransport => 'Transporte';
|
||||
|
||||
@override
|
||||
String get paymentRequestMintNotAccepted =>
|
||||
'Seu mint ativo não está na lista de mints aceitos';
|
||||
|
||||
@override
|
||||
String paymentRequestUnitMismatch(String unit) {
|
||||
return 'Unidade incompatível: a solicitação requer $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get paymentRequestInsufficientBalance => 'Saldo insuficiente';
|
||||
|
||||
@override
|
||||
String get paymentRequestErrorParsing =>
|
||||
'Erro ao ler a solicitação de pagamento';
|
||||
|
||||
@override
|
||||
String get p2pkTitle => 'Chaves P2PK';
|
||||
@@ -1160,4 +1209,104 @@ class L10nPt extends L10n {
|
||||
@override
|
||||
String get p2pkErrorCannotDeletePrimary =>
|
||||
'Não é possível excluir a chave principal';
|
||||
|
||||
@override
|
||||
String get request => 'Solicitar';
|
||||
|
||||
@override
|
||||
String get requestPayment => 'Solicitar pagamento';
|
||||
|
||||
@override
|
||||
String get requestPaymentDescription =>
|
||||
'Gerar solicitação de pagamento unificada';
|
||||
|
||||
@override
|
||||
String get generateRequest => 'Gerar solicitação';
|
||||
|
||||
@override
|
||||
String get generatingRequest => 'Gerando solicitação...';
|
||||
|
||||
@override
|
||||
String get requestPaymentReceived => 'Pagamento recebido';
|
||||
|
||||
@override
|
||||
String get requestDescriptionHint => 'Descrição (opcional)';
|
||||
|
||||
@override
|
||||
String get universal => 'Universal';
|
||||
|
||||
@override
|
||||
String get copiedToClipboard => 'Copiado para a área de transferência';
|
||||
|
||||
@override
|
||||
String get swap => 'Trocar';
|
||||
|
||||
@override
|
||||
String get swapDescription => 'Converter entre sats e USD';
|
||||
|
||||
@override
|
||||
String get swapFrom => 'De';
|
||||
|
||||
@override
|
||||
String get swapTo => 'Para';
|
||||
|
||||
@override
|
||||
String get swapAction => 'Trocar';
|
||||
|
||||
@override
|
||||
String get swapEstimatedFee => 'Taxa estimada';
|
||||
|
||||
@override
|
||||
String get swapUseAll => 'Usar tudo';
|
||||
|
||||
@override
|
||||
String swapMinimum(String amount) {
|
||||
return 'Mínimo: $amount';
|
||||
}
|
||||
|
||||
@override
|
||||
String get swapProcessing => 'Processando swap...';
|
||||
|
||||
@override
|
||||
String get swapSuccess => 'Swap concluído';
|
||||
|
||||
@override
|
||||
String get swapErrorInsufficient => 'Saldo insuficiente';
|
||||
|
||||
@override
|
||||
String get swapErrorExpired => 'A cotação expirou';
|
||||
|
||||
@override
|
||||
String swapErrorGeneric(String error) {
|
||||
return 'Erro no swap: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get swapChartUnavailable =>
|
||||
'Preço indisponível · Toque para tentar novamente';
|
||||
|
||||
@override
|
||||
String swapChartMinMax(String minPrice, String maxPrice) {
|
||||
return '24h Mín: $minPrice — Máx: $maxPrice';
|
||||
}
|
||||
|
||||
@override
|
||||
String get privacyPolicy => 'Política de privacidade';
|
||||
|
||||
@override
|
||||
String get privacyTitle => 'NÃO RECOLHEMOS NADA';
|
||||
|
||||
@override
|
||||
String get privacyGoodbye => 'ADEUS';
|
||||
|
||||
@override
|
||||
String get privacyKeepReading => '(continue lendo se quiser…)';
|
||||
|
||||
@override
|
||||
String get privacyBody =>
|
||||
'Não sabemos quem você é\nNão sabemos quanto você tem\nNão sabemos o que você faz';
|
||||
|
||||
@override
|
||||
String get privacyConclusion =>
|
||||
'A melhor forma de proteger seus dados\né não tê-los';
|
||||
}
|
||||
|
||||
@@ -292,6 +292,9 @@ class L10nRu extends L10n {
|
||||
@override
|
||||
String get insufficientBalance => 'Недостаточный баланс';
|
||||
|
||||
@override
|
||||
String get feeExceedsAmount => 'Комиссия превышает сумму отправки';
|
||||
|
||||
@override
|
||||
String tokenCreationError(String error) {
|
||||
return 'Ошибка создания токена: $error';
|
||||
@@ -1054,8 +1057,53 @@ class L10nRu extends L10n {
|
||||
String get cameraPermissionDenied => 'Доступ к камере запрещён';
|
||||
|
||||
@override
|
||||
String get paymentRequestNotSupported =>
|
||||
'Запросы на оплату пока не поддерживаются';
|
||||
String get paymentRequestTitle => 'Запрос на оплату';
|
||||
|
||||
@override
|
||||
String get paymentRequestFrom => 'Запрос от';
|
||||
|
||||
@override
|
||||
String get paymentRequestAmount => 'Запрошенная сумма';
|
||||
|
||||
@override
|
||||
String get paymentRequestDescription => 'Описание';
|
||||
|
||||
@override
|
||||
String get paymentRequestMints => 'Принимаемые минты';
|
||||
|
||||
@override
|
||||
String get paymentRequestAnyMint => 'Любой минт';
|
||||
|
||||
@override
|
||||
String get paymentRequestPay => 'Оплатить';
|
||||
|
||||
@override
|
||||
String get paymentRequestPaying => 'Оплата...';
|
||||
|
||||
@override
|
||||
String get paymentRequestSuccess => 'Платёж успешно отправлен';
|
||||
|
||||
@override
|
||||
String get paymentRequestNoTransport =>
|
||||
'В этом запросе не настроен метод доставки';
|
||||
|
||||
@override
|
||||
String get paymentRequestTransport => 'Транспорт';
|
||||
|
||||
@override
|
||||
String get paymentRequestMintNotAccepted =>
|
||||
'Ваш активный минт не в списке принимаемых';
|
||||
|
||||
@override
|
||||
String paymentRequestUnitMismatch(String unit) {
|
||||
return 'Несовместимая единица: запрос требует $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get paymentRequestInsufficientBalance => 'Недостаточный баланс';
|
||||
|
||||
@override
|
||||
String get paymentRequestErrorParsing => 'Ошибка чтения запроса на оплату';
|
||||
|
||||
@override
|
||||
String get p2pkTitle => 'Ключи P2PK';
|
||||
@@ -1157,4 +1205,103 @@ class L10nRu extends L10n {
|
||||
|
||||
@override
|
||||
String get p2pkErrorCannotDeletePrimary => 'Невозможно удалить основной ключ';
|
||||
|
||||
@override
|
||||
String get request => 'Запросить';
|
||||
|
||||
@override
|
||||
String get requestPayment => 'Запросить платёж';
|
||||
|
||||
@override
|
||||
String get requestPaymentDescription =>
|
||||
'Создать унифицированный запрос на оплату';
|
||||
|
||||
@override
|
||||
String get generateRequest => 'Создать запрос';
|
||||
|
||||
@override
|
||||
String get generatingRequest => 'Создание запроса...';
|
||||
|
||||
@override
|
||||
String get requestPaymentReceived => 'Платёж получен';
|
||||
|
||||
@override
|
||||
String get requestDescriptionHint => 'Описание (необязательно)';
|
||||
|
||||
@override
|
||||
String get universal => 'Универсальный';
|
||||
|
||||
@override
|
||||
String get copiedToClipboard => 'Скопировано в буфер обмена';
|
||||
|
||||
@override
|
||||
String get swap => 'Обменять';
|
||||
|
||||
@override
|
||||
String get swapDescription => 'Конвертировать между sats и USD';
|
||||
|
||||
@override
|
||||
String get swapFrom => 'Из';
|
||||
|
||||
@override
|
||||
String get swapTo => 'В';
|
||||
|
||||
@override
|
||||
String get swapAction => 'Обменять';
|
||||
|
||||
@override
|
||||
String get swapEstimatedFee => 'Ориентировочная комиссия';
|
||||
|
||||
@override
|
||||
String get swapUseAll => 'Использовать всё';
|
||||
|
||||
@override
|
||||
String swapMinimum(String amount) {
|
||||
return 'Минимум: $amount';
|
||||
}
|
||||
|
||||
@override
|
||||
String get swapProcessing => 'Обработка обмена...';
|
||||
|
||||
@override
|
||||
String get swapSuccess => 'Обмен завершён';
|
||||
|
||||
@override
|
||||
String get swapErrorInsufficient => 'Недостаточный баланс';
|
||||
|
||||
@override
|
||||
String get swapErrorExpired => 'Котировка истекла';
|
||||
|
||||
@override
|
||||
String swapErrorGeneric(String error) {
|
||||
return 'Ошибка обмена: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get swapChartUnavailable => 'Цена недоступна · Нажмите для повтора';
|
||||
|
||||
@override
|
||||
String swapChartMinMax(String minPrice, String maxPrice) {
|
||||
return '24h Мин: $minPrice — Макс: $maxPrice';
|
||||
}
|
||||
|
||||
@override
|
||||
String get privacyPolicy => 'Политика конфиденциальности';
|
||||
|
||||
@override
|
||||
String get privacyTitle => 'МЫ НЕ СОБИРАЕМ НИЧЕГО';
|
||||
|
||||
@override
|
||||
String get privacyGoodbye => 'ПОКА';
|
||||
|
||||
@override
|
||||
String get privacyKeepReading => '(читай дальше, если хочешь…)';
|
||||
|
||||
@override
|
||||
String get privacyBody =>
|
||||
'Мы не знаем, кто ты\nМы не знаем, сколько у тебя\nМы не знаем, что ты делаешь';
|
||||
|
||||
@override
|
||||
String get privacyConclusion =>
|
||||
'Лучший способ защитить твои данные —\nне иметь их';
|
||||
}
|
||||
|
||||
@@ -293,6 +293,9 @@ class L10nSw extends L10n {
|
||||
@override
|
||||
String get insufficientBalance => 'Salio halitoshi';
|
||||
|
||||
@override
|
||||
String get feeExceedsAmount => 'Ada inazidi kiasi cha kutuma';
|
||||
|
||||
@override
|
||||
String tokenCreationError(String error) {
|
||||
return 'Hitilafu ya kuunda tokeni: $error';
|
||||
@@ -1057,7 +1060,54 @@ class L10nSw extends L10n {
|
||||
String get cameraPermissionDenied => 'Ruhusa ya kamera imekataliwa';
|
||||
|
||||
@override
|
||||
String get paymentRequestNotSupported => 'Maombi ya malipo bado hayatumiki';
|
||||
String get paymentRequestTitle => 'Ombi la malipo';
|
||||
|
||||
@override
|
||||
String get paymentRequestFrom => 'Ombi kutoka';
|
||||
|
||||
@override
|
||||
String get paymentRequestAmount => 'Kiasi kilichoombwa';
|
||||
|
||||
@override
|
||||
String get paymentRequestDescription => 'Maelezo';
|
||||
|
||||
@override
|
||||
String get paymentRequestMints => 'Mint zinazokubaliwa';
|
||||
|
||||
@override
|
||||
String get paymentRequestAnyMint => 'Mint yoyote';
|
||||
|
||||
@override
|
||||
String get paymentRequestPay => 'Lipa';
|
||||
|
||||
@override
|
||||
String get paymentRequestPaying => 'Inalipa...';
|
||||
|
||||
@override
|
||||
String get paymentRequestSuccess => 'Malipo yametumwa kwa mafanikio';
|
||||
|
||||
@override
|
||||
String get paymentRequestNoTransport =>
|
||||
'Ombi hili halina njia ya uwasilishaji iliyosanidiwa';
|
||||
|
||||
@override
|
||||
String get paymentRequestTransport => 'Njia ya usafirishaji';
|
||||
|
||||
@override
|
||||
String get paymentRequestMintNotAccepted =>
|
||||
'Mint yako hai haiko kwenye orodha ya mint zinazokubaliwa';
|
||||
|
||||
@override
|
||||
String paymentRequestUnitMismatch(String unit) {
|
||||
return 'Kitengo kisichooana: ombi linahitaji $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get paymentRequestInsufficientBalance => 'Salio haitoshi';
|
||||
|
||||
@override
|
||||
String get paymentRequestErrorParsing =>
|
||||
'Hitilafu wakati wa kusoma ombi la malipo';
|
||||
|
||||
@override
|
||||
String get p2pkTitle => 'Funguo za P2PK';
|
||||
@@ -1157,4 +1207,102 @@ class L10nSw extends L10n {
|
||||
|
||||
@override
|
||||
String get p2pkErrorCannotDeletePrimary => 'Haiwezekani kufuta ufunguo mkuu';
|
||||
|
||||
@override
|
||||
String get request => 'Omba';
|
||||
|
||||
@override
|
||||
String get requestPayment => 'Omba malipo';
|
||||
|
||||
@override
|
||||
String get requestPaymentDescription => 'Tengeneza ombi la malipo la pamoja';
|
||||
|
||||
@override
|
||||
String get generateRequest => 'Tengeneza ombi';
|
||||
|
||||
@override
|
||||
String get generatingRequest => 'Inatengeneza ombi...';
|
||||
|
||||
@override
|
||||
String get requestPaymentReceived => 'Malipo limepokelewa';
|
||||
|
||||
@override
|
||||
String get requestDescriptionHint => 'Maelezo (si lazima)';
|
||||
|
||||
@override
|
||||
String get universal => 'Universal';
|
||||
|
||||
@override
|
||||
String get copiedToClipboard => 'Imenakiliwa kwenye ubao wa kunakili';
|
||||
|
||||
@override
|
||||
String get swap => 'Badilisha';
|
||||
|
||||
@override
|
||||
String get swapDescription => 'Badilisha kati ya sats na USD';
|
||||
|
||||
@override
|
||||
String get swapFrom => 'Kutoka';
|
||||
|
||||
@override
|
||||
String get swapTo => 'Kwenda';
|
||||
|
||||
@override
|
||||
String get swapAction => 'Badilisha';
|
||||
|
||||
@override
|
||||
String get swapEstimatedFee => 'Ada inayokadiriwa';
|
||||
|
||||
@override
|
||||
String get swapUseAll => 'Tumia yote';
|
||||
|
||||
@override
|
||||
String swapMinimum(String amount) {
|
||||
return 'Kiwango cha chini: $amount';
|
||||
}
|
||||
|
||||
@override
|
||||
String get swapProcessing => 'Inashughulikia ubadilishaji...';
|
||||
|
||||
@override
|
||||
String get swapSuccess => 'Ubadilishaji umekamilika';
|
||||
|
||||
@override
|
||||
String get swapErrorInsufficient => 'Salio haitoshi';
|
||||
|
||||
@override
|
||||
String get swapErrorExpired => 'Bei imeisha muda';
|
||||
|
||||
@override
|
||||
String swapErrorGeneric(String error) {
|
||||
return 'Kosa la ubadilishaji: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get swapChartUnavailable => 'Bei haipatikani · Gusa kurudia';
|
||||
|
||||
@override
|
||||
String swapChartMinMax(String minPrice, String maxPrice) {
|
||||
return '24h Chini: $minPrice — Juu: $maxPrice';
|
||||
}
|
||||
|
||||
@override
|
||||
String get privacyPolicy => 'Sera ya faragha';
|
||||
|
||||
@override
|
||||
String get privacyTitle => 'HATUKUSANYI CHOCHOTE';
|
||||
|
||||
@override
|
||||
String get privacyGoodbye => 'KWAHERI';
|
||||
|
||||
@override
|
||||
String get privacyKeepReading => '(endelea kusoma ukitaka…)';
|
||||
|
||||
@override
|
||||
String get privacyBody =>
|
||||
'Hatujui wewe ni nani\nHatujui una kiasi gani\nHatujui unafanya nini';
|
||||
|
||||
@override
|
||||
String get privacyConclusion =>
|
||||
'Njia bora ya kulinda data yako\nni kutokuwa nayo';
|
||||
}
|
||||
|
||||
@@ -287,6 +287,9 @@ class L10nZh extends L10n {
|
||||
@override
|
||||
String get insufficientBalance => '余额不足';
|
||||
|
||||
@override
|
||||
String get feeExceedsAmount => '手续费超过发送金额';
|
||||
|
||||
@override
|
||||
String tokenCreationError(String error) {
|
||||
return '创建代币错误:$error';
|
||||
@@ -1034,7 +1037,51 @@ class L10nZh extends L10n {
|
||||
String get cameraPermissionDenied => '相机权限被拒绝';
|
||||
|
||||
@override
|
||||
String get paymentRequestNotSupported => '付款请求尚不支持';
|
||||
String get paymentRequestTitle => '付款请求';
|
||||
|
||||
@override
|
||||
String get paymentRequestFrom => '来自';
|
||||
|
||||
@override
|
||||
String get paymentRequestAmount => '请求金额';
|
||||
|
||||
@override
|
||||
String get paymentRequestDescription => '描述';
|
||||
|
||||
@override
|
||||
String get paymentRequestMints => '接受的铸造厂';
|
||||
|
||||
@override
|
||||
String get paymentRequestAnyMint => '任何铸造厂';
|
||||
|
||||
@override
|
||||
String get paymentRequestPay => '支付';
|
||||
|
||||
@override
|
||||
String get paymentRequestPaying => '支付中...';
|
||||
|
||||
@override
|
||||
String get paymentRequestSuccess => '付款发送成功';
|
||||
|
||||
@override
|
||||
String get paymentRequestNoTransport => '此请求未配置交付方式';
|
||||
|
||||
@override
|
||||
String get paymentRequestTransport => '传输方式';
|
||||
|
||||
@override
|
||||
String get paymentRequestMintNotAccepted => '您的活跃铸造厂不在接受的铸造厂列表中';
|
||||
|
||||
@override
|
||||
String paymentRequestUnitMismatch(String unit) {
|
||||
return '单位不兼容:请求需要 $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get paymentRequestInsufficientBalance => '余额不足';
|
||||
|
||||
@override
|
||||
String get paymentRequestErrorParsing => '读取付款请求时出错';
|
||||
|
||||
@override
|
||||
String get p2pkTitle => 'P2PK密钥';
|
||||
@@ -1131,4 +1178,100 @@ class L10nZh extends L10n {
|
||||
|
||||
@override
|
||||
String get p2pkErrorCannotDeletePrimary => '无法删除主密钥';
|
||||
|
||||
@override
|
||||
String get request => '请求';
|
||||
|
||||
@override
|
||||
String get requestPayment => '请求付款';
|
||||
|
||||
@override
|
||||
String get requestPaymentDescription => '生成统一付款请求';
|
||||
|
||||
@override
|
||||
String get generateRequest => '生成请求';
|
||||
|
||||
@override
|
||||
String get generatingRequest => '正在生成请求...';
|
||||
|
||||
@override
|
||||
String get requestPaymentReceived => '已收到付款';
|
||||
|
||||
@override
|
||||
String get requestDescriptionHint => '描述(可选)';
|
||||
|
||||
@override
|
||||
String get universal => '通用';
|
||||
|
||||
@override
|
||||
String get copiedToClipboard => '已复制到剪贴板';
|
||||
|
||||
@override
|
||||
String get swap => '兑换';
|
||||
|
||||
@override
|
||||
String get swapDescription => '在 Sats 和 USD 之间转换';
|
||||
|
||||
@override
|
||||
String get swapFrom => '从';
|
||||
|
||||
@override
|
||||
String get swapTo => '到';
|
||||
|
||||
@override
|
||||
String get swapAction => '兑换';
|
||||
|
||||
@override
|
||||
String get swapEstimatedFee => '预估手续费';
|
||||
|
||||
@override
|
||||
String get swapUseAll => '全部使用';
|
||||
|
||||
@override
|
||||
String swapMinimum(String amount) {
|
||||
return '最低: $amount';
|
||||
}
|
||||
|
||||
@override
|
||||
String get swapProcessing => '兑换处理中...';
|
||||
|
||||
@override
|
||||
String get swapSuccess => '兑换完成';
|
||||
|
||||
@override
|
||||
String get swapErrorInsufficient => '余额不足';
|
||||
|
||||
@override
|
||||
String get swapErrorExpired => '报价已过期';
|
||||
|
||||
@override
|
||||
String swapErrorGeneric(String error) {
|
||||
return '兑换错误: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get swapChartUnavailable => '价格不可用 · 点击重试';
|
||||
|
||||
@override
|
||||
String swapChartMinMax(String minPrice, String maxPrice) {
|
||||
return '24h 最低: $minPrice — 最高: $maxPrice';
|
||||
}
|
||||
|
||||
@override
|
||||
String get privacyPolicy => '隐私政策';
|
||||
|
||||
@override
|
||||
String get privacyTitle => '我们什么都不收集';
|
||||
|
||||
@override
|
||||
String get privacyGoodbye => '再见';
|
||||
|
||||
@override
|
||||
String get privacyKeepReading => '(想继续看就看吧…)';
|
||||
|
||||
@override
|
||||
String get privacyBody => '我们不知道你是谁\n我们不知道你有多少\n我们不知道你在做什么';
|
||||
|
||||
@override
|
||||
String get privacyConclusion => '保护数据的最好方式\n就是不拥有它';
|
||||
}
|
||||
|
||||
+50
-2
@@ -118,6 +118,7 @@
|
||||
"confirm": "Confirmar",
|
||||
"cancel": "Cancelar",
|
||||
"insufficientBalance": "Saldo insuficiente",
|
||||
"feeExceedsAmount": "A taxa excede o valor a enviar",
|
||||
"tokenCreationError": "Erro ao criar token: {error}",
|
||||
"@tokenCreationError": {
|
||||
"placeholders": {
|
||||
@@ -502,7 +503,22 @@
|
||||
"scanLightningInvoiceHint": "Escaneie um invoice Lightning (lnbc...)",
|
||||
"addMintQuestion": "Adicionar este mint?",
|
||||
"cameraPermissionDenied": "Permissão de câmera negada",
|
||||
"paymentRequestNotSupported": "Solicitações de pagamento ainda não são suportadas",
|
||||
"paymentRequestTitle": "Solicitação de pagamento",
|
||||
"paymentRequestFrom": "Solicitação de",
|
||||
"paymentRequestAmount": "Valor solicitado",
|
||||
"paymentRequestDescription": "Descrição",
|
||||
"paymentRequestMints": "Mints aceitos",
|
||||
"paymentRequestAnyMint": "Qualquer mint",
|
||||
"paymentRequestPay": "Pagar",
|
||||
"paymentRequestPaying": "Pagando...",
|
||||
"paymentRequestSuccess": "Pagamento enviado com sucesso",
|
||||
"paymentRequestNoTransport": "Esta solicitação não tem método de entrega configurado",
|
||||
"paymentRequestTransport": "Transporte",
|
||||
"paymentRequestMintNotAccepted": "Seu mint ativo não está na lista de mints aceitos",
|
||||
"paymentRequestUnitMismatch": "Unidade incompatível: a solicitação requer {unit}",
|
||||
"paymentRequestInsufficientBalance": "Saldo insuficiente",
|
||||
"paymentRequestErrorParsing": "Erro ao ler a solicitação de pagamento",
|
||||
|
||||
"p2pkTitle": "Chaves P2PK",
|
||||
"p2pkSettingsDescription": "Receber ecash bloqueado",
|
||||
"p2pkExperimental": "P2PK é experimental. Use com cautela.",
|
||||
@@ -534,5 +550,37 @@
|
||||
"p2pkErrorInvalidNsec": "nsec inválido",
|
||||
"p2pkErrorKeyAlreadyExists": "Esta chave já existe",
|
||||
"p2pkErrorKeyNotFound": "Chave não encontrada",
|
||||
"p2pkErrorCannotDeletePrimary": "Não é possível excluir a chave principal"
|
||||
"p2pkErrorCannotDeletePrimary": "Não é possível excluir a chave principal",
|
||||
|
||||
"request": "Solicitar",
|
||||
"requestPayment": "Solicitar pagamento",
|
||||
"requestPaymentDescription": "Gerar solicitação de pagamento unificada",
|
||||
"generateRequest": "Gerar solicitação",
|
||||
"generatingRequest": "Gerando solicitação...",
|
||||
"requestPaymentReceived": "Pagamento recebido",
|
||||
"requestDescriptionHint": "Descrição (opcional)",
|
||||
"universal": "Universal",
|
||||
"copiedToClipboard": "Copiado para a área de transferência",
|
||||
|
||||
"swap": "Trocar",
|
||||
"swapDescription": "Converter entre sats e USD",
|
||||
"swapFrom": "De",
|
||||
"swapTo": "Para",
|
||||
"swapAction": "Trocar",
|
||||
"swapEstimatedFee": "Taxa estimada",
|
||||
"swapUseAll": "Usar tudo",
|
||||
"swapMinimum": "Mínimo: {amount}",
|
||||
"swapProcessing": "Processando swap...",
|
||||
"swapSuccess": "Swap concluído",
|
||||
"swapErrorInsufficient": "Saldo insuficiente",
|
||||
"swapErrorExpired": "A cotação expirou",
|
||||
"swapErrorGeneric": "Erro no swap: {error}",
|
||||
"swapChartUnavailable": "Preço indisponível · Toque para tentar novamente",
|
||||
"swapChartMinMax": "24h Mín: {minPrice} — Máx: {maxPrice}",
|
||||
"privacyPolicy": "Política de privacidade",
|
||||
"privacyTitle": "NÃO RECOLHEMOS NADA",
|
||||
"privacyGoodbye": "ADEUS",
|
||||
"privacyKeepReading": "(continue lendo se quiser…)",
|
||||
"privacyBody": "Não sabemos quem você é\nNão sabemos quanto você tem\nNão sabemos o que você faz",
|
||||
"privacyConclusion": "A melhor forma de proteger seus dados\né não tê-los"
|
||||
}
|
||||
|
||||
+50
-2
@@ -118,6 +118,7 @@
|
||||
"confirm": "Подтвердить",
|
||||
"cancel": "Отмена",
|
||||
"insufficientBalance": "Недостаточный баланс",
|
||||
"feeExceedsAmount": "Комиссия превышает сумму отправки",
|
||||
"tokenCreationError": "Ошибка создания токена: {error}",
|
||||
"@tokenCreationError": {
|
||||
"placeholders": {
|
||||
@@ -502,7 +503,22 @@
|
||||
"scanLightningInvoiceHint": "Сканируйте Lightning счёт (lnbc...)",
|
||||
"addMintQuestion": "Добавить этот mint?",
|
||||
"cameraPermissionDenied": "Доступ к камере запрещён",
|
||||
"paymentRequestNotSupported": "Запросы на оплату пока не поддерживаются",
|
||||
"paymentRequestTitle": "Запрос на оплату",
|
||||
"paymentRequestFrom": "Запрос от",
|
||||
"paymentRequestAmount": "Запрошенная сумма",
|
||||
"paymentRequestDescription": "Описание",
|
||||
"paymentRequestMints": "Принимаемые минты",
|
||||
"paymentRequestAnyMint": "Любой минт",
|
||||
"paymentRequestPay": "Оплатить",
|
||||
"paymentRequestPaying": "Оплата...",
|
||||
"paymentRequestSuccess": "Платёж успешно отправлен",
|
||||
"paymentRequestNoTransport": "В этом запросе не настроен метод доставки",
|
||||
"paymentRequestTransport": "Транспорт",
|
||||
"paymentRequestMintNotAccepted": "Ваш активный минт не в списке принимаемых",
|
||||
"paymentRequestUnitMismatch": "Несовместимая единица: запрос требует {unit}",
|
||||
"paymentRequestInsufficientBalance": "Недостаточный баланс",
|
||||
"paymentRequestErrorParsing": "Ошибка чтения запроса на оплату",
|
||||
|
||||
"p2pkTitle": "Ключи P2PK",
|
||||
"p2pkSettingsDescription": "Получить заблокированный ecash",
|
||||
"p2pkExperimental": "P2PK экспериментальный. Используйте с осторожностью.",
|
||||
@@ -534,5 +550,37 @@
|
||||
"p2pkErrorInvalidNsec": "Недействительный nsec",
|
||||
"p2pkErrorKeyAlreadyExists": "Этот ключ уже существует",
|
||||
"p2pkErrorKeyNotFound": "Ключ не найден",
|
||||
"p2pkErrorCannotDeletePrimary": "Невозможно удалить основной ключ"
|
||||
"p2pkErrorCannotDeletePrimary": "Невозможно удалить основной ключ",
|
||||
|
||||
"request": "Запросить",
|
||||
"requestPayment": "Запросить платёж",
|
||||
"requestPaymentDescription": "Создать унифицированный запрос на оплату",
|
||||
"generateRequest": "Создать запрос",
|
||||
"generatingRequest": "Создание запроса...",
|
||||
"requestPaymentReceived": "Платёж получен",
|
||||
"requestDescriptionHint": "Описание (необязательно)",
|
||||
"universal": "Универсальный",
|
||||
"copiedToClipboard": "Скопировано в буфер обмена",
|
||||
|
||||
"swap": "Обменять",
|
||||
"swapDescription": "Конвертировать между sats и USD",
|
||||
"swapFrom": "Из",
|
||||
"swapTo": "В",
|
||||
"swapAction": "Обменять",
|
||||
"swapEstimatedFee": "Ориентировочная комиссия",
|
||||
"swapUseAll": "Использовать всё",
|
||||
"swapMinimum": "Минимум: {amount}",
|
||||
"swapProcessing": "Обработка обмена...",
|
||||
"swapSuccess": "Обмен завершён",
|
||||
"swapErrorInsufficient": "Недостаточный баланс",
|
||||
"swapErrorExpired": "Котировка истекла",
|
||||
"swapErrorGeneric": "Ошибка обмена: {error}",
|
||||
"swapChartUnavailable": "Цена недоступна · Нажмите для повтора",
|
||||
"swapChartMinMax": "24h Мин: {minPrice} — Макс: {maxPrice}",
|
||||
"privacyPolicy": "Политика конфиденциальности",
|
||||
"privacyTitle": "МЫ НЕ СОБИРАЕМ НИЧЕГО",
|
||||
"privacyGoodbye": "ПОКА",
|
||||
"privacyKeepReading": "(читай дальше, если хочешь…)",
|
||||
"privacyBody": "Мы не знаем, кто ты\nМы не знаем, сколько у тебя\nМы не знаем, что ты делаешь",
|
||||
"privacyConclusion": "Лучший способ защитить твои данные —\nне иметь их"
|
||||
}
|
||||
|
||||
+50
-2
@@ -118,6 +118,7 @@
|
||||
"confirm": "Thibitisha",
|
||||
"cancel": "Ghairi",
|
||||
"insufficientBalance": "Salio halitoshi",
|
||||
"feeExceedsAmount": "Ada inazidi kiasi cha kutuma",
|
||||
"tokenCreationError": "Hitilafu ya kuunda tokeni: {error}",
|
||||
"@tokenCreationError": {
|
||||
"placeholders": {
|
||||
@@ -502,7 +503,22 @@
|
||||
"scanLightningInvoiceHint": "Changanua ankara ya Lightning (lnbc...)",
|
||||
"addMintQuestion": "Ongeza mint hii?",
|
||||
"cameraPermissionDenied": "Ruhusa ya kamera imekataliwa",
|
||||
"paymentRequestNotSupported": "Maombi ya malipo bado hayatumiki",
|
||||
"paymentRequestTitle": "Ombi la malipo",
|
||||
"paymentRequestFrom": "Ombi kutoka",
|
||||
"paymentRequestAmount": "Kiasi kilichoombwa",
|
||||
"paymentRequestDescription": "Maelezo",
|
||||
"paymentRequestMints": "Mint zinazokubaliwa",
|
||||
"paymentRequestAnyMint": "Mint yoyote",
|
||||
"paymentRequestPay": "Lipa",
|
||||
"paymentRequestPaying": "Inalipa...",
|
||||
"paymentRequestSuccess": "Malipo yametumwa kwa mafanikio",
|
||||
"paymentRequestNoTransport": "Ombi hili halina njia ya uwasilishaji iliyosanidiwa",
|
||||
"paymentRequestTransport": "Njia ya usafirishaji",
|
||||
"paymentRequestMintNotAccepted": "Mint yako hai haiko kwenye orodha ya mint zinazokubaliwa",
|
||||
"paymentRequestUnitMismatch": "Kitengo kisichooana: ombi linahitaji {unit}",
|
||||
"paymentRequestInsufficientBalance": "Salio haitoshi",
|
||||
"paymentRequestErrorParsing": "Hitilafu wakati wa kusoma ombi la malipo",
|
||||
|
||||
"p2pkTitle": "Funguo za P2PK",
|
||||
"p2pkSettingsDescription": "Pokea ecash iliyofungwa",
|
||||
"p2pkExperimental": "P2PK ni ya majaribio. Tumia kwa uangalifu.",
|
||||
@@ -534,5 +550,37 @@
|
||||
"p2pkErrorInvalidNsec": "nsec batili",
|
||||
"p2pkErrorKeyAlreadyExists": "Ufunguo huu tayari upo",
|
||||
"p2pkErrorKeyNotFound": "Ufunguo haujapatikana",
|
||||
"p2pkErrorCannotDeletePrimary": "Haiwezekani kufuta ufunguo mkuu"
|
||||
"p2pkErrorCannotDeletePrimary": "Haiwezekani kufuta ufunguo mkuu",
|
||||
|
||||
"request": "Omba",
|
||||
"requestPayment": "Omba malipo",
|
||||
"requestPaymentDescription": "Tengeneza ombi la malipo la pamoja",
|
||||
"generateRequest": "Tengeneza ombi",
|
||||
"generatingRequest": "Inatengeneza ombi...",
|
||||
"requestPaymentReceived": "Malipo limepokelewa",
|
||||
"requestDescriptionHint": "Maelezo (si lazima)",
|
||||
"universal": "Universal",
|
||||
"copiedToClipboard": "Imenakiliwa kwenye ubao wa kunakili",
|
||||
|
||||
"swap": "Badilisha",
|
||||
"swapDescription": "Badilisha kati ya sats na USD",
|
||||
"swapFrom": "Kutoka",
|
||||
"swapTo": "Kwenda",
|
||||
"swapAction": "Badilisha",
|
||||
"swapEstimatedFee": "Ada inayokadiriwa",
|
||||
"swapUseAll": "Tumia yote",
|
||||
"swapMinimum": "Kiwango cha chini: {amount}",
|
||||
"swapProcessing": "Inashughulikia ubadilishaji...",
|
||||
"swapSuccess": "Ubadilishaji umekamilika",
|
||||
"swapErrorInsufficient": "Salio haitoshi",
|
||||
"swapErrorExpired": "Bei imeisha muda",
|
||||
"swapErrorGeneric": "Kosa la ubadilishaji: {error}",
|
||||
"swapChartUnavailable": "Bei haipatikani · Gusa kurudia",
|
||||
"swapChartMinMax": "24h Chini: {minPrice} — Juu: {maxPrice}",
|
||||
"privacyPolicy": "Sera ya faragha",
|
||||
"privacyTitle": "HATUKUSANYI CHOCHOTE",
|
||||
"privacyGoodbye": "KWAHERI",
|
||||
"privacyKeepReading": "(endelea kusoma ukitaka…)",
|
||||
"privacyBody": "Hatujui wewe ni nani\nHatujui una kiasi gani\nHatujui unafanya nini",
|
||||
"privacyConclusion": "Njia bora ya kulinda data yako\nni kutokuwa nayo"
|
||||
}
|
||||
|
||||
+50
-2
@@ -118,6 +118,7 @@
|
||||
"confirm": "确认",
|
||||
"cancel": "取消",
|
||||
"insufficientBalance": "余额不足",
|
||||
"feeExceedsAmount": "手续费超过发送金额",
|
||||
"tokenCreationError": "创建代币错误:{error}",
|
||||
"@tokenCreationError": {
|
||||
"placeholders": {
|
||||
@@ -502,7 +503,22 @@
|
||||
"scanLightningInvoiceHint": "扫描闪电发票(lnbc...)",
|
||||
"addMintQuestion": "添加此铸造厂?",
|
||||
"cameraPermissionDenied": "相机权限被拒绝",
|
||||
"paymentRequestNotSupported": "付款请求尚不支持",
|
||||
"paymentRequestTitle": "付款请求",
|
||||
"paymentRequestFrom": "来自",
|
||||
"paymentRequestAmount": "请求金额",
|
||||
"paymentRequestDescription": "描述",
|
||||
"paymentRequestMints": "接受的铸造厂",
|
||||
"paymentRequestAnyMint": "任何铸造厂",
|
||||
"paymentRequestPay": "支付",
|
||||
"paymentRequestPaying": "支付中...",
|
||||
"paymentRequestSuccess": "付款发送成功",
|
||||
"paymentRequestNoTransport": "此请求未配置交付方式",
|
||||
"paymentRequestTransport": "传输方式",
|
||||
"paymentRequestMintNotAccepted": "您的活跃铸造厂不在接受的铸造厂列表中",
|
||||
"paymentRequestUnitMismatch": "单位不兼容:请求需要 {unit}",
|
||||
"paymentRequestInsufficientBalance": "余额不足",
|
||||
"paymentRequestErrorParsing": "读取付款请求时出错",
|
||||
|
||||
"p2pkTitle": "P2PK密钥",
|
||||
"p2pkSettingsDescription": "接收锁定的ecash",
|
||||
"p2pkExperimental": "P2PK是实验性功能。请谨慎使用。",
|
||||
@@ -534,5 +550,37 @@
|
||||
"p2pkErrorInvalidNsec": "无效的nsec",
|
||||
"p2pkErrorKeyAlreadyExists": "此密钥已存在",
|
||||
"p2pkErrorKeyNotFound": "未找到密钥",
|
||||
"p2pkErrorCannotDeletePrimary": "无法删除主密钥"
|
||||
"p2pkErrorCannotDeletePrimary": "无法删除主密钥",
|
||||
|
||||
"request": "请求",
|
||||
"requestPayment": "请求付款",
|
||||
"requestPaymentDescription": "生成统一付款请求",
|
||||
"generateRequest": "生成请求",
|
||||
"generatingRequest": "正在生成请求...",
|
||||
"requestPaymentReceived": "已收到付款",
|
||||
"requestDescriptionHint": "描述(可选)",
|
||||
"universal": "通用",
|
||||
"copiedToClipboard": "已复制到剪贴板",
|
||||
|
||||
"swap": "兑换",
|
||||
"swapDescription": "在 Sats 和 USD 之间转换",
|
||||
"swapFrom": "从",
|
||||
"swapTo": "到",
|
||||
"swapAction": "兑换",
|
||||
"swapEstimatedFee": "预估手续费",
|
||||
"swapUseAll": "全部使用",
|
||||
"swapMinimum": "最低: {amount}",
|
||||
"swapProcessing": "兑换处理中...",
|
||||
"swapSuccess": "兑换完成",
|
||||
"swapErrorInsufficient": "余额不足",
|
||||
"swapErrorExpired": "报价已过期",
|
||||
"swapErrorGeneric": "兑换错误: {error}",
|
||||
"swapChartUnavailable": "价格不可用 · 点击重试",
|
||||
"swapChartMinMax": "24h 最低: {minPrice} — 最高: {maxPrice}",
|
||||
"privacyPolicy": "隐私政策",
|
||||
"privacyTitle": "我们什么都不收集",
|
||||
"privacyGoodbye": "再见",
|
||||
"privacyKeepReading": "(想继续看就看吧…)",
|
||||
"privacyBody": "我们不知道你是谁\n我们不知道你有多少\n我们不知道你在做什么",
|
||||
"privacyConclusion": "保护数据的最好方式\n就是不拥有它"
|
||||
}
|
||||
|
||||
@@ -7,13 +7,16 @@ import '../src/rust/api/wallet.dart';
|
||||
import '../src/rust/api/token.dart';
|
||||
import '../src/rust/api/mint_info.dart';
|
||||
import '../src/rust/api/keys.dart';
|
||||
import '../src/rust/api/payment_request.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import '../data/transaction_meta_storage.dart';
|
||||
import '../data/pending_token.dart';
|
||||
import '../data/pending_token_storage.dart';
|
||||
import '../core/utils/keyset_debug.dart';
|
||||
import '../core/utils/p2pk_utils.dart';
|
||||
import '../widgets/effects/cashu_confetti.dart';
|
||||
|
||||
/// Helper class para info de token parseado
|
||||
@@ -65,6 +68,10 @@ class WalletProvider extends ChangeNotifier {
|
||||
/// Caché de MintInfo por URL (nombre, logo, contactos, etc.)
|
||||
final Map<String, MintInfo> _mintInfoCache = {};
|
||||
|
||||
/// Caché del balance stream para evitar recrear StreamSink en cada build().
|
||||
Stream<BigInt>? _cachedBalanceStream;
|
||||
String? _cachedBalanceKey;
|
||||
|
||||
/// Mint activo actualmente
|
||||
String? _activeMintUrl;
|
||||
|
||||
@@ -76,6 +83,7 @@ class WalletProvider extends ChangeNotifier {
|
||||
static const _activeMintKey = 'wallet_active_mint';
|
||||
static const _activeUnitKey = 'wallet_active_unit';
|
||||
static const _pendingMintInvoicesKey = 'pending_mint_invoices';
|
||||
static const _pendingNostrRequestKey = 'pending_nostr_request';
|
||||
|
||||
/// Mint de Cuba Bitcoin - siempre aparece primero en la lista
|
||||
static const cubaBitcoinMint = 'https://mint.cubabitcoin.org';
|
||||
@@ -348,6 +356,10 @@ class WalletProvider extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
debugPrint('Mints restaurados: ${_mintUnits.keys.length}');
|
||||
|
||||
// Re-detectar unidades en background (sin bloquear arranque).
|
||||
// Si el mint ahora soporta más unidades (ej: usd), se actualizan.
|
||||
_refreshAllMintUnits();
|
||||
} else {
|
||||
// Primera vez - agregar mint por defecto
|
||||
await addMint('https://mint.cubabitcoin.org');
|
||||
@@ -399,6 +411,62 @@ class WalletProvider extends ChangeNotifier {
|
||||
return await getWallet(_activeMintUrl!, _activeUnit);
|
||||
}
|
||||
|
||||
/// Re-detecta unidades de todos los mints conocidos via NUT-04.
|
||||
/// Corre en background sin bloquear el arranque. Solo notifica si hay cambios.
|
||||
void _refreshAllMintUnits() async {
|
||||
bool changed = false;
|
||||
|
||||
for (final mintUrl in _mintUnits.keys.toList()) {
|
||||
try {
|
||||
final mintInfo = await getMintInfo(mintUrl: mintUrl);
|
||||
final units = <String>{};
|
||||
for (final method in mintInfo.nuts.nut04.methods) {
|
||||
units.add(method.unit);
|
||||
}
|
||||
final unitList = units.isEmpty ? ['sat'] : units.toList();
|
||||
unitList.sort((a, b) {
|
||||
if (a == 'sat') return -1;
|
||||
if (b == 'sat') return 1;
|
||||
return a.compareTo(b);
|
||||
});
|
||||
|
||||
final previous = _mintUnits[mintUrl];
|
||||
if (!_listEquals(previous, unitList)) {
|
||||
_mintUnits[mintUrl] = unitList;
|
||||
changed = true;
|
||||
debugPrint('Unidades actualizadas para $mintUrl: $unitList');
|
||||
|
||||
// Validar _activeUnit si este es el mint activo
|
||||
if (mintUrl == _activeMintUrl && !unitList.contains(_activeUnit)) {
|
||||
_activeUnit = unitList.first;
|
||||
debugPrint('Active unit reset to ${unitList.first} for $mintUrl');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Error refrescando unidades de $mintUrl: $e');
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
try {
|
||||
await _saveMints();
|
||||
} catch (e) {
|
||||
debugPrint('Error guardando mints actualizados: $e');
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Compara dos listas de strings por valor.
|
||||
bool _listEquals(List<String>? a, List<String>? b) {
|
||||
if (a == null || b == null) return a == b;
|
||||
if (a.length != b.length) return false;
|
||||
for (int i = 0; i < a.length; i++) {
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// MULTI-MINT OPERATIONS
|
||||
// ============================================================
|
||||
@@ -598,8 +666,21 @@ class WalletProvider extends ChangeNotifier {
|
||||
// ============================================================
|
||||
|
||||
/// Stream de balance del wallet activo (reactivo).
|
||||
/// Cacheado para que StreamBuilder reciba el mismo objeto entre rebuilds
|
||||
/// y no mate el StreamSink de Rust.
|
||||
Stream<BigInt>? streamBalance() {
|
||||
return activeWallet?.streamBalance();
|
||||
final wallet = activeWallet;
|
||||
if (wallet == null) {
|
||||
_cachedBalanceStream = null;
|
||||
_cachedBalanceKey = null;
|
||||
return null;
|
||||
}
|
||||
final key = '$_activeMintUrl:$_activeUnit';
|
||||
if (_cachedBalanceKey != key) {
|
||||
_cachedBalanceStream = wallet.streamBalance().asBroadcastStream();
|
||||
_cachedBalanceKey = key;
|
||||
}
|
||||
return _cachedBalanceStream;
|
||||
}
|
||||
|
||||
/// Obtiene el balance del wallet activo.
|
||||
@@ -840,7 +921,20 @@ class WalletProvider extends ChangeNotifier {
|
||||
// DEBUG: transacciones ANTES del receive
|
||||
await _debugLogTransactions(wallet, 'BEFORE receive');
|
||||
|
||||
final amount = await wallet.receive(token: token, opts: opts);
|
||||
BigInt amount;
|
||||
try {
|
||||
amount = await wallet.receive(token: token, opts: opts);
|
||||
} catch (e) {
|
||||
final errorStr = e.toString().toLowerCase();
|
||||
if (errorStr.contains('already signed')) {
|
||||
debugPrint('[RECEIVE] Already signed error, restoring wallet counters...');
|
||||
await wallet.restore();
|
||||
// Retry once after counter resync
|
||||
amount = await wallet.receive(token: token, opts: opts);
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// DEBUG: counters después de receive
|
||||
await KeysetDebug.logCounters('AFTER receive ($unit)');
|
||||
@@ -962,45 +1056,33 @@ class WalletProvider extends ChangeNotifier {
|
||||
Future<String> confirmSend(PreparedSend prepared, String? memo) async {
|
||||
final wallet = await getActiveWallet();
|
||||
|
||||
final token = await wallet.send(
|
||||
final result = await wallet.send(
|
||||
send: prepared,
|
||||
memo: memo,
|
||||
includeMemo: memo != null && memo.isNotEmpty,
|
||||
);
|
||||
|
||||
// Guardar token en storage local para mostrar en detalles del historial.
|
||||
// Usamos hash del token como key temporal; después buscaremos la transacción.
|
||||
await _saveTokenForRecentTransaction(token.encoded);
|
||||
|
||||
notifyListeners();
|
||||
return token.encoded;
|
||||
}
|
||||
|
||||
/// Guarda el token para la transacción más reciente de tipo send.
|
||||
Future<void> _saveTokenForRecentTransaction(String tokenEncoded) async {
|
||||
try {
|
||||
// Obtener transacciones outgoing más recientes
|
||||
final wallet = await getActiveWallet();
|
||||
final txs = await wallet.listTransactions(
|
||||
direction: TransactionDirection.outgoing,
|
||||
);
|
||||
|
||||
if (txs.isNotEmpty) {
|
||||
// La más reciente debería ser la que acabamos de crear
|
||||
final recentTx = txs.first;
|
||||
|
||||
// Save token metadata using the deterministic transaction ID returned by CDK
|
||||
// (SHA-256 of sorted proof Y values — no racy listTransactions needed)
|
||||
// Best-effort: don't fail the send if metadata persistence fails
|
||||
final txId = result.transactionId;
|
||||
if (txId != null && txId.isNotEmpty) {
|
||||
try {
|
||||
await _txMetaStorage.save(
|
||||
recentTx.id,
|
||||
txId,
|
||||
TransactionMeta(
|
||||
type: TransactionType.cashu,
|
||||
token: tokenEncoded,
|
||||
token: result.token.encoded,
|
||||
),
|
||||
);
|
||||
debugPrint('Token guardado para tx ${recentTx.id}');
|
||||
debugPrint('Token guardado para tx $txId');
|
||||
} catch (e) {
|
||||
debugPrint('Error guardando send metadata: $e');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Error guardando token metadata: $e');
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
return result.token.encoded;
|
||||
}
|
||||
|
||||
/// Cancela un envío preparado (libera proofs reservados).
|
||||
@@ -1062,6 +1144,27 @@ class WalletProvider extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PAYMENT REQUEST (NUT-18/26)
|
||||
// ============================================================
|
||||
|
||||
/// Paga un Payment Request (NUT-18 creqA / NUT-26 creqB / BIP-321).
|
||||
/// CDK maneja: NUT-10 spending conditions, transporte (Nostr/HTTP POST),
|
||||
/// preparación del token y entrega automática.
|
||||
Future<void> payPaymentRequest(
|
||||
String encodedRequest, {
|
||||
BigInt? customAmount,
|
||||
}) async {
|
||||
final wallet = await getActiveWallet();
|
||||
debugPrint('[PAY_REQUEST] Paying payment request via ${wallet.mintUrl}');
|
||||
await wallet.payPaymentRequest(
|
||||
encoded: encodedRequest,
|
||||
customAmount: customAmount,
|
||||
);
|
||||
debugPrint('[PAY_REQUEST] Payment delivered successfully');
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// MINT (Depositar via Lightning)
|
||||
// ============================================================
|
||||
@@ -1091,12 +1194,14 @@ class WalletProvider extends ChangeNotifier {
|
||||
_activeMintController!.close();
|
||||
}
|
||||
|
||||
// StreamController que la UI puede escuchar y cancelar libremente
|
||||
final controller = StreamController<MintQuote>();
|
||||
// StreamController que la UI puede escuchar y cancelar libremente.
|
||||
// sync: true para no perder eventos rápidos (paid → issued en µs).
|
||||
final controller = StreamController<MintQuote>(sync: true);
|
||||
_activeMintController = controller;
|
||||
|
||||
// Cancelar suscripción anterior si existe (await evita race de callbacks)
|
||||
await _activeMintSubscription?.cancel();
|
||||
// Cancelar suscripción anterior sin await: el task de Rust (polling + WS)
|
||||
// puede tardar en cerrar y bloquearía la creación de una nueva factura.
|
||||
_activeMintSubscription?.cancel();
|
||||
_activeMintSubscription = null;
|
||||
|
||||
// Suscribirse al stream del CDK desde el provider (persiste sin UI)
|
||||
@@ -1104,7 +1209,7 @@ class WalletProvider extends ChangeNotifier {
|
||||
amount: amount,
|
||||
description: description,
|
||||
).listen(
|
||||
(quote) {
|
||||
(quote) async {
|
||||
// Guardar invoice temprano en SharedPreferences
|
||||
if (quote.state == MintQuoteState.unpaid) {
|
||||
invoiceBolt11 = quote.request;
|
||||
@@ -1113,8 +1218,10 @@ class WalletProvider extends ChangeNotifier {
|
||||
|
||||
// Cuando se completa, guardar metadata, confetti, limpiar pending
|
||||
if (quote.state == MintQuoteState.issued && invoiceBolt11 != null) {
|
||||
_saveMintMetadata(wallet, invoiceBolt11!);
|
||||
_removePendingMintInvoice(quote.id);
|
||||
final saved = await _saveMintMetadata(wallet, invoiceBolt11!, quote.transactionId);
|
||||
// Only remove pending invoice if metadata was saved;
|
||||
// otherwise _matchPendingMintInvoices can recover it on next startup
|
||||
if (saved) _removePendingMintInvoice(quote.id);
|
||||
}
|
||||
|
||||
// Reenviar a la UI (si sigue escuchando)
|
||||
@@ -1223,29 +1330,155 @@ class WalletProvider extends ChangeNotifier {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Guarda metadata para una transacción de mint (Lightning deposit).
|
||||
Future<void> _saveMintMetadata(Wallet wallet, String invoice) async {
|
||||
// ============================================================
|
||||
// PENDING NOSTR PAYMENT REQUESTS
|
||||
// ============================================================
|
||||
|
||||
/// TTL for pending Nostr requests (24 hours).
|
||||
static const _pendingNostrRequestTtl = Duration(hours: 24);
|
||||
StreamSubscription<NostrPaymentEvent>? _pendingNostrSubscription;
|
||||
bool _pendingNostrResumeAttempted = false;
|
||||
static const _pendingNostrSecretKey = 'pending_nostr_secret';
|
||||
static const _secureStorage = FlutterSecureStorage();
|
||||
|
||||
/// Save a pending Nostr payment request for recovery after app restart.
|
||||
/// Secret key goes to FlutterSecureStorage, metadata to SharedPreferences.
|
||||
Future<void> savePendingNostrRequest(PersistedRequestData data) async {
|
||||
try {
|
||||
final txs = await wallet.listTransactions(
|
||||
direction: TransactionDirection.incoming,
|
||||
// Secret key in encrypted storage
|
||||
await _secureStorage.write(
|
||||
key: _pendingNostrSecretKey,
|
||||
value: data.secretHex,
|
||||
);
|
||||
// Metadata in SharedPreferences (no secrets)
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_pendingNostrRequestKey, jsonEncode({
|
||||
'pubkeyHex': data.pubkeyHex,
|
||||
'relays': data.relays,
|
||||
'amount': data.amount?.toString(),
|
||||
'unit': data.unit,
|
||||
'mintUrl': data.mintUrl,
|
||||
'createdAt': DateTime.now().toIso8601String(),
|
||||
}));
|
||||
} catch (e) {
|
||||
debugPrint('Error saving pending Nostr request: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove the pending Nostr payment request (payment received or cancelled).
|
||||
Future<void> removePendingNostrRequest() async {
|
||||
try {
|
||||
await _secureStorage.delete(key: _pendingNostrSecretKey);
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_pendingNostrRequestKey);
|
||||
} catch (e) {
|
||||
debugPrint('Error removing pending Nostr request: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Check for a pending Nostr payment request and resume listening.
|
||||
/// Called automatically during app startup. Guarded against duplicate calls.
|
||||
Future<void> resumePendingNostrRequest() async {
|
||||
if (_pendingNostrResumeAttempted) return;
|
||||
_pendingNostrResumeAttempted = true;
|
||||
await _pendingNostrSubscription?.cancel();
|
||||
_pendingNostrSubscription = null;
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonStr = prefs.getString(_pendingNostrRequestKey);
|
||||
if (jsonStr == null) return;
|
||||
|
||||
final map = Map<String, dynamic>.from(jsonDecode(jsonStr));
|
||||
|
||||
// Check TTL
|
||||
final createdAt = DateTime.tryParse(map['createdAt'] ?? '');
|
||||
if (createdAt == null ||
|
||||
DateTime.now().difference(createdAt) > _pendingNostrRequestTtl) {
|
||||
await removePendingNostrRequest();
|
||||
debugPrint('Pending Nostr request expired, removed');
|
||||
return;
|
||||
}
|
||||
|
||||
// Read secret from secure storage
|
||||
final secretHex = await _secureStorage.read(key: _pendingNostrSecretKey);
|
||||
if (secretHex == null) {
|
||||
await prefs.remove(_pendingNostrRequestKey);
|
||||
debugPrint('Pending Nostr request has no secret key, removed');
|
||||
return;
|
||||
}
|
||||
|
||||
// Reconstruct handle
|
||||
final data = PersistedRequestData(
|
||||
secretHex: secretHex,
|
||||
pubkeyHex: map['pubkeyHex'] as String,
|
||||
relays: List<String>.from(map['relays']),
|
||||
amount: map['amount'] != null
|
||||
? BigInt.tryParse(map['amount'] as String)
|
||||
: null,
|
||||
unit: map['unit'] as String,
|
||||
mintUrl: map['mintUrl'] as String,
|
||||
);
|
||||
|
||||
if (txs.isNotEmpty) {
|
||||
final recentTx = txs.first;
|
||||
final handle = NostrListenerHandle.fromPersisted(data: data);
|
||||
|
||||
// Get or create the wallet for this mint+unit (lazy instantiation)
|
||||
Wallet wallet;
|
||||
try {
|
||||
wallet = await getWallet(data.mintUrl, data.unit);
|
||||
} catch (e) {
|
||||
debugPrint('No wallet for pending Nostr request: $e');
|
||||
await removePendingNostrRequest();
|
||||
return;
|
||||
}
|
||||
|
||||
// Resume listening in background
|
||||
debugPrint('Resuming pending Nostr payment request...');
|
||||
_pendingNostrSubscription = wallet.waitForNostrPayment(handle: handle).listen(
|
||||
(event) {
|
||||
if (event.state == NostrPaymentState.received) {
|
||||
debugPrint('Pending Nostr payment received: ${event.amount}');
|
||||
removePendingNostrRequest();
|
||||
confettiController.fire();
|
||||
_pendingNostrSubscription?.cancel();
|
||||
_pendingNostrSubscription = null;
|
||||
}
|
||||
},
|
||||
onError: (error) {
|
||||
debugPrint('Pending Nostr listener error (kept for retry): $error');
|
||||
_pendingNostrSubscription = null;
|
||||
_pendingNostrResumeAttempted = false; // Allow retry on next startup
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('Error resuming pending Nostr request: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Guarda metadata para una transacción de mint (Lightning deposit).
|
||||
/// Returns true if metadata was actually saved.
|
||||
Future<bool> _saveMintMetadata(Wallet wallet, String invoice, String? transactionId) async {
|
||||
try {
|
||||
if (transactionId != null && transactionId.isNotEmpty) {
|
||||
await _txMetaStorage.save(
|
||||
recentTx.id,
|
||||
transactionId,
|
||||
TransactionMeta(
|
||||
type: TransactionType.lightning,
|
||||
invoice: invoice,
|
||||
),
|
||||
);
|
||||
debugPrint('Mint metadata guardada para tx ${recentTx.id}');
|
||||
debugPrint('Mint metadata guardada para tx $transactionId');
|
||||
confettiController.fire();
|
||||
notifyListeners();
|
||||
return true;
|
||||
} else {
|
||||
debugPrint('Mint metadata: no transaction ID available');
|
||||
confettiController.fire();
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Error guardando mint metadata: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1265,19 +1498,57 @@ class WalletProvider extends ChangeNotifier {
|
||||
}
|
||||
|
||||
/// Ejecuta el pago del invoice.
|
||||
/// Guarda metadata type=lightning para identificar en historial.
|
||||
/// Usa prepare/confirm/cancel para recuperar proofs si el pago falla.
|
||||
Future<BigInt> melt(MeltQuote quote) async {
|
||||
final wallet = await getActiveWallet();
|
||||
final totalPaid = await wallet.melt(quote: quote);
|
||||
final prepared = await wallet.prepareMelt(quote: quote);
|
||||
try {
|
||||
final totalPaid = await wallet.confirmMelt(melt: prepared);
|
||||
|
||||
// Guardar metadata con el invoice
|
||||
if (_pendingMeltInvoice != null) {
|
||||
await _saveMeltMetadata(wallet, _pendingMeltInvoice!);
|
||||
_pendingMeltInvoice = null;
|
||||
// Guardar metadata con el invoice
|
||||
if (_pendingMeltInvoice != null) {
|
||||
await _saveMeltMetadata(wallet, _pendingMeltInvoice!);
|
||||
_pendingMeltInvoice = null;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
return totalPaid;
|
||||
} catch (e) {
|
||||
try {
|
||||
await wallet.cancelMelt(melt: prepared);
|
||||
} catch (cancelErr) {
|
||||
debugPrint('[MELT] cancelMelt failed: $cancelErr');
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
return totalPaid;
|
||||
/// Recupera proofs huérfanas en PendingSpent consultando el mint.
|
||||
/// Solo revierte proofs que el mint confirma como no gastadas.
|
||||
/// Si [mintUrl] es null, escanea todos los mints.
|
||||
/// Retorna {count, amount} total de proofs recuperadas.
|
||||
Future<ReclaimResult> reclaimPendingProofs({String? mintUrl}) async {
|
||||
var totalCount = BigInt.zero;
|
||||
var totalAmount = BigInt.zero;
|
||||
final mints = mintUrl != null
|
||||
? {mintUrl: _mintUnits[mintUrl] ?? ['sat']}
|
||||
: _mintUnits;
|
||||
for (final entry in mints.entries) {
|
||||
for (final unit in entry.value) {
|
||||
try {
|
||||
final wallet = await getWallet(entry.key, unit);
|
||||
final result = await wallet.reclaimPendingProofs();
|
||||
totalCount += result.count;
|
||||
totalAmount += result.amount;
|
||||
} catch (e) {
|
||||
debugPrint('Reclaim pending proofs failed for ${entry.key}:$unit: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (totalCount > BigInt.zero) {
|
||||
notifyListeners();
|
||||
}
|
||||
return ReclaimResult(count: totalCount, amount: totalAmount);
|
||||
}
|
||||
|
||||
/// Guarda metadata para una transacción de melt (Lightning withdrawal).
|
||||
@@ -1304,6 +1575,58 @@ class WalletProvider extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Guarda metadata para el lado melt (enviado) de un swap.
|
||||
/// Busca la tx outgoing más reciente que coincida con el monto.
|
||||
Future<void> saveSwapMeltMetadata(Wallet wallet, String invoice, BigInt amount) async {
|
||||
try {
|
||||
final txs = await wallet.listTransactions(
|
||||
direction: TransactionDirection.outgoing,
|
||||
);
|
||||
final tx = txs.cast<Transaction?>().firstWhere(
|
||||
(t) => t!.amount == amount && !_txMetaStorage.has(t.id),
|
||||
orElse: () => null,
|
||||
);
|
||||
if (tx != null) {
|
||||
await _txMetaStorage.save(
|
||||
tx.id,
|
||||
TransactionMeta(type: TransactionType.lightning, invoice: invoice),
|
||||
);
|
||||
debugPrint('Swap melt metadata guardada para tx ${tx.id}');
|
||||
} else {
|
||||
debugPrint('No matching untagged outgoing tx found for amount $amount');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Error guardando swap melt metadata: $e');
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Guarda metadata para el lado mint (recibido) de un swap.
|
||||
/// Busca la tx incoming más reciente que coincida con el monto.
|
||||
Future<void> saveSwapMintMetadata(Wallet wallet, String invoice, BigInt amount) async {
|
||||
try {
|
||||
final txs = await wallet.listTransactions(
|
||||
direction: TransactionDirection.incoming,
|
||||
);
|
||||
final tx = txs.cast<Transaction?>().firstWhere(
|
||||
(t) => t!.amount == amount && !_txMetaStorage.has(t.id),
|
||||
orElse: () => null,
|
||||
);
|
||||
if (tx != null) {
|
||||
await _txMetaStorage.save(
|
||||
tx.id,
|
||||
TransactionMeta(type: TransactionType.lightning, invoice: invoice),
|
||||
);
|
||||
debugPrint('Swap mint metadata guardada para tx ${tx.id}');
|
||||
} else {
|
||||
debugPrint('No matching untagged incoming tx found for amount $amount');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Error guardando swap mint metadata: $e');
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// HISTORIAL
|
||||
// ============================================================
|
||||
@@ -1409,6 +1732,14 @@ class WalletProvider extends ChangeNotifier {
|
||||
// Silencioso - puede fallar offline
|
||||
debugPrint('Check pending failed: $e');
|
||||
}
|
||||
|
||||
try {
|
||||
// Recuperar proofs huérfanas (melts fallidos, sends no completados)
|
||||
await wallet.reclaimPendingProofs();
|
||||
} catch (e) {
|
||||
debugPrint('Reclaim pending proofs failed: $e');
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
debugPrint('Error getting wallet ${entry.key}:$unit: $e');
|
||||
}
|
||||
@@ -1417,6 +1748,9 @@ class WalletProvider extends ChangeNotifier {
|
||||
|
||||
// Vincular transacciones incoming sin metadata con pending invoices
|
||||
await _matchPendingMintInvoices();
|
||||
|
||||
// Resume pending Nostr payment request if app was killed mid-wait
|
||||
await resumePendingNostrRequest();
|
||||
}
|
||||
|
||||
/// Busca transacciones incoming sin metadata y las vincula con
|
||||
@@ -1570,7 +1904,7 @@ class WalletProvider extends ChangeNotifier {
|
||||
// Enviar todo el balance
|
||||
final prepared =
|
||||
await tempWallet.prepareSend(amount: tempBalance);
|
||||
final token = await tempWallet.send(
|
||||
final result = await tempWallet.send(
|
||||
send: prepared,
|
||||
memo: 'Recuperación El Caju',
|
||||
includeMemo: true,
|
||||
@@ -1578,7 +1912,7 @@ class WalletProvider extends ChangeNotifier {
|
||||
|
||||
// Reclamar en nuestro wallet
|
||||
final ourWallet = await getWallet(mintUrl, unit);
|
||||
final received = await ourWallet.receive(token: token);
|
||||
final received = await ourWallet.receive(token: result.token);
|
||||
totalRecovered += received;
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -1617,6 +1951,8 @@ class WalletProvider extends ChangeNotifier {
|
||||
_activeUnit = 'sat';
|
||||
_mnemonic = null;
|
||||
_db = null;
|
||||
_cachedBalanceStream = null;
|
||||
_cachedBalanceKey = null;
|
||||
|
||||
// Limpiar metadata de transacciones
|
||||
await _txMetaStorage.clear();
|
||||
@@ -1645,6 +1981,8 @@ class WalletProvider extends ChangeNotifier {
|
||||
_activeUnit = 'sat';
|
||||
_mnemonic = null;
|
||||
_db = null;
|
||||
_cachedBalanceStream = null;
|
||||
_cachedBalanceKey = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -1701,7 +2039,8 @@ class WalletProvider extends ChangeNotifier {
|
||||
/// Retorna el monto recibido si tiene éxito, o lanza excepción.
|
||||
/// Si el token está gastado o es inválido, lo elimina automáticamente.
|
||||
/// Verifica conectividad al mint antes de intentar reclamar.
|
||||
Future<BigInt> claimPendingToken(String id) async {
|
||||
/// [p2pkPrivateKey] clave privada para desbloquear tokens P2PK.
|
||||
Future<BigInt> claimPendingToken(String id, {String? p2pkPrivateKey}) async {
|
||||
final pending = _pendingTokenStorage.get(id);
|
||||
if (pending == null) {
|
||||
throw Exception('Token pendiente no encontrado');
|
||||
@@ -1714,8 +2053,16 @@ class WalletProvider extends ChangeNotifier {
|
||||
}
|
||||
|
||||
try {
|
||||
// Intentar reclamar usando el método existente
|
||||
final amount = await receiveToken(pending.encoded);
|
||||
BigInt amount;
|
||||
|
||||
if (P2PKUtils.isP2PKLocked(pending.encoded) && p2pkPrivateKey == null) {
|
||||
throw Exception('P2PK token requires a private key to claim');
|
||||
}
|
||||
|
||||
amount = await receiveToken(
|
||||
pending.encoded,
|
||||
p2pkPrivateKey: p2pkPrivateKey,
|
||||
);
|
||||
|
||||
// Éxito: eliminar de pending
|
||||
await _pendingTokenStorage.remove(id);
|
||||
@@ -1744,7 +2091,8 @@ class WalletProvider extends ChangeNotifier {
|
||||
|
||||
/// Verifica y reclama automáticamente tokens pendientes.
|
||||
/// Retorna un mapa con estadísticas: claimed, failed, removed, totalClaimed, unit.
|
||||
Future<Map<String, dynamic>> checkPendingTokens() async {
|
||||
/// [p2pkKeyResolver] función que dado un token encoded retorna la clave privada P2PK (o null).
|
||||
Future<Map<String, dynamic>> checkPendingTokens({String? Function(String encodedToken)? p2pkKeyResolver}) async {
|
||||
final tokens = _pendingTokenStorage.listValid();
|
||||
if (tokens.isEmpty) {
|
||||
return {'claimed': 0, 'failed': 0, 'removed': 0, 'totalClaimed': BigInt.zero, 'unit': _activeUnit};
|
||||
@@ -1758,7 +2106,8 @@ class WalletProvider extends ChangeNotifier {
|
||||
|
||||
for (final token in tokens) {
|
||||
try {
|
||||
final amount = await claimPendingToken(token.id);
|
||||
final p2pkKey = p2pkKeyResolver?.call(token.encoded);
|
||||
final amount = await claimPendingToken(token.id, p2pkPrivateKey: p2pkKey);
|
||||
claimed++;
|
||||
totalClaimed += amount;
|
||||
claimedUnit ??= token.unit; // Usar la unidad del primer token reclamado
|
||||
@@ -1809,6 +2158,8 @@ class WalletProvider extends ChangeNotifier {
|
||||
if (_activeMintController != null && !_activeMintController!.isClosed) {
|
||||
_activeMintController!.close();
|
||||
}
|
||||
_cachedBalanceStream = null;
|
||||
_cachedBalanceKey = null;
|
||||
confettiController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import '../../widgets/scanner/qr_scanner_widget.dart';
|
||||
import '../../providers/wallet_provider.dart';
|
||||
import '../4_receive/receive_screen.dart';
|
||||
import '../7_melt/melt_screen.dart';
|
||||
import '../11_payment_request/payment_request_screen.dart';
|
||||
|
||||
/// Pantalla de escaneo QR con soporte para diferentes modos
|
||||
class ScanScreen extends StatefulWidget {
|
||||
@@ -195,8 +196,17 @@ class _ScanScreenState extends State<ScanScreen> {
|
||||
break;
|
||||
|
||||
case IncomingDataType.paymentRequest:
|
||||
// TODO: Implementar manejo de payment requests (post-MVP)
|
||||
_showError(l10n.paymentRequestNotSupported);
|
||||
if (mounted) {
|
||||
Navigator.pop(context);
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => PaymentRequestScreen(
|
||||
encodedRequest: data.raw,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case IncomingDataType.unknown:
|
||||
@@ -221,8 +231,10 @@ class _ScanScreenState extends State<ScanScreen> {
|
||||
}
|
||||
|
||||
void _handleInvoiceOnlyMode(ParsedData data) {
|
||||
if (data.type == IncomingDataType.lightningInvoice) {
|
||||
// Retornar el invoice vía callback
|
||||
if (data.type == IncomingDataType.lightningInvoice ||
|
||||
(data.type == IncomingDataType.paymentRequest &&
|
||||
data.invoiceBolt11 != null)) {
|
||||
// Return the invoice — works for pure BOLT11 and BIP-321 URIs with lightning=
|
||||
Navigator.pop(context, data.invoiceBolt11 ?? data.raw);
|
||||
widget.onDataScanned?.call(data.invoiceBolt11 ?? data.raw);
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,338 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
import 'package:elcaju/l10n/app_localizations.dart';
|
||||
import '../../core/constants/colors.dart';
|
||||
import '../../core/constants/dimensions.dart';
|
||||
import '../../core/utils/formatters.dart';
|
||||
import '../../src/rust/api/payment_request.dart';
|
||||
import '../../widgets/common/gradient_background.dart';
|
||||
import '../../widgets/common/glass_card.dart';
|
||||
import '../../widgets/common/primary_button.dart';
|
||||
import '../../providers/wallet_provider.dart';
|
||||
|
||||
/// Pantalla de confirmación para pagar un Payment Request (NUT-18/26)
|
||||
class PaymentRequestScreen extends StatefulWidget {
|
||||
/// Raw encoded payment request (creqA, CREQB1, or bitcoin:?creq=)
|
||||
final String encodedRequest;
|
||||
|
||||
const PaymentRequestScreen({super.key, required this.encodedRequest});
|
||||
|
||||
@override
|
||||
State<PaymentRequestScreen> createState() => _PaymentRequestScreenState();
|
||||
}
|
||||
|
||||
class _PaymentRequestScreenState extends State<PaymentRequestScreen> {
|
||||
PaymentRequestInfo? _info;
|
||||
String? _parseError;
|
||||
bool _isPaying = false;
|
||||
String? _payError;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_parseRequest();
|
||||
}
|
||||
|
||||
void _parseRequest() {
|
||||
try {
|
||||
final info = PaymentRequestInfo.parse(encoded: widget.encodedRequest);
|
||||
setState(() => _info = info);
|
||||
} catch (e) {
|
||||
setState(() => _parseError = e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = L10n.of(context)!;
|
||||
|
||||
return GradientBackground(
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
title: Text(
|
||||
l10n.paymentRequestTitle,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: const Icon(LucideIcons.arrowLeft),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
),
|
||||
body: _parseError != null
|
||||
? _buildError(l10n)
|
||||
: _info == null
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _buildContent(l10n),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildError(L10n l10n) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(AppDimensions.paddingLarge),
|
||||
child: GlassCard(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(LucideIcons.alertCircle, color: Colors.redAccent, size: 48),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
l10n.paymentRequestErrorParsing,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_parseError!,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 13,
|
||||
color: Colors.white.withValues(alpha: 0.6),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent(L10n l10n) {
|
||||
final info = _info!;
|
||||
final walletProvider = context.watch<WalletProvider>();
|
||||
final activeUnit = walletProvider.activeUnit;
|
||||
final requestUnit = info.unit ?? 'sat';
|
||||
|
||||
// Validaciones
|
||||
final unitMismatch = info.unit != null && info.unit != activeUnit;
|
||||
String stripSlash(String url) =>
|
||||
url.endsWith('/') ? url.substring(0, url.length - 1) : url;
|
||||
final normalizedMints = info.mints.map(stripSlash).toSet();
|
||||
final activeMint = walletProvider.activeMintUrl;
|
||||
final mintNotAccepted = normalizedMints.isNotEmpty &&
|
||||
(activeMint == null || !normalizedMints.contains(stripSlash(activeMint)));
|
||||
final hasTransport = info.transports.isNotEmpty;
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(AppDimensions.paddingLarge),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Monto
|
||||
if (info.amount != null)
|
||||
_buildAmountCard(info.amount!, requestUnit, l10n),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Detalles
|
||||
GlassCard(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Descripción
|
||||
if (info.description != null && info.description!.isNotEmpty) ...[
|
||||
_buildDetailRow(
|
||||
LucideIcons.fileText,
|
||||
l10n.paymentRequestDescription,
|
||||
info.description!,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
|
||||
// Mints aceptados
|
||||
_buildDetailRow(
|
||||
LucideIcons.server,
|
||||
l10n.paymentRequestMints,
|
||||
info.mints.isEmpty
|
||||
? l10n.paymentRequestAnyMint
|
||||
: info.mints.map((m) => Uri.parse(m).host).join(', '),
|
||||
),
|
||||
|
||||
// Transporte (solo mostrar si hay transporte configurado)
|
||||
if (info.transports.isNotEmpty) ...[
|
||||
const SizedBox(height: 12),
|
||||
_buildDetailRow(
|
||||
LucideIcons.send,
|
||||
l10n.paymentRequestTransport,
|
||||
info.transports
|
||||
.map((t) => t.transportType == 'nostr' ? 'Nostr (NIP-17)' : 'HTTP POST')
|
||||
.join(', '),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Warnings
|
||||
if (unitMismatch)
|
||||
_buildWarning(
|
||||
LucideIcons.alertTriangle,
|
||||
l10n.paymentRequestUnitMismatch(requestUnit),
|
||||
),
|
||||
if (mintNotAccepted)
|
||||
_buildWarning(
|
||||
LucideIcons.alertTriangle,
|
||||
l10n.paymentRequestMintNotAccepted,
|
||||
),
|
||||
if (!hasTransport)
|
||||
_buildWarning(
|
||||
LucideIcons.info,
|
||||
l10n.paymentRequestNoTransport,
|
||||
),
|
||||
|
||||
if (_payError != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
_buildWarning(LucideIcons.alertCircle, _payError!),
|
||||
],
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// Botón pagar
|
||||
PrimaryButton(
|
||||
text: _isPaying ? l10n.paymentRequestPaying : l10n.paymentRequestPay,
|
||||
icon: LucideIcons.zap,
|
||||
isLoading: _isPaying,
|
||||
onPressed: (!hasTransport || _isPaying)
|
||||
? null
|
||||
: _pay,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAmountCard(BigInt amount, String unit, L10n l10n) {
|
||||
return GlassCard(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
l10n.paymentRequestAmount,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 14,
|
||||
color: Colors.white.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
UnitFormatter.formatBalanceWithUnit(amount, unit),
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDetailRow(IconData icon, String label, String value) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(icon, size: 18, color: AppColors.primaryAction),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white.withValues(alpha: 0.6),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 14,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWarning(IconData icon, String text) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: GlassCard(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, size: 20, color: Colors.amber),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 13,
|
||||
color: Colors.amber,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _pay() async {
|
||||
setState(() {
|
||||
_isPaying = true;
|
||||
_payError = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final walletProvider = context.read<WalletProvider>();
|
||||
await walletProvider.payPaymentRequest(
|
||||
widget.encodedRequest,
|
||||
customAmount: _info!.amount,
|
||||
);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
// Éxito — mostrar snackbar y volver
|
||||
final l10n = L10n.of(context)!;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(l10n.paymentRequestSuccess),
|
||||
backgroundColor: Colors.green.shade700,
|
||||
),
|
||||
);
|
||||
Navigator.pop(context, true);
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isPaying = false;
|
||||
_payError = e.toString();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,909 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import '../../src/rust/api/wallet.dart';
|
||||
import '../../src/rust/api/payment_request.dart';
|
||||
import 'package:elcaju/l10n/app_localizations.dart';
|
||||
import '../../core/constants/colors.dart';
|
||||
import '../../core/constants/dimensions.dart';
|
||||
import '../../core/utils/formatters.dart';
|
||||
// TODO: re-enable once CDK fixes NIP-17 (cashubtc/cdk#1807)
|
||||
// import '../../core/utils/bip321_builder.dart';
|
||||
import '../../core/services/nfc_service.dart';
|
||||
import '../../widgets/common/gradient_background.dart';
|
||||
import '../../widgets/common/glass_card.dart';
|
||||
import '../../widgets/common/primary_button.dart';
|
||||
import '../../widgets/common/numpad_widget.dart';
|
||||
import '../../providers/wallet_provider.dart';
|
||||
|
||||
enum RequestStatus { input, generating, waiting, received, error }
|
||||
enum QrMode { universal, cashu, lightning }
|
||||
|
||||
/// Pantalla para solicitar pagos via Payment Request unificado.
|
||||
/// Genera creqB (Cashu/Nostr) + Lightning invoice en paralelo.
|
||||
class RequestScreen extends StatefulWidget {
|
||||
const RequestScreen({super.key});
|
||||
|
||||
@override
|
||||
State<RequestScreen> createState() => _RequestScreenState();
|
||||
}
|
||||
|
||||
class _RequestScreenState extends State<RequestScreen> {
|
||||
final TextEditingController _descriptionController = TextEditingController();
|
||||
|
||||
// Input state
|
||||
String _amountValue = '';
|
||||
late String _activeUnit;
|
||||
|
||||
// Request state
|
||||
RequestStatus _status = RequestStatus.input;
|
||||
String? _errorMessage;
|
||||
|
||||
// Payment data
|
||||
String? _creqB;
|
||||
String? _bolt11;
|
||||
// TODO: re-enable QrMode.cashu / universal once CDK fixes NIP-17 sender pubkey bug
|
||||
// See: https://github.com/cashubtc/cdk/issues/1807
|
||||
// QrMode _activeMode = QrMode.lightning;
|
||||
bool _paymentHandled = false;
|
||||
|
||||
// Listeners
|
||||
StreamSubscription<NostrPaymentEvent>? _nostrSubscription;
|
||||
StreamSubscription<MintQuote>? _mintSubscription;
|
||||
|
||||
// NFC
|
||||
NfcState _nfcState = NfcState.unsupported;
|
||||
bool _nfcEmulating = false;
|
||||
|
||||
// Success
|
||||
BigInt _receivedAmount = BigInt.zero;
|
||||
|
||||
late final WalletProvider _walletProvider;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_walletProvider = context.read<WalletProvider>();
|
||||
_activeUnit = _walletProvider.activeUnit;
|
||||
_checkNfc();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_descriptionController.dispose();
|
||||
// TODO: re-enable once CDK fixes NIP-17 (cashubtc/cdk#1807)
|
||||
// No-op while Nostr is disabled (_nostrSubscription is always null, _creqB is always null)
|
||||
_nostrSubscription?.cancel();
|
||||
_mintSubscription?.cancel();
|
||||
if (_nfcEmulating) NfcService.stopEmulating();
|
||||
if (!_paymentHandled && _creqB != null) {
|
||||
_walletProvider.removePendingNostrRequest();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _checkNfc() async {
|
||||
final state = await NfcService.checkState();
|
||||
if (mounted) setState(() => _nfcState = state);
|
||||
}
|
||||
|
||||
String get _unitLabel => UnitFormatter.getUnitLabel(_activeUnit);
|
||||
BigInt get _amount => UnitFormatter.parseRawDigits(_amountValue, _activeUnit);
|
||||
bool get _isValidAmount => _amount > BigInt.zero;
|
||||
|
||||
// ─── Build ───
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GradientBackground(
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(LucideIcons.arrowLeft, color: Colors.white),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
title: Text(
|
||||
L10n.of(context)!.requestPayment,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
if (_status == RequestStatus.waiting)
|
||||
IconButton(
|
||||
icon: const Icon(LucideIcons.share2, color: Colors.white),
|
||||
onPressed: _shareRequest,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(child: _buildBody()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
switch (_status) {
|
||||
case RequestStatus.input:
|
||||
return _buildInputView();
|
||||
case RequestStatus.generating:
|
||||
return _buildGeneratingView();
|
||||
case RequestStatus.waiting:
|
||||
return _buildWaitingView();
|
||||
case RequestStatus.received:
|
||||
return _buildSuccessView();
|
||||
case RequestStatus.error:
|
||||
return _buildErrorView();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Input View ───
|
||||
|
||||
Widget _buildInputView() {
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(AppDimensions.paddingMedium),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildAmountSection(),
|
||||
const SizedBox(height: AppDimensions.paddingLarge),
|
||||
_buildDescriptionSection(),
|
||||
const SizedBox(height: AppDimensions.paddingMedium),
|
||||
if (_errorMessage != null) _buildErrorMessage(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(AppDimensions.paddingMedium),
|
||||
child: PrimaryButton(
|
||||
text: L10n.of(context)!.generateRequest,
|
||||
onPressed: _isValidAmount ? _generateRequest : null,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAmountSection() {
|
||||
final displayAmount =
|
||||
UnitFormatter.formatRawDigitsForDisplay(_amountValue, _activeUnit);
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
displayAmount,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 48,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: _isValidAmount || _amountValue.isEmpty
|
||||
? Colors.white
|
||||
: AppColors.error,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
_unitLabel,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 18,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppDimensions.paddingMedium),
|
||||
NumpadWidget(
|
||||
value: _amountValue,
|
||||
onChanged: (v) => setState(() {
|
||||
_errorMessage = null;
|
||||
_amountValue = v;
|
||||
}),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDescriptionSection() {
|
||||
final l10n = L10n.of(context)!;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
l10n.descriptionOptional,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppDimensions.paddingSmall),
|
||||
GlassCard(
|
||||
padding: const EdgeInsets.all(AppDimensions.paddingSmall),
|
||||
child: TextField(
|
||||
controller: _descriptionController,
|
||||
maxLines: 2,
|
||||
maxLength: 100,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 16,
|
||||
color: Colors.white,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintText: l10n.requestDescriptionHint,
|
||||
hintStyle: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 16,
|
||||
color: Colors.white.withValues(alpha: 0.3),
|
||||
),
|
||||
border: InputBorder.none,
|
||||
counterStyle: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 12,
|
||||
color: AppColors.textSecondary.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildErrorMessage() {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: AppDimensions.paddingMedium),
|
||||
padding: const EdgeInsets.all(AppDimensions.paddingMedium),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.error.withValues(alpha: 0.15),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: AppColors.error.withValues(alpha: 0.3),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(LucideIcons.alertCircle, color: AppColors.error, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_errorMessage!,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 14,
|
||||
color: AppColors.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Generating View ───
|
||||
|
||||
Widget _buildGeneratingView() {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(color: AppColors.primaryAction),
|
||||
const SizedBox(height: AppDimensions.paddingMedium),
|
||||
Text(
|
||||
L10n.of(context)!.generatingRequest,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 16,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Waiting View (QR + sub-toggle + actions) ───
|
||||
|
||||
Widget _buildWaitingView() {
|
||||
final l10n = L10n.of(context)!;
|
||||
final formattedAmount = UnitFormatter.formatBalance(_amount, _activeUnit);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(AppDimensions.paddingMedium),
|
||||
child: Column(
|
||||
children: [
|
||||
// Amount header
|
||||
Text(
|
||||
'$formattedAmount $_unitLabel',
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppDimensions.paddingLarge),
|
||||
|
||||
// QR Code with logo overlay
|
||||
Center(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
QrImageView(
|
||||
data: _getActiveQrContent(),
|
||||
version: QrVersions.auto,
|
||||
size: 260,
|
||||
backgroundColor: Colors.white,
|
||||
// TODO: use QrErrorCorrectLevel.M for universal mode once CDK fixes NIP-17 (cashubtc/cdk#1807)
|
||||
errorCorrectionLevel: QrErrorCorrectLevel.H,
|
||||
),
|
||||
// TODO: re-enable Cashu logo overlay once CDK fixes NIP-17 (cashubtc/cdk#1807)
|
||||
// if (_activeMode == QrMode.cashu)
|
||||
// Container(
|
||||
// width: 48, height: 48,
|
||||
// decoration: BoxDecoration(
|
||||
// color: Colors.white,
|
||||
// borderRadius: BorderRadius.circular(10),
|
||||
// border: Border.all(color: Colors.white, width: 4),
|
||||
// ),
|
||||
// child: ClipRRect(
|
||||
// borderRadius: BorderRadius.circular(6),
|
||||
// child: Image.asset('assets/img/cashu.png', fit: BoxFit.contain),
|
||||
// ),
|
||||
// ),
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.bitcoinOrange,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: Colors.white, width: 4),
|
||||
),
|
||||
child: const Icon(
|
||||
LucideIcons.zap,
|
||||
color: Colors.white,
|
||||
size: 28,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppDimensions.paddingMedium),
|
||||
|
||||
// Sub-toggle: Universal / Cashu / Lightning
|
||||
_buildModeToggle(),
|
||||
const SizedBox(height: AppDimensions.paddingMedium),
|
||||
|
||||
// Action buttons: Copy, NFC, Share
|
||||
_buildActionButtons(),
|
||||
const SizedBox(height: AppDimensions.paddingMedium),
|
||||
|
||||
// Status indicator
|
||||
_buildStatusIndicator(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Cancel button
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(AppDimensions.paddingMedium),
|
||||
child: GestureDetector(
|
||||
onTap: () => Navigator.pop(context),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
l10n.cancel,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildModeToggle() {
|
||||
// TODO: re-enable Universal/Cashu tabs once CDK fixes NIP-17 (cashubtc/cdk#1807)
|
||||
// Hidden while only Lightning is available (single button toggle is a no-op)
|
||||
return const SizedBox.shrink(
|
||||
// child: Container(
|
||||
// padding: const EdgeInsets.all(4),
|
||||
// decoration: BoxDecoration(
|
||||
// color: Colors.white.withValues(alpha: 0.1),
|
||||
// borderRadius: BorderRadius.circular(12),
|
||||
// ),
|
||||
// child: Row(
|
||||
// children: [
|
||||
// _buildToggleButton(label: L10n.of(context)!.universal, mode: QrMode.universal, enabled: _bolt11 != null),
|
||||
// _buildToggleButton(label: 'Cashu', mode: QrMode.cashu, enabled: true),
|
||||
// _buildToggleButton(label: 'Lightning', mode: QrMode.lightning, enabled: _bolt11 != null),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: re-enable once CDK fixes NIP-17 (cashubtc/cdk#1807)
|
||||
// Widget _buildToggleButton({
|
||||
// required String label,
|
||||
// required QrMode mode,
|
||||
// required bool enabled,
|
||||
// }) {
|
||||
// final isActive = _activeMode == mode;
|
||||
// return Expanded(
|
||||
// child: GestureDetector(
|
||||
// onTap: enabled
|
||||
// ? () {
|
||||
// setState(() => _activeMode = mode);
|
||||
// _updateNfcPayload();
|
||||
// }
|
||||
// : null,
|
||||
// child: Container(
|
||||
// padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
// decoration: BoxDecoration(
|
||||
// gradient: isActive
|
||||
// ? const LinearGradient(colors: AppColors.buttonGradient)
|
||||
// : null,
|
||||
// borderRadius: BorderRadius.circular(10),
|
||||
// ),
|
||||
// child: Center(
|
||||
// child: Text(
|
||||
// label,
|
||||
// style: TextStyle(
|
||||
// fontFamily: 'Inter',
|
||||
// fontSize: 13,
|
||||
// fontWeight: isActive ? FontWeight.w600 : FontWeight.w400,
|
||||
// color: enabled
|
||||
// ? (isActive ? Colors.white : AppColors.textSecondary)
|
||||
// : Colors.white.withValues(alpha: 0.3),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
Widget _buildActionButtons() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_buildActionButton(
|
||||
icon: LucideIcons.copy,
|
||||
label: L10n.of(context)!.copy,
|
||||
onTap: _copyRequest,
|
||||
),
|
||||
if (_nfcState == NfcState.enabled) ...[
|
||||
const SizedBox(width: 16),
|
||||
_buildActionButton(
|
||||
icon: _nfcEmulating ? LucideIcons.wifiOff : LucideIcons.wifi,
|
||||
label: 'NFC',
|
||||
onTap: _toggleNfc,
|
||||
active: _nfcEmulating,
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 16),
|
||||
_buildActionButton(
|
||||
icon: LucideIcons.share2,
|
||||
label: L10n.of(context)!.share,
|
||||
onTap: _shareRequest,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActionButton({
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required VoidCallback onTap,
|
||||
bool active = false,
|
||||
}) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: active
|
||||
? AppColors.primaryAction.withValues(alpha: 0.2)
|
||||
: Colors.white.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: active
|
||||
? Border.all(color: AppColors.primaryAction.withValues(alpha: 0.5))
|
||||
: null,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, color: active ? AppColors.primaryAction : AppColors.textSecondary, size: 18),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 14,
|
||||
color: active ? AppColors.primaryAction : AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusIndicator() {
|
||||
return GlassCard(
|
||||
padding: const EdgeInsets.all(AppDimensions.paddingMedium),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(AppColors.textSecondary),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
L10n.of(context)!.waitingForPayment,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Success View ───
|
||||
|
||||
Widget _buildSuccessView() {
|
||||
final l10n = L10n.of(context)!;
|
||||
final formattedAmount =
|
||||
'+${UnitFormatter.formatBalance(_receivedAmount, _activeUnit)}';
|
||||
final unitLabel = UnitFormatter.getUnitLabel(_activeUnit);
|
||||
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.success.withValues(alpha: 0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
LucideIcons.checkCircle2,
|
||||
color: AppColors.success,
|
||||
size: 40,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppDimensions.paddingMedium),
|
||||
Text(
|
||||
'$formattedAmount $unitLabel',
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppDimensions.paddingSmall),
|
||||
Text(
|
||||
l10n.requestPaymentReceived,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 16,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppDimensions.paddingLarge * 2),
|
||||
PrimaryButton(
|
||||
text: l10n.backToHome,
|
||||
onPressed: () =>
|
||||
Navigator.popUntil(context, (route) => route.isFirst),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Error View ───
|
||||
|
||||
Widget _buildErrorView() {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.error.withValues(alpha: 0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
LucideIcons.alertCircle,
|
||||
color: AppColors.error,
|
||||
size: 40,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppDimensions.paddingMedium),
|
||||
Text(
|
||||
L10n.of(context)!.error,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppDimensions.paddingSmall),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: Text(
|
||||
_errorMessage ?? L10n.of(context)!.unknownError,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 14,
|
||||
color: AppColors.textSecondary.withValues(alpha: 0.7),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppDimensions.paddingLarge),
|
||||
PrimaryButton(
|
||||
text: L10n.of(context)!.back,
|
||||
onPressed: () => setState(() {
|
||||
_status = RequestStatus.input;
|
||||
_errorMessage = null;
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Logic ───
|
||||
|
||||
// TODO: re-enable once CDK fixes NIP-17 (cashubtc/cdk#1807)
|
||||
// static const List<String> _defaultNostrRelays = [
|
||||
// 'wss://relay.damus.io',
|
||||
// 'wss://relay.primal.net',
|
||||
// 'wss://nos.lol',
|
||||
// ];
|
||||
|
||||
Future<void> _generateRequest() async {
|
||||
final walletProvider = context.read<WalletProvider>();
|
||||
final wallet = walletProvider.activeWallet;
|
||||
|
||||
// Clean up previous request state — await to ensure Rust locks are released
|
||||
await _nostrSubscription?.cancel();
|
||||
_nostrSubscription = null;
|
||||
await _mintSubscription?.cancel();
|
||||
_mintSubscription = null;
|
||||
if (_nfcEmulating) {
|
||||
NfcService.stopEmulating();
|
||||
_nfcEmulating = false;
|
||||
}
|
||||
_paymentHandled = false;
|
||||
_creqB = null;
|
||||
_bolt11 = null;
|
||||
// TODO: re-enable Cashu/Nostr payment request once CDK fixes NIP-17 sender pubkey bug
|
||||
// See: https://github.com/cashubtc/cdk/issues/1807
|
||||
// _activeMode = QrMode.lightning;
|
||||
|
||||
setState(() => _status = RequestStatus.generating);
|
||||
|
||||
if (wallet == null) {
|
||||
setState(() {
|
||||
_status = RequestStatus.error;
|
||||
_errorMessage = 'No active wallet';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final description = _descriptionController.text.isNotEmpty
|
||||
? _descriptionController.text
|
||||
: null;
|
||||
|
||||
// TODO: re-enable once CDK fixes NIP-17 (cashubtc/cdk#1807)
|
||||
// // 1. Create payment request (creqB + Nostr keys)
|
||||
// final request = await wallet.createPaymentRequest(
|
||||
// params: CreateRequestParams(
|
||||
// amount: _amount,
|
||||
// unit: _activeUnit,
|
||||
// description: description,
|
||||
// nostrRelays: _defaultNostrRelays,
|
||||
// ),
|
||||
// );
|
||||
//
|
||||
// _creqB = request.creqB;
|
||||
//
|
||||
// // Persist handle for recovery if app is killed
|
||||
// await walletProvider.savePendingNostrRequest(
|
||||
// request.listenerHandle.toPersisted(),
|
||||
// );
|
||||
//
|
||||
// // 2. Start Nostr listener
|
||||
// _nostrSubscription = wallet
|
||||
// .waitForNostrPayment(handle: request.listenerHandle)
|
||||
// .listen(_onNostrEvent);
|
||||
|
||||
// Lightning invoice generation via wallet provider
|
||||
// (walletProvider.mintTokens handles pending invoice persistence and metadata)
|
||||
final mintStream = await walletProvider.mintTokens(
|
||||
_amount,
|
||||
description,
|
||||
);
|
||||
if (!mounted) return;
|
||||
_mintSubscription = mintStream.listen(_onMintEvent);
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_status = RequestStatus.error;
|
||||
_errorMessage = e.toString();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: re-enable once CDK fixes NIP-17 (cashubtc/cdk#1807)
|
||||
// void _onNostrEvent(NostrPaymentEvent event) {
|
||||
// if (!mounted || _paymentHandled) return;
|
||||
// if (event.state == NostrPaymentState.received) {
|
||||
// _paymentHandled = true;
|
||||
// _mintSubscription?.cancel();
|
||||
// _onPaymentSuccess(event.amount ?? _amount);
|
||||
// }
|
||||
// }
|
||||
|
||||
void _onMintEvent(MintQuote quote) {
|
||||
if (!mounted) return;
|
||||
switch (quote.state) {
|
||||
case MintQuoteState.unpaid:
|
||||
if (_bolt11 == null) {
|
||||
setState(() {
|
||||
_bolt11 = quote.request;
|
||||
// TODO: switch to QrMode.universal once CDK fixes NIP-17 (cashubtc/cdk#1807)
|
||||
// _activeMode = QrMode.lightning;
|
||||
_status = RequestStatus.waiting;
|
||||
});
|
||||
_updateNfcPayload();
|
||||
}
|
||||
break;
|
||||
case MintQuoteState.issued:
|
||||
if (!_paymentHandled) {
|
||||
_paymentHandled = true;
|
||||
_nostrSubscription?.cancel();
|
||||
_onPaymentSuccess(quote.amount ?? _amount);
|
||||
}
|
||||
break;
|
||||
case MintQuoteState.error:
|
||||
// No Nostr fallback while CDK NIP-17 bug is open (cashubtc/cdk#1807)
|
||||
if (!_paymentHandled) {
|
||||
if (_nfcEmulating) {
|
||||
NfcService.stopEmulating();
|
||||
_nfcEmulating = false;
|
||||
}
|
||||
setState(() {
|
||||
_status = RequestStatus.error;
|
||||
_errorMessage = quote.error ?? L10n.of(context)!.unknownError;
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onPaymentSuccess(BigInt amount) async {
|
||||
if (_nfcEmulating) NfcService.stopEmulating();
|
||||
setState(() {
|
||||
_status = RequestStatus.received;
|
||||
_receivedAmount = amount;
|
||||
});
|
||||
final walletProvider = context.read<WalletProvider>();
|
||||
walletProvider.confettiController.fire();
|
||||
// TODO: re-enable once CDK fixes NIP-17 (cashubtc/cdk#1807)
|
||||
// Only remove pending Nostr request if this screen created one.
|
||||
// Disabled while Nostr payment requests are disabled to avoid
|
||||
// deleting a recovery record from a previous session.
|
||||
// await walletProvider.removePendingNostrRequest();
|
||||
}
|
||||
|
||||
// ─── QR Content ───
|
||||
|
||||
String _getActiveQrContent() {
|
||||
// TODO: re-enable once CDK fixes NIP-17 (cashubtc/cdk#1807)
|
||||
// case QrMode.universal:
|
||||
// return buildUnifiedUri(creqB: _creqB!, bolt11: _bolt11);
|
||||
// case QrMode.cashu:
|
||||
// return _creqB!.toUpperCase();
|
||||
return _bolt11?.toUpperCase() ?? '';
|
||||
}
|
||||
|
||||
// ─── Actions ───
|
||||
|
||||
Future<void> _copyRequest() async {
|
||||
final content = _getActiveQrContent();
|
||||
await Clipboard.setData(ClipboardData(text: content));
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(L10n.of(context)!.copiedToClipboard),
|
||||
backgroundColor: AppColors.success,
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _toggleNfc() {
|
||||
if (_nfcEmulating) {
|
||||
NfcService.stopEmulating();
|
||||
setState(() => _nfcEmulating = false);
|
||||
} else {
|
||||
NfcService.startEmulating(_getActiveQrContent());
|
||||
setState(() => _nfcEmulating = true);
|
||||
}
|
||||
}
|
||||
|
||||
void _updateNfcPayload() {
|
||||
if (_nfcEmulating) {
|
||||
NfcService.startEmulating(_getActiveQrContent());
|
||||
}
|
||||
}
|
||||
|
||||
void _shareRequest() async {
|
||||
final content = _getActiveQrContent();
|
||||
final formattedAmount =
|
||||
UnitFormatter.formatBalance(_amount, _activeUnit);
|
||||
final unitLabel = UnitFormatter.getUnitLabel(_activeUnit);
|
||||
|
||||
await SharePlus.instance.share(
|
||||
ShareParams(
|
||||
text: '$formattedAmount $unitLabel\n\n$content',
|
||||
subject: 'Payment Request - $formattedAmount $unitLabel',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,9 +11,10 @@ import '../../widgets/common/glass_card.dart';
|
||||
import '../../widgets/common/animated_action_button.dart';
|
||||
import '../../providers/wallet_provider.dart';
|
||||
import '../../providers/settings_provider.dart';
|
||||
import '../../providers/p2pk_provider.dart';
|
||||
import '../4_receive/receive_screen.dart';
|
||||
import '../5_send/send_screen.dart';
|
||||
import '../6_mint/mint_screen.dart';
|
||||
import '../12_request/request_screen.dart';
|
||||
import '../7_melt/melt_screen.dart';
|
||||
import '../8_settings/settings_screen.dart';
|
||||
import '../8_settings/mints_screen.dart';
|
||||
@@ -51,7 +52,10 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
if (!walletProvider.hasPendingTokens) return;
|
||||
|
||||
try {
|
||||
final result = await walletProvider.checkPendingTokens();
|
||||
final p2pkProvider = context.read<P2PKProvider>();
|
||||
final result = await walletProvider.checkPendingTokens(
|
||||
p2pkKeyResolver: p2pkProvider.getPrivateKeyForToken,
|
||||
);
|
||||
final claimed = (result['claimed'] as int?) ?? 0;
|
||||
final totalClaimed = result['totalClaimed'] as BigInt? ?? BigInt.zero;
|
||||
final unit = (result['unit'] as String?) ?? walletProvider.activeUnit;
|
||||
@@ -433,14 +437,14 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
},
|
||||
),
|
||||
_MethodOption(
|
||||
icon: LucideIcons.zap,
|
||||
label: l10n.lightning,
|
||||
description: l10n.generateInvoiceToDeposit,
|
||||
icon: LucideIcons.bellRing,
|
||||
label: l10n.request,
|
||||
description: l10n.requestPaymentDescription,
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const MintScreen()),
|
||||
MaterialPageRoute(builder: (context) => const RequestScreen()),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -16,6 +16,7 @@ import '../../widgets/common/primary_button.dart';
|
||||
import '../../providers/wallet_provider.dart';
|
||||
import '../../providers/p2pk_provider.dart';
|
||||
import '../10_scanner/scan_screen.dart';
|
||||
import '../11_payment_request/payment_request_screen.dart';
|
||||
|
||||
/// Pantalla para recibir tokens Cashu
|
||||
class ReceiveScreen extends StatefulWidget {
|
||||
@@ -993,6 +994,25 @@ class _ReceiveScreenState extends State<ReceiveScreen> {
|
||||
return;
|
||||
}
|
||||
|
||||
// Detectar payment request y redirigir
|
||||
final parsed = IncomingDataParser.parse(tokenValue);
|
||||
if (parsed.type == IncomingDataType.paymentRequest) {
|
||||
_isValidToken = false;
|
||||
_tokenInfo = null;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => PaymentRequestScreen(
|
||||
encodedRequest: tokenValue,
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Parsear token real con cdk-flutter
|
||||
final tokenInfo = walletProvider.parseToken(tokenValue);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
import 'package:elcaju/l10n/app_localizations.dart';
|
||||
@@ -5,10 +6,12 @@ import '../../core/constants/colors.dart';
|
||||
import '../../core/constants/dimensions.dart';
|
||||
import '../../core/models/proof.dart';
|
||||
import '../../core/services/proof_service.dart';
|
||||
import '../../src/rust/api/token.dart' as cdk;
|
||||
import '../../widgets/common/gradient_background.dart';
|
||||
import '../../widgets/common/glass_card.dart';
|
||||
import '../../widgets/common/primary_button.dart';
|
||||
import '../../widgets/proof/proof_selector.dart';
|
||||
import '../../core/utils/keyset_debug.dart';
|
||||
import 'share_token_screen.dart';
|
||||
|
||||
/// Pantalla para enviar tokens de forma offline seleccionando proofs manualmente.
|
||||
@@ -45,7 +48,7 @@ class _OfflineSendScreenState extends State<OfflineSendScreen> {
|
||||
@override
|
||||
void dispose() {
|
||||
_memoController.dispose();
|
||||
_proofService.close();
|
||||
if (!_isCreating) _proofService.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -321,32 +324,55 @@ class _OfflineSendScreenState extends State<OfflineSendScreen> {
|
||||
}
|
||||
|
||||
Future<void> _createOfflineToken() async {
|
||||
final selectedProofs = _selectedProofs;
|
||||
final selectedTotal = _proofService.calculateTotal(selectedProofs);
|
||||
|
||||
// Validar monto mínimo viable para el receptor
|
||||
// Leer ppk del keyset local, fallback a 100 (estándar)
|
||||
var ppk = await KeysetDebug.getInputFeePpk(widget.mintUrl, widget.unit);
|
||||
if (ppk < 0) ppk = 100; // -1 = error leyendo DB, asumir fee estándar
|
||||
final minReceiveFee = ppk > 0 ? BigInt.from((ppk + 999) ~/ 1000) : BigInt.zero;
|
||||
if (selectedTotal <= minReceiveFee) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_errorMessage = L10n.of(context)!.feeExceedsAmount;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var proofsMarkedPending = false;
|
||||
|
||||
setState(() {
|
||||
_isCreating = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final selectedProofs = _selectedProofs;
|
||||
|
||||
// Marcar proofs como PENDING_SPENT
|
||||
await _proofService.markProofsPendingSpent(selectedProofs);
|
||||
proofsMarkedPending = true;
|
||||
|
||||
// Crear token V3
|
||||
// Crear token via CDK (produce V4/cashuB válido)
|
||||
final memo = _memoController.text.isNotEmpty ? _memoController.text : null;
|
||||
final token = _proofService.createTokenV3(
|
||||
final proofsJson = jsonEncode(
|
||||
selectedProofs.map((p) => p.toTokenProof()).toList(),
|
||||
);
|
||||
final cdkToken = cdk.createOfflineToken(
|
||||
mintUrl: widget.mintUrl,
|
||||
proofs: selectedProofs,
|
||||
proofsJson: proofsJson,
|
||||
memo: memo,
|
||||
unit: widget.unit,
|
||||
);
|
||||
final token = cdkToken.encoded;
|
||||
|
||||
if (mounted) {
|
||||
_isCreating = false;
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ShareTokenScreen(
|
||||
token: token,
|
||||
amount: _selectedTotal,
|
||||
amount: selectedTotal,
|
||||
unit: widget.unit,
|
||||
memo: memo,
|
||||
),
|
||||
@@ -354,7 +380,17 @@ class _OfflineSendScreenState extends State<OfflineSendScreen> {
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
if (proofsMarkedPending) {
|
||||
try {
|
||||
await _proofService.markProofsUnspent(selectedProofs);
|
||||
} catch (rollbackErr) {
|
||||
debugPrint('Failed to rollback proofs to UNSPENT: $rollbackErr');
|
||||
}
|
||||
}
|
||||
if (!mounted) {
|
||||
_proofService.close();
|
||||
return;
|
||||
}
|
||||
final errorMessage = L10n.of(context)!.creatingTokenError(e.toString());
|
||||
setState(() {
|
||||
_errorMessage = errorMessage;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
@@ -7,6 +8,7 @@ import 'package:elcaju/l10n/app_localizations.dart';
|
||||
import '../../core/constants/colors.dart';
|
||||
import '../../core/constants/dimensions.dart';
|
||||
import '../../core/utils/formatters.dart';
|
||||
import '../../core/utils/keyset_debug.dart';
|
||||
import '../../widgets/common/gradient_background.dart';
|
||||
import '../../widgets/common/glass_card.dart';
|
||||
import '../../widgets/common/primary_button.dart';
|
||||
@@ -572,15 +574,37 @@ class _SendScreenState extends State<SendScreen> {
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Offline: verificar monto mínimo con ppk del keyset local (fallback 100)
|
||||
var ppk = await KeysetDebug.getInputFeePpk(mintUrl, _activeUnit);
|
||||
if (ppk < 0) ppk = 100;
|
||||
final minReceiveFee = ppk > 0 ? BigInt.from((ppk + 999) ~/ 1000) : BigInt.zero;
|
||||
if (!mounted) return;
|
||||
if (_amount <= minReceiveFee) {
|
||||
setState(() {
|
||||
_errorMessage = L10n.of(context)!.feeExceedsAmount;
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Offline: ir directo a selección de monedas
|
||||
_goToOfflineModeWithMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
// Online: verificar monto mínimo con ppk real del mint
|
||||
final minReceiveFee = await _getMinReceiveFee(mintUrl);
|
||||
if (!mounted) return;
|
||||
if (_amount <= minReceiveFee) {
|
||||
setState(() {
|
||||
_errorMessage = L10n.of(context)!.feeExceedsAmount;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Online: mostrar modal de confirmación normal
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => _ConfirmationModal(
|
||||
amount: _amount,
|
||||
unit: _activeUnit,
|
||||
@@ -594,6 +618,32 @@ class _SendScreenState extends State<SendScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Calcula el fee mínimo que el receptor pagará al reclamar el token.
|
||||
/// Consulta /v1/keysets del mint para obtener el ppk real.
|
||||
/// Fallback: ppk=100 (estándar) → fee mínimo = 1 sat.
|
||||
Future<BigInt> _getMinReceiveFee(String mintUrl) async {
|
||||
try {
|
||||
final response = await http.get(
|
||||
Uri.parse('$mintUrl/v1/keysets'),
|
||||
).timeout(const Duration(seconds: 3));
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body) as Map<String, dynamic>;
|
||||
final keysets = data['keysets'] as List<dynamic>? ?? [];
|
||||
for (final ks in keysets) {
|
||||
if (ks['active'] == true && ks['unit'] == _activeUnit) {
|
||||
final ppk = (ks['input_fee_ppk'] as num?)?.toInt() ?? 0;
|
||||
if (ppk > 0) {
|
||||
return BigInt.from((ppk + 999) ~/ 1000); // ceil(ppk / 1000)
|
||||
}
|
||||
return BigInt.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
// Fallback: ppk=100 estándar
|
||||
return BigInt.one;
|
||||
}
|
||||
|
||||
/// Verifica conectividad haciendo petición HTTP real al mint.
|
||||
Future<bool> _checkConnectivity(String mintUrl) async {
|
||||
try {
|
||||
|
||||
@@ -72,8 +72,13 @@ class _ShareTokenScreenState extends State<ShareTokenScreen> {
|
||||
}
|
||||
|
||||
void _encodeTokenToUR() {
|
||||
// Parsear el token string a objeto Token de cdk-flutter
|
||||
final token = cdk.Token.parse(encoded: widget.token);
|
||||
// Construir Token directamente sin Token.parse para evitar validación
|
||||
// estricta de keyset IDs. encodeQrToken solo usa token.encoded.
|
||||
final token = cdk.Token(
|
||||
encoded: widget.token,
|
||||
amount: widget.amount,
|
||||
mintUrl: '',
|
||||
);
|
||||
|
||||
// Codificar a fragmentos UR usando cdk-flutter
|
||||
// maxFragmentLength por defecto es 150 bytes
|
||||
|
||||
@@ -80,10 +80,9 @@ class _InvoiceScreenState extends State<InvoiceScreen> {
|
||||
_onMintCompleted();
|
||||
break;
|
||||
|
||||
default:
|
||||
// Cualquier otro estado se trata como error
|
||||
case MintQuoteState.error:
|
||||
_status = MintStatus.error;
|
||||
_errorMessage = L10n.of(context)!.unknownState;
|
||||
_errorMessage = quote.error ?? L10n.of(context)!.unknownError;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -249,6 +249,7 @@ class _MintScreenState extends State<MintScreen> {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => _ConfirmationModal(
|
||||
amount: _amount,
|
||||
unit: _activeUnit,
|
||||
|
||||
@@ -404,6 +404,8 @@ class _AmountScreenState extends State<AmountScreen> {
|
||||
Future<void> _processPayment() async {
|
||||
if (!_canPay) return;
|
||||
|
||||
final walletProvider = context.read<WalletProvider>();
|
||||
|
||||
setState(() {
|
||||
_isProcessing = true;
|
||||
_errorMessage = null;
|
||||
@@ -431,14 +433,22 @@ class _AmountScreenState extends State<AmountScreen> {
|
||||
if (!mounted) return;
|
||||
|
||||
// 3. Obtener quote del mint (en la unidad del mint)
|
||||
final walletProvider = context.read<WalletProvider>();
|
||||
final quote = await walletProvider.getMeltQuote(invoiceResult.invoice);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
final total = quote.amount + quote.feeReserve;
|
||||
|
||||
// 4. Verificar balance suficiente
|
||||
// 4a. Verificar que el fee no supere el monto (operación inviable)
|
||||
if (quote.amount <= quote.feeReserve) {
|
||||
setState(() {
|
||||
_isProcessing = false;
|
||||
_errorMessage = L10n.of(context)!.feeExceedsAmount;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 4b. Verificar balance suficiente
|
||||
if (total > _availableBalance) {
|
||||
setState(() {
|
||||
_isProcessing = false;
|
||||
|
||||
@@ -594,15 +594,28 @@ class _MeltScreenState extends State<MeltScreen> {
|
||||
}
|
||||
});
|
||||
|
||||
// Extract lightning invoice from BIP-321 URI if present
|
||||
var inputValue = value;
|
||||
if (value.trim().toLowerCase().startsWith('bitcoin:')) {
|
||||
final parsed = IncomingDataParser.parse(value.trim());
|
||||
if (parsed.invoiceBolt11 != null) {
|
||||
inputValue = parsed.invoiceBolt11!;
|
||||
_invoiceController.text = inputValue;
|
||||
_invoiceController.selection = TextSelection.fromPosition(
|
||||
TextPosition(offset: inputValue.length),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Detectar tipo de input (sin mostrar error, solo detectar)
|
||||
final inputType = LnurlService.detectType(value);
|
||||
final inputType = LnurlService.detectType(inputValue);
|
||||
setState(() => _inputType = inputType);
|
||||
|
||||
// Solo procesar automáticamente invoices BOLT11
|
||||
// LNURL y Lightning Address requieren botón explícito
|
||||
if (inputType == LnInputType.bolt11Invoice) {
|
||||
// BOLT11 invoices son 200+ chars; evitar llamar API con input parcial
|
||||
final cleaned = LnurlService.cleanInput(value);
|
||||
final cleaned = LnurlService.cleanInput(inputValue);
|
||||
if (cleaned.length > 50) {
|
||||
_getQuote(cleaned);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:elcaju/l10n/app_localizations.dart';
|
||||
import '../../core/constants/colors.dart';
|
||||
|
||||
/// Muestra el diálogo "Acerca de" de ElCaju
|
||||
void showElCajuAboutDialog(BuildContext context, String appVersion) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: AppColors.deepVoidPurple,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Logo
|
||||
Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Image.asset(
|
||||
'assets/img/elcajucubano.png',
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
'El Caju',
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'v$appVersion',
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 14,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
L10n.of(context)!.aboutTagline,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 16,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildAboutDescription(context),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(
|
||||
L10n.of(context)!.close,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAboutDescription(BuildContext context) {
|
||||
final description = L10n.of(context)!.aboutDescription;
|
||||
const keyword = 'LaChispa';
|
||||
final index = description.indexOf(keyword);
|
||||
if (index == -1) {
|
||||
return Text(
|
||||
description,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 14,
|
||||
color: AppColors.textSecondary.withValues(alpha: 0.7),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
);
|
||||
}
|
||||
final before = description.substring(0, index);
|
||||
final after = description.substring(index + keyword.length);
|
||||
return RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 14,
|
||||
color: AppColors.textSecondary.withValues(alpha: 0.7),
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: before),
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.baseline,
|
||||
baseline: TextBaseline.alphabetic,
|
||||
child: GestureDetector(
|
||||
onTap: () => _openLaChispa(context),
|
||||
child: Text(
|
||||
keyword,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 14,
|
||||
color: AppColors.secondaryAction,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: AppColors.secondaryAction,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
TextSpan(text: after),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _openLaChispa(BuildContext context) async {
|
||||
final url = Uri.parse('https://app.lachispa.me');
|
||||
try {
|
||||
await launchUrl(url, mode: LaunchMode.externalApplication);
|
||||
} catch (_) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(L10n.of(context)!.couldNotOpenLink),
|
||||
backgroundColor: AppColors.error,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
import 'package:elcaju/l10n/app_localizations.dart';
|
||||
import '../../core/constants/colors.dart';
|
||||
import '../../core/constants/dimensions.dart';
|
||||
|
||||
/// Modal para confirmar borrado de wallet
|
||||
class DeleteWalletModal extends StatefulWidget {
|
||||
final VoidCallback onConfirm;
|
||||
final VoidCallback onCancel;
|
||||
|
||||
const DeleteWalletModal({
|
||||
super.key,
|
||||
required this.onConfirm,
|
||||
required this.onCancel,
|
||||
});
|
||||
|
||||
@override
|
||||
State<DeleteWalletModal> createState() => _DeleteWalletModalState();
|
||||
}
|
||||
|
||||
class _DeleteWalletModalState extends State<DeleteWalletModal> {
|
||||
final TextEditingController _controller = TextEditingController();
|
||||
bool _canDelete = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = L10n.of(context)!;
|
||||
final confirmWord = l10n.deleteConfirmWord;
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(AppDimensions.paddingMedium),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.deepVoidPurple,
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
|
||||
border: Border.all(
|
||||
color: Colors.white.withValues(alpha: 0.1),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Handle
|
||||
Container(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.3),
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
|
||||
// Icono advertencia
|
||||
Container(
|
||||
width: 64,
|
||||
height: 64,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.error.withValues(alpha: 0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
LucideIcons.alertTriangle,
|
||||
color: AppColors.error,
|
||||
size: 32,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppDimensions.paddingMedium),
|
||||
|
||||
// Título
|
||||
Text(
|
||||
l10n.deleteWalletQuestion,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppDimensions.paddingSmall),
|
||||
|
||||
// Advertencia
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.error.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
LucideIcons.alertCircle,
|
||||
color: AppColors.error,
|
||||
size: 16,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
l10n.actionIrreversible,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
l10n.deleteWalletWarning,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 12,
|
||||
color: AppColors.error.withValues(alpha: 0.8),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: AppDimensions.paddingMedium),
|
||||
|
||||
// Input de confirmación
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
l10n.typeDeleteToConfirm,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 14,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: _controller,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 16,
|
||||
color: Colors.white,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintText: confirmWord,
|
||||
hintStyle: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 16,
|
||||
color: Colors.white.withValues(alpha: 0.3),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.white.withValues(alpha: 0.05),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_canDelete = value == confirmWord;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: AppDimensions.paddingLarge),
|
||||
|
||||
// Botones
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: widget.onCancel,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
l10n.cancel,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: AppDimensions.paddingMedium),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: _canDelete ? widget.onConfirm : null,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: _canDelete
|
||||
? AppColors.error
|
||||
: AppColors.error.withValues(alpha: 0.3),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
l10n.deleteWallet,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: _canDelete
|
||||
? Colors.white
|
||||
: Colors.white.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: AppDimensions.paddingSmall),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
import '../../core/constants/colors.dart';
|
||||
|
||||
/// Diálogo para ingresar PIN de 4 dígitos
|
||||
class PinDialog extends StatefulWidget {
|
||||
final String title;
|
||||
final String subtitle;
|
||||
|
||||
const PinDialog({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
});
|
||||
|
||||
@override
|
||||
State<PinDialog> createState() => _PinDialogState();
|
||||
}
|
||||
|
||||
class _PinDialogState extends State<PinDialog> {
|
||||
final List<String> _pin = [];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
backgroundColor: AppColors.deepVoidPurple,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
|
||||
title: Text(
|
||||
widget.title,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
widget.subtitle,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 14,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
// Indicadores de PIN
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: List.generate(4, (index) {
|
||||
final filled = index < _pin.length;
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8),
|
||||
width: 16,
|
||||
height: 16,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: filled
|
||||
? AppColors.primaryAction
|
||||
: Colors.white.withValues(alpha: 0.2),
|
||||
border: Border.all(
|
||||
color: filled
|
||||
? AppColors.primaryAction
|
||||
: Colors.white.withValues(alpha: 0.3),
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
// Teclado numérico
|
||||
_buildNumPad(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNumPad() {
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: ['1', '2', '3'].map((n) => _buildNumKey(n)).toList(),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: ['4', '5', '6'].map((n) => _buildNumKey(n)).toList(),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: ['7', '8', '9'].map((n) => _buildNumKey(n)).toList(),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
// Cancelar
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.pop(context),
|
||||
child: Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.05),
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Icon(
|
||||
LucideIcons.x,
|
||||
color: AppColors.textSecondary,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildNumKey('0'),
|
||||
// Borrar
|
||||
GestureDetector(
|
||||
onTap: _deleteDigit,
|
||||
child: Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.05),
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Icon(
|
||||
LucideIcons.delete,
|
||||
color: AppColors.textSecondary,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNumKey(String number) {
|
||||
return GestureDetector(
|
||||
onTap: () => _addDigit(number),
|
||||
child: Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
number,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _addDigit(String digit) {
|
||||
if (_pin.length < 4) {
|
||||
setState(() {
|
||||
_pin.add(digit);
|
||||
});
|
||||
|
||||
if (_pin.length == 4) {
|
||||
// PIN completo, cerrar con resultado
|
||||
Future.delayed(const Duration(milliseconds: 200), () {
|
||||
if (mounted) {
|
||||
Navigator.pop(context, _pin.join());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _deleteDigit() {
|
||||
if (_pin.isNotEmpty) {
|
||||
setState(() {
|
||||
_pin.removeLast();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
import 'package:elcaju/l10n/app_localizations.dart';
|
||||
import '../../core/constants/colors.dart';
|
||||
import '../../core/constants/dimensions.dart';
|
||||
import '../../widgets/common/gradient_background.dart';
|
||||
|
||||
class PrivacyScreen extends StatelessWidget {
|
||||
const PrivacyScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = L10n.of(context)!;
|
||||
final conclusionLines = l10n.privacyConclusion
|
||||
.split('\n')
|
||||
.map((s) => s.trim())
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toList();
|
||||
|
||||
return GradientBackground(
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(LucideIcons.arrowLeft, color: Colors.white),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
title: Text(
|
||||
l10n.privacyPolicy,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) => SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(AppDimensions.paddingLarge),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minHeight: constraints.maxHeight),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
l10n.privacyTitle,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
l10n.privacyGoodbye,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.primaryAction,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
Text(
|
||||
l10n.privacyKeepReading,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 13,
|
||||
fontStyle: FontStyle.italic,
|
||||
color: AppColors.textSecondary.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 28),
|
||||
...l10n.privacyBody.split('\n')
|
||||
.map((s) => s.trim())
|
||||
.where((s) => s.isNotEmpty)
|
||||
.map(
|
||||
(line) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Text(
|
||||
line,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 16,
|
||||
color: AppColors.textSecondary.withValues(alpha: 0.85),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 28),
|
||||
...conclusionLines.asMap().entries.map((entry) {
|
||||
final isLast = entry.key == conclusionLines.length - 1;
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: isLast ? 0 : 8),
|
||||
child: Text(
|
||||
entry.value,
|
||||
style: isLast
|
||||
? const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Colors.white,
|
||||
)
|
||||
: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.textSecondary.withValues(alpha: 0.7),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,721 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:elcaju/l10n/app_localizations.dart';
|
||||
import '../../core/constants/colors.dart';
|
||||
import '../../core/constants/dimensions.dart';
|
||||
import '../../core/utils/formatters.dart';
|
||||
import '../../providers/wallet_provider.dart';
|
||||
|
||||
/// Modal para recuperar tokens usando NUT-13
|
||||
class RecoverTokensModal extends StatefulWidget {
|
||||
const RecoverTokensModal({super.key});
|
||||
|
||||
@override
|
||||
State<RecoverTokensModal> createState() => _RecoverTokensModalState();
|
||||
}
|
||||
|
||||
class _RecoverTokensModalState extends State<RecoverTokensModal> {
|
||||
final TextEditingController _mnemonicController = TextEditingController();
|
||||
bool _useCurrentMnemonic = true;
|
||||
bool _scanAllMints = true;
|
||||
String? _selectedMintUrl;
|
||||
List<String> _availableMints = [];
|
||||
bool _isLoading = false;
|
||||
bool _isLoadingMints = true;
|
||||
String? _result;
|
||||
bool _isSuccess = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _loadMints());
|
||||
}
|
||||
|
||||
void _loadMints() {
|
||||
final walletProvider = context.read<WalletProvider>();
|
||||
setState(() {
|
||||
_availableMints = walletProvider.mintUrls;
|
||||
_isLoadingMints = false;
|
||||
if (_availableMints.isNotEmpty) {
|
||||
_selectedMintUrl = _availableMints.first;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_mnemonicController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(AppDimensions.paddingMedium),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.deepVoidPurple,
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
|
||||
border: Border.all(
|
||||
color: Colors.white.withValues(alpha: 0.1),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Handle
|
||||
Container(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.3),
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
|
||||
// Icono
|
||||
Container(
|
||||
width: 64,
|
||||
height: 64,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primaryAction.withValues(alpha: 0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
LucideIcons.refreshCw,
|
||||
color: AppColors.primaryAction,
|
||||
size: 32,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppDimensions.paddingMedium),
|
||||
|
||||
// Título
|
||||
Text(
|
||||
L10n.of(context)!.recoverTokensTitle,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppDimensions.paddingSmall),
|
||||
|
||||
// Descripción
|
||||
Text(
|
||||
L10n.of(context)!.recoverTokensDescription,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 14,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
||||
const SizedBox(height: AppDimensions.paddingLarge),
|
||||
|
||||
// Opciones de mnemonic
|
||||
_buildMnemonicOptions(),
|
||||
|
||||
const SizedBox(height: AppDimensions.paddingMedium),
|
||||
|
||||
// Selector de mint (solo si usa mnemonic actual)
|
||||
if (_useCurrentMnemonic) _buildMintSelector(),
|
||||
|
||||
// Input para mnemonic personalizado
|
||||
if (!_useCurrentMnemonic) _buildMnemonicInput(),
|
||||
|
||||
// Resultado
|
||||
if (_result != null) _buildResult(),
|
||||
|
||||
const SizedBox(height: AppDimensions.paddingMedium),
|
||||
|
||||
// Botón de acción
|
||||
_buildActionButton(),
|
||||
|
||||
const SizedBox(height: AppDimensions.paddingSmall),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMnemonicOptions() {
|
||||
final l10n = L10n.of(context)!;
|
||||
return Column(
|
||||
children: [
|
||||
// Opción: Usar mnemonic actual
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _useCurrentMnemonic = true),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: _useCurrentMnemonic
|
||||
? AppColors.primaryAction.withValues(alpha: 0.2)
|
||||
: Colors.white.withValues(alpha: 0.05),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: _useCurrentMnemonic
|
||||
? AppColors.primaryAction.withValues(alpha: 0.5)
|
||||
: Colors.transparent,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
_useCurrentMnemonic
|
||||
? LucideIcons.checkCircle
|
||||
: LucideIcons.circle,
|
||||
color: _useCurrentMnemonic
|
||||
? AppColors.primaryAction
|
||||
: AppColors.textSecondary,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
l10n.useCurrentSeedPhrase,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
l10n.scanWithSavedWords,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 12,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Opción: Usar otro mnemonic
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _useCurrentMnemonic = false),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: !_useCurrentMnemonic
|
||||
? AppColors.primaryAction.withValues(alpha: 0.2)
|
||||
: Colors.white.withValues(alpha: 0.05),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: !_useCurrentMnemonic
|
||||
? AppColors.primaryAction.withValues(alpha: 0.5)
|
||||
: Colors.transparent,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
!_useCurrentMnemonic
|
||||
? LucideIcons.checkCircle
|
||||
: LucideIcons.circle,
|
||||
color: !_useCurrentMnemonic
|
||||
? AppColors.primaryAction
|
||||
: AppColors.textSecondary,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
l10n.useOtherSeedPhrase,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
l10n.recoverFromOtherWords,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 12,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMintSelector() {
|
||||
final l10n = L10n.of(context)!;
|
||||
if (_isLoadingMints) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(
|
||||
color: AppColors.primaryAction,
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
l10n.mintsToScan,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Opción: Escanear todos
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _scanAllMints = true),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: _scanAllMints
|
||||
? AppColors.primaryAction.withValues(alpha: 0.2)
|
||||
: Colors.white.withValues(alpha: 0.05),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: _scanAllMints
|
||||
? AppColors.primaryAction.withValues(alpha: 0.5)
|
||||
: Colors.transparent,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
_scanAllMints ? LucideIcons.checkCircle : LucideIcons.circle,
|
||||
color: _scanAllMints
|
||||
? AppColors.primaryAction
|
||||
: AppColors.textSecondary,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
l10n.allMints(_availableMints.length),
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 14,
|
||||
color: Colors.white,
|
||||
fontWeight: _scanAllMints ? FontWeight.w500 : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Opción: Mint específico
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _scanAllMints = false),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: !_scanAllMints
|
||||
? AppColors.primaryAction.withValues(alpha: 0.2)
|
||||
: Colors.white.withValues(alpha: 0.05),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: !_scanAllMints
|
||||
? AppColors.primaryAction.withValues(alpha: 0.5)
|
||||
: Colors.transparent,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
!_scanAllMints ? LucideIcons.checkCircle : LucideIcons.circle,
|
||||
color: !_scanAllMints
|
||||
? AppColors.primaryAction
|
||||
: AppColors.textSecondary,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
l10n.specificMint,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 14,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Dropdown de mints (solo si selecciona específico)
|
||||
if (!_scanAllMints && _availableMints.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.05),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: Colors.white.withValues(alpha: 0.1),
|
||||
),
|
||||
),
|
||||
child: DropdownButton<String>(
|
||||
value: _selectedMintUrl,
|
||||
isExpanded: true,
|
||||
dropdownColor: AppColors.deepVoidPurple,
|
||||
underline: const SizedBox(),
|
||||
icon: Icon(
|
||||
LucideIcons.chevronDown,
|
||||
color: AppColors.textSecondary,
|
||||
size: 20,
|
||||
),
|
||||
items: _availableMints.map((url) {
|
||||
final host = Uri.parse(url).host;
|
||||
return DropdownMenuItem(
|
||||
value: url,
|
||||
child: Text(
|
||||
host,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 14,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
setState(() => _selectedMintUrl = value);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMnemonicInput() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: AppDimensions.paddingMedium),
|
||||
child: TextField(
|
||||
controller: _mnemonicController,
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
textCapitalization: TextCapitalization.none,
|
||||
maxLines: 3,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 14,
|
||||
color: Colors.white,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintText: L10n.of(context)!.enterMnemonicWords,
|
||||
hintStyle: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 14,
|
||||
color: Colors.white.withValues(alpha: 0.3),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.white.withValues(alpha: 0.05),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: const EdgeInsets.all(16),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildResult() {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(top: AppDimensions.paddingMedium),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: _isSuccess
|
||||
? AppColors.success.withValues(alpha: 0.1)
|
||||
: AppColors.error.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: _isSuccess
|
||||
? AppColors.success.withValues(alpha: 0.3)
|
||||
: AppColors.error.withValues(alpha: 0.3),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
_isSuccess ? LucideIcons.checkCircle : LucideIcons.alertCircle,
|
||||
color: _isSuccess ? AppColors.success : AppColors.error,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_result!,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 14,
|
||||
color: _isSuccess ? AppColors.success : AppColors.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActionButton() {
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: GestureDetector(
|
||||
onTap: _isLoading ? null : _startRecover,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
gradient: _isLoading
|
||||
? null
|
||||
: const LinearGradient(
|
||||
colors: [AppColors.primaryAction, Color(0xFFFF9100)],
|
||||
),
|
||||
color: _isLoading ? Colors.grey : null,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Center(
|
||||
child: _isLoading
|
||||
? const SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.white,
|
||||
strokeWidth: 2,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
L10n.of(context)!.scanMints,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Inter',
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _startRecover() async {
|
||||
if (!mounted) return;
|
||||
final l10n = L10n.of(context)!;
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_result = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final walletProvider = context.read<WalletProvider>();
|
||||
|
||||
if (_useCurrentMnemonic) {
|
||||
// Usar mnemonic actual
|
||||
if (_scanAllMints) {
|
||||
// Escanear todos los mints (retorna Map<String, Map<String, BigInt>>)
|
||||
final results = await walletProvider.restoreAllMints();
|
||||
|
||||
int mintsRecovered = 0;
|
||||
int mintsWithError = 0;
|
||||
final recoveredDetails = <String>[];
|
||||
|
||||
for (final mintEntry in results.entries) {
|
||||
final unitBalances = mintEntry.value;
|
||||
bool hasError = false;
|
||||
bool hasRecovered = false;
|
||||
for (final unitEntry in unitBalances.entries) {
|
||||
final unit = unitEntry.key;
|
||||
final balance = unitEntry.value;
|
||||
if (balance < BigInt.zero) {
|
||||
hasError = true;
|
||||
} else if (balance > BigInt.zero) {
|
||||
final formatted = UnitFormatter.formatBalance(balance, unit);
|
||||
final label = UnitFormatter.getUnitLabel(unit);
|
||||
recoveredDetails.add('$formatted $label');
|
||||
hasRecovered = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasError) mintsWithError++;
|
||||
if (hasRecovered) mintsRecovered++;
|
||||
}
|
||||
|
||||
// Recuperar proofs pendientes (sends no reclamados, melts fallidos)
|
||||
final reclaim = await walletProvider.reclaimPendingProofs();
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
// Sumar monto del reclaim a los detalles de recuperación
|
||||
if (reclaim.count > BigInt.zero) {
|
||||
final activeUnit = walletProvider.activeUnit;
|
||||
final formatted = UnitFormatter.formatBalance(reclaim.amount, activeUnit);
|
||||
final label = UnitFormatter.getUnitLabel(activeUnit);
|
||||
recoveredDetails.add('$formatted $label (${reclaim.count} proofs)');
|
||||
mintsRecovered++;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isSuccess = true;
|
||||
if (recoveredDetails.isNotEmpty) {
|
||||
_result = l10n.recoveredTokens(recoveredDetails.join(", "), mintsRecovered);
|
||||
} else {
|
||||
_result = l10n.scanCompleteNoTokens;
|
||||
}
|
||||
if (mintsWithError > 0) {
|
||||
_result = '$_result ${l10n.mintsWithError(mintsWithError)}';
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Escanear mint específico (retorna Map<String, BigInt>)
|
||||
if (_selectedMintUrl == null) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isSuccess = false;
|
||||
_result = l10n.selectMintToScan;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final unitBalances = await walletProvider.restoreFromMint(_selectedMintUrl!);
|
||||
final mintHost = UnitFormatter.getMintDisplayName(_selectedMintUrl!);
|
||||
|
||||
final recoveredDetails = <String>[];
|
||||
for (final entry in unitBalances.entries) {
|
||||
final unit = entry.key;
|
||||
final balance = entry.value;
|
||||
if (balance > BigInt.zero) {
|
||||
final formatted = UnitFormatter.formatBalance(balance, unit);
|
||||
final label = UnitFormatter.getUnitLabel(unit);
|
||||
recoveredDetails.add('$formatted $label');
|
||||
}
|
||||
}
|
||||
|
||||
// Recuperar proofs pendientes de este mint
|
||||
final reclaim = await walletProvider.reclaimPendingProofs(mintUrl: _selectedMintUrl);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
// Sumar monto del reclaim a los detalles
|
||||
if (reclaim.count > BigInt.zero) {
|
||||
final activeUnit = walletProvider.activeUnit;
|
||||
final formatted = UnitFormatter.formatBalance(reclaim.amount, activeUnit);
|
||||
final label = UnitFormatter.getUnitLabel(activeUnit);
|
||||
recoveredDetails.add('$formatted $label (${reclaim.count} proofs)');
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isSuccess = true;
|
||||
if (recoveredDetails.isNotEmpty) {
|
||||
_result = l10n.recoveredFromMint(recoveredDetails.join(", "), mintHost);
|
||||
} else {
|
||||
_result = l10n.noTokensFoundInMint(mintHost);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Usar otro mnemonic
|
||||
final mnemonic = _mnemonicController.text.trim().toLowerCase();
|
||||
final words = mnemonic.split(RegExp(r'\s+'));
|
||||
|
||||
if (words.length != 12 && words.length != 24) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isSuccess = false;
|
||||
_result = l10n.mnemonicMustHaveWords;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Obtener lista de mints actuales para escanear
|
||||
final mintUrls = walletProvider.mintUrls;
|
||||
|
||||
if (mintUrls.isEmpty) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isSuccess = false;
|
||||
_result = l10n.noConnectedMintsToScan;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final recovered = await walletProvider.restoreWithMnemonic(
|
||||
mnemonic,
|
||||
mintUrls,
|
||||
);
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isSuccess = true;
|
||||
if (recovered > BigInt.zero) {
|
||||
// Usamos la unidad activa como aproximación para el formato
|
||||
final activeUnit = walletProvider.activeUnit;
|
||||
final formatted = UnitFormatter.formatBalance(recovered, activeUnit);
|
||||
final label = UnitFormatter.getUnitLabel(activeUnit);
|
||||
_result = l10n.recoveredAndTransferred(formatted, label);
|
||||
} else {
|
||||
_result = l10n.noTokensForMnemonic;
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isSuccess = false;
|
||||
_result = '${l10n.error}: $e';
|
||||
});
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,8 @@ import '../../core/utils/formatters.dart';
|
||||
import '../../data/transaction_meta_storage.dart';
|
||||
import '../../data/pending_token.dart';
|
||||
import '../../providers/wallet_provider.dart';
|
||||
import '../../providers/p2pk_provider.dart';
|
||||
import '../../core/utils/p2pk_utils.dart';
|
||||
import '../../widgets/common/gradient_background.dart';
|
||||
|
||||
/// Filtros disponibles para el historial
|
||||
@@ -222,9 +224,16 @@ class _HistoryScreenState extends State<HistoryScreen> {
|
||||
Future<void> _claimPendingToken(PendingToken token) async {
|
||||
final l10n = L10n.of(context)!;
|
||||
final walletProvider = context.read<WalletProvider>();
|
||||
final p2pkProvider = context.read<P2PKProvider>();
|
||||
|
||||
try {
|
||||
final amount = await walletProvider.claimPendingToken(token.id);
|
||||
// Detectar P2PK y obtener clave privada si es necesario
|
||||
String? p2pkKey;
|
||||
if (P2PKUtils.isP2PKLocked(token.encoded)) {
|
||||
p2pkKey = p2pkProvider.getPrivateKeyForToken(token.encoded);
|
||||
}
|
||||
|
||||
final amount = await walletProvider.claimPendingToken(token.id, p2pkPrivateKey: p2pkKey);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
// This file is automatically generated, so please do not edit it.
|
||||
// @generated by `flutter_rust_bridge`@ 2.11.1.
|
||||
|
||||
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
||||
|
||||
import '../frb_generated.dart';
|
||||
import 'error.dart';
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
|
||||
// These functions are ignored because they are not marked as `pub`: `extract_creq_from_uri`, `parse_payment_request_inner`, `percent_decode`, `wait_for_nostr_payment_inner`
|
||||
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`
|
||||
|
||||
// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<CreatedPaymentRequest>>
|
||||
abstract class CreatedPaymentRequest implements RustOpaqueInterface {
|
||||
String get creqA;
|
||||
|
||||
String get creqB;
|
||||
|
||||
NostrListenerHandle get listenerHandle;
|
||||
|
||||
set creqA(String creqA);
|
||||
|
||||
set creqB(String creqB);
|
||||
|
||||
set listenerHandle(NostrListenerHandle listenerHandle);
|
||||
}
|
||||
|
||||
// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<NostrListenerHandle>>
|
||||
abstract class NostrListenerHandle implements RustOpaqueInterface {
|
||||
/// Reconstruct a handle from persisted data (e.g. after app restart).
|
||||
static NostrListenerHandle fromPersisted({
|
||||
required PersistedRequestData data,
|
||||
}) => RustLib.instance.api
|
||||
.crateApiPaymentRequestNostrListenerHandleFromPersisted(data: data);
|
||||
|
||||
/// Export handle data for persistence (e.g. SharedPreferences).
|
||||
/// The secret key is exposed as hex — the caller is responsible
|
||||
/// for storing it securely.
|
||||
PersistedRequestData toPersisted();
|
||||
}
|
||||
|
||||
/// Parameters for creating a payment request.
|
||||
class CreateRequestParams {
|
||||
/// Amount to request (in smallest unit, e.g. sats)
|
||||
final BigInt? amount;
|
||||
|
||||
/// Currency unit ("sat", "usd", etc.)
|
||||
final String unit;
|
||||
|
||||
/// Human-readable description
|
||||
final String? description;
|
||||
|
||||
/// Nostr relay URLs for the transport
|
||||
final List<String> nostrRelays;
|
||||
|
||||
const CreateRequestParams({
|
||||
this.amount,
|
||||
required this.unit,
|
||||
this.description,
|
||||
required this.nostrRelays,
|
||||
});
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
amount.hashCode ^
|
||||
unit.hashCode ^
|
||||
description.hashCode ^
|
||||
nostrRelays.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is CreateRequestParams &&
|
||||
runtimeType == other.runtimeType &&
|
||||
amount == other.amount &&
|
||||
unit == other.unit &&
|
||||
description == other.description &&
|
||||
nostrRelays == other.nostrRelays;
|
||||
}
|
||||
|
||||
/// Event emitted by the Nostr payment listener.
|
||||
class NostrPaymentEvent {
|
||||
final NostrPaymentState state;
|
||||
final BigInt? amount;
|
||||
final String? error;
|
||||
|
||||
const NostrPaymentEvent({required this.state, this.amount, this.error});
|
||||
|
||||
@override
|
||||
int get hashCode => state.hashCode ^ amount.hashCode ^ error.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is NostrPaymentEvent &&
|
||||
runtimeType == other.runtimeType &&
|
||||
state == other.state &&
|
||||
amount == other.amount &&
|
||||
error == other.error;
|
||||
}
|
||||
|
||||
/// State of a Nostr payment listener.
|
||||
enum NostrPaymentState {
|
||||
/// Connected to relays, waiting for payment
|
||||
waiting,
|
||||
|
||||
/// Payment received and tokens claimed
|
||||
received,
|
||||
|
||||
/// Error occurred
|
||||
error,
|
||||
}
|
||||
|
||||
/// Parsed payment request info exposed to Flutter.
|
||||
/// CDK's FromStr auto-detects creqA (CBOR) vs creqB (Bech32m).
|
||||
class PaymentRequestInfo {
|
||||
/// Raw encoded string (creqA... or CREQB1...)
|
||||
final String raw;
|
||||
|
||||
/// Payment id
|
||||
final String? paymentId;
|
||||
|
||||
/// Requested amount (in base unit)
|
||||
final BigInt? amount;
|
||||
|
||||
/// Currency unit ("sat", "usd", etc.)
|
||||
final String? unit;
|
||||
|
||||
/// Whether this is a single-use request
|
||||
final bool? singleUse;
|
||||
|
||||
/// Accepted mints (empty = any mint)
|
||||
final List<String> mints;
|
||||
|
||||
/// Human-readable description
|
||||
final String? description;
|
||||
|
||||
/// Available transports
|
||||
final List<TransportInfo> transports;
|
||||
|
||||
/// Whether NUT-10 spending conditions are required
|
||||
final bool hasNut10;
|
||||
|
||||
const PaymentRequestInfo({
|
||||
required this.raw,
|
||||
this.paymentId,
|
||||
this.amount,
|
||||
this.unit,
|
||||
this.singleUse,
|
||||
required this.mints,
|
||||
this.description,
|
||||
required this.transports,
|
||||
required this.hasNut10,
|
||||
});
|
||||
|
||||
/// Parse a payment request string (creqA, CREQB1, or bitcoin:?creq=).
|
||||
/// Supports NUT-18 (CBOR+base64) and NUT-26 (Bech32m) automatically.
|
||||
static PaymentRequestInfo parse({required String encoded}) => RustLib
|
||||
.instance
|
||||
.api
|
||||
.crateApiPaymentRequestPaymentRequestInfoParse(encoded: encoded);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
raw.hashCode ^
|
||||
paymentId.hashCode ^
|
||||
amount.hashCode ^
|
||||
unit.hashCode ^
|
||||
singleUse.hashCode ^
|
||||
mints.hashCode ^
|
||||
description.hashCode ^
|
||||
transports.hashCode ^
|
||||
hasNut10.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is PaymentRequestInfo &&
|
||||
runtimeType == other.runtimeType &&
|
||||
raw == other.raw &&
|
||||
paymentId == other.paymentId &&
|
||||
amount == other.amount &&
|
||||
unit == other.unit &&
|
||||
singleUse == other.singleUse &&
|
||||
mints == other.mints &&
|
||||
description == other.description &&
|
||||
transports == other.transports &&
|
||||
hasNut10 == other.hasNut10;
|
||||
}
|
||||
|
||||
/// Serializable data for persisting a NostrListenerHandle across app restarts.
|
||||
class PersistedRequestData {
|
||||
final String secretHex;
|
||||
final String pubkeyHex;
|
||||
final List<String> relays;
|
||||
final BigInt? amount;
|
||||
final String unit;
|
||||
final String mintUrl;
|
||||
|
||||
const PersistedRequestData({
|
||||
required this.secretHex,
|
||||
required this.pubkeyHex,
|
||||
required this.relays,
|
||||
this.amount,
|
||||
required this.unit,
|
||||
required this.mintUrl,
|
||||
});
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
secretHex.hashCode ^
|
||||
pubkeyHex.hashCode ^
|
||||
relays.hashCode ^
|
||||
amount.hashCode ^
|
||||
unit.hashCode ^
|
||||
mintUrl.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is PersistedRequestData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
secretHex == other.secretHex &&
|
||||
pubkeyHex == other.pubkeyHex &&
|
||||
relays == other.relays &&
|
||||
amount == other.amount &&
|
||||
unit == other.unit &&
|
||||
mintUrl == other.mintUrl;
|
||||
}
|
||||
|
||||
/// Transport info exposed to Flutter
|
||||
class TransportInfo {
|
||||
/// "nostr" or "post"
|
||||
final String transportType;
|
||||
|
||||
/// Target (nprofile or URL)
|
||||
final String target;
|
||||
|
||||
const TransportInfo({required this.transportType, required this.target});
|
||||
|
||||
@override
|
||||
int get hashCode => transportType.hashCode ^ target.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is TransportInfo &&
|
||||
runtimeType == other.runtimeType &&
|
||||
transportType == other.transportType &&
|
||||
target == other.target;
|
||||
}
|
||||
@@ -10,6 +10,21 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
// These functions are ignored because they are not marked as `pub`: `proofs`
|
||||
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `fmt`, `from_str`, `try_from`, `try_into`, `try_into`
|
||||
|
||||
/// Create a token from raw proof data without network access.
|
||||
/// Uses CDK's CdkToken::new to produce a valid V4 (cashuB) token.
|
||||
/// `proofs_json` is a JSON array of proofs: [{"id":"...","amount":1,"secret":"...","C":"..."}, ...]
|
||||
Token createOfflineToken({
|
||||
required String mintUrl,
|
||||
required String proofsJson,
|
||||
String? memo,
|
||||
String? unit,
|
||||
}) => RustLib.instance.api.crateApiTokenCreateOfflineToken(
|
||||
mintUrl: mintUrl,
|
||||
proofsJson: proofsJson,
|
||||
memo: memo,
|
||||
unit: unit,
|
||||
);
|
||||
|
||||
List<String> encodeQrToken({required Token token, BigInt? maxFragmentLength}) =>
|
||||
RustLib.instance.api.crateApiTokenEncodeQrToken(
|
||||
token: token,
|
||||
|
||||
@@ -7,11 +7,31 @@ import '../frb_generated.dart';
|
||||
import 'error.dart';
|
||||
import 'mint_info.dart';
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
import 'payment_request.dart';
|
||||
import 'token.dart';
|
||||
|
||||
// These functions are ignored because they are not marked as `pub`: `mint_url`, `unit`, `update_balance_streams`
|
||||
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `clone`, `clone`, `clone`, `clone`, `cmp`, `eq`, `eq`, `eq`, `fmt`, `fmt`, `from`, `from`, `from`, `from`, `from`, `from`, `into`, `partial_cmp`, `try_into`, `try_into`
|
||||
|
||||
// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<PreparedMelt>>
|
||||
abstract class PreparedMelt implements RustOpaqueInterface {
|
||||
BigInt get amount;
|
||||
|
||||
BigInt get feeReserve;
|
||||
|
||||
BigInt get inputFee;
|
||||
|
||||
BigInt get swapFee;
|
||||
|
||||
set amount(BigInt amount);
|
||||
|
||||
set feeReserve(BigInt feeReserve);
|
||||
|
||||
set inputFee(BigInt inputFee);
|
||||
|
||||
set swapFee(BigInt swapFee);
|
||||
}
|
||||
|
||||
// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<PreparedSend>>
|
||||
abstract class PreparedSend implements RustOpaqueInterface {
|
||||
BigInt get amount;
|
||||
@@ -43,12 +63,25 @@ abstract class Wallet implements RustOpaqueInterface {
|
||||
|
||||
Future<BigInt> balance();
|
||||
|
||||
Future<void> cancelMelt({required PreparedMelt melt});
|
||||
|
||||
Future<void> cancelSend({required PreparedSend send});
|
||||
|
||||
Future<void> checkAllMintQuotes();
|
||||
|
||||
Future<void> checkPendingTransactions();
|
||||
|
||||
Future<BigInt> confirmMelt({required PreparedMelt melt});
|
||||
|
||||
/// Create a NUT-18 payment request with Nostr transport.
|
||||
///
|
||||
/// Builds a PaymentRequest with the wallet's mint URL and unit,
|
||||
/// generates ephemeral Nostr keys, and returns both creqA and creqB
|
||||
/// encodings plus the keys needed for the Nostr listener.
|
||||
Future<CreatedPaymentRequest> createPaymentRequest({
|
||||
required CreateRequestParams params,
|
||||
});
|
||||
|
||||
Future<void> finalizePendingMelts();
|
||||
|
||||
Future<MintInfo?> getMint();
|
||||
@@ -57,8 +90,6 @@ abstract class Wallet implements RustOpaqueInterface {
|
||||
|
||||
Future<List<Transaction>> listTransactions({TransactionDirection? direction});
|
||||
|
||||
Future<BigInt> melt({required MeltQuote quote});
|
||||
|
||||
Future<MeltQuote> meltQuote({required String request});
|
||||
|
||||
Stream<MintQuote> mint({required BigInt amount, String? description});
|
||||
@@ -77,21 +108,45 @@ abstract class Wallet implements RustOpaqueInterface {
|
||||
db: db,
|
||||
);
|
||||
|
||||
/// Pay a NUT-18 payment request.
|
||||
/// Uses CDK's pay_request() which handles:
|
||||
/// - NUT-10 spending conditions
|
||||
/// - Transport selection (Nostr preferred, HTTP POST fallback)
|
||||
/// - Token preparation and delivery
|
||||
Future<void> payPaymentRequest({
|
||||
required String encoded,
|
||||
BigInt? customAmount,
|
||||
});
|
||||
|
||||
Future<PreparedMelt> prepareMelt({required MeltQuote quote});
|
||||
|
||||
Future<PreparedSend> prepareSend({required BigInt amount, SendOptions? opts});
|
||||
|
||||
Future<BigInt> receive({required Token token, ReceiveOptions? opts});
|
||||
|
||||
/// Check pending-spent proofs with the mint and revert unspent ones.
|
||||
/// Returns count and total amount of proofs recovered.
|
||||
Future<ReclaimResult> reclaimPendingProofs();
|
||||
|
||||
Future<void> recoverIncompleteSagas();
|
||||
|
||||
Future<void> restore();
|
||||
|
||||
Future<Token> send({
|
||||
Future<SendResult> send({
|
||||
required PreparedSend send,
|
||||
String? memo,
|
||||
bool? includeMemo,
|
||||
});
|
||||
|
||||
Stream<BigInt> streamBalance();
|
||||
|
||||
/// Wait for an incoming Nostr payment (NIP-17 gift-wrap).
|
||||
///
|
||||
/// Takes the opaque NostrListenerHandle returned by create_payment_request().
|
||||
/// The secret key never leaves Rust memory.
|
||||
Stream<NostrPaymentEvent> waitForNostrPayment({
|
||||
required NostrListenerHandle handle,
|
||||
});
|
||||
}
|
||||
|
||||
// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<WalletDatabase>>
|
||||
@@ -150,6 +205,9 @@ class MintQuote {
|
||||
final Token? token;
|
||||
final String? error;
|
||||
|
||||
/// Deterministic transaction ID (set when proofs are available, typically on Issued)
|
||||
final String? transactionId;
|
||||
|
||||
const MintQuote({
|
||||
required this.id,
|
||||
required this.request,
|
||||
@@ -158,6 +216,7 @@ class MintQuote {
|
||||
required this.state,
|
||||
this.token,
|
||||
this.error,
|
||||
this.transactionId,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -168,7 +227,8 @@ class MintQuote {
|
||||
expiry.hashCode ^
|
||||
state.hashCode ^
|
||||
token.hashCode ^
|
||||
error.hashCode;
|
||||
error.hashCode ^
|
||||
transactionId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
@@ -181,7 +241,8 @@ class MintQuote {
|
||||
expiry == other.expiry &&
|
||||
state == other.state &&
|
||||
token == other.token &&
|
||||
error == other.error;
|
||||
error == other.error &&
|
||||
transactionId == other.transactionId;
|
||||
}
|
||||
|
||||
enum MintQuoteState { unpaid, paid, issued, error }
|
||||
@@ -207,6 +268,24 @@ class ReceiveOptions {
|
||||
preimages == other.preimages;
|
||||
}
|
||||
|
||||
class ReclaimResult {
|
||||
final BigInt count;
|
||||
final BigInt amount;
|
||||
|
||||
const ReclaimResult({required this.count, required this.amount});
|
||||
|
||||
@override
|
||||
int get hashCode => count.hashCode ^ amount.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ReclaimResult &&
|
||||
runtimeType == other.runtimeType &&
|
||||
count == other.count &&
|
||||
amount == other.amount;
|
||||
}
|
||||
|
||||
class SendOptions {
|
||||
final String? pubkey;
|
||||
final bool? includeFee;
|
||||
@@ -228,6 +307,29 @@ class SendOptions {
|
||||
includeFee == other.includeFee;
|
||||
}
|
||||
|
||||
/// Result of a confirmed send, carrying both the ecash token and the
|
||||
/// deterministic transaction ID so Dart can save metadata without a racy
|
||||
/// listTransactions lookup.
|
||||
class SendResult {
|
||||
final Token token;
|
||||
|
||||
/// Deterministic transaction ID (None if computation failed)
|
||||
final String? transactionId;
|
||||
|
||||
const SendResult({required this.token, this.transactionId});
|
||||
|
||||
@override
|
||||
int get hashCode => token.hashCode ^ transactionId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is SendResult &&
|
||||
runtimeType == other.runtimeType &&
|
||||
token == other.token &&
|
||||
transactionId == other.transactionId;
|
||||
}
|
||||
|
||||
class Transaction {
|
||||
final String id;
|
||||
final String mintUrl;
|
||||
|
||||
+2107
-100
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@
|
||||
import 'api/error.dart';
|
||||
import 'api/keys.dart';
|
||||
import 'api/mint_info.dart';
|
||||
import 'api/payment_request.dart';
|
||||
import 'api/token.dart';
|
||||
import 'api/wallet.dart';
|
||||
import 'dart:async';
|
||||
@@ -22,6 +23,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
required super.portManager,
|
||||
});
|
||||
|
||||
CrossPlatformFinalizerArg
|
||||
get rust_arc_decrement_strong_count_CreatedPaymentRequestPtr => wire
|
||||
._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequestPtr;
|
||||
|
||||
CrossPlatformFinalizerArg
|
||||
get rust_arc_decrement_strong_count_NostrListenerHandlePtr => wire
|
||||
._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandlePtr;
|
||||
|
||||
CrossPlatformFinalizerArg
|
||||
get rust_arc_decrement_strong_count_PreparedMeltPtr => wire
|
||||
._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMeltPtr;
|
||||
|
||||
CrossPlatformFinalizerArg
|
||||
get rust_arc_decrement_strong_count_PreparedSendPtr => wire
|
||||
._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSendPtr;
|
||||
@@ -41,6 +54,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
AnyhowException dco_decode_AnyhowException(dynamic raw);
|
||||
|
||||
@protected
|
||||
CreatedPaymentRequest
|
||||
dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
NostrListenerHandle
|
||||
dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedMelt
|
||||
dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedSend
|
||||
dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -65,6 +96,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
CreatedPaymentRequest
|
||||
dco_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedMelt
|
||||
dco_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedSend
|
||||
dco_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -83,6 +126,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
CreatedPaymentRequest
|
||||
dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
NostrListenerHandle
|
||||
dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedMelt
|
||||
dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedSend
|
||||
dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -110,6 +171,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
Map<String, String> dco_decode_Map_String_String_None(dynamic raw);
|
||||
|
||||
@protected
|
||||
CreatedPaymentRequest
|
||||
dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
NostrListenerHandle
|
||||
dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedMelt
|
||||
dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedSend
|
||||
dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -137,6 +216,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
RustStreamSink<MintQuote> dco_decode_StreamSink_mint_quote_Sse(dynamic raw);
|
||||
|
||||
@protected
|
||||
RustStreamSink<NostrPaymentEvent>
|
||||
dco_decode_StreamSink_nostr_payment_event_Sse(dynamic raw);
|
||||
|
||||
@protected
|
||||
RustStreamSink<BigInt> dco_decode_StreamSink_u_64_Sse(dynamic raw);
|
||||
|
||||
@@ -149,6 +232,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
bool dco_decode_box_autoadd_bool(dynamic raw);
|
||||
|
||||
@protected
|
||||
CreateRequestParams dco_decode_box_autoadd_create_request_params(dynamic raw);
|
||||
|
||||
@protected
|
||||
MeltQuote dco_decode_box_autoadd_melt_quote(dynamic raw);
|
||||
|
||||
@@ -158,6 +244,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
MintVersion dco_decode_box_autoadd_mint_version(dynamic raw);
|
||||
|
||||
@protected
|
||||
PersistedRequestData dco_decode_box_autoadd_persisted_request_data(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
ReceiveOptions dco_decode_box_autoadd_receive_options(dynamic raw);
|
||||
|
||||
@@ -181,6 +272,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
ContactInfo dco_decode_contact_info(dynamic raw);
|
||||
|
||||
@protected
|
||||
CreateRequestParams dco_decode_create_request_params(dynamic raw);
|
||||
|
||||
@protected
|
||||
Error dco_decode_error(dynamic raw);
|
||||
|
||||
@@ -217,6 +311,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
List<Transaction> dco_decode_list_transaction(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<TransportInfo> dco_decode_list_transport_info(dynamic raw);
|
||||
|
||||
@protected
|
||||
MeltMethodSettings dco_decode_melt_method_settings(dynamic raw);
|
||||
|
||||
@@ -238,6 +335,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
MintVersion dco_decode_mint_version(dynamic raw);
|
||||
|
||||
@protected
|
||||
NostrPaymentEvent dco_decode_nostr_payment_event(dynamic raw);
|
||||
|
||||
@protected
|
||||
NostrPaymentState dco_decode_nostr_payment_state(dynamic raw);
|
||||
|
||||
@protected
|
||||
Nut04Settings dco_decode_nut_04_settings(dynamic raw);
|
||||
|
||||
@@ -288,15 +391,27 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
Uint8List? dco_decode_opt_list_prim_u_8_strict(dynamic raw);
|
||||
|
||||
@protected
|
||||
PaymentRequestInfo dco_decode_payment_request_info(dynamic raw);
|
||||
|
||||
@protected
|
||||
PersistedRequestData dco_decode_persisted_request_data(dynamic raw);
|
||||
|
||||
@protected
|
||||
ReceiveOptions dco_decode_receive_options(dynamic raw);
|
||||
|
||||
@protected
|
||||
ReclaimResult dco_decode_reclaim_result(dynamic raw);
|
||||
|
||||
@protected
|
||||
(String, String) dco_decode_record_string_string(dynamic raw);
|
||||
|
||||
@protected
|
||||
SendOptions dco_decode_send_options(dynamic raw);
|
||||
|
||||
@protected
|
||||
SendResult dco_decode_send_result(dynamic raw);
|
||||
|
||||
@protected
|
||||
SupportedSettings dco_decode_supported_settings(dynamic raw);
|
||||
|
||||
@@ -312,6 +427,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
TransactionStatus dco_decode_transaction_status(dynamic raw);
|
||||
|
||||
@protected
|
||||
TransportInfo dco_decode_transport_info(dynamic raw);
|
||||
|
||||
@protected
|
||||
BigInt dco_decode_u_64(dynamic raw);
|
||||
|
||||
@@ -327,6 +445,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
CreatedPaymentRequest
|
||||
sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
NostrListenerHandle
|
||||
sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedMelt
|
||||
sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedSend
|
||||
sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -351,6 +487,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
CreatedPaymentRequest
|
||||
sse_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedMelt
|
||||
sse_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedSend
|
||||
sse_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -369,6 +517,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
CreatedPaymentRequest
|
||||
sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
NostrListenerHandle
|
||||
sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedMelt
|
||||
sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedSend
|
||||
sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -398,6 +564,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
CreatedPaymentRequest
|
||||
sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
NostrListenerHandle
|
||||
sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedMelt
|
||||
sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedSend
|
||||
sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -427,6 +611,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
RustStreamSink<NostrPaymentEvent>
|
||||
sse_decode_StreamSink_nostr_payment_event_Sse(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RustStreamSink<BigInt> sse_decode_StreamSink_u_64_Sse(
|
||||
SseDeserializer deserializer,
|
||||
@@ -441,6 +629,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
bool sse_decode_box_autoadd_bool(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
CreateRequestParams sse_decode_box_autoadd_create_request_params(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
MeltQuote sse_decode_box_autoadd_melt_quote(SseDeserializer deserializer);
|
||||
|
||||
@@ -450,6 +643,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
MintVersion sse_decode_box_autoadd_mint_version(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
PersistedRequestData sse_decode_box_autoadd_persisted_request_data(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
ReceiveOptions sse_decode_box_autoadd_receive_options(
|
||||
SseDeserializer deserializer,
|
||||
@@ -475,6 +673,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
ContactInfo sse_decode_contact_info(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
CreateRequestParams sse_decode_create_request_params(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
Error sse_decode_error(SseDeserializer deserializer);
|
||||
|
||||
@@ -517,6 +720,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
List<Transaction> sse_decode_list_transaction(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<TransportInfo> sse_decode_list_transport_info(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
MeltMethodSettings sse_decode_melt_method_settings(
|
||||
SseDeserializer deserializer,
|
||||
@@ -542,6 +750,16 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
MintVersion sse_decode_mint_version(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
NostrPaymentEvent sse_decode_nostr_payment_event(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
NostrPaymentState sse_decode_nostr_payment_state(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
Nut04Settings sse_decode_nut_04_settings(SseDeserializer deserializer);
|
||||
|
||||
@@ -600,9 +818,22 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
Uint8List? sse_decode_opt_list_prim_u_8_strict(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
PaymentRequestInfo sse_decode_payment_request_info(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PersistedRequestData sse_decode_persisted_request_data(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
ReceiveOptions sse_decode_receive_options(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
ReclaimResult sse_decode_reclaim_result(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
(String, String) sse_decode_record_string_string(
|
||||
SseDeserializer deserializer,
|
||||
@@ -611,6 +842,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
SendOptions sse_decode_send_options(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
SendResult sse_decode_send_result(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
SupportedSettings sse_decode_supported_settings(SseDeserializer deserializer);
|
||||
|
||||
@@ -628,6 +862,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
TransactionStatus sse_decode_transaction_status(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
TransportInfo sse_decode_transport_info(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
BigInt sse_decode_u_64(SseDeserializer deserializer);
|
||||
|
||||
@@ -646,6 +883,27 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
CreatedPaymentRequest self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
NostrListenerHandle self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
PreparedMelt self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -674,6 +932,20 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
CreatedPaymentRequest self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
PreparedMelt self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -695,6 +967,27 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
CreatedPaymentRequest self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
NostrListenerHandle self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
PreparedMelt self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -729,6 +1022,27 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
CreatedPaymentRequest self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
NostrListenerHandle self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
PreparedMelt self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -763,6 +1077,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_StreamSink_nostr_payment_event_Sse(
|
||||
RustStreamSink<NostrPaymentEvent> self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_StreamSink_u_64_Sse(
|
||||
RustStreamSink<BigInt> self,
|
||||
@@ -778,6 +1098,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_box_autoadd_bool(bool self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_create_request_params(
|
||||
CreateRequestParams self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_melt_quote(
|
||||
MeltQuote self,
|
||||
@@ -796,6 +1122,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_persisted_request_data(
|
||||
PersistedRequestData self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_receive_options(
|
||||
ReceiveOptions self,
|
||||
@@ -826,6 +1158,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_contact_info(ContactInfo self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_create_request_params(
|
||||
CreateRequestParams self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_error(Error self, SseSerializer serializer);
|
||||
|
||||
@@ -883,6 +1221,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_transport_info(
|
||||
List<TransportInfo> self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_melt_method_settings(
|
||||
MeltMethodSettings self,
|
||||
@@ -913,6 +1257,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_mint_version(MintVersion self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_nostr_payment_event(
|
||||
NostrPaymentEvent self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_nostr_payment_state(
|
||||
NostrPaymentState self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_nut_04_settings(Nut04Settings self, SseSerializer serializer);
|
||||
|
||||
@@ -982,12 +1338,27 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_payment_request_info(
|
||||
PaymentRequestInfo self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_persisted_request_data(
|
||||
PersistedRequestData self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_receive_options(
|
||||
ReceiveOptions self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_reclaim_result(ReclaimResult self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_record_string_string(
|
||||
(String, String) self,
|
||||
@@ -997,6 +1368,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_send_options(SendOptions self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_send_result(SendResult self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_supported_settings(
|
||||
SupportedSettings self,
|
||||
@@ -1021,6 +1395,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_transport_info(TransportInfo self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_u_64(BigInt self, SseSerializer serializer);
|
||||
|
||||
@@ -1048,6 +1425,108 @@ class RustLibWire implements BaseWire {
|
||||
RustLibWire(ffi.DynamicLibrary dynamicLibrary)
|
||||
: _lookup = dynamicLibrary.lookup;
|
||||
|
||||
void
|
||||
rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
ffi.Pointer<ffi.Void> ptr,
|
||||
) {
|
||||
return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
ptr,
|
||||
);
|
||||
}
|
||||
|
||||
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequestPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
|
||||
'frbgen_elcaju_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest',
|
||||
);
|
||||
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest =
|
||||
_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequestPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
|
||||
|
||||
void
|
||||
rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
ffi.Pointer<ffi.Void> ptr,
|
||||
) {
|
||||
return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
ptr,
|
||||
);
|
||||
}
|
||||
|
||||
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequestPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
|
||||
'frbgen_elcaju_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest',
|
||||
);
|
||||
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest =
|
||||
_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequestPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
|
||||
|
||||
void
|
||||
rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
ffi.Pointer<ffi.Void> ptr,
|
||||
) {
|
||||
return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
ptr,
|
||||
);
|
||||
}
|
||||
|
||||
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandlePtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
|
||||
'frbgen_elcaju_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle',
|
||||
);
|
||||
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle =
|
||||
_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandlePtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
|
||||
|
||||
void
|
||||
rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
ffi.Pointer<ffi.Void> ptr,
|
||||
) {
|
||||
return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
ptr,
|
||||
);
|
||||
}
|
||||
|
||||
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandlePtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
|
||||
'frbgen_elcaju_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle',
|
||||
);
|
||||
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle =
|
||||
_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandlePtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
|
||||
|
||||
void
|
||||
rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
ffi.Pointer<ffi.Void> ptr,
|
||||
) {
|
||||
return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
ptr,
|
||||
);
|
||||
}
|
||||
|
||||
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMeltPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
|
||||
'frbgen_elcaju_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt',
|
||||
);
|
||||
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt =
|
||||
_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMeltPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
|
||||
|
||||
void
|
||||
rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
ffi.Pointer<ffi.Void> ptr,
|
||||
) {
|
||||
return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
ptr,
|
||||
);
|
||||
}
|
||||
|
||||
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMeltPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
|
||||
'frbgen_elcaju_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt',
|
||||
);
|
||||
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt =
|
||||
_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMeltPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
|
||||
|
||||
void
|
||||
rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
ffi.Pointer<ffi.Void> ptr,
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import 'api/error.dart';
|
||||
import 'api/keys.dart';
|
||||
import 'api/mint_info.dart';
|
||||
import 'api/payment_request.dart';
|
||||
import 'api/token.dart';
|
||||
import 'api/wallet.dart';
|
||||
import 'dart:async';
|
||||
@@ -24,6 +25,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
required super.portManager,
|
||||
});
|
||||
|
||||
CrossPlatformFinalizerArg
|
||||
get rust_arc_decrement_strong_count_CreatedPaymentRequestPtr => wire
|
||||
.rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest;
|
||||
|
||||
CrossPlatformFinalizerArg
|
||||
get rust_arc_decrement_strong_count_NostrListenerHandlePtr => wire
|
||||
.rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle;
|
||||
|
||||
CrossPlatformFinalizerArg
|
||||
get rust_arc_decrement_strong_count_PreparedMeltPtr => wire
|
||||
.rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt;
|
||||
|
||||
CrossPlatformFinalizerArg
|
||||
get rust_arc_decrement_strong_count_PreparedSendPtr => wire
|
||||
.rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend;
|
||||
@@ -43,6 +56,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
AnyhowException dco_decode_AnyhowException(dynamic raw);
|
||||
|
||||
@protected
|
||||
CreatedPaymentRequest
|
||||
dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
NostrListenerHandle
|
||||
dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedMelt
|
||||
dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedSend
|
||||
dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -67,6 +98,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
CreatedPaymentRequest
|
||||
dco_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedMelt
|
||||
dco_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedSend
|
||||
dco_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -85,6 +128,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
CreatedPaymentRequest
|
||||
dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
NostrListenerHandle
|
||||
dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedMelt
|
||||
dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedSend
|
||||
dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -112,6 +173,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
Map<String, String> dco_decode_Map_String_String_None(dynamic raw);
|
||||
|
||||
@protected
|
||||
CreatedPaymentRequest
|
||||
dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
NostrListenerHandle
|
||||
dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedMelt
|
||||
dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedSend
|
||||
dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -139,6 +218,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
RustStreamSink<MintQuote> dco_decode_StreamSink_mint_quote_Sse(dynamic raw);
|
||||
|
||||
@protected
|
||||
RustStreamSink<NostrPaymentEvent>
|
||||
dco_decode_StreamSink_nostr_payment_event_Sse(dynamic raw);
|
||||
|
||||
@protected
|
||||
RustStreamSink<BigInt> dco_decode_StreamSink_u_64_Sse(dynamic raw);
|
||||
|
||||
@@ -151,6 +234,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
bool dco_decode_box_autoadd_bool(dynamic raw);
|
||||
|
||||
@protected
|
||||
CreateRequestParams dco_decode_box_autoadd_create_request_params(dynamic raw);
|
||||
|
||||
@protected
|
||||
MeltQuote dco_decode_box_autoadd_melt_quote(dynamic raw);
|
||||
|
||||
@@ -160,6 +246,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
MintVersion dco_decode_box_autoadd_mint_version(dynamic raw);
|
||||
|
||||
@protected
|
||||
PersistedRequestData dco_decode_box_autoadd_persisted_request_data(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
ReceiveOptions dco_decode_box_autoadd_receive_options(dynamic raw);
|
||||
|
||||
@@ -183,6 +274,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
ContactInfo dco_decode_contact_info(dynamic raw);
|
||||
|
||||
@protected
|
||||
CreateRequestParams dco_decode_create_request_params(dynamic raw);
|
||||
|
||||
@protected
|
||||
Error dco_decode_error(dynamic raw);
|
||||
|
||||
@@ -219,6 +313,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
List<Transaction> dco_decode_list_transaction(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<TransportInfo> dco_decode_list_transport_info(dynamic raw);
|
||||
|
||||
@protected
|
||||
MeltMethodSettings dco_decode_melt_method_settings(dynamic raw);
|
||||
|
||||
@@ -240,6 +337,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
MintVersion dco_decode_mint_version(dynamic raw);
|
||||
|
||||
@protected
|
||||
NostrPaymentEvent dco_decode_nostr_payment_event(dynamic raw);
|
||||
|
||||
@protected
|
||||
NostrPaymentState dco_decode_nostr_payment_state(dynamic raw);
|
||||
|
||||
@protected
|
||||
Nut04Settings dco_decode_nut_04_settings(dynamic raw);
|
||||
|
||||
@@ -290,15 +393,27 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
Uint8List? dco_decode_opt_list_prim_u_8_strict(dynamic raw);
|
||||
|
||||
@protected
|
||||
PaymentRequestInfo dco_decode_payment_request_info(dynamic raw);
|
||||
|
||||
@protected
|
||||
PersistedRequestData dco_decode_persisted_request_data(dynamic raw);
|
||||
|
||||
@protected
|
||||
ReceiveOptions dco_decode_receive_options(dynamic raw);
|
||||
|
||||
@protected
|
||||
ReclaimResult dco_decode_reclaim_result(dynamic raw);
|
||||
|
||||
@protected
|
||||
(String, String) dco_decode_record_string_string(dynamic raw);
|
||||
|
||||
@protected
|
||||
SendOptions dco_decode_send_options(dynamic raw);
|
||||
|
||||
@protected
|
||||
SendResult dco_decode_send_result(dynamic raw);
|
||||
|
||||
@protected
|
||||
SupportedSettings dco_decode_supported_settings(dynamic raw);
|
||||
|
||||
@@ -314,6 +429,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
TransactionStatus dco_decode_transaction_status(dynamic raw);
|
||||
|
||||
@protected
|
||||
TransportInfo dco_decode_transport_info(dynamic raw);
|
||||
|
||||
@protected
|
||||
BigInt dco_decode_u_64(dynamic raw);
|
||||
|
||||
@@ -329,6 +447,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
CreatedPaymentRequest
|
||||
sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
NostrListenerHandle
|
||||
sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedMelt
|
||||
sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedSend
|
||||
sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -353,6 +489,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
CreatedPaymentRequest
|
||||
sse_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedMelt
|
||||
sse_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedSend
|
||||
sse_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -371,6 +519,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
CreatedPaymentRequest
|
||||
sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
NostrListenerHandle
|
||||
sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedMelt
|
||||
sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedSend
|
||||
sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -400,6 +566,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
CreatedPaymentRequest
|
||||
sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
NostrListenerHandle
|
||||
sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedMelt
|
||||
sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedSend
|
||||
sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -429,6 +613,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
RustStreamSink<NostrPaymentEvent>
|
||||
sse_decode_StreamSink_nostr_payment_event_Sse(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RustStreamSink<BigInt> sse_decode_StreamSink_u_64_Sse(
|
||||
SseDeserializer deserializer,
|
||||
@@ -443,6 +631,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
bool sse_decode_box_autoadd_bool(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
CreateRequestParams sse_decode_box_autoadd_create_request_params(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
MeltQuote sse_decode_box_autoadd_melt_quote(SseDeserializer deserializer);
|
||||
|
||||
@@ -452,6 +645,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
MintVersion sse_decode_box_autoadd_mint_version(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
PersistedRequestData sse_decode_box_autoadd_persisted_request_data(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
ReceiveOptions sse_decode_box_autoadd_receive_options(
|
||||
SseDeserializer deserializer,
|
||||
@@ -477,6 +675,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
ContactInfo sse_decode_contact_info(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
CreateRequestParams sse_decode_create_request_params(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
Error sse_decode_error(SseDeserializer deserializer);
|
||||
|
||||
@@ -519,6 +722,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
List<Transaction> sse_decode_list_transaction(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<TransportInfo> sse_decode_list_transport_info(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
MeltMethodSettings sse_decode_melt_method_settings(
|
||||
SseDeserializer deserializer,
|
||||
@@ -544,6 +752,16 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
MintVersion sse_decode_mint_version(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
NostrPaymentEvent sse_decode_nostr_payment_event(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
NostrPaymentState sse_decode_nostr_payment_state(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
Nut04Settings sse_decode_nut_04_settings(SseDeserializer deserializer);
|
||||
|
||||
@@ -602,9 +820,22 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
Uint8List? sse_decode_opt_list_prim_u_8_strict(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
PaymentRequestInfo sse_decode_payment_request_info(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PersistedRequestData sse_decode_persisted_request_data(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
ReceiveOptions sse_decode_receive_options(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
ReclaimResult sse_decode_reclaim_result(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
(String, String) sse_decode_record_string_string(
|
||||
SseDeserializer deserializer,
|
||||
@@ -613,6 +844,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
SendOptions sse_decode_send_options(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
SendResult sse_decode_send_result(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
SupportedSettings sse_decode_supported_settings(SseDeserializer deserializer);
|
||||
|
||||
@@ -630,6 +864,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
TransactionStatus sse_decode_transaction_status(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
TransportInfo sse_decode_transport_info(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
BigInt sse_decode_u_64(SseDeserializer deserializer);
|
||||
|
||||
@@ -648,6 +885,27 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
CreatedPaymentRequest self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
NostrListenerHandle self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
PreparedMelt self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -676,6 +934,20 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
CreatedPaymentRequest self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
PreparedMelt self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -697,6 +969,27 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
CreatedPaymentRequest self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
NostrListenerHandle self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
PreparedMelt self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -731,6 +1024,27 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
CreatedPaymentRequest self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
NostrListenerHandle self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
PreparedMelt self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
@@ -765,6 +1079,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_StreamSink_nostr_payment_event_Sse(
|
||||
RustStreamSink<NostrPaymentEvent> self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_StreamSink_u_64_Sse(
|
||||
RustStreamSink<BigInt> self,
|
||||
@@ -780,6 +1100,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_box_autoadd_bool(bool self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_create_request_params(
|
||||
CreateRequestParams self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_melt_quote(
|
||||
MeltQuote self,
|
||||
@@ -798,6 +1124,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_persisted_request_data(
|
||||
PersistedRequestData self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_receive_options(
|
||||
ReceiveOptions self,
|
||||
@@ -828,6 +1160,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_contact_info(ContactInfo self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_create_request_params(
|
||||
CreateRequestParams self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_error(Error self, SseSerializer serializer);
|
||||
|
||||
@@ -885,6 +1223,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_transport_info(
|
||||
List<TransportInfo> self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_melt_method_settings(
|
||||
MeltMethodSettings self,
|
||||
@@ -915,6 +1259,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_mint_version(MintVersion self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_nostr_payment_event(
|
||||
NostrPaymentEvent self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_nostr_payment_state(
|
||||
NostrPaymentState self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_nut_04_settings(Nut04Settings self, SseSerializer serializer);
|
||||
|
||||
@@ -984,12 +1340,27 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_payment_request_info(
|
||||
PaymentRequestInfo self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_persisted_request_data(
|
||||
PersistedRequestData self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_receive_options(
|
||||
ReceiveOptions self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_reclaim_result(ReclaimResult self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_record_string_string(
|
||||
(String, String) self,
|
||||
@@ -999,6 +1370,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_send_options(SendOptions self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_send_result(SendResult self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_supported_settings(
|
||||
SupportedSettings self,
|
||||
@@ -1023,6 +1397,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_transport_info(TransportInfo self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_u_64(BigInt self, SseSerializer serializer);
|
||||
|
||||
@@ -1041,6 +1418,54 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
class RustLibWire implements BaseWire {
|
||||
RustLibWire.fromExternalLibrary(ExternalLibrary lib);
|
||||
|
||||
void
|
||||
rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
int ptr,
|
||||
) => wasmModule
|
||||
.rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
ptr,
|
||||
);
|
||||
|
||||
void
|
||||
rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
int ptr,
|
||||
) => wasmModule
|
||||
.rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
ptr,
|
||||
);
|
||||
|
||||
void
|
||||
rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
int ptr,
|
||||
) => wasmModule
|
||||
.rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
ptr,
|
||||
);
|
||||
|
||||
void
|
||||
rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
int ptr,
|
||||
) => wasmModule
|
||||
.rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
ptr,
|
||||
);
|
||||
|
||||
void
|
||||
rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
int ptr,
|
||||
) => wasmModule
|
||||
.rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
ptr,
|
||||
);
|
||||
|
||||
void
|
||||
rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
int ptr,
|
||||
) => wasmModule
|
||||
.rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
ptr,
|
||||
);
|
||||
|
||||
void
|
||||
rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
int ptr,
|
||||
@@ -1112,6 +1537,36 @@ external RustLibWasmModule get wasmModule;
|
||||
@JS()
|
||||
@anonymous
|
||||
extension type RustLibWasmModule._(JSObject _) implements JSObject {
|
||||
external void
|
||||
rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
int ptr,
|
||||
);
|
||||
|
||||
external void
|
||||
rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCreatedPaymentRequest(
|
||||
int ptr,
|
||||
);
|
||||
|
||||
external void
|
||||
rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
int ptr,
|
||||
);
|
||||
|
||||
external void
|
||||
rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerNostrListenerHandle(
|
||||
int ptr,
|
||||
);
|
||||
|
||||
external void
|
||||
rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
int ptr,
|
||||
);
|
||||
|
||||
external void
|
||||
rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedMelt(
|
||||
int ptr,
|
||||
);
|
||||
|
||||
external void
|
||||
rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPreparedSend(
|
||||
int ptr,
|
||||
|
||||
@@ -7,7 +7,7 @@ project(runner LANGUAGES CXX)
|
||||
set(BINARY_NAME "elcaju")
|
||||
# The unique GTK application identifier for this application. See:
|
||||
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
||||
set(APPLICATION_ID "com.example.elcaju")
|
||||
set(APPLICATION_ID "me.elcaju")
|
||||
|
||||
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
||||
# versions of CMake.
|
||||
|
||||
@@ -385,7 +385,7 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.elcaju.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.elcaju.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/elcaju.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/elcaju";
|
||||
@@ -399,7 +399,7 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.elcaju.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.elcaju.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/elcaju.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/elcaju";
|
||||
@@ -413,7 +413,7 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.elcaju.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.elcaju.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/elcaju.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/elcaju";
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
PRODUCT_NAME = elcaju
|
||||
|
||||
// The application's bundle identifier
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.elcaju
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.elcaju
|
||||
|
||||
// The copyright displayed in application information
|
||||
PRODUCT_COPYRIGHT = Copyright © 2026 com.example. All rights reserved.
|
||||
PRODUCT_COPYRIGHT = Copyright © 2026 ElCaju. All rights reserved.
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
name: elcaju
|
||||
description: "ElCaju - Tu wallet de ecash privado"
|
||||
publish_to: 'none'
|
||||
version: 0.1.0+2
|
||||
version: 0.2.0+3
|
||||
|
||||
environment:
|
||||
sdk: ^3.11.0
|
||||
|
||||
Generated
+393
-17
@@ -17,6 +17,27 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
@@ -184,12 +205,49 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-utility"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a34a3b57207a7a1007832416c3e4862378c8451b4e8e093e436f48c2d3d2c151"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"gloo-timers",
|
||||
"tokio",
|
||||
"wasm-bindgen-futures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-wsocket"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c92385c7c8b3eb2de1b78aeca225212e4c9a69a78b802832759b108681a5069"
|
||||
dependencies = [
|
||||
"async-utility",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"js-sys",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tokio-socks",
|
||||
"tokio-tungstenite",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba"
|
||||
|
||||
[[package]]
|
||||
name = "atomic-destructor"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef49f5882e4b6afaac09ad239a4f8c70a24b8f2b0897edb1f706008efd109cf4"
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
@@ -239,6 +297,12 @@ version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
|
||||
|
||||
[[package]]
|
||||
name = "bc-ur"
|
||||
version = "0.12.0"
|
||||
@@ -303,6 +367,17 @@ version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953"
|
||||
|
||||
[[package]]
|
||||
name = "bitcoin-payment-instructions"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c300c948b2ff78c965ea3a613372352125448a22f1acf49e95e3878149824091"
|
||||
dependencies = [
|
||||
"bitcoin",
|
||||
"lightning",
|
||||
"lightning-invoice",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitcoin-private"
|
||||
version = "0.1.0"
|
||||
@@ -354,6 +429,15 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brotli"
|
||||
version = "8.0.2"
|
||||
@@ -416,15 +500,16 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||
|
||||
[[package]]
|
||||
name = "cashu"
|
||||
version = "0.15.1"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d612261d61069314bb0e39adbd63d0fd3bd22084f332e85c98e615c15f24e5a"
|
||||
checksum = "26b4ad04070fb67914feb2641e7b54001000f1f7f67a7c39a08f32edf01fd3ae"
|
||||
dependencies = [
|
||||
"bitcoin",
|
||||
"cbor-diag",
|
||||
"ciborium",
|
||||
"lightning",
|
||||
"lightning-invoice",
|
||||
"nostr-sdk",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -433,12 +518,22 @@ dependencies = [
|
||||
"strum_macros",
|
||||
"thiserror 2.0.18",
|
||||
"tracing",
|
||||
"unicode-normalization",
|
||||
"url",
|
||||
"uuid",
|
||||
"web-time",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cbc"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cbor-diag"
|
||||
version = "0.1.12"
|
||||
@@ -472,14 +567,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cdk"
|
||||
version = "0.15.1"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8955437beea95ee6849c6eab59012760a7589e6eeff4d6e36ebf324c641388d"
|
||||
checksum = "c6536eb1df7a497cb334edf6bb9eea1fa8e4a9b08c79fe45c40d43413ecb88c8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
"async-trait",
|
||||
"bitcoin",
|
||||
"bitcoin-payment-instructions",
|
||||
"cbor-diag",
|
||||
"cdk-common",
|
||||
"cdk-signatory",
|
||||
@@ -490,6 +586,7 @@ dependencies = [
|
||||
"jsonwebtoken",
|
||||
"lightning",
|
||||
"lightning-invoice",
|
||||
"nostr-sdk",
|
||||
"regex",
|
||||
"ring",
|
||||
"rustls",
|
||||
@@ -498,7 +595,6 @@ dependencies = [
|
||||
"serde_with",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"url",
|
||||
@@ -509,9 +605,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cdk-common"
|
||||
version = "0.15.1"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f5bf771d3780d2321211eb575e1f8341cec50f8a16c4f50b2787807b5090a38"
|
||||
checksum = "a371d218ffe83dcabd202278251af53744b8fa3e846124ddcd34247c4e38bdc3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -522,6 +618,7 @@ dependencies = [
|
||||
"ciborium",
|
||||
"futures",
|
||||
"getrandom 0.2.17",
|
||||
"jsonwebtoken",
|
||||
"lightning",
|
||||
"lightning-invoice",
|
||||
"parking_lot",
|
||||
@@ -542,23 +639,31 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cdk-http-client"
|
||||
version = "0.15.1"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b58f47c4af4a4c862d145863211f5454c3ccfbacaaef9e4913276519a1ac498e"
|
||||
checksum = "bf194792d45975b360911417d3713e4d3f0142e18b082e2a8c2c88a91630b43d"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"futures-channel",
|
||||
"js-sys",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.18",
|
||||
"tokio-tungstenite",
|
||||
"tracing",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cdk-signatory"
|
||||
version = "0.15.1"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1dca3b84999ac0e6fd15330d566aff6df6082a349134fd884deef8c9eeb570"
|
||||
checksum = "4f40745c9402049bbba47ae52e1fc7552d4af8e99572fd73697448f51bbb3a02"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -578,9 +683,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cdk-sql-common"
|
||||
version = "0.15.1"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6625a8248a43fbebb92cc57f1b09c453d5de94d62a512b62e9a62b25d0dd5506"
|
||||
checksum = "36e920cffaa36f396d16727649b05d6dc8d42aecc4108fa955876515149bdb0e"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bitcoin",
|
||||
@@ -597,9 +702,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cdk-sqlite"
|
||||
version = "0.15.1"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9334877fe3f9fb25f6819c0876af1b0690f47aa2b1e548a20ac904740cbd574c"
|
||||
checksum = "c706b86b76d89bbbfad7e106328c14f1d99440ee5829840510e50922f48fdc51"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bitcoin",
|
||||
@@ -628,6 +733,30 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chacha20poly1305"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"chacha20",
|
||||
"cipher",
|
||||
"poly1305",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.44"
|
||||
@@ -669,6 +798,17 @@ dependencies = [
|
||||
"half",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.0"
|
||||
@@ -807,6 +947,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core 0.6.4",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
@@ -915,6 +1056,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -933,6 +1075,9 @@ name = "dnssec-prover"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec4f825369fc7134da70ca4040fddc8e03b80a46d249ae38d9c1c39b7b4476bf"
|
||||
dependencies = [
|
||||
"bitcoin_hashes 0.14.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
@@ -940,6 +1085,12 @@ version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "elcaju_core"
|
||||
version = "0.1.0"
|
||||
@@ -953,6 +1104,7 @@ dependencies = [
|
||||
"flutter_rust_bridge",
|
||||
"getrandom 0.3.4",
|
||||
"log",
|
||||
"nostr-sdk",
|
||||
"oslog",
|
||||
"reqwest",
|
||||
"serde_json",
|
||||
@@ -1313,6 +1465,15 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd"
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.12"
|
||||
@@ -1397,7 +1558,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower-service",
|
||||
"webpki-roots",
|
||||
"webpki-roots 1.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1584,6 +1745,28 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||
dependencies = [
|
||||
"block-padding",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.12.0"
|
||||
@@ -1754,6 +1937,12 @@ version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
version = "0.16.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
|
||||
|
||||
[[package]]
|
||||
name = "lru-slab"
|
||||
version = "0.1.2"
|
||||
@@ -1832,6 +2021,12 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "negentropy"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0efe882e02d206d8d279c20eb40e03baf7cb5136a1476dc084a324fbc3ec42d"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
@@ -1842,6 +2037,84 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nostr"
|
||||
version = "0.44.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3aa5e3b6a278ed061835fe1ee293b71641e6bf8b401cfe4e1834bbf4ef0a34e1"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"base64 0.22.1",
|
||||
"bech32",
|
||||
"bip39",
|
||||
"bitcoin_hashes 0.14.1",
|
||||
"cbc",
|
||||
"chacha20",
|
||||
"chacha20poly1305",
|
||||
"getrandom 0.2.17",
|
||||
"hex",
|
||||
"instant",
|
||||
"scrypt",
|
||||
"secp256k1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"unicode-normalization",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nostr-database"
|
||||
version = "0.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7462c9d8ae5ef6a28d66a192d399ad2530f1f2130b13186296dbb11bdef5b3d1"
|
||||
dependencies = [
|
||||
"lru",
|
||||
"nostr",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nostr-gossip"
|
||||
version = "0.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ade30de16869618919c6b5efc8258f47b654a98b51541eb77f85e8ec5e3c83a6"
|
||||
dependencies = [
|
||||
"nostr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nostr-relay-pool"
|
||||
version = "0.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b1073ccfbaea5549fb914a9d52c68dab2aecda61535e5143dd73e95445a804b"
|
||||
dependencies = [
|
||||
"async-utility",
|
||||
"async-wsocket",
|
||||
"atomic-destructor",
|
||||
"hex",
|
||||
"lru",
|
||||
"negentropy",
|
||||
"nostr",
|
||||
"nostr-database",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nostr-sdk"
|
||||
version = "0.44.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "471732576710e779b64f04c55e3f8b5292f865fea228436daf19694f0bf70393"
|
||||
dependencies = [
|
||||
"async-utility",
|
||||
"nostr",
|
||||
"nostr-database",
|
||||
"nostr-gossip",
|
||||
"nostr-relay-pool",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.3"
|
||||
@@ -1927,6 +2200,12 @@ version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.2.1"
|
||||
@@ -1967,12 +2246,33 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "password-hash"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"hmac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem"
|
||||
version = "3.0.6"
|
||||
@@ -2069,6 +2369,17 @@ version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "poly1305"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
|
||||
dependencies = [
|
||||
"cpufeatures",
|
||||
"opaque-debug",
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.13.1"
|
||||
@@ -2365,7 +2676,7 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"webpki-roots",
|
||||
"webpki-roots 1.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2467,6 +2778,15 @@ version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||
|
||||
[[package]]
|
||||
name = "salsa20"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.29"
|
||||
@@ -2506,6 +2826,18 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "scrypt"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
|
||||
dependencies = [
|
||||
"password-hash",
|
||||
"pbkdf2",
|
||||
"salsa20",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secp256k1"
|
||||
version = "0.29.1"
|
||||
@@ -2659,6 +2991,17 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
@@ -2960,6 +3303,18 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-socks"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f"
|
||||
dependencies = [
|
||||
"either",
|
||||
"futures-util",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.18"
|
||||
@@ -2985,6 +3340,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tungstenite",
|
||||
"webpki-roots 0.26.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3184,6 +3540,16 @@ version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
@@ -3213,6 +3579,7 @@ dependencies = [
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3405,6 +3772,15 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.26.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
|
||||
dependencies = [
|
||||
"webpki-roots 1.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "1.0.6"
|
||||
|
||||
+6
-3
@@ -8,9 +8,9 @@ crate-type = ["cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
# Cashu — acceso directo, sin intermediarios
|
||||
cdk = { version = "0.15.1", default-features = false, features = ["wallet"] }
|
||||
cdk-common = { version = "0.15.1", default-features = false }
|
||||
cdk-sqlite = { version = "0.15.1", default-features = false, features = ["wallet"] }
|
||||
cdk = { version = "0.16.0", default-features = false, features = ["wallet", "nostr"] }
|
||||
cdk-common = { version = "0.16.0", default-features = false, features = ["nostr"] }
|
||||
cdk-sqlite = { version = "0.16.0", default-features = false, features = ["wallet"] }
|
||||
|
||||
# Bridge Flutter <-> Rust
|
||||
flutter_rust_bridge = { version = "=2.11.1", default-features = false, features = [
|
||||
@@ -31,6 +31,9 @@ bip39 = { version = "2.1", default-features = false, features = ["std"] }
|
||||
# Entropy
|
||||
getrandom = { version = "0.3", default-features = false, features = ["std"] }
|
||||
|
||||
# Nostr keys (same version as CDK 0.16.0 — CDK does not re-export Keys)
|
||||
nostr-sdk = { version = "0.44.1", default-features = false, features = ["nip04", "nip44", "nip59"] }
|
||||
|
||||
# Async runtime
|
||||
tokio = { version = "1", default-features = false }
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod error;
|
||||
pub mod keys;
|
||||
pub mod mint_info;
|
||||
pub mod payment_request;
|
||||
pub mod token;
|
||||
pub mod wallet;
|
||||
|
||||
@@ -0,0 +1,581 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use cdk::{
|
||||
amount::Amount,
|
||||
mint_url::MintUrl,
|
||||
nuts::{
|
||||
CurrencyUnit, PaymentRequest as CdkPaymentRequest, Transport, TransportType,
|
||||
},
|
||||
wallet::ReceiveOptions as CdkReceiveOptions,
|
||||
};
|
||||
use flutter_rust_bridge::frb;
|
||||
use nostr_sdk::{
|
||||
nips::nip19::{Nip19Profile, ToBech32},
|
||||
Keys as NostrKeys, PublicKey, RelayUrl,
|
||||
};
|
||||
|
||||
use crate::frb_generated::StreamSink;
|
||||
|
||||
use super::{error::Error, wallet::Wallet};
|
||||
|
||||
// ========================================================================
|
||||
// Payment Request parsing (NUT-18 creqA + NUT-26 creqB)
|
||||
// ========================================================================
|
||||
|
||||
/// Parsed payment request info exposed to Flutter.
|
||||
/// CDK's FromStr auto-detects creqA (CBOR) vs creqB (Bech32m).
|
||||
pub struct PaymentRequestInfo {
|
||||
/// Raw encoded string (creqA... or CREQB1...)
|
||||
pub raw: String,
|
||||
/// Payment id
|
||||
pub payment_id: Option<String>,
|
||||
/// Requested amount (in base unit)
|
||||
pub amount: Option<u64>,
|
||||
/// Currency unit ("sat", "usd", etc.)
|
||||
pub unit: Option<String>,
|
||||
/// Whether this is a single-use request
|
||||
pub single_use: Option<bool>,
|
||||
/// Accepted mints (empty = any mint)
|
||||
pub mints: Vec<String>,
|
||||
/// Human-readable description
|
||||
pub description: Option<String>,
|
||||
/// Available transports
|
||||
pub transports: Vec<TransportInfo>,
|
||||
/// Whether NUT-10 spending conditions are required
|
||||
pub has_nut10: bool,
|
||||
}
|
||||
|
||||
/// Transport info exposed to Flutter
|
||||
pub struct TransportInfo {
|
||||
/// "nostr" or "post"
|
||||
pub transport_type: String,
|
||||
/// Target (nprofile or URL)
|
||||
pub target: String,
|
||||
}
|
||||
|
||||
impl PaymentRequestInfo {
|
||||
/// Parse a payment request string (creqA, CREQB1, or bitcoin:?creq=).
|
||||
/// Supports NUT-18 (CBOR+base64) and NUT-26 (Bech32m) automatically.
|
||||
#[frb(sync)]
|
||||
pub fn parse(encoded: String) -> Result<PaymentRequestInfo, Error> {
|
||||
parse_payment_request_inner(encoded)
|
||||
}
|
||||
}
|
||||
|
||||
/// Inner parsing logic, outside of frb macro scope to avoid iterator issues.
|
||||
fn parse_payment_request_inner(encoded: String) -> Result<PaymentRequestInfo, Error> {
|
||||
let creq_str = extract_creq_from_uri(&encoded).unwrap_or(encoded);
|
||||
|
||||
let pr = CdkPaymentRequest::from_str(&creq_str)
|
||||
.map_err(|e| Error::Cdk(format!("Invalid payment request: {e}")))?;
|
||||
|
||||
let mints: Vec<String> = pr.mints.iter().map(|m| m.to_string()).collect();
|
||||
let mut transports = Vec::new();
|
||||
for t in &pr.transports {
|
||||
transports.push(TransportInfo {
|
||||
transport_type: match t._type {
|
||||
TransportType::Nostr => "nostr".into(),
|
||||
TransportType::HttpPost => "post".into(),
|
||||
},
|
||||
target: t.target.clone(),
|
||||
});
|
||||
}
|
||||
let has_nut10 = pr.nut10.is_some();
|
||||
|
||||
Ok(PaymentRequestInfo {
|
||||
raw: creq_str,
|
||||
payment_id: pr.payment_id,
|
||||
amount: pr.amount.map(|a| a.into()),
|
||||
unit: pr.unit.map(|u| format!("{}", u)),
|
||||
single_use: pr.single_use,
|
||||
mints,
|
||||
description: pr.description,
|
||||
transports,
|
||||
has_nut10,
|
||||
})
|
||||
}
|
||||
|
||||
/// Extract creq parameter from a BIP-321 bitcoin: URI.
|
||||
/// e.g. "bitcoin:?creq=CREQB1...&lightning=lnbc..." → "CREQB1..."
|
||||
/// Handles percent-encoding and case-insensitive key matching.
|
||||
fn extract_creq_from_uri(input: &str) -> Option<String> {
|
||||
if !input
|
||||
.get(..8)
|
||||
.is_some_and(|scheme| scheme.eq_ignore_ascii_case("bitcoin:"))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let query = input.split_once('?')?.1;
|
||||
for param in query.split('&') {
|
||||
let (key, value) = param.split_once('=')?;
|
||||
if key.eq_ignore_ascii_case("creq") {
|
||||
return Some(percent_decode(value));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Simple percent-decoding for URI query values.
|
||||
fn percent_decode(value: &str) -> String {
|
||||
let bytes = value.as_bytes();
|
||||
let mut out = Vec::with_capacity(bytes.len());
|
||||
let mut i = 0;
|
||||
while i < bytes.len() {
|
||||
if bytes[i] == b'%' && i + 2 < bytes.len() {
|
||||
if let Ok(byte) = u8::from_str_radix(
|
||||
std::str::from_utf8(&bytes[i + 1..i + 3]).unwrap_or(""),
|
||||
16,
|
||||
) {
|
||||
out.push(byte);
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
out.push(bytes[i]);
|
||||
i += 1;
|
||||
}
|
||||
String::from_utf8(out).unwrap_or_else(|_| value.to_string())
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Payment execution (payer side)
|
||||
// ========================================================================
|
||||
|
||||
impl Wallet {
|
||||
/// Pay a NUT-18 payment request.
|
||||
/// Uses CDK's pay_request() which handles:
|
||||
/// - NUT-10 spending conditions
|
||||
/// - Transport selection (Nostr preferred, HTTP POST fallback)
|
||||
/// - Token preparation and delivery
|
||||
pub async fn pay_payment_request(
|
||||
&self,
|
||||
encoded: String,
|
||||
custom_amount: Option<u64>,
|
||||
) -> Result<(), Error> {
|
||||
let creq_str = extract_creq_from_uri(&encoded).unwrap_or(encoded);
|
||||
|
||||
let pr = CdkPaymentRequest::from_str(&creq_str)
|
||||
.map_err(|e| Error::Cdk(format!("Invalid payment request: {e}")))?;
|
||||
|
||||
self.inner
|
||||
.pay_request(pr, custom_amount.map(Amount::from))
|
||||
.await?;
|
||||
|
||||
self.update_balance_streams().await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Payment Request creation (payee/receiver side) — NUT-18/26
|
||||
// ========================================================================
|
||||
|
||||
/// Parameters for creating a payment request.
|
||||
pub struct CreateRequestParams {
|
||||
/// Amount to request (in smallest unit, e.g. sats)
|
||||
pub amount: Option<u64>,
|
||||
/// Currency unit ("sat", "usd", etc.)
|
||||
pub unit: String,
|
||||
/// Human-readable description
|
||||
pub description: Option<String>,
|
||||
/// Nostr relay URLs for the transport
|
||||
pub nostr_relays: Vec<String>,
|
||||
}
|
||||
|
||||
/// Result of creating a payment request.
|
||||
pub struct CreatedPaymentRequest {
|
||||
/// NUT-18 encoding: creqA... (CBOR+base64url)
|
||||
pub creq_a: String,
|
||||
/// NUT-26 encoding: CREQB1... (Bech32m, uppercase for QR)
|
||||
pub creq_b: String,
|
||||
/// Opaque handle holding the ephemeral Nostr keys — pass to wait_for_nostr_payment().
|
||||
/// The secret key never leaves Rust.
|
||||
pub listener_handle: NostrListenerHandle,
|
||||
}
|
||||
|
||||
/// Opaque handle that keeps Nostr ephemeral keys in Rust memory.
|
||||
/// Dart receives this as an opaque reference and passes it back
|
||||
/// to wait_for_nostr_payment() without ever seeing the secret key.
|
||||
/// Also carries the expected payment parameters for validation.
|
||||
#[derive(Clone)]
|
||||
pub struct NostrListenerHandle {
|
||||
keys: NostrKeys,
|
||||
pubkey: PublicKey,
|
||||
relays: Vec<String>,
|
||||
/// Expected amount (None = any amount accepted)
|
||||
expected_amount: Option<Amount>,
|
||||
/// Expected currency unit
|
||||
expected_unit: CurrencyUnit,
|
||||
/// This wallet's mint URL
|
||||
mint_url: MintUrl,
|
||||
}
|
||||
|
||||
/// Serializable data for persisting a NostrListenerHandle across app restarts.
|
||||
pub struct PersistedRequestData {
|
||||
pub secret_hex: String,
|
||||
pub pubkey_hex: String,
|
||||
pub relays: Vec<String>,
|
||||
pub amount: Option<u64>,
|
||||
pub unit: String,
|
||||
pub mint_url: String,
|
||||
}
|
||||
|
||||
impl NostrListenerHandle {
|
||||
/// Export handle data for persistence (e.g. SharedPreferences).
|
||||
/// The secret key is exposed as hex — the caller is responsible
|
||||
/// for storing it securely.
|
||||
#[frb(sync)]
|
||||
pub fn to_persisted(&self) -> PersistedRequestData {
|
||||
PersistedRequestData {
|
||||
secret_hex: self.keys.secret_key().to_secret_hex(),
|
||||
pubkey_hex: self.pubkey.to_hex(),
|
||||
relays: self.relays.clone(),
|
||||
amount: self.expected_amount.map(|a| a.into()),
|
||||
unit: self.expected_unit.to_string(),
|
||||
mint_url: self.mint_url.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reconstruct a handle from persisted data (e.g. after app restart).
|
||||
#[frb(sync)]
|
||||
pub fn from_persisted(data: PersistedRequestData) -> Result<NostrListenerHandle, Error> {
|
||||
let secret_key = nostr_sdk::SecretKey::from_hex(&data.secret_hex)
|
||||
.map_err(|e| Error::Cdk(format!("Invalid persisted secret key: {e}")))?;
|
||||
let keys = NostrKeys::new(secret_key);
|
||||
// Derive pubkey from secret — don't trust the persisted copy
|
||||
let pubkey = keys.public_key;
|
||||
let unit = CurrencyUnit::from_str(&data.unit)
|
||||
.unwrap_or(CurrencyUnit::Custom(data.unit));
|
||||
let mint_url = MintUrl::from_str(&data.mint_url)
|
||||
.map_err(|e| Error::Cdk(format!("Invalid persisted mint URL: {e}")))?;
|
||||
|
||||
Ok(NostrListenerHandle {
|
||||
keys,
|
||||
pubkey,
|
||||
relays: data.relays,
|
||||
expected_amount: data.amount.map(Amount::from),
|
||||
expected_unit: unit,
|
||||
mint_url,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// State of a Nostr payment listener.
|
||||
pub enum NostrPaymentState {
|
||||
/// Connected to relays, waiting for payment
|
||||
Waiting,
|
||||
/// Payment received and tokens claimed
|
||||
Received,
|
||||
/// Error occurred
|
||||
Error,
|
||||
}
|
||||
|
||||
/// Event emitted by the Nostr payment listener.
|
||||
pub struct NostrPaymentEvent {
|
||||
pub state: NostrPaymentState,
|
||||
pub amount: Option<u64>,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
impl Wallet {
|
||||
/// Create a NUT-18 payment request with Nostr transport.
|
||||
///
|
||||
/// Builds a PaymentRequest with the wallet's mint URL and unit,
|
||||
/// generates ephemeral Nostr keys, and returns both creqA and creqB
|
||||
/// encodings plus the keys needed for the Nostr listener.
|
||||
pub async fn create_payment_request(
|
||||
&self,
|
||||
params: CreateRequestParams,
|
||||
) -> Result<CreatedPaymentRequest, Error> {
|
||||
// Generate ephemeral Nostr keys for this request
|
||||
let keys = NostrKeys::generate();
|
||||
|
||||
// Parse relay URLs for nprofile
|
||||
if params.nostr_relays.is_empty() {
|
||||
return Err(Error::InvalidInput);
|
||||
}
|
||||
|
||||
let relay_urls: Vec<RelayUrl> = params
|
||||
.nostr_relays
|
||||
.iter()
|
||||
.map(|r| RelayUrl::parse(r))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|e| Error::Cdk(format!("Invalid relay URL: {e}")))?;
|
||||
|
||||
let nprofile = Nip19Profile::new(keys.public_key, relay_urls);
|
||||
let nprofile_bech32 = nprofile
|
||||
.to_bech32()
|
||||
.map_err(|e| Error::Cdk(format!("nprofile encoding failed: {e}")))?;
|
||||
|
||||
// Build the Nostr transport
|
||||
let nostr_transport = Transport {
|
||||
_type: TransportType::Nostr,
|
||||
target: nprofile_bech32,
|
||||
tags: vec![vec!["n".to_string(), "17".to_string()]],
|
||||
};
|
||||
|
||||
// Build the PaymentRequest with this wallet's mint and unit
|
||||
let mint_url = self.mint_url()?;
|
||||
if params.unit != self.unit {
|
||||
return Err(Error::InvalidInput);
|
||||
}
|
||||
let unit = CurrencyUnit::from_str(&self.unit)
|
||||
.unwrap_or(CurrencyUnit::Custom(self.unit.clone()));
|
||||
|
||||
let pr = CdkPaymentRequest {
|
||||
payment_id: None,
|
||||
amount: params.amount.map(Amount::from),
|
||||
unit: Some(unit.clone()),
|
||||
single_use: Some(true),
|
||||
mints: vec![mint_url.clone()],
|
||||
description: params.description,
|
||||
transports: vec![nostr_transport],
|
||||
nut10: None,
|
||||
};
|
||||
|
||||
// Encode both formats
|
||||
let creq_a = pr.to_string(); // NUT-18: creqA...
|
||||
let creq_b = pr
|
||||
.to_bech32_string()
|
||||
.map_err(|e| Error::Cdk(format!("Bech32m encoding failed: {e}")))?;
|
||||
|
||||
let pubkey = keys.public_key;
|
||||
Ok(CreatedPaymentRequest {
|
||||
creq_a,
|
||||
creq_b,
|
||||
listener_handle: NostrListenerHandle {
|
||||
keys,
|
||||
pubkey,
|
||||
relays: params.nostr_relays,
|
||||
expected_amount: params.amount.map(Amount::from),
|
||||
expected_unit: unit,
|
||||
mint_url,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Wait for an incoming Nostr payment (NIP-17 gift-wrap).
|
||||
///
|
||||
/// Takes the opaque NostrListenerHandle returned by create_payment_request().
|
||||
/// The secret key never leaves Rust memory.
|
||||
pub async fn wait_for_nostr_payment(
|
||||
&self,
|
||||
handle: NostrListenerHandle,
|
||||
sink: StreamSink<NostrPaymentEvent>,
|
||||
) -> Result<(), Error> {
|
||||
let NostrListenerHandle {
|
||||
keys,
|
||||
pubkey,
|
||||
relays,
|
||||
expected_amount,
|
||||
expected_unit,
|
||||
mint_url,
|
||||
} = handle;
|
||||
|
||||
// Fail fast if the handle was created for a different wallet
|
||||
let current_mint = self.mint_url()?;
|
||||
let current_unit = CurrencyUnit::from_str(&self.unit)
|
||||
.unwrap_or(CurrencyUnit::Custom(self.unit.clone()));
|
||||
if mint_url != current_mint || expected_unit != current_unit {
|
||||
return Err(Error::InvalidInput);
|
||||
}
|
||||
|
||||
if sink
|
||||
.add(NostrPaymentEvent {
|
||||
state: NostrPaymentState::Waiting,
|
||||
amount: None,
|
||||
error: None,
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let _self = self.clone();
|
||||
flutter_rust_bridge::spawn(async move {
|
||||
match wait_for_nostr_payment_inner(
|
||||
&_self, keys, pubkey, relays, expected_amount, expected_unit, mint_url, &sink,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(amount) => {
|
||||
let _ = sink.add(NostrPaymentEvent {
|
||||
state: NostrPaymentState::Received,
|
||||
amount: Some(amount.into()),
|
||||
error: None,
|
||||
});
|
||||
_self.update_balance_streams().await;
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = sink.add(NostrPaymentEvent {
|
||||
state: NostrPaymentState::Error,
|
||||
amount: None,
|
||||
error: Some(e.to_string()),
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal: connect to relays, listen for gift-wrapped payment, receive tokens.
|
||||
/// Validates incoming payments against expected amount/unit/mint before accepting.
|
||||
/// Periodically checks if the Dart stream is still alive and disconnects if not.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn wait_for_nostr_payment_inner(
|
||||
wallet: &Wallet,
|
||||
keys: NostrKeys,
|
||||
pubkey: PublicKey,
|
||||
relays: Vec<String>,
|
||||
expected_amount: Option<Amount>,
|
||||
expected_unit: CurrencyUnit,
|
||||
expected_mint: MintUrl,
|
||||
sink: &StreamSink<NostrPaymentEvent>,
|
||||
) -> Result<Amount, Error> {
|
||||
use cdk::nuts::{nut00::ProofsMethods, PaymentRequestPayload, Token as CdkToken};
|
||||
use nostr_sdk::{Client, Filter, Kind};
|
||||
use std::time::Duration;
|
||||
|
||||
// Create Nostr client with ephemeral keys
|
||||
let client = Client::new(keys.clone());
|
||||
for relay in &relays {
|
||||
client
|
||||
.add_read_relay(relay)
|
||||
.await
|
||||
.map_err(|e| Error::Cdk(format!("Failed to add relay {relay}: {e}")))?;
|
||||
}
|
||||
|
||||
// Connect with timeout so we fail fast if relays are unreachable
|
||||
tokio::time::timeout(Duration::from_secs(10), client.connect())
|
||||
.await
|
||||
.map_err(|_| Error::Network("Relay connection timed out".to_string()))?;
|
||||
|
||||
// Create the broadcast receiver BEFORE subscribing so we capture historical
|
||||
// events the relay sends back in response to our subscription filter.
|
||||
// (broadcast::Receiver only sees messages sent after its creation)
|
||||
let mut notifications = client.notifications();
|
||||
|
||||
// Subscribe to NIP-17 gift-wrap events (kind 1059) addressed to our ephemeral pubkey
|
||||
let filter = Filter::new().pubkey(pubkey).kind(Kind::GiftWrap);
|
||||
client
|
||||
.subscribe(filter, None)
|
||||
.await
|
||||
.map_err(|e| Error::Cdk(format!("Subscription failed: {e}")))?;
|
||||
loop {
|
||||
match tokio::time::timeout(Duration::from_secs(1), notifications.recv()).await {
|
||||
Ok(Ok(notification)) => {
|
||||
// Check sink is alive before processing
|
||||
if sink
|
||||
.add(NostrPaymentEvent {
|
||||
state: NostrPaymentState::Waiting,
|
||||
amount: None,
|
||||
error: None,
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
client.disconnect().await;
|
||||
return Err(Error::Network("Listener cancelled by client".to_string()));
|
||||
}
|
||||
|
||||
if let nostr_sdk::RelayPoolNotification::Event { event, .. } = notification {
|
||||
// Try to unwrap NIP-17 gift-wrap
|
||||
let unwrapped = match client.unwrap_gift_wrap(&event).await {
|
||||
Ok(rumor) => rumor,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
// Parse PaymentRequestPayload from the rumor content
|
||||
let payload: PaymentRequestPayload =
|
||||
match serde_json::from_str(&unwrapped.rumor.content) {
|
||||
Ok(p) => p,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
// Validate unit matches
|
||||
if payload.unit != expected_unit {
|
||||
log::warn!(
|
||||
"Ignoring payment: unit mismatch (expected {}, got {})",
|
||||
expected_unit,
|
||||
payload.unit
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate mint matches
|
||||
if payload.mint != expected_mint {
|
||||
log::warn!(
|
||||
"Ignoring payment: mint mismatch (expected {}, got {})",
|
||||
expected_mint,
|
||||
payload.mint
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate amount if a specific amount was requested
|
||||
if let Some(expected) = expected_amount {
|
||||
let received_amount = payload.proofs.total_amount().unwrap_or_default();
|
||||
if received_amount < expected {
|
||||
log::warn!(
|
||||
"Ignoring payment: underpayment (expected {}, got {})",
|
||||
expected,
|
||||
received_amount
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Build a token from the validated payload and receive it
|
||||
let token = CdkToken::new(
|
||||
payload.mint,
|
||||
payload.proofs,
|
||||
payload.memo,
|
||||
payload.unit,
|
||||
);
|
||||
let token_str = token.to_string();
|
||||
|
||||
// Continue listening on receive errors (e.g. already-spent proofs)
|
||||
match wallet
|
||||
.inner
|
||||
.receive(&token_str, CdkReceiveOptions::default())
|
||||
.await
|
||||
{
|
||||
Ok(received) => {
|
||||
client.disconnect().await;
|
||||
return Ok(received);
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("Failed to receive token, continuing: {e}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Err(_)) => {
|
||||
// Notification channel closed
|
||||
break;
|
||||
}
|
||||
Err(_) => {
|
||||
// Timeout — check if the Dart stream is still alive
|
||||
if sink
|
||||
.add(NostrPaymentEvent {
|
||||
state: NostrPaymentState::Waiting,
|
||||
amount: None,
|
||||
error: None,
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
// Dart side cancelled the stream, clean up
|
||||
client.disconnect().await;
|
||||
return Err(Error::Network("Listener cancelled by client".to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.disconnect().await;
|
||||
Err(Error::Network(
|
||||
"Nostr listener ended before a payment was received".to_string(),
|
||||
))
|
||||
}
|
||||
+25
-1
@@ -4,7 +4,10 @@ use std::{
|
||||
};
|
||||
|
||||
use bc_ur::{prelude::CBOR, MultipartDecoder, MultipartEncoder, UR};
|
||||
use cdk::nuts::Token as CdkToken;
|
||||
use cdk::{
|
||||
mint_url::MintUrl,
|
||||
nuts::{CurrencyUnit, Proof, Token as CdkToken},
|
||||
};
|
||||
use cdk_common::{KeySetInfo, Proofs};
|
||||
use flutter_rust_bridge::frb;
|
||||
|
||||
@@ -92,6 +95,27 @@ impl TryInto<CdkToken> for &Token {
|
||||
}
|
||||
}
|
||||
|
||||
// === Offline token creation ===
|
||||
|
||||
/// Create a token from raw proof data without network access.
|
||||
/// Uses CDK's CdkToken::new to produce a valid V4 (cashuB) token.
|
||||
/// `proofs_json` is a JSON array of proofs: [{"id":"...","amount":1,"secret":"...","C":"..."}, ...]
|
||||
#[frb(sync)]
|
||||
pub fn create_offline_token(
|
||||
mint_url: &str,
|
||||
proofs_json: &str,
|
||||
memo: Option<String>,
|
||||
unit: Option<String>,
|
||||
) -> Result<Token, Error> {
|
||||
let mint_url = MintUrl::from_str(mint_url)?;
|
||||
let unit = unit
|
||||
.map(|u| CurrencyUnit::from_str(&u).unwrap_or(CurrencyUnit::Custom(u)))
|
||||
.unwrap_or(CurrencyUnit::Sat);
|
||||
let proofs: Vec<Proof> = serde_json::from_str(proofs_json)?;
|
||||
let token = CdkToken::new(mint_url, proofs, memo, unit);
|
||||
Token::try_from(token)
|
||||
}
|
||||
|
||||
// === QR Multiframe (bc-ur) ===
|
||||
|
||||
pub struct TokenDecoder {
|
||||
|
||||
+334
-93
@@ -12,19 +12,21 @@ use cdk::{
|
||||
wallet::{
|
||||
MeltQuote as CdkMeltQuote, MintQuote as CdkMintQuote, PreparedSend as CdkPreparedSend,
|
||||
ReceiveOptions as CdkReceiveOptions, SendMemo, SendOptions as CdkSendOptions,
|
||||
Wallet as CdkWallet,
|
||||
Wallet as CdkWallet, WalletSubscription,
|
||||
},
|
||||
};
|
||||
use cdk_common::{
|
||||
util::unix_time,
|
||||
wallet::{
|
||||
Transaction as CdkTransaction, TransactionDirection as CdkTransactionDirection,
|
||||
TransactionId,
|
||||
},
|
||||
NotificationPayload,
|
||||
};
|
||||
use cdk_sqlite::WalletSqliteDatabase;
|
||||
use flutter_rust_bridge::frb;
|
||||
use log::info;
|
||||
use tokio::{sync::broadcast, time::sleep};
|
||||
use tokio::sync::broadcast;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::frb_generated::StreamSink;
|
||||
@@ -45,7 +47,7 @@ pub struct Wallet {
|
||||
pub unit: String,
|
||||
|
||||
balance_broadcast: broadcast::Sender<u64>,
|
||||
inner: CdkWallet,
|
||||
pub(crate) inner: CdkWallet,
|
||||
seed: [u8; 64],
|
||||
}
|
||||
|
||||
@@ -131,12 +133,12 @@ impl Wallet {
|
||||
send: PreparedSend,
|
||||
memo: Option<String>,
|
||||
include_memo: Option<bool>,
|
||||
) -> Result<Token, Error> {
|
||||
) -> Result<SendResult, Error> {
|
||||
let send_memo = memo.map(|m| SendMemo {
|
||||
memo: m,
|
||||
include_memo: include_memo.unwrap_or_default(),
|
||||
});
|
||||
let token = self
|
||||
let cdk_token = self
|
||||
.inner
|
||||
.confirm_send(
|
||||
send.operation_id,
|
||||
@@ -148,10 +150,35 @@ impl Wallet {
|
||||
send.cdk_send_fee,
|
||||
send_memo,
|
||||
)
|
||||
.await?
|
||||
.to_string();
|
||||
.await?;
|
||||
|
||||
// Compute the deterministic transaction ID from the token's proofs
|
||||
// (SHA-256 of sorted Y values — same as CDK uses internally)
|
||||
// Best-effort: send is already committed, don't fail on ID computation
|
||||
let tx_id = match self.inner.get_mint_keysets(cdk::wallet::KeysetFilter::Active).await {
|
||||
Ok(keysets) => match cdk_token.proofs(&keysets) {
|
||||
Ok(proofs) => TransactionId::try_from(proofs)
|
||||
.map(|id| id.to_string())
|
||||
.map_err(|e| info!("Failed to compute tx ID from proofs: {e}"))
|
||||
.ok(),
|
||||
Err(e) => {
|
||||
info!("Failed to extract proofs from token: {e}");
|
||||
None
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
info!("Failed to fetch keysets for tx ID: {e}");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let token_str = cdk_token.to_string();
|
||||
self.update_balance_streams().await;
|
||||
Ok(Token::from_str(&token)?)
|
||||
|
||||
Ok(SendResult {
|
||||
token: Token::from_str(&token_str)?,
|
||||
transaction_id: tx_id,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn cancel_send(&self, send: PreparedSend) -> Result<(), Error> {
|
||||
@@ -179,97 +206,198 @@ impl Wallet {
|
||||
.inner
|
||||
.mint_quote(PaymentMethod::BOLT11, Some(amount.into()), description, None)
|
||||
.await?;
|
||||
let _ = sink.add(MintQuote::from(quote.clone()));
|
||||
|
||||
if sink.add(MintQuote::from(quote.clone())).is_err() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Try WebSocket (NUT-17) subscription — if unavailable, HTTP polling still works
|
||||
let subscription = match self
|
||||
.inner
|
||||
.subscribe(WalletSubscription::Bolt11MintQuoteState(vec![quote.id.clone()]))
|
||||
.await
|
||||
{
|
||||
Ok(sub) => Some(sub),
|
||||
Err(e) => {
|
||||
info!(
|
||||
"Mint quote {}: WebSocket unavailable, using HTTP polling only: {e}",
|
||||
quote.id
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let _self = self.clone();
|
||||
flutter_rust_bridge::spawn(async move {
|
||||
loop {
|
||||
sleep(Duration::from_secs(3)).await;
|
||||
info!("Checking mint quote state for {}", quote.id);
|
||||
match _self.inner.check_mint_quote_status("e.id).await {
|
||||
Ok(state_res) => match state_res.state {
|
||||
CdkMintQuoteState::Unpaid => {
|
||||
if state_res.expiry < unix_time() {
|
||||
let _ = sink.add(MintQuote {
|
||||
id: quote.id,
|
||||
request: quote.request,
|
||||
amount: quote.amount.map(|a| a.into()),
|
||||
expiry: Some(state_res.expiry),
|
||||
state: MintQuoteState::Error,
|
||||
token: None,
|
||||
error: Some("Quote expired".to_string()),
|
||||
});
|
||||
break;
|
||||
// Timeout: time until quote expires + 30s buffer, or 1 hour if no expiry
|
||||
let remaining = quote.expiry.saturating_sub(unix_time());
|
||||
let timeout_dur = if quote.expiry == 0 {
|
||||
Duration::from_secs(3600)
|
||||
} else {
|
||||
Duration::from_secs(remaining + 30)
|
||||
};
|
||||
|
||||
// Clone for the timeout error path (originals are moved into the async block)
|
||||
let expired_id = quote.id.clone();
|
||||
let expired_request = quote.request.clone();
|
||||
let expired_amount = quote.amount;
|
||||
let expired_expiry = quote.expiry;
|
||||
|
||||
// Detect payment via two parallel paths:
|
||||
// - Path A: WebSocket (NUT-17) — fast when it works (sats on most mints)
|
||||
// - Path B: HTTP polling every 5s — fallback for mints that don't send
|
||||
// WebSocket notifications for all units (e.g. Nutshell 0.20.0 + USD)
|
||||
// First one to detect payment wins.
|
||||
let quote_id_for_poll = quote.id.clone();
|
||||
let poll_wallet = _self.clone();
|
||||
|
||||
// Timeout only covers detection, not minting.
|
||||
// If payment is detected near expiry, mint() must still complete.
|
||||
enum Detected {
|
||||
Paid,
|
||||
Issued,
|
||||
}
|
||||
|
||||
let mut subscription = subscription;
|
||||
let detected = tokio::time::timeout(timeout_dur, async {
|
||||
tokio::select! {
|
||||
// Path A: WebSocket subscription (skipped if unavailable)
|
||||
result = async {
|
||||
let Some(ref mut sub) = subscription else {
|
||||
return std::future::pending::<Detected>().await;
|
||||
};
|
||||
while let Some(event) = sub.recv().await {
|
||||
match event.into_inner() {
|
||||
NotificationPayload::MintQuoteBolt11Response(info)
|
||||
if info.state == CdkMintQuoteState::Paid =>
|
||||
{
|
||||
info!("Mint quote {} paid via WebSocket", quote.id);
|
||||
return Detected::Paid;
|
||||
}
|
||||
NotificationPayload::MintQuoteBolt11Response(info)
|
||||
if info.state == CdkMintQuoteState::Issued =>
|
||||
{
|
||||
return Detected::Issued;
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
continue;
|
||||
}
|
||||
CdkMintQuoteState::Issued => {
|
||||
break;
|
||||
std::future::pending::<Detected>().await
|
||||
} => result,
|
||||
|
||||
// Path B: HTTP polling fallback
|
||||
result = async {
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
match poll_wallet.inner.check_mint_quote_status("e_id_for_poll).await {
|
||||
Ok(q) if q.state == CdkMintQuoteState::Paid => {
|
||||
info!("Mint quote {} paid via HTTP polling", quote_id_for_poll);
|
||||
return Detected::Paid;
|
||||
}
|
||||
Ok(q) if q.state == CdkMintQuoteState::Issued => {
|
||||
return Detected::Issued;
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
CdkMintQuoteState::Paid => {
|
||||
} => result,
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
// Handle detection result — mint logic runs outside the timeout
|
||||
match detected {
|
||||
Err(_) => {
|
||||
// Timeout: quote expired
|
||||
let _ = sink.add(MintQuote {
|
||||
id: expired_id,
|
||||
request: expired_request,
|
||||
amount: expired_amount.map(|a| a.into()),
|
||||
expiry: Some(expired_expiry),
|
||||
state: MintQuoteState::Error,
|
||||
token: None,
|
||||
error: Some("Quote expired".to_string()),
|
||||
transaction_id: None,
|
||||
});
|
||||
}
|
||||
Ok(Detected::Paid) => {
|
||||
// Notify Dart: payment detected
|
||||
let _ = sink.add(MintQuote {
|
||||
id: quote.id.clone(),
|
||||
request: quote.request.clone(),
|
||||
amount: quote.amount.map(|a| a.into()),
|
||||
expiry: Some(quote.expiry),
|
||||
state: CdkMintQuoteState::Paid.into(),
|
||||
token: None,
|
||||
error: None,
|
||||
transaction_id: None,
|
||||
});
|
||||
|
||||
// Mint the ecash tokens (outside timeout)
|
||||
match _self
|
||||
.inner
|
||||
.mint("e.id, SplitTarget::None, None)
|
||||
.await
|
||||
{
|
||||
Ok(mint_proofs) => {
|
||||
let tx_id = match TransactionId::try_from(
|
||||
mint_proofs.clone(),
|
||||
) {
|
||||
Ok(id) => Some(id.to_string()),
|
||||
Err(e) => {
|
||||
info!("Failed to compute mint tx ID: {e}");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let mint_amount =
|
||||
mint_proofs.total_amount().unwrap_or_default();
|
||||
let _ = sink.add(MintQuote {
|
||||
id: quote.id.clone(),
|
||||
request: quote.request.clone(),
|
||||
id: quote.id,
|
||||
request: quote.request,
|
||||
amount: Some(mint_amount.into()),
|
||||
expiry: Some(quote.expiry),
|
||||
state: CdkMintQuoteState::Issued.into(),
|
||||
token: Token::try_from(CdkToken::new(
|
||||
mint_url,
|
||||
mint_proofs,
|
||||
None,
|
||||
unit,
|
||||
))
|
||||
.ok(),
|
||||
error: None,
|
||||
transaction_id: tx_id,
|
||||
});
|
||||
_self.update_balance_streams().await;
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = sink.add(MintQuote {
|
||||
id: quote.id,
|
||||
request: quote.request,
|
||||
amount: quote.amount.map(|a| a.into()),
|
||||
expiry: Some(quote.expiry),
|
||||
state: CdkMintQuoteState::Paid.into(),
|
||||
state: MintQuoteState::Error,
|
||||
token: None,
|
||||
error: None,
|
||||
error: Some(e.to_string()),
|
||||
transaction_id: None,
|
||||
});
|
||||
match _self
|
||||
.inner
|
||||
.mint("e.id, SplitTarget::None, None)
|
||||
.await
|
||||
{
|
||||
Ok(mint_proofs) => {
|
||||
let mint_amount =
|
||||
mint_proofs.total_amount().unwrap_or_default();
|
||||
let _ = sink.add(MintQuote {
|
||||
id: quote.id,
|
||||
request: quote.request,
|
||||
amount: Some(mint_amount.into()),
|
||||
expiry: Some(quote.expiry),
|
||||
state: CdkMintQuoteState::Issued.into(),
|
||||
token: Token::try_from(CdkToken::new(
|
||||
mint_url,
|
||||
mint_proofs,
|
||||
None,
|
||||
unit,
|
||||
))
|
||||
.ok(),
|
||||
error: None,
|
||||
});
|
||||
_self.update_balance_streams().await;
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = sink.add(MintQuote {
|
||||
id: quote.id,
|
||||
request: quote.request,
|
||||
amount: quote.amount.map(|a| a.into()),
|
||||
expiry: Some(quote.expiry),
|
||||
state: MintQuoteState::Error,
|
||||
token: None,
|
||||
error: Some(e.to_string()),
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
let _ = sink.add(MintQuote {
|
||||
id: quote.id,
|
||||
request: quote.request,
|
||||
amount: quote.amount.map(|a| a.into()),
|
||||
expiry: Some(quote.expiry),
|
||||
state: MintQuoteState::Error,
|
||||
token: None,
|
||||
error: Some(e.to_string()),
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(Detected::Issued) => {
|
||||
// Already issued (recovered from previous session)
|
||||
let _ = sink.add(MintQuote {
|
||||
id: quote.id.clone(),
|
||||
request: quote.request.clone(),
|
||||
amount: quote.amount.map(|a| a.into()),
|
||||
expiry: Some(quote.expiry),
|
||||
state: CdkMintQuoteState::Issued.into(),
|
||||
token: None,
|
||||
error: None,
|
||||
transaction_id: None,
|
||||
});
|
||||
_self.update_balance_streams().await;
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
@@ -293,15 +421,52 @@ impl Wallet {
|
||||
.into())
|
||||
}
|
||||
|
||||
pub async fn melt(&self, quote: MeltQuote) -> Result<u64, Error> {
|
||||
let melted = self
|
||||
pub async fn prepare_melt(&self, quote: MeltQuote) -> Result<PreparedMelt, Error> {
|
||||
let prepared = self
|
||||
.inner
|
||||
.prepare_melt("e.id, HashMap::new())
|
||||
.await?
|
||||
.confirm()
|
||||
.await?;
|
||||
Ok(PreparedMelt {
|
||||
amount: prepared.amount().into(),
|
||||
fee_reserve: prepared.quote().fee_reserve.into(),
|
||||
swap_fee: prepared.swap_fee().into(),
|
||||
input_fee: prepared.input_fee().into(),
|
||||
operation_id: prepared.operation_id(),
|
||||
cdk_quote: prepared.quote().clone(),
|
||||
proofs: prepared.proofs().clone(),
|
||||
proofs_to_swap: prepared.proofs_to_swap().clone(),
|
||||
cdk_input_fee: prepared.input_fee(),
|
||||
cdk_input_fee_without_swap: prepared.input_fee_without_swap(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn confirm_melt(&self, melt: PreparedMelt) -> Result<u64, Error> {
|
||||
let finalized = self
|
||||
.inner
|
||||
.confirm_prepared_melt(
|
||||
melt.operation_id,
|
||||
melt.cdk_quote,
|
||||
melt.proofs,
|
||||
melt.proofs_to_swap,
|
||||
melt.cdk_input_fee,
|
||||
melt.cdk_input_fee_without_swap,
|
||||
HashMap::new(),
|
||||
)
|
||||
.await?;
|
||||
self.update_balance_streams().await;
|
||||
Ok(melted.total_amount().into())
|
||||
Ok(finalized.total_amount().into())
|
||||
}
|
||||
|
||||
pub async fn cancel_melt(&self, melt: PreparedMelt) -> Result<(), Error> {
|
||||
self.inner
|
||||
.cancel_prepared_melt(
|
||||
melt.operation_id,
|
||||
melt.proofs,
|
||||
melt.proofs_to_swap,
|
||||
)
|
||||
.await?;
|
||||
self.update_balance_streams().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// === Transactions ===
|
||||
@@ -363,11 +528,56 @@ impl Wallet {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// === Reclaim orphaned proofs ===
|
||||
|
||||
/// Check pending-spent proofs with the mint and revert unspent ones.
|
||||
/// Returns count and total amount of proofs recovered.
|
||||
pub async fn reclaim_pending_proofs(&self) -> Result<ReclaimResult, Error> {
|
||||
let pending = self.inner.get_pending_spent_proofs().await?;
|
||||
if pending.is_empty() {
|
||||
return Ok(ReclaimResult { count: 0, amount: 0 });
|
||||
}
|
||||
|
||||
// Build a map of Y → amount for later lookup
|
||||
let mut amount_by_y: HashMap<PublicKey, u64> = HashMap::new();
|
||||
for proof in &pending {
|
||||
if let Ok(y) = proof.y() {
|
||||
amount_by_y.insert(y, u64::from(proof.amount));
|
||||
}
|
||||
}
|
||||
|
||||
// check_proofs_spent: queries mint AND removes spent proofs from local DB
|
||||
let states = self.inner.check_proofs_spent(pending).await?;
|
||||
|
||||
// Collect Y values of proofs the mint says are NOT spent
|
||||
// Only reclaim proofs the mint explicitly reports as Unspent.
|
||||
// Pending proofs (still being processed) must not be unreserved.
|
||||
let unspent_ys: Vec<PublicKey> = states
|
||||
.into_iter()
|
||||
.filter(|s| s.state == ProofState::Unspent)
|
||||
.map(|s| s.y)
|
||||
.collect();
|
||||
|
||||
let count = unspent_ys.len() as u64;
|
||||
let amount: u64 = unspent_ys.iter()
|
||||
.filter_map(|y| amount_by_y.get(y))
|
||||
.sum();
|
||||
|
||||
if count > 0 {
|
||||
// Revert from PendingSpent to Unspent
|
||||
self.inner.unreserve_proofs(unspent_ys).await?;
|
||||
}
|
||||
|
||||
// Always refresh: check_proofs_spent may have removed spent proofs
|
||||
self.update_balance_streams().await;
|
||||
Ok(ReclaimResult { count, amount })
|
||||
}
|
||||
|
||||
// === Utility ===
|
||||
|
||||
pub async fn is_token_spent(&self, token: Token) -> Result<bool, Error> {
|
||||
let token: CdkToken = token.try_into()?;
|
||||
let mint_keysets = self.inner.get_mint_keysets().await?;
|
||||
let mint_keysets = self.inner.get_mint_keysets(cdk::wallet::KeysetFilter::All).await?;
|
||||
let proof_states = self
|
||||
.inner
|
||||
.check_proofs_spent(token.proofs(&mint_keysets)?)
|
||||
@@ -383,7 +593,7 @@ impl Wallet {
|
||||
|
||||
// === Internal helpers ===
|
||||
|
||||
fn mint_url(&self) -> Result<MintUrl, Error> {
|
||||
pub(crate) fn mint_url(&self) -> Result<MintUrl, Error> {
|
||||
Ok(MintUrl::from_str(&self.mint_url)?)
|
||||
}
|
||||
|
||||
@@ -391,7 +601,7 @@ impl Wallet {
|
||||
CurrencyUnit::from_str(&self.unit).unwrap_or(CurrencyUnit::Custom(self.unit.clone()))
|
||||
}
|
||||
|
||||
async fn update_balance_streams(&self) {
|
||||
pub(crate) async fn update_balance_streams(&self) {
|
||||
let balance = self
|
||||
.inner
|
||||
.total_balance()
|
||||
@@ -406,6 +616,15 @@ impl Wallet {
|
||||
// Types
|
||||
// ========================================================================
|
||||
|
||||
/// Result of a confirmed send, carrying both the ecash token and the
|
||||
/// deterministic transaction ID so Dart can save metadata without a racy
|
||||
/// listTransactions lookup.
|
||||
pub struct SendResult {
|
||||
pub token: Token,
|
||||
/// Deterministic transaction ID (None if computation failed)
|
||||
pub transaction_id: Option<String>,
|
||||
}
|
||||
|
||||
pub struct MintQuote {
|
||||
pub id: String,
|
||||
pub request: String,
|
||||
@@ -414,6 +633,8 @@ pub struct MintQuote {
|
||||
pub state: MintQuoteState,
|
||||
pub token: Option<Token>,
|
||||
pub error: Option<String>,
|
||||
/// Deterministic transaction ID (set when proofs are available, typically on Issued)
|
||||
pub transaction_id: Option<String>,
|
||||
}
|
||||
|
||||
impl From<CdkMintQuote> for MintQuote {
|
||||
@@ -426,6 +647,7 @@ impl From<CdkMintQuote> for MintQuote {
|
||||
state: quote.state.into(),
|
||||
token: None,
|
||||
error: None,
|
||||
transaction_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -504,6 +726,25 @@ impl<'a> From<CdkPreparedSend<'a>> for PreparedSend {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReclaimResult {
|
||||
pub count: u64,
|
||||
pub amount: u64,
|
||||
}
|
||||
|
||||
pub struct PreparedMelt {
|
||||
pub amount: u64,
|
||||
pub fee_reserve: u64,
|
||||
pub swap_fee: u64,
|
||||
pub input_fee: u64,
|
||||
|
||||
operation_id: Uuid,
|
||||
cdk_quote: CdkMeltQuote,
|
||||
proofs: cdk::nuts::Proofs,
|
||||
proofs_to_swap: cdk::nuts::Proofs,
|
||||
cdk_input_fee: Amount,
|
||||
cdk_input_fee_without_swap: Amount,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ReceiveOptions {
|
||||
pub signing_keys: Option<Vec<String>>,
|
||||
|
||||
+2067
-165
File diff suppressed because it is too large
Load Diff
@@ -89,11 +89,11 @@ BEGIN
|
||||
BEGIN
|
||||
BLOCK "040904e4"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "com.example" "\0"
|
||||
VALUE "CompanyName", "ElCaju" "\0"
|
||||
VALUE "FileDescription", "elcaju" "\0"
|
||||
VALUE "FileVersion", VERSION_AS_STRING "\0"
|
||||
VALUE "InternalName", "elcaju" "\0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2026 ElCaju. All rights reserved." "\0"
|
||||
VALUE "OriginalFilename", "elcaju.exe" "\0"
|
||||
VALUE "ProductName", "elcaju" "\0"
|
||||
VALUE "ProductVersion", VERSION_AS_STRING "\0"
|
||||
|
||||
Reference in New Issue
Block a user