diff --git a/AGENTS.md b/AGENTS.md index 8e12b34..3b362cf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,3 +8,13 @@ - Never publish without explicit permission. Before any publish action — running `bun run build:stable`, uploading a release asset to Gitea (`https://gitea.tzgyn.dev/TZGyn/koins`), updating the Homebrew cask, or pushing the tap repo (`https://gitea.tzgyn.dev/TZGyn/homebrew-tap`) — describe the intended action (what will be built, what SHA, which release) and ask for permission. Once you have asked and received a clear affirmative (any clear yes, "go", "publish", etc.), you may proceed through the whole pipeline without pausing between steps: build, notarize, verify, upload to the Gitea release, update the Homebrew cask, and push the Gitea tap repo. One permission covers the entire publish flow. You must still get a separate permission before starting each new publish cycle. - Permission to commit ("commit", "commit and push", or similar) authorizes `git commit` and also `git push` of the committed changes. Only push the branches/files described in the permission. - At the end of every response, always surface a "Pending from you" section listing anything you need from the user (decisions, permissions, credentials, confirming a plan, etc.). If nothing is pending, say "Nothing pending." List each pending item as a numbered entry (`1.`, `2.`, …) so the user can approve or act on them by number (e.g. "go ahead with 1, 2"). + +## iOS app (`apps/ios`) + +Native SwiftUI wallet for Monero and XRP — no JavaScript runtime, no CocoaPods, no Swift packages. + +- Regenerate the project after adding or removing files: `xcodegen generate` (from `apps/ios`). +- Simulator build: `xcodebuild -project Koins.xcodeproj -scheme Koins -configuration Debug -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' ARCHS=arm64 ONLY_ACTIVE_ARCH=NO -derivedDataPath /tmp/koins-dd build`. +- Crypto/serialization self-test: launch with `-koinsSelfTest`, then read `log show --predicate 'subsystem == "com.koins.app"'`. +- Live network self-test: launch with `-koinsSelfTestNetwork`. +- See `apps/ios/README.md` for the full layout and verification workflow. diff --git a/apps/ios/.gitignore b/apps/ios/.gitignore new file mode 100644 index 0000000..187a65e --- /dev/null +++ b/apps/ios/.gitignore @@ -0,0 +1,9 @@ +# Generated by XcodeGen from project.yml +Koins.xcodeproj/ + +# Build output +build/ +DerivedData/ +*.xcworkspace +xcuserdata/ +*.xcuserstate \ No newline at end of file diff --git a/apps/ios/Koins/AppModel.swift b/apps/ios/Koins/AppModel.swift new file mode 100644 index 0000000..749a7c9 --- /dev/null +++ b/apps/ios/Koins/AppModel.swift @@ -0,0 +1,337 @@ +import Foundation +import SwiftUI + +@MainActor +final class AppModel: ObservableObject { + @Published var chain: Chain = .xrp + @Published var xrpWallet: WalletRecord? + @Published var xmrWallet: WalletRecord? + @Published var balance = Balance(total: "0", unlocked: "", funded: false) + @Published var transactions: [TransactionRecord] = [] + @Published var syncStatus: SyncStatus? + @Published var isBusy = false + @Published var errorMessage: String? + @Published var statusMessage: String? + @Published var moneroReady = false + @Published var moneroAddress: String? + /// Rough time remaining for the Monero sync, derived from observed block rate. + @Published var syncEta: String? + + private var moneroOpened = false + private var refreshTick = 0 + private var refreshTask: Task? + private var lastSample: (height: UInt64, date: Date)? + + var wallet: WalletRecord? { + chain == .xrp ? xrpWallet : xmrWallet + } + + var hasWallet: Bool { wallet != nil } + + /// Address for the chain currently on screen. Kept chain-aware because the + /// Monero address stays cached once its wallet is open. + var displayAddress: String { + chain == .xrp ? (xrpWallet?.address ?? "") : (moneroAddress ?? xmrWallet?.address ?? "") + } + + var symbol: String { chain.symbol } + + // MARK: - Bootstrap + + func bootstrap() async { + xrpWallet = WalletStore.wallet(.xrp) + xmrWallet = WalletStore.wallet(.xmr) + if xmrWallet == nil, let orphan = MoneroService.orphanedWalletNames().first { + // Wallet file on disk with no stored record (for example after the + // preferences were lost): re-attach it so funds are not stranded. + await adoptMoneroWallet(named: orphan) + } + if let xmrWallet { + await openMonero(afterRestore: false, wallet: xmrWallet) + } + if xrpWallet != nil || moneroReady { + chain = xrpWallet != nil ? .xrp : .xmr + } + await refresh(force: true) + startAutoRefresh() + } + + private func adoptMoneroWallet(named name: String) async { + do { + let address = try await MoneroService.shared.openWallet(name: name) + let record = WalletRecord(id: "xmr_\(Int(Date().timeIntervalSince1970))", + name: name, + address: address, + createdAt: Date()) + xmrWallet = record + WalletStore.saveWallet(record, chain: .xmr) + moneroOpened = true + moneroReady = true + moneroAddress = address + statusMessage = "Recovered the existing Monero wallet “\(name)” from this device." + } catch { + errorMessage = "Found a Monero wallet file but could not open it: \(error.localizedDescription)" + } + } + + /// Polls the wallet while the app is in the foreground. Monero sync progress + /// changes constantly, so it is refreshed far more often than XRP. + func startAutoRefresh() { + guard refreshTask == nil else { return } + refreshTask = Task { [weak self] in + while !Task.isCancelled { + guard let self else { return } + let seconds: UInt64 = self.chain == .xmr ? 5 : 30 + try? await Task.sleep(nanoseconds: seconds * 1_000_000_000) + if Task.isCancelled { return } + await self.refresh() + } + } + } + + func select(_ newChain: Chain) async { + chain = newChain + errorMessage = nil + statusMessage = nil + balance = Balance(total: "0", unlocked: "", funded: false) + transactions = [] + syncStatus = nil + + if newChain == .xmr, let xmrWallet, !moneroOpened { + await openMonero(afterRestore: false, wallet: xmrWallet) + } + await refresh(force: true) + } + + // MARK: - Monero lifecycle + + private func openMonero(afterRestore: Bool, wallet: WalletRecord) async { + isBusy = true + defer { isBusy = false } + do { + let address = try await MoneroService.shared.openWallet(name: wallet.name) + moneroOpened = true + moneroReady = true + moneroAddress = address.isEmpty ? wallet.address : address + if let address = moneroAddress, address != wallet.address { + var updated = wallet + updated.address = address + xmrWallet = updated + WalletStore.saveWallet(updated, chain: .xmr) + } + // Always try to catch up, not only right after a restore. + _ = try? await MoneroService.shared.startSync() + } catch { + // Keep the stored wallet so a transient daemon failure is not fatal. + moneroOpened = false + moneroReady = false + moneroAddress = nil + errorMessage = "Monero wallet could not be opened: \(error.localizedDescription)" + } + } + + // MARK: - Create / import + + func createWallet(name: String) async throws -> String { + let trimmed = name.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { throw XRPError.rpc("Enter a wallet name") } + isBusy = true + defer { isBusy = false } + + if chain == .xrp { + let (record, seed) = try XRPWalletService.shared.createWallet(name: trimmed) + xrpWallet = record + await refresh() + return seed + } + + let result = try await MoneroService.shared.createWallet(name: trimmed) + let record = WalletRecord(id: "xmr_\(Int(Date().timeIntervalSince1970))", + name: trimmed, + address: result.address, + createdAt: Date()) + xmrWallet = record + WalletStore.saveWallet(record, chain: .xmr) + if !result.mnemonic.isEmpty { + WalletStore.saveSecret(result.mnemonic, chain: .xmr) + } + moneroOpened = true + moneroReady = true + moneroAddress = result.address + _ = try? await MoneroService.shared.startSync() + await refresh(force: true) + return result.mnemonic + } + + func importWallet(name: String, secret: String, restoreHeight: UInt64?) async throws { + let trimmedName = name.trimmingCharacters(in: .whitespacesAndNewlines) + let trimmedSecret = secret.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmedName.isEmpty else { throw XRPError.rpc("Enter a wallet name") } + guard !trimmedSecret.isEmpty else { throw XRPError.rpc("Enter your seed or secret") } + isBusy = true + defer { isBusy = false } + + if chain == .xrp { + xrpWallet = try XRPWalletService.shared.importWallet(name: trimmedName, secret: trimmedSecret) + await refresh(force: true) + return + } + + let address = try await MoneroService.shared.restoreWallet(name: trimmedName, + seed: trimmedSecret, + restoreHeight: restoreHeight ?? 0) + let record = WalletRecord(id: "xmr_\(Int(Date().timeIntervalSince1970))", + name: trimmedName, + address: address, + createdAt: Date()) + xmrWallet = record + WalletStore.saveWallet(record, chain: .xmr) + WalletStore.saveSecret(trimmedSecret, chain: .xmr) + moneroOpened = true + moneroReady = true + moneroAddress = address + _ = try? await MoneroService.shared.startSync() + await refresh(force: true) + } + + // MARK: - Reads + + func refresh(force: Bool = false) async { + guard hasWallet else { return } + errorMessage = nil + if chain == .xrp, let wallet = xrpWallet { + do { + async let balanceCall = XRPWalletService.shared.balance(address: wallet.address) + async let txCall = XRPWalletService.shared.transactions(address: wallet.address) + balance = try await balanceCall + transactions = try await txCall + } catch { + errorMessage = error.localizedDescription + } + // The Monero wallet keeps scanning while XRP is on screen, so keep its + // progress current here too. + if moneroReady, let status = try? await MoneroService.shared.syncStatus() { + updateSyncStatus(status) + } + return + } + + guard moneroReady else { return } + do { + // Sync progress is cheap and polled every tick; the balance and transfer + // list touch the wallet's data files, so they are refreshed less often. + updateSyncStatus(try await MoneroService.shared.syncStatus()) + refreshTick += 1 + if force || refreshTick == 1 || refreshTick % 4 == 0 { + async let balanceCall = MoneroService.shared.balance() + async let txCall = MoneroService.shared.transactions() + balance = try await balanceCall + transactions = try await txCall + } + } catch { + errorMessage = error.localizedDescription + } + } + + /// Tracks how fast blocks are being scanned so the UI can show an estimate. + private func updateSyncStatus(_ status: SyncStatus) { + syncStatus = status + let now = Date() + defer { lastSample = (status.height, now) } + + guard status.syncing, + let sample = lastSample, + status.height > sample.height, + status.daemonHeight > status.height else { + if !status.syncing { syncEta = nil } + return + } + + let elapsed = now.timeIntervalSince(sample.date) + guard elapsed > 1 else { return } + let blocksPerSecond = Double(status.height - sample.height) / elapsed + guard blocksPerSecond > 0 else { return } + + let remaining = Double(status.daemonHeight - status.height) / blocksPerSecond + syncEta = Self.formatDuration(remaining) + } + + private static func formatDuration(_ seconds: Double) -> String { + guard seconds.isFinite, seconds > 0 else { return "unknown" } + let total = Int(seconds.rounded()) + let hours = total / 3600 + let minutes = (total % 3600) / 60 + if hours >= 24 { return "\(hours / 24)d \(hours % 24)h" } + if hours > 0 { return "\(hours)h \(minutes)m" } + if minutes > 0 { return "\(minutes)m" } + return "under a minute" + } + + func startMoneroSync() async { + guard moneroReady else { + if let wallet = xmrWallet { await openMonero(afterRestore: true, wallet: wallet) } + return + } + _ = try? await MoneroService.shared.startSync() + await refresh(force: true) + } + + func revealSeed() async throws -> String { + if chain == .xrp { + guard let seed = WalletStore.secret(.xrp) else { + throw XRPError.rpc("No stored secret for this wallet") + } + return seed + } + if let stored = WalletStore.secret(.xmr), !stored.isEmpty { return stored } + return try await MoneroService.shared.seed() + } + + // MARK: - Sends + + func send(to: String, amount: String, tag: String, priority: TransactionPriority) async throws { + let destinationTag = UInt32(tag.trimmingCharacters(in: .whitespaces)) + if chain == .xrp { + guard let seed = WalletStore.secret(.xrp) else { + throw XRPError.rpc("Wallet secret is unavailable") + } + statusMessage = nil + _ = try await XRPWalletService.shared.send(secret: seed, + to: to, + amountXRP: amount, + destinationTag: destinationTag) + statusMessage = "Payment sent" + await refresh(force: true) + return + } + _ = try await MoneroService.shared.send(address: to, amountXMR: amount, priority: priority) + statusMessage = "Payment sent" + await refresh(force: true) + } + + // MARK: - Delete + + func deleteWallet() async { + if chain == .xrp { + WalletStore.removeWallet(.xrp) + xrpWallet = nil + } else { + if let wallet = xmrWallet { await MoneroService.shared.removeWallet(name: wallet.name) } + WalletStore.removeWallet(.xmr) + xmrWallet = nil + moneroOpened = false + moneroReady = false + moneroAddress = nil + syncStatus = nil + } + balance = Balance(total: "0", unlocked: "", funded: false) + transactions = [] + errorMessage = nil + statusMessage = nil + } + + func clearMessages() { + errorMessage = nil + statusMessage = nil + } +} \ No newline at end of file diff --git a/apps/ios/Koins/Bridge/Koins-Bridging-Header.h b/apps/ios/Koins/Bridge/Koins-Bridging-Header.h new file mode 100644 index 0000000..d7acc3c --- /dev/null +++ b/apps/ios/Koins/Bridge/Koins-Bridging-Header.h @@ -0,0 +1,2 @@ +#import "KoinsMoneroWallet.h" +#import "KoinsSecp256k1.h" \ No newline at end of file diff --git a/apps/ios/Koins/Bridge/KoinsMoneroWallet.h b/apps/ios/Koins/Bridge/KoinsMoneroWallet.h new file mode 100644 index 0000000..4c11e42 --- /dev/null +++ b/apps/ios/Koins/Bridge/KoinsMoneroWallet.h @@ -0,0 +1,16 @@ +#import +#import "KoinsSecp256k1.h" + +NS_ASSUME_NONNULL_BEGIN + +/// Objective-C++ bridge to the on-device monero-cpp wallet. A single JSON +/// in/out entry point keeps the C++ surface out of Swift; work is serialized +/// on a private queue because a monero_wallet_full is not thread safe. +@interface KoinsMoneroWallet : NSObject + ++ (void)invoke:(NSString *)jsonRequest + completion:(void (^)(NSString *response))completion; + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/apps/ios/Koins/Bridge/KoinsMoneroWallet.mm b/apps/ios/Koins/Bridge/KoinsMoneroWallet.mm new file mode 100644 index 0000000..3e58d0b --- /dev/null +++ b/apps/ios/Koins/Bridge/KoinsMoneroWallet.mm @@ -0,0 +1,276 @@ +#import "KoinsMoneroWallet.h" + +#import + +#include "monero_wallet_full.h" + +static std::string walletPath(NSString *name) { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *dir = paths.firstObject ?: NSTemporaryDirectory(); + NSFileManager *fm = [NSFileManager defaultManager]; + if (![fm fileExistsAtPath:dir]) { + [fm createDirectoryAtPath:dir withIntermediateDirectories:YES attributes:nil error:nil]; + } + return std::string([dir stringByAppendingPathComponent:name].UTF8String); +} + +static monero_wallet_full *g_wallet = nullptr; +static NSString *g_daemonURL = @"xmr-node.cakewallet.com:18081"; +static NSArray *g_fallbackDaemons = nil; +static std::atomic s_cachedConnected(false); +static std::atomic s_cachedDaemonHeight(0); +static std::atomic s_lastDaemonProbe(0); + +static void connectDaemon(monero_wallet_full *w) { + if (!w) return; + @autoreleasepool { + if (!g_fallbackDaemons) { + g_fallbackDaemons = @[ + @"xmr-node.cakewallet.com:18081", + @"node.monerodevs.org:18089", + @"monero.stackwallet.com:18081", + ]; + } + NSMutableArray *candidates = [NSMutableArray arrayWithObject:g_daemonURL]; + for (NSString *url in g_fallbackDaemons) { + if (![url isEqualToString:g_daemonURL]) [candidates addObject:url]; + } + for (NSString *url in candidates) { + try { + w->set_daemon_connection(std::string(url.UTF8String)); + if (w->is_connected_to_daemon()) { + g_daemonURL = url; + return; + } + } catch (...) { + // Try the next daemon. + } + } + } +} + +@implementation KoinsMoneroWallet + ++ (void)invoke:(NSString *)jsonRequest + completion:(void (^)(NSString *response))completion { + static dispatch_queue_t invokeQueue; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + invokeQueue = dispatch_queue_create("com.koins.monero.invoke", DISPATCH_QUEUE_SERIAL); + }); + + dispatch_async(invokeQueue, ^{ + NSMutableDictionary *resp = [NSMutableDictionary new]; + try { + NSData *data = [jsonRequest dataUsingEncoding:NSUTF8StringEncoding]; + NSDictionary *req = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + NSString *op = req[@"op"] ?: @""; + resp[@"op"] = op; + + if ([op isEqualToString:@"setDaemon"]) { + NSString *url = req[@"url"]; + if (url.length > 0) g_daemonURL = url; + resp[@"url"] = g_daemonURL; + } else if ([op isEqualToString:@"createWallet"]) { + NSString *name = req[@"name"]; + std::string path = walletPath(name); + std::string password = std::string([req[@"password"] UTF8String]); + bool exists = false; + try { exists = monero_wallet_full::wallet_exists(path); } catch (...) {} + if (exists) { + g_wallet = monero_wallet_full::open_wallet(path, password, monero_network_type::MAINNET, false); + connectDaemon(g_wallet); + resp[@"address"] = [NSString stringWithUTF8String:g_wallet->get_address(0, 0).c_str()]; + try { resp[@"mnemonic"] = [NSString stringWithUTF8String:g_wallet->get_seed().c_str()]; } catch (...) {} + } else { + monero_wallet_config config; + config.m_path = path; + config.m_password = password; + config.m_network_type = monero_network_type::MAINNET; + config.m_language = std::string([req[@"language"] ?: @"English" UTF8String]); + config.m_save_current = false; + monero_wallet_full *w = monero_wallet_full::create_wallet(config, nullptr); + g_wallet = w; + connectDaemon(w); + resp[@"address"] = [NSString stringWithUTF8String:w->get_address(0, 0).c_str()]; + resp[@"mnemonic"] = [NSString stringWithUTF8String:w->get_seed().c_str()]; + } + } else if ([op isEqualToString:@"openWallet"] || [op isEqualToString:@"restoreWallet"]) { + if (g_wallet) { + try { g_wallet->close(true); } catch (...) {} + delete g_wallet; + g_wallet = nullptr; + } + if ([op isEqualToString:@"openWallet"]) { + g_wallet = monero_wallet_full::open_wallet( + walletPath(req[@"name"]), + std::string([req[@"password"] UTF8String]), + monero_network_type::MAINNET, false); + } else { + monero_wallet_config config; + config.m_path = walletPath(req[@"name"]); + config.m_password = std::string([req[@"password"] UTF8String]); + config.m_network_type = monero_network_type::MAINNET; + config.m_seed = std::string([req[@"seed"] UTF8String]); + config.m_restore_height = (uint64_t)[req[@"restoreHeight"] longLongValue]; + config.m_language = "English"; + config.m_save_current = false; + g_wallet = monero_wallet_full::create_wallet(config, nullptr); + } + connectDaemon(g_wallet); + resp[@"address"] = [NSString stringWithUTF8String:g_wallet->get_address(0, 0).c_str()]; + } else if ([op isEqualToString:@"getSeed"]) { + if (g_wallet) { + resp[@"mnemonic"] = [NSString stringWithUTF8String:g_wallet->get_seed().c_str()]; + } else { + resp[@"error"] = @"No wallet open"; + } + } else if ([op isEqualToString:@"removeWallet"]) { + NSString *name = req[@"name"]; + if (g_wallet) { + try { g_wallet->close(false); } catch (...) {} + delete g_wallet; + g_wallet = nullptr; + } + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *dir = paths.firstObject; + NSFileManager *fm = [NSFileManager defaultManager]; + for (NSString *ext in @[ @"", @".keys", @".address.txt", @".mms" ]) { + NSString *file = [dir stringByAppendingPathComponent:[name stringByAppendingString:ext]]; + [fm removeItemAtPath:file error:nil]; + } + } else if ([op isEqualToString:@"closeWallet"]) { + if (g_wallet) { + try { g_wallet->close(true); } catch (...) {} + delete g_wallet; + g_wallet = nullptr; + } + } else if ([op isEqualToString:@"getBalance"]) { + if (g_wallet) { + uint64_t balance = 0, unlocked = 0; + try { + balance = g_wallet->get_balance(); + unlocked = g_wallet->get_unlocked_balance(); + } catch (const std::exception &e) { + os_log_error(OS_LOG_DEFAULT, "[koins] getBalance: %{public}s", e.what()); + } + resp[@"balance"] = @(balance).stringValue; + resp[@"unlocked"] = @(unlocked).stringValue; + } else { + resp[@"error"] = @"No wallet open"; + } + } else if ([op isEqualToString:@"getAddress"]) { + if (g_wallet) { + resp[@"address"] = [NSString stringWithUTF8String:g_wallet->get_address(0, 0).c_str()]; + } else { + resp[@"error"] = @"No wallet open"; + } + } else if ([op isEqualToString:@"getTransactions"]) { + NSMutableArray *entries = [NSMutableArray new]; + if (!g_wallet) { + resp[@"transactions"] = entries; + } else { + try { + monero_transfer_query query; + for (const auto &transfer : g_wallet->get_transfers(query)) { + const auto &tx = transfer->m_tx; + const auto isIn = transfer->is_incoming(); + boost::optional txHeight = tx ? tx->get_height() : boost::none; + [entries addObject:@{ + @"hash" : [NSString stringWithUTF8String:((tx && tx->m_hash) ? *tx->m_hash : "").c_str()], + @"amount" : @((transfer->m_amount ? *transfer->m_amount : 0)).stringValue, + @"timestamp" : @((tx && tx->m_received_timestamp) ? *tx->m_received_timestamp : 0), + @"direction" : (isIn && *isIn) ? @"in" : @"out", + @"confirmed" : @((tx && tx->m_is_confirmed) ? *tx->m_is_confirmed : false), + @"height" : @(txHeight ? *txHeight : 0), + @"fee" : @((tx && tx->m_fee) ? *tx->m_fee : 0).stringValue, + }]; + } + } catch (const std::exception &e) { + os_log_error(OS_LOG_DEFAULT, "[koins] getTransactions: %{public}s", e.what()); + } + resp[@"transactions"] = entries; + } + } else if ([op isEqualToString:@"startSync"]) { + if (g_wallet) { + connectDaemon(g_wallet); + try { + if (g_wallet->is_connected_to_daemon()) { + g_wallet->start_syncing(5000); + resp[@"syncing"] = @YES; + } else { + resp[@"syncing"] = @NO; + } + } catch (const std::exception &e) { + resp[@"syncing"] = @NO; + } + } else { + resp[@"error"] = @"No wallet open"; + } + } else if ([op isEqualToString:@"stopSync"]) { + try { if (g_wallet) g_wallet->stop_syncing(); } catch (...) {} + } else if ([op isEqualToString:@"getSyncStatus"]) { + uint64_t height = 0, daemonHeight = 0; + bool connected = false; + if (g_wallet) { + try { + height = g_wallet->get_height(); + // is_connected_to_daemon performs a live round trip; public + // daemons rate limit aggressive polling, so cache for 10s. + long long now = (long long)([[NSDate date] timeIntervalSince1970] * 1000); + if (now - s_lastDaemonProbe.load() > 10000) { + s_lastDaemonProbe.store(now); + connected = g_wallet->is_connected_to_daemon(); + s_cachedConnected = connected; + } else { + connected = s_cachedConnected; + } + daemonHeight = s_cachedDaemonHeight; + if (connected) { + daemonHeight = g_wallet->get_daemon_height(); + s_cachedDaemonHeight = daemonHeight; + } + } catch (const std::exception &e) { + os_log_error(OS_LOG_DEFAULT, "[koins] getSyncStatus: %{public}s", e.what()); + } + } + resp[@"height"] = @(height); + resp[@"daemonHeight"] = @(daemonHeight); + resp[@"syncing"] = @(connected && height < daemonHeight); + resp[@"connected"] = @(connected); + } else if ([op isEqualToString:@"transferTo"]) { + if (!g_wallet) { + resp[@"error"] = @"No wallet open"; + } else { + monero_tx_config config; + auto destination = std::make_shared(); + destination->m_address = std::string([req[@"address"] UTF8String]); + destination->m_amount = (uint64_t)[req[@"amount"] longLongValue]; + config.m_destinations = { destination }; + config.m_priority = (monero_tx_priority)[req[@"priority"] integerValue]; + config.m_relay = true; + auto txs = g_wallet->create_txs(config); + const auto &txSet = txs[0]->m_tx_set; + std::string signedHex = (txSet && txSet->m_signed_tx_hex) ? *txSet->m_signed_tx_hex : ""; + g_wallet->submit_txs(signedHex); + resp[@"txHash"] = [NSString stringWithUTF8String:(txs[0]->m_hash ? *txs[0]->m_hash : "").c_str()]; + resp[@"fee"] = @((txs[0]->m_fee ? *txs[0]->m_fee : 0)).stringValue; + } + } else { + resp[@"error"] = [NSString stringWithFormat:@"Unknown op: %@", op]; + } + } catch (const std::exception &e) { + os_log_error(OS_LOG_DEFAULT, "[koins] monero %{public}@ failed: %{public}s", + resp[@"op"], e.what()); + resp[@"error"] = [NSString stringWithUTF8String:e.what()]; + } catch (...) { + resp[@"error"] = @"Unknown native exception"; + } + + NSData *out = [NSJSONSerialization dataWithJSONObject:resp options:0 error:nil]; + NSString *outStr = [[NSString alloc] initWithData:out encoding:NSUTF8StringEncoding]; + completion(outStr ?: @"{\"error\":\"encode failed\"}"); + }); +} + +@end \ No newline at end of file diff --git a/apps/ios/Koins/Bridge/KoinsSecp256k1.h b/apps/ios/Koins/Bridge/KoinsSecp256k1.h new file mode 100644 index 0000000..a22787f --- /dev/null +++ b/apps/ios/Koins/Bridge/KoinsSecp256k1.h @@ -0,0 +1,25 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +/// secp256k1 helpers used by the XRP wallet. Backed by the OpenSSL build that +/// already ships inside the on-device monero static library, so no extra +/// dependency is linked. +@interface KoinsSecp256k1 : NSObject + +/// XRP account private scalar derived from a 16-byte family seed entropy. ++ (nullable NSData *)privateKeyFromEntropy:(NSData *)entropy; + +/// 33-byte compressed public key for a 32-byte private scalar. ++ (nullable NSData *)publicKeyFromPrivateKey:(NSData *)privateKey; + +/// DER-encoded, low-S ECDSA signature over a 32-byte digest. ++ (nullable NSData *)signDigest:(NSData *)digest + withPrivateKey:(NSData *)privateKey; + +/// (left + right) mod n, used by BIP-32 hardened child derivation. ++ (nullable NSData *)addScalar:(NSData *)left toScalar:(NSData *)right; + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/apps/ios/Koins/Bridge/KoinsSecp256k1.mm b/apps/ios/Koins/Bridge/KoinsSecp256k1.mm new file mode 100644 index 0000000..430e727 --- /dev/null +++ b/apps/ios/Koins/Bridge/KoinsSecp256k1.mm @@ -0,0 +1,187 @@ +#import "KoinsSecp256k1.h" + +#import +#import +#import +#import +#import + +// secp256k1 group order. +static const char *kCurveOrderHex = + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"; + +static BIGNUM *KoinsCurveOrder(void) { + BIGNUM *order = NULL; + BN_hex2bn(&order, kCurveOrderHex); + return order; +} + +static NSData *KoinsSHA512Half(NSData *input) { + unsigned char digest[SHA512_DIGEST_LENGTH]; + SHA512((const unsigned char *)input.bytes, (size_t)input.length, digest); + return [NSData dataWithBytes:digest length:32]; +} + +// Mirror of ripple-keypairs deriveScalar: hash the bytes (optionally with a +// 32-bit discriminator) until the result is a valid non-zero curve scalar. +static BIGNUM *KoinsDeriveScalar(NSData *bytes, BOOL hasDiscriminator, + uint32_t discriminator, const BIGNUM *order) { + for (uint32_t i = 0; i <= 4294967295u; i++) { + NSMutableData *input = [NSMutableData dataWithData:bytes]; + if (hasDiscriminator) { + uint32_t d = htonl(discriminator); + [input appendBytes:&d length:4]; + } + uint32_t idx = htonl(i); + [input appendBytes:&idx length:4]; + + NSData *half = KoinsSHA512Half(input); + BIGNUM *scalar = BN_bin2bn((const unsigned char *)half.bytes, (int)half.length, NULL); + if (scalar && !BN_is_zero(scalar) && BN_cmp(scalar, order) < 0) { + return scalar; + } + if (scalar) BN_free(scalar); + } + return NULL; +} + +@implementation KoinsSecp256k1 + ++ (nullable NSData *)privateKeyFromEntropy:(NSData *)entropy { + if (entropy.length != 16) return nil; + + BN_CTX *ctx = BN_CTX_new(); + BIGNUM *order = KoinsCurveOrder(); + EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp256k1); + EC_POINT *publicGen = EC_POINT_new(group); + NSData *result = nil; + + BIGNUM *privateGen = KoinsDeriveScalar(entropy, NO, 0, order); + if (privateGen && EC_POINT_mul(group, publicGen, privateGen, NULL, NULL, ctx)) { + unsigned char compressed[33]; + size_t written = EC_POINT_point2oct(group, publicGen, + POINT_CONVERSION_COMPRESSED, + compressed, sizeof(compressed), ctx); + if (written == sizeof(compressed)) { + NSData *publicGenBytes = [NSData dataWithBytes:compressed length:written]; + BIGNUM *offset = KoinsDeriveScalar(publicGenBytes, YES, 0, order); + if (offset) { + BIGNUM *derived = BN_new(); + if (BN_mod_add(derived, offset, privateGen, order, ctx)) { + unsigned char out[32] = {0}; + BN_bn2binpad(derived, out, sizeof(out)); + result = [NSData dataWithBytes:out length:sizeof(out)]; + } + BN_free(derived); + BN_free(offset); + } + } + } + + if (privateGen) BN_free(privateGen); + if (publicGen) EC_POINT_free(publicGen); + if (group) EC_GROUP_free(group); + if (order) BN_free(order); + if (ctx) BN_CTX_free(ctx); + return result; +} + ++ (nullable NSData *)publicKeyFromPrivateKey:(NSData *)privateKey { + if (privateKey.length != 32) return nil; + + BN_CTX *ctx = BN_CTX_new(); + EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp256k1); + EC_POINT *point = EC_POINT_new(group); + BIGNUM *scalar = BN_bin2bn((const unsigned char *)privateKey.bytes, (int)privateKey.length, NULL); + NSData *result = nil; + + if (scalar && EC_POINT_mul(group, point, scalar, NULL, NULL, ctx)) { + unsigned char compressed[33]; + size_t written = EC_POINT_point2oct(group, point, + POINT_CONVERSION_COMPRESSED, + compressed, sizeof(compressed), ctx); + if (written == sizeof(compressed)) { + result = [NSData dataWithBytes:compressed length:written]; + } + } + + if (scalar) BN_free(scalar); + if (point) EC_POINT_free(point); + if (group) EC_GROUP_free(group); + if (ctx) BN_CTX_free(ctx); + return result; +} + ++ (nullable NSData *)addScalar:(NSData *)left toScalar:(NSData *)right { + if (left.length != 32 || right.length != 32) return nil; + + BN_CTX *ctx = BN_CTX_new(); + BIGNUM *order = KoinsCurveOrder(); + BIGNUM *a = BN_bin2bn((const unsigned char *)left.bytes, (int)left.length, NULL); + BIGNUM *b = BN_bin2bn((const unsigned char *)right.bytes, (int)right.length, NULL); + BIGNUM *sum = BN_new(); + NSData *result = nil; + + if (a && b && sum && BN_mod_add(sum, a, b, order, ctx)) { + unsigned char out[32] = {0}; + BN_bn2binpad(sum, out, sizeof(out)); + result = [NSData dataWithBytes:out length:sizeof(out)]; + } + + if (sum) BN_free(sum); + if (b) BN_free(b); + if (a) BN_free(a); + if (order) BN_free(order); + if (ctx) BN_CTX_free(ctx); + return result; +} + ++ (nullable NSData *)signDigest:(NSData *)digest + withPrivateKey:(NSData *)privateKey { + if (digest.length != 32 || privateKey.length != 32) return nil; + + BN_CTX *ctx = BN_CTX_new(); + EC_KEY *key = EC_KEY_new_by_curve_name(NID_secp256k1); + BIGNUM *scalar = BN_bin2bn((const unsigned char *)privateKey.bytes, (int)privateKey.length, NULL); + NSData *result = nil; + + if (key && scalar && EC_KEY_set_private_key(key, scalar)) { + const EC_GROUP *group = EC_KEY_get0_group(key); + EC_POINT *pub = EC_POINT_new(group); + if (pub && EC_POINT_mul(group, pub, scalar, NULL, NULL, ctx) && + EC_KEY_set_public_key(key, pub)) { + ECDSA_SIG *sig = ECDSA_do_sign((const unsigned char *)digest.bytes, (int)digest.length, key); + if (sig) { + // Ripple only accepts canonical (low-S) signatures. + BIGNUM *order = KoinsCurveOrder(); + BIGNUM *halfOrder = BN_dup(order); + BN_rshift1(halfOrder, halfOrder); + const BIGNUM *r = NULL; + const BIGNUM *s = NULL; + ECDSA_SIG_get0(sig, &r, &s); + if (BN_cmp(s, halfOrder) > 0) { + BIGNUM *lowS = BN_new(); + BN_sub(lowS, order, s); + ECDSA_SIG_set0(sig, BN_dup(r), lowS); + } + unsigned char *der = NULL; + int derLen = i2d_ECDSA_SIG(sig, &der); + if (derLen > 0) { + result = [NSData dataWithBytes:der length:(NSUInteger)derLen]; + OPENSSL_free(der); + } + BN_free(halfOrder); + BN_free(order); + ECDSA_SIG_free(sig); + } + } + if (pub) EC_POINT_free(pub); + } + + if (scalar) BN_free(scalar); + if (key) EC_KEY_free(key); + if (ctx) BN_CTX_free(ctx); + return result; +} + +@end \ No newline at end of file diff --git a/apps/ios/Koins/Info.plist b/apps/ios/Koins/Info.plist new file mode 100644 index 0000000..9423b5d --- /dev/null +++ b/apps/ios/Koins/Info.plist @@ -0,0 +1,35 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + UILaunchScreen + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UIUserInterfaceStyle + Dark + + diff --git a/apps/ios/Koins/KoinsApp.swift b/apps/ios/Koins/KoinsApp.swift new file mode 100644 index 0000000..e4af8a7 --- /dev/null +++ b/apps/ios/Koins/KoinsApp.swift @@ -0,0 +1,30 @@ +import SwiftUI + +@main +struct KoinsApp: App { + @StateObject private var model: AppModel + + init() { + Secp256k1Backend.derivePrivateKey = { entropy in + KoinsSecp256k1.privateKey(fromEntropy: entropy) + } + Secp256k1Backend.publicKey = { privateKey in + KoinsSecp256k1.publicKey(fromPrivateKey: privateKey) + } + Secp256k1Backend.sign = { digest, privateKey in + KoinsSecp256k1.signDigest(digest, withPrivateKey: privateKey) + } + Secp256k1Backend.addScalar = { left, right in + KoinsSecp256k1.addScalar(left, toScalar: right) + } + _model = StateObject(wrappedValue: AppModel()) + SelfTest.runIfRequested() + } + + var body: some Scene { + WindowGroup { + RootView() + .environmentObject(model) + } + } +} \ No newline at end of file diff --git a/apps/ios/Koins/Models.swift b/apps/ios/Koins/Models.swift new file mode 100644 index 0000000..84febe4 --- /dev/null +++ b/apps/ios/Koins/Models.swift @@ -0,0 +1,69 @@ +import Foundation + +enum Chain: String, Codable, CaseIterable { + case xrp + case xmr + + var symbol: String { self == .xrp ? "XRP" : "XMR" } + var title: String { self == .xrp ? "XRP Ledger" : "Monero" } + var subtitle: String { + self == .xrp ? "Fast, low-fee payments" : "Private, untraceable payments" + } +} + +struct WalletRecord: Codable, Identifiable, Equatable { + let id: String + var name: String + var address: String + var createdAt: Date +} + +enum Direction { + case incoming + case outgoing +} + +struct TransactionRecord: Identifiable { + let id: String + let amount: String + let timestamp: Date? + let direction: Direction + let confirmed: Bool + var fee: String? + var tag: Int? + var counterparty: String? +} + +struct Balance { + var total: String + var unlocked: String + var funded: Bool +} + +struct SyncStatus { + var height: UInt64 + var daemonHeight: UInt64 + var syncing: Bool + var connected: Bool + + var progress: Double { + guard daemonHeight > 0 else { return 0 } + return min(1, Double(height) / Double(daemonHeight)) + } +} + +enum TransactionPriority: Int { + case defaultPriority = 0 + case low = 1 + case medium = 2 + case high = 3 + + var label: String { + switch self { + case .defaultPriority: return "Default" + case .low: return "Low" + case .medium: return "Medium" + case .high: return "High" + } + } +} \ No newline at end of file diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/common/monero_error.h b/apps/ios/Koins/Monero/include/monero-cpp-src/common/monero_error.h new file mode 100644 index 0000000..c6ada46 --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/common/monero_error.h @@ -0,0 +1,107 @@ +/** + * Copyright (c) woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ + +#pragma once + +#include +#include + +/** + * Public interface for libmonero-cpp library. + */ +namespace monero { + + /** + * Exception when interacting with a Monero wallet or daemon. + */ + class monero_error : public std::exception { + public: + std::string message; + + monero_error() {} + + /** + * Construct the exception. + * + * @param msg is a human-readable description of the error + */ + monero_error(const std::string& msg) : message(msg) {} + + const char* what() const noexcept override { + return message.c_str(); + } + }; + + /** + * Exception when interacting with the Monero daemon or wallet RPC API. + */ + class monero_rpc_error : public monero_error { + public: + int code; + + /** + * Construct the exception. + * + * @param error_code is the error code (optional) + * @param msg is a human-readable description of the error + */ + monero_rpc_error(int error_code, const std::string& msg) : code(error_code) { message = msg; } + + /** + * Construct the exception. + * + * @param msg is a human-readable description of the error + */ + monero_rpc_error(const std::string& msg) : code(-1) { message = msg; } + }; + +} diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/common/monero_rpc_connection.cpp b/apps/ios/Koins/Monero/include/monero-cpp-src/common/monero_rpc_connection.cpp new file mode 100644 index 0000000..fe6089b --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/common/monero_rpc_connection.cpp @@ -0,0 +1,439 @@ +#include "monero_rpc_connection.h" +#include "utils/gen_utils.h" +#include "utils/monero_utils.h" +#include "daemon/monero_daemon_rpc_model.h" +#include "monero_error.h" + +static const uint32_t DEFAULT_TIMEOUT_MS = 3 * 60 * 1000; // 3 minutes + +namespace monero { + + // --------------------------- MONERO REQUEST PARAMS --------------------------- + + rapidjson::Value monero_request_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + rapidjson::Value root(rapidjson::kObjectType); + return root; + } + + // --------------------------- MONERO RPC REQUEST --------------------------- + + monero_rpc_request::monero_rpc_request(const std::string& method, const std::shared_ptr& params, bool json_rpc): m_method(method), m_params(params) { + if (params == nullptr) m_params = std::make_shared(); + if (json_rpc) { + m_id = "0"; + m_version = "2.0"; + } + } + + rapidjson::Value monero_rpc_request::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + if (!is_json_rpc()) { + if (m_params == nullptr) throw monero_error("No params provided"); + return m_params->to_rapidjson_val(allocator); + } + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + + if (m_version != boost::none) monero_utils::add_json_member("jsonrpc", m_version.get(), allocator, root, value_str); + if (m_id != boost::none) monero_utils::add_json_member("id", m_id.get(), allocator, root, value_str); + if (m_method != boost::none) monero_utils::add_json_member("method", m_method.get(), allocator, root, value_str); + if (m_params != nullptr) root.AddMember("params", m_params->to_rapidjson_val(allocator), allocator); + + // return root + return root; + } + + // --------------------------- MONERO RPC RESPONSE --------------------------- + + void raise_rpc_error(const boost::property_tree::ptree& error_node, const boost::string_ref uri, const std::string& method) { + std::string err_message = "Unknown error"; + int err_code = -1; + + for (auto it = error_node.begin(); it != error_node.end(); ++it) { + std::string key_err = it->first; + if (key_err == std::string("message")) { + if (it->second.data().empty()) { + err_message = "Received error response from RPC request with method '" + method + "' to " + uri.to_string(); + } + else err_message = it->second.data(); + } else if (key_err == std::string("code")) { + try { + err_code = it->second.get_value(); + } catch (...) { + MERROR("non-numeric error code from a malformed/unusual RPC response"); + err_code = -1; + } + } + } + + throw monero_rpc_error(err_code, err_message); + } + + // --------------------------- MONERO RPC CONNECTION --------------------------- + + bool monero_rpc_connection::compare(int p1, int p2) { + if (p1 == p2) return false; + // 0 alway first + if (p1 == 0) return true; + if (p2 == 0) return false; + return p1 > p2; + } + + monero_rpc_connection::monero_rpc_connection(const std::string& uri, const std::string& username, const std::string& password, const std::string& proxy_uri, const std::string& zmq_uri, int priority, const boost::optional& timeout_ms) { + if (!uri.empty()) m_uri = uri; + else m_uri = boost::none; + if (!proxy_uri.empty()) m_proxy_uri = proxy_uri; + else m_proxy_uri = boost::none; + if (!zmq_uri.empty()) m_zmq_uri = zmq_uri; + else m_zmq_uri = boost::none; + m_priority = priority; + m_timeout_ms = timeout_ms; + set_credentials(username, password); + } + + monero_rpc_connection::monero_rpc_connection(const monero_rpc_connection& rpc) { + boost::lock_guard src_lock(rpc.m_mutex); + m_uri = rpc.m_uri; + m_proxy_uri = rpc.m_proxy_uri; + m_zmq_uri = rpc.m_zmq_uri; + m_priority = rpc.m_priority; + m_timeout_ms = rpc.m_timeout_ms; + set_credentials(rpc.m_username.value_or(""), rpc.m_password.value_or("")); + m_is_online = rpc.m_is_online; + m_is_authenticated = rpc.m_is_authenticated; + m_response_time = rpc.m_response_time; + m_attributes = rpc.m_attributes; + } + + rapidjson::Value monero_rpc_connection::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + boost::lock_guard lock(m_mutex); + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_uri != boost::none) monero_utils::add_json_member("uri", m_uri.get(), allocator, root, value_str); + if (m_username != boost::none) monero_utils::add_json_member("username", m_username.get(), allocator, root, value_str); + if (m_password != boost::none) monero_utils::add_json_member("password", m_password.get(), allocator, root, value_str); + if (m_proxy_uri != boost::none) monero_utils::add_json_member("proxyUri", m_proxy_uri.get(), allocator, root, value_str); + if (m_zmq_uri != boost::none) monero_utils::add_json_member("zmqUri", m_zmq_uri.get(), allocator, root, value_str); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + monero_utils::add_json_member("priority", m_priority, allocator, root, value_num); + if (m_timeout_ms != boost::none) monero_utils::add_json_member("timeoutMs", m_timeout_ms.get(), allocator, root, value_num); + if (m_response_time != boost::none) monero_utils::add_json_member("responseTime", m_response_time.get(), allocator, root, value_num); + + // set bool values + if (m_is_online != boost::none) monero_utils::add_json_member("isOnline", m_is_online.get(), allocator, root); + if (m_is_authenticated != boost::none) monero_utils::add_json_member("isAuthenticated", m_is_authenticated.get(), allocator, root); + + return root; + } + + bool monero_rpc_connection::is_onion() const { + boost::lock_guard lock(m_mutex); + epee::net_utils::http::url_content parsed{}; + if (!epee::net_utils::parse_url(m_uri.value_or(""), parsed)) return false; + return parsed.host.size() >= 6 && parsed.host.compare(parsed.host.size() - 6, 6, ".onion") == 0; + } + + bool monero_rpc_connection::is_i2p() const { + boost::lock_guard lock(m_mutex); + epee::net_utils::http::url_content parsed{}; + if (!epee::net_utils::parse_url(m_uri.value_or(""), parsed)) return false; + return parsed.host.size() >= 8 && parsed.host.compare(parsed.host.size() - 8, 8, ".b32.i2p") == 0; + } + + void monero_rpc_connection::set_credentials(const std::string& username, const std::string& password) { + boost::lock_guard lock(m_mutex); + + // disconnect http client if connected + if (m_http_client != nullptr && m_http_client->is_connected()) m_http_client->disconnect(); + + bool username_empty = username.empty(); + bool password_empty = password.empty(); + + // check username and password consistency + if (!username_empty || !password_empty) { + if (password_empty) { + throw monero_error("password cannot be empty because username is not empty"); + } + if (username_empty) { + throw monero_error("username cannot be empty because password is not empty"); + } + } + + // check username and password changes + bool username_equals = (m_username == boost::none && username_empty) || (m_username != boost::none && *m_username == username); + bool password_equals = (m_password == boost::none && password_empty) || (m_password != boost::none && *m_password == password); + + // connection reset values + if (!username_equals || !password_equals) { + m_is_online = boost::none; + m_is_authenticated = boost::none; + } + + // setup username and password + if (!username_empty && !password_empty) { + m_username = username; + m_password = password; + } else { + m_username = boost::none; + m_password = boost::none; + } + } + + void monero_rpc_connection::set_attribute(const std::string& key, const std::string& val) { + boost::lock_guard lock(m_mutex); + m_attributes[key] = val; + } + + std::string monero_rpc_connection::get_attribute(const std::string& key) const { + boost::lock_guard lock(m_mutex); + std::unordered_map::const_iterator i = m_attributes.find(key); + if (i == m_attributes.end()) { + return std::string(""); // attribute not found + } + return i->second; + } + + boost::optional monero_rpc_connection::is_online() const { + boost::lock_guard lock(m_mutex); + return m_is_online; + } + + boost::optional monero_rpc_connection::is_authenticated() const { + boost::lock_guard lock(m_mutex); + return m_is_authenticated; + } + + boost::optional monero_rpc_connection::is_connected() const { + boost::lock_guard lock(m_mutex); + if (m_is_online == boost::none) return boost::none; + return m_is_online.get() && (m_is_authenticated == boost::none || m_is_authenticated.get()); + } + + bool monero_rpc_connection::check_connection(const boost::optional& timeout_ms) { + boost::lock_guard lock(m_mutex); + boost::optional is_online_before = m_is_online; + boost::optional is_authenticated_before = m_is_authenticated; + boost::optional start_time = boost::none; + + try { + + // assume daemon connection + monero_get_blocks_by_height_request request(100); + start_time = std::chrono::high_resolution_clock::now(); + send_binary_request(request, timeout_ms); + + // set response time + m_response_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time.get()).count(); + m_is_online = true; + m_is_authenticated = true; + } + catch (const monero_rpc_error& ex) { + m_is_online = false; + m_is_authenticated = boost::none; + m_response_time = boost::none; + + if (ex.code == 401 || ex.code == 404) { + + // fallback to latency check + m_is_online = true; + m_is_authenticated = ex.code == 404; + } + } + catch (...) { + m_is_online = false; + m_is_authenticated = boost::none; + m_response_time = boost::none; + } + + if (gen_utils::bool_equals(true, m_is_online) && start_time != boost::none) { + m_response_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time.get()).count(); + } + + return is_online_before != m_is_online || is_authenticated_before != m_is_authenticated; + } + + boost::property_tree::ptree monero_rpc_connection::send_json_request(const std::string& path, const std::shared_ptr& params, const boost::optional& timeout_ms) const { + + // send JSON-RPC request + monero_rpc_request request(path, params); + auto response = send_json_request(request, timeout_ms); + + // assert JSON-RPC response is defined + if (response.m_result == boost::none) throw monero_error("Invalid Monero JSONRPC response"); + return response.m_result.get(); + } + + monero_rpc_response monero_rpc_connection::send_json_request(const monero_rpc_request &request, const boost::optional& timeout_ms) const { + + // invoke JSON-RPC method + monero_rpc_response response; + send_rpc_request("/json_rpc", request, response, timeout_ms); + + // return JSON-RPC response + return response; + } + + boost::property_tree::ptree monero_rpc_connection::send_path_request(const std::string& path, const std::shared_ptr& params, const boost::optional& timeout_ms) const { + + // send RPC request + monero_rpc_request request(path, params, false); + auto response = send_path_request(request, timeout_ms); + + // assert RPC response is defined + if (response.m_response == boost::none) throw monero_error("Invalid Monero RPC response"); + return response.m_response.get(); + } + + monero_rpc_response monero_rpc_connection::send_path_request(const monero_rpc_request &request, const boost::optional& timeout_ms) const { + + // validate parameters + if (request.m_method == boost::none || request.m_method->empty()) throw monero_error("No RPC method set in path request"); + + // invoke RPC method + monero_rpc_response response; + send_rpc_request(std::string("/") + request.m_method.get(), request, response, timeout_ms); + + // return RPC response + return response; + } + + monero_rpc_response monero_rpc_connection::send_binary_request(const monero_rpc_request &request, const boost::optional& timeout_ms) const { + + // validate parameters + if (request.m_method == boost::none || request.m_method->empty()) throw monero_error("No RPC method set in binary request"); + + // invoke binary RPC method + monero_rpc_response response; + send_rpc_request(std::string("/") + request.m_method.get(), request, response, timeout_ms, true); + + // return binary response + return response; + } + + void monero_rpc_connection::ensure_configured() const { + + // create http client on first use, so constructing a connection does not require network support + if (m_http_client == nullptr) { + auto factory = std::make_unique(); + m_http_client = factory->create(); + m_http_client->set_auto_connect(true); + } + + // only reconfigure when uri/creds/proxy changed since last apply + std::string uri = m_uri.value_or(""); + std::string username = m_username.value_or(""); + std::string password = m_password.value_or(""); + std::string proxy_uri = m_proxy_uri.value_or(""); + auto key = std::make_tuple(uri, username, password, proxy_uri); + + if (m_applied == key) return; // unchanged, so reuse live connection + + // setup connection credentials + boost::optional login; + if(!username.empty() && !password.empty()) { + login = epee::net_utils::http::login(); + login->username = username; + login->password = password; + } + + // detect ssl + epee::net_utils::ssl_support_t ssl = uri.rfind("https", 0) == 0 ? epee::net_utils::ssl_support_t::e_ssl_support_enabled : epee::net_utils::ssl_support_t::e_ssl_support_disabled; + + if (!m_http_client->set_proxy(proxy_uri)) throw monero_error("Could not set proxy uri: " + proxy_uri); + if (!m_http_client->set_server(uri, login, std::move(ssl))) throw monero_error("Could not set uri: " + uri); + if (!m_http_client->connect(std::chrono::milliseconds(DEFAULT_TIMEOUT_MS))) MWARNING("Could not connect to server: " + uri); + + m_applied = key; + } + + std::string monero_rpc_connection::invoke_post(const boost::string_ref uri, const std::string& body, const boost::optional& timeout_ms) const { + boost::lock_guard lock(m_mutex); + + // request/response timeout: user-configurable, defaults to 3 minutes when not set. + uint32_t timeout = DEFAULT_TIMEOUT_MS; + if (timeout_ms != boost::none) timeout = timeout_ms.get(); + else if (m_timeout_ms != boost::none) timeout = m_timeout_ms.get(); + if (timeout == 0) timeout = std::numeric_limits::max(); // timeout of 0 is treated as indefinite + + // ensure_configured() bounds only the first connect; epee reconnects with this request timeout + ensure_configured(); + const epee::net_utils::http::http_response_info* pri = NULL; + + // invoke http json + const uint64_t received_before = m_http_client->get_bytes_received(); + if (!m_http_client->invoke_post(uri, body, std::chrono::milliseconds(timeout), std::addressof(pri))) { + + // epee never surfaces the 401 status, but a rejected login means the server answered; + // a dead or idle-closed socket yields no response bytes at all + bool received = m_http_client->get_bytes_received() > received_before; + if (received && m_http_client->is_connected()) { + throw monero_rpc_error(401, "Unauthorized"); + } + + // drop the socket: epee leaves EOF'd sockets marked connected, so without this the next + // call reuses the poisoned socket and hits the same misdiagnosis again + m_http_client->disconnect(); + if (m_uri == boost::none || m_uri->empty()) throw monero_error("Cannot send RPC request: uri not set."); + throw monero_error("Network error: " + std::string(received ? "connection dropped mid-response" : "no response") + " from " + m_uri.get() + uri.to_string() + " (timeout: " + std::to_string(timeout) + "ms)"); + } + if (!pri) throw monero_error("Could not get response info"); + if (pri->m_response_code < 200 || pri->m_response_code > 299) { + std::string err_msg = std::to_string(pri->m_response_code) + " " + pri->m_response_comment + (pri->m_body.empty() ? "" : ": " + pri->m_body); + throw monero_rpc_error(pri->m_response_code, err_msg); + } + + // return response info body + return pri->m_body; + } + + void deserialize_rpc_response(const std::string& json, monero_rpc_response& response, const boost::string_ref uri, const std::string& method) { + + // parse json to property node + boost::property_tree::ptree node; + gen_utils::deserialize(json, node); + + // parse JSON-RPC response + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("error")) raise_rpc_error(it->second, uri, method); + else if (key == std::string("jsonrpc")) response.m_jsonrpc = it->second.data(); + else if (key == std::string("result")) response.m_result = it->second; + } + + // if not JSON-RPC, set RPC response + if (response.m_jsonrpc == boost::none) response.m_response = node; + } + + void monero_rpc_connection::send_rpc_request(const boost::string_ref uri, const monero_rpc_request& request, monero_rpc_response& response, const boost::optional& timeout_ms, bool binary) const { + std::string body; + if (request.m_method == boost::none) throw monero_error("Must provide a valid request method"); + if (binary) monero_utils::json_to_binary(request.serialize(), body); + else body = request.serialize(); + + std::string result = invoke_post(uri, body, timeout_ms); + if (binary) response.m_binary = result; + else deserialize_rpc_response(result, response, uri, request.m_method.get()); + } + + std::shared_ptr monero_rpc_connection::from_property_tree(const boost::property_tree::ptree& node) { + std::shared_ptr connection = std::make_shared(); + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("uri")) connection->m_uri = it->second.data(); + else if (key == std::string("username")) connection->m_username = it->second.data(); + else if (key == std::string("password")) connection->m_password = it->second.data(); + else if (key == std::string("proxyUri") || key == std::string("proxy_uri")) connection->m_proxy_uri = it->second.data(); + else if (key == std::string("zmqUri")) connection->m_zmq_uri = it->second.data(); + } + return connection; + } +} diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/common/monero_rpc_connection.h b/apps/ios/Koins/Monero/include/monero-cpp-src/common/monero_rpc_connection.h new file mode 100644 index 0000000..94e8d8c --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/common/monero_rpc_connection.h @@ -0,0 +1,281 @@ +/** + * Copyright (c) woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ + +#pragma once + +#include +#include +#include +#include +#include +#include "rapidjson/document.h" +#include "rapidjson/writer.h" +#include "rapidjson/stringbuffer.h" +#include "net/http.h" +#include "daemon/monero_daemon_model.h" + +/** + * Public interface for libmonero-cpp library. + */ +namespace monero { + + /** + * Models parameters for a request to a RPC server. + */ + struct monero_request_params : public serializable_struct { + monero_request_params() { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + /** + * Models a request to a RPC server. + */ + struct monero_rpc_request : public serializable_struct { + boost::optional m_id; + boost::optional m_version; + boost::optional m_method; + std::shared_ptr m_params; + + monero_rpc_request() { } + monero_rpc_request(const std::string& method, const std::shared_ptr& params, bool json_rpc = true); + + bool is_json_rpc() const { return m_id != boost::none && m_version != boost::none; } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + /** + * Models a response from a RPC server. + */ + struct monero_rpc_response { + boost::optional m_jsonrpc; + boost::optional m_result; + boost::optional m_response; + boost::optional m_binary; + }; + + /** + * Maintains a connection and sends requests to a Monero RPC API. + */ + class monero_rpc_connection : public serializable_struct { + public: + boost::optional m_uri; + boost::optional m_username; + boost::optional m_password; + boost::optional m_proxy_uri; + boost::optional m_zmq_uri; // TODO: implement zmq listener + boost::optional m_timeout_ms; // RPC request timeout in milliseconds + boost::optional m_response_time; // automatically set by calling check_connection() + int m_priority; // priority relative to other connections where 1 is highest, then 2, etc, and 0 is lowest (default) + + static std::shared_ptr from_property_tree(const boost::property_tree::ptree& node); + + /** + * Checks RPC connection priority order. + * + * @param c1 first priority to compare + * @param c2 second priority to compare + */ + static bool compare(int p1, int p2); + + /** + * Initialize a new RPC connection. + * + * @param uri RPC connection uri + * @param username RPC connection authentication username + * @param password RPC connection authentication password + * @param proxy_uri RPC connection proxy uri + * @param zmq_uri RPC connection zmq uri + * @param priority RPC connection priority + * @param timeout_ms RPC connection timeout in milliseconds + */ + monero_rpc_connection(const std::string& uri = "", const std::string& username = "", const std::string& password = "", const std::string& proxy_uri = "", const std::string& zmq_uri = "", int priority = 0, const boost::optional& timeout_ms = boost::none); + + /** + * Copy a RPC connection. + * + * @param rpc RPC connection to copy + */ + monero_rpc_connection(const monero_rpc_connection& rpc); + + /** + * Indicates if the connection uri is a TOR server. + * + * @return true or false to indicate if connection uri is a TOR server + */ + bool is_onion() const; + + /** + * Indicates if the connection uri is a I2P server. + * + * @return true or false to indicate if connection uri is a I2P server + */ + bool is_i2p() const; + + /** + * Set connection credentials. + * + * @param username username to use in RPC authentication + * @param password password to use in RPC authentication + */ + void set_credentials(const std::string& username, const std::string& password); + + /** + * Set connection attribute. + * + * @param key is the attribute key + * @param val is the attribute value + */ + void set_attribute(const std::string& key, const std::string& val); + + /** + * Get connection attribute. + * + * @param key is the attribute to get the value of + * @return key's value if set + */ + std::string get_attribute(const std::string& key) const; + + /** + * Indicates if the connection is online, which is set automatically by calling check_connection(). + * + * @return true or false to indicate if online, or null if check_connection() has not been called + */ + boost::optional is_online() const; + + /** + * Indicates if the connection is authenticated, which is set automatically by calling check_connection(). + * + * @return true if authenticated or no authentication, false if not authenticated, or null if not set + */ + boost::optional is_authenticated() const; + + /** + * Indicates if the connection is connected, which is set automatically by calling check_connection(). + * + * @return true or false to indicate if connected, or null if check_connection() has not been called + */ + boost::optional is_connected() const; + + /** + * Check the connection and update online, authentication, and response time status. + * + * @param timeout_ms the maximum response time before considered offline + * @return + */ + bool check_connection(const boost::optional& timeout_ms = boost::none); + + /** + * Send a request to the RPC API. + * + * @param path specifies the method to request + * @param params are the request's input parameters + * @return the RPC API response as a map + */ + boost::property_tree::ptree send_json_request(const std::string& path, const std::shared_ptr& params = nullptr, const boost::optional& timeout_ms = boost::none) const; + + /** + * Send a request to the RPC API. + * + * @param request specifies the method to request with parameters + * @param timeout request timeout in milliseconds + * @return the RPC API response as a map + */ + monero_rpc_response send_json_request(const monero_rpc_request &request, const boost::optional& timeout_ms = boost::none) const; + + /** + * Send a RPC request to the given path and with the given paramters. + * + * E.g. "/get_transactions" with params + * + * @param path is the url path of the request to invoke + * @param params are request parameters sent in the body + * @return the RPC API response as a map + */ + boost::property_tree::ptree send_path_request(const std::string& path, const std::shared_ptr& params = nullptr, const boost::optional& timeout_ms = boost::none) const; + + /** + * Send a RPC request to the given path and with the given paramters. + * + * @param request specifies the method to request with parameters + * @param timeout request timeout in milliseconds + * @return the request's deserialized response + */ + monero_rpc_response send_path_request(const monero_rpc_request &request, const boost::optional& timeout_ms = boost::none) const; + + /** + * Send a binary RPC request. + * + * @param request specifies the method to request with paramesters + * @param timeout request timeout in milliseconds + * @return the request's deserialized response + */ + monero_rpc_response send_binary_request(const monero_rpc_request &request, const boost::optional& timeout_ms = boost::none) const; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + + protected: + // instance variables + mutable boost::recursive_mutex m_mutex; + boost::optional m_is_online; + boost::optional m_is_authenticated; + mutable std::unique_ptr m_http_client; + std::unordered_map m_attributes; + mutable std::tuple m_applied; + + void ensure_configured() const; + std::string invoke_post(const boost::string_ref uri, const std::string& body, const boost::optional& timeout_ms = boost::none) const; + void send_rpc_request(const boost::string_ref uri, const monero_rpc_request& request, monero_rpc_response& response, const boost::optional& timeout_ms = boost::none, bool binary = false) const; + }; + +} diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon.cpp b/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon.cpp new file mode 100644 index 0000000..38feaaa --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon.cpp @@ -0,0 +1,60 @@ +/** + * Copyright (c) woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ + +#include "monero_daemon.h" + +/** + * Public library intereface. + */ +namespace monero { + +} diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon.h b/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon.h new file mode 100644 index 0000000..4d923c4 --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon.h @@ -0,0 +1,805 @@ +/** + * Copyright (c) woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ + +#pragma once + +#include "common/monero_rpc_connection.h" +#include "monero_daemon_model.h" + +/** + * Public library interface. + */ +namespace monero { + + /** + * Receives notifications as a daemon is updated. + */ + class monero_daemon_listener { + public: + std::shared_ptr m_last_header; + + /** + * Called when a new block is added to the chain. + * + * @param header is the header of the block added to the chain + */ + virtual void on_block_header(const std::shared_ptr& header) { + m_last_header = header; + } + }; + + /** + * Monero daemon interface. + */ + class monero_daemon { + public: + + /** + * Virtual destructor. + */ + virtual ~monero_daemon() {} + + /** + * Register a listener receive daemon notifications. + * + * @param listener is the listener to receive daemon notifications + */ + virtual void add_listener(monero_daemon_listener &listener) { + throw std::runtime_error("monero_daemon::add_listener(): not supported"); + } + + /** + * Unregister a listener to receive daemon notifications. + * + * @param listener is the listener to unregister + */ + virtual void remove_listener(monero_daemon_listener &listener) { + throw std::runtime_error("monero_daemon::remove_listener(): not supported"); + } + + /** + * Get the listeners registered with the daemon. + */ + virtual std::set get_listeners() { + throw std::runtime_error("monero_daemon::get_listeners(): not supported"); + } + + /** + * Remove all listeners registered with the daemon. + */ + virtual void remove_listeners() { + throw std::runtime_error("monero_daemon::remove_listeners(): not supported"); + } + + /** + * Get the daemon's version. + * + * @return the daemon's version + */ + virtual monero_version get_version() { + throw std::runtime_error("monero_daemon::get_version(): not supported"); + } + + /** + * Indicates if the daemon is trusted or untrusted. + * + * @return true if the daemon is trusted, false otherwise + */ + virtual bool is_trusted() { + throw std::runtime_error("monero_daemon::is_trusted(): not supported"); + } + + /** + * Get the number of blocks in the longest chain known to the node. + * + * @return the number of blocks + */ + virtual uint64_t get_height() { + throw std::runtime_error("monero_daemon::get_height(): not supported"); + } + + /** + * Get a block's hash by its height. + * + * @param height is the height of the block hash to get + * @return tthe block's hash at the given height + */ + virtual std::string get_block_hash(uint64_t height) { + throw std::runtime_error("monero_daemon::get_block_hash(): not supported"); + } + + /** + * Get a block template for mining a new block. + * + * @param wallet_address is the address of the wallet to receive miner transactions if block is successfully mined + * @param reserve_size is the reserve size (optional) + * @return a block template for mining a new block + */ + virtual std::shared_ptr get_block_template(const std::string& wallet_address, const boost::optional& reserve_size = boost::none) { + throw std::runtime_error("monero_daemon::get_block_template(): not supported"); + } + + /** + * Get the last block's header. + * + * @return the last block's header + */ + virtual std::shared_ptr get_last_block_header() { + throw std::runtime_error("monero_daemon::get_last_block_header(): not supported"); + } + + /** + * Get a block header by its hash. + * + * @param block_hash is the hash of the block to get the header of + * @return the block's header + */ + virtual std::shared_ptr get_block_header_by_hash(const std::string& block_hash) { + throw std::runtime_error("monero_daemon::get_block_header_by_hash(): not supported"); + } + + /** + * Get a block header by its height. + * + * @param height is the height of the block to get the header of + * @return the block's header + */ + virtual std::shared_ptr get_block_header_by_height(uint64_t height) { + throw std::runtime_error("monero_daemon::get_block_header_by_height(): not supported"); + } + + /** + * Get block headers for the given range. + * + * @param start_height is the start height lower bound inclusive (optional) + * @param end_height is the end height upper bound inclusive (optional) + * @return block headers in the given range + */ + virtual std::vector> get_block_headers_by_range(uint64_t start_height, uint64_t end_height) { + throw std::runtime_error("monero_daemon::get_block_headers_by_range(): not supported"); + } + + /** + * Get a block by hash. + * + * @param block_hash is the hash of the block to get + * @return the block with the given hash + */ + virtual std::shared_ptr get_block_by_hash(const std::string& block_hash) { + throw std::runtime_error("monero_daemon::get_block_by_hash(): not supported"); + } + + /** + * Get blocks by hash. + * + * @param block_hashes are array of hashes; first 10 blocks hash goes sequential, + * next goes in pow(2,n) offset, like 2, 4, 8, 16, 32, 64 and so on, + * and the last one is always genesis block + * @param start_height is the start height to get blocks by hash + * @param prune specifies if returned blocks should be pruned (defaults to false) // TODO: test default + * @return the retrieved blocks + */ + virtual std::vector> get_blocks_by_hash(const std::vector& block_hashes, uint64_t start_height, bool prune) { + throw std::runtime_error("monero_daemon::get_blocks_by_hash(): not supported"); + } + + /** + * Get a block by height. + * + * @param height is the height of the block to get + * @return the block at the given height + */ + virtual std::shared_ptr get_block_by_height(uint64_t height) { + throw std::runtime_error("monero_daemon::get_block_by_height(): not supported"); + } + + /** + * Get blocks at the given heights. + * + * @param heights are the heights of the blocks to get + * @return blocks at the given heights + */ + virtual std::vector> get_blocks_by_height(const std::vector& heights) { + throw std::runtime_error("monero_daemon::get_blocks_by_height(): not supported"); + } + + /** + * Get blocks in the given height range. + * + * @param start_height is the start height lower bound inclusive (optional) + * @param end_height is the end height upper bound inclusive (optional) + * @return blocks in the given height range + */ + virtual std::vector> get_blocks_by_range(boost::optional start_height, boost::optional end_height) { + throw std::runtime_error("monero_daemon::get_blocks_by_range(): not supported"); + } + + /** + * Get blocks in the given height range as chunked requests so that each request is + * not too big. + * + * @param start_height is the start height lower bound inclusive (optional) + * @param end_height is the end height upper bound inclusive (optional) + * @return blocks in the given height range + */ + virtual std::vector> get_blocks_by_range_chunked(boost::optional start_height, boost::optional end_height, boost::optional max_chunk_size) { + throw std::runtime_error("monero_daemon::get_blocks_by_range_chunked(): not supported"); + } + + /** + * Get block hashes as a binary request to the daemon. + * + * @param block_hashes specify block hashes to fetch; first 10 blocks hash goes + * sequential, next goes in pow(2,n) offset, like 2, 4, 8, 16, 32, 64 + * and so on, and the last one is always genesis block + * @param start_height is the starting height of block hashes to return + * @return the requested block hashes + */ + virtual std::vector get_block_hashes(const std::vector& block_hashes, uint64_t start_height) { + throw std::runtime_error("monero_daemon::get_block_hashes(): not supported"); + } + + /** + * Get a transaction by hash. + * + * @param tx_hash is the hash of the transaction to get + * @param prune specifies if the returned tx should be pruned (defaults to false) + * @return the transaction with the given hash or null if not found + */ + virtual std::shared_ptr get_tx(const std::string& tx_hash, bool prune = false) { + std::vector hashes; + hashes.push_back(tx_hash); + auto txs = get_txs(hashes, prune); + std::shared_ptr tx; + + if (txs.size() > 0) { + tx = txs[0]; + } + + return tx; + } + + /** + * Get transactions by hashes. + * + * @param tx_hashes are hashes of transactions to get + * @param prune specifies if the returned txs should be pruned (defaults to false) + * @return found transactions with the given hashes + */ + virtual std::vector> get_txs(const std::vector& tx_hashes, bool prune = false) { + throw std::runtime_error("monero_daemon::get_txs(): not supported"); + } + + + /** + * Get a transaction hex by hash. + * + * @param tx_hash is the hash of the transaction to get hex from + * @param prune specifies if the returned tx hex should be pruned (defaults to false) + * @return the tx hex with the given hash + */ + virtual boost::optional get_tx_hex(const std::string& tx_hash, bool prune = false) { + std::vector hashes; + hashes.push_back(tx_hash); + auto hexes = get_tx_hexes(hashes, prune); + boost::optional hex; + if (hexes.size() > 0) { + hex = hexes[0]; + } + + return hex; + } + + /** + * Get transaction hexes by hashes. + * + * @param tx_hashes are hashes of transactions to get hexes from + * @param prune specifies if the returned tx hexes should be pruned (defaults to false) + * @return are the tx hexes + */ + virtual std::vector get_tx_hexes(const std::vector& tx_hashes, bool prune = false) { + throw std::runtime_error("monero_daemon::get_tx_hexes(): not supported"); + } + + /** + * Gets the total emissions and fees from the genesis block to the current height. + * + * @param height is the height to start computing the miner sum + * @param num_blocks are the number of blocks to include in the sum + * @return the sum emission and fees since the geneis block + */ + virtual std::shared_ptr get_miner_tx_sum(uint64_t height, uint64_t num_blocks) { + throw std::runtime_error("monero_daemon::get_miner_tx_sum(): not supported"); + } + + /** + * Get mining fee estimates per kB. + * + * @param grace_blocks TODO + * @return mining fee estimates per kB + */ + virtual std::shared_ptr get_fee_estimate(uint64_t grace_blocks = 0) { + throw std::runtime_error("monero_daemon::get_fee_estimate(): not supported"); + } + + /** + * Submits a transaction to the daemon's pool. + * + * @param tx_hex is the raw transaction hex to submit + * @param do_not_relay specifies if the tx should be relayed (optional) + * @return the submission results + */ + virtual std::shared_ptr submit_tx_hex(const std::string& tx_hex, bool do_not_relay = false) { + throw std::runtime_error("monero_daemon::submit_tx_hex(): not supported"); + } + + /** + * Relays a transaction by hash. + * + * @param tx_hash identifies the transaction to relay + */ + virtual void relay_tx_by_hash(const std::string& tx_hash) { + std::vector tx_hashes; + tx_hashes.push_back(tx_hash); + relay_txs_by_hash(tx_hashes); + } + + /** + * Relays transactions by hash. + * + * @param tx_hashes identify the transactions to relay + */ + virtual void relay_txs_by_hash(const std::vector& tx_hashes) { + throw std::runtime_error("monero_daemon::relay_txs_by_hash(): not supported"); + } + + /** + * Get valid transactions seen by the node but not yet mined into a block, as well + * as spent key image information for the tx pool. + * + * @return transactions in the transaction pool + */ + virtual std::vector> get_tx_pool() { + throw std::runtime_error("monero_daemon::get_tx_pool(): not supported"); + } + + /** + * Get hashes of transactions in the transaction pool. + * + * @return hashes of transactions in the transaction pool + */ + virtual std::vector get_tx_pool_hashes() { + throw std::runtime_error("monero_daemon::get_tx_pool_hashes(): not supported"); + } + + /** + * Get all transaction pool backlog. + * + * @return transaction pool backlog entries + */ + virtual std::vector get_tx_pool_backlog() { + throw std::runtime_error("monero_daemon::get_tx_pool_backlog(): not supported"); + } + + /** + * Get transaction pool statistics. + * + * @return statistics about the transaction pool + */ + virtual std::shared_ptr get_tx_pool_stats() { + throw std::runtime_error("monero_daemon::get_tx_pool_stats(): not supported"); + } + + /** + * Flushes all transactions from the tx pool. + */ + virtual void flush_tx_pool() { + throw std::runtime_error("monero_daemon::flush_tx_pool(): not supported"); + } + + /** + * Flush transactions from the tx pool. + * + * @param hashes are hashes of transactions to flush + */ + virtual void flush_tx_pool(const std::vector &hashes) { + throw std::runtime_error("monero_daemon::flush_tx_pool(): not supported"); + } + + /** + * Flush a single transaction from the tx pool. + * + * @param hash is the hash of transaction to flush + */ + virtual void flush_tx_pool(const std::string &hash) { + throw std::runtime_error("monero_daemon::flush_tx_pool(): not supported"); + } + + /** + * Get the spent status of the given key image. + * + * @param key_image is key image hex to get the status of + * @return the status of the key image + */ + virtual monero_key_image_spent_status get_key_image_spent_status(const std::string& key_image) { + std::vector key_images; + key_images.push_back(key_image); + auto statuses = get_key_image_spent_statuses(key_images); + if (statuses.empty()) throw std::runtime_error("Could not get key image spent status"); + return statuses[0]; + } + + /** + * Get the spent status of each given key image. + * + * @param key_image are hex key images to get the statuses of + * @return the spent status for each key image + */ + virtual std::vector get_key_image_spent_statuses(const std::vector& key_images) { + throw std::runtime_error("monero_daemon::get_key_image_spent_statuses(): not supported"); + } + + /** + * Get outputs identified by a list of output amounts and indices as a binary + * request. + * + * @param outputs identify each output by amount and index + * @return the identified outputs + */ + virtual std::vector> get_outputs(const std::vector& outputs) { + throw std::runtime_error("monero_daemon::get_outputs(): not supported"); + } + + /** + * Get a histogram of output amounts. For all amounts (possibly filtered by + * parameters), gives the number of outputs on the chain for that amount. + * RingCT outputs counts as 0 amount. + * + * @param amounts are amounts of outputs to make the histogram with + * @param min_count TODO + * @param max_count TODO + * @param is_unlocked makes a histogram with outputs with the specified lock state + * @param recent_cutoff TODO + * @return output histogram entries meeting the parameters + */ + virtual std::vector> get_output_histogram(const std::vector& amounts, const boost::optional& min_count, const boost::optional& max_count, const boost::optional& is_unlocked, const boost::optional& recent_cutoff) { + throw std::runtime_error("monero_daemon::get_output_histogram(): not supported"); + } + + /** + * Creates an output distribution. + * + * @param amounts are amounts of outputs to make the distribution with + * @param is_cumulative specifies if the results should be cumulative (defaults to TODO) + * @param start_height is the start height lower bound inclusive (optional) + * @param end_height is the end height upper bound inclusive (optional) + * @return output distribution entries meeting the parameters + */ + virtual std::vector> get_output_distribution(const std::vector& amounts, const boost::optional& is_cumulative = boost::none, const boost::optional& start_height = boost::none, const boost::optional& end_height = boost::none) { + throw std::runtime_error("monero_daemon::get_output_distribution(): not supported"); + } + + /** + * Get general information about the state of the node and the network. + * + * @return general information about the node and network + */ + virtual std::shared_ptr get_info() { + throw std::runtime_error("monero_daemon::get_info(): not supported"); + } + + /** + * Get synchronization information. + * + * @return contains sync information + */ + virtual std::shared_ptr get_sync_info() { + throw std::runtime_error("monero_daemon::get_sync_info(): not supported"); + } + + /** + * Look up information regarding hard fork voting and readiness. + * + * @return hard fork information + */ + virtual std::shared_ptr get_hard_fork_info() { + throw std::runtime_error("monero_daemon::get_hard_fork_info(): not supported"); + } + + /** + * Get alternative chains seen by the node. + * + * @return alternative chains seen by the node + */ + virtual std::vector> get_alt_chains() { + throw std::runtime_error("monero_daemon::get_alt_chains(): not supported"); + } + + /** + * Get known block hashes which are not on the main chain. + * + * @return known block hashes which are not on the main chain + */ + virtual std::vector get_alt_block_hashes() { + throw std::runtime_error("monero_daemon::get_alt_block_hashes(): not supported"); + } + + /** + * Get the download bandwidth limit. + * + * @return is the download bandwidth limit + */ + virtual int get_download_limit() { + throw std::runtime_error("monero_daemon::get_download_limit(): not supported"); + } + + /** + * Set the download bandwidth limit. + * + * @param limit is the download limit to set (-1 to reset to default) + * @return int is the new download limit after setting + */ + virtual int set_download_limit(int limit) { + throw std::runtime_error("monero_daemon::set_download_limit(): not supported"); + } + + /** + * Reset the download bandwidth limit. + * + * @return the download bandwidth limit after resetting + */ + virtual int reset_download_limit() { + throw std::runtime_error("monero_daemon::reset_download_limit(): not supported"); + } + + /** + * Get the upload bandwidth limit. + * + * @return is the upload bandwidth limit + */ + virtual int get_upload_limit() { + throw std::runtime_error("monero_daemon::get_upload_limit(): not supported"); + } + + /** + * Set the upload bandwidth limit. + * + * @param limit is the upload limit to set (-1 to reset to default) + * @return int is the new upload limit after setting + */ + virtual int set_upload_limit(int limit) { + throw std::runtime_error("monero_daemon::set_upload_limit(): not supported"); + } + + /** + * Reset the upload bandwidth limit. + * + * @return the upload bandwidth limit after resetting + */ + virtual int reset_upload_limit() { + throw std::runtime_error("monero_daemon::reset_upload_limit(): not supported"); + } + + /** + * Get peers with active incoming or outgoing connections to the node. + * + * @return the daemon's peers + */ + virtual std::vector> get_peers() { + throw std::runtime_error("monero_daemon::get_peers(): not supported"); + } + + /** + * Get all known peers including their last known online status. + * + * @return the daemon's known peers + */ + virtual std::vector> get_known_peers() { + throw std::runtime_error("monero_daemon::get_known_peers(): not supported"); + } + + /** + * Limit number of outgoing peers. + * + * @param limit is the maximum number of outgoing peers + */ + virtual void set_outgoing_peer_limit(int limit) { + throw std::runtime_error("monero_daemon::set_outgoing_peer_limit(): not supported"); + } + + /** + * Limit number of incoming peers. + * + * @param limit is the maximum number of incoming peers + */ + virtual void set_incoming_peer_limit(int limit) { + throw std::runtime_error("monero_daemon::set_incoming_peer_limit(): not supported"); + } + + /** + * Get peer bans. + * + * @return entries about banned peers + */ + virtual std::vector> get_peer_bans() { + throw std::runtime_error("monero_daemon::get_peer_bans(): not supported"); + } + + /** + * Ban peers nodes. + * + * @param bans are bans to apply against peer nodes + */ + virtual void set_peer_bans(const std::vector>& bans) { + throw std::runtime_error("monero_daemon::set_peer_bans(): not supported"); + } + + /** + * Ban a peer node. + * + * @param ban contains information about a node to ban + */ + virtual void set_peer_ban(const std::shared_ptr& ban) { + if (ban == nullptr) throw std::runtime_error("Ban is none"); + std::vector> bans; + bans.push_back(ban); + set_peer_bans(bans); + } + + /** + * Start mining. + * + * @param address is the address given miner rewards if the daemon mines a block + * @param num_threads is the number of mining threads to run + * @param is_background specifies if the miner should run in the background or not + * @param ignore_battery specifies if the battery state (e.g. on laptop) should be ignored or not + */ + virtual void start_mining(const std::string &address, boost::optional num_threads, boost::optional is_background, boost::optional ignore_battery) { + throw std::runtime_error("monero_daemon::start_mining(): not supported"); + } + + /** + * Stop mining. + */ + virtual void stop_mining() { + throw std::runtime_error("monero_daemon::stop_mining(): not supported"); + } + + /** + * Get the daemon's mining status. + * + * @return the daemon's mining status + */ + virtual std::shared_ptr get_mining_status() { + throw std::runtime_error("monero_daemon::get_mining_status(): not supported"); + } + + /** + * Generate blocks to a wallet address (regtest only). + * + * @param wallet_address is the address of the wallet to receive miner transactions if block is successfully mined + * @param num_blocks is the number of blocks to generate + * @param prev_block_hash is the hash of the previous block to build on top of (optional, builds on the current tip if not given) + * @param starting_nonce is the starting nonce to use (optional) + * @return the result of generating blocks; height is the height of the last block generated + */ + virtual std::shared_ptr generate_blocks(const std::string& wallet_address, uint64_t num_blocks, const boost::optional& prev_block_hash = boost::none, const boost::optional& starting_nonce = boost::none) { + throw std::runtime_error("monero_daemon::generate_blocks(): not supported"); + } + + /** + * Submit a mined block to the network. + * + * @param block_blob is the mined block to submit + */ + virtual void submit_block(const std::string& block_blob) { + std::vector block_blobs; + block_blobs.push_back(block_blob); + return submit_blocks(block_blobs); + } + + /** + * Submit mined blocks to the network. + * + * @param block_blobs are the mined blocks to submit + */ + virtual void submit_blocks(const std::vector& block_blobs) { + throw std::runtime_error("monero_daemon::submit_blocks(): not supported"); + } + + /** + * Prune the blockchain. + * + * @param check specifies to check the pruning (default false) + * @return the prune result + */ + virtual std::shared_ptr prune_blockchain(bool check) { + throw std::runtime_error("monero_daemon::prune_blockchain(): not supported"); + } + + /** + * Check for update. + * + * @return the result of the update check + */ + virtual std::shared_ptr check_for_update() { + throw std::runtime_error("monero_daemon::check_for_update(): not supported"); + } + + /** + * Download an update. + * + * @param path is the path to download the update (optional) + * @return the result of the update download + */ + virtual std::shared_ptr download_update(const std::string& path = "") { + throw std::runtime_error("monero_daemon::download_update(): not supported"); + } + + /** + * Safely disconnect and shut down the daemon. + */ + virtual void stop() { + throw std::runtime_error("monero_daemon::stop(): not supported"); + } + + /** + * Get the header of the next block added to the chain. + * + * @return the header of the next block added to the chain + */ + virtual std::shared_ptr wait_for_next_block_header() { + throw std::runtime_error("monero_daemon::wait_for_next_block_header(): not supported"); + } + + }; +} diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon_model.cpp b/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon_model.cpp new file mode 100644 index 0000000..c27df17 --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon_model.cpp @@ -0,0 +1,1508 @@ +/** + * Copyright (c) woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ + +#include "monero_daemon_model.h" + +#include "common/monero_error.h" +#include "utils/gen_utils.h" +#include "utils/monero_utils.h" +#include "include_base_utils.h" +#include "common/util.h" + +/** + * Public library interface. + */ +namespace monero { + + // ----------------------- UNDECLARED PRIVATE HELPERS ----------------------- + + void merge_tx(std::vector>& txs, const std::shared_ptr& tx) { + for (const std::shared_ptr& aTx : txs) { + if (aTx->m_hash.get() == tx->m_hash.get()) { + aTx->merge(aTx, tx); + return; + } + } + txs.push_back(tx); + } + + // ------------------------- INITIALIZE CONSTANTS --------------------------- + + const std::string monero_tx::DEFAULT_PAYMENT_ID = std::string("0000000000000000"); + + const std::string monero_tx::DEFAULT_ID = std::string("0000000000000000000000000000000000000000000000000000000000000000"); + + // ------------------------- SERIALIZABLE STRUCT ---------------------------- + + std::string serializable_struct::serialize() const { + rapidjson::Document doc; + doc.SetObject(); + rapidjson::Value val = to_rapidjson_val(doc.GetAllocator()); + val.Swap(doc); + return gen_utils::serialize(doc); + } + + // ----------------------------- MONERO VERSION ----------------------------- + + rapidjson::Value monero_version::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_number != boost::none) monero_utils::add_json_member("number", m_number.get(), allocator, root, value_num); + + // set bool values + if (m_is_release != boost::none) monero_utils::add_json_member("isRelease", m_is_release.get(), allocator, root); + + // return root + return root; + } + + void monero_version::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& version) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("number")) version->m_number = it->second.get_value(); + else if (key == std::string("isRelease")) version->m_is_release = it->second.get_value(); + } + } + + // --------------------------- SSL OPTIONS --------------------------- + + rapidjson::Value ssl_options::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_ssl_private_key_path != boost::none) monero_utils::add_json_member("sslPrivateKeyPath", m_ssl_private_key_path.get(), allocator, root, value_str); + if (m_ssl_certificate_path != boost::none) monero_utils::add_json_member("sslCertificatePath", m_ssl_certificate_path.get(), allocator, root, value_str); + if (m_ssl_ca_file != boost::none) monero_utils::add_json_member("sslCaFile", m_ssl_ca_file.get(), allocator, root, value_str); + if (!m_ssl_allowed_fingerprints.empty()) root.AddMember("sslAllowedFingerprints", monero_utils::to_rapidjson_val(allocator, m_ssl_allowed_fingerprints), allocator); + + // set bool values + if (m_ssl_allow_any_cert != boost::none) monero_utils::add_json_member("sslAllowAnyCert", m_ssl_allow_any_cert.get(), allocator, root); + + return root; + } + + // ------------------------- MONERO BLOCK HEADER ---------------------------- + + rapidjson::Value monero_block_header::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_height != boost::none) monero_utils::add_json_member("height", m_height.get(), allocator, root, value_num); + if (m_timestamp != boost::none) monero_utils::add_json_member("timestamp", m_timestamp.get(), allocator, root, value_num); + if (m_size != boost::none) monero_utils::add_json_member("size", m_size.get(), allocator, root, value_num); + if (m_weight != boost::none) monero_utils::add_json_member("weight", m_weight.get(), allocator, root, value_num); + if (m_long_term_weight != boost::none) monero_utils::add_json_member("longTermWeight", m_long_term_weight.get(), allocator, root, value_num); + if (m_depth != boost::none) monero_utils::add_json_member("depth", m_depth.get(), allocator, root, value_num); + if (m_difficulty_low != boost::none) monero_utils::add_json_member("difficultyLow", m_difficulty_low.get(), allocator, root, value_num); + if (m_difficulty_high != boost::none) monero_utils::add_json_member("difficultyHigh", m_difficulty_high.get(), allocator, root, value_num); + if (m_cumulative_difficulty_low != boost::none) monero_utils::add_json_member("cumulativeDifficultyLow", m_cumulative_difficulty_low.get(), allocator, root, value_num); + if (m_cumulative_difficulty_high != boost::none) monero_utils::add_json_member("cumulativeDifficultyHigh", m_cumulative_difficulty_high.get(), allocator, root, value_num); + if (m_major_version != boost::none) monero_utils::add_json_member("majorVersion", m_major_version.get(), allocator, root, value_num); + if (m_minor_version != boost::none) monero_utils::add_json_member("minorVersion", m_minor_version.get(), allocator, root, value_num); + if (m_nonce != boost::none) monero_utils::add_json_member("nonce", m_nonce.get(), allocator, root, value_num); + if (m_miner_tx_hash != boost::none) monero_utils::add_json_member("minerTxHash", m_miner_tx_hash.get(), allocator, root, value_num); + if (m_num_txs != boost::none) monero_utils::add_json_member("numTxs", m_num_txs.get(), allocator, root, value_num); + if (m_reward != boost::none) monero_utils::add_json_member("reward", m_reward.get(), allocator, root, value_num); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_hash != boost::none) monero_utils::add_json_member("hash", m_hash.get(), allocator, root, value_str); + if (m_prev_hash != boost::none) monero_utils::add_json_member("prevHash", m_prev_hash.get(), allocator, root, value_str); + if (m_pow_hash != boost::none) monero_utils::add_json_member("powHash", m_pow_hash.get(), allocator, root, value_str); + + // set bool values + if (m_orphan_status != boost::none) monero_utils::add_json_member("orphanStatus", m_orphan_status.get(), allocator, root); + + // return root + return root; + } + + std::shared_ptr monero_block_header::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + if (this != src.get()) throw std::runtime_error("this block header != src"); + tgt->m_hash = src->m_hash; + tgt->m_height = src->m_height; + tgt->m_timestamp = src->m_timestamp; + tgt->m_size = src->m_size; + tgt->m_weight = src->m_weight; + tgt->m_long_term_weight = src->m_long_term_weight; + tgt->m_depth = src->m_depth; + tgt->m_difficulty_low = src->m_difficulty_low; + tgt->m_difficulty_high = src->m_difficulty_high; + tgt->m_cumulative_difficulty_low = src->m_cumulative_difficulty_low; + tgt->m_cumulative_difficulty_high = src->m_cumulative_difficulty_high; + tgt->m_major_version = src->m_major_version; + tgt->m_minor_version = src->m_minor_version; + tgt->m_nonce = src->m_nonce; + tgt->m_miner_tx_hash = src->m_miner_tx_hash; + tgt->m_num_txs = src->m_num_txs; + tgt->m_orphan_status = src->m_orphan_status; + tgt->m_prev_hash = src->m_prev_hash; + tgt->m_reward = src->m_reward; + tgt->m_pow_hash = src->m_pow_hash; + tgt->m_hash = src->m_hash; + return tgt; + } + + void monero_block_header::merge(const std::shared_ptr& self, const std::shared_ptr& other) { + if (this != self.get()) throw std::runtime_error("this != self"); + if (self == other) return; + m_hash = gen_utils::reconcile(m_hash, other->m_hash, "block header m_hash"); + m_height = gen_utils::reconcile(m_height, other->m_height, boost::none, boost::none, true, "block header m_height"); // height can increase + m_timestamp = gen_utils::reconcile(m_timestamp, other->m_timestamp, boost::none, boost::none, true, "block header m_timestamp"); // timestamp can increase + m_size = gen_utils::reconcile(m_size, other->m_size, "block header m_size"); + m_weight = gen_utils::reconcile(m_weight, other->m_weight, "block header m_weight "); + m_long_term_weight = gen_utils::reconcile(m_long_term_weight, other->m_long_term_weight, "block header m_long_term_weight"); + m_depth = gen_utils::reconcile(m_depth, other->m_depth, "block header m_depth"); + m_difficulty_low = gen_utils::reconcile(m_difficulty_low, other->m_difficulty_low, "block header m_difficulty_low"); + m_difficulty_high = gen_utils::reconcile(m_difficulty_high, other->m_difficulty_high, "block header m_difficulty_high"); + m_cumulative_difficulty_low = gen_utils::reconcile(m_cumulative_difficulty_low, other->m_cumulative_difficulty_low, "block header m_cumulative_difficulty_low"); + m_cumulative_difficulty_high = gen_utils::reconcile(m_cumulative_difficulty_high, other->m_cumulative_difficulty_high, "block header m_cumulative_difficulty_high"); + m_major_version = gen_utils::reconcile(m_major_version, other->m_major_version, "block header m_major_version"); + m_minor_version = gen_utils::reconcile(m_minor_version, other->m_minor_version, "block header m_minor_version"); + m_nonce = gen_utils::reconcile(m_nonce, other->m_nonce, "block header m_nonce"); + m_miner_tx_hash = gen_utils::reconcile(m_miner_tx_hash, other->m_miner_tx_hash, "block header m_miner_tx_hash"); + m_num_txs = gen_utils::reconcile(m_num_txs, other->m_num_txs, "block header m_num_txs"); + m_orphan_status = gen_utils::reconcile(m_orphan_status, other->m_orphan_status, "block header m_orphan_status"); + m_prev_hash = gen_utils::reconcile(m_prev_hash, other->m_prev_hash, "block header m_prev_hash"); + m_reward = gen_utils::reconcile(m_reward, other->m_reward, "block header m_reward"); + m_pow_hash = gen_utils::reconcile(m_pow_hash, other->m_pow_hash, "block header m_pow_hash"); + } + + // ----------------------------- MONERO BLOCK ------------------------------- + + rapidjson::Value monero_block::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // serialize root from superclass + rapidjson::Value root = monero_block_header::to_rapidjson_val(allocator); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_hex != boost::none) monero_utils::add_json_member("hex", m_hex.get(), allocator, root, value_str); + + // set sub-arrays + if (!m_txs.empty()) root.AddMember("txs", monero_utils::to_rapidjson_val(allocator, m_txs), allocator); + if (!m_tx_hashes.empty()) root.AddMember("txHashes", monero_utils::to_rapidjson_val(allocator, m_tx_hashes), allocator); + + // set sub-objects + if (m_miner_tx != nullptr) root.AddMember("minerTx", m_miner_tx->to_rapidjson_val(allocator), allocator); + + // return root + return root; + } + + std::shared_ptr monero_block::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + if (this != src.get()) throw std::runtime_error("this block != src"); + monero_block_header::copy(std::static_pointer_cast(src), std::static_pointer_cast(tgt)); + tgt->m_hex = src->m_hex; + if (src->m_miner_tx) { + tgt->m_miner_tx = src->m_miner_tx->copy(src->m_miner_tx, std::make_shared()); + tgt->m_miner_tx->m_block = tgt; + } + if (!src->m_txs.empty()) { + bool use_wallet_types = std::dynamic_pointer_cast(src->m_txs[0]) != 0; + tgt->m_txs = std::vector>(); + for (const auto& tx : src->m_txs) { + if (use_wallet_types) { + std::shared_ptr tx_wallet = std::static_pointer_cast(tx); + std::shared_ptr tx_copy = tx_wallet->copy(tx_wallet, std::make_shared()); + tx_copy->m_block = tgt; + tgt->m_txs.push_back(tx_copy); + } else { + std::shared_ptr tx_copy = tx->copy(tx, std::make_shared()); + tx_copy->m_block = tgt; + tgt->m_txs.push_back(tx_copy); + } + } + } + if (!src->m_tx_hashes.empty()) tgt->m_tx_hashes = std::vector(src->m_tx_hashes); + return tgt; + } + + void monero_block::merge(const std::shared_ptr& self, const std::shared_ptr& other) { + merge(std::static_pointer_cast(self), std::static_pointer_cast(other)); + } + + void monero_block::merge(const std::shared_ptr& self, const std::shared_ptr& other) { + if (this != self.get()) throw std::runtime_error("this != self"); + if (self == other) return; + + // merge header fields + monero_block_header::merge(self, other); + + // merge reconcilable block extensions + m_hex = gen_utils::reconcile(m_hex, other->m_hex, "block m_hex"); + m_tx_hashes = gen_utils::reconcile(m_tx_hashes, other->m_tx_hashes, "block m_tx_hahes"); + + // merge miner tx + if (m_miner_tx == nullptr) m_miner_tx = other->m_miner_tx; + if (other->m_miner_tx != nullptr) { + other->m_miner_tx->m_block = self; + m_miner_tx->merge(m_miner_tx, other->m_miner_tx); + } + + // merge non-miner txs + if (!other->m_txs.empty()) { + for (const std::shared_ptr otherTx : other->m_txs) { // NOTE: not using reference so std::shared_ptr is not deleted when block is dereferenced + otherTx->m_block = self; + merge_tx(self->m_txs, otherTx); + } + } + } + + // ------------------------------- MONERO TX -------------------------------- + + rapidjson::Value monero_tx::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_version != boost::none) monero_utils::add_json_member("version", m_version.get(), allocator, root, value_num); + if (m_fee != boost::none) monero_utils::add_json_member("fee", m_fee.get(), allocator, root, value_num); + if (m_ring_size != boost::none) monero_utils::add_json_member("ringSize", m_ring_size.get(), allocator, root, value_num); + if (m_num_confirmations != boost::none) monero_utils::add_json_member("numConfirmations", m_num_confirmations.get(), allocator, root, value_num); + if (m_unlock_time != boost::none) monero_utils::add_json_member("unlockTime", m_unlock_time.get(), allocator, root, value_num); + if (m_last_relayed_timestamp != boost::none) monero_utils::add_json_member("lastRelayedTimestamp", m_last_relayed_timestamp.get(), allocator, root, value_num); + if (m_received_timestamp != boost::none) monero_utils::add_json_member("receivedTimestamp", m_received_timestamp.get(), allocator, root, value_num); + if (m_size != boost::none) monero_utils::add_json_member("size", m_size.get(), allocator, root, value_num); + if (m_weight != boost::none) monero_utils::add_json_member("weight", m_weight.get(), allocator, root, value_num); + if (m_last_failed_height != boost::none) monero_utils::add_json_member("lastFailedHeight", m_last_failed_height.get(), allocator, root, value_num); + if (m_max_used_block_height != boost::none) monero_utils::add_json_member("maxUsedBlockHeight", m_max_used_block_height.get(), allocator, root, value_num); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_hash != boost::none) monero_utils::add_json_member("hash", m_hash.get(), allocator, root, value_str); + if (m_payment_id != boost::none) monero_utils::add_json_member("paymentId", m_payment_id.get(), allocator, root, value_str); + if (m_key != boost::none) monero_utils::add_json_member("key", m_key.get(), allocator, root, value_str); + if (m_full_hex != boost::none) monero_utils::add_json_member("fullHex", m_full_hex.get(), allocator, root, value_str); + if (m_pruned_hex != boost::none) monero_utils::add_json_member("prunedHex", m_pruned_hex.get(), allocator, root, value_str); + if (m_prunable_hex != boost::none) monero_utils::add_json_member("prunableHex", m_prunable_hex.get(), allocator, root, value_str); + if (m_prunable_hash != boost::none) monero_utils::add_json_member("prunableHash", m_prunable_hash.get(), allocator, root, value_str); + if (m_metadata != boost::none) monero_utils::add_json_member("metadata", m_metadata.get(), allocator, root, value_str); + if (m_common_tx_sets != boost::none) monero_utils::add_json_member("commonTxSets", m_common_tx_sets.get(), allocator, root, value_str); + if (m_rct_signatures != boost::none) monero_utils::add_json_member("rctSignatures", m_rct_signatures.get(), allocator, root, value_str); + if (m_rct_sig_prunable != boost::none) monero_utils::add_json_member("rctSigPrunable", m_rct_sig_prunable.get(), allocator, root, value_str); + if (m_last_failed_hash != boost::none) monero_utils::add_json_member("lastFailedHash", m_last_failed_hash.get(), allocator, root, value_str); + if (m_max_used_block_hash != boost::none) monero_utils::add_json_member("maxUsedBlockHash", m_max_used_block_hash.get(), allocator, root, value_str); + + // set bool values + if (m_is_miner_tx != boost::none) monero_utils::add_json_member("isMinerTx", m_is_miner_tx.get(), allocator, root); + if (m_relay != boost::none) monero_utils::add_json_member("relay", m_relay.get(), allocator, root); + if (m_is_relayed != boost::none) monero_utils::add_json_member("isRelayed", m_is_relayed.get(), allocator, root); + if (m_is_confirmed != boost::none) monero_utils::add_json_member("isConfirmed", m_is_confirmed.get(), allocator, root); + if (m_in_tx_pool != boost::none) monero_utils::add_json_member("inTxPool", m_in_tx_pool.get(), allocator, root); + if (m_is_double_spend_seen != boost::none) monero_utils::add_json_member("isDoubleSpendSeen", m_is_double_spend_seen.get(), allocator, root); + if (m_is_kept_by_block != boost::none) monero_utils::add_json_member("isKeptByBlock", m_is_kept_by_block.get(), allocator, root); + if (m_is_failed != boost::none) monero_utils::add_json_member("isFailed", m_is_failed.get(), allocator, root); + + // set sub-arrays + if (!m_inputs.empty()) root.AddMember("inputs", monero_utils::to_rapidjson_val(allocator, m_inputs), allocator); + if (!m_outputs.empty()) root.AddMember("outputs", monero_utils::to_rapidjson_val(allocator, m_outputs), allocator); + if (!m_output_indices.empty()) root.AddMember("outputIndices", monero_utils::to_rapidjson_val(allocator, m_output_indices), allocator); + if (!m_extra.empty()) root.AddMember("extra", monero_utils::to_rapidjson_val(allocator, m_extra), allocator); + if (!m_signatures.empty()) root.AddMember("signatures", monero_utils::to_rapidjson_val(allocator, m_signatures), allocator); + + // return root + return root; + } + + void monero_tx::from_property_tree(const boost::property_tree::ptree& node, std::shared_ptr tx) { + + // initialize tx from node + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("hash")) tx->m_hash = it->second.data(); + else if (key == std::string("version")) throw std::runtime_error("version deserialization not implemented"); + else if (key == std::string("isMinerTx")) tx->m_is_miner_tx = it->second.get_value(); + else if (key == std::string("paymentId")) tx->m_payment_id = it->second.data(); + else if (key == std::string("fee")) tx->m_fee = it->second.get_value(); + else if (key == std::string("mixin")) throw std::runtime_error("mixin deserialization not implemented"); + else if (key == std::string("relay")) tx->m_relay = it->second.get_value(); + else if (key == std::string("isRelayed")) tx->m_is_relayed = it->second.get_value(); + else if (key == std::string("isConfirmed")) tx->m_is_confirmed = it->second.get_value(); + else if (key == std::string("inTxPool")) tx->m_in_tx_pool = it->second.get_value(); + else if (key == std::string("numConfirmations")) tx->m_num_confirmations = it->second.get_value(); + else if (key == std::string("unlockTime")) tx->m_unlock_time = it->second.get_value(); + else if (key == std::string("lastRelayedTimestamp")) tx->m_last_relayed_timestamp = it->second.get_value(); + else if (key == std::string("receivedTimestamp")) tx->m_received_timestamp = it->second.get_value(); + else if (key == std::string("isDoubleSpendSeen")) tx->m_is_double_spend_seen = it->second.get_value(); + else if (key == std::string("key")) tx->m_key = it->second.data(); + else if (key == std::string("fullHex")) tx->m_full_hex = it->second.data(); + else if (key == std::string("prunedHex")) tx->m_pruned_hex = it->second.data(); + else if (key == std::string("prunableHex")) tx->m_prunable_hex = it->second.data(); + else if (key == std::string("prunableHash")) tx->m_prunable_hash = it->second.data(); + else if (key == std::string("size")) tx->m_size = it->second.get_value(); + else if (key == std::string("weight")) tx->m_weight = it->second.get_value(); + else if (key == std::string("inputs")) throw std::runtime_error("inputs deserialization not implemented"); + else if (key == std::string("outputs")) throw std::runtime_error("outputs deserialization not implemented"); + else if (key == std::string("outputIndices")) throw std::runtime_error("m_output_indices deserialization not implemented"); + else if (key == std::string("metadata")) tx->m_metadata = it->second.data(); + else if (key == std::string("commonTxSets")) throw std::runtime_error("commonTxSets deserialization not implemented"); + else if (key == std::string("extra")) throw std::runtime_error("extra deserialization not implemented"); + else if (key == std::string("rctSignatures")) throw std::runtime_error("rctSignatures deserialization not implemented"); + else if (key == std::string("rctSigPrunable")) throw std::runtime_error("rctSigPrunable deserialization not implemented"); + else if (key == std::string("isKeptByBlock")) tx->m_is_kept_by_block = it->second.get_value(); + else if (key == std::string("isFailed")) tx->m_is_failed = it->second.get_value(); + else if (key == std::string("lastFailedHeight")) throw std::runtime_error("lastFailedHeight deserialization not implemented"); + else if (key == std::string("lastFailedHash")) tx->m_last_failed_hash = it->second.data(); + else if (key == std::string("maxUsedBlockHeight")) throw std::runtime_error("maxUsedBlockHeight deserialization not implemented"); + else if (key == std::string("maxUsedBlockHash")) tx->m_max_used_block_hash = it->second.data(); + else if (key == std::string("signatures")) throw std::runtime_error("signatures deserialization not implemented"); + } + } + + std::shared_ptr monero_tx::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + MTRACE("monero_tx::copy(const std::shared_ptr& src, const std::shared_ptr& tgt)"); + tgt->m_hash = src->m_hash; + tgt->m_version = src->m_version; + tgt->m_is_miner_tx = src->m_is_miner_tx; + tgt->m_payment_id = src->m_payment_id; + tgt->m_fee = src->m_fee; + tgt->m_ring_size = src->m_ring_size; + tgt->m_relay = src->m_relay; + tgt->m_is_relayed = src->m_is_relayed; + tgt->m_is_confirmed = src->m_is_confirmed; + tgt->m_in_tx_pool = src->m_in_tx_pool; + tgt->m_num_confirmations = src->m_num_confirmations; + tgt->m_unlock_time = src->m_unlock_time; + tgt->m_last_relayed_timestamp = src->m_last_relayed_timestamp; + tgt->m_received_timestamp = src->m_received_timestamp; + tgt->m_is_double_spend_seen = src->m_is_double_spend_seen; + tgt->m_key = src->m_key; + tgt->m_full_hex = src->m_full_hex; + tgt->m_pruned_hex = src->m_pruned_hex; + tgt->m_prunable_hex = src->m_prunable_hex; + tgt->m_prunable_hash = src->m_prunable_hash; + tgt->m_size = src->m_size; + tgt->m_weight = src->m_weight; + if (!src->m_inputs.empty()) { + tgt->m_inputs = std::vector>(); + for (const std::shared_ptr& input : src->m_inputs) { + std::shared_ptr input_copy = input->copy(input, std::make_shared()); + input_copy->m_tx = tgt; + tgt->m_inputs.push_back(input_copy); + } + } + if (!src->m_outputs.empty()) { + tgt->m_outputs = std::vector>(); + for (const std::shared_ptr& output : src->m_outputs) { + std::shared_ptr output_copy = output->copy(output, std::make_shared()); + output_copy->m_tx = tgt; + tgt->m_outputs.push_back(output_copy); + } + } + if (!src->m_output_indices.empty()) tgt->m_output_indices = std::vector(src->m_output_indices); + tgt->m_metadata = src->m_metadata; + tgt->m_common_tx_sets = src->m_common_tx_sets; + if (!src->m_extra.empty()) throw std::runtime_error("extra deep copy not implemented"); // TODO: implement extra + tgt->m_rct_signatures = src->m_rct_signatures; + tgt->m_rct_sig_prunable = src->m_rct_sig_prunable; + tgt->m_is_kept_by_block = src->m_is_kept_by_block; + tgt->m_is_failed = src->m_is_failed; + tgt->m_last_failed_height = src->m_last_failed_height; + tgt->m_last_failed_hash = src->m_last_failed_hash; + tgt->m_max_used_block_height = src->m_max_used_block_height; + tgt->m_max_used_block_hash = src->m_max_used_block_hash; + if (!src->m_signatures.empty()) tgt->m_signatures = std::vector(src->m_signatures); + return tgt; + } + + boost::optional monero_tx::get_height() const { + if (m_block == nullptr) return boost::none; + return *(m_block->m_height); + } + + void monero_tx::merge(const std::shared_ptr& self, const std::shared_ptr& other) { + if (this != self.get()) throw std::runtime_error("this != self"); + if (self == other) return; + + // merge blocks if they're different + if (m_block != other->m_block) { + if (m_block == nullptr) { + m_block = other->m_block; + std::replace(m_block->m_txs.begin(), m_block->m_txs.end(), other, self); // update block to point to this tx + } else if (other->m_block != nullptr) { + m_block->merge(m_block, other->m_block); // comes back to merging txs + return; + } + } + + // otherwise merge tx fields + m_hash = gen_utils::reconcile(m_hash, other->m_hash, "tx m_hash"); + m_version = gen_utils::reconcile(m_version, other->m_version, "tx m_version"); + m_payment_id = gen_utils::reconcile(m_payment_id, other->m_payment_id, "tx m_payment_id"); + m_fee = gen_utils::reconcile(m_fee, other->m_fee, "tx m_fee"); + m_ring_size = gen_utils::reconcile(m_ring_size, other->m_ring_size, "tx m_ring_size"); + m_is_confirmed = gen_utils::reconcile(m_is_confirmed, other->m_is_confirmed, boost::none, true, boost::none, "tx m_is_confirmed"); // tx can become confirmed + m_is_miner_tx = gen_utils::reconcile(m_is_miner_tx, other->m_is_miner_tx, "tx m_is_miner_tx"); + m_relay = gen_utils::reconcile(m_relay, other->m_relay, "tx m_relay"); + m_is_relayed = gen_utils::reconcile(m_is_relayed, other->m_is_relayed, "tx m_is_relayed"); + m_is_double_spend_seen = gen_utils::reconcile(m_is_double_spend_seen, other->m_is_double_spend_seen, boost::none, true, boost::none, "tx m_is_double_spend_seen"); // double spend can become seen + m_key = gen_utils::reconcile(m_key, other->m_key, "tx m_key"); + m_full_hex = gen_utils::reconcile(m_full_hex, other->m_full_hex, "tx m_full_hex"); + m_pruned_hex = gen_utils::reconcile(m_pruned_hex, other->m_pruned_hex, "tx m_pruned_hex"); + m_prunable_hex = gen_utils::reconcile(m_prunable_hex, other->m_prunable_hex, "tx m_prunable_hex"); + m_prunable_hash = gen_utils::reconcile(m_prunable_hash, other->m_prunable_hash, "tx m_prunable_hash"); + m_size = gen_utils::reconcile(m_size, other->m_size, "tx m_size"); + m_weight = gen_utils::reconcile(m_weight, other->m_weight, "tx m_weight"); + //m_output_indices = gen_utils::reconcile(m_output_indices, other->m_output_indices, "tx m_output_indices"); // TODO + m_metadata = gen_utils::reconcile(m_metadata, other->m_metadata, "tx m_metadata"); + m_common_tx_sets = gen_utils::reconcile(m_common_tx_sets, other->m_common_tx_sets, "tx m_common_tx_sets"); + //m_extra = gen_utils::reconcile(m_extra, other->m_extra, "tx m_extra"); // TODO + m_rct_signatures = gen_utils::reconcile(m_rct_signatures, other->m_rct_signatures, "tx m_rct_signatures"); + m_rct_sig_prunable = gen_utils::reconcile(m_rct_sig_prunable, other->m_rct_sig_prunable, "tx m_rct_sig_prunable"); + m_is_kept_by_block = gen_utils::reconcile(m_is_kept_by_block, other->m_is_kept_by_block, "tx m_is_kept_by_block"); + m_is_failed = gen_utils::reconcile(m_is_failed, other->m_is_failed, "tx m_is_failed"); + m_last_failed_height = gen_utils::reconcile(m_last_failed_height, other->m_last_failed_height, "tx m_last_failed_height"); + m_last_failed_hash = gen_utils::reconcile(m_last_failed_hash, other->m_last_failed_hash, "tx m_last_failed_hash"); + m_max_used_block_height = gen_utils::reconcile(m_max_used_block_height, other->m_max_used_block_height, "tx m_max_used_block_height"); + m_max_used_block_hash = gen_utils::reconcile(m_max_used_block_hash, other->m_max_used_block_hash, "tx m_max_used_block_hash"); + //m_signatures = gen_utils::reconcile(m_signatures, other->m_signatures, "tx m_signatures"); // TODO + m_unlock_time = gen_utils::reconcile(m_unlock_time, other->m_unlock_time, "tx m_unlock_time"); + m_num_confirmations = gen_utils::reconcile(m_num_confirmations, other->m_num_confirmations, boost::none, boost::none, true, "tx m_num_confirmations"); // num confirmations can increase + + // merge inputs + if (!other->m_inputs.empty()) { + for (const std::shared_ptr& merger : other->m_inputs) { + bool merged = false; + merger->m_tx = self; + for (const std::shared_ptr& mergee : m_inputs) { + if (mergee->m_key_image->m_hex == merger->m_key_image->m_hex) { + mergee->merge(mergee, merger); + merged = true; + break; + } + } + if (!merged) m_inputs.push_back(merger); + } + } + + // merge outputs + if (!other->m_outputs.empty()) { + for (const std::shared_ptr& output : other->m_outputs) output->m_tx = self; + if (m_outputs.empty()) m_outputs = other->m_outputs; + else { + + // merge outputs if key image or stealth public key present, otherwise append + for (const std::shared_ptr& merger : other->m_outputs) { + bool merged = false; + merger->m_tx = self; + for (const std::shared_ptr& mergee : m_outputs) { + if ((merger->m_key_image != nullptr && mergee->m_key_image->m_hex == merger->m_key_image->m_hex) || + (merger->m_stealth_public_key != boost::none && *mergee->m_stealth_public_key == *merger->m_stealth_public_key)) { + mergee->merge(mergee, merger); + merged = true; + break; + } + } + if (!merged) m_outputs.push_back(merger); // append output + } + } + } + + // handle unrelayed -> relayed -> confirmed + if (*m_is_confirmed) { + m_in_tx_pool = false; + m_received_timestamp = boost::none; + m_last_relayed_timestamp = boost::none; + } else { + m_in_tx_pool = gen_utils::reconcile(m_in_tx_pool, other->m_in_tx_pool, boost::none, true, boost::none, "tx m_in_tx_pool"); // unrelayed -> tx pool + m_received_timestamp = gen_utils::reconcile(m_received_timestamp, other->m_received_timestamp, boost::none, boost::none, false, "tx m_received_timestamp"); // take earliest receive time + m_last_relayed_timestamp = gen_utils::reconcile(m_last_relayed_timestamp, other->m_last_relayed_timestamp, boost::none, boost::none, true, "tx m_last_relayed_timestamp"); // take latest relay time + } + } + + // --------------------------- MONERO KEY IMAGE ----------------------------- + + rapidjson::Value monero_key_image::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_hex != boost::none) monero_utils::add_json_member("hex", m_hex.get(), allocator, root, value_str); + if (m_signature != boost::none) monero_utils::add_json_member("signature", m_signature.get(), allocator, root, value_str); + + // return root + return root; + } + + void monero_key_image::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& key_image) { + + // initialize key image from node + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("hex")) key_image->m_hex = it->second.data(); + else if (key == std::string("signature")) key_image->m_signature = it->second.data(); + } + } + + std::vector> monero_key_image::deserialize_key_images(const std::string& key_images_json) { + + // deserialize json to property node + std::istringstream iss = key_images_json.empty() ? std::istringstream() : std::istringstream(key_images_json); + boost::property_tree::ptree node; + boost::property_tree::read_json(iss, node); + + // convert property tree to key images + std::vector> key_images; + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("keyImages")) { + for (boost::property_tree::ptree::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) { + std::shared_ptr key_image = std::make_shared(); + monero_key_image::from_property_tree(it2->second, key_image); + key_images.push_back(key_image); + } + } + else MWARNING("WARNING MoneroWalletJni::deserialize_key_images() unrecognized key: " << key); + } + return key_images; + } + + std::shared_ptr monero_key_image::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + if (this != src.get()) throw std::runtime_error("this != src"); + tgt->m_hex = src->m_hex; + tgt->m_signature = src->m_signature; + return tgt; + } + + void monero_key_image::merge(const std::shared_ptr& self, const std::shared_ptr& other) { + MTRACE("monero_key_image::merge(self, other)"); + if (this != self.get()) throw std::runtime_error("this != self"); + if (self == other) return; + m_hex = gen_utils::reconcile(m_hex, other->m_hex, "key image m_hex"); + m_signature = gen_utils::reconcile(m_signature, other->m_signature, "key image m_signature"); + } + + // ------------------------------ MONERO OUTPUT ----------------------------- + + rapidjson::Value monero_output::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_amount != boost::none) monero_utils::add_json_member("amount", m_amount.get(), allocator, root, value_num); + if (m_index != boost::none) monero_utils::add_json_member("index", m_index.get(), allocator, root, value_num); + if (m_stealth_public_key != boost::none) monero_utils::add_json_member("stealthPublicKey", m_stealth_public_key.get(), allocator, root, value_num); + + // set sub-arrays + if (!m_ring_output_indices.empty()) root.AddMember("ringOutputIndices", monero_utils::to_rapidjson_val(allocator, m_ring_output_indices), allocator); + + // set sub-objects + if (m_key_image != nullptr) root.AddMember("keyImage", m_key_image->to_rapidjson_val(allocator), allocator); + + // return root + return root; + } + + void monero_output::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& output) { + + // initialize output from node + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("keyImage")) { + output->m_key_image = std::make_shared(); + monero_key_image::from_property_tree(it->second, output->m_key_image); + } + else if (key == std::string("amount")) output->m_amount = it->second.get_value(); + else if (key == std::string("index")) output->m_index = it->second.get_value(); + else if (key == std::string("ringOutputIndices")) throw std::runtime_error("node_to_tx() deserialize ringOutputIndices not implemented"); + else if (key == std::string("stealthPublicKey")) throw std::runtime_error("node_to_tx() deserialize stealthPublicKey not implemented"); + } + } + + std::shared_ptr monero_output::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + if (this != src.get()) throw std::runtime_error("this != src"); + tgt->m_tx = src->m_tx; // reference same parent tx by default + if (src->m_key_image != nullptr) tgt->m_key_image = src->m_key_image->copy(src->m_key_image, std::make_shared()); + tgt->m_amount = src->m_amount; + tgt->m_index = src->m_index; + if (!src->m_ring_output_indices.empty()) tgt->m_ring_output_indices = std::vector(src->m_ring_output_indices); + tgt->m_stealth_public_key = src->m_stealth_public_key; + return tgt; + } + + void monero_output::merge(const std::shared_ptr& self, const std::shared_ptr& other) { + if (this != self.get()) throw std::runtime_error("this != self"); + if (self == other) return; + + // merge txs if they're different which comes back to merging outputs + if (m_tx != other->m_tx) { + m_tx->merge(m_tx, other->m_tx); + return; + } + + // otherwise merge output fields + if (m_key_image == nullptr) m_key_image = other->m_key_image; + else if (other->m_key_image != nullptr) m_key_image->merge(m_key_image, other->m_key_image); + m_amount = gen_utils::reconcile(m_amount, other->m_amount, "output amount"); + m_index = gen_utils::reconcile(m_index, other->m_index, "output index"); + } + + // --------------------------- MONERO RPC PAYMENT INFO --------------------------- + + void monero_rpc_payment_info::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& rpc_payment_info) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if ((key == std::string("topBlockHash")) && !it->second.data().empty()) rpc_payment_info->m_top_block_hash = it->second.data(); + else if (key == std::string("credits")) rpc_payment_info->m_credits = it->second.get_value(); + } + } + + rapidjson::Value monero_rpc_payment_info::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_top_block_hash != boost::none) monero_utils::add_json_member("topBlockHash", m_top_block_hash.get(), allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_credits != boost::none) monero_utils::add_json_member("credits", m_credits.get(), allocator, root, value_num); + + // return root + return root; + } + + // --------------------------- MONERO ALT CHAIN --------------------------- + + void monero_alt_chain::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& alt_chain) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("difficultyLow")) alt_chain->m_difficulty_low = it->second.get_value(); + else if (key == std::string("difficultyHigh")) alt_chain->m_difficulty_high = it->second.get_value(); + else if (key == std::string("blockHashes")) { + for (const auto& child : it->second) alt_chain->m_block_hashes.push_back(child.second.data()); + } + else if (key == std::string("height")) alt_chain->m_height = it->second.get_value(); + else if (key == std::string("length")) alt_chain->m_length = it->second.get_value(); + else if (key == std::string("mainChainParentBlockHash")) alt_chain->m_main_chain_parent_block_hash = it->second.data(); + } + } + + rapidjson::Value monero_alt_chain::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_main_chain_parent_block_hash != boost::none) monero_utils::add_json_member("mainChainParentBlockHash", m_main_chain_parent_block_hash.get(), allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_difficulty_low != boost::none) monero_utils::add_json_member("difficultyLow", m_difficulty_low.get(), allocator, root, value_num); + if (m_difficulty_high != boost::none) monero_utils::add_json_member("difficultyHigh", m_difficulty_high.get(), allocator, root, value_num); + if (m_height != boost::none) monero_utils::add_json_member("height", m_height.get(), allocator, root, value_num); + if (m_length != boost::none) monero_utils::add_json_member("length", m_length.get(), allocator, root, value_num); + + // set sub-arrays + if (!m_block_hashes.empty()) root.AddMember("blockHashes", monero_utils::to_rapidjson_val(allocator, m_block_hashes), allocator); + + // return root + return root; + } + + // --------------------------- MONERO BAN --------------------------- + + void monero_ban::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& ban) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("host")) ban->m_host = it->second.data(); + else if (key == std::string("ip")) ban->m_ip = it->second.get_value(); + else if (key == std::string("isBanned")) ban->m_is_banned = it->second.get_value(); + else if (key == std::string("seconds")) ban->m_seconds = it->second.get_value(); + } + } + + rapidjson::Value monero_ban::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_host != boost::none) monero_utils::add_json_member("host", m_host.get(), allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_ip != boost::none) monero_utils::add_json_member("ip", m_ip.get(), allocator, root, value_num); + if (m_seconds != boost::none) monero_utils::add_json_member("seconds", m_seconds.get(), allocator, root, value_num); + + // set bool values + if (m_is_banned != boost::none) monero_utils::add_json_member("isBanned", m_is_banned.get(), allocator, root); + + // return root + return root; + } + + // --------------------------- MONERO PRUNE RESULT --------------------------- + + void monero_prune_result::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("pruned")) result->m_is_pruned = it->second.get_value(); + else if (key == std::string("pruningSeed")) result->m_pruning_seed = it->second.get_value(); + } + } + + rapidjson::Value monero_prune_result::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_pruning_seed != boost::none) monero_utils::add_json_member("pruningSeed", m_pruning_seed.get(), allocator, root, value_num); + + // set bool values + if (m_is_pruned != boost::none) monero_utils::add_json_member("isPruned", m_is_pruned.get(), allocator, root); + + // return root + return root; + } + + // --------------------------- MONERO MINING STATUS --------------------------- + + void monero_mining_status::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& status) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("isActive")) status->m_is_active = it->second.get_value(); + else if (key == std::string("isBackground")) status->m_is_background = it->second.get_value(); + else if (key == std::string("address") && !it->second.data().empty()) status->m_address = it->second.data(); + else if (key == std::string("speed")) status->m_speed = it->second.get_value(); + else if (key == std::string("numThreads")) status->m_num_threads = it->second.get_value(); + } + + if (status->m_is_active != boost::none && *status->m_is_active == false) { + status->m_is_background = boost::none; + status->m_address = boost::none; + } + } + + rapidjson::Value monero_mining_status::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_speed != boost::none) monero_utils::add_json_member("speed", m_speed.get(), allocator, root, value_num); + if (m_num_threads != boost::none) monero_utils::add_json_member("numThreads", m_num_threads.get(), allocator, root, value_num); + + // set bool values + if (m_is_active != boost::none) monero_utils::add_json_member("isActive", m_is_active.get(), allocator, root); + if (m_is_background != boost::none) monero_utils::add_json_member("isBackground", m_is_background.get(), allocator, root); + + // return root + return root; + } + + // --------------------------- MONERO MINER TX SUM --------------------------- + + void monero_miner_tx_sum::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& sum) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("emissionSumLow")) sum->m_emission_sum_low = it->second.get_value(); + else if (key == std::string("emissionSumHigh")) sum->m_emission_sum_high = it->second.get_value(); + else if (key == std::string("feeSumLow")) sum->m_fee_sum_low = it->second.get_value(); + else if (key == std::string("feeSumHigh")) sum->m_fee_sum_high = it->second.get_value(); + } + } + + rapidjson::Value monero_miner_tx_sum::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_emission_sum_low != boost::none) monero_utils::add_json_member("emissionSumLow", m_emission_sum_low.get(), allocator, root, value_num); + if (m_emission_sum_high != boost::none) monero_utils::add_json_member("emissionSumHigh", m_emission_sum_high.get(), allocator, root, value_num); + if (m_fee_sum_low != boost::none) monero_utils::add_json_member("feeSumLow", m_fee_sum_low.get(), allocator, root, value_num); + if (m_fee_sum_high != boost::none) monero_utils::add_json_member("feeSumHigh", m_fee_sum_high.get(), allocator, root, value_num); + + // return root + return root; + } + + // --------------------------- MONERO BLOCK TEMPLATE --------------------------- + + void monero_block_template::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& tmplt) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("blockTemplateBlob")) tmplt->m_block_template_blob = it->second.data(); + else if (key == std::string("blockHashingBlob")) tmplt->m_block_hashing_blob = it->second.data(); + else if (key == std::string("difficultyLow")) tmplt->m_difficulty_low = it->second.get_value(); + else if (key == std::string("difficultyHigh")) tmplt->m_difficulty_high = it->second.get_value(); + else if (key == std::string("expectedReward")) tmplt->m_expected_reward = it->second.get_value(); + else if (key == std::string("height")) tmplt->m_height = it->second.get_value(); + else if (key == std::string("prevHash")) tmplt->m_prev_hash = it->second.data(); + else if (key == std::string("reservedOffset")) tmplt->m_reserved_offset = it->second.get_value(); + else if (key == std::string("seedHeight")) tmplt->m_seed_height = it->second.get_value(); + else if (key == std::string("seedHash")) tmplt->m_seed_hash = it->second.data(); + else if (key == std::string("nextSeedHash")) tmplt->m_next_seed_hash = it->second.data(); + } + } + + rapidjson::Value monero_block_template::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_block_template_blob != boost::none) monero_utils::add_json_member("blockTemplateBlob", m_block_template_blob.get(), allocator, root, value_str); + if (m_block_hashing_blob != boost::none) monero_utils::add_json_member("blockHashingBlob", m_block_hashing_blob.get(), allocator, root, value_str); + if (m_prev_hash != boost::none) monero_utils::add_json_member("prevHash", m_prev_hash.get(), allocator, root, value_str); + if (m_seed_hash != boost::none) monero_utils::add_json_member("seedHash", m_seed_hash.get(), allocator, root, value_str); + if (m_next_seed_hash != boost::none) monero_utils::add_json_member("nextSeedHash", m_next_seed_hash.get(), allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_difficulty_low != boost::none) monero_utils::add_json_member("difficultyLow", m_difficulty_low.get(), allocator, root, value_num); + if (m_difficulty_high != boost::none) monero_utils::add_json_member("difficultyHigh", m_difficulty_high.get(), allocator, root, value_num); + if (m_expected_reward != boost::none) monero_utils::add_json_member("expectedReward", m_expected_reward.get(), allocator, root, value_num); + if (m_height != boost::none) monero_utils::add_json_member("height", m_height.get(), allocator, root, value_num); + if (m_reserved_offset != boost::none) monero_utils::add_json_member("reservedOffset", m_reserved_offset.get(), allocator, root, value_num); + if (m_seed_height != boost::none) monero_utils::add_json_member("seedHeight", m_seed_height.get(), allocator, root, value_num); + + // return root + return root; + } + + // --------------------------- MONERO CONNECTION SPAN --------------------------- + + void monero_connection_span::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& span) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("connectionId")) span->m_connection_id = it->second.data(); + else if (key == std::string("numBlocks")) span->m_num_blocks = it->second.get_value(); + else if (key == std::string("remoteAddress")) span->m_remote_address = it->second.data(); + else if (key == std::string("rate")) span->m_rate = it->second.get_value(); + else if (key == std::string("speed")) span->m_speed = it->second.get_value(); + else if (key == std::string("size")) span->m_size = it->second.get_value(); + else if (key == std::string("startHeight")) span->m_start_height = it->second.get_value(); + } + } + + rapidjson::Value monero_connection_span::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_connection_id != boost::none) monero_utils::add_json_member("connectionId", m_connection_id.get(), allocator, root, value_str); + if (m_remote_address != boost::none) monero_utils::add_json_member("remoteAddress", m_remote_address.get(), allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_num_blocks != boost::none) monero_utils::add_json_member("numBlocks", m_num_blocks.get(), allocator, root, value_num); + if (m_rate != boost::none) monero_utils::add_json_member("rate", m_rate.get(), allocator, root, value_num); + if (m_speed != boost::none) monero_utils::add_json_member("speed", m_speed.get(), allocator, root, value_num); + if (m_size != boost::none) monero_utils::add_json_member("size", m_size.get(), allocator, root, value_num); + if (m_start_height != boost::none) monero_utils::add_json_member("startHeight", m_start_height.get(), allocator, root, value_num); + + // return root + return root; + } + + // --------------------------- MONERO PEER --------------------------- + + void monero_peer::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& peer) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("host")) peer->m_host = it->second.data(); + else if (key == std::string("address")) peer->m_address = it->second.data(); + else if (key == std::string("currentDownload")) peer->m_current_download = it->second.get_value(); + else if (key == std::string("currentUpload")) peer->m_current_upload = it->second.get_value(); + else if (key == std::string("avgDownload")) peer->m_avg_download = it->second.get_value(); + else if (key == std::string("avgUpload")) peer->m_avg_upload = it->second.get_value(); + else if (key == std::string("hash")) peer->m_hash = it->second.data(); + else if (key == std::string("height")) peer->m_height = it->second.get_value(); + else if (key == std::string("isIncoming")) peer->m_is_incoming = it->second.get_value(); + else if (key == std::string("liveTime")) peer->m_live_time = it->second.get_value(); + else if (key == std::string("isLocalIp")) peer->m_is_local_ip = it->second.get_value(); + else if (key == std::string("isLocalHost")) peer->m_is_local_host = it->second.get_value(); + else if (key == std::string("numReceives")) peer->m_num_receives = it->second.get_value(); + else if (key == std::string("numSends")) peer->m_num_sends = it->second.get_value(); + else if (key == std::string("receiveIdleTime")) peer->m_receive_idle_time = it->second.get_value(); + else if (key == std::string("sendIdleTime")) peer->m_send_idle_time = it->second.get_value(); + else if (key == std::string("state")) peer->m_state = it->second.data(); + else if (key == std::string("numSupportFlags")) peer->m_num_support_flags = it->second.get_value(); + else if (key == std::string("id")) peer->m_id = it->second.data(); + else if (key == std::string("lastSeenTimestamp")) peer->m_last_seen_timestamp = it->second.get_value(); + else if (key == std::string("port")) peer->m_port = it->second.get_value(); + else if (key == std::string("rpcPort")) peer->m_rpc_port = it->second.get_value(); + else if (key == std::string("pruningSeed")) peer->m_pruning_seed = it->second.get_value(); + else if (key == std::string("rpcCreditsPerHash")) peer->m_rpc_credits_per_hash = it->second.get_value(); + else if (key == std::string("addressType")) { + int rpc_type = it->second.get_value(); + // TODO: epee's address_type enum (net/enums.h) is invalid=0, ipv4=1, ipv6=2, i2p=3, + // tor=4. Fix this with monero-java/ts together once the reference. + if (rpc_type == 0) peer->m_connection_type = monero_connection_type::INVALID; + else if (rpc_type == 1) peer->m_connection_type = monero_connection_type::IPV4; + else if (rpc_type == 2) peer->m_connection_type = monero_connection_type::IPV6; + else if (rpc_type == 3) peer->m_connection_type = monero_connection_type::TOR; + else if (rpc_type == 4) peer->m_connection_type = monero_connection_type::I2P; + else throw monero_error("Invalid RPC peer type, expected 0-4: " + std::to_string(rpc_type)); + } + } + } + + rapidjson::Value monero_peer::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_id != boost::none) monero_utils::add_json_member("id", m_id.get(), allocator, root, value_str); + if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); + if (m_host != boost::none) monero_utils::add_json_member("host", m_host.get(), allocator, root, value_str); + if (m_hash != boost::none) monero_utils::add_json_member("hash", m_hash.get(), allocator, root, value_str); + if (m_state != boost::none) monero_utils::add_json_member("state", m_state.get(), allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_port != boost::none) monero_utils::add_json_member("port", m_port.get(), allocator, root, value_num); + if (m_rpc_port != boost::none) monero_utils::add_json_member("rpcPort", m_rpc_port.get(), allocator, root, value_num); + if (m_last_seen_timestamp != boost::none) monero_utils::add_json_member("lastSeenTimestamp", m_last_seen_timestamp.get(), allocator, root, value_num); + if (m_pruning_seed != boost::none) monero_utils::add_json_member("pruningSeed", m_pruning_seed.get(), allocator, root, value_num); + if (m_rpc_credits_per_hash != boost::none) monero_utils::add_json_member("rpcCreditsPerHash", m_rpc_credits_per_hash.get(), allocator, root, value_num); + if (m_avg_download != boost::none) monero_utils::add_json_member("avgDownload", m_avg_download.get(), allocator, root, value_num); + if (m_avg_upload != boost::none) monero_utils::add_json_member("avgUpload", m_avg_upload.get(), allocator, root, value_num); + if (m_current_download != boost::none) monero_utils::add_json_member("currentDownload", m_current_download.get(), allocator, root, value_num); + if (m_current_upload != boost::none) monero_utils::add_json_member("currentUpload", m_current_upload.get(), allocator, root, value_num); + if (m_height != boost::none) monero_utils::add_json_member("height", m_height.get(), allocator, root, value_num); + if (m_live_time != boost::none) monero_utils::add_json_member("liveTime", m_live_time.get(), allocator, root, value_num); + if (m_num_receives != boost::none) monero_utils::add_json_member("numReceives", m_num_receives.get(), allocator, root, value_num); + if (m_num_sends != boost::none) monero_utils::add_json_member("numSends", m_num_sends.get(), allocator, root, value_num); + if (m_receive_idle_time != boost::none) monero_utils::add_json_member("receiveIdleTime", m_receive_idle_time.get(), allocator, root, value_num); + if (m_send_idle_time != boost::none) monero_utils::add_json_member("sendIdleTime", m_send_idle_time.get(), allocator, root, value_num); + if (m_num_support_flags != boost::none) monero_utils::add_json_member("numSupportFlags", m_num_support_flags.get(), allocator, root, value_num); + + // set bool values + if (m_is_online != boost::none) monero_utils::add_json_member("isOnline", m_is_online.get(), allocator, root); + if (m_is_incoming != boost::none) monero_utils::add_json_member("isIncoming", m_is_incoming.get(), allocator, root); + if (m_is_local_ip != boost::none) monero_utils::add_json_member("isLocalIp", m_is_local_ip.get(), allocator, root); + if (m_is_local_host != boost::none) monero_utils::add_json_member("isLocalHost", m_is_local_host.get(), allocator, root); + + // return root + return root; + } + + // --------------------------- MONERO SUBMIT TX RESULT --------------------------- + + void monero_submit_tx_result::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result) { + monero_rpc_payment_info::from_property_tree(node, result); + + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("isDoubleSpend")) result->m_is_double_spend = it->second.get_value(); + else if (key == std::string("isFeeTooLow")) result->m_is_fee_too_low = it->second.get_value(); + else if (key == std::string("hasInvalidInput")) result->m_has_invalid_input = it->second.get_value(); + else if (key == std::string("hasInvalidOutput")) result->m_has_invalid_output = it->second.get_value(); + else if (key == std::string("hasTooFewOutputs")) result->m_has_too_few_outputs = it->second.get_value(); + else if (key == std::string("isMixinTooLow")) result->m_is_mixin_too_low = it->second.get_value(); + else if (key == std::string("isRelayed")) result->m_is_relayed = it->second.get_value(); + else if (key == std::string("isOverspend")) result->m_is_overspend = it->second.get_value(); + else if (key == std::string("reason") && !it->second.data().empty()) result->m_reason = it->second.data(); + else if (key == std::string("isTooBig")) result->m_is_too_big = it->second.get_value(); + else if (key == std::string("sanityCheckFailed")) result->m_sanity_check_failed = it->second.get_value(); + else if (key == std::string("isTxExtraTooBig")) result->m_is_tx_extra_too_big = it->second.get_value(); + else if (key == std::string("isNonZeroUnlockTime")) result->m_is_nonzero_unlock_time = it->second.get_value(); + } + } + + rapidjson::Value monero_submit_tx_result::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root = monero_rpc_payment_info::to_rapidjson_val(allocator); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_reason != boost::none) monero_utils::add_json_member("reason", m_reason.get(), allocator, root, value_str); + + // set bool values + if (m_has_invalid_input != boost::none) monero_utils::add_json_member("hasInvalidInput", m_has_invalid_input.get(), allocator, root); + if (m_has_invalid_output != boost::none) monero_utils::add_json_member("hasInvalidOutput", m_has_invalid_output.get(), allocator, root); + if (m_has_too_few_outputs != boost::none) monero_utils::add_json_member("hasTooFewOutputs", m_has_too_few_outputs.get(), allocator, root); + if (m_is_good != boost::none) monero_utils::add_json_member("isGood", m_is_good.get(), allocator, root); + if (m_is_relayed != boost::none) monero_utils::add_json_member("isRelayed", m_is_relayed.get(), allocator, root); + if (m_is_double_spend != boost::none) monero_utils::add_json_member("isDoubleSpend", m_is_double_spend.get(), allocator, root); + if (m_is_fee_too_low != boost::none) monero_utils::add_json_member("isFeeTooLow", m_is_fee_too_low.get(), allocator, root); + if (m_is_mixin_too_low != boost::none) monero_utils::add_json_member("isMixinTooLow", m_is_mixin_too_low.get(), allocator, root); + if (m_is_overspend != boost::none) monero_utils::add_json_member("isOverspend", m_is_overspend.get(), allocator, root); + if (m_is_too_big != boost::none) monero_utils::add_json_member("isTooBig", m_is_too_big.get(), allocator, root); + if (m_is_tx_extra_too_big != boost::none) monero_utils::add_json_member("isTxExtraTooBig", m_is_tx_extra_too_big.get(), allocator, root); + if (m_is_nonzero_unlock_time != boost::none) monero_utils::add_json_member("isNonZeroUnlockTime", m_is_nonzero_unlock_time.get(), allocator, root); + if (m_sanity_check_failed != boost::none) monero_utils::add_json_member("sanityCheckFailed", m_sanity_check_failed.get(), allocator, root); + + // return root + return root; + } + + // --------------------------- MONERO OUTPUT DISTRIBUTION ENTRY --------------------------- + + void monero_output_distribution_entry::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& entry) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("amount")) entry->m_amount = it->second.get_value(); + else if (key == std::string("base")) entry->m_base = it->second.get_value(); + else if (key == std::string("distribution")) { + for(auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) { + entry->m_distribution.push_back(it2->second.get_value()); + } + } + else if (key == std::string("startHeight")) entry->m_start_height = it->second.get_value(); + } + } + + rapidjson::Value monero_output_distribution_entry::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_amount != boost::none) monero_utils::add_json_member("amount", m_amount.get(), allocator, root, value_num); + if (m_base != boost::none) monero_utils::add_json_member("base", m_base.get(), allocator, root, value_num); + if (m_start_height != boost::none) monero_utils::add_json_member("startHeight", m_start_height.get(), allocator, root, value_num); + + // set sub-arrays + if (!m_distribution.empty()) root.AddMember("distribution", monero_utils::to_rapidjson_val(allocator, m_distribution), allocator); + + // return root + return root; + } + + // --------------------------- MONERO OUTPUT HISTOGRAM ENTRY --------------------------- + + void monero_output_histogram_entry::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& entry) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("amount")) entry->m_amount = it->second.get_value(); + else if (key == std::string("numInstances")) entry->m_num_instances = it->second.get_value(); + else if (key == std::string("unlockedInstances")) entry->m_unlocked_instances = it->second.get_value(); + else if (key == std::string("recentInstances")) entry->m_recent_instances = it->second.get_value(); + } + } + + rapidjson::Value monero_output_histogram_entry::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_amount != boost::none) monero_utils::add_json_member("amount", m_amount.get(), allocator, root, value_num); + if (m_num_instances != boost::none) monero_utils::add_json_member("numInstances", m_num_instances.get(), allocator, root, value_num); + if (m_unlocked_instances != boost::none) monero_utils::add_json_member("unlockedInstances", m_unlocked_instances.get(), allocator, root, value_num); + if (m_recent_instances != boost::none) monero_utils::add_json_member("recentInstances", m_recent_instances.get(), allocator, root, value_num); + + // return root + return root; + } + + // --------------------------- MONERO TX POOL STATS --------------------------- + + void monero_tx_pool_stats::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& stats) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("numTxs")) stats->m_num_txs = it->second.get_value(); + else if (key == std::string("numNotRelayed")) stats->m_num_not_relayed = it->second.get_value(); + else if (key == std::string("numFailing")) stats->m_num_failing = it->second.get_value(); + else if (key == std::string("numDoubleSpends")) stats->m_num_double_spends = it->second.get_value(); + else if (key == std::string("num10m")) stats->m_num10m = it->second.get_value(); + else if (key == std::string("feeTotal")) stats->m_fee_total = it->second.get_value(); + else if (key == std::string("bytesMax")) stats->m_bytes_max = it->second.get_value(); + else if (key == std::string("bytesMed")) stats->m_bytes_med = it->second.get_value(); + else if (key == std::string("bytesMin")) stats->m_bytes_min = it->second.get_value(); + else if (key == std::string("bytesTotal")) stats->m_bytes_total = it->second.get_value(); + else if (key == std::string("histo98pc")) stats->m_histo98pc = it->second.get_value(); + else if (key == std::string("oldestTimestamp")) stats->m_oldest_timestamp = it->second.get_value(); + else if (key == std::string("histo")) { + // TODO implement histogram deserialization + } + } + } + + rapidjson::Value monero_tx_pool_stats::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_num_txs != boost::none) monero_utils::add_json_member("numTxs", m_num_txs.get(), allocator, root, value_num); + if (m_num_not_relayed != boost::none) monero_utils::add_json_member("numNotRelayed", m_num_not_relayed.get(), allocator, root, value_num); + if (m_num_failing != boost::none) monero_utils::add_json_member("numFailing", m_num_failing.get(), allocator, root, value_num); + if (m_num_double_spends != boost::none) monero_utils::add_json_member("numDoubleSpends", m_num_double_spends.get(), allocator, root, value_num); + if (m_num10m != boost::none) monero_utils::add_json_member("num10m", m_num10m.get(), allocator, root, value_num); + if (m_fee_total != boost::none) monero_utils::add_json_member("feeTotal", m_fee_total.get(), allocator, root, value_num); + if (m_bytes_max != boost::none) monero_utils::add_json_member("bytesMax", m_bytes_max.get(), allocator, root, value_num); + if (m_bytes_med != boost::none) monero_utils::add_json_member("bytesMed", m_bytes_med.get(), allocator, root, value_num); + if (m_bytes_min != boost::none) monero_utils::add_json_member("bytesMin", m_bytes_min.get(), allocator, root, value_num); + if (m_bytes_total != boost::none) monero_utils::add_json_member("bytesTotal", m_bytes_total.get(), allocator, root, value_num); + if (m_histo98pc != boost::none) monero_utils::add_json_member("histo98pc", m_histo98pc.get(), allocator, root, value_num); + if (m_oldest_timestamp != boost::none) monero_utils::add_json_member("oldestTimestamp", m_oldest_timestamp.get(), allocator, root, value_num); + + // set object values + rapidjson::Value histo(rapidjson::kObjectType); + for(const auto& kv : m_histo) { + std::string key = std::to_string(kv.first); + rapidjson::Value field_key(key.c_str(), key.size(), allocator); + histo.AddMember(field_key, kv.second, allocator); + } + root.AddMember("histo", histo, allocator); + + // return root + return root; + } + + // --------------------------- MONERO DAEMON UPDATE CHECK RESULT --------------------------- + + void monero_daemon_update_check_result::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("isUpdateAvailable")) check->m_is_update_available = it->second.get_value(); + else if (key == std::string("version") && !it->second.data().empty()) check->m_version = it->second.data(); + else if (key == std::string("hash") && !it->second.data().empty()) check->m_hash = it->second.data(); + else if (key == std::string("autoUri") && !it->second.data().empty()) check->m_auto_uri = it->second.data(); + else if (key == std::string("userUri") && !it->second.data().empty()) check->m_user_uri = it->second.data(); + } + } + + rapidjson::Value monero_daemon_update_check_result::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_version != boost::none) monero_utils::add_json_member("version", m_version.get(), allocator, root, value_str); + if (m_hash != boost::none) monero_utils::add_json_member("hash", m_hash.get(), allocator, root, value_str); + if (m_auto_uri != boost::none) monero_utils::add_json_member("autoUri", m_auto_uri.get(), allocator, root, value_str); + if (m_user_uri != boost::none) monero_utils::add_json_member("userUri", m_user_uri.get(), allocator, root, value_str); + + // set bool values + if (m_is_update_available != boost::none) monero_utils::add_json_member("isUpdateAvailable", m_is_update_available.get(), allocator, root); + + // return root + return root; + } + + // --------------------------- MONERO DAEMON UPDATE DOWNLOAD RESULT --------------------------- + + void monero_daemon_update_download_result::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check) { + monero_daemon_update_check_result::from_property_tree(node, check); + + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("downloadPath") && !it->second.data().empty()) check->m_download_path = it->second.data(); + } + } + + rapidjson::Value monero_daemon_update_download_result::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root = monero_daemon_update_check_result::to_rapidjson_val(allocator); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_download_path != boost::none) monero_utils::add_json_member("downloadPath", m_download_path.get(), allocator, root, value_str); + + // return root + return root; + } + + // --------------------------- MONERO FEE ESTIMATE --------------------------- + + void monero_fee_estimate::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& estimate) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("fee")) estimate->m_fee = it->second.get_value(); + else if (key == std::string("quantizationMask")) estimate->m_quantization_mask = it->second.get_value(); + else if (key == std::string("fees")) { + auto node2 = it->second; + for (boost::property_tree::ptree::const_iterator it2 = node2.begin(); it2 != node2.end(); ++it2) { + uint64_t fee = it2->second.get_value(); + estimate->m_fees.push_back(fee); + } + } + } + } + + rapidjson::Value monero_fee_estimate::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_fee != boost::none) monero_utils::add_json_member("fee", m_fee.get(), allocator, root, value_num); + if (m_quantization_mask != boost::none) monero_utils::add_json_member("quantizationMask", m_quantization_mask.get(), allocator, root, value_num); + + // set sub-arrays + if (!m_fees.empty()) root.AddMember("fees", monero_utils::to_rapidjson_val(allocator, m_fees), allocator); + + // return root + return root; + } + + // --------------------------- MONERO DAEMON INFO --------------------------- + + void monero_daemon_info::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info) { + monero_rpc_payment_info::from_property_tree(node, info); + + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("version")) info->m_version = it->second.data(); + else if (key == std::string("numAltBlocks")) info->m_num_alt_blocks = it->second.get_value(); + else if (key == std::string("blockSizeLimit")) info->m_block_size_limit = it->second.get_value(); + else if (key == std::string("blockSizeMedian")) info->m_block_size_median = it->second.get_value(); + else if (key == std::string("blockWeightLimit")) info->m_block_weight_limit = it->second.get_value(); + else if (key == std::string("blockWeightMedian")) info->m_block_weight_median = it->second.get_value(); + else if (key == std::string("bootstrapDaemonAddress") && !it->second.data().empty()) info->m_bootstrap_daemon_address = it->second.data(); + else if (key == std::string("difficultyLow")) info->m_difficulty_low = it->second.get_value(); + else if (key == std::string("difficultyHigh")) info->m_difficulty_high = it->second.get_value(); + else if (key == std::string("cumulativeDifficultyLow")) info->m_cumulative_difficulty_low = it->second.get_value(); + else if (key == std::string("cumulativeDifficultyHigh")) info->m_cumulative_difficulty_high = it->second.get_value(); + else if (key == std::string("freeSpace")) info->m_free_space = it->second.get_value(); + else if (key == std::string("numOfflinePeers")) info->m_num_offline_peers = it->second.get_value(); + else if (key == std::string("numOnlinePeers")) info->m_num_online_peers = it->second.get_value(); + else if (key == std::string("height")) info->m_height = it->second.get_value(); + else if (key == std::string("heightWithoutBootstrap")) info->m_height_without_bootstrap = it->second.get_value(); + else if (key == std::string("networkType")) { + uint8_t nettype = it->second.get_value(); + if (nettype == 0) info->m_network_type = monero::monero_network_type::MAINNET; + else if (nettype == 1) info->m_network_type = monero::monero_network_type::TESTNET; + else if (nettype == 2) info->m_network_type = monero::monero_network_type::STAGENET; + else throw std::runtime_error("Could not deserialize daemon info: invalid network type " + std::to_string(nettype)); + } + else if (key == std::string("isOffline")) info->m_is_offline = it->second.get_value(); + else if (key == std::string("numIncomingConnections")) info->m_num_incoming_connections = it->second.get_value(); + else if (key == std::string("numOutgoingConnections")) info->m_num_outgoing_connections = it->second.get_value(); + else if (key == std::string("numRpcConnections")) info->m_num_rpc_connections = it->second.get_value(); + else if (key == std::string("startTimestamp")) info->m_start_timestamp = it->second.get_value(); + else if (key == std::string("adjustedTimestamp")) info->m_adjusted_timestamp = it->second.get_value(); + else if (key == std::string("target")) info->m_target = it->second.get_value(); + else if (key == std::string("targetHeight")) info->m_target_height = it->second.get_value(); + else if (key == std::string("numTxs")) info->m_num_txs = it->second.get_value(); + else if (key == std::string("numTxsPool")) info->m_num_txs_pool = it->second.get_value(); + else if (key == std::string("wasBootstrapEverUsed")) info->m_was_bootstrap_ever_used = it->second.get_value(); + else if (key == std::string("databaseSize")) info->m_database_size = it->second.get_value(); + else if (key == std::string("updateAvailable")) info->m_update_available = it->second.get_value(); + else if (key == std::string("isBusySyncing")) info->m_is_busy_syncing = it->second.get_value(); + else if (key == std::string("isSynchronized")) info->m_is_synchronized = it->second.get_value(); + else if (key == std::string("isRestricted")) info->m_is_restricted = it->second.get_value(); + } + } + + rapidjson::Value monero_daemon_info::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root = monero_rpc_payment_info::to_rapidjson_val(allocator); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_version != boost::none) monero_utils::add_json_member("version", m_version.get(), allocator, root, value_str); + if (m_bootstrap_daemon_address != boost::none) monero_utils::add_json_member("bootstrapDaemonAddress", m_bootstrap_daemon_address.get(), allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_num_alt_blocks != boost::none) monero_utils::add_json_member("numAltBlocks", m_num_alt_blocks.get(), allocator, root, value_num); + if (m_block_size_limit != boost::none) monero_utils::add_json_member("blockSizeLimit", m_block_size_limit.get(), allocator, root, value_num); + if (m_block_size_median != boost::none) monero_utils::add_json_member("blockSizeMedian", m_block_size_median.get(), allocator, root, value_num); + if (m_block_weight_limit != boost::none) monero_utils::add_json_member("blockWeightLimit", m_block_weight_limit.get(), allocator, root, value_num); + if (m_block_weight_median != boost::none) monero_utils::add_json_member("blockWeightMedian", m_block_weight_median.get(), allocator, root, value_num); + if (m_difficulty_low != boost::none) monero_utils::add_json_member("difficultyLow", m_difficulty_low.get(), allocator, root, value_num); + if (m_difficulty_high != boost::none) monero_utils::add_json_member("difficultyHigh", m_difficulty_high.get(), allocator, root, value_num); + if (m_cumulative_difficulty_low != boost::none) monero_utils::add_json_member("cumulativeDifficultyLow", m_cumulative_difficulty_low.get(), allocator, root, value_num); + if (m_cumulative_difficulty_high != boost::none) monero_utils::add_json_member("cumulativeDifficultyHigh", m_cumulative_difficulty_high.get(), allocator, root, value_num); + if (m_free_space != boost::none) monero_utils::add_json_member("freeSpace", m_free_space.get(), allocator, root, value_num); + if (m_num_offline_peers != boost::none) monero_utils::add_json_member("numOfflinePeers", m_num_offline_peers.get(), allocator, root, value_num); + if (m_num_online_peers != boost::none) monero_utils::add_json_member("numOnlinePeers", m_num_online_peers.get(), allocator, root, value_num); + if (m_height != boost::none) monero_utils::add_json_member("height", m_height.get(), allocator, root, value_num); + if (m_height_without_bootstrap != boost::none) monero_utils::add_json_member("heightWithoutBootstrap", m_height_without_bootstrap.get(), allocator, root, value_num); + if (m_network_type != boost::none) monero_utils::add_json_member("networkType", (uint8_t)m_network_type.get(), allocator, root, value_num); + if (m_num_incoming_connections != boost::none) monero_utils::add_json_member("numIncomingConnections", m_num_incoming_connections.get(), allocator, root, value_num); + if (m_num_outgoing_connections != boost::none) monero_utils::add_json_member("numOutgoingConnections", m_num_outgoing_connections.get(), allocator, root, value_num); + if (m_num_rpc_connections != boost::none) monero_utils::add_json_member("numRpcConnections", m_num_rpc_connections.get(), allocator, root, value_num); + if (m_start_timestamp != boost::none) monero_utils::add_json_member("startTimestamp", m_start_timestamp.get(), allocator, root, value_num); + if (m_adjusted_timestamp != boost::none) monero_utils::add_json_member("adjustedTimestamp", m_adjusted_timestamp.get(), allocator, root, value_num); + if (m_target != boost::none) monero_utils::add_json_member("target", m_target.get(), allocator, root, value_num); + if (m_target_height != boost::none) monero_utils::add_json_member("targetHeight", m_target_height.get(), allocator, root, value_num); + if (m_num_txs != boost::none) monero_utils::add_json_member("numTxs", m_num_txs.get(), allocator, root, value_num); + if (m_num_txs_pool != boost::none) monero_utils::add_json_member("numTxsPool", m_num_txs_pool.get(), allocator, root, value_num); + if (m_database_size != boost::none) monero_utils::add_json_member("databaseSize", m_database_size.get(), allocator, root, value_num); + + // set bool values + if (m_is_offline != boost::none) monero_utils::add_json_member("isOffline", m_is_offline.get(), allocator, root); + if (m_was_bootstrap_ever_used != boost::none) monero_utils::add_json_member("wasBootstrapEverUsed", m_was_bootstrap_ever_used.get(), allocator, root); + if (m_update_available != boost::none) monero_utils::add_json_member("updateAvailable", m_update_available.get(), allocator, root); + if (m_is_busy_syncing != boost::none) monero_utils::add_json_member("isBusySyncing", m_is_busy_syncing.get(), allocator, root); + if (m_is_synchronized != boost::none) monero_utils::add_json_member("isSynchronized", m_is_synchronized.get(), allocator, root); + if (m_is_restricted != boost::none) monero_utils::add_json_member("isRestricted", m_is_restricted.get(), allocator, root); + + // return root + return root; + } + + // --------------------------- MONERO DAEMON SYNC INFO --------------------------- + + void monero_daemon_sync_info::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info) { + monero_rpc_payment_info::from_property_tree(node, info); + + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("height")) info->m_height = it->second.get_value(); + else if (key == std::string("targetHeight")) info->m_target_height = it->second.get_value(); + else if (key == std::string("nextNeededPruningSeed")) info->m_next_needed_pruning_seed = it->second.get_value(); + else if (key == std::string("overview") && !it->second.data().empty() && it->second.data() != std::string("[]")) info->m_overview = it->second.data(); + } + } + + rapidjson::Value monero_daemon_sync_info::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root = monero_rpc_payment_info::to_rapidjson_val(allocator); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_overview != boost::none) monero_utils::add_json_member("overview", m_overview.get(), allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_height != boost::none) monero_utils::add_json_member("height", m_height.get(), allocator, root, value_num); + if (m_target_height != boost::none) monero_utils::add_json_member("targetHeight", m_target_height.get(), allocator, root, value_num); + if (m_next_needed_pruning_seed != boost::none) monero_utils::add_json_member("nextNeededPruningSeed", m_next_needed_pruning_seed.get(), allocator, root, value_num); + + // set sub-arrays + if (!m_peers.empty()) root.AddMember("peers", monero_utils::to_rapidjson_val(allocator, m_peers), allocator); + if (!m_spans.empty()) root.AddMember("spans", monero_utils::to_rapidjson_val(allocator, m_spans), allocator); + + // return root + return root; + } + + // --------------------------- MONERO HARD FORK INFO --------------------------- + + void monero_hard_fork_info::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info) { + monero_rpc_payment_info::from_property_tree(node, info); + + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("earliestHeight")) info->m_earliest_height = it->second.get_value(); + else if (key == std::string("isEnabled")) info->m_is_enabled = it->second.get_value(); + else if (key == std::string("state")) info->m_state = it->second.get_value(); + else if (key == std::string("threshold")) info->m_threshold = it->second.get_value(); + else if (key == std::string("version")) info->m_version = it->second.get_value(); + else if (key == std::string("numVotes")) info->m_num_votes = it->second.get_value(); + else if (key == std::string("window")) info->m_window = it->second.get_value(); + else if (key == std::string("voting")) info->m_voting = it->second.get_value(); + } + } + + rapidjson::Value monero_hard_fork_info::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root = monero_rpc_payment_info::to_rapidjson_val(allocator); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_earliest_height != boost::none) monero_utils::add_json_member("earliestHeight", m_earliest_height.get(), allocator, root, value_num); + if (m_state != boost::none) monero_utils::add_json_member("state", m_state.get(), allocator, root, value_num); + if (m_threshold != boost::none) monero_utils::add_json_member("threshold", m_threshold.get(), allocator, root, value_num); + if (m_version != boost::none) monero_utils::add_json_member("version", m_version.get(), allocator, root, value_num); + if (m_num_votes != boost::none) monero_utils::add_json_member("numVotes", m_num_votes.get(), allocator, root, value_num); + if (m_window != boost::none) monero_utils::add_json_member("window", m_window.get(), allocator, root, value_num); + if (m_voting != boost::none) monero_utils::add_json_member("voting", m_voting.get(), allocator, root, value_num); + + // set bool values + if (m_is_enabled != boost::none) monero_utils::add_json_member("isEnabled", m_is_enabled.get(), allocator, root); + + // return root + return root; + } + + // --------------------------- MONERO GENERATE BLOCKS RESULT --------------------------- + + void monero_generate_blocks_result::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("blockHashes")) { + for (const auto& child : it->second) result->m_block_hashes.push_back(child.second.data()); + } + else if (key == std::string("height")) result->m_height = it->second.get_value(); + } + } + + rapidjson::Value monero_generate_blocks_result::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_height != boost::none) monero_utils::add_json_member("height", m_height.get(), allocator, root, value_num); + + // set sub-arrays + if (!m_block_hashes.empty()) root.AddMember("blockHashes", monero_utils::to_rapidjson_val(allocator, m_block_hashes), allocator); + + // return root + return root; + } + +} diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon_model.h b/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon_model.h new file mode 100644 index 0000000..7f77225 --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon_model.h @@ -0,0 +1,639 @@ +/** + * Copyright (c) woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ + +#pragma once + +#include +#include +#include +#include "rapidjson/document.h" +#include "rapidjson/writer.h" +#include "rapidjson/stringbuffer.h" + +/** + * Public interface for libmonero-cpp library. + */ +namespace monero { + + /** + * Base struct which can be serialized. + */ + struct serializable_struct { + + virtual ~serializable_struct() = default; + + /** + * Serializes the struct to a json std::string. + * + * @return the struct serialized to a json std::string + */ + std::string serialize() const; + + /** + * Converts the struct to a rapidjson Value. + * + * @param allocator is the rapidjson document allocator + * @return the struct as a rapidjson Value + */ + virtual rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const = 0; + }; + + /** + * Models connection ssl options. + */ + struct ssl_options : public serializable_struct { + boost::optional m_ssl_private_key_path; + boost::optional m_ssl_certificate_path; + boost::optional m_ssl_ca_file; + std::vector m_ssl_allowed_fingerprints; + boost::optional m_ssl_allow_any_cert; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + /** + * Enumerates Monero connection types. + */ + enum monero_connection_type : uint8_t { + INVALID = 0, + IPV4, + IPV6, + TOR, + I2P + }; + + /** + * Enumerates Monero network types. + */ + enum monero_network_type : uint8_t { + MAINNET = 0, + TESTNET, + STAGENET + }; + + /** + * Models connection bandwidth limits. + */ + struct monero_bandwidth_limits : public serializable_struct { + boost::optional m_up; + boost::optional m_down; + + monero_bandwidth_limits() { } + monero_bandwidth_limits(int up, int down): m_up(up), m_down(down) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& limits); + }; + + /** + * Models a Monero version. + */ + struct monero_version : public serializable_struct { + boost::optional m_number; + boost::optional m_is_release; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& version); + }; + + // forward declarations + struct monero_tx; + struct monero_output; + + /** + * Models a Monero block header which contains information about the block. + * + * TODO: a header that is transmitted may have fewer fields like cryptonote::block_header; separate? + */ + struct monero_block_header : public serializable_struct { + boost::optional m_hash; + boost::optional m_height; + boost::optional m_timestamp; + boost::optional m_size; + boost::optional m_weight; + boost::optional m_long_term_weight; + boost::optional m_depth; + boost::optional m_difficulty_low; + boost::optional m_difficulty_high; + boost::optional m_cumulative_difficulty_low; + boost::optional m_cumulative_difficulty_high; + boost::optional m_major_version; + boost::optional m_minor_version; + boost::optional m_nonce; + boost::optional m_miner_tx_hash; + boost::optional m_num_txs; + boost::optional m_orphan_status; + boost::optional m_prev_hash; + boost::optional m_reward; + boost::optional m_pow_hash; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; + virtual void merge(const std::shared_ptr& self, const std::shared_ptr& other); + }; + + /** + * Models a Monero block in the blockchain. + */ + struct monero_block : public monero_block_header { + boost::optional m_hex; + std::shared_ptr m_miner_tx; + std::vector> m_txs; + std::vector m_tx_hashes; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; + void merge(const std::shared_ptr& self, const std::shared_ptr& other); + void merge(const std::shared_ptr& self, const std::shared_ptr& other); + }; + + /** + * Models a Monero transaction on the blockchain. + */ + struct monero_tx : public serializable_struct { + static const std::string DEFAULT_PAYMENT_ID; // default payment id "0000000000000000" + static const std::string DEFAULT_ID; + + std::shared_ptr m_block; + boost::optional m_hash; + boost::optional m_version; + boost::optional m_is_miner_tx; + boost::optional m_payment_id; + boost::optional m_fee; + boost::optional m_ring_size; + boost::optional m_relay; + boost::optional m_is_relayed; + boost::optional m_is_confirmed; + boost::optional m_in_tx_pool; + boost::optional m_num_confirmations; + boost::optional m_unlock_time; + boost::optional m_last_relayed_timestamp; + boost::optional m_received_timestamp; + boost::optional m_is_double_spend_seen; + boost::optional m_key; + boost::optional m_full_hex; + boost::optional m_pruned_hex; + boost::optional m_prunable_hex; + boost::optional m_prunable_hash; + boost::optional m_size; + boost::optional m_weight; + std::vector> m_inputs; + std::vector> m_outputs; + std::vector m_output_indices; + boost::optional m_metadata; + boost::optional m_common_tx_sets; + std::vector m_extra; + boost::optional m_rct_signatures; // TODO: implement + boost::optional m_rct_sig_prunable; // TODO: implement + boost::optional m_is_kept_by_block; + boost::optional m_is_failed; + boost::optional m_last_failed_height; + boost::optional m_last_failed_hash; + boost::optional m_max_used_block_height; + boost::optional m_max_used_block_hash; + std::vector m_signatures; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static void from_property_tree(const boost::property_tree::ptree& node, std::shared_ptr tx); + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; + virtual void merge(const std::shared_ptr& self, const std::shared_ptr& other); + boost::optional get_height() const; + }; + + /** + * Models a Monero key image. + */ + struct monero_key_image : public serializable_struct { + boost::optional m_hex; + boost::optional m_signature; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& key_image); + static std::vector> deserialize_key_images(const std::string& key_images_json); // TODO: remove this specialty util used once + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; + void merge(const std::shared_ptr& self, const std::shared_ptr& other); + }; + + /** + * Models a Monero transaction output. + */ + struct monero_output : public serializable_struct { + std::shared_ptr m_tx; + std::shared_ptr m_key_image; + boost::optional m_amount; + boost::optional m_index; + std::vector m_ring_output_indices; + boost::optional m_stealth_public_key; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& output); + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; + virtual void merge(const std::shared_ptr& self, const std::shared_ptr& other); + }; + + /** + * Models the status of a Monero key image. + */ + enum monero_key_image_spent_status : uint8_t { + NOT_SPENT = 0, + CONFIRMED, + TX_POOL + }; + + /** + * Models a Monero RPC payment information. + */ + struct monero_rpc_payment_info : public serializable_struct { + boost::optional m_credits; + boost::optional m_top_block_hash; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& rpc_payment_info); + }; + + /** + * Models an alternative chain seen by the node. + */ + struct monero_alt_chain : public serializable_struct { + std::vector m_block_hashes; + boost::optional m_difficulty_low; + boost::optional m_difficulty_high; + boost::optional m_height; + boost::optional m_length; + boost::optional m_main_chain_parent_block_hash; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& alt_chain); + }; + + /** + * Monero banhammer. + */ + struct monero_ban : public serializable_struct { + boost::optional m_host; + boost::optional m_ip; + boost::optional m_is_banned; + boost::optional m_seconds; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& ban); + }; + + /** + * Result of pruning the blockchain. + */ + struct monero_prune_result : public serializable_struct { + boost::optional m_is_pruned; + boost::optional m_pruning_seed; + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + /** + * Monero daemon mining status. + */ + struct monero_mining_status : public serializable_struct { + boost::optional m_is_active; + boost::optional m_is_background; + boost::optional m_address; + boost::optional m_speed; + boost::optional m_num_threads; + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& status); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + /** + * Model for the summation of miner emissions and fees. + */ + struct monero_miner_tx_sum : public serializable_struct { + boost::optional m_emission_sum_low; + boost::optional m_emission_sum_high; + boost::optional m_fee_sum_low; + boost::optional m_fee_sum_high; + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& sum); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + /** + * Monero block template to mine. + */ + struct monero_block_template : public serializable_struct { + boost::optional m_block_template_blob; + boost::optional m_block_hashing_blob; + boost::optional m_prev_hash; + boost::optional m_seed_hash; + boost::optional m_next_seed_hash; + boost::optional m_difficulty_low; + boost::optional m_difficulty_high; + boost::optional m_expected_reward; + boost::optional m_height; + boost::optional m_reserved_offset; + boost::optional m_seed_height; + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& tmplt); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + /** + * Monero daemon connection span. + */ + struct monero_connection_span : public serializable_struct { + boost::optional m_connection_id; + boost::optional m_remote_address; + boost::optional m_num_blocks; + boost::optional m_rate; + boost::optional m_speed; + boost::optional m_size; + boost::optional m_start_height; + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& span); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + /** + * Models a peer to the daemon. + */ + struct monero_peer : public serializable_struct { + boost::optional m_id; + boost::optional m_address; + boost::optional m_host; + boost::optional m_port; + boost::optional m_is_online; + boost::optional m_last_seen_timestamp; + boost::optional m_pruning_seed; + boost::optional m_rpc_port; + boost::optional m_rpc_credits_per_hash; + boost::optional m_hash; + boost::optional m_avg_download; + boost::optional m_avg_upload; + boost::optional m_current_download; + boost::optional m_current_upload; + boost::optional m_height; + boost::optional m_is_incoming; + boost::optional m_live_time; + boost::optional m_is_local_ip; + boost::optional m_is_local_host; + boost::optional m_num_receives; + boost::optional m_num_sends; + boost::optional m_receive_idle_time; + boost::optional m_send_idle_time; + boost::optional m_state; + boost::optional m_num_support_flags; + boost::optional m_connection_type; + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& peer); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + /** + * Models the result from submitting a tx to a daemon. + */ + struct monero_submit_tx_result : public monero_rpc_payment_info { + boost::optional m_has_invalid_input; + boost::optional m_has_invalid_output; + boost::optional m_has_too_few_outputs; + boost::optional m_is_good; + boost::optional m_is_relayed; + boost::optional m_is_double_spend; + boost::optional m_is_fee_too_low; + boost::optional m_is_mixin_too_low; + boost::optional m_is_overspend; + boost::optional m_is_too_big; + boost::optional m_sanity_check_failed; + boost::optional m_is_tx_extra_too_big; + boost::optional m_is_nonzero_unlock_time; + boost::optional m_reason; + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + /** + * TODO. + */ + struct monero_tx_backlog_entry { + // TODO + }; + + /** + * Monero output distribution entry. + */ + struct monero_output_distribution_entry : public serializable_struct { + boost::optional m_amount; + boost::optional m_base; + std::vector m_distribution; + boost::optional m_start_height; + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& entry); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + /** + * Entry in a Monero output histogram (see get_output_histogram of Daemon RPC documentation). + */ + struct monero_output_histogram_entry : public serializable_struct { + boost::optional m_amount; + boost::optional m_num_instances; + boost::optional m_unlocked_instances; + boost::optional m_recent_instances; + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& entry); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + /** + * Models transaction pool statistics. + */ + struct monero_tx_pool_stats : public serializable_struct { + boost::optional m_num_txs; + boost::optional m_num_not_relayed; + boost::optional m_num_failing; + boost::optional m_num_double_spends; + boost::optional m_num10m; + boost::optional m_fee_total; + boost::optional m_bytes_max; + boost::optional m_bytes_med; + boost::optional m_bytes_min; + boost::optional m_bytes_total; + std::map m_histo; + boost::optional m_histo98pc; + boost::optional m_oldest_timestamp; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& stats); + }; + + /** + * Models the result of checking for a daemon update. + */ + struct monero_daemon_update_check_result : public serializable_struct { + boost::optional m_is_update_available; + boost::optional m_version; + boost::optional m_hash; + boost::optional m_auto_uri; + boost::optional m_user_uri; + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + /** + * Models the result of downloading an update. + */ + struct monero_daemon_update_download_result : public monero_daemon_update_check_result { + boost::optional m_download_path; + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + /** + * Models a Monero fee estimate. + */ + struct monero_fee_estimate : public serializable_struct { + boost::optional m_quantization_mask; + boost::optional m_fee; + std::vector m_fees; + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& estimate); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + /** + * Monero daemon info. + */ + struct monero_daemon_info : public monero_rpc_payment_info { + boost::optional m_version; + boost::optional m_num_alt_blocks; + boost::optional m_block_size_limit; + boost::optional m_block_size_median; + boost::optional m_block_weight_limit; + boost::optional m_block_weight_median; + boost::optional m_bootstrap_daemon_address; + boost::optional m_difficulty_low; + boost::optional m_difficulty_high; + boost::optional m_cumulative_difficulty_low; + boost::optional m_cumulative_difficulty_high; + boost::optional m_free_space; + boost::optional m_num_offline_peers; + boost::optional m_num_online_peers; + boost::optional m_height; + boost::optional m_height_without_bootstrap; + boost::optional m_network_type; + boost::optional m_is_offline; + boost::optional m_num_incoming_connections; + boost::optional m_num_outgoing_connections; + boost::optional m_num_rpc_connections; + boost::optional m_start_timestamp; + boost::optional m_adjusted_timestamp; + boost::optional m_target; + boost::optional m_target_height; + boost::optional m_num_txs; + boost::optional m_num_txs_pool; + boost::optional m_was_bootstrap_ever_used; + boost::optional m_database_size; + boost::optional m_update_available; + boost::optional m_is_busy_syncing; + boost::optional m_is_synchronized; + boost::optional m_is_restricted; + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + /** + * Models daemon synchronization information. + */ + struct monero_daemon_sync_info : public monero_rpc_payment_info { + boost::optional m_overview; + boost::optional m_height; + boost::optional m_target_height; + boost::optional m_next_needed_pruning_seed; + std::vector> m_peers; + std::vector> m_spans; + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + /** + * Monero hard fork info. + */ + struct monero_hard_fork_info : public monero_rpc_payment_info { + boost::optional m_is_enabled; + boost::optional m_earliest_height; + boost::optional m_state; + boost::optional m_threshold; + boost::optional m_version; + boost::optional m_num_votes; + boost::optional m_window; + boost::optional m_voting; + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + /** + * Models the result of generating blocks. + */ + struct monero_generate_blocks_result : public serializable_struct { + std::vector m_block_hashes; + boost::optional m_height; + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + +} diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon_rpc.cpp b/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon_rpc.cpp new file mode 100644 index 0000000..98f428d --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon_rpc.cpp @@ -0,0 +1,892 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ +#include "monero_daemon_rpc.h" +#include "monero_daemon_rpc_model.h" +#include "common/monero_error.h" +#include "utils/gen_utils.h" +#include "utils/monero_utils.h" + +static const uint64_t MAX_REQ_SIZE = 3000000; +static const uint64_t NUM_HEADERS_PER_REQ = 750; + +namespace monero { + + /** + * Polls daemon and sends notifications in order to notify external daemon listeners. + */ + class monero_daemon_poller : public gen_utils::thread_poller { + public: + + explicit monero_daemon_poller(monero_daemon* daemon, uint64_t poll_period_ms = 10000): m_daemon(daemon) { + init_common("monero_daemon_rpc"); + m_poll_period_ms = poll_period_ms; + } + + ~monero_daemon_poller() override { + set_is_polling(false); + } + + void poll() override { + if (!m_last_header) { + m_last_header = m_daemon->get_last_block_header(); + return; + } + + auto header = m_daemon->get_last_block_header(); + if (header->m_hash != m_last_header->m_hash) { + m_last_header = header; + announce_block_header(header); + } + } + + private: + monero_daemon* m_daemon; + std::shared_ptr m_last_header; + + void announce_block_header(const std::shared_ptr& header) { + gen_utils::thread_poller::announce_scope scope(*this); // see wait_for_callbacks_idle() + auto listeners = m_daemon->get_listeners(); + for (auto& listener : listeners) { + try { + listener->on_block_header(header); + } catch (const std::exception& e) { + MERROR("Error calling listener on new block header: " << e.what()); + } + } + } + }; + + /** + * Sends a notification on a condition variable when a block is added to blockchain. + */ + class block_notifier : public monero_daemon_listener { + public: + block_notifier(boost::mutex* temp, boost::condition_variable* cv, bool* ready) { this->temp = temp; this->cv = cv; this->ready = ready; } + + void on_block_header(const std::shared_ptr& header) override { + boost::mutex::scoped_lock lock(*temp); + m_last_header = header; + *ready = true; + cv->notify_one(); + } + + private: + boost::mutex* temp; + boost::condition_variable* cv; + bool* ready; + }; + + void monero_daemon_rpc::set_poll_period_in_ms(uint64_t period_ms) { + boost::lock_guard poller_lock(m_poller_mutex); + if (!m_poller) m_poller = std::make_unique(this); + m_poller->set_period_in_ms(period_ms); + } + + /** + * Validate RPC response status. + */ + void check_response_status(const boost::property_tree::ptree& node) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("status")) { + std::string status = it->second.data(); + + // TODO monero-project empty string status is returned for download update response when an update is available + if (status == std::string("OK") || status == std::string("")) { + return; + } + else throw monero_rpc_error(status); + } + } + + throw monero_error("Could not get JSON RPC response status"); + } + + monero_daemon_rpc::monero_daemon_rpc(const std::shared_ptr& rpc): m_rpc(rpc) { + if (rpc == nullptr) throw monero_error("Connection cannot be null"); + if (!rpc->is_online().value_or(false) && rpc->m_uri != boost::none) rpc->check_connection(); + } + + monero_daemon_rpc::monero_daemon_rpc(const std::string& uri, const std::string& username, const std::string& password, const std::string& proxy_uri, const std::string& zmq_uri, const boost::optional& timeout): + m_rpc(std::make_shared(uri, username, password, proxy_uri, zmq_uri, 0, timeout)) { + if (!uri.empty()) m_rpc->check_connection(); + } + + std::set monero_daemon_rpc::get_listeners() { + boost::lock_guard lock(m_listeners_mutex); + return m_listeners; + } + + void monero_daemon_rpc::add_listener(monero_daemon_listener &listener) { + { + boost::lock_guard lock(m_listeners_mutex); + m_listeners.insert(&listener); + } + refresh_listening(); + } + + void monero_daemon_rpc::remove_listener(monero_daemon_listener &listener) { + { + boost::lock_guard lock(m_listeners_mutex); + m_listeners.erase(&listener); + } + refresh_listening(); + wait_for_listener_callbacks_idle(); + } + + void monero_daemon_rpc::remove_listeners() { + { + boost::lock_guard lock(m_listeners_mutex); + m_listeners.clear(); + } + refresh_listening(); + wait_for_listener_callbacks_idle(); + } + + void monero_daemon_rpc::wait_for_listener_callbacks_idle() { + // ensures no in-flight callback dispatch can still reference a just-removed listener + monero_daemon_poller* poller; + { + boost::lock_guard poller_lock(m_poller_mutex); + poller = m_poller.get(); + } + if (poller != nullptr) poller->wait_for_callbacks_idle(); + } + + std::shared_ptr monero_daemon_rpc::get_rpc_connection() const { + MTRACE("monero_daemon_rpc::get_rpc_connection()"); + return m_rpc; + } + + bool monero_daemon_rpc::is_connected() { + try { + get_version(); + return true; + } + catch (...) { + return false; + } + } + + monero_version monero_daemon_rpc::get_version() { + auto result = m_rpc->send_json_request("get_version"); + check_response_status(result); + monero_version version; + deserialize_version(result, version); + return version; + } + + bool monero_daemon_rpc::is_trusted() { + auto result = m_rpc->send_path_request("get_height"); + check_response_status(result); + auto get_height_response = std::make_shared(); + monero_get_block_result::from_property_tree(result, get_height_response); + return gen_utils::bool_equals(false, get_height_response->m_untrusted); + } + + uint64_t monero_daemon_rpc::get_height() { + auto result = m_rpc->send_json_request("get_block_count"); + check_response_status(result); + std::shared_ptr block_result = std::make_shared(); + monero_get_block_result::from_property_tree(result, block_result); + if (block_result->m_count == boost::none) throw monero_error("Could not get height"); + return block_result->m_count.get(); + } + + std::string monero_daemon_rpc::get_block_hash(uint64_t height) { + std::shared_ptr params = std::make_shared(height); + auto result = m_rpc->send_json_request("on_get_block_hash", params); + return result.data(); + } + + std::shared_ptr monero_daemon_rpc::get_block_template(const std::string& wallet_address, const boost::optional& reserve_size) { + MTRACE("monero_daemon_rpc::get_block_template()"); + auto params = std::make_shared(wallet_address, reserve_size); + auto result = m_rpc->send_json_request("get_block_template", params); + check_response_status(result); + std::shared_ptr tmplt = std::make_shared(); + deserialize_block_template(result, tmplt); + return tmplt; + } + + std::shared_ptr monero_daemon_rpc::get_last_block_header() { + auto result = m_rpc->send_json_request("get_last_block_header"); + check_response_status(result); + std::shared_ptr header = std::make_shared(); + deserialize_block_header(result, header); + return header; + } + + std::shared_ptr monero_daemon_rpc::get_block_header_by_hash(const std::string& hash) { + std::shared_ptr params = std::make_shared(hash); + auto result = m_rpc->send_json_request("get_block_header_by_hash", params); + check_response_status(result); + std::shared_ptr header = std::make_shared(); + deserialize_block_header(result, header); + return header; + } + + std::shared_ptr monero_daemon_rpc::get_block_header_by_height(uint64_t height) { + std::shared_ptr params = std::make_shared(height); + auto result = m_rpc->send_json_request("get_block_header_by_height", params); + check_response_status(result); + std::shared_ptr header = std::make_shared(); + deserialize_block_header(result, header); + return header; + } + + std::vector> monero_daemon_rpc::get_block_headers_by_range(uint64_t start_height, uint64_t end_height) { + auto params = std::make_shared(start_height, end_height); + auto result = m_rpc->send_json_request("get_block_headers_range", params); + check_response_status(result); + std::vector> headers; + deserialize_block_headers(result, headers); + return headers; + } + + std::shared_ptr monero_daemon_rpc::get_block_by_hash(const std::string& hash) { + std::shared_ptr params = std::make_shared(hash); + auto result = m_rpc->send_json_request("get_block", params); + check_response_status(result); + auto block = std::make_shared(); + deserialize_block(result, block); + return block; + } + + std::shared_ptr monero_daemon_rpc::get_block_header_by_height_cached(uint64_t height, uint64_t max_height, std::unordered_map>& header_cache) { + // get header from cache + auto found = header_cache.find(height); + if (found != header_cache.end()) return found->second; + + // fetch and cache headers if not in cache + uint64_t end_height = std::min(max_height, height + NUM_HEADERS_PER_REQ - 1); + auto headers = get_block_headers_by_range(height, end_height); + + for(const auto& header : headers) { + if (header->m_height == boost::none) throw monero_error("Malformed block header: expected height to be defined"); + header_cache[header->m_height.get()] = header; + } + + // look up rather than default-construct via operator[]: a daemon response missing the + // requested height must not silently cache (and return) a null header + found = header_cache.find(height); + if (found == header_cache.end()) throw monero_error("Daemon did not return a header for height " + std::to_string(height)); + return found->second; + } + + std::vector> monero_daemon_rpc::get_blocks_by_hash(const std::vector& block_hashes, uint64_t start_height, bool prune) { + throw monero_error("monero_daemon_rpc::get_blocks_by_hash(): not implemented"); + } + + std::shared_ptr monero_daemon_rpc::get_block_by_height(uint64_t height) { + std::shared_ptr params = std::make_shared(height); + auto result = m_rpc->send_json_request("get_block", params); + check_response_status(result); + auto block = std::make_shared(); + deserialize_block(result, block); + return block; + } + + std::vector> monero_daemon_rpc::get_blocks_by_height(const std::vector& heights) { + // fetch blocks in binary + monero_get_blocks_by_height_request request(heights); + auto response = m_rpc->send_binary_request(request); + if (response.m_binary == boost::none) throw monero_error("Invalid Monero Binary response"); + boost::property_tree::ptree node; + monero_utils::binary_blocks_to_property_tree(response.m_binary.get(), node); + check_response_status(node); + std::vector> blocks; + deserialize_blocks(node, heights, blocks); + return blocks; + } + + std::vector> monero_daemon_rpc::get_blocks_by_range(boost::optional start_height, boost::optional end_height) { + if (start_height == boost::none) start_height = 0; + if (end_height == boost::none) { + uint64_t block_height = get_height(); + end_height = block_height == 0 ? 0 : block_height - 1; + } + + std::vector heights; + for (uint64_t height = start_height.get(); height <= end_height.get(); height++) heights.push_back(height); + + return get_blocks_by_height(heights); + } + + std::vector> monero_daemon_rpc::get_blocks_by_range_chunked(boost::optional start_height, boost::optional end_height, boost::optional max_chunk_size) { + if (start_height == boost::none) start_height = 0; + if (end_height == boost::none) { + uint64_t block_height = get_height(); + end_height = block_height == 0 ? 0 : block_height - 1; + } + + boost::optional last_height = boost::none; + std::vector> blocks; + + if (start_height.get() > end_height.get()) return blocks; + + // scoped to this call: see the comment on get_max_blocks()/get_block_header_by_height_cached() in the header + std::unordered_map> header_cache; + + while (last_height == boost::none || last_height.get() < end_height.get()) { + uint64_t height_to_get = (last_height == boost::none) ? start_height.get() : last_height.get() + 1; + + auto max_blocks = get_max_blocks(height_to_get, end_height, max_chunk_size, header_cache); + if (max_blocks.empty()) throw monero_error("Daemon returned an empty block chunk for a non-empty range at height " + std::to_string(height_to_get)); + blocks.insert(blocks.end(), max_blocks.begin(), max_blocks.end()); + last_height = blocks[blocks.size() - 1]->m_height.get(); + } + + return blocks; + } + + std::vector> monero_daemon_rpc::get_max_blocks(boost::optional start_height, boost::optional max_height, boost::optional chunk_size, std::unordered_map>& header_cache) { + if (start_height == boost::none) start_height = 0; + if (max_height == boost::none) { + uint64_t block_height = get_height(); + max_height = block_height == 0 ? 0 : block_height - 1; + } + if (chunk_size == boost::none) chunk_size = MAX_REQ_SIZE; + + // determine end height to fetch + uint64_t req_size = 0; + boost::optional end_height = boost::none; // none = no block accepted yet + uint64_t height_to_get = start_height.get(); + + while (req_size < chunk_size.get() && height_to_get <= max_height.get()) { + // get header of next block + auto header = get_block_header_by_height_cached(height_to_get, max_height.get(), header_cache); + if (header->m_size == boost::none) throw monero_error("Malformed block header: expected size to be defined"); + uint64_t header_size = header->m_size.get(); + // block cannot be bigger than max request size + if (header_size > chunk_size.get()) throw monero_error("Block exceeds maximum request size: " + std::to_string(header_size)); + + // done iterating if fetching block would exceed max request size + if (req_size + header_size > chunk_size.get()) break; + + // otherwise block is included + req_size += header_size; + end_height = height_to_get; + height_to_get++; + } + + if (end_height != boost::none) { + return get_blocks_by_range(start_height, end_height.get()); + } + + return std::vector>(); + } + + std::vector monero_daemon_rpc::get_block_hashes(const std::vector& block_hashes, uint64_t start_height) { + throw monero_error("monero_daemon_rpc::get_block_hashes(): not implemented"); + } + + std::vector> monero_daemon_rpc::get_txs(const std::vector& tx_hashes, bool prune) { + MTRACE("monero_daemon_rpc::get_txs()"); + if (tx_hashes.empty()) throw monero_error("Must provide an array of transaction hashes"); + auto params = std::make_shared(tx_hashes, prune); + auto result = m_rpc->send_path_request("get_transactions", params); + + try { check_response_status(result); } + catch (const std::exception& ex) { + if (std::string(ex.what()).find("Failed to parse hex representation of transaction hash") != std::string::npos) { + throw monero_error("Invalid transaction hash"); + } + throw; + } + + std::vector> txs; + deserialize_txs(result, txs); + return txs; + } + + std::vector monero_daemon_rpc::get_tx_hexes(const std::vector& tx_hashes, bool prune) { + MTRACE("monero_daemon_rpc::get_tx_hexes()"); + std::vector hexes; + for(const auto& tx : get_txs(tx_hashes, prune)) { + // tx may be pruned regardless of configuration + if (tx->m_pruned_hex == boost::none) { + if (tx->m_full_hex == boost::none) throw monero_error("Tx has no hex"); + hexes.push_back(tx->m_full_hex.get()); + } else { + hexes.push_back(tx->m_pruned_hex.get()); + } + } + return hexes; + } + + std::shared_ptr monero_daemon_rpc::get_miner_tx_sum(uint64_t height, uint64_t num_blocks) { + auto params = std::make_shared(height, num_blocks); + auto result = m_rpc->send_json_request("get_coinbase_tx_sum", params); + check_response_status(result); + auto sum = std::make_shared(); + deserialize_miner_tx_sum(result, sum); + return sum; + } + + std::shared_ptr monero_daemon_rpc::get_fee_estimate(uint64_t grace_blocks) { + auto params = std::make_shared(grace_blocks); + auto result = m_rpc->send_json_request("get_fee_estimate", params); + check_response_status(result); + auto estimate = std::make_shared(); + deserialize_fee_estimate(result, estimate); + return estimate; + } + + std::shared_ptr monero_daemon_rpc::submit_tx_hex(const std::string& tx_hex, bool do_not_relay) { + MTRACE("monero_daemon_rpc::submit_tx_hex()"); + auto params = std::make_shared(tx_hex, do_not_relay); + auto result = m_rpc->send_path_request("send_raw_transaction", params); + auto sum = std::make_shared(); + deserialize_submit_tx_result(result, sum); + + // set m_is_good based on status + try { + check_response_status(result); + sum->m_is_good = true; + } catch (...) { + sum->m_is_good = false; + } + return sum; + } + + void monero_daemon_rpc::relay_txs_by_hash(const std::vector& tx_hashes) { + MTRACE("monero_daemon_rpc::relay_txs_by_hash()"); + auto params = std::make_shared(tx_hashes); + auto result = m_rpc->send_json_request("relay_tx", params); + check_response_status(result); + } + + std::shared_ptr monero_daemon_rpc::get_tx_pool_stats() { + auto result = m_rpc->send_path_request("get_transaction_pool_stats"); + check_response_status(result); + auto stats = std::make_shared(); + deserialize_tx_pool_stats(result, stats); + return stats; + } + + std::vector> monero_daemon_rpc::get_tx_pool() { + auto result = m_rpc->send_path_request("get_transaction_pool"); + check_response_status(result); + std::vector> pool; + deserialize_txs(result, pool); + return pool; + } + + std::vector monero_daemon_rpc::get_tx_pool_hashes() { + auto result = m_rpc->send_path_request("get_transaction_pool_hashes"); + check_response_status(result); + std::vector tx_hashes; + deserialize_tx_hashes(result, tx_hashes); + return tx_hashes; + } + + void monero_daemon_rpc::flush_tx_pool(const std::vector &hashes) { + MTRACE("monero_daemon_rpc::flush_tx_pool()"); + auto params = std::make_shared(hashes); + auto result = m_rpc->send_json_request("flush_txpool", params); + check_response_status(result); + } + + void monero_daemon_rpc::flush_tx_pool() { + std::vector hashes; + flush_tx_pool(hashes); + } + + void monero_daemon_rpc::flush_tx_pool(const std::string &hash) { + std::vector hashes; + hashes.push_back(hash); + flush_tx_pool(hashes); + } + + std::vector monero_daemon_rpc::get_key_image_spent_statuses(const std::vector& key_images) { + if (key_images.empty()) throw monero_error("Must provide key images to check the status of"); + auto params = std::make_shared(key_images); + auto result = m_rpc->send_path_request("is_key_image_spent", params); + check_response_status(result); + std::vector statuses; + deserialize_key_image_spent_status(result, statuses); + return statuses; + } + + std::vector> monero_daemon_rpc::get_outputs(const std::vector& outputs) { + throw monero_error("monero_daemon_rpc::get_outputs(): not implemented"); + } + + std::vector> monero_daemon_rpc::get_output_histogram(const std::vector& amounts, const boost::optional& min_count, const boost::optional& max_count, const boost::optional& is_unlocked, const boost::optional& recent_cutoff) { + MTRACE("monero_daemon_rpc::get_output_histogram()"); + auto params = std::make_shared(amounts, min_count, max_count, is_unlocked, recent_cutoff); + auto result = m_rpc->send_json_request("get_output_histogram", params); + check_response_status(result); + std::vector> entries; + deserialize_output_histogram_entries(result, entries); + return entries; + } + + std::vector> monero_daemon_rpc::get_output_distribution(const std::vector& amounts, const boost::optional& is_cumulative, const boost::optional& start_height, const boost::optional& end_height) { + MTRACE("monero_daemon_rpc::get_output_distribution()"); + auto params = std::make_shared(amounts, is_cumulative, start_height, end_height); + auto result = m_rpc->send_json_request("get_output_distribution", params); + check_response_status(result); + std::vector> entries; + deserialize_output_distribution_entries(result, entries); + return entries; + } + + std::shared_ptr monero_daemon_rpc::get_info() { + auto result = m_rpc->send_json_request("get_info"); + check_response_status(result); + std::shared_ptr info = std::make_shared(); + deserialize_daemon_info(result, info); + return info; + } + + std::shared_ptr monero_daemon_rpc::get_sync_info() { + auto result = m_rpc->send_json_request("sync_info"); + check_response_status(result); + std::shared_ptr info = std::make_shared(); + deserialize_daemon_sync_info(result, info); + return info; + } + + std::shared_ptr monero_daemon_rpc::get_hard_fork_info() { + auto result = m_rpc->send_json_request("hard_fork_info"); + check_response_status(result); + std::shared_ptr info = std::make_shared(); + deserialize_hard_fork_info(result, info); + return info; + } + + std::vector> monero_daemon_rpc::get_alt_chains() { + auto result = m_rpc->send_json_request("get_alternate_chains"); + check_response_status(result); + std::vector> alt_chains; + deserialize_alt_chains(result, alt_chains); + return alt_chains; + } + + std::vector monero_daemon_rpc::get_alt_block_hashes() { + auto result = m_rpc->send_path_request("get_alt_blocks_hashes"); + check_response_status(result); + std::vector hashes; + deserialize_alt_block_hashes(result, hashes); + return hashes; + } + + int monero_daemon_rpc::get_download_limit() { + MTRACE("monero_daemon_rpc::get_download_limit()"); + auto limits = get_bandwidth_limits(); + if (limits->m_down != boost::none) return limits->m_down.get(); + throw monero_error("Could not get download limit"); + } + + int monero_daemon_rpc::set_download_limit(int limit) { + MTRACE("monero_daemon_rpc::set_download_limit()"); + if (limit == -1) return reset_download_limit(); + if (limit <= 0) throw monero_error("Download limit must be an integer greater than 0"); + auto result = set_bandwidth_limits(0, limit); + if (result->m_down != boost::none) return result->m_down.get(); + throw monero_error("Could not set download limit"); + } + + int monero_daemon_rpc::reset_download_limit() { + MTRACE("monero_daemon_rpc::reset_download_limit()"); + auto result = set_bandwidth_limits(0, -1); + if (result->m_down != boost::none) return result->m_down.get(); + throw monero_error("Could not set download limit"); + } + + int monero_daemon_rpc::get_upload_limit() { + MTRACE("monero_daemon_rpc::get_upload_limit()"); + auto limits = get_bandwidth_limits(); + if (limits->m_up != boost::none) return limits->m_up.get(); + throw monero_error("Could not get upload limit"); + } + + int monero_daemon_rpc::set_upload_limit(int limit) { + MTRACE("monero_daemon_rpc::set_upload_limit()"); + if (limit == -1) return reset_upload_limit(); + if (limit <= 0) throw monero_error("Upload limit must be an integer greater than 0"); + auto result = set_bandwidth_limits(limit, 0); + if (result->m_up != boost::none) return result->m_up.get(); + throw monero_error("Could not set download limit"); + } + + int monero_daemon_rpc::reset_upload_limit() { + MTRACE("monero_daemon_rpc::reset_upload_limit()"); + auto result = set_bandwidth_limits(-1, 0); + if (result->m_up != boost::none) return result->m_up.get(); + throw monero_error("Could not set download limit"); + } + + std::vector> monero_daemon_rpc::get_peers() { + auto result = m_rpc->send_json_request("get_connections"); + check_response_status(result); + std::vector> peers; + deserialize_peers(result, peers); + return peers; + } + + std::vector> monero_daemon_rpc::get_known_peers() { + auto result = m_rpc->send_path_request("get_peer_list"); + check_response_status(result); + std::vector> peers; + deserialize_peers(result, peers); + return peers; + } + + void monero_daemon_rpc::set_outgoing_peer_limit(int limit) { + MTRACE("monero_daemon_rpc::set_outgoing_peer_limit()"); + if (limit < 0) throw monero_error("Outgoing peer limit must be >= 0"); + auto params = std::make_shared(); + params->m_out_peers = limit; + auto result = m_rpc->send_path_request("out_peers", params); + check_response_status(result); + } + + void monero_daemon_rpc::set_incoming_peer_limit(int limit) { + MTRACE("monero_daemon_rpc::set_incoming_peer_limit()"); + if (limit < 0) throw monero_error("Incoming peer limit must be >= 0"); + auto params = std::make_shared(); + params->m_in_peers = limit; + auto result = m_rpc->send_path_request("in_peers", params); + check_response_status(result); + } + + std::vector> monero_daemon_rpc::get_peer_bans() { + MTRACE("monero_daemon_rpc::get_peer_bans()"); + auto result = m_rpc->send_json_request("get_bans"); + check_response_status(result); + std::vector> bans; + deserialize_bans(result, bans); + return bans; + } + + void monero_daemon_rpc::set_peer_bans(const std::vector>& bans) { + MTRACE("monero_daemon_rpc::set_peer_bans()"); + auto params = std::make_shared(bans); + auto result = m_rpc->send_json_request("set_bans", params); + check_response_status(result); + } + + void monero_daemon_rpc::start_mining(const std::string &address, boost::optional num_threads, boost::optional background_mining, boost::optional ignore_battery) { + MTRACE("monero_daemon_rpc::start_mining()"); + if (address.empty()) throw monero_error("Must provide address to mine to"); + if (num_threads != boost::none && num_threads.get() == 0) throw monero_error("Number of threads must be an integer greater than 0"); + auto params = std::make_shared(address, num_threads, background_mining, ignore_battery); + auto result = m_rpc->send_path_request("start_mining", params); + check_response_status(result); + } + + void monero_daemon_rpc::stop_mining() { + MTRACE("monero_daemon_rpc::stop_mining()"); + auto result = m_rpc->send_path_request("stop_mining"); + check_response_status(result); + } + + std::shared_ptr monero_daemon_rpc::get_mining_status() { + MTRACE("monero_daemon_rpc::get_mining_status()"); + auto result = m_rpc->send_path_request("mining_status"); + check_response_status(result); + auto status = std::make_shared(); + deserialize_mining_status(result, status); + return status; + } + + std::shared_ptr monero_daemon_rpc::generate_blocks(const std::string& wallet_address, uint64_t num_blocks, const boost::optional& prev_block_hash, const boost::optional& starting_nonce) { + MTRACE("monero_daemon_rpc::generate_blocks()"); + if (wallet_address.empty()) throw monero_error("Must provide an address to mine to"); + if (num_blocks == 0) throw monero_error("Must provide a number of blocks to generate greater than 0"); + auto params = std::make_shared(wallet_address, num_blocks, prev_block_hash, starting_nonce); + auto result = m_rpc->send_json_request("generateblocks", params); + check_response_status(result); + auto generate_result = std::make_shared(); + deserialize_generate_blocks_result(result, generate_result); + return generate_result; + } + + void monero_daemon_rpc::submit_blocks(const std::vector& block_blobs) { + MTRACE("monero_daemon_rpc::submit_blocks()"); + if (block_blobs.empty()) throw monero_error("Must provide an array of mined block blobs to submit"); + auto params = std::make_shared(block_blobs); + auto result = m_rpc->send_json_request("submit_block", params); + check_response_status(result); + } + + std::shared_ptr monero_daemon_rpc::prune_blockchain(bool check) { + MTRACE("monero_daemon_rpc::prune_blockchain()"); + auto params = std::make_shared(check); + auto result = m_rpc->send_json_request("prune_blockchain", params); + check_response_status(result); + std::shared_ptr prune_result = std::make_shared(); + deserialize_prune_result(result, prune_result); + return prune_result; + } + + std::shared_ptr monero_daemon_rpc::check_for_update() { + MTRACE("monero_daemon_rpc::check_for_update()"); + auto params = std::make_shared("check"); + auto result = m_rpc->send_path_request("update", params); + check_response_status(result); + auto check_result = std::make_shared(); + deserialize_update_check_result(result, check_result); + return check_result; + } + + std::shared_ptr monero_daemon_rpc::download_update(const std::string& path) { + MTRACE("monero_daemon_rpc::download_update()"); + auto params = std::make_shared("download", path); + auto result = m_rpc->send_path_request("update", params); + check_response_status(result); + auto download_result = std::make_shared(); + deserialize_update_download_result(result, download_result); + return download_result; + } + + void monero_daemon_rpc::stop() { + MTRACE("monero_daemon_rpc::stop()"); + auto result = m_rpc->send_path_request("stop_daemon"); + check_response_status(result); + } + + std::shared_ptr monero_daemon_rpc::wait_for_next_block_header() { + MTRACE("monero_daemon_rpc::wait_for_next_block_header()"); + + // use mutex and condition variable with predicate to wait for block + boost::mutex temp; + boost::condition_variable cv; + bool ready = false; + + // create listener which notifies condition variable when block is added + block_notifier block_listener(&temp, &cv, &ready); + + // register the listener, and unregister it unconditionally on the way out + // otherwise m_listeners keeps a pointer into this stack + // frame after it's gone, and the next block dispatch calls through it (UAF) + struct listener_guard { + monero_daemon_rpc* daemon; + monero_daemon_listener& listener; + ~listener_guard() { + // never let remove_listener() escape a destructor since if we're + // already unwinding from another exception, that would call std::terminate + try { daemon->remove_listener(listener); } catch (...) {} + } + } unregister_guard{this, block_listener}; + + add_listener(block_listener); + + // guard against an offline/unresponsive daemon (no connection, stalled node, etc.): + // such a daemon will never announce a new block, so bound the wait instead of hanging + // this call forever. + // TODO monero-java waits indefinitely. + bool timed_out; + std::shared_ptr header; + { + boost::mutex::scoped_lock lock(temp); + cv.timed_wait(lock, boost::posix_time::minutes(30), [&]() { return ready; }); + // check ready directly rather than trust timed_wait's return value, still under the + // lock: avoids any discrepancy if a header arrives right at the timeout boundary + timed_out = !ready; + // copy out while still holding the lock: block_listener stays registered until the + // unregister_guard destructor runs (at function exit, after this point), so + // on_block_header() could still write m_last_header under temp concurrently with an + // unlocked read here + if (!timed_out) header = block_listener.m_last_header; + } + + if (timed_out) throw monero_error("Timed out waiting for next block header"); + + // return last height + if (header == nullptr) throw monero_error("Could not get last block header."); + return header; + } + + std::shared_ptr monero_daemon_rpc::get_bandwidth_limits() { + MTRACE("monero_daemon_rpc::get_bandwidth_limits()"); + auto result = m_rpc->send_path_request("get_limit"); + check_response_status(result); + auto limits = std::make_shared(); + monero_bandwidth_limits::from_property_tree(result, limits); + return limits; + } + + std::shared_ptr monero_daemon_rpc::set_bandwidth_limits(int up, int down) { + MTRACE("monero_daemon_rpc::set_bandwidth_limits()"); + auto limits = std::make_shared(up, down); + auto result = m_rpc->send_path_request("set_limit", limits); + check_response_status(result); + monero_bandwidth_limits::from_property_tree(result, limits); + return limits; + } + + void monero_daemon_rpc::refresh_listening() { + boost::lock_guard poller_lock(m_poller_mutex); + + bool should_poll; + { + boost::lock_guard lock(m_listeners_mutex); + should_poll = m_listeners.size() > 0; + } + + if (!m_poller && should_poll) { + m_poller = std::make_unique(this); + } + if (m_poller) m_poller->request_is_polling(should_poll); + } + + monero_daemon_rpc::~monero_daemon_rpc() { + MTRACE("~monero_daemon_rpc()"); + remove_listeners(); + } +} \ No newline at end of file diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon_rpc.h b/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon_rpc.h new file mode 100644 index 0000000..01872d1 --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon_rpc.h @@ -0,0 +1,189 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ +#pragma once + +#include "monero_daemon.h" + +/** + * Implements a monero_daemon.h using monero-project's monerod. + */ +namespace monero { + + // forward declaration of internal daemon poller + class monero_daemon_poller; + + /** + * Implements a Monero daemon using monerod-rpc. + */ + class monero_daemon_rpc : public monero_daemon { + public: + + /** + * Destruct the daemon. + */ + ~monero_daemon_rpc() override; + monero_daemon_rpc(const std::shared_ptr& rpc); + monero_daemon_rpc(const std::string& uri, const std::string& username = "", const std::string& password = "", const std::string& proxy_uri = "", const std::string& zmq_uri = "", const boost::optional& timeout = boost::none); + + /** + * Get the daemon's RPC connection. + * + * @return the daemon's rpc connection + */ + std::shared_ptr get_rpc_connection() const; + + /** + * Indicates if the client is connected to the daemon via RPC. + * + * @return true if the client is connected to the daemon, false otherwise + */ + bool is_connected(); + + /** + * Set the daemon poll period. + * + * @param period_ms poll period in milliseconds. + */ + void set_poll_period_in_ms(uint64_t period_ms); + + /** + * Supported daemon methods. + */ + std::set get_listeners() override; + void add_listener(monero_daemon_listener &listener) override; + void remove_listener(monero_daemon_listener &listener) override; + void remove_listeners() override; + monero_version get_version() override; + bool is_trusted() override; + uint64_t get_height() override; + std::string get_block_hash(uint64_t height) override; + std::shared_ptr get_block_template(const std::string& wallet_address, const boost::optional& reserve_size = boost::none) override; + std::shared_ptr get_last_block_header() override; + std::shared_ptr get_block_header_by_hash(const std::string& hash) override; + std::shared_ptr get_block_header_by_height(uint64_t height) override; + std::vector> get_block_headers_by_range(uint64_t start_height, uint64_t end_height) override; + std::shared_ptr get_block_by_hash(const std::string& hash) override; + std::vector> get_blocks_by_hash(const std::vector& block_hashes, uint64_t start_height, bool prune) override; + std::shared_ptr get_block_by_height(uint64_t height) override; + std::vector> get_blocks_by_height(const std::vector& heights) override; + std::vector> get_blocks_by_range(boost::optional start_height, boost::optional end_height) override; + std::vector> get_blocks_by_range_chunked(boost::optional start_height, boost::optional end_height, boost::optional max_chunk_size) override; + std::vector get_block_hashes(const std::vector& block_hashes, uint64_t start_height) override; + std::vector> get_txs(const std::vector& tx_hashes, bool prune = false) override; + std::vector get_tx_hexes(const std::vector& tx_hashes, bool prune = false) override; + std::shared_ptr get_miner_tx_sum(uint64_t height, uint64_t num_blocks) override; + std::shared_ptr get_fee_estimate(uint64_t grace_blocks = 0) override; + std::shared_ptr submit_tx_hex(const std::string& tx_hex, bool do_not_relay = false) override; + void relay_txs_by_hash(const std::vector& tx_hashes) override; + std::shared_ptr get_tx_pool_stats() override; + std::vector> get_tx_pool() override; + std::vector get_tx_pool_hashes() override; + void flush_tx_pool(const std::vector &hashes) override; + void flush_tx_pool() override; + void flush_tx_pool(const std::string &hash) override; + std::vector get_key_image_spent_statuses(const std::vector& key_images) override; + std::vector> get_outputs(const std::vector& outputs) override; + std::vector> get_output_histogram(const std::vector& amounts, const boost::optional& min_count, const boost::optional& max_count, const boost::optional& is_unlocked, const boost::optional& recent_cutoff) override; + std::vector> get_output_distribution(const std::vector& amounts, const boost::optional& is_cumulative = boost::none, const boost::optional& start_height = boost::none, const boost::optional& end_height = boost::none) override; + std::shared_ptr get_info() override; + std::shared_ptr get_sync_info() override; + std::shared_ptr get_hard_fork_info() override; + std::vector> get_alt_chains() override; + std::vector get_alt_block_hashes() override; + int get_download_limit() override; + int set_download_limit(int limit) override; + int reset_download_limit() override; + int get_upload_limit() override; + int set_upload_limit(int limit) override; + int reset_upload_limit() override; + std::vector> get_peers() override; + std::vector> get_known_peers() override; + void set_outgoing_peer_limit(int limit) override; + void set_incoming_peer_limit(int limit) override; + std::vector> get_peer_bans() override; + void set_peer_bans(const std::vector>& bans) override; + void start_mining(const std::string &address, boost::optional num_threads, boost::optional background_mining, boost::optional ignore_battery) override; + void stop_mining() override; + std::shared_ptr get_mining_status() override; + std::shared_ptr generate_blocks(const std::string& wallet_address, uint64_t num_blocks, const boost::optional& prev_block_hash = boost::none, const boost::optional& starting_nonce = boost::none) override; + void submit_blocks(const std::vector& block_blobs) override; + std::shared_ptr prune_blockchain(bool check) override; + std::shared_ptr check_for_update() override; + std::shared_ptr download_update(const std::string& path = "") override; + void stop() override; + std::shared_ptr wait_for_next_block_header() override; + + // --------------------------------- PRIVATE -------------------------------- + + private: + friend class monero_daemon_poller; + mutable boost::recursive_mutex m_listeners_mutex; + std::set m_listeners; + std::shared_ptr m_rpc; + boost::mutex m_poller_mutex; + std::unique_ptr m_poller; + + // header_cache is scoped to a single get_blocks_by_range_chunked() call (see below) rather than + // stored as a member: it only helps avoid re-fetching a header already seen earlier in that one + // chunking pass, and keeping it as instance state would let it grow unbounded over the connection's + // lifetime and serve stale headers across reorgs on later calls + std::vector> get_max_blocks(boost::optional start_height, boost::optional max_height, boost::optional chunk_size, std::unordered_map>& header_cache); + std::shared_ptr get_block_header_by_height_cached(uint64_t height, uint64_t max_height, std::unordered_map>& header_cache); + std::shared_ptr get_bandwidth_limits(); + std::shared_ptr set_bandwidth_limits(int up, int down); + void refresh_listening(); + void wait_for_listener_callbacks_idle(); + }; + +} diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon_rpc_model.cpp b/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon_rpc_model.cpp new file mode 100644 index 0000000..96f4dde --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon_rpc_model.cpp @@ -0,0 +1,1302 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ +#include "monero_daemon_rpc_model.h" +#include "common/monero_error.h" +#include "utils/gen_utils.h" +#include "utils/monero_utils.h" + +namespace monero { + + // --------------------------- MONERO RPC BAN --------------------------- + + monero_rpc_ban::monero_rpc_ban(const std::shared_ptr &ban) { + m_host = ban->m_host; + m_ip = ban->m_ip; + m_is_banned = ban->m_is_banned; + m_seconds = ban->m_seconds; + } + + rapidjson::Value monero_rpc_ban::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_host != boost::none) monero_utils::add_json_member("host", m_host.get(), allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_ip != boost::none) monero_utils::add_json_member("ip", m_ip.get(), allocator, root, value_num); + if (m_seconds != boost::none) monero_utils::add_json_member("seconds", m_seconds.get(), allocator, root, value_num); + + // set bool values + if (m_is_banned != boost::none) monero_utils::add_json_member("ban", m_is_banned.get(), allocator, root); + + // return root + return root; + } + + // --------------------------- MONERO GET BLOCKS BY HEIGHT REQUEST --------------------------- + + monero_get_blocks_by_height_request::monero_get_blocks_by_height_request(uint64_t num_blocks) { + m_method = "get_blocks_by_height.bin"; + m_heights.reserve(num_blocks); + for (uint64_t i = 0; i < num_blocks; i++) m_heights.push_back(i); + } + + rapidjson::Value monero_get_blocks_by_height_request::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + rapidjson::Value root(rapidjson::kObjectType); + if (!m_heights.empty()) root.AddMember("heights", monero_utils::to_rapidjson_val(allocator, m_heights), allocator); + return root; + } + + // --------------------------- MONERO DOWNLOAD UPDATE PARAMS --------------------------- + + monero_download_update_params::monero_download_update_params(const std::string& command, const std::string& path): m_command(command) { + if (!path.empty()) m_path = path; + } + + rapidjson::Value monero_download_update_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_command != boost::none) monero_utils::add_json_member("command", m_command.get(), allocator, root, value_str); + if (m_path != boost::none) monero_utils::add_json_member("path", m_path.get(), allocator, root, value_str); + + // return root + return root; + } + + // --------------------------- MONERO BANDWITH LIMITS PARAMS --------------------------- + + void monero_bandwidth_limits::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& limits) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("limit_up")) limits->m_up = it->second.get_value(); + else if (key == std::string("limit_down")) limits->m_down = it->second.get_value(); + } + } + + rapidjson::Value monero_bandwidth_limits::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_up != boost::none) monero_utils::add_json_member("limit_up", m_up.get(), allocator, root, value_num); + if (m_down != boost::none) monero_utils::add_json_member("limit_down", m_down.get(), allocator, root, value_num); + + // return root + return root; + } + + // --------------------------- MONERO SUBMIT TX PARAMS --------------------------- + + rapidjson::Value monero_submit_tx_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_tx_hex != boost::none) monero_utils::add_json_member("tx_as_hex", m_tx_hex.get(), allocator, root, value_str); + + // set bool values + if (m_do_not_relay != boost::none) monero_utils::add_json_member("do_not_relay", m_do_not_relay.get(), allocator, root); + + // set sub-arrays + if (!m_tx_hashes.empty()) root.AddMember("txids", monero_utils::to_rapidjson_val(allocator, m_tx_hashes), allocator); + + // return root + return root; + } + + // --------------------------- MONERO PEER LIMITS PARAMS --------------------------- + + rapidjson::Value monero_peer_limits_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_in_peers != boost::none) monero_utils::add_json_member("in_peers", m_in_peers.get(), allocator, root, value_num); + if (m_out_peers != boost::none) monero_utils::add_json_member("out_peers", m_out_peers.get(), allocator, root, value_num); + + // return root + return root; + } + + // --------------------------- MONERO GET TXS PARAMS --------------------------- + + rapidjson::Value monero_get_txs_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set bool values + if (m_prune != boost::none) monero_utils::add_json_member("prune", m_prune.get(), allocator, root); + if (m_decode_as_json != boost::none) monero_utils::add_json_member("decode_as_json", m_decode_as_json.get(), allocator, root); + + // set sub-arrays + if (!m_tx_hashes.empty()) root.AddMember("txs_hashes", monero_utils::to_rapidjson_val(allocator, m_tx_hashes), allocator); + + // return root + return root; + } + + // --------------------------- MONERO IS KEY IMAGE SPENT PARAMS --------------------------- + + rapidjson::Value monero_is_key_image_spent_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set sub-arrays + if (!m_key_images.empty()) root.AddMember("key_images", monero_utils::to_rapidjson_val(allocator, m_key_images), allocator); + + // return root + return root; + } + + // --------------------------- MONERO START MINING PARAMS --------------------------- + + rapidjson::Value monero_start_mining_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_miner_address != boost::none) monero_utils::add_json_member("miner_address", m_miner_address.get(), allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_num_threads != boost::none) monero_utils::add_json_member("threads_count", m_num_threads.get(), allocator, root, value_num); + + // set bool values + if (m_is_background != boost::none) monero_utils::add_json_member("do_background_mining", m_is_background.get(), allocator, root); + if (m_ignore_battery != boost::none) monero_utils::add_json_member("ignore_battery", m_ignore_battery.get(), allocator, root); + + // return root + return root; + } + + // --------------------------- MONERO GENERATE BLOCKS PARAMS --------------------------- + + rapidjson::Value monero_generate_blocks_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_wallet_address != boost::none) monero_utils::add_json_member("wallet_address", m_wallet_address.get(), allocator, root, value_str); + if (m_prev_block_hash != boost::none) monero_utils::add_json_member("prev_block", m_prev_block_hash.get(), allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_num_blocks != boost::none) monero_utils::add_json_member("amount_of_blocks", m_num_blocks.get(), allocator, root, value_num); + if (m_starting_nonce != boost::none) monero_utils::add_json_member("starting_nonce", m_starting_nonce.get(), allocator, root, value_num); + + // return root + return root; + } + + // --------------------------- MONERO PRUNE BLOCKCHAIN PARAMS --------------------------- + + rapidjson::Value monero_prune_blockchain_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set bool values + if (m_check != boost::none) monero_utils::add_json_member("check", m_check.get(), allocator, root); + + // return root + return root; + } + + // --------------------------- MONERO SUBMIT BLOCKS PARAMS --------------------------- + + rapidjson::Value monero_submit_blocks_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + return monero_utils::to_rapidjson_val(allocator, m_block_blobs); + } + + // --------------------------- MONERO GET BLOCK PARAMS --------------------------- + + rapidjson::Value monero_get_block_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + rapidjson::Value value_num(rapidjson::kNumberType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_hash != boost::none) monero_utils::add_json_member("hash", m_hash.get(), allocator, root, value_str); + if (m_wallet_address != boost::none) monero_utils::add_json_member("wallet_address", m_wallet_address.get(), allocator, root, value_str); + + // set num values + if (m_height != boost::none) monero_utils::add_json_member("height", m_height.get(), allocator, root, value_num); + if (m_start_height != boost::none) monero_utils::add_json_member("start_height", m_start_height.get(), allocator, root, value_num); + if (m_end_height != boost::none) monero_utils::add_json_member("end_height", m_end_height.get(), allocator, root, value_num); + if (m_reserve_size != boost::none) monero_utils::add_json_member("reserve_size", m_reserve_size.get(), allocator, root, value_num); + + // set bool values + if (m_fill_pow_hash != boost::none) monero_utils::add_json_member("fill_pow_hash", m_fill_pow_hash.get(), allocator, root); + + // return root + return root; + } + + // --------------------------- MONERO GET BLOCK HASH PARAMS --------------------------- + + rapidjson::Value monero_get_block_hash_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + std::vector params; + if (m_height != boost::none) params.push_back(m_height.get()); + return monero_utils::to_rapidjson_val(allocator, params); + } + + // --------------------------- MONERO GET MINER TX SUM PARAMS --------------------------- + + rapidjson::Value monero_get_miner_tx_sum_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_height != boost::none) monero_utils::add_json_member("height", m_height.get(), allocator, root, value_num); + if (m_count != boost::none) monero_utils::add_json_member("count", m_count.get(), allocator, root, value_num); + + // return root + return root; + } + + // --------------------------- MONERO GET FEE ESTIMATE PARAMS --------------------------- + + rapidjson::Value monero_get_fee_estimate_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_grace_blocks != boost::none) monero_utils::add_json_member("grace_blocks", m_grace_blocks.get(), allocator, root, value_num); + + // return root + return root; + } + + // --------------------------- MONERO SET BANS PARAMS --------------------------- + + monero_set_bans_params::monero_set_bans_params(const std::vector>& bans) { + for(const auto& ban : bans) m_bans.push_back(std::make_shared(ban)); + } + + rapidjson::Value monero_set_bans_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set sub-arrays + if (!m_bans.empty()) root.AddMember("bans", monero_utils::to_rapidjson_val(allocator, m_bans), allocator); + + // return root + return root; + } + + // --------------------------- MONERO GET OUTPUT HISTOGRAM PARAMS --------------------------- + + rapidjson::Value monero_get_output_histogram_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_min_count != boost::none) monero_utils::add_json_member("min_count", m_min_count.get(), allocator, root, value_num); + if (m_max_count != boost::none) monero_utils::add_json_member("max_count", m_max_count.get(), allocator, root, value_num); + if (m_recent_cutoff != boost::none) monero_utils::add_json_member("recent_cutoff", m_recent_cutoff.get(), allocator, root, value_num); + + // set bool values + if (m_is_unlocked != boost::none) monero_utils::add_json_member("unlocked", m_is_unlocked.get(), allocator, root); + + // set sub-array values + if (!m_amounts.empty()) root.AddMember("amounts", monero_utils::to_rapidjson_val(allocator, m_amounts), allocator); + + // return root + return root; + } + + // --------------------------- MONERO GET OUTPUT DISTRIBUTION PARAMS --------------------------- + + rapidjson::Value monero_get_output_distribution_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_from_height != boost::none) monero_utils::add_json_member("from_height", m_from_height.get(), allocator, root, value_num); + if (m_to_height != boost::none) monero_utils::add_json_member("to_height", m_to_height.get(), allocator, root, value_num); + + // set bool values + if (m_cumulative != boost::none) monero_utils::add_json_member("cumulative", m_cumulative.get(), allocator, root); + if (m_binary != boost::none) monero_utils::add_json_member("binary", m_binary.get(), allocator, root); + + // set sub-array values + if (!m_amounts.empty()) root.AddMember("amounts", monero_utils::to_rapidjson_val(allocator, m_amounts), allocator); + + // return root + return root; + } + + // --------------------------- MONERO GET BLOCK RESULT --------------------------- + + void monero_get_block_result::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("count")) result->m_count = it->second.get_value(); + else if (key == std::string("height")) result->m_height = it->second.get_value(); + else if (key == std::string("untrusted")) result->m_untrusted = it->second.get_value(); + } + } + + // ------------------------------ RPC Deserialization --------------------------------- + + void deserialize_version(const boost::property_tree::ptree& node, monero_version& version) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("version")) version.m_number = it->second.get_value(); + else if (key == std::string("release")) version.m_is_release = it->second.get_value(); + } + } + + void deserialize_output(const boost::property_tree::ptree& node, const std::shared_ptr& output) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("gen")) throw monero_error("Output with 'gen' from daemon rpc is miner tx which we ignore (i.e. each miner input is null)"); + else if (key == std::string("key")) { + auto key_node = it->second; + for (auto it2 = key_node.begin(); it2 != key_node.end(); ++it2) { + std::string key_key = it2->first; + if (key_key == std::string("amount")) output->m_amount = it2->second.get_value(); + else if (key_key == std::string("k_image")) { + if (!output->m_key_image) output->m_key_image = std::make_shared(); + output->m_key_image->m_hex = it2->second.data(); + } + else if (key_key == std::string("key_offsets")) { + auto offsets_node = it2->second; + + for (auto it3 = offsets_node.begin(); it3 != offsets_node.end(); ++it3) { + output->m_ring_output_indices.push_back(it3->second.get_value()); + } + } + } + } + else if (key == std::string("amount")) output->m_amount = it->second.get_value(); + else if (key == std::string("target")) { + auto target_node = it->second; + + for(auto it2 = target_node.begin(); it2 != target_node.end(); ++it2) { + std::string target_key = it2->first; + + if (target_key == std::string("key")) { + output->m_stealth_public_key = it2->second.data(); + } + else if (target_key == std::string("tagged_key")) { + auto tagged_key_node = it2->second; + + for (auto it3 = tagged_key_node.begin(); it3 != tagged_key_node.end(); ++it3) { + std::string tagged_key_key = it3->first; + + if (tagged_key_key == std::string("key")) { + output->m_stealth_public_key = it3->second.data(); + } + } + } + } + } + } + } + + void deserialize_tx(const boost::property_tree::ptree& node, const std::shared_ptr& tx) { + std::shared_ptr block = tx->m_block; + std::string as_json; + std::string tx_json; + + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("tx_hash") || key == std::string("id_hash")) { + std::string tx_hash = it->second.data(); + if (!tx_hash.empty()) tx->m_hash = tx_hash; + } + else if (key == std::string("block_timestamp")) { + if (block == nullptr) block = std::make_shared(); + block->m_timestamp = it->second.get_value(); + } + else if (key == std::string("block_height")) { + if (block == nullptr) block = std::make_shared(); + block->m_height = it->second.get_value(); + } + else if (key == std::string("last_relayed_time")) { + tx->m_last_relayed_timestamp = it->second.get_value(); + } + else if (key == std::string("receive_time") || key == std::string("received_timestamp")) { + tx->m_received_timestamp = it->second.get_value(); + } + else if (key == std::string("confirmations")) { + tx->m_num_confirmations = it->second.get_value(); + } + else if (key == std::string("in_pool")) { + bool in_pool = it->second.get_value(); + tx->m_is_confirmed = !in_pool; + tx->m_in_tx_pool = in_pool; + } + else if (key == std::string("double_spend_seen")) { + tx->m_is_double_spend_seen = it->second.get_value(); + } + else if (key == std::string("version")) { + tx->m_version = it->second.get_value(); + } + else if (key == std::string("vin")) { + auto &rpc_inputs = it->second; + bool is_miner_input = false; + + if (rpc_inputs.size() == 1) { + auto first = rpc_inputs.begin()->second; + if (first.get_child_optional("gen")) { + is_miner_input = true; + } + } + // ignore miner input + // TODO why? + if (!is_miner_input) { + std::vector> inputs; + for (auto &vin_entry : rpc_inputs) { + auto output = std::make_shared(); + deserialize_output(vin_entry.second, output); + output->m_tx = tx; + inputs.push_back(output); + } + + tx->m_inputs = inputs; + } + } + else if (key == std::string("vout")) { + auto node2 = it->second; + + for(auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + auto output = std::make_shared(); + deserialize_output(it2->second, output); + output->m_tx = tx; + tx->m_outputs.push_back(output); + } + } + else if (key == std::string("rct_signatures")) { + auto node2 = it->second; + + for(auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + std::string _key = it2->first; + + if (_key == std::string("txnFee")) { + tx->m_fee = it2->second.get_value(); + } + } + } + else if (key == std::string("rctsig_prunable")) { + // TODO: implement + } + else if (key == std::string("unlock_time")) { + tx->m_unlock_time = it->second.get_value(); + } + else if (key == std::string("as_json")) as_json = it->second.data(); + else if (key == std::string("tx_json")) tx_json = it->second.data(); + else if ((key == std::string("as_hex") || key == std::string("tx_blob")) && !it->second.data().empty()) tx->m_full_hex = it->second.data(); + else if (key == std::string("blob_size")) tx->m_size = it->second.get_value(); + else if (key == std::string("weight")) tx->m_weight = it->second.get_value(); + else if (key == std::string("fee")) tx->m_fee = it->second.get_value(); + else if (key == std::string("relayed")) tx->m_is_relayed = it->second.get_value(); + else if (key == std::string("output_indices")) { + auto node2 = it->second; + std::vector output_indices; + for(auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + output_indices.push_back(it2->second.get_value()); + } + tx->m_output_indices = output_indices; + } + else if (key == std::string("do_not_relay")) tx->m_relay = !it->second.get_value(); + else if (key == std::string("kept_by_block")) tx->m_is_kept_by_block = it->second.get_value(); + else if (key == std::string("signatures")) { + auto node2 = it->second; + for(auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + tx->m_signatures.push_back(it2->second.data()); + } + } + else if (key == std::string("last_failed_height")) { + uint64_t last_failed_height = it->second.get_value(); + if (last_failed_height == 0) tx->m_is_failed = false; + else { + tx->m_is_failed = true; + tx->m_last_failed_height = last_failed_height; + } + } + else if (key == std::string("last_failed_id_hash")) { + std::string hash = it->second.data(); + if (hash == monero_tx::DEFAULT_ID) tx->m_is_failed = false; + else { + tx->m_is_failed = true; + tx->m_last_failed_hash = hash; + } + } + else if (key == std::string("extra")) { + auto extra_node = it->second; + for(auto it_extra = extra_node.begin(); it_extra != extra_node.end(); ++it_extra) { + tx->m_extra.push_back(it_extra->second.get_value()); + } + } + else if (key == std::string("max_used_block_height")) tx->m_max_used_block_height = it->second.get_value(); + else if (key == std::string("max_used_block_id_hash") && !it->second.data().empty()) tx->m_max_used_block_hash = it->second.data(); + else if (key == std::string("prunable_hash") && !it->second.data().empty()) tx->m_prunable_hash = it->second.data(); + else if (key == std::string("prunable_as_hex") && !it->second.data().empty()) tx->m_prunable_hex = it->second.data(); + else if (key == std::string("pruned_as_hex") && !it->second.data().empty()) tx->m_pruned_hex = it->second.data(); + } + + // link block and tx + if (block != nullptr) { + block->m_txs.clear(); + block->m_txs.push_back(tx); + tx->m_block = block; + } + + // TODO monerod: unconfirmed txs misreport block height and timestamp + if (block != nullptr && block->m_height != boost::none && block->m_height == block->m_timestamp) { + tx->m_block = nullptr; + tx->m_is_confirmed = false; + } + + // initialize remaining known fields + if (gen_utils::bool_equals(true, tx->m_is_confirmed)) { + tx->m_relay = true; + tx->m_is_relayed = true; + tx->m_is_failed = false; + } else { + tx->m_num_confirmations = 0; + tx->m_block = nullptr; + } + + if (tx->m_is_failed == boost::none) tx->m_is_failed = false; + if (!tx->m_output_indices.empty() && !tx->m_outputs.empty()) { + if (tx->m_output_indices.size() != tx->m_outputs.size()) throw monero_error("Expected outputs count equal to indices count"); + int i = 0; + for (const auto &output : tx->m_outputs) { + output->m_index = tx->m_output_indices[i++]; + } + } + + if (!as_json.empty()) { + boost::property_tree::ptree n; + gen_utils::deserialize(as_json, n); + deserialize_tx(n, tx); + } + if (!tx_json.empty()) { + boost::property_tree::ptree n; + gen_utils::deserialize(tx_json, n); + deserialize_tx(n, tx); + } + + if (tx->m_is_relayed != boost::none && !tx->m_is_relayed.get()) tx->m_last_relayed_timestamp = boost::none; + } + + void deserialize_txs(const boost::property_tree::ptree& node, std::vector>& txs) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + bool pool_txs = key == std::string("transactions"); + + if (pool_txs || key == std::string("txs")) { + for(boost::property_tree::ptree::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) { + auto tx = std::make_shared(); + deserialize_tx(it2->second, tx); + tx->m_is_miner_tx = false; + if (pool_txs) { + tx->m_is_confirmed = false; + tx->m_in_tx_pool = true; + tx->m_num_confirmations = 0; + } + txs.push_back(tx); + } + } + } + } + + void deserialize_tx_hashes(const boost::property_tree::ptree& node, std::vector& tx_hashes) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("tx_hashes")) { + auto node2 = it->second; + + for(boost::property_tree::ptree::const_iterator it2 = node2.begin(); it2 != node2.end(); ++it2) { + tx_hashes.push_back(it2->second.data()); + } + } + } + } + + void deserialize_block_header(const boost::property_tree::ptree& node, const std::shared_ptr& header) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("block_header")) { + deserialize_block_header(it->second, header); + return; + } + else if (key == std::string("hash")) header->m_hash = it->second.data(); + else if (key == std::string("height")) header->m_height = it->second.get_value(); + else if (key == std::string("timestamp")) header->m_timestamp = it->second.get_value(); + else if (key == std::string("block_size")) header->m_size = it->second.get_value(); + else if (key == std::string("block_weight")) header->m_weight = it->second.get_value(); + else if (key == std::string("long_term_weight")) header->m_long_term_weight = it->second.get_value(); + else if (key == std::string("depth")) header->m_depth = it->second.get_value(); + else if (key == std::string("difficulty")) header->m_difficulty_low = it->second.get_value(); + else if (key == std::string("difficulty_top64")) header->m_difficulty_high = it->second.get_value(); + else if (key == std::string("cumulative_difficulty")) header->m_cumulative_difficulty_low = it->second.get_value(); + else if (key == std::string("cumulative_difficulty_top64")) header->m_cumulative_difficulty_high = it->second.get_value(); + else if (key == std::string("major_version")) header->m_major_version = it->second.get_value(); + else if (key == std::string("minor_version")) header->m_minor_version = it->second.get_value(); + else if (key == std::string("nonce")) header->m_nonce = it->second.get_value(); + else if (key == std::string("miner_tx_hash")) header->m_miner_tx_hash = it->second.data(); + else if (key == std::string("num_txes")) header->m_num_txs = it->second.get_value(); + else if (key == std::string("orphan_status")) header->m_orphan_status = it->second.get_value(); + else if (key == std::string("prev_hash") || key == std::string("prev_id")) header->m_prev_hash = it->second.data(); + else if (key == std::string("reward")) header->m_reward = it->second.get_value(); + else if (key == std::string("pow_hash")) { + std::string pow_hash = it->second.data(); + if (!pow_hash.empty()) header->m_pow_hash = pow_hash; + } + } + } + + void deserialize_block_headers(const boost::property_tree::ptree& node, std::vector>& headers) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("headers")) { + auto node2 = it->second; + + for(boost::property_tree::ptree::const_iterator it2 = node2.begin(); it2 != node2.end(); ++it2) { + auto header = std::make_shared(); + deserialize_block_header(it2->second, header); + headers.push_back(header); + } + } + } + } + + void deserialize_block(const boost::property_tree::ptree& node, const std::shared_ptr& block, bool is_nested) { + deserialize_block_header(node, block); + + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("blob")) block->m_hex = it->second.data(); + else if (key == std::string("tx_hashes") && !is_nested) { + for(const auto &hex : it->second) block->m_tx_hashes.push_back(hex.second.data()); + } + else if (key == std::string("txs")) { + for (const auto &tx_node : it->second) { + auto tx = std::make_shared(); + deserialize_tx(tx_node.second, tx); + block->m_txs.push_back(tx); + } + } + else if (key == std::string("miner_tx")) { + auto tx = std::make_shared(); + deserialize_tx(it->second, tx); + tx->m_is_miner_tx = true; + tx->m_is_confirmed = true; + tx->m_is_failed = false; + tx->m_relay = true; + tx->m_is_relayed = true; + tx->m_block = block; + tx->m_num_confirmations = block->m_depth == boost::none ? boost::none : boost::make_optional(block->m_depth.get() + 1); + + block->m_miner_tx = tx; + } + else if (key == std::string("json")) { + auto json = it->second.data(); + std::istringstream iss = json.empty() ? std::istringstream() : std::istringstream(json); + boost::property_tree::ptree json_node; + boost::property_tree::read_json(iss, json_node); + deserialize_block(json_node, block, true); + } + } + + // TODO trusting standard, implement key-order validation? + if (block->m_num_txs == boost::none) { + block->m_num_txs = static_cast(block->m_tx_hashes.size()); + } + } + + void deserialize_blocks(const boost::property_tree::ptree& node, const std::vector& heights, std::vector>& blocks) { + // used by get_blocks_by_height + const auto& rpc_blocks = node.get_child("blocks"); + const auto& rpc_txs = node.get_child("txs"); + if (rpc_blocks.size() != rpc_txs.size()) { + throw monero_error("blocks and txs size mismatch"); + } + + auto it_block = rpc_blocks.begin(); + auto it_txs = rpc_txs.begin(); + size_t idx = 0; + + for (; it_block != rpc_blocks.end(); ++it_block, ++it_txs, ++idx) { + // build block + auto block = std::make_shared(); + deserialize_block(it_block->second, block); + block->m_height = heights.at(idx); + blocks.push_back(block); + + // build transactions + std::vector> txs; + size_t tx_idx = 0; + for (const auto& tx_node : it_txs->second) { + auto tx = std::make_shared(); + tx->m_hash = block->m_tx_hashes.at(tx_idx++); + tx->m_is_confirmed = true; + tx->m_in_tx_pool = false; + tx->m_is_miner_tx = false; + tx->m_relay = true; + tx->m_is_relayed = true; + tx->m_is_failed = false; + tx->m_is_double_spend_seen = false; + deserialize_tx(tx_node.second, tx); + txs.push_back(tx); + } + // merge into one block + block->m_txs.clear(); + for (auto& tx : txs) { + if (tx->m_block != nullptr) block->merge(block, tx->m_block); + else { + tx->m_block = block; + block->m_txs.push_back(tx); + } + } + } + } + + void deserialize_alt_block_hashes(const boost::property_tree::ptree& node, std::vector& block_hashes) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("blks_hashes")) { + auto node2 = it->second; + for(auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + block_hashes.push_back(it2->second.data()); + } + } + } + } + + void deserialize_key_image_spent_status(const boost::property_tree::ptree& node, std::vector& statuses) { + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("spent_status")) { + for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) { + uint64_t value = it2->second.get_value(); + if (value == 0) statuses.push_back(monero_key_image_spent_status::NOT_SPENT); + else if (value == 1) statuses.push_back(monero_key_image_spent_status::CONFIRMED); + else if (value == 2) statuses.push_back(monero_key_image_spent_status::TX_POOL); + else throw monero_error("Invalid key image spent status, expected 0-2:" + std::to_string(value)); + } + } + } + } + + void deserialize_payment_info(const boost::property_tree::ptree& node, const std::shared_ptr& rpc_payment_info) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if ((key == std::string("top_hash") || key == std::string("top_block_hash")) && !it->second.data().empty()) rpc_payment_info->m_top_block_hash = it->second.data(); + else if (key == std::string("credits")) rpc_payment_info->m_credits = it->second.get_value(); + } + } + + void deserialize_alt_chain(const boost::property_tree::ptree& node, const std::shared_ptr& alt_chain) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("difficulty")) alt_chain->m_difficulty_low = it->second.get_value(); + else if (key == std::string("difficulty_top64")) alt_chain->m_difficulty_high = it->second.get_value(); + else if (key == std::string("block_hashes")) { + for (const auto& child : it->second) alt_chain->m_block_hashes.push_back(child.second.data()); + } + else if (key == std::string("height")) alt_chain->m_height = it->second.get_value(); + else if (key == std::string("length")) alt_chain->m_length = it->second.get_value(); + else if (key == std::string("main_chain_parent_block")) alt_chain->m_main_chain_parent_block_hash = it->second.data(); + } + } + + void deserialize_alt_chains(const boost::property_tree::ptree& node, std::vector>& alt_chains) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("chains")) { + boost::property_tree::ptree chains_node = it->second; + for (boost::property_tree::ptree::const_iterator it2 = chains_node.begin(); it2 != chains_node.end(); ++it2) { + std::shared_ptr alt_chain = std::make_shared(); + deserialize_alt_chain(it2->second, alt_chain); + alt_chains.push_back(alt_chain); + } + } + } + } + + void deserialize_ban(const boost::property_tree::ptree& node, const std::shared_ptr& ban) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("host")) ban->m_host = it->second.data(); + else if (key == std::string("ip")) ban->m_ip = it->second.get_value(); + else if (key == std::string("ban")) ban->m_is_banned = it->second.get_value(); + else if (key == std::string("seconds")) ban->m_seconds = it->second.get_value(); + } + } + + void deserialize_bans(const boost::property_tree::ptree& node, std::vector>& bans) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("bans")) { + auto node2 = it->second; + for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + auto ban = std::make_shared(); + deserialize_ban(it2->second, ban); + bans.push_back(ban); + } + } + } + } + + void deserialize_prune_result(const boost::property_tree::ptree& node, const std::shared_ptr& result) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("pruned")) result->m_is_pruned = it->second.get_value(); + else if (key == std::string("pruning_seed")) result->m_pruning_seed = it->second.get_value(); + } + } + + void deserialize_mining_status(const boost::property_tree::ptree& node, const std::shared_ptr& status) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("active")) status->m_is_active = it->second.get_value(); + else if (key == std::string("is_background_mining_enabled")) status->m_is_background = it->second.get_value(); + else if (key == std::string("address") && !it->second.data().empty()) status->m_address = it->second.data(); + else if (key == std::string("speed")) status->m_speed = it->second.get_value(); + else if (key == std::string("threads_count")) status->m_num_threads = it->second.get_value(); + } + + if (status->m_is_active != boost::none && *status->m_is_active == false) { + status->m_is_background = boost::none; + status->m_address = boost::none; + } + } + + void deserialize_generate_blocks_result(const boost::property_tree::ptree& node, const std::shared_ptr& result) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("blocks")) { + for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) { + result->m_block_hashes.push_back(it2->second.data()); + } + } + else if (key == std::string("height")) result->m_height = it->second.get_value(); + } + } + + void deserialize_miner_tx_sum(const boost::property_tree::ptree& node, const std::shared_ptr& sum) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("emission_amount")) sum->m_emission_sum_low = it->second.get_value(); + else if (key == std::string("emission_amount_top64")) sum->m_emission_sum_high = it->second.get_value(); + else if (key == std::string("fee_amount")) sum->m_fee_sum_low = it->second.get_value(); + else if (key == std::string("fee_amount_top64")) sum->m_fee_sum_high = it->second.get_value(); + } + } + + void deserialize_block_template(const boost::property_tree::ptree& node, const std::shared_ptr& tmplt) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("blocktemplate_blob")) tmplt->m_block_template_blob = it->second.data(); + else if (key == std::string("blockhashing_blob")) tmplt->m_block_hashing_blob = it->second.data(); + else if (key == std::string("difficulty")) tmplt->m_difficulty_low = it->second.get_value(); + else if (key == std::string("difficulty_top64")) tmplt->m_difficulty_high = it->second.get_value(); + else if (key == std::string("expected_reward")) tmplt->m_expected_reward = it->second.get_value(); + else if (key == std::string("height")) tmplt->m_height = it->second.get_value(); + else if (key == std::string("prev_hash")) tmplt->m_prev_hash = it->second.data(); + else if (key == std::string("reserved_offset")) tmplt->m_reserved_offset = it->second.get_value(); + else if (key == std::string("seed_height")) tmplt->m_seed_height = it->second.get_value(); + else if (key == std::string("seed_hash")) tmplt->m_seed_hash = it->second.data(); + else if (key == std::string("next_seed_hash")) tmplt->m_next_seed_hash = it->second.data(); + } + } + + void deserialize_connection_span(const boost::property_tree::ptree& node, const std::shared_ptr& span) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("connection_id")) span->m_connection_id = it->second.data(); + else if (key == std::string("nblocks")) span->m_num_blocks = it->second.get_value(); + else if (key == std::string("remote_address")) span->m_remote_address = it->second.data(); + else if (key == std::string("rate")) span->m_rate = it->second.get_value(); + else if (key == std::string("speed")) span->m_speed = it->second.get_value(); + else if (key == std::string("size")) span->m_size = it->second.get_value(); + else if (key == std::string("start_block_height")) span->m_start_height = it->second.get_value(); + } + } + + void deserialize_connection_spans(const boost::property_tree::ptree& node, std::vector>& spans) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + auto span = std::make_shared(); + deserialize_connection_span(it->second, span); + spans.push_back(span); + } + } + + void deserialize_peer(const boost::property_tree::ptree& node, const std::shared_ptr& peer) { + peer->m_is_online = true; + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("host")) peer->m_host = it->second.data(); + else if (key == std::string("address")) peer->m_address = it->second.data(); + else if (key == std::string("current_download")) peer->m_current_download = it->second.get_value(); + else if (key == std::string("current_upload")) peer->m_current_upload = it->second.get_value(); + else if (key == std::string("avg_download")) peer->m_avg_download = it->second.get_value(); + else if (key == std::string("avg_upload")) peer->m_avg_upload = it->second.get_value(); + else if (key == std::string("connection_id")) peer->m_hash = it->second.data(); + else if (key == std::string("height")) peer->m_height = it->second.get_value(); + else if (key == std::string("incoming")) peer->m_is_incoming = it->second.get_value(); + else if (key == std::string("live_time")) peer->m_live_time = it->second.get_value(); + else if (key == std::string("local_ip")) peer->m_is_local_ip = it->second.get_value(); + else if (key == std::string("localhost")) peer->m_is_local_host = it->second.get_value(); + else if (key == std::string("recv_count")) peer->m_num_receives = it->second.get_value(); + else if (key == std::string("send_count")) peer->m_num_sends = it->second.get_value(); + else if (key == std::string("recv_idle_time")) peer->m_receive_idle_time = it->second.get_value(); + else if (key == std::string("send_idle_time")) peer->m_send_idle_time = it->second.get_value(); + else if (key == std::string("state")) peer->m_state = it->second.data(); + else if (key == std::string("support_flags")) peer->m_num_support_flags = it->second.get_value(); + else if (key == std::string("id") || key == std::string("peer_id")) peer->m_id = it->second.data(); + else if (key == std::string("last_seen")) peer->m_last_seen_timestamp = it->second.get_value(); + else if (key == std::string("port")) peer->m_port = it->second.get_value(); + else if (key == std::string("rpc_port")) peer->m_rpc_port = it->second.get_value(); + else if (key == std::string("pruning_seed")) peer->m_pruning_seed = it->second.get_value(); + else if (key == std::string("rpc_credits_per_hash")) peer->m_rpc_credits_per_hash = it->second.get_value(); + else if (key == std::string("address_type")) { + int rpc_type = it->second.get_value(); + // TODO: epee's address_type enum (net/enums.h) is invalid=0, ipv4=1, ipv6=2, i2p=3, + // tor=4. Fix this with monero-java/ts together once the reference. + if (rpc_type == 0) peer->m_connection_type = monero_connection_type::INVALID; + else if (rpc_type == 1) peer->m_connection_type = monero_connection_type::IPV4; + else if (rpc_type == 2) peer->m_connection_type = monero_connection_type::IPV6; + else if (rpc_type == 3) peer->m_connection_type = monero_connection_type::TOR; + else if (rpc_type == 4) peer->m_connection_type = monero_connection_type::I2P; + else throw monero_error("Invalid RPC peer type, expected 0-4: " + std::to_string(rpc_type)); + } + } + } + + void deserialize_peers(const boost::property_tree::ptree& node, std::vector>& peers) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + bool is_offline = key == std::string("gray_list"); + if (key == std::string("connections") || key == std::string("white_list") || key == std::string("gray_list") ) { + for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) { + auto peer = std::make_shared(); + deserialize_peer(it2->second, peer); + if (is_offline) peer->m_is_online = false; + peers.push_back(peer); + } + } + } + } + + void deserialize_peer_infos(const boost::property_tree::ptree& node, std::vector>& peers) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) { + std::string key = it2->first; + if (key == std::string("info")) { + auto peer = std::make_shared(); + deserialize_peer(it2->second, peer); + peers.push_back(peer); + } + } + } + } + + void deserialize_submit_tx_result(const boost::property_tree::ptree& node, const std::shared_ptr& result) { + deserialize_payment_info(node, result); + + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("double_spend")) result->m_is_double_spend = it->second.get_value(); + else if (key == std::string("fee_too_low")) result->m_is_fee_too_low = it->second.get_value(); + else if (key == std::string("invalid_input")) result->m_has_invalid_input = it->second.get_value(); + else if (key == std::string("invalid_output")) result->m_has_invalid_output = it->second.get_value(); + else if (key == std::string("too_few_outputs")) result->m_has_too_few_outputs = it->second.get_value(); + else if (key == std::string("low_mixin")) result->m_is_mixin_too_low = it->second.get_value(); + else if (key == std::string("not_relayed")) result->m_is_relayed = !it->second.get_value(); + else if (key == std::string("overspend")) result->m_is_overspend = it->second.get_value(); + else if (key == std::string("reason") && !it->second.data().empty()) result->m_reason = it->second.data(); + else if (key == std::string("too_big")) result->m_is_too_big = it->second.get_value(); + else if (key == std::string("sanity_check_failed")) result->m_sanity_check_failed = it->second.get_value(); + else if (key == std::string("tx_extra_too_big")) result->m_is_tx_extra_too_big = it->second.get_value(); + else if (key == std::string("nonzero_unlock_time")) result->m_is_nonzero_unlock_time = it->second.get_value(); + } + } + + void deserialize_output_distribution_entry(const boost::property_tree::ptree& node, const std::shared_ptr& entry) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("amount")) entry->m_amount = it->second.get_value(); + else if (key == std::string("base")) entry->m_base = it->second.get_value(); + else if (key == std::string("distribution")) { + auto node2 = it->second; + for(auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + entry->m_distribution.push_back(it2->second.get_value()); + } + } + else if (key == std::string("start_height")) entry->m_start_height = it->second.get_value(); + } + } + + void deserialize_output_distribution_entries(const boost::property_tree::ptree& node, std::vector>& entries) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("distributions")) { + auto node2 = it->second; + for(boost::property_tree::ptree::const_iterator it2 = node2.begin(); it2 != node2.end(); ++it2) { + auto entry = std::make_shared(); + deserialize_output_distribution_entry(it2->second, entry); + entries.push_back(entry); + } + } + } + } + + void deserialize_output_histogram_entry(const boost::property_tree::ptree& node, const std::shared_ptr& entry) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("amount")) entry->m_amount = it->second.get_value(); + else if (key == std::string("total_instances")) entry->m_num_instances = it->second.get_value(); + else if (key == std::string("unlocked_instances")) entry->m_unlocked_instances = it->second.get_value(); + else if (key == std::string("recent_instances")) entry->m_recent_instances = it->second.get_value(); + } + } + + void deserialize_output_histogram_entries(const boost::property_tree::ptree& node, std::vector>& entries) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("histogram")) { + auto node2 = it->second; + + for(boost::property_tree::ptree::const_iterator it2 = node2.begin(); it2 != node2.end(); ++it2) { + auto entry = std::make_shared(); + deserialize_output_histogram_entry(it2->second, entry); + entries.push_back(entry); + } + } + } + } + + void deserialize_tx_pool_stats(const boost::property_tree::ptree& node, const std::shared_ptr& stats) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("pool_stats")) { + deserialize_tx_pool_stats(it->second, stats); + break; + } + else if (key == std::string("txs_total")) stats->m_num_txs = it->second.get_value(); + else if (key == std::string("num_not_relayed")) stats->m_num_not_relayed = it->second.get_value(); + else if (key == std::string("num_failing")) stats->m_num_failing = it->second.get_value(); + else if (key == std::string("num_double_spends")) stats->m_num_double_spends = it->second.get_value(); + else if (key == std::string("num_10m")) stats->m_num10m = it->second.get_value(); + else if (key == std::string("fee_total")) stats->m_fee_total = it->second.get_value(); + else if (key == std::string("bytes_max")) stats->m_bytes_max = it->second.get_value(); + else if (key == std::string("bytes_med")) stats->m_bytes_med = it->second.get_value(); + else if (key == std::string("bytes_min")) stats->m_bytes_min = it->second.get_value(); + else if (key == std::string("bytes_total")) stats->m_bytes_total = it->second.get_value(); + else if (key == std::string("histo_98pc")) stats->m_histo98pc = it->second.get_value(); + else if (key == std::string("oldest")) stats->m_oldest_timestamp = it->second.get_value(); + else if (key == std::string("histo")) { + for(const auto& elem : it->second) { + uint64_t bytes = 0; + uint64_t txs = 0; + for(boost::property_tree::ptree::const_iterator elem_it = elem.second.begin(); elem_it != elem.second.end(); ++elem_it) { + std::string elem_key = elem_it->first; + if (elem_key == "bytes") bytes = elem_it->second.get_value(); + else if (elem_key == "txs") txs = elem_it->second.get_value(); + } + + stats->m_histo[bytes] = txs; + } + } + } + + // uninitialize some stats if not applicable + if (stats->m_histo98pc != boost::none && stats->m_histo98pc.get() == 0) stats->m_histo98pc = boost::none; + if (stats->m_num_txs != boost::none && stats->m_num_txs.get() == 0) { + stats->m_bytes_min = boost::none; + stats->m_bytes_max = boost::none; + stats->m_bytes_med = boost::none; + stats->m_histo98pc = boost::none; + stats->m_oldest_timestamp = boost::none; + } + } + + void deserialize_update_check_result(const boost::property_tree::ptree& node, const std::shared_ptr& check) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("update")) check->m_is_update_available = it->second.get_value(); + else if (key == std::string("version") && !it->second.data().empty()) check->m_version = it->second.data(); + else if (key == std::string("hash") && !it->second.data().empty()) check->m_hash = it->second.data(); + else if (key == std::string("auto_uri") && !it->second.data().empty()) check->m_auto_uri = it->second.data(); + else if (key == std::string("user_uri") && !it->second.data().empty()) check->m_user_uri = it->second.data(); + } + } + + void deserialize_update_download_result(const boost::property_tree::ptree& node, const std::shared_ptr& check) { + deserialize_update_check_result(node, check); + + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("path") && !it->second.data().empty()) check->m_download_path = it->second.data(); + } + } + + void deserialize_fee_estimate(const boost::property_tree::ptree& node, const std::shared_ptr& estimate) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("fee")) estimate->m_fee = it->second.get_value(); + else if (key == std::string("quantization_mask")) estimate->m_quantization_mask = it->second.get_value(); + else if (key == std::string("fees")) { + auto node2 = it->second; + for (boost::property_tree::ptree::const_iterator it2 = node2.begin(); it2 != node2.end(); ++it2) { + uint64_t fee = it2->second.get_value(); + estimate->m_fees.push_back(fee); + } + } + } + } + + void deserialize_daemon_info(const boost::property_tree::ptree& node, const std::shared_ptr& info) { + deserialize_payment_info(node, info); + + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("version")) info->m_version = it->second.data(); + else if (key == std::string("alt_blocks_count")) info->m_num_alt_blocks = it->second.get_value(); + else if (key == std::string("block_size_limit")) info->m_block_size_limit = it->second.get_value(); + else if (key == std::string("block_size_median")) info->m_block_size_median = it->second.get_value(); + else if (key == std::string("block_weight_limit")) info->m_block_weight_limit = it->second.get_value(); + else if (key == std::string("block_weight_median")) info->m_block_weight_median = it->second.get_value(); + else if (key == std::string("bootstrap_daemon_address") && !it->second.data().empty()) info->m_bootstrap_daemon_address = it->second.data(); + else if (key == std::string("difficulty")) info->m_difficulty_low = it->second.get_value(); + else if (key == std::string("difficulty_top64")) info->m_difficulty_high = it->second.get_value(); + else if (key == std::string("cumulative_difficulty")) info->m_cumulative_difficulty_low = it->second.get_value(); + else if (key == std::string("cumulative_difficulty_top64")) info->m_cumulative_difficulty_high = it->second.get_value(); + else if (key == std::string("free_space")) info->m_free_space = it->second.get_value(); + else if (key == std::string("grey_peerlist_size")) info->m_num_offline_peers = it->second.get_value(); + else if (key == std::string("white_peerlist_size")) info->m_num_online_peers = it->second.get_value(); + else if (key == std::string("height")) info->m_height = it->second.get_value(); + else if (key == std::string("height_without_bootstrap")) info->m_height_without_bootstrap = it->second.get_value(); + else if (key == std::string("nettype")) { + std::string nettype = it->second.data(); + if (nettype == std::string("mainnet") || nettype == std::string("fakechain")) info->m_network_type = monero_network_type::MAINNET; + else if (nettype == std::string("testnet")) info->m_network_type = monero_network_type::TESTNET; + else if (nettype == std::string("stagenet")) info->m_network_type = monero_network_type::STAGENET; + } + else if (key == std::string("offline")) info->m_is_offline = it->second.get_value(); + else if (key == std::string("incoming_connections_count")) info->m_num_incoming_connections = it->second.get_value(); + else if (key == std::string("outgoing_connections_count")) info->m_num_outgoing_connections = it->second.get_value(); + else if (key == std::string("rpc_connections_count")) info->m_num_rpc_connections = it->second.get_value(); + else if (key == std::string("start_time")) info->m_start_timestamp = it->second.get_value(); + else if (key == std::string("adjusted_time")) info->m_adjusted_timestamp = it->second.get_value(); + else if (key == std::string("target")) info->m_target = it->second.get_value(); + else if (key == std::string("target_height")) info->m_target_height = it->second.get_value(); + else if (key == std::string("tx_count")) info->m_num_txs = it->second.get_value(); + else if (key == std::string("tx_pool_size")) info->m_num_txs_pool = it->second.get_value(); + else if (key == std::string("was_bootstrap_ever_used")) info->m_was_bootstrap_ever_used = it->second.get_value(); + else if (key == std::string("database_size")) info->m_database_size = it->second.get_value(); + else if (key == std::string("update_available")) info->m_update_available = it->second.get_value(); + else if (key == std::string("busy_syncing")) info->m_is_busy_syncing = it->second.get_value(); + else if (key == std::string("synchronized")) info->m_is_synchronized = it->second.get_value(); + else if (key == std::string("restricted")) info->m_is_restricted = it->second.get_value(); + } + } + + void deserialize_daemon_sync_info(const boost::property_tree::ptree& node, const std::shared_ptr& info) { + deserialize_payment_info(node, info); + + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("height")) info->m_height = it->second.get_value(); + else if (key == std::string("peers")) deserialize_peer_infos(it->second, info->m_peers); + else if (key == std::string("spans")) deserialize_connection_spans(it->second, info->m_spans); + else if (key == std::string("target_height")) info->m_target_height = it->second.get_value(); + else if (key == std::string("next_needed_pruning_seed")) info->m_next_needed_pruning_seed = it->second.get_value(); + else if (key == std::string("overview") && !it->second.data().empty() && it->second.data() != std::string("[]")) info->m_overview = it->second.data(); + } + } + + void deserialize_hard_fork_info(const boost::property_tree::ptree& node, const std::shared_ptr& info) { + deserialize_payment_info(node, info); + + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("earliest_height")) info->m_earliest_height = it->second.get_value(); + else if (key == std::string("enabled")) info->m_is_enabled = it->second.get_value(); + else if (key == std::string("state")) info->m_state = it->second.get_value(); + else if (key == std::string("threshold")) info->m_threshold = it->second.get_value(); + else if (key == std::string("version")) info->m_version = it->second.get_value(); + else if (key == std::string("votes")) info->m_num_votes = it->second.get_value(); + else if (key == std::string("window")) info->m_window = it->second.get_value(); + else if (key == std::string("voting")) info->m_voting = it->second.get_value(); + } + } + +} \ No newline at end of file diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon_rpc_model.h b/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon_rpc_model.h new file mode 100644 index 0000000..de9b903 --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/daemon/monero_daemon_rpc_model.h @@ -0,0 +1,288 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ +#pragma once + +#include "common/monero_rpc_connection.h" + +/** + * Internal data model for monero_daemon_rpc.h. + */ +namespace monero { + + /** + * Models a Monero RPC banhammer. + */ + struct monero_rpc_ban : public monero_ban { + monero_rpc_ban(const std::shared_ptr &ban); + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + // ------------------------------ Binary Request --------------------------------- + + /** + * Models paramaters for monerod-rpc `/get_blocks_by_height.bin` method. + */ + struct monero_get_blocks_by_height_request : public monero_rpc_request { + std::vector m_heights; + + monero_get_blocks_by_height_request(uint64_t num_blocks); + monero_get_blocks_by_height_request(const std::vector& heights): m_heights(heights) { m_method = "get_blocks_by_height.bin"; } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + // ------------------------------ RPC Params --------------------------------- + + struct monero_download_update_params : public serializable_struct { + boost::optional m_command; + boost::optional m_path; + + monero_download_update_params(const std::string& command = "download", const std::string& path = ""); + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_submit_tx_params : public serializable_struct { + boost::optional m_tx_hex; + boost::optional m_do_not_relay; + std::vector m_tx_hashes; + + monero_submit_tx_params(const std::vector& tx_hashes): m_tx_hashes(tx_hashes) { } + monero_submit_tx_params(const std::string& tx_hex, bool do_not_relay): m_tx_hex(tx_hex), m_do_not_relay(do_not_relay) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_peer_limits_params : public serializable_struct { + boost::optional m_in_peers; + boost::optional m_out_peers; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_get_txs_params : public serializable_struct { + std::vector m_tx_hashes; + boost::optional m_decode_as_json; + boost::optional m_prune; + + monero_get_txs_params(const std::vector &tx_hashes, bool prune, bool decode_as_json = true): m_tx_hashes(tx_hashes), m_prune(prune), m_decode_as_json(decode_as_json) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_is_key_image_spent_params : public serializable_struct { + std::vector m_key_images; + + monero_is_key_image_spent_params(const std::vector& key_images): m_key_images(key_images) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + // ------------------------------ JSON-RPC Params --------------------------------- + + struct monero_start_mining_params : public serializable_struct { + boost::optional m_miner_address; + boost::optional m_num_threads; + boost::optional m_is_background; + boost::optional m_ignore_battery; + + monero_start_mining_params(const boost::optional& address, const boost::optional& num_threads, const boost::optional& is_background, const boost::optional& ignore_battery): m_miner_address(address), m_num_threads(num_threads), m_is_background(is_background), m_ignore_battery(ignore_battery) { } + monero_start_mining_params(const boost::optional& num_threads, const boost::optional& is_background, const boost::optional& ignore_battery): m_num_threads(num_threads), m_is_background(is_background), m_ignore_battery(ignore_battery) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_generate_blocks_params : public serializable_struct { + boost::optional m_wallet_address; + boost::optional m_num_blocks; + boost::optional m_prev_block_hash; + boost::optional m_starting_nonce; + + monero_generate_blocks_params(const std::string& wallet_address, uint64_t num_blocks, const boost::optional& prev_block_hash, const boost::optional& starting_nonce): m_wallet_address(wallet_address), m_num_blocks(num_blocks), m_prev_block_hash(prev_block_hash), m_starting_nonce(starting_nonce) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_prune_blockchain_params : public serializable_struct { + boost::optional m_check; + + monero_prune_blockchain_params(bool check = true): m_check(check) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_submit_blocks_params : public serializable_struct { + std::vector m_block_blobs; + + monero_submit_blocks_params(const std::vector& block_blobs): m_block_blobs(block_blobs) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_get_block_params : public serializable_struct { + boost::optional m_height; + boost::optional m_hash; + boost::optional m_fill_pow_hash; + boost::optional m_start_height; + boost::optional m_end_height; + boost::optional m_wallet_address; + boost::optional m_reserve_size; + + monero_get_block_params(uint64_t height, bool fill_pow_hash = false): m_height(height), m_fill_pow_hash(fill_pow_hash) { } + monero_get_block_params(const std::string& hash, bool fill_pow_hash = false): m_hash(hash), m_fill_pow_hash(fill_pow_hash) { } + monero_get_block_params(uint64_t start_height, uint64_t end_height): m_start_height(start_height), m_end_height(end_height) { } + monero_get_block_params(const std::string& wallet_address, const boost::optional& reserve_size): m_wallet_address(wallet_address), m_reserve_size(reserve_size) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_get_block_hash_params : public serializable_struct { + boost::optional m_height; + + monero_get_block_hash_params(uint64_t height): m_height(height) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_get_miner_tx_sum_params : public serializable_struct { + boost::optional m_height; + boost::optional m_count; + + monero_get_miner_tx_sum_params(uint64_t height, uint64_t count): m_height(height), m_count(count) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_get_fee_estimate_params : public serializable_struct { + boost::optional m_grace_blocks; + + monero_get_fee_estimate_params(uint64_t grace_blocks = 0): m_grace_blocks(grace_blocks) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_set_bans_params : public serializable_struct { + std::vector> m_bans; + + monero_set_bans_params(const std::vector>& bans); + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_get_output_histogram_params : public serializable_struct { + std::vector m_amounts; + boost::optional m_min_count; + boost::optional m_max_count; + boost::optional m_is_unlocked; + boost::optional m_recent_cutoff; + + monero_get_output_histogram_params(const std::vector& amounts, const boost::optional& min_count, const boost::optional& max_count, const boost::optional& is_unlocked, const boost::optional& recent_cutoff) : m_amounts(amounts), m_min_count(min_count), m_max_count(max_count), m_is_unlocked(is_unlocked), m_recent_cutoff(recent_cutoff) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_get_output_distribution_params : public serializable_struct { + std::vector m_amounts; + boost::optional m_cumulative; + boost::optional m_binary; + boost::optional m_from_height; + boost::optional m_to_height; + + monero_get_output_distribution_params(const std::vector& amounts, const boost::optional& cumulative, const boost::optional& from_height, const boost::optional& to_height) : m_amounts(amounts), m_cumulative(cumulative), m_from_height(from_height), m_to_height(to_height), m_binary(false) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + // ------------------------------ JSON-RPC Response --------------------------------- + + struct monero_get_block_result { + boost::optional m_count; + boost::optional m_height; + boost::optional m_untrusted; + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result); + }; + + // ------------------------------ RPC Deserialization --------------------------------- + + void deserialize_version(const boost::property_tree::ptree& node, monero_version& version); + void deserialize_block_header(const boost::property_tree::ptree& node, const std::shared_ptr& header); + void deserialize_block_headers(const boost::property_tree::ptree& node, std::vector>& headers); + void deserialize_block(const boost::property_tree::ptree& node, const std::shared_ptr& block, bool is_nested = false); + void deserialize_blocks(const boost::property_tree::ptree& node, const std::vector& heights, std::vector>& blocks); + void deserialize_alt_block_hashes(const boost::property_tree::ptree& node, std::vector& block_hashes); + void deserialize_txs(const boost::property_tree::ptree& node, std::vector>& txs); + void deserialize_tx_hashes(const boost::property_tree::ptree& node, std::vector& tx_hashes); + void deserialize_key_image_spent_status(const boost::property_tree::ptree& node, std::vector& statuses); + void deserialize_alt_chains(const boost::property_tree::ptree& node, std::vector>& alt_chains); + void deserialize_bans(const boost::property_tree::ptree& node, std::vector>& bans); + void deserialize_prune_result(const boost::property_tree::ptree& node, const std::shared_ptr& result); + void deserialize_mining_status(const boost::property_tree::ptree& node, const std::shared_ptr& status); + void deserialize_generate_blocks_result(const boost::property_tree::ptree& node, const std::shared_ptr& result); + void deserialize_miner_tx_sum(const boost::property_tree::ptree& node, const std::shared_ptr& sum); + void deserialize_block_template(const boost::property_tree::ptree& node, const std::shared_ptr& tmplt); + void deserialize_peers(const boost::property_tree::ptree& node, std::vector>& peers); + void deserialize_submit_tx_result(const boost::property_tree::ptree& node, const std::shared_ptr& result); + void deserialize_output_distribution_entries(const boost::property_tree::ptree& node, std::vector>& entries); + void deserialize_output_histogram_entries(const boost::property_tree::ptree& node, std::vector>& entries); + void deserialize_tx_pool_stats(const boost::property_tree::ptree& node, const std::shared_ptr& stats); + void deserialize_update_check_result(const boost::property_tree::ptree& node, const std::shared_ptr& check); + void deserialize_update_download_result(const boost::property_tree::ptree& node, const std::shared_ptr& check); + void deserialize_fee_estimate(const boost::property_tree::ptree& node, const std::shared_ptr& estimate); + void deserialize_daemon_info(const boost::property_tree::ptree& node, const std::shared_ptr& info); + void deserialize_daemon_sync_info(const boost::property_tree::ptree& node, const std::shared_ptr& info); + void deserialize_hard_fork_info(const boost::property_tree::ptree& node, const std::shared_ptr& info); + +} \ No newline at end of file diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/utils/gen_utils.cpp b/apps/ios/Koins/Monero/include/monero-cpp-src/utils/gen_utils.cpp new file mode 100644 index 0000000..a3368f2 --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/utils/gen_utils.cpp @@ -0,0 +1,220 @@ +/** + * Copyright (c) woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ + +#include "gen_utils.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/writer.h" + +using namespace gen_utils; + +// ------------------------- SERIALIZATION --------------------------- + +std::string gen_utils::serialize(const rapidjson::Document& doc) { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + doc.Accept(writer); + return buffer.GetString(); +} + +std::string gen_utils::serialize(const boost::property_tree::ptree& node) { + std::stringstream ss; + boost::property_tree::write_json(ss, node, false); + std::string str = ss.str(); + return str.substr(0, str.size() - 1); // strip newline +} + +void gen_utils::deserialize(const std::string& json, boost::property_tree::ptree& root) { + std::istringstream iss = json.empty() ? std::istringstream() : std::istringstream(json); + try { + boost::property_tree::read_json(iss, root); + } catch (std::exception const& e) { + throw std::runtime_error("Invalid JSON"); + } +} + +// --------------------------- THREAD POLLER --------------------------- + +thread_poller::thread_poller(): m_is_polling(false), m_poll_loop_running(false), m_poll_period_ms(20000) { +} + +thread_poller::~thread_poller() { + // safety net only: derived classes must stop polling in their own destructor + // (before their members are torn down) since by the time this base destructor + // runs, the derived part of the object is already gone and a concurrently + // running poll() (a virtual call) would hit a partially-destructed object + set_is_polling(false); +} + +namespace { + // stack of poller instances whose announce_scope is currently active on the current + // thread, innermost last. Per-instance so that a callback of poller A calling into + // a *different* poller B's wait_for_callbacks_idle() still waits normally. + thread_local std::vector t_active_dispatch_stack; +} + +thread_poller::announce_scope::announce_scope(thread_poller& poller): m_poller(poller) { + t_active_dispatch_stack.push_back(&poller); + boost::lock_guard lock(m_poller.m_announce_mutex); + ++m_poller.m_announce_count; +} + +thread_poller::announce_scope::~announce_scope() { + { + boost::lock_guard lock(m_poller.m_announce_mutex); + if (--m_poller.m_announce_count == 0) m_poller.m_announce_cv.notify_all(); + } + t_active_dispatch_stack.pop_back(); +} + +void thread_poller::wait_for_callbacks_idle() { + // skip the wait only if this poller's own dispatch is active on the current thread; + // waiting would deadlock in that case. A different poller's active dispatch must not + // short-circuit this wait. + for (const thread_poller* active : t_active_dispatch_stack) { + if (active == this) return; + } + boost::unique_lock lock(m_announce_mutex); + m_announce_cv.wait(lock, [&]() { return m_announce_count == 0; }); +} + +void thread_poller::init_common(const std::string& name) { + m_name = name; +} + +void thread_poller::request_is_polling(bool is_polling) { + boost::lock_guard lifecycle_lock(m_lifecycle_mutex); + + if (is_polling) { + if (m_is_polling) return; + m_is_polling = true; + run_poll_loop(); + return; + } + + bool was_polling = m_is_polling.exchange(false); + if (was_polling) { + boost::lock_guard polling_lock(m_polling_mutex); + m_poll_cv.notify_one(); + } +} + +void thread_poller::set_is_polling(bool is_polling) { + boost::unique_lock lifecycle_lock(m_lifecycle_mutex); + + if (is_polling) { + if (m_is_polling) return; + m_is_polling = true; + run_poll_loop(); + return; + } + + bool was_polling = m_is_polling.exchange(false); + if (was_polling) { + boost::lock_guard polling_lock(m_polling_mutex); + m_poll_cv.notify_one(); + } + + if (!m_poll_loop_running) return; // loop already fully exited; nothing to wait for + + if (boost::this_thread::get_id() == m_poll_thread_id) { + // called from the poll thread itself (e.g. a listener removed itself from within a + // callback invoked by poll()): waiting for our own loop to exit would deadlock. Just + // return -- the loop re-checks m_is_polling itself and will exit and clear + // m_poll_loop_running/m_poll_thread_id on its own (see run_poll_loop). + return; + } + + // wait for the loop to actually finish + m_lifecycle_cv.wait(lifecycle_lock, [&]() { return !m_poll_loop_running; }); +} + +void thread_poller::run_poll_loop() { + // called with m_lifecycle_mutex held + if (m_poll_loop_running) return; // only run one loop at a time + m_poll_loop_running = true; + + // TODO: use global threadpool, background sync wasm wallet in c++ thread + try { + boost::thread([this]() { + { + boost::lock_guard lifecycle_lock(m_lifecycle_mutex); + m_poll_thread_id = boost::this_thread::get_id(); + } + + while (true) { + while (m_is_polling) { + try { poll(); } + catch (const std::exception& e) { MERROR(m_name << " failed to background poll: " << e.what()); } + catch (...) { MERROR(m_name << " failed to background poll"); } + + // only wait if polling still enabled + if (m_is_polling) { + boost::mutex::scoped_lock lock(m_polling_mutex); + boost::posix_time::milliseconds wait_for_ms(m_poll_period_ms.load()); + m_poll_cv.timed_wait(lock, wait_for_ms, [&]() { return !m_is_polling; }); + } + } + + boost::unique_lock lifecycle_lock(m_lifecycle_mutex); + if (m_is_polling) continue; + m_poll_loop_running = false; + m_poll_thread_id = boost::thread::id(); + m_lifecycle_cv.notify_all(); + return; + } + }).detach(); + } catch (...) { + m_poll_loop_running = false; + m_is_polling = false; + throw; + } +} diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/utils/gen_utils.h b/apps/ios/Koins/Monero/include/monero-cpp-src/utils/gen_utils.h new file mode 100644 index 0000000..54f0aa1 --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/utils/gen_utils.h @@ -0,0 +1,220 @@ +/** + * Copyright (c) woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ +#pragma once + +#ifndef gen_utils_h +#define gen_utils_h + +#include +#include +#include +#include +#include +#include +#include "rapidjson/document.h" +#include +#include +#include +#include +#include +#include +#include "include_base_utils.h" +#include "common/util.h" + +/** + * Collection of generic utilities. + */ +namespace gen_utils +{ + + /** + * Return a unique identifier. + * + * @return a unique id + */ + static std::string get_uuid() { + boost::uuids::random_generator generator; + boost::uuids::uuid uuid = generator(); + return boost::uuids::to_string(uuid); + } + + /** + * Wait for the given duration. + * + * @param duration_ms the duration to wait in milliseconds + */ + static void wait_for(uint64_t duration_ms) { + std::this_thread::sleep_for(std::chrono::milliseconds(duration_ms)); + } + + static bool bool_equals(bool val, const boost::optional& opt_val) { + return opt_val == boost::none ? false : val == *opt_val; + } + + // ------------------------- SERIALIZATION --------------------------- + + // TODO: fully switch from property trees to rapidjson + + std::string serialize(const rapidjson::Document& doc); + std::string serialize(const boost::property_tree::ptree& node); + void deserialize(const std::string& json, boost::property_tree::ptree& root); + + // ------------------------- VALUE RECONCILATION ---------------------------- + + // TODO: refactor common template code + template ::value, T>::type* = nullptr> + boost::optional reconcile(const boost::optional& val1, const boost::optional& val2, boost::optional resolve_defined, boost::optional resolve_true, boost::optional resolve_max, const std::string& err_msg = "") { + + // check for equality + if (val1 == val2) return val1; + + // resolve one value none + if (val1 == boost::none || val2 == boost::none) { + if (resolve_defined != boost::none && *resolve_defined == false) return boost::none; + else return val1 == boost::none ? val2 : val1; + } + + throw std::runtime_error(std::string("Cannot reconcile strings: ") + boost::lexical_cast(val1) + std::string(" vs ") + boost::lexical_cast(val2) + (!err_msg.empty() ? std::string(". ") + err_msg : std::string(""))); + } + template ::value, T>::type* = nullptr> + boost::optional reconcile(const boost::optional& val1, const boost::optional& val2, const std::string& err_msg = "") { + return reconcile(val1, val2, boost::none, boost::none, boost::none, err_msg); + } + + template ::value, T>::type* = nullptr> + boost::optional reconcile(const boost::optional& val1, const boost::optional& val2, boost::optional resolve_defined, boost::optional resolve_true, boost::optional resolve_max, const std::string& err_msg = "") { + + // check for equality + if (val1 == val2) return val1; + + // resolve one value none + if (val1 == boost::none || val2 == boost::none) { + if (resolve_defined != boost::none && *resolve_defined == false) return boost::none; + else return val1 == boost::none ? val2 : val1; + } + + // resolve different booleans + if (resolve_true != boost::none) return (bool) val1 == *resolve_true ? val1 : val2; // if resolve true, return true, else return false + + // resolve different numbers + if (resolve_max != boost::none) return *resolve_max ? std::max(*val1, *val2) : std::min(*val1, *val2); + + // cannot reconcile + throw std::runtime_error(std::string("Cannot reconcile integrals: ") + boost::lexical_cast(val1) + std::string(" vs ") + boost::lexical_cast(val2) + (!err_msg.empty() ? std::string(". ") + err_msg : std::string(""))); + } + template ::value, T>::type* = nullptr> + boost::optional reconcile(const boost::optional& val1, const boost::optional& val2, const std::string& err_msg = "") { + return reconcile(val1, val2, boost::none, boost::none, boost::none, err_msg); + } + + template + std::vector reconcile(const std::vector& v1, const std::vector& v2, const std::string& err_msg = "") { + + // check for equality + if (v1 == v2) return v1; + + // resolve one vector empty + if (v1.empty()) return v2; + if (v2.empty()) return v1; + + // otherwise cannot reconcile + throw std::runtime_error("Cannot reconcile vectors" + (!err_msg.empty() ? std::string(". ") + err_msg : std::string(""))); + } + + // ------------------------- THREAD POLLER ---------------------------- + + // WARNING: Only destroy a thread_poller from a thread other than its own pool thread. + // (e.g. a listener callback running on the poll thread, drops the last owning reference/unique_ptr + // to the poller.) + class thread_poller { + public: + thread_poller(); + virtual ~thread_poller(); + + bool is_polling() const { return m_is_polling; } + void set_is_polling(bool is_polling); + void request_is_polling(bool is_polling); + + void set_period_in_ms(uint64_t period_ms) { m_poll_period_ms = period_ms; } + virtual void poll() = 0; + void wait_for_callbacks_idle(); + + class announce_scope { + public: + explicit announce_scope(thread_poller& poller); + ~announce_scope(); + announce_scope(const announce_scope&) = delete; + announce_scope& operator=(const announce_scope&) = delete; + private: + thread_poller& m_poller; + }; + + protected: + std::string m_name; + boost::recursive_mutex m_mutex; + boost::mutex m_polling_mutex; // guards the interruptible periodic sleep (m_poll_cv) + boost::mutex m_lifecycle_mutex; // guards m_poll_loop_running/m_poll_thread_id and start/stop coordination + boost::thread::id m_poll_thread_id; // id of the currently running loop thread; guarded by m_lifecycle_mutex + std::atomic m_is_polling; + bool m_poll_loop_running; // guarded by m_lifecycle_mutex + std::atomic m_poll_period_ms; + boost::condition_variable m_poll_cv; // periodic-sleep interrupt + boost::condition_variable m_lifecycle_cv; // notified (under m_lifecycle_mutex) when the loop actually exits + boost::mutex m_announce_mutex; // guards m_announce_count + boost::condition_variable m_announce_cv; // notified (under m_announce_mutex) when m_announce_count reaches 0 + int m_announce_count = 0; // number of announce_scope instances currently alive, across all threads + + void init_common(const std::string& name); + void run_poll_loop(); + }; +} +#endif /* gen_utils_h */ diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/utils/monero_utils.cpp b/apps/ios/Koins/Monero/include/monero-cpp-src/utils/monero_utils.cpp new file mode 100644 index 0000000..9e7a31a --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/utils/monero_utils.cpp @@ -0,0 +1,624 @@ +/** + * Copyright (c) woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ + +#include "monero_utils.h" +#include "rpc/core_rpc_server_commands_defs.h" +#include "storages/portable_storage_template_helper.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "mnemonics/electrum-words.h" +#include "mnemonics/english.h" +#include "string_tools.h" +#include "byte_stream.h" +#include "gen_utils.h" + +using namespace cryptonote; +using namespace monero_utils; + +void monero_utils::set_log_level(int level) { + mlog_set_log_level(level); +} + +void monero_utils::set_log_categories(const std::string& categories) { + mlog_set_categories(categories.c_str()); +} + +void monero_utils::configure_logging(const std::string& path, bool console) { + mlog_configure(path, console); +} + +// --------------------------- VALIDATION UTILS ------------------------------- + +monero_integrated_address monero_utils::get_integrated_address(monero_network_type network_type, const std::string& standard_address, const std::string& payment_id) { + + // parse and validate address + cryptonote::address_parse_info address_info; + if (!get_account_address_from_str(address_info, static_cast(network_type), standard_address)) throw std::runtime_error("Invalid address"); + if (address_info.has_payment_id) throw std::runtime_error("The given address already has a payment id"); + + // randomly generate payment id if not given, else validate + crypto::hash8 payment_id_h8; + if (payment_id.empty()) { + payment_id_h8 = crypto::rand(); + } else { + cryptonote::blobdata payment_id_data; + if (!epee::string_tools::parse_hexstr_to_binbuff(payment_id, payment_id_data) || sizeof(crypto::hash8) != payment_id_data.size()) throw std::runtime_error("Invalid payment id"); + payment_id_h8 = *reinterpret_cast(payment_id_data.data()); + } + + // build integrated address + monero_integrated_address integrated_address; + integrated_address.m_integrated_address = cryptonote::get_account_integrated_address_as_str(static_cast(network_type), address_info.address, payment_id_h8); + integrated_address.m_standard_address = standard_address; + integrated_address.m_payment_id = epee::string_tools::pod_to_hex(payment_id_h8); + return integrated_address; +} + +bool monero_utils::is_valid_address(const std::string& address, monero_network_type network_type) { + try { + validate_address(address, network_type); + return true; + } catch (...) { + return false; + } +} + +bool monero_utils::is_valid_private_view_key(const std::string& private_view_key) { + try { + validate_private_view_key(private_view_key); + return true; + } catch (...) { + return false; + } +} + +bool monero_utils::is_valid_private_spend_key(const std::string& private_spend_key) { + try { + validate_private_spend_key(private_spend_key); + return true; + } catch (...) { + return false; + } +} + +bool monero_utils::is_valid_public_view_key(const std::string& public_view_key) { + try { + validate_public_view_key(public_view_key); + return true; + } catch (...) { + return false; + } +} + +bool monero_utils::is_valid_public_spend_key(const std::string& public_spend_key) { + try { + validate_public_spend_key(public_spend_key); + return true; + } catch (...) { + return false; + } +} + +bool monero_utils::is_valid_payment_id(const std::string& payment_id) { + try { + validate_payment_id(payment_id); + return true; + } catch (...) { + return false; + } +} + +bool monero_utils::is_valid_mnemonic(const std::string& mnemonic, const std::string& language) { + try { + validate_mnemonic(mnemonic, language); + return true; + } + catch (...) { + return false; + } +} + +void monero_utils::validate_address(const std::string& address, monero_network_type network_type) { + cryptonote::address_parse_info info; + if (!get_account_address_from_str(info, static_cast(network_type), address)) throw std::runtime_error("Invalid address"); +} + +void monero_utils::validate_private_view_key(const std::string& private_view_key) { + if (private_view_key.length() != 64) throw std::runtime_error("private view key expected to be 64 hex characters"); + cryptonote::blobdata private_view_key_data; + if (!epee::string_tools::parse_hexstr_to_binbuff(private_view_key, private_view_key_data) || private_view_key_data.size() != sizeof(crypto::secret_key)) { + throw std::runtime_error("private view key expected to be 64 hex characters"); + } +} + +void monero_utils::validate_private_spend_key(const std::string& private_spend_key) { + if (private_spend_key.length() != 64) throw std::runtime_error("private spend key expected to be 64 hex characters"); + cryptonote::blobdata private_spend_key_data; + if (!epee::string_tools::parse_hexstr_to_binbuff(private_spend_key, private_spend_key_data) || private_spend_key_data.size() != sizeof(crypto::secret_key)) { + throw std::runtime_error("private spend key expected to be 64 hex characters"); + } +} + +void monero_utils::validate_public_view_key(const std::string& public_view_key) { + if (public_view_key.length() != 64) throw std::runtime_error("public view key expected to be 64 hex characters"); + cryptonote::blobdata public_view_key_data; + if (!epee::string_tools::parse_hexstr_to_binbuff(public_view_key, public_view_key_data) || public_view_key_data.size() != sizeof(crypto::public_key)) { + throw std::runtime_error("public view key expected to be 64 hex characters"); + } +} + +void monero_utils::validate_public_spend_key(const std::string& public_spend_key) { + if (public_spend_key.length() != 64) throw std::runtime_error("public spend key expected to be 64 hex characters"); + cryptonote::blobdata public_spend_key_data; + if (!epee::string_tools::parse_hexstr_to_binbuff(public_spend_key, public_spend_key_data) || public_spend_key_data.size() != sizeof(crypto::public_key)) { + throw std::runtime_error("public spend key expected to be 64 hex characters"); + } +} + +void monero_utils::validate_payment_id(const std::string& payment_id) { + if (payment_id.length() != 64 && payment_id.length() != 16) throw std::runtime_error("payment id expected to be 64 or 16 hex characters"); + cryptonote::blobdata payment_id_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(payment_id, payment_id_data) || (payment_id_data.size() != sizeof(crypto::hash8) && payment_id_data.size() != sizeof(crypto::hash))) { + throw std::runtime_error("payment id expected to be 64 or 16 hex characters"); + } +} + +void monero_utils::validate_mnemonic(const std::string& mnemonic, const std::string& language) { + if (mnemonic.empty()) throw std::runtime_error("Mnemonic phrase is empty"); + std::string seed_language; + crypto::secret_key recovery_key; + + if (crypto::ElectrumWords::get_is_old_style_seed(mnemonic)) throw std::runtime_error("Mnemonic phrased words must be 25"); + if(!crypto::ElectrumWords::words_to_bytes(mnemonic, recovery_key, seed_language)) throw std::runtime_error("Invalid mnemonic"); + if (seed_language == crypto::ElectrumWords::old_language_name) seed_language = "English"; + + // validate language + if (!language.empty()) { + if (!crypto::ElectrumWords::is_valid_language(language)) throw std::runtime_error("Invalid language: " + language); + if (language != seed_language) throw std::runtime_error("Seed language mismatch"); + } + +} + +// -------------------------- BINARY SERIALIZATION ---------------------------- + +void monero_utils::json_to_binary(const std::string &json, std::string &bin) { + epee::serialization::portable_storage ps; + ps.load_from_json(json); + epee::byte_stream bs; + ps.store_to_binary(bs); + bin = std::string((char*) bs.data(), bs.size()); +} + +void monero_utils::binary_to_json(const std::string &bin, std::string &json) { + epee::serialization::portable_storage ps; + ps.load_from_binary(bin); + ps.dump_as_json(json); +} + +void monero_utils::binary_blocks_to_json(const std::string &bin, std::string &json) { + + // load binary rpc response to struct + cryptonote::COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::response resp_struct; + epee::serialization::load_t_from_binary(resp_struct, bin); + + // build property tree from deserialized blocks and transactions + boost::property_tree::ptree root; + boost::property_tree::ptree blocksNode; // array of block strings + boost::property_tree::ptree txsNodes; // array of txs per block (array of array) + for (int blockIdx = 0; blockIdx < resp_struct.blocks.size(); blockIdx++) { + + // parse and validate block + cryptonote::block block; + if (cryptonote::parse_and_validate_block_from_blob(resp_struct.blocks[blockIdx].block, block)) { + + // add block node to blocks node + boost::property_tree::ptree blockNode; + blockNode.put("", cryptonote::obj_to_json_str(block)); // TODO: no pretty print + blocksNode.push_back(std::make_pair("", blockNode)); + } else { + throw std::runtime_error("failed to parse block blob at index " + std::to_string(blockIdx)); + } + + // parse and validate txs + boost::property_tree::ptree txs_node; + for (int txIdx = 0; txIdx < resp_struct.blocks[blockIdx].txs.size(); txIdx++) { + cryptonote::transaction tx; + if (cryptonote::parse_and_validate_tx_from_blob(resp_struct.blocks[blockIdx].txs[txIdx].blob, tx)) { + + // add tx node to txs node + boost::property_tree::ptree txNode; + //MTRACE("PRUNED:\n" << monero_utils::get_pruned_tx_json(tx)); + txNode.put("", monero_utils::get_pruned_tx_json(tx)); // TODO: no pretty print + txs_node.push_back(std::make_pair("", txNode)); + } else { + throw std::runtime_error("failed to parse tx blob at index " + std::to_string(txIdx)); + } + } + txsNodes.push_back(std::make_pair("", txs_node)); // array of array of transactions, one array per block + } + root.add_child("blocks", blocksNode); + root.add_child("txs", txsNodes); + root.put("status", resp_struct.status); + root.put("untrusted", resp_struct.untrusted); // TODO: loss of ints and bools + + // convert root to string // TODO: common utility with serial_bridge + std::stringstream ss; + boost::property_tree::write_json(ss, root, false/*pretty*/); + json = ss.str(); +} + +// ------------------------------- RAPIDJSON ---------------------------------- + +void monero_utils::add_json_member(std::string key, std::string val, rapidjson::Document::AllocatorType& allocator, rapidjson::Value& root, rapidjson::Value& field) { + rapidjson::Value field_key(key.c_str(), key.size(), allocator); + field.SetString(val.c_str(), val.size(), allocator); + root.AddMember(field_key, field, allocator); +} + +void monero_utils::add_json_member(std::string key, bool val, rapidjson::Document::AllocatorType& allocator, rapidjson::Value& root) { + rapidjson::Value field_key(key.c_str(), key.size(), allocator); + if (val) { + rapidjson::Value field_val(rapidjson::kTrueType); + root.AddMember(field_key, field_val, allocator); + } else { + rapidjson::Value field_val(rapidjson::kFalseType); + root.AddMember(field_key, field_val, allocator); + } +} + +rapidjson::Value monero_utils::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector& strs) { + rapidjson::Value value_arr(rapidjson::kArrayType); + rapidjson::Value value_str(rapidjson::kStringType); + for (const std::string& str : strs) { + value_str.SetString(str.c_str(), str.size(), allocator); + value_arr.PushBack(value_str, allocator); + } + return value_arr; +} + +rapidjson::Value monero_utils::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector& nums) { + rapidjson::Value value_arr(rapidjson::kArrayType); + rapidjson::Value value_num(rapidjson::kNumberType); + for (const auto& num : nums) { + value_num.SetInt(num); + value_arr.PushBack(value_num, allocator); + } + return value_arr; +} + +rapidjson::Value monero_utils::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector& nums) { + rapidjson::Value value_arr(rapidjson::kArrayType); + rapidjson::Value value_num(rapidjson::kNumberType); + for (const auto& num : nums) { + value_num.SetInt(num); + value_arr.PushBack(value_num, allocator); + } + return value_arr; +} + +// TODO: remove these redundant implementations for different sizes? +rapidjson::Value monero_utils::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector& nums) { + rapidjson::Value value_arr(rapidjson::kArrayType); + rapidjson::Value value_num(rapidjson::kNumberType); + for (const auto& num : nums) { + value_num.SetUint64(num); + value_arr.PushBack(value_num, allocator); + } + return value_arr; +} + +rapidjson::Value monero_utils::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector& nums) { + rapidjson::Value value_arr(rapidjson::kArrayType); + rapidjson::Value value_num(rapidjson::kNumberType); + for (const auto& num : nums) { + value_num.SetUint64(num); + value_arr.PushBack(value_num, allocator); + } + return value_arr; +} + +// ---------------------------------------------------------------------------- + +bool monero_utils::is_valid_language(const std::string& language) { + std::vector languages; + crypto::ElectrumWords::get_language_list(languages, false); + std::vector::iterator it = std::find(languages.begin(), languages.end(), language); + if (it == languages.end()) { + crypto::ElectrumWords::get_language_list(languages, true); + it = std::find(languages.begin(), languages.end(), language); + } + if (it == languages.end()) return false; + return true; +} + +// TODO: this is unused +std::shared_ptr monero_utils::cn_block_to_block(const cryptonote::block& cn_block) { + cryptonote::block temp = cn_block; + std::cout << cryptonote::obj_to_json_str(temp) << std::endl; + std::shared_ptr block = std::make_shared(); + block->m_major_version = cn_block.major_version; + block->m_minor_version = cn_block.minor_version; + block->m_timestamp = cn_block.timestamp; + block->m_prev_hash = epee::string_tools::pod_to_hex(cn_block.prev_id); + block->m_nonce = cn_block.nonce; + block->m_miner_tx = monero_utils::cn_tx_to_tx(cn_block.miner_tx); + for (const crypto::hash& tx_hash : cn_block.tx_hashes) { + block->m_tx_hashes.push_back(epee::string_tools::pod_to_hex(tx_hash)); + } + return block; +} + +std::shared_ptr monero_utils::cn_tx_to_tx(const cryptonote::transaction& cn_tx, bool init_as_tx_wallet) { + std::shared_ptr tx = init_as_tx_wallet ? std::make_shared() : std::make_shared(); + tx->m_version = cn_tx.version; + tx->m_unlock_time = cn_tx.unlock_time; + tx->m_hash = epee::string_tools::pod_to_hex(cn_tx.hash); + tx->m_extra = cn_tx.extra; + + // init inputs + for (const txin_v& cnVin : cn_tx.vin) { + if (cnVin.which() != 0 && cnVin.which() != 3) throw std::runtime_error("Unsupported variant type"); + if (tx->m_is_miner_tx == boost::none) tx->m_is_miner_tx = cnVin.which() == 0; + if (cnVin.which() != 3) continue; // only process txin_to_key of variant TODO: support other types, like 0 "gen" which is miner tx? + std::shared_ptr input = init_as_tx_wallet ? std::make_shared() : std::make_shared(); + input->m_tx = tx; + tx->m_inputs.push_back(input); + const txin_to_key& txin = boost::get(cnVin); + input->m_amount = txin.amount; + input->m_ring_output_indices = txin.key_offsets; + crypto::key_image cnKeyImage = txin.k_image; + input->m_key_image = std::make_shared(); + input->m_key_image->m_hex = epee::string_tools::pod_to_hex(cnKeyImage); + } + + // init outputs + for (const tx_out& cnVout : cn_tx.vout) { + std::shared_ptr output = init_as_tx_wallet ? std::make_shared() : std::make_shared(); + output->m_tx = tx; + tx->m_outputs.push_back(output); + output->m_amount = cnVout.amount; + + // before HF_VERSION_VIEW_TAGS, outputs with public keys are of type txout_to_key + // after HF_VERSION_VIEW_TAGS, outputs with public keys are of type txout_to_tagged_key + crypto::public_key cnStealthPublicKey; + if (cnVout.target.type() == typeid(txout_to_key)) + cnStealthPublicKey = boost::get(cnVout.target).key; + else if (cnVout.target.type() == typeid(txout_to_tagged_key)) + cnStealthPublicKey = boost::get(cnVout.target).key; + else + throw std::runtime_error(std::string("Unexpected output target type found: ") + std::string(cnVout.target.type().name())); + output->m_stealth_public_key = epee::string_tools::pod_to_hex(cnStealthPublicKey); + } + + return tx; + + // TODO: finish this, cryptonote::transaction has: +// std::vector > m_signatures; +// rct::rctSig m_rct_signatures; +// mutable size_t blob_size; +} + +void monero_utils::binary_blocks_to_property_tree(const std::string &bin, boost::property_tree::ptree &node) { + std::string response_json; + monero_utils::binary_blocks_to_json(bin, response_json); + std::istringstream iss(response_json); + boost::property_tree::read_json(iss, node); + + auto blocks = node.get_child("blocks"); + boost::property_tree::ptree parsed_blocks; + + for (auto &entry : blocks) { + const std::string &block_str = entry.second.get_value(); + boost::property_tree::ptree block_node; + gen_utils::deserialize(block_str, block_node); + parsed_blocks.push_back(std::make_pair("", block_node)); + } + + node.put_child("blocks", parsed_blocks); + + auto txs = node.get_child("txs"); + boost::property_tree::ptree all_txs; + + for (auto &rpc_txs_entry : txs) { + boost::property_tree::ptree txs_for_block; + const auto &rpc_txs = rpc_txs_entry.second; + + if (!rpc_txs.empty() || !rpc_txs.data().empty()) { + for (auto &tx_entry : rpc_txs) { + std::string tx_str = tx_entry.second.get_value(); + + auto pos = tx_str.find(','); + if (pos != std::string::npos) { + tx_str.replace(pos, 1, "{"); + tx_str += "}"; + } + + boost::property_tree::ptree tx_node; + gen_utils::deserialize(tx_str, tx_node); + txs_for_block.push_back(std::make_pair("", tx_node)); + } + } + + all_txs.push_back(std::make_pair("", txs_for_block)); + } + + node.put_child("txs", all_txs); +} + +void monero_utils::merge_tx(const std::shared_ptr& tx, std::map>& tx_map, std::map>& block_map) { + if (tx->m_hash == boost::none) throw std::runtime_error("Tx hash is not initialized"); + + // merge tx + std::map>::const_iterator tx_iter = tx_map.find(*tx->m_hash); + if (tx_iter == tx_map.end()) { + tx_map[*tx->m_hash] = tx; // cache new tx + } else { + std::shared_ptr& a_tx = tx_map[*tx->m_hash]; + a_tx->merge(a_tx, tx); // merge with existing tx + } + + // merge tx's block if confirmed + if (tx->get_height() != boost::none) { + std::map>::const_iterator block_iter = block_map.find(tx->get_height().get()); + if (block_iter == block_map.end()) { + block_map[tx->get_height().get()] = tx->m_block; // cache new block + } else { + std::shared_ptr& a_block = block_map[tx->get_height().get()]; + a_block->merge(a_block, tx->m_block); // merge with existing block + } + } +} + +bool monero_utils::tx_height_less_than(const std::shared_ptr& tx1, const std::shared_ptr& tx2) { + if (tx1->m_block != nullptr && tx2->m_block != nullptr) return tx1->get_height() < tx2->get_height(); + else if (tx1->m_block == nullptr) return false; + else return true; +} + +bool monero_utils::incoming_transfer_before(const std::shared_ptr& transfer1, const std::shared_ptr& transfer2) { + + // compare by height + if (tx_height_less_than(transfer1->m_tx, transfer2->m_tx)) return true; + + // compare by account and subaddress index + if (transfer1->m_account_index.get() < transfer2->m_account_index.get()) return true; + else if (transfer1->m_account_index.get() == transfer2->m_account_index.get()) return transfer1->m_subaddress_index.get() < transfer2->m_subaddress_index.get(); + else return false; +} + +bool monero_utils::vout_before(const std::shared_ptr& o1, const std::shared_ptr& o2) { + if (o1 == o2) return false; // ignore equal references + std::shared_ptr ow1 = std::static_pointer_cast(o1); + std::shared_ptr ow2 = std::static_pointer_cast(o2); + + // compare by height + if (tx_height_less_than(ow1->m_tx, ow2->m_tx)) return true; + + // compare by account index, subaddress index, output index, then key image hex + if (ow1->m_account_index.get() < ow2->m_account_index.get()) return true; + if (ow1->m_account_index.get() == ow2->m_account_index.get()) { + if (ow1->m_subaddress_index.get() < ow2->m_subaddress_index.get()) return true; + if (ow1->m_subaddress_index.get() == ow2->m_subaddress_index.get()) { + if (ow1->m_index.get() < ow2->m_index.get()) return true; + if (ow1->m_index.get() == ow2->m_index.get()) throw std::runtime_error("Should never sort outputs with duplicate indices"); + } + } + return false; +} + +static std::string make_uri(const std::string &address, const std::string &payment_id, uint64_t amount, const std::string &tx_description, const std::string &recipient_name, monero_network_type network_type) { + cryptonote::address_parse_info info; + + if(!get_account_address_from_str(info, static_cast(network_type), address)) { + throw std::runtime_error(std::string("Invalid address: ") + address); + } + if (!payment_id.empty()) { + throw std::runtime_error("Standalone payment id deprecated, use integrated address instead"); + } + + std::string uri = "monero:" + address; + unsigned int n_fields = 0; + + if (amount > 0) { + // URI encoded amount is in decimal units, not atomic units + uri += (n_fields++ ? "&" : "?") + std::string("tx_amount=") + cryptonote::print_money(amount); + } + if (!recipient_name.empty()) { + uri += (n_fields++ ? "&" : "?") + std::string("recipient_name=") + epee::net_utils::conver_to_url_format(recipient_name); + } + if (!tx_description.empty()) { + uri += (n_fields++ ? "&" : "?") + std::string("tx_description=") + epee::net_utils::conver_to_url_format(tx_description); + } + + return uri; +} + +std::string monero_utils::get_payment_uri(const monero_tx_config& config, monero_network_type network_type) { + // validate config + std::vector> destinations = config.get_normalized_destinations(); + if (destinations.size() != 1) throw std::runtime_error("Cannot make URI from supplied parameters: must provide exactly one destination to send funds"); + if (destinations.at(0)->m_address == boost::none) throw std::runtime_error("Cannot make URI from supplied parameters: must provide destination address"); + if (destinations.at(0)->m_amount == boost::none) throw std::runtime_error("Cannot make URI from supplied parameters: must provide destination amount"); + + // prepare wallet2 params + std::string address = destinations.at(0)->m_address.get(); + std::string payment_id = config.m_payment_id == boost::none ? "" : config.m_payment_id.get(); + uint64_t amount = destinations.at(0)->m_amount.get(); + std::string note = config.m_note == boost::none ? "" : config.m_note.get(); + std::string m_recipient_name = config.m_recipient_name == boost::none ? "" : config.m_recipient_name.get(); + + // make uri + std::string uri = make_uri(address, payment_id, amount, note, m_recipient_name, network_type); + if (uri.empty()) throw std::runtime_error("Cannot make URI from supplied parameters"); + return uri; +} + +uint64_t monero_utils::xmr_to_atomic_units(double amount_xmr) { + if (!std::isfinite(amount_xmr) || amount_xmr < 0) throw std::invalid_argument("amount must be a finite, non-negative number"); + + long double atomic = std::round(static_cast(amount_xmr) * static_cast(XMR_AU_MULTIPLIER)); + + // validate the actual rounded value about to be cast, not a precomputed "amount_xmr" threshold: + // a threshold like UINT64_MAX / XMR_AU_MULTIPLIER carries its own division rounding error and can + // let a value that overflows uint64_t slip through, and casting an out-of-range floating point + // value to uint64_t is undefined behavior (not a wraparound). 2^64 is exactly representable in any + // binary floating point type with enough exponent range (it's a pure power of two), so this bound + // check is exact regardless of long double's precision on a given platform. + static const long double MAX_ATOMIC_UNITS_EXCLUSIVE = std::ldexp(1.0L, 64); // 2^64 + if (atomic < 0.0L || atomic >= MAX_ATOMIC_UNITS_EXCLUSIVE) throw std::invalid_argument("amount exceeds maximum representable atomic units"); + + return static_cast(atomic); +} + +double monero_utils::atomic_units_to_xmr(uint64_t amount_atomic_units) { + return static_cast(amount_atomic_units) / static_cast(XMR_AU_MULTIPLIER); +} diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/utils/monero_utils.h b/apps/ios/Koins/Monero/include/monero-cpp-src/utils/monero_utils.h new file mode 100644 index 0000000..d04282c --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/utils/monero_utils.h @@ -0,0 +1,322 @@ +/** + * Copyright (c) woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ + +#pragma once + +#ifndef monero_utils_h +#define monero_utils_h + +#include "wallet/monero_wallet_model.h" +#include "cryptonote_basic/cryptonote_basic.h" +#include "serialization/keyvalue_serialization.h" // TODO: consolidate with other binary deps? +#include "storages/portable_storage.h" + +/** + * Collection of utilities for the Monero library. + */ +namespace monero_utils +{ + using namespace cryptonote; + + // ------------------------------ CONSTANTS --------------------------------- + + static const int RING_SIZE = 12; // network-enforced ring size + static const uint64_t XMR_AU_MULTIPLIER = 1000000000000ULL; + + // -------------------------------- UTILS ----------------------------------- + + void set_log_level(int level); + void set_log_categories(const std::string& categories); + void configure_logging(const std::string& path, bool console); + monero_integrated_address get_integrated_address(monero_network_type network_type, const std::string& standard_address, const std::string& payment_id); + std::string get_payment_uri(const monero_tx_config& config, monero_network_type network_type); + bool is_valid_address(const std::string& address, monero_network_type network_type); + bool is_valid_private_view_key(const std::string& private_view_key); + bool is_valid_private_spend_key(const std::string& private_spend_key); + bool is_valid_public_view_key(const std::string& public_view_key); + bool is_valid_public_spend_key(const std::string& public_spend_key); + bool is_valid_payment_id(const std::string& payment_id); + bool is_valid_mnemonic(const std::string& mnemonic, const std::string& language = ""); + void validate_address(const std::string& address, monero_network_type network_type); + void validate_private_view_key(const std::string& private_view_key); + void validate_private_spend_key(const std::string& private_spend_key); + void validate_public_view_key(const std::string& public_view_key); + void validate_public_spend_key(const std::string& public_spend_key); + void validate_payment_id(const std::string& payment_id); + void validate_mnemonic(const std::string& mnemonic, const std::string& language = ""); + void json_to_binary(const std::string &json, std::string &bin); + void binary_to_json(const std::string &bin, std::string &json); + void binary_blocks_to_json(const std::string &bin, std::string &json); + uint64_t xmr_to_atomic_units(double amount_xmr); + double atomic_units_to_xmr(uint64_t amount_atomic_units); + + // ------------------------------ RAPIDJSON --------------------------------- + + /** + * Add number, string, and boolean json members using template specialization. + * + * TODO: add_json_member("key", "val", ...) treated as integer instead of string literal + */ + template + void add_json_member(std::string key, T val, rapidjson::Document::AllocatorType& allocator, rapidjson::Value& root, rapidjson::Value& field) { + rapidjson::Value field_key(key.c_str(), key.size(), allocator); + if (std::is_signed::value) field.SetInt64((int64_t) val); + else field.SetUint64((uint64_t) val); + root.AddMember(field_key, field, allocator); + } + void add_json_member(std::string key, std::string val, rapidjson::Document::AllocatorType& allocator, rapidjson::Value& root, rapidjson::Value& field); + void add_json_member(std::string key, bool val, rapidjson::Document::AllocatorType& allocator, rapidjson::Value& root); + + // TODO: template implementation here, could move to monero_utils.hpp per https://stackoverflow.com/questions/3040480/c-template-function-compiles-in-header-but-not-implementation + template rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector>& vals) { + rapidjson::Value value_arr(rapidjson::kArrayType); + for (const auto& val : vals) { + value_arr.PushBack(val->to_rapidjson_val(allocator), allocator); + } + return value_arr; + } + + // TODO: template implementation here, could move to monero_utils.hpp per https://stackoverflow.com/questions/3040480/c-template-function-compiles-in-header-but-not-implementation + template rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector& vals) { + rapidjson::Value value_arr(rapidjson::kArrayType); + for (const auto& val : vals) { + value_arr.PushBack(val.to_rapidjson_val(allocator), allocator); + } + return value_arr; + } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector& strs); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector& nums); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector& nums); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector& nums); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector& nums); + + // -------------------------------------------------------------------------- + + /** + * Indicates if the given language is valid. + * + * @param language is the language to validate + * @return true if the language is valid, false otherwise + */ + bool is_valid_language(const std::string& language); + + /** + * Convert a cryptonote::block to a block in this library's native model. + * + * @param cn_block is the block to convert + * @return a block in this library's native model + */ + std::shared_ptr cn_block_to_block(const cryptonote::block& cn_block); + + /** + * Convert a cryptonote::transaction to a transaction in this library's + * native model. + * + * @param cn_tx is the transaction to convert + * @param init_as_tx_wallet specifies if a monero_tx xor monero_tx_wallet should be initialized + */ + std::shared_ptr cn_tx_to_tx(const cryptonote::transaction& cn_tx, bool init_as_tx_wallet = false); + + /** + * Modified from core_rpc_server.cpp to return a std::string. + * + * TODO: remove this duplicate, use core_rpc_server instead + */ + static std::string get_pruned_tx_json(cryptonote::transaction &tx) + { + std::stringstream ss; + json_archive ar(ss); + bool r = tx.serialize_base(ar); + CHECK_AND_ASSERT_MES(r, std::string(), "Failed to serialize rct signatures base"); + return ss.str(); + } + + void binary_blocks_to_property_tree(const std::string &bin, boost::property_tree::ptree &node); + + /** + * Merges a transaction into a unique set of transactions. + * + * @param tx is the transaction to merge into the existing txs + * @param tx_map maps tx hashes to txs + * @param block_map maps block heights to blocks + */ + void merge_tx(const std::shared_ptr& tx, std::map>& tx_map, std::map>& block_map); + + /** + * Returns true iff tx1's height is known to be less than tx2's height for sorting. + */ + bool tx_height_less_than(const std::shared_ptr& tx1, const std::shared_ptr& tx2); + + /** + * Returns true iff transfer1 is ordered before transfer2 by ascending account and subaddress indices. + */ + bool incoming_transfer_before(const std::shared_ptr& transfer1, const std::shared_ptr& transfer2); + + /** + * Returns true iff wallet vout1 is ordered before vout2 by ascending account and subaddress indices then index. + */ + bool vout_before(const std::shared_ptr& o1, const std::shared_ptr& o2); + + // ----------------------------- GATHER BLOCKS ------------------------------ + + static std::vector> get_blocks_from_txs(std::vector> txs) { + std::shared_ptr unconfirmed_block = nullptr; // placeholder for unconfirmed txs + std::vector> blocks; + std::unordered_set> seen_block_ptrs; + for (const std::shared_ptr& tx : txs) { + if (tx->m_block == nullptr) { + if (unconfirmed_block == nullptr) unconfirmed_block = std::make_shared(); + tx->m_block = unconfirmed_block; + unconfirmed_block->m_txs.push_back(tx); + } + std::unordered_set>::const_iterator got = seen_block_ptrs.find(tx->m_block); + if (got == seen_block_ptrs.end()) { + seen_block_ptrs.insert(tx->m_block); + blocks.push_back(tx->m_block); + } + } + return blocks; + } + + static std::vector> get_blocks_from_transfers(std::vector> transfers) { + std::shared_ptr unconfirmed_block = nullptr; // placeholder for unconfirmed txs in return json + std::vector> blocks; + std::unordered_set> seen_block_ptrs; + for (auto const& transfer : transfers) { + std::shared_ptr tx = transfer->m_tx; + if (tx->m_block == nullptr) { + if (unconfirmed_block == nullptr) unconfirmed_block = std::make_shared(); + tx->m_block = unconfirmed_block; + unconfirmed_block->m_txs.push_back(tx); + } + std::unordered_set>::const_iterator got = seen_block_ptrs.find(tx->m_block); + if (got == seen_block_ptrs.end()) { + seen_block_ptrs.insert(tx->m_block); + blocks.push_back(tx->m_block); + } + } + return blocks; + } + + static std::vector> get_blocks_from_outputs(std::vector> outputs) { + std::vector> blocks; + std::unordered_set> seen_block_ptrs; + for (auto const& output : outputs) { + std::shared_ptr tx = std::static_pointer_cast(output->m_tx); + if (tx->m_block == nullptr) throw std::runtime_error("Need to handle unconfirmed output"); + std::unordered_set>::const_iterator got = seen_block_ptrs.find(tx->m_block); + if (got == seen_block_ptrs.end()) { + seen_block_ptrs.insert(tx->m_block); + blocks.push_back(tx->m_block); + } + } + return blocks; + } + + // ------------------------------ FREE MEMORY ------------------------------- + + static void free(std::shared_ptr block) { + for (std::shared_ptr& tx : block->m_txs) { + tx->m_block.reset(); + monero_tx_wallet* tx_wallet = dynamic_cast(tx.get()); + if (tx_wallet != nullptr) { + if (tx_wallet->m_tx_set != nullptr) tx_wallet->m_tx_set.reset(); + if (tx_wallet->m_outgoing_transfer != nullptr) tx_wallet->m_outgoing_transfer->m_tx.reset(); + for (std::shared_ptr transfer : tx_wallet->m_incoming_transfers) transfer->m_tx.reset(); + for (std::shared_ptr output : tx_wallet->m_outputs) output->m_tx.reset(); + for (std::shared_ptr input : tx_wallet->m_inputs) { + input->m_key_image.reset(); + input->m_tx.reset(); + } + } + monero_tx_query* tx_query = dynamic_cast(tx.get()); + if (tx_query != nullptr) { + if (tx_query->m_transfer_query != nullptr) { + tx_query->m_transfer_query->m_tx_query.reset(); + tx_query->m_transfer_query.reset(); + } + if (tx_query->m_output_query != nullptr) { + tx_query->m_output_query->m_tx_query.reset(); + tx_query->m_output_query.reset(); + } + } + } + block.reset(); + } + + static void free(std::vector> blocks) { + for (std::shared_ptr& block : blocks) monero_utils::free(block); + } + + static void free(std::shared_ptr tx) { + if (tx->m_block == nullptr) { + std::shared_ptr block = std::make_shared(); + tx->m_block = block; + block->m_txs.push_back(tx); + } + monero_utils::free(tx->m_block); + } + + static void free(std::vector> txs) { + return monero_utils::free(monero_utils::get_blocks_from_txs(txs)); + } + + static void free(std::vector> transfers) { + return monero_utils::free(monero_utils::get_blocks_from_transfers(transfers)); + } + + static void free(std::vector> outputs) { + return monero_utils::free(monero_utils::get_blocks_from_outputs(outputs)); + } +} +#endif /* monero_utils_h */ diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet.h b/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet.h new file mode 100644 index 0000000..3e56592 --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet.h @@ -0,0 +1,1432 @@ +/** + * Copyright (c) woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ + +#pragma once + +#include "monero_wallet_model.h" +#include +#include + +using namespace monero; + +/** + * Public library interface. + */ +namespace monero { + + // --------------------------------- LISTENERS ------------------------------ + + /** + * Interface to receive wallet notifications. + */ + class monero_wallet_listener { + public: + + /** + * Invoked when sync progress is made. + * + * @param height - height of the synced block + * @param start_height - starting height of the sync request + * @param end_height - ending height of the sync request + * @param percent_done - sync progress as a percentage + * @param message - human-readable description of the current progress + */ + virtual void on_sync_progress(uint64_t height, uint64_t start_height, uint64_t end_height, double percent_done, const std::string& message) {} + + /** + * Invoked when a new block is processed. + * + * @param block - the newly processed block + */ + virtual void on_new_block(uint64_t height) {}; + + /** + * Invoked when the wallet's balances change. + * + * @param new_balance - new balance + * @param new_unlocked_balance - new unlocked balance + */ + virtual void on_balances_changed(uint64_t new_balance, uint64_t new_unlocked_balance) {}; + + /** + * Invoked when the wallet receives an output. + * + * @param output - the received output + */ + virtual void on_output_received(const monero_output_wallet& output) {}; + + /** + * Invoked when the wallet spends an output. + * + * @param output - the spent output + */ + virtual void on_output_spent(const monero_output_wallet& output) {}; + }; + + // forward declaration of internal wallet2 listener + struct wallet2_listener; + + // ----------------------------- WALLET METHODS ----------------------------- + + /** + * Base wallet with default implementations. + */ + class monero_wallet { + + public: + + /** + * Virtual destructor. + */ + virtual ~monero_wallet() {} + + /** + * Indicates if the wallet is view-only, meaning it does have the private + * spend key and can therefore only observe incoming outputs. + * + * @return true if the wallet is view-only, false otherwise + */ + virtual bool is_view_only() const { + throw std::runtime_error("is_view_only() not supported"); + } + + /** + * Set the wallet's daemon connection. + * + * @param uri is the daemon's URI + * @param username is the username to authenticate with the daemon (optional) + * @param password is the password to authenticate with the daemon (optional) + * @param proxy_uri is the proxy to the daemon (e.g. over Tor). + * @param is_trusted specifies if the daemon is trusted (default = trusted if local address) + */ + virtual void set_daemon_connection(const std::string& uri, const std::string& username = "", const std::string& password = "", const std::string& proxy_uri = "", const boost::optional& is_trusted = boost::none) { + throw std::runtime_error("set_daemon_connection() not supported"); + } + + /** + * Set the wallet's daemon connection. + * + * @param connection is the connection to set + * @param is_trusted specifies if the daemon is trusted (default = trusted if local address) + */ + virtual void set_daemon_connection(const std::shared_ptr& connection, const boost::optional& is_trusted = boost::none) { + throw std::runtime_error("set_daemon_connection() not supported"); + } + + /** + * Get the wallet's daemon connection. + * + * @return the wallet's daemon connection or null if not set + */ + virtual std::shared_ptr get_daemon_connection() const { + throw std::runtime_error("get_daemon_connection() not supported"); + } + + /** + * Indicates if the wallet is connected a daemon. + * + * @return true if the wallet is connected to a daemon, false otherwise + */ + virtual bool is_connected_to_daemon() const { + throw std::runtime_error("is_connected_to_daemon() not supported"); + } + + /** + * Indicates if the wallet's daemon is synced with the network. + * + * @return true if the daemon is synced with the network, false otherwise + */ + virtual bool is_daemon_synced() const { + throw std::runtime_error("is_daemon_synced() not supported"); + } + + /** + * Indicates if the daemon is trusted or untrusted. + * + * @return true if the daemon is trusted, false otherwise + */ + virtual bool is_daemon_trusted() const { + throw std::runtime_error("is_daemon_trusted() not supported"); + } + + /** + * Indicates if the wallet is synced with the daemon. + * + * @return true if the wallet is synced with the daemon, false otherwise + */ + virtual bool is_synced() const { + throw std::runtime_error("is_synced() not supported"); + } + + /** + * Get the wallet's version. + * + * @return the wallet's version + */ + virtual monero_version get_version() const { + throw std::runtime_error("get_version() not supported"); + } + + /** + * Get the path of this wallet's file on disk. + * + * @return the path of this wallet's file on disk + */ + virtual std::string get_path() const { + throw std::runtime_error("get_path() not supported"); + } + + /** + * Get the wallet's network type (mainnet, testnet, or stagenet). + * + * @return the wallet's network type + */ + virtual monero_network_type get_network_type() const { + throw std::runtime_error("get_network_type() not supported"); + } + + /** + * Get the wallet's mnemonic phrase or seed. + * + * @return the wallet's mnemonic phrase or seed. + */ + virtual std::string get_seed() const { + throw std::runtime_error("get_seed() not supported"); + } + + /** + * Get the language of the wallet's mnemonic phrase or seed. + * + * @return the language of the wallet's mnemonic phrase or seed. + */ + virtual std::string get_seed_language() const { + throw std::runtime_error("get_seed_language() not supported"); + } + + /** + * Get the wallet's public view key. + * + * @return the wallet's public view key + */ + virtual std::string get_public_view_key() const { + throw std::runtime_error("get_public_view_key() not supported"); + } + + /** + * Get the wallet's private view key. + * + * @return the wallet's private view key + */ + virtual std::string get_private_view_key() const { + throw std::runtime_error("get_private_view_key() not supported"); + } + + /** + * Get the wallet's public spend key. + * + * @return the wallet's public spend key + */ + virtual std::string get_public_spend_key() const { + throw std::runtime_error("get_public_spend_key() not supported"); + } + + /** + * Get the wallet's private spend key. + * + * @return the wallet's private spend key + */ + virtual std::string get_private_spend_key() const { + throw std::runtime_error("get_private_spend_key() not supported"); + } + + /** + * Get the wallet's primary address. + * + * @return the wallet's primary address + */ + virtual std::string get_primary_address() const { + return get_address(0, 0); + } + + /** + * Get the address of a specific subaddress. + * + * @param account_idx specifies the account index of the address's subaddress + * @param subaddress_idx specifies the subaddress index within the account + * @return the receive address of the specified subaddress + */ + virtual std::string get_address(const uint32_t account_idx, const uint32_t subaddress_idx) const { + throw std::runtime_error("get_address() not supported"); + } + + /** + * Get the account and subaddress index of the given address. + * + * @param address is the address to get the account and subaddress index from + * @return the account and subaddress indices + * @throws exception if address is not a wallet address + */ + virtual monero_subaddress get_address_index(const std::string& address) const { + throw std::runtime_error("get_address_index() not supported"); + } + + /** + * Get an integrated address from a standard address and a payment id. + * + * @param standard_address is the integrated addresse's standard address (defaults to wallet's primary address) + * @param payment_id is the integrated addresse's payment id (defaults to randomly generating new payment id) + * @return the integrated address + */ + virtual monero_integrated_address get_integrated_address(const std::string& standard_address = "", const std::string& payment_id = "") const { + throw std::runtime_error("get_integrated_address() not supported"); + } + + /** + * Decode an integrated address to get its standard address and payment id. + * + * @param integrated_address is an integrated address to decode + * @return the decoded integrated address including standard address and payment id + */ + virtual monero_integrated_address decode_integrated_address(const std::string& integrated_address) const { + throw std::runtime_error("decode_integrated_address() not supported"); + } + + /** + * Get the height of the last block processed by the wallet (its index + 1). + * + * @return the height of the last block processed by the wallet + */ + virtual uint64_t get_height() const { + throw std::runtime_error("get_height() not supported"); + } + + /** + * Get the height of the first block that the wallet scans. + * + * @return the height of the first block that the wallet scans + */ + virtual uint64_t get_restore_height() const { + throw std::runtime_error("get_restore_height() not supported"); + } + + /** + * Set the height of the first block that the wallet scans. + * + * @param restore_height is the height of the first block that the wallet scans + */ + virtual void set_restore_height(uint64_t restore_height) { + throw std::runtime_error("set_restore_height() not supported"); + } + + /** + * Get the height that the wallet's daemon is synced to. + * + * @return the height that the wallet's daemon is synced to + */ + virtual uint64_t get_daemon_height() const { + throw std::runtime_error("get_daemon_height() not supported"); + } + + /** + * Get the maximum height of the peers the wallet's daemon is connected to. + * + * @return the maximum height of the peers the wallet's daemon is connected to + */ + virtual uint64_t get_daemon_max_peer_height() const { + throw std::runtime_error("get_daemon_max_peer_height() not supported"); + } + + /** + * Get the blockchain's height by date as a conservative estimate for scanning. + * + * @param year - year of the height to get + * @param month - month of the height to get as a number between 1 and 12 + * @param day - day of the height to get as a number between 1 and 31 + * @return the blockchain's approximate height at the given date + */ + virtual uint64_t get_height_by_date(uint16_t year, uint8_t month, uint8_t day) const { + throw std::runtime_error("get_height_by_date(year, month, day) not supported"); + } + + /** + * Register a listener receive wallet notifications. + * + * @param listener is the listener to receive wallet notifications + */ + virtual void add_listener(monero_wallet_listener& listener) { + throw std::runtime_error("add_listener() not supported"); + } + + /** + * Unregister a listener to receive wallet notifications. + * + * @param listener is the listener to unregister + */ + virtual void remove_listener(monero_wallet_listener& listener) { + throw std::runtime_error("remove_listener() not supported"); + } + + /** + * Get the listeners registered with the wallet. + */ + virtual std::set get_listeners() { + throw std::runtime_error("get_listeners() not supported"); + } + + /** + * Synchronize the wallet with the daemon as a one-time synchronous process. + * + * @return the sync result + */ + virtual monero_sync_result sync() { + throw std::runtime_error("sync() not supported"); + } + + /** + * Synchronize the wallet with the daemon as a one-time synchronous process. + * + * @param listener - listener to receive notifications during synchronization + * @return the sync result + */ + virtual monero_sync_result sync(monero_wallet_listener& listener) { + throw std::runtime_error("sync() not supported"); + } + + /** + * Synchronize the wallet with the daemon as a one-time synchronous process. + * + * @param start_height is the start height to sync from (ignored if less than last processed block) + * @return the sync result + */ + virtual monero_sync_result sync(uint64_t start_height) { + throw std::runtime_error("sync() not supported"); + } + + /** + * Synchronizes the wallet with the blockchain. + * + * @param start_height - start height to sync from (ignored if less than last processed block) + * @param listener - listener to receive notifications during synchronization + * @return the sync result + */ + virtual monero_sync_result sync(uint64_t start_height, monero_wallet_listener& listener) { + throw std::runtime_error("sync() not supported"); + } + + /** + * Start background synchronizing with a maximum period between syncs. + * + * @param sync_period_in_ms - maximum period between syncs in milliseconds + */ + virtual void start_syncing(uint64_t sync_period_in_ms = 10000) { + throw std::runtime_error("start_syncing() not supported"); + } + + /** + * Stop the asynchronous thread to continuously synchronize the wallet with the daemon. + */ + virtual void stop_syncing() { + throw std::runtime_error("stop_syncing() not supported"); + } + + /** + * Scan transactions by their hash/id. + * + * @param tx_hashes - tx hashes to scan + */ + virtual void scan_txs(const std::vector& tx_hashes) { + throw std::runtime_error("scan_txs() not supported"); + } + + /** + * Rescan the blockchain for spent outputs. + * + * Note: this can only be called with a trusted daemon. + * + * Example use case: peer multisig hex is import when connected to an untrusted daemon, + * so the wallet will not rescan spent outputs. Then the wallet connects to a trusted + * daemon. This method should be manually invoked to rescan outputs. + */ + virtual void rescan_spent() { + throw std::runtime_error("rescan_spent() not supported"); + } + + /** + * Rescan the blockchain from scratch, losing any information which cannot be recovered from + * the blockchain itself. + * + * WARNING: This method discards local wallet data like destination addresses, tx secret keys, + * tx notes, etc. + */ + virtual void rescan_blockchain() { + throw std::runtime_error("rescan_blockchain() not supported"); + } + + /** + * Get the wallet's balance. + * + * @return the wallet's balance + */ + virtual uint64_t get_balance() const { + throw std::runtime_error("get_balance() not supported"); + } + + /** + * Get an account's balance. + * + * @param account_idx is the index of the account to get the balance of + * @return the account's balance + */ + virtual uint64_t get_balance(uint32_t account_idx) const { + throw std::runtime_error("get_balance() not supported"); + } + + /** + * Get a subaddress's balance. + * + * @param account_idx is the index of the subaddress's account to get the balance of + * @param subaddress_idx is the index of the subaddress to get the balance of + * @return the subaddress's balance + */ + virtual uint64_t get_balance(uint32_t account_idx, uint32_t subaddress_idx) const { + throw std::runtime_error("get_balance() not supported"); + } + + /** + * Get the wallet's unlocked balance. + * + * @return the wallet's unlocked balance + */ + virtual uint64_t get_unlocked_balance() const { + throw std::runtime_error("get_unlocked_balance() not supported"); + } + + /** + * Get an account's unlocked balance. + * + * @param account_idx is the index of the account to get the unlocked balance of + * @return the account's unlocked balance + */ + virtual uint64_t get_unlocked_balance(uint32_t account_idx) const { + throw std::runtime_error("get_unlocked_balance() not supported"); + } + + /** + * Get a subaddress's unlocked balance. + * + * @param account_idx is the index of the subaddress's account to get the unlocked balance of + * @param subaddress_idx is the index of the subaddress to get the unlocked balance of + * @return the subaddress's balance + */ + virtual uint64_t get_unlocked_balance(uint32_t account_idx, uint32_t subaddress_idx) const { + throw std::runtime_error("get_unlocked_balance() not supported"); + } + + /** + * Get all accounts. + * + * @return List are all accounts within the wallet + */ + virtual std::vector get_accounts() const { + return get_accounts(false, std::string("")); + } + + /** + * Get all accounts. + * + * @param include_subaddresses specifies if subaddresses should be included + * @return List are all accounts + */ + virtual std::vector get_accounts(bool include_subaddresses) const { + return get_accounts(include_subaddresses, ""); + } + + /** + * Get accounts with a given tag. + * + * @param tag is the tag for filtering accounts, all accounts if null + * @return List are all accounts for the wallet with the given tag + */ + virtual std::vector get_accounts(const std::string& tag) const { + return get_accounts(false, tag); + } + + /** + * Get accounts with a given tag. + * + * @param include_subaddresses specifies if subaddresses should be included + * @param tag is the tag for filtering accounts, all accounts if null + * @return List are all accounts for the wallet with the given tag + */ + virtual std::vector get_accounts(bool include_subaddresses, const std::string& tag) const { + throw std::runtime_error("get_accounts() not supported"); + } + + /** + * Get an account without subaddress information. + * + * @param account_idx specifies the account to get + * @return the retrieved account + */ + virtual monero_account get_account(uint32_t account_idx) const { + return get_account(account_idx, false); + } + + /** + * Get an account. + * + * @param account_idx specifies the account to get + * @param include_subaddresses specifies if subaddresses should be included + * @return the retrieved account + */ + virtual monero_account get_account(const uint32_t account_idx, bool include_subaddresses) const { + throw std::runtime_error("get_account() not supported"); + } + + /** + * Create a new account with a label for the first subaddress. + * + * @param label specifies the label for the account's first subaddress (optional) + * @return the created account + */ + virtual monero_account create_account(const std::string& label = "") { + throw std::runtime_error("create_account() not supported"); + } + + /** + * Tag accounts. + * + * @param tag is the tag to apply to the specified accounts + * @param account_indices are the indices of the accounts to tag + */ + virtual void tag_accounts(const std::string& tag, const std::vector& account_indices) { + throw std::runtime_error("tag_accounts() not supported"); + } + + /** + * Untag accounts. + * + * @param account_indices are the indices of the accounts to untag + */ + virtual void untag_accounts(const std::vector& account_indices) { + throw std::runtime_error("untag_accounts() not supported"); + } + + /** + * Return all account tags. + * + * @return the wallet's account tags + */ + virtual std::vector> get_account_tags() const { + throw std::runtime_error("get_account_tags() not supported"); + } + + /** + * Sets a human-readable description for a tag. + * + * @param tag is the tag to set a description for + * @param label is the label to set for the tag + */ + virtual void set_account_tag_label(const std::string& tag, const std::string& label) { + throw std::runtime_error("set_account_tag_label() not supported"); + } + + /** + * Get all subaddresses in an account. + * + * @param account_idx specifies the account to get subaddresses within + * @return List are the retrieved subaddresses + */ + virtual std::vector get_subaddresses(const uint32_t account_idx) const { + return get_subaddresses(account_idx, std::vector()); + } + + /** + * Get subaddresses in an account. + * + * @param account_idx specifies the account to get subaddresses within + * @param subaddress_indices are specific subaddresses to get (optional) + * @return the retrieved subaddresses + */ + virtual std::vector get_subaddresses(const uint32_t account_idx, const std::vector& subaddress_indices) const { + throw std::runtime_error("get_subaddresses() not supported"); + } + + /** + * Get a subaddress. + * + * @param account_idx specifies the index of the subaddress's account + * @param subaddress_idx specifies index of the subaddress within the account + * @return the retrieved subaddress + */ + virtual monero_subaddress get_subaddress(const uint32_t account_idx, const uint32_t subaddress_idx) const { + throw std::runtime_error("get_subaddress() not supported"); + } + + /** + * Create a subaddress within an account. + * + * @param account_idx specifies the index of the account to create the subaddress within + * @param label specifies the the label for the subaddress (defaults to empty std::string) + * @return the created subaddress + */ + virtual monero_subaddress create_subaddress(uint32_t account_idx, const std::string& label = "") { + throw std::runtime_error("create_subaddress() not supported"); + } + + /** + * Set a subaddress label. + * + * @param account_idx index of the account to set the label for + * @param subaddress_idx index of the subaddress to set the label for + * @param label the label to set + */ + virtual void set_subaddress_label(uint32_t account_idx, uint32_t subaddress_idx, const std::string& label = "") { + throw std::runtime_error("set_subaddress_label() not supported"); + } + + /** + * Get all wallet transactions. Wallet transactions contain one or more + * transfers that are either incoming or outgoing to the wallet. + * + * @return all wallet transactions (free memory using monero_utils::free) + */ + virtual std::vector> get_txs() const { + throw std::runtime_error("get_txs() not supported"); + } + + /** + * Get wallet transactions. Wallet transactions contain one or more + * transfers that are either incoming or outgoing to the wallet. + * + * Query results can be filtered by passing in a transaction query. + * Transactions must meet every criteria defined in the query in order to + * be returned. All filtering is optional and no filtering is applied when + * not defined. + * + * @param query filters query results (optional) + * @return wallet transactions per the query (free memory using monero_utils::free) + */ + virtual std::vector> get_txs(const monero_tx_query& query) const { + throw std::runtime_error("get_txs(query) not supported"); + } + + /** + * Get incoming and outgoing transfers to and from this wallet. An outgoing + * transfer represents a total amount sent from one or more subaddresses + * within an account to individual destination addresses, each with their + * own amount. An incoming transfer represents a total amount received into + * a subaddress within an account. Transfers belong to transactions which + * are stored on the blockchain. + * + * Query results can be filtered by passing in a monero_transfer_query. + * Transfers must meet every criteria defined in the query in order to be + * returned. All filtering is optional and no filtering is applied when not + * defined. + * + * @param query filters query results (optional) + * @return wallet transfers per the query (free memory using monero_utils::free) + */ + virtual std::vector> get_transfers(const monero_transfer_query& query) const { + throw std::runtime_error("get_transfers() not supported"); + } + + /** + * Get outputs created from previous transactions that belong to the wallet + * (i.e. that the wallet can spend one time). Outputs are part of + * transactions which are stored in blocks on the blockchain. + * + * Results can be configured by passing a monero_output_query. Outputs must + * meet every criteria defined in the query in order to be returned. All + * filtering is optional and no filtering is applied when not defined. + * + * @param query specifies query options (optional) + * @return wallet outputs per the query (free memory using monero_utils::free) + */ + virtual std::vector> get_outputs(const monero_output_query& query) const { + throw std::runtime_error("get_outputs() not supported"); + } + + /** + * Export outputs in hex format. + * + * @param all - export all outputs if true, else export outputs since the last export + * @return outputs in hex format, empty std::string if no outputs + */ + virtual std::string export_outputs(bool all = false) const { + throw std::runtime_error("export_outputs() not supported"); + } + + /** + * Import outputs in hex format. + * + * @param outputs_hex are outputs in hex format + * @return the number of outputs imported + */ + virtual int import_outputs(const std::string& outputs_hex) { + throw std::runtime_error("import_outputs() not supported"); + } + + /** + * Export signed key images. + * + * @param all - export all key images if true, else export key images since the last export + * @return the wallet's signed key images and their offset among the wallet's outputs + */ + virtual std::shared_ptr export_key_images(bool all = false) const { + throw std::runtime_error("export_key_images() not supported"); + } + + /** + * Import signed key images and verify their spent status. + * + * @param key_images are key images to import and verify (requires hex and signature) + * @param offset - offset of the first key image among the wallet's outputs (default 0) + * @return results of the import + */ + virtual std::shared_ptr import_key_images(const std::vector>& key_images, uint64_t offset = 0) { + throw std::runtime_error("import_key_images() not supported"); + } + + /** + * Freeze an output. + * + * @param key_image key image of the output to freeze + */ + virtual void freeze_output(const std::string& key_image) { + throw std::runtime_error("freeze_output() not supported"); + } + + /** + * Thaw a frozen output. + * + * @param key_image key image of the output to thaw + */ + virtual void thaw_output(const std::string& key_image) { + throw std::runtime_error("thaw_output() not supported"); + } + + /** + * Check if an output is frozen. + * + * @param key_image key image of the output to check if frozen + * @return true if the output is frozen, false otherwise + */ + virtual bool is_output_frozen(const std::string& key_image) { + throw std::runtime_error("is_output_frozen() not supported"); + } + + /** + * Get the current default fee priority (unimportant, normal, elevated, etc). + * + * @return the current fee priority + */ + virtual monero_tx_priority get_default_fee_priority() const { + throw std::runtime_error("get_default_fee_priority() not supported"); + } + + /** + * Create a transaction to transfer funds from this wallet. + * + * @param config configures the transaction to create + * @return the created transaction (free memory using monero_utils::free) + */ + virtual std::shared_ptr create_tx(const monero_tx_config& config) { + if (config.m_can_split != boost::none && config.m_can_split.get()) throw std::runtime_error("Cannot split transactions with create_tx(); use create_txs() instead"); + monero_tx_config config_copy = monero_tx_config(config); + config_copy.m_can_split = false; + return create_txs(config_copy)[0]; + } + + /** + * Create one or more transactions to transfer funds from this wallet. + * + * @param config configures the transactions to create + * @return the created transactions (free memory using monero_utils::free) + */ + virtual std::vector> create_txs(const monero_tx_config& config) { + throw std::runtime_error("create_txs() not supported"); + } + + /** + * Sweep unlocked funds according to the given config. + * + * @param config is the sweep configuration + * @return the created transactions (free memory using monero_utils::free) + */ + virtual std::vector> sweep_unlocked(const monero_tx_config& config) { + throw std::runtime_error("sweep_unlocked() not supported"); + } + + /** + * Sweep an output with a given key image. + * + * @param config configures the sweep transaction + * @return the created transaction (free memory using monero_utils::free) + */ + virtual std::shared_ptr sweep_output(const monero_tx_config& config) { + throw std::runtime_error("sweep_output() not supported"); + } + + /** + * Sweep all unmixable dust outputs back to the wallet to make them easier to spend and mix. + * + * @param relay specifies if the resulting transaction should be relayed (default false) + * @return the created transactions (free memory using monero_utils::free) + */ + virtual std::vector> sweep_dust(bool relay = false) { + throw std::runtime_error("sweep_dust() not supported"); + } + + /** + * Relay a transaction previously created without relaying. + * + * @param tx_metadata is transaction metadata previously created without relaying + * @return std::string is the hash of the relayed tx + */ + virtual std::string relay_tx(const std::string& tx_metadata) { + std::vector tx_metadatas; + tx_metadatas.push_back(tx_metadata); + return relay_txs(tx_metadatas)[0]; + } + + /** + * Relay a previously created transaction. + * + * @param tx is the transaction to relay + * @return the hash of the relayed tx + */ + virtual std::string relay_tx(const monero_tx_wallet& tx) { + return relay_tx(tx.m_metadata.get()); + } + + /** + * Relay previously created transactions. + * + * @param txs are the transactions to relay + * @return the hashes of the relayed txs + */ + virtual std::vector relay_txs(const std::vector>& txs) { + std::vector tx_hexes; + for (const std::shared_ptr& tx : txs) tx_hexes.push_back(tx->m_metadata.get()); + return relay_txs(tx_hexes); + } + + /** + * Relay transactions previously created without relaying. + * + * @param tx_metadatas are transaction metadata previously created without relaying + * @return the hashes of the relayed txs + */ + virtual std::vector relay_txs(const std::vector& tx_metadatas) { + throw std::runtime_error("relay_txs() not supported"); + } + + /** + * Describes a tx set containing unsigned or multisig tx hex to a new tx set containing structured transactions. + * + * @param tx_set is a tx set containing unsigned or multisig tx hex + * @return the tx set containing structured transactions + */ + virtual monero_tx_set describe_tx_set(const monero_tx_set& tx_set) { + throw std::runtime_error("describe_tx_set() not supported"); + } + + /** + * Sign unsigned transactions from a view-only wallet. + * + * @param unsigned_tx_hex is unsigned transaction hex from when the transactions were created + * @return the signed transaction set + */ + virtual monero_tx_set sign_txs(const std::string& unsigned_tx_hex) { + throw std::runtime_error("sign_txs() not supported"); + } + + /** + * Submit signed transactions from a view-only wallet. + * + * @param signed_tx_hex is signed transaction hex from sign_txs() + * @return the resulting transaction hashes + */ + virtual std::vector submit_txs(const std::string& signed_tx_hex) { + throw std::runtime_error("submit_txs() not supported"); + } + + /** + * Sign a message. + * + * @param msg - the message to sign + * @param signature_type - sign with spend key or spend key + * @param account_idx - the account index of the message signature (default 0) + * @param subaddress_idx - the subaddress index of the message signature (default 0) + * @return the message signature + */ + virtual std::string sign_message(const std::string& msg, monero_message_signature_type signature_type, uint32_t account_idx = 0, uint32_t subaddress_idx = 0) const { + throw std::runtime_error("sign_message() not supported"); + } + + /** + * Verify a message signature. + * + * @param msg - the signed message + * @param address - signing address + * @param signature - signature + * @return the message signature result + */ + virtual monero_message_signature_result verify_message(const std::string& msg, const std::string& address, const std::string& signature) const { + throw std::runtime_error("verify_message() not supported"); + } + + /** + * Get a transaction's secret key from its hash. + * + * @param tx_hash is the transaction's hash + * @return is the transaction's secret key + */ + virtual std::string get_tx_key(const std::string& tx_hash) const { + throw std::runtime_error("get_tx_key() not supported"); + } + + /** + * Check a transaction in the blockchain with its secret key. + * + * @param tx_hash specifies the transaction to check + * @param tx_key is the transaction's secret key + * @param address is the destination public address of the transaction + * @return the result of the check + */ + virtual std::shared_ptr check_tx_key(const std::string& tx_hash, const std::string& tx_key, const std::string& address) const { + throw std::runtime_error("check_tx_key() not supported"); + } + + /** + * Get a transaction signature to prove it. + * + * @param tx_hash specifies the transaction to prove + * @param address is the destination public address of the transaction + * @param message is a message to include with the signature to further authenticate the proof (optional) + * @return the transaction signature + */ + virtual std::string get_tx_proof(const std::string& tx_hash, const std::string& address, const std::string& message) const { + throw std::runtime_error("get_tx_proof() not supported"); + } + + /** + * Prove a transaction by checking its signature. + * + * @param tx_hash specifies the transaction to prove + * @param address is the destination public address of the transaction + * @param message is a message included with the signature to further authenticate the proof (optional) + * @param signature is the transaction signature to confirm + * @return the result of the check + */ + virtual std::shared_ptr check_tx_proof(const std::string& tx_hash, const std::string& address, const std::string& message, const std::string& signature) const { + throw std::runtime_error("check_tx_proof() not supported"); + } + + /** + * Generate a signature to prove a spend. Unlike proving a transaction, it does not require the destination public address. + * + * @param tx_hash specifies the transaction to prove + * @param message is a message to include with the signature to further authenticate the proof (optional) + * @return the transaction signature + */ + virtual std::string get_spend_proof(const std::string& tx_hash, const std::string& message) const { + throw std::runtime_error("get_spend_proof() not supported"); + } + + /** + * Prove a spend using a signature. Unlike proving a transaction, it does not require the destination public address. + * + * @param tx_hash specifies the transaction to prove + * @param message is a message included with the signature to further authenticate the proof (optional) + * @param signature is the transaction signature to confirm + * @return true if the signature is good, false otherwise + */ + virtual bool check_spend_proof(const std::string& tx_hash, const std::string& message, const std::string& signature) const { + throw std::runtime_error("check_spend_proof() not supported"); + } + + /** + * Generate a signature to prove the entire balance of the wallet. + * + * @param message is a message included with the signature to further authenticate the proof (optional) + * @return the reserve proof signature + */ + virtual std::string get_reserve_proof_wallet(const std::string& message) const { + throw std::runtime_error("get_reserve_proof_wallet() not supported"); + } + + /** + * Generate a signature to prove an available amount in an account. + * + * @param account_idx specifies the account to prove ownership of the amount + * @param amount is the minimum amount to prove as available in the account + * @param message is a message to include with the signature to further authenticate the proof (optional) + * @return the reserve proof signature + */ + virtual std::string get_reserve_proof_account(uint32_t account_idx, uint64_t amount, const std::string& message) const { + throw std::runtime_error("get_reserve_proof_account() not supported"); + } + + /** + * Proves a wallet has a disposable reserve using a signature. + * + * @param address is the public wallet address + * @param message is a message included with the signature to further authenticate the proof (optional) + * @param signature is the reserve proof signature to check + * @return the result of checking the signature proof + */ + virtual std::shared_ptr check_reserve_proof(const std::string& address, const std::string& message, const std::string& signature) const { + throw std::runtime_error("check_reserve_proof() not supported"); + } + + /** + * Get a transaction note. + * + * @param tx_hash specifies the transaction to get the note of + * @return the tx note + */ + virtual std::string get_tx_note(const std::string& tx_hash) const { + throw std::runtime_error("get_tx_note() not supported"); + } + + /** + * Get notes for multiple transactions. + * + * @param tx_hashes identify the transactions to get notes for + * @returns notes for the transactions + */ + virtual std::vector get_tx_notes(const std::vector& tx_hashes) const { + throw std::runtime_error("get_tx_notes() not supported"); + } + + /** + * Set a note for a specific transaction. + * + * @param tx_hash specifies the transaction + * @param note specifies the note + */ + virtual void set_tx_note(const std::string& tx_hash, const std::string& note) { + throw std::runtime_error("set_tx_note() not supported"); + } + + /** + * Set notes for multiple transactions. + * + * @param tx_hashes specify the transactions to set notes for + * @param notes are the notes to set for the transactions + */ + virtual void set_tx_notes(const std::vector& tx_hashes, const std::vector& notes) { + throw std::runtime_error("set_tx_notes() not supported"); + } + + /** + * Get all address book entries. + * + * @param indices are indices of the entries to get + * @return the address book entries + */ + virtual std::vector get_address_book_entries(const std::vector& indices) const { + throw std::runtime_error("get_address_book_entries() not supported"); + } + + /** + * Add an address book entry. + * + * @param address is the entry address + * @param description is the entry description (optional) + * @return the index of the added entry + */ + virtual uint64_t add_address_book_entry(const std::string& address, const std::string& description) { + throw std::runtime_error("add_address_book_entry() not supported"); + } + + /** + * Edit an address book entry. + * + * @param index is the index of the address book entry to edit + * @param set_address specifies if the address should be updated + * @param address is the updated address + * @param set_description specifies if the description should be updated + * @param description is the updated description + */ + virtual void edit_address_book_entry(uint64_t index, bool set_address, const std::string& address, bool set_description, const std::string& description) { + throw std::runtime_error("edit_address_book_entry() not supported"); + } + + /** + * Delete an address book entry. + * + * @param index is the index of the entry to delete + */ + virtual void delete_address_book_entry(uint64_t index) { + throw std::runtime_error("delete_address_book_entry() not supported"); + } + + /** + * Creates a payment URI from a tx configuration. + * + * @param config specifies configuration for a potential tx + * @return is the payment uri + */ + virtual std::string get_payment_uri(const monero_tx_config& config) const { + throw std::runtime_error("get_payment_uri() not supported"); + } + + /** + * Parses a payment URI to a tx configuration. + * + * @param uri is the payment uri to parse + * @return the tx configuration parsed from the uri + */ + virtual std::shared_ptr parse_payment_uri(const std::string& uri) const { + throw std::runtime_error("parse_payment_uri() not supported"); + } + + /** + * Get an attribute. + * + * @param key is the attribute to get the value of + * @param value is set to the key's value if set + * @return true if the key's value has been set, false otherwise + */ + virtual bool get_attribute(const std::string& key, std::string& value) const { + throw std::runtime_error("get_attribute() not supported"); + } + + /** + * Set an arbitrary attribute. + * + * @param key is the attribute key + * @param val is the attribute value + */ + virtual void set_attribute(const std::string& key, const std::string& val) { + throw std::runtime_error("set_attribute() not supported"); + } + + /** + * Start mining. + * + * @param num_threads is the number of threads created for mining (optional) + * @param background_mining specifies if mining should occur in the background (optional) + * @param ignore_battery specifies if the battery should be ignored for mining (optional) + */ + virtual void start_mining(boost::optional num_threads, boost::optional background_mining, boost::optional ignore_battery) { + throw std::runtime_error("start_mining() not supported"); + } + + /** + * Stop mining. + */ + virtual void stop_mining() { + throw std::runtime_error("stop_mining() not supported"); + } + + /** + * Wait for the next block to be added to the chain. + * + * @return the height of the next block when it is added to the chain + */ + virtual uint64_t wait_for_next_block() { + throw std::runtime_error("wait_for_next_block() not supported"); + } + + /** + * Indicates if importing multisig data is needed for returning a correct balance. + * + * @return true if importing multisig data is needed for returning a correct balance, false otherwise + */ + virtual bool is_multisig_import_needed() const { + throw std::runtime_error("is_multisig_import_needed() not supported"); + } + + /** + * Indicates if this wallet is a multisig wallet. + * + * @return true if this is a multisig wallet, false otherwise + */ + virtual bool is_multisig() const { + return get_multisig_info().m_is_multisig; + } + + /** + * Get multisig info about this wallet. + * + * @return multisig info about this wallet + */ + virtual monero_multisig_info get_multisig_info() const { + throw std::runtime_error("get_multisig_info() not supported"); + } + + /** + * Get multisig info as hex to share with participants to begin creating a + * multisig wallet. + * + * @return this wallet's multisig hex to share with participants + */ + virtual std::string prepare_multisig() { + throw std::runtime_error("prepare_multisig() not supported"); + } + + /** + * Make this wallet multisig by importing multisig hex from participants. + * + * @param multisig_hexes are multisig hex from each participant + * @param threshold is the number of signatures needed to sign transfers + * @param password is the wallet password + * @return this wallet's multisig hex to share with participants + */ + virtual std::string make_multisig(const std::vector& multisig_hexes, int threshold, const std::string& password) { + throw std::runtime_error("make_multisig() not supported"); + } + + /** + * Exchange multisig hex with participants in a M/N multisig wallet. + * + * This process must be repeated with participants exactly N-M times. + * + * @param multisig_hexes are multisig hex from each participant + * @param password is the wallet's password // TODO monero-project: redundant? wallet is created with password + * @return the result which has the multisig's address xor this wallet's multisig hex to share with participants iff not done + */ + virtual monero_multisig_init_result exchange_multisig_keys(const std::vector& multisig_hexes, const std::string& password) { + throw std::runtime_error("exchange_multisig_keys() not supported"); + } + + /** + * Export this wallet's multisig info as hex for other participants. + * + * @return this wallet's multisig info as hex for other participants + */ + virtual std::string export_multisig_hex() { + throw std::runtime_error("export_multisig_hex() not supported"); + } + + /** + * Import multisig info as hex from other participants. + * + * Note: If the daemon is not trusted, this method will not automatically + * update the spent status after importing peer multisig hex. + * + * @param multisig_hexes are multisig hex from each participant + * @param refresh_after_import specifies if the wallet should be refreshed after importing (default true) + * @return the number of outputs signed with the given multisig hex + */ + virtual int import_multisig_hex(const std::vector& multisig_hexes, bool refresh_after_import = true) { + throw std::runtime_error("import_multisig_hex() not supported"); + } + + /** + * Sign previously created multisig transactions as represented by hex. + * + * @param multisig_tx_hex is the hex shared among the multisig transactions when they were created + * @return the result of signing the multisig transactions + */ + virtual monero_multisig_sign_result sign_multisig_tx_hex(const std::string& multisig_tx_hex) { + throw std::runtime_error("sign_multisig_tx_hex() not supported"); + } + + /** + * Submit signed multisig transactions as represented by a hex std::string. + * + * @param signed_multisig_tx_hex is the signed multisig hex returned from signMultisigTxs() + * @return the resulting transaction hashes + */ + virtual std::vector submit_multisig_tx_hex(const std::string& signed_multisig_tx_hex) { + throw std::runtime_error("submit_multisig_tx_hex() not supported"); + } + + /** + * Change the wallet password. + * + * @param old_password is the wallet's old password + * @param new_password is the wallet's new password + */ + virtual void change_password(const std::string& old_password, const std::string& new_password) { + throw std::runtime_error("change_password() not supported"); + } + + /** + * Move the wallet from its current path to the given path. + * + * @param path is the new wallet's path + * @param password is the new wallet's password + */ + virtual void move_to(const std::string& path, const std::string& password) { + throw std::runtime_error("move_to() not supported"); + } + + /** + * Save the wallet at its current path. + */ + virtual void save() { + throw std::runtime_error("save() not supported"); + } + + /** + * Optionally save then close the wallet. + * + * @param save specifies if the wallet should be saved before being closed (default false) + */ + virtual void close(bool save = false) { + throw std::runtime_error("close() not supported"); + } + + /** + * Check if the wallet is closed. + */ + virtual bool is_closed() const { + throw std::runtime_error("is_closed() not supported"); + } + }; +} diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_full.cpp b/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_full.cpp new file mode 100644 index 0000000..d56c1b6 --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_full.cpp @@ -0,0 +1,3979 @@ +/** + * Copyright (c) woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this std::list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this std::list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ + +#include "monero_wallet_full.h" + +#ifdef WIN32_LEAN_AND_MEAN +#include +#include +#include +#endif + +#include "utils/gen_utils.h" +#include "utils/monero_utils.h" +#include +#include +#include "mnemonics/electrum-words.h" +#include "mnemonics/english.h" +#include "wallet/wallet_rpc_server_commands_defs.h" +#include "serialization/binary_utils.h" +#include "serialization/string.h" +#include "common/threadpool.h" + +using namespace tools; + +/** + * Implements a monero_wallet.h by wrapping wallet2.h. + */ +namespace monero { + + // ------------------------- INITIALIZE CONSTANTS --------------------------- + + static const int DEFAULT_CONNECTION_TIMEOUT_MILLIS = 1000 * 30; // default connection timeout 30 sec + static const bool STRICT_ = false; // relies exclusively on blockchain data if true, includes local wallet data if false TODO: good use case to expose externally? (note: cannot use `STRICT` due to namespace collision on Windows) + + // ----------------------- INTERNAL PRIVATE HELPERS ----------------------- + + struct key_image_list + { + std::list key_images; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(key_images) + END_KV_SERIALIZE_MAP() + }; + + // compute m_num_confirmations TODO monero-project: this logic is based on wallet_rpc_server.cpp `set_confirmations` but it should be encapsulated in wallet2 + void set_num_confirmations(std::shared_ptr& tx, uint64_t blockchain_height) { + std::shared_ptr& block = tx->m_block; + if (block->m_height.get() >= blockchain_height || (block->m_height.get() == 0 && !tx->m_in_tx_pool.get())) tx->m_num_confirmations = 0; + else tx->m_num_confirmations = blockchain_height - block->m_height.get(); + } + + // compute m_num_suggested_confirmations TODO monero-project: this logic is based on wallet_rpc_server.cpp `set_confirmations` but it should be encapsulated in wallet2 + void set_num_suggested_confirmations(std::shared_ptr& incoming_transfer, uint64_t blockchain_height, uint64_t block_reward, uint64_t unlock_time) { + if (block_reward == 0) incoming_transfer->m_num_suggested_confirmations = 0; + else incoming_transfer->m_num_suggested_confirmations = (incoming_transfer->m_amount.get() + block_reward - 1) / block_reward; + if (unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER) { + if (unlock_time > blockchain_height) incoming_transfer->m_num_suggested_confirmations = std::max(incoming_transfer->m_num_suggested_confirmations.get(), unlock_time - blockchain_height); + } else { + const uint64_t now = time(NULL); + if (unlock_time > now) incoming_transfer->m_num_suggested_confirmations = std::max(incoming_transfer->m_num_suggested_confirmations.get(), (unlock_time - now + DIFFICULTY_TARGET_V2 - 1) / DIFFICULTY_TARGET_V2); + } + } + + std::shared_ptr build_tx_with_incoming_transfer(tools::wallet2& m_w2, uint64_t height, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd) { + + // construct block + std::shared_ptr block = std::make_shared(); + block->m_height = pd.m_block_height; + block->m_timestamp = pd.m_timestamp; + + // construct tx + std::shared_ptr tx = std::make_shared(); + tx->m_block = block; + block->m_txs.push_back(tx); + tx->m_hash = epee::string_tools::pod_to_hex(pd.m_tx_hash); + tx->m_is_incoming = true; + tx->m_payment_id = epee::string_tools::pod_to_hex(payment_id); + if (tx->m_payment_id->substr(16).find_first_not_of('0') == std::string::npos) tx->m_payment_id = tx->m_payment_id->substr(0, 16); // TODO monero-project: this should be part of core wallet + if (tx->m_payment_id == monero_tx::DEFAULT_PAYMENT_ID) tx->m_payment_id = boost::none; // clear default payment id + tx->m_unlock_time = pd.m_unlock_time; + tx->m_is_locked = !m_w2.is_transfer_unlocked(pd.m_unlock_time, pd.m_block_height); + tx->m_fee = pd.m_fee; + tx->m_note = m_w2.get_tx_note(pd.m_tx_hash); + if (tx->m_note->empty()) tx->m_note = boost::none; // clear empty note + tx->m_is_miner_tx = pd.m_coinbase ? true : false; + tx->m_is_confirmed = true; + tx->m_is_failed = false; + tx->m_is_relayed = true; + tx->m_in_tx_pool = false; + tx->m_relay = true; + tx->m_is_double_spend_seen = false; + set_num_confirmations(tx, height); + + // construct transfer + std::shared_ptr incoming_transfer = std::make_shared(); + incoming_transfer->m_tx = tx; + tx->m_incoming_transfers.push_back(incoming_transfer); + incoming_transfer->m_amount = pd.m_amount; + incoming_transfer->m_account_index = pd.m_subaddr_index.major; + incoming_transfer->m_subaddress_index = pd.m_subaddr_index.minor; + incoming_transfer->m_address = m_w2.get_subaddress_as_str(pd.m_subaddr_index); + set_num_suggested_confirmations(incoming_transfer, height, m_w2.get_last_block_reward(), pd.m_unlock_time); + + // return pointer to new tx + return tx; + } + + std::shared_ptr build_tx_with_outgoing_transfer(tools::wallet2& m_w2, uint64_t height, const crypto::hash &txid, const tools::wallet2::confirmed_transfer_details &pd) { + + // construct block + std::shared_ptr block = std::make_shared(); + block->m_height = pd.m_block_height; + block->m_timestamp = pd.m_timestamp; + + // construct tx + std::shared_ptr tx = std::make_shared(); + tx->m_block = block; + block->m_txs.push_back(tx); + tx->m_hash = epee::string_tools::pod_to_hex(txid); + tx->m_is_outgoing = true; + tx->m_payment_id = epee::string_tools::pod_to_hex(pd.m_payment_id); + if (tx->m_payment_id->substr(16).find_first_not_of('0') == std::string::npos) tx->m_payment_id = tx->m_payment_id->substr(0, 16); // TODO monero-project: this should be part of core wallet + if (tx->m_payment_id == monero_tx::DEFAULT_PAYMENT_ID) tx->m_payment_id = boost::none; // clear default payment id + tx->m_unlock_time = pd.m_unlock_time; + tx->m_is_locked = !m_w2.is_transfer_unlocked(pd.m_unlock_time, pd.m_block_height); + tx->m_fee = pd.m_amount_in - pd.m_amount_out; + tx->m_note = m_w2.get_tx_note(txid); + if (tx->m_note->empty()) tx->m_note = boost::none; // clear empty note + tx->m_is_miner_tx = false; + tx->m_is_confirmed = true; + tx->m_is_failed = false; + tx->m_is_relayed = true; + tx->m_in_tx_pool = false; + tx->m_relay = true; + tx->m_is_double_spend_seen = false; + set_num_confirmations(tx, height); + + // construct transfer + std::shared_ptr outgoing_transfer = std::make_shared(); + outgoing_transfer->m_tx = tx; + tx->m_outgoing_transfer = outgoing_transfer; + uint64_t change = pd.m_change == (uint64_t)-1 ? 0 : pd.m_change; // change may not be known + outgoing_transfer->m_amount = pd.m_amount_in - change - *tx->m_fee; + outgoing_transfer->m_account_index = pd.m_subaddr_account; + std::vector subaddress_indices; + std::vector addresses; + for (uint32_t i: pd.m_subaddr_indices) { + subaddress_indices.push_back(i); + addresses.push_back(m_w2.get_subaddress_as_str({pd.m_subaddr_account, i})); + } + outgoing_transfer->m_subaddress_indices = subaddress_indices; + outgoing_transfer->m_addresses = addresses; + + // initialize destinations + for (const auto &d: pd.m_dests) { + std::shared_ptr destination = std::make_shared(); + destination->m_amount = d.amount; + destination->m_address = d.address(m_w2.nettype(), pd.m_payment_id); + outgoing_transfer->m_destinations.push_back(destination); + } + + // replace transfer amount with destination sum + // TODO monero-project: confirmed tx from/to same account has amount 0 but cached transfer destinations + if (*outgoing_transfer->m_amount == 0 && !outgoing_transfer->m_destinations.empty()) { + uint64_t amount = 0; + for (const std::shared_ptr& destination : outgoing_transfer->m_destinations) amount += *destination->m_amount; + outgoing_transfer->m_amount = amount; + } + + // return pointer to new tx + return tx; + } + + std::shared_ptr build_tx_with_incoming_transfer_unconfirmed(const tools::wallet2& m_w2, uint64_t height, const crypto::hash &payment_id, const tools::wallet2::pool_payment_details &ppd) { + + // construct tx + const tools::wallet2::payment_details &pd = ppd.m_pd; + std::shared_ptr tx = std::make_shared(); + tx->m_hash = epee::string_tools::pod_to_hex(pd.m_tx_hash); + tx->m_is_incoming = true; + tx->m_payment_id = epee::string_tools::pod_to_hex(payment_id); + if (tx->m_payment_id->substr(16).find_first_not_of('0') == std::string::npos) tx->m_payment_id = tx->m_payment_id->substr(0, 16); // TODO monero-project: this should be part of core wallet + if (tx->m_payment_id == monero_tx::DEFAULT_PAYMENT_ID) tx->m_payment_id = boost::none; // clear default payment id + tx->m_unlock_time = pd.m_unlock_time; + tx->m_is_locked = true; + tx->m_fee = pd.m_fee; + tx->m_note = m_w2.get_tx_note(pd.m_tx_hash); + if (tx->m_note->empty()) tx->m_note = boost::none; // clear empty note + tx->m_is_miner_tx = false; + tx->m_is_confirmed = false; + tx->m_is_failed = false; + tx->m_is_relayed = true; + tx->m_in_tx_pool = true; + tx->m_relay = true; + tx->m_is_double_spend_seen = ppd.m_double_spend_seen; + tx->m_num_confirmations = 0; + + // construct transfer + std::shared_ptr incoming_transfer = std::make_shared(); + incoming_transfer->m_tx = tx; + tx->m_incoming_transfers.push_back(incoming_transfer); + incoming_transfer->m_amount = pd.m_amount; + incoming_transfer->m_account_index = pd.m_subaddr_index.major; + incoming_transfer->m_subaddress_index = pd.m_subaddr_index.minor; + incoming_transfer->m_address = m_w2.get_subaddress_as_str(pd.m_subaddr_index); + set_num_suggested_confirmations(incoming_transfer, height, m_w2.get_last_block_reward(), pd.m_unlock_time); + + // return pointer to new tx + return tx; + } + + std::shared_ptr build_tx_with_outgoing_transfer_unconfirmed(const tools::wallet2& m_w2, const crypto::hash &txid, const tools::wallet2::unconfirmed_transfer_details &pd) { + + // construct tx + std::shared_ptr tx = std::make_shared(); + tx->m_is_failed = pd.m_state == tools::wallet2::unconfirmed_transfer_details::failed; + tx->m_hash = epee::string_tools::pod_to_hex(txid); + tx->m_is_outgoing = true; + tx->m_payment_id = epee::string_tools::pod_to_hex(pd.m_payment_id); + if (tx->m_payment_id->substr(16).find_first_not_of('0') == std::string::npos) tx->m_payment_id = tx->m_payment_id->substr(0, 16); // TODO monero-project: this should be part of core wallet + if (tx->m_payment_id == monero_tx::DEFAULT_PAYMENT_ID) tx->m_payment_id = boost::none; // clear default payment id + tx->m_unlock_time = pd.m_tx.unlock_time; + tx->m_is_locked = true; + tx->m_fee = pd.m_amount_in - pd.m_amount_out; + tx->m_note = m_w2.get_tx_note(txid); + if (tx->m_note->empty()) tx->m_note = boost::none; // clear empty note + tx->m_is_miner_tx = false; + tx->m_is_confirmed = false; + tx->m_is_relayed = !tx->m_is_failed.get(); + tx->m_in_tx_pool = !tx->m_is_failed.get(); + tx->m_relay = true; + if (!tx->m_is_failed.get() && tx->m_is_relayed.get()) tx->m_is_double_spend_seen = false; // TODO: test and handle if true + tx->m_num_confirmations = 0; + + // construct transfer + std::shared_ptr outgoing_transfer = std::make_shared(); + outgoing_transfer->m_tx = tx; + tx->m_outgoing_transfer = outgoing_transfer; + outgoing_transfer->m_amount = pd.m_amount_in - pd.m_change - tx->m_fee.get(); + outgoing_transfer->m_account_index = pd.m_subaddr_account; + std::vector subaddress_indices; + std::vector addresses; + for (uint32_t i: pd.m_subaddr_indices) { + subaddress_indices.push_back(i); + addresses.push_back(m_w2.get_subaddress_as_str({pd.m_subaddr_account, i})); + } + outgoing_transfer->m_subaddress_indices = subaddress_indices; + outgoing_transfer->m_addresses = addresses; + + // initialize destinations + for (const auto &d: pd.m_dests) { + std::shared_ptr destination = std::make_shared(); + destination->m_amount = d.amount; + destination->m_address = d.address(m_w2.nettype(), pd.m_payment_id); + outgoing_transfer->m_destinations.push_back(destination); + } + + // replace transfer amount with destination sum + // TODO monero-project: confirmed tx from/to same account has amount 0 but cached transfer destinations + if (*outgoing_transfer->m_amount == 0 && !outgoing_transfer->m_destinations.empty()) { + uint64_t amount = 0; + for (const std::shared_ptr& destination : outgoing_transfer->m_destinations) amount += *destination->m_amount; + outgoing_transfer->m_amount = amount; + } + + // return pointer to new tx + return tx; + } + + std::shared_ptr build_tx_with_vout(tools::wallet2& m_w2, const tools::wallet2::transfer_details& td) { + + // construct block + std::shared_ptr block = std::make_shared(); + block->m_height = td.m_block_height; + + // construct tx + std::shared_ptr tx = std::make_shared(); + tx->m_block = block; + block->m_txs.push_back(tx); + tx->m_hash = epee::string_tools::pod_to_hex(td.m_txid); + tx->m_is_confirmed = true; + tx->m_is_failed = false; + tx->m_is_relayed = true; + tx->m_in_tx_pool = false; + tx->m_relay = true; + tx->m_is_double_spend_seen = false; + tx->m_is_locked = !m_w2.is_transfer_unlocked(td); + + // construct output + std::shared_ptr output = std::make_shared(); + output->m_tx = tx; + tx->m_outputs.push_back(output); + output->m_amount = td.amount(); + output->m_index = td.m_global_output_index; + output->m_account_index = td.m_subaddr_index.major; + output->m_subaddress_index = td.m_subaddr_index.minor; + output->m_is_spent = td.m_spent; + output->m_is_frozen = td.m_frozen; + output->m_stealth_public_key = epee::string_tools::pod_to_hex(td.get_public_key()); + if (td.m_key_image_known) { + output->m_key_image = std::make_shared(); + output->m_key_image->m_hex = epee::string_tools::pod_to_hex(td.m_key_image); + } + + // return pointer to new tx + return tx; + } + + std::string get_default_ringdb_path(cryptonote::network_type nettype) + { + boost::filesystem::path dir = tools::get_default_data_dir(); + // remove .bitmonero, replace with .shared-ringdb + dir = dir.remove_filename(); + dir /= ".shared-ringdb"; + if (nettype == cryptonote::TESTNET) + dir /= "testnet"; + else if (nettype == cryptonote::STAGENET) + dir /= "stagenet"; + return dir.string(); + } + + /** + * ---------------- DUPLICATED WALLET RPC TRANSFER CODE --------------------- + * + * These functions are duplicated from private functions in wallet rpc + * on_transfer/on_transfer_split, with minor modifications to not be class members. + * + * This code is used to generate and send transactions with equivalent functionality as + * wallet rpc. + * + * Duplicated code is not ideal. Solutions considered: + * + * (1) Duplicate wallet rpc code as done here. + * (2) Modify monero-wallet-rpc on_transfer() / on_transfer_split() to be public. + * (3) Modify monero-wallet-rpc to make this class a friend. + * (4) Move all logic in monero-wallet-rpc to wallet2 so all users can access. + * + * Options 2-4 require modification of monero-project C++. Of those, (4) is probably ideal. + * TODO: open patch on monero-project which moves common wallet rpc logic (e.g. on_transfer, on_transfer_split) to m_w2. + * + * Until then, option (1) is used because it allows monero-project binaries to be used without modification, it's easy, and + * anything other than (4) is temporary. + */ + //------------------------------------------------------------------------------------------------------------------------------ + bool validate_transfer(wallet2* m_w2, const std::list& destinations, const std::string& payment_id, std::vector& dsts, std::vector& extra, bool at_least_one_destination, epee::json_rpc::error& er) + { + crypto::hash8 integrated_payment_id = crypto::null_hash8; + std::string extra_nonce; + for (auto it = destinations.begin(); it != destinations.end(); it++) + { + cryptonote::address_parse_info info; + cryptonote::tx_destination_entry de; + er.message = ""; + if(!get_account_address_from_str_or_url(info, m_w2->nettype(), it->address, + [&er](const std::string &url, const std::vector &addresses, bool dnssec_valid)->std::string { + if (!dnssec_valid) + { + er.message = std::string("Invalid DNSSEC for ") + url; + return {}; + } + if (addresses.empty()) + { + er.message = std::string("No Monero address found at ") + url; + return {}; + } + return addresses[0]; + })) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; + if (er.message.empty()) + er.message = std::string("Invalid destination address"); + return false; + } + + de.original = it->address; + de.addr = info.address; + de.is_subaddress = info.is_subaddress; + de.amount = it->amount; + de.is_integrated = info.has_payment_id; + dsts.push_back(de); + + if (info.has_payment_id) + { + if (!payment_id.empty() || integrated_payment_id != crypto::null_hash8) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; + er.message = "A single payment id is allowed per transaction"; + return false; + } + integrated_payment_id = info.payment_id; + cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, integrated_payment_id); + + /* Append Payment ID data into extra */ + if (!cryptonote::add_extra_nonce_to_tx_extra(extra, extra_nonce)) { + er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; + er.message = "Something went wrong with integrated payment_id."; + return false; + } + } + } + + if (at_least_one_destination && dsts.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_ZERO_DESTINATION; + er.message = "No destinations for this transfer"; + return false; + } + + if (!payment_id.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; + er.message = "Standalone payment IDs are obsolete. Use subaddresses or integrated addresses instead"; + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + static std::string ptx_to_string(const tools::wallet2::pending_tx &ptx) + { + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << ptx; + } + catch (...) + { + return ""; + } + return epee::string_tools::buff_to_hex_nodelimer(oss.str()); + } + //------------------------------------------------------------------------------------------------------------------------------ + template static bool is_error_value(const T &val) { return false; } + static bool is_error_value(const std::string &s) { return s.empty(); } + //------------------------------------------------------------------------------------------------------------------------------ + template + static bool fill(T &where, V s) + { + if (is_error_value(s)) return false; + where = std::move(s); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + template + static bool fill(std::list &where, V s) + { + if (is_error_value(s)) return false; + where.emplace_back(std::move(s)); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + static uint64_t total_amount(const tools::wallet2::pending_tx &ptx) + { + uint64_t amount = 0; + for (const auto &dest: ptx.dests) amount += dest.amount; + return amount; + } + //------------------------------------------------------------------------------------------------------------------------------ + template + bool fill_response(wallet2* m_w2, std::vector &ptx_vector, + bool get_tx_key, Ts& tx_key, Tu &amount, Ta &amounts_by_dest, Tu &fee, Tu &weight, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay, + Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata, Tk &spent_key_images, epee::json_rpc::error &er) + { + for (const auto & ptx : ptx_vector) + { + if (get_tx_key) + { + epee::wipeable_string s = epee::to_hex::wipeable_string(ptx.tx_key); + for (const crypto::secret_key& additional_tx_key : ptx.additional_tx_keys) + s += epee::to_hex::wipeable_string(additional_tx_key); + fill(tx_key, std::string(s.data(), s.size())); + } + // Compute amount leaving wallet in tx. By convention dests does not include change outputs + fill(amount, total_amount(ptx)); + fill(fee, ptx.fee); + fill(weight, cryptonote::get_transaction_weight(ptx.tx)); + + // add amounts by destination + tools::wallet_rpc::amounts_list abd; + for (const auto& dst : ptx.dests) + abd.amounts.push_back(dst.amount); + fill(amounts_by_dest, abd); + + // add spent key images + key_image_list key_image_list; + bool all_are_txin_to_key = std::all_of(ptx.tx.vin.begin(), ptx.tx.vin.end(), [&](const cryptonote::txin_v& s_e) -> bool + { + CHECKED_GET_SPECIFIC_VARIANT(s_e, const cryptonote::txin_to_key, in, false); + key_image_list.key_images.push_back(epee::string_tools::pod_to_hex(in.k_image)); + return true; + }); + THROW_WALLET_EXCEPTION_IF(!all_are_txin_to_key, error::unexpected_txin_type, ptx.tx); + fill(spent_key_images, key_image_list); + } + + if (m_w2->multisig()) + { + multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_w2->save_multisig_tx(ptx_vector)); + if (multisig_txset.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; + } + } + else + { + if (m_w2->watch_only()){ + unsigned_txset = epee::string_tools::buff_to_hex_nodelimer(m_w2->dump_tx_to_str(ptx_vector)); + if (unsigned_txset.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save unsigned tx set after creation"; + return false; + } + } + else if (!do_not_relay) + m_w2->commit_tx(ptx_vector); + + // populate response with tx hashes + for (auto & ptx : ptx_vector) + { + bool r = fill(tx_hash, epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); + r = r && (!get_tx_hex || fill(tx_blob, epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx)))); + r = r && (!get_tx_metadata || fill(tx_metadata, ptx_to_string(ptx))); + if (!r) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save tx info"; + return false; + } + } + } + return true; + } + + // ----------------------------- WALLET LISTENER ---------------------------- + + /** + * Listens to wallet2 notifications in order to notify external wallet listeners. + */ + struct wallet2_listener : public tools::i_wallet2_callback { + + public: + + /** + * Constructs the listener. + * + * @param wallet provides context to notify external listeners + * @param wallet2 provides source notifications which this listener propagates to external listeners + */ + wallet2_listener(monero_wallet_full& wallet, tools::wallet2& wallet2) : m_wallet(wallet), m_w2(wallet2) { + this->m_sync_start_height = boost::none; + this->m_sync_end_height = boost::none; + m_prev_balance = wallet.get_balance(); + m_prev_unlocked_balance = wallet.get_unlocked_balance(); + m_notification_pool = std::unique_ptr(tools::threadpool::getNewForUnitTests(1)); // TODO (monero-project): utility can be for general use + } + + ~wallet2_listener() { + MTRACE("~wallet2_listener()"); + m_w2.callback(nullptr); + m_notification_pool->recycle(); + } + + void update_listening() { + boost::lock_guard guarg(m_listener_mutex); + + // update callback + m_w2.callback(m_wallet.get_listeners().empty() ? nullptr : this); + + // if starting to listen, cache locked txs for later comparison + if (!m_wallet.get_listeners().empty() && m_w2.callback() == nullptr) { + tools::threadpool::waiter waiter(*m_notification_pool); + m_notification_pool->submit(&waiter, [this]() { + check_for_changed_unlocked_txs(); + }); + waiter.wait(); + } + } + + void on_sync_start(uint64_t start_height) { + tools::threadpool::waiter waiter(*m_notification_pool); + m_notification_pool->submit(&waiter, [this, start_height]() { + if (m_sync_start_height != boost::none || m_sync_end_height != boost::none) throw std::runtime_error("Sync start or end height should not already be allocated, is previous sync in progress?"); + m_sync_start_height = start_height; + m_sync_end_height = m_wallet.get_daemon_height(); + }); + waiter.wait(); // TODO: this processes notification on thread, process off thread + } + + void on_sync_end() { + tools::threadpool::waiter waiter(*m_notification_pool); + m_notification_pool->submit(&waiter, [this]() { + check_for_changed_balances(); + if (m_prev_locked_tx_hashes.size() > 0) check_for_changed_unlocked_txs(); + m_sync_start_height = boost::none; + m_sync_end_height = boost::none; + }); + m_notification_pool->recycle(); + waiter.wait(); + } + + void on_new_block(uint64_t height, const cryptonote::block& cn_block) override { + if (m_wallet.get_listeners().empty()) return; + + // ignore notifications before sync start height, irrelevant to clients + if (m_sync_start_height == boost::none || height < *m_sync_start_height) return; + + // queue notification processing off main thread + tools::threadpool::waiter waiter(*m_notification_pool); + m_notification_pool->submit(&waiter, [this, height]() { + + // notify listeners of new block + for (monero_wallet_listener* listener : m_wallet.get_listeners()) { + listener->on_new_block(height); + } + + // notify listeners of sync progress + if (height >= *m_sync_end_height) m_sync_end_height = height + 1; // increase end height if necessary + double percent_done = (double) (height - *m_sync_start_height + 1) / (double) (*m_sync_end_height - *m_sync_start_height); + std::string message = std::string("Synchronizing"); + for (monero_wallet_listener* listener : m_wallet.get_listeners()) { + listener->on_sync_progress(height, *m_sync_start_height, *m_sync_end_height, percent_done, message); + } + + // notify if balances change + bool balances_changed = check_for_changed_balances(); + + // notify when txs unlock after wallet is synced + if (balances_changed && m_wallet.is_synced()) check_for_changed_unlocked_txs(); + }); + waiter.wait(); + } + + void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& cn_tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) override { + if (m_wallet.get_listeners().empty()) return; + + // queue notification processing off main thread + tools::threadpool::waiter waiter(*m_notification_pool); + m_notification_pool->submit(&waiter, [this, height, txid, cn_tx, amount, subaddr_index]() { + try { + + // create library tx + std::shared_ptr tx = std::static_pointer_cast(monero_utils::cn_tx_to_tx(cn_tx, true)); + tx->m_hash = epee::string_tools::pod_to_hex(txid); + tx->m_is_confirmed = false; + tx->m_is_locked = true; + std::shared_ptr output = std::make_shared(); + tx->m_outputs.push_back(output); + output->m_tx = tx; + output->m_amount = amount; + output->m_account_index = subaddr_index.major; + output->m_subaddress_index = subaddr_index.minor; + + // notify listeners of output + for (monero_wallet_listener* listener : m_wallet.get_listeners()) { + listener->on_output_received(*output); + } + + // notify if balances changed + check_for_changed_balances(); + + // watch for unlock + m_prev_locked_tx_hashes.insert(tx->m_hash.get()); + + // free memory + monero_utils::free(tx); + } catch (std::exception& e) { + std::cout << "Error processing unconfirmed output received: " << std::string(e.what()) << std::endl; + } + }); + waiter.wait(); + } + + void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& cn_tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) override { + if (m_wallet.get_listeners().empty()) return; + + // queue notification processing off main thread + tools::threadpool::waiter waiter(*m_notification_pool); + m_notification_pool->submit(&waiter, [this, height, txid, cn_tx, amount, burnt, subaddr_index, is_change, unlock_time]() { + try { + + // create native library tx + std::shared_ptr block = std::make_shared(); + block->m_height = height; + std::shared_ptr tx = std::static_pointer_cast(monero_utils::cn_tx_to_tx(cn_tx, true)); + block->m_txs.push_back(tx); + tx->m_block = block; + tx->m_hash = epee::string_tools::pod_to_hex(txid); + tx->m_is_confirmed = true; + tx->m_is_locked = true; + tx->m_unlock_time = unlock_time; + std::shared_ptr output = std::make_shared(); + tx->m_outputs.push_back(output); + output->m_tx = tx; + output->m_amount = amount - burnt; + output->m_account_index = subaddr_index.major; + output->m_subaddress_index = subaddr_index.minor; + + // notify listeners of output + for (monero_wallet_listener* listener : m_wallet.get_listeners()) { + listener->on_output_received(*output); + } + + // watch for unlock + m_prev_locked_tx_hashes.insert(tx->m_hash.get()); + + // free memory + monero_utils::free(block); + } catch (std::exception& e) { + std::cout << "Error processing confirmed output received: " << std::string(e.what()) << std::endl; + } + }); + waiter.wait(); + } + + void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& cn_tx_in, uint64_t amount, const cryptonote::transaction& cn_tx_out, const cryptonote::subaddress_index& subaddr_index) override { + if (m_wallet.get_listeners().empty()) return; + if (&cn_tx_in != &cn_tx_out) throw std::runtime_error("on_money_spent() in tx is different than out tx"); + + // queue notification processing off main thread + tools::threadpool::waiter waiter(*m_notification_pool); + m_notification_pool->submit(&waiter, [this, height, txid, cn_tx_in, amount, cn_tx_out, subaddr_index]() { + try { + + // create native library tx + std::shared_ptr block = std::make_shared(); + block->m_height = height; + std::shared_ptr tx = std::static_pointer_cast(monero_utils::cn_tx_to_tx(cn_tx_in, true)); + block->m_txs.push_back(tx); + tx->m_block = block; + tx->m_hash = epee::string_tools::pod_to_hex(txid); + tx->m_is_confirmed = true; + tx->m_is_locked = true; + std::shared_ptr output = std::make_shared(); + tx->m_inputs.push_back(output); + output->m_tx = tx; + output->m_amount = amount; + output->m_account_index = subaddr_index.major; + output->m_subaddress_index = subaddr_index.minor; + + // notify listeners of output + for (monero_wallet_listener* listener : m_wallet.get_listeners()) { + listener->on_output_spent(*output); + } + + // watch for unlock + m_prev_locked_tx_hashes.insert(tx->m_hash.get()); + + // free memory + monero_utils::free(block); + } catch (std::exception& e) { + std::cout << "Error processing confirmed output spent: " << std::string(e.what()) << std::endl; + } + }); + waiter.wait(); + } + + void on_spend_tx_hashes(const std::vector& tx_hashes) { + if (m_wallet.get_listeners().empty()) return; + monero_tx_query tx_query; + tx_query.m_hashes = tx_hashes; + tx_query.m_include_outputs = true; + tx_query.m_is_locked = true; + on_spend_txs(m_wallet.get_txs(tx_query)); + } + + void on_spend_txs(const std::vector>& txs) { + if (m_wallet.get_listeners().empty()) return; + tools::threadpool::waiter waiter(*m_notification_pool); + m_notification_pool->submit(&waiter, [this, txs]() { + check_for_changed_balances(); + for (const std::shared_ptr& tx : txs) notify_outputs(tx); + }); + waiter.wait(); + } + + private: + monero_wallet_full& m_wallet; // wallet to provide context for notifications + tools::wallet2& m_w2; // internal wallet implementation to listen to + boost::optional m_sync_start_height; + boost::optional m_sync_end_height; + boost::mutex m_listener_mutex; + uint64_t m_prev_balance; + uint64_t m_prev_unlocked_balance; + std::set m_prev_locked_tx_hashes; + std::unique_ptr m_notification_pool; // threadpool of size 1 to queue notifications for external announcement + + bool check_for_changed_balances() { + uint64_t balance = m_wallet.get_balance(); + uint64_t unlocked_balance = m_wallet.get_unlocked_balance(); + if (balance != m_prev_balance || unlocked_balance != m_prev_unlocked_balance) { + m_prev_balance = balance; + m_prev_unlocked_balance = unlocked_balance; + for (monero_wallet_listener* listener : m_wallet.get_listeners()) { + listener->on_balances_changed(balance, unlocked_balance); + } + return true; + } + return false; + } + + // TODO: this can probably be optimized using e.g. wallet2.get_num_rct_outputs() or wallet2.get_num_transfer_details(), or by retaining confirmed block height and only checking on or after unlock height, etc + void check_for_changed_unlocked_txs() { + + // get confirmed and locked txs + monero_tx_query query = monero_tx_query(); + query.m_is_locked = true; + query.m_is_confirmed = true; + query.m_min_height = m_wallet.get_height() - 70; // only monitor recent txs + std::vector> locked_txs = m_wallet.get_txs(query); + + // collect hashes of txs no longer locked + std::vector tx_hashes_no_longer_locked; + for (const std::string prev_locked_tx_hash : m_prev_locked_tx_hashes) { + bool found = false; + for (const std::shared_ptr& locked_tx : locked_txs) { + if (locked_tx->m_hash.get() == prev_locked_tx_hash) { + found = true; + break; + } + } + if (!found) tx_hashes_no_longer_locked.push_back(prev_locked_tx_hash); + } + + // fetch txs that are no longer locked + std::vector> txs_no_longer_locked; + if (!tx_hashes_no_longer_locked.empty()) { + query.m_hashes = tx_hashes_no_longer_locked; + query.m_is_locked = false; + query.m_include_outputs = true; + txs_no_longer_locked = m_wallet.get_txs(query); + } + + // notify listeners of newly unlocked inputs and outputs + for (const std::shared_ptr& unlocked_tx : txs_no_longer_locked) { + notify_outputs(unlocked_tx); + } + + // re-assign currently locked tx hashes // TODO: needs mutex for thread safety? + m_prev_locked_tx_hashes.clear(); + for (const std::shared_ptr& locked_tx : locked_txs) { + m_prev_locked_tx_hashes.insert(locked_tx->m_hash.get()); + } + + // free memory + monero_utils::free(locked_txs); + monero_utils::free(txs_no_longer_locked); + } + + void notify_outputs(const std::shared_ptr& tx) { + + // notify spent outputs + if (tx->m_outgoing_transfer != nullptr) { + + // build dummy input for notification // TODO: this provides one input with outgoing amount like monero-wallet-rpc client, use real inputs instead + std::shared_ptr input = std::make_shared(); + input->m_amount = tx->m_outgoing_transfer->m_amount.get() + tx->m_fee.get(); + input->m_account_index = tx->m_outgoing_transfer->m_account_index; + if (tx->m_outgoing_transfer->m_subaddress_indices.size() == 1) input->m_subaddress_index = tx->m_outgoing_transfer->m_subaddress_indices[0]; // initialize if transfer sourced from single subaddress + std::shared_ptr tx_notify = std::make_shared(); + input->m_tx = tx_notify; + tx_notify->m_inputs.push_back(input); + tx_notify->m_hash = tx->m_hash; + tx_notify->m_is_locked = tx->m_is_locked; + tx_notify->m_unlock_time = tx->m_unlock_time; + if (tx->m_block != nullptr) { + std::shared_ptr block_notify = std::make_shared(); + tx_notify->m_block = block_notify; + block_notify->m_height = tx->get_height(); + block_notify->m_txs.push_back(tx_notify); + } + + // notify listeners and free memory + for (monero_wallet_listener* listener : m_wallet.get_listeners()) listener->on_output_spent(*input); + monero_utils::free(tx_notify); + } + + // notify received outputs + if (!tx->m_incoming_transfers.empty()) { + for (const std::shared_ptr& output : tx->get_outputs_wallet()) { + for (monero_wallet_listener* listener : m_wallet.get_listeners()) listener->on_output_received(*output); + } + } + } + }; + + // --------------------------- STATIC WALLET UTILS -------------------------- + + bool monero_wallet_full::wallet_exists(const std::string& path) { + MTRACE("wallet_exists(" << path << ")"); + bool key_file_exists; + bool wallet_file_exists; + tools::wallet2::wallet_exists(path, key_file_exists, wallet_file_exists); + return key_file_exists; + } + + monero_wallet_full* monero_wallet_full::open_wallet(const std::string& path, const std::string& password, const monero_network_type network_type, bool regtest) { + MTRACE("open_wallet(" << path << ", ***, " << network_type << ")"); + monero_wallet_full* wallet = new monero_wallet_full(); + wallet->m_w2 = std::unique_ptr(new tools::wallet2(static_cast(network_type), 1, true)); + wallet->m_w2->load(path, password); + wallet->m_w2->init(""); + if (regtest) { + if (network_type != monero_network_type::MAINNET) throw std::runtime_error("Network type must be mainnet when using regtest option"); + wallet->m_w2->allow_mismatched_daemon_version(true); + wallet->m_w2->max_reorg_depth(100000); + } + if (network_type == monero_network_type::TESTNET) wallet->m_w2->allow_mismatched_daemon_version(true); // testnet may use custom hard fork heights + wallet->init_common(); + return wallet; + } + + monero_wallet_full* monero_wallet_full::open_wallet_data(const std::string& password, const monero_network_type network_type, const std::string& keys_data, const std::string& cache_data, const std::shared_ptr& daemon_connection, std::unique_ptr http_client_factory, bool regtest) { + MTRACE("open_wallet_data(...)"); + monero_wallet_full* wallet = new monero_wallet_full(); + if (http_client_factory == nullptr) wallet->m_w2 = std::unique_ptr(new tools::wallet2(static_cast(network_type), 1, true)); + else wallet->m_w2 = std::unique_ptr(new tools::wallet2(static_cast(network_type), 1, true, std::move(http_client_factory))); + wallet->m_w2->load("", password, keys_data, cache_data); + wallet->m_w2->init(""); + if (regtest) { + if (network_type != monero_network_type::MAINNET) throw std::runtime_error("Network type must be mainnet when using regtest option"); + wallet->m_w2->allow_mismatched_daemon_version(true); + wallet->m_w2->max_reorg_depth(100000); + } + if (network_type == monero_network_type::TESTNET) wallet->m_w2->allow_mismatched_daemon_version(true); // testnet may use custom hard fork heights + wallet->set_daemon_connection(daemon_connection); + wallet->init_common(); + return wallet; + } + + monero_wallet_full* monero_wallet_full::create_wallet(const monero_wallet_config& config, std::unique_ptr http_client_factory) { + MTRACE("create_wallet(config)"); + + // validate and normalize config + monero_wallet_config config_normalized = config.copy(); + if (config.m_path == boost::none) config_normalized.m_path = std::string(""); + if (config.m_password == boost::none) config_normalized.m_password = std::string(""); + if (config.m_language == boost::none) config_normalized.m_language = std::string(""); + if (config.m_seed == boost::none) config_normalized.m_seed = std::string(""); + if (config.m_primary_address == boost::none) config_normalized.m_primary_address = std::string(""); + if (config.m_private_spend_key == boost::none) config_normalized.m_private_spend_key = std::string(""); + if (config.m_private_view_key == boost::none) config_normalized.m_private_view_key = std::string(""); + if (config.m_seed_offset == boost::none) config_normalized.m_seed_offset = std::string(""); + if (config.m_is_multisig == boost::none) config_normalized.m_is_multisig = false; + if (config.m_account_lookahead != boost::none && config.m_subaddress_lookahead == boost::none) throw std::runtime_error("No subaddress lookahead provided with account lookahead"); + if (config.m_account_lookahead == boost::none && config.m_subaddress_lookahead != boost::none) throw std::runtime_error("No account lookahead provided with subaddress lookahead"); + if (config_normalized.m_language.get().empty()) config_normalized.m_language = std::string("English"); + if (!monero_utils::is_valid_language(config_normalized.m_language.get())) throw std::runtime_error("Unknown language: " + config_normalized.m_language.get()); + if (config.m_network_type == boost::none) throw std::runtime_error("Must provide wallet network type"); + if (config.m_regtest != boost::none && *config.m_regtest == true && *config.m_network_type != monero_network_type::MAINNET) throw std::runtime_error("Network type must be mainnet when using regtest option"); + + // create wallet + if (!config_normalized.m_seed.get().empty()) { + return create_wallet_from_seed(config_normalized, std::move(http_client_factory)); + } else if (!config_normalized.m_primary_address.get().empty() || !config_normalized.m_private_spend_key.get().empty() || !config_normalized.m_private_view_key.get().empty()) { + return create_wallet_from_keys(config_normalized, std::move(http_client_factory)); + } else { + return create_wallet_random(config_normalized, std::move(http_client_factory)); + } + } + + monero_wallet_full* monero_wallet_full::create_wallet_from_seed(monero_wallet_config& config, std::unique_ptr http_client_factory) { + MTRACE("create_wallet_from_seed(...)"); + + // normalize config + if (config.m_restore_height == boost::none) config.m_restore_height = 0; + + // validate mnemonic and get recovery key and language if not multisig + crypto::secret_key recovery_key; + std::string language = config.m_language.get(); + if (!config.m_is_multisig.get()) { + bool is_valid = crypto::ElectrumWords::words_to_bytes(config.m_seed.get(), recovery_key, language); + if (!is_valid) throw std::runtime_error("Invalid mnemonic"); + if (language == crypto::ElectrumWords::old_language_name) language = config.m_language.get(); + } + + // validate language + if (!crypto::ElectrumWords::is_valid_language(language)) throw std::runtime_error("Invalid language: " + language); + + // apply offset if given + if (!config.m_seed_offset.get().empty()) recovery_key = cryptonote::decrypt_key(recovery_key, config.m_seed_offset.get()); + + // initialize wallet + monero_wallet_full* wallet = new monero_wallet_full(); + if (http_client_factory == nullptr) wallet->m_w2 = std::unique_ptr(new tools::wallet2(static_cast(config.m_network_type.get()), 1, true)); + else wallet->m_w2 = std::unique_ptr(new tools::wallet2(static_cast(config.m_network_type.get()), 1, true, std::move(http_client_factory))); + wallet->m_w2->init(""); + if (config.m_regtest != boost::none && config.m_regtest.get() == true) { + wallet->m_w2->allow_mismatched_daemon_version(true); + wallet->m_w2->max_reorg_depth(100000); + } + if (config.m_network_type.get() == monero_network_type::TESTNET) wallet->m_w2->allow_mismatched_daemon_version(true); // testnet may use custom hard fork heights + wallet->set_daemon_connection(config.m_server, config.m_is_trusted_daemon); + wallet->m_w2->set_seed_language(language); + if (config.m_account_lookahead != boost::none) wallet->m_w2->set_subaddress_lookahead(config.m_account_lookahead.get(), config.m_subaddress_lookahead.get()); + + // generate wallet + if (config.m_is_multisig.get()) { + + // parse multisig data + epee::wipeable_string multisig_data; + multisig_data.resize(config.m_seed.get().size() / 2); + if (!epee::from_hex::to_buffer(epee::to_mut_byte_span(multisig_data), config.m_seed.get())) throw std::runtime_error("Multisig seed not represented as hexadecimal string"); + + // generate multisig wallet + wallet->m_w2->generate(config.m_path.get(), config.m_password.get(), multisig_data, false); + wallet->m_w2->enable_multisig(true); + } else { + + // generate normal wallet + crypto::secret_key recovery_val = wallet->m_w2->generate(config.m_path.get(), config.m_password.get(), recovery_key, true, false); + + // validate mnemonic + epee::wipeable_string electrum_words; + if (!crypto::ElectrumWords::bytes_to_words(recovery_val, electrum_words, language)) throw std::runtime_error("Failed to encode seed"); + } + wallet->m_w2->set_refresh_from_block_height(config.m_restore_height.get()); + wallet->init_common(); + return wallet; + } + + monero_wallet_full* monero_wallet_full::create_wallet_from_keys(monero_wallet_config& config, std::unique_ptr http_client_factory) { + MTRACE("create_wallet_from_keys(...)"); + + // validate and normalize config + if (config.m_restore_height == boost::none) config.m_restore_height = 0; + if (!config.m_seed_offset.get().empty()) throw std::runtime_error("Cannot specify seed offset when creating wallet from keys"); + + // parse and validate private spend key + crypto::secret_key spend_key_sk; + bool has_spend_key = false; + if (!config.m_private_spend_key.get().empty()) { + cryptonote::blobdata spend_key_data; + if (!epee::string_tools::parse_hexstr_to_binbuff(config.m_private_spend_key.get(), spend_key_data) || spend_key_data.size() != sizeof(crypto::secret_key)) { + throw std::runtime_error("failed to parse secret spend key"); + } + has_spend_key = true; + spend_key_sk = *reinterpret_cast(spend_key_data.data()); + } + + // parse and validate private view key + bool has_view_key = true; + crypto::secret_key view_key_sk; + if (config.m_private_view_key.get().empty()) { + if (has_spend_key) has_view_key = false; + else throw std::runtime_error("Neither spend key nor view key supplied"); + } + if (has_view_key) { + cryptonote::blobdata view_key_data; + if (!epee::string_tools::parse_hexstr_to_binbuff(config.m_private_view_key.get(), view_key_data) || view_key_data.size() != sizeof(crypto::secret_key)) { + throw std::runtime_error("failed to parse secret view key"); + } + view_key_sk = *reinterpret_cast(view_key_data.data()); + } + + // parse and validate address + cryptonote::address_parse_info address_info; + if (config.m_primary_address.get().empty()) { + if (has_view_key) throw std::runtime_error("must provide primary address if providing private view key"); + } else { + if (!get_account_address_from_str(address_info, static_cast(config.m_network_type.get()), config.m_primary_address.get())) throw std::runtime_error("failed to parse address"); + + // check the spend and view keys match the given address + crypto::public_key pkey; + if (has_spend_key) { + if (!crypto::secret_key_to_public_key(spend_key_sk, pkey)) throw std::runtime_error("failed to verify secret spend key"); + if (address_info.address.m_spend_public_key != pkey) throw std::runtime_error("spend key does not match address"); + } + if (has_view_key) { + if (!crypto::secret_key_to_public_key(view_key_sk, pkey)) throw std::runtime_error("failed to verify secret view key"); + if (address_info.address.m_view_public_key != pkey) throw std::runtime_error("view key does not match address"); + } + } + + // validate language + if (!monero_utils::is_valid_language(config.m_language.get())) throw std::runtime_error("Unknown language: " + config.m_language.get()); + + // initialize wallet + // TODO: delete wallet pointer if exception + monero_wallet_full* wallet = new monero_wallet_full(); + if (http_client_factory == nullptr) wallet->m_w2 = std::unique_ptr(new tools::wallet2(static_cast(config.m_network_type.get()), 1, true)); + else wallet->m_w2 = std::unique_ptr(new tools::wallet2(static_cast(config.m_network_type.get()), 1, true, std::move(http_client_factory))); + if (config.m_account_lookahead != boost::none) wallet->m_w2->set_subaddress_lookahead(config.m_account_lookahead.get(), config.m_subaddress_lookahead.get()); + if (has_spend_key && has_view_key) wallet->m_w2->generate(config.m_path.get(), config.m_password.get(), address_info.address, spend_key_sk, view_key_sk); + else if (has_spend_key) wallet->m_w2->generate(config.m_path.get(), config.m_password.get(), spend_key_sk, true, false); + else wallet->m_w2->generate(config.m_path.get(), config.m_password.get(), address_info.address, view_key_sk); + wallet->m_w2->init(""); + if (config.m_regtest != boost::none && config.m_regtest.get() == true) { + wallet->m_w2->allow_mismatched_daemon_version(true); + wallet->m_w2->max_reorg_depth(100000); + } + if (config.m_network_type.get() == monero_network_type::TESTNET) wallet->m_w2->allow_mismatched_daemon_version(true); // testnet may use custom hard fork heights + wallet->set_daemon_connection(config.m_server, config.m_is_trusted_daemon); + wallet->m_w2->set_refresh_from_block_height(config.m_restore_height.get()); + wallet->m_w2->set_seed_language(config.m_language.get()); + wallet->init_common(); + return wallet; + } + + monero_wallet_full* monero_wallet_full::create_wallet_random(monero_wallet_config& config, std::unique_ptr http_client_factory) { + MTRACE("create_wallet_random(...)"); + + // validate config + if (!config.m_seed_offset.get().empty()) throw std::runtime_error("Cannot specify seed offset when creating random wallet"); + if (config.m_restore_height != boost::none) throw std::runtime_error("Cannot specify restore height when creating random wallet"); + + // initialize wallet + monero_wallet_full* wallet = new monero_wallet_full(); + if (http_client_factory == nullptr) wallet->m_w2 = std::unique_ptr(new tools::wallet2(static_cast(config.m_network_type.get()), 1, true)); + else wallet->m_w2 = std::unique_ptr(new tools::wallet2(static_cast(config.m_network_type.get()), 1, true, std::move(http_client_factory))); + wallet->m_w2->init(""); + if (config.m_regtest != boost::none && config.m_regtest.get() == true) { + wallet->m_w2->allow_mismatched_daemon_version(true); + wallet->m_w2->max_reorg_depth(100000); + } + if (config.m_network_type.get() == monero_network_type::TESTNET) wallet->m_w2->allow_mismatched_daemon_version(true); // testnet may use custom hard fork heights + wallet->set_daemon_connection(config.m_server, config.m_is_trusted_daemon); + wallet->m_w2->set_seed_language(config.m_language.get()); + crypto::secret_key secret_key; + if (config.m_account_lookahead != boost::none) wallet->m_w2->set_subaddress_lookahead(config.m_account_lookahead.get(), config.m_subaddress_lookahead.get()); + wallet->m_w2->generate(config.m_path.get(), config.m_password.get(), secret_key, false, false); + wallet->init_common(); + if (wallet->is_connected_to_daemon()) wallet->m_w2->set_refresh_from_block_height(wallet->get_daemon_height()); + return wallet; + } + + std::vector monero_wallet_full::get_seed_languages() { + std::vector languages; + crypto::ElectrumWords::get_language_list(languages, true); + return languages; + } + + // ----------------------------- WALLET METHODS ----------------------------- + + monero_wallet_full::~monero_wallet_full() { + MTRACE("~monero_wallet_full()"); + close(false); + } + + void monero_wallet_full::set_daemon_connection(const std::string& uri, const std::string& username, const std::string& password, const std::string& proxy_uri, const boost::optional& is_trusted) { + MTRACE("set_daemon_connection(" << uri << ", " << username << ", " << "***" << ", " << proxy_uri << ")"); + assert_not_closed(); + + // prepare uri, login, and trusted for wallet2 + boost::optional login{}; + login.emplace(username, password); + bool trusted = false; + if (is_trusted != boost::none) trusted = is_trusted.get(); + else { + try { trusted = tools::is_local_address(uri); } + catch (const std::exception& e) { } + } + + // detect ssl TODO: wallet2 does not detect ssl from uri + epee::net_utils::ssl_support_t ssl = uri.rfind("https", 0) == 0 ? epee::net_utils::ssl_support_t::e_ssl_support_enabled : epee::net_utils::ssl_support_t::e_ssl_support_disabled; + + if (!m_w2->set_daemon(uri, login, trusted, std::move(ssl), proxy_uri)) { + throw std::runtime_error("Failed to initialize wallet with daemon connection"); + } + is_connected_to_daemon(); // update m_is_connected cache // TODO: better naming? + } + + void monero_wallet_full::set_daemon_connection(const std::shared_ptr& connection, const boost::optional& is_trusted) { + assert_not_closed(); + if (connection == nullptr) set_daemon_connection("", "", "", "", is_trusted); + else set_daemon_connection(connection->m_uri == boost::none ? "" : connection->m_uri.get(), connection->m_username == boost::none ? "" : connection->m_username.get(), connection->m_password == boost::none ? "" : connection->m_password.get(), connection->m_proxy_uri == boost::none ? "" : connection->m_proxy_uri.get(), is_trusted); + } + + std::shared_ptr monero_wallet_full::get_daemon_connection() const { + MTRACE("monero_wallet_full::get_daemon_connection()"); + assert_not_closed(); + if (m_w2->get_daemon_address().empty()) return nullptr; + std::shared_ptr connection = std::make_shared(); + connection->m_uri = m_w2->get_daemon_address(); + std::string proxy_uri = m_w2->get_daemon_proxy(); + if (!proxy_uri.empty()) connection->m_proxy_uri = proxy_uri; + if (m_w2->get_daemon_login()) { + if (!m_w2->get_daemon_login()->username.empty()) connection->m_username = m_w2->get_daemon_login()->username; + epee::wipeable_string wipeablePassword = m_w2->get_daemon_login()->password; + std::string password = std::string(wipeablePassword.data(), wipeablePassword.size()); + if (!password.empty()) connection->m_password = password; + } + return connection; + } + + // TODO: could return Wallet::ConnectionStatus_Disconnected, Wallet::ConnectionStatus_WrongVersion, Wallet::ConnectionStatus_Connected like wallet.cpp::connected() + bool monero_wallet_full::is_connected_to_daemon() const { + assert_not_closed(); + uint32_t version = 0; + m_is_connected = m_w2->check_connection(&version, NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS); // TODO: should this be updated elsewhere? + if (!m_is_connected) return false; + //if (!m_w2->light_wallet() && (version >> 16) != CORE_RPC_VERSION_MAJOR) m_is_connected = false; // wrong network type // TODO: disallow rpc version mismatch by configuration + return m_is_connected; + } + + bool monero_wallet_full::is_daemon_synced() const { + assert_not_closed(); + if (!m_is_connected) throw std::runtime_error("Wallet is not connected to daemon"); + uint64_t daemonHeight = get_daemon_height(); + return daemonHeight >= get_daemon_max_peer_height() && daemonHeight > 1; + } + + bool monero_wallet_full::is_daemon_trusted() const { + assert_not_closed(); + if (!m_is_connected) throw std::runtime_error("Wallet is not connected to daemon"); + return m_w2->is_trusted_daemon(); + } + + bool monero_wallet_full::is_synced() const { + assert_not_closed(); + return m_is_synced; + } + + monero_version monero_wallet_full::get_version() const { + assert_not_closed(); + monero_version version; + version.m_number = 65552; // same as monero-wallet-rpc v0.15.0.1 release + version.m_is_release = false; // TODO: could pull from MONERO_VERSION_IS_RELEASE in version.cpp + return version; + } + + std::string monero_wallet_full::get_path() const { + assert_not_closed(); + return m_w2->path(); + } + + monero_network_type monero_wallet_full::get_network_type() const { + assert_not_closed(); + return static_cast(m_w2->nettype()); + } + + std::string monero_wallet_full::get_seed() const { + assert_not_closed(); + epee::wipeable_string seed; + bool ready; + if (m_w2->multisig(&ready)) { + if (!ready) throw std::runtime_error("This wallet is multisig, but not yet finalized"); + if (!m_w2->get_multisig_seed(seed)) throw std::runtime_error("Failed to get multisig seed."); + } else { + if (m_w2->watch_only()) throw std::runtime_error("The wallet is watch-only. Cannot retrieve seed."); + if (!m_w2->is_deterministic()) throw std::runtime_error("The wallet is non-deterministic. Cannot display seed."); + if (!m_w2->get_seed(seed)) throw std::runtime_error("Failed to get seed."); + } + return std::string(seed.data(), seed.size()); + } + + std::string monero_wallet_full::get_seed_language() const { + assert_not_closed(); + if (m_w2->watch_only()) throw std::runtime_error("The wallet is watch-only. Cannot retrieve seed language."); + return m_w2->get_seed_language(); + } + + std::string monero_wallet_full::get_public_view_key() const { + MTRACE("get_private_view_key()"); + assert_not_closed(); + return epee::string_tools::pod_to_hex(m_w2->get_account().get_keys().m_account_address.m_view_public_key); + } + + std::string monero_wallet_full::get_private_view_key() const { + MTRACE("get_private_view_key()"); + assert_not_closed(); + return epee::string_tools::pod_to_hex(unwrap(unwrap(m_w2->get_account().get_keys().m_view_secret_key))); + } + + std::string monero_wallet_full::get_public_spend_key() const { + MTRACE("get_public_spend_key()"); + assert_not_closed(); + return epee::string_tools::pod_to_hex(m_w2->get_account().get_keys().m_account_address.m_spend_public_key); + } + + std::string monero_wallet_full::get_private_spend_key() const { + MTRACE("get_private_spend_key()"); + assert_not_closed(); + if (m_w2->watch_only()) throw std::runtime_error("The wallet is watch-only. Cannot retrieve spend key."); + std::string spend_key = epee::string_tools::pod_to_hex(unwrap(unwrap(m_w2->get_account().get_keys().m_spend_secret_key))); + if (spend_key == "0000000000000000000000000000000000000000000000000000000000000000") spend_key = ""; + return spend_key; + } + + std::string monero_wallet_full::get_address(uint32_t account_idx, uint32_t subaddress_idx) const { + assert_not_closed(); + return m_w2->get_subaddress_as_str({account_idx, subaddress_idx}); + } + + monero_subaddress monero_wallet_full::get_address_index(const std::string& address) const { + MTRACE("get_address_index(" << address << ")"); + assert_not_closed(); + + // validate address + cryptonote::address_parse_info info; + if (!get_account_address_from_str(info, m_w2->nettype(), address)) { + throw std::runtime_error("Invalid address"); + } + + // get index of address in wallet + auto index = m_w2->get_subaddress_index(info.address); + if (!index) throw std::runtime_error("Address doesn't belong to the wallet"); + + // return indices in subaddress + monero_subaddress subaddress; + cryptonote::subaddress_index cn_index = *index; + subaddress.m_account_index = cn_index.major; + subaddress.m_index = cn_index.minor; + return subaddress; + } + + monero_integrated_address monero_wallet_full::get_integrated_address(const std::string& standard_address, const std::string& payment_id) const { + MTRACE("get_integrated_address(" << standard_address << ", " << payment_id << ")"); + assert_not_closed(); + + // TODO monero-project: this logic is based on wallet_rpc_server::on_make_integrated_address() and should be moved to wallet so this is unecessary for api users + + // randomly generate payment id if not given, else validate + crypto::hash8 payment_id_h8; + if (payment_id.empty()) { + payment_id_h8 = crypto::rand(); + } else { + if (!tools::wallet2::parse_short_payment_id(payment_id, payment_id_h8)) throw std::runtime_error("Invalid payment ID: " + payment_id); + } + + // use primary address if standard address not given, else validate + if (standard_address.empty()) { + return decode_integrated_address(m_w2->get_integrated_address_as_str(payment_id_h8)); + } else { + + // validate standard address + cryptonote::address_parse_info info; + if (!cryptonote::get_account_address_from_str(info, m_w2->nettype(), standard_address)) throw std::runtime_error("Invalid address"); + if (info.is_subaddress) throw std::runtime_error("Subaddress shouldn't be used"); + if (info.has_payment_id) throw std::runtime_error("Already integrated address"); + if (payment_id.empty()) throw std::runtime_error("Payment ID shouldn't be left unspecified"); + + // create integrated address from given standard address + return decode_integrated_address(cryptonote::get_account_integrated_address_as_str(m_w2->nettype(), info.address, payment_id_h8)); + } + } + + monero_integrated_address monero_wallet_full::decode_integrated_address(const std::string& integrated_address) const { + MTRACE("decode_integrated_address(" << integrated_address << ")"); + assert_not_closed(); + + // validate integrated address + cryptonote::address_parse_info info; + if (!cryptonote::get_account_address_from_str(info, m_w2->nettype(), integrated_address)) throw std::runtime_error("Invalid address"); + if (!info.has_payment_id) throw std::runtime_error("Address is not an integrated address"); + + // initialize and return result + monero_integrated_address result; + result.m_standard_address = cryptonote::get_account_address_as_str(m_w2->nettype(), info.is_subaddress, info.address); + result.m_payment_id = epee::string_tools::pod_to_hex(info.payment_id); + result.m_integrated_address = integrated_address; + return result; + } + + uint64_t monero_wallet_full::get_height() const { + assert_not_closed(); + return m_w2->get_blockchain_current_height(); + } + + uint64_t monero_wallet_full::get_restore_height() const { // TODO: rename to get_sync_from_height() + assert_not_closed(); + return m_w2->get_refresh_from_block_height(); + } + + void monero_wallet_full::set_restore_height(uint64_t restore_height) { + assert_not_closed(); + m_w2->set_refresh_from_block_height(restore_height); + } + + uint64_t monero_wallet_full::get_daemon_height() const { + assert_not_closed(); + if (!m_is_connected) throw std::runtime_error("Wallet is not connected to daemon"); + std::string err; + uint64_t result = m_w2->get_daemon_blockchain_height(err); + if (!err.empty()) throw std::runtime_error(err); + return result; + } + + uint64_t monero_wallet_full::get_daemon_max_peer_height() const { + assert_not_closed(); + if (!m_is_connected) throw std::runtime_error("Wallet is not connected to daemon"); + std::string err; + uint64_t result = m_w2->get_daemon_blockchain_target_height(err); + if (!err.empty()) throw std::runtime_error(err); + if (result == 0) result = get_daemon_height(); // TODO monero-project: target height can be 0 when daemon is synced. Use blockchain height instead + return result; + } + + uint64_t monero_wallet_full::get_height_by_date(uint16_t year, uint8_t month, uint8_t day) const { + assert_not_closed(); + return m_w2->get_blockchain_height_by_date(year, month, day); + } + + void monero_wallet_full::add_listener(monero_wallet_listener& listener) { + assert_not_closed(); + m_listeners.insert(&listener); + m_w2_listener->update_listening(); + } + + void monero_wallet_full::remove_listener(monero_wallet_listener& listener) { + assert_not_closed(); + m_listeners.erase(&listener); + if (!m_sync_loop_running) m_w2_listener->update_listening(); // listener is unregistered after sync to avoid segfault + } + + std::set monero_wallet_full::get_listeners() { + assert_not_closed(); + return m_listeners; + } + + monero_sync_result monero_wallet_full::sync() { + MTRACE("sync()"); + assert_not_closed(); + if (!m_is_connected) throw std::runtime_error("Wallet is not connected to daemon"); + return lock_and_sync(); + } + + monero_sync_result monero_wallet_full::sync(monero_wallet_listener& listener) { + MTRACE("sync(listener)"); + assert_not_closed(); + if (!m_is_connected) throw std::runtime_error("Wallet is not connected to daemon"); + + // register listener + add_listener(listener); + + // sync wallet + monero_sync_result result = lock_and_sync(boost::none); + + // unregister listener + remove_listener(listener); + + // return sync result + return result; + } + + monero_sync_result monero_wallet_full::sync(uint64_t start_height) { + MTRACE("sync(" << start_height << ")"); + assert_not_closed(); + if (!m_is_connected) throw std::runtime_error("Wallet is not connected to daemon"); + return lock_and_sync(start_height); + } + + monero_sync_result monero_wallet_full::sync(uint64_t start_height, monero_wallet_listener& listener) { + MTRACE("sync(" << start_height << ", listener)"); + assert_not_closed(); + if (!m_is_connected) throw std::runtime_error("Wallet is not connected to daemon"); + + // wrap and register sync listener as wallet listener + add_listener(listener); + + // sync wallet + monero_sync_result result = lock_and_sync(start_height); + + // unregister sync listener + remove_listener(listener); + + // return sync result + return result; + } + + void monero_wallet_full::start_syncing(uint64_t sync_period_in_ms) { + assert_not_closed(); + if (!m_is_connected) throw std::runtime_error("Wallet is not connected to daemon"); + m_syncing_interval = sync_period_in_ms; + if (!m_syncing_enabled) { + m_syncing_enabled = true; + run_sync_loop(); // sync wallet on loop in background + } + } + + void monero_wallet_full::stop_syncing() { + assert_not_closed(); + m_syncing_enabled = false; + m_w2->stop(); + } + + void monero_wallet_full::scan_txs(const std::vector& tx_ids) { + MTRACE("scan_txs()"); + assert_not_closed(); + if (!m_is_connected) throw std::runtime_error("Wallet is not connected to daemon"); + + // convert string ids to crypto hashes + std::unordered_set tx_hashes; + std::vector::const_iterator i = tx_ids.begin(); + while (i != tx_ids.end()) { + cryptonote::blobdata tx_hash_blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(*i++, tx_hash_blob) || tx_hash_blob.size() != sizeof(crypto::hash)) throw std::runtime_error("TX ID has invalid format"); + crypto::hash tx_hash = *reinterpret_cast(tx_hash_blob.data()); + tx_hashes.insert(tx_hash); + } + + // scan txs + sync_op_lock op_lock(*this); // synchronize scanning + m_w2->scan_tx(tx_hashes); + } + + void monero_wallet_full::rescan_spent() { + MTRACE("rescan_spent()"); + assert_not_closed(); + if (!m_is_connected) throw std::runtime_error("Wallet is not connected to daemon"); + if (!is_daemon_trusted()) throw std::runtime_error("Rescan spent can only be used with a trusted daemon"); + sync_op_lock op_lock(*this); // do not refresh while rescanning spent + m_w2->rescan_spent(); + } + + // TODO: support arguments bool hard, bool refresh = true, bool keep_key_images = false + void monero_wallet_full::rescan_blockchain() { + MTRACE("rescan_blockchain()"); + assert_not_closed(); + if (!m_is_connected) throw std::runtime_error("Wallet is not connected to daemon"); + m_rescan_on_sync = true; + lock_and_sync(); + } + + // isMultisigImportNeeded + + uint64_t monero_wallet_full::get_balance() const { + assert_not_closed(); + return m_w2->balance_all(STRICT_); + } + + uint64_t monero_wallet_full::get_balance(uint32_t account_idx) const { + assert_not_closed(); + return m_w2->balance(account_idx, STRICT_); + } + + uint64_t monero_wallet_full::get_balance(uint32_t account_idx, uint32_t subaddress_idx) const { + assert_not_closed(); + std::map balance_per_subaddress = m_w2->balance_per_subaddress(account_idx, STRICT_); + auto iter = balance_per_subaddress.find(subaddress_idx); + return iter == balance_per_subaddress.end() ? 0 : iter->second; + } + + uint64_t monero_wallet_full::get_unlocked_balance() const { + assert_not_closed(); + return m_w2->unlocked_balance_all(STRICT_); + } + + uint64_t monero_wallet_full::get_unlocked_balance(uint32_t account_idx) const { + assert_not_closed(); + return m_w2->unlocked_balance(account_idx, STRICT_); + } + + uint64_t monero_wallet_full::get_unlocked_balance(uint32_t account_idx, uint32_t subaddress_idx) const { + assert_not_closed(); + std::map>> unlocked_balance_per_subaddress = m_w2->unlocked_balance_per_subaddress(account_idx, STRICT_); + auto iter = unlocked_balance_per_subaddress.find(subaddress_idx); + return iter == unlocked_balance_per_subaddress.end() ? 0 : iter->second.first; + } + + std::vector monero_wallet_full::get_accounts(bool include_subaddresses, const std::string& tag) const { + MTRACE("get_accounts(" << include_subaddresses << ", " << tag << ")"); + assert_not_closed(); + + // need transfers to inform if subaddresses used + std::vector transfers; + if (include_subaddresses) m_w2->get_transfers(transfers); + + // build accounts + std::vector accounts; + for (uint32_t account_idx = 0; account_idx < m_w2->get_num_subaddress_accounts(); account_idx++) { + monero_account account; + account.m_index = account_idx; + account.m_primary_address = get_address(account_idx, 0); + account.m_balance = m_w2->balance(account_idx, STRICT_); + account.m_unlocked_balance = m_w2->unlocked_balance(account_idx, STRICT_); + if (include_subaddresses) account.m_subaddresses = get_subaddresses_aux(account_idx, std::vector(), transfers); + accounts.push_back(account); + } + + return accounts; + } + + monero_account monero_wallet_full::get_account(uint32_t account_idx, bool include_subaddresses) const { + MTRACE("get_account(" << account_idx << ", " << include_subaddresses << ")"); + assert_not_closed(); + + // need transfers to inform if subaddresses used + std::vector transfers; + if (include_subaddresses) m_w2->get_transfers(transfers); + + // build and return account + monero_account account; + account.m_index = account_idx; + account.m_primary_address = get_address(account_idx, 0); + account.m_balance = m_w2->balance(account_idx, STRICT_); + account.m_unlocked_balance = m_w2->unlocked_balance(account_idx, STRICT_); + if (include_subaddresses) account.m_subaddresses = get_subaddresses_aux(account_idx, std::vector(), transfers); + return account; + } + + monero_account monero_wallet_full::create_account(const std::string& label) { + MTRACE("create_account(" << label << ")"); + assert_not_closed(); + + // create account + sync_op_lock op_lock(*this); // do not refresh while modifying accounts + m_w2->add_subaddress_account(label); + + // initialize and return result + monero_account account; + account.m_index = m_w2->get_num_subaddress_accounts() - 1; + account.m_primary_address = m_w2->get_subaddress_as_str({account.m_index.get(), 0}); + account.m_balance = 0; + account.m_unlocked_balance = 0; + return account; + } + + std::vector monero_wallet_full::get_subaddresses(const uint32_t account_idx, const std::vector& subaddress_indices) const { + MTRACE("get_subaddresses(" << account_idx << ", ...)"); + MTRACE("Subaddress indices size: " << subaddress_indices.size()); + assert_not_closed(); + + std::vector transfers; + m_w2->get_transfers(transfers); + return get_subaddresses_aux(account_idx, subaddress_indices, transfers); + } + + monero_subaddress monero_wallet_full::create_subaddress(const uint32_t account_idx, const std::string& label) { + MTRACE("create_subaddress(" << account_idx << ", " << label << ")"); + assert_not_closed(); + + // create subaddress + sync_op_lock op_lock(*this); // do not refresh while modifying subaddresses + m_w2->add_subaddress(account_idx, label); + + // initialize and return result + monero_subaddress subaddress; + subaddress.m_account_index = account_idx; + subaddress.m_index = m_w2->get_num_subaddresses(account_idx) - 1; + subaddress.m_address = m_w2->get_subaddress_as_str({account_idx, subaddress.m_index.get()}); + if (!label.empty()) subaddress.m_label = label; + subaddress.m_balance = 0; + subaddress.m_unlocked_balance = 0; + subaddress.m_num_unspent_outputs = 0; + subaddress.m_is_used = false; + subaddress.m_num_blocks_to_unlock = 0; + return subaddress; + } + + void monero_wallet_full::set_subaddress_label(const uint32_t account_idx, const uint32_t subaddress_idx, const std::string& label) { + MTRACE("set_subaddress_label(" << account_idx << ", " << subaddress_idx << ", " << label << ")"); + assert_not_closed(); + cryptonote::subaddress_index index = {account_idx, subaddress_idx}; + sync_op_lock op_lock(*this); // do not refresh while modifying subaddresses + m_w2->set_subaddress_label(index, label); + } + + std::vector> monero_wallet_full::get_txs() const { + return get_txs(monero_tx_query()); + } + + std::vector> monero_wallet_full::get_txs(const monero_tx_query& query) const { + MTRACE("get_txs(query)"); + assert_not_closed(); + + // copy query + std::shared_ptr query_sp = std::make_shared(query); // convert to shared pointer + std::shared_ptr _query = query_sp->copy(query_sp, std::make_shared()); // deep copy + +// // log query +// if (_query->m_block != nullptr) std::cout << "Tx query's rooted at [block]: " << _query->m_block->serialize() << std::endl; +// else std::cout << "Tx _query: " << _query->serialize() << std::endl; + + // temporarily disable transfer and output queries in order to collect all tx context + std::shared_ptr transfer_query = _query->m_transfer_query; + std::shared_ptr input_query = _query->m_input_query; + std::shared_ptr output_query = _query->m_output_query; + _query->m_transfer_query = nullptr; + _query->m_input_query = nullptr; + _query->m_output_query = nullptr; + + // fetch all transfers that meet tx query + std::shared_ptr temp_transfer_query = std::make_shared(); + temp_transfer_query->m_tx_query = monero_tx_query::decontextualize(_query->copy(_query, std::make_shared())); + temp_transfer_query->m_tx_query->m_transfer_query = temp_transfer_query; + std::vector> transfers = get_transfers_aux(*temp_transfer_query); + monero_utils::free(temp_transfer_query->m_tx_query); + + // collect unique txs from transfers while retaining order + std::vector> txs = std::vector>(); + std::unordered_set> txsSet; + for (const std::shared_ptr& transfer : transfers) { + if (txsSet.find(transfer->m_tx) == txsSet.end()) { + txs.push_back(transfer->m_tx); + txsSet.insert(transfer->m_tx); + } + } + + // cache types into maps for merging and lookup + std::map> tx_map; + std::map> block_map; + for (const std::shared_ptr& tx : txs) { + monero_utils::merge_tx(tx, tx_map, block_map); + } + + // fetch and merge outputs if requested + if ((_query->m_include_outputs != boost::none && *_query->m_include_outputs) || output_query != nullptr) { + std::shared_ptr temp_output_query = std::make_shared(); + temp_output_query->m_tx_query = monero_tx_query::decontextualize(_query->copy(_query, std::make_shared())); + temp_output_query->m_tx_query->m_output_query = temp_output_query; + std::vector> outputs = get_outputs_aux(*temp_output_query); + monero_utils::free(temp_output_query->m_tx_query); + + // merge output txs one time while retaining order + std::unordered_set> output_txs; + for (const std::shared_ptr& output : outputs) { + std::shared_ptr tx = std::static_pointer_cast(output->m_tx); + if (output_txs.find(tx) == output_txs.end()) { + monero_utils::merge_tx(tx, tx_map, block_map); + output_txs.insert(tx); + } + } + } + + // restore transfer and output queries + _query->m_transfer_query = transfer_query; + _query->m_input_query = input_query; + _query->m_output_query = output_query; + + // filter txs that don't meet transfer query + std::vector> queried_txs; + std::vector>::iterator tx_iter = txs.begin(); + while (tx_iter != txs.end()) { + std::shared_ptr tx = *tx_iter; + if (_query->meets_criteria(tx.get())) { + queried_txs.push_back(tx); + tx_iter++; + } else { + tx_map.erase(tx->m_hash.get()); + tx_iter = txs.erase(tx_iter); + if (tx->m_block != nullptr) tx->m_block->m_txs.erase(std::remove(tx->m_block->m_txs.begin(), tx->m_block->m_txs.end(), tx), tx->m_block->m_txs.end()); // TODO, no way to use tx_iter? + } + } + txs = queried_txs; + + // special case: re-fetch txs if inconsistency caused by needing to make multiple wallet calls // TODO monero-project: offer wallet.get_txs(...) + for (const std::shared_ptr& tx : txs) { + if ((*tx->m_is_confirmed && tx->m_block == nullptr) || (!*tx->m_is_confirmed && tx->m_block != nullptr)) { + std::cout << "WARNING: Inconsistency detected building txs from multiple wallet2 calls, re-fetching" << std::endl; + monero_utils::free(txs); + txs.clear(); + txs = get_txs(*_query); + monero_utils::free(_query); + return txs; + } + } + + // if tx hashes requested, order txs + if (!_query->m_hashes.empty()) { + txs.clear(); + for (const std::string& tx_hash : _query->m_hashes) { + std::map>::const_iterator tx_iter = tx_map.find(tx_hash); + if (tx_iter != tx_map.end()) txs.push_back(tx_iter->second); + } + } + + // free query and return + monero_utils::free(_query); + return txs; + } + + std::vector> monero_wallet_full::get_transfers(const monero_transfer_query& query) const { + assert_not_closed(); + +// // log query +// if (query.m_tx_query != nullptr) { +// if (query.m_tx_query->m_block == nullptr) std::cout << "Transfer query's tx query rooted at [tx]:" << query.m_tx_query->serialize() << std::endl; +// else std::cout << "Transfer query's tx query rooted at [block]: " << query.m_tx_query->m_block->serialize() << std::endl; +// } else std::cout << "Transfer query: " << query.serialize() << std::endl; + + // get transfers directly if query does not require tx context (e.g. other transfers, outputs) + if (!monero_transfer_query::is_contextual(query)) return get_transfers_aux(query); + + // otherwise get txs with full models to fulfill query + std::vector> transfers; + for (const std::shared_ptr& tx : get_txs(*(query.m_tx_query))) { + for (const std::shared_ptr& transfer : tx->filter_transfers(query)) { // collect queried transfers, erase if excluded + transfers.push_back(transfer); + } + } + return transfers; + } + + std::vector> monero_wallet_full::get_outputs(const monero_output_query& query) const { + assert_not_closed(); + +// // log query +// if (query.m_tx_query != nullptr) { +// if (query.m_tx_query->m_block == nullptr) std::cout << "Output query's tx query rooted at [tx]:" << query.m_tx_query->serialize() << std::endl; +// else std::cout << "Output query's tx query rooted at [block]: " << query.m_tx_query->m_block->serialize() << std::endl; +// } else std::cout << "Output query: " << query.serialize() << std::endl; + + // get outputs directly if query does not require tx context (e.g. other outputs, transfers) + if (!monero_output_query::is_contextual(query)) return get_outputs_aux(query); + + // otherwise get txs with full models to fulfill query + std::vector> outputs; + for (const std::shared_ptr& tx : get_txs(*(query.m_tx_query))) { + for (const std::shared_ptr& output : tx->filter_outputs_wallet(query)) { // collect queried outputs, erase if excluded + outputs.push_back(output); + } + } + return outputs; + } + + std::string monero_wallet_full::export_outputs(bool all) const { + assert_not_closed(); + sync_op_lock op_lock(*this); // do not refresh while exporting outputs + return epee::string_tools::buff_to_hex_nodelimer(m_w2->export_outputs_to_str(all)); + } + + int monero_wallet_full::import_outputs(const std::string& outputs_hex) { + assert_not_closed(); + + // validate and parse hex data + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(outputs_hex, blob)) { + throw std::runtime_error("Failed to parse hex"); + } + + // import hex and return result + sync_op_lock op_lock(*this); // do not refresh while importing outputs + return m_w2->import_outputs_from_str(blob); + } + + std::shared_ptr monero_wallet_full::export_key_images(bool all) const { + MTRACE("monero_wallet_full::export_key_images()"); + assert_not_closed(); + + // build export result from wallet2 types + std::shared_ptr result = std::make_shared(); + sync_op_lock op_lock(*this); // do not refresh while exporting key images + std::pair>> ski = m_w2->export_key_images(all); + result->m_offset = ski.first; + for (uint64_t n = 0; n < ski.second.size(); ++n) { + std::shared_ptr key_image = std::make_shared(); + result->m_key_images.push_back(key_image); + key_image->m_hex = epee::string_tools::pod_to_hex(ski.second[n].first); + key_image->m_signature = epee::string_tools::pod_to_hex(ski.second[n].second); + } + return result; + } + + std::shared_ptr monero_wallet_full::import_key_images(const std::vector>& key_images, uint64_t offset) { + MTRACE("monero_wallet_full::import_key_images()"); + assert_not_closed(); + + // validate and prepare key images for wallet2 + std::vector> ski; + ski.resize(key_images.size()); + for (uint64_t n = 0; n < ski.size(); ++n) { + if (!epee::string_tools::hex_to_pod(key_images[n]->m_hex.get(), ski[n].first)) { + throw std::runtime_error("failed to parse key image"); + } + if (!epee::string_tools::hex_to_pod(key_images[n]->m_signature.get(), ski[n].second)) { + throw std::runtime_error("failed to parse signature"); + } + } + + // import key images + uint64_t spent = 0, unspent = 0; + sync_op_lock op_lock(*this); // do not refresh while importing key images + uint64_t height = m_w2->import_key_images(ski, offset, spent, unspent, is_connected_to_daemon()); + + // translate results + std::shared_ptr result = std::make_shared(); + result->m_height = height; + result->m_spent_amount = spent; + result->m_unspent_amount = unspent; + return result; + } + + void monero_wallet_full::freeze_output(const std::string& key_image) { + assert_not_closed(); + if (key_image.empty()) throw std::runtime_error("Must specify key image to freeze"); + crypto::key_image ki; + if (!epee::string_tools::hex_to_pod(key_image, ki)) throw std::runtime_error("failed to parse key image"); + sync_op_lock op_lock(*this); // do not refresh while modifying outputs + m_w2->freeze(ki); + } + + void monero_wallet_full::thaw_output(const std::string& key_image) { + assert_not_closed(); + if (key_image.empty()) throw std::runtime_error("Must specify key image to thaw"); + crypto::key_image ki; + if (!epee::string_tools::hex_to_pod(key_image, ki)) throw std::runtime_error("failed to parse key image"); + sync_op_lock op_lock(*this); // do not refresh while modifying outputs + m_w2->thaw(ki); + } + + bool monero_wallet_full::is_output_frozen(const std::string& key_image) { + assert_not_closed(); + if (key_image.empty()) throw std::runtime_error("Must specify key image to check if frozen"); + crypto::key_image ki; + if (!epee::string_tools::hex_to_pod(key_image, ki)) throw std::runtime_error("failed to parse key image"); + sync_op_lock op_lock(*this); // do not refresh while reading outputs + return m_w2->frozen(ki); + } + + monero_tx_priority monero_wallet_full::get_default_fee_priority() const { + assert_not_closed(); + return static_cast(m_w2->adjust_priority(0)); + } + + std::vector> monero_wallet_full::create_txs(const monero_tx_config& config) { + MTRACE("monero_wallet_full::create_txs"); + //std::cout << "monero_tx_config: " << config.serialize() << std::endl; + assert_not_closed(); + sync_op_lock op_lock(*this); // do not refresh while creating txs + + // validate config + if (config.m_account_index == boost::none) throw std::runtime_error("Must specify account index to send from"); + + // prepare parameters for wallet rpc's validate_transfer() + std::string payment_id = config.m_payment_id == boost::none ? std::string("") : config.m_payment_id.get(); + std::list tr_destinations; + for (const std::shared_ptr& destination : config.get_normalized_destinations()) { + tools::wallet_rpc::transfer_destination tr_destination; + if (destination->m_amount == boost::none) throw std::runtime_error("Destination amount not defined"); + if (destination->m_address == boost::none) throw std::runtime_error("Destination address not defined"); + tr_destination.amount = destination->m_amount.get(); + tr_destination.address = destination->m_address.get(); + tr_destinations.push_back(tr_destination); + } + + // validate the requested txs and populate dsts & extra + std::vector dsts; + std::vector extra; + epee::json_rpc::error err; + if (!validate_transfer(m_w2.get(), tr_destinations, payment_id, dsts, extra, true, err)) { + throw std::runtime_error(err.message); + } + + // prepare parameters for wallet2's create_transactions_2() + uint64_t mixin = m_w2->adjust_mixin(0); // get mixin for call to 'create_transactions_2' + uint32_t priority = m_w2->adjust_priority(config.m_priority == boost::none ? 0 : config.m_priority.get()); + uint32_t account_index = config.m_account_index.get(); + std::set subaddress_indices; + for (const uint32_t& subaddress_idx : config.m_subaddress_indices) subaddress_indices.insert(subaddress_idx); + std::set subtract_fee_from; + for (const uint32_t& subtract_fee_from_idx : config.m_subtract_fee_from) subtract_fee_from.insert(subtract_fee_from_idx); + + // prepare transactions + std::vector ptx_vector = m_w2->create_transactions_2(dsts, mixin, priority, extra, account_index, subaddress_indices, subtract_fee_from); + if (ptx_vector.empty()) throw std::runtime_error("No transaction created"); + + // check if request cannot be fulfilled due to splitting + if (ptx_vector.size() > 1) { + if (config.m_can_split != boost::none && !config.m_can_split.get()) { + throw std::runtime_error("Transaction would be too large. Try create_txs()"); + } + if (subtract_fee_from.size() > 0 && config.m_can_split != boost::none && config.m_can_split.get()) { + throw std::runtime_error("subtractfeefrom transfers cannot be split over multiple transactions yet"); + } + } + + // config for fill_response() + bool get_tx_keys = true; + bool get_tx_hex = true; + bool get_tx_metadata = true; + bool relay = config.m_relay != boost::none && config.m_relay.get(); + if (config.m_relay != boost::none && config.m_relay.get() == true && is_multisig()) throw std::runtime_error("Cannot relay multisig transaction until co-signed"); + + // commit txs (if relaying) and get response using wallet rpc's fill_response() + std::list tx_keys; + std::list tx_amounts; + std::list tx_amounts_by_dest; + std::list tx_fees; + std::list tx_weights; + std::string multisig_tx_hex; + std::string unsigned_tx_hex; + std::list tx_hashes; + std::list tx_blobs; + std::list tx_metadatas; + std::list input_key_images_list; + if (!fill_response(m_w2.get(), ptx_vector, get_tx_keys, tx_keys, tx_amounts, tx_amounts_by_dest, tx_fees, tx_weights, multisig_tx_hex, unsigned_tx_hex, !relay, tx_hashes, get_tx_hex, tx_blobs, get_tx_metadata, tx_metadatas, input_key_images_list, err)) { + throw std::runtime_error("need to handle error filling response!"); // TODO + } + + // build sent txs from results // TODO: break this into separate utility function + std::vector> txs; + auto tx_hashes_iter = tx_hashes.begin(); + auto tx_keys_iter = tx_keys.begin(); + auto tx_amounts_iter = tx_amounts.begin(); + auto tx_fees_iter = tx_fees.begin(); + auto tx_weights_iter = tx_weights.begin(); + auto tx_blobs_iter = tx_blobs.begin(); + auto tx_metadatas_iter = tx_metadatas.begin(); + auto input_key_images_list_iter = input_key_images_list.begin(); + auto ptx_iter = ptx_vector.begin(); + while (tx_fees_iter != tx_fees.end()) { + + // init tx with outgoing transfer from filled values + std::shared_ptr tx = std::make_shared(); + txs.push_back(tx); + if (tx_hashes_iter != tx_hashes.end()) tx->m_hash = *tx_hashes_iter; // empty if multisig + tx->m_key = *tx_keys_iter; + tx->m_fee = *tx_fees_iter; + tx->m_weight = *tx_weights_iter; + if (tx_blobs_iter != tx_blobs.end()) tx->m_full_hex = *tx_blobs_iter; + if (tx_metadatas_iter != tx_metadatas.end()) tx->m_metadata = *tx_metadatas_iter; + std::shared_ptr out_transfer = std::make_shared(); + out_transfer->m_tx = tx; + tx->m_outgoing_transfer = out_transfer; + out_transfer->m_amount = *tx_amounts_iter; + + // init inputs with key images + std::list input_key_images = (*input_key_images_list_iter).key_images; + for (const std::string& input_key_image : input_key_images) { + std::shared_ptr input = std::make_shared(); + input->m_tx = tx; + tx->m_inputs.push_back(input); + input->m_key_image = std::make_shared(); + input->m_key_image->m_hex = input_key_image; + } + + // init destinations from this tx's dests since a payment may be split across txs + for (const cryptonote::tx_destination_entry& dest : ptx_iter->dests) { + std::shared_ptr destination = std::make_shared(); + destination->m_address = dest.address(m_w2->nettype(), crypto::null_hash); + destination->m_amount = dest.amount; + tx->m_outgoing_transfer->m_destinations.push_back(destination); + } + + // init other known fields + tx->m_is_outgoing = true; + tx->m_payment_id = config.m_payment_id; + tx->m_is_confirmed = false; + tx->m_is_miner_tx = false; + tx->m_is_failed = false; // TODO: test and handle if true + tx->m_relay = config.m_relay != boost::none && config.m_relay.get(); + tx->m_is_relayed = tx->m_relay.get(); + tx->m_in_tx_pool = tx->m_relay.get(); + if (!tx->m_is_failed.get() && tx->m_is_relayed.get()) tx->m_is_double_spend_seen = false; // TODO: test and handle if true + tx->m_num_confirmations = 0; + tx->m_ring_size = monero_utils::RING_SIZE; + tx->m_unlock_time = 0; + tx->m_is_locked = true; + if (tx->m_is_relayed.get()) tx->m_last_relayed_timestamp = static_cast(time(NULL)); // set last relayed timestamp to current time iff relayed // TODO monero-project: this should be encapsulated in wallet2 + out_transfer->m_account_index = config.m_account_index; + if (config.m_subaddress_indices.size() == 1) out_transfer->m_subaddress_indices.push_back(config.m_subaddress_indices[0]); // subaddress index is known iff 1 requested // TODO: get all known subaddress indices here + + // iterate to next element + tx_keys_iter++; + tx_amounts_iter++; + tx_fees_iter++; + tx_hashes_iter++; + tx_blobs_iter++; + tx_metadatas_iter++; + input_key_images_list_iter++; + ptx_iter++; + } + + // build tx set + std::shared_ptr tx_set = std::make_shared(); + tx_set->m_txs = txs; + for (int i = 0; i < txs.size(); i++) txs[i]->m_tx_set = tx_set; + if (!multisig_tx_hex.empty()) tx_set->m_multisig_tx_hex = multisig_tx_hex; + if (!unsigned_tx_hex.empty()) tx_set->m_unsigned_tx_hex = unsigned_tx_hex; + + // notify listeners of spent funds + if (relay) m_w2_listener->on_spend_txs(txs); + return txs; + } + + std::vector> monero_wallet_full::sweep_unlocked(const monero_tx_config& config) { + assert_not_closed(); + + // validate config + std::vector> destinations = config.get_normalized_destinations(); + if (destinations.size() != 1) throw std::runtime_error("Must specify exactly one destination to sweep to"); + if (destinations[0]->m_address == boost::none) throw std::runtime_error("Must specify destination address to sweep to"); + if (destinations[0]->m_amount != boost::none) throw std::runtime_error("Cannot specify amount to sweep"); + if (config.m_account_index == boost::none && config.m_subaddress_indices.size() != 0) throw std::runtime_error("Must specify account index if subaddress indices are specified"); + + // determine account and subaddress indices to sweep; default to all with unlocked balance if not specified + std::map> indices; + if (config.m_account_index != boost::none) { + if (config.m_subaddress_indices.size() != 0) { + indices[config.m_account_index.get()] = config.m_subaddress_indices; + } else { + std::vector subaddress_indices; + for (const monero_subaddress& subaddress : monero_wallet::get_subaddresses(config.m_account_index.get())) { + if (subaddress.m_unlocked_balance.get() > 0) subaddress_indices.push_back(subaddress.m_index.get()); + } + indices[config.m_account_index.get()] = subaddress_indices; + } + } else { + std::vector accounts = monero_wallet::get_accounts(true); + for (const monero_account& account : accounts) { + if (account.m_unlocked_balance.get() > 0) { + std::vector subaddress_indices; + for (const monero_subaddress& subaddress : account.m_subaddresses) { + if (subaddress.m_unlocked_balance.get() > 0) subaddress_indices.push_back(subaddress.m_index.get()); + } + indices[account.m_index.get()] = subaddress_indices; + } + } + } + + // sweep from each account and collect resulting txs + std::vector> txs; + for (std::pair> subaddress_indices_pair : indices) { + + // copy and modify the original config + monero_tx_config copy = config.copy(); + copy.m_account_index = subaddress_indices_pair.first; + copy.m_sweep_each_subaddress = false; + + // sweep all subaddresses together // TODO monero-project: can this reveal outputs belong to the same wallet? + if (copy.m_sweep_each_subaddress == boost::none || copy.m_sweep_each_subaddress.get() != true) { + copy.m_subaddress_indices = subaddress_indices_pair.second; + std::vector> account_txs = sweep_account(copy); + txs.insert(std::end(txs), std::begin(account_txs), std::end(account_txs)); + } + + // otherwise sweep each subaddress individually + else { + for (uint32_t subaddress_index : subaddress_indices_pair.second) { + std::vector subaddress_indices; + subaddress_indices.push_back(subaddress_index); + copy.m_subaddress_indices = subaddress_indices; + std::vector> account_txs = sweep_account(copy); + txs.insert(std::end(txs), std::begin(account_txs), std::end(account_txs)); + } + } + } + + // notify listeners of spent funds + if (config.m_relay != boost::none && config.m_relay.get()) m_w2_listener->on_spend_txs(txs); + return txs; + } + + std::vector> monero_wallet_full::sweep_account(const monero_tx_config& config) { + assert_not_closed(); + sync_op_lock op_lock(*this); // do not refresh while creating txs + + // validate config + std::vector> destinations = config.get_normalized_destinations(); + if (config.m_account_index == boost::none) throw std::runtime_error("Must specify account index to sweep from"); + if (destinations.size() != 1 || destinations[0]->m_address == boost::none || destinations[0]->m_address.get().empty()) throw std::runtime_error("Must provide exactly one destination address to sweep output to"); + if (destinations[0]->m_amount != boost::none) throw std::runtime_error("Cannot specify destination amount to sweep"); + if (config.m_key_image != boost::none) throw std::runtime_error("Cannot define key image in sweep_account(); use sweep_output() to sweep an output by its key image"); + if (config.m_sweep_each_subaddress != boost::none && config.m_sweep_each_subaddress.get() == true) throw std::runtime_error("Cannot sweep each subaddress individually with sweep_account"); + if (config.m_subtract_fee_from.size() > 0) throw std::runtime_error("Sweep transactions do not support subtracting fees from destinations"); + + // validate the transfer requested and populate dsts & extra + std::list destination; + destination.push_back(wallet_rpc::transfer_destination()); + destination.back().amount = 0; + destination.back().address = destinations[0]->m_address.get(); + std::string payment_id = config.m_payment_id == boost::none ? std::string("") : config.m_payment_id.get(); + std::vector dsts; + std::vector extra; + epee::json_rpc::error err; + if (!validate_transfer(m_w2.get(), destination, payment_id, dsts, extra, true, err)) { + throw std::runtime_error("Failed to validate sweep_account transfer request"); + } + + // TODO monero-project: this is default `outputs` in COMMAND_RPC_SWEEP_ALL which is not documented + uint64_t num_outputs = 1; + + // prepare parameters for wallet2's create_transactions_all() + uint64_t below_amount = config.m_below_amount == boost::none ? 0 : config.m_below_amount.get(); + uint64_t mixin = m_w2->adjust_mixin(0); + uint32_t priority = m_w2->adjust_priority(config.m_priority == boost::none ? 0 : config.m_priority.get()); + uint32_t account_index = config.m_account_index.get(); + std::set subaddress_indices; + for (const uint32_t& subaddress_idx : config.m_subaddress_indices) subaddress_indices.insert(subaddress_idx); + + // prepare transactions + std::vector ptx_vector = m_w2->create_transactions_all(below_amount, dsts[0].addr, dsts[0].is_subaddress, num_outputs, mixin, priority, extra, account_index, subaddress_indices); + + // config for fill_response() + bool get_tx_keys = true; + bool get_tx_hex = true; + bool get_tx_metadata = true; + bool relay = config.m_relay != boost::none && config.m_relay.get(); + + // commit txs (if relaying) and get response using wallet rpc's fill_response() + std::list tx_keys; + std::list tx_amounts; + std::list tx_amounts_by_dest; + std::list tx_fees; + std::list tx_weights; + std::string multisig_tx_hex; + std::string unsigned_tx_hex; + std::list tx_hashes; + std::list tx_blobs; + std::list tx_metadatas; + std::list input_key_images_list; + if (!fill_response(m_w2.get(), ptx_vector, get_tx_keys, tx_keys, tx_amounts, tx_amounts_by_dest, tx_fees, tx_weights, multisig_tx_hex, unsigned_tx_hex, !relay, tx_hashes, get_tx_hex, tx_blobs, get_tx_metadata, tx_metadatas, input_key_images_list, err)) { + throw std::runtime_error("need to handle error filling response!"); // TODO + } + + // build sent txs from results // TODO: break this into separate utility function + std::vector> txs; + auto tx_hashes_iter = tx_hashes.begin(); + auto tx_keys_iter = tx_keys.begin(); + auto tx_amounts_iter = tx_amounts.begin(); + auto tx_fees_iter = tx_fees.begin(); + auto tx_weights_iter = tx_weights.begin(); + auto tx_blobs_iter = tx_blobs.begin(); + auto tx_metadatas_iter = tx_metadatas.begin(); + auto input_key_images_list_iter = input_key_images_list.begin(); + while (tx_fees_iter != tx_fees.end()) { + + // init tx with outgoing transfer from filled values + std::shared_ptr tx = std::make_shared(); + txs.push_back(tx); + if (tx_hashes_iter != tx_hashes.end()) tx->m_hash = *tx_hashes_iter; // empty if multisig + tx->m_is_locked = true; + tx->m_is_outgoing = true; + tx->m_key = *tx_keys_iter; + tx->m_fee = *tx_fees_iter; + tx->m_weight = *tx_weights_iter; + if (tx_blobs_iter != tx_blobs.end()) tx->m_full_hex = *tx_blobs_iter; + if (tx_metadatas_iter != tx_metadatas.end()) tx->m_metadata = *tx_metadatas_iter; + std::shared_ptr out_transfer = std::make_shared(); + tx->m_outgoing_transfer = out_transfer; + out_transfer->m_tx = tx; + out_transfer->m_amount = *tx_amounts_iter; + std::shared_ptr destination = std::make_shared(destinations[0]->m_address.get(), out_transfer->m_amount.get()); + out_transfer->m_destinations.push_back(destination); + + // init inputs with key images + std::list input_key_images = (*input_key_images_list_iter).key_images; + for (const std::string& input_key_image : input_key_images) { + std::shared_ptr input = std::make_shared(); + input->m_tx = tx; + tx->m_inputs.push_back(input); + input->m_key_image = std::make_shared(); + input->m_key_image->m_hex = input_key_image; + } + + // init other known fields + tx->m_payment_id = config.m_payment_id; + tx->m_is_confirmed = false; + tx->m_is_miner_tx = false; + tx->m_is_failed = false; // TODO: test and handle if true + tx->m_relay = relay; + tx->m_is_relayed = relay; + tx->m_in_tx_pool = relay; + if (!tx->m_is_failed.get() && tx->m_is_relayed.get()) tx->m_is_double_spend_seen = false; // TODO: test and handle if true + tx->m_num_confirmations = 0; + tx->m_ring_size = monero_utils::RING_SIZE; + tx->m_unlock_time = 0; + if (tx->m_is_relayed.get()) tx->m_last_relayed_timestamp = static_cast(time(NULL)); // set last relayed timestamp to current time iff relayed // TODO monero-project: this should be encapsulated in wallet2 + out_transfer->m_account_index = config.m_account_index; + if (config.m_subaddress_indices.size() == 1) out_transfer->m_subaddress_indices.push_back(config.m_subaddress_indices[0]); // subaddress index is known iff 1 requested // TODO: get all known subaddress indices here + + // iterate to next element + tx_keys_iter++; + tx_amounts_iter++; + tx_fees_iter++; + tx_hashes_iter++; + tx_blobs_iter++; + tx_metadatas_iter++; + input_key_images_list_iter++; + } + + // link tx set and return + std::shared_ptr tx_set = std::make_shared(); + tx_set->m_txs = txs; + for (int i = 0; i < txs.size(); i++) txs[i]->m_tx_set = tx_set; + if (!multisig_tx_hex.empty()) tx_set->m_multisig_tx_hex = multisig_tx_hex; + if (!unsigned_tx_hex.empty()) tx_set->m_unsigned_tx_hex = unsigned_tx_hex; + return txs; + } + + std::shared_ptr monero_wallet_full::sweep_output(const monero_tx_config& config) { + MTRACE("sweep_output()"); + //MTRACE("monero_tx_config: " << config.serialize()); + assert_not_closed(); + sync_op_lock op_lock(*this); // do not refresh while creating txs + + // validate input config + std::vector> destinations = config.get_normalized_destinations(); + if (config.m_key_image == boost::none || config.m_key_image.get().empty()) throw std::runtime_error("Must provide key image of output to sweep"); + if (destinations.size() != 1 || destinations[0]->m_address == boost::none || destinations[0]->m_address.get().empty()) throw std::runtime_error("Must provide exactly one destination address to sweep output to"); + if (config.m_subtract_fee_from.size() > 0) throw std::runtime_error("Sweep transactions do not support subtracting fees from destinations"); + + // validate the transfer queried and populate dsts & extra + std::string m_payment_id = config.m_payment_id == boost::none ? std::string("") : config.m_payment_id.get(); + std::vector dsts; + std::vector extra; + std::list destination; + destination.push_back(wallet_rpc::transfer_destination()); + destination.back().amount = 0; + destination.back().address = destinations[0]->m_address.get(); + epee::json_rpc::error err; + if (!validate_transfer(m_w2.get(), destination, m_payment_id, dsts, extra, true, err)) { + //throw std::runtime_error(er); // TODO + throw std::runtime_error("Handle validate_transfer error!"); + } + + // validate key image + crypto::key_image ki; + if (!epee::string_tools::hex_to_pod(config.m_key_image.get(), ki)) { + throw std::runtime_error("failed to parse key image"); + } + + // create transaction + uint64_t mixin = m_w2->adjust_mixin(0); + uint32_t priority = m_w2->adjust_priority(config.m_priority == boost::none ? 0 : config.m_priority.get()); + std::vector ptx_vector = m_w2->create_transactions_single(ki, dsts[0].addr, dsts[0].is_subaddress, 1, mixin, priority, extra); + + // validate created transaction + if (ptx_vector.empty()) throw std::runtime_error("No outputs found"); + if (ptx_vector.size() > 1) throw std::runtime_error("Multiple transactions are created, which is not supposed to happen"); + const wallet2::pending_tx &ptx = ptx_vector[0]; + if (ptx.selected_transfers.size() > 1) throw std::runtime_error("The transaction uses multiple inputs, which is not supposed to happen"); + + // config for fill_response() + bool get_tx_keys = true; + bool get_tx_hex = true; + bool get_tx_metadata = true; + bool relay = config.m_relay != boost::none && config.m_relay.get(); + + // commit txs (if relaying) and get response using wallet rpc's fill_response() + std::list tx_keys; + std::list tx_amounts; + std::list tx_amounts_by_dest; + std::list tx_fees; + std::list tx_weights; + std::string multisig_tx_hex; + std::string unsigned_tx_hex; + std::list tx_hashes; + std::list tx_blobs; + std::list tx_metadatas; + std::list input_key_images_list; + if (!fill_response(m_w2.get(), ptx_vector, get_tx_keys, tx_keys, tx_amounts, tx_amounts_by_dest, tx_fees, tx_weights, multisig_tx_hex, unsigned_tx_hex, !relay, tx_hashes, get_tx_hex, tx_blobs, get_tx_metadata, tx_metadatas, input_key_images_list, err)) { + throw std::runtime_error("need to handle error filling response!"); // TODO: return err message + } + + // build sent txs from results // TODO: use common utility with create_txs() to avoid code duplication + std::vector> txs; + auto tx_hashes_iter = tx_hashes.begin(); + auto tx_keys_iter = tx_keys.begin(); + auto tx_amounts_iter = tx_amounts.begin(); + auto tx_fees_iter = tx_fees.begin(); + auto tx_weights_iter = tx_weights.begin(); + auto tx_blobs_iter = tx_blobs.begin(); + auto tx_metadatas_iter = tx_metadatas.begin(); + auto input_key_images_list_iter = input_key_images_list.begin(); + while (tx_fees_iter != tx_fees.end()) { + + // init tx with outgoing transfer from filled values + std::shared_ptr tx = std::make_shared(); + txs.push_back(tx); + if (tx_hashes_iter != tx_hashes.end()) tx->m_hash = *tx_hashes_iter; // empty if multisig + tx->m_is_outgoing = true; + tx->m_key = *tx_keys_iter; + tx->m_fee = *tx_fees_iter; + tx->m_weight = *tx_weights_iter; + if (tx_blobs_iter != tx_blobs.end()) tx->m_full_hex = *tx_blobs_iter; + if (tx_metadatas_iter != tx_metadatas.end()) tx->m_metadata = *tx_metadatas_iter; + std::shared_ptr out_transfer = std::make_shared(); + tx->m_outgoing_transfer = out_transfer; + out_transfer->m_tx = tx; + out_transfer->m_amount = *tx_amounts_iter; + + // init inputs with key images + std::list input_key_images = (*input_key_images_list_iter).key_images; + for (const std::string& input_key_image : input_key_images) { + std::shared_ptr input = std::make_shared(); + input->m_tx = tx; + tx->m_inputs.push_back(input); + input->m_key_image = std::make_shared(); + input->m_key_image->m_hex = input_key_image; + } + + // init other known fields + tx->m_payment_id = config.m_payment_id; + tx->m_is_confirmed = false; + tx->m_is_miner_tx = false; + tx->m_is_failed = false; // TODO: test and handle if true + tx->m_relay = relay; + tx->m_is_relayed = relay; + tx->m_in_tx_pool = relay; + if (!tx->m_is_failed.get() && tx->m_is_relayed.get()) tx->m_is_double_spend_seen = false; // TODO: test and handle if true + tx->m_num_confirmations = 0; + tx->m_ring_size = monero_utils::RING_SIZE; + tx->m_unlock_time = 0; + tx->m_is_locked = true; + if (tx->m_is_relayed.get()) tx->m_last_relayed_timestamp = static_cast(time(NULL)); // set last relayed timestamp to current time iff relayed // TODO monero-project: this should be encapsulated in wallet2 + out_transfer->m_account_index = config.m_account_index; + if (config.m_subaddress_indices.size() == 1) out_transfer->m_subaddress_indices.push_back(config.m_subaddress_indices[0]); // subaddress index is known iff 1 requested // TODO: get all known subaddress indices here + out_transfer->m_destinations = destinations; + out_transfer->m_destinations[0]->m_amount = *tx_amounts_iter; + + // iterate to next element + tx_keys_iter++; + tx_amounts_iter++; + tx_fees_iter++; + tx_hashes_iter++; + tx_blobs_iter++; + tx_metadatas_iter++; + input_key_images_list_iter++; + } + + // validate one transaction + if (txs.size() != 1) throw std::runtime_error("Expected 1 transaction but was " + boost::lexical_cast(txs.size())); + + // link tx set + std::shared_ptr tx_set = std::make_shared(); + tx_set->m_txs = txs; + txs[0]->m_tx_set = tx_set; + if (!multisig_tx_hex.empty()) tx_set->m_multisig_tx_hex = multisig_tx_hex; + if (!unsigned_tx_hex.empty()) tx_set->m_unsigned_tx_hex = unsigned_tx_hex; + + // notify listeners of spent funds + if (relay) m_w2_listener->on_spend_txs(txs); + return txs[0]; + } + + std::vector> monero_wallet_full::sweep_dust(bool relay) { + MTRACE("monero_wallet_full::sweep_dust()"); + assert_not_closed(); + + // create transaction to fill + sync_op_lock op_lock(*this); // do not refresh while creating txs + std::vector ptx_vector = m_w2->create_unmixable_sweep_transactions(); + + // config for fill_response + bool get_tx_keys = true; + bool get_tx_hex = true; + bool get_tx_metadata = true; + + // commit txs (if relaying) and get response using wallet rpc's fill_response() + std::list tx_keys; + std::list tx_amounts; + std::list tx_amounts_by_dest; + std::list tx_fees; + std::list tx_weights; + std::string multisig_tx_hex; + std::string unsigned_tx_hex; + std::list tx_hashes; + std::list tx_blobs; + std::list tx_metadatas; + std::list input_key_images_list; + epee::json_rpc::error er; + if (!fill_response(m_w2.get(), ptx_vector, get_tx_keys, tx_keys, tx_amounts, tx_amounts_by_dest, tx_fees, tx_weights, multisig_tx_hex, unsigned_tx_hex, !relay, tx_hashes, get_tx_hex, tx_blobs, get_tx_metadata, tx_metadatas, input_key_images_list, er)) { + throw std::runtime_error("need to handle error filling response!"); // TODO: return err message + } + + // build sent txs from results // TODO: use common utility with create_txs() to avoid code duplication + std::vector> txs; + auto tx_hashes_iter = tx_hashes.begin(); + auto tx_keys_iter = tx_keys.begin(); + auto tx_amounts_iter = tx_amounts.begin(); + auto tx_fees_iter = tx_fees.begin(); + auto tx_weights_iter = tx_weights.begin(); + auto tx_blobs_iter = tx_blobs.begin(); + auto tx_metadatas_iter = tx_metadatas.begin(); + auto input_key_images_list_iter = input_key_images_list.begin(); + while (tx_fees_iter != tx_fees.end()) { + + // init tx with outgoing transfer from filled values + std::shared_ptr tx = std::make_shared(); + txs.push_back(tx); + if (tx_hashes_iter != tx_hashes.end()) tx->m_hash = *tx_hashes_iter; // empty if multisig + tx->m_is_outgoing = true; + tx->m_key = *tx_keys_iter; + tx->m_fee = *tx_fees_iter; + tx->m_weight = *tx_weights_iter; + if (tx_blobs_iter != tx_blobs.end()) tx->m_full_hex = *tx_blobs_iter; + if (tx_metadatas_iter != tx_metadatas.end()) tx->m_metadata = *tx_metadatas_iter; + std::shared_ptr out_transfer = std::make_shared(); + tx->m_outgoing_transfer = out_transfer; + out_transfer->m_tx = tx; + out_transfer->m_amount = *tx_amounts_iter; + + // init inputs with key images + std::list input_key_images = (*input_key_images_list_iter).key_images; + for (const std::string& input_key_image : input_key_images) { + std::shared_ptr input = std::make_shared(); + input->m_tx = tx; + tx->m_inputs.push_back(input); + input->m_key_image = std::make_shared(); + input->m_key_image->m_hex = input_key_image; + } + + // init other known fields + tx->m_is_confirmed = false; + tx->m_is_miner_tx = false; + tx->m_is_failed = false; // TODO: test and handle if true + tx->m_relay = relay; + tx->m_is_relayed = relay; + tx->m_in_tx_pool = relay; + if (!tx->m_is_failed.get() && tx->m_is_relayed.get()) tx->m_is_double_spend_seen = false; // TODO: test and handle if true + tx->m_num_confirmations = 0; + tx->m_ring_size = monero_utils::RING_SIZE; + tx->m_unlock_time = 0; + if (tx->m_is_relayed.get()) tx->m_last_relayed_timestamp = static_cast(time(NULL)); // set last relayed timestamp to current time iff relayed // TODO monero-project: this should be encapsulated in wallet2 + out_transfer->m_destinations[0]->m_amount = *tx_amounts_iter; + + // iterate to next element + tx_keys_iter++; + tx_amounts_iter++; + tx_fees_iter++; + tx_hashes_iter++; + tx_blobs_iter++; + tx_metadatas_iter++; + input_key_images_list_iter++; + } + + // link tx set + std::shared_ptr tx_set = std::make_shared(); + tx_set->m_txs = txs; + for (int i = 0; i < txs.size(); i++) txs[i]->m_tx_set = tx_set; + if (!multisig_tx_hex.empty()) tx_set->m_multisig_tx_hex = multisig_tx_hex; + if (!unsigned_tx_hex.empty()) tx_set->m_unsigned_tx_hex = unsigned_tx_hex; + + // notify listeners of spent funds + if (relay) m_w2_listener->on_spend_txs(txs); + return txs; + } + + std::vector monero_wallet_full::relay_txs(const std::vector& tx_metadatas) { + MTRACE("relay_txs()"); + assert_not_closed(); + sync_op_lock op_lock(*this); // do not refresh while relaying txs + + // relay each metadata as a tx + std::vector tx_hashes; + for (const auto& txMetadata : tx_metadatas) { + + // parse tx metadata hex + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(txMetadata, blob)) { + throw std::runtime_error("Failed to parse hex"); + } + + // deserialize tx + bool loaded = false; + tools::wallet2::pending_tx ptx; + try { + binary_archive ar{epee::strspan(blob)}; + if (::serialization::serialize(ar, ptx)) loaded = true; + } catch (...) {} + if (!loaded) { + try { + std::istringstream iss(blob); + boost::archive::portable_binary_iarchive ar(iss); + ar >> ptx; + loaded = true; + } catch (...) {} + } + if (!loaded) throw std::runtime_error("Failed to parse tx metadata"); + + // commit tx + try { + m_w2->commit_tx(ptx); + } catch (const std::exception& e) { + throw std::runtime_error("Failed to commit tx"); + } + + // collect resulting hash + tx_hashes.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); + } + + // notify listeners of spent funds + m_w2_listener->on_spend_tx_hashes(tx_hashes); + + // return relayed tx hashes + return tx_hashes; + } + + monero_tx_set monero_wallet_full::describe_tx_set(const monero_tx_set& tx_set) { + assert_not_closed(); + + // get unsigned and multisig tx sets + std::string unsigned_tx_hex = tx_set.m_unsigned_tx_hex == boost::none ? "" : tx_set.m_unsigned_tx_hex.get(); + std::string multisig_tx_hex = tx_set.m_multisig_tx_hex == boost::none ? "" : tx_set.m_multisig_tx_hex.get(); + + // validate request + if (m_w2->key_on_device()) throw std::runtime_error("command not supported by HW wallet"); + if (m_w2->watch_only()) throw std::runtime_error("command not supported by view-only wallet"); + if (unsigned_tx_hex.empty() && multisig_tx_hex.empty()) throw std::runtime_error("no txset provided"); + + std::vector tx_constructions; + if (!unsigned_tx_hex.empty()) { + try { + tools::wallet2::unsigned_tx_set exported_txs; + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(unsigned_tx_hex, blob)) throw std::runtime_error("Failed to parse hex."); + if (!m_w2->parse_unsigned_tx_from_str(blob, exported_txs)) throw std::runtime_error("cannot load unsigned_txset"); + tx_constructions = exported_txs.txes; + } + catch (const std::exception &e) { + throw std::runtime_error("failed to parse unsigned transfers: " + std::string(e.what())); + } + } else if (!multisig_tx_hex.empty()) { + try { + tools::wallet2::multisig_tx_set exported_txs; + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(multisig_tx_hex, blob)) throw std::runtime_error("Failed to parse hex."); + if (!m_w2->parse_multisig_tx_from_str(blob, exported_txs)) throw std::runtime_error("cannot load multisig_txset"); + for (uint64_t n = 0; n < exported_txs.m_ptx.size(); ++n) { + tx_constructions.push_back(exported_txs.m_ptx[n].construction_data); + } + } + catch (const std::exception &e) { + throw std::runtime_error("failed to parse multisig transfers: " + std::string(e.what())); + } + } + + std::vector ptx; // TODO wallet_rpc_server: unused variable + try { + + // gather info for each tx + std::vector> txs; + std::unordered_map> dests; + int first_known_non_zero_change_index = -1; + for (int64_t n = 0; n < tx_constructions.size(); ++n) + { + // init tx + std::shared_ptr tx = std::make_shared(); + tx->m_is_outgoing = true; + tx->m_is_confirmed = false; + tx->m_num_confirmations = 0; + tx->m_input_sum = 0; + tx->m_output_sum = 0; + tx->m_change_amount = 0; + tx->m_num_dummy_outputs = 0; + tx->m_ring_size = std::numeric_limits::max(); // smaller ring sizes will overwrite + + const tools::wallet2::tx_construction_data &cd = tx_constructions[n]; + std::vector tx_extra_fields; + bool has_encrypted_payment_id = false; + crypto::hash8 payment_id8 = crypto::null_hash8; + if (cryptonote::parse_tx_extra(cd.extra, tx_extra_fields)) + { + cryptonote::tx_extra_nonce extra_nonce; + if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) + { + crypto::hash payment_id; + if(cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) + { + if (payment_id8 != crypto::null_hash8) + { + tx->m_payment_id = epee::string_tools::pod_to_hex(payment_id8); + has_encrypted_payment_id = true; + } + } + else if (cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) + { + tx->m_payment_id = epee::string_tools::pod_to_hex(payment_id); + } + } + } + + for (uint64_t s = 0; s < cd.sources.size(); ++s) + { + tx->m_input_sum = tx->m_input_sum.get() + cd.sources[s].amount; + uint64_t ring_size = cd.sources[s].outputs.size(); + if (ring_size < tx->m_ring_size.get()) + tx->m_ring_size = ring_size; + } + for (uint64_t d = 0; d < cd.splitted_dsts.size(); ++d) + { + const cryptonote::tx_destination_entry &entry = cd.splitted_dsts[d]; + std::string address = cryptonote::get_account_address_as_str(m_w2->nettype(), entry.is_subaddress, entry.addr); + if (has_encrypted_payment_id && !entry.is_subaddress && address != entry.original) + address = cryptonote::get_account_integrated_address_as_str(m_w2->nettype(), entry.addr, payment_id8); + auto i = dests.find(entry.addr); + if (i == dests.end()) + dests.insert(std::make_pair(entry.addr, std::make_pair(address, entry.amount))); + else + i->second.second += entry.amount; + tx->m_output_sum = tx->m_output_sum.get() + entry.amount; + } + if (cd.change_dts.amount > 0) + { + auto it = dests.find(cd.change_dts.addr); + if (it == dests.end()) throw std::runtime_error("Claimed change does not go to a paid address"); + if (it->second.second < cd.change_dts.amount) throw std::runtime_error("Claimed change is larger than payment to the change address"); + if (cd.change_dts.amount > 0) + { + if (first_known_non_zero_change_index == -1) + first_known_non_zero_change_index = n; + const tools::wallet2::tx_construction_data &cdn = tx_constructions[first_known_non_zero_change_index]; + if (memcmp(&cd.change_dts.addr, &cdn.change_dts.addr, sizeof(cd.change_dts.addr))) throw std::runtime_error("Change goes to more than one address"); + } + tx->m_change_amount = tx->m_change_amount.get() + cd.change_dts.amount; + it->second.second -= cd.change_dts.amount; + if (it->second.second == 0) + dests.erase(cd.change_dts.addr); + } + + tx->m_outgoing_transfer = std::make_shared(); + uint64_t n_dummy_outputs = 0; + for (auto i = dests.begin(); i != dests.end(); ) + { + if (i->second.second > 0) + { + std::shared_ptr destination = std::make_shared(); + destination->m_address = i->second.first; + destination->m_amount = i->second.second; + tx->m_outgoing_transfer->m_destinations.push_back(destination); + } + else + tx->m_num_dummy_outputs = tx->m_num_dummy_outputs.get() + 1; + ++i; + } + + if (tx->m_change_amount.get() > 0) + { + const tools::wallet2::tx_construction_data &cd0 = tx_constructions[0]; + tx->m_change_address = get_account_address_as_str(m_w2->nettype(), cd0.subaddr_account > 0, cd0.change_dts.addr); + } + + tx->m_fee = tx->m_input_sum.get() - tx->m_output_sum.get(); + tx->m_unlock_time = cd.unlock_time; + tx->m_extra_hex = epee::to_hex::string({cd.extra.data(), cd.extra.size()}); + txs.push_back(tx); + } + + // build and return tx set + monero_tx_set tx_set; + tx_set.m_txs = txs; + return tx_set; + } + catch (const std::exception &e) + { + throw std::runtime_error("failed to parse unsigned transfers"); + } + } + + // implementation based on monero-project wallet_rpc_server.cpp::on_sign_transfer() + monero_tx_set monero_wallet_full::sign_txs(const std::string& unsigned_tx_hex) { + assert_not_closed(); + if (m_w2->key_on_device()) throw std::runtime_error("command not supported by HW wallet"); + if (m_w2->watch_only()) throw std::runtime_error("command not supported by view-only wallet"); + + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(unsigned_tx_hex, blob)) throw std::runtime_error("Failed to parse hex."); + + tools::wallet2::unsigned_tx_set exported_txs; + if(!m_w2->parse_unsigned_tx_from_str(blob, exported_txs)) throw std::runtime_error("cannot load unsigned_txset"); + + std::vector ptxs; + std::vector> txs; + try { + tools::wallet2::signed_tx_set signed_txs; + std::string ciphertext = m_w2->sign_tx_dump_to_str(exported_txs, ptxs, signed_txs); + if (ciphertext.empty()) throw std::runtime_error("Failed to sign unsigned tx"); + + // init tx set + monero_tx_set tx_set; + tx_set.m_signed_tx_hex = epee::string_tools::buff_to_hex_nodelimer(ciphertext); + for (auto &ptx : ptxs) { + + // init tx + std::shared_ptr tx = std::make_shared(); + tx->m_is_outgoing = true; + tx->m_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)); + tx->m_key = epee::string_tools::pod_to_hex(unwrap(unwrap(ptx.tx_key))); + for (const crypto::secret_key& additional_tx_key : ptx.additional_tx_keys) { + tx->m_key = tx->m_key.get() += epee::string_tools::pod_to_hex(unwrap(unwrap(additional_tx_key))); + } + tx->m_full_hex = epee::string_tools::buff_to_hex_nodelimer(cryptonote::tx_to_blob(ptx.tx)); + tx_set.m_txs.push_back(tx); + } + return tx_set; + } catch (const std::exception &e) { + throw std::runtime_error(std::string("Failed to sign unsigned tx: ") + e.what()); + } + } + + // implementation based on monero-project wallet_rpc_server.cpp::on_submit_transfer() + std::vector monero_wallet_full::submit_txs(const std::string& signed_tx_hex) { + assert_not_closed(); + if (m_w2->key_on_device()) throw std::runtime_error("command not supported by HW wallet"); + + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(signed_tx_hex, blob)) throw std::runtime_error("Failed to parse hex."); + + std::vector ptx_vector; + try { + if (!m_w2->parse_tx_from_str(blob, ptx_vector, NULL)) throw std::runtime_error("Failed to parse signed tx data."); + } catch (const std::exception &e) { + throw std::runtime_error(std::string("Failed to parse signed tx: ") + e.what()); + } + + sync_op_lock op_lock(*this); // do not refresh while relaying txs + try { + std::vector tx_hashes; + for (auto &ptx: ptx_vector) { + m_w2->commit_tx(ptx); + tx_hashes.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); + } + m_w2_listener->on_spend_tx_hashes(tx_hashes); // notify listeners of spent funds + return tx_hashes; + } catch (const std::exception &e) { + throw std::runtime_error(std::string("Failed to submit signed tx: ") + e.what()); + } + } + + std::string monero_wallet_full::sign_message(const std::string& msg, monero_message_signature_type signature_type, uint32_t account_idx, uint32_t subaddress_idx) const { + assert_not_closed(); + cryptonote::subaddress_index index = {account_idx, subaddress_idx}; + tools::wallet2::message_signature_type_t signature_type_w2 = signature_type == monero_message_signature_type::SIGN_WITH_SPEND_KEY ? tools::wallet2::message_signature_type_t::sign_with_spend_key : tools::wallet2::message_signature_type_t::sign_with_view_key; + return m_w2->sign(msg, signature_type_w2, index); + } + + monero_message_signature_result monero_wallet_full::verify_message(const std::string& msg, const std::string& address, const std::string& signature) const { + assert_not_closed(); + + // validate and parse address or url + cryptonote::address_parse_info info; + std::string err = "Invalid address"; + if (!get_account_address_from_str_or_url(info, m_w2->nettype(), address, + [&err](const std::string &url, const std::vector &addresses, bool dnssec_valid)->std::string { + if (!dnssec_valid) { + err = std::string("Invalid DNSSEC for ") + url; + return {}; + } + if (addresses.empty()) { + err = std::string("No Monero address found at ") + url; + return {}; + } + return addresses[0]; + })) + { + throw std::runtime_error(err); + } + + // verify message + tools:wallet2::message_signature_result_t result_w2 = m_w2->verify(msg, info.address, signature); + + // translate result + monero_message_signature_result result; + result.m_is_good = result_w2.valid; + result.m_is_old = result_w2.old; + result.m_signature_type = result_w2.type == tools::wallet2::message_signature_type_t::sign_with_spend_key ? monero_message_signature_type::SIGN_WITH_SPEND_KEY : monero_message_signature_type::SIGN_WITH_VIEW_KEY; + result.m_version = result_w2.version; + return result; + } + + std::string monero_wallet_full::get_tx_key(const std::string& tx_hash) const { + MTRACE("monero_wallet_full::get_tx_key()"); + assert_not_closed(); + + // validate and parse tx hash + crypto::hash _tx_hash; + if (!epee::string_tools::hex_to_pod(tx_hash, _tx_hash)) { + throw std::runtime_error("TX hash has invalid format"); + } + + // get tx key and additional keys + crypto::secret_key _tx_key; + std::vector additional_tx_keys; + if (!m_w2->get_tx_key(_tx_hash, _tx_key, additional_tx_keys)) { + throw std::runtime_error("No tx secret key is stored for this tx"); + } + + // build and return tx key with additional keys + epee::wipeable_string s; + s += epee::to_hex::wipeable_string(_tx_key); + for (uint64_t i = 0; i < additional_tx_keys.size(); ++i) { + s += epee::to_hex::wipeable_string(additional_tx_keys[i]); + } + return std::string(s.data(), s.size()); + } + + std::shared_ptr monero_wallet_full::check_tx_key(const std::string& tx_hash, const std::string& tx_key, const std::string& address) const { + MTRACE("monero_wallet_full::check_tx_key()"); + assert_not_closed(); + + // validate and parse tx hash + crypto::hash _tx_hash; + if (!epee::string_tools::hex_to_pod(tx_hash, _tx_hash)) { + throw std::runtime_error("TX hash has invalid format"); + } + + // validate and parse tx key + epee::wipeable_string tx_key_str = tx_key; + if (tx_key_str.size() < 64 || tx_key_str.size() % 64) { + throw std::runtime_error("Tx key has invalid format"); + } + const char *data = tx_key_str.data(); + crypto::secret_key _tx_key; + if (!epee::wipeable_string(data, 64).hex_to_pod(unwrap(unwrap(_tx_key)))) { + throw std::runtime_error("Tx key has invalid format"); + } + + // get additional keys + uint64_t offset = 64; + std::vector additional_tx_keys; + while (offset < tx_key_str.size()) { + additional_tx_keys.resize(additional_tx_keys.size() + 1); + if (!epee::wipeable_string(data + offset, 64).hex_to_pod(unwrap(unwrap(additional_tx_keys.back())))) { + throw std::runtime_error("Tx key has invalid format"); + } + offset += 64; + } + + // validate and parse address + cryptonote::address_parse_info info; + if (!get_account_address_from_str(info, m_w2->nettype(), address)) { + throw std::runtime_error("Invalid address"); + } + + // initialize and return tx check using wallet2 + uint64_t received_amount; + bool in_tx_pool; + uint64_t num_confirmations; + m_w2->check_tx_key(_tx_hash, _tx_key, additional_tx_keys, info.address, received_amount, in_tx_pool, num_confirmations); + std::shared_ptr check_tx = std::make_shared(); + check_tx->m_is_good = true; // check is good if we get this far + check_tx->m_received_amount = received_amount; + check_tx->m_in_tx_pool = in_tx_pool; + check_tx->m_num_confirmations = num_confirmations; + return check_tx; + } + + std::string monero_wallet_full::get_tx_proof(const std::string& tx_hash, const std::string& address, const std::string& message) const { + assert_not_closed(); + + // validate and parse tx hash + crypto::hash _tx_hash; + if (!epee::string_tools::hex_to_pod(tx_hash, _tx_hash)) { + throw std::runtime_error("TX hash has invalid format"); + } + + // validate and parse address + cryptonote::address_parse_info info; + if (!get_account_address_from_str(info, m_w2->nettype(), address)) { + throw std::runtime_error("Invalid address"); + } + + // get tx proof + return m_w2->get_tx_proof(_tx_hash, info.address, info.is_subaddress, message); + } + + std::shared_ptr monero_wallet_full::check_tx_proof(const std::string& tx_hash, const std::string& address, const std::string& message, const std::string& signature) const { + MTRACE("monero_wallet_full::check_tx_proof()"); + assert_not_closed(); + + // validate and parse tx hash + crypto::hash _tx_hash; + if (!epee::string_tools::hex_to_pod(tx_hash, _tx_hash)) { + throw std::runtime_error("TX hash has invalid format"); + } + + // validate and parse address + cryptonote::address_parse_info info; + if (!get_account_address_from_str(info, m_w2->nettype(), address)) { + throw std::runtime_error("Invalid address"); + } + + // validate signature + if (signature.empty()) throw std::runtime_error("Must provide signature to check tx proof"); + + // initialize and return tx check using wallet2 + std::shared_ptr check_tx = std::make_shared(); + uint64_t received_amount; + bool in_tx_pool; + uint64_t num_confirmations; + check_tx->m_is_good = m_w2->check_tx_proof(_tx_hash, info.address, info.is_subaddress, message, signature, received_amount, in_tx_pool, num_confirmations); + if (check_tx->m_is_good) { + check_tx->m_received_amount = received_amount; + check_tx->m_in_tx_pool = in_tx_pool; + check_tx->m_num_confirmations = num_confirmations; + } + return check_tx; + } + + std::string monero_wallet_full::get_spend_proof(const std::string& tx_hash, const std::string& message) const { + MTRACE("monero_wallet_full::get_spend_proof()"); + assert_not_closed(); + + // validate and parse tx hash + crypto::hash _tx_hash; + if (!epee::string_tools::hex_to_pod(tx_hash, _tx_hash)) { + throw std::runtime_error("TX hash has invalid format"); + } + + // return spend proof signature + sync_op_lock op_lock(*this); // do not refresh while generating proof + return m_w2->get_spend_proof(_tx_hash, message); + } + + bool monero_wallet_full::check_spend_proof(const std::string& tx_hash, const std::string& message, const std::string& signature) const { + MTRACE("monero_wallet_full::check_spend_proof()"); + assert_not_closed(); + + // validate and parse tx hash + crypto::hash _tx_hash; + if (!epee::string_tools::hex_to_pod(tx_hash, _tx_hash)) { + throw std::runtime_error("TX hash has invalid format"); + } + + // validate signature + if (signature.empty()) throw std::runtime_error("Must provide signature to check spend proof"); + + // check spend proof + return m_w2->check_spend_proof(_tx_hash, message, signature); + } + + std::string monero_wallet_full::get_reserve_proof_wallet(const std::string& message) const { + MTRACE("monero_wallet_full::get_reserve_proof_wallet()"); + assert_not_closed(); + boost::optional> account_minreserve; + sync_op_lock op_lock(*this); // do not refresh while generating proof + return m_w2->get_reserve_proof(account_minreserve, message); + } + + std::string monero_wallet_full::get_reserve_proof_account(uint32_t account_idx, uint64_t amount, const std::string& message) const { + MTRACE("monero_wallet_full::get_reserve_proof_account()"); + assert_not_closed(); + boost::optional> account_minreserve; + if (account_idx >= m_w2->get_num_subaddress_accounts()) throw std::runtime_error("Account index is out of bound"); + account_minreserve = std::make_pair(account_idx, amount); + sync_op_lock op_lock(*this); // do not refresh while generating proof + return m_w2->get_reserve_proof(account_minreserve, message); + } + + std::shared_ptr monero_wallet_full::check_reserve_proof(const std::string& address, const std::string& message, const std::string& signature) const { + MTRACE("monero_wallet_full::check_reserve_proof()"); + assert_not_closed(); + + // validate and parse input + cryptonote::address_parse_info info; + if (!get_account_address_from_str(info, m_w2->nettype(), address)) throw std::runtime_error("Invalid address"); + if (info.is_subaddress) throw std::runtime_error("Address must not be a subaddress"); + if (signature.empty()) throw std::runtime_error("Must provide signature to check reserve proof"); + + // initialize check reserve using wallet2 + std::shared_ptr reserve_check = std::make_shared(); + uint64_t total_amount; + uint64_t unconfirmed_spent_amount; + reserve_check->m_is_good = m_w2->check_reserve_proof(info.address, message, signature, total_amount, unconfirmed_spent_amount); + if (reserve_check->m_is_good) { + reserve_check->m_total_amount = total_amount; + reserve_check->m_unconfirmed_spent_amount = unconfirmed_spent_amount; + } + return reserve_check; + } + + std::string monero_wallet_full::get_tx_note(const std::string& tx_hash) const { + MTRACE("monero_wallet_full::get_tx_note()"); + assert_not_closed(); + cryptonote::blobdata tx_blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(tx_hash, tx_blob) || tx_blob.size() != sizeof(crypto::hash)) { + throw std::runtime_error("TX hash has invalid format"); + } + crypto::hash _tx_hash = *reinterpret_cast(tx_blob.data()); + return m_w2->get_tx_note(_tx_hash); + } + + std::vector monero_wallet_full::get_tx_notes(const std::vector& tx_hashes) const { + MTRACE("monero_wallet_full::get_tx_notes()"); + assert_not_closed(); + std::vector notes; + for (const auto& tx_hash : tx_hashes) notes.push_back(get_tx_note(tx_hash)); + return notes; + } + + void monero_wallet_full::set_tx_note(const std::string& tx_hash, const std::string& note) { + MTRACE("monero_wallet_full::set_tx_note()"); + assert_not_closed(); + cryptonote::blobdata tx_blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(tx_hash, tx_blob) || tx_blob.size() != sizeof(crypto::hash)) { + throw std::runtime_error("TX hash has invalid format"); + } + crypto::hash _tx_hash = *reinterpret_cast(tx_blob.data()); + m_w2->set_tx_note(_tx_hash, note); + } + + void monero_wallet_full::set_tx_notes(const std::vector& tx_hashes, const std::vector& notes) { + MTRACE("monero_wallet_full::set_tx_notes()"); + assert_not_closed(); + if (tx_hashes.size() != notes.size()) throw std::runtime_error("Different amount of txids and notes"); + for (int i = 0; i < tx_hashes.size(); i++) { + set_tx_note(tx_hashes[i], notes[i]); + } + } + + std::vector monero_wallet_full::get_address_book_entries(const std::vector& indices) const { + MTRACE("monero_wallet_full::get_address_book_entries()"); + assert_not_closed(); + + // get wallet2 address book entries + const auto w2_entries = m_w2->get_address_book(); + + // build entries from wallet2 types + std::vector entries; + if (indices.empty()) { + uint64_t idx = 0; + for (const auto &w2_entry: w2_entries) { + std::string address; + if (w2_entry.m_has_payment_id) address = cryptonote::get_account_integrated_address_as_str(m_w2->nettype(), w2_entry.m_address, w2_entry.m_payment_id); + else address = get_account_address_as_str(m_w2->nettype(), w2_entry.m_is_subaddress, w2_entry.m_address); + monero_address_book_entry entry(idx++, address, w2_entry.m_description); + entries.push_back(entry); + } + } else { + for (uint64_t idx : indices) { + if (idx >= w2_entries.size()) throw std::runtime_error("Index out of range: " + std::to_string(idx)); + const auto &w2_entry = w2_entries[idx]; + std::string address; + if (w2_entry.m_has_payment_id) address = cryptonote::get_account_integrated_address_as_str(m_w2->nettype(), w2_entry.m_address, w2_entry.m_payment_id); + else address = get_account_address_as_str(m_w2->nettype(), w2_entry.m_is_subaddress, w2_entry.m_address); + monero_address_book_entry entry(idx++, address, w2_entry.m_description); + entries.push_back(entry); + } + } + + return entries; + } + + uint64_t monero_wallet_full::add_address_book_entry(const std::string& address, const std::string& description) { + MTRACE("add_address_book_entry()"); + assert_not_closed(); + cryptonote::address_parse_info info; + epee::json_rpc::error er; + if(!get_account_address_from_str_or_url(info, m_w2->nettype(), address, + [&er](const std::string &url, const std::vector &addresses, bool dnssec_valid)->std::string { + if (!dnssec_valid) throw std::runtime_error(std::string("Invalid DNSSEC for ") + url); + if (addresses.empty()) throw std::runtime_error(std::string("No Monero address found at ") + url); + return addresses[0]; + })) + { + throw std::runtime_error(std::string("Invalid address: ") + address); + } + if (!m_w2->add_address_book_row(info.address, info.has_payment_id ? &info.payment_id : NULL, description, info.is_subaddress)) throw std::runtime_error("Failed to add address book entry"); + return m_w2->get_address_book().size() - 1; + } + + void monero_wallet_full::edit_address_book_entry(uint64_t index, bool set_address, const std::string& address, bool set_description, const std::string& description) { + MTRACE("edit_address_book_entry()"); + assert_not_closed(); + + const auto ab = m_w2->get_address_book(); + if (index >= ab.size()) throw std::runtime_error("Index out of range: " + std::to_string(index)); + + tools::wallet2::address_book_row entry = ab[index]; + + cryptonote::address_parse_info info; + epee::json_rpc::error er; + if (set_address) { + er.message = ""; + if(!get_account_address_from_str_or_url(info, m_w2->nettype(), address, + [&er](const std::string &url, const std::vector &addresses, bool dnssec_valid)->std::string { + if (!dnssec_valid) throw std::runtime_error(std::string("Invalid DNSSEC for ") + url); + if (addresses.empty()) throw std::runtime_error(std::string("No Monero address found at ") + url); + return addresses[0]; + })) + { + throw std::runtime_error("Invalid address: " + address); + } + entry.m_address = info.address; + entry.m_is_subaddress = info.is_subaddress; + if (info.has_payment_id) entry.m_payment_id = info.payment_id; + } + + if (set_description) entry.m_description = description; + + if (!m_w2->set_address_book_row(index, entry.m_address, set_address && entry.m_has_payment_id ? &entry.m_payment_id : NULL, entry.m_description, entry.m_is_subaddress)) { + throw std::runtime_error("Failed to edit address book entry"); + } + } + + void monero_wallet_full::delete_address_book_entry(uint64_t index) { + assert_not_closed(); + const auto w2_entries = m_w2->get_address_book(); + if (index >= w2_entries.size()) throw std::runtime_error("Index out of range: " + std::to_string(index)); + if (!m_w2->delete_address_book_row(index)) throw std::runtime_error("Failed to delete address book entry"); + } + + std::string monero_wallet_full::get_payment_uri(const monero_tx_config& config) const { + MTRACE("get_payment_uri()"); + assert_not_closed(); + + // validate config + std::vector> destinations = config.get_normalized_destinations(); + if (destinations.size() != 1) throw std::runtime_error("Cannot make URI from supplied parameters: must provide exactly one destination to send funds"); + if (destinations.at(0)->m_address == boost::none) throw std::runtime_error("Cannot make URI from supplied parameters: must provide destination address"); + if (destinations.at(0)->m_amount == boost::none) throw std::runtime_error("Cannot make URI from supplied parameters: must provide destination amount"); + + // prepare wallet2 params + std::string address = destinations.at(0)->m_address.get(); + std::string payment_id = config.m_payment_id == boost::none ? "" : config.m_payment_id.get(); + uint64_t amount = destinations.at(0)->m_amount.get(); + std::string note = config.m_note == boost::none ? "" : config.m_note.get(); + std::string m_recipient_name = config.m_recipient_name == boost::none ? "" : config.m_recipient_name.get(); + + // make uri using wallet2 + std::string error; + std::string uri = m_w2->make_uri(address, payment_id, amount, note, m_recipient_name, error); + if (uri.empty()) throw std::runtime_error("Cannot make URI from supplied parameters: " + error); + return uri; + } + + std::shared_ptr monero_wallet_full::parse_payment_uri(const std::string& uri) const { + MTRACE("parse_payment_uri(" << uri << ")"); + assert_not_closed(); + + // decode uri to parameters + std::string address; + std::string payment_id; + uint64_t amount = 0; + std::string note; + std::string m_recipient_name; + std::vector unknown_parameters; + std::string error; + if (!m_w2->parse_uri(uri, address, payment_id, amount, note, m_recipient_name, unknown_parameters, error)) { + throw std::runtime_error("Error parsing URI: " + error); + } + + // initialize config + std::shared_ptr config = std::make_shared(); + std::shared_ptr destination = std::make_shared(); + config->m_destinations.push_back(destination); + if (!address.empty()) destination->m_address = address; + destination->m_amount = amount; + if (!payment_id.empty()) config->m_payment_id = payment_id; + if (!note.empty()) config->m_note = note; + if (!m_recipient_name.empty()) config->m_recipient_name = m_recipient_name; + if (!unknown_parameters.empty()) MWARNING("WARNING in monero_wallet_full::parse_payment_uri: URI contains unknown parameters which are discarded"); // TODO: return unknown parameters? + return config; + } + + bool monero_wallet_full::get_attribute(const std::string& key, std::string& value) const { + assert_not_closed(); + return m_w2->get_attribute(key, value); + } + + void monero_wallet_full::set_attribute(const std::string& key, const std::string& val) { + assert_not_closed(); + m_w2->set_attribute(key, val); + } + + void monero_wallet_full::start_mining(boost::optional num_threads, boost::optional background_mining, boost::optional ignore_battery) { + MTRACE("start_mining()"); + assert_not_closed(); + + // only mine on trusted daemon + if (!m_w2->is_trusted_daemon()) throw std::runtime_error("This command requires a trusted daemon."); + + // set defaults + if (num_threads == boost::none || num_threads.get() == 0) num_threads = 1; // TODO: how to autodetect optimal number of threads which daemon supports? + if (background_mining == boost::none) background_mining = false; + if (ignore_battery == boost::none) ignore_battery = false; + + // validate num threads + uint64_t max_mining_threads_count = (std::max)(tools::get_max_concurrency(), static_cast(2)); + if (num_threads.get() < 1 || max_mining_threads_count < num_threads.get()) { + throw std::runtime_error("The specified number of threads is inappropriate."); + } + + // start mining on daemon + cryptonote::COMMAND_RPC_START_MINING::request daemon_req = AUTO_VAL_INIT(daemon_req); + daemon_req.miner_address = m_w2->get_account().get_public_address_str(m_w2->nettype()); + daemon_req.threads_count = num_threads.get(); + daemon_req.do_background_mining = background_mining.get(); + daemon_req.ignore_battery = ignore_battery.get(); + cryptonote::COMMAND_RPC_START_MINING::response daemon_res; + bool r = m_w2->invoke_http_json("/start_mining", daemon_req, daemon_res); + if (!r || daemon_res.status != CORE_RPC_STATUS_OK) { + throw std::runtime_error("Couldn't start mining due to unknown error."); + } + } + + void monero_wallet_full::stop_mining() { + MTRACE("stop_mining()"); + assert_not_closed(); + cryptonote::COMMAND_RPC_STOP_MINING::request daemon_req; + cryptonote::COMMAND_RPC_STOP_MINING::response daemon_res; + bool r = m_w2->invoke_http_json("/stop_mining", daemon_req, daemon_res); + if (!r || daemon_res.status != CORE_RPC_STATUS_OK) { + throw std::runtime_error("Couldn't stop mining due to unknown error."); + } + } + + uint64_t monero_wallet_full::wait_for_next_block() { + assert_not_closed(); + + // use mutex and condition variable to wait for block + boost::mutex temp; + boost::condition_variable cv; + + // create listener which notifies condition variable when block is added + struct block_notifier : monero_wallet_listener { + boost::mutex* temp; + boost::condition_variable* cv; + uint64_t last_height; + block_notifier(boost::mutex* temp, boost::condition_variable* cv) { this->temp = temp; this->cv = cv; } + void on_new_block(uint64_t height) { + last_height = height; + cv->notify_one(); + } + } block_listener(&temp, &cv); + + // register the listener + add_listener(block_listener); + + // wait until condition variable is notified + boost::mutex::scoped_lock lock(temp); + cv.wait(lock); + + // unregister the listener + remove_listener(block_listener); + + // return last height + return block_listener.last_height; + } + + bool monero_wallet_full::is_multisig_import_needed() const { + assert_not_closed(); + return m_w2->multisig() && m_w2->has_multisig_partial_key_images(); + } + + monero_multisig_info monero_wallet_full::get_multisig_info() const { + assert_not_closed(); + monero_multisig_info info; + info.m_is_multisig = m_w2->multisig(&info.m_is_ready, &info.m_threshold, &info.m_num_participants); + return info; + } + + std::string monero_wallet_full::prepare_multisig() { + assert_not_closed(); + if (m_w2->multisig()) throw std::runtime_error("This wallet is already multisig"); + if (m_w2->watch_only()) throw std::runtime_error("This wallet is view-only and cannot be made multisig"); + m_w2->enable_multisig(true); + return m_w2->get_multisig_first_kex_msg(); + } + + std::string monero_wallet_full::make_multisig(const std::vector& multisig_hexes, int threshold, const std::string& password) { + assert_not_closed(); + if (m_w2->multisig()) throw std::runtime_error("This wallet is already multisig"); + if (m_w2->watch_only()) throw std::runtime_error("This wallet is view-only and cannot be made multisig"); + sync_op_lock op_lock(*this); // do not refresh while making multisig + return m_w2->make_multisig(epee::wipeable_string(password), multisig_hexes, threshold); + } + + monero_multisig_init_result monero_wallet_full::exchange_multisig_keys(const std::vector& multisig_hexes, const std::string& password) { + assert_not_closed(); + + // validate state and args + bool ready; + uint32_t threshold, total; + if (!m_w2->multisig(&ready, &threshold, &total)) throw std::runtime_error("This wallet is not multisig"); + if (ready) throw std::runtime_error("This wallet is multisig, and already finalized"); + if (multisig_hexes.size() + 1 < total) throw std::runtime_error("Needs multisig info from more participants"); + + // do not refresh while exchanging multisig keys + sync_op_lock op_lock(*this); + + // import peer multisig keys and get multisig hex to be shared next round + std::string multisig_hex = m_w2->exchange_multisig_keys(epee::wipeable_string(password), multisig_hexes); + + // build and return exchange result + monero_multisig_init_result result; + m_w2->multisig(&ready); + result.m_multisig_hex = multisig_hex; + if (ready) result.m_address = m_w2->get_account().get_public_address_str(m_w2->nettype()); + return result; + } + + std::string monero_wallet_full::export_multisig_hex() { + assert_not_closed(); + bool ready; + if (!m_w2->multisig(&ready)) throw std::runtime_error("This wallet is not multisig"); + if (!ready) throw std::runtime_error("This wallet is multisig, but not yet finalized"); + sync_op_lock op_lock(*this); // do not refresh while exporting multisig info + return epee::string_tools::buff_to_hex_nodelimer(m_w2->export_multisig()); + } + + int monero_wallet_full::import_multisig_hex(const std::vector& multisig_hexes, const bool refresh_after_import) { + assert_not_closed(); + + // validate state and args + bool ready; + uint32_t threshold, total; + if (!m_w2->multisig(&ready, &threshold, &total)) throw std::runtime_error("This wallet is not multisig"); + if (!ready) throw std::runtime_error("This wallet is multisig, but not yet finalized"); + if (multisig_hexes.size() < threshold - 1) throw std::runtime_error("Needs multisig export info from more participants"); + + // validate and parse each peer multisig hex + std::vector multisig_blobs; + multisig_blobs.resize(multisig_hexes.size()); + for (uint64_t n = 0; n < multisig_hexes.size(); ++n) { + if (!epee::string_tools::parse_hexstr_to_binbuff(multisig_hexes[n], multisig_blobs[n])) { + throw std::runtime_error("Failed to parse hex"); + } + } + + // import peer multisig hex + sync_op_lock op_lock(*this); // do not refresh while importing multisig info + int num_outputs = m_w2->import_multisig(multisig_blobs, refresh_after_import); + + // if daemon is trusted, rescan spent (directly since rescan_spent() locks m_sync_mutex) + if (is_daemon_trusted() && refresh_after_import) m_w2->rescan_spent(); + + // return the number of outputs signed by the given multisig hex + return num_outputs; + } + + monero_multisig_sign_result monero_wallet_full::sign_multisig_tx_hex(const std::string& multisig_tx_hex) { + assert_not_closed(); + + // validate state and args + bool ready; + uint32_t threshold, total; + if (!m_w2->multisig(&ready, &threshold, &total)) throw std::runtime_error("This wallet is not multisig"); + if (!ready) throw std::runtime_error("This wallet is multisig, but not yet finalized"); + + sync_op_lock op_lock(*this); // do not refresh while signing multisig txs + + // validate and parse multisig tx hex as blob + cryptonote::blobdata multisig_tx_blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(multisig_tx_hex, multisig_tx_blob)) { + throw std::runtime_error("Failed to parse hex"); + } + + // validate and parse blob as multisig_tx_set + tools::wallet2::multisig_tx_set ms_tx_set; + if (!m_w2->load_multisig_tx(multisig_tx_blob, ms_tx_set, NULL)) { + throw std::runtime_error("Failed to parse multisig tx data"); + } + + // sign multisig txs + bool success = false; + std::vector tx_hashes; + try { + success = m_w2->sign_multisig_tx(ms_tx_set, tx_hashes); + } catch (const std::exception& e) { + std::string msg = std::string("Failed to sign multisig tx: ") + e.what(); + throw std::runtime_error(msg); + } + if (!success) throw std::runtime_error("Failed to sign multisig tx"); + + // save multisig txs + std::string signed_multisig_tx_hex = epee::string_tools::buff_to_hex_nodelimer(m_w2->save_multisig_tx(ms_tx_set)); + + // build sign result + monero_multisig_sign_result result; + result.m_signed_multisig_tx_hex = signed_multisig_tx_hex; + for (const crypto::hash& tx_hash : tx_hashes) { + result.m_tx_hashes.push_back(epee::string_tools::pod_to_hex(tx_hash)); + } + return result; + } + + std::vector monero_wallet_full::submit_multisig_tx_hex(const std::string& signed_multisig_tx_hex) { + assert_not_closed(); + + // validate state and args + bool ready; + uint32_t threshold, total; + if (!m_w2->multisig(&ready, &threshold, &total)) throw std::runtime_error("This wallet is not multisig"); + if (!ready) throw std::runtime_error("This wallet is multisig, but not yet finalized"); + + sync_op_lock op_lock(*this); // do not refresh while submitting multisig txs + + // validate signed multisig tx hex as blob + cryptonote::blobdata signed_multisig_tx_blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(signed_multisig_tx_hex, signed_multisig_tx_blob)) { + throw std::runtime_error("Failed to parse hex"); + } + + // validate and parse blob as multisig_tx_set + tools::wallet2::multisig_tx_set signed_multisig_tx_set; + if (!m_w2->load_multisig_tx(signed_multisig_tx_blob, signed_multisig_tx_set, NULL)) { + throw std::runtime_error("Failed to parse multisig tx data"); + } + + // ensure sufficient number of participants have signed + if (signed_multisig_tx_set.m_signers.size() < threshold) { + throw std::runtime_error("Not enough signers signed this transaction"); + } + + // commit the transactions + std::vector tx_hashes; + try { + for (auto& pending_tx : signed_multisig_tx_set.m_ptx) { + m_w2->commit_tx(pending_tx); + tx_hashes.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(pending_tx.tx))); + } + } catch (const std::exception& e) { + std::string msg = std::string("Failed to submit multisig tx: ") + e.what(); + throw std::runtime_error(msg); + } + + // return the resulting tx hashes + return tx_hashes; + } + + void monero_wallet_full::change_password(const std::string& old_password, const std::string& new_password) { + MTRACE("change_password(" << "***" << ", ***)"); + assert_not_closed(); + if (!get_path().empty()) { // TODO: skipping password verification of in-memory wallet + if (!m_w2->verify_password(old_password)) throw std::runtime_error("Invalid original password."); + } + sync_op_lock op_lock(*this); // do not store while syncing + m_w2->change_password(m_w2->get_wallet_file(), old_password, new_password); + } + + void monero_wallet_full::move_to(const std::string& path, const std::string& password) { + MTRACE("move_to(" << path << ", ***)"); + assert_not_closed(); + sync_op_lock op_lock(*this); // do not store while syncing + m_w2->store_to(path, password); + } + + void monero_wallet_full::save() { + MTRACE("save()"); + assert_not_closed(); + sync_op_lock op_lock(*this); // do not store while syncing + m_w2->store(); + } + + std::string monero_wallet_full::get_keys_file_buffer(const epee::wipeable_string& password, bool view_only) const { + assert_not_closed(); + sync_op_lock op_lock(*this); // do not serialize keys while syncing + boost::optional keys_file_data = m_w2->get_keys_file_data(password, view_only); + std::string buf; + ::serialization::dump_binary(keys_file_data.get(), buf); + return buf; + } + + std::string monero_wallet_full::get_cache_file_buffer() const { + assert_not_closed(); + sync_op_lock op_lock(*this); // do not serialize cache while syncing + boost::optional cache_file_data = m_w2->get_cache_file_data(); + std::string buf; + ::serialization::dump_binary(cache_file_data.get(), buf); + return buf; + } + + void monero_wallet_full::close(bool save) { + MTRACE("close()"); + if (m_is_closed) return; // closing a closed wallet has no effect + stop_syncing(); // prevent sync thread from starting again and interrupt refresh + if (m_sync_loop_running) { + m_sync_cv.notify_one(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); // TODO: in emscripten, m_sync_cv.notify_one() returns without waiting, so sleep; bug in emscripten upstream llvm? + m_syncing_thread.join(); // join before locking because the sync loop holds the sync mutex + } + boost::lock_guard guarg(m_sync_mutex); // wait for external sync to finish before saving or tearing down + if (save) m_w2->store(); // store directly since save() locks m_sync_mutex + m_w2->stop(); + m_w2->deinit(); + m_w2->callback(nullptr); // unregister listener after sync + m_w2_listener.reset(); // wait for queued notifications + m_is_connected = false; + m_is_closed = true; + } + + // ------------------------------- PRIVATE HELPERS ---------------------------- + + void monero_wallet_full::assert_not_closed() const { + if (m_is_closed) throw std::runtime_error("Wallet is closed"); + } + + // serialize a wallet operation with background sync, like upstream wallet2 consumers (e.g. simplewallet's LOCK_IDLE_SCOPE) + monero_wallet_full::sync_op_lock::sync_op_lock(const monero_wallet_full& wallet) : m_wallet(wallet) { + wallet.m_num_sync_pauses++; // pause background sync + if (wallet.m_background_syncing) wallet.m_w2->stop(); // interrupt background refresh + wallet.m_sync_mutex.lock(); + } + + monero_wallet_full::sync_op_lock::~sync_op_lock() { + m_wallet.m_sync_mutex.unlock(); + m_wallet.m_num_sync_pauses--; // resume background sync + } + + void monero_wallet_full::init_common() { + MTRACE("monero_wallet_full.cpp init_common()"); + + // emscripten config + #if defined(__EMSCRIPTEN__) + m_w2->enable_dns(false); + + // single-threaded if emscripten pthreads not defined + #if !defined(__EMSCRIPTEN_PTHREADS__) + tools::set_max_concurrency(1); // TODO: single-threaded emscripten tools::get_max_concurrency() correctly returns 1 on Safari but 8 on Chrome which fails in common/threadpool constructor + #endif + #endif + + // initialize internal state + m_w2_listener = std::unique_ptr(new wallet2_listener(*this, *m_w2)); + if (get_daemon_connection() == nullptr) m_is_connected = false; + m_is_synced = false; + m_rescan_on_sync = false; + m_syncing_enabled = false; + m_sync_loop_running = false; + m_num_sync_pauses = 0; + m_background_syncing = false; + m_is_closed = false; + } + + std::vector> monero_wallet_full::get_transfers_aux(const monero_transfer_query& query) const { + MTRACE("monero_wallet_full::get_transfers(query)"); + +// // log query +// if (query.m_tx_query != nullptr) { +// if (query.m_tx_query->m_block == nullptr) std::cout << "Transfer query's tx query rooted at [tx]:" << query.m_tx_query->serialize() << std::endl; +// else std::cout << "Transfer query's tx query rooted at [block]: " << query.m_tx_query->m_block->serialize() << std::endl; +// } else std::cout << "Transfer query: " << query.serialize() << std::endl; + + // copy and normalize query + std::shared_ptr _query; + if (query.m_tx_query == nullptr) { + std::shared_ptr query_ptr = std::make_shared(query); // convert to shared pointer for copy // TODO: does this copy unecessarily? copy constructor is not defined + _query = query_ptr->copy(query_ptr, std::make_shared()); + _query->m_tx_query = std::make_shared(); + _query->m_tx_query->m_transfer_query = _query; + } else { + std::shared_ptr tx_query = query.m_tx_query->copy(query.m_tx_query, std::make_shared()); + _query = tx_query->m_transfer_query; + } + std::shared_ptr tx_query = _query->m_tx_query; + + // build parameters for m_w2->get_payments() + uint64_t min_height = tx_query->m_min_height == boost::none ? 0 : *tx_query->m_min_height; + uint64_t max_height = tx_query->m_max_height == boost::none ? CRYPTONOTE_MAX_BLOCK_NUMBER : std::min((uint64_t) CRYPTONOTE_MAX_BLOCK_NUMBER, *tx_query->m_max_height); + if (min_height > 0) min_height--; // TODO monero-project: wallet2::get_payments() m_min_height is exclusive, so manually offset to match intended range (issues #5751, #5598) + boost::optional account_index = boost::none; + if (_query->m_account_index != boost::none) account_index = *_query->m_account_index; + std::set subaddress_indices; + for (int i = 0; i < _query->m_subaddress_indices.size(); i++) { + subaddress_indices.insert(_query->m_subaddress_indices[i]); + } + + // translate from monero_tx_query to in, out, pending, pool, failed terminology used by monero-wallet-rpc + bool can_be_confirmed = !gen_utils::bool_equals(false, tx_query->m_is_confirmed) && !gen_utils::bool_equals(true, tx_query->m_in_tx_pool) && !gen_utils::bool_equals(true, tx_query->m_is_failed) && !gen_utils::bool_equals(false, tx_query->m_is_relayed); + bool can_be_in_tx_pool = !gen_utils::bool_equals(true, tx_query->m_is_confirmed) && !gen_utils::bool_equals(false, tx_query->m_in_tx_pool) && !gen_utils::bool_equals(true, tx_query->m_is_failed) && tx_query->get_height() == boost::none && tx_query->m_min_height == boost::none && !gen_utils::bool_equals(false, tx_query->m_is_locked); + bool can_be_incoming = !gen_utils::bool_equals(false, _query->m_is_incoming) && !gen_utils::bool_equals(true, _query->is_outgoing()) && !gen_utils::bool_equals(true, _query->m_has_destinations); + bool can_be_outgoing = !gen_utils::bool_equals(false, _query->is_outgoing()) && !gen_utils::bool_equals(true, _query->m_is_incoming); + bool is_in = can_be_incoming && can_be_confirmed; + bool is_out = can_be_outgoing && can_be_confirmed; + bool is_pending = can_be_outgoing && can_be_in_tx_pool; + bool is_pool = can_be_incoming && can_be_in_tx_pool; + bool is_failed = !gen_utils::bool_equals(false, tx_query->m_is_failed) && !gen_utils::bool_equals(true, tx_query->m_is_confirmed) && !gen_utils::bool_equals(true, tx_query->m_in_tx_pool) && !gen_utils::bool_equals(false, tx_query->m_is_locked); + + // check if fetching pool txs contradicted by configuration + if (tx_query->m_in_tx_pool != boost::none && tx_query->m_in_tx_pool.get() && !can_be_in_tx_pool) { + monero_utils::free(tx_query); + throw std::runtime_error("Cannot fetch pool transactions because it contradicts configuration"); + } + + // cache unique txs and blocks + uint64_t height = get_height(); + std::map> tx_map; + std::map> block_map; + + // get unconfirmed or failed outgoing transfers + if (is_pending || is_failed) { + std::list> upayments; + m_w2->get_unconfirmed_payments_out(upayments, account_index, subaddress_indices); + for (std::list>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) { + if (!tx_query->m_hashes.empty() && std::find(tx_query->m_hashes.begin(), tx_query->m_hashes.end(), epee::string_tools::pod_to_hex(i->first)) == tx_query->m_hashes.end()) continue; // skip if hash filtered + std::shared_ptr tx = build_tx_with_outgoing_transfer_unconfirmed(*m_w2, i->first, i->second); + if (tx_query->m_is_failed != boost::none && tx_query->m_is_failed.get() != tx->m_is_failed.get()) continue; // skip if failure filtered + monero_utils::merge_tx(tx, tx_map, block_map); + } + } + + // get unconfirmed incoming transfers + if (is_pool) { + + // update pool state TODO monero-project: this should be encapsulated in wallet when unconfirmed transfers queried + std::vector> process_txs; + m_w2->update_pool_state(process_txs); + if (!process_txs.empty()) m_w2->process_pool_state(process_txs); + + std::list> payments; + m_w2->get_unconfirmed_payments(payments, account_index, subaddress_indices); + for (std::list>::const_iterator i = payments.begin(); i != payments.end(); ++i) { + if (!tx_query->m_hashes.empty() && std::find(tx_query->m_hashes.begin(), tx_query->m_hashes.end(), epee::string_tools::pod_to_hex(i->second.m_pd.m_tx_hash)) == tx_query->m_hashes.end()) continue; // skip if hash filtered + std::shared_ptr tx = build_tx_with_incoming_transfer_unconfirmed(*m_w2, height, i->first, i->second); + monero_utils::merge_tx(tx, tx_map, block_map); + } + } + + // get confirmed incoming transfers + if (is_in) { + std::list> payments; + m_w2->get_payments(payments, min_height, max_height, account_index, subaddress_indices); + for (std::list>::const_iterator i = payments.begin(); i != payments.end(); ++i) { + if (!tx_query->m_hashes.empty() && std::find(tx_query->m_hashes.begin(), tx_query->m_hashes.end(), epee::string_tools::pod_to_hex(i->second.m_tx_hash)) == tx_query->m_hashes.end()) continue; // skip if hash filtered + std::shared_ptr tx = build_tx_with_incoming_transfer(*m_w2, height, i->first, i->second); + monero_utils::merge_tx(tx, tx_map, block_map); + } + } + + // get confirmed outgoing transfers + if (is_out) { + std::list> payments; + m_w2->get_payments_out(payments, min_height, max_height, account_index, subaddress_indices); + for (std::list>::const_iterator i = payments.begin(); i != payments.end(); ++i) { + if (!tx_query->m_hashes.empty() && std::find(tx_query->m_hashes.begin(), tx_query->m_hashes.end(), epee::string_tools::pod_to_hex(i->first)) == tx_query->m_hashes.end()) continue; // skip if hash filtered + std::shared_ptr tx = build_tx_with_outgoing_transfer(*m_w2, height, i->first, i->second); + monero_utils::merge_tx(tx, tx_map, block_map); + } + } + + // sort txs by block height + std::vector> txs; + for (std::map>::const_iterator tx_iter = tx_map.begin(); tx_iter != tx_map.end(); tx_iter++) { + txs.push_back(tx_iter->second); + } + sort(txs.begin(), txs.end(), monero_utils::tx_height_less_than); + + // filter transfers + std::vector> transfers; + for (const std::shared_ptr& tx : txs) { + + // tx is not incoming/outgoing unless already set + if (tx->m_is_incoming == boost::none) tx->m_is_incoming = false; + if (tx->m_is_outgoing == boost::none) tx->m_is_outgoing = false; + + // sort incoming transfers + sort(tx->m_incoming_transfers.begin(), tx->m_incoming_transfers.end(), monero_utils::incoming_transfer_before); + + // collect queried transfers, erase if excluded + for (const std::shared_ptr& transfer : tx->filter_transfers(*_query)) transfers.push_back(transfer); + + // remove excluded txs from block + if (tx->m_block != nullptr && tx->m_outgoing_transfer == nullptr && tx->m_incoming_transfers.empty()) { + tx->m_block->m_txs.erase(std::remove(tx->m_block->m_txs.begin(), tx->m_block->m_txs.end(), tx), tx->m_block->m_txs.end()); // TODO, no way to use const_iterator? + } + } + MTRACE("monero_wallet_full.cpp get_transfers() returning " << transfers.size() << " transfers"); + + // free query and return transfers + monero_utils::free(tx_query); + return transfers; + } + + std::vector> monero_wallet_full::get_outputs_aux(const monero_output_query& query) const { + MTRACE("monero_wallet_full::get_outputs_aux(query)"); + +// // log query +// if (query.m_tx_query != nullptr) { +// if (query.m_tx_query->m_block == nullptr) std::cout << "Output query's tx query rooted at [tx]:" << query.m_tx_query->serialize() << std::endl; +// else std::cout << "Output query's tx query rooted at [block]: " << query.m_tx_query->m_block->serialize() << std::endl; +// } else std::cout << "Output query: " << query.serialize() << std::endl; + + // copy and normalize query + std::shared_ptr _query; + if (query.m_tx_query == nullptr) { + std::shared_ptr query_ptr = std::make_shared(query); // convert to shared pointer for copy + _query = query_ptr->copy(query_ptr, std::make_shared()); + } else { + std::shared_ptr tx_query = query.m_tx_query->copy(query.m_tx_query, std::make_shared()); + if (query.m_tx_query->m_output_query != nullptr && query.m_tx_query->m_output_query.get() == &query) { + _query = tx_query->m_output_query; + } else { + if (query.m_tx_query->m_output_query != nullptr) throw std::runtime_error("Output query's tx query must be a circular reference or null"); + std::shared_ptr query_ptr = std::make_shared(query); // convert query to shared pointer for copy + _query = query_ptr->copy(query_ptr, std::make_shared()); + _query->m_tx_query = tx_query; + } + } + if (_query->m_tx_query == nullptr) _query->m_tx_query = std::make_shared(); + std::shared_ptr tx_query = _query->m_tx_query; + + // get output data from wallet2 + tools::wallet2::transfer_container outputs_w2; + m_w2->get_transfers(outputs_w2); + + // cache unique txs and blocks + std::map> tx_map; + std::map> block_map; + for (const auto& output_w2 : outputs_w2) { + // TODO: skip tx building if m_w2 output excluded by indices, etc + std::shared_ptr tx = build_tx_with_vout(*m_w2, output_w2); + monero_utils::merge_tx(tx, tx_map, block_map); + } + + // sort txs by block height + std::vector> txs ; + for (std::map>::const_iterator tx_iter = tx_map.begin(); tx_iter != tx_map.end(); tx_iter++) { + txs.push_back(tx_iter->second); + } + sort(txs.begin(), txs.end(), monero_utils::tx_height_less_than); + + // filter and return outputs + std::vector> outputs; + for (const std::shared_ptr& tx : txs) { + + // sort outputs + sort(tx->m_outputs.begin(), tx->m_outputs.end(), monero_utils::vout_before); + + // collect queried outputs, erase if excluded + for (const std::shared_ptr& output : tx->filter_outputs_wallet(*_query)) outputs.push_back(output); + + // remove txs without outputs + if (tx->m_outputs.empty() && tx->m_block != nullptr) tx->m_block->m_txs.erase(std::remove(tx->m_block->m_txs.begin(), tx->m_block->m_txs.end(), tx), tx->m_block->m_txs.end()); // TODO, no way to use const_iterator? + } + + // free query and return outputs + monero_utils::free(tx_query); + return outputs; + } + + // private helper to initialize subaddresses using transfer details + std::vector monero_wallet_full::get_subaddresses_aux(const uint32_t account_idx, const std::vector& subaddress_indices, const std::vector& transfers) const { + std::vector subaddresses; + + // get balances per subaddress as maps + std::map balance_per_subaddress = m_w2->balance_per_subaddress(account_idx, STRICT_); + std::map>> unlocked_balance_per_subaddress = m_w2->unlocked_balance_per_subaddress(account_idx, STRICT_); + + // get all indices if no indices given + std::vector subaddress_indices_req; + if (subaddress_indices.empty()) { + for (uint32_t subaddress_idx = 0; subaddress_idx < m_w2->get_num_subaddresses(account_idx); subaddress_idx++) { + subaddress_indices_req.push_back(subaddress_idx); + } + } else { + subaddress_indices_req = subaddress_indices; + } + + // initialize subaddresses at indices + for (uint32_t subaddressIndicesIdx = 0; subaddressIndicesIdx < subaddress_indices_req.size(); subaddressIndicesIdx++) { + monero_subaddress subaddress; + subaddress.m_account_index = account_idx; + uint32_t subaddress_idx = subaddress_indices_req.at(subaddressIndicesIdx); + subaddress.m_index = subaddress_idx; + subaddress.m_address = get_address(account_idx, subaddress_idx); + std::string label = m_w2->get_subaddress_label({account_idx, subaddress_idx}); + if (!label.empty()) subaddress.m_label = label; + auto iter1 = balance_per_subaddress.find(subaddress_idx); + subaddress.m_balance = iter1 == balance_per_subaddress.end() ? 0 : iter1->second; + auto iter2 = unlocked_balance_per_subaddress.find(subaddress_idx); + subaddress.m_unlocked_balance = iter2 == unlocked_balance_per_subaddress.end() ? 0 : iter2->second.first; + cryptonote::subaddress_index index = {account_idx, subaddress_idx}; + subaddress.m_num_unspent_outputs = count_if(transfers.begin(), transfers.end(), [&](const tools::wallet2::transfer_details& td) { return !td.m_spent && td.m_subaddr_index == index; }); + subaddress.m_is_used = find_if(transfers.begin(), transfers.end(), [&](const tools::wallet2::transfer_details& td) { return td.m_subaddr_index == index; }) != transfers.end(); + subaddress.m_num_blocks_to_unlock = iter1 == balance_per_subaddress.end() ? 0 : iter2->second.second.first; + subaddresses.push_back(subaddress); + } + + return subaddresses; + } + + void monero_wallet_full::run_sync_loop() { + if (m_sync_loop_running) return; // only run one loop at a time + m_sync_loop_running = true; + + // start sync loop thread + // TODO: use global threadpool, background sync wasm wallet in c++ thread + m_syncing_thread = boost::thread([this]() { + + // sync while enabled and not paused by a wallet operation + while (m_syncing_enabled) { + if (m_num_sync_pauses == 0) { + try { lock_and_sync(boost::none, true); } + catch (std::exception const& e) { std::cout << "monero_wallet_full failed to background synchronize: " << e.what() << std::endl; } + catch (...) { std::cout << "monero_wallet_full failed to background synchronize" << std::endl; } + } + + // only wait if syncing still enabled + if (m_syncing_enabled) { + boost::mutex::scoped_lock lock(m_syncing_mutex); + boost::posix_time::milliseconds wait_for_ms(m_syncing_interval.load()); + m_sync_cv.timed_wait(lock, wait_for_ms); + } + } + + m_sync_loop_running = false; + }); + } + + monero_sync_result monero_wallet_full::lock_and_sync(boost::optional start_height, bool background) { + boost::lock_guard guarg(m_sync_mutex); // synchronize sync() and syncAsync() + assert_not_closed(); // wallet can be closed while waiting for the lock + monero_sync_result result; + result.m_num_blocks_fetched = 0; + result.m_received_money = false; + if (background && m_num_sync_pauses > 0) return result; // skip background sync while a wallet operation waits + bool rescan = m_rescan_on_sync.exchange(false); + m_background_syncing = background; // set under mutex so interrupts only target background sync + try { + do { + // skip if daemon is not connected or synced + if (m_is_connected && is_daemon_synced()) { + + // rescan blockchain if requested + if (rescan) m_w2->rescan_blockchain(false); + + // sync wallet + result = sync_aux(start_height); + } + } while (!rescan && (rescan = m_rescan_on_sync.exchange(false))); // repeat if not rescanned and rescan was requested + } catch (...) { + m_background_syncing = false; + throw; + } + m_background_syncing = false; + return result; + } + + monero_sync_result monero_wallet_full::sync_aux(boost::optional start_height) { + MTRACE("sync_aux()"); + + // determine sync start height + uint64_t sync_start_height = start_height == boost::none ? std::max(get_height(), get_restore_height()) : *start_height; + if (sync_start_height < get_restore_height()) set_restore_height(sync_start_height); // TODO monero-project: start height processed > requested start height unless sync height manually set + + // notify listeners of sync start + m_w2_listener->on_sync_start(sync_start_height); + monero_sync_result result; + + // attempt to refresh wallet2 which may throw exception + try { + m_w2->refresh(m_w2->is_trusted_daemon(), sync_start_height, result.m_num_blocks_fetched, result.m_received_money, true); + if (!m_is_synced) m_is_synced = true; + m_w2_listener->update_listening(); // cannot unregister during sync which would segfault + } catch (std::exception& e) { + m_w2_listener->on_sync_end(); // signal end of sync to reset listener's start and end heights + throw; + } + + // notify listeners of sync end and check for updated funds + m_w2_listener->on_sync_end(); + return result; + } +} diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_full.h b/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_full.h new file mode 100644 index 0000000..c501557 --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_full.h @@ -0,0 +1,311 @@ +/** + * Copyright (c) woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "monero_wallet.h" +#include "wallet/wallet2.h" + +/** + * Implements a monero_wallet.h by wrapping monero-project's wallet2. + */ +namespace monero { + + // -------------------------------- LISTENERS ------------------------------- + + // forward declaration of internal wallet2 listener + struct wallet2_listener; + + // --------------------------- STATIC WALLET UTILS -------------------------- + + /** + * Monero wallet implementation which uses monero-project's wallet2. + */ + class monero_wallet_full : public monero_wallet { + + public: + + /** + * Indicates if a wallet exists at the given path. + * + * @param path is the path to check for a wallet + * @return true if a wallet exists at the given path, false otherwise + */ + static bool wallet_exists(const std::string& path); + + /** + * Open an existing wallet from disk. + * + * @param path is the path to the wallet file to open + * @param password is the password of the wallet file to open + * @param network_type is the wallet's network type + * @param regtest enable regtest + * @return a pointer to the wallet instance + */ + static monero_wallet_full* open_wallet(const std::string& path, const std::string& password, const monero_network_type network_type, bool regtest = false); + + /** + * Open an in-memory wallet from existing data buffers. + * + * @param password is the password of the wallet file to open + * @param network_type is the wallet's network type + * @param keys_data contains the contents of the ".keys" file + * @param cache_data contains the contents of the wallet cache file (no extension) + * @param daemon_connection is connection information to a daemon (default = an unconnected wallet) + * @param http_client_factory allows use of custom http clients + * @param regtest enable regtest + * @return a pointer to the wallet instance + */ + static monero_wallet_full* open_wallet_data(const std::string& password, const monero_network_type, const std::string& keys_data, const std::string& cache_data, const std::shared_ptr& daemon_connection = std::make_shared(), std::unique_ptr http_client_factory = nullptr, bool regtest = false); + + /** + * Create a new wallet with the given configuration. + * + * @param config is the wallet configuration + * @param http_client_factory allows use of custom http clients + * @return a pointer to the wallet instance + */ + static monero_wallet_full* create_wallet(const monero_wallet_config& config, std::unique_ptr http_client_factory = nullptr); + + /** + * Get a list of available languages for the wallet's seed. + * + * @return the available languages for the wallet's seed + */ + static std::vector get_seed_languages(); + + // ----------------------------- WALLET METHODS ----------------------------- + + /** + * Destruct the wallet. + */ + ~monero_wallet_full(); + + /** + * Supported wallet methods. + */ + bool is_view_only() const override { return m_w2->watch_only(); } + void set_daemon_connection(const std::string& uri, const std::string& username = "", const std::string& password = "", const std::string& proxy_uri = "", const boost::optional& is_trusted = boost::none) override; + void set_daemon_connection(const std::shared_ptr& connection, const boost::optional& is_trusted = boost::none) override; + std::shared_ptr get_daemon_connection() const override; + bool is_connected_to_daemon() const override; + bool is_daemon_synced() const override; + bool is_daemon_trusted() const override; + bool is_synced() const override; + monero_version get_version() const override; + std::string get_path() const override; + monero_network_type get_network_type() const override; + std::string get_seed() const override; + std::string get_seed_language() const override; + std::string get_public_view_key() const override; + std::string get_private_view_key() const override; + std::string get_public_spend_key() const override; + std::string get_private_spend_key() const override; + std::string get_address(const uint32_t account_idx, const uint32_t subaddress_idx) const override; + monero_subaddress get_address_index(const std::string& address) const override; + monero_integrated_address get_integrated_address(const std::string& standard_address = "", const std::string& payment_id = "") const override; + monero_integrated_address decode_integrated_address(const std::string& integrated_address) const override; + uint64_t get_height() const override; + uint64_t get_restore_height() const override; + void set_restore_height(uint64_t restore_height) override; + uint64_t get_daemon_height() const override; + uint64_t get_daemon_max_peer_height() const override; + uint64_t get_height_by_date(uint16_t year, uint8_t month, uint8_t day) const override; + void add_listener(monero_wallet_listener& listener) override; + void remove_listener(monero_wallet_listener& listener) override; + std::set get_listeners() override; + monero_sync_result sync() override; + monero_sync_result sync(monero_wallet_listener& listener) override; + monero_sync_result sync(uint64_t start_height) override; + monero_sync_result sync(uint64_t start_height, monero_wallet_listener& listener) override; + void start_syncing(uint64_t sync_period_in_ms) override; + void stop_syncing() override; + void scan_txs(const std::vector& tx_hashes) override; + void rescan_spent() override; + void rescan_blockchain() override; + uint64_t get_balance() const override; + uint64_t get_balance(uint32_t account_idx) const override; + uint64_t get_balance(uint32_t account_idx, uint32_t subaddress_idx) const override; + uint64_t get_unlocked_balance() const override; + uint64_t get_unlocked_balance(uint32_t account_idx) const override; + uint64_t get_unlocked_balance(uint32_t account_idx, uint32_t subaddress_idx) const override; + std::vector get_accounts(bool include_subaddresses, const std::string& tag) const override; + monero_account get_account(const uint32_t account_idx, bool include_subaddresses) const override; + monero_account create_account(const std::string& label = "") override; + std::vector get_subaddresses(const uint32_t account_idx, const std::vector& subaddress_indices) const override; + monero_subaddress create_subaddress(uint32_t account_idx, const std::string& label = "") override; + void set_subaddress_label(uint32_t account_idx, uint32_t subaddress_idx, const std::string& label = "") override; + std::vector> get_txs() const override; + std::vector> get_txs(const monero_tx_query& query) const override; + std::vector> get_transfers(const monero_transfer_query& query) const override; + std::vector> get_outputs(const monero_output_query& query) const override; + std::string export_outputs(bool all = false) const override; + int import_outputs(const std::string& outputs_hex) override; + std::shared_ptr export_key_images(bool all = false) const override; + std::shared_ptr import_key_images(const std::vector>& key_images, uint64_t offset = 0) override; + void freeze_output(const std::string& key_image) override; + void thaw_output(const std::string& key_image) override; + bool is_output_frozen(const std::string& key_image) override; + monero_tx_priority get_default_fee_priority() const override; + std::vector> create_txs(const monero_tx_config& config) override; + std::vector> sweep_unlocked(const monero_tx_config& config) override; + std::shared_ptr sweep_output(const monero_tx_config& config) override; + std::vector> sweep_dust(bool relay = false) override; + std::vector relay_txs(const std::vector& tx_metadatas) override; + monero_tx_set describe_tx_set(const monero_tx_set& tx_set) override; + monero_tx_set sign_txs(const std::string& unsigned_tx_hex) override; + std::vector submit_txs(const std::string& signed_tx_hex) override; + std::string sign_message(const std::string& msg, monero_message_signature_type signature_type, uint32_t account_idx = 0, uint32_t subaddress_idx = 0) const override; + monero_message_signature_result verify_message(const std::string& msg, const std::string& address, const std::string& signature) const override; + std::string get_tx_key(const std::string& tx_hash) const override; + std::shared_ptr check_tx_key(const std::string& tx_hash, const std::string& txKey, const std::string& address) const override; + std::string get_tx_proof(const std::string& tx_hash, const std::string& address, const std::string& message) const override; + std::shared_ptr check_tx_proof(const std::string& tx_hash, const std::string& address, const std::string& message, const std::string& signature) const override; + std::string get_spend_proof(const std::string& tx_hash, const std::string& message) const override; + bool check_spend_proof(const std::string& tx_hash, const std::string& message, const std::string& signature) const override; + std::string get_reserve_proof_wallet(const std::string& message) const override; + std::string get_reserve_proof_account(uint32_t account_idx, uint64_t amount, const std::string& message) const override; + std::shared_ptr check_reserve_proof(const std::string& address, const std::string& message, const std::string& signature) const override; + std::string get_tx_note(const std::string& tx_hash) const override; + std::vector get_tx_notes(const std::vector& tx_hashes) const override; + void set_tx_note(const std::string& tx_hash, const std::string& note) override; + void set_tx_notes(const std::vector& tx_hashes, const std::vector& notes) override; + std::vector get_address_book_entries(const std::vector& indices) const override; + uint64_t add_address_book_entry(const std::string& address, const std::string& description) override; + void edit_address_book_entry(uint64_t index, bool set_address, const std::string& address, bool set_description, const std::string& description) override; + void delete_address_book_entry(uint64_t index) override; + std::string get_payment_uri(const monero_tx_config& config) const override; + std::shared_ptr parse_payment_uri(const std::string& uri) const override; + bool get_attribute(const std::string& key, std::string& value) const override; + void set_attribute(const std::string& key, const std::string& val) override; + void start_mining(boost::optional num_threads, boost::optional background_mining, boost::optional ignore_battery) override; + void stop_mining() override; + uint64_t wait_for_next_block() override; + bool is_multisig_import_needed() const override; + monero_multisig_info get_multisig_info() const override; + std::string prepare_multisig() override; + std::string make_multisig(const std::vector& multisig_hexes, int threshold, const std::string& password) override; + monero_multisig_init_result exchange_multisig_keys(const std::vector& multisig_hexes, const std::string& password) override; + std::string export_multisig_hex() override; + int import_multisig_hex(const std::vector& multisig_hexes, const bool refresh_after_import = true) override; + monero_multisig_sign_result sign_multisig_tx_hex(const std::string& multisig_tx_hex) override; + std::vector submit_multisig_tx_hex(const std::string& signed_multisig_tx_hex) override; + void change_password(const std::string& old_password, const std::string& new_password) override; + void move_to(const std::string& path, const std::string& password) override; + void save() override; + void close(bool save = false) override; + bool is_closed() const override { return m_is_closed; } + + /** + * Wallet import and export using buffers and not the file system. + */ + std::string get_keys_file_buffer(const epee::wipeable_string& password, bool view_only) const; + std::string get_cache_file_buffer() const; + + // --------------------------------- PROTECTED -------------------------------- + + protected: + std::unique_ptr m_w2; // internal wallet implementation + + void init_common(); + + // ---------------------------------- PRIVATE --------------------------------- + + private: + friend struct wallet2_listener; + std::unique_ptr m_w2_listener; // internal wallet implementation listener + std::set m_listeners; // external wallet listeners + std::atomic m_is_closed; + + static monero_wallet_full* create_wallet_from_seed(monero_wallet_config& config, std::unique_ptr http_client_factory); + static monero_wallet_full* create_wallet_from_keys(monero_wallet_config& config, std::unique_ptr http_client_factory); + static monero_wallet_full* create_wallet_random(monero_wallet_config& config, std::unique_ptr http_client_factory); + + std::vector get_subaddresses_aux(uint32_t account_idx, const std::vector& subaddress_indices, const std::vector& transfers) const; + std::vector> get_transfers_aux(const monero_transfer_query& query) const; + std::vector> get_outputs_aux(const monero_output_query& query) const; + std::vector> sweep_account(const monero_tx_config& config); // sweeps unlocked funds within an account; private helper to sweep_unlocked() + + void assert_not_closed() const; + + // serializes a wallet operation with background sync: pauses the sync loop, interrupts background refresh, and locks m_sync_mutex + struct sync_op_lock { + sync_op_lock(const monero_wallet_full& wallet); + sync_op_lock(const sync_op_lock&) = delete; + ~sync_op_lock(); + const monero_wallet_full& m_wallet; + }; + + // blockchain sync management + mutable std::atomic m_is_synced; // whether or not wallet is synced + mutable std::atomic m_is_connected; // cache connection status to avoid unecessary RPC calls + boost::condition_variable m_sync_cv; // to make sync threads woke + mutable boost::mutex m_sync_mutex; // synchronize sync and other wallet operations + mutable std::atomic m_num_sync_pauses; // number of operations pausing background sync + std::atomic m_background_syncing; // whether or not a background sync pass is in progress + std::atomic m_rescan_on_sync; // whether or not to rescan on sync + std::atomic m_syncing_enabled; // whether or not auto sync is enabled + std::atomic m_sync_loop_running; // whether or not the syncing thread is shut down + std::atomic m_syncing_interval; // auto sync loop interval in milliseconds + boost::thread m_syncing_thread; // thread for auto sync loop + boost::mutex m_syncing_mutex; // synchronize auto sync loop + void run_sync_loop(); // run the sync loop in a thread + monero_sync_result lock_and_sync(boost::optional start_height = boost::none, bool background = false); // internal function to synchronize request to sync and rescan + monero_sync_result sync_aux(boost::optional start_height = boost::none); // internal function to immediately block, sync, and report progress + }; +} diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_keys.cpp b/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_keys.cpp new file mode 100644 index 0000000..aececae --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_keys.cpp @@ -0,0 +1,320 @@ +/** + * Copyright (c) woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ + +#include "monero_wallet_keys.h" + +#include "utils/monero_utils.h" +#include +#include +#include "mnemonics/electrum-words.h" +#include "mnemonics/english.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "cryptonote_basic/cryptonote_basic_impl.h" +#include "string_tools.h" +#include "device/device.hpp" + +using namespace epee; +using namespace tools; +using namespace crypto; + +/** + * Public library interface. + */ +namespace monero { + + // ---------------------------- WALLET MANAGEMENT --------------------------- + + monero_wallet_keys* monero_wallet_keys::create_wallet_random(const monero_wallet_config& config) { + + // validate and normalize config + monero_wallet_config config_normalized = config.copy(); + if (config_normalized.m_network_type == boost::none) throw std::runtime_error("Must provide wallet network type"); + if (config_normalized.m_language == boost::none || config_normalized.m_language.get().empty()) config_normalized.m_language = "English"; + if (!monero_utils::is_valid_language(config_normalized.m_language.get())) throw std::runtime_error("Unknown language: " + config_normalized.m_language.get()); + + // initialize random wallet account + monero_wallet_keys* wallet = new monero_wallet_keys(); + crypto::secret_key spend_key_sk = wallet->m_account.generate(); + + // initialize remaining wallet + wallet->m_network_type = config_normalized.m_network_type.get(); + wallet->m_language = config_normalized.m_language.get(); + epee::wipeable_string wipeable_mnemonic; + if (!crypto::ElectrumWords::bytes_to_words(spend_key_sk, wipeable_mnemonic, wallet->m_language)) { + throw std::runtime_error("Failed to create mnemonic from private spend key for language: " + std::string(wallet->m_language)); + } + wallet->m_seed = std::string(wipeable_mnemonic.data(), wipeable_mnemonic.size()); + wallet->init_common(); + + return wallet; + } + + monero_wallet_keys* monero_wallet_keys::create_wallet_from_seed(const monero_wallet_config& config) { + + // validate config + if (config.m_is_multisig != boost::none && config.m_is_multisig.get()) throw std::runtime_error("Restoring from multisig seed not supported"); + if (config.m_network_type == boost::none) throw std::runtime_error("Must provide wallet network type"); + if (config.m_seed == boost::none || config.m_seed.get().empty()) throw std::runtime_error("Must provide wallet seed"); + + // validate mnemonic and get recovery key and language + crypto::secret_key spend_key_sk; + std::string language; + bool is_valid = crypto::ElectrumWords::words_to_bytes(config.m_seed.get(), spend_key_sk, language); + if (!is_valid) throw std::runtime_error("Invalid mnemonic"); + if (language == crypto::ElectrumWords::old_language_name) language = Language::English().get_language_name(); + + // apply offset if given + if (config.m_seed_offset != boost::none && !config.m_seed_offset.get().empty()) spend_key_sk = cryptonote::decrypt_key(spend_key_sk, config.m_seed_offset.get()); + + // initialize wallet account + monero_wallet_keys* wallet = new monero_wallet_keys(); + wallet->m_account = cryptonote::account_base{}; + wallet->m_account.generate(spend_key_sk, true, false); + + // initialize remaining wallet + wallet->m_network_type = config.m_network_type.get(); + wallet->m_language = language; + epee::wipeable_string wipeable_mnemonic; + if (!crypto::ElectrumWords::bytes_to_words(spend_key_sk, wipeable_mnemonic, wallet->m_language)) { + throw std::runtime_error("Failed to create mnemonic from private spend key for language: " + std::string(wallet->m_language)); + } + wallet->m_seed = std::string(wipeable_mnemonic.data(), wipeable_mnemonic.size()); + wallet->init_common(); + + return wallet; + } + + monero_wallet_keys* monero_wallet_keys::create_wallet_from_keys(const monero_wallet_config& config) { + + // validate and normalize config + monero_wallet_config config_normalized = config.copy(); + if (config.m_network_type == boost::none) throw std::runtime_error("Must provide wallet network type"); + if (config.m_language == boost::none || config_normalized.m_language.get().empty()) config_normalized.m_language = "English"; + if (config.m_private_spend_key == boost::none) config_normalized.m_private_spend_key = std::string(""); + if (config.m_private_view_key == boost::none) config_normalized.m_private_view_key = std::string(""); + if (!monero_utils::is_valid_language(config_normalized.m_language.get())) throw std::runtime_error("Unknown language: " + config_normalized.m_language.get()); + + // parse and validate private spend key + crypto::secret_key spend_key_sk; + bool has_spend_key = false; + if (!config_normalized.m_private_spend_key.get().empty()) { + cryptonote::blobdata spend_key_data; + if (!epee::string_tools::parse_hexstr_to_binbuff(config.m_private_spend_key.get(), spend_key_data) || spend_key_data.size() != sizeof(crypto::secret_key)) { + throw std::runtime_error("failed to parse secret spend key"); + } + has_spend_key = true; + spend_key_sk = *reinterpret_cast(spend_key_data.data()); + } + + // parse and validate private view key + bool has_view_key = true; + crypto::secret_key view_key_sk; + if (config_normalized.m_private_view_key.get().empty()) { + if (has_spend_key) has_view_key = false; + else throw std::runtime_error("Neither spend key nor view key supplied"); + } + if (has_view_key) { + cryptonote::blobdata view_key_data; + if (!epee::string_tools::parse_hexstr_to_binbuff(config_normalized.m_private_view_key.get(), view_key_data) || view_key_data.size() != sizeof(crypto::secret_key)) { + throw std::runtime_error("failed to parse secret view key"); + } + view_key_sk = *reinterpret_cast(view_key_data.data()); + } + + // parse and validate address + cryptonote::address_parse_info address_info; + if (config_normalized.m_primary_address.get().empty()) { + if (has_view_key) throw std::runtime_error("must provide address if providing private view key"); + } else { + if (!get_account_address_from_str(address_info, static_cast(config_normalized.m_network_type.get()), config_normalized.m_primary_address.get())) throw std::runtime_error("failed to parse address"); + + // check the spend and view keys match the given address + crypto::public_key pkey; + if (has_spend_key) { + if (!crypto::secret_key_to_public_key(spend_key_sk, pkey)) throw std::runtime_error("failed to verify secret spend key"); + if (address_info.address.m_spend_public_key != pkey) throw std::runtime_error("spend key does not match address"); + } + if (has_view_key) { + if (!crypto::secret_key_to_public_key(view_key_sk, pkey)) throw std::runtime_error("failed to verify secret view key"); + if (address_info.address.m_view_public_key != pkey) throw std::runtime_error("view key does not match address"); + } + } + + // initialize wallet account + monero_wallet_keys* wallet = new monero_wallet_keys(); + if (has_spend_key && has_view_key) { + wallet->m_account.create_from_keys(address_info.address, spend_key_sk, view_key_sk); + } else if (has_spend_key) { + wallet->m_account.generate(spend_key_sk, true, false); + } else { + wallet->m_account.create_from_viewkey(address_info.address, view_key_sk); + } + + // initialize remaining wallet + wallet->m_is_view_only = !has_spend_key; + wallet->m_network_type = config_normalized.m_network_type.get(); + if (!config_normalized.m_private_spend_key.get().empty()) { + wallet->m_language = config_normalized.m_language.get(); + epee::wipeable_string wipeable_mnemonic; + if (!crypto::ElectrumWords::bytes_to_words(spend_key_sk, wipeable_mnemonic, wallet->m_language)) { + throw std::runtime_error("Failed to create mnemonic from private spend key for language: " + std::string(wallet->m_language)); + } + wallet->m_seed = std::string(wipeable_mnemonic.data(), wipeable_mnemonic.size()); + } + wallet->init_common(); + + return wallet; + } + + std::vector monero_wallet_keys::get_seed_languages() { + std::vector languages; + crypto::ElectrumWords::get_language_list(languages, true); // TODO: support getting names in language + return languages; + } + + // ----------------------------- WALLET METHODS ----------------------------- + + monero_wallet_keys::~monero_wallet_keys() { + MTRACE("~monero_wallet_keys()"); + close(); + } + + monero_version monero_wallet_keys::get_version() const { + monero_version version; + version.m_number = 65552; // same as monero-wallet-rpc v0.15.0.1 release + version.m_is_release = false; // TODO: could pull from MONERO_VERSION_IS_RELEASE in version.cpp + return version; + } + + std::string monero_wallet_keys::get_address(uint32_t account_idx, uint32_t subaddress_idx) const { + hw::device &hwdev = m_account.get_device(); + cryptonote::subaddress_index index{account_idx, subaddress_idx}; + cryptonote::account_public_address address = hwdev.get_subaddress(m_account.get_keys(), index); + return cryptonote::get_account_address_as_str(static_cast(m_network_type), !index.is_zero(), address); + } + + monero_integrated_address monero_wallet_keys::get_integrated_address(const std::string& standard_address, const std::string& payment_id) const { + MTRACE("monero_wallet_keys::get_integrated_address()"); + throw std::runtime_error("monero_wallet_keys::get_integrated_address() not implemented"); + } + + monero_integrated_address monero_wallet_keys::decode_integrated_address(const std::string& integrated_address) const { + MTRACE("monero_wallet_keys::decode_integrated_address()"); + throw std::runtime_error("monero_wallet_keys::decode_integrated_address() not implemented"); + } + + monero_account monero_wallet_keys::get_account(uint32_t account_idx, bool include_subaddresses) const { + MTRACE("monero_wallet_keys::get_account()"); + + if (include_subaddresses) { + std::string err = "monero_wallet_keys::get_account(account_idx, include_subaddresses) include_subaddresses must be false"; + MERROR(err); + throw std::runtime_error(err); + } + + // build and return account + monero_account account; + account.m_index = account_idx; + account.m_primary_address = get_address(account_idx, 0); + return account; + } + + std::vector monero_wallet_keys::get_subaddresses(const uint32_t account_idx, const std::vector& subaddress_indices) const { + + // must provide subaddress indices + if (subaddress_indices.empty()) { + std::string err = "Keys-only wallet does not have enumerable set of subaddresses; specific specific subaddresses"; + MERROR(err); + throw std::runtime_error(err); + } + + // initialize subaddresses at indices + std::vector subaddresses; + for (uint32_t subaddressIndicesIdx = 0; subaddressIndicesIdx < subaddress_indices.size(); subaddressIndicesIdx++) { + monero_subaddress subaddress; + subaddress.m_account_index = account_idx; + uint32_t subaddress_idx = subaddress_indices.at(subaddressIndicesIdx); + subaddress.m_index = subaddress_idx; + subaddress.m_address = get_address(account_idx, subaddress_idx); + subaddresses.push_back(subaddress); + } + + return subaddresses; + } + + std::string monero_wallet_keys::sign_message(const std::string& msg, monero_message_signature_type signature_type, uint32_t account_idx, uint32_t subaddress_idx) const { + MTRACE("monero_wallet_keys::sign_message()"); + throw std::runtime_error("monero_wallet_keys::sign_message() not implemented"); + } + + monero_message_signature_result monero_wallet_keys::verify_message(const std::string& msg, const std::string& address, const std::string& signature) const { + MTRACE("monero_wallet_keys::verify_message()"); + throw std::runtime_error("monero_wallet_keys::verify_message() not implemented"); + } + + void monero_wallet_keys::close(bool save) { + if (save) throw std::runtime_error("MoneroWalletKeys does not support saving"); + // no pointers to destroy + } + + // ------------------------------- PRIVATE HELPERS ---------------------------- + + void monero_wallet_keys::init_common() { + m_primary_address = m_account.get_public_address_str(static_cast(m_network_type)); + const cryptonote::account_keys& keys = m_account.get_keys(); + m_pub_view_key = epee::string_tools::pod_to_hex(keys.m_account_address.m_view_public_key); + m_prv_view_key = epee::string_tools::pod_to_hex(unwrap(unwrap(keys.m_view_secret_key))); + m_pub_spend_key = epee::string_tools::pod_to_hex(keys.m_account_address.m_spend_public_key); + m_prv_spend_key = epee::string_tools::pod_to_hex(unwrap(unwrap(keys.m_spend_secret_key))); + if (m_prv_spend_key == "0000000000000000000000000000000000000000000000000000000000000000") m_prv_spend_key = ""; + } +} diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_keys.h b/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_keys.h new file mode 100644 index 0000000..f4d3ae9 --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_keys.h @@ -0,0 +1,147 @@ +/** + * Copyright (c) woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ + +#pragma once + +#include "monero_wallet.h" +#include "cryptonote_basic/account.h" + +using namespace monero; + +/** + * Public library interface. + */ +namespace monero { + + /** + * Implements a Monero wallet to provide basic key management. + */ + class monero_wallet_keys : public monero_wallet { + + public: + + // --------------------------- STATIC WALLET UTILS -------------------------- + + /** + * Create a new wallet with a randomly generated seed. + * + * @param config is the wallet configuration (network type and language) + */ + static monero_wallet_keys* create_wallet_random(const monero_wallet_config& config); + + /** + * Create a wallet from an existing mnemonic phrase or seed. + * + * @param config is the wallet configuration (network type, seed, seed offset, isMultisig) + */ + static monero_wallet_keys* create_wallet_from_seed(const monero_wallet_config& config); + + /** + * Create a wallet from an address, view key, and spend key. + * + * @param config is the wallet configuration (network type, address, view key, spend key, language) + */ + static monero_wallet_keys* create_wallet_from_keys(const monero_wallet_config& config); + + /** + * Get a list of available languages for the wallet's seed. + * + * @return the available languages for the wallet's seed + */ + static std::vector get_seed_languages(); + + // ----------------------------- WALLET METHODS ----------------------------- + + /** + * Destruct the wallet. + */ + ~monero_wallet_keys(); + + /** + * Supported wallet methods. + */ + bool is_view_only() const override { return m_is_view_only; } + monero_version get_version() const override; + monero_network_type get_network_type() const override { return m_network_type; } + std::string get_seed() const override { return m_seed; } + std::string get_seed_language() const override { return m_language; } + std::string get_private_view_key() const override { return m_prv_view_key; } + std::string get_private_spend_key() const override { return m_prv_spend_key; } + std::string get_public_view_key() const override { return m_pub_view_key; } + std::string get_public_spend_key() const override { return m_pub_spend_key; } + std::string get_primary_address() const override { return m_primary_address; } + std::string get_address(const uint32_t account_idx, const uint32_t subaddress_idx) const override; + monero_integrated_address get_integrated_address(const std::string& standard_address = "", const std::string& payment_id = "") const override; + monero_integrated_address decode_integrated_address(const std::string& integrated_address) const override; + monero_account get_account(const uint32_t account_idx, bool include_subaddresses) const override; + std::vector get_subaddresses(const uint32_t account_idx, const std::vector& subaddress_indices) const override; + std::string sign_message(const std::string& msg, monero_message_signature_type signature_type, uint32_t account_idx = 0, uint32_t subaddress_idx = 0) const override; + monero_message_signature_result verify_message(const std::string& msg, const std::string& address, const std::string& signature) const override; + void close(bool save = false) override; + + // --------------------------------- PRIVATE -------------------------------- + + private: + bool m_is_view_only; + monero_network_type m_network_type; + cryptonote::account_base m_account; + std::string m_seed; + std::string m_language; + std::string m_pub_view_key; + std::string m_prv_view_key; + std::string m_pub_spend_key; + std::string m_prv_spend_key; + std::string m_primary_address; + + void init_common(); + }; +} diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_model.cpp b/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_model.cpp new file mode 100644 index 0000000..bf4ae09 --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_model.cpp @@ -0,0 +1,1882 @@ +/** + * Copyright (c) woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ + +#include "monero_wallet_model.h" + +#include "utils/gen_utils.h" +#include "utils/monero_utils.h" +#include + +/** + * Public library interface. + */ +namespace monero { + + // ----------------------- UNDECLARED PRIVATE HELPERS ----------------------- + + void merge_incoming_transfer(std::vector>& transfers, const std::shared_ptr& transfer) { + for (const std::shared_ptr& aTransfer : transfers) { + if (aTransfer->m_account_index.get() == transfer->m_account_index.get() && aTransfer->m_subaddress_index.get() == transfer->m_subaddress_index.get()) { + aTransfer->merge(aTransfer, transfer); + return; + } + } + transfers.push_back(transfer); + } + + std::shared_ptr node_to_block_query(const boost::property_tree::ptree& node) { + std::shared_ptr block = std::make_shared(); + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("txs")) { + boost::property_tree::ptree txs_node = it->second; + for (boost::property_tree::ptree::const_iterator it2 = txs_node.begin(); it2 != txs_node.end(); ++it2) { + std::shared_ptr tx_query = std::make_shared(); + monero_tx_query::from_property_tree(it2->second, tx_query); + block->m_txs.push_back(tx_query); + tx_query->m_block = block; + } + } + } + return block; + } + + // --------------------------- MONERO WALLET CONFIG ----------------------------- + + monero_wallet_config::monero_wallet_config(const monero_wallet_config& config) { + m_path = config.m_path; + m_password = config.m_password; + m_network_type = config.m_network_type; + m_server = config.m_server; + m_is_trusted_daemon = config.m_is_trusted_daemon; + m_seed = config.m_seed; + m_seed_offset = config.m_seed_offset; + m_primary_address = config.m_primary_address; + m_private_view_key = config.m_private_view_key; + m_private_spend_key = config.m_private_spend_key; + m_restore_height = config.m_restore_height; + m_language = config.m_language; + m_save_current = config.m_save_current; + m_account_lookahead = config.m_account_lookahead; + m_subaddress_lookahead = config.m_subaddress_lookahead; + m_is_multisig = config.m_is_multisig; + m_regtest = config.m_regtest; + } + + monero_wallet_config monero_wallet_config::copy() const { + return monero_wallet_config(*this); + } + + rapidjson::Value monero_wallet_config::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set sub-objects + if (m_server != nullptr) root.AddMember("server", m_server->to_rapidjson_val(allocator), allocator); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_network_type != boost::none) monero_utils::add_json_member("networkType", m_network_type.get(), allocator, root, value_num); + if (m_restore_height != boost::none) monero_utils::add_json_member("restoreHeight", m_restore_height.get(), allocator, root, value_num); + if (m_account_lookahead != boost::none) monero_utils::add_json_member("accountLookahead", m_account_lookahead.get(), allocator, root, value_num); + if (m_subaddress_lookahead != boost::none) monero_utils::add_json_member("subaddressLookahead", m_subaddress_lookahead.get(), allocator, root, value_num); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_path != boost::none) monero_utils::add_json_member("path", m_path.get(), allocator, root, value_str); + if (m_password != boost::none) monero_utils::add_json_member("password", m_password.get(), allocator, root, value_str); + if (m_seed != boost::none) monero_utils::add_json_member("seed", m_seed.get(), allocator, root, value_str); + if (m_seed_offset != boost::none) monero_utils::add_json_member("seedOffset", m_seed_offset.get(), allocator, root, value_str); + if (m_primary_address != boost::none) monero_utils::add_json_member("primaryAddress", m_primary_address.get(), allocator, root, value_str); + if (m_private_view_key != boost::none) monero_utils::add_json_member("privateViewKey", m_private_view_key.get(), allocator, root, value_str); + if (m_private_spend_key != boost::none) monero_utils::add_json_member("privateSpendKey", m_private_spend_key.get(), allocator, root, value_str); + if (m_language != boost::none) monero_utils::add_json_member("language", m_language.get(), allocator, root, value_str); + if (m_account_lookahead != boost::none) monero_utils::add_json_member("accountLookahead", m_account_lookahead.get(), allocator, root, value_str); + if (m_subaddress_lookahead != boost::none) monero_utils::add_json_member("subaddressLookahead", m_subaddress_lookahead.get(), allocator, root, value_str); + + // set bool values + if (m_is_trusted_daemon != boost::none) monero_utils::add_json_member("isTrustedDaemon", m_is_trusted_daemon.get(), allocator, root); + if (m_save_current != boost::none) monero_utils::add_json_member("saveCurrent", m_save_current.get(), allocator, root); + if (m_is_multisig != boost::none) monero_utils::add_json_member("isMultisig", m_is_multisig.get(), allocator, root); + if (m_regtest != boost::none) monero_utils::add_json_member("regtest", m_regtest.get(), allocator, root); + + // return root + return root; + } + + std::shared_ptr monero_wallet_config::deserialize(const std::string& config_json) { + + // deserialize config json to property node + std::istringstream iss = config_json.empty() ? std::istringstream() : std::istringstream(config_json); + boost::property_tree::ptree node; + boost::property_tree::read_json(iss, node); + + // convert config property tree to monero_wallet_config + std::shared_ptr config = std::make_shared(); + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("path")) config->m_path = it->second.data(); + else if (key == std::string("password")) config->m_password = it->second.data(); + else if (key == std::string("networkType")) { + uint32_t network_type_num = it->second.get_value(); + if (network_type_num == 0) config->m_network_type = monero_network_type::MAINNET; + else if (network_type_num == 1) config->m_network_type = monero_network_type::TESTNET; + else if (network_type_num == 2) config->m_network_type = monero_network_type::STAGENET; + else throw std::runtime_error("Invalid network type, expected 0-2: " + std::to_string(network_type_num)); + } + else if (key == std::string("server")) config->m_server = monero_rpc_connection::from_property_tree(it->second); + else if (key == std::string("isTrustedDaemon")) config->m_is_trusted_daemon = it->second.get_value(); + else if (key == std::string("seed")) config->m_seed = it->second.data(); + else if (key == std::string("seedOffset")) config->m_seed_offset = it->second.data(); + else if (key == std::string("primaryAddress")) config->m_primary_address = it->second.data(); + else if (key == std::string("privateViewKey")) config->m_private_view_key = it->second.data(); + else if (key == std::string("privateSpendKey")) config->m_private_spend_key = it->second.data(); + else if (key == std::string("restoreHeight")) config->m_restore_height = it->second.get_value(); + else if (key == std::string("language")) config->m_language = it->second.data(); + else if (key == std::string("saveCurrent")) config->m_save_current = it->second.get_value(); + else if (key == std::string("accountLookahead")) config->m_account_lookahead = it->second.get_value(); + else if (key == std::string("subaddressLookahead")) config->m_subaddress_lookahead = it->second.get_value(); + else if (key == std::string("isMultisig")) config->m_is_multisig = it->second.get_value(); + else if (key == std::string("regtest")) config->m_regtest = it->second.get_value(); + } + + return config; + } + + // -------------------------- MONERO SYNC RESULT ---------------------------- + + rapidjson::Value monero_sync_result::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + monero_utils::add_json_member("numBlocksFetched", m_num_blocks_fetched, allocator, root, value_num); + + // set bool values + monero_utils::add_json_member("receivedMoney", m_received_money, allocator, root); + + // return root + return root; + } + + // -------------------------- MONERO ACCOUNT ----------------------------- + + void monero_account::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& account) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("index")) account->m_index = it->second.get_value(); + else if (key == std::string("balance")) account->m_balance = it->second.get_value(); + else if (key == std::string("unlockedBalance")) account->m_unlocked_balance = it->second.get_value(); + else if (key == std::string("primaryAddress")) account->m_primary_address = it->second.data(); + else if (key == std::string("tag") && !it->second.data().empty()) account->m_tag = it->second.data(); + } + } + + rapidjson::Value monero_account::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_index != boost::none) monero_utils::add_json_member("index", m_index.get(), allocator, root, value_num); + if (m_balance != boost::none) monero_utils::add_json_member("balance", m_balance.get(), allocator, root, value_num); + if (m_unlocked_balance != boost::none) monero_utils::add_json_member("unlockedBalance", m_unlocked_balance.get(), allocator, root, value_num); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_primary_address != boost::none) monero_utils::add_json_member("primaryAddress", m_primary_address.get(), allocator, root, value_str); + if (m_tag != boost::none) monero_utils::add_json_member("tag", m_tag.get(), allocator, root, value_str); + + // set subaddresses + if (!m_subaddresses.empty()) root.AddMember("subaddresses", monero_utils::to_rapidjson_val(allocator, m_subaddresses), allocator); + + // return root + return root; + } + + // -------------------------- MONERO SUBADDRESS ----------------------------- + + void monero_subaddress::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& subaddress) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("accountIndex")) subaddress->m_account_index = it->second.get_value(); + else if (key == std::string("index")) subaddress->m_index = it->second.get_value(); + else if (key == std::string("address")) subaddress->m_address = it->second.data(); + else if (key == std::string("balance")) subaddress->m_balance = it->second.get_value(); + else if (key == std::string("unlockedBalance")) subaddress->m_unlocked_balance = it->second.get_value(); + else if (key == std::string("label") && !it->second.data().empty()) subaddress->m_label = it->second.data(); + else if (key == std::string("isUsed")) subaddress->m_is_used = it->second.get_value(); + else if (key == std::string("numUnspentOutputs")) subaddress->m_num_unspent_outputs = it->second.get_value(); + else if (key == std::string("numBlocksToUnlock")) subaddress->m_num_blocks_to_unlock = it->second.get_value(); + } + } + + rapidjson::Value monero_subaddress::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_account_index != boost::none) monero_utils::add_json_member("accountIndex", m_account_index.get(), allocator, root, value_num); + if (m_index != boost::none) monero_utils::add_json_member("index", m_index.get(), allocator, root, value_num); + if (m_balance != boost::none) monero_utils::add_json_member("balance", m_balance.get(), allocator, root, value_num); + if (m_unlocked_balance != boost::none) monero_utils::add_json_member("unlockedBalance", m_unlocked_balance.get(), allocator, root, value_num); + if (m_num_unspent_outputs != boost::none) monero_utils::add_json_member("numUnspentOutputs", m_num_unspent_outputs.get(), allocator, root, value_num); + if (m_num_blocks_to_unlock) monero_utils::add_json_member("numBlocksToUnlock", m_num_blocks_to_unlock.get(), allocator, root, value_num); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); + if (m_label != boost::none) monero_utils::add_json_member("label", m_label.get(), allocator, root, value_str); + + // set bool values + if (m_is_used != boost::none) monero_utils::add_json_member("isUsed", m_is_used.get(), allocator, root); + + // return root + return root; + } + + // --------------------------- MONERO TX WALLET ----------------------------- + + rapidjson::Value monero_tx_wallet::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // serialize root from superclass + rapidjson::Value root = monero_tx::to_rapidjson_val(allocator); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_input_sum != boost::none) monero_utils::add_json_member("inputSum", m_input_sum.get(), allocator, root, value_num); + if (m_output_sum != boost::none) monero_utils::add_json_member("outputSum", m_output_sum.get(), allocator, root, value_num); + if (m_change_amount != boost::none) monero_utils::add_json_member("changeAmount", m_change_amount.get(), allocator, root, value_num); + if (m_num_dummy_outputs != boost::none) monero_utils::add_json_member("numDummyOutputs", m_num_dummy_outputs.get(), allocator, root, value_num); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_note != boost::none) monero_utils::add_json_member("note", m_note.get(), allocator, root, value_str); + if (m_change_address != boost::none) monero_utils::add_json_member("changeAddress", m_change_address.get(), allocator, root, value_str); + if (m_extra_hex != boost::none) monero_utils::add_json_member("extraHex", m_extra_hex.get(), allocator, root, value_str); + + // set bool values + if (m_is_incoming != boost::none) monero_utils::add_json_member("isIncoming", m_is_incoming.get(), allocator, root); + if (m_is_outgoing != boost::none) monero_utils::add_json_member("isOutgoing", m_is_outgoing.get(), allocator, root); + if (m_is_locked != boost::none) monero_utils::add_json_member("isLocked", m_is_locked.get(), allocator, root); + + // set sub-arrays + if (!m_incoming_transfers.empty()) root.AddMember("incomingTransfers", monero_utils::to_rapidjson_val(allocator, m_incoming_transfers), allocator); + + // set sub-objects + if (m_outgoing_transfer != nullptr) root.AddMember("outgoingTransfer", m_outgoing_transfer->to_rapidjson_val(allocator), allocator); + + // return root + return root; + } + + void monero_tx_wallet::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& tx_wallet) { + monero_tx::from_property_tree(node, tx_wallet); + + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("txSet")) throw std::runtime_error("monero_tx_wallet " + key + " deserialization not implemented"); + else if (key == std::string("isIncoming")) tx_wallet->m_is_incoming = it->second.get_value(); + else if (key == std::string("isOutgoing")) tx_wallet->m_is_outgoing = it->second.get_value(); + else if (key == std::string("incomingTransfers")) throw std::runtime_error("monero_tx_wallet " + key + " deserialization not implemented"); + else if (key == std::string("outgoingTransfer")) throw std::runtime_error("monero_tx_wallet " + key + " deserialization not implemented"); + else if (key == std::string("note")) tx_wallet->m_note = it->second.data(); + else if (key == std::string("isLocked")) tx_wallet->m_is_locked = it->second.get_value(); + else if (key == std::string("inputSum")) tx_wallet->m_input_sum = it->second.get_value(); + else if (key == std::string("outputSum")) tx_wallet->m_output_sum = it->second.get_value(); + else if (key == std::string("changeAddress")) throw std::runtime_error("monero_tx_wallet " + key + " deserialization not implemented"); + else if (key == std::string("changeAmount")) throw std::runtime_error("monero_tx_wallet " + key + " deserialization not implemented"); + else if (key == std::string("numDummyOutputs")) throw std::runtime_error("monero_tx_wallet " + key + " deserialization not implemented"); + else if (key == std::string("extraHex")) throw std::runtime_error("monero_tx_wallet " + key + " deserialization not implemented"); + } + } + + std::shared_ptr monero_tx_wallet::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + return monero_tx_wallet::copy(std::static_pointer_cast(src), std::static_pointer_cast(tgt)); + }; + + std::shared_ptr monero_tx_wallet::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + if (this != src.get()) throw std::runtime_error("this tx != src"); + + // copy base class + monero_tx::copy(std::static_pointer_cast(src), std::static_pointer_cast(tgt)); + + // copy wallet extensions + tgt->m_tx_set = src->m_tx_set; + tgt->m_is_incoming = src->m_is_incoming; + tgt->m_is_outgoing = src->m_is_outgoing; + if (!src->m_incoming_transfers.empty()) { + tgt->m_incoming_transfers = std::vector>(); + for (const std::shared_ptr& transfer : src->m_incoming_transfers) { + std::shared_ptr transfer_copy = transfer->copy(transfer, std::make_shared()); + transfer_copy->m_tx = tgt; + tgt->m_incoming_transfers.push_back(transfer_copy); + } + } + if (src->m_outgoing_transfer != nullptr) { + std::shared_ptr transfer_copy = src->m_outgoing_transfer->copy(src->m_outgoing_transfer, std::make_shared()); + transfer_copy->m_tx = tgt; + tgt->m_outgoing_transfer = transfer_copy; + } + tgt->m_note = src->m_note; + tgt->m_is_locked = src->m_is_locked; + tgt->m_input_sum = src->m_input_sum; + tgt->m_output_sum = src->m_output_sum; + tgt->m_change_address = src->m_change_address; + tgt->m_change_amount = src->m_change_amount; + tgt->m_num_dummy_outputs = src->m_num_dummy_outputs; + tgt->m_extra_hex = src->m_extra_hex; + + return tgt; + }; + + void monero_tx_wallet::merge(const std::shared_ptr& self, const std::shared_ptr& other) { + merge(std::static_pointer_cast(self), std::static_pointer_cast(other)); + } + + void monero_tx_wallet::merge(const std::shared_ptr& self, const std::shared_ptr& other) { + if (this != self.get()) throw std::runtime_error("this != self"); + if (self == other) return; + + // merge base classes + monero_tx::merge(self, other); + + // merge incoming transfers + if (!other->m_incoming_transfers.empty()) { + for (const std::shared_ptr& transfer : other->m_incoming_transfers) { // NOTE: not using reference so std::shared_ptr is not deleted when tx is dereferenced + transfer->m_tx = self; + merge_incoming_transfer(self->m_incoming_transfers, transfer); + } + } + + // merge outgoing transfer + if (other->m_outgoing_transfer != nullptr) { + other->m_outgoing_transfer->m_tx = self; + if (self->m_outgoing_transfer == nullptr) self->m_outgoing_transfer = other->m_outgoing_transfer; + else self->m_outgoing_transfer->merge(self->m_outgoing_transfer, other->m_outgoing_transfer); + } + + // merge simple extensions + m_is_incoming = gen_utils::reconcile(m_is_incoming, other->m_is_incoming, "tx wallet m_is_incoming"); + m_is_outgoing = gen_utils::reconcile(m_is_outgoing, other->m_is_outgoing, "tx wallet m_is_outgoing"); + m_note = gen_utils::reconcile(m_note, other->m_note, "tx wallet m_note"); + m_is_locked = gen_utils::reconcile(m_is_locked, other->m_is_locked, boost::none, false, boost::none, "tx wallet m_is_locked"); // tx can become unlocked + m_input_sum = gen_utils::reconcile(m_input_sum, other->m_input_sum, "tx wallet m_input_sum"); + m_output_sum = gen_utils::reconcile(m_output_sum, other->m_output_sum, "tx wallet m_output_sum"); + m_change_address = gen_utils::reconcile(m_change_address, other->m_change_address, "tx wallet m_change_address"); + m_change_amount = gen_utils::reconcile(m_change_amount, other->m_change_amount, "tx wallet m_change_amount"); + m_num_dummy_outputs = gen_utils::reconcile(m_num_dummy_outputs, other->m_num_dummy_outputs, "tx wallet m_num_dummy_outputs"); + m_extra_hex = gen_utils::reconcile(m_extra_hex, other->m_extra_hex, "tx wallet m_extra_hex"); + } + + std::vector> monero_tx_wallet::get_transfers() const { + monero_transfer_query query; + return get_transfers(query); + } + + std::vector> monero_tx_wallet::get_transfers(const monero_transfer_query& query) const { + std::vector> transfers; + if (m_outgoing_transfer != nullptr && query.meets_criteria(m_outgoing_transfer.get())) transfers.push_back(m_outgoing_transfer); + for (const std::shared_ptr& transfer : m_incoming_transfers) if (query.meets_criteria(transfer.get())) transfers.push_back(transfer); + return transfers; + } + + std::vector> monero_tx_wallet::filter_transfers(const monero_transfer_query& query) { + + // collect outgoing transfer, erase if excluded + std::vector> transfers; + if (m_outgoing_transfer != nullptr && query.meets_criteria(m_outgoing_transfer.get())) transfers.push_back(m_outgoing_transfer); + else m_outgoing_transfer = nullptr; + + // collect incoming transfers, erase if excluded + std::vector>::iterator iter = m_incoming_transfers.begin(); + while (iter != m_incoming_transfers.end()) { + if (query.meets_criteria((*iter).get())) { + transfers.push_back(*iter); + iter++; + } else { + iter = m_incoming_transfers.erase(iter); + } + } + return transfers; + } + + std::vector> monero_tx_wallet::get_outputs_wallet() const { + monero_output_query query; + return get_outputs_wallet(query); + } + + std::vector> monero_tx_wallet::get_outputs_wallet(const monero_output_query& query) const { + std::vector> outputs; + for (const std::shared_ptr& output : m_outputs) { + std::shared_ptr output_wallet = std::dynamic_pointer_cast(output); + if (query.meets_criteria(output_wallet.get())) outputs.push_back(output_wallet); + } + return outputs; + } + + std::vector> monero_tx_wallet::filter_outputs_wallet(const monero_output_query& query) { + std::vector> outputs; + std::vector>::iterator iter = m_outputs.begin(); + while (iter != m_outputs.end()) { + std::shared_ptr output_wallet = std::dynamic_pointer_cast(*iter); + if (query.meets_criteria(output_wallet.get())) { + outputs.push_back(output_wallet); + iter++; + } else { + iter = m_outputs.erase(iter); + } + } + return outputs; + } + + // -------------------------- MONERO TX QUERY ----------------------------- + + rapidjson::Value monero_tx_query::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // serialize root from superclass + rapidjson::Value root = monero_tx_wallet::to_rapidjson_val(allocator); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_height != boost::none) monero_utils::add_json_member("height", m_height.get(), allocator, root, value_num); + if (m_min_height != boost::none) monero_utils::add_json_member("minHeight", m_min_height.get(), allocator, root, value_num); + if (m_max_height != boost::none) monero_utils::add_json_member("maxHeight", m_max_height.get(), allocator, root, value_num); + + // set bool values + if (m_is_outgoing != boost::none) monero_utils::add_json_member("isOutgoing", m_is_outgoing.get(), allocator, root); + if (m_is_incoming != boost::none) monero_utils::add_json_member("isIncoming", m_is_incoming.get(), allocator, root); + if (m_has_payment_id != boost::none) monero_utils::add_json_member("hasPaymentId", m_has_payment_id.get(), allocator, root); + if (m_include_outputs != boost::none) monero_utils::add_json_member("includeOutputs", m_include_outputs.get(), allocator, root); + + // set sub-arrays + if (!m_hashes.empty()) root.AddMember("hashes", monero_utils::to_rapidjson_val(allocator, m_hashes), allocator); + if (!m_payment_ids.empty()) root.AddMember("paymentIds", monero_utils::to_rapidjson_val(allocator, m_payment_ids), allocator); + + // set sub-objects + if (m_transfer_query != nullptr) root.AddMember("transferQuery", m_transfer_query->to_rapidjson_val(allocator), allocator); + + // return root + return root; + } + + void monero_tx_query::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& tx_query) { + monero_tx_wallet::from_property_tree(node, tx_query); + + // initialize query from node + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("isOutgoing")) tx_query->m_is_outgoing = it->second.get_value(); + else if (key == std::string("isIncoming")) tx_query->m_is_incoming = it->second.get_value(); + else if (key == std::string("hashes")) for (boost::property_tree::ptree::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) tx_query->m_hashes.push_back(it2->second.data()); + else if (key == std::string("hasPaymentId")) tx_query->m_has_payment_id = it->second.get_value(); + else if (key == std::string("paymentIds")) for (boost::property_tree::ptree::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) tx_query->m_payment_ids.push_back(it2->second.data()); + else if (key == std::string("height")) tx_query->m_height = it->second.get_value(); + else if (key == std::string("minHeight")) tx_query->m_min_height = it->second.get_value(); + else if (key == std::string("maxHeight")) tx_query->m_max_height = it->second.get_value(); + else if (key == std::string("includeOutputs")) tx_query->m_include_outputs = it->second.get_value(); + else if (key == std::string("transferQuery")) { + tx_query->m_transfer_query = std::make_shared(); + monero_transfer_query::from_property_tree(it->second, tx_query->m_transfer_query); + tx_query->m_transfer_query->m_tx_query = tx_query; + } + else if (key == std::string("inputQuery")) { + tx_query->m_input_query = std::make_shared(); + monero_output_query::from_property_tree(it->second, tx_query->m_input_query); + tx_query->m_input_query->m_tx_query = tx_query; + } + else if (key == std::string("outputQuery")) { + tx_query->m_output_query = std::make_shared(); + monero_output_query::from_property_tree(it->second, tx_query->m_output_query); + tx_query->m_output_query->m_tx_query = tx_query; + } + } + } + + std::shared_ptr monero_tx_query::deserialize_from_block(const std::string& tx_query_json) { + + // deserialize tx query std::string to property rooted at block + std::istringstream iss = tx_query_json.empty() ? std::istringstream() : std::istringstream(tx_query_json); + boost::property_tree::ptree block_node; + boost::property_tree::read_json(iss, block_node); + + // convert query property tree to block + std::shared_ptr block = node_to_block_query(block_node); + + // get tx query + std::shared_ptr tx_query = std::static_pointer_cast(block->m_txs[0]); + + // return deserialized query + return tx_query; + } + + std::shared_ptr monero_tx_query::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + return copy(std::static_pointer_cast(src), std::static_pointer_cast(tgt)); + }; + + std::shared_ptr monero_tx_query::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + return copy(std::static_pointer_cast(src), std::static_pointer_cast(tgt)); + }; + + std::shared_ptr monero_tx_query::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + if (this != src.get()) throw std::runtime_error("this != src"); + + // copy base class + monero_tx_wallet::copy(std::static_pointer_cast(src), std::static_pointer_cast(tgt)); + + // copy query extensions + tgt->m_is_outgoing = src->m_is_outgoing; + tgt->m_is_incoming = src->m_is_incoming; + if (!src->m_hashes.empty()) tgt->m_hashes = std::vector(src->m_hashes); + tgt-> m_has_payment_id = src->m_has_payment_id; + if (!src->m_payment_ids.empty()) tgt->m_payment_ids = std::vector(src->m_payment_ids); + tgt->m_height = src->m_height; + tgt->m_min_height = src->m_min_height; + tgt->m_max_height = src->m_max_height; + tgt->m_include_outputs = src->m_include_outputs; + if (src->m_transfer_query != nullptr) { + tgt->m_transfer_query = src->m_transfer_query->copy(src->m_transfer_query, std::make_shared()); + tgt->m_transfer_query->m_tx_query = tgt; + } + if (src->m_input_query != nullptr) { + tgt->m_input_query = src->m_input_query->copy(src->m_input_query, std::make_shared()); + tgt->m_input_query->m_tx_query = tgt; + } + if (src->m_output_query != nullptr) { + tgt->m_output_query = src->m_output_query->copy(src->m_output_query, std::make_shared()); + tgt->m_output_query->m_tx_query = tgt; + } + return tgt; + }; + + bool monero_tx_query::meets_criteria(monero_tx_wallet* tx, bool query_children) const { + if (tx == nullptr) throw std::runtime_error("nullptr given to monero_tx_query::meets_criteria()"); + + // filter on tx + if (m_hash != boost::none && m_hash != tx->m_hash) return false; + if (m_payment_id != boost::none && m_payment_id != tx->m_payment_id) return false; + if (m_is_confirmed != boost::none && m_is_confirmed != tx->m_is_confirmed) return false; + if (m_in_tx_pool != boost::none && m_in_tx_pool != tx->m_in_tx_pool) return false; + if (m_relay != boost::none && m_relay != tx->m_relay) return false; + if (m_is_failed != boost::none && m_is_failed != tx->m_is_failed) return false; + if (m_is_miner_tx != boost::none && m_is_miner_tx != tx->m_is_miner_tx) return false; + if (m_is_locked != boost::none && m_is_locked != tx->m_is_locked) return false; + + // filter on having a payment id + if (m_has_payment_id != boost::none) { + if (*m_has_payment_id && tx->m_payment_id == boost::none) return false; + if (!*m_has_payment_id && tx->m_payment_id != boost::none) return false; + } + + // filter on incoming + if (m_is_incoming != boost::none && m_is_incoming != tx->m_is_incoming) return false; + + // filter on outgoing + if (m_is_outgoing != boost::none && m_is_outgoing != tx->m_is_outgoing) return false; + + // filter on remaining fields + boost::optional txHeight = tx->get_height(); + if (!m_hashes.empty() && find(m_hashes.begin(), m_hashes.end(), *tx->m_hash) == m_hashes.end()) return false; + if (!m_payment_ids.empty() && (tx->m_payment_id == boost::none || find(m_payment_ids.begin(), m_payment_ids.end(), *tx->m_payment_id) == m_payment_ids.end())) return false; + if (m_height != boost::none && (txHeight == boost::none || *txHeight != *m_height)) return false; + if (m_min_height != boost::none && (txHeight == boost::none || *txHeight < *m_min_height)) return false; + if (m_max_height != boost::none && (txHeight == boost::none || *txHeight > *m_max_height)) return false; + + // done if not querying transfers or outputs + if (!query_children) return true; + + // at least one transfer must meet transfer query if defined + if (m_transfer_query != nullptr) { + bool match_found = false; + if (tx->m_outgoing_transfer != nullptr && m_transfer_query->meets_criteria(tx->m_outgoing_transfer.get())) match_found = true; + else if (!tx->m_incoming_transfers.empty()) { + for (const std::shared_ptr& incoming_transfer : tx->m_incoming_transfers) { + if (m_transfer_query->meets_criteria(incoming_transfer.get(), false)) { + match_found = true; + break; + } + } + } + if (!match_found) return false; + } + + // at least one input must meet input query if defined + if (m_input_query != nullptr) { + if (tx->m_inputs.empty()) return false; + bool match_found = false; + for (const std::shared_ptr& input : tx->m_inputs) { + std::shared_ptr vin_wallet = std::static_pointer_cast(input); + if (m_input_query->meets_criteria(vin_wallet.get(), false)) { + match_found = true; + break; + } + } + if (!match_found) return false; + } + + // at least one output must meet output query if defined + if (m_output_query != nullptr) { + if (tx->m_outputs.empty()) return false; + bool match_found = false; + for (const std::shared_ptr& output : tx->m_outputs) { + std::shared_ptr vout_wallet = std::static_pointer_cast(output); + if (m_output_query->meets_criteria(vout_wallet.get(), false)) { + match_found = true; + break; + } + } + if (!match_found) return false; + } + + // transaction meets query criteria + return true; + } + + std::shared_ptr monero_tx_query::decontextualize(std::shared_ptr query) { + query->m_is_incoming = boost::none; + query->m_is_outgoing = boost::none; + query->m_transfer_query = nullptr; + query->m_input_query = nullptr; + query->m_output_query = nullptr; + return query; + } + + // -------------------------- MONERO DESTINATION ---------------------------- + + rapidjson::Value monero_destination::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_amount != boost::none) monero_utils::add_json_member("amount", m_amount.get(), allocator, root, value_num); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); + + // return root + return root; + } + + void monero_destination::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& destination) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("address")) destination->m_address = it->second.data(); + else if (key == std::string("amount")) destination->m_amount = it->second.get_value(); + } + } + + std::shared_ptr monero_destination::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + if (this != src.get()) throw std::runtime_error("this destination!= src"); + tgt->m_address = src->m_address; + tgt->m_amount = src->m_amount; + return tgt; + }; + + // ----------------------------- MONERO TX SET ------------------------------ + + rapidjson::Value monero_tx_set::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_multisig_tx_hex != boost::none) monero_utils::add_json_member("multisigTxHex", m_multisig_tx_hex.get(), allocator, root, value_str); + if (m_unsigned_tx_hex != boost::none) monero_utils::add_json_member("unsignedTxHex", m_unsigned_tx_hex.get(), allocator, root, value_str); + if (m_signed_tx_hex != boost::none) monero_utils::add_json_member("signedTxHex", m_signed_tx_hex.get(), allocator, root, value_str); + + // set sub-arrays + if (!m_txs.empty()) root.AddMember("txs", monero_utils::to_rapidjson_val(allocator, m_txs), allocator); + + // return root + return root; + } + + monero_tx_set monero_tx_set::deserialize(const std::string& tx_set_json) { + + // deserialize tx set to property + std::istringstream iss = tx_set_json.empty() ? std::istringstream() : std::istringstream(tx_set_json); + boost::property_tree::ptree tx_set_node; + boost::property_tree::read_json(iss, tx_set_node); + + // initialize tx_set from property node + monero_tx_set tx_set; + for (boost::property_tree::ptree::const_iterator it = tx_set_node.begin(); it != tx_set_node.end(); ++it) { + std::string key = it->first; + if (key == std::string("unsignedTxHex")) tx_set.m_unsigned_tx_hex = it->second.data(); + else if (key == std::string("multisigTxHex")) tx_set.m_multisig_tx_hex = it->second.data(); + else if (key == std::string("txs")) { + boost::property_tree::ptree txs_node = it->second; + for (boost::property_tree::ptree::const_iterator it2 = txs_node.begin(); it2 != txs_node.end(); ++it2) { + std::shared_ptr tx_wallet = std::make_shared(); + monero_tx_wallet::from_property_tree(it2->second, tx_wallet); + tx_set.m_txs.push_back(tx_wallet); + } + } + else throw std::runtime_error("monero_utils::deserialize_tx_set() field '" + key + "' not supported"); + } + + return tx_set; + } + + void monero_tx_set::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& set) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("multisigTxHex") && !it->second.data().empty()) set->m_multisig_tx_hex = it->second.data(); + else if (key == std::string("unsignedTxHex") && !it->second.data().empty()) set->m_unsigned_tx_hex = it->second.data(); + else if (key == std::string("signedTxHex") && !it->second.data().empty()) set->m_signed_tx_hex = it->second.data(); + } + } + + // ---------------------------- MONERO TRANSFER ----------------------------- + + rapidjson::Value monero_transfer::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_amount != boost::none) monero_utils::add_json_member("amount", m_amount.get(), allocator, root, value_num); + if (m_account_index != boost::none) monero_utils::add_json_member("accountIndex", m_account_index.get(), allocator, root, value_num); + + // return root + return root; + } + + void monero_transfer::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& transfer) { + + // initialize transfer from node + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("amount")) transfer->m_amount = it->second.get_value(); + else if (key == std::string("accountIndex")) transfer->m_account_index = it->second.get_value(); + } + } + + std::shared_ptr monero_transfer::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + MTRACE("monero_transfer::copy(const std::shared_ptr& src, const std::shared_ptr& tgt)"); + tgt->m_tx = src->m_tx; // reference parent tx by default + tgt->m_amount = src->m_amount; + tgt->m_account_index = src->m_account_index; + return tgt; + } + + void monero_transfer::merge(const std::shared_ptr& self, const std::shared_ptr& other) { + if (this != self.get()) throw std::runtime_error("this != self"); + if (self == other) return; + + // merge txs if they're different which comes back to merging transfers + if (m_tx != other->m_tx) { + m_tx->merge(m_tx, other->m_tx); + return; + } + + // otherwise merge transfer fields + m_account_index = gen_utils::reconcile(m_account_index, other->m_account_index, "transfer m_account_index"); + + // TODO monero-project: failed tx in pool (after testUpdateLockedDifferentAccounts()) causes non-originating saved wallets to return duplicate incoming transfers but one has amount of 0 + if (m_amount != boost::none && other->m_amount != boost::none && *m_amount != *other->m_amount && (*m_amount == 0 || *other->m_amount == 0)) { + MWARNING("monero-project returning transfers with 0 amount/numSuggestedConfirmations"); + //if (*m_amount == 0) m_amount = *other->m_amount; + } else { + m_amount = gen_utils::reconcile(m_amount, other->m_amount, "transfer amount"); + } + } + + // ----------------------- MONERO INCOMING TRANSFER ------------------------- + + rapidjson::Value monero_incoming_transfer::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // serialize root from superclass + rapidjson::Value root = monero_transfer::to_rapidjson_val(allocator); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_subaddress_index != boost::none) monero_utils::add_json_member("subaddressIndex", m_subaddress_index.get(), allocator, root, value_num); + if (m_num_suggested_confirmations != boost::none) monero_utils::add_json_member("numSuggestedConfirmations", m_num_suggested_confirmations.get(), allocator, root, value_num); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); + + // return root + return root; + } + + std::shared_ptr monero_incoming_transfer::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + return copy(std::static_pointer_cast(src), std::static_pointer_cast(tgt)); + } + + std::shared_ptr monero_incoming_transfer::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + if (this != src.get()) throw std::runtime_error("this incoming transfer != src"); + monero_transfer::copy(std::static_pointer_cast(src), std::static_pointer_cast(tgt)); + tgt->m_subaddress_index = src->m_subaddress_index; + tgt->m_address = src->m_address; + tgt->m_num_suggested_confirmations = src->m_num_suggested_confirmations; + return tgt; + }; + + boost::optional monero_incoming_transfer::is_incoming() const { return true; } + + void monero_incoming_transfer::merge(const std::shared_ptr& self, const std::shared_ptr& other) { + merge(std::static_pointer_cast(self), std::static_pointer_cast(other)); + } + + void monero_incoming_transfer::merge(const std::shared_ptr& self, const std::shared_ptr& other) { + if (self == other) return; + monero_transfer::merge(self, other); + m_subaddress_index = gen_utils::reconcile(m_subaddress_index, other->m_subaddress_index, "incoming transfer m_subaddress_index"); + m_address = gen_utils::reconcile(m_address, other->m_address, "incoming transfer m_address"); + m_num_suggested_confirmations = gen_utils::reconcile(m_num_suggested_confirmations, other->m_num_suggested_confirmations, boost::none, boost::none, false, "incoming transfer m_num_suggested_confirmations"); + } + + // ----------------------- MONERO OUTGOING TRANSFER ------------------------- + + rapidjson::Value monero_outgoing_transfer::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // serialize root from superclass + rapidjson::Value root = monero_transfer::to_rapidjson_val(allocator); + + // set sub-arrays + if (!m_subaddress_indices.empty()) root.AddMember("subaddressIndices", monero_utils::to_rapidjson_val(allocator, m_subaddress_indices), allocator); + if (!m_addresses.empty()) root.AddMember("addresses", monero_utils::to_rapidjson_val(allocator, m_addresses), allocator); + if (!m_destinations.empty()) root.AddMember("destinations", monero_utils::to_rapidjson_val(allocator, m_destinations), allocator); + + // return root + return root; + } + + std::shared_ptr monero_outgoing_transfer::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + return copy(std::static_pointer_cast(src), std::static_pointer_cast(tgt)); + }; + + std::shared_ptr monero_outgoing_transfer::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + if (this != src.get()) throw std::runtime_error("this outgoing transfer != src"); + monero_transfer::copy(std::static_pointer_cast(src), std::static_pointer_cast(tgt)); + if (!src->m_subaddress_indices.empty()) tgt->m_subaddress_indices = std::vector(src->m_subaddress_indices); + if (!src->m_addresses.empty()) tgt->m_addresses = std::vector(src->m_addresses); + if (!src->m_destinations.empty()) { + tgt->m_destinations = std::vector>(); + for (const std::shared_ptr& destination : src->m_destinations) { + tgt->m_destinations.push_back(destination->copy(destination, std::make_shared())); + } + } + return tgt; + }; + + boost::optional monero_outgoing_transfer::is_incoming() const { return false; } + + void monero_outgoing_transfer::merge(const std::shared_ptr& self, const std::shared_ptr& other) { + merge(std::static_pointer_cast(self), std::static_pointer_cast(other)); + } + + void monero_outgoing_transfer::merge(const std::shared_ptr& self, const std::shared_ptr& other) { + if (self == other) return; + monero_transfer::merge(self, other); + m_subaddress_indices = gen_utils::reconcile(m_subaddress_indices, other->m_subaddress_indices, "outgoing transfer m_subaddress_indices"); + m_addresses = gen_utils::reconcile(m_addresses, other->m_addresses, "outgoing transfer m_addresses"); + + // use destinations if available on one, otherwise check deep equality + // TODO: java/javascript use reconcile() for deep comparison, but c++ would require specialized equality check for structs with shared pointers, so checking equality here + if (m_destinations.empty() && !other->m_destinations.empty()) m_destinations = other->m_destinations; + else if (!m_destinations.empty() && !other->m_destinations.empty()) { + if (m_destinations.size() != other->m_destinations.size()) throw std::runtime_error("Destination vectors are different sizes"); + for (int i = 0; i < m_destinations.size(); i++) { + if (m_destinations[i]->m_address.get() != other->m_destinations[i]->m_address.get() || + m_destinations[i]->m_amount.get() != other->m_destinations[i]->m_amount.get()) { + throw std::runtime_error("Destination vectors are different"); + } + } + } + } + + // ----------------------- MONERO TRANSFER QUERY -------------------------- + + rapidjson::Value monero_transfer_query::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // serialize root from superclass + rapidjson::Value root = monero_transfer::to_rapidjson_val(allocator); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_subaddress_index != boost::none) monero_utils::add_json_member("subaddressIndex", m_subaddress_index.get(), allocator, root, value_num); + + // set bool values + if (m_is_incoming != boost::none) monero_utils::add_json_member("isIncoming", m_is_incoming.get(), allocator, root); + if (m_has_destinations != boost::none) monero_utils::add_json_member("hasDestinations", m_has_destinations.get(), allocator, root); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); + + // set sub-arrays + if (!m_subaddress_indices.empty()) root.AddMember("subaddressIndices", monero_utils::to_rapidjson_val(allocator, m_subaddress_indices), allocator); + if (!m_addresses.empty()) root.AddMember("addresses", monero_utils::to_rapidjson_val(allocator, m_addresses), allocator); + if (!m_destinations.empty()) root.AddMember("destinations", monero_utils::to_rapidjson_val(allocator, m_destinations), allocator); + + // return root + return root; + } + + void monero_transfer_query::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& transfer_query) { + monero_transfer::from_property_tree(node, transfer_query); + + // initialize query from node + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("isIncoming")) transfer_query->m_is_incoming = it->second.get_value(); + else if (key == std::string("address")) transfer_query->m_address = it->second.data(); + else if (key == std::string("addresses")) throw std::runtime_error("monero_transfer_query " + key + " deserialization not implemented"); + else if (key == std::string("subaddressIndex")) transfer_query->m_subaddress_index = it->second.get_value(); + else if (key == std::string("subaddressIndices")) { + std::vector m_subaddress_indices; + for (const auto& child : it->second) m_subaddress_indices.push_back(child.second.get_value()); + transfer_query->m_subaddress_indices = m_subaddress_indices; + } + else if (key == std::string("destinations")) throw std::runtime_error("monero_transfer_query " + key + " deserialization not implemented"); + else if (key == std::string("hasDestinations")) transfer_query->m_has_destinations = it->second.get_value(); + else if (key == std::string("txQuery")) throw std::runtime_error("monero_transfer_query " + key + " deserialization not implemented"); + } + } + + std::shared_ptr monero_transfer_query::deserialize_from_block(const std::string& transfer_query_json) { + + // deserialize transfer query std::string to property rooted at block + std::istringstream iss = transfer_query_json.empty() ? std::istringstream() : std::istringstream(transfer_query_json); + boost::property_tree::ptree blockNode; + boost::property_tree::read_json(iss, blockNode); + + // convert query property tree to block + std::shared_ptr block = node_to_block_query(blockNode); + + // return empty query if no txs + if (block->m_txs.empty()) return std::make_shared(); + + // get tx query + std::shared_ptr tx_query = std::static_pointer_cast(block->m_txs[0]); + + // get / create transfer query + std::shared_ptr transfer_query = tx_query->m_transfer_query == nullptr ? std::make_shared() : tx_query->m_transfer_query; + + // set transfer query's tx query + transfer_query->m_tx_query = tx_query; + + // return deserialized query + return transfer_query; + } + + std::shared_ptr monero_transfer_query::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + return copy(std::static_pointer_cast(src), std::static_pointer_cast(tgt)); + }; + + std::shared_ptr monero_transfer_query::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + MTRACE("monero_transfer_query::copy(const std::shared_ptr& src, const std::shared_ptr& tgt)"); + if (this != src.get()) throw std::runtime_error("this != src"); + + // copy base class + monero_transfer::copy(std::static_pointer_cast(src), std::static_pointer_cast(tgt)); + + // copy extensions + tgt->m_is_incoming = src->m_is_incoming; + tgt->m_address = src->m_address; + if (!src->m_addresses.empty()) tgt->m_addresses = std::vector(src->m_addresses); + tgt->m_subaddress_index = src->m_subaddress_index; + if (!src->m_subaddress_indices.empty()) tgt->m_subaddress_indices = std::vector(src->m_subaddress_indices); + if (!src->m_destinations.empty()) { + for (const std::shared_ptr& destination : src->m_destinations) { + tgt->m_destinations.push_back(destination->copy(destination, std::make_shared())); + } + } + tgt->m_has_destinations = src->m_has_destinations; + tgt->m_tx_query = src->m_tx_query; + return tgt; + }; + + bool monero_transfer_query::meets_criteria(monero_transfer* transfer, bool query_parent) const { + if (transfer == nullptr) throw std::runtime_error("nullptr given to monero_transfer_query::meets_criteria()"); + + // filter on common fields + if (is_incoming() != boost::none && *is_incoming() != *transfer->is_incoming()) return false; + if (is_outgoing() != boost::none && *is_outgoing() != *transfer->is_outgoing()) return false; + if (m_amount != boost::none && *m_amount != *transfer->m_amount) return false; + if (m_account_index != boost::none && *m_account_index != *transfer->m_account_index) return false; + + // filter on incoming fields + monero_incoming_transfer* inTransfer = dynamic_cast(transfer); + if (inTransfer != nullptr) { + if (m_has_destinations != boost::none) return false; + if (m_address != boost::none && *m_address != *inTransfer->m_address) return false; + if (!m_addresses.empty() && find(m_addresses.begin(), m_addresses.end(), *inTransfer->m_address) == m_addresses.end()) return false; + if (m_subaddress_index != boost::none && *m_subaddress_index != *inTransfer->m_subaddress_index) return false; + if (!m_subaddress_indices.empty() && find(m_subaddress_indices.begin(), m_subaddress_indices.end(), *inTransfer->m_subaddress_index) == m_subaddress_indices.end()) return false; + } + + // filter on outgoing fields + monero_outgoing_transfer* out_transfer = dynamic_cast(transfer); + if (out_transfer != nullptr) { + + // filter on addresses + if (m_address != boost::none && (out_transfer->m_addresses.empty() || find(out_transfer->m_addresses.begin(), out_transfer->m_addresses.end(), *m_address) == out_transfer->m_addresses.end())) return false; // TODO: will filter all transfers if they don't contain addresses + if (!m_addresses.empty()) { + bool intersects = false; + for (const std::string& addressReq : m_addresses) { + for (const std::string& address : out_transfer->m_addresses) { + if (addressReq == address) { + intersects = true; + break; + } + } + } + if (!intersects) return false; // must have overlapping addresses + } + + // filter on subaddress indices + if (m_subaddress_index != boost::none && (out_transfer->m_subaddress_indices.empty() || find(out_transfer->m_subaddress_indices.begin(), out_transfer->m_subaddress_indices.end(), *m_subaddress_index) == out_transfer->m_subaddress_indices.end())) return false; // TODO: will filter all transfers if they don't contain subaddress indices + if (!m_subaddress_indices.empty()) { + bool intersects = false; + for (const uint32_t& subaddressIndexReq : m_subaddress_indices) { + for (const uint32_t& m_subaddress_index : out_transfer->m_subaddress_indices) { + if (subaddressIndexReq == m_subaddress_index) { + intersects = true; + break; + } + } + } + if (!intersects) return false; // must have overlapping subaddress indices + } + + // filter on having destinations + if (m_has_destinations != boost::none) { + if (*m_has_destinations && out_transfer->m_destinations.empty()) return false; + if (!*m_has_destinations && !out_transfer->m_destinations.empty()) return false; + } + + // filter on destinations TODO: start with test for this + // if (this.getDestionations() != null && this.getDestionations() != transfer.getDestionations()) return false; + } + + // validate type + if (inTransfer == nullptr && out_transfer == nullptr) throw std::runtime_error("Transfer must be monero_incoming_transfer or monero_outgoing_transfer"); + + // filter with tx query + if (query_parent && m_tx_query != nullptr && !m_tx_query->meets_criteria(transfer->m_tx.get(), false)) return false; + return true; + } + + bool monero_transfer_query::is_contextual(const monero_transfer_query& query) { + if (query.m_tx_query == nullptr) return false; + if (query.m_tx_query->m_is_incoming != boost::none) return true; // requires context of all transfers + if (query.m_tx_query->m_is_outgoing != boost::none) return true; + if (query.m_tx_query->m_input_query != nullptr) return true; // requires context of inputs + if (query.m_tx_query->m_output_query != nullptr) return true; // requires context of outputs + return false; + } + + boost::optional monero_transfer_query::is_incoming() const { return m_is_incoming; } + + // ------------------------- MONERO OUTPUT WALLET --------------------------- + + rapidjson::Value monero_output_wallet::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // serialize root from superclass + rapidjson::Value root = monero_output::to_rapidjson_val(allocator); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_account_index != boost::none) monero_utils::add_json_member("accountIndex", m_account_index.get(), allocator, root, value_num); + if (m_subaddress_index != boost::none) monero_utils::add_json_member("subaddressIndex", m_subaddress_index.get(), allocator, root, value_num); + + // set bool values + if (m_is_spent != boost::none) monero_utils::add_json_member("isSpent", m_is_spent.get(), allocator, root); + if (m_is_frozen != boost::none) monero_utils::add_json_member("isFrozen", m_is_frozen.get(), allocator, root); + + // return root + return root; + } + + void monero_output_wallet::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& output_wallet) { + monero_output::from_property_tree(node, output_wallet); + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("accountIndex")) output_wallet->m_account_index = it->second.get_value(); + else if (key == std::string("subaddressIndex")) output_wallet->m_subaddress_index = it->second.get_value(); + else if (key == std::string("isSpent")) output_wallet->m_is_spent = it->second.get_value(); + else if (key == std::string("isFrozen")) output_wallet->m_is_frozen = it->second.get_value(); + } + } + + std::shared_ptr monero_output_wallet::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + MTRACE("monero_output_wallet::copy(output)"); + return monero_output_wallet::copy(std::static_pointer_cast(src), std::static_pointer_cast(tgt)); + }; + + std::shared_ptr monero_output_wallet::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + MTRACE("monero_output_wallet::copy(output_wallet)"); + if (this != src.get()) throw std::runtime_error("this != src"); + + // copy base class + monero_output::copy(std::static_pointer_cast(src), std::static_pointer_cast(tgt)); + + // copy extensions + tgt->m_account_index = src->m_account_index; + tgt->m_subaddress_index = src->m_subaddress_index; + tgt->m_is_spent = src->m_is_spent; + tgt->m_is_frozen = src->m_is_frozen; + return tgt; + }; + + void monero_output_wallet::merge(const std::shared_ptr& self, const std::shared_ptr& other) { + merge(std::static_pointer_cast(self), std::static_pointer_cast(other)); + } + + void monero_output_wallet::merge(const std::shared_ptr& self, const std::shared_ptr& other) { + MTRACE("monero_output_wallet::merge(self, other)"); + if (this != self.get()) throw std::runtime_error("this != self"); + if (self == other) return; + + // merge base classes + monero_output::merge(self, other); + + // merge output wallet extensions + m_account_index = gen_utils::reconcile(m_account_index, other->m_account_index, "output wallet m_account_index"); + m_subaddress_index = gen_utils::reconcile(m_subaddress_index, other->m_subaddress_index, "output wallet m_subaddress_index"); + m_is_spent = gen_utils::reconcile(m_is_spent, other->m_is_spent, "output wallet m_is_spent"); + m_is_frozen = gen_utils::reconcile(m_is_frozen, other->m_is_frozen, "output wallet m_is_frozen"); + } + + // ------------------------ MONERO OUTPUT QUERY --------------------------- + + rapidjson::Value monero_output_query::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // serialize root from superclass + rapidjson::Value root = monero_output_wallet::to_rapidjson_val(allocator); + + // set sub-arrays + if (!m_subaddress_indices.empty()) root.AddMember("subaddressIndices", monero_utils::to_rapidjson_val(allocator, m_subaddress_indices), allocator); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_min_amount != boost::none) monero_utils::add_json_member("minAmount", m_min_amount.get(), allocator, root, value_num); + if (m_max_amount != boost::none) monero_utils::add_json_member("maxAmount", m_max_amount.get(), allocator, root, value_num); + + // return root + return root; + } + + void monero_output_query::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& output_query) { + monero_output_wallet::from_property_tree(node, output_query); + + // initialize query from node + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("subaddressIndices")) for (boost::property_tree::ptree::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) output_query->m_subaddress_indices.push_back(it2->second.get_value()); + else if (key == std::string("minAmount")) output_query->m_min_amount = it->second.get_value(); + else if (key == std::string("maxAmount")) output_query->m_max_amount = it->second.get_value(); + else if (key == std::string("txQuery")) {} // ignored + } + } + + std::shared_ptr monero_output_query::deserialize_from_block(const std::string& output_query_json) { + + // deserialize output query string to property rooted at block + std::istringstream iss = output_query_json.empty() ? std::istringstream() : std::istringstream(output_query_json); + boost::property_tree::ptree blockNode; + boost::property_tree::read_json(iss, blockNode); + + // convert query property tree to block + std::shared_ptr block = node_to_block_query(blockNode); + + // empty query if no txs + if (block->m_txs.empty()) return std::make_shared(); + + // get tx query + std::shared_ptr tx_query = std::static_pointer_cast(block->m_txs[0]); + + // get / create input query + std::shared_ptr input_query = tx_query->m_input_query == nullptr ? std::make_shared() : tx_query->m_input_query; + input_query->m_tx_query = tx_query; + + // get / create output query + std::shared_ptr output_query = tx_query->m_output_query == nullptr ? std::make_shared() : tx_query->m_output_query; + output_query->m_tx_query = tx_query; + + // return deserialized query + return output_query; + } + + std::shared_ptr monero_output_query::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + return monero_output_query::copy(std::static_pointer_cast(src), std::static_pointer_cast(tgt)); + }; + + std::shared_ptr monero_output_query::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + return monero_output_query::copy(std::static_pointer_cast(src), std::static_pointer_cast(tgt)); + }; + + std::shared_ptr monero_output_query::copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const { + MTRACE("monero_output_query::copy(output_query)"); + if (this != src.get()) throw std::runtime_error("this != src"); + + // copy base class + monero_output_wallet::copy(std::static_pointer_cast(src), std::static_pointer_cast(tgt)); + + // copy extensions + if (!src->m_subaddress_indices.empty()) tgt->m_subaddress_indices = std::vector(src->m_subaddress_indices); + tgt->m_min_amount = src->m_min_amount; + tgt->m_max_amount = src->m_max_amount; + tgt->m_tx_query = src->m_tx_query; + return tgt; + }; + + bool monero_output_query::meets_criteria(monero_output_wallet* output, bool query_parent) const { + if (output == nullptr) throw std::runtime_error("nullptr given to monero_output_query::meets_criteria()"); + + // filter on output + if (m_account_index != boost::none && (output->m_account_index == boost::none || *m_account_index != *output->m_account_index)) return false; + if (m_subaddress_index != boost::none && (output->m_subaddress_index == boost::none || *m_subaddress_index != *output->m_subaddress_index)) return false; + if (m_amount != boost::none && (output->m_amount == boost::none || *m_amount != *output->m_amount)) return false; + if (m_is_spent != boost::none && (output->m_is_spent == boost::none || *m_is_spent != *output->m_is_spent)) return false; + if (m_is_frozen != boost::none && (output->m_is_frozen == boost::none || *m_is_frozen != *output->m_is_frozen)) return false; + + // filter on output key image + if (m_key_image != nullptr) { + if (output->m_key_image == nullptr) return false; + if (m_key_image->m_hex != boost::none && (output->m_key_image->m_hex == boost::none || *m_key_image->m_hex != *output->m_key_image->m_hex)) return false; + if (m_key_image->m_signature != boost::none && (output->m_key_image->m_signature == boost::none || *m_key_image->m_signature != *output->m_key_image->m_signature)) return false; + } + + // filter on extensions + if (!m_subaddress_indices.empty() && find(m_subaddress_indices.begin(), m_subaddress_indices.end(), *output->m_subaddress_index) == m_subaddress_indices.end()) return false; + if (m_min_amount != boost::none && (output->m_amount == boost::none || output->m_amount.get() < m_min_amount.get())) return false; + if (m_max_amount != boost::none && (output->m_amount == boost::none || output->m_amount.get() > m_max_amount.get())) return false; + + // filter with tx query + if (query_parent && m_tx_query != nullptr && !m_tx_query->meets_criteria(std::static_pointer_cast(output->m_tx).get(), false)) return false; + + // output meets query + return true; + } + + bool monero_output_query::is_contextual(const monero_output_query& query) { + if (query.m_tx_query == nullptr) return false; + if (query.m_tx_query->m_is_incoming != boost::none) return true; // requires context of all transfers + if (query.m_tx_query->m_is_outgoing != boost::none) return true; + if (query.m_tx_query->m_transfer_query != nullptr) return true; // requires context of transfers + return false; + } + + // --------------------------- MONERO TX CONFIG ----------------------------- + + monero_tx_config::monero_tx_config(const monero_tx_config& config) { + m_address = config.m_address; + m_amount = config.m_amount; + if (config.m_destinations.size() > 0) { + for (const std::shared_ptr& destination : config.m_destinations) { + m_destinations.push_back(destination->copy(destination, std::make_shared())); + } + } + m_subtract_fee_from = config.m_subtract_fee_from; + m_payment_id = config.m_payment_id; + m_priority = config.m_priority; + m_ring_size = config.m_ring_size; + m_fee = config.m_fee; + m_account_index = config.m_account_index; + m_subaddress_indices = config.m_subaddress_indices; + m_can_split = config.m_can_split; + m_relay = config.m_relay; + m_note = config.m_note; + m_recipient_name = config.m_recipient_name; + m_below_amount = config.m_below_amount; + m_sweep_each_subaddress = config.m_sweep_each_subaddress; + m_key_image = config.m_key_image; + } + + monero_tx_config monero_tx_config::copy() const { + return monero_tx_config(*this); + } + + rapidjson::Value monero_tx_config::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_priority != boost::none) monero_utils::add_json_member("priority", m_priority.get(), allocator, root, value_num); + if (m_ring_size != boost::none) monero_utils::add_json_member("ringSize", m_ring_size.get(), allocator, root, value_num); + if (m_account_index != boost::none) monero_utils::add_json_member("accountIndex", m_account_index.get(), allocator, root, value_num); + if (m_below_amount != boost::none) monero_utils::add_json_member("belowAmount", m_below_amount.get(), allocator, root, value_num); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_payment_id != boost::none) monero_utils::add_json_member("paymentId", m_payment_id.get(), allocator, root, value_str); + if (m_note != boost::none) monero_utils::add_json_member("note", m_note.get(), allocator, root, value_str); + if (m_recipient_name != boost::none) monero_utils::add_json_member("recipientName", m_recipient_name.get(), allocator, root, value_str); + if (m_key_image != boost::none) monero_utils::add_json_member("keyImage", m_key_image.get(), allocator, root, value_str); + + // set bool values + if (m_can_split != boost::none) monero_utils::add_json_member("canSplit", m_can_split.get(), allocator, root); + if (m_relay != boost::none) monero_utils::add_json_member("relay", m_relay.get(), allocator, root); + if (m_sweep_each_subaddress != boost::none) monero_utils::add_json_member("sweepEachSubaddress", m_sweep_each_subaddress.get(), allocator, root); + + // set sub-arrays + if (!m_destinations.empty()) root.AddMember("destinations", monero_utils::to_rapidjson_val(allocator, m_destinations), allocator); + if (!m_subaddress_indices.empty()) root.AddMember("subaddressIndices", monero_utils::to_rapidjson_val(allocator, m_subaddress_indices), allocator); + if (!m_subtract_fee_from.empty()) root.AddMember("subtractFeeFrom", monero_utils::to_rapidjson_val(allocator, m_subtract_fee_from), allocator); + + // return root + return root; + } + + std::shared_ptr monero_tx_config::deserialize(const std::string& config_json) { + + // deserialize config json to property node + std::istringstream iss = config_json.empty() ? std::istringstream() : std::istringstream(config_json); + boost::property_tree::ptree node; + boost::property_tree::read_json(iss, node); + + // convert config property tree to monero_tx_config + std::shared_ptr config = std::make_shared(); + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("destinations")) { + boost::property_tree::ptree destinationsNode = it->second; + for (boost::property_tree::ptree::const_iterator it2 = destinationsNode.begin(); it2 != destinationsNode.end(); ++it2) { + std::shared_ptr destination = std::make_shared(); + monero_destination::from_property_tree(it2->second, destination); + config->m_destinations.push_back(destination); + } + } + else if (key == std::string("subtractFeeFrom")) for (boost::property_tree::ptree::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) config->m_subtract_fee_from.push_back(it2->second.get_value()); + else if (key == std::string("paymentId")) config->m_payment_id = it->second.data(); + else if (key == std::string("priority")) { + uint32_t priority_num = it->second.get_value(); + if (priority_num == 0) config->m_priority = monero_tx_priority::DEFAULT; + else if (priority_num == 1) config->m_priority = monero_tx_priority::UNIMPORTANT; + else if (priority_num == 2) config->m_priority = monero_tx_priority::NORMAL; + else if (priority_num == 3) config->m_priority = monero_tx_priority::ELEVATED; + else throw std::runtime_error("Invalid priority number: " + std::to_string(priority_num)); + } + else if (key == std::string("ringSize")) config->m_ring_size = it->second.get_value(); + else if (key == std::string("fee")) config->m_fee = it->second.get_value(); + else if (key == std::string("accountIndex")) config->m_account_index = it->second.get_value(); + else if (key == std::string("subaddressIndices")) for (boost::property_tree::ptree::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) config->m_subaddress_indices.push_back(it2->second.get_value()); + else if (key == std::string("canSplit")) config->m_can_split = it->second.get_value(); + else if (key == std::string("relay")) config->m_relay = it->second.get_value(); + else if (key == std::string("note")) config->m_note = it->second.data(); + else if (key == std::string("recipientName")) config->m_recipient_name = it->second.data(); + else if (key == std::string("belowAmount")) config->m_below_amount = it->second.get_value(); + else if (key == std::string("sweepEachSubaddress")) config->m_sweep_each_subaddress = it->second.get_value(); + else if (key == std::string("keyImage")) config->m_key_image = it->second.data(); + } + + return config; + } + + std::vector> monero_tx_config::get_normalized_destinations() const { + if (m_address == boost::none && m_amount == boost::none) return m_destinations; + else if (m_destinations.empty()) { + std::vector> destinations; + destinations.push_back(std::make_shared(m_address, m_amount)); + return destinations; + } else { + if (m_destinations.size() > 1) throw std::runtime_error("Invalid tx configuration: single destination address/amount incompatible with multiple destinations"); + if (m_address != m_destinations[0]->m_address) throw std::runtime_error("Invalid tx configuration: single destination address does not match first destination address"); + if (m_amount != m_destinations[0]->m_amount) throw std::runtime_error("Invalid tx configuration: single destination amount does not match first destination amount"); + return m_destinations; + } + } + + // ---------------------- MONERO INTEGRATED ADDRESS ------------------------- + + rapidjson::Value monero_integrated_address::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + monero_utils::add_json_member("standardAddress", m_standard_address, allocator, root, value_str); + monero_utils::add_json_member("paymentId", m_payment_id, allocator, root, value_str); + monero_utils::add_json_member("integratedAddress", m_integrated_address, allocator, root, value_str); + + // return root + return root; + } + + void monero_integrated_address::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& subaddress) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("integratedAddress")) subaddress->m_integrated_address = it->second.data(); + else if (key == std::string("standardAddress")) subaddress->m_standard_address = it->second.data(); + else if (key == std::string("paymentId")) subaddress->m_payment_id = it->second.data(); + } + } + + // -------------------- MONERO KEY IMAGE EXPORT RESULT ---------------------- + + rapidjson::Value monero_key_image_export_result::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_offset != boost::none) monero_utils::add_json_member("offset", m_offset.get(), allocator, root, value_num); + + // set sub-arrays + if (!m_key_images.empty()) root.AddMember("keyImages", monero_utils::to_rapidjson_val(allocator, m_key_images), allocator); + + // return root + return root; + } + + std::shared_ptr monero_key_image_export_result::deserialize(const std::string& result_json) { + + // deserialize json to property node + std::istringstream iss = result_json.empty() ? std::istringstream() : std::istringstream(result_json); + boost::property_tree::ptree node; + boost::property_tree::read_json(iss, node); + + // convert property tree to export result + std::shared_ptr result = std::make_shared(); + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("offset")) result->m_offset = it->second.get_value(); + else if (key == std::string("keyImages")) { + for (boost::property_tree::ptree::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) { + std::shared_ptr key_image = std::make_shared(); + monero_key_image::from_property_tree(it2->second, key_image); + result->m_key_images.push_back(key_image); + } + } + } + return result; + } + + // -------------------- MONERO KEY IMAGE IMPORT RESULT ---------------------- + + void monero_key_image_import_result::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("height")) result->m_height = it->second.get_value(); + else if (key == std::string("spentAmount")) result->m_spent_amount = it->second.get_value(); + else if (key == std::string("unspentAmount")) result->m_unspent_amount = it->second.get_value(); + } + } + + rapidjson::Value monero_key_image_import_result::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_height != boost::none) monero_utils::add_json_member("height", m_height.get(), allocator, root, value_num); + if (m_spent_amount != boost::none) monero_utils::add_json_member("spentAmount", m_spent_amount.get(), allocator, root, value_num); + if (m_unspent_amount != boost::none) monero_utils::add_json_member("unspentAmount", m_unspent_amount.get(), allocator, root, value_num); + + // return root + return root; + } + + // -------------------- MONERO MESSAGE SIGNATURE RESULT --------------------- + + void monero_message_signature_result::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr result) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("isGood")) result->m_is_good = it->second.get_value(); + else if (key == std::string("isOld")) result->m_is_old = it->second.get_value(); + else if (key == std::string("signatureType")) { + std::string sig_type = it->second.data(); + if (sig_type == "view") result->m_signature_type = monero_message_signature_type::SIGN_WITH_VIEW_KEY; + else if (sig_type == "spend") result->m_signature_type = monero_message_signature_type::SIGN_WITH_SPEND_KEY; + else throw std::runtime_error("Invalid message signature type: " + sig_type); + } + else if (key == std::string("version")) result->m_version = it->second.get_value(); + } + } + + rapidjson::Value monero_message_signature_result::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set bool values + monero_utils::add_json_member("isGood", m_is_good, allocator, root); + monero_utils::add_json_member("isOld", m_is_old, allocator, root); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + monero_utils::add_json_member("version", m_version, allocator, root, value_num); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + monero_utils::add_json_member("signatureType", m_signature_type == monero_message_signature_type::SIGN_WITH_SPEND_KEY ? std::string("spend") : std::string("view"), allocator, root, value_str); + + // return root + return root; + } + + // ----------------------------- MONERO CHECK ------------------------------- + + rapidjson::Value monero_check::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set bool values + monero_utils::add_json_member("isGood", m_is_good, allocator, root); + + // return root + return root; + } + + // --------------------------- MONERO CHECK TX ------------------------------ + + void monero_check_tx::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("isGood")) check->m_is_good = it->second.get_value(); + if (key == std::string("inTxPool")) check->m_in_tx_pool = it->second.get_value(); + else if (key == std::string("numConfirmations")) check->m_num_confirmations = it->second.get_value(); + else if (key == std::string("receivedAmount")) check->m_received_amount = it->second.get_value(); + } + } + + rapidjson::Value monero_check_tx::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // serialize root from superclass + rapidjson::Value root = monero_check::to_rapidjson_val(allocator); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_num_confirmations != boost::none) monero_utils::add_json_member("numConfirmations", m_num_confirmations.get(), allocator, root, value_num); + if (m_received_amount != boost::none) monero_utils::add_json_member("receivedAmount", m_received_amount.get(), allocator, root, value_num); + + // set bool values + if (m_in_tx_pool != boost::none) monero_utils::add_json_member("inTxPool", m_in_tx_pool.get(), allocator, root); + + // return root + return root; + } + + // ------------------------ MONERO CHECK RESERVE ---------------------------- + + rapidjson::Value monero_check_reserve::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // serialize root from superclass + rapidjson::Value root = monero_check::to_rapidjson_val(allocator); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_total_amount != boost::none) monero_utils::add_json_member("totalAmount", m_total_amount.get(), allocator, root, value_num); + if (m_unconfirmed_spent_amount != boost::none) monero_utils::add_json_member("unconfirmedSpentAmount", m_unconfirmed_spent_amount.get(), allocator, root, value_num); + + // return root + return root; + } + + void monero_check_reserve::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("isGood")) check->m_is_good = it->second.get_value(); + else if (key == std::string("totalAmount")) check->m_total_amount = it->second.get_value(); + else if (key == std::string("unconfirmedSpentAmount")) check->m_unconfirmed_spent_amount = it->second.get_value(); + } + } + + // --------------------------- MONERO MULTISIG ------------------------------ + + void monero_multisig_info::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("isMultisig")) info->m_is_multisig = it->second.get_value(); + else if (key == std::string("isReady")) info->m_is_ready = it->second.get_value(); + else if (key == std::string("threshold")) info->m_threshold = it->second.get_value(); + else if (key == std::string("numParticipants")) info->m_num_participants = it->second.get_value(); + } + } + + rapidjson::Value monero_multisig_info::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + monero_utils::add_json_member("threshold", m_threshold, allocator, root, value_num); + monero_utils::add_json_member("numParticipants", m_num_participants, allocator, root, value_num); + + // set bool values + monero_utils::add_json_member("isMultisig", m_is_multisig, allocator, root); + monero_utils::add_json_member("isReady", m_is_ready, allocator, root); + + // return root + return root; + } + + void monero_multisig_init_result::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("address")) info->m_address = it->second.data(); + else if (key == std::string("multisigHex")) info->m_multisig_hex = it->second.data(); + } + } + + rapidjson::Value monero_multisig_init_result::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); + if (m_multisig_hex != boost::none) monero_utils::add_json_member("multisigHex", m_multisig_hex.get(), allocator, root, value_str); + + // return root + return root; + } + + void monero_multisig_sign_result::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& res) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("signedMultisigTxHex")) res->m_signed_multisig_tx_hex = it->second.data(); + else if (key == std::string("txHashes")) { + for (boost::property_tree::ptree::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) { + res->m_tx_hashes.push_back(it2->second.data()); + } + } + } + } + + rapidjson::Value monero_multisig_sign_result::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_signed_multisig_tx_hex != boost::none) monero_utils::add_json_member("signedMultisigTxHex", m_signed_multisig_tx_hex.get(), allocator, root, value_str); + + // set sub-arrays + if (!m_tx_hashes.empty()) root.AddMember("txHashes", monero_utils::to_rapidjson_val(allocator, m_tx_hashes), allocator); + + // return root + return root; + } + + // -------------------------- MONERO ADDRESS BOOK --------------------------- + + rapidjson::Value monero_address_book_entry::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_index != boost::none) monero_utils::add_json_member("index", m_index.get(), allocator, root, value_num); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); + if (m_description != boost::none) monero_utils::add_json_member("description", m_description.get(), allocator, root, value_str); + if (m_payment_id != boost::none) monero_utils::add_json_member("paymentId", m_payment_id.get(), allocator, root, value_str); + + // return root + return root; + } + + void monero_address_book_entry::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& entry) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("index")) entry->m_index = it->second.get_value(); + else if (key == std::string("address")) entry->m_address = it->second.data(); + else if (key == std::string("description")) entry->m_description = it->second.data(); + else if (key == std::string("paymentId")) entry->m_payment_id = it->second.data(); + } + } + + // -------------------------- MONERO COMPARATORS --------------------------- + + bool monero_tx_height_comparator::operator()(const std::shared_ptr& tx1, const std::shared_ptr& tx2) const { + auto h1 = tx1->get_height(); + auto h2 = tx2->get_height(); + + if (h1 == boost::none && h2 == boost::none) { + // both unconfirmed + return false; + } + else if (h1 == boost::none) { + // tx1 is unconfirmed + return false; + } + else if (h2 == boost::none) { + // tx2 is unconfirmed + return true; + } + + if (*h1 != *h2) { + return *h1 < *h2; + } + + // txs are in the same block so retain their original order + const auto& txs = tx1->m_block->m_txs; + auto it1 = std::find(txs.begin(), txs.end(), tx1); + auto it2 = std::find(txs.begin(), txs.end(), tx2); + + return std::distance(txs.begin(), it1) < std::distance(txs.begin(), it2); + } + + bool monero_incoming_transfer_comparator::operator()(const std::shared_ptr& t1, const std::shared_ptr& t2) const { + return (*this)(*t1, *t2); + } + + bool monero_incoming_transfer_comparator::operator()(const monero_incoming_transfer& t1, const monero_incoming_transfer& t2) const { + monero_tx_height_comparator tx_comp; + + // compare by height + if (tx_comp(t1.m_tx, t2.m_tx)) return true; + if (tx_comp(t2.m_tx, t1.m_tx)) return false; + + // compare by account and subaddress index + if (t1.m_account_index.value() != t2.m_account_index.value()) { + return t1.m_account_index.value() < t2.m_account_index.value(); + } + + return t1.m_subaddress_index.value() < t2.m_subaddress_index.value(); + } + + bool monero_output_comparator::operator()(const monero_output_wallet& o1, const monero_output_wallet& o2) const { + monero_tx_height_comparator tx_comp; + + if (tx_comp(o1.m_tx, o2.m_tx)) return true; + if (tx_comp(o2.m_tx, o1.m_tx)) return false; + + if (o1.m_account_index.value() != o2.m_account_index.value()) { + return o1.m_account_index.value() < o2.m_account_index.value(); + } + + if (o1.m_subaddress_index.value() != o2.m_subaddress_index.value()) { + return o1.m_subaddress_index.value() < o2.m_subaddress_index.value(); + } + + if (o1.m_index.value() != o2.m_index.value()) { + return o1.m_index.value() < o2.m_index.value(); + } + + return o1.m_key_image->m_hex.value() < o2.m_key_image->m_hex.value(); + } + + // --------------------------- MONERO DECODED ADDRESS --------------------------- + + monero_decoded_address::monero_decoded_address(const std::string& address, monero_address_type address_type, monero_network_type network_type): + m_address(address), + m_address_type(address_type), + m_network_type(network_type) { + } + + rapidjson::Value monero_decoded_address::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + monero_utils::add_json_member("address", m_address, allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + monero_utils::add_json_member("addressType", (uint8_t)m_address_type, allocator, root, value_num); + monero_utils::add_json_member("networkType", (uint8_t)m_network_type, allocator, root, value_num); + + // return root + return root; + } + + // --------------------------- MONERO ACCOUNT TAG --------------------------- + + void monero_account_tag::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& account_tag) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("tag")) account_tag->m_tag = it->second.data(); + else if (key == std::string("label") && !it->second.data().empty()) account_tag->m_label = it->second.data(); + else if (key == std::string("accountIndices")) { + for (boost::property_tree::ptree::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) { + account_tag->m_account_indices.push_back(it2->second.get_value()); + } + } + } + } + + rapidjson::Value monero_account_tag::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_tag != boost::none) monero_utils::add_json_member("tag", m_tag.get(), allocator, root, value_str); + if (m_label != boost::none) monero_utils::add_json_member("label", m_label.get(), allocator, root, value_str); + if (!m_account_indices.empty()) root.AddMember("accountIndices", monero_utils::to_rapidjson_val(allocator, m_account_indices), allocator); + + // return root + return root; + } +} diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_model.h b/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_model.h new file mode 100644 index 0000000..bfa03df --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_model.h @@ -0,0 +1,609 @@ +/** + * Copyright (c) woodser + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ + +#pragma once + +#include "common/monero_rpc_connection.h" +#include "daemon/monero_daemon_model.h" + +using namespace monero; + +/** + * Public library interface. + */ +namespace monero { + + /** + * Configures a wallet to create. + */ + struct monero_wallet_config : public serializable_struct { + boost::optional m_path; + boost::optional m_password; + boost::optional m_network_type; + std::shared_ptr m_server; + boost::optional m_is_trusted_daemon; + boost::optional m_seed; + boost::optional m_seed_offset; + boost::optional m_primary_address; + boost::optional m_private_view_key; + boost::optional m_private_spend_key; + boost::optional m_restore_height; + boost::optional m_language; + boost::optional m_save_current; + boost::optional m_account_lookahead; + boost::optional m_subaddress_lookahead; + boost::optional m_is_multisig; + boost::optional m_regtest; + + monero_wallet_config() {} + monero_wallet_config(const monero_wallet_config& config); + monero_wallet_config copy() const; + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static std::shared_ptr deserialize(const std::string& config_json); + }; + + /** + * Models a result of syncing a wallet. + */ + struct monero_sync_result : public serializable_struct { + uint64_t m_num_blocks_fetched; + bool m_received_money; + monero_sync_result() {} + monero_sync_result(const uint64_t num_blocks_fetched, const bool received_money) : m_num_blocks_fetched(num_blocks_fetched), m_received_money(received_money) {} + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + }; + + /** + * Models a Monero subaddress. + */ + struct monero_subaddress : public serializable_struct { + boost::optional m_account_index; + boost::optional m_index; + boost::optional m_address; + boost::optional m_label; + boost::optional m_balance; + boost::optional m_unlocked_balance; + boost::optional m_num_unspent_outputs; + boost::optional m_is_used; + boost::optional m_num_blocks_to_unlock; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& subaddress); + }; + + /** + * Models a Monero account. + */ + struct monero_account : public serializable_struct { + boost::optional m_index; + boost::optional m_primary_address; + boost::optional m_balance; + boost::optional m_unlocked_balance; + boost::optional m_tag; + std::vector m_subaddresses; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& account); + }; + + /** + * Models an outgoing transfer destination. + */ + struct monero_destination : public serializable_struct { + boost::optional m_address; + boost::optional m_amount; + + monero_destination(boost::optional address = boost::none, boost::optional amount = boost::none) : m_address(address), m_amount(amount) {} + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& destination); + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; + }; + + // forward declarations + struct monero_tx_config; + struct monero_tx_wallet; + struct monero_tx_query; + struct monero_tx_set; + + /** + * Models a base transfer of funds to or from the wallet. + * + * TODO: m_is_incoming for api consistency + */ + struct monero_transfer : public serializable_struct { + std::shared_ptr m_tx; + boost::optional m_amount; + boost::optional m_account_index; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& transfer); + virtual boost::optional is_incoming() const = 0; // derived class must implement + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; + boost::optional is_outgoing() const { + if (is_incoming() == boost::none) return boost::none; + return !(*is_incoming()); + } + void merge(const std::shared_ptr& self, const std::shared_ptr& other); + }; + + /** + * Models an incoming transfer of funds to the wallet. + */ + struct monero_incoming_transfer : public monero_transfer { + boost::optional m_subaddress_index; + boost::optional m_address; + boost::optional m_num_suggested_confirmations; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; + boost::optional is_incoming() const; + void merge(const std::shared_ptr& self, const std::shared_ptr& other); + void merge(const std::shared_ptr& self, const std::shared_ptr& other); + }; + + /** + * Models an outgoing transfer of funds from the wallet. + */ + struct monero_outgoing_transfer : public monero_transfer { + std::vector m_subaddress_indices; + std::vector m_addresses; + std::vector> m_destinations; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; + boost::optional is_incoming() const; + void merge(const std::shared_ptr& self, const std::shared_ptr& other); + void merge(const std::shared_ptr& self, const std::shared_ptr& other); + }; + + /** + * Configures a query to retrieve transfers. + * + * All transfers are returned except those that do not meet the criteria defined in this query. + */ + struct monero_transfer_query : public monero_transfer { + boost::optional m_is_incoming; + boost::optional m_address; + std::vector m_addresses; + boost::optional m_subaddress_index; + std::vector m_subaddress_indices; + std::vector> m_destinations; + boost::optional m_has_destinations; + std::shared_ptr m_tx_query; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& transfer_query); + static std::shared_ptr deserialize_from_block(const std::string& transfer_query_json); + static bool is_contextual(const monero_transfer_query& query); + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; + boost::optional is_incoming() const; + bool meets_criteria(monero_transfer* transfer, bool query_parent = true) const; + }; + + /** + * Models a Monero output with wallet extensions. + */ + struct monero_output_wallet : public monero_output { + boost::optional m_account_index; + boost::optional m_subaddress_index; + boost::optional m_is_spent; + boost::optional m_is_frozen; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& output_wallet); + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; + void merge(const std::shared_ptr& self, const std::shared_ptr& other); + void merge(const std::shared_ptr& self, const std::shared_ptr& other); + }; + + /** + * Configures a query to retrieve wallet outputs (i.e. outputs that the wallet has or had the + * ability to spend). + * + * All outputs are returned except those that do not meet the criteria defined in this query. + */ + struct monero_output_query : public monero_output_wallet { + std::vector m_subaddress_indices; + boost::optional m_min_amount; + boost::optional m_max_amount; + std::shared_ptr m_tx_query; + + //boost::property_tree::ptree to_property_tree() const; + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& output_query); + static std::shared_ptr deserialize_from_block(const std::string& output_query_json); + static bool is_contextual(const monero_output_query& query); + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; // TODO: necessary to override all super classes? + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; + bool meets_criteria(monero_output_wallet* output, bool query_parent = true) const; + }; + + /** + * Models a Monero transaction in the context of a wallet. + */ + struct monero_tx_wallet : public monero_tx { + std::shared_ptr m_tx_set; + boost::optional m_is_incoming; + boost::optional m_is_outgoing; + std::vector> m_incoming_transfers; + std::shared_ptr m_outgoing_transfer; + boost::optional m_note; + boost::optional m_is_locked; + boost::optional m_input_sum; + boost::optional m_output_sum; + boost::optional m_change_address; + boost::optional m_change_amount; + boost::optional m_num_dummy_outputs; + boost::optional m_extra_hex; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& tx_wallet); + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; + void merge(const std::shared_ptr& self, const std::shared_ptr& other); + void merge(const std::shared_ptr& self, const std::shared_ptr& other); + std::vector> get_transfers() const; + std::vector> get_transfers(const monero_transfer_query& query) const; + std::vector> filter_transfers(const monero_transfer_query& query); + std::vector> get_outputs_wallet() const; + std::vector> get_outputs_wallet(const monero_output_query& query) const; + std::vector> filter_outputs_wallet(const monero_output_query& query); + }; + + /** + * Configures a query to retrieve transactions. + * + * All transactions are returned except those that do not meet the criteria defined in this query. + */ + struct monero_tx_query : public monero_tx_wallet { + boost::optional m_is_outgoing; + boost::optional m_is_incoming; + std::vector m_hashes; + boost::optional m_has_payment_id; + std::vector m_payment_ids; + boost::optional m_height; + boost::optional m_min_height; + boost::optional m_max_height; + boost::optional m_include_outputs; + std::shared_ptr m_transfer_query; + std::shared_ptr m_input_query; + std::shared_ptr m_output_query; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& tx_query); + static std::shared_ptr deserialize_from_block(const std::string& tx_query_json); + + /** + * Remove query criteria which require looking up other transfers/outputs to + * fulfill query. + * + * @param query the query to decontextualize + * @return a reference to the query for convenience + */ + static std::shared_ptr decontextualize(std::shared_ptr query); + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; // TODO: necessary to override all super classes? + std::shared_ptr copy(const std::shared_ptr& src, const std::shared_ptr& tgt) const; + bool meets_criteria(monero_tx_wallet* tx, bool query_children = true) const; + }; + + /** + * Groups transactions who share common hex data which is needed in order to + * sign and submit the transactions. + * + * For example, multisig transactions created from create_txs() share a common + * hex string which is needed in order to sign and submit the multisig + * transactions. + */ + struct monero_tx_set : public serializable_struct { + std::vector> m_txs; + boost::optional m_signed_tx_hex; + boost::optional m_unsigned_tx_hex; + boost::optional m_multisig_tx_hex; + + //boost::property_tree::ptree to_property_tree() const; + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& set); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static monero_tx_set deserialize(const std::string& tx_set_json); + }; + + /** + * Monero integrated address model. + */ + struct monero_integrated_address : public serializable_struct { + std::string m_standard_address; + std::string m_payment_id; + std::string m_integrated_address; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& subaddress); + }; + + /** + * Enumerates Monero network types. + */ + enum monero_tx_priority : uint8_t { + DEFAULT = 0, + UNIMPORTANT, + NORMAL, + ELEVATED + }; + + /** + * Configures a transaction to send, sweep, or create a payment URI. + */ + struct monero_tx_config : public serializable_struct { + boost::optional m_address; + boost::optional m_amount; + std::vector> m_destinations; + std::vector m_subtract_fee_from; + boost::optional m_payment_id; + boost::optional m_priority; + boost::optional m_ring_size; + boost::optional m_fee; + boost::optional m_account_index; + std::vector m_subaddress_indices; + boost::optional m_can_split; + boost::optional m_relay; + boost::optional m_note; + boost::optional m_recipient_name; + boost::optional m_below_amount; + boost::optional m_sweep_each_subaddress; + boost::optional m_key_image; + + monero_tx_config() {} + monero_tx_config(const monero_tx_config& config); + monero_tx_config copy() const; + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static std::shared_ptr deserialize(const std::string& config_json); + std::vector> get_normalized_destinations() const; + }; + + /** + * Models results from exporting signed key images. + */ + struct monero_key_image_export_result : public serializable_struct { + boost::optional m_offset; + std::vector> m_key_images; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static std::shared_ptr deserialize(const std::string& result_json); + }; + + /** + * Models results from importing key images. + */ + struct monero_key_image_import_result : public serializable_struct { + boost::optional m_height; + boost::optional m_spent_amount; + boost::optional m_unspent_amount; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result); + }; + + /** + * Enumerates message verification results. + */ + enum monero_message_signature_type : uint8_t { + SIGN_WITH_SPEND_KEY = 0, + SIGN_WITH_VIEW_KEY + }; + + /** + * Models results from message verification. + */ + struct monero_message_signature_result : public serializable_struct { + bool m_is_good; + uint32_t m_version; + bool m_is_old; + monero_message_signature_type m_signature_type; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr result); + }; + + /** + * Base class for results from checking a transaction or reserve proof. + */ + struct monero_check : public serializable_struct { + bool m_is_good; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + }; + + /** + * Results from checking a transaction key. + */ + struct monero_check_tx : public monero_check { + boost::optional m_in_tx_pool; + boost::optional m_num_confirmations; + boost::optional m_received_amount; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check); + }; + + /** + * Results from checking a reserve proof. + */ + struct monero_check_reserve : public monero_check { + boost::optional m_total_amount; + boost::optional m_unconfirmed_spent_amount; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check); + }; + + /** + * Models information about a multisig wallet. + */ + struct monero_multisig_info : public serializable_struct { + bool m_is_multisig; + bool m_is_ready; + uint32_t m_threshold; + uint32_t m_num_participants; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info); + }; + + /** + * Models the result of initializing a multisig wallet which results in the + * multisig wallet's address xor another multisig hex to share with + * participants to create the wallet. + */ + struct monero_multisig_init_result : public serializable_struct { + boost::optional m_address; + boost::optional m_multisig_hex; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& res); + }; + + /** + * Models the result of signing multisig tx hex. + */ + struct monero_multisig_sign_result : public serializable_struct { + boost::optional m_signed_multisig_tx_hex; + std::vector m_tx_hashes; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& res); + }; + + /** + * Monero address book entry model. + */ + struct monero_address_book_entry : public serializable_struct { + boost::optional m_index; // TODO: not boost::optional + boost::optional m_address; + boost::optional m_description; + boost::optional m_payment_id; + + monero_address_book_entry() {} + monero_address_book_entry(uint64_t index, const std::string& address, const std::string& description) : m_index(index), m_address(address), m_description(description) {} + monero_address_book_entry(uint64_t index, const std::string& address, const std::string& description, const std::string& payment_id) : m_index(index), m_address(address), m_description(description), m_payment_id(payment_id) {} + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& entry); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const; + }; + + /** + * Models a Monero address type. + */ + enum monero_address_type : uint8_t { + PRIMARY_ADDRESS = 0, + INTEGRATED_ADDRESS, + SUBADDRESS + }; + + /** + * Compares two transactions by their height. + */ + struct monero_tx_height_comparator { + bool operator()(const std::shared_ptr& tx1, const std::shared_ptr& tx2) const; + }; + + /** + * Compares two transfers by ascending account and subaddress indices. + */ + struct monero_incoming_transfer_comparator { + bool operator()(const std::shared_ptr& t1, const std::shared_ptr& t2) const; + bool operator()(const monero_incoming_transfer& t1, const monero_incoming_transfer& t2) const; + }; + + /** + * Compares two outputs by ascending account and subaddress indices. + */ + struct monero_output_comparator { + bool operator()(const monero_output_wallet& o1, const monero_output_wallet& o2) const; + }; + + /** + * Maintains metadata for a decoded address. + */ + struct monero_decoded_address : public serializable_struct { + std::string m_address; + monero_address_type m_address_type; + monero_network_type m_network_type; + + monero_decoded_address(const std::string& address, monero_address_type address_type, monero_network_type network_type); + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + /** + * Represents an account tag. + */ + struct monero_account_tag : public serializable_struct { + boost::optional m_tag; + boost::optional m_label; + std::vector m_account_indices; + + monero_account_tag() { } + monero_account_tag(const std::string& tag, const std::string& label): m_tag(tag), m_label(label) { } + monero_account_tag(const std::string& tag, const std::string& label, const std::vector& account_indices): m_tag(tag), m_label(label), m_account_indices(account_indices) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& account_tag); + }; + +} diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_rpc.cpp b/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_rpc.cpp new file mode 100644 index 0000000..0c83f1e --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_rpc.cpp @@ -0,0 +1,2204 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ +#include "monero_wallet_rpc.h" +#include "monero_wallet_rpc_model.h" +#include "common/monero_error.h" +#include "utils/gen_utils.h" +#include "utils/monero_utils.h" +#include + +namespace monero { + + namespace { + // RAII guard for monero_tx_query trees copied for get_txs()/get_transfers_aux()/ + // get_outputs_aux(): they hold circular shared_ptr references (m_tx_query <-> + // m_transfer_query/m_output_query) that plain refcounting can't break, so + // monero_utils::free() must run on every exit path or the copies leak. Swallow: free() + // can throw (e.g. allocation failure), and a throwing destructor during unwind is + // std::terminate. + struct query_free_guard { + std::shared_ptr tx_query; + ~query_free_guard() { try { monero_utils::free(tx_query); } catch (...) {} } + }; + } + + /** + * Polls wallet and sends notifications in order to notify external wallet listeners. + */ + class monero_wallet_poller: public gen_utils::thread_poller { + public: + + explicit monero_wallet_poller(monero_wallet_rpc *wallet): m_num_polling(0) { + m_wallet = wallet; + init_common("monero_wallet_rpc"); + }; + + ~monero_wallet_poller() override { + set_is_polling(false); + } + + void poll() override { + // skip if next poll is queued + if (m_num_polling.fetch_add(1) > 1) { + m_num_polling--; + return; + } + + struct decrement_guard { + std::atomic& counter; + ~decrement_guard() { counter--; } + } num_polling_guard{m_num_polling}; + + // synchronize polls + boost::lock_guard lock(m_mutex); + gen_utils::thread_poller::announce_scope announce_guard(*this); // see wait_for_callbacks_idle() + try { + // skip if wallet is closed + if (m_wallet->is_closed()) { + return; + } + + // take initial snapshot + if (m_prev_balances == nullptr) { + m_prev_height = m_wallet->get_height(); + monero_tx_query tx_query; + tx_query.m_is_locked = true; + m_prev_locked_txs = m_wallet->get_txs(tx_query); + m_prev_balances = m_wallet->get_balances(boost::none, boost::none); + return; + } + + // announce height changes + uint64_t height = m_wallet->get_height(); + if (m_prev_height.get() != height) { + for (uint64_t i = m_prev_height.get(); i < height; i++) { + on_new_block(i); + } + + m_prev_height = height; + } + + // get locked txs for comparison to previous + uint64_t min_height = 0; // only monitor recent txs + if (height > 70) min_height = height - 70; + monero_tx_query tx_query; + tx_query.m_is_locked = true; + tx_query.m_min_height = min_height; + tx_query.m_include_outputs = true; + + auto locked_txs = m_wallet->get_txs(tx_query); + + // collect hashes of txs no longer locked + std::vector no_longer_locked_hashes; + for (const auto &prev_locked_tx : m_prev_locked_txs) { + if (get_tx(locked_txs, prev_locked_tx->m_hash.get()) == nullptr) { + no_longer_locked_hashes.push_back(prev_locked_tx->m_hash.get()); + } + } + + // save locked txs for next comparison + m_prev_locked_txs = locked_txs; + std::vector> unlocked_txs; + + if (!no_longer_locked_hashes.empty()) { + // fetch txs which are no longer locked + monero_tx_query tx_query; + tx_query.m_is_locked = false; + tx_query.m_min_height = min_height; + tx_query.m_hashes = no_longer_locked_hashes; + tx_query.m_include_outputs = true; + unlocked_txs = m_wallet->get_txs(tx_query); + } + + // announce new unconfirmed and confirmed txs + for (const auto &locked_tx : locked_txs) { + bool announced = false; + const std::string& tx_hash = locked_tx->m_hash.get(); + if (gen_utils::bool_equals(true, locked_tx->m_is_confirmed)) { + if (std::find(m_prev_confirmed_notifications.begin(), m_prev_confirmed_notifications.end(), tx_hash) == m_prev_confirmed_notifications.end()) { + m_prev_confirmed_notifications.push_back(tx_hash); + announced = true; + } + } + else { + if (std::find(m_prev_unconfirmed_notifications.begin(), m_prev_unconfirmed_notifications.end(), tx_hash) == m_prev_unconfirmed_notifications.end()) { + m_prev_unconfirmed_notifications.push_back(tx_hash); + announced = true; + } + } + + if (announced) notify_outputs(locked_tx); + } + + // announce new unlocked outputs + for (const auto &unlocked_tx : unlocked_txs) { + std::string tx_hash = unlocked_tx->m_hash.get(); + // stop tracking tx notifications + m_prev_confirmed_notifications.erase(std::remove_if(m_prev_confirmed_notifications.begin(), m_prev_confirmed_notifications.end(), [&tx_hash](const std::string& iter){ return iter == tx_hash; }), m_prev_confirmed_notifications.end()); + m_prev_unconfirmed_notifications.erase(std::remove_if(m_prev_unconfirmed_notifications.begin(), m_prev_unconfirmed_notifications.end(), [&tx_hash](const std::string& iter){ return iter == tx_hash; }), m_prev_unconfirmed_notifications.end()); + notify_outputs(unlocked_tx); + } + + // announce balance changes + check_for_changed_balances(); + } + catch (const std::exception &e) { + if (m_is_polling) { + MERROR("Failed to background poll wallet " << m_wallet->get_path() << ": " << e.what()); + } + } + catch (...) { + if (m_is_polling) { + MERROR("Failed to background poll wallet " << m_wallet->get_path()); + } + } + } + + private: + monero_wallet_rpc *m_wallet; + std::atomic m_num_polling; + + std::vector m_prev_unconfirmed_notifications; + std::vector m_prev_confirmed_notifications; + std::shared_ptr m_prev_balances; + boost::optional m_prev_height; + std::vector> m_prev_locked_txs; + + std::shared_ptr get_tx(const std::vector>& txs, const std::string& tx_hash){ + for (const auto& tx : txs) { + if (tx->m_hash == tx_hash) return tx; + } + + return nullptr; + } + + void on_new_block(uint64_t height) { + announce_new_block(height); + } + + void notify_outputs(const std::shared_ptr &tx) { + // notify spent outputs + // TODO (monero-project): monero-wallet-rpc does not allow scrape of tx inputs so providing one input with outgoing amount + if (tx->m_outgoing_transfer != nullptr) { + auto outgoing_transfer = tx->m_outgoing_transfer; + if (!tx->m_inputs.empty()) throw monero_error("Tx inputs should be empty"); + auto output = std::make_shared(); + output->m_amount = outgoing_transfer->m_amount.get() + tx->m_fee.get(); + output->m_account_index = outgoing_transfer->m_account_index; + output->m_tx = tx; + // initialize if transfer sourced from single subaddress + if (outgoing_transfer->m_subaddress_indices.size() == 1) { + output->m_subaddress_index = outgoing_transfer->m_subaddress_indices[0]; + } + tx->m_inputs.clear(); + tx->m_inputs.push_back(output); + announce_output_spent(output); + } + + // notify received outputs + if (tx->m_incoming_transfers.size() > 0) { + if (!tx->m_outputs.empty()) { + // TODO (monero-project): outputs only returned for confirmed txs + for(const auto &output : tx->get_outputs_wallet()) { + announce_output_received(output); + } + } + else { + // TODO (monero-project): monero-wallet-rpc does not allow scrape of unconfirmed received outputs so using incoming transfer values + tx->m_outputs.clear(); + for (const auto &transfer : tx->m_incoming_transfers) { + auto output = std::make_shared(); + output->m_account_index = transfer->m_account_index; + output->m_subaddress_index = transfer->m_subaddress_index; + output->m_amount = transfer->m_amount.get(); + output->m_tx = tx; + tx->m_outputs.push_back(output); + } + + for (const auto &output : tx->get_outputs_wallet()) { + announce_output_received(output); + } + } + } + } + + bool check_for_changed_balances() { + std::shared_ptr balances = m_wallet->get_balances(boost::none, boost::none); + if (balances->m_balance != m_prev_balances->m_balance || balances->m_unlocked_balance != m_prev_balances->m_unlocked_balance) { + m_prev_balances = balances; + announce_balances_changed(balances->m_balance.get(), balances->m_unlocked_balance.get()); + return true; + } + return false; + } + + // mirrors monero_daemon_poller + void announce_new_block(uint64_t height) { + for (const auto &listener : m_wallet->get_listeners()) { + try { + listener->on_new_block(height); + } catch (const std::exception &e) { + MERROR(e.what()); + } + } + } + + void announce_balances_changed(uint64_t balance, uint64_t unlocked_balance) { + for (const auto &listener : m_wallet->get_listeners()) { + try { + listener->on_balances_changed(balance, unlocked_balance); + } catch (const std::exception &e) { + MERROR(e.what()); + } + } + } + + void announce_output_spent(const std::shared_ptr &output) { + for (const auto &listener : m_wallet->get_listeners()) { + try { + listener->on_output_spent(*output); + } catch (const std::exception &e) { + MERROR(e.what()); + } + } + } + + void announce_output_received(const std::shared_ptr &output) { + for (const auto &listener : m_wallet->get_listeners()) { + try { + listener->on_output_received(*output); + } catch (const std::exception &e) { + MERROR(e.what()); + } + } + } + }; + + monero_wallet_rpc::monero_wallet_rpc(const std::shared_ptr& rpc_connection): m_rpc(rpc_connection) { + if (rpc_connection == nullptr) throw monero_error("Connection cannot be null"); + if (!rpc_connection->is_online().value_or(false) && rpc_connection->m_uri != boost::none) rpc_connection->check_connection(); + } + + monero_wallet_rpc::monero_wallet_rpc(const std::string& uri, const std::string& username, const std::string& password, const std::string& proxy_uri, const std::string& zmq_uri, const boost::optional& timeout): m_rpc(std::make_shared(uri, username, password, proxy_uri, zmq_uri, 0, timeout)) { + if (m_rpc->m_uri != boost::none) m_rpc->check_connection(); + } + + monero_wallet_rpc::~monero_wallet_rpc() { + MTRACE("~monero_wallet_rpc()"); + clear(); + } + + void monero_wallet_rpc::set_poll_period_in_ms(uint64_t period_ms) { + boost::lock_guard poller_lock(m_poller_mutex); + if (!m_poller) m_poller = std::make_unique(this); + m_poller->set_period_in_ms(period_ms); + } + + std::set monero_wallet_rpc::get_listeners() { + boost::lock_guard lock(m_listeners_mutex); + return m_listeners; + } + + void monero_wallet_rpc::add_listener(monero_wallet_listener& listener) { + { + boost::lock_guard lock(m_listeners_mutex); + m_listeners.insert(&listener); + } + refresh_listening(); + } + + void monero_wallet_rpc::remove_listener(monero_wallet_listener& listener) { + { + boost::lock_guard lock(m_listeners_mutex); + m_listeners.erase(&listener); + } + refresh_listening(); + wait_for_listener_callbacks_idle(); + } + + void monero_wallet_rpc::wait_for_listener_callbacks_idle() { + // ensures no in-flight callback dispatch can still reference a removed listener + monero_wallet_poller* poller; + { + boost::lock_guard poller_lock(m_poller_mutex); + poller = m_poller.get(); + } + if (poller != nullptr) poller->wait_for_callbacks_idle(); + } + + monero_wallet_rpc* monero_wallet_rpc::open_wallet(const std::shared_ptr &config) { + MTRACE("monero_wallet_rpc::open_wallet(...)"); + if (config == nullptr) throw monero_error("Must provide configuration of wallet to open"); + if (config->m_path == boost::none || config->m_path->empty()) throw monero_error("Filename is not initialized"); + std::string path = config->m_path.get(); + std::string password = std::string(""); + if (config->m_password != boost::none) password = config->m_password.get(); + + auto params = std::make_shared(path, password); + m_rpc->send_json_request("open_wallet", params); + clear(); + + if (config->m_server != nullptr) { + set_daemon_connection(config->m_server, config->m_is_trusted_daemon); + } + + m_path = path; + + return this; + } + + monero_wallet_rpc* monero_wallet_rpc::open_wallet(const std::string& name, const std::string& password) { + MTRACE("monero_wallet_rpc::open_wallet(" << name << ", ***"); + auto config = std::make_shared(); + config->m_path = name; + config->m_password = password; + return open_wallet(config); + } + + void handle_create_wallet_error(const monero_rpc_error& ex, const std::string& path) { + std::string msg = ex.what(); + std::transform(msg.begin(), msg.end(), msg.begin(), [](unsigned char c){ return std::tolower(c); }); + if (msg.find("already exists") != std::string::npos) throw monero_rpc_error(ex.code, std::string("Wallet already exists: ") + path); + if (msg == std::string("electrum-style word list failed verification")) throw monero_rpc_error(ex.code, std::string("Invalid mnemonic")); + throw ex; + } + + monero_wallet_rpc* monero_wallet_rpc::create_wallet(const std::shared_ptr &config) { + MTRACE("monero_wallet_rpc::create_wallet(...)"); + + if (config == nullptr) throw monero_error("Must specify config to create wallet"); + if (config->m_network_type != boost::none) throw monero_error("Cannot specify network type when creating RPC wallet"); + if (config->m_seed != boost::none && (config->m_primary_address != boost::none || config->m_private_view_key != boost::none || config->m_private_spend_key != boost::none)) { + throw monero_error("Wallet can be initialized with a seed or keys but not both"); + } + if (config->m_account_lookahead != boost::none || config->m_subaddress_lookahead != boost::none) throw monero_error("monero-wallet-rpc does not support creating wallets with subaddress lookahead over rpc"); + + if (config->m_seed != boost::none) create_wallet_from_seed(config); + else if (config->m_private_spend_key != boost::none || config->m_primary_address != boost::none) create_wallet_from_keys(config); + else create_wallet_random(config); + + if (config->m_server != nullptr) { + set_daemon_connection(config->m_server, config->m_is_trusted_daemon); + } + + return this; + } + + std::vector monero_wallet_rpc::get_seed_languages() const { + auto result = m_rpc->send_json_request("get_languages"); + std::vector languages; + deserialize_seed_languages(result, languages); + return languages; + } + + void monero_wallet_rpc::stop() { + MTRACE("monero_wallet_rpc::stop()"); + clear(); + m_rpc->send_json_request("stop_wallet"); + } + + bool monero_wallet_rpc::is_view_only() const { + try { + std::string key = "mnemonic"; + query_key(key); + return false; + } + catch (const monero_rpc_error& e) { + if (e.code == -29) return true; + if (e.code == -1) return false; + throw; + } + } + + std::shared_ptr monero_wallet_rpc::get_daemon_connection() const { + MTRACE("monero_wallet_rpc::get_daemon_connection()"); + return m_daemon_connection; + } + + void monero_wallet_rpc::set_daemon_connection(const std::string& uri, const std::string& username, const std::string& password, const std::string& proxy_uri, const boost::optional& is_trusted) { + MTRACE("monero_wallet_rpc::set_daemon_connection(" << uri << ", " << username << ", " << "***" << ", " << proxy_uri << ")"); + + if (uri.empty()) { + set_daemon_connection(std::shared_ptr()); + return; + } + std::shared_ptr rpc = std::make_shared(uri, username, password, proxy_uri); + set_daemon_connection(rpc, is_trusted); + } + + void monero_wallet_rpc::set_daemon_connection(const std::shared_ptr& connection, bool is_trusted, const boost::optional& ssl_options) { + auto params = std::make_shared(); + if (connection == nullptr) { + params->m_address = "placeholder"; + params->m_username = ""; + params->m_password = ""; + params->m_proxy = ""; + } + else { + params->m_address = connection->m_uri; + params->m_username = connection->m_username; + params->m_password = connection->m_password; + params->m_proxy = connection->m_proxy_uri.value_or(""); + } + + params->m_trusted = is_trusted; + params->m_ssl_support = "autodetect"; + params->m_ssl_options = ssl_options; + + m_rpc->send_json_request("set_daemon", params); + + if (connection == nullptr || connection->m_uri == boost::none || connection->m_uri->empty()) { + m_daemon_connection = nullptr; + } + else { + m_daemon_connection = connection; + } + } + + void monero_wallet_rpc::set_daemon_connection(const std::shared_ptr& connection, const boost::optional& is_trusted) { + set_daemon_connection(connection, is_trusted.value_or(false), boost::none); + } + + bool monero_wallet_rpc::is_connected_to_daemon() const { + try { + check_reserve_proof(get_primary_address(), "", ""); + return false; + } + catch (const monero_rpc_error& e) { + if (e.code == -13) throw; // no wallet file + return e.message.find("Failed to connect to daemon") == std::string::npos; + } + } + + monero_version monero_wallet_rpc::get_version() const { + auto result = m_rpc->send_json_request("get_version"); + monero_version version; + deserialize_version(result, version); + return version; + } + + std::string monero_wallet_rpc::get_path() const { + return m_path; + } + + std::string monero_wallet_rpc::get_seed() const { + std::string key = "mnemonic"; + return query_key(key); + } + + std::string monero_wallet_rpc::get_seed_language() const { + throw monero_error("MoneroWalletRpc::get_seed_language() not supported"); + } + + std::string monero_wallet_rpc::get_public_view_key() const { + MTRACE("monero_wallet_rpc::get_public_view_key()"); + std::string key = "public_view_key"; + return query_key(key); + } + + std::string monero_wallet_rpc::get_private_view_key() const { + MTRACE("monero_wallet_rpc::get_private_view_key()"); + std::string key = "view_key"; + return query_key(key); + } + + std::string monero_wallet_rpc::get_public_spend_key() const { + MTRACE("monero_wallet_rpc::get_public_spend_key()"); + std::string key = "public_spend_key"; + return query_key(key); + } + + std::string monero_wallet_rpc::get_private_spend_key() const { + MTRACE("monero_wallet_rpc::get_private_spend_key()"); + std::string key = "spend_key"; + return query_key(key); + } + + std::string monero_wallet_rpc::get_address(const uint32_t account_idx, const uint32_t subaddress_idx) const { + boost::unique_lock lock(m_address_cache_mutex); + auto it = m_address_cache.find(account_idx); + if (it == m_address_cache.end()) { + // cache's all addresses at this account + lock.unlock(); + std::vector empty_indices; + get_subaddresses(account_idx, empty_indices, true); + // uses cache + return get_address(account_idx, subaddress_idx); + } + + auto subaddress_map = it->second; + auto it2 = subaddress_map.find(subaddress_idx); + + if (it2 == subaddress_map.end()) { + // cache's all addresses at this account + lock.unlock(); + std::vector empty_indices; + get_subaddresses(account_idx, empty_indices, true); + lock.lock(); + auto it3 = m_address_cache.find(account_idx); + if (it3 == m_address_cache.end()) return std::string(""); + auto it4 = it3->second.find(subaddress_idx); + if (it4 == it3->second.end()) return std::string(""); + return it4->second; + } + + return it2->second; + } + + monero_subaddress monero_wallet_rpc::get_address_index(const std::string& address) const { + MTRACE("monero_wallet_rpc:.get_address_index(" << address << ")"); + + auto params = std::make_shared(address); + auto result = m_rpc->send_json_request("get_address_index", params); + auto subaddress = std::make_shared(); + deserialize_subaddress(result, subaddress); + return *subaddress; + } + + monero_integrated_address monero_wallet_rpc::get_integrated_address(const std::string& standard_address, const std::string& payment_id) const { + MTRACE("monero_wallet_rpc::get_integrated_address(" << standard_address << ", " << payment_id << ")"); + try { + auto params = std::make_shared(standard_address, payment_id); + auto result = m_rpc->send_json_request("make_integrated_address", params); + monero_integrated_address integrated_address; + deserialize_integrated_address(result, integrated_address); + return decode_integrated_address(integrated_address.m_integrated_address); + } catch (const monero_rpc_error& ex) { + if (ex.code == -5 && std::string(ex.what()).find("Invalid payment ID") != std::string::npos) { + throw monero_rpc_error(-5, "Invalid payment ID: " + payment_id); + } + throw; + } + } + + monero_integrated_address monero_wallet_rpc::decode_integrated_address(const std::string& integrated_address) const { + MTRACE("monero_wallet_rpc::decode_integrated_address(" << integrated_address << ")"); + auto params = std::make_shared(integrated_address); + auto result = m_rpc->send_json_request("split_integrated_address", params); + monero_integrated_address integrated_addr; + deserialize_integrated_address(result, integrated_addr); + integrated_addr.m_integrated_address = integrated_address; + return integrated_addr; + } + + uint64_t monero_wallet_rpc::get_height() const { + auto result = m_rpc->send_json_request("get_height"); + return deserialize_block_height(result); + } + + uint64_t monero_wallet_rpc::get_daemon_height() const { + throw monero_error("monero-wallet-rpc does not support getting the chain height"); + } + + uint64_t monero_wallet_rpc::get_height_by_date(uint16_t year, uint8_t month, uint8_t day) const { + throw monero_error("monero-wallet-rpc does not support getting a height by date"); + } + + monero_sync_result monero_wallet_rpc::refresh(const std::shared_ptr& params) { + monero_sync_result sync_result(0, false); + { + // released before poll() since poll() takes the poller's own m_mutex, and a listener + // callback dispatched from the poll thread may call back into sync()/refresh(), which + // needs m_sync_mutex. Holding both at once (in opposite order on each thread) is an ABBA deadlock + boost::lock_guard lock(m_sync_mutex); + try { + auto result = m_rpc->send_json_request("refresh", params); + deserialize_sync_result(result, sync_result); + } catch (const monero_rpc_error& ex) { + if (ex.message == std::string("no connection to daemon")) throw monero_error("Wallet is not connected to daemon"); + throw; + } + } + poll(); + return sync_result; + } + + monero_sync_result monero_wallet_rpc::sync() { + MTRACE("monero_wallet_rpc::sync()"); + auto params = std::make_shared(); + return refresh(params); + } + + monero_sync_result monero_wallet_rpc::sync(monero_wallet_listener& listener) { + throw monero_error("Monero Wallet RPC does not support reporting sync progress"); + } + + monero_sync_result monero_wallet_rpc::sync(uint64_t start_height, monero_wallet_listener& listener) { + throw monero_error("Monero Wallet RPC does not support reporting sync progress"); + } + + monero_sync_result monero_wallet_rpc::sync(uint64_t start_height) { + MTRACE("monero_wallet_rpc::sync(" << start_height << ")"); + auto params = std::make_shared(start_height); + return refresh(params); + } + + void monero_wallet_rpc::start_syncing(uint64_t sync_period_in_ms) { + // convert ms to seconds for rpc parameter + uint64_t sync_period_in_seconds = sync_period_in_ms / 1000; + + // send rpc request + auto params = std::make_shared(true, sync_period_in_seconds); + m_rpc->send_json_request("auto_refresh", params); + + // update sync period for poller + { + boost::lock_guard poller_lock(m_poller_mutex); + m_sync_period_in_ms = sync_period_in_ms; + if (m_poller != nullptr) m_poller->set_period_in_ms(m_sync_period_in_ms.get()); + } + + // poll if listening + poll(); + } + + void monero_wallet_rpc::stop_syncing() { + auto params = std::make_shared(false); + m_rpc->send_json_request("auto_refresh", params); + } + + void monero_wallet_rpc::scan_txs(const std::vector& tx_hashes) { + MTRACE("monero_wallet_rpc::scan_txs()"); + if (tx_hashes.empty()) throw monero_error("No tx hashes given to scan"); + auto params = std::make_shared(tx_hashes); + m_rpc->send_json_request("scan_tx", params); + poll(); + } + + void monero_wallet_rpc::rescan_spent() { + MTRACE("monero_wallet_rpc::rescan_spent()"); + m_rpc->send_json_request("rescan_spent"); + } + + void monero_wallet_rpc::rescan_blockchain() { + MTRACE("monero_wallet_rpc::rescan_blockchain()"); + m_rpc->send_json_request("rescan_blockchain"); + } + + uint64_t monero_wallet_rpc::get_balance() const { + auto wallet_balance = get_balances(boost::none, boost::none); + return wallet_balance->m_balance.get(); + } + + uint64_t monero_wallet_rpc::get_balance(uint32_t account_index) const { + auto wallet_balance = get_balances(account_index, boost::none); + return wallet_balance->m_balance.get(); + } + + uint64_t monero_wallet_rpc::get_balance(uint32_t account_idx, uint32_t subaddress_idx) const { + auto wallet_balance = get_balances(account_idx, subaddress_idx); + return wallet_balance->m_balance.get(); + } + + uint64_t monero_wallet_rpc::get_unlocked_balance() const { + auto wallet_balance = get_balances(boost::none, boost::none); + return wallet_balance->m_unlocked_balance.get(); + } + + uint64_t monero_wallet_rpc::get_unlocked_balance(uint32_t account_index) const { + auto wallet_balance = get_balances(account_index, boost::none); + return wallet_balance->m_unlocked_balance.get(); + } + + uint64_t monero_wallet_rpc::get_unlocked_balance(uint32_t account_idx, uint32_t subaddress_idx) const { + auto wallet_balance = get_balances(account_idx, subaddress_idx); + return wallet_balance->m_unlocked_balance.get(); + } + + monero_account monero_wallet_rpc::get_account(const uint32_t account_idx, bool include_subaddresses) const { + return get_account(account_idx, include_subaddresses, false); + } + + monero_account monero_wallet_rpc::get_account(const uint32_t account_idx, bool include_subaddresses, bool skip_balances) const { + MTRACE("monero_wallet_rpc::get_account(" << account_idx << ", " << include_subaddresses << ")"); + + for(auto& account : monero_wallet::get_accounts()) { + if (account.m_index.get() == account_idx) { + if (include_subaddresses) { + std::vector empty_indices; + account.m_subaddresses = get_subaddresses(account_idx, empty_indices, skip_balances); + } + return account; + } + } + throw monero_error("Account with index " + std::to_string(account_idx) + " does not exist"); + } + + std::vector monero_wallet_rpc::get_accounts(bool include_subaddresses, const std::string& tag) const { + return get_accounts(include_subaddresses, tag, false); + } + + std::vector monero_wallet_rpc::get_accounts(bool include_subaddresses, const std::string& tag, bool skip_balances) const { + MTRACE("monero_wallet_rpc::get_accounts(" << include_subaddresses << ", " << tag << ")"); + + auto params = std::make_shared(tag); + auto accounts_result = m_rpc->send_json_request("get_accounts", params); + std::vector accounts; + deserialize_accounts(accounts_result, accounts); + if (include_subaddresses) { + + for (auto &account : accounts) { + std::vector empty_indices; + account.m_subaddresses = get_subaddresses(account.m_index.get(), empty_indices, true); + + if (!skip_balances) { + for (auto &subaddress : account.m_subaddresses) { + subaddress.m_balance = 0; + subaddress.m_unlocked_balance = 0; + subaddress.m_num_unspent_outputs = 0; + subaddress.m_num_blocks_to_unlock = 0; + } + } + } + + if (!skip_balances) { + auto balance_params = std::make_shared(true); + auto balance_result = m_rpc->send_json_request("get_balance", balance_params); + auto balance_response = std::make_shared(); + monero_get_balance_response::from_property_tree(balance_result, balance_response); + for (const auto &subaddress : balance_response->m_per_subaddress) { + // merge info + auto account = &accounts.at(subaddress->m_account_index.get()); + if (account->m_index != subaddress->m_account_index) throw monero_error("RPC accounts are out of order"); + auto tgt_subaddress = &account->m_subaddresses.at(subaddress->m_index.get()); + if (tgt_subaddress->m_index != subaddress->m_index) throw monero_error("RPC subaddresses are out of order"); + + if (subaddress->m_balance != boost::none) tgt_subaddress->m_balance = subaddress->m_balance; + if (subaddress->m_unlocked_balance != boost::none) tgt_subaddress->m_unlocked_balance = subaddress->m_unlocked_balance; + if (subaddress->m_num_unspent_outputs != boost::none) tgt_subaddress->m_num_unspent_outputs = subaddress->m_num_unspent_outputs; + if (subaddress->m_num_blocks_to_unlock != boost::none) tgt_subaddress->m_num_blocks_to_unlock = subaddress->m_num_blocks_to_unlock; + } + } + } + + return accounts; + } + + monero_account monero_wallet_rpc::create_account(const std::string& label) { + MTRACE("monero_wallet_rpc::create_account(" << label << ")"); + auto params = std::make_shared(); + params->m_label = label; + auto result = m_rpc->send_json_request("create_account", params); + monero_account account; + deserialize_account(result, account); + account.m_balance = 0; + account.m_unlocked_balance = 0; + if (account.m_index == boost::none || account.m_primary_address == boost::none) throw monero_error("Could not create account"); + return account; + } + + std::vector monero_wallet_rpc::get_subaddresses(const uint32_t account_idx, const std::vector& subaddress_indices, bool skip_balances) const { + MTRACE("monero_wallet_rpc::get_subaddresses(" << account_idx << ", ...)"); + MTRACE("monero_wallet_rpc::get_subaddresses(): Subaddress indices size: " << subaddress_indices.size()); + + // fetch subaddresses + auto params = std::make_shared(account_idx, subaddress_indices); + auto subaddress_result = m_rpc->send_json_request("get_address", params); + std::vector subaddresses; + std::vector> subaddresses_ptr; + deserialize_subaddresses(subaddress_result, subaddresses_ptr); + + for(const auto& subaddress_ptr : subaddresses_ptr) { + subaddress_ptr->m_account_index = account_idx; + subaddresses.push_back(*subaddress_ptr); + } + + // initialize subaddresses + + // fetch and initialize subaddress balances + if (!skip_balances) { + // these fields are not initialized if subaddress is unused and therefore not returned from `get_balance` + for (auto &subaddress : subaddresses) { + subaddress.m_balance = 0; + subaddress.m_unlocked_balance = 0; + subaddress.m_num_unspent_outputs = 0; + subaddress.m_num_blocks_to_unlock = 0; + } + + // fetch and initialize balances + auto balance_result = m_rpc->send_json_request("get_balance", params); + std::vector> subaddresses2; + deserialize_subaddresses(balance_result, subaddresses2); + + for (auto &tgt_subaddress: subaddresses) { + for (const auto &rpc_subaddress : subaddresses2) { + if (rpc_subaddress->m_index != tgt_subaddress.m_index) continue; // skip to subaddress with same index + if (rpc_subaddress->m_balance != boost::none) tgt_subaddress.m_balance = rpc_subaddress->m_balance; + if (rpc_subaddress->m_unlocked_balance != boost::none) tgt_subaddress.m_unlocked_balance = rpc_subaddress->m_unlocked_balance; + if (rpc_subaddress->m_num_unspent_outputs != boost::none) tgt_subaddress.m_num_unspent_outputs = rpc_subaddress->m_num_unspent_outputs; + if (rpc_subaddress->m_num_blocks_to_unlock != boost::none) tgt_subaddress.m_num_blocks_to_unlock = rpc_subaddress->m_num_blocks_to_unlock; + } + } + } + + // cache addresses + { + boost::lock_guard lock(m_address_cache_mutex); + auto it = m_address_cache.find(account_idx); + if (it == m_address_cache.end()) { + m_address_cache[account_idx] = std::unordered_map(); + } + + for (const auto& subaddress : subaddresses) { + m_address_cache[account_idx][subaddress.m_index.get()] = subaddress.m_address.get(); + } + } + + // return results + return subaddresses; + } + + std::vector monero_wallet_rpc::get_subaddresses(uint32_t account_idx, const std::vector& subaddress_indices) const { + return get_subaddresses(account_idx, subaddress_indices, false); + } + + std::vector monero_wallet_rpc::get_subaddresses(const uint32_t account_idx) const { + std::vector empty_indices; + return get_subaddresses(account_idx, empty_indices); + } + + monero_subaddress monero_wallet_rpc::get_subaddress(const uint32_t account_idx, const uint32_t subaddress_idx) const { + std::vector subaddress_indices; + subaddress_indices.push_back(subaddress_idx); + auto subaddresses = get_subaddresses(account_idx, subaddress_indices); + if (subaddresses.empty()) throw monero_error("Subaddress is not initialized"); + if (subaddresses.size() != 1) throw monero_error("Only 1 subaddress should be returned"); + return subaddresses[0]; + } + + monero_subaddress monero_wallet_rpc::create_subaddress(uint32_t account_idx, const std::string& label) { + MTRACE("monero_wallet_rpc::create_subaddress(" << account_idx << ", " << label << ")"); + auto params = std::make_shared(account_idx, label); + auto result = m_rpc->send_json_request("create_address", params); + auto subaddress = std::make_shared(); + deserialize_subaddress(result, subaddress); + subaddress->m_account_index = account_idx; + subaddress->m_balance = 0; + subaddress->m_unlocked_balance = 0; + subaddress->m_num_unspent_outputs = 0; + subaddress->m_is_used = false; + subaddress->m_num_blocks_to_unlock = 0; + if (!label.empty()) subaddress->m_label = label; + return *subaddress; + } + + void monero_wallet_rpc::set_subaddress_label(uint32_t account_idx, uint32_t subaddress_idx, const std::string& label) { + MTRACE("monero_wallet_rpc::set_subaddress_label(" << account_idx << ", " << subaddress_idx << ", " << label << ")"); + auto params = std::make_shared(account_idx, subaddress_idx, label); + m_rpc->send_json_request("label_address", params); + } + + std::string monero_wallet_rpc::export_outputs(bool all) const { + auto params = std::make_shared(all); + auto result = m_rpc->send_json_request("export_outputs", params); + return deserialize_exported_outputs(result); + } + + int monero_wallet_rpc::import_outputs(const std::string& outputs_hex) { + auto params = std::make_shared(outputs_hex); + auto result = m_rpc->send_json_request("import_outputs", params); + return deserialize_num_imported_outputs(result); + } + + std::shared_ptr monero_wallet_rpc::export_key_images(bool all) const { + MTRACE("monero_wallet_rpc::export_key_images()"); + auto params = std::make_shared(all); + auto result = m_rpc->send_json_request("export_key_images", params); + auto export_result = std::make_shared(); + deserialize_key_image_export_result(result, export_result); + return export_result; + } + + std::shared_ptr monero_wallet_rpc::import_key_images(const std::vector>& key_images, uint64_t offset) { + MTRACE("monero_wallet_rpc::import_key_images()"); + auto params = std::make_shared(key_images, offset); + auto result = m_rpc->send_json_request("import_key_images", params); + auto import_result = std::make_shared(); + deserialize_key_image_import_result(result, import_result); + return import_result; + } + + void monero_wallet_rpc::freeze_output(const std::string& key_image) { + auto params = std::make_shared(key_image); + m_rpc->send_json_request("freeze", params); + } + + void monero_wallet_rpc::thaw_output(const std::string& key_image) { + auto params = std::make_shared(key_image); + m_rpc->send_json_request("thaw", params); + } + + bool monero_wallet_rpc::is_output_frozen(const std::string& key_image) { + auto params = std::make_shared(key_image); + auto result = m_rpc->send_json_request("frozen", params); + return deserialize_frozen_output_info(result); + } + + monero_tx_priority monero_wallet_rpc::get_default_fee_priority() const { + auto result = m_rpc->send_json_request("get_default_fee_priority"); + return deserialize_tx_priority(result); + } + + std::vector> monero_wallet_rpc::create_txs(const monero_tx_config& conf) { + MTRACE("monero_wallet_rpc::create_txs"); + + // validate, copy, and normalize request + monero_tx_config config = conf; + if (config.m_address == boost::none && config.m_destinations.empty()) throw monero_error("Destinations cannot be empty"); + if (config.m_sweep_each_subaddress != boost::none) throw monero_error("Sweep each subaddress not supported"); + if (config.m_below_amount != boost::none) throw monero_error("Below amount not supported"); + + if (config.m_can_split == boost::none) { + config = config.copy(); + config.m_can_split = true; + } + if (gen_utils::bool_equals(true, config.m_relay) && is_multisig()) throw monero_error("Cannot relay multisig transaction until co-signed"); + + // determine account and subaddresses to send from + if (config.m_account_index == boost::none) throw monero_error("Must specify the account index to send from"); + auto account_idx = config.m_account_index.get(); + + // cannot apply subtractFeeFrom with `transfer_split` call + if (gen_utils::bool_equals(true, config.m_can_split) && config.m_subtract_fee_from.size() > 0) { + throw monero_error("subtractfeefrom transfers cannot be split over multiple transactions yet"); + } + + // build request parameters + auto params = std::make_shared(config); + std::string request_path = "transfer"; + if (gen_utils::bool_equals(true, config.m_can_split)) request_path = "transfer_split"; + + boost::property_tree::ptree result; + try { + result = m_rpc->send_json_request(request_path, params); + } catch (const monero_rpc_error& ex) { + std::string message = ex.what(); + if (message.find("WALLET_RPC_ERROR_CODE_WRONG_ADDRESS") != std::string::npos) throw monero_error("Invalid destination address"); + throw; + } + + // pre-initialize txs iff present. multisig and view-only wallets will have tx set without transactions + bool can_split = gen_utils::bool_equals(true, config.m_can_split); + int num_txs = deserialize_num_created_txs(result, can_split); + bool copy_destinations = num_txs == 1; + + std::vector> txs; + for (int i = 0; i < num_txs; i++) { + auto tx = std::make_shared(); + init_sent_tx(config, tx, copy_destinations); + tx->m_outgoing_transfer->m_account_index = account_idx; + + if (config.m_subaddress_indices.size() == 1) { + tx->m_outgoing_transfer->m_subaddress_indices = config.m_subaddress_indices; + } + + txs.push_back(tx); + } + + // notify of changes + if (gen_utils::bool_equals(true, config.m_relay)) poll(); + + // initialize tx set from rpc response with pre-initialized txs + auto tx_set = std::make_shared(); + if (can_split) { + deserialize_sent_tx_set(result, tx_set, txs, config); + } + else if (txs.empty()) { + auto __tx = std::make_shared(); + deserialize_tx_set(result, tx_set, __tx, true, config); + } + else { + deserialize_tx_set(result, tx_set, txs[0], true, config); + } + + return tx_set->m_txs; + } + + std::vector> monero_wallet_rpc::sweep_unlocked(const monero_tx_config& config) { + // validate config + std::vector> destinations = config.get_normalized_destinations(); + if (destinations.size() != 1) throw monero_error("Must specify exactly one destination to sweep to"); + if (destinations[0]->m_address == boost::none) throw monero_error("Must specify destination address to sweep to"); + if (destinations[0]->m_amount != boost::none) throw monero_error("Cannot specify amount to sweep"); + if (config.m_account_index == boost::none && config.m_subaddress_indices.size() != 0) throw monero_error("Must specify account index if subaddress indices are specified"); + + // determine account and subaddress indices to sweep; default to all with unlocked balance if not specified + std::map> indices; + if (config.m_account_index != boost::none) { + if (config.m_subaddress_indices.size() != 0) { + indices[config.m_account_index.get()] = config.m_subaddress_indices; + } else { + std::vector subaddress_indices; + for (const monero_subaddress& subaddress : monero_wallet::get_subaddresses(config.m_account_index.get())) { + // TODO wallet rpc sweep_all now supports req.subaddr_indices_all + if (subaddress.m_unlocked_balance.get() > 0) subaddress_indices.push_back(subaddress.m_index.get()); + } + indices[config.m_account_index.get()] = subaddress_indices; + } + } else { + std::vector accounts = monero_wallet::get_accounts(true); + for (const monero_account& account : accounts) { + if (account.m_unlocked_balance.get() > 0) { + std::vector subaddress_indices; + for (const monero_subaddress& subaddress : account.m_subaddresses) { + if (subaddress.m_unlocked_balance.get() > 0) subaddress_indices.push_back(subaddress.m_index.get()); + } + indices[account.m_index.get()] = subaddress_indices; + } + } + } + + // sweep from each account and collect resulting txs + std::vector> txs; + for (std::pair> subaddress_indices_pair : indices) { + + // copy and modify the original config + monero_tx_config copy = config.copy(); + copy.m_account_index = subaddress_indices_pair.first; + copy.m_sweep_each_subaddress = false; + + // sweep all subaddresses together // TODO monero-project: can this reveal outputs belong to the same wallet? + if (copy.m_sweep_each_subaddress == boost::none || copy.m_sweep_each_subaddress.get() != true) { + copy.m_subaddress_indices = subaddress_indices_pair.second; + std::vector> account_txs = sweep_account(copy); + txs.insert(std::end(txs), std::begin(account_txs), std::end(account_txs)); + } + + // otherwise sweep each subaddress individually + else { + for (uint32_t subaddress_index : subaddress_indices_pair.second) { + std::vector subaddress_indices; + subaddress_indices.push_back(subaddress_index); + copy.m_subaddress_indices = subaddress_indices; + std::vector> account_txs = sweep_account(copy); + txs.insert(std::end(txs), std::begin(account_txs), std::end(account_txs)); + } + } + } + + // notify listeners of spent funds + if (config.m_relay != boost::none && config.m_relay.get()) poll(); + return txs; + } + + std::shared_ptr monero_wallet_rpc::sweep_output(const monero_tx_config& config) { + MTRACE("monero_wallet_rpc::sweep_output()"); + + // validate request + std::vector> destinations = config.get_normalized_destinations(); + if (config.m_sweep_each_subaddress != boost::none) throw monero_error("Cannot sweep each subaddress when sweeping single output"); + if (config.m_below_amount != boost::none) throw monero_error("Cannot specifiy below_amount when sweeping single output"); + if (config.m_can_split != boost::none) throw monero_error("Splitting is not applicable when sweeping output"); + if (destinations.size() != 1) throw monero_error("Must provide exactly one destination address to sweep output to"); + if (destinations[0]->m_address == boost::none) throw monero_error("Must specify destination address to sweep to"); + if (destinations[0]->m_amount != boost::none) throw monero_error("Cannot specify amount to sweep"); + if (config.m_subtract_fee_from.size() > 0) throw monero_error("Sweep transactions do not support subtracting fees from destinations"); + + auto params = std::make_shared(config); + auto result = m_rpc->send_json_request("sweep_single", params); + if (gen_utils::bool_equals(true, config.m_relay)) poll(); + auto set = std::make_shared(); + auto tx = std::make_shared(); + init_sent_tx(config, tx, true); + deserialize_tx_set(result, set, tx, true, config); + // initialize destination amount + tx->m_outgoing_transfer->m_destinations[0]->m_amount = tx->m_outgoing_transfer->m_amount; + return tx; + } + + std::vector> monero_wallet_rpc::sweep_dust(bool relay) { + MTRACE("monero_wallet_rpc::sweep_dust()"); + auto params = std::make_shared(relay); + auto result = m_rpc->send_json_request("sweep_dust", params); + if (relay) poll(); + auto set = std::make_shared(); + deserialize_sent_tx_set(result, set); + + for(const std::shared_ptr& tx : set->m_txs) { + tx->m_is_relayed = relay; + tx->m_in_tx_pool = relay; + } + + return set->m_txs; + } + + std::vector monero_wallet_rpc::relay_txs(const std::vector& tx_metadatas) { + MTRACE("monero_wallet_rpc::relay_txs()"); + + if (tx_metadatas.empty()) throw monero_error("Must provide an array of tx metadata to relay"); + + std::vector tx_hashes; + + for (const auto &tx_metadata : tx_metadatas) { + auto params = std::make_shared(tx_metadata); + auto result = m_rpc->send_json_request("relay_tx", params); + deserialize_relayed_tx_hash(result, tx_hashes); + } + + // notify of changes + poll(); + + return tx_hashes; + } + + monero_tx_set monero_wallet_rpc::describe_tx_set(const monero_tx_set& tx_set) { + auto params = std::make_shared(); + params->m_multisig_txset = tx_set.m_multisig_tx_hex; + params->m_unsigned_txset = tx_set.m_unsigned_tx_hex; + auto result = m_rpc->send_json_request("describe_transfer", params); + auto set = std::make_shared(); + deserialize_described_tx_set(result, set); + return *set; + } + + monero_tx_set monero_wallet_rpc::sign_txs(const std::string& unsigned_tx_hex) { + auto params = std::make_shared(unsigned_tx_hex); + params->m_export_raw = true; + params->m_get_tx_keys = true; + auto result = m_rpc->send_json_request("sign_transfer", params); + auto set = std::make_shared(); + deserialize_sent_tx_set(result, set); + return *set; + } + + std::vector monero_wallet_rpc::submit_txs(const std::string& signed_tx_hex) { + auto params = std::make_shared(); + params->m_signed_tx_hex = signed_tx_hex; + auto result = m_rpc->send_json_request("submit_transfer", params); + poll(); + std::vector tx_hashes; + deserialize_submitted_tx_hashes(result, tx_hashes); + return tx_hashes; + } + + std::string monero_wallet_rpc::sign_message(const std::string& msg, monero_message_signature_type signature_type, uint32_t account_idx, uint32_t subaddress_idx) const { + auto params = std::make_shared(msg, signature_type, account_idx, subaddress_idx); + auto result = m_rpc->send_json_request("sign", params); + return deserialize_signature(result); + } + + monero_message_signature_result monero_wallet_rpc::verify_message(const std::string& msg, const std::string& address, const std::string& signature) const { + auto params = std::make_shared(msg, address, signature); + monero_message_signature_result sig_result; + sig_result.m_is_good = false; + try { + auto result = m_rpc->send_json_request("verify", params); + deserialize_message_signature_result(result, sig_result); + } catch (const monero_rpc_error& ex) { + if (ex.code != -2) throw; + } + + return sig_result; + } + + void normalize_wallet_error(const monero_rpc_error& ex) { + // normalize error message + if (ex.code == -1 && std::string(ex.what()).find("basic_string") != std::string::npos) { + throw monero_rpc_error(-1, "Must provide signature to check tx proof"); + } else if (ex.code == -8 && ex.what() == std::string("TX ID has invalid format")) { + throw monero_rpc_error(-8, "TX hash has invalid format"); + } + throw; + } + + std::string monero_wallet_rpc::get_tx_key(const std::string& tx_hash) const { + MTRACE("monero_wallet_rpc::get_tx_key()"); + std::string tx_key; + + try { + auto params = std::make_shared(tx_hash); + auto result = m_rpc->send_json_request("get_tx_key", params); + tx_key = deserialize_tx_key(result); + } catch (const monero_rpc_error& ex) { + normalize_wallet_error(ex); + } + + return tx_key; + } + + std::shared_ptr monero_wallet_rpc::check_tx_key(const std::string& tx_hash, const std::string& tx_key, const std::string& address) const { + MTRACE("monero_wallet_rpc::check_tx_key()"); + auto check = std::make_shared(); + + try { + auto params = std::make_shared(tx_hash, tx_key, address); + auto result = m_rpc->send_json_request("check_tx_key", params); + check->m_is_good = true; + deserialize_check_tx(result, check); + } catch (const monero_rpc_error& ex) { + normalize_wallet_error(ex); + } + + return check; + } + + std::string monero_wallet_rpc::get_tx_proof(const std::string& tx_hash, const std::string& address, const std::string& message) const { + std::string tx_proof; + try { + auto params = std::make_shared(tx_hash, message); + params->m_address = address; + auto result = m_rpc->send_json_request("get_tx_proof", params); + tx_proof = deserialize_signature(result); + } catch (const monero_rpc_error& ex) { + normalize_wallet_error(ex); + } + + return tx_proof; + } + + std::shared_ptr monero_wallet_rpc::check_tx_proof(const std::string& tx_hash, const std::string& address, const std::string& message, const std::string& signature) const { + MTRACE("monero_wallet_rpc::check_tx_proof()"); + + auto check = std::make_shared(); + try { + auto params = std::make_shared(tx_hash, address, message, signature); + auto result = m_rpc->send_json_request("check_tx_proof", params); + deserialize_check_tx(result, check); + } catch (const monero_rpc_error& ex) { + normalize_wallet_error(ex); + } + + return check; + } + + std::string monero_wallet_rpc::get_spend_proof(const std::string& tx_hash, const std::string& message) const { + MTRACE("monero_wallet_rpc::get_spend_proof()"); + + std::string spend_proof; + try { + auto params = std::make_shared(tx_hash, message); + auto result = m_rpc->send_json_request("get_spend_proof", params); + spend_proof = deserialize_signature(result); + } catch (const monero_rpc_error& ex) { + normalize_wallet_error(ex); + } + + return spend_proof; + } + + bool monero_wallet_rpc::check_spend_proof(const std::string& tx_hash, const std::string& message, const std::string& signature) const { + MTRACE("monero_wallet_rpc::check_spend_proof()"); + + auto proof = std::make_shared(); + try { + auto params = std::make_shared(tx_hash, message); + params->m_signature = signature; + auto result = m_rpc->send_json_request("check_spend_proof", params); + deserialize_check_reserve(result, proof); + } catch (const monero_rpc_error& ex) { + normalize_wallet_error(ex); + } + + return proof->m_is_good; + } + + std::string monero_wallet_rpc::get_reserve_proof_wallet(const std::string& message) const { + MTRACE("monero_wallet_rpc::get_reserve_proof_wallet()"); + auto params = std::make_shared(message); + auto result = m_rpc->send_json_request("get_reserve_proof", params); + return deserialize_signature(result); + } + + std::string monero_wallet_rpc::get_reserve_proof_account(uint32_t account_idx, uint64_t amount, const std::string& message) const { + MTRACE("monero_wallet_rpc::get_reserve_proof_account()"); + auto params = std::make_shared(account_idx, amount, message); + auto result = m_rpc->send_json_request("get_reserve_proof", params); + return deserialize_signature(result); + } + + std::shared_ptr monero_wallet_rpc::check_reserve_proof(const std::string& address, const std::string& message, const std::string& signature) const { + MTRACE("monero_wallet_rpc::check_reserve_proof()"); + auto params = std::make_shared(address, message, signature); + auto result = m_rpc->send_json_request("check_reserve_proof", params); + auto proof = std::make_shared(); + deserialize_check_reserve(result, proof); + return proof; + } + + std::string monero_wallet_rpc::get_tx_note(const std::string& tx_hash) const { + MTRACE("monero_wallet_rpc::get_tx_note()"); + std::vector tx_hashes; + tx_hashes.push_back(tx_hash); + auto notes = get_tx_notes(tx_hashes); + if (notes.size() != 1) throw monero_error("Expected one tx note"); + return notes[0]; + } + + std::vector monero_wallet_rpc::get_tx_notes(const std::vector& tx_hashes) const { + MTRACE("monero_wallet_rpc::get_tx_notes()"); + auto params = std::make_shared(tx_hashes); + auto result = m_rpc->send_json_request("get_tx_notes", params); + std::vector tx_notes; + deserialize_tx_notes(result, tx_notes); + return tx_notes; + } + + void monero_wallet_rpc::set_tx_note(const std::string& tx_hash, const std::string& note) { + MTRACE("monero_wallet_rpc::set_tx_note()"); + std::vector tx_hashes; + std::vector notes; + tx_hashes.push_back(tx_hash); + notes.push_back(note); + set_tx_notes(tx_hashes, notes); + } + + void monero_wallet_rpc::set_tx_notes(const std::vector& tx_hashes, const std::vector& notes) { + MTRACE("monero_wallet_rpc::set_tx_notes()"); + auto params = std::make_shared(tx_hashes, notes); + m_rpc->send_json_request("set_tx_notes", params); + } + + std::vector monero_wallet_rpc::get_address_book_entries(const std::vector& indices) const { + MTRACE("monero_wallet_rpc::get_address_book_entries()"); + auto params = std::make_shared(indices); + auto result = m_rpc->send_json_request("get_address_book", params); + std::vector entries; + deserialize_address_book_entries(result, entries); + return entries; + } + + uint64_t monero_wallet_rpc::add_address_book_entry(const std::string& address, const std::string& description) { + MTRACE("monero_wallet_rpc::add_address_book_entry()"); + auto params = std::make_shared(address, description); + auto result = m_rpc->send_json_request("add_address_book", params); + return deserialize_address_book_index(result); + } + + void monero_wallet_rpc::edit_address_book_entry(uint64_t index, bool set_address, const std::string& address, bool set_description, const std::string& description) { + MTRACE("monero_wallet_rpc::edit_address_book_entry()"); + auto params = std::make_shared(index, set_address, address, set_description, description); + m_rpc->send_json_request("edit_address_book", params); + } + + void monero_wallet_rpc::delete_address_book_entry(uint64_t index) { + auto params = std::make_shared(index); + m_rpc->send_json_request("delete_address_book", params); + } + + void monero_wallet_rpc::tag_accounts(const std::string& tag, const std::vector& account_indices) { + auto params = std::make_shared(tag, account_indices); + m_rpc->send_json_request("tag_accounts", params); + } + + void monero_wallet_rpc::untag_accounts(const std::vector& account_indices) { + auto params = std::make_shared(account_indices); + m_rpc->send_json_request("untag_accounts", params); + } + + std::vector> monero_wallet_rpc::get_account_tags() const { + auto result = m_rpc->send_json_request("get_account_tags"); + std::vector> account_tags; + deserialize_account_tags(result, account_tags); + return account_tags; + } + + void monero_wallet_rpc::set_account_tag_label(const std::string& tag, const std::string& label) { + auto params = std::make_shared(tag, label); + m_rpc->send_json_request("set_account_tag_description", params); + } + + std::string monero_wallet_rpc::get_payment_uri(const monero_tx_config& config) const { + MTRACE("monero_wallet_rpc::get_payment_uri()"); + auto params = std::make_shared(config); + auto result = m_rpc->send_json_request("make_uri", params); + return deserialize_payment_uri(result); + } + + std::shared_ptr monero_wallet_rpc::parse_payment_uri(const std::string& uri) const { + MTRACE("monero_wallet_rpc::parse_payment_uri(" << uri << ")"); + auto params = std::make_shared(uri); + auto result = m_rpc->send_json_request("parse_uri", params); + auto tx_config = std::make_shared(); + deserialize_payment_uri(result, tx_config); + return tx_config; + } + + void monero_wallet_rpc::set_attribute(const std::string& key, const std::string& val) { + auto params = std::make_shared(key, val); + m_rpc->send_json_request("set_attribute", params); + } + + bool monero_wallet_rpc::get_attribute(const std::string& key, std::string& value) const { + try { + auto params = std::make_shared(key); + auto result = m_rpc->send_json_request("get_attribute", params); + key_value::from_property_tree(result, params); + if (params->m_value == boost::none) return false; + value = params->m_value.get(); + return true; + } + catch (const monero_rpc_error& ex) { + if (ex.code == -45) { + // attribute not found + value = std::string(""); + return false; + } + throw; + } + + return false; + } + + void monero_wallet_rpc::start_mining(boost::optional num_threads, boost::optional background_mining, boost::optional ignore_battery) { + MTRACE("monero_wallet_rpc::start_mining()"); + auto params = std::make_shared(num_threads, background_mining, ignore_battery); + m_rpc->send_json_request("start_mining", params); + } + + void monero_wallet_rpc::stop_mining() { + MTRACE("monero_wallet_rpc::stop_mining()"); + m_rpc->send_json_request("stop_mining"); + } + + bool monero_wallet_rpc::is_multisig_import_needed() const { + auto result = m_rpc->send_json_request("get_balance"); + auto balance = std::make_shared(); + monero_get_balance_response::from_property_tree(result, balance); + return gen_utils::bool_equals(true, balance->m_multisig_import_needed); + } + + monero_multisig_info monero_wallet_rpc::get_multisig_info() const { + auto result = m_rpc->send_json_request("is_multisig"); + monero_multisig_info info; + deserialize_multisig_info(result, info); + return info; + } + + std::string monero_wallet_rpc::prepare_multisig() { + auto params = std::make_shared(); + auto result = m_rpc->send_json_request("prepare_multisig", params); + clear_address_cache(); + auto response = std::make_shared(); + monero_multisig_response::from_property_tree(result, response); + if (response->m_multisig_info == boost::none) throw monero_error("Failed to prepare multisig"); + return response->m_multisig_info.get(); + } + + std::string monero_wallet_rpc::make_multisig(const std::vector& multisig_hexes, int threshold, const std::string& password) { + auto params = std::make_shared(multisig_hexes, threshold, password); + auto result = m_rpc->send_json_request("make_multisig", params); + clear_address_cache(); + auto response = std::make_shared(); + monero_multisig_response::from_property_tree(result, response); + if (response->m_multisig_info == boost::none) throw monero_error("Failed to make multisig"); + return response->m_multisig_info.get(); + } + + monero_multisig_init_result monero_wallet_rpc::exchange_multisig_keys(const std::vector& multisig_hexes, const std::string& password) { + auto params = std::make_shared(multisig_hexes, password); + auto result = m_rpc->send_json_request("exchange_multisig_keys", params); + clear_address_cache(); + monero_multisig_init_result multisig_init; + deserialize_multisig_init_result(result, multisig_init); + return multisig_init; + } + + std::string monero_wallet_rpc::export_multisig_hex() { + auto result = m_rpc->send_json_request("export_multisig_info"); + auto response = std::make_shared(); + monero_multisig_response::from_property_tree(result, response); + if (response->m_multisig_info == boost::none) throw monero_error("Failed to export multisig"); + return response->m_multisig_info.get(); + } + + int monero_wallet_rpc::import_multisig_hex(const std::vector& multisig_hexes, const bool refresh_after_import) { + auto params = std::make_shared(multisig_hexes, refresh_after_import); + auto result = m_rpc->send_json_request("import_multisig_info", params); + auto response = std::make_shared(); + monero_multisig_response::from_property_tree(result, response); + if (response->m_num_outputs == boost::none) throw monero_error("Failed to import multisig"); + return response->m_num_outputs.get(); + } + + monero_multisig_sign_result monero_wallet_rpc::sign_multisig_tx_hex(const std::string& multisig_tx_hex) { + auto params = std::make_shared(multisig_tx_hex); + auto result = m_rpc->send_json_request("sign_multisig", params); + monero_multisig_sign_result multisig_result; + deserialize_multisig_sign_result(result, multisig_result); + return multisig_result; + } + + std::vector monero_wallet_rpc::submit_multisig_tx_hex(const std::string& signed_multisig_tx_hex) { + auto params = std::make_shared(signed_multisig_tx_hex); + auto result = m_rpc->send_json_request("submit_multisig", params); + auto response = std::make_shared(); + monero_multisig_response::from_property_tree(result, response); + return response->m_tx_hashes; + } + + void monero_wallet_rpc::change_password(const std::string& old_password, const std::string& new_password) { + MTRACE("monero_wallet_rpc::change_password(" << "***" << ", ***)"); + auto params = std::make_shared(old_password, new_password); + m_rpc->send_json_request("change_wallet_password", params); + } + + void monero_wallet_rpc::save() { + MTRACE("monero_wallet_rpc::save()"); + m_rpc->send_json_request("store"); + } + + bool monero_wallet_rpc::is_closed() const { + try { + get_primary_address(); + } catch (const monero_rpc_error& ex) { + return ex.code == -8 && std::string(ex.what()).find("No wallet file") != std::string::npos; + } catch (const monero_error&) { + return false; + } + + return false; + } + + void monero_wallet_rpc::close(bool save) { + MTRACE("monero_wallet_rpc::close()"); + clear(); + auto params = std::make_shared(save); + m_rpc->send_json_request("close_wallet", params); + } + + std::shared_ptr monero_wallet_rpc::get_balances(const boost::optional& account_idx, const boost::optional& subaddress_idx) const { + auto balance = std::make_shared(); + balance->m_balance = 0; + balance->m_unlocked_balance = 0; + + if (account_idx == boost::none) { + if (subaddress_idx != boost::none) throw monero_error("Must provide account index with subaddress index"); + + auto accounts = monero_wallet::get_accounts(); + + for(const auto &account : accounts) { + balance->m_balance = balance->m_balance.get() + account.m_balance.get(); + balance->m_unlocked_balance = balance->m_unlocked_balance.get() + account.m_unlocked_balance.get(); + } + + return balance; + } + else { + auto params = std::make_shared(account_idx.get(), subaddress_idx); + auto result = m_rpc->send_json_request("get_balance", params); + auto balance_response = std::make_shared(); + monero_get_balance_response::from_property_tree(result, balance_response); + + if (subaddress_idx == boost::none) { + balance->m_balance = balance_response->m_balance.get(); + balance->m_unlocked_balance = balance_response->m_unlocked_balance.get(); + return balance; + } + else if (balance_response->m_per_subaddress.size() > 0) { + auto sub = balance_response->m_per_subaddress[0]; + balance->m_balance = sub->m_balance.get(); + balance->m_unlocked_balance = sub->m_unlocked_balance.get(); + } + } + + return balance; + } + + monero_wallet_rpc* monero_wallet_rpc::create_wallet_random(const std::shared_ptr &conf) { + // validate and normalize config + auto config = conf->copy(); + if (config.m_seed_offset != boost::none) throw monero_error("Cannot specify seed offset when creating random wallet"); + if (config.m_restore_height != boost::none) throw monero_error("Cannot specify restore height when creating random wallet"); + if (config.m_save_current != boost::none && config.m_save_current == false) throw monero_error("Current wallet is saved automatically when creating random wallet"); + if (config.m_path == boost::none || config.m_path->empty()) throw monero_error("Wallet name is not initialized"); + if (config.m_language == boost::none || config.m_language->empty()) config.m_language = "English"; + + // send request + std::string filename = config.m_path.get(); + std::string password = config.m_password.value_or(""); + std::string language = config.m_language.get(); + + auto params = std::make_shared(filename, password, language); + try { m_rpc->send_json_request("create_wallet", params); } + catch (const monero_rpc_error& ex) { handle_create_wallet_error(ex, filename); } + clear(); + m_path = filename; + return this; + } + + monero_wallet_rpc* monero_wallet_rpc::create_wallet_from_seed(const std::shared_ptr &conf) { + auto config = conf->copy(); + if (config.m_seed == boost::none || config.m_seed->empty()) throw monero_error("Must specify a valid mnemonic when creating wallet from seed"); + if (config.m_path == boost::none || config.m_path->empty()) throw monero_error("Wallet name is not initialized"); + if (config.m_language == boost::none || config.m_language->empty()) config.m_language = "English"; + + // send request + bool autosave_current = config.m_save_current.value_or(false); + bool enable_multisig_experimental = config.m_is_multisig.value_or(false); + auto params = std::make_shared(config.m_path, config.m_password, config.m_seed, config.m_seed_offset, config.m_restore_height, config.m_language, autosave_current, enable_multisig_experimental); + try { m_rpc->send_json_request("restore_deterministic_wallet", params); } + catch (const monero_rpc_error& ex) { handle_create_wallet_error(ex, config.m_path.get()); } + clear(); + m_path = config.m_path.get(); + return this; + } + + monero_wallet_rpc* monero_wallet_rpc::create_wallet_from_keys(const std::shared_ptr &conf) { + auto config = conf->copy(); + if (config.m_seed_offset != boost::none) throw monero_error("Cannot specify seed offset when creating wallet from keys"); + if (config.m_path == boost::none || config.m_path->empty()) throw monero_error("Wallet name is not initialized"); + + std::string filename = config.m_path.get(); + std::string password = config.m_password.value_or(""); + std::string address = config.m_primary_address.value_or(""); + std::string view_key = config.m_private_view_key.value_or(""); + std::string spend_key = config.m_private_spend_key.value_or(""); + uint64_t restore_height = config.m_restore_height.value_or(0); + bool autosave_current = config.m_save_current.value_or(false); + auto params = std::make_shared(filename, password, address, view_key, spend_key, restore_height, autosave_current); + try { m_rpc->send_json_request("generate_from_keys", params); } + catch (const monero_rpc_error& ex) { handle_create_wallet_error(ex, filename); } + clear(); + m_path = config.m_path.get(); + return this; + } + + std::string monero_wallet_rpc::query_key(const std::string& key_type) const { + auto params = std::make_shared(key_type); + auto result = m_rpc->send_json_request("query_key", params); + auto kv = std::make_shared(); + key_value::from_property_tree(result, kv); + if (kv->m_key == boost::none) throw monero_error(std::string("Could not query key: ") + key_type); + return *kv->m_key; + } + + std::vector> monero_wallet_rpc::sweep_account(const monero_tx_config &conf) { + auto config = conf.copy(); + // validate config + if (config.m_account_index == boost::none) throw monero_error("Must specify an account index to sweep from"); + std::vector> destinations = config.get_normalized_destinations(); + if (destinations.size() != 1) throw monero_error("Must provide exactly one destination address to sweep output to"); + if (destinations[0]->m_address == boost::none || destinations[0]->m_address->empty()) throw monero_error("Must specify destination address to sweep to"); + if (destinations[0]->m_amount != boost::none) throw monero_error("Cannot specify destination amount to sweep"); + if (config.m_key_image != boost::none) throw monero_error("Cannot define key image in sweep_account(); use sweep_output() to sweep an output by its key image"); + if (gen_utils::bool_equals(true, config.m_sweep_each_subaddress)) throw monero_error("Cannot sweep each subaddress with RPC `sweep_all`"); + if (config.m_subtract_fee_from.size() > 0) throw monero_error("Sweep transactions do not support subtracting fees from destinations"); + + // sweep from all subaddresses if not otherwise defined + if (config.m_subaddress_indices.empty()) { + uint32_t account_idx = config.m_account_index.get(); + auto subaddresses = get_subaddresses(account_idx); + for (const auto &subaddress : subaddresses) { + config.m_subaddress_indices.push_back(subaddress.m_index.get()); + } + } + if (config.m_subaddress_indices.size() == 0) throw monero_error("No subaddresses to sweep from"); + bool relay = config.m_relay == true; + auto params = std::make_shared(config); + params->m_get_tx_key = boost::none; + params->m_get_tx_keys = true; + auto result = m_rpc->send_json_request("sweep_all", params); + if (gen_utils::bool_equals(true, config.m_relay)) poll(); + std::vector> txs; + auto set = std::make_shared(); + deserialize_sent_tx_set(result, set, txs, config); + + for (auto &tx : set->m_txs) { + tx->m_is_locked = true; + tx->m_is_confirmed = false; + tx->m_num_confirmations = 0; + tx->m_relay = relay; + tx->m_in_tx_pool = relay; + tx->m_is_relayed = relay; + tx->m_is_miner_tx = false; + tx->m_is_failed = false; + tx->m_ring_size = monero_utils::RING_SIZE; + if (tx->m_outgoing_transfer == nullptr) throw monero_error("Tx outgoing transfer is none"); + auto transfer = tx->m_outgoing_transfer; + transfer->m_account_index = config.m_account_index; + if (config.m_subaddress_indices.size() == 1) { + transfer->m_subaddress_indices = config.m_subaddress_indices; + } + auto destination = std::make_shared(); + destination->m_address = destinations[0]->m_address; + destination->m_amount = transfer->m_amount; + transfer->m_destinations.clear(); + transfer->m_destinations.push_back(destination); + tx->m_payment_id = config.m_payment_id; + if (tx->m_unlock_time == boost::none) tx->m_unlock_time = 0; + if (relay) { + if (tx->m_last_relayed_timestamp == boost::none) { + // TODO (monero-wallet-rpc): provide timestamp on response; unconfirmed timestamps vary + tx->m_last_relayed_timestamp = static_cast(time(NULL)); + } + if (tx->m_is_double_spend_seen == boost::none) tx->m_is_double_spend_seen = false; + } + } + + return set->m_txs; + } + + void monero_wallet_rpc::clear_address_cache() { + boost::lock_guard lock(m_address_cache_mutex); + m_address_cache.clear(); + } + + void monero_wallet_rpc::refresh_listening() { + boost::lock_guard poller_lock(m_poller_mutex); + + bool should_poll; + { + boost::lock_guard lock(m_listeners_mutex); + should_poll = !m_listeners.empty(); + } + + if (m_poller == nullptr && should_poll) { + m_poller = std::make_unique(this); + if (m_sync_period_in_ms != boost::none) m_poller->set_period_in_ms(m_sync_period_in_ms.get()); + } + if (m_poller != nullptr) m_poller->request_is_polling(should_poll); + } + + void monero_wallet_rpc::poll() { + monero_wallet_poller* poller; + { + boost::lock_guard poller_lock(m_poller_mutex); + poller = m_poller.get(); + } + if (poller != nullptr && poller->is_polling()) { + poller->poll(); + } + } + + void monero_wallet_rpc::clear() { + { + boost::lock_guard lock(m_listeners_mutex); + m_listeners.clear(); + } + refresh_listening(); + wait_for_listener_callbacks_idle(); + clear_address_cache(); + m_path = ""; + } + + std::vector> monero_wallet_rpc::get_txs() const { + return get_txs(monero_tx_query()); + } + + std::vector> monero_wallet_rpc::get_txs(const monero_tx_query& query) const { + MTRACE("monero_wallet_rpc::get_txs(query)"); + + // copy query + std::shared_ptr query_sp = std::make_shared(query); // convert to shared pointer + std::shared_ptr _query = query_sp->copy(query_sp, std::make_shared()); // deep copy + + // covers the special-case re-fetch below throwing on a recursive call, too + query_free_guard free_guard{_query}; + + // temporarily disable transfer and output queries in order to collect all tx context + std::shared_ptr transfer_query = _query->m_transfer_query; + std::shared_ptr input_query = _query->m_input_query; + std::shared_ptr output_query = _query->m_output_query; + _query->m_transfer_query = nullptr; + _query->m_input_query = nullptr; + _query->m_output_query = nullptr; + + // fetch all transfers that meet tx query + std::shared_ptr temp_transfer_query = std::make_shared(); + temp_transfer_query->m_tx_query = monero_tx_query::decontextualize(_query->copy(_query, std::make_shared())); + temp_transfer_query->m_tx_query->m_transfer_query = temp_transfer_query; + std::vector> transfers; + { + // free unconditionally, even if get_transfers_aux() throws an error + // otherwise this circularly-referenced copy leaks + query_free_guard free_guard{temp_transfer_query->m_tx_query}; + transfers = get_transfers_aux(*temp_transfer_query); + } + + // collect unique txs from transfers while retaining order + std::vector> txs = std::vector>(); + std::unordered_set> txsSet; + for (const std::shared_ptr& transfer : transfers) { + if (txsSet.find(transfer->m_tx) == txsSet.end()) { + txs.push_back(transfer->m_tx); + txsSet.insert(transfer->m_tx); + } + } + + // cache types into maps for merging and lookup + std::map> tx_map; + std::map> block_map; + for (const std::shared_ptr& tx : txs) { + monero_utils::merge_tx(tx, tx_map, block_map); + } + + // fetch and merge outputs if requested + if ((_query->m_include_outputs != boost::none && *_query->m_include_outputs) || output_query != nullptr) { + // start from the caller's own output_query (if any) rather than a blank default, so + // the outputs merged into the returned txs actually honor the caller's output filter + std::shared_ptr temp_output_query = output_query != nullptr + ? output_query->copy(output_query, std::make_shared()) + : std::make_shared(); + temp_output_query->m_tx_query = monero_tx_query::decontextualize(_query->copy(_query, std::make_shared())); + temp_output_query->m_tx_query->m_output_query = temp_output_query; + std::vector> outputs; + { + // free unconditionally, even if get_outputs_aux() throws an error + // otherwise this circularly-referenced copy leaks + query_free_guard free_guard{temp_output_query->m_tx_query}; + outputs = get_outputs_aux(*temp_output_query); + } + + // merge output txs one time while retaining order + std::unordered_set> output_txs; + for (const std::shared_ptr& output : outputs) { + std::shared_ptr tx = std::static_pointer_cast(output->m_tx); + if (output_txs.find(tx) == output_txs.end()) { + monero_utils::merge_tx(tx, tx_map, block_map); + output_txs.insert(tx); + } + } + } + + // restore transfer and output queries + _query->m_transfer_query = transfer_query; + _query->m_input_query = input_query; + _query->m_output_query = output_query; + + // filter txs that don't meet transfer query + std::vector> queried_txs; + std::vector>::iterator tx_iter = txs.begin(); + while (tx_iter != txs.end()) { + std::shared_ptr tx = *tx_iter; + if (_query->meets_criteria(tx.get())) { + queried_txs.push_back(tx); + ++tx_iter; + } else { + tx_map.erase(tx->m_hash.get()); + tx_iter = txs.erase(tx_iter); + if (tx->m_block != nullptr) tx->m_block->m_txs.erase(std::remove(tx->m_block->m_txs.begin(), tx->m_block->m_txs.end(), tx), tx->m_block->m_txs.end()); // TODO, no way to use tx_iter? + } + } + txs = queried_txs; + + // special case: re-fetch txs if inconsistency caused by needing to make multiple wallet calls + // TODO monero-project: offer wallet.get_txs(...) + for (const std::shared_ptr& tx : txs) { + if ((*tx->m_is_confirmed && tx->m_block == nullptr) || (!*tx->m_is_confirmed && tx->m_block != nullptr)) { + MWARNING("Inconsistency detected building txs from multiple wallet2 calls, re-fetching"); + monero_utils::free(txs); + txs.clear(); + txs = get_txs(*_query); + return txs; + } + } + + // if tx hashes requested, order txs + if (!_query->m_hashes.empty()) { + txs.clear(); + for (const std::string& tx_hash : _query->m_hashes) { + std::map>::const_iterator tx_iter = tx_map.find(tx_hash); + if (tx_iter != tx_map.end()) txs.push_back(tx_iter->second); + } + } + + return txs; + } + + std::vector> monero_wallet_rpc::get_transfers(const monero_transfer_query& query) const { + // get transfers directly if query does not require tx context (e.g. other transfers, outputs) + if (!monero_transfer_query::is_contextual(query)) return get_transfers_aux(query); + + // otherwise get txs with full models to fulfill query + std::vector> transfers; + for (const std::shared_ptr& tx : get_txs(*(query.m_tx_query))) { + for (const std::shared_ptr& transfer : tx->filter_transfers(query)) { // collect queried transfers, erase if excluded + transfers.push_back(transfer); + } + } + return transfers; + } + + std::vector> monero_wallet_rpc::get_outputs(const monero_output_query& query) const { + // get outputs directly if query does not require tx context (e.g. other outputs, transfers) + if (!monero_output_query::is_contextual(query)) return get_outputs_aux(query); + + // otherwise get txs with full models to fulfill query + std::vector> outputs; + for (const std::shared_ptr& tx : get_txs(*(query.m_tx_query))) { + for (const std::shared_ptr& output : tx->filter_outputs_wallet(query)) { // collect queried outputs, erase if excluded + outputs.push_back(output); + } + } + return outputs; + } + + std::map> monero_wallet_rpc::get_account_indices(bool get_subaddr_indices) const { + std::map> indices; + for (const auto& account : monero_wallet::get_accounts()) { + uint32_t account_idx = account.m_index.get(); + if (get_subaddr_indices) { + indices[account_idx] = get_subaddress_indices(account_idx); + } + else indices[account_idx] = std::vector(); + } + return indices; + } + + std::vector monero_wallet_rpc::get_subaddress_indices(uint32_t account_idx) const { + // fetch subaddresses + auto params = std::make_shared(account_idx); + auto result = m_rpc->send_json_request("get_address", params); + std::vector subadress_indices; + std::vector> subaddresses; + deserialize_subaddresses(result, subaddresses); + for (const auto& subaddress : subaddresses) { + subadress_indices.push_back(subaddress->m_index.get()); + } + return subadress_indices; + } + + std::vector> monero_wallet_rpc::get_transfers_aux(const monero_transfer_query& query) const { + MTRACE("monero_wallet_rpc::get_transfers_aux(query)"); + + // copy and normalize query + std::shared_ptr _query; + if (query.m_tx_query == nullptr) { + std::shared_ptr query_ptr = std::make_shared(query); // convert to shared pointer for copy // TODO: does this copy unecessarily? copy constructor is not defined + _query = query_ptr->copy(query_ptr, std::make_shared()); + _query->m_tx_query = std::make_shared(); + _query->m_tx_query->m_transfer_query = _query; + } else { + std::shared_ptr tx_query = query.m_tx_query->copy(query.m_tx_query, std::make_shared()); + _query = tx_query->m_transfer_query; + } + std::shared_ptr tx_query = _query->m_tx_query; + + // _query/tx_query hold circular shared_ptr references (m_tx_query <-> m_transfer_query) + // that plain refcounting can't break; monero_utils::free() breaks the cycle manually and + // must run on every exit path, or the copies leak + query_free_guard free_guard{tx_query}; + + // translate from monero_tx_query to in, out, pending, pool, failed terminology used by monero-wallet-rpc + bool can_be_confirmed = !gen_utils::bool_equals(false, tx_query->m_is_confirmed) && !gen_utils::bool_equals(true, tx_query->m_in_tx_pool) && !gen_utils::bool_equals(true, tx_query->m_is_failed) && !gen_utils::bool_equals(false, tx_query->m_is_relayed); + bool can_be_in_tx_pool = !gen_utils::bool_equals(true, tx_query->m_is_confirmed) && !gen_utils::bool_equals(false, tx_query->m_in_tx_pool) && !gen_utils::bool_equals(true, tx_query->m_is_failed) && tx_query->get_height() == boost::none && tx_query->m_max_height == boost::none && !gen_utils::bool_equals(false, tx_query->m_is_locked); + bool can_be_incoming = !gen_utils::bool_equals(false, _query->m_is_incoming) && !gen_utils::bool_equals(true, _query->is_outgoing()) && !gen_utils::bool_equals(true, _query->m_has_destinations); + bool can_be_outgoing = !gen_utils::bool_equals(false, _query->is_outgoing()) && !gen_utils::bool_equals(true, _query->m_is_incoming); + bool is_in = can_be_incoming && can_be_confirmed; + bool is_out = can_be_outgoing && can_be_confirmed; + bool is_pending = can_be_outgoing && can_be_in_tx_pool; + bool is_pool = can_be_incoming && can_be_in_tx_pool; + bool is_failed = !gen_utils::bool_equals(false, tx_query->m_is_failed) && !gen_utils::bool_equals(true, tx_query->m_is_confirmed) && !gen_utils::bool_equals(true, tx_query->m_in_tx_pool); + + // check if fetching pool txs contradicted by configuration + if (tx_query->m_in_tx_pool != boost::none && tx_query->m_in_tx_pool.get() && !can_be_in_tx_pool) { + throw monero_error("Cannot fetch pool transactions because it contradicts configuration"); + } + + // cache unique txs and blocks + std::map> tx_map; + std::map> block_map; + + auto params = std::make_shared(); + params->m_in = is_in; + params->m_out = is_out; + params->m_pool = is_pool; + params->m_pending = is_pending; + params->m_failed = is_failed; + params->m_max_height = tx_query->m_max_height; + + if (tx_query->m_min_height != boost::none) { + uint64_t min_height = tx_query->m_min_height.get(); + // TODO monero-project: wallet2::get_payments() min_height is exclusive, so manually offset to match intended range (issues #5751, #5598) + if (min_height > 0) params->m_min_height = min_height - 1; + else params->m_min_height = min_height; + } + + if (_query->m_account_index == boost::none) { + if (_query->m_subaddress_index != boost::none || !_query->m_subaddress_indices.empty()) throw monero_error("Filter specifies a subaddress index but not an account index"); + params->m_all_accounts = true; + } else { + params->m_account_index = _query->m_account_index; + + // set subaddress indices param + if (_query->m_subaddress_index != boost::none) { + params->m_subaddr_indices.push_back(_query->m_subaddress_index.get()); + } + for (const uint32_t& subaddress_idx : _query->m_subaddress_indices) { + params->m_subaddr_indices.push_back(subaddress_idx); + } + } + + // build txs using `get_transfers` + auto result = m_rpc->send_json_request("get_transfers", params); + + deserialize_tx_with_transfer_and_merge(result, tx_map, block_map); + + // sort txs by block height + std::vector> txs; + for (std::map>::const_iterator tx_iter = tx_map.begin(); tx_iter != tx_map.end(); ++tx_iter) { + txs.push_back(tx_iter->second); + } + sort(txs.begin(), txs.end(), monero_utils::tx_height_less_than); + + // filter transfers + std::vector> transfers; + for (const std::shared_ptr& tx : txs) { + + // tx is not incoming/outgoing unless already set + if (tx->m_is_incoming == boost::none) tx->m_is_incoming = false; + if (tx->m_is_outgoing == boost::none) tx->m_is_outgoing = false; + + // sort incoming transfers + sort(tx->m_incoming_transfers.begin(), tx->m_incoming_transfers.end(), monero_utils::incoming_transfer_before); + + // collect queried transfers, erase if excluded + for (const std::shared_ptr& transfer : tx->filter_transfers(*_query)) transfers.push_back(transfer); + + // remove excluded txs from block + if (tx->m_block != nullptr && tx->m_outgoing_transfer == nullptr && tx->m_incoming_transfers.empty()) { + tx->m_block->m_txs.erase(std::remove(tx->m_block->m_txs.begin(), tx->m_block->m_txs.end(), tx), tx->m_block->m_txs.end()); // TODO, no way to use const_iterator? + } + } + MTRACE("monero_wallet_rpc::get_transfers() returning " << transfers.size() << " transfers"); + + return transfers; + } + + std::vector> monero_wallet_rpc::get_outputs_aux(const monero_output_query& query) const { + MTRACE("monero_wallet_rpc::get_outputs_aux(query)"); + + // copy and normalize query + std::shared_ptr _query; + if (query.m_tx_query == nullptr) { + std::shared_ptr query_ptr = std::make_shared(query); // convert to shared pointer for copy + _query = query_ptr->copy(query_ptr, std::make_shared()); + } else { + std::shared_ptr tx_query = query.m_tx_query->copy(query.m_tx_query, std::make_shared()); + if (query.m_tx_query->m_output_query != nullptr && query.m_tx_query->m_output_query.get() == &query) { + _query = tx_query->m_output_query; + } else { + if (query.m_tx_query->m_output_query != nullptr) throw monero_error("Output query's tx query must be a circular reference or null"); + std::shared_ptr query_ptr = std::make_shared(query); // convert query to shared pointer for copy + _query = query_ptr->copy(query_ptr, std::make_shared()); + _query->m_tx_query = tx_query; + } + } + if (_query->m_tx_query == nullptr) _query->m_tx_query = std::make_shared(); + std::shared_ptr tx_query = _query->m_tx_query; + + // _query/tx_query hold circular shared_ptr references (m_tx_query <-> m_transfer_query) + // that plain refcounting can't break; monero_utils::free() breaks the cycle manually and + // must run on every exit path, or the copies leak + query_free_guard free_guard{tx_query}; + + // determine account and subaddress indices to be queried + std::map> indices; + if (_query->m_account_index != boost::none) { + std::vector subaddress_indices; + if (_query->m_subaddress_index != boost::none) { + subaddress_indices.push_back(_query->m_subaddress_index.get()); + } + for (const auto& subaddress_idx : _query->m_subaddress_indices) { + subaddress_indices.push_back(subaddress_idx); + } + // null will fetch from all subaddresses + indices[_query->m_account_index.get()] = subaddress_indices; + } + else { + if (_query->m_subaddress_index != boost::none) throw monero_error("Request specifies a subaddress index but not an account index"); + if (!_query->m_subaddress_indices.empty()) throw monero_error("Request specifies subaddress indices but not an account index"); + // fetch all account indices without subaddresses + indices = get_account_indices(false); + } + + // cache unique txs and blocks + std::map> tx_map; + std::map> block_map; + + // collect txs with outputs for each indicated account using `incoming_transfers` rpc call + std::string transfer_type = "all"; + if (_query->m_is_spent != boost::none) { + if (_query->m_is_spent.value() == true) transfer_type = "unavailable"; + else transfer_type = "available"; + } + + auto params = std::make_shared(transfer_type); + + for(const auto& kv : indices) { + uint32_t account_idx = kv.first; + params->m_account_index = account_idx; + params->m_subaddr_indices = kv.second; + // send request + auto result = m_rpc->send_json_request("incoming_transfers", params); + + // convert response to txs with outputs and merge + deserialize_tx_with_output_and_merge(result, tx_map, block_map); + } + + // sort txs by block height + std::vector> txs ; + for (std::map>::const_iterator tx_iter = tx_map.begin(); tx_iter != tx_map.end(); ++tx_iter) { + txs.push_back(tx_iter->second); + } + sort(txs.begin(), txs.end(), monero_utils::tx_height_less_than); + + // filter and return outputs + std::vector> outputs; + for (const std::shared_ptr& tx : txs) { + + // sort outputs + sort(tx->m_outputs.begin(), tx->m_outputs.end(), monero_utils::vout_before); + + // collect queried outputs, erase if excluded + for (const std::shared_ptr& output : tx->filter_outputs_wallet(*_query)) outputs.push_back(output); + + // remove txs without outputs + if (tx->m_outputs.empty() && tx->m_block != nullptr) tx->m_block->m_txs.erase(std::remove(tx->m_block->m_txs.begin(), tx->m_block->m_txs.end(), tx), tx->m_block->m_txs.end()); // TODO, no way to use const_iterator? + } + + return outputs; + } + +} \ No newline at end of file diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_rpc.h b/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_rpc.h new file mode 100644 index 0000000..cdb622a --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_rpc.h @@ -0,0 +1,282 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ +#pragma once + +#include "monero_wallet.h" + +/** + * Implements a monero_wallet.h by wrapping monero-project's monero-wallet-rpc. + */ +namespace monero { + + // forward declaration of internal wallet poller + class monero_wallet_poller; + + /** + * Implements a Monero wallet using monero-wallet-rpc. + */ + class monero_wallet_rpc : public monero_wallet { + public: + + /** + * Destruct the wallet. + */ + ~monero_wallet_rpc(); + monero_wallet_rpc(const std::shared_ptr& rpc_connection); + monero_wallet_rpc(const std::string& uri = "", const std::string& username = "", const std::string& password = "", const std::string& proxy_uri = "", const std::string& zmq_uri = "", const boost::optional& timeout = boost::none); + + /** + * Set the wallet poll period. + * + * @param period_ms poll period in milliseconds. + */ + void set_poll_period_in_ms(uint64_t period_ms); + + /** + * Open an existing wallet from rpc server. + * + * @param config is the wallet configuration + * @return a pointer to the wallet instance + */ + monero_wallet_rpc* open_wallet(const std::shared_ptr &config); + + /** + * Open an existing wallet from rpc server. + * + * @param name is the wallet's name to open + * @param password is the password of the wallet file to open + * @return a pointer to the wallet instance + */ + monero_wallet_rpc* open_wallet(const std::string& name, const std::string& password); + + /** + * Create a new wallet with the given configuration. + * + * @param config is the wallet configuration + * @return a pointer to the wallet instance + */ + monero_wallet_rpc* create_wallet(const std::shared_ptr &config); + + /** + * Get the wallet's RPC connection. + * + * @return the wallet's rpc connection + */ + std::shared_ptr get_rpc_connection() const { return m_rpc; } + + /** + * Get a list of available languages for the wallet's seed. + * + * @return the available languages for the wallet's seed + */ + std::vector get_seed_languages() const; + + /** + * Save and close the current wallet and stop the RPC server. + */ + void stop(); + + /** + * Supported wallet methods. + */ + void add_listener(monero_wallet_listener& listener) override; + void remove_listener(monero_wallet_listener& listener) override; + std::set get_listeners() override; + bool is_view_only() const override; + std::shared_ptr get_daemon_connection() const override; + void set_daemon_connection(const std::shared_ptr& connection, bool is_trusted, const boost::optional& ssl_options); + void set_daemon_connection(const std::shared_ptr& connection, const boost::optional& is_trusted = boost::none) override; + void set_daemon_connection(const std::string& uri, const std::string& username = "", const std::string& password = "", const std::string& proxy_uri = "", const boost::optional& is_trusted = boost::none) override; + bool is_connected_to_daemon() const override; + monero_version get_version() const override; + std::string get_path() const override; + std::string get_seed() const override; + std::string get_seed_language() const override; + std::string get_public_view_key() const override; + std::string get_private_view_key() const override; + std::string get_public_spend_key() const override; + std::string get_private_spend_key() const override; + std::string get_address(const uint32_t account_idx, const uint32_t subaddress_idx) const override; + monero_subaddress get_address_index(const std::string& address) const override; + monero_integrated_address get_integrated_address(const std::string& standard_address = "", const std::string& payment_id = "") const override; + monero_integrated_address decode_integrated_address(const std::string& integrated_address) const override; + uint64_t get_height() const override; + uint64_t get_daemon_height() const override; + uint64_t get_height_by_date(uint16_t year, uint8_t month, uint8_t day) const override; + monero_sync_result sync() override; + monero_sync_result sync(monero_wallet_listener& listener) override; + monero_sync_result sync(uint64_t start_height, monero_wallet_listener& listener) override; + monero_sync_result sync(uint64_t start_height) override; + void start_syncing(uint64_t sync_period_in_ms = 10000) override; + void stop_syncing() override; + void scan_txs(const std::vector& tx_hashes) override; + void rescan_spent() override; + void rescan_blockchain() override; + uint64_t get_balance() const override; + uint64_t get_balance(uint32_t account_index) const override; + uint64_t get_balance(uint32_t account_idx, uint32_t subaddress_idx) const override; + uint64_t get_unlocked_balance() const override; + uint64_t get_unlocked_balance(uint32_t account_index) const override; + uint64_t get_unlocked_balance(uint32_t account_idx, uint32_t subaddress_idx) const override; + monero_account get_account(const uint32_t account_idx, bool include_subaddresses) const override; + monero_account get_account(const uint32_t account_idx, bool include_subaddresses, bool skip_balances) const; + std::vector get_accounts(bool include_subaddresses, const std::string& tag) const override; + std::vector get_accounts(bool include_subaddresses, const std::string& tag, bool skip_balances) const; + monero_account create_account(const std::string& label = "") override; + std::vector get_subaddresses(const uint32_t account_idx, const std::vector& subaddress_indices, bool skip_balances) const; + std::vector get_subaddresses(uint32_t account_idx, const std::vector& subaddress_indices) const override; + std::vector get_subaddresses(const uint32_t account_idx) const override; + monero_subaddress get_subaddress(const uint32_t account_idx, const uint32_t subaddress_idx) const override; + monero_subaddress create_subaddress(uint32_t account_idx, const std::string& label = "") override; + void set_subaddress_label(uint32_t account_idx, uint32_t subaddress_idx, const std::string& label = "") override; + std::vector> get_txs() const override; + std::vector> get_txs(const monero_tx_query& query) const override; + std::vector> get_transfers(const monero_transfer_query& query) const override; + std::vector> get_outputs(const monero_output_query& query) const override; + std::string export_outputs(bool all = false) const override; + int import_outputs(const std::string& outputs_hex) override; + std::shared_ptr export_key_images(bool all = false) const override; + std::shared_ptr import_key_images(const std::vector>& key_images, uint64_t offset = 0) override; + void freeze_output(const std::string& key_image) override; + void thaw_output(const std::string& key_image) override; + bool is_output_frozen(const std::string& key_image) override; + monero_tx_priority get_default_fee_priority() const override; + std::vector> create_txs(const monero_tx_config& conf) override; + std::shared_ptr sweep_output(const monero_tx_config& config) override; + std::vector> sweep_dust(bool relay = false) override; + std::vector> sweep_unlocked(const monero_tx_config& config) override; + std::vector relay_txs(const std::vector& tx_metadatas) override; + monero_tx_set describe_tx_set(const monero_tx_set& tx_set) override; + monero_tx_set sign_txs(const std::string& unsigned_tx_hex) override; + std::vector submit_txs(const std::string& signed_tx_hex) override; + std::string sign_message(const std::string& msg, monero_message_signature_type signature_type, uint32_t account_idx = 0, uint32_t subaddress_idx = 0) const override; + monero_message_signature_result verify_message(const std::string& msg, const std::string& address, const std::string& signature) const override; + std::string get_tx_key(const std::string& tx_hash) const override; + std::shared_ptr check_tx_key(const std::string& tx_hash, const std::string& tx_key, const std::string& address) const override; + std::string get_tx_proof(const std::string& tx_hash, const std::string& address, const std::string& message) const override; + std::shared_ptr check_tx_proof(const std::string& tx_hash, const std::string& address, const std::string& message, const std::string& signature) const override; + std::string get_spend_proof(const std::string& tx_hash, const std::string& message) const override; + bool check_spend_proof(const std::string& tx_hash, const std::string& message, const std::string& signature) const override; + std::string get_reserve_proof_wallet(const std::string& message) const override; + std::string get_reserve_proof_account(uint32_t account_idx, uint64_t amount, const std::string& message) const override; + std::shared_ptr check_reserve_proof(const std::string& address, const std::string& message, const std::string& signature) const override; + std::string get_tx_note(const std::string& tx_hash) const override; + std::vector get_tx_notes(const std::vector& tx_hashes) const override; + void set_tx_note(const std::string& tx_hashes, const std::string& notes) override; + void set_tx_notes(const std::vector& tx_hashes, const std::vector& notes) override; + std::vector get_address_book_entries(const std::vector& indices) const override; + uint64_t add_address_book_entry(const std::string& address, const std::string& description) override; + void edit_address_book_entry(uint64_t index, bool set_address, const std::string& address, bool set_description, const std::string& description) override; + void delete_address_book_entry(uint64_t index) override; + void tag_accounts(const std::string& tag, const std::vector& account_indices) override; + void untag_accounts(const std::vector& account_indices) override; + std::vector> get_account_tags() const override; + void set_account_tag_label(const std::string& tag, const std::string& label) override; + std::string get_payment_uri(const monero_tx_config& config) const override; + std::shared_ptr parse_payment_uri(const std::string& uri) const override; + void set_attribute(const std::string& key, const std::string& val) override; + bool get_attribute(const std::string& key, std::string& value) const override; + void start_mining(boost::optional num_threads, boost::optional background_mining, boost::optional ignore_battery) override; + void stop_mining() override; + bool is_multisig_import_needed() const override; + monero_multisig_info get_multisig_info() const override; + std::string prepare_multisig() override; + std::string make_multisig(const std::vector& multisig_hexes, int threshold, const std::string& password) override; + monero_multisig_init_result exchange_multisig_keys(const std::vector& multisig_hexes, const std::string& password) override; + std::string export_multisig_hex() override; + int import_multisig_hex(const std::vector& multisig_hexes, const bool refresh_after_import = true) override; + monero_multisig_sign_result sign_multisig_tx_hex(const std::string& multisig_tx_hex) override; + std::vector submit_multisig_tx_hex(const std::string& signed_multisig_tx_hex) override; + void change_password(const std::string& old_password, const std::string& new_password) override; + void save() override; + bool is_closed() const override; + void close(bool save = false) override; + std::shared_ptr get_balances(const boost::optional& account_idx, const boost::optional& subaddress_idx) const; + + // --------------------------------- PRIVATE -------------------------------- + + private: + friend class monero_wallet_poller; + boost::optional m_sync_period_in_ms; + std::string m_path = ""; + std::shared_ptr m_rpc; + std::shared_ptr m_daemon_connection; + mutable boost::recursive_mutex m_listeners_mutex; + std::set m_listeners; + + mutable boost::recursive_mutex m_sync_mutex; + mutable boost::mutex m_address_cache_mutex; + mutable std::unordered_map> m_address_cache; + boost::mutex m_poller_mutex; + std::unique_ptr m_poller; + + monero_wallet_rpc* create_wallet_random(const std::shared_ptr &config); + monero_wallet_rpc* create_wallet_from_seed(const std::shared_ptr &config); + monero_wallet_rpc* create_wallet_from_keys(const std::shared_ptr &config); + + monero_sync_result refresh(const std::shared_ptr& params); + + std::map> get_account_indices(bool get_subaddress_indices) const; + std::vector get_subaddress_indices(uint32_t account_idx) const; + std::vector> get_outputs_aux(const monero_output_query& query) const; + std::vector> get_transfers_aux(const monero_transfer_query& query) const; + std::string query_key(const std::string& key_type) const; + std::vector> sweep_account(const monero_tx_config &conf); + void clear_address_cache(); + void refresh_listening(); + void wait_for_listener_callbacks_idle(); + void poll(); + void clear(); + }; +} \ No newline at end of file diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_rpc_model.cpp b/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_rpc_model.cpp new file mode 100644 index 0000000..fa3c546 --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_rpc_model.cpp @@ -0,0 +1,1879 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ + +#include "monero_wallet_rpc_model.h" +#include "common/monero_error.h" +#include "utils/gen_utils.h" +#include "utils/monero_utils.h" + +namespace monero { + + // --------------------------- KEY VALUE --------------------------- + + void key_value::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& attributes) { + attributes->m_key = boost::none; + attributes->m_value = boost::none; + + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("key")) attributes->m_key = it->second.data(); + else if (key == std::string("value")) attributes->m_value = it->second.data(); + } + } + + rapidjson::Value key_value::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_key != boost::none) monero_utils::add_json_member("key", m_key.get(), allocator, root, value_str); + if (m_value != boost::none) monero_utils::add_json_member("value", m_value.get(), allocator, root, value_str); + + // return root + return root; + } + + // --------------------------- MONERO RPC KEY IMAGE --------------------------- + + monero_rpc_key_image::monero_rpc_key_image(const std::shared_ptr &key_image) { + m_hex = key_image->m_hex; + m_signature = key_image->m_signature; + } + + rapidjson::Value monero_rpc_key_image::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_hex != boost::none) monero_utils::add_json_member("key_image", m_hex.get(), allocator, root, value_str); + if (m_signature != boost::none) monero_utils::add_json_member("signature", m_signature.get(), allocator, root, value_str); + + // return root + return root; + } + + // --------------------------- MONERO GET PAYMENT URI --------------------------- + + monero_payment_uri_params::monero_payment_uri_params(const monero_tx_config& config): + m_recipient_name(config.m_recipient_name), + m_tx_description(config.m_note), + m_payment_id(config.m_payment_id) { + + if (config.m_destinations.empty()) { + m_address = config.m_address; + m_amount = config.m_amount; + } else { + const auto& dest = config.m_destinations[0]; + m_address = dest->m_address; + m_amount = dest->m_amount; + } + } + + rapidjson::Value monero_payment_uri_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); + if (m_payment_id != boost::none) monero_utils::add_json_member("payment_id", m_payment_id.get(), allocator, root, value_str); + if (m_recipient_name != boost::none) monero_utils::add_json_member("recipient_name", m_recipient_name.get(), allocator, root, value_str); + if (m_tx_description != boost::none) monero_utils::add_json_member("tx_description", m_tx_description.get(), allocator, root, value_str); + if (m_uri != boost::none) monero_utils::add_json_member("uri", m_uri.get(), allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_amount != boost::none) monero_utils::add_json_member("amount", m_amount.get(), allocator, root, value_num); + + // return root + return root; + } + + // --------------------------- MONERO GET BALANCE PARAMS --------------------------- + + monero_get_balance_params::monero_get_balance_params(uint32_t account_idx, boost::optional address_idx, bool all_accounts, bool strict): + m_account_idx(account_idx), + m_all_accounts(all_accounts), + m_strict(strict) { + if (address_idx != boost::none) m_address_indices.push_back(address_idx.get()); + } + + rapidjson::Value monero_get_balance_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_account_idx != boost::none) monero_utils::add_json_member("account_index", m_account_idx.get(), allocator, root, value_num); + + // set bool values + if (m_all_accounts != boost::none) monero_utils::add_json_member("all_accounts", m_all_accounts.get(), allocator, root); + if (m_strict != boost::none) monero_utils::add_json_member("strict", m_strict.get(), allocator, root); + + // set sub-arrays + if (!m_address_indices.empty()) root.AddMember("address_indices", monero_utils::to_rapidjson_val(allocator, m_address_indices), allocator); + + // return root + return root; + } + + // --------------------------- MONERO WALLET DATA PARAMS --------------------------- + + rapidjson::Value monero_wallet_data_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_outputs_hex != boost::none) monero_utils::add_json_member("outputs_data_hex", m_outputs_hex.get(), allocator, root, value_str); + + // set bool values + if (m_all != boost::none) monero_utils::add_json_member("all", m_all.get(), allocator, root); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_offset != boost::none) monero_utils::add_json_member("offset", m_offset.get(), allocator, root, value_num); + + // set sub-arrays + std::vector> signed_key_images; + for (const auto& ki : m_key_images) signed_key_images.push_back(std::make_shared(ki)); + if (m_all == boost::none && signed_key_images.size() > 0) root.AddMember("signed_key_images", monero_utils::to_rapidjson_val(allocator, signed_key_images), allocator); + + // return root + return root; + } + + // --------------------------- MONERO SWEEP PARAMS --------------------------- + + monero_sweep_params::monero_sweep_params(const monero_tx_config& config): + m_account_index(config.m_account_index), + m_subaddr_indices(config.m_subaddress_indices), + m_key_image(config.m_key_image), + m_relay(config.m_relay), + m_priority(config.m_priority), + m_payment_id(config.m_payment_id), + m_below_amount(config.m_below_amount), + m_get_tx_key(true), + m_get_tx_hex(true), + m_get_tx_metadata(true) { + auto destinations = config.get_normalized_destinations(); + m_address = destinations.empty() ? config.m_address : destinations[0]->m_address; + } + + rapidjson::Value monero_sweep_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); + if (m_key_image != boost::none) monero_utils::add_json_member("key_image", m_key_image.get(), allocator, root, value_str); + if (m_payment_id != boost::none) monero_utils::add_json_member("payment_id", m_payment_id.get(), allocator, root, value_str); + + // set number values + rapidjson::Value val_num(rapidjson::kNumberType); + if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, val_num); + if (m_priority != boost::none) monero_utils::add_json_member("priority", m_priority.get(), allocator, root, val_num); + if (m_below_amount != boost::none) monero_utils::add_json_member("below_amount", m_below_amount.get(), allocator, root, val_num); + + // set bool values + if (m_get_tx_key != boost::none) monero_utils::add_json_member("get_tx_key", m_get_tx_key.get(), allocator, root); + if (m_get_tx_keys != boost::none) monero_utils::add_json_member("get_tx_keys", m_get_tx_keys.get(), allocator, root); + if (m_get_tx_hex != boost::none) monero_utils::add_json_member("get_tx_hex", m_get_tx_hex.get(), allocator, root); + if (m_get_tx_metadata != boost::none) monero_utils::add_json_member("get_tx_metadata", m_get_tx_metadata.get(), allocator, root); + monero_utils::add_json_member("do_not_relay", !gen_utils::bool_equals(true, m_relay), allocator, root); + + // set sub-arrays + if (m_subaddr_indices.size() > 0) root.AddMember("subaddr_indices", monero_utils::to_rapidjson_val(allocator, m_subaddr_indices), allocator); + + // return root + return root; + } + + // --------------------------- MONERO TRANSFER PARAMS --------------------------- + + monero_transfer_params::monero_transfer_params(const monero_tx_config &config) { + for (const auto& sub_idx : config.m_subaddress_indices) { + m_subaddress_indices.push_back(sub_idx); + } + + for (const auto &dest : config.get_normalized_destinations()) { + if (dest->m_address == boost::none) throw monero_error("Destination address is not defined"); + if (dest->m_amount == boost::none) throw monero_error("Destination amount is not defined"); + m_destinations.push_back(dest); + } + + m_subtract_fee_from_outputs = config.m_subtract_fee_from; + m_account_index = config.m_account_index; + m_payment_id = config.m_payment_id; + if (gen_utils::bool_equals(true, config.m_relay)) { + m_do_not_relay = false; + } + else { + m_do_not_relay = true; + } + if (config.m_priority == monero_tx_priority::DEFAULT) { + m_priority = 0; + } + else if (config.m_priority == monero_tx_priority::UNIMPORTANT) { + m_priority = 1; + } + else if (config.m_priority == monero_tx_priority::NORMAL) { + m_priority = 2; + } + else if (config.m_priority == monero_tx_priority::ELEVATED) { + m_priority = 3; + } + m_get_tx_hex = true; + m_get_tx_metadata = true; + if (gen_utils::bool_equals(true, config.m_can_split)) m_get_tx_keys = true; + else m_get_tx_key = true; + } + + rapidjson::Value monero_transfer_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_payment_id != boost::none) monero_utils::add_json_member("payment_id", m_payment_id.get(), allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, value_num); + if (m_priority != boost::none) monero_utils::add_json_member("priority", m_priority.get(), allocator, root, value_num); + + // set bool values + if (m_do_not_relay != boost::none) monero_utils::add_json_member("do_not_relay", m_do_not_relay.get(), allocator, root); + if (m_get_tx_hex != boost::none) monero_utils::add_json_member("get_tx_hex", m_get_tx_hex.get(), allocator, root); + if (m_get_tx_metadata != boost::none) monero_utils::add_json_member("get_tx_metadata", m_get_tx_metadata.get(), allocator, root); + if (m_get_tx_keys != boost::none) monero_utils::add_json_member("get_tx_keys", m_get_tx_keys.get(), allocator, root); + if (m_get_tx_key != boost::none) monero_utils::add_json_member("get_tx_key", m_get_tx_key.get(), allocator, root); + + // set sub-arrays + if (!m_subtract_fee_from_outputs.empty()) root.AddMember("subtract_fee_from_outputs", monero_utils::to_rapidjson_val(allocator, m_subtract_fee_from_outputs), allocator); + if (!m_subaddress_indices.empty()) root.AddMember("subaddr_indices", monero_utils::to_rapidjson_val(allocator, m_subaddress_indices), allocator); + if (!m_destinations.empty()) root.AddMember("destinations", monero_utils::to_rapidjson_val(allocator, m_destinations), allocator); + + // return root + return root; + } + + // --------------------------- MONERO QUERY KEY PARAMS --------------------------- + + rapidjson::Value monero_query_key_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_key_type != boost::none) monero_utils::add_json_member("key_type", m_key_type.get(), allocator, root, value_str); + + // return root + return root; + } + + // --------------------------- MONERO QUERY OUTPUT PARAMS --------------------------- + + rapidjson::Value monero_query_output_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_key_image != boost::none) monero_utils::add_json_member("key_image", m_key_image.get(), allocator, root, value_str); + + // return root + return root; + } + + // --------------------------- MONERO GET ADDRESS PARAMS --------------------------- + + rapidjson::Value monero_get_address_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, value_num); + + // set sub-arrays + if (!m_subaddress_indices.empty()) root.AddMember("address_index", monero_utils::to_rapidjson_val(allocator, m_subaddress_indices), allocator); + + // return root + return root; + } + + // --------------------------- MONERO INTEGRATED ADDRESS PARAMS --------------------------- + + rapidjson::Value monero_integrated_address_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_standard_address != boost::none) monero_utils::add_json_member("standard_address", m_standard_address.get(), allocator, root, value_str); + if (m_payment_id != boost::none) monero_utils::add_json_member("payment_id", m_payment_id.get(), allocator, root, value_str); + if (m_integrated_address != boost::none) monero_utils::add_json_member("integrated_address", m_integrated_address.get(), allocator, root, value_str); + + // return root + return root; + } + + // --------------------------- MONERO MULTISIG PARAMS --------------------------- + + rapidjson::Value monero_multisig_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_password != boost::none) monero_utils::add_json_member("password", m_password.get(), allocator, root, value_str); + if (m_multisig_tx_hex != boost::none) monero_utils::add_json_member("tx_data_hex", m_multisig_tx_hex.get(), allocator, root, value_str); + + // set number values + rapidjson::Value val_num(rapidjson::kNumberType); + if (m_threshold != boost::none) monero_utils::add_json_member("threshold", m_threshold.get(), allocator, root, val_num); + + // set bool values + if (m_enable_multisig_experimental != boost::none) monero_utils::add_json_member("enable_multisig_experimental", m_enable_multisig_experimental.get(), allocator, root); + if (m_refresh_after_import != boost::none) monero_utils::add_json_member("refresh_after_import", m_refresh_after_import.get(), allocator, root); + + // set sub-arrays + if (!m_multisig_info.empty()) root.AddMember("multisig_info", monero_utils::to_rapidjson_val(allocator, m_multisig_info), allocator); + if (!m_multisig_hexes.empty()) root.AddMember("info", monero_utils::to_rapidjson_val(allocator, m_multisig_hexes), allocator); + + // return root + return root; + } + + // --------------------------- MONERO CLOSE WALLET PARAMS --------------------------- + + rapidjson::Value monero_close_wallet_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set bool values + if (m_save != boost::none) monero_utils::add_json_member("autosave_current", m_save.get(), allocator, root); + + // return root + return root; + } + + // --------------------------- MONERO CHANGE WALLET PASSWORD PARAMS --------------------------- + + rapidjson::Value monero_change_wallet_password_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_old_password != boost::none) monero_utils::add_json_member("old_password", m_old_password.get(), allocator, root, value_str); + if (m_new_password != boost::none) monero_utils::add_json_member("new_password", m_new_password.get(), allocator, root, value_str); + + // return root + return root; + } + + // --------------------------- MONERO SET DAEMON PARAMS --------------------------- + + rapidjson::Value monero_set_daemon_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); + if (m_username != boost::none) monero_utils::add_json_member("username", m_username.get(), allocator, root, value_str); + if (m_password != boost::none) monero_utils::add_json_member("password", m_password.get(), allocator, root, value_str); + if (m_proxy != boost::none) monero_utils::add_json_member("proxy", m_proxy.get(), allocator, root, value_str); + if (m_ssl_support != boost::none) monero_utils::add_json_member("ssl_support", m_ssl_support.get(), allocator, root, value_str); + if (m_ssl_options != boost::none && m_ssl_options->m_ssl_private_key_path != boost::none) monero_utils::add_json_member("ssl_private_key_path", m_ssl_options->m_ssl_private_key_path.get(), allocator, root, value_str); + if (m_ssl_options != boost::none && m_ssl_options->m_ssl_certificate_path != boost::none) monero_utils::add_json_member("ssl_certificate_path", m_ssl_options->m_ssl_certificate_path.get(), allocator, root, value_str); + if (m_ssl_options != boost::none && m_ssl_options->m_ssl_ca_file != boost::none) monero_utils::add_json_member("ssl_ca_file", m_ssl_options->m_ssl_ca_file.get(), allocator, root, value_str); + + // set bool values + if (m_trusted != boost::none) monero_utils::add_json_member("trusted", m_trusted.get(), allocator, root); + if (m_ssl_options != boost::none && m_ssl_options->m_ssl_allow_any_cert != boost::none) monero_utils::add_json_member("ssl_allow_any_cert", m_ssl_options->m_ssl_allow_any_cert.get(), allocator, root); + + // set sub-arrays + if (m_ssl_options != boost::none && !m_ssl_options->m_ssl_allowed_fingerprints.empty()) root.AddMember("ssl_allowed_fingerprints", monero_utils::to_rapidjson_val(allocator, m_ssl_options->m_ssl_allowed_fingerprints), allocator); + + // return root + return root; + } + + // --------------------------- MONERO TAG ACCOUNT PARAMS --------------------------- + + rapidjson::Value monero_account_tag_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_tag != boost::none) monero_utils::add_json_member("tag", m_tag.get(), allocator, root, value_str); + if (m_label != boost::none) monero_utils::add_json_member("label", m_label.get(), allocator, root, value_str); + if (m_description != boost::none) monero_utils::add_json_member("description", m_description.get(), allocator, root, value_str); + + // set sub-arrays + if (!m_account_indices.empty()) root.AddMember("accounts", monero_utils::to_rapidjson_val(allocator, m_account_indices), allocator); + + // return root + return root; + } + + // --------------------------- MONERO TX NOTES PARAMS --------------------------- + + rapidjson::Value monero_tx_notes_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set sub-arrays + if (!m_tx_hashes.empty()) root.AddMember("txids", monero_utils::to_rapidjson_val(allocator, m_tx_hashes), allocator); + if (!m_notes.empty()) root.AddMember("notes", monero_utils::to_rapidjson_val(allocator, m_notes), allocator); + + // return root + return root; + } + + // --------------------------- MONERO ADDRESS BOOK ENTRY PARAMS --------------------------- + + rapidjson::Value monero_address_book_entry_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); + if (m_description != boost::none) monero_utils::add_json_member("description", m_description.get(), allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_index != boost::none) monero_utils::add_json_member("index", m_index.get(), allocator, root, value_num); + + // set bool values + if (m_set_address != boost::none) monero_utils::add_json_member("set_address", m_set_address.get(), allocator, root); + if (m_set_description != boost::none) monero_utils::add_json_member("set_description", m_set_description.get(), allocator, root); + + // set sub-arrays + if (!m_entries.empty()) root.AddMember("entries", monero_utils::to_rapidjson_val(allocator, m_entries), allocator); + + // return root + return root; + } + + // --------------------------- MONERO VERIFY SIGN MESSAGE PARAMS --------------------------- + + rapidjson::Value monero_verify_sign_message_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_data != boost::none) monero_utils::add_json_member("data", m_data.get(), allocator, root, value_str); + if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); + if (m_signature != boost::none) monero_utils::add_json_member("signature", m_signature.get(), allocator, root, value_str); + if (m_signature_type != boost::none) { + if (m_signature_type == monero_message_signature_type::SIGN_WITH_VIEW_KEY) { + monero_utils::add_json_member("signature_type", std::string("view"), allocator, root, value_str); + } + else { + monero_utils::add_json_member("signature_type", std::string("spend"), allocator, root, value_str); + } + } + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, value_num); + if (m_address_index != boost::none) monero_utils::add_json_member("address_index", m_address_index.get(), allocator, root, value_num); + + // return root + return root; + } + + // --------------------------- MONERO CHECK TX KEY PARAMS --------------------------- + + rapidjson::Value monero_check_tx_key_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_tx_hash != boost::none) monero_utils::add_json_member("txid", m_tx_hash.get(), allocator, root, value_str); + if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); + if (m_tx_key != boost::none) monero_utils::add_json_member("tx_key", m_tx_key.get(), allocator, root, value_str); + + // return root + return root; + } + + // --------------------------- MONERO SIGN DESCRIBE TRANSFER PARAMS --------------------------- + + rapidjson::Value monero_sign_describe_transfer_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_unsigned_txset != boost::none) monero_utils::add_json_member("unsigned_txset", m_unsigned_txset.get(), allocator, root, value_str); + if (m_multisig_txset != boost::none) monero_utils::add_json_member("multisig_txset", m_multisig_txset.get(), allocator, root, value_str); + + // set bool values + if (m_export_raw != boost::none) monero_utils::add_json_member("export_raw", m_export_raw.get(), allocator, root); + if (m_get_tx_keys != boost::none) monero_utils::add_json_member("get_tx_keys", m_get_tx_keys.get(), allocator, root); + + // return root + return root; + } + + // --------------------------- MONERO WALLET RELAY TX PARAMS --------------------------- + + rapidjson::Value monero_wallet_relay_tx_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_hex != boost::none) monero_utils::add_json_member("hex", m_hex.get(), allocator, root, value_str); + if (m_signed_tx_hex != boost::none) monero_utils::add_json_member("tx_data_hex", m_signed_tx_hex.get(), allocator, root, value_str); + + // return root + return root; + } + + // --------------------------- MONERO CREATE EDIT SUBADDRESS PARAMS --------------------------- + + rapidjson::Value monero_create_edit_subaddress_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_label != boost::none) monero_utils::add_json_member("label", m_label.get(), allocator, root, value_str); + + // set number values + rapidjson::Value val_num(rapidjson::kNumberType); + if (m_account_index != boost::none && m_subaddress_index != boost::none) { + rapidjson::Value index(rapidjson::kObjectType); + monero_utils::add_json_member("major", m_account_index.get(), allocator, index, val_num); + monero_utils::add_json_member("minor", m_subaddress_index.get(), allocator, index, val_num); + root.AddMember("index", index, allocator); + } + else if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, val_num); + + // return root + return root; + } + + // --------------------------- MONERO CREATE OPEN WALLET PARAMS --------------------------- + + rapidjson::Value monero_create_open_wallet_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); + if (m_view_key != boost::none && !m_view_key->empty()) monero_utils::add_json_member("viewkey", m_view_key.get(), allocator, root, value_str); + if (m_spend_key != boost::none && !m_spend_key->empty()) monero_utils::add_json_member("spendkey", m_spend_key.get(), allocator, root, value_str); + if (m_filename != boost::none) monero_utils::add_json_member("filename", m_filename.get(), allocator, root, value_str); + if (m_password != boost::none) monero_utils::add_json_member("password", m_password.get(), allocator, root, value_str); + if (m_language != boost::none) monero_utils::add_json_member("language", m_language.get(), allocator, root, value_str); + if (m_seed != boost::none) monero_utils::add_json_member("seed", m_seed.get(), allocator, root, value_str); + if (m_seed_offset != boost::none) monero_utils::add_json_member("seed_offset", m_seed_offset.get(), allocator, root, value_str); + + // set number values + rapidjson::Value val_num(rapidjson::kNumberType); + if (m_restore_height != boost::none) monero_utils::add_json_member("restore_height", m_restore_height.get(), allocator, root, val_num); + + // set bool values + if (m_autosave_current != boost::none) monero_utils::add_json_member("autosave_current", m_autosave_current.get(), allocator, root); + if (m_enable_multisig_experimental != boost::none) monero_utils::add_json_member("enable_multisig_experimental", m_enable_multisig_experimental.get(), allocator, root); + + // return root + return root; + } + + // --------------------------- MONERO RESERVE PROOF PARAMS --------------------------- + + rapidjson::Value monero_reserve_proof_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_message != boost::none) monero_utils::add_json_member("message", m_message.get(), allocator, root, value_str); + if (m_tx_hash != boost::none) monero_utils::add_json_member("txid", m_tx_hash.get(), allocator, root, value_str); + if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); + if (m_signature != boost::none) monero_utils::add_json_member("signature", m_signature.get(), allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, value_num); + if (m_amount != boost::none) monero_utils::add_json_member("amount", m_amount.get(), allocator, root, value_num); + + // set bool values + if (m_all != boost::none) monero_utils::add_json_member("all", m_all.get(), allocator, root); + + // return root + return root; + } + + // --------------------------- MONERO REFRESH WALLET PARAMS --------------------------- + + rapidjson::Value monero_wallet_refresh_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_period != boost::none) monero_utils::add_json_member("period", m_period.get(), allocator, root, value_num); + if (m_start_height != boost::none) monero_utils::add_json_member("start_height", m_start_height.get(), allocator, root, value_num); + + // set bool values + if (m_enable != boost::none) monero_utils::add_json_member("enable", m_enable.get(), allocator, root); + + // return root + return root; + } + + // --------------------------- MONERO GET INCOMING TRANSFERS PARAMS --------------------------- + + rapidjson::Value monero_get_incoming_transfers_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_transfer_type != boost::none) monero_utils::add_json_member("transfer_type", m_transfer_type.get(), allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, value_num); + + // set bool values + if (m_verbose != boost::none) monero_utils::add_json_member("verbose", m_verbose.get(), allocator, root); + + // set sub-arrays + if (!m_subaddr_indices.empty()) root.AddMember("subaddr_indices", monero_utils::to_rapidjson_val(allocator, m_subaddr_indices), allocator); + + // return root + return root; + } + + // --------------------------- MONERO GET TRANSFERS PARAMS --------------------------- + + rapidjson::Value monero_get_transfers_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + + if (m_min_height != boost::none) monero_utils::add_json_member("min_height", m_min_height.get(), allocator, root, value_num); + if (m_max_height != boost::none) monero_utils::add_json_member("max_height", m_max_height.get(), allocator, root, value_num); + if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, value_num); + + // set bool values + monero_utils::add_json_member("filter_by_height", m_min_height != boost::none || m_max_height != boost::none, allocator, root); + if (m_in != boost::none) monero_utils::add_json_member("in", m_in.get(), allocator, root); + if (m_out != boost::none) monero_utils::add_json_member("out", m_out.get(), allocator, root); + if (m_pool != boost::none) monero_utils::add_json_member("pool", m_pool.get(), allocator, root); + if (m_pending != boost::none) monero_utils::add_json_member("pending", m_pending.get(), allocator, root); + if (m_failed != boost::none) monero_utils::add_json_member("failed", m_failed.get(), allocator, root); + if (m_all_accounts != boost::none) monero_utils::add_json_member("all_accounts", m_all_accounts.get(), allocator, root); + + // set sub-arrays + if (!m_subaddr_indices.empty()) root.AddMember("subaddr_indices", monero_utils::to_rapidjson_val(allocator, m_subaddr_indices), allocator); + + // return root + return root; + } + + // --------------------------- MONERO MULTISIG RESPONSE --------------------------- + + void monero_multisig_response::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& response) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("n_outputs")) response->m_num_outputs = it->second.get_value(); + else if ((key == std::string("info") || key == std::string("multisig_info")) && !it->second.data().empty()) response->m_multisig_info = it->second.data(); + else if (key == std::string("tx_hash_list")) { + const auto& tx_hash_list_node = it->second; + std::vector hashes; + for (auto it2 = tx_hash_list_node.begin(); it2 != tx_hash_list_node.end(); ++it2) { + response->m_tx_hashes.push_back(it2->second.data()); + } + } + } + } + + // --------------------------- MONERO GET BALANCE RESPONSE --------------------------- + + void monero_get_balance_response::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& response) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("balance")) response->m_balance = it->second.get_value(); + else if (key == std::string("unlocked_balance")) response->m_unlocked_balance = it->second.get_value(); + else if (key == std::string("multisig_import_needed")) response->m_multisig_import_needed = it->second.get_value(); + else if (key == std::string("time_to_unlock")) response->m_time_to_unlock = it->second.get_value(); + else if (key == std::string("blocks_to_unlock")) response->m_blocks_to_unlock = it->second.get_value(); + else if (key == std::string("per_subaddress")) { + auto node2 = it->second; + + for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + auto sub = std::make_shared(); + deserialize_subaddress(it2->second, sub); + response->m_per_subaddress.push_back(sub); + } + } + } + } + + // --------------------------- RPC Deserialization --------------------------- + + void deserialize_subaddress(const boost::property_tree::ptree& node, const std::shared_ptr& subaddress) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("account_index") || key == std::string("major")) subaddress->m_account_index = it->second.get_value(); + else if (key == std::string("address_index") || key == std::string("minor")) subaddress->m_index = it->second.get_value(); + else if (key == std::string("address")) subaddress->m_address = it->second.data(); + else if (key == std::string("balance")) subaddress->m_balance = it->second.get_value(); + else if (key == std::string("unlocked_balance")) subaddress->m_unlocked_balance = it->second.get_value(); + else if (key == std::string("label") && !it->second.data().empty()) subaddress->m_label = it->second.data(); + else if (key == std::string("used")) subaddress->m_is_used = it->second.get_value(); + else if (key == std::string("num_unspent_outputs")) subaddress->m_num_unspent_outputs = it->second.get_value(); + else if (key == std::string("blocks_to_unlock")) subaddress->m_num_blocks_to_unlock = it->second.get_value(); + else if (key == std::string("index")) deserialize_subaddress(it->second, subaddress); + } + } + + void deserialize_subaddresses(const boost::property_tree::ptree& node, std::vector>& subaddresses) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("per_subaddress") || key == std::string("addresses")) { + auto per_subaddress_node = it->second; + + for (auto it2 = per_subaddress_node.begin(); it2 != per_subaddress_node.end(); ++it2) { + auto sub = std::make_shared(); + deserialize_subaddress(it2->second, sub); + subaddresses.push_back(sub); + } + } + } + } + + void deserialize_account(const boost::property_tree::ptree& node, monero_account& account) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("account_index")) account.m_index = it->second.get_value(); + else if (key == std::string("address")) account.m_primary_address = it->second.data(); + else if (key == std::string("balance")) account.m_balance = it->second.get_value(); + else if (key == std::string("unlocked_balance")) account.m_unlocked_balance = it->second.get_value(); + else if (key == std::string("base_address")) account.m_primary_address = it->second.data(); + else if (key == std::string("tag") && !it->second.data().empty()) account.m_tag = it->second.data(); + else if (key == std::string("label")) { + // label belongs to first subaddress + } + } + } + + void deserialize_accounts(const boost::property_tree::ptree& node, std::vector& accounts) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("subaddress_accounts")) { + auto accounts_node = it->second; + for (auto account_it = accounts_node.begin(); account_it != accounts_node.end(); ++account_it) { + monero_account account; + deserialize_account(account_it->second, account); + accounts.push_back(account); + } + } + } + } + + bool decode_rpc_tx_type(const std::string &rpc_type, const std::shared_ptr &tx) { + bool is_outgoing = false; + if (rpc_type == std::string("in")) { + tx->m_is_confirmed = true; + tx->m_in_tx_pool = false; + tx->m_is_relayed = true; + tx->m_relay = true; + tx->m_is_failed = false; + tx->m_is_miner_tx = false; + } else if (rpc_type == std::string("out")) { + is_outgoing = true; + tx->m_is_confirmed = true; + tx->m_in_tx_pool = false; + tx->m_is_relayed = true; + tx->m_relay = true; + tx->m_is_failed = false; + tx->m_is_miner_tx = false; + } else if (rpc_type == std::string("pool")) { + tx->m_is_confirmed = false; + tx->m_in_tx_pool = true; + tx->m_is_relayed = true; + tx->m_relay = true; + tx->m_is_failed = false; + tx->m_is_miner_tx = false; // TODO: but could it be? + } else if (rpc_type == std::string("pending")) { + is_outgoing = true; + tx->m_is_confirmed = false; + tx->m_in_tx_pool = true; + tx->m_is_relayed = true; + tx->m_relay = true; + tx->m_is_failed = false; + tx->m_is_miner_tx = false; + } else if (rpc_type == std::string("block")) { + tx->m_is_confirmed = true; + tx->m_in_tx_pool = false; + tx->m_is_relayed = true; + tx->m_relay = true; + tx->m_is_failed = false; + tx->m_is_miner_tx = true; + } else if (rpc_type == std::string("failed")) { + is_outgoing = true; + tx->m_is_confirmed = false; + tx->m_in_tx_pool = false; + tx->m_is_relayed = false; + tx->m_relay = true; + tx->m_is_failed = true; + tx->m_is_miner_tx = false; + } else throw monero_error(std::string("Unrecognized transfer type: ") + rpc_type); + return is_outgoing; + } + + void init_sent_tx(const monero_tx_config &config, std::shared_ptr &tx, bool copy_destinations) { + bool relay = gen_utils::bool_equals(true, config.m_relay); + tx->m_is_outgoing = true; + tx->m_is_confirmed = false; + tx->m_num_confirmations = 0; + tx->m_in_tx_pool = relay; + tx->m_relay = relay; + tx->m_is_relayed = relay; + tx->m_is_miner_tx = false; + tx->m_is_failed = false; + tx->m_is_locked = true; + tx->m_ring_size = monero_utils::RING_SIZE; + + auto outgoing_transfer = std::make_shared(); + outgoing_transfer->m_tx = tx; + + if (config.m_subaddress_indices.size() == 1) { + // we know src subaddress indices iff request specifies 1 + outgoing_transfer->m_subaddress_indices = config.m_subaddress_indices; + } + + if (copy_destinations) { + auto conf_dests = config.get_normalized_destinations(); + for(const auto &conf_dest : conf_dests) { + auto dest = std::make_shared(); + conf_dest->copy(conf_dest, dest); + outgoing_transfer->m_destinations.push_back(dest); + } + } + + tx->m_outgoing_transfer = outgoing_transfer; + tx->m_payment_id = config.m_payment_id; + if (tx->m_unlock_time == boost::none) tx->m_unlock_time = 0; + if (gen_utils::bool_equals(true, tx->m_relay)) { + if (tx->m_last_relayed_timestamp == boost::none) { + // set last relayed timestamp to current time iff relayed + // TODO (monero-wallet-rpc): provide timestamp on response; unconfirmed timestamps vary + tx->m_last_relayed_timestamp = static_cast(time(NULL)); + } + if (tx->m_is_double_spend_seen == boost::none) tx->m_is_double_spend_seen = false; + } + } + + void deserialize_tx_with_transfer(const boost::property_tree::ptree& node, const std::shared_ptr& tx, boost::optional &is_outgoing, const monero_tx_config &config) { + std::shared_ptr header = nullptr; + std::shared_ptr outgoing_transfer = nullptr; + std::shared_ptr incoming_transfer = nullptr; + + bool key_found = false; + + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("type")) { + is_outgoing = decode_rpc_tx_type(it->second.data(), tx); + key_found = true; + } + } + + if (!key_found && is_outgoing == boost::none) throw monero_error("Must indicate if tx is outgoing (true) xor incoming (false) since unknown"); + + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("txid") || key == std::string("tx_hash")) tx->m_hash = it->second.data(); + else if (key == std::string("fee")) tx->m_fee = it->second.get_value(); + else if (key == std::string("note") && !it->second.data().empty()) tx->m_note = it->second.data(); + else if (key == std::string("tx_key") && !it->second.data().empty()) tx->m_key = it->second.data(); + else if (key == std::string("tx_size")) tx->m_size = it->second.get_value(); + else if (key == std::string("unlock_time")) tx->m_unlock_time = it->second.get_value(); + else if (key == std::string("weight")) tx->m_weight = it->second.get_value(); + else if (key == std::string("locked")) tx->m_is_locked = it->second.get_value(); + else if (key == std::string("tx_blob") && !it->second.data().empty()) tx->m_full_hex = it->second.data(); + else if (key == std::string("tx_metadata") && !it->second.data().empty()) tx->m_metadata = it->second.data(); + else if (key == std::string("double_spend_seen")) tx->m_is_double_spend_seen = it->second.get_value(); + else if (key == std::string("block_height") || key == std::string("height")) { + if (gen_utils::bool_equals(true, tx->m_is_confirmed)) { + if (header == nullptr) header = std::make_shared(); + header->m_height = it->second.get_value(); + } + } + else if (key == std::string("timestamp")) { + if (gen_utils::bool_equals(true, tx->m_is_confirmed)) { + if (header == nullptr) header = std::make_shared(); + header->m_timestamp = it->second.get_value(); + } + } + else if (key == std::string("confirmations")) tx->m_num_confirmations = it->second.get_value(); + else if (key == std::string("suggested_confirmations_threshold")) { + if (*is_outgoing) { + if (outgoing_transfer == nullptr) + outgoing_transfer = std::make_shared(); + outgoing_transfer->m_tx = tx; + } + else { + if (incoming_transfer == nullptr) + incoming_transfer = std::make_shared(); + incoming_transfer->m_tx = tx; + incoming_transfer->m_num_suggested_confirmations = it->second.get_value(); + } + } + else if (key == std::string("amount")) { + if (*is_outgoing) { + if (outgoing_transfer == nullptr) { + outgoing_transfer = std::make_shared(); + outgoing_transfer->m_tx = tx; + } + outgoing_transfer->m_amount = it->second.get_value(); + } + else { + if (incoming_transfer == nullptr) incoming_transfer = std::make_shared(); + incoming_transfer->m_tx = tx; + incoming_transfer->m_amount = it->second.get_value(); + } + } + else if (key == std::string("address")) { + if (!*is_outgoing) { + if (incoming_transfer == nullptr) incoming_transfer = std::make_shared(); + incoming_transfer->m_tx = tx; + incoming_transfer->m_address = it->second.data(); + } + } + else if (key == std::string("payment_id")) { + std::string payment_id = it->second.data(); + if (payment_id != std::string("") && payment_id != monero_tx_wallet::DEFAULT_PAYMENT_ID) { + tx->m_payment_id = payment_id; + } + } + else if (key == std::string("subaddr_indices")) { + if (*is_outgoing) { + if (outgoing_transfer == nullptr) { + outgoing_transfer = std::make_shared(); + outgoing_transfer->m_tx = tx; + } + } + else { + if (incoming_transfer == nullptr) incoming_transfer = std::make_shared(); + incoming_transfer->m_tx = tx; + } + + bool first_major = true; + bool first_minor = true; + + for(auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) { + for(auto it3 = it2->second.begin(); it3 != it2->second.end(); ++it3) { + std::string index_key = it3->first; + + if (index_key == std::string("major") && first_major) { + if (*is_outgoing) outgoing_transfer->m_account_index = it3->second.get_value(); + else incoming_transfer->m_account_index = it3->second.get_value(); + first_major = false; + } + else if (index_key == std::string("minor")) { + if (*is_outgoing) { + outgoing_transfer->m_subaddress_indices.push_back(it3->second.get_value()); + } + else if (first_minor) { + incoming_transfer->m_subaddress_index = it3->second.get_value(); + first_minor = false; + } + else throw monero_error("Expected 1 subaddress index for incoming transfer"); + } + } + } + } + else if (key == std::string("destinations") || key == std::string("recipients")) { + if (!*is_outgoing) throw monero_error("Expected outgoing transaction"); + if (outgoing_transfer == nullptr) { + outgoing_transfer = std::make_shared(); + outgoing_transfer->m_tx = tx; + } + auto node2 = it->second; + outgoing_transfer->m_destinations.clear(); + + for(auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + auto node3 = it2->second; + auto dest = std::make_shared(); + + for(auto it3 = node3.begin(); it3 != node3.end(); ++it3) { + std::string _key = it3->first; + + if (_key == std::string("address")) dest->m_address = it3->second.data(); + else if (_key == std::string("amount")) dest->m_amount = it3->second.get_value(); + else throw monero_error(std::string("Unrecognized transaction destination field: ") + _key); + } + + outgoing_transfer->m_destinations.push_back(dest); + } + } + else if (key == std::string("amount_in")) tx->m_input_sum = it->second.get_value(); + else if (key == std::string("amount_out")) tx->m_output_sum = it->second.get_value(); + else if (key == std::string("change_address") && !it->second.data().empty()) tx->m_change_address = it->second.data(); + else if (key == std::string("change_amount")) tx->m_change_amount = it->second.get_value(); + else if (key == std::string("dummy_outputs")) tx->m_num_dummy_outputs = it->second.get_value(); + else if (key == std::string("extra")) tx->m_extra_hex = it->second.data(); + else if (key == std::string("ring_size")) tx->m_ring_size = it->second.get_value(); + else if (key == std::string("spent_key_images")) { + auto node2 = it->second; + + for(auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + std::string _key = it2->first; + + if (_key == std::string("key_images")) { + auto node3 = it2->second; + if (tx->m_inputs.size() > 0) throw monero_error("inputs should be empty"); + + for(auto it3 = node3.begin(); it3 != node3.end(); ++it3) { + auto output = std::make_shared(); + auto key_image = std::make_shared(); + + key_image->m_hex = it3->second.data(); + output->m_key_image = key_image; + output->m_tx = tx; + tx->m_inputs.push_back(output); + } + } + } + } + else if (key == std::string("amounts_by_dest")) { + if (!*is_outgoing) throw monero_error("Expected outgoing transaction"); + if (outgoing_transfer == nullptr) { + outgoing_transfer = std::make_shared(); + outgoing_transfer->m_tx = tx; + } + auto node2 = it->second; + std::vector amounts_by_dest; + + for(auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + std::string _key = it2->first; + + if (_key == std::string("amounts")) { + auto node3 = it2->second; + + for(auto it3 = node3.begin(); it3 != node3.end(); ++it3) { + amounts_by_dest.push_back(it3->second.get_value()); + } + } + } + + auto destinations = config.get_normalized_destinations(); + size_t num_destinations = destinations.size(); + if (num_destinations != amounts_by_dest.size()) throw monero_error("Expected destinations size equal to amounts by dest size"); + outgoing_transfer->m_destinations.clear(); + for(uint64_t i = 0; i < num_destinations; i++) { + auto dest = std::make_shared(); + dest->m_address = destinations[i]->m_address; + dest->m_amount = amounts_by_dest[i]; + outgoing_transfer->m_destinations.push_back(dest); + } + } + } + + // link block and tx + if (header != nullptr) { + auto block = std::make_shared(); + header->copy(header, block); + block->m_txs.push_back(tx); + tx->m_block = block; + } + + if (*is_outgoing && outgoing_transfer != nullptr) { + if (tx->m_is_confirmed == boost::none) tx->m_is_confirmed = false; + if (gen_utils::bool_equals(false, outgoing_transfer->m_tx->m_is_confirmed)) tx->m_num_confirmations = 0; + tx->m_is_outgoing = true; + + if (tx->m_outgoing_transfer != nullptr) { + // overwrite to avoid reconcile error TODO: remove after >18.3.1 when amounts_by_dest supported + if (!outgoing_transfer->m_destinations.empty()) { + tx->m_outgoing_transfer->m_destinations.clear(); + } + tx->m_outgoing_transfer->merge(tx->m_outgoing_transfer, outgoing_transfer); + } + else tx->m_outgoing_transfer = outgoing_transfer; + } + else if (is_outgoing != boost::none && *is_outgoing == false && incoming_transfer != nullptr) { + if (tx->m_is_confirmed == boost::none) tx->m_is_confirmed = false; + if (gen_utils::bool_equals(false, incoming_transfer->m_tx->m_is_confirmed)) tx->m_num_confirmations = 0; + tx->m_is_incoming = true; + tx->m_incoming_transfers.push_back(incoming_transfer); + } + + } + + void deserialize_tx_with_transfer(const boost::property_tree::ptree& node, const std::shared_ptr& tx, boost::optional &is_outgoing) { + monero_tx_config config; + deserialize_tx_with_transfer(node, tx, is_outgoing, config); + } + + void deserialize_tx_with_transfer(const boost::property_tree::ptree& node, const std::shared_ptr& tx) { + boost::optional is_outgoing; + deserialize_tx_with_transfer(node, tx, is_outgoing); + } + + void deserialize_tx_with_output(const boost::property_tree::ptree& node, const std::shared_ptr& tx) { + tx->m_is_confirmed = true; + tx->m_is_relayed = true; + tx->m_is_failed = false; + tx->m_in_tx_pool = false; + + auto output = std::make_shared(); + output->m_tx = tx; + + for(auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("amount")) output->m_amount = it->second.get_value(); + else if (key == std::string("spent")) output->m_is_spent = it->second.get_value(); + else if (key == std::string("key_image") && !it->second.data().empty()) { + auto key_image = std::make_shared(); + key_image->m_hex = it->second.data(); + output->m_key_image = key_image; + } + else if (key == std::string("global_index")) output->m_index = it->second.get_value(); + else if (key == std::string("tx_hash")) tx->m_hash = it->second.data(); + else if (key == std::string("unlocked")) tx->m_is_locked = !it->second.get_value(); + else if (key == std::string("frozen")) output->m_is_frozen = it->second.get_value(); + else if (key == std::string("pubkey")) output->m_stealth_public_key = it->second.data(); + else if (key == std::string("subaddr_index")) { + for(auto indices_it = it->second.begin(); indices_it != it->second.end(); ++indices_it) { + std::string indices_key = indices_it->first; + if (indices_key == std::string("major")) output->m_account_index = indices_it->second.get_value(); + if (indices_key == std::string("minor")) output->m_subaddress_index = indices_it->second.get_value(); + } + } + else if (key == std::string("block_height")) { + auto block = std::make_shared(); + block->m_height = it->second.get_value(); + block->m_txs.push_back(tx); + tx->m_block = block; + } + } + + tx->m_outputs.push_back(output); + } + + void deserialize_tx_with_output_and_merge(const boost::property_tree::ptree& node, std::map>& tx_map, std::map>& block_map) { + for(auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("transfers")) { + for(auto rpc_output_it = it->second.begin(); rpc_output_it != it->second.end(); ++rpc_output_it) { + auto tx = std::make_shared(); + deserialize_tx_with_output(rpc_output_it->second, tx); + monero_utils::merge_tx(tx, tx_map, block_map); + } + } + } + } + + void deserialize_tx_with_transfer_and_merge(const boost::property_tree::ptree& node, std::map>& tx_map, std::map>& block_map) { + for (auto it = node.begin(); it != node.end(); ++it) { + for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) { + auto tx = std::make_shared(); + deserialize_tx_with_transfer(it2->second, tx); + + if (tx->m_is_confirmed != boost::none && *tx->m_is_confirmed == true) { + if (tx->m_block == nullptr) throw monero_error("Confirmed tx has no block"); + auto& block_txs = tx->m_block->m_txs; + if (std::find(block_txs.begin(), block_txs.end(), tx) == block_txs.end()) { + throw monero_error("Tx not found in its block"); + } + } + + // replace transfer amount with destination sum + // TODO monero-wallet-rpc: confirmed tx from/to same account has amount 0 but cached transfers + if (tx->m_outgoing_transfer != nullptr && gen_utils::bool_equals(true, tx->m_is_relayed) && !gen_utils::bool_equals(true, tx->m_is_failed) && + !tx->m_outgoing_transfer->m_destinations.empty() && tx->m_outgoing_transfer->m_amount.get() == 0) { + auto outgoing_transfer = tx->m_outgoing_transfer; + uint64_t transfer_total = 0; + for(const auto& destination : outgoing_transfer->m_destinations) { + transfer_total += destination->m_amount.get(); + } + outgoing_transfer->m_amount = transfer_total; + } + + // merge tx + monero_utils::merge_tx(tx, tx_map, block_map); + } + } + } + + void deserialize_tx_set(const boost::property_tree::ptree& node, const std::shared_ptr& set) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("multisig_txset") && !it->second.data().empty()) set->m_multisig_tx_hex = it->second.data(); + else if (key == std::string("unsigned_txset") && !it->second.data().empty()) set->m_unsigned_tx_hex = it->second.data(); + else if (key == std::string("signed_txset") && !it->second.data().empty()) set->m_signed_tx_hex = it->second.data(); + } + } + + void deserialize_tx_set(const boost::property_tree::ptree& node, const std::shared_ptr& set, const std::shared_ptr &tx, bool is_outgoing, const monero_tx_config &config) { + deserialize_tx_set(node, set); + boost::optional outgoing = is_outgoing; + deserialize_tx_with_transfer(node, tx, outgoing, config); + tx->m_tx_set = set; + set->m_txs.push_back(tx); + } + + void deserialize_sent_tx_set(const boost::property_tree::ptree& node, const std::shared_ptr& set) { + std::vector> txs; + deserialize_sent_tx_set(node, set, txs, boost::none); + } + + void deserialize_sent_tx_set(const boost::property_tree::ptree& node, const std::shared_ptr& set, std::vector> &txs, const boost::optional &conf) { + deserialize_tx_set(node, set); + int num_txs = 0; + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("fee_list") && num_txs == 0) { + auto fee_list_node = it->second; + for (auto it2 = fee_list_node.begin(); it2 != fee_list_node.end(); ++it2) { + num_txs++; + } + } + else if (key == std::string("tx_hash_list") && num_txs == 0) { + auto tx_hash_list_node = it->second; + for (auto it2 = tx_hash_list_node.begin(); it2 != tx_hash_list_node.end(); ++it2) { + num_txs++; + } + } + } + + if (num_txs == 0) { + if (txs.size() > 0) throw monero_error("txs should be empty"); + return; + } + + if (txs.size() > 0) set->m_txs = txs; + else { + for(int i = 0; i < num_txs; i++) { + auto tx = std::make_shared(); + txs.push_back(tx); + } + } + + for(const auto &tx : txs) { + tx->m_tx_set = set; + tx->m_is_outgoing = true; + } + + set->m_txs = txs; + auto tx_at = [&txs](size_t i) -> const std::shared_ptr& { + if (i >= txs.size()) throw monero_error("Malformed sent tx set response: list has more entries than expected tx count (" + std::to_string(txs.size()) + ")"); + return txs[i]; + }; + + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("tx_hash_list")) { + auto node2 = it->second; + int i = 0; + for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + auto tx = tx_at(i); + tx->m_hash = it2->second.data(); + i++; + } + } + else if (key == std::string("tx_key_list")) { + auto node2 = it->second; + int i = 0; + for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + auto tx = tx_at(i); + tx->m_key = it2->second.data(); + i++; + } + } + else if (key == std::string("tx_blob_list") || key == std::string("tx_raw_list")) { + auto node2 = it->second; + int i = 0; + for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + auto tx = tx_at(i); + tx->m_full_hex = it2->second.data(); + i++; + } + } + else if (key == std::string("tx_metadata_list")) { + auto node2 = it->second; + int i = 0; + for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + auto tx = tx_at(i); + tx->m_metadata = it2->second.data(); + i++; + } + } + else if (key == std::string("fee_list")) { + auto node2 = it->second; + int i = 0; + for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + auto tx = tx_at(i); + tx->m_fee = it2->second.get_value(); + i++; + } + } + else if (key == std::string("amount_list")) { + auto node2 = it->second; + int i = 0; + for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + auto tx = tx_at(i); + if (tx->m_outgoing_transfer == nullptr) { + auto outgoing_transfer = std::make_shared(); + outgoing_transfer->m_tx = tx; + tx->m_outgoing_transfer = outgoing_transfer; + } + tx->m_outgoing_transfer->m_amount = it2->second.get_value(); + i++; + } + } + else if (key == std::string("weight_list")) { + auto node2 = it->second; + int i = 0; + for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + auto tx = tx_at(i); + tx->m_weight = it2->second.get_value(); + i++; + } + } + else if (key == std::string("spent_key_images_list")) { + auto node2 = it->second; + int i = 0; + for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + auto tx = tx_at(i); + if (tx->m_inputs.size() > 0) throw monero_error("Expected no inputs in sent tx"); + + auto node3 = it2->second; + for (auto it3 = node3.begin(); it3 != node3.end(); ++it3) { + std::string _key = it3->first; + + if (_key == std::string("key_images")) { + auto node4 = it3->second; + + for (auto it4 = node4.begin(); it4 != node4.end(); ++it4) { + auto output = std::make_shared(); + output->m_key_image = std::make_shared(); + output->m_key_image->m_hex = it4->second.data(); + output->m_tx = tx; + tx->m_inputs.push_back(output); + } + } + } + + i++; + } + } + else if (key == std::string("amounts_by_dest_list")) { + int i = 0; + int destination_idx = 0; + + for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) { + auto tx = tx_at(i); + + for (auto it3 = it2->second.begin(); it3 != it2->second.end(); ++it3) { + std::string amounts_by_dest_key = it3->first; + + if (amounts_by_dest_key == std::string("amounts")) { + std::vector amounts_by_dest; + + for(auto it4 = it3->second.begin(); it4 != it3->second.end(); ++it4) { + amounts_by_dest.push_back(it4->second.get_value()); + } + + if (tx->m_outgoing_transfer == nullptr) { + auto outgoing_transfer = std::make_shared(); + outgoing_transfer->m_tx = tx; + tx->m_outgoing_transfer = outgoing_transfer; + } + + tx->m_outgoing_transfer->m_destinations.clear(); + + for(const auto& amount : amounts_by_dest) { + if (conf == boost::none) throw monero_error("Expected tx configuration"); + auto config = conf.get(); + if (config.get_normalized_destinations().size() == 1) { + // sweeping can create multiple withone address + auto dest = std::make_shared(); + dest->m_address = config.get_normalized_destinations()[0]->m_address; + dest->m_amount = amount; + tx->m_outgoing_transfer->m_destinations.push_back(dest); + } + else { + auto normalized_destinations = config.get_normalized_destinations(); + if (destination_idx >= normalized_destinations.size()) throw monero_error("Malformed sent tx set response: amounts_by_dest_list has more entries than configured destinations"); + auto dest = std::make_shared(); + dest->m_address = normalized_destinations[destination_idx]->m_address; + dest->m_amount = amount; + tx->m_outgoing_transfer->m_destinations.push_back(dest); + destination_idx++; + } + } + } + } + + i++; + } + } + } + } + + void deserialize_described_tx_set(const boost::property_tree::ptree& node, const std::shared_ptr& set) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("desc")) { + auto node2 = it->second; + + for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + auto tx = std::make_shared(); + boost::optional outgoing = true; + deserialize_tx_with_transfer(it2->second, tx, outgoing); + tx->m_tx_set = set; + set->m_txs.push_back(tx); + } + } + } + } + + void deserialize_submitted_tx_hashes(const boost::property_tree::ptree& node, std::vector& tx_hashes) { + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("tx_hash_list")) { + auto hashes_node = it->second; + for (auto it2 = hashes_node.begin(); it2 != hashes_node.end(); ++it2) { + tx_hashes.push_back(it2->second.data()); + } + } + } + } + + void deserialize_relayed_tx_hash(const boost::property_tree::ptree& node, std::vector& tx_hashes) { + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("tx_hash")) tx_hashes.push_back(it->second.data()); + } + } + + int deserialize_num_created_txs(const boost::property_tree::ptree& node, bool can_split) { + int num_txs = 0; + for(auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (can_split && key == std::string("fee_list")) { + auto fee_list_node = it->second; + for(auto it2 = fee_list_node.begin(); it2 != fee_list_node.end(); ++it2) { + num_txs++; + } + } + else if (!can_split && key == std::string("fee")) { + num_txs = 1; + } + } + return num_txs; + } + + std::string deserialize_tx_key(const boost::property_tree::ptree& node) { + std::string tx_key; + + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("tx_key")) { + tx_key = it->second.data(); + break; + } + } + + return tx_key; + } + + void deserialize_tx_notes(const boost::property_tree::ptree& node, std::vector& tx_notes) { + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("notes")) { + auto notes_node = it->second; + for (auto it2 = notes_node.begin(); it2 != notes_node.end(); ++it2) { + tx_notes.push_back(it2->second.data()); + } + } + } + } + + void deserialize_integrated_address(const boost::property_tree::ptree& node, monero_integrated_address& subaddress) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("integrated_address")) subaddress.m_integrated_address = it->second.data(); + else if (key == std::string("standard_address")) subaddress.m_standard_address = it->second.data(); + else if (key == std::string("payment_id")) subaddress.m_payment_id = it->second.data(); + } + } + + void deserialize_key_image(const boost::property_tree::ptree& node, const std::shared_ptr& key_image) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("key_image")) key_image->m_hex = it->second.data(); + else if (key == std::string("signature")) key_image->m_signature = it->second.data(); + } + } + + void deserialize_key_image_export_result(const boost::property_tree::ptree& node, const std::shared_ptr& result) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("offset")) result->m_offset = it->second.get_value(); + else if (key == std::string("signed_key_images")) { + auto key_images_node = it->second; + + for (auto it2 = key_images_node.begin(); it2 != key_images_node.end(); ++it2) { + auto key_image = std::make_shared(); + deserialize_key_image(it2->second, key_image); + result->m_key_images.push_back(key_image); + } + } + } + } + + void deserialize_key_image_import_result(const boost::property_tree::ptree& node, const std::shared_ptr& result) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("height")) result->m_height = it->second.get_value(); + else if (key == std::string("spent")) result->m_spent_amount = it->second.get_value(); + else if (key == std::string("unspent")) result->m_unspent_amount = it->second.get_value(); + } + } + + void deserialize_message_signature_result(const boost::property_tree::ptree& node, monero_message_signature_result& result) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("good")) result.m_is_good = it->second.get_value(); + else if (key == std::string("old")) result.m_is_old = it->second.get_value(); + else if (key == std::string("signature_type")) { + std::string sig_type = it->second.data(); + if (sig_type == std::string("view")) { + result.m_signature_type = monero_message_signature_type::SIGN_WITH_VIEW_KEY; + } + else { + result.m_signature_type = monero_message_signature_type::SIGN_WITH_SPEND_KEY; + } + } + else if (key == std::string("version")) result.m_version = it->second.get_value(); + } + } + + void deserialize_check_tx(const boost::property_tree::ptree& node, const std::shared_ptr& check) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("good")) check->m_is_good = it->second.get_value(); + if (key == std::string("in_pool")) check->m_in_tx_pool = it->second.get_value(); + else if (key == std::string("confirmations")) check->m_num_confirmations = it->second.get_value(); + else if (key == std::string("received")) check->m_received_amount = it->second.get_value(); + } + + if (!gen_utils::bool_equals(true, check->m_is_good)) { + // normalize invalid tx proof + check->m_in_tx_pool = boost::none; + check->m_num_confirmations = boost::none; + check->m_received_amount = boost::none; + } + } + + void deserialize_check_reserve(const boost::property_tree::ptree& node, const std::shared_ptr& check) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("good")) check->m_is_good = it->second.get_value(); + else if (key == std::string("total")) check->m_total_amount = it->second.get_value(); + else if (key == std::string("spent")) check->m_unconfirmed_spent_amount = it->second.get_value(); + } + + if (!gen_utils::bool_equals(true, check->m_is_good)) { + // normalize invalid check reserve + check->m_total_amount = boost::none; + check->m_unconfirmed_spent_amount = boost::none; + } + } + + void deserialize_multisig_info(const boost::property_tree::ptree& node, monero_multisig_info& info) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("multisig")) info.m_is_multisig = it->second.get_value(); + else if (key == std::string("ready")) info.m_is_ready = it->second.get_value(); + else if (key == std::string("threshold")) info.m_threshold = it->second.get_value(); + else if (key == std::string("total")) info.m_num_participants = it->second.get_value(); + } + } + + void deserialize_multisig_init_result(const boost::property_tree::ptree& node, monero_multisig_init_result& info) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("address")) info.m_address = it->second.data(); + else if (key == std::string("multisig_info")) info.m_multisig_hex = it->second.data(); + } + } + + void deserialize_multisig_sign_result(const boost::property_tree::ptree& node, monero_multisig_sign_result& res) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("tx_data_hex")) res.m_signed_multisig_tx_hex = it->second.data(); + else if (key == std::string("tx_hash_list")) { + auto node2 = it->second; + for (boost::property_tree::ptree::const_iterator it2 = node2.begin(); it2 != node2.end(); ++it2) { + res.m_tx_hashes.push_back(it2->second.data()); + } + } + } + } + + void deserialize_address_book_entry(const boost::property_tree::ptree& node, const std::shared_ptr& entry) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("index")) entry->m_index = it->second.get_value(); + else if (key == std::string("address")) entry->m_address = it->second.data(); + else if (key == std::string("description")) entry->m_description = it->second.data(); + else if (key == std::string("payment_id")) entry->m_payment_id = it->second.data(); + } + } + + void deserialize_address_book_entries(const boost::property_tree::ptree& node, std::vector& entries) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("entries")) { + auto entries_node = it->second; + for (auto it2 = entries_node.begin(); it2 != entries_node.end(); ++it2) { + auto entry = std::make_shared(); + deserialize_address_book_entry(it2->second, entry); + entries.push_back(*entry); + } + } + } + } + + void deserialize_seed_languages(const boost::property_tree::ptree& node, std::vector& languages) { + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("languages")) { + auto languages_node = it->second; + for (auto it2 = languages_node.begin(); it2 != languages_node.end(); ++it2) { + languages.push_back(it2->second.data()); + } + } + } + } + + uint64_t deserialize_address_book_index(const boost::property_tree::ptree& node) { + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("index")) { + return it->second.get_value(); + } + } + + throw monero_error("Could not deserialize address book entry index"); + } + + void deserialize_account_tag(const boost::property_tree::ptree& node, const std::shared_ptr& account_tag) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("tag")) account_tag->m_tag = it->second.data(); + else if (key == std::string("label") && !it->second.data().empty()) account_tag->m_label = it->second.data(); + else if (key == std::string("accounts")) { + for (boost::property_tree::ptree::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) { + account_tag->m_account_indices.push_back(it2->second.get_value()); + } + } + } + } + + void deserialize_account_tags(const boost::property_tree::ptree& node, std::vector>& account_tags) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("account_tags")) { + for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) { + auto account_tag = std::make_shared(); + deserialize_account_tag(it2->second, account_tag); + account_tags.push_back(account_tag); + } + } + } + } + + std::string deserialize_signature(const boost::property_tree::ptree& node) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("signature")) return it->second.data(); + } + + throw monero_error("Invalid reserve proof response"); + } + + std::string deserialize_exported_outputs(const boost::property_tree::ptree& node) { + std::string exported_outputs_hex; + + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("outputs_data_hex")) exported_outputs_hex = it->second.data(); + } + + return exported_outputs_hex; + } + + int deserialize_num_imported_outputs(const boost::property_tree::ptree& node) { + int num_imported = 0; + + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("num_imported")) { + num_imported = it->second.get_value(); + break; + } + } + + return num_imported; + } + + bool deserialize_frozen_output_info(const boost::property_tree::ptree& node) { + bool is_frozen = false; + + for(auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("frozen")) { + is_frozen = it->second.get_value(); + } + } + + return is_frozen; + } + + monero_tx_priority deserialize_tx_priority(const boost::property_tree::ptree& node) { + for(auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("priority")) { + int priority = it->second.get_value(); + if (priority == 0) return monero_tx_priority::DEFAULT; + else if (priority == 1) return monero_tx_priority::UNIMPORTANT; + else if (priority == 2) return monero_tx_priority::NORMAL; + else if (priority == 3) return monero_tx_priority::ELEVATED; + } + } + + throw monero_error("Could not get default fee priority"); + } + + uint64_t deserialize_block_height(const boost::property_tree::ptree& node) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("height")) return it->second.get_value(); + } + throw monero_error("Invalid get_height response"); + } + + std::string deserialize_payment_uri(const boost::property_tree::ptree& node) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("uri")) return it->second.data(); + } + throw monero_error("Could not deserialize payment uri"); + } + + void deserialize_payment_uri(const boost::property_tree::ptree& node, const std::shared_ptr& tx_config) { + std::shared_ptr destination = std::make_shared(); + + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("uri")) { + deserialize_payment_uri(it->second, tx_config); + return; + } + if (key == std::string("address") && !it->second.data().empty()) destination->m_address = it->second.data(); + else if (key == std::string("amount")) destination->m_amount = it->second.get_value(); + else if (key == std::string("payment_id") && !it->second.data().empty()) tx_config->m_payment_id = it->second.data(); + else if (key == std::string("recipient_name") && !it->second.data().empty()) tx_config->m_recipient_name = it->second.data(); + else if (key == std::string("tx_description") && !it->second.data().empty()) tx_config->m_note = it->second.data(); + } + + if (destination->m_address != boost::none || destination->m_amount != boost::none) tx_config->m_destinations.push_back(destination); + } + + void deserialize_sync_result(const boost::property_tree::ptree& node, monero_sync_result& sync_result) { + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("blocks_fetched")) sync_result.m_num_blocks_fetched = it->second.get_value(); + else if (key == std::string("received_money")) sync_result.m_received_money = it->second.get_value(); + } + } +} diff --git a/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_rpc_model.h b/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_rpc_model.h new file mode 100644 index 0000000..e17221b --- /dev/null +++ b/apps/ios/Koins/Monero/include/monero-cpp-src/wallet/monero_wallet_rpc_model.h @@ -0,0 +1,500 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ +#pragma once + +#include "daemon/monero_daemon_rpc_model.h" +#include "monero_wallet_model.h" + +/** + * Internal data model for monero_wallet_rpc. + */ +namespace monero { + + /** + * key-value struct which can be serialized. + */ + struct key_value : public serializable_struct { + boost::optional m_key; + boost::optional m_value; + + key_value() { } + key_value(const std::string& key): m_key(key) { } + key_value(const std::string& key, const std::string& value): m_key(key), m_value(value) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& attributes); + }; + + /** + * Models a RPC Monero key image. + */ + struct monero_rpc_key_image : public monero_key_image { + monero_rpc_key_image(const std::shared_ptr &key_image); + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + // ------------------------------ JSON-RPC Params --------------------------------- + + struct monero_query_key_params : public serializable_struct { + boost::optional m_key_type; + + monero_query_key_params(const std::string& key_type): m_key_type(key_type) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_query_output_params : public serializable_struct { + boost::optional m_key_image; + + monero_query_output_params(const std::string& key_image): m_key_image(key_image) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_get_address_params : public serializable_struct { + boost::optional m_address; + boost::optional m_account_index; + std::vector m_subaddress_indices; + + monero_get_address_params(const std::string& address): m_address(address) { } + monero_get_address_params(uint32_t account_index): m_account_index(account_index) { } + monero_get_address_params(uint32_t account_index, const std::vector& subaddress_indices): m_account_index(account_index), m_subaddress_indices(subaddress_indices) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_integrated_address_params : public serializable_struct { + boost::optional m_integrated_address; + boost::optional m_standard_address; + boost::optional m_payment_id; + + monero_integrated_address_params(const std::string& integrated_address): m_integrated_address(integrated_address) {} + monero_integrated_address_params(const std::string& standard_address, const std::string& payment_id): m_standard_address(standard_address), m_payment_id(payment_id) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_multisig_params : public serializable_struct { + // TODO monero-docs document this parameter + boost::optional m_enable_multisig_experimental; + boost::optional m_refresh_after_import; + std::vector m_multisig_info; + std::vector m_multisig_hexes; + boost::optional m_multisig_tx_hex; + boost::optional m_threshold; + boost::optional m_password; + + monero_multisig_params(bool enable_multisig_experimental = true): m_enable_multisig_experimental(enable_multisig_experimental) { } + monero_multisig_params(const std::vector& multisig_hexes, const std::string& password): m_multisig_info(multisig_hexes), m_password(password) { } + monero_multisig_params(const std::vector& multisig_hexes, const char* password): monero_multisig_params(multisig_hexes, std::string(password)) { } + monero_multisig_params(const std::vector& multisig_hexes, int threshold, const std::string& password): m_multisig_info(multisig_hexes), m_threshold(threshold), m_password(password) { } + monero_multisig_params(const std::vector& multisig_hexes, const bool refresh_after_import): m_multisig_hexes(multisig_hexes), m_refresh_after_import(refresh_after_import) { } + monero_multisig_params(const std::string& multisig_tx_hex): m_multisig_tx_hex(multisig_tx_hex) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_payment_uri_params : public serializable_struct { + boost::optional m_uri; + boost::optional m_address; + boost::optional m_amount; + boost::optional m_payment_id; + boost::optional m_recipient_name; + boost::optional m_tx_description; + + monero_payment_uri_params() { } + monero_payment_uri_params(const std::string& uri): m_uri(uri) { } + monero_payment_uri_params(const monero_tx_config& config); + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_get_balance_params : public serializable_struct { + boost::optional m_account_idx; + std::vector m_address_indices; + boost::optional m_all_accounts; + boost::optional m_strict; + + monero_get_balance_params(bool all_accounts, bool strict = false): m_all_accounts(all_accounts), m_strict(strict) { } + monero_get_balance_params(uint32_t account_idx, const std::vector& address_indices, bool all_accounts = false, bool strict = false): m_account_idx(account_idx), m_address_indices(address_indices), m_all_accounts(all_accounts), m_strict(strict) { } + monero_get_balance_params(uint32_t account_idx, boost::optional address_idx, bool all_accounts = false, bool strict = false); + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_close_wallet_params : public serializable_struct { + boost::optional m_save; + + monero_close_wallet_params(bool save = false): m_save(save) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_change_wallet_password_params : public serializable_struct { + boost::optional m_old_password; + boost::optional m_new_password; + + monero_change_wallet_password_params(const std::string& old_password, const std::string& new_password): m_old_password(old_password), m_new_password(new_password) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_set_daemon_params : public serializable_struct { + boost::optional m_address; + boost::optional m_username; + boost::optional m_password; + boost::optional m_proxy; + boost::optional m_trusted; + boost::optional m_ssl_support; + boost::optional m_ssl_options; + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_account_tag_params : public serializable_struct { + std::vector m_account_indices; + boost::optional m_tag; + boost::optional m_label; + boost::optional m_description; + + monero_account_tag_params() {} + monero_account_tag_params(const std::string& tag, const std::string& description = ""): m_tag(tag), m_description(description) { } + monero_account_tag_params(const std::vector& account_indices): m_account_indices(account_indices) { } + monero_account_tag_params(const std::string& tag, const std::vector& account_indices): m_tag(tag), m_account_indices(account_indices) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_tx_notes_params : public serializable_struct { + std::vector m_tx_hashes; + std::vector m_notes; + + monero_tx_notes_params(const std::vector& tx_hashes): m_tx_hashes(tx_hashes) { } + monero_tx_notes_params(const std::vector& tx_hashes, const std::vector& notes): m_tx_hashes(tx_hashes), m_notes(notes) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_address_book_entry_params : public serializable_struct { + boost::optional m_index; // TODO: not boost::optional + boost::optional m_set_address; + boost::optional m_address; + boost::optional m_set_description; + boost::optional m_description; + std::vector m_entries; + + monero_address_book_entry_params(uint64_t index): m_index(index) { } + monero_address_book_entry_params(const std::vector& entries): m_entries(entries) { } + monero_address_book_entry_params(uint64_t index, bool set_address, const std::string& address, bool set_description, const std::string& description): m_index(index), m_set_address(set_address), m_address(address), m_set_description(set_description), m_description(description) { } + monero_address_book_entry_params(const std::string& address, const std::string& description): m_address(address), m_description(description) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_verify_sign_message_params : public serializable_struct { + boost::optional m_data; + boost::optional m_address; + boost::optional m_signature; + boost::optional m_signature_type; + boost::optional m_account_index; + boost::optional m_address_index; + + monero_verify_sign_message_params(const std::string &data, const std::string &address, const std::string& signature): m_data(data), m_address(address), m_signature(signature) { } + monero_verify_sign_message_params(const std::string &data, monero_message_signature_type signature_type, uint32_t account_index, uint32_t address_index): m_data(data), m_signature_type(signature_type), m_account_index(account_index), m_address_index(address_index) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_check_tx_key_params : public serializable_struct { + boost::optional m_tx_hash; + boost::optional m_address; + boost::optional m_tx_key; + + monero_check_tx_key_params(const std::string &tx_hash): m_tx_hash(tx_hash) { } + monero_check_tx_key_params(const std::string &tx_hash, const std::string &tx_key, const std::string &address): m_tx_hash(tx_hash), m_tx_key(tx_key), m_address(address) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_sign_describe_transfer_params : public serializable_struct { + boost::optional m_unsigned_txset; + boost::optional m_multisig_txset; + boost::optional m_export_raw; + boost::optional m_get_tx_keys; + + monero_sign_describe_transfer_params() { } + monero_sign_describe_transfer_params(const std::string &unsigned_txset) : m_unsigned_txset(unsigned_txset) { } + monero_sign_describe_transfer_params(const std::string &unsigned_txset, const std::string &multisig_txset) : m_unsigned_txset(unsigned_txset), m_multisig_txset(multisig_txset) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_wallet_relay_tx_params : public serializable_struct { + boost::optional m_hex; + boost::optional m_signed_tx_hex; + + monero_wallet_relay_tx_params() {} + monero_wallet_relay_tx_params(const std::string &hex): m_hex(hex) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_sweep_params : public serializable_struct { + boost::optional m_address; + boost::optional m_account_index; + std::vector m_subaddr_indices; + boost::optional m_key_image; + boost::optional m_relay; + boost::optional m_priority; + boost::optional m_payment_id; + boost::optional m_below_amount; + boost::optional m_get_tx_key; + boost::optional m_get_tx_keys; + boost::optional m_get_tx_hex; + boost::optional m_get_tx_metadata; + + monero_sweep_params(bool relay = false): m_relay(relay) { } + monero_sweep_params(const monero_tx_config& config); + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_create_edit_subaddress_params : public serializable_struct { + boost::optional m_label; + boost::optional m_account_index; + boost::optional m_subaddress_index; + + monero_create_edit_subaddress_params(uint32_t account_idx, const std::string& label): m_account_index(account_idx), m_label(label) { } + monero_create_edit_subaddress_params(uint32_t account_idx, uint32_t subaddress_idx, const std::string& label): m_account_index(account_idx), m_subaddress_index(subaddress_idx), m_label(label) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_wallet_data_params : public serializable_struct { + boost::optional m_all; + std::vector> m_key_images; + boost::optional m_offset; + boost::optional m_outputs_hex; + + monero_wallet_data_params(const std::vector> &key_images, uint64_t offset): m_key_images(key_images), m_offset(offset) { }; + monero_wallet_data_params(bool all): m_all(all) { } + monero_wallet_data_params(const std::string& outputs_hex): m_outputs_hex(outputs_hex) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_create_open_wallet_params : public serializable_struct { + boost::optional m_filename; + boost::optional m_password; + boost::optional m_language; + boost::optional m_seed; + boost::optional m_seed_offset; + boost::optional m_restore_height; + boost::optional m_autosave_current; + boost::optional m_enable_multisig_experimental; + boost::optional m_address; + boost::optional m_view_key; + boost::optional m_spend_key; + + monero_create_open_wallet_params(const boost::optional& filename, const boost::optional &password): m_filename(filename), m_password(password), m_autosave_current(false) { } + monero_create_open_wallet_params(const boost::optional& filename, const boost::optional &password, const boost::optional &language): m_filename(filename), m_password(password), m_language(language), m_autosave_current(false) { } + monero_create_open_wallet_params(const boost::optional& filename, const boost::optional &password, const boost::optional &seed, const boost::optional &seed_offset, const boost::optional &restore_height, const boost::optional &language, const boost::optional &autosave_current, const boost::optional &enable_multisig_experimental): m_filename(filename), m_password(password), m_seed(seed), m_seed_offset(seed_offset), m_restore_height(restore_height), m_language(language), m_autosave_current(autosave_current), m_enable_multisig_experimental(enable_multisig_experimental) { } + monero_create_open_wallet_params(const boost::optional& filename, const boost::optional &password, const boost::optional &address, const boost::optional &view_key, const boost::optional &spend_key, const boost::optional &restore_height, const boost::optional &autosave_current): m_filename(filename), m_password(password), m_address(address), m_view_key(view_key), m_spend_key(spend_key), m_restore_height(restore_height), m_autosave_current(autosave_current) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_reserve_proof_params : public serializable_struct { + boost::optional m_all; + boost::optional m_message; + boost::optional m_tx_hash; + boost::optional m_account_index; + boost::optional m_amount; + boost::optional m_address; + boost::optional m_signature; + + monero_reserve_proof_params(const std::string &message, bool all = true): m_all(all), m_message(message) { } + monero_reserve_proof_params(const std::string &address, const std::string &message, const std::string &signature): m_address(address), m_message(message), m_signature(signature) { } + monero_reserve_proof_params(const std::string &tx_hash, const std::string &address, const std::string &message, const std::string &signature): m_tx_hash(tx_hash), m_address(address), m_message(message), m_signature(signature) { } + monero_reserve_proof_params(const std::string &tx_hash, const std::string &message): m_tx_hash(tx_hash), m_message(message) { } + monero_reserve_proof_params(uint32_t account_index, uint64_t amount, const std::string &message): m_account_index(account_index), m_amount(amount), m_message(message) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_wallet_refresh_params : public serializable_struct { + boost::optional m_enable; + boost::optional m_period; + boost::optional m_start_height; + + monero_wallet_refresh_params() { } + monero_wallet_refresh_params(bool enable): m_enable(enable) { } + monero_wallet_refresh_params(bool enable, uint64_t period): m_enable(enable), m_period(period) { } + monero_wallet_refresh_params(uint64_t start_height): m_start_height(start_height) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_transfer_params : public serializable_struct { + std::vector m_subtract_fee_from_outputs; + boost::optional m_account_index; + std::vector m_subaddress_indices; + boost::optional m_payment_id; + boost::optional m_do_not_relay; + boost::optional m_priority; + boost::optional m_get_tx_hex; + boost::optional m_get_tx_metadata; + boost::optional m_get_tx_keys; + boost::optional m_get_tx_key; + std::vector> m_destinations; + + monero_transfer_params(const monero_tx_config &config); + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_get_transfers_params : public serializable_struct { + boost::optional m_in; + boost::optional m_out; + boost::optional m_pool; + boost::optional m_pending; + boost::optional m_failed; + boost::optional m_min_height; + boost::optional m_max_height; + boost::optional m_all_accounts; + boost::optional m_account_index; + std::vector m_subaddr_indices; + + bool filter_by_height() const { return m_min_height != boost::none || m_max_height != boost::none; } + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + struct monero_get_incoming_transfers_params : public serializable_struct { + boost::optional m_transfer_type; + boost::optional m_verbose; + boost::optional m_account_index; + std::vector m_subaddr_indices; + + monero_get_incoming_transfers_params(const std::string& transfer_type, bool verbose = true): m_transfer_type(transfer_type), m_verbose(verbose) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + }; + + // ------------------------------ JSON-RPC Response --------------------------------- + + struct monero_multisig_response { + boost::optional m_num_outputs; + boost::optional m_multisig_info; + std::vector m_tx_hashes; + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& response); + }; + + struct monero_get_balance_response { + boost::optional m_balance; + boost::optional m_unlocked_balance; + boost::optional m_multisig_import_needed; + boost::optional m_time_to_unlock; + boost::optional m_blocks_to_unlock; + std::vector> m_per_subaddress; + + monero_get_balance_response(): m_balance(0), m_unlocked_balance(0) { } + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& response); + }; + + // ------------------------------ RPC Deserialization --------------------------------- + + void deserialize_subaddress(const boost::property_tree::ptree& node, const std::shared_ptr& subaddress); + void deserialize_subaddresses(const boost::property_tree::ptree& node, std::vector>& subaddresses); + void deserialize_account(const boost::property_tree::ptree& node, monero_account& account); + void deserialize_accounts(const boost::property_tree::ptree& node, std::vector& accounts); + void init_sent_tx(const monero_tx_config &config, std::shared_ptr &tx, bool copy_destinations); + void deserialize_tx_with_transfer_and_merge(const boost::property_tree::ptree& node, std::map>& tx_map, std::map>& block_map); + void deserialize_tx_with_output_and_merge(const boost::property_tree::ptree& node, std::map>& tx_map, std::map>& block_map); + void deserialize_tx_set(const boost::property_tree::ptree& node, const std::shared_ptr& set); + void deserialize_tx_set(const boost::property_tree::ptree& node, const std::shared_ptr& set, const std::shared_ptr &tx, bool is_outgoing, const monero_tx_config &config); + void deserialize_sent_tx_set(const boost::property_tree::ptree& node, const std::shared_ptr& set); + void deserialize_sent_tx_set(const boost::property_tree::ptree& node, const std::shared_ptr& set, std::vector> &txs, const boost::optional &conf); + void deserialize_described_tx_set(const boost::property_tree::ptree& node, const std::shared_ptr& set); + void deserialize_submitted_tx_hashes(const boost::property_tree::ptree& node, std::vector& tx_hashes); + void deserialize_relayed_tx_hash(const boost::property_tree::ptree& node, std::vector& tx_hashes); + int deserialize_num_created_txs(const boost::property_tree::ptree& node, bool can_split); + std::string deserialize_tx_key(const boost::property_tree::ptree& node); + void deserialize_tx_notes(const boost::property_tree::ptree& node, std::vector& tx_notes); + void deserialize_integrated_address(const boost::property_tree::ptree& node, monero_integrated_address& subaddress); + void deserialize_key_image_export_result(const boost::property_tree::ptree& node, const std::shared_ptr& result); + void deserialize_key_image_import_result(const boost::property_tree::ptree& node, const std::shared_ptr& result); + void deserialize_message_signature_result(const boost::property_tree::ptree& node, monero_message_signature_result& result); + void deserialize_check_tx(const boost::property_tree::ptree& node, const std::shared_ptr& check); + void deserialize_check_reserve(const boost::property_tree::ptree& node, const std::shared_ptr& check); + void deserialize_multisig_info(const boost::property_tree::ptree& node, monero_multisig_info& info); + void deserialize_multisig_init_result(const boost::property_tree::ptree& node, monero_multisig_init_result& res); + void deserialize_multisig_sign_result(const boost::property_tree::ptree& node, monero_multisig_sign_result& res); + void deserialize_address_book_entries(const boost::property_tree::ptree& node, std::vector& entries); + void deserialize_seed_languages(const boost::property_tree::ptree& node, std::vector& languages); + uint64_t deserialize_address_book_index(const boost::property_tree::ptree& node); + void deserialize_account_tags(const boost::property_tree::ptree& node, std::vector>& account_tags); + std::string deserialize_signature(const boost::property_tree::ptree& node); + std::string deserialize_exported_outputs(const boost::property_tree::ptree& node); + uint64_t deserialize_block_height(const boost::property_tree::ptree& node); + int deserialize_num_imported_outputs(const boost::property_tree::ptree& node); + bool deserialize_frozen_output_info(const boost::property_tree::ptree& node); + monero_tx_priority deserialize_tx_priority(const boost::property_tree::ptree& node); + std::string deserialize_payment_uri(const boost::property_tree::ptree& node); + void deserialize_payment_uri(const boost::property_tree::ptree& node, const std::shared_ptr& tx_config); + void deserialize_sync_result(const boost::property_tree::ptree& node, monero_sync_result& sync_result); + +} diff --git a/apps/ios/Koins/Monero/libs/libkoins_monero_merged.a b/apps/ios/Koins/Monero/libs/libkoins_monero_merged.a new file mode 100644 index 0000000..2a0085d Binary files /dev/null and b/apps/ios/Koins/Monero/libs/libkoins_monero_merged.a differ diff --git a/apps/ios/Koins/SelfTest.swift b/apps/ios/Koins/SelfTest.swift new file mode 100644 index 0000000..3a4660c --- /dev/null +++ b/apps/ios/Koins/SelfTest.swift @@ -0,0 +1,290 @@ +import Foundation +import os + +/// On-device verification against vectors generated from the reference +/// implementation (xrpl.js + ripple-keypairs + ripple-binary-codec). Runs only +/// when the app is launched with `-koinsSelfTest`. +enum SelfTest { + private static let log = OSLog(subsystem: "com.koins.app", category: "selftest") + + /// Writes to both stdout and the unified log so results are visible however + /// the app was launched. + private static func emit(_ line: String) { + print(line) + os_log("%{public}@", log: log, type: .default, line) + } + + static func runIfRequested() { + if ProcessInfo.processInfo.arguments.contains("-koinsSelfTest") { + print("KOINS_SELFTEST begin") + run() + print("KOINS_SELFTEST end") + } + if ProcessInfo.processInfo.arguments.contains("-koinsSelfTestNetwork") { + runNetwork() + } + } + + /// End-to-end check of the live network paths (XRP RPC + native Monero + /// wallet). Triggered with `-koinsSelfTestNetwork`; cleans up after itself. + static func runNetwork() { + // Detached so the test does not inherit the main actor: the UI thread must + // stay free, otherwise the launch-time call would deadlock. + Task.detached(priority: .userInitiated) { + emit("KOINS_NET begin") + + // The test creates temporary wallets, so snapshot the real ones and put + // them back afterwards instead of wiping them. + let backup = (xrp: WalletStore.wallet(.xrp), + xmr: WalletStore.wallet(.xmr), + xrpSecret: WalletStore.secret(.xrp), + xmrSecret: WalletStore.secret(.xmr)) + + func restoreWallets() { + if let xrp = backup.xrp { WalletStore.saveWallet(xrp, chain: .xrp) } else { WalletStore.removeWallet(.xrp) } + if let secret = backup.xrpSecret { WalletStore.saveSecret(secret, chain: .xrp) } + if let xmr = backup.xmr { WalletStore.saveWallet(xmr, chain: .xmr) } else { WalletStore.removeWallet(.xmr) } + if let secret = backup.xmrSecret { WalletStore.saveSecret(secret, chain: .xmr) } + emit("KOINS_NET restored saved wallets") + } + + do { + let (record, phrase) = try XRPWalletService.shared.createWallet(name: "koins-net-selftest") + emit("KOINS_NET xrp address \(record.address) phraseWords \(phrase.split(separator: " ").count)") + let balance = try await XRPWalletService.shared.balance(address: record.address) + emit("KOINS_NET xrp funded=\(balance.funded) balance=\(balance.total)") + let history = try await XRPWalletService.shared.transactions(address: record.address) + emit("KOINS_NET xrp transactions=\(history.count)") + + // A known funded account, to exercise the funded-balance path. + for reference in ["rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "rHR6xtDW7BxeQN8VB8R9HzXzyMeS42n7C"] { + let reference = try await XRPWalletService.shared.balance(address: reference) + emit("KOINS_NET xrp reference funded=\(reference.funded) balance=\(reference.total)") + let referenceHistory = try await XRPWalletService.shared.transactions(address: "rHR6xtDW7BxeQN8VB8R9HzXzyMeS42n7C") + emit("KOINS_NET xrp reference transactions=\(referenceHistory.count)") + } + } catch { + emit("KOINS_NET xrp FAIL \(error)") + } + + do { + let created = try await MoneroService.shared.createWallet(name: "koins-net-selftest") + let words = created.mnemonic.split(separator: " ").count + emit("KOINS_NET xmr address \(created.address) mnemonicWords \(words)") + let started = try await MoneroService.shared.startSync() + emit("KOINS_NET xmr startedSync \(started)") + for attempt in 1...6 { + try? await Task.sleep(nanoseconds: 5_000_000_000) + let status = try await MoneroService.shared.syncStatus() + emit("KOINS_NET xmr status \(attempt) height=\(status.height) daemon=\(status.daemonHeight) connected=\(status.connected) syncing=\(status.syncing)") + } + let balance = try await MoneroService.shared.balance() + emit("KOINS_NET xmr balance \(balance.total) unlocked \(balance.unlocked)") + await MoneroService.shared.stopSync() + await MoneroService.shared.removeWallet(name: "koins-net-selftest") + } catch { + emit("KOINS_NET xmr FAIL \(error)") + } + + restoreWallets() + emit("KOINS_NET end") + } + } + + static func run() { + var failures = 0 + + func expect(_ name: String, _ actual: String, _ wanted: String) { + let ok = actual.lowercased() == wanted.lowercased() + if !ok { failures += 1 } + let line = "KOINS_SELFTEST \(name): \(ok ? "PASS" : "FAIL") \(actual)" + print(line) + os_log("%{public}@", log: log, type: .default, line) + } + + expect("ripemd160", Hex.encode(RIPEMD160.hash(Array("abc".utf8))), + "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc") + + // ed25519 vector + let edSeed = "sEdSPx8CLbFUna3DJdvJ74aQPLdemqu" + let edAddress = "rEhh6f9rj5UUBhFzGGaxS5zYU2CCqKFXBC" + let edPublicKey = "EDF9096653463E3A0B79A67E69D9E66AB83D93D4E867335ADE5DAFE8467F4F1734" + if let keypair = try? XRPKey.keypair(fromSeed: edSeed) { + expect("ed25519 pubkey", Hex.encode(keypair.publicKey), edPublicKey) + expect("ed25519 address", keypair.address, edAddress) + } else { + failures += 1 + print("KOINS_SELFTEST ed25519: FAIL keypair threw") + } + + // secp256k1 vector: exercises the OpenSSL-backed derivation on device. + let secpSeed = "spFbtYoPd7Hg7AD9XZn7CBwUKC5Qe" + let secpPublicKey = "0322DC9BB14903F71186BC46B0107D55A069D23CD97BBD205491A992EC68FA0ABE" + let secpAddress = "rpsRYc8DbXzfVN32w3hZjUtyuF1K89hu47" + do { + let keypair = try XRPKey.keypair(fromSeed: secpSeed) + expect("secp256k1 algorithm", keypair.algorithm.rawValue, "secp256k1") + expect("secp256k1 pubkey", Hex.encode(keypair.publicKey), secpPublicKey) + expect("secp256k1 address", keypair.address, secpAddress) + } catch { + failures += 1 + print("KOINS_SELFTEST secp256k1: FAIL \(error)") + } + + // Serialization + hashing reference vector. + let signingHex = "53545800120000220000000024000030392E00000309201B05E69F3B61400000000016E36068400000000000000C7321EDF9096653463E3A0B79A67E69D9E66AB83D93D4E867335ADE5DAFE8467F4F173481149A26172134BFDA9708A73305AF2776A26DB7B4F5831492891F44F454D156327AA24F4C8BB72540E31BF1" + let referenceSignature = "F560672EA3103D2E43179302CD66ACF563A4EDFBA90ADE24E378B743135FD989EE132FA4B703021C2DD1979E770A6CEBC99556EA330AF9E6E7186673F837CB0D" + let expectedBlob = "120000220000000024000030392E00000309201B05E69F3B61400000000016E36068400000000000000C7321EDF9096653463E3A0B79A67E69D9E66AB83D93D4E867335ADE5DAFE8467F4F17347440F560672EA3103D2E43179302CD66ACF563A4EDFBA90ADE24E378B743135FD989EE132FA4B703021C2DD1979E770A6CEBC99556EA330AF9E6E7186673F837CB0D81149A26172134BFDA9708A73305AF2776A26DB7B4F5831492891F44F454D156327AA24F4C8BB72540E31BF1" + let expectedHash = "F853B5391CD6317EA370830EEA3C047AC8D21E9FA00E4116D94C48E360B0434F" + + var payment = XRPPayment(account: edAddress, + destination: "rNMovRR3WPbFLVaSbETCCR71XsqyxhJ9P6", + amountDrops: 1_500_000, + feeDrops: 12, + sequence: 12345, + lastLedgerSequence: 99000123, + destinationTag: 777, + signingPublicKey: Hex.decode(edPublicKey) ?? [], + signature: Hex.decode(referenceSignature)) + if let payload = try? payment.signingPayload() { + expect("signing payload", Hex.encode(payload), signingHex) + } else { + failures += 1 + } + if let blob = try? payment.txBlob() { + expect("tx blob", Hex.encode(blob), expectedBlob) + if let hash = try? payment.transactionHash() { + expect("tx hash", Hex.encode(hash), expectedHash) + } else { + failures += 1 + } + } else { + failures += 1 + } + + // BIP-39 / BIP-32 (m/44'/144'/0'/0/0): the recovery phrase used for new XRP + // wallets. Vectors come from @scure/bip39 + @scure/bip32 + ripple-keypairs. + let zeroMnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" + expect("bip39 encode", BIP39.mnemonic(from: [UInt8](repeating: 0, count: 16)), zeroMnemonic) + expect("bip39 entropy", Hex.encode(BIP39.entropy(from: zeroMnemonic) ?? []), String(repeating: "00", count: 16)) + expect("bip39 rejects bad checksum", + BIP39.entropy(from: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon") == nil ? "rejected" : "accepted", + "rejected") + + // BIP-32 intermediate values, to isolate derivation problems. + if let master = BIP32.masterKey(fromSeed: BIP39.seed(from: zeroMnemonic)) { + expect("bip32 master key", Hex.encode(master.privateKey), + "1837c1be8e2995ec11cda2b066151be2cfb48adf9e47b151d46adab3a21cdf67") + expect("bip32 master chain code", Hex.encode(master.chainCode), + "7923408dadd3c7b56eed15567707ae5e5dca089de972e07f3b860450e2a3b70e") + let H = BIP32.hardenedBit + for (level, path, expected) in [ + (1, [H | 44], "cd46049fb82a4fccbd967c5234e61152f4df87d01d1ef833420814c67ee4b7ea"), + (2, [H | 44, H | 144], "a12250f1f38fb41b62f0f1a4ec9d3555d24788843cb30e6d98a777fe540adc1d"), + (3, [H | 44, H | 144, H | 0], "54bac451906ecd52648a49126a2606c9177761f14d31dda079c0f768ac3501fd"), + (4, [H | 44, H | 144, H | 0, 0], "e582ce1724e93eebe3f38f1d42c01620b66d136946fec450dfe71b62e65de54f"), + (5, BIP32.xrpPath, "90802a50aa84efb6cdb225f17c27616ea94048c179142fecf03f4712a07ea7a4"), + ] { + if let child = BIP32.derive(path, from: master) { + expect("bip32 level \(level)", Hex.encode(child.privateKey), expected) + } else { + failures += 1 + print("KOINS_SELFTEST bip32 level \(level): FAIL derivation returned nil") + } + } + } else { + failures += 1 + print("KOINS_SELFTEST bip32 master: FAIL") + } + + let mnemonicCases: [(String, String, String)] = [ + (zeroMnemonic, + "031d68bc1a142e6766b2bdfb006ccfe135ef2e0e2e94abb5cf5c9ab6104776fbae", + "rHsMGQEkVNJmpGWs8XUBoTBiAAbwxZN5v3"), + ("legal winner thank year wave sausage worth useful legal winner thank yellow", + "0283e0926eb0ab7bb8f5c0b5637d9e2526bdc398763af79cd904f013178961692d", + "rpaLPza5oxhJ7V8nFDwPm9F7kmZ16uc8do"), + ] + for (position, vector) in mnemonicCases.enumerated() { + do { + let keypair = try XRPKey.keypair(fromMnemonic: vector.0) + expect("bip44 pubkey #\(position)", Hex.encode(keypair.publicKey), vector.1) + expect("bip44 address #\(position)", keypair.address, vector.2) + } catch { + failures += 1 + print("KOINS_SELFTEST bip44 #\(position): FAIL \(error)") + } + } + + // secp256k1 signing must produce a low-S DER signature. + if let keypair = try? XRPKey.keypair(fromSeed: secpSeed), + let message = Hex.decode(signingHex), + let signature = try? XRPKey.sign(message: message, keypair: keypair) { + let isDer = signature.count > 8 && signature[0] == 0x30 + expect("secp256k1 signature is DER", isDer ? "yes" : "no", "yes") + if let order = BigNumber.fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"), + let half = order.dividedByTwo(), + let s = DER.signatureScalar(signature) { + expect("secp256k1 signature is low-S", s.lessThanOrEqual(half) ? "yes" : "no", "yes") + } + print("KOINS_SELFTEST secp256k1 signature len: \(signature.count)") + } else { + failures += 1 + print("KOINS_SELFTEST secp256k1 sign: FAIL") + } + + let summary = "KOINS_SELFTEST summary: \(failures == 0 ? "ALL PASS" : "\(failures) FAILURE(S)")" + print(summary) + os_log("%{public}@", log: log, type: .default, summary) + } +} + +/// Minimal big-endian integer helper used only for the low-S check. +struct BigNumber { + let bytes: [UInt8] + + static func fromHex(_ hex: String) -> BigNumber? { + Hex.decode(hex).map { BigNumber(bytes: $0) } + } + + func dividedByTwo() -> BigNumber? { + var result: [UInt8] = [] + var carry: UInt16 = 0 + for byte in bytes { + let value = carry * 256 + UInt16(byte) + result.append(UInt8(value / 2)) + carry = value % 2 + } + return BigNumber(bytes: result) + } + + func lessThanOrEqual(_ other: BigNumber) -> Bool { + let left = trimmed() + let right = other.trimmed() + if left.count != right.count { return left.count < right.count } + for (a, b) in zip(left, right) where a != b { return a < b } + return true + } + + private func trimmed() -> [UInt8] { + var value = bytes + while value.first == 0 && value.count > 1 { value.removeFirst() } + return value + } +} + +enum DER { + /// Extracts the `s` component from a DER-encoded ECDSA signature. + static func signatureScalar(_ der: [UInt8]) -> BigNumber? { + guard der.count > 8, der[0] == 0x30 else { return nil } + var index = 2 + guard der[index] == 0x02 else { return nil } + let rLength = Int(der[index + 1]) + index += 2 + rLength + guard index + 2 <= der.count, der[index] == 0x02 else { return nil } + let sLength = Int(der[index + 1]) + let start = index + 2 + guard start + sLength <= der.count else { return nil } + return BigNumber(bytes: Array(der[start..<(start + sLength)])) + } +} \ No newline at end of file diff --git a/apps/ios/Koins/Support/BIP32.swift b/apps/ios/Koins/Support/BIP32.swift new file mode 100644 index 0000000..5ed6863 --- /dev/null +++ b/apps/ios/Koins/Support/BIP32.swift @@ -0,0 +1,92 @@ +import CryptoKit +import Foundation + +/// BIP-32 key derivation for the XRP account path `m/44'/144'/0'/0/0`. The +/// first three steps are hardened, the last two are not, so both cases are +/// supported (hardened steps sign with the parent private key, normal steps +/// with the parent public key). +enum BIP32 { + static let hardenedBit: UInt32 = 0x8000_0000 + + /// Standard XRP derivation path: 44' / 144' / 0' / 0 / 0. + static let xrpPath: [UInt32] = [ + hardenedBit | 44, + hardenedBit | 144, + hardenedBit | 0, + 0, + 0, + ] + + struct Key { + var privateKey: [UInt8] + var chainCode: [UInt8] + var publicKey: [UInt8] + } + + static func masterKey(fromSeed seed: [UInt8]) -> Key? { + let derived = hmac(key: Array("Bitcoin seed".utf8), data: seed) + guard derived.count == 64 else { return nil } + let privateKey = Array(derived[0..<32]) + guard isValidScalar(privateKey), + let publicKey = Secp256k1Backend.publicKey?(Data(privateKey)) else { return nil } + return Key(privateKey: privateKey, + chainCode: Array(derived[32..<64]), + publicKey: Array(publicKey)) + } + + static func derive(_ path: [UInt32], from key: Key) -> Key? { + var current = key + for index in path { + guard let next = deriveChild(current, index: index) else { return nil } + current = next + } + return current + } + + private static func deriveChild(_ parent: Key, index: UInt32) -> Key? { + let isHardened = index & hardenedBit != 0 + + var data: [UInt8] = [] + if isHardened { + data.append(0x00) + data += parent.privateKey + } else { + data += parent.publicKey + } + data += [ + UInt8((index >> 24) & 0xff), + UInt8((index >> 16) & 0xff), + UInt8((index >> 8) & 0xff), + UInt8(index & 0xff), + ] + + let derived = hmac(key: parent.chainCode, data: data) + guard derived.count == 64 else { return nil } + let left = Array(derived[0..<32]) + guard isValidScalar(left), + let sum = Secp256k1Backend.addScalar?(Data(left), Data(parent.privateKey)) else { return nil } + let privateKey = Array(sum) + // The child public key is the same whether it is computed from the child + // private key or by point addition, so derive it directly. + guard isValidScalar(privateKey), + let publicKey = Secp256k1Backend.publicKey?(Data(privateKey)) else { return nil } + + return Key(privateKey: privateKey, + chainCode: Array(derived[32..<64]), + publicKey: Array(publicKey)) + } + + /// A key is usable when 0 < key < the secp256k1 group order. + private static func isValidScalar(_ bytes: [UInt8]) -> Bool { + guard bytes.count == 32 else { return false } + let orderHex = "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141" + guard let order = Hex.decode(orderHex) else { return false } + if bytes.allSatisfy({ $0 == 0 }) { return false } + for (left, right) in zip(bytes, order) where left != right { return left < right } + return false + } + + private static func hmac(key: [UInt8], data: [UInt8]) -> [UInt8] { + Array(HMAC.authenticationCode(for: Data(data), using: SymmetricKey(data: Data(key)))) + } +} \ No newline at end of file diff --git a/apps/ios/Koins/Support/BIP39.swift b/apps/ios/Koins/Support/BIP39.swift new file mode 100644 index 0000000..8235cf2 --- /dev/null +++ b/apps/ios/Koins/Support/BIP39.swift @@ -0,0 +1,94 @@ +import CryptoKit +import Foundation + +/// BIP-39 mnemonics. The XRP wallet uses a 12 word phrase as its recovery +/// secret, matching what hardware and mobile wallets expect. +enum BIP39 { + static let wordCount = 12 + static let entropyBytes = 16 + private static let checksumBits = entropyBytes / 4 + + private static let index: [String: Int] = { + var map: [String: Int] = [:] + for (position, word) in BIP39Wordlist.words.enumerated() { map[word] = position } + return map + }() + + static func generateMnemonic() -> String { + var entropy = [UInt8](repeating: 0, count: entropyBytes) + for position in entropy.indices { entropy[position] = UInt8.random(in: 0...255) } + return mnemonic(from: entropy) + } + + /// 16 bytes of entropy plus its 4 bit SHA-256 checksum, as 12 words. + static func mnemonic(from entropy: [UInt8]) -> String { + precondition(entropy.count == entropyBytes, "BIP-39 entropy must be 16 bytes") + var bits = entropy.flatMap { byte in (0..<8).map { (byte >> (7 - $0)) & 1 } } + let checksum = XRPHash.sha256(entropy) + bits += (0..> (7 - $0)) & 1 } + + var words: [String] = [] + for start in stride(from: 0, to: bits.count, by: 11) { + var value = 0 + for offset in 0..<11 { value = (value << 1) | Int(bits[start + offset]) } + words.append(BIP39Wordlist.words[value]) + } + return words.joined(separator: " ") + } + + static func entropy(from mnemonic: String) -> [UInt8]? { + let words = normalize(mnemonic).split(separator: " ").map(String.init) + guard words.count == wordCount else { return nil } + + var bits: [Int] = [] + for word in words { + guard let position = index[word] else { return nil } + bits += (0..<11).map { (position >> (10 - $0)) & 1 } + } + + var entropy: [UInt8] = [] + for start in stride(from: 0, to: entropyBytes * 8, by: 8) { + var value: UInt8 = 0 + for offset in 0..<8 { value = (value << 1) | UInt8(bits[start + offset]) } + entropy.append(value) + } + + let checksum = XRPHash.sha256(entropy) + for offset in 0..> (7 - offset)) & 1 + guard bits[entropyBytes * 8 + offset] == expected else { return nil } + } + return entropy + } + + /// 64 byte BIP-39 seed (PBKDF2-HMAC-SHA512, empty passphrase). + static func seed(from mnemonic: String) -> [UInt8] { + let password = Array(normalize(mnemonic).utf8) + let salt = Array("mnemonic".utf8) + // Derived length equals the hash length, so a single PBKDF2 block is needed. + let block = salt + [0, 0, 0, 1] + var previous = hmacSHA512(key: password, data: block) + var result = previous + for _ in 1..<2_048 { + previous = hmacSHA512(key: password, data: previous) + for position in result.indices { result[position] ^= previous[position] } + } + return result + } + + static func isMnemonic(_ secret: String) -> Bool { + normalize(secret).split(separator: " ").count == wordCount + } + + private static func normalize(_ text: String) -> String { + text.trimmingCharacters(in: .whitespacesAndNewlines) + .lowercased() + .precomposedStringWithCompatibilityMapping + } + + private static func hmacSHA512(key: [UInt8], data: [UInt8]) -> [UInt8] { + let authentication = HMAC.authenticationCode(for: Data(data), + using: SymmetricKey(data: Data(key))) + return Array(authentication) + } +} \ No newline at end of file diff --git a/apps/ios/Koins/Support/BIP39Wordlist.swift b/apps/ios/Koins/Support/BIP39Wordlist.swift new file mode 100644 index 0000000..87a4656 --- /dev/null +++ b/apps/ios/Koins/Support/BIP39Wordlist.swift @@ -0,0 +1,263 @@ +import Foundation + +/// BIP-39 English word list (2048 words), in the canonical order. +enum BIP39Wordlist { + static let words: [String] = [ + "abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract", + "absurd", "abuse", "access", "accident", "account", "accuse", "achieve", "acid", + "acoustic", "acquire", "across", "act", "action", "actor", "actress", "actual", + "adapt", "add", "addict", "address", "adjust", "admit", "adult", "advance", + "advice", "aerobic", "affair", "afford", "afraid", "again", "age", "agent", + "agree", "ahead", "aim", "air", "airport", "aisle", "alarm", "album", + "alcohol", "alert", "alien", "all", "alley", "allow", "almost", "alone", + "alpha", "already", "also", "alter", "always", "amateur", "amazing", "among", + "amount", "amused", "analyst", "anchor", "ancient", "anger", "angle", "angry", + "animal", "ankle", "announce", "annual", "another", "answer", "antenna", "antique", + "anxiety", "any", "apart", "apology", "appear", "apple", "approve", "april", + "arch", "arctic", "area", "arena", "argue", "arm", "armed", "armor", + "army", "around", "arrange", "arrest", "arrive", "arrow", "art", "artefact", + "artist", "artwork", "ask", "aspect", "assault", "asset", "assist", "assume", + "asthma", "athlete", "atom", "attack", "attend", "attitude", "attract", "auction", + "audit", "august", "aunt", "author", "auto", "autumn", "average", "avocado", + "avoid", "awake", "aware", "away", "awesome", "awful", "awkward", "axis", + "baby", "bachelor", "bacon", "badge", "bag", "balance", "balcony", "ball", + "bamboo", "banana", "banner", "bar", "barely", "bargain", "barrel", "base", + "basic", "basket", "battle", "beach", "bean", "beauty", "because", "become", + "beef", "before", "begin", "behave", "behind", "believe", "below", "belt", + "bench", "benefit", "best", "betray", "better", "between", "beyond", "bicycle", + "bid", "bike", "bind", "biology", "bird", "birth", "bitter", "black", + "blade", "blame", "blanket", "blast", "bleak", "bless", "blind", "blood", + "blossom", "blouse", "blue", "blur", "blush", "board", "boat", "body", + "boil", "bomb", "bone", "bonus", "book", "boost", "border", "boring", + "borrow", "boss", "bottom", "bounce", "box", "boy", "bracket", "brain", + "brand", "brass", "brave", "bread", "breeze", "brick", "bridge", "brief", + "bright", "bring", "brisk", "broccoli", "broken", "bronze", "broom", "brother", + "brown", "brush", "bubble", "buddy", "budget", "buffalo", "build", "bulb", + "bulk", "bullet", "bundle", "bunker", "burden", "burger", "burst", "bus", + "business", "busy", "butter", "buyer", "buzz", "cabbage", "cabin", "cable", + "cactus", "cage", "cake", "call", "calm", "camera", "camp", "can", + "canal", "cancel", "candy", "cannon", "canoe", "canvas", "canyon", "capable", + "capital", "captain", "car", "carbon", "card", "cargo", "carpet", "carry", + "cart", "case", "cash", "casino", "castle", "casual", "cat", "catalog", + "catch", "category", "cattle", "caught", "cause", "caution", "cave", "ceiling", + "celery", "cement", "census", "century", "cereal", "certain", "chair", "chalk", + "champion", "change", "chaos", "chapter", "charge", "chase", "chat", "cheap", + "check", "cheese", "chef", "cherry", "chest", "chicken", "chief", "child", + "chimney", "choice", "choose", "chronic", "chuckle", "chunk", "churn", "cigar", + "cinnamon", "circle", "citizen", "city", "civil", "claim", "clap", "clarify", + "claw", "clay", "clean", "clerk", "clever", "click", "client", "cliff", + "climb", "clinic", "clip", "clock", "clog", "close", "cloth", "cloud", + "clown", "club", "clump", "cluster", "clutch", "coach", "coast", "coconut", + "code", "coffee", "coil", "coin", "collect", "color", "column", "combine", + "come", "comfort", "comic", "common", "company", "concert", "conduct", "confirm", + "congress", "connect", "consider", "control", "convince", "cook", "cool", "copper", + "copy", "coral", "core", "corn", "correct", "cost", "cotton", "couch", + "country", "couple", "course", "cousin", "cover", "coyote", "crack", "cradle", + "craft", "cram", "crane", "crash", "crater", "crawl", "crazy", "cream", + "credit", "creek", "crew", "cricket", "crime", "crisp", "critic", "crop", + "cross", "crouch", "crowd", "crucial", "cruel", "cruise", "crumble", "crunch", + "crush", "cry", "crystal", "cube", "culture", "cup", "cupboard", "curious", + "current", "curtain", "curve", "cushion", "custom", "cute", "cycle", "dad", + "damage", "damp", "dance", "danger", "daring", "dash", "daughter", "dawn", + "day", "deal", "debate", "debris", "decade", "december", "decide", "decline", + "decorate", "decrease", "deer", "defense", "define", "defy", "degree", "delay", + "deliver", "demand", "demise", "denial", "dentist", "deny", "depart", "depend", + "deposit", "depth", "deputy", "derive", "describe", "desert", "design", "desk", + "despair", "destroy", "detail", "detect", "develop", "device", "devote", "diagram", + "dial", "diamond", "diary", "dice", "diesel", "diet", "differ", "digital", + "dignity", "dilemma", "dinner", "dinosaur", "direct", "dirt", "disagree", "discover", + "disease", "dish", "dismiss", "disorder", "display", "distance", "divert", "divide", + "divorce", "dizzy", "doctor", "document", "dog", "doll", "dolphin", "domain", + "donate", "donkey", "donor", "door", "dose", "double", "dove", "draft", + "dragon", "drama", "drastic", "draw", "dream", "dress", "drift", "drill", + "drink", "drip", "drive", "drop", "drum", "dry", "duck", "dumb", + "dune", "during", "dust", "dutch", "duty", "dwarf", "dynamic", "eager", + "eagle", "early", "earn", "earth", "easily", "east", "easy", "echo", + "ecology", "economy", "edge", "edit", "educate", "effort", "egg", "eight", + "either", "elbow", "elder", "electric", "elegant", "element", "elephant", "elevator", + "elite", "else", "embark", "embody", "embrace", "emerge", "emotion", "employ", + "empower", "empty", "enable", "enact", "end", "endless", "endorse", "enemy", + "energy", "enforce", "engage", "engine", "enhance", "enjoy", "enlist", "enough", + "enrich", "enroll", "ensure", "enter", "entire", "entry", "envelope", "episode", + "equal", "equip", "era", "erase", "erode", "erosion", "error", "erupt", + "escape", "essay", "essence", "estate", "eternal", "ethics", "evidence", "evil", + "evoke", "evolve", "exact", "example", "excess", "exchange", "excite", "exclude", + "excuse", "execute", "exercise", "exhaust", "exhibit", "exile", "exist", "exit", + "exotic", "expand", "expect", "expire", "explain", "expose", "express", "extend", + "extra", "eye", "eyebrow", "fabric", "face", "faculty", "fade", "faint", + "faith", "fall", "false", "fame", "family", "famous", "fan", "fancy", + "fantasy", "farm", "fashion", "fat", "fatal", "father", "fatigue", "fault", + "favorite", "feature", "february", "federal", "fee", "feed", "feel", "female", + "fence", "festival", "fetch", "fever", "few", "fiber", "fiction", "field", + "figure", "file", "film", "filter", "final", "find", "fine", "finger", + "finish", "fire", "firm", "first", "fiscal", "fish", "fit", "fitness", + "fix", "flag", "flame", "flash", "flat", "flavor", "flee", "flight", + "flip", "float", "flock", "floor", "flower", "fluid", "flush", "fly", + "foam", "focus", "fog", "foil", "fold", "follow", "food", "foot", + "force", "forest", "forget", "fork", "fortune", "forum", "forward", "fossil", + "foster", "found", "fox", "fragile", "frame", "frequent", "fresh", "friend", + "fringe", "frog", "front", "frost", "frown", "frozen", "fruit", "fuel", + "fun", "funny", "furnace", "fury", "future", "gadget", "gain", "galaxy", + "gallery", "game", "gap", "garage", "garbage", "garden", "garlic", "garment", + "gas", "gasp", "gate", "gather", "gauge", "gaze", "general", "genius", + "genre", "gentle", "genuine", "gesture", "ghost", "giant", "gift", "giggle", + "ginger", "giraffe", "girl", "give", "glad", "glance", "glare", "glass", + "glide", "glimpse", "globe", "gloom", "glory", "glove", "glow", "glue", + "goat", "goddess", "gold", "good", "goose", "gorilla", "gospel", "gossip", + "govern", "gown", "grab", "grace", "grain", "grant", "grape", "grass", + "gravity", "great", "green", "grid", "grief", "grit", "grocery", "group", + "grow", "grunt", "guard", "guess", "guide", "guilt", "guitar", "gun", + "gym", "habit", "hair", "half", "hammer", "hamster", "hand", "happy", + "harbor", "hard", "harsh", "harvest", "hat", "have", "hawk", "hazard", + "head", "health", "heart", "heavy", "hedgehog", "height", "hello", "helmet", + "help", "hen", "hero", "hidden", "high", "hill", "hint", "hip", + "hire", "history", "hobby", "hockey", "hold", "hole", "holiday", "hollow", + "home", "honey", "hood", "hope", "horn", "horror", "horse", "hospital", + "host", "hotel", "hour", "hover", "hub", "huge", "human", "humble", + "humor", "hundred", "hungry", "hunt", "hurdle", "hurry", "hurt", "husband", + "hybrid", "ice", "icon", "idea", "identify", "idle", "ignore", "ill", + "illegal", "illness", "image", "imitate", "immense", "immune", "impact", "impose", + "improve", "impulse", "inch", "include", "income", "increase", "index", "indicate", + "indoor", "industry", "infant", "inflict", "inform", "inhale", "inherit", "initial", + "inject", "injury", "inmate", "inner", "innocent", "input", "inquiry", "insane", + "insect", "inside", "inspire", "install", "intact", "interest", "into", "invest", + "invite", "involve", "iron", "island", "isolate", "issue", "item", "ivory", + "jacket", "jaguar", "jar", "jazz", "jealous", "jeans", "jelly", "jewel", + "job", "join", "joke", "journey", "joy", "judge", "juice", "jump", + "jungle", "junior", "junk", "just", "kangaroo", "keen", "keep", "ketchup", + "key", "kick", "kid", "kidney", "kind", "kingdom", "kiss", "kit", + "kitchen", "kite", "kitten", "kiwi", "knee", "knife", "knock", "know", + "lab", "label", "labor", "ladder", "lady", "lake", "lamp", "language", + "laptop", "large", "later", "latin", "laugh", "laundry", "lava", "law", + "lawn", "lawsuit", "layer", "lazy", "leader", "leaf", "learn", "leave", + "lecture", "left", "leg", "legal", "legend", "leisure", "lemon", "lend", + "length", "lens", "leopard", "lesson", "letter", "level", "liar", "liberty", + "library", "license", "life", "lift", "light", "like", "limb", "limit", + "link", "lion", "liquid", "list", "little", "live", "lizard", "load", + "loan", "lobster", "local", "lock", "logic", "lonely", "long", "loop", + "lottery", "loud", "lounge", "love", "loyal", "lucky", "luggage", "lumber", + "lunar", "lunch", "luxury", "lyrics", "machine", "mad", "magic", "magnet", + "maid", "mail", "main", "major", "make", "mammal", "man", "manage", + "mandate", "mango", "mansion", "manual", "maple", "marble", "march", "margin", + "marine", "market", "marriage", "mask", "mass", "master", "match", "material", + "math", "matrix", "matter", "maximum", "maze", "meadow", "mean", "measure", + "meat", "mechanic", "medal", "media", "melody", "melt", "member", "memory", + "mention", "menu", "mercy", "merge", "merit", "merry", "mesh", "message", + "metal", "method", "middle", "midnight", "milk", "million", "mimic", "mind", + "minimum", "minor", "minute", "miracle", "mirror", "misery", "miss", "mistake", + "mix", "mixed", "mixture", "mobile", "model", "modify", "mom", "moment", + "monitor", "monkey", "monster", "month", "moon", "moral", "more", "morning", + "mosquito", "mother", "motion", "motor", "mountain", "mouse", "move", "movie", + "much", "muffin", "mule", "multiply", "muscle", "museum", "mushroom", "music", + "must", "mutual", "myself", "mystery", "myth", "naive", "name", "napkin", + "narrow", "nasty", "nation", "nature", "near", "neck", "need", "negative", + "neglect", "neither", "nephew", "nerve", "nest", "net", "network", "neutral", + "never", "news", "next", "nice", "night", "noble", "noise", "nominee", + "noodle", "normal", "north", "nose", "notable", "note", "nothing", "notice", + "novel", "now", "nuclear", "number", "nurse", "nut", "oak", "obey", + "object", "oblige", "obscure", "observe", "obtain", "obvious", "occur", "ocean", + "october", "odor", "off", "offer", "office", "often", "oil", "okay", + "old", "olive", "olympic", "omit", "once", "one", "onion", "online", + "only", "open", "opera", "opinion", "oppose", "option", "orange", "orbit", + "orchard", "order", "ordinary", "organ", "orient", "original", "orphan", "ostrich", + "other", "outdoor", "outer", "output", "outside", "oval", "oven", "over", + "own", "owner", "oxygen", "oyster", "ozone", "pact", "paddle", "page", + "pair", "palace", "palm", "panda", "panel", "panic", "panther", "paper", + "parade", "parent", "park", "parrot", "party", "pass", "patch", "path", + "patient", "patrol", "pattern", "pause", "pave", "payment", "peace", "peanut", + "pear", "peasant", "pelican", "pen", "penalty", "pencil", "people", "pepper", + "perfect", "permit", "person", "pet", "phone", "photo", "phrase", "physical", + "piano", "picnic", "picture", "piece", "pig", "pigeon", "pill", "pilot", + "pink", "pioneer", "pipe", "pistol", "pitch", "pizza", "place", "planet", + "plastic", "plate", "play", "please", "pledge", "pluck", "plug", "plunge", + "poem", "poet", "point", "polar", "pole", "police", "pond", "pony", + "pool", "popular", "portion", "position", "possible", "post", "potato", "pottery", + "poverty", "powder", "power", "practice", "praise", "predict", "prefer", "prepare", + "present", "pretty", "prevent", "price", "pride", "primary", "print", "priority", + "prison", "private", "prize", "problem", "process", "produce", "profit", "program", + "project", "promote", "proof", "property", "prosper", "protect", "proud", "provide", + "public", "pudding", "pull", "pulp", "pulse", "pumpkin", "punch", "pupil", + "puppy", "purchase", "purity", "purpose", "purse", "push", "put", "puzzle", + "pyramid", "quality", "quantum", "quarter", "question", "quick", "quit", "quiz", + "quote", "rabbit", "raccoon", "race", "rack", "radar", "radio", "rail", + "rain", "raise", "rally", "ramp", "ranch", "random", "range", "rapid", + "rare", "rate", "rather", "raven", "raw", "razor", "ready", "real", + "reason", "rebel", "rebuild", "recall", "receive", "recipe", "record", "recycle", + "reduce", "reflect", "reform", "refuse", "region", "regret", "regular", "reject", + "relax", "release", "relief", "rely", "remain", "remember", "remind", "remove", + "render", "renew", "rent", "reopen", "repair", "repeat", "replace", "report", + "require", "rescue", "resemble", "resist", "resource", "response", "result", "retire", + "retreat", "return", "reunion", "reveal", "review", "reward", "rhythm", "rib", + "ribbon", "rice", "rich", "ride", "ridge", "rifle", "right", "rigid", + "ring", "riot", "ripple", "risk", "ritual", "rival", "river", "road", + "roast", "robot", "robust", "rocket", "romance", "roof", "rookie", "room", + "rose", "rotate", "rough", "round", "route", "royal", "rubber", "rude", + "rug", "rule", "run", "runway", "rural", "sad", "saddle", "sadness", + "safe", "sail", "salad", "salmon", "salon", "salt", "salute", "same", + "sample", "sand", "satisfy", "satoshi", "sauce", "sausage", "save", "say", + "scale", "scan", "scare", "scatter", "scene", "scheme", "school", "science", + "scissors", "scorpion", "scout", "scrap", "screen", "script", "scrub", "sea", + "search", "season", "seat", "second", "secret", "section", "security", "seed", + "seek", "segment", "select", "sell", "seminar", "senior", "sense", "sentence", + "series", "service", "session", "settle", "setup", "seven", "shadow", "shaft", + "shallow", "share", "shed", "shell", "sheriff", "shield", "shift", "shine", + "ship", "shiver", "shock", "shoe", "shoot", "shop", "short", "shoulder", + "shove", "shrimp", "shrug", "shuffle", "shy", "sibling", "sick", "side", + "siege", "sight", "sign", "silent", "silk", "silly", "silver", "similar", + "simple", "since", "sing", "siren", "sister", "situate", "six", "size", + "skate", "sketch", "ski", "skill", "skin", "skirt", "skull", "slab", + "slam", "sleep", "slender", "slice", "slide", "slight", "slim", "slogan", + "slot", "slow", "slush", "small", "smart", "smile", "smoke", "smooth", + "snack", "snake", "snap", "sniff", "snow", "soap", "soccer", "social", + "sock", "soda", "soft", "solar", "soldier", "solid", "solution", "solve", + "someone", "song", "soon", "sorry", "sort", "soul", "sound", "soup", + "source", "south", "space", "spare", "spatial", "spawn", "speak", "special", + "speed", "spell", "spend", "sphere", "spice", "spider", "spike", "spin", + "spirit", "split", "spoil", "sponsor", "spoon", "sport", "spot", "spray", + "spread", "spring", "spy", "square", "squeeze", "squirrel", "stable", "stadium", + "staff", "stage", "stairs", "stamp", "stand", "start", "state", "stay", + "steak", "steel", "stem", "step", "stereo", "stick", "still", "sting", + "stock", "stomach", "stone", "stool", "story", "stove", "strategy", "street", + "strike", "strong", "struggle", "student", "stuff", "stumble", "style", "subject", + "submit", "subway", "success", "such", "sudden", "suffer", "sugar", "suggest", + "suit", "summer", "sun", "sunny", "sunset", "super", "supply", "supreme", + "sure", "surface", "surge", "surprise", "surround", "survey", "suspect", "sustain", + "swallow", "swamp", "swap", "swarm", "swear", "sweet", "swift", "swim", + "swing", "switch", "sword", "symbol", "symptom", "syrup", "system", "table", + "tackle", "tag", "tail", "talent", "talk", "tank", "tape", "target", + "task", "taste", "tattoo", "taxi", "teach", "team", "tell", "ten", + "tenant", "tennis", "tent", "term", "test", "text", "thank", "that", + "theme", "then", "theory", "there", "they", "thing", "this", "thought", + "three", "thrive", "throw", "thumb", "thunder", "ticket", "tide", "tiger", + "tilt", "timber", "time", "tiny", "tip", "tired", "tissue", "title", + "toast", "tobacco", "today", "toddler", "toe", "together", "toilet", "token", + "tomato", "tomorrow", "tone", "tongue", "tonight", "tool", "tooth", "top", + "topic", "topple", "torch", "tornado", "tortoise", "toss", "total", "tourist", + "toward", "tower", "town", "toy", "track", "trade", "traffic", "tragic", + "train", "transfer", "trap", "trash", "travel", "tray", "treat", "tree", + "trend", "trial", "tribe", "trick", "trigger", "trim", "trip", "trophy", + "trouble", "truck", "true", "truly", "trumpet", "trust", "truth", "try", + "tube", "tuition", "tumble", "tuna", "tunnel", "turkey", "turn", "turtle", + "twelve", "twenty", "twice", "twin", "twist", "two", "type", "typical", + "ugly", "umbrella", "unable", "unaware", "uncle", "uncover", "under", "undo", + "unfair", "unfold", "unhappy", "uniform", "unique", "unit", "universe", "unknown", + "unlock", "until", "unusual", "unveil", "update", "upgrade", "uphold", "upon", + "upper", "upset", "urban", "urge", "usage", "use", "used", "useful", + "useless", "usual", "utility", "vacant", "vacuum", "vague", "valid", "valley", + "valve", "van", "vanish", "vapor", "various", "vast", "vault", "vehicle", + "velvet", "vendor", "venture", "venue", "verb", "verify", "version", "very", + "vessel", "veteran", "viable", "vibrant", "vicious", "victory", "video", "view", + "village", "vintage", "violin", "virtual", "virus", "visa", "visit", "visual", + "vital", "vivid", "vocal", "voice", "void", "volcano", "volume", "vote", + "voyage", "wage", "wagon", "wait", "walk", "wall", "walnut", "want", + "warfare", "warm", "warrior", "wash", "wasp", "waste", "water", "wave", + "way", "wealth", "weapon", "wear", "weasel", "weather", "web", "wedding", + "weekend", "weird", "welcome", "west", "wet", "whale", "what", "wheat", + "wheel", "when", "where", "whip", "whisper", "wide", "width", "wife", + "wild", "will", "win", "window", "wine", "wing", "wink", "winner", + "winter", "wire", "wisdom", "wise", "wish", "witness", "wolf", "woman", + "wonder", "wood", "wool", "word", "work", "world", "worry", "worth", + "wrap", "wreck", "wrestle", "wrist", "write", "wrong", "yard", "year", + "yellow", "you", "young", "youth", "zebra", "zero", "zone", "zoo", + ] +} diff --git a/apps/ios/Koins/Support/Base58.swift b/apps/ios/Koins/Support/Base58.swift new file mode 100644 index 0000000..77fefd2 --- /dev/null +++ b/apps/ios/Koins/Support/Base58.swift @@ -0,0 +1,76 @@ +import Foundation + +/// Base58 with the XRP Ledger alphabet and Base58Check helpers. +enum XRPBase58 { + static let alphabet: [Character] = Array("rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz") + + private static let valueOf: [Character: Int] = { + var map: [Character: Int] = [:] + for (index, character) in alphabet.enumerated() { map[character] = index } + return map + }() + + static func encode(_ bytes: [UInt8]) -> String { + var zeros = 0 + for byte in bytes { + if byte == 0 { zeros += 1 } else { break } + } + + var digits: [Int] = [] + for byte in bytes[zeros...] { + var carry = Int(byte) + for index in digits.indices { + carry += digits[index] << 8 + digits[index] = carry % 58 + carry /= 58 + } + while carry > 0 { + digits.append(carry % 58) + carry /= 58 + } + } + + var output = String(repeating: String(alphabet[0]), count: zeros) + for digit in digits.reversed() { output.append(alphabet[digit]) } + return output + } + + static func decode(_ string: String) throws -> [UInt8] { + let characters = Array(string) + var zeros = 0 + for character in characters { + if character == alphabet[0] { zeros += 1 } else { break } + } + + var bytes: [Int] = [] + for character in characters[zeros...] { + guard let value = valueOf[character] else { throw XRPError.invalidBase58 } + var carry = value + for index in bytes.indices { + carry += bytes[index] * 58 + bytes[index] = carry & 0xff + carry >>= 8 + } + while carry > 0 { + bytes.append(carry & 0xff) + carry >>= 8 + } + } + + return [UInt8](repeating: 0, count: zeros) + bytes.reversed().map { UInt8($0) } + } + + static func checkEncode(version: [UInt8], payload: [UInt8]) -> String { + let body = version + payload + return encode(body + XRPHash.checksum(body)) + } + + static func checkDecode(_ string: String, versionLength: Int) throws -> [UInt8] { + let decoded = try decode(string) + guard decoded.count > versionLength + 4 else { throw XRPError.invalidAddress } + let body = Array(decoded[0..<(decoded.count - 4)]) + let checksum = Array(decoded[(decoded.count - 4)...]) + guard XRPHash.checksum(body) == checksum else { throw XRPError.invalidAddress } + return Array(body[versionLength...]) + } +} \ No newline at end of file diff --git a/apps/ios/Koins/Support/Hashing.swift b/apps/ios/Koins/Support/Hashing.swift new file mode 100644 index 0000000..806fc56 --- /dev/null +++ b/apps/ios/Koins/Support/Hashing.swift @@ -0,0 +1,68 @@ +import CryptoKit +import Foundation + +enum XRPError: LocalizedError { + case invalidBase58 + case invalidAddress + case invalidSeed + case unsupportedSeed + case rpc(String) + case network(String) + + var errorDescription: String? { + switch self { + case .invalidBase58: return "Invalid Base58 text" + case .invalidAddress: return "Invalid XRP address" + case .invalidSeed: return "Check the recovery phrase or secret" + case .unsupportedSeed: return "Unsupported XRP secret" + case .rpc(let message): return message + case .network(let message): return message + } + } +} + +enum XRPHash { + static func sha256(_ bytes: [UInt8]) -> [UInt8] { + Array(SHA256.hash(data: Data(bytes))) + } + + static func sha512(_ bytes: [UInt8]) -> [UInt8] { + Array(SHA512.hash(data: Data(bytes))) + } + + /// First 32 bytes of SHA-512, used by the XRP Ledger everywhere. + static func sha512Half(_ bytes: [UInt8]) -> [UInt8] { + Array(sha512(bytes).prefix(32)) + } + + static func ripemd160(_ bytes: [UInt8]) -> [UInt8] { + RIPEMD160.hash(bytes) + } + + /// Base58Check double-SHA-256 checksum. + static func checksum(_ bytes: [UInt8]) -> [UInt8] { + Array(sha256(sha256(bytes)).prefix(4)) + } +} + +enum Hex { + static func encode(_ bytes: [UInt8]) -> String { + bytes.map { String(format: "%02x", $0) }.joined() + } + + static func decode(_ string: String) -> [UInt8]? { + var text = string.trimmingCharacters(in: .whitespacesAndNewlines) + if text.hasPrefix("0x") || text.hasPrefix("0X") { text = String(text.dropFirst(2)) } + guard text.count % 2 == 0 else { return nil } + var bytes: [UInt8] = [] + bytes.reserveCapacity(text.count / 2) + var index = text.startIndex + while index < text.endIndex { + let next = text.index(index, offsetBy: 2) + guard let byte = UInt8(text[index.. [UInt8] { + var padded = message + let bitLength = UInt64(message.count) * 8 + padded.append(0x80) + while padded.count % 64 != 56 { padded.append(0) } + for shift in stride(from: 0, through: 56, by: 8) { + padded.append(UInt8((bitLength >> UInt64(shift)) & 0xff)) + } + + var h0: UInt32 = 0x6745_2301 + var h1: UInt32 = 0xEFCD_AB89 + var h2: UInt32 = 0x98BA_DCFE + var h3: UInt32 = 0x1032_5476 + var h4: UInt32 = 0xC3D2_E1F0 + + var offset = 0 + while offset < padded.count { + var words = [UInt32](repeating: 0, count: 16) + for index in 0..<16 { + let base = offset + index * 4 + words[index] = UInt32(padded[base]) + | (UInt32(padded[base + 1]) << 8) + | (UInt32(padded[base + 2]) << 16) + | (UInt32(padded[base + 3]) << 24) + } + + var al = h0, bl = h1, cl = h2, dl = h3, el = h4 + var ar = h0, br = h1, cr = h2, dr = h3, er = h4 + + for step in 0..<80 { + let round = step / 16 + + var leftTemp = rotate(al &+ f(round, bl, cl, dl) &+ words[roundLeft[step]] &+ constantsLeft[round], by: shiftsLeft[step]) &+ el + al = el; el = dl; dl = rotate(cl, by: 10); cl = bl; bl = leftTemp + + let rightRound = 4 - round + leftTemp = rotate(ar &+ f(rightRound, br, cr, dr) &+ words[roundRight[step]] &+ constantsRight[round], by: shiftsRight[step]) &+ er + ar = er; er = dr; dr = rotate(cr, by: 10); cr = br; br = leftTemp + } + + let temp = h1 &+ cl &+ dr + h1 = h2 &+ dl &+ er + h2 = h3 &+ el &+ ar + h3 = h4 &+ al &+ br + h4 = h0 &+ bl &+ cr + h0 = temp + + offset += 64 + } + + var digest: [UInt8] = [] + for word in [h0, h1, h2, h3, h4] { + digest.append(UInt8(word & 0xff)) + digest.append(UInt8((word >> 8) & 0xff)) + digest.append(UInt8((word >> 16) & 0xff)) + digest.append(UInt8((word >> 24) & 0xff)) + } + return digest + } + + private static func f(_ round: Int, _ x: UInt32, _ y: UInt32, _ z: UInt32) -> UInt32 { + switch round { + case 0: return x ^ y ^ z + case 1: return (x & y) | (~x & z) + case 2: return (x | ~y) ^ z + case 3: return (x & z) | (y & ~z) + default: return x ^ (y | ~z) + } + } + + private static func rotate(_ value: UInt32, by amount: UInt32) -> UInt32 { + (value << amount) | (value >> (32 - amount)) + } +} \ No newline at end of file diff --git a/apps/ios/Koins/Support/WalletStore.swift b/apps/ios/Koins/Support/WalletStore.swift new file mode 100644 index 0000000..07c6f8e --- /dev/null +++ b/apps/ios/Koins/Support/WalletStore.swift @@ -0,0 +1,76 @@ +import Foundation +import Security + +/// Small Keychain wrapper for wallet secrets. Secrets never touch UserDefaults. +enum Keychain { + private static let service = "com.koins.app" + + static func set(_ value: String, for key: String) { + guard let data = value.data(using: .utf8) else { return } + remove(key) + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: service, + kSecAttrAccount as String: key, + kSecValueData as String: data, + kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, + ] + SecItemAdd(query as CFDictionary, nil) + } + + static func get(_ key: String) -> String? { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: service, + kSecAttrAccount as String: key, + kSecReturnData as String: true, + kSecMatchLimit as String: kSecMatchLimitOne, + ] + var item: CFTypeRef? + guard SecItemCopyMatching(query as CFDictionary, &item) == errSecSuccess, + let data = item as? Data, + let value = String(data: data, encoding: .utf8) else { + return nil + } + return value + } + + static func remove(_ key: String) { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: service, + kSecAttrAccount as String: key, + ] + SecItemDelete(query as CFDictionary) + } +} + +enum WalletStore { + private static let defaults = UserDefaults.standard + + private static func walletKey(_ chain: Chain) -> String { "koins.wallet.\(chain.rawValue)" } + private static func secretKey(_ chain: Chain) -> String { "koins.secret.\(chain.rawValue)" } + + static func wallet(_ chain: Chain) -> WalletRecord? { + guard let data = defaults.data(forKey: walletKey(chain)) else { return nil } + return try? JSONDecoder().decode(WalletRecord.self, from: data) + } + + static func saveWallet(_ wallet: WalletRecord, chain: Chain) { + guard let data = try? JSONEncoder().encode(wallet) else { return } + defaults.set(data, forKey: walletKey(chain)) + } + + static func removeWallet(_ chain: Chain) { + defaults.removeObject(forKey: walletKey(chain)) + Keychain.remove(secretKey(chain)) + } + + static func secret(_ chain: Chain) -> String? { + Keychain.get(secretKey(chain)) + } + + static func saveSecret(_ secret: String, chain: Chain) { + Keychain.set(secret, for: secretKey(chain)) + } +} \ No newline at end of file diff --git a/apps/ios/Koins/Views/Components.swift b/apps/ios/Koins/Views/Components.swift new file mode 100644 index 0000000..106de1c --- /dev/null +++ b/apps/ios/Koins/Views/Components.swift @@ -0,0 +1,260 @@ +import CoreImage.CIFilterBuiltins +import SwiftUI + +enum Theme { + static let background = Color(red: 0.035, green: 0.035, blue: 0.05) + static let surface = Color(red: 0.09, green: 0.09, blue: 0.12) + static let surfaceAlt = Color(red: 0.13, green: 0.13, blue: 0.17) + static let border = Color.white.opacity(0.08) + static let accent = Color(red: 1.0, green: 0.56, blue: 0.16) + static let accentSoft = Color(red: 1.0, green: 0.56, blue: 0.16).opacity(0.15) + static let text = Color.white + static let muted = Color.white.opacity(0.55) + static let danger = Color(red: 1.0, green: 0.36, blue: 0.36) + static let success = Color(red: 0.35, green: 0.85, blue: 0.55) +} + +struct PrimaryButton: View { + var title: String + var disabled: Bool = false + var action: () -> Void + + var body: some View { + Button(action: action) { + Text(title) + .font(.system(size: 16, weight: .semibold)) + .frame(maxWidth: .infinity) + .padding(.vertical, 15) + .background(disabled ? Theme.surfaceAlt : Theme.accent) + .foregroundStyle(disabled ? Theme.muted : Color.black) + .clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous)) + } + .disabled(disabled) + } +} + +struct SecondaryButton: View { + var title: String + var action: () -> Void + + var body: some View { + Button(action: action) { + Text(title) + .font(.system(size: 15, weight: .medium)) + .frame(maxWidth: .infinity) + .padding(.vertical, 14) + .background(Theme.surface) + .foregroundStyle(Theme.text) + .overlay(RoundedRectangle(cornerRadius: 14, style: .continuous).stroke(Theme.border)) + .clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous)) + } + } +} + +struct Card: View { + @ViewBuilder var content: Content + + var body: some View { + content + .padding(18) + .frame(maxWidth: .infinity, alignment: .leading) + .background(Theme.surface) + .overlay(RoundedRectangle(cornerRadius: 20, style: .continuous).stroke(Theme.border)) + .clipShape(RoundedRectangle(cornerRadius: 20, style: .continuous)) + } +} + +struct FieldLabel: View { + var text: String + + var body: some View { + Text(text) + .font(.system(size: 12, weight: .medium)) + .foregroundStyle(Theme.muted) + .padding(.leading, 4) + } +} + +struct KoinsTextField: View { + var placeholder: String + @Binding var text: String + var multiline = false + var keyboard: UIKeyboardType = .default + var autocapitalization: TextInputAutocapitalization = .never + + var body: some View { + Group { + if multiline { + TextEditor(text: $text) + .scrollContentBackground(.hidden) + .frame(minHeight: 96) + .padding(8) + } else { + TextField(placeholder, text: $text) + .keyboardType(keyboard) + .textInputAutocapitalization(autocapitalization) + .autocorrectionDisabled() + .padding(.horizontal, 14) + .padding(.vertical, 13) + } + } + .font(.system(size: 15)) + .foregroundStyle(Theme.text) + .background(Theme.surface) + .overlay(RoundedRectangle(cornerRadius: 14, style: .continuous).stroke(Theme.border)) + .clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous)) + } +} + +struct MessageBanner: View { + var message: String + var isError: Bool + + var body: some View { + Text(message) + .font(.system(size: 13)) + .foregroundStyle(isError ? Theme.danger : Theme.success) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(12) + .background((isError ? Theme.danger : Theme.success).opacity(0.12)) + .clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous)) + } +} + +struct ChainPicker: View { + @Binding var selection: Chain + + var body: some View { + HStack(spacing: 6) { + ForEach(Chain.allCases, id: \.self) { chain in + Button { + selection = chain + } label: { + Text(chain.title) + .font(.system(size: 13, weight: .semibold)) + .frame(maxWidth: .infinity) + .padding(.vertical, 10) + .background(selection == chain ? Theme.accent : Color.clear) + .foregroundStyle(selection == chain ? Color.black : Theme.muted) + .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) + } + } + } + .padding(4) + .background(Theme.surface) + .clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous)) + } +} + +struct QRCodeView: View { + var text: String + + var body: some View { + if let image = QRCodeView.makeImage(from: text) { + Image(uiImage: image) + .interpolation(.none) + .resizable() + .scaledToFit() + .padding(12) + .background(Color.white) + .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) + } else { + RoundedRectangle(cornerRadius: 16, style: .continuous) + .fill(Theme.surfaceAlt) + .aspectRatio(1, contentMode: .fit) + } + } + + private static func makeImage(from text: String) -> UIImage? { + guard !text.isEmpty else { return nil } + let filter = CIFilter.qrCodeGenerator() + filter.message = Data(text.utf8) + filter.correctionLevel = "M" + guard let output = filter.outputImage else { return nil } + let scaled = output.transformed(by: CGAffineTransform(scaleX: 10, y: 10)) + let context = CIContext() + guard let cgImage = context.createCGImage(scaled, from: scaled.extent) else { return nil } + return UIImage(cgImage: cgImage) + } +} + +struct TransactionRow: View { + var record: TransactionRecord + var symbol: String + + private var formattedDate: String { + guard let timestamp = record.timestamp else { return "Pending" } + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeStyle = .short + return formatter.string(from: timestamp) + } + + var body: some View { + HStack(spacing: 12) { + ZStack { + Circle() + .fill(record.direction == .incoming ? Theme.success.opacity(0.15) : Theme.accentSoft) + .frame(width: 38, height: 38) + Image(systemName: record.direction == .incoming ? "arrow.down.left" : "arrow.up.right") + .font(.system(size: 15, weight: .bold)) + .foregroundStyle(record.direction == .incoming ? Theme.success : Theme.accent) + } + + VStack(alignment: .leading, spacing: 3) { + Text(record.direction == .incoming ? "Received" : "Sent") + .font(.system(size: 14, weight: .semibold)) + .foregroundStyle(Theme.text) + Text(record.confirmed ? formattedDate : "Pending confirmation") + .font(.system(size: 11)) + .foregroundStyle(Theme.muted) + } + + Spacer() + + VStack(alignment: .trailing, spacing: 3) { + Text("\(record.direction == .incoming ? "+" : "-")\(record.amount) \(symbol)") + .font(.system(size: 14, weight: .semibold)) + .foregroundStyle(record.direction == .incoming ? Theme.success : Theme.text) + if let fee = record.fee, !fee.isEmpty, record.direction == .outgoing { + Text("fee \(fee)") + .font(.system(size: 11)) + .foregroundStyle(Theme.muted) + } + } + } + .padding(.vertical, 6) + } +} + +struct ScreenHeader: View { + var title: String + var subtitle: String? + var onBack: (() -> Void)? + + var body: some View { + HStack(alignment: .center, spacing: 12) { + if let onBack { + Button(action: onBack) { + Image(systemName: "chevron.left") + .font(.system(size: 16, weight: .semibold)) + .foregroundStyle(Theme.text) + .frame(width: 34, height: 34) + .background(Theme.surface) + .clipShape(Circle()) + } + } + VStack(alignment: .leading, spacing: 2) { + Text(title) + .font(.system(size: 22, weight: .bold)) + .foregroundStyle(Theme.text) + if let subtitle { + Text(subtitle) + .font(.system(size: 12)) + .foregroundStyle(Theme.muted) + } + } + Spacer() + } + } +} \ No newline at end of file diff --git a/apps/ios/Koins/Views/DashboardViews.swift b/apps/ios/Koins/Views/DashboardViews.swift new file mode 100644 index 0000000..d40d364 --- /dev/null +++ b/apps/ios/Koins/Views/DashboardViews.swift @@ -0,0 +1,228 @@ +import SwiftUI + +struct DashboardView: View { + @EnvironmentObject private var model: AppModel + @Binding var route: Route + + @State private var showSeed = false + @State private var revealedSeed: String? + @State private var seedError: String? + + var body: some View { + VStack(alignment: .leading, spacing: 18) { + ScreenHeader(title: model.wallet?.name ?? "Wallet", + subtitle: model.chain.title, + onBack: { route = .home }) + + Card { + VStack(alignment: .leading, spacing: 10) { + Text("Balance") + .font(.system(size: 11, weight: .medium)) + .foregroundStyle(Theme.muted) + Text("\(model.balance.total) \(model.symbol)") + .font(.system(size: 32, weight: .bold, design: .rounded)) + .foregroundStyle(Theme.text) + if model.chain == .xmr, model.balance.unlocked != model.balance.total { + Text("\(model.balance.unlocked) \(model.symbol) spendable now") + .font(.system(size: 11)) + .foregroundStyle(Theme.muted) + } + AddressRow(address: model.displayAddress) + } + } + + if model.chain == .xmr { + Card { + VStack(alignment: .leading, spacing: 10) { + HStack { + Text("Sync") + .font(.system(size: 13, weight: .semibold)) + .foregroundStyle(Theme.text) + Spacer() + if let status = model.syncStatus { SyncBadge(status: status) } + } + if let status = model.syncStatus { + SyncProgress(status: status, eta: model.syncEta) + } else { + Text("Sync status is not available yet.") + .font(.system(size: 11)) + .foregroundStyle(Theme.muted) + } + } + } + } + + HStack(spacing: 10) { + PrimaryButton(title: "Send") { route = .send } + SecondaryButton(title: "Receive") { route = .receive } + } + + if !model.transactions.isEmpty { + Card { + VStack(alignment: .leading, spacing: 10) { + Text("Activity") + .font(.system(size: 13, weight: .semibold)) + .foregroundStyle(Theme.muted) + ForEach(model.transactions) { record in + TransactionRow(record: record, symbol: model.symbol) + if record.id != model.transactions.last?.id { + Divider().overlay(Theme.border) + } + } + } + } + } + + if let message = model.errorMessage { + MessageBanner(message: message, isError: true) + } + if let message = model.statusMessage { + MessageBanner(message: message, isError: false) + } + + SecondaryButton(title: "Reveal recovery phrase") { + Task { + seedError = nil + do { + revealedSeed = try await model.revealSeed() + showSeed = true + } catch { + seedError = error.localizedDescription + } + } + } + + if let seedError { + MessageBanner(message: seedError, isError: true) + } + } + .sheet(isPresented: $showSeed) { + if let revealedSeed { + SeedRevealView(seed: revealedSeed, symbol: model.symbol) { showSeed = false } + } + } + } +} + +struct SendView: View { + @EnvironmentObject private var model: AppModel + @Binding var route: Route + + @State private var destination = "" + @State private var amount = "" + @State private var tag = "" + @State private var priority: TransactionPriority = .defaultPriority + @State private var localError: String? + + var body: some View { + VStack(alignment: .leading, spacing: 18) { + ScreenHeader(title: "Send \(model.symbol)", + subtitle: "Balance \(model.balance.total) \(model.symbol)", + onBack: { route = .home }) + + VStack(alignment: .leading, spacing: 6) { + FieldLabel(text: model.chain == .xrp ? "Destination address" : "Recipient address") + KoinsTextField(placeholder: model.chain == .xrp ? "r…" : "4…", text: $destination) + } + + VStack(alignment: .leading, spacing: 6) { + FieldLabel(text: "Amount (\(model.symbol))") + KoinsTextField(placeholder: "0.0", text: $amount, keyboard: .decimalPad) + } + + if model.chain == .xrp { + VStack(alignment: .leading, spacing: 6) { + FieldLabel(text: "Destination tag (optional)") + KoinsTextField(placeholder: "e.g. 12345", text: $tag, keyboard: .numberPad) + } + } + + if model.chain == .xmr { + VStack(alignment: .leading, spacing: 8) { + FieldLabel(text: "Priority") + Picker("Priority", selection: $priority) { + ForEach([TransactionPriority.low, .medium, .high, .defaultPriority], id: \.rawValue) { item in + Text(item.label).tag(item) + } + } + .pickerStyle(.segmented) + } + } + + if let message = localError ?? model.errorMessage { + MessageBanner(message: message, isError: true) + } + if let message = model.statusMessage { + MessageBanner(message: message, isError: false) + } + + PrimaryButton(title: model.isBusy ? "Sending…" : "Send", disabled: model.isBusy) { + Task { + localError = nil + model.clearMessages() + do { + try await model.send(to: destination, amount: amount, tag: tag, priority: priority) + amount = "" + tag = "" + route = .dashboard + } catch { + localError = error.localizedDescription + } + } + } + + Spacer(minLength: 0) + } + } +} + +struct ReceiveView: View { + @EnvironmentObject private var model: AppModel + @Binding var route: Route + + private var address: String { + model.displayAddress + } + + var body: some View { + VStack(alignment: .leading, spacing: 18) { + ScreenHeader(title: "Receive \(model.symbol)", + subtitle: "Share this address to get paid", + onBack: { route = .home }) + + Card { + VStack(spacing: 16) { + QRCodeView(text: address) + .frame(maxWidth: 240) + Text(address) + .font(.system(size: 12, design: .monospaced)) + .foregroundStyle(Theme.muted) + .multilineTextAlignment(.center) + .lineLimit(3) + .truncationMode(.middle) + } + .frame(maxWidth: .infinity) + } + + SecondaryButton(title: "Copy address") { + UIPasteboard.general.string = address + } + + if model.chain == .xrp { + Card { + Text("XRP accounts must hold at least 1 XRP as a reserve to be active. Only send XRP on the XRP Ledger to this address.") + .font(.system(size: 12)) + .foregroundStyle(Theme.muted) + } + } else { + Card { + Text("Send only Monero (XMR) to this address. Transfers need confirmations before the balance becomes spendable.") + .font(.system(size: 12)) + .foregroundStyle(Theme.muted) + } + } + + Spacer(minLength: 0) + } + } +} \ No newline at end of file diff --git a/apps/ios/Koins/Views/RootView.swift b/apps/ios/Koins/Views/RootView.swift new file mode 100644 index 0000000..6bbc2f7 --- /dev/null +++ b/apps/ios/Koins/Views/RootView.swift @@ -0,0 +1,379 @@ +import SwiftUI + +enum Route: Equatable { + case home + case create + case importWallet + case dashboard + case send + case receive +} + +struct SeedBox: Identifiable { + let id = UUID() + let seed: String + let symbol: String +} + +struct RootView: View { + @EnvironmentObject private var model: AppModel + @State private var route: Route = .home + @State private var seedBox: SeedBox? + + var body: some View { + ZStack { + Theme.background.ignoresSafeArea() + ScrollView { + VStack(spacing: 18) { + switch route { + case .home: + HomeView(route: $route) + case .create: + CreateWalletView(route: $route, seedBox: $seedBox) + case .importWallet: + ImportWalletView(route: $route) + case .dashboard: + DashboardView(route: $route) + case .send: + SendView(route: $route) + case .receive: + ReceiveView(route: $route) + } + } + .padding(.horizontal, 20) + .padding(.top, 12) + .padding(.bottom, 40) + } + .scrollDismissesKeyboard(.interactively) + } + .preferredColorScheme(.dark) + .task { await model.bootstrap() } + .sheet(item: $seedBox) { box in + SeedRevealView(seed: box.seed, symbol: box.symbol) { seedBox = nil } + } + .onChange(of: model.chain) { _ in + if route == .dashboard { route = .home } + } + } +} + +struct HomeView: View { + @EnvironmentObject private var model: AppModel + @Binding var route: Route + @State private var showDeleteConfirm = false + + var body: some View { + VStack(spacing: 18) { + header + + if model.hasWallet, model.chain == .xrp || model.moneroReady { + walletCard + } else if model.hasWallet { + moneroUnavailableCard + } else { + emptyState + } + + if let message = model.errorMessage { + MessageBanner(message: message, isError: true) + } + if let message = model.statusMessage { + MessageBanner(message: message, isError: false) + } + } + .confirmationDialog("Delete this wallet?", + isPresented: $showDeleteConfirm, + titleVisibility: .visible) { + Button("Delete wallet", role: .destructive) { + Task { + await model.deleteWallet() + route = .home + } + } + Button("Cancel", role: .cancel) {} + } message: { + Text("The keys stored on this device are removed. Make sure your recovery seed is backed up.") + } + } + + private var header: some View { + VStack(alignment: .leading, spacing: 4) { + Text("Koins") + .font(.system(size: 30, weight: .heavy)) + .foregroundStyle(Theme.text) + Text("Monero and XRP wallet") + .font(.system(size: 13)) + .foregroundStyle(Theme.muted) + } + .frame(maxWidth: .infinity, alignment: .leading) + } + + private var walletCard: some View { + VStack(spacing: 14) { + ChainPicker(selection: Binding(get: { model.chain }, + set: { newValue in Task { await model.select(newValue) } })) + + Card { + VStack(alignment: .leading, spacing: 12) { + HStack { + Text(model.wallet?.name ?? "Wallet") + .font(.system(size: 16, weight: .semibold)) + .foregroundStyle(Theme.text) + Spacer() + if model.chain == .xmr, let status = model.syncStatus { + SyncBadge(status: status) + } + } + + VStack(alignment: .leading, spacing: 4) { + Text("Balance") + .font(.system(size: 11, weight: .medium)) + .foregroundStyle(Theme.muted) + Text("\(model.balance.total) \(model.symbol)") + .font(.system(size: 30, weight: .bold, design: .rounded)) + .foregroundStyle(Theme.text) + if model.chain == .xmr, model.balance.unlocked != model.balance.total { + Text("\(model.balance.unlocked) \(model.symbol) unlocked") + .font(.system(size: 11)) + .foregroundStyle(Theme.muted) + } + } + + AddressRow(address: model.displayAddress) + + if model.chain == .xmr, let status = model.syncStatus { + SyncProgress(status: status, eta: model.syncEta) + } + } + } + + HStack(spacing: 10) { + PrimaryButton(title: "Send") { route = .send } + SecondaryButton(title: "Receive") { route = .receive } + } + + if model.chain == .xmr { + SecondaryButton(title: model.syncStatus?.syncing == true ? "Syncing…" : "Sync now") { + Task { await model.startMoneroSync() } + } + } + + HStack(spacing: 10) { + SecondaryButton(title: "Refresh") { Task { await model.refresh(force: true) } } + SecondaryButton(title: "Delete") { showDeleteConfirm = true } + } + + if !model.transactions.isEmpty { + Card { + VStack(alignment: .leading, spacing: 10) { + Text("Activity") + .font(.system(size: 13, weight: .semibold)) + .foregroundStyle(Theme.muted) + ForEach(model.transactions) { record in + TransactionRow(record: record, symbol: model.symbol) + if record.id != model.transactions.last?.id { + Divider().overlay(Theme.border) + } + } + } + } + } else if model.chain == .xrp, model.balance.funded == false { + Text("This account is not funded yet. Send XRP to the address above to activate it.") + .font(.system(size: 12)) + .foregroundStyle(Theme.muted) + } + + if model.chain == .xrp, model.moneroReady, let status = model.syncStatus, status.syncing { + Card { + VStack(alignment: .leading, spacing: 8) { + HStack(spacing: 8) { + Circle() + .fill(status.connected ? Theme.success : Theme.danger) + .frame(width: 7, height: 7) + Text("Monero is syncing in the background") + .font(.system(size: 12, weight: .medium)) + .foregroundStyle(Theme.text) + Spacer() + Text("\(Int(status.progress * 100))%") + .font(.system(size: 12, weight: .semibold)) + .foregroundStyle(Theme.muted) + } + SyncProgress(status: status, eta: model.syncEta) + } + } + } + } + } + + private var moneroUnavailableCard: some View { + VStack(spacing: 14) { + ChainPicker(selection: Binding(get: { model.chain }, + set: { newValue in Task { await model.select(newValue) } })) + Card { + VStack(alignment: .leading, spacing: 10) { + Text("Monero wallet") + .font(.system(size: 16, weight: .semibold)) + .foregroundStyle(Theme.text) + Text("The wallet is on this device but could not be opened. It is safe to retry; nothing was deleted.") + .font(.system(size: 12)) + .foregroundStyle(Theme.muted) + SecondaryButton(title: "Retry") { + Task { await model.select(.xmr) } + } + } + } + } + } + + private var emptyState: some View { + VStack(spacing: 14) { + ChainPicker(selection: Binding(get: { model.chain }, + set: { newValue in Task { await model.select(newValue) } })) + + Card { + VStack(alignment: .leading, spacing: 8) { + Text("\(model.chain.title) wallet") + .font(.system(size: 17, weight: .semibold)) + .foregroundStyle(Theme.text) + Text("Create a new wallet on this device, or import one with its recovery seed.") + .font(.system(size: 12)) + .foregroundStyle(Theme.muted) + } + } + + VStack(spacing: 10) { + PrimaryButton(title: "Create wallet") { route = .create } + SecondaryButton(title: "Import wallet") { route = .importWallet } + } + } + } +} + +struct SyncBadge: View { + var status: SyncStatus + + private var label: String { + if status.syncing { + let percent = status.progress * 100 + return percent < 10 ? String(format: "Syncing %.1f%%", percent) : "Syncing \(Int(percent))%" + } + return status.connected ? "Synced" : "Offline" + } + + var body: some View { + HStack(spacing: 6) { + Circle() + .fill(status.connected ? Theme.success : Theme.danger) + .frame(width: 7, height: 7) + Text(label) + .font(.system(size: 11, weight: .medium)) + .foregroundStyle(Theme.muted) + } + } +} + +/// Live scan progress: how far the wallet has reached, and roughly how long the +/// remaining blocks will take at the observed rate. +struct SyncProgress: View { + var status: SyncStatus + var eta: String? + + var body: some View { + VStack(alignment: .leading, spacing: 6) { + ProgressView(value: status.progress) + .tint(Theme.accent) + HStack(spacing: 8) { + Text("Block \(status.height.formatted()) of \(status.daemonHeight.formatted())") + Spacer() + if status.syncing { + Text(eta.map { "about \($0) left" } ?? "estimating…") + } else if status.connected { + Text("up to date") + } + } + .font(.system(size: 11)) + .foregroundStyle(Theme.muted) + } + } +} + +struct AddressRow: View { + var address: String + @State private var copied = false + + var body: some View { + HStack(spacing: 8) { + Text(address.isEmpty ? "—" : address) + .font(.system(size: 11, design: .monospaced)) + .foregroundStyle(Theme.muted) + .lineLimit(1) + .truncationMode(.middle) + Spacer(minLength: 6) + Button { + UIPasteboard.general.string = address + copied = true + DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { copied = false } + } label: { + Image(systemName: copied ? "checkmark" : "doc.on.doc") + .font(.system(size: 12, weight: .semibold)) + .foregroundStyle(Theme.accent) + } + .disabled(address.isEmpty) + } + .padding(.horizontal, 12) + .padding(.vertical, 10) + .background(Theme.surfaceAlt) + .clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous)) + } +} + +struct SeedRevealView: View { + var seed: String + var symbol: String + var onDone: () -> Void + + @State private var acknowledged = false + + var body: some View { + ZStack { + Theme.background.ignoresSafeArea() + VStack(alignment: .leading, spacing: 16) { + Text("Your recovery phrase") + .font(.system(size: 24, weight: .bold)) + .foregroundStyle(Theme.text) + Text("Write these words down in order and keep them offline. Anyone with this phrase can spend your \(symbol).") + .font(.system(size: 13)) + .foregroundStyle(Theme.muted) + + Text(seed) + .font(.system(size: 15, design: .monospaced)) + .foregroundStyle(Theme.text) + .padding(16) + .frame(maxWidth: .infinity, alignment: .leading) + .background(Theme.surface) + .overlay(RoundedRectangle(cornerRadius: 16, style: .continuous).stroke(Theme.border)) + .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) + + Button { + UIPasteboard.general.string = seed + } label: { + Label("Copy phrase", systemImage: "doc.on.doc") + .font(.system(size: 14, weight: .medium)) + .foregroundStyle(Theme.accent) + } + + Toggle(isOn: $acknowledged) { + Text("I have written down my phrase") + .font(.system(size: 13)) + .foregroundStyle(Theme.muted) + } + .tint(Theme.accent) + + Spacer() + + PrimaryButton(title: "Continue", disabled: !acknowledged, action: onDone) + } + .padding(22) + } + } +} \ No newline at end of file diff --git a/apps/ios/Koins/Views/WalletSetupViews.swift b/apps/ios/Koins/Views/WalletSetupViews.swift new file mode 100644 index 0000000..be081be --- /dev/null +++ b/apps/ios/Koins/Views/WalletSetupViews.swift @@ -0,0 +1,142 @@ +import SwiftUI + +struct CreateWalletView: View { + @EnvironmentObject private var model: AppModel + @Binding var route: Route + @Binding var seedBox: SeedBox? + + @State private var name = "" + @State private var localError: String? + + var body: some View { + VStack(alignment: .leading, spacing: 18) { + ScreenHeader(title: "Create wallet", + subtitle: "A new \(model.chain.title) wallet on this device", + onBack: { route = .home }) + + VStack(alignment: .leading, spacing: 6) { + FieldLabel(text: "Wallet name") + KoinsTextField(placeholder: "My \(model.symbol) wallet", text: $name) + } + + if model.chain == .xmr { + Card { + Text("The wallet file is created locally and encrypted on this device. Syncing starts right away and can take a while for a new Monero wallet.") + .font(.system(size: 12)) + .foregroundStyle(Theme.muted) + } + } + + if let message = localError ?? model.errorMessage { + MessageBanner(message: message, isError: true) + } + + PrimaryButton(title: model.isBusy ? "Generating…" : "Generate wallet", + disabled: model.isBusy) { + Task { + localError = nil + model.clearMessages() + do { + let seed = try await model.createWallet(name: name) + seedBox = SeedBox(seed: seed, symbol: model.symbol) + route = .dashboard + } catch { + localError = error.localizedDescription + } + } + } + + Spacer(minLength: 0) + } + } +} + +struct ImportWalletView: View { + @EnvironmentObject private var model: AppModel + @Binding var route: Route + + @State private var name = "" + @State private var secret = "" + @State private var restoreHeight = "" + @State private var localError: String? + @State private var tipError: String? + @State private var isLoadingTip = false + + var body: some View { + VStack(alignment: .leading, spacing: 18) { + ScreenHeader(title: "Import wallet", + subtitle: "Restore a \(model.chain.title) wallet from its seed", + onBack: { route = .home }) + + VStack(alignment: .leading, spacing: 6) { + FieldLabel(text: "Wallet name") + KoinsTextField(placeholder: "My \(model.symbol) wallet", text: $name) + } + + VStack(alignment: .leading, spacing: 6) { + FieldLabel(text: model.chain == .xrp ? "Recovery phrase" : "Mnemonic seed") + KoinsTextField(placeholder: model.chain == .xrp ? "12 words, or a family seed (sEd…)" : "25 word seed phrase", + text: $secret, + multiline: model.chain == .xmr) + } + + if model.chain == .xmr { + VStack(alignment: .leading, spacing: 6) { + FieldLabel(text: "Restore height (optional)") + KoinsTextField(placeholder: "e.g. 3200000", text: $restoreHeight, keyboard: .numberPad) + } + Card { + VStack(alignment: .leading, spacing: 10) { + Text("Without a height the wallet scans the whole chain — millions of blocks, which takes hours on a phone. Set the block your wallet was first used, or start from the current tip if you only care about new funds.") + .font(.system(size: 12)) + .foregroundStyle(Theme.muted) + Button { + Task { + tipError = nil + isLoadingTip = true + defer { isLoadingTip = false } + do { + let tip = try await MoneroService.shared.daemonHeight() + restoreHeight = String(tip) + } catch { + tipError = error.localizedDescription + } + } + } label: { + Text(isLoadingTip ? "Fetching chain tip…" : "Use the current chain tip") + .font(.system(size: 13, weight: .medium)) + .foregroundStyle(Theme.accent) + } + .disabled(isLoadingTip) + if let tipError { + Text(tipError) + .font(.system(size: 11)) + .foregroundStyle(Theme.danger) + } + } + } + } + + if let message = localError ?? model.errorMessage { + MessageBanner(message: message, isError: true) + } + + PrimaryButton(title: model.isBusy ? "Importing…" : "Import wallet", + disabled: model.isBusy) { + Task { + localError = nil + model.clearMessages() + do { + let height = UInt64(restoreHeight.trimmingCharacters(in: .whitespaces)) + try await model.importWallet(name: name, secret: secret, restoreHeight: height) + route = .dashboard + } catch { + localError = error.localizedDescription + } + } + } + + Spacer(minLength: 0) + } + } +} \ No newline at end of file diff --git a/apps/ios/Koins/Wallet/MoneroService.swift b/apps/ios/Koins/Wallet/MoneroService.swift new file mode 100644 index 0000000..aa80fa8 --- /dev/null +++ b/apps/ios/Koins/Wallet/MoneroService.swift @@ -0,0 +1,224 @@ +import Foundation + +/// Monero amounts are atomic units (1 XMR = 1e12 piconero). +enum MoneroAmount { + static let atomicPerXMR: UInt64 = 1_000_000_000_000 + + static func xmr(fromAtomic value: String) -> String { + guard let atomic = UInt64(value) else { return "0" } + return xmr(fromAtomic: atomic) + } + + static func xmr(fromAtomic value: UInt64) -> String { + let whole = value / atomicPerXMR + let fraction = value % atomicPerXMR + if fraction == 0 { return String(whole) } + var text = String(format: "%012llu", fraction) + while text.hasSuffix("0") { text.removeLast() } + return "\(whole).\(text)" + } + + static func atomic(fromXMR value: String) throws -> UInt64 { + let text = value.trimmingCharacters(in: .whitespacesAndNewlines) + guard !text.isEmpty, !text.hasPrefix("-") else { throw XRPError.rpc("Enter a valid amount") } + let parts = text.split(separator: ".", omittingEmptySubsequences: false) + guard parts.count <= 2 else { throw XRPError.rpc("Enter a valid amount") } + guard let whole = UInt64(parts[0].isEmpty ? "0" : String(parts[0])) else { + throw XRPError.rpc("Enter a valid amount") + } + var fraction: UInt64 = 0 + if parts.count == 2 { + let fractionText = String(parts[1]) + guard fractionText.count <= 12, fractionText.allSatisfy({ $0.isNumber }) else { + throw XRPError.rpc("XMR supports at most 12 decimal places") + } + let padded = fractionText.padding(toLength: 12, withPad: "0", startingAt: 0) + guard let parsed = UInt64(padded) else { throw XRPError.rpc("Enter a valid amount") } + fraction = parsed + } + let (wholeAtomic, overflow) = whole.multipliedReportingOverflow(by: atomicPerXMR) + guard !overflow else { throw XRPError.rpc("Amount is too large") } + let (total, carry) = wholeAtomic.addingReportingOverflow(fraction) + guard !carry else { throw XRPError.rpc("Amount is too large") } + return total + } +} + +/// Swift wrapper around the Objective-C++ monero-cpp bridge. +final class MoneroService { + static let shared = MoneroService() + + /// Wallet files that exist on disk without a stored record, so a lost + /// preference file does not strand a wallet. + static func orphanedWalletNames() -> [String] { + let manager = FileManager.default + guard let documents = manager.urls(for: .documentDirectory, in: .userDomainMask).first, + let items = try? manager.contentsOfDirectory(at: documents, + includingPropertiesForKeys: nil) else { + return [] + } + let keys = Set(items.filter { $0.pathExtension == "keys" } + .map { $0.deletingPathExtension().lastPathComponent }) + return items + .filter { $0.pathExtension.isEmpty && !$0.lastPathComponent.hasPrefix(".") } + .map { $0.lastPathComponent } + .filter { keys.contains($0) } + .sorted() + } + + /// The wallet file is encrypted with this password; the recovery seed lives + /// in the Keychain. + static let walletPassword = "koins" + + /// Default daemon, kept in sync with the bridge. Used for the cheap + /// daemon-only queries that do not need an open wallet. + static let daemonHost = "xmr-node.cakewallet.com" + + /// Current chain tip, so an import can start scanning from now instead of + /// from the genesis block. + func daemonHeight() async throws -> UInt64 { + guard let url = URL(string: "http://\(Self.daemonHost):18081/json_rpc") else { + throw XRPError.network("Invalid Monero daemon address") + } + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.timeoutInterval = 15 + request.httpBody = try JSONSerialization.data(withJSONObject: [ + "jsonrpc": "2.0", + "id": "koins", + "method": "get_info", + ]) + + let (data, response) = try await URLSession.shared.data(for: request) + guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else { + throw XRPError.network("Monero daemon is unreachable") + } + guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any], + let result = json["result"] as? [String: Any], + let height = (result["height"] as? NSNumber)?.uint64Value else { + throw XRPError.network("Could not read the Monero chain tip") + } + return height + } + + private func invoke(_ request: [String: Any]) async throws -> [String: Any] { + let json = try JSONSerialization.data(withJSONObject: request) + let requestString = String(data: json, encoding: .utf8) ?? "{}" + + let responseString: String = await withCheckedContinuation { continuation in + KoinsMoneroWallet.invoke(requestString) { response in + continuation.resume(returning: response) + } + } + + guard let data = responseString.data(using: .utf8), + let response = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { + throw XRPError.rpc("Unexpected response from the Monero wallet") + } + if let error = response["error"] as? String, !error.isEmpty { + throw XRPError.rpc(error) + } + return response + } + + // MARK: - Wallet lifecycle + + func createWallet(name: String) async throws -> (address: String, mnemonic: String) { + let response = try await invoke(["op": "createWallet", + "name": name, + "password": Self.walletPassword, + "language": "English"]) + return (response["address"] as? String ?? "", response["mnemonic"] as? String ?? "") + } + + func openWallet(name: String) async throws -> String { + let response = try await invoke(["op": "openWallet", + "name": name, + "password": Self.walletPassword]) + return response["address"] as? String ?? "" + } + + func restoreWallet(name: String, + seed: String, + restoreHeight: UInt64) async throws -> String { + let response = try await invoke(["op": "restoreWallet", + "name": name, + "password": Self.walletPassword, + "seed": seed, + "restoreHeight": restoreHeight]) + return response["address"] as? String ?? "" + } + + func removeWallet(name: String) async { + _ = try? await invoke(["op": "removeWallet", "name": name]) + } + + func closeWallet() async { + _ = try? await invoke(["op": "closeWallet"]) + } + + func seed() async throws -> String { + let response = try await invoke(["op": "getSeed"]) + return response["mnemonic"] as? String ?? "" + } + + // MARK: - Reads + + func balance() async throws -> Balance { + let response = try await invoke(["op": "getBalance"]) + let total = response["balance"] as? String ?? "0" + let unlocked = response["unlocked"] as? String ?? total + return Balance(total: MoneroAmount.xmr(fromAtomic: total), + unlocked: MoneroAmount.xmr(fromAtomic: unlocked), + funded: (UInt64(total) ?? 0) > 0) + } + + func transactions() async throws -> [TransactionRecord] { + let response = try await invoke(["op": "getTransactions"]) + let entries = response["transactions"] as? [[String: Any]] ?? [] + return entries.map { entry in + let timestamp = (entry["timestamp"] as? NSNumber)?.doubleValue ?? 0 + return TransactionRecord( + id: entry["hash"] as? String ?? UUID().uuidString, + amount: MoneroAmount.xmr(fromAtomic: entry["amount"] as? String ?? "0"), + timestamp: timestamp > 0 ? Date(timeIntervalSince1970: timestamp) : nil, + direction: (entry["direction"] as? String) == "in" ? .incoming : .outgoing, + confirmed: (entry["confirmed"] as? Bool) ?? false, + fee: MoneroAmount.xmr(fromAtomic: entry["fee"] as? String ?? "0"), + tag: nil, + counterparty: nil) + } + } + + // MARK: - Sync + + func startSync() async throws -> Bool { + let response = try await invoke(["op": "startSync"]) + return (response["syncing"] as? Bool) ?? false + } + + func stopSync() async { + _ = try? await invoke(["op": "stopSync"]) + } + + func syncStatus() async throws -> SyncStatus { + let response = try await invoke(["op": "getSyncStatus"]) + return SyncStatus(height: (response["height"] as? NSNumber)?.uint64Value ?? 0, + daemonHeight: (response["daemonHeight"] as? NSNumber)?.uint64Value ?? 0, + syncing: (response["syncing"] as? Bool) ?? false, + connected: (response["connected"] as? Bool) ?? false) + } + + // MARK: - Sends + + func send(address: String, amountXMR: String, priority: TransactionPriority) async throws -> (hash: String, fee: String) { + let atomic = try MoneroAmount.atomic(fromXMR: amountXMR) + let response = try await invoke(["op": "transferTo", + "address": address.trimmingCharacters(in: .whitespacesAndNewlines), + "amount": String(atomic), + "priority": priority.rawValue]) + return (response["txHash"] as? String ?? "", + MoneroAmount.xmr(fromAtomic: response["fee"] as? String ?? "0")) + } +} \ No newline at end of file diff --git a/apps/ios/Koins/Wallet/XRPAmount.swift b/apps/ios/Koins/Wallet/XRPAmount.swift new file mode 100644 index 0000000..c966ed5 --- /dev/null +++ b/apps/ios/Koins/Wallet/XRPAmount.swift @@ -0,0 +1,53 @@ +import Foundation + +/// Exact decimal <-> drops conversion (1 XRP = 1_000_000 drops) without +/// floating point rounding errors. +enum XRPAmount { + static let dropsPerXRP: UInt64 = 1_000_000 + static let maxDrops: UInt64 = 100_000_000_000 * dropsPerXRP // 1e11 XRP + + static func drops(fromXRP value: String) throws -> UInt64 { + let text = value.trimmingCharacters(in: .whitespacesAndNewlines) + guard !text.isEmpty, !text.hasPrefix("-"), !text.hasPrefix("+") else { + throw XRPError.rpc("Enter a valid amount") + } + let parts = text.split(separator: ".", omittingEmptySubsequences: false) + guard parts.count <= 2 else { throw XRPError.rpc("Enter a valid amount") } + + let wholeText = parts[0].isEmpty ? "0" : String(parts[0]) + guard let whole = UInt64(wholeText) else { throw XRPError.rpc("Enter a valid amount") } + + var fraction: UInt64 = 0 + if parts.count == 2 { + let fractionText = String(parts[1]) + guard fractionText.count <= 6, fractionText.allSatisfy({ $0.isNumber }) else { + throw XRPError.rpc("XRP supports at most 6 decimal places") + } + let padded = fractionText.padding(toLength: 6, withPad: "0", startingAt: 0) + guard let parsed = UInt64(padded) else { throw XRPError.rpc("Enter a valid amount") } + fraction = parsed + } + + let (wholeDrops, overflow) = whole.multipliedReportingOverflow(by: dropsPerXRP) + guard !overflow else { throw XRPError.rpc("Amount is too large") } + let (total, carry) = wholeDrops.addingReportingOverflow(fraction) + guard !carry, total <= maxDrops else { throw XRPError.rpc("Amount is too large") } + return total + } + + static func xrp(fromDrops drops: String) -> String { + guard let value = UInt64(drops.trimmingCharacters(in: .whitespacesAndNewlines)) else { + return "0" + } + return xrp(fromDrops: value) + } + + static func xrp(fromDrops value: UInt64) -> String { + let whole = value / dropsPerXRP + let fraction = value % dropsPerXRP + if fraction == 0 { return String(whole) } + var fractionText = String(format: "%06llu", fraction) + while fractionText.hasSuffix("0") { fractionText.removeLast() } + return "\(whole).\(fractionText)" + } +} \ No newline at end of file diff --git a/apps/ios/Koins/Wallet/XRPBinaryCodec.swift b/apps/ios/Koins/Wallet/XRPBinaryCodec.swift new file mode 100644 index 0000000..483ac0c --- /dev/null +++ b/apps/ios/Koins/Wallet/XRPBinaryCodec.swift @@ -0,0 +1,136 @@ +import Foundation + +/// Canonical XRP Ledger binary serialization for the Payment transactions this +/// wallet sends, plus the signing / transaction-id hashing around it. +struct XRPPayment { + var account: String + var destination: String + var amountDrops: UInt64 + var feeDrops: UInt64 + var sequence: UInt32 + var lastLedgerSequence: UInt32? + var destinationTag: UInt32? + var signingPublicKey: [UInt8] + var signature: [UInt8]? + + private static let typeUInt16: UInt8 = 1 + private static let typeUInt32: UInt8 = 2 + private static let typeAmount: UInt8 = 6 + private static let typeBlob: UInt8 = 7 + private static let typeAccountID: UInt8 = 8 + + func serialized(includingSignature: Bool) throws -> [UInt8] { + var output: [UInt8] = [] + + // Fields must appear in (type, field) order. + output += header(type: Self.typeUInt16, field: 2) // TransactionType + output += uint16(0) // ttPAYMENT + + output += header(type: Self.typeUInt32, field: 2) // Flags + output += uint32(0) + + output += header(type: Self.typeUInt32, field: 4) // Sequence + output += uint32(sequence) + + if let tag = destinationTag { + output += header(type: Self.typeUInt32, field: 14) // DestinationTag + output += uint32(tag) + } + + if let lastLedger = lastLedgerSequence { + output += header(type: Self.typeUInt32, field: 27) // LastLedgerSequence + output += uint32(lastLedger) + } + + output += header(type: Self.typeAmount, field: 1) // Amount + output += try amount(amountDrops) + + output += header(type: Self.typeAmount, field: 8) // Fee + output += try amount(feeDrops) + + output += header(type: Self.typeBlob, field: 3) // SigningPubKey + output += variableLength(signingPublicKey) + + if includingSignature, let signature { + output += header(type: Self.typeBlob, field: 4) // TxnSignature + output += variableLength(signature) + } + + output += header(type: Self.typeAccountID, field: 1) // Account + output += variableLength(try XRPKey.accountId(fromAddress: account)) + + output += header(type: Self.typeAccountID, field: 3) // Destination + output += variableLength(try XRPKey.accountId(fromAddress: destination)) + + return output + } + + /// Bytes the private key signs (HashPrefix.transactionSig + tx). + func signingPayload() throws -> [UInt8] { + [0x53, 0x54, 0x58, 0x00] + (try serialized(includingSignature: false)) + } + + /// The signed transaction blob submitted to the network (no hash prefix). + func txBlob() throws -> [UInt8] { + guard signature != nil else { throw XRPError.rpc("Transaction is not signed") } + return try serialized(includingSignature: true) + } + + func transactionHash() throws -> [UInt8] { + guard signature != nil else { throw XRPError.rpc("Transaction is not signed") } + let blob = try serialized(includingSignature: true) + return XRPHash.sha512Half([0x54, 0x58, 0x4e, 0x00] + blob) + } + + // MARK: - Encoding helpers + + private func header(type: UInt8, field: UInt8) -> [UInt8] { + if field < 16 { return [(type << 4) | field] } + return [type << 4, field] + } + + private func uint16(_ value: UInt16) -> [UInt8] { + [UInt8(value >> 8), UInt8(value & 0xff)] + } + + private func uint32(_ value: UInt32) -> [UInt8] { + [ + UInt8((value >> 24) & 0xff), + UInt8((value >> 16) & 0xff), + UInt8((value >> 8) & 0xff), + UInt8(value & 0xff), + ] + } + + /// Native (XRP) amount: 8 bytes big-endian drops with the XRP flag bit set. + private func amount(_ drops: UInt64) throws -> [UInt8] { + guard drops <= XRPAmount.maxDrops else { throw XRPError.rpc("Amount is too large") } + var bytes = uint64(drops) + bytes[0] |= 0x40 + return bytes + } + + private func uint64(_ value: UInt64) -> [UInt8] { + var bytes: [UInt8] = [] + for shift in stride(from: 56, through: 0, by: -8) { + bytes.append(UInt8((value >> UInt64(shift)) & 0xff)) + } + return bytes + } + + private func variableLength(_ bytes: [UInt8]) -> [UInt8] { + var output: [UInt8] = [] + let count = bytes.count + if count < 192 { + output.append(UInt8(count)) + } else if count <= 12_480 { + output.append(UInt8((count >> 8) + 192)) + output.append(UInt8(count & 0xff)) + } else { + output.append(UInt8((count >> 16) + 240)) + output.append(UInt8((count >> 8) & 0xff)) + output.append(UInt8(count & 0xff)) + } + return output + bytes + } +} \ No newline at end of file diff --git a/apps/ios/Koins/Wallet/XRPClient.swift b/apps/ios/Koins/Wallet/XRPClient.swift new file mode 100644 index 0000000..8091cf0 --- /dev/null +++ b/apps/ios/Koins/Wallet/XRPClient.swift @@ -0,0 +1,72 @@ +import Foundation + +/// Minimal XRP Ledger JSON-RPC client. A couple of public endpoints are tried +/// in order so a single outage does not break the wallet. +final class XRPClient { + static let shared = XRPClient() + + private let endpoints = [ + "https://s1.ripple.com:51234/", + "https://s2.ripple.com:51234/", + "https://xrplcluster.com/", + ] + + private var preferredEndpoint = 0 + private let session: URLSession + + private init() { + let configuration = URLSessionConfiguration.ephemeral + configuration.timeoutIntervalForRequest = 20 + configuration.waitsForConnectivity = false + session = URLSession(configuration: configuration) + } + + func rpc(_ method: String, _ params: [String: Any] = [:]) async throws -> [String: Any] { + var lastError: Error = XRPError.network("XRP network is unavailable") + for attempt in 0.. [String: Any] { + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + let body: [String: Any] = ["method": method, "params": [params], "id": 1] + request.httpBody = try JSONSerialization.data(withJSONObject: body) + + let (data, response) = try await session.data(for: request) + guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else { + throw XRPError.network("Ledger request failed (\(method))") + } + guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else { + throw XRPError.network("Malformed ledger response") + } + + let result = json["result"] as? [String: Any] ?? [:] + if let error = result["error"] as? String { + let message = result["error_message"] as? String ?? error + throw XRPError.rpc(message) + } + if let error = json["error"] as? String { + throw XRPError.rpc(json["error_message"] as? String ?? error) + } + return result + } + + static func isAccountNotFound(_ error: Error) -> Bool { + if case XRPError.rpc(let message) = error { + return message == "actNotFound" || message.contains("Account not found") + } + return false + } +} \ No newline at end of file diff --git a/apps/ios/Koins/Wallet/XRPKey.swift b/apps/ios/Koins/Wallet/XRPKey.swift new file mode 100644 index 0000000..8fc8dde --- /dev/null +++ b/apps/ios/Koins/Wallet/XRPKey.swift @@ -0,0 +1,148 @@ +import CryptoKit +import Foundation + +enum XRPAlgorithm: String { + case ed25519 + case secp256k1 +} + +/// Set by the app target so this file stays compilable outside iOS. +enum Secp256k1Backend { + static var derivePrivateKey: ((Data) -> Data?)? + static var publicKey: ((Data) -> Data?)? + static var sign: ((Data, Data) -> Data?)? + static var addScalar: ((Data, Data) -> Data?)? +} + +struct XRPKeypair { + let algorithm: XRPAlgorithm + /// ed25519: 32-byte seed. secp256k1: 32-byte scalar. + let privateKey: [UInt8] + /// ed25519: 0xED + 32-byte key. secp256k1: 33-byte compressed point. + let publicKey: [UInt8] + + var address: String { XRPKey.address(fromPublicKey: publicKey) } + + var signingPublicKeyHex: String { Hex.encode(publicKey) } +} + +enum XRPKey { + static let ed25519SeedVersion: [UInt8] = [0x01, 0xe1, 0x4b] + static let familySeedVersion: [UInt8] = [0x21] + static let accountIdVersion: [UInt8] = [0x00] + static let ed25519Prefix: UInt8 = 0xED + + // MARK: - Seeds + + static func generateSeed(algorithm: XRPAlgorithm = .ed25519) -> String { + var entropy = [UInt8](repeating: 0, count: 16) + for index in entropy.indices { entropy[index] = UInt8.random(in: 0...255) } + let version = algorithm == .ed25519 ? ed25519SeedVersion : familySeedVersion + return XRPBase58.checkEncode(version: version, payload: entropy) + } + + static func decodeSeed(_ seed: String) throws -> (entropy: [UInt8], algorithm: XRPAlgorithm) { + let decoded = try XRPBase58.decode(seed.trimmingCharacters(in: .whitespacesAndNewlines)) + guard decoded.count > 4 else { throw XRPError.invalidSeed } + let body = Array(decoded[0..<(decoded.count - 4)]) + let checksum = Array(decoded[(decoded.count - 4)...]) + guard XRPHash.checksum(body) == checksum else { throw XRPError.invalidSeed } + + if body.count == ed25519SeedVersion.count + 16, Array(body.prefix(3)) == ed25519SeedVersion { + return (Array(body.dropFirst(3)), .ed25519) + } + if body.count == familySeedVersion.count + 16, body[0] == familySeedVersion[0] { + return (Array(body.dropFirst(1)), .secp256k1) + } + throw XRPError.unsupportedSeed + } + + // MARK: - Keys + + /// Recovery phrase for a new wallet: 12 BIP-39 words. + static func generateMnemonic() -> String { + BIP39.generateMnemonic() + } + + /// Accepts either a 12 word BIP-39 recovery phrase or a legacy XRP family + /// seed (`sEd…` / `s…`). + static func keypair(fromSecret secret: String) throws -> XRPKeypair { + let trimmed = secret.trimmingCharacters(in: .whitespacesAndNewlines) + if BIP39.isMnemonic(trimmed) { + return try keypair(fromMnemonic: trimmed) + } + return try keypair(fromSeed: trimmed) + } + + /// Derives the account at `m/44'/144'/0'/0/0`, the path hardware and mobile + /// wallets use for XRP. + static func keypair(fromMnemonic mnemonic: String) throws -> XRPKeypair { + guard BIP39.entropy(from: mnemonic) != nil else { + throw XRPError.invalidSeed + } + let seed = BIP39.seed(from: mnemonic) + guard let master = BIP32.masterKey(fromSeed: seed), + let derived = BIP32.derive(BIP32.xrpPath, from: master) else { + throw XRPError.unsupportedSeed + } + return XRPKeypair(algorithm: .secp256k1, + privateKey: derived.privateKey, + publicKey: derived.publicKey) + } + + static func keypair(fromSeed seed: String) throws -> XRPKeypair { + let (entropy, algorithm) = try decodeSeed(seed) + switch algorithm { + case .ed25519: + let raw = XRPHash.sha512Half(entropy) + let privateKey = try Curve25519.Signing.PrivateKey(rawRepresentation: Data(raw)) + let publicKey = [ed25519Prefix] + Array(privateKey.publicKey.rawRepresentation) + return XRPKeypair(algorithm: .ed25519, privateKey: raw, publicKey: publicKey) + case .secp256k1: + guard let derive = Secp256k1Backend.derivePrivateKey, + let toPublic = Secp256k1Backend.publicKey, + let privateKey = derive(Data(entropy)), + let publicKey = toPublic(privateKey) else { + throw XRPError.unsupportedSeed + } + return XRPKeypair(algorithm: .secp256k1, + privateKey: Array(privateKey), + publicKey: Array(publicKey)) + } + } + + static func address(fromPublicKey publicKey: [UInt8]) -> String { + let accountId = XRPHash.ripemd160(XRPHash.sha256(publicKey)) + return XRPBase58.checkEncode(version: accountIdVersion, payload: accountId) + } + + static func isValidAddress(_ address: String) -> Bool { + (try? XRPBase58.checkDecode(address, versionLength: 1)) != nil + } + + static func accountId(fromAddress address: String) throws -> [UInt8] { + let payload = try XRPBase58.checkDecode(address, versionLength: 1) + guard payload.count == 20 else { throw XRPError.invalidAddress } + return payload + } + + // MARK: - Signing + + /// ed25519 returns a 64-byte signature; secp256k1 a DER-encoded one, matching + /// what the XRP Ledger expects for each signing scheme. + static func sign(message: [UInt8], keypair: XRPKeypair) throws -> [UInt8] { + switch keypair.algorithm { + case .ed25519: + let privateKey = try Curve25519.Signing.PrivateKey(rawRepresentation: Data(keypair.privateKey)) + let signature = try privateKey.signature(for: Data(message)) + return Array(signature) + case .secp256k1: + let digest = XRPHash.sha512Half(message) + guard let signFn = Secp256k1Backend.sign, + let signature = signFn(Data(digest), Data(keypair.privateKey)) else { + throw XRPError.unsupportedSeed + } + return Array(signature) + } + } +} \ No newline at end of file diff --git a/apps/ios/Koins/Wallet/XRPWalletService.swift b/apps/ios/Koins/Wallet/XRPWalletService.swift new file mode 100644 index 0000000..9ac82e3 --- /dev/null +++ b/apps/ios/Koins/Wallet/XRPWalletService.swift @@ -0,0 +1,157 @@ +import Foundation + +/// High level XRP operations: wallet creation/import, balances, history, sends. +final class XRPWalletService { + static let shared = XRPWalletService() + + private let rippleEpoch: TimeInterval = 946_684_800 + + // MARK: - Wallet lifecycle + + func createWallet(name: String) throws -> (WalletRecord, String) { + let mnemonic = XRPKey.generateMnemonic() + let keypair = try XRPKey.keypair(fromMnemonic: mnemonic) + let record = WalletRecord(id: "xrp_\(Int(Date().timeIntervalSince1970))", + name: name, + address: keypair.address, + createdAt: Date()) + WalletStore.saveWallet(record, chain: .xrp) + WalletStore.saveSecret(mnemonic, chain: .xrp) + return (record, mnemonic) + } + + func importWallet(name: String, secret: String) throws -> WalletRecord { + let trimmed = secret.trimmingCharacters(in: .whitespacesAndNewlines) + let keypair = try XRPKey.keypair(fromSecret: trimmed) + let record = WalletRecord(id: "xrp_\(Int(Date().timeIntervalSince1970))", + name: name, + address: keypair.address, + createdAt: Date()) + WalletStore.saveWallet(record, chain: .xrp) + WalletStore.saveSecret(trimmed, chain: .xrp) + return record + } + + func address(forSecret secret: String) throws -> String { + try XRPKey.keypair(fromSecret: secret.trimmingCharacters(in: .whitespacesAndNewlines)).address + } + + // MARK: - Reads + + func balance(address: String) async throws -> Balance { + do { + let result = try await XRPClient.shared.rpc("account_info", + ["account": address, + "ledger_index": "current"]) + let accountData = result["account_data"] as? [String: Any] ?? [:] + let drops = accountData["Balance"] as? String ?? "0" + return Balance(total: XRPAmount.xrp(fromDrops: drops), unlocked: "", funded: true) + } catch { + if XRPClient.isAccountNotFound(error) { + return Balance(total: "0", unlocked: "", funded: false) + } + throw error + } + } + + func transactions(address: String) async throws -> [TransactionRecord] { + let result = try await XRPClient.shared.rpc("account_tx", + ["account": address, + "ledger_index_min": -1, + "ledger_index_max": -1, + "limit": 40]) + let entries = result["transactions"] as? [[String: Any]] ?? [] + var records: [TransactionRecord] = [] + + for entry in entries { + guard let tx = entry["tx_json"] as? [String: Any] ?? entry["tx"] as? [String: Any] else { continue } + guard (tx["TransactionType"] as? String) == "Payment" else { continue } + let meta = entry["meta"] as? [String: Any] ?? entry["metaData"] as? [String: Any] ?? [:] + if let txResult = meta["TransactionResult"] as? String, txResult != "tesSUCCESS" { continue } + + // IOU / MPT deliveries carry an object; only native XRP is shown. + let delivered = (meta["delivered_amount"] as? String) + ?? (meta["DeliveredAmount"] as? String) + ?? (tx["Amount"] as? String) + guard let deliveredDrops = delivered else { continue } + + let destination = tx["Destination"] as? String ?? "" + let isIncoming = destination == address + let date = (tx["date"] as? NSNumber).map { Date(timeIntervalSince1970: $0.doubleValue + rippleEpoch) } + + records.append(TransactionRecord( + id: entry["hash"] as? String ?? tx["hash"] as? String ?? UUID().uuidString, + amount: XRPAmount.xrp(fromDrops: deliveredDrops), + timestamp: date, + direction: isIncoming ? .incoming : .outgoing, + confirmed: (entry["validated"] as? Bool) ?? false, + fee: (tx["Fee"] as? String).map { XRPAmount.xrp(fromDrops: $0) }, + tag: (tx["DestinationTag"] as? NSNumber)?.intValue, + counterparty: isIncoming ? (tx["Account"] as? String) : destination)) + } + return records + } + + // MARK: - Sends + + func send(secret: String, + to: String, + amountXRP: String, + destinationTag: UInt32?) async throws -> (hash: String, fee: String) { + let destination = to.trimmingCharacters(in: .whitespacesAndNewlines) + guard XRPKey.isValidAddress(destination) else { + throw XRPError.rpc("Enter a valid XRP address") + } + + let keypair = try XRPKey.keypair(fromSeed: secret) + let amountDrops = try XRPAmount.drops(fromXRP: amountXRP) + + async let infoCall = XRPClient.shared.rpc("account_info", + ["account": keypair.address, + "ledger_index": "current"]) + async let feeCall = XRPClient.shared.rpc("fee", [:]) + async let ledgerCall = XRPClient.shared.rpc("ledger_current", [:]) + + let info = try await infoCall + let accountData = info["account_data"] as? [String: Any] ?? [:] + guard let sequence = (accountData["Sequence"] as? NSNumber)?.uint32Value else { + throw XRPError.rpc("Could not read the account sequence") + } + let balanceDrops = UInt64((accountData["Balance"] as? String) ?? "0") ?? 0 + + let feeResult = try await feeCall + let feeDrops = (feeResult["drops"] as? [String: Any]) + .flatMap { $0["open_ledger_fee"] as? String } + .flatMap { UInt64($0) } ?? 12 + + let ledger = try await ledgerCall + let ledgerIndex = (ledger["ledger_current_index"] as? NSNumber)?.uint32Value + + guard amountDrops + feeDrops <= balanceDrops else { + throw XRPError.rpc("Insufficient balance to cover the amount and network fee") + } + + var payment = XRPPayment(account: keypair.address, + destination: destination, + amountDrops: amountDrops, + feeDrops: feeDrops, + sequence: sequence, + lastLedgerSequence: ledgerIndex.map { $0 + 20 }, + destinationTag: destinationTag, + signingPublicKey: keypair.publicKey, + signature: nil) + + let signature = try XRPKey.sign(message: payment.signingPayload(), keypair: keypair) + payment.signature = signature + + let blob = Hex.encode(try payment.txBlob()) + let result = try await XRPClient.shared.rpc("submit", ["tx_blob": blob]) + let engineResult = result["engine_result"] as? String ?? "" + guard engineResult.hasPrefix("tes") else { + let message = result["engine_result_message"] as? String ?? "Transaction failed" + throw XRPError.rpc("\(engineResult): \(message)") + } + + return (Hex.encode(try payment.transactionHash()), XRPAmount.xrp(fromDrops: feeDrops)) + } +} \ No newline at end of file diff --git a/apps/ios/README.md b/apps/ios/README.md new file mode 100644 index 0000000..8488298 --- /dev/null +++ b/apps/ios/README.md @@ -0,0 +1,91 @@ +# Koins iOS + +Native SwiftUI wallet for Monero and XRP. iOS only, no JavaScript runtime. + +## Layout + +``` +Koins/ + KoinsApp.swift App entry point + AppModel.swift Observable state, orchestrates both chains + Models.swift Chain / wallet / transaction models + SelfTest.swift Launch-time verification (see below) + Bridge/ + Koins-Bridging-Header.h + KoinsMoneroWallet.h/.mm ObjC++ bridge to the monero-cpp wallet + KoinsSecp256k1.h/.mm secp256k1 via the OpenSSL bundled in the monero lib + Monero/ + libs/ libkoins_monero_merged.a (device + simulator arm64) + include/ monero-cpp headers + Support/ Base58, RIPEMD-160, hashing, Keychain, wallet store + Wallet/ XRP keys, binary codec, RPC client, services + Views/ SwiftUI screens +``` + +## How the chains are implemented + +- **Monero**: the wallet runs on device through `libkoins_monero_merged.a` + (monero-project `wallet2` + monero-cpp, built for `aarch64-apple-ios` and the + arm64 simulator). `KoinsMoneroWallet` serialises all C++ work on a private + queue and exchanges JSON with Swift. Wallet files live in the app's Documents + directory; the recovery seed is stored in the Keychain. +- **XRP**: implemented directly in Swift, no third-party package. + - Recovery phrase: a 12 word BIP-39 mnemonic, derived to the standard XRP + account path `m/44'/144'/0'/0/0` (BIP-32, secp256k1), so the same words + restore the same account in hardware and other mobile wallets. + - Imports also accept legacy XRP family seeds (`sEd…` / `s…`), including the + Ed25519 and secp256k1 variants. + - Key derivation and signing: Ed25519 via CryptoKit and secp256k1 via the + OpenSSL build embedded in the monero static library. + - Transaction serialization: canonical XRP binary encoding plus the + `STX`/`TXN` hash prefixes, matching `ripple-binary-codec`. + - Network: JSON-RPC against public XRP Ledger endpoints with failover. + +## Build + +```sh +cd apps/ios +xcodegen generate +xcodebuild -project Koins.xcodeproj -scheme Koins -configuration Debug \ + -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' \ + ARCHS=arm64 ONLY_ACTIVE_ARCH=NO -derivedDataPath /tmp/koins-dd build +``` + +Install and run on a booted simulator: + +```sh +xcrun simctl install /tmp/koins-dd/Build/Products/Debug-iphonesimulator/Koins.app +xcrun simctl launch com.koins.app +``` + +Re-run `xcodegen generate` after adding or removing source files. No CocoaPods +and no Swift packages are used. + +## Verification + +The XRP implementation is checked against vectors produced by the reference +JavaScript stack (`xrpl`, `ripple-keypairs`, `ripple-binary-codec`). + +Cryptography and serialization (no network): + +```sh +xcrun simctl launch com.koins.app -koinsSelfTest +xcrun simctl spawn log show --last 1m --predicate 'subsystem == "com.koins.app"' --style compact +``` + +Live network check (creates and then removes a temporary wallet on both chains, +talks to a Monero daemon and the XRP Ledger): + +```sh +xcrun simctl launch com.koins.app -koinsSelfTestNetwork +``` + +## Notes + +- The Monero daemon default is `xmr-node.cakewallet.com:18081` with two + fallbacks; `MoneroService` reconnects on demand. +- Ed25519 and OpenSSL ECDSA both use randomised nonces, so signatures are valid + but not byte-identical to deterministic implementations such as + `ripple-keypairs`. Signatures are verified rather than compared. +- Refresh the simulator build when `/tmp` is cleared: the intermediate build + products live in `/tmp/koins-dd`. \ No newline at end of file diff --git a/apps/ios/project.yml b/apps/ios/project.yml new file mode 100644 index 0000000..48ca4a2 --- /dev/null +++ b/apps/ios/project.yml @@ -0,0 +1,70 @@ +name: Koins +options: + bundleIdPrefix: com.koins + deploymentTarget: + iOS: "16.0" + xcodeVersion: "16.0" + +settings: + base: + ENABLE_USER_SCRIPT_SANDBOXING: NO + CLANG_CXX_LANGUAGE_STANDARD: "c++14" + GCC_TREAT_WARNINGS_AS_ERRORS: NO + CLANG_CXX_LIBRARY: "libc++" + SWIFT_VERSION: "5.0" + +targets: + Koins: + type: application + platform: iOS + sources: + - Koins + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: com.koins.app + INFOPLIST_FILE: Koins/Info.plist + CODE_SIGN_STYLE: Automatic + CODE_SIGNING_ALLOWED: NO + SWIFT_OBJC_BRIDGING_HEADER: Koins/Bridge/Koins-Bridging-Header.h + OTHER_LDFLAGS: + - "$(inherited)" + - "-lkoins_monero_merged" + LIBRARY_SEARCH_PATHS: + - "$(SRCROOT)/Koins/Monero/libs" + HEADER_SEARCH_PATHS: + - "$(SRCROOT)/Koins/Monero/include/monero-cpp-src" + - "$(SRCROOT)/Koins/Monero/include/monero-cpp-src/wallet" + - "$(SRCROOT)/Koins/Monero/include/monero-cpp-src/common" + - "$(SRCROOT)/Koins/Monero/include/monero-cpp-src/utils" + - "$(SRCROOT)/Koins/Monero/include/monero-cpp-src/daemon" + - "$(SRCROOT)/../../build/monero-cpp/external/monero-project/src" + - "$(SRCROOT)/../../build/monero-cpp/external/monero-project/contrib" + - "$(SRCROOT)/../../build/monero-cpp/external/monero-project/external/rapidjson/include" + - "$(SRCROOT)/../../build/monero-cpp/external/monero-project/contrib/epee/include" + - "$(SRCROOT)/../../build/monero-cpp/external/monero-project/external/easylogging++" + - "$(SRCROOT)/../../build/monero-cpp/external/monero-project/external/boost" + - "$(SRCROOT)/../../build/monero-cpp/external/monero-project/contrib/depends/aarch64-apple-ios/include" + OTHER_CPLUSPLUSFLAGS: + - "$(inherited)" + - "-DKOINS_MONERO_NATIVE=1" + - "-DBOOST_UUID_DISABLE_ALIGNMENT" + GCC_PREPROCESSOR_DEFINITIONS: + - "$(inherited)" + - "KOINS_MONERO_NATIVE=1" + info: + path: Koins/Info.plist + properties: + UILaunchScreen: {} + UISupportedInterfaceOrientations: + - UIInterfaceOrientationPortrait + UIUserInterfaceStyle: Dark + NSAppTransportSecurity: + NSAllowsArbitraryLoads: true + +schemes: + Koins: + build: + targets: + Koins: all + run: + config: Debug diff --git a/apps/mobile/.gitignore b/apps/mobile/.gitignore deleted file mode 100644 index 2381db7..0000000 --- a/apps/mobile/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -# Local -.DS_Store -*.local -*.log* - -# Dist -node_modules -dist/ - -# IDE -.vscode/* -!.vscode/extensions.json -.idea - -# TypeScript -*.tsbuildinfo diff --git a/apps/mobile/.vscode/extensions.json b/apps/mobile/.vscode/extensions.json deleted file mode 100644 index a7cea0b..0000000 --- a/apps/mobile/.vscode/extensions.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "recommendations": ["Vue.volar"] -} diff --git a/apps/mobile/AGENTS.md b/apps/mobile/AGENTS.md deleted file mode 100644 index 594a82d..0000000 --- a/apps/mobile/AGENTS.md +++ /dev/null @@ -1,24 +0,0 @@ -# AGENTS.md - -You are an expert in JavaScript, Rspeedy, and Vue Lynx application development. You write maintainable, performant, and accessible code. - -## Read in Advance - -- Vue Lynx: [llms.txt](https://vue.lynxjs.org/llms.txt), **REQUIRED**. - While dealing with a Vue Lynx task, an agent **MUST** read this doc because it is an entry point of all available docs about Vue Lynx. - -## Commands - -- `npm run dev` - Start the dev server - -- `npm run build` - Build the app for production - -- `npm run preview` - Preview the production build locally - -- `npm exec rspeedy inspect` - Inspect the Rspeedy config and Rspack config of the project. - -## Related Docs - -- Rsbuild: - -- Rspack: diff --git a/apps/mobile/README.md b/apps/mobile/README.md deleted file mode 100644 index 3391ee5..0000000 --- a/apps/mobile/README.md +++ /dev/null @@ -1,27 +0,0 @@ -## Vue Lynx project - -This is a Vue Lynx project bootstrapped with `create-vue-lynx`. - -## IDE Setup - -This project is pre-configured for [Vue Language Features (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.volar). VS Code will prompt you to install it automatically. - -Volar provides prop completions, type checking, and Lynx-specific diagnostics for `.vue` files. - -## Getting Started - -First, install the dependencies: - -```bash -pnpm install -``` - -Then, run the development server: - -```bash -pnpm run dev -``` - -Scan the QRCode in the terminal with your LynxExplorer App to see the result. - -You can start editing the page by modifying `src/App.vue`. The page auto-updates as you edit the file. diff --git a/apps/mobile/lynx.config.ts b/apps/mobile/lynx.config.ts deleted file mode 100644 index c2fe50c..0000000 --- a/apps/mobile/lynx.config.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { defineConfig } from '@lynx-js/rspeedy' - -import { pluginQRCode } from '@lynx-js/qrcode-rsbuild-plugin' -import { pluginVueLynx } from 'vue-lynx/plugin' -import { pluginTailwindCSS } from 'rsbuild-plugin-tailwindcss' -import { pluginNodePolyfill } from '@rsbuild/plugin-node-polyfill' - -export default defineConfig({ - environments: { - lynx: { - resolve: { - alias: { - // crypto-browserify -> create-hash -> require('crypto') forms a - // circular dependency; webpack resolves createHash to undefined in - // the lynx build. Point create-hash at its browser implementation - // (no require('crypto') cycle). - 'create-hash$': '/Users/tzgyn/Git/koins/node_modules/.bun/create-hash@1.2.0/node_modules/create-hash/browser.js', - }, - }, - }, - web: {}, - }, - plugins: [ - pluginNodePolyfill(), - pluginQRCode({ - schema(url) { - return `${url}?fullscreen=true` - }, - }), - pluginVueLynx({ - optionsApi: false, - enableCSSInlineVariables: true, - enableCSSInheritance: true, - }), - pluginTailwindCSS({ - config: 'tailwind.config.ts', - exclude: [/[\\/]node_modules[\\/]/], - }), - ], -}) diff --git a/apps/mobile/package.json b/apps/mobile/package.json deleted file mode 100644 index a4f10a6..0000000 --- a/apps/mobile/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "mobile", - "version": "0.0.0", - "type": "module", - "scripts": { - "build": "rspeedy build", - "dev": "rspeedy dev", - "preview": "rspeedy preview" - }, - "dependencies": { - "vue": "^3.5.0", - "vue-lynx": "^0.5.1", - "xrpl": "^5.0.0" - }, - "devDependencies": { - "@lynx-js/qrcode-rsbuild-plugin": "^0.4.6", - "@lynx-js/rspeedy": "^0.13.5", - "@lynx-js/tailwind-preset": "^0.5.1", - "@rsbuild/plugin-node-polyfill": "^1.4.6", - "@rsbuild/plugin-vue": "^1.2.6", - "rsbuild-plugin-tailwindcss": "^0.2.4", - "tailwindcss": "3", - "typescript": "^5.0.0" - }, - "engines": { - "node": ">=18" - } -} diff --git a/apps/mobile/postcss.config.js b/apps/mobile/postcss.config.js deleted file mode 100644 index 2841802..0000000 --- a/apps/mobile/postcss.config.js +++ /dev/null @@ -1,5 +0,0 @@ -export default { - plugins: { - tailwindcss: {}, - }, -} diff --git a/apps/mobile/src/App.css b/apps/mobile/src/App.css deleted file mode 100644 index 5b245a9..0000000 --- a/apps/mobile/src/App.css +++ /dev/null @@ -1,29 +0,0 @@ -@tailwind base; -@tailwind utilities; - -:root { - background-color: #0a0a0b; - color: #fafafa; -} - -page { - background-color: #0a0a0b; -} - -input { - background-color: #18181b; - color: #fafafa; - caret-color: #fafafa; -} - -scroll-view { - background-color: transparent; -} - -view { - background-color: transparent; -} - -text { - color: #fafafa; -} diff --git a/apps/mobile/src/App.vue b/apps/mobile/src/App.vue deleted file mode 100644 index c1900bb..0000000 --- a/apps/mobile/src/App.vue +++ /dev/null @@ -1,399 +0,0 @@ - - - diff --git a/apps/mobile/src/index.ts b/apps/mobile/src/index.ts deleted file mode 100644 index 480984e..0000000 --- a/apps/mobile/src/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -import './lib/text-encoder' - -declare const lynxCoreInject: { tt?: Record } | undefined - -// The LynxExplorer SDK calls tt.callDestroyLifetimeFun() when destroying a -// card. vue-lynx's runtime does not define it, which throws during page -// teardown and can break subsequent loads. Provide the expected no-op hook. -if (typeof lynxCoreInject !== 'undefined' && lynxCoreInject?.tt) { - const tt = lynxCoreInject.tt - if (typeof tt.callDestroyLifetimeFun !== 'function') { - tt.callDestroyLifetimeFun = () => {} - } -} - -import { createApp } from 'vue-lynx' - -import App from './App.vue' - -const app = createApp(App) -app.mount() diff --git a/apps/mobile/src/lib/storage.ts b/apps/mobile/src/lib/storage.ts deleted file mode 100644 index 5fab9a6..0000000 --- a/apps/mobile/src/lib/storage.ts +++ /dev/null @@ -1,38 +0,0 @@ -declare const lynx: { - getStorageSync(key: string): string - setStorageSync(key: string, value: string): void - removeStorageSync(key: string): void -} | undefined - -function lynxStorageAvailable(): boolean { - return typeof lynx !== 'undefined' && typeof lynx.getStorageSync === 'function' -} - -export function getItem(key: string): string | null { - try { - const ls = globalThis.localStorage?.getItem(key) - if (ls !== null && ls !== undefined) return ls - } catch {} - try { - if (lynxStorageAvailable()) return lynx!.getStorageSync(key) - } catch {} - return null -} - -export function setItem(key: string, value: string): void { - try { - globalThis.localStorage?.setItem(key, value) - } catch {} - try { - if (lynxStorageAvailable()) lynx!.setStorageSync(key, value) - } catch {} -} - -export function removeItem(key: string): void { - try { - globalThis.localStorage?.removeItem(key) - } catch {} - try { - if (lynxStorageAvailable()) lynx!.removeStorageSync(key) - } catch {} -} diff --git a/apps/mobile/src/lib/text-encoder.ts b/apps/mobile/src/lib/text-encoder.ts deleted file mode 100644 index b9188c6..0000000 --- a/apps/mobile/src/lib/text-encoder.ts +++ /dev/null @@ -1,75 +0,0 @@ -// TextEncoder/TextDecoder polyfill for runtimes that lack them (Lynx PrimJS). -// Needed by @noble/hashes (xrpl crypto stack) and other WASM-adjacent libs. -// Must be imported BEFORE any module that transitively imports xrpl, because -// @noble/curves calls utf8ToBytes at module-evaluation time. -if (typeof globalThis.TextEncoder === 'undefined') { - const utf8Encode = (str: string): Uint8Array => { - const bytes: number[] = [] - for (let i = 0; i < str.length; i++) { - let code = str.charCodeAt(i) - if (code < 0x80) { - bytes.push(code) - } else if (code < 0x800) { - bytes.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f)) - } else if (code >= 0xd800 && code <= 0xdbff && i + 1 < str.length) { - const low = str.charCodeAt(i + 1) - if (low >= 0xdc00 && low <= 0xdfff) { - i++ - code = 0x10000 + ((code - 0xd800) << 10) + (low - 0xdc00) - bytes.push( - 0xf0 | (code >> 18), - 0x80 | ((code >> 12) & 0x3f), - 0x80 | ((code >> 6) & 0x3f), - 0x80 | (code & 0x3f), - ) - } else { - bytes.push(0xef, 0xbf, 0xbd) - } - } else if (code >= 0xdc00 && code <= 0xdfff) { - bytes.push(0xef, 0xbf, 0xbd) - } else { - bytes.push(0xe0 | (code >> 12), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f)) - } - } - return Uint8Array.from(bytes) - } - - globalThis.TextEncoder = class TextEncoder { - encoding = 'utf-8' - encode(input: string = ''): Uint8Array { - return utf8Encode(input) - } - } as unknown as typeof TextEncoder -} - -if (typeof globalThis.TextDecoder === 'undefined') { - globalThis.TextDecoder = class TextDecoder { - encoding = 'utf-8' - decode(bytes?: Uint8Array): string { - if (!bytes) return '' - let result = '' - for (let i = 0; i < bytes.length; i++) { - const b = bytes[i] - if (b < 0x80) { - result += String.fromCharCode(b) - } else if (b < 0xe0 && i + 1 < bytes.length) { - result += String.fromCharCode(((b & 0x1f) << 6) | (bytes[++i] & 0x3f)) - } else if (b < 0xf0 && i + 2 < bytes.length) { - result += String.fromCharCode( - ((b & 0x0f) << 12) | ((bytes[++i] & 0x3f) << 6) | (bytes[++i] & 0x3f), - ) - } else if (i + 3 < bytes.length) { - const cp = - ((b & 0x07) << 18) | - ((bytes[++i] & 0x3f) << 12) | - ((bytes[++i] & 0x3f) << 6) | - (bytes[++i] & 0x3f) - result += String.fromCodePoint(cp) - } - } - return result - } - } as unknown as typeof TextDecoder -} - -export {} diff --git a/apps/mobile/src/lib/xrp.ts b/apps/mobile/src/lib/xrp.ts deleted file mode 100644 index e83088c..0000000 --- a/apps/mobile/src/lib/xrp.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { Wallet, xrpToDrops, dropsToXrp, isValidClassicAddress } from 'xrpl' -import { getItem, setItem, removeItem } from './storage' - -const RPC_URL = 'https://s1.ripple.com:51234/' -const RIPPLE_EPOCH = 946684800 -const WALLET_KEY = 'koins_xrp_wallet' -const SEED_KEY = 'koins_xrp_seed' - -export interface XrpWalletInfo { - id: string - name: string - address: string - createdAt: string -} - -export interface XrpBalance { - balance: string - funded: boolean -} - -export interface XrpTxEntry { - hash: string - amount: string - timestamp: number - direction: 'in' | 'out' - fee?: string - confirmed: boolean - destinationTag?: number -} - -async function rpc(command: string, params: Record): Promise { - const res = await fetch(RPC_URL, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - method: command, - params: [params], - id: 1, - }), - }) - if (!res.ok) throw new Error(`RPC ${command} failed: HTTP ${res.status}`) - const data = await res.json() - const err = data?.result?.error ?? data?.error - if (err) { - const msg = data?.result?.error_message ?? data?.error_message ?? err - const e = new Error(String(msg)) - ;(e as any).code = err - throw e - } - return data.result -} - -export function isValidAddress(address: string): boolean { - return isValidClassicAddress(address) -} - -export function generateWallet(): { address: string; seed: string } { - const wallet = Wallet.generate() - return { address: wallet.classicAddress, seed: wallet.seed! } -} - -export function walletFromSecret(secret: string): { address: string } { - const s = secret.trim() - try { - return { address: Wallet.fromSeed(s).classicAddress } - } catch { - return { address: Wallet.fromMnemonic(s).classicAddress } - } -} - -export function walletForSigning(secret: string): Wallet { - const s = secret.trim() - try { - return Wallet.fromSeed(s) - } catch { - return Wallet.fromMnemonic(s) - } -} - -export function getWallet(): XrpWalletInfo | null { - const raw = getItem(WALLET_KEY) - if (!raw) return null - try { - return JSON.parse(raw) as XrpWalletInfo - } catch { - return null - } -} - -export function saveWallet(wallet: XrpWalletInfo): void { - setItem(WALLET_KEY, JSON.stringify(wallet)) -} - -export function deleteWallet(): void { - removeItem(WALLET_KEY) - removeItem(SEED_KEY) -} - -export function getSeed(): string | null { - return getItem(SEED_KEY) -} - -export function saveSeed(seed: string): void { - setItem(SEED_KEY, seed) -} - -export async function getBalance(address: string): Promise { - try { - const result = await rpc('account_info', { - account: address, - ledger_index: 'current', - }) - const balance = (result.account_data?.Balance ?? '0') as string - return { balance: dropsAmount(balance), funded: true } - } catch (e: any) { - if (e?.code === 'actNotFound') { - return { balance: '0', funded: false } - } - throw e - } -} - -function dropsAmount(value: unknown): string { - if (typeof value !== 'string') return '0' - return String(dropsToXrp(value)) -} - -export async function getTransactions(address: string): Promise { - const result = await rpc('account_tx', { - account: address, - ledger_index_min: -1, - ledger_index_max: -1, - limit: 25, - }) - const entries: XrpTxEntry[] = [] - for (const entry of result.transactions as any[]) { - const tx = entry.tx_json ?? entry.tx - if (!tx || tx.TransactionType !== 'Payment') continue - const meta = entry.meta ?? entry.metaData ?? {} - if (meta.TransactionResult && meta.TransactionResult !== 'tesSUCCESS') continue - const delivered = meta.delivered_amount ?? meta.DeliveredAmount ?? tx.Amount - entries.push({ - hash: entry.hash ?? tx.hash, - amount: dropsAmount(delivered), - timestamp: tx.date ? tx.date + RIPPLE_EPOCH : 0, - direction: tx.Destination === address ? 'in' : 'out', - fee: dropsAmount(tx.Fee), - confirmed: entry.validated === true, - destinationTag: tx.DestinationTag, - }) - } - return entries -} - -export async function sendPayment( - secret: string, - to: string, - amount: string, - destinationTag?: number, -): Promise<{ hash: string; fee: string }> { - if (!isValidClassicAddress(to)) throw new Error('Invalid XRP address') - const wallet = walletForSigning(secret) - const account = wallet.classicAddress - - const [info, feeResult] = await Promise.all([ - rpc('account_info', { account, ledger_index: 'current' }), - rpc('fee', {}), - ]) - const accountData = info.account_data ?? {} - const sequence = accountData.Sequence as number - const balanceDrops = BigInt(accountData.Balance ?? '0') - const feeDrops = BigInt(feeResult.drops?.open_ledger_fee ?? '10') - - const amountDrops = BigInt(xrpToDrops(amount)) - if (amountDrops + feeDrops > balanceDrops) { - throw new Error('Insufficient balance (includes network fee)') - } - - const tx: Record = { - TransactionType: 'Payment', - Account: account, - Destination: to, - Amount: xrpToDrops(amount), - Fee: feeDrops.toString(), - Sequence: sequence, - Flags: 0, - } - if (destinationTag !== undefined) tx.DestinationTag = destinationTag - - const signed = wallet.sign(tx) - const result = await rpc('submit', { tx_blob: signed.tx_blob }) - const engineResult = result.engine_result - if (engineResult !== 'tesSUCCESS') { - const msg = result.engine_result_message ?? 'Transaction failed' - throw new Error(`${engineResult}: ${msg}`) - } - return { - hash: signed.hash, - fee: dropsAmount(feeDrops.toString()), - } -} diff --git a/apps/mobile/src/rspeedy-env.d.ts b/apps/mobile/src/rspeedy-env.d.ts deleted file mode 100644 index 6793dd9..0000000 --- a/apps/mobile/src/rspeedy-env.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -/// - -declare module '@lynx-js/types' { - interface GlobalProps { - /** - * Define your global properties in this interface. - * These types will be accessible through `lynx.__globalProps`. - */ - } -} - -export {} diff --git a/apps/mobile/src/shims-vue.d.ts b/apps/mobile/src/shims-vue.d.ts deleted file mode 100644 index e140b61..0000000 --- a/apps/mobile/src/shims-vue.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -declare module '*.vue' { - import type { Component } from 'vue-lynx' - - const component: Component - export default component -} - -declare module '*.png' { - const src: string - export default src -} diff --git a/apps/mobile/src/tsconfig.json b/apps/mobile/src/tsconfig.json deleted file mode 100644 index e7a0b54..0000000 --- a/apps/mobile/src/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "composite": true, - - "module": "ESNext", - "moduleResolution": "Bundler", - "types": ["vue-lynx/types"], - - "noEmit": true, - }, - "vueCompilerOptions": { - "plugins": ["vue-lynx/types/volar-plugin"] - }, - "include": ["./**/*.ts", "./**/*.vue"], -} diff --git a/apps/mobile/tailwind.config.ts b/apps/mobile/tailwind.config.ts deleted file mode 100644 index 3ce9f8b..0000000 --- a/apps/mobile/tailwind.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { Config } from 'tailwindcss' -import preset from '@lynx-js/tailwind-preset' - -const config: Config = { - content: ['./src/**/*.{vue,js,ts}'], - presets: [preset], - theme: { - extend: { - colors: { - background: '#0a0a0b', - foreground: '#fafafa', - muted: { - DEFAULT: '#a1a1aa', - foreground: '#71717a', - }, - card: { - DEFAULT: '#18181b', - foreground: '#fafafa', - }, - border: '#27272a', - primary: { - DEFAULT: '#3b82f6', - foreground: '#ffffff', - }, - destructive: '#f87171', - success: '#34d399', - }, - }, - }, -} - -export default config diff --git a/apps/mobile/tsconfig.json b/apps/mobile/tsconfig.json deleted file mode 100644 index a0acf91..0000000 --- a/apps/mobile/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "strict": true, - "isolatedModules": true, - "verbatimModuleSyntax": true, - - "esModuleInterop": true, - "skipLibCheck": true, - }, - "references": [ - { "path": "./tsconfig.node.json" }, - { "path": "./src" }, - ], - "files": [], -} diff --git a/apps/mobile/tsconfig.node.json b/apps/mobile/tsconfig.node.json deleted file mode 100644 index 0a83246..0000000 --- a/apps/mobile/tsconfig.node.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "composite": true, - - // Rspeedy uses Node.js's module resolution - "module": "node16", - "moduleResolution": "node16", - "erasableSyntaxOnly": true, - - // Node.js 18+ - "lib": ["es2023"], - "target": "es2022", - - "noEmit": true, - }, - "include": ["./lynx.config.ts"], -} diff --git a/bun.lock b/bun.lock index c4b3f6e..50ef359 100644 --- a/bun.lock +++ b/bun.lock @@ -64,77 +64,6 @@ "vite": "^8.0.8", }, }, - "apps/mobile": { - "name": "koins-mobile", - "version": "1.0.0", - "dependencies": { - "@expo/vector-icons": "^14.0.2", - "@react-navigation/native": "^7.0.14", - "expo": "~53.0.0", - "expo-barcode-generator": "^4.0.0", - "expo-camera": "^16.0.0", - "expo-clipboard": "~7.1.5", - "expo-constants": "~17.0.0", - "expo-dev-client": "~5.2.4", - "expo-font": "~13.0.0", - "expo-haptics": "~14.0.0", - "expo-linking": "~7.0.0", - "expo-local-authentication": "~16.0.0", - "expo-router": "~4.0.0", - "expo-secure-store": "~14.0.0", - "expo-splash-screen": "~0.29.0", - "expo-status-bar": "~2.0.0", - "expo-system-ui": "~4.0.0", - "expo-web-browser": "~14.0.0", - "lucide-react-native": "^1.30.0", - "nativewind": "^4.1.23", - "react": "19.0.0", - "react-dom": "19.0.0", - "react-native": "0.79.0", - "react-native-gesture-handler": "~2.24.0", - "react-native-qrcode-svg": "^6.3.21", - "react-native-reanimated": "~3.17.0", - "react-native-safe-area-context": "5.4.0", - "react-native-screens": "~4.10.0", - "react-native-svg": "15.11.2", - "react-native-web": "^0.20.0", - "react-native-worklets": "^0.11.3", - "xrpl": "^5.0.0", - "zustand": "^5.0.0", - }, - "devDependencies": { - "@babel/core": "^7.25.0", - "@babel/plugin-transform-react-jsx": "^7.25.0", - "@types/jest": "^29.5.12", - "@types/react": "~19.0.10", - "@types/react-test-renderer": "^18.3.0", - "babel-preset-expo": "~12.0.0", - "jest": "^29.7.0", - "jest-expo": "~53.0.0", - "react-test-renderer": "19.0.0", - "tailwindcss": "^3.4.0", - "typescript": "~5.8.0", - }, - }, - "apps/mobile-lynx": { - "name": "mobile-lynx", - "version": "0.0.0", - "dependencies": { - "vue": "^3.5.0", - "vue-lynx": "^0.5.1", - "xrpl": "^5.0.0", - }, - "devDependencies": { - "@lynx-js/qrcode-rsbuild-plugin": "^0.4.6", - "@lynx-js/rspeedy": "^0.13.5", - "@lynx-js/tailwind-preset": "^0.5.1", - "@rsbuild/plugin-node-polyfill": "^1.4.6", - "@rsbuild/plugin-vue": "^1.2.6", - "rsbuild-plugin-tailwindcss": "^0.2.4", - "tailwindcss": "3", - "typescript": "^5.0.0", - }, - }, "packages/utils": { "name": "@koins/utils", "version": "0.0.0", @@ -144,288 +73,28 @@ }, }, "packages": { - "@0no-co/graphql.web": ["@0no-co/graphql.web@1.3.3", "", { "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "optionalPeers": ["graphql"] }, "sha512-4gFGBdyaFmQ6n9euhp5JtIGS4ZeivwDr1tCPENUxTvy5wyv532yOtFCr9zzYAJh1s6uibgC+TRXUcay+mxzCoQ=="], - - "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], - "@ark/schema": ["@ark/schema@0.56.0", "", { "dependencies": { "@ark/util": "0.56.0" } }, "sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA=="], "@ark/util": ["@ark/util@0.56.0", "", {}, "sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA=="], - "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], + "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], - "@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="], + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], - "@babel/core": ["@babel/core@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="], + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], - "@babel/generator": ["@babel/generator@7.29.8", "", { "dependencies": { "@babel/parser": "^7.29.8", "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg=="], - - "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw=="], - - "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="], - - "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/traverse": "^7.29.7", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg=="], - - "@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg=="], - - "@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.8", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "debug": "^4.4.3", "lodash.debounce": "^4.0.8", "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA=="], - - "@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="], - - "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg=="], - - "@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="], - - "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="], - - "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong=="], - - "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="], - - "@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-wrap-function": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og=="], - - "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.29.7", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ=="], - - "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ=="], - - "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], - - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], - - "@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="], - - "@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw=="], - - "@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="], - - "@babel/highlight": ["@babel/highlight@7.25.9", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw=="], - - "@babel/parser": ["@babel/parser@7.29.8", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA=="], - - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": ["@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w=="], - - "@babel/plugin-bugfix-safari-class-field-initializer-scope": ["@babel/plugin-bugfix-safari-class-field-initializer-scope@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ=="], - - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ["@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ=="], - - "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": ["@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA=="], - - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ["@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/plugin-transform-optional-chaining": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.13.0" } }, "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw=="], - - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": ["@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw=="], - - "@babel/plugin-proposal-class-properties": ["@babel/plugin-proposal-class-properties@7.18.6", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ=="], - - "@babel/plugin-proposal-decorators": ["@babel/plugin-proposal-decorators@7.29.7", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-syntax-decorators": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-EtU0Hi3GvrTqD56xKmZvV/uCXK2ZbwVNPNLAquVItcAZpUhkXwWlo3Fmj0c2LxgSf2I8IDULeAepwNP1OefLXg=="], - - "@babel/plugin-proposal-export-default-from": ["@babel/plugin-proposal-export-default-from@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-p+G5BNXDcy3bOXplhY4HybQ1GxH3i2Tppmdm/3epyRu2VgJJZuUlZ61MqRTg582Q7ZLBdP7fePYvsumSEkMxcQ=="], - - "@babel/plugin-proposal-nullish-coalescing-operator": ["@babel/plugin-proposal-nullish-coalescing-operator@7.18.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA=="], - - "@babel/plugin-proposal-optional-chaining": ["@babel/plugin-proposal-optional-chaining@7.21.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.20.2", "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA=="], - - "@babel/plugin-proposal-private-property-in-object": ["@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w=="], - - "@babel/plugin-syntax-async-generators": ["@babel/plugin-syntax-async-generators@7.8.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw=="], - - "@babel/plugin-syntax-bigint": ["@babel/plugin-syntax-bigint@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg=="], - - "@babel/plugin-syntax-class-properties": ["@babel/plugin-syntax-class-properties@7.12.13", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA=="], - - "@babel/plugin-syntax-class-static-block": ["@babel/plugin-syntax-class-static-block@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw=="], - - "@babel/plugin-syntax-decorators": ["@babel/plugin-syntax-decorators@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg=="], - - "@babel/plugin-syntax-dynamic-import": ["@babel/plugin-syntax-dynamic-import@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ=="], - - "@babel/plugin-syntax-export-default-from": ["@babel/plugin-syntax-export-default-from@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-foag0BB37ROhdeIX9O8G0jX7hw0UekJc04cHMrYLOnrErsnBKqJGHJ8eDRpoCFZBvEPPygmmtw4qyU97qa4oOw=="], - - "@babel/plugin-syntax-flow": ["@babel/plugin-syntax-flow@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ajMX6QPcyomotqwpzhkYGxcK2i/us0rs1Qo9QvUpa+Fca0FTmqrzKrctoIYLMxcOhGZldGT/BAVkRGTWBiR8gQ=="], - - "@babel/plugin-syntax-import-assertions": ["@babel/plugin-syntax-import-assertions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw=="], - - "@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg=="], - - "@babel/plugin-syntax-import-meta": ["@babel/plugin-syntax-import-meta@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g=="], - - "@babel/plugin-syntax-json-strings": ["@babel/plugin-syntax-json-strings@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA=="], - - "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A=="], - - "@babel/plugin-syntax-logical-assignment-operators": ["@babel/plugin-syntax-logical-assignment-operators@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig=="], - - "@babel/plugin-syntax-nullish-coalescing-operator": ["@babel/plugin-syntax-nullish-coalescing-operator@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ=="], - - "@babel/plugin-syntax-numeric-separator": ["@babel/plugin-syntax-numeric-separator@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug=="], - - "@babel/plugin-syntax-object-rest-spread": ["@babel/plugin-syntax-object-rest-spread@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA=="], - - "@babel/plugin-syntax-optional-catch-binding": ["@babel/plugin-syntax-optional-catch-binding@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q=="], - - "@babel/plugin-syntax-optional-chaining": ["@babel/plugin-syntax-optional-chaining@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg=="], - - "@babel/plugin-syntax-private-property-in-object": ["@babel/plugin-syntax-private-property-in-object@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg=="], - - "@babel/plugin-syntax-top-level-await": ["@babel/plugin-syntax-top-level-await@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw=="], - - "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA=="], - - "@babel/plugin-syntax-unicode-sets-regex": ["@babel/plugin-syntax-unicode-sets-regex@7.18.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg=="], - - "@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ=="], - - "@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-remap-async-to-generator": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA=="], - - "@babel/plugin-transform-async-to-generator": ["@babel/plugin-transform-async-to-generator@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-remap-async-to-generator": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w=="], - - "@babel/plugin-transform-block-scoped-functions": ["@babel/plugin-transform-block-scoped-functions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA=="], - - "@babel/plugin-transform-block-scoping": ["@babel/plugin-transform-block-scoping@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ=="], - - "@babel/plugin-transform-class-properties": ["@babel/plugin-transform-class-properties@7.29.7", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA=="], - - "@babel/plugin-transform-class-static-block": ["@babel/plugin-transform-class-static-block@7.29.7", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.12.0" } }, "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A=="], - - "@babel/plugin-transform-classes": ["@babel/plugin-transform-classes@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-globals": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g=="], - - "@babel/plugin-transform-computed-properties": ["@babel/plugin-transform-computed-properties@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/template": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA=="], - - "@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg=="], - - "@babel/plugin-transform-dotall-regex": ["@babel/plugin-transform-dotall-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ=="], - - "@babel/plugin-transform-duplicate-keys": ["@babel/plugin-transform-duplicate-keys@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ=="], - - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": ["@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA=="], - - "@babel/plugin-transform-dynamic-import": ["@babel/plugin-transform-dynamic-import@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg=="], - - "@babel/plugin-transform-explicit-resource-management": ["@babel/plugin-transform-explicit-resource-management@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-transform-destructuring": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw=="], - - "@babel/plugin-transform-exponentiation-operator": ["@babel/plugin-transform-exponentiation-operator@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ=="], - - "@babel/plugin-transform-export-namespace-from": ["@babel/plugin-transform-export-namespace-from@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA=="], - - "@babel/plugin-transform-flow-strip-types": ["@babel/plugin-transform-flow-strip-types@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-syntax-flow": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wRHeUjUjCZnMHmiO5bRgjFLcoEh7JyTdByOW11ahhwNa4V0bmeGEaIvt51yq0zQp2yWIpqfxXXPyUP6GFJZHOQ=="], - - "@babel/plugin-transform-for-of": ["@babel/plugin-transform-for-of@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ=="], - - "@babel/plugin-transform-function-name": ["@babel/plugin-transform-function-name@7.29.7", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg=="], - - "@babel/plugin-transform-json-strings": ["@babel/plugin-transform-json-strings@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg=="], - - "@babel/plugin-transform-literals": ["@babel/plugin-transform-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw=="], - - "@babel/plugin-transform-logical-assignment-operators": ["@babel/plugin-transform-logical-assignment-operators@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q=="], - - "@babel/plugin-transform-member-expression-literals": ["@babel/plugin-transform-member-expression-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg=="], - - "@babel/plugin-transform-modules-amd": ["@babel/plugin-transform-modules-amd@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew=="], - - "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ=="], - - "@babel/plugin-transform-modules-systemjs": ["@babel/plugin-transform-modules-systemjs@7.29.8", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.8" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6iSnEK0zlkLKU4heofK/AdmRD4e2SHVpJMtrwnTCzhnaM98ria4rTrOXBBi45BTTYnJtO8txnPsX4fChYXkmeA=="], - - "@babel/plugin-transform-modules-umd": ["@babel/plugin-transform-modules-umd@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA=="], - - "@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ=="], - - "@babel/plugin-transform-new-target": ["@babel/plugin-transform-new-target@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A=="], - - "@babel/plugin-transform-nullish-coalescing-operator": ["@babel/plugin-transform-nullish-coalescing-operator@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg=="], - - "@babel/plugin-transform-numeric-separator": ["@babel/plugin-transform-numeric-separator@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw=="], - - "@babel/plugin-transform-object-rest-spread": ["@babel/plugin-transform-object-rest-spread@7.29.7", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-transform-destructuring": "^7.29.7", "@babel/plugin-transform-parameters": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A=="], - - "@babel/plugin-transform-object-super": ["@babel/plugin-transform-object-super@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA=="], - - "@babel/plugin-transform-optional-catch-binding": ["@babel/plugin-transform-optional-catch-binding@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng=="], - - "@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ=="], - - "@babel/plugin-transform-parameters": ["@babel/plugin-transform-parameters@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g=="], - - "@babel/plugin-transform-private-methods": ["@babel/plugin-transform-private-methods@7.29.7", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug=="], - - "@babel/plugin-transform-private-property-in-object": ["@babel/plugin-transform-private-property-in-object@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA=="], - - "@babel/plugin-transform-property-literals": ["@babel/plugin-transform-property-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA=="], - - "@babel/plugin-transform-react-display-name": ["@babel/plugin-transform-react-display-name@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q=="], - - "@babel/plugin-transform-react-jsx": ["@babel/plugin-transform-react-jsx@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-module-imports": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-syntax-jsx": "^7.29.7", "@babel/types": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A=="], - - "@babel/plugin-transform-react-jsx-development": ["@babel/plugin-transform-react-jsx-development@7.29.7", "", { "dependencies": { "@babel/plugin-transform-react-jsx": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g=="], - - "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw=="], - - "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q=="], - - "@babel/plugin-transform-react-pure-annotations": ["@babel/plugin-transform-react-pure-annotations@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-H5E+HBgDpr6Q5t+Aj11tL7XkIui1jhbIoArVQnqjgXo5/3YxkN7ZEBcWF4RQlB0T4rrxJQbXS6kiFV6B7XTqUA=="], - - "@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.29.8", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg=="], - - "@babel/plugin-transform-regexp-modifiers": ["@babel/plugin-transform-regexp-modifiers@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ=="], - - "@babel/plugin-transform-reserved-words": ["@babel/plugin-transform-reserved-words@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA=="], - - "@babel/plugin-transform-runtime": ["@babel/plugin-transform-runtime@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "babel-plugin-polyfill-corejs2": "^0.4.14", "babel-plugin-polyfill-corejs3": "^0.13.0", "babel-plugin-polyfill-regenerator": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xmAscdE/AsqRW7vutbPNoUmu/nF5SrLKPs7aoJgEjo35lLKA/Bc0i2rMv/hr1+Y0o1bQCiVtith3u2vdgRL39Q=="], - - "@babel/plugin-transform-shorthand-properties": ["@babel/plugin-transform-shorthand-properties@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg=="], - - "@babel/plugin-transform-spread": ["@babel/plugin-transform-spread@7.29.8", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-4S9ksMGVWUshvgK0mKfvZky7leuG5/uoFVwMpAomJ8bMoDJiNHRVmc1EglwW/CmGVSqqWpEbXm9FmbRit22qoA=="], - - "@babel/plugin-transform-sticky-regex": ["@babel/plugin-transform-sticky-regex@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA=="], - - "@babel/plugin-transform-template-literals": ["@babel/plugin-transform-template-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA=="], - - "@babel/plugin-transform-typeof-symbol": ["@babel/plugin-transform-typeof-symbol@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A=="], - - "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/plugin-syntax-typescript": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw=="], - - "@babel/plugin-transform-unicode-escapes": ["@babel/plugin-transform-unicode-escapes@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA=="], - - "@babel/plugin-transform-unicode-property-regex": ["@babel/plugin-transform-unicode-property-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw=="], - - "@babel/plugin-transform-unicode-regex": ["@babel/plugin-transform-unicode-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA=="], - - "@babel/plugin-transform-unicode-sets-regex": ["@babel/plugin-transform-unicode-sets-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg=="], - - "@babel/preset-env": ["@babel/preset-env@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.29.7", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.29.7", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.29.7", "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.7", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.29.7", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.29.7", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.29.7", "@babel/plugin-syntax-import-attributes": "^7.29.7", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.29.7", "@babel/plugin-transform-async-generator-functions": "^7.29.7", "@babel/plugin-transform-async-to-generator": "^7.29.7", "@babel/plugin-transform-block-scoped-functions": "^7.29.7", "@babel/plugin-transform-block-scoping": "^7.29.7", "@babel/plugin-transform-class-properties": "^7.29.7", "@babel/plugin-transform-class-static-block": "^7.29.7", "@babel/plugin-transform-classes": "^7.29.7", "@babel/plugin-transform-computed-properties": "^7.29.7", "@babel/plugin-transform-destructuring": "^7.29.7", "@babel/plugin-transform-dotall-regex": "^7.29.7", "@babel/plugin-transform-duplicate-keys": "^7.29.7", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.7", "@babel/plugin-transform-dynamic-import": "^7.29.7", "@babel/plugin-transform-explicit-resource-management": "^7.29.7", "@babel/plugin-transform-exponentiation-operator": "^7.29.7", "@babel/plugin-transform-export-namespace-from": "^7.29.7", "@babel/plugin-transform-for-of": "^7.29.7", "@babel/plugin-transform-function-name": "^7.29.7", "@babel/plugin-transform-json-strings": "^7.29.7", "@babel/plugin-transform-literals": "^7.29.7", "@babel/plugin-transform-logical-assignment-operators": "^7.29.7", "@babel/plugin-transform-member-expression-literals": "^7.29.7", "@babel/plugin-transform-modules-amd": "^7.29.7", "@babel/plugin-transform-modules-commonjs": "^7.29.7", "@babel/plugin-transform-modules-systemjs": "^7.29.7", "@babel/plugin-transform-modules-umd": "^7.29.7", "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.7", "@babel/plugin-transform-new-target": "^7.29.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.29.7", "@babel/plugin-transform-numeric-separator": "^7.29.7", "@babel/plugin-transform-object-rest-spread": "^7.29.7", "@babel/plugin-transform-object-super": "^7.29.7", "@babel/plugin-transform-optional-catch-binding": "^7.29.7", "@babel/plugin-transform-optional-chaining": "^7.29.7", "@babel/plugin-transform-parameters": "^7.29.7", "@babel/plugin-transform-private-methods": "^7.29.7", "@babel/plugin-transform-private-property-in-object": "^7.29.7", "@babel/plugin-transform-property-literals": "^7.29.7", "@babel/plugin-transform-regenerator": "^7.29.7", "@babel/plugin-transform-regexp-modifiers": "^7.29.7", "@babel/plugin-transform-reserved-words": "^7.29.7", "@babel/plugin-transform-shorthand-properties": "^7.29.7", "@babel/plugin-transform-spread": "^7.29.7", "@babel/plugin-transform-sticky-regex": "^7.29.7", "@babel/plugin-transform-template-literals": "^7.29.7", "@babel/plugin-transform-typeof-symbol": "^7.29.7", "@babel/plugin-transform-unicode-escapes": "^7.29.7", "@babel/plugin-transform-unicode-property-regex": "^7.29.7", "@babel/plugin-transform-unicode-regex": "^7.29.7", "@babel/plugin-transform-unicode-sets-regex": "^7.29.7", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.15", "babel-plugin-polyfill-corejs3": "^0.14.0", "babel-plugin-polyfill-regenerator": "^0.6.6", "core-js-compat": "^3.48.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA=="], - - "@babel/preset-flow": ["@babel/preset-flow@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "@babel/plugin-transform-flow-strip-types": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-KYIRV0BuaN68CDdsqFkAD7MU7yipUqQNuNElwATdxaIdpTjhvtY82QvkBJs7zV3Evxj2jFAAZ1iO8nyy0nhjqA=="], - - "@babel/preset-modules": ["@babel/preset-modules@0.1.6-no-external-plugins", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", "esutils": "^2.0.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA=="], - - "@babel/preset-react": ["@babel/preset-react@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "@babel/plugin-transform-react-display-name": "^7.29.7", "@babel/plugin-transform-react-jsx": "^7.29.7", "@babel/plugin-transform-react-jsx-development": "^7.29.7", "@babel/plugin-transform-react-pure-annotations": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-C+PV1TFUPTmBQGoPBL8j2QmLpZ117YTCwxIZeJOM96GbYMFSc7/pOXU5lVykwnZxyTqQxRsvoRk6f2FktZgGHA=="], - - "@babel/preset-typescript": ["@babel/preset-typescript@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "@babel/plugin-syntax-jsx": "^7.29.7", "@babel/plugin-transform-modules-commonjs": "^7.29.7", "@babel/plugin-transform-typescript": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ=="], - - "@babel/register": ["@babel/register@7.29.7", "", { "dependencies": { "clone-deep": "^4.0.1", "find-cache-dir": "^2.0.0", "make-dir": "^2.1.0", "pirates": "^4.0.6", "source-map-support": "^0.5.16" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-AMGJoWuES861riy6pcB0fphE1YXybtQnBYQMuIyPv6mKLiosfa79BKTnAOyx215c/3RJPJpdQwoHZ3earVH7AA=="], + "@babel/parser": ["@babel/parser@7.29.3", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="], "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], - "@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="], - - "@babel/traverse": ["@babel/traverse@7.29.8", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", "@babel/types": "^7.29.8", "debug": "^4.3.1" } }, "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg=="], - - "@babel/traverse--for-generate-function-map": ["@babel/traverse@7.29.8", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", "@babel/types": "^7.29.8", "debug": "^4.3.1" } }, "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg=="], - - "@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], + "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], "@babylonjs/core": ["@babylonjs/core@7.54.3", "", {}, "sha512-P5ncXVd8GEUJLhwloP9V0oVwQYIrvZztguVeLlvd5Rx+9aQnenKjpV8auJ6SRsUlAmNZU4pFTKzwF6o2EUfhAw=="], - "@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="], - - "@colordx/core": ["@colordx/core@5.5.0", "", {}, "sha512-3PxTH8itZzltK0U9jTwVVnjLXvnDYuq3m+QXsHkENxWiPRh4WaoLcs1SQjqgZ55kS+QyirpH5BVwzP2gMVG6EQ=="], - "@dagrejs/dagre": ["@dagrejs/dagre@2.0.4", "", { "dependencies": { "@dagrejs/graphlib": "3.0.4" } }, "sha512-J6vCWTNpicHF4zFlZG1cS5DkGzMr9941gddYkakjrg3ZNev4bbqEgLHFTWiFrcJm7UCRu7olO3K6IRDd9gSGhA=="], "@dagrejs/graphlib": ["@dagrejs/graphlib@3.0.4", "", {}, "sha512-HxZ7fCvAwTLCWCO0WjDkzAFQze8LdC6iOpKbetDKHIuDfIgMlIzYzqZ4nxwLlclQX+3ZVeZ1K2OuaOE2WWcyOg=="], - "@egjs/hammerjs": ["@egjs/hammerjs@2.0.17", "", { "dependencies": { "@types/hammerjs": "^2.0.36" } }, "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A=="], - "@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], @@ -486,54 +155,6 @@ "@exodus/schemasafe": ["@exodus/schemasafe@1.3.0", "", {}, "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw=="], - "@expo/cli": ["@expo/cli@0.24.24", "", { "dependencies": { "@0no-co/graphql.web": "^1.0.8", "@babel/runtime": "^7.20.0", "@expo/code-signing-certificates": "^0.0.6", "@expo/config": "~11.0.13", "@expo/config-plugins": "~10.1.2", "@expo/devcert": "^1.1.2", "@expo/env": "~1.0.7", "@expo/image-utils": "^0.7.6", "@expo/json-file": "^9.1.5", "@expo/metro-config": "~0.20.18", "@expo/osascript": "^2.2.5", "@expo/package-manager": "^1.8.6", "@expo/plist": "^0.3.5", "@expo/prebuild-config": "^9.0.12", "@expo/schema-utils": "^0.1.0", "@expo/spawn-async": "^1.7.2", "@expo/ws-tunnel": "^1.0.1", "@expo/xcpretty": "^4.3.0", "@react-native/dev-middleware": "0.79.6", "@urql/core": "^5.0.6", "@urql/exchange-retry": "^1.3.0", "accepts": "^1.3.8", "arg": "^5.0.2", "better-opn": "~3.0.2", "bplist-creator": "0.1.0", "bplist-parser": "^0.3.1", "chalk": "^4.0.0", "ci-info": "^3.3.0", "compression": "^1.7.4", "connect": "^3.7.0", "debug": "^4.3.4", "env-editor": "^0.4.1", "freeport-async": "^2.0.0", "getenv": "^2.0.0", "glob": "^10.4.2", "lan-network": "^0.1.6", "minimatch": "^9.0.0", "node-forge": "^1.3.3", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "picomatch": "^3.0.1", "pretty-bytes": "^5.6.0", "pretty-format": "^29.7.0", "progress": "^2.0.3", "prompts": "^2.3.2", "qrcode-terminal": "0.11.0", "require-from-string": "^2.0.2", "requireg": "^0.2.2", "resolve": "^1.22.2", "resolve-from": "^5.0.0", "resolve.exports": "^2.0.3", "semver": "^7.6.0", "send": "^0.19.0", "slugify": "^1.3.4", "source-map-support": "~0.5.21", "stacktrace-parser": "^0.1.10", "structured-headers": "^0.4.1", "tar": "^7.4.3", "terminal-link": "^2.1.1", "undici": "^6.18.2", "wrap-ansi": "^7.0.0", "ws": "^8.12.1" }, "bin": { "expo-internal": "build/bin/cli" } }, "sha512-XybHfF2QNPJNnHoUKHcG796iEkX5126UuTAs6MSpZuvZRRQRj/sGCLX+driCOVHbDOpcCOusMuHrhxHbtTApyg=="], - - "@expo/code-signing-certificates": ["@expo/code-signing-certificates@0.0.6", "", { "dependencies": { "node-forge": "^1.3.3" } }, "sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w=="], - - "@expo/config": ["@expo/config@11.0.13", "", { "dependencies": { "@babel/code-frame": "~7.10.4", "@expo/config-plugins": "~10.1.2", "@expo/config-types": "^53.0.5", "@expo/json-file": "^9.1.5", "deepmerge": "^4.3.1", "getenv": "^2.0.0", "glob": "^10.4.2", "require-from-string": "^2.0.2", "resolve-from": "^5.0.0", "resolve-workspace-root": "^2.0.0", "semver": "^7.6.0", "slugify": "^1.3.4", "sucrase": "3.35.0" } }, "sha512-TnGb4u/zUZetpav9sx/3fWK71oCPaOjZHoVED9NaEncktAd0Eonhq5NUghiJmkUGt3gGSjRAEBXiBbbY9/B1LA=="], - - "@expo/config-plugins": ["@expo/config-plugins@10.1.2", "", { "dependencies": { "@expo/config-types": "^53.0.5", "@expo/json-file": "~9.1.5", "@expo/plist": "^0.3.5", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", "debug": "^4.3.5", "getenv": "^2.0.0", "glob": "^10.4.2", "resolve-from": "^5.0.0", "semver": "^7.5.4", "slash": "^3.0.0", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, "sha512-IMYCxBOcnuFStuK0Ay+FzEIBKrwW8OVUMc65+v0+i7YFIIe8aL342l7T4F8lR4oCfhXn7d6M5QPgXvjtc/gAcw=="], - - "@expo/config-types": ["@expo/config-types@53.0.5", "", {}, "sha512-kqZ0w44E+HEGBjy+Lpyn0BVL5UANg/tmNixxaRMLS6nf37YsDrLk2VMAmeKMMk5CKG0NmOdVv3ngeUjRQMsy9g=="], - - "@expo/devcert": ["@expo/devcert@1.2.1", "", { "dependencies": { "@expo/sudo-prompt": "^9.3.1", "debug": "^3.1.0" } }, "sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA=="], - - "@expo/env": ["@expo/env@0.4.2", "", { "dependencies": { "chalk": "^4.0.0", "debug": "^4.3.4", "dotenv": "~16.4.5", "dotenv-expand": "~11.0.6", "getenv": "^1.0.0" } }, "sha512-TgbCgvSk0Kq0e2fLoqHwEBL4M0ztFjnBEz0YCDm5boc1nvkV1VMuIMteVdeBwnTh8Z0oPJTwHCD49vhMEt1I6A=="], - - "@expo/fingerprint": ["@expo/fingerprint@0.13.4", "", { "dependencies": { "@expo/spawn-async": "^1.7.2", "arg": "^5.0.2", "chalk": "^4.1.2", "debug": "^4.3.4", "find-up": "^5.0.0", "getenv": "^2.0.0", "glob": "^10.4.2", "ignore": "^5.3.1", "minimatch": "^9.0.0", "p-limit": "^3.1.0", "resolve-from": "^5.0.0", "semver": "^7.6.0" }, "bin": { "fingerprint": "bin/cli.js" } }, "sha512-MYfPYBTMfrrNr07DALuLhG6EaLVNVrY/PXjEzsjWdWE4ZFn0yqI0IdHNkJG7t1gePT8iztHc7qnsx+oo/rDo6w=="], - - "@expo/image-utils": ["@expo/image-utils@0.7.6", "", { "dependencies": { "@expo/spawn-async": "^1.7.2", "chalk": "^4.0.0", "getenv": "^2.0.0", "jimp-compact": "0.16.1", "parse-png": "^2.1.0", "resolve-from": "^5.0.0", "semver": "^7.6.0", "temp-dir": "~2.0.0", "unique-string": "~2.0.0" } }, "sha512-GKnMqC79+mo/1AFrmAcUcGfbsXXTRqOMNS1umebuevl3aaw+ztsYEFEiuNhHZW7PQ3Xs3URNT513ZxKhznDscw=="], - - "@expo/json-file": ["@expo/json-file@9.1.5", "", { "dependencies": { "@babel/code-frame": "~7.10.4", "json5": "^2.2.3" } }, "sha512-prWBhLUlmcQtvN6Y7BpW2k9zXGd3ySa3R6rAguMJkp1z22nunLN64KYTUWfijFlprFoxm9r2VNnGkcbndAlgKA=="], - - "@expo/metro-config": ["@expo/metro-config@0.20.18", "", { "dependencies": { "@babel/core": "^7.20.0", "@babel/generator": "^7.20.5", "@babel/parser": "^7.20.0", "@babel/types": "^7.20.0", "@expo/config": "~11.0.13", "@expo/env": "~1.0.7", "@expo/json-file": "~9.1.5", "@expo/spawn-async": "^1.7.2", "chalk": "^4.1.0", "debug": "^4.3.2", "dotenv": "~16.4.5", "dotenv-expand": "~11.0.6", "getenv": "^2.0.0", "glob": "^10.4.2", "jsc-safe-url": "^0.2.4", "lightningcss": "~1.27.0", "minimatch": "^9.0.0", "postcss": "~8.4.32", "resolve-from": "^5.0.0" } }, "sha512-qPYq3Cq61KQO1CppqtmxA1NGKpzFOmdiL7WxwLhEVnz73LPSgneW7dV/3RZwVFkjThzjA41qB4a9pxDqtpepPg=="], - - "@expo/metro-runtime": ["@expo/metro-runtime@4.0.1", "", { "peerDependencies": { "react-native": "*" } }, "sha512-CRpbLvdJ1T42S+lrYa1iZp1KfDeBp4oeZOK3hdpiS5n0vR0nhD6sC1gGF0sTboCTp64tLteikz5Y3j53dvgOIw=="], - - "@expo/osascript": ["@expo/osascript@2.7.1", "", { "dependencies": { "@expo/spawn-async": "^1.8.0" } }, "sha512-Zn03EX6In7ts2lPUW2ESUSkEhEWQN1qqsiXjadtZMJOuZRkMiAg1ZQHuvz9DjByDWNJ2pBwAGyrts9lj9k389g=="], - - "@expo/package-manager": ["@expo/package-manager@1.13.1", "", { "dependencies": { "@expo/json-file": "^11.0.1", "@expo/spawn-async": "^1.8.0", "chalk": "^4.0.0", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "resolve-workspace-root": "^2.0.0" } }, "sha512-y/K+CaYYpZpNGZhSX4HyLT/vyIunFjNfyoxNysPBCefeLKI/VCx6f9LNPzrxayr3rCYO5bl9O8H+HRQK265Nkg=="], - - "@expo/plist": ["@expo/plist@0.3.5", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.2.3", "xmlbuilder": "^15.1.1" } }, "sha512-9RYVU1iGyCJ7vWfg3e7c/NVyMFs8wbl+dMWZphtFtsqyN9zppGREU3ctlD3i8KUE0sCUTVnLjCWr+VeUIDep2g=="], - - "@expo/prebuild-config": ["@expo/prebuild-config@8.2.0", "", { "dependencies": { "@expo/config": "~10.0.11", "@expo/config-plugins": "~9.0.17", "@expo/config-types": "^52.0.5", "@expo/image-utils": "^0.6.5", "@expo/json-file": "^9.0.2", "@react-native/normalize-colors": "0.76.9", "debug": "^4.3.1", "fs-extra": "^9.0.0", "resolve-from": "^5.0.0", "semver": "^7.6.0", "xml2js": "0.6.0" } }, "sha512-CxiPpd980s0jyxi7eyN3i/7YKu3XL+8qPjBZUCYtc0+axpGweqIkq2CslyLSKHyqVyH/zlPkbVgWdyiYavFS5Q=="], - - "@expo/schema-utils": ["@expo/schema-utils@0.1.9", "", {}, "sha512-t9bYwG4Z0yCVzHYJoDMci1OFq2FkBkhStlfUGSkspKYTwB/84+x6sY+CXCgdhkQNQtvWaugW5KUs9YZfAXq9Sg=="], - - "@expo/sdk-runtime-versions": ["@expo/sdk-runtime-versions@1.0.0", "", {}, "sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ=="], - - "@expo/server": ["@expo/server@0.5.3", "", { "dependencies": { "abort-controller": "^3.0.0", "debug": "^4.3.4", "source-map-support": "~0.5.21", "undici": "^6.18.2" } }, "sha512-WXsWzeBs5v/h0PUfHyNLLz07rwwO5myQ1A5DGYewyyGLmsyl61yVCe8AgAlp1wkiMsqhj2hZqI2u3K10QnCMrQ=="], - - "@expo/spawn-async": ["@expo/spawn-async@1.8.0", "", { "dependencies": { "cross-spawn": "^7.0.6" } }, "sha512-eb9xxd/LbuEGSdua4NumCu/McVB9EM+F/JxB9pWgnERw4HQ9XyTNH1KapG6oqLWR8TuRK2LQfzJlmNi94CVobw=="], - - "@expo/sudo-prompt": ["@expo/sudo-prompt@9.3.2", "", {}, "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw=="], - - "@expo/vector-icons": ["@expo/vector-icons@14.1.0", "", { "peerDependencies": { "expo-font": "*", "react": "*", "react-native": "*" } }, "sha512-7T09UE9h8QDTsUeMGymB4i+iqvtEeaO5VvUjryFB4tugDTG/bkzViWA74hm5pfjjDEhYMXWaX112mcvhccmIwQ=="], - - "@expo/ws-tunnel": ["@expo/ws-tunnel@1.0.6", "", {}, "sha512-nDRbLmSrJar7abvUjp3smDwH8HcbZcoOEa5jVPUv9/9CajgmWw20JNRwTuBRzWIWIkEJDkz20GoNA+tSwUqk0Q=="], - - "@expo/xcpretty": ["@expo/xcpretty@4.4.4", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "chalk": "^4.1.0", "js-yaml": "^4.1.0" }, "bin": { "excpretty": "build/cli.js" } }, "sha512-4aQzz9vgxcNXFfo/iyNgDDYfsU5XGKKxWxZopw0cVotHiW+U8IJbIxMaxsINs6bHhtkG3StKNPcOrn3eBuxKPw=="], - "@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="], "@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="], @@ -548,54 +169,12 @@ "@internationalized/date": ["@internationalized/date@3.12.1", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-6IedsVWXyq4P9Tj+TxuU8WGWM70hYLl12nbYU8jkikVpa6WXapFazPUcHUMDMoWftIDE2ILDkFFte6W2nFCkRQ=="], - "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], - - "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], - - "@isaacs/ttlcache": ["@isaacs/ttlcache@1.4.1", "", {}, "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA=="], - - "@istanbuljs/load-nyc-config": ["@istanbuljs/load-nyc-config@1.1.0", "", { "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" } }, "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ=="], - - "@istanbuljs/schema": ["@istanbuljs/schema@0.1.6", "", {}, "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw=="], - - "@jest/console": ["@jest/console@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "slash": "^3.0.0" } }, "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg=="], - - "@jest/core": ["@jest/core@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-changed-files": "^29.7.0", "jest-config": "^29.7.0", "jest-haste-map": "^29.7.0", "jest-message-util": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-resolve-dependencies": "^29.7.0", "jest-runner": "^29.7.0", "jest-runtime": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "jest-watcher": "^29.7.0", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg=="], - - "@jest/create-cache-key-function": ["@jest/create-cache-key-function@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3" } }, "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA=="], - - "@jest/environment": ["@jest/environment@29.7.0", "", { "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0" } }, "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw=="], - - "@jest/expect": ["@jest/expect@29.7.0", "", { "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" } }, "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ=="], - - "@jest/expect-utils": ["@jest/expect-utils@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3" } }, "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA=="], - - "@jest/fake-timers": ["@jest/fake-timers@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ=="], - - "@jest/globals": ["@jest/globals@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", "@jest/types": "^29.6.3", "jest-mock": "^29.7.0" } }, "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ=="], - - "@jest/reporters": ["@jest/reporters@29.7.0", "", { "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", "v8-to-istanbul": "^9.0.1" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg=="], - - "@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="], - - "@jest/source-map": ["@jest/source-map@29.6.3", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" } }, "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw=="], - - "@jest/test-result": ["@jest/test-result@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA=="], - - "@jest/test-sequencer": ["@jest/test-sequencer@29.7.0", "", { "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "slash": "^3.0.0" } }, "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw=="], - - "@jest/transform": ["@jest/transform@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", "write-file-atomic": "^4.0.2" } }, "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw=="], - - "@jest/types": ["@jest/types@29.6.3", "", { "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" } }, "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw=="], - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - "@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="], - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], @@ -612,120 +191,22 @@ "@lucide/svelte": ["@lucide/svelte@1.14.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-MVuP5VRCBQa2OaIpaRbuEV4k5OV2dy9MyxA6Tf4Sz/IN0v3zzUU72ObHc9r2Zn/wSMXDg6lLrHrczqI7w7gCzQ=="], - "@lynx-js/cache-events-webpack-plugin": ["@lynx-js/cache-events-webpack-plugin@0.0.3", "", { "dependencies": { "@lynx-js/webpack-runtime-globals": "^0.0.6" } }, "sha512-ijKI7zlBgZYo4Hbai3BpsNkKmetNrV8VqbVbFCwlX70+DET2jvxm5Lngzqo4yteHNAFOA58XoMa81bQKFTX7WQ=="], - - "@lynx-js/chunk-loading-webpack-plugin": ["@lynx-js/chunk-loading-webpack-plugin@0.3.4", "", { "dependencies": { "@lynx-js/webpack-runtime-globals": "0.0.6" } }, "sha512-SHe1STEa2oUJodcEU8sHphFT2NaM/PB1h4fC7Wr/3miquaPDudmpEXTrHt9YBuqQMVm0JAVXXJTsTPkGHZrPqQ=="], - - "@lynx-js/css-extract-webpack-plugin": ["@lynx-js/css-extract-webpack-plugin@0.7.1", "", { "dependencies": { "mini-css-extract-plugin": "^2.10.0" }, "peerDependencies": { "@lynx-js/template-webpack-plugin": "^0.11.0" } }, "sha512-+8FGoQeckfOJTo6XKeKU+LxxywSl6wAbl02bdZgt2RBA0Do6lCqcFxL1R/TV9vc1oITW9evHP3ZGqyyrveHb4A=="], - - "@lynx-js/css-serializer": ["@lynx-js/css-serializer@0.1.5", "", { "dependencies": { "css-tree": "^3.1.0" } }, "sha512-eRVsGFQR0DjclwXjZF7f/SnYRHtS2H29H1iFlAo71Rdyrjbl4y8kIk4s0qD41YbmlXqRP+UnWtUIaXiivTJLQQ=="], - - "@lynx-js/qrcode-rsbuild-plugin": ["@lynx-js/qrcode-rsbuild-plugin@0.4.7", "", {}, "sha512-7Y4jK5I7lbmzbmmBhXPbbFw+4+mAWkwqzF1+PKLSzgY9BPsnwuEyu4EsrRo7RqyXpgcpunacZC7IgyB6BRDh+g=="], - - "@lynx-js/react": ["@lynx-js/react@0.116.5", "", { "dependencies": { "preact": "npm:@hongzhiyuan/preact@10.28.0-fc4af453" }, "peerDependencies": { "@lynx-js/types": "*", "@types/react": "^18" }, "optionalPeers": ["@lynx-js/types"] }, "sha512-qhh9mZv7d5hKMQOXoiAOYaWQe/My6MhJ8EcJAi/c9g1EH4TQQoR0Q1o/7dIWj/pZbIiXSt6bgXpTJNQpXr+Qyg=="], - - "@lynx-js/rspeedy": ["@lynx-js/rspeedy@0.13.6", "", { "dependencies": { "@lynx-js/cache-events-webpack-plugin": "^0.0.3", "@lynx-js/chunk-loading-webpack-plugin": "^0.3.3", "@lynx-js/web-rsbuild-server-middleware": "0.19.9", "@lynx-js/webpack-dev-transport": "^0.2.0", "@lynx-js/websocket": "^0.0.4", "@rsbuild/core": "1.7.3", "@rsbuild/plugin-css-minimizer": "1.1.1", "@rsdoctor/rspack-plugin": "1.2.3" }, "peerDependencies": { "typescript": "5.1.6 - 5.9.x" }, "optionalPeers": ["typescript"], "bin": { "rspeedy": "bin/rspeedy.js" } }, "sha512-pEshb4pMR3+56/81j+HIgtBL82UKQ16BzZok88Zu0MMPFTITmLwIoknuRZhj+HJ69kzPJT02j/fChiHAJKf7oA=="], - - "@lynx-js/runtime-wrapper-webpack-plugin": ["@lynx-js/runtime-wrapper-webpack-plugin@0.1.3", "", { "dependencies": { "@lynx-js/webpack-runtime-globals": "0.0.6" } }, "sha512-F/spRqXWE5XS4h36mN89ulGGXjvs7DEiBFUO/Xf6barstKXbhLM485CwMzXz/Zh07ZIFvnHFtRZprOT0jNrrag=="], - - "@lynx-js/tailwind-preset": ["@lynx-js/tailwind-preset@0.5.1", "", { "peerDependencies": { "tailwindcss": "^3.4.0" } }, "sha512-I44EGXw2Xygo/RxpBw0UA+806wvegYakMIhCxxLi0ji8yd/MWTKs2n7QoydQADqdBu3AfttgxDG/2/e6cs5gag=="], - - "@lynx-js/tasm": ["@lynx-js/tasm@0.0.26", "", {}, "sha512-WmcuTYh/KfdRqriT7+Qg8iwxsCDN4PKbCuXoN35npn23p33grpSNW306DJi9tX7LzP2vDZkOjxulSOknfiB/0Q=="], - - "@lynx-js/template-webpack-plugin": ["@lynx-js/template-webpack-plugin@0.10.9", "", { "dependencies": { "@lynx-js/css-serializer": "0.1.5", "@lynx-js/tasm": "0.0.26", "@lynx-js/web-core": "0.20.2", "@lynx-js/webpack-runtime-globals": "^0.0.6", "@rspack/lite-tapable": "1.1.0", "css-tree": "^3.1.0", "object.groupby": "^1.0.3" } }, "sha512-oQn3TWnCOibwTIOprc/N5lhfTvIusUx+Hs6gxiO3wwhSJri78qP1lj1Ajngs8RsaXM0BeTl2aRxezUCmr3zVbg=="], - - "@lynx-js/web-core": ["@lynx-js/web-core@0.20.2", "", { "dependencies": { "@lynx-js/web-elements": "0.12.0", "@lynx-js/web-worker-rpc": "0.20.2", "wasm-feature-detect": "^1.8.0" }, "peerDependencies": { "@lynx-js/css-serializer": "0.1.5", "@lynx-js/lynx-core": "0.1.3", "tslib": "^2.5.0" }, "optionalPeers": ["@lynx-js/css-serializer", "@lynx-js/lynx-core", "tslib"] }, "sha512-9FlpQSnHOVnxS9yJe2SH2GkO33jf2wDZGGhXBQQRVDP8Hso52wpGE+ik4e4A0g+Pk6RCrXRp3UNA+bo5E96vOQ=="], - - "@lynx-js/web-elements": ["@lynx-js/web-elements@0.12.0", "", { "peerDependencies": { "tslib": "^2.5.0" } }, "sha512-7z8PQQSDMUWrxoizySOg1pKGIiSDeGGomOr5FV0Tx9gniHNJYx0e7/h6wQmwhyHdAZYyihyaBks88QkX8DRNEw=="], - - "@lynx-js/web-rsbuild-server-middleware": ["@lynx-js/web-rsbuild-server-middleware@0.19.9", "", {}, "sha512-iOqUxrXUD6L6ot01AMb9VXdR/Twh/e6t4SRKFC1NafIw99gUHumVsQHUGh5oZQLtP7mZHJMkeBcavzqvfcW58w=="], - - "@lynx-js/web-worker-rpc": ["@lynx-js/web-worker-rpc@0.20.2", "", {}, "sha512-fbOIku3UsVOa2coXq0z8mJekYgz56614ebwba6q21D96ujgLjJjuowxRmAKQsDC2DFFcf3sAGJd0M0IHsbt9tQ=="], - - "@lynx-js/webpack-dev-transport": ["@lynx-js/webpack-dev-transport@0.2.0", "", {}, "sha512-RSy02FSoMsavEn2wna4khSJwT2uGW4XeLduKH8UDT3aCsBNM/rXndUyG6PbwMcywXCeyb8UofkhaQDtJvNJklA=="], - - "@lynx-js/webpack-runtime-globals": ["@lynx-js/webpack-runtime-globals@0.0.6", "", {}, "sha512-VzpJc/w7v38/SHaZ+f3WBVBrsgXOG13YX9mRRxRrCgJQa+wZ6waRz7OIjntuTYJk2p7rQ0wKRGzsMgMfRBs/3g=="], - - "@lynx-js/websocket": ["@lynx-js/websocket@0.0.4", "", { "dependencies": { "eventemitter3": "^5.0.1" } }, "sha512-yXuMiTALLNvkDz8hG+0KdkBodnyJDvyfr8bdIq6zMP9X8JAoBC/czc1HIhUgYEJ3Zee5F/vGKjNxcoSx2t1E6w=="], - "@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@1.1.1", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ=="], - "@module-federation/error-codes": ["@module-federation/error-codes@0.22.0", "", {}, "sha512-xF9SjnEy7vTdx+xekjPCV5cIHOGCkdn3pIxo9vU7gEZMIw0SvAEdsy6Uh17xaCpm8V0FWvR0SZoK9Ik6jGOaug=="], - - "@module-federation/runtime": ["@module-federation/runtime@0.22.0", "", { "dependencies": { "@module-federation/error-codes": "0.22.0", "@module-federation/runtime-core": "0.22.0", "@module-federation/sdk": "0.22.0" } }, "sha512-38g5iPju2tPC3KHMPxRKmy4k4onNp6ypFPS1eKGsNLUkXgHsPMBFqAjDw96iEcjri91BrahG4XcdyKi97xZzlA=="], - - "@module-federation/runtime-core": ["@module-federation/runtime-core@0.22.0", "", { "dependencies": { "@module-federation/error-codes": "0.22.0", "@module-federation/sdk": "0.22.0" } }, "sha512-GR1TcD6/s7zqItfhC87zAp30PqzvceoeDGYTgF3Vx2TXvsfDrhP6Qw9T4vudDQL3uJRne6t7CzdT29YyVxlgIA=="], - - "@module-federation/runtime-tools": ["@module-federation/runtime-tools@0.22.0", "", { "dependencies": { "@module-federation/runtime": "0.22.0", "@module-federation/webpack-bundler-runtime": "0.22.0" } }, "sha512-4ScUJ/aUfEernb+4PbLdhM/c60VHl698Gn1gY21m9vyC1Ucn69fPCA1y2EwcCB7IItseRMoNhdcWQnzt/OPCNA=="], - - "@module-federation/sdk": ["@module-federation/sdk@0.22.0", "", {}, "sha512-x4aFNBKn2KVQRuNVC5A7SnrSCSqyfIWmm1DvubjbO9iKFe7ith5niw8dqSFBekYBg2Fwy+eMg4sEFNVvCAdo6g=="], - - "@module-federation/webpack-bundler-runtime": ["@module-federation/webpack-bundler-runtime@0.22.0", "", { "dependencies": { "@module-federation/runtime": "0.22.0", "@module-federation/sdk": "0.22.0" } }, "sha512-aM8gCqXu+/4wBmJtVeMeeMN5guw3chf+2i6HajKtQv7SJfxV/f4IyNQJUeUQu9HfiAZHjqtMV5Lvq/Lvh8LdyA=="], - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], "@noble/curves": ["@noble/curves@2.2.0", "", { "dependencies": { "@noble/hashes": "2.2.0" } }, "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ=="], "@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="], - "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], - - "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], - - "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - "@oxc-project/types": ["@oxc-project/types@0.95.0", "", {}, "sha512-vACy7vhpMPhjEJhULNxrdR0D943TkA/MigMpJCHmBHvMXxRStRi/dPtTlfQ3uDwWSzRpT8z+7ImjZVf8JWBocQ=="], - "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], - "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], "@poppinss/macroable": ["@poppinss/macroable@1.1.2", "", {}, "sha512-FAVBRzzWhYP5mA3lCwLH1A0fKBqq5anyjGet90Z81aRK5c/+LTGUE1zJhZrErjaenBSOOI9BVUs3WVmotneFQA=="], "@quansync/fs": ["@quansync/fs@1.0.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="], - "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA=="], - - "@radix-ui/react-slot": ["@radix-ui/react-slot@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-compose-refs": "1.0.0" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw=="], - - "@react-native/assets-registry": ["@react-native/assets-registry@0.79.0", "", {}, "sha512-Rwvpu3A05lM1HVlX4klH4UR52JbQPDKc8gi2mst2REZL1KeVgJRJxPPw8d8euVlYcq/s8XI1Ol827JaRtSZBTA=="], - - "@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.76.9", "", { "dependencies": { "@react-native/codegen": "0.76.9" } }, "sha512-vxL/vtDEIYHfWKm5oTaEmwcnNGsua/i9OjIxBDBFiJDu5i5RU3bpmDiXQm/bJxrJNPRp5lW0I0kpGihVhnMAIQ=="], - - "@react-native/babel-preset": ["@react-native/babel-preset@0.76.9", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-shorthand-properties": "^7.24.7", "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "0.76.9", "babel-plugin-syntax-hermes-parser": "^0.25.1", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-TbSeCplCM6WhL3hR2MjC/E1a9cRnMLz7i767T7mP90oWkklEjyPxWl+0GGoVGnJ8FC/jLUupg/HvREKjjif6lw=="], - - "@react-native/codegen": ["@react-native/codegen@0.79.0", "", { "dependencies": { "glob": "^7.1.1", "hermes-parser": "0.25.1", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" }, "peerDependencies": { "@babel/core": "*" } }, "sha512-D8bFlD0HH9SMUI00svdg64hEvLbu4ETeWQDlmEP8WmNbuILjwoLFqbnBmlGn69Tot0DM1PuBd1l1ooIzs8sU7w=="], - - "@react-native/community-cli-plugin": ["@react-native/community-cli-plugin@0.79.0", "", { "dependencies": { "@react-native/dev-middleware": "0.79.0", "chalk": "^4.0.0", "debug": "^2.2.0", "invariant": "^2.2.4", "metro": "^0.82.0", "metro-config": "^0.82.0", "metro-core": "^0.82.0", "semver": "^7.1.3" }, "peerDependencies": { "@react-native-community/cli": "*" }, "optionalPeers": ["@react-native-community/cli"] }, "sha512-pl+aSXxGj3ug80FpMDrArjxUbJWY2ibWiSP3MLKX+Xk7An2GUmFFjCzNVSbs0jzWv8814EG2oI60/GH2RXwE4g=="], - - "@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.79.6", "", {}, "sha512-lIK/KkaH7ueM22bLO0YNaQwZbT/oeqhaghOvmZacaNVbJR1Cdh/XAqjT8FgCS+7PUnbxA8B55NYNKGZG3O2pYw=="], - - "@react-native/dev-middleware": ["@react-native/dev-middleware@0.79.6", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.79.6", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", "debug": "^2.2.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", "ws": "^6.2.3" } }, "sha512-BK3GZBa9c7XSNR27EDRtxrgyyA3/mf1j3/y+mPk7Ac0Myu85YNrXnC9g3mL5Ytwo0g58TKrAIgs1fF2Q5Mn6mQ=="], - - "@react-native/gradle-plugin": ["@react-native/gradle-plugin@0.79.0", "", {}, "sha512-c+/qKnmTx3kf8xZesp2BkZ9pAQVSnEPZziQUwviSJaq9jm8tKb/B8fyGG8yIuw/ZTKyGprD+ByzUSzJmCpC/Ow=="], - - "@react-native/js-polyfills": ["@react-native/js-polyfills@0.79.0", "", {}, "sha512-+8lk/zP90JC9xZBGhI8TPqqR1Y5dYXwXvfhXygr/LlHoo+H8TeQxcPrXWdT+PWOJl6Gf7dbCOGh9Std8J7CSQA=="], - - "@react-native/metro-babel-transformer": ["@react-native/metro-babel-transformer@0.86.2", "", { "dependencies": { "@babel/core": "^7.25.2", "@react-native/babel-preset": "0.86.2", "hermes-parser": "0.36.0", "nullthrows": "^1.1.1" } }, "sha512-mX1wgLErdb2hDgXJr9zM9SWLe+ZteZTFTwRWGOQ53yEJwc9DVSnxdTlAtVdMeOj2ntycKh0R8jYdat2Am43cwQ=="], - - "@react-native/metro-config": ["@react-native/metro-config@0.86.2", "", { "dependencies": { "@react-native/js-polyfills": "0.86.2", "@react-native/metro-babel-transformer": "0.86.2", "metro-config": "^0.84.3", "metro-runtime": "^0.84.3" } }, "sha512-hJno256j+MS0b3JD1aD3ouTGZVacKNVBuXL2atMQQ8BZ060vl1ptnZ83y569aDW+/rgFSOcqn6ydKeSz4uUKQQ=="], - - "@react-native/normalize-colors": ["@react-native/normalize-colors@0.76.8", "", {}, "sha512-FRjRvs7RgsXjkbGSOjYSxhX5V70c0IzA/jy3HXeYpATMwD9fOR1DbveLW497QGsVdCa0vThbJUtR8rIzAfpHQA=="], - - "@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.79.0", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.0.0", "react": "*", "react-native": "*" }, "optionalPeers": ["@types/react"] }, "sha512-tCT1sHSI1O5KSclDwNfnkLTLe3cgiyYWjIlmNxWJHqhCCz017HGOS/oH0zs0ZgxYwN7xCzTkqY330XMDo+yj2g=="], - - "@react-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.18.16", "", { "dependencies": { "@react-navigation/elements": "^2.9.38", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { "@react-navigation/native": "^7.3.16", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-5tBFuxvsCQBvYauDZK6u/uPdD86i/caWgBzABlZfTh8W2J7YZSHjT9SWOsquSMmR6dpcNj6idMd4utCNzuOnZg=="], - - "@react-navigation/core": ["@react-navigation/core@7.21.12", "", { "dependencies": { "@react-navigation/routers": "^7.6.4", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "query-string": "^7.1.3", "react-is": "^19.1.0", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-FopGMGy5df+IMqUYg9w7ygwK0oP4RaQbMnpb7VFP30+vZdd8MqsAGc+bZZwdiVSnzK9hzhgJtZV9XF0ZFlUYOQ=="], - - "@react-navigation/elements": ["@react-navigation/elements@2.9.38", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.3.16", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-M/2HWDiSO4zO7VyfAnovrTPWOAzTC+1DnVT7ZWZW93+/KVRF9OYlqixIUpPenQ0iQh2j1EaQ4zOe/134Arisqw=="], - - "@react-navigation/native": ["@react-navigation/native@7.3.16", "", { "dependencies": { "@react-navigation/core": "^7.21.12", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "standard-navigation": "^0.0.8", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-Wy/6mai2HpA5AEVFk7JFfvv4RUArwuN2UenP/fc3NK7kOyfSlNu1tmb/3JDPpMzPIRbF1MPB8PKAHjVB29qNEQ=="], - - "@react-navigation/native-stack": ["@react-navigation/native-stack@7.18.8", "", { "dependencies": { "@react-navigation/elements": "^2.9.38", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.3.16", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-Abcdt2ndmbeNSzJCXggxduK7X9yvMIajPNPdHgxsAZaA+16aIhb7TXZ2kRXprStZX5TRg8x+8G07Ne3kFwIQDg=="], - - "@react-navigation/routers": ["@react-navigation/routers@7.6.4", "", { "dependencies": { "nanoid": "^3.3.11" } }, "sha512-GI7eJm8/KsZUQaYcXvEExikKurRZRgEsSzyZ7faENfi65yqJBCXjDMwyN1pF6pNW1MoLH1ErDwDivFxY6BzD3w=="], - "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-beta.45", "", { "os": "android", "cpu": "arm64" }, "sha512-bfgKYhFiXJALeA/riil908+2vlyWGdwa7Ju5S+JgWZYdR4jtiPOGdM6WLfso1dojCh+4ZWeiTwPeV9IKQEX+4g=="], "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.45", "", { "os": "darwin", "cpu": "arm64" }, "sha512-xjCv4CRVsSnnIxTuyH1RDJl5OEQ1c9JYOwfDAHddjJDxCw46ZX9q80+xq7Eok7KC4bRSZudMJllkvOKv0T9SeA=="], @@ -760,56 +241,6 @@ "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.45", "", {}, "sha512-Le9ulGCrD8ggInzWw/k2J8QcbPz7eGIOWqfJ2L+1R0Opm7n6J37s2hiDWlh6LJN0Lk9L5sUzMvRHKW7UxBZsQA=="], - "@rsbuild/core": ["@rsbuild/core@1.7.3", "", { "dependencies": { "@rspack/core": "~1.7.5", "@rspack/lite-tapable": "~1.1.0", "@swc/helpers": "^0.5.18", "core-js": "~3.47.0", "jiti": "^2.6.1" }, "bin": { "rsbuild": "bin/rsbuild.js" } }, "sha512-kI1oQvCXbQYxUvQPnDLdjSX4gFsbrFNpuUj6jXEJ7IcJ74Q+n4oeFj74/8tKerhxhe0L90m/ZQfzLeN5ORGA9w=="], - - "@rsbuild/plugin-check-syntax": ["@rsbuild/plugin-check-syntax@1.3.0", "", { "dependencies": { "acorn": "^8.14.0", "browserslist-to-es-version": "^1.0.0", "htmlparser2": "10.0.0", "picocolors": "^1.1.1", "source-map": "^0.7.4" }, "peerDependencies": { "@rsbuild/core": "1.x" }, "optionalPeers": ["@rsbuild/core"] }, "sha512-lHrd6hToPFVOGWr0U/Ox7pudHWdhPSFsr2riWpjNRlUuwiXdU2SYMROaVUCrLJvYFzJyEMsFOi1w59rBQCG2HQ=="], - - "@rsbuild/plugin-css-minimizer": ["@rsbuild/plugin-css-minimizer@1.1.1", "", { "dependencies": { "css-minimizer-webpack-plugin": "7.0.2", "reduce-configs": "^1.1.1" }, "peerDependencies": { "@rsbuild/core": "^1.0.0 || ^2.0.0-0" }, "optionalPeers": ["@rsbuild/core"] }, "sha512-cTEAqiCR8X6iJ4+r0Gl0bC+bW8MUAqqXNyVBu7N0DrEatr9gsqo4e3x2d8gCyOnOUNsF6W35LvDWREsCCzDRHg=="], - - "@rsbuild/plugin-node-polyfill": ["@rsbuild/plugin-node-polyfill@1.4.6", "", { "dependencies": { "assert": "^2.1.0", "browserify-zlib": "^0.2.0", "buffer": "^5.7.1", "console-browserify": "^1.2.0", "constants-browserify": "^1.0.0", "crypto-browserify": "^3.12.1", "domain-browser": "^5.7.0", "events": "^3.3.0", "https-browserify": "^1.0.0", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "process": "^0.11.10", "punycode": "^2.3.1", "querystring-es3": "^0.2.1", "readable-stream": "^4.7.0", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", "string_decoder": "^1.3.0", "timers-browserify": "^2.0.12", "tty-browserify": "^0.0.1", "url": "^0.11.4", "util": "^0.12.5", "vm-browserify": "^1.1.2" }, "peerDependencies": { "@rsbuild/core": "^1.0.0 || ^2.0.0" }, "optionalPeers": ["@rsbuild/core"] }, "sha512-uJHDmfinJe00h9JvZfzOeiYYpIkAOorMsX2nv9c6n+YQBFgQalJZ3O2q2CLoqtwspQl5Pa8WU1hsUvzRi1Ro3g=="], - - "@rsbuild/plugin-vue": ["@rsbuild/plugin-vue@1.2.9", "", { "dependencies": { "rspack-vue-loader": "^17.5.0" }, "peerDependencies": { "@rsbuild/core": "^1.0.0 || ^2.0.0-0" }, "optionalPeers": ["@rsbuild/core"] }, "sha512-ufD4eufkZZlNK3iY5NWg57AMK9qrTFmHycDkSTfRTua4Qczf7q9s7rCLYVfVv2w36cOXJIewM+376jD0zaQShQ=="], - - "@rsdoctor/client": ["@rsdoctor/client@1.2.3", "", {}, "sha512-KzfRONtUFMOhgyd9Kur9C/eqh+qPE0UDQEwp/uCMIQHwmompGgChuGniVENd2mGgkZX4MDHubFSvjoeI7j4UBg=="], - - "@rsdoctor/core": ["@rsdoctor/core@1.2.3", "", { "dependencies": { "@rsbuild/plugin-check-syntax": "1.3.0", "@rsdoctor/graph": "1.2.3", "@rsdoctor/sdk": "1.2.3", "@rsdoctor/types": "1.2.3", "@rsdoctor/utils": "1.2.3", "axios": "^1.11.0", "browserslist-load-config": "^1.0.0", "enhanced-resolve": "5.12.0", "filesize": "^10.1.6", "fs-extra": "^11.1.1", "lodash": "^4.17.21", "path-browserify": "1.0.1", "semver": "^7.7.2", "source-map": "^0.7.4" } }, "sha512-7o+SoN0JVwvxi0LSToA9G5PvSDag9ri0pQ/krlsJdkg2aVCRSR1xVjS8pzaKR9F+Q5Mnj6RixYOhIkWqWoMWFw=="], - - "@rsdoctor/graph": ["@rsdoctor/graph@1.2.3", "", { "dependencies": { "@rsdoctor/types": "1.2.3", "@rsdoctor/utils": "1.2.3", "lodash.unionby": "^4.8.0", "source-map": "^0.7.4" } }, "sha512-HYnUGnpWfkvrwex0VvfP4G5BTe/18bc03GHTM4iBwRuVkgi2PN1OkU/iGFSS3AbctnHLNQMC2NFuV8+U1wzynw=="], - - "@rsdoctor/rspack-plugin": ["@rsdoctor/rspack-plugin@1.2.3", "", { "dependencies": { "@rsdoctor/core": "1.2.3", "@rsdoctor/graph": "1.2.3", "@rsdoctor/sdk": "1.2.3", "@rsdoctor/types": "1.2.3", "@rsdoctor/utils": "1.2.3", "lodash": "^4.17.21" }, "peerDependencies": { "@rspack/core": "*" }, "optionalPeers": ["@rspack/core"] }, "sha512-IW0dyCmEC9Sxz7pHpUPDIjUTpFdxPocIGrcw04J5CgD5hYbyDrJ6ubDRAY4W6jm4CGvr0524s4xfW2pfsKY1Ew=="], - - "@rsdoctor/sdk": ["@rsdoctor/sdk@1.2.3", "", { "dependencies": { "@rsdoctor/client": "1.2.3", "@rsdoctor/graph": "1.2.3", "@rsdoctor/types": "1.2.3", "@rsdoctor/utils": "1.2.3", "@types/fs-extra": "^11.0.4", "body-parser": "1.20.3", "cors": "2.8.5", "dayjs": "1.11.13", "fs-extra": "^11.1.1", "json-cycle": "^1.5.0", "open": "^8.4.2", "sirv": "2.0.4", "socket.io": "4.8.1", "source-map": "^0.7.4", "tapable": "2.2.2" } }, "sha512-kd6JQKNihmfwKrem2LhGih5MV6zb/RBNqnkuu/BDTuOClaKzq9JEbQlfYstGDv1yF3ECR/OEOCKNGNbnCbNtmg=="], - - "@rsdoctor/types": ["@rsdoctor/types@1.2.3", "", { "dependencies": { "@types/connect": "3.4.38", "@types/estree": "1.0.5", "@types/tapable": "2.2.7", "source-map": "^0.7.4" }, "peerDependencies": { "@rspack/core": "*", "webpack": "5.x" }, "optionalPeers": ["@rspack/core", "webpack"] }, "sha512-UMh4zdyKs3K4Jh7YQvHiaXKY9c7gFwCUcRaPtpB4XjlzE3lML21d0SBAXkGduwz9AmhuTRQCqaLKA2NNuDUivQ=="], - - "@rsdoctor/utils": ["@rsdoctor/utils@1.2.3", "", { "dependencies": { "@babel/code-frame": "7.26.2", "@rsdoctor/types": "1.2.3", "@types/estree": "1.0.5", "acorn": "^8.10.0", "acorn-import-attributes": "^1.9.5", "acorn-walk": "8.3.4", "connect": "3.7.0", "deep-eql": "4.1.4", "envinfo": "7.14.0", "filesize": "^10.1.6", "fs-extra": "^11.1.1", "get-port": "5.1.1", "json-stream-stringify": "3.0.1", "lines-and-columns": "2.0.4", "picocolors": "^1.1.1", "rslog": "^1.2.9", "strip-ansi": "^6.0.1" } }, "sha512-HawWrcqXeqdhj4dKhdjJg4BXvstcQauYSRx0cnYH78f7z9PW60Smr6vYpByXC87rK3JKpa6CNiZi+qhI5yTAog=="], - - "@rspack/binding": ["@rspack/binding@1.7.12", "", { "optionalDependencies": { "@rspack/binding-darwin-arm64": "1.7.12", "@rspack/binding-darwin-x64": "1.7.12", "@rspack/binding-linux-arm64-gnu": "1.7.12", "@rspack/binding-linux-arm64-musl": "1.7.12", "@rspack/binding-linux-x64-gnu": "1.7.12", "@rspack/binding-linux-x64-musl": "1.7.12", "@rspack/binding-wasm32-wasi": "1.7.12", "@rspack/binding-win32-arm64-msvc": "1.7.12", "@rspack/binding-win32-ia32-msvc": "1.7.12", "@rspack/binding-win32-x64-msvc": "1.7.12" } }, "sha512-f4HHuLbvuld8Ba4iB/4ibse5XrKxFrgmM3S4P2AOKnPlekAFlBjmltCuaTL/W2ggYvILaVY+YcFXrEH1rrKeQA=="], - - "@rspack/binding-darwin-arm64": ["@rspack/binding-darwin-arm64@1.7.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-rbFprJaJiqrmfy8SHth8EsoRS0wg4bXcucwj9NiMzpGFq14Opw8c04iQ6H9BECYzgmN0PKZ9rh41LdVvhdZe4A=="], - - "@rspack/binding-darwin-x64": ["@rspack/binding-darwin-x64@1.7.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-jnOp+/UXOJa9xqUb8KXH03sysoO2e4Ij6tw6MqDdmdj8n/A8PQENRPUbW9AwXpPtVDJPus9r4fi7b3+6e4B8Hg=="], - - "@rspack/binding-linux-arm64-gnu": ["@rspack/binding-linux-arm64-gnu@1.7.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-C8owWG+yvo7X0oVLIXetkoJhIFBP1LYNcAQqtgLmJnQLQDklGuP83dKC+zISGQWpjawHfZ1ER96vLgoTrxKZdw=="], - - "@rspack/binding-linux-arm64-musl": ["@rspack/binding-linux-arm64-musl@1.7.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-i51WWI64aRpsfSki6rN0aepPqXkVfS+vZM7+4bWDcmnhUmdMvhIPcYg0QRk3DtyJnu33jqNLM0WHY78k00NyfA=="], - - "@rspack/binding-linux-x64-gnu": ["@rspack/binding-linux-x64-gnu@1.7.12", "", { "os": "linux", "cpu": "x64" }, "sha512-MSos0FuPEefqo9V92ULd5hggKG29EkSNg1zDcypy0OkpsKh5pfjVxTLYFXgTcVyFoUQQbdG8zFBzYbwmJ8V4ew=="], - - "@rspack/binding-linux-x64-musl": ["@rspack/binding-linux-x64-musl@1.7.12", "", { "os": "linux", "cpu": "x64" }, "sha512-JcAMVKXOnjfpC3coWjCFPWD3Yl8RBw6a+IXQQ8mfRlHaHMIiOv8IfZqx15XRxMUn49CtP7Z0Na8iiAg2aKrcfw=="], - - "@rspack/binding-wasm32-wasi": ["@rspack/binding-wasm32-wasi@1.7.12", "", { "dependencies": { "@napi-rs/wasm-runtime": "1.0.7" }, "cpu": "none" }, "sha512-n+ZqP6ZMc0nhOgvadg5VhEs9ojtbES80AcWeFnmGkbzIszvGSO63GKNiRkXtjJ9KFuRzytbbmsCqkUVH+Tywxg=="], - - "@rspack/binding-win32-arm64-msvc": ["@rspack/binding-win32-arm64-msvc@1.7.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-8+h5fYDXYdmugbdfZ+D1y8IQ3rv2EhSfyGP7vBe+bjNyaMa4jWrpucmZbtxojUL1AzaeuHbvMdj9UO/gelk/+g=="], - - "@rspack/binding-win32-ia32-msvc": ["@rspack/binding-win32-ia32-msvc@1.7.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-cDMGwTRSa2p9fNBVe1wTRkF2AEXZ9ARWW36QeC5CkLaI0Ezz8lvhF2+CSOPnhaQ1O1qtn0L0SF+lFnrY+I7xGQ=="], - - "@rspack/binding-win32-x64-msvc": ["@rspack/binding-win32-x64-msvc@1.7.12", "", { "os": "win32", "cpu": "x64" }, "sha512-wIqFvlgFqrgUyj/6S/FJcvShnkZOmIeXTfqvheLY67MGq8qd8jb1YimQVKAIrmWB3yuJKUFACI3Ag1UBtEedEA=="], - - "@rspack/core": ["@rspack/core@1.7.12", "", { "dependencies": { "@module-federation/runtime-tools": "0.22.0", "@rspack/binding": "1.7.12", "@rspack/lite-tapable": "1.1.0" }, "peerDependencies": { "@swc/helpers": ">=0.5.1" }, "optionalPeers": ["@swc/helpers"] }, "sha512-6CwFIHlhRmXfZoMj3v9MZ1SMTPBn+cHVXeMIeaGp5sufqinKsISbsqHu6ZMJu2wDSmZLdmQJX6zLxkhcAUlhkQ=="], - - "@rspack/lite-tapable": ["@rspack/lite-tapable@1.1.5", "", {}, "sha512-uzB782zJbFTM3ta+e2Glikx36dca/6Y+DXyvFN+wb0Tx5ItIW+g03A0t3amP3LGzPHSkb0k81VHCm4jxLQwfag=="], - "@scure/base": ["@scure/base@2.2.0", "", {}, "sha512-b8XEupJibegiXV+tDUseI8oLQc8ei3d/4Jkb2RpbHh3MfE054ov3uIz2dhFkB3FI8iwYkEh0gGCApkrYggkPNg=="], "@scure/bip32": ["@scure/bip32@2.2.0", "", { "dependencies": { "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "@scure/base": "2.2.0" } }, "sha512-zFr7t2F+a9+5tB7QbarF2HQNYrgjCNaoLAupZdKkrFMYMozJf5zqH2WJCQibMzm1qQ0QogrxVGO3qXfQDYMaQg=="], @@ -822,14 +253,6 @@ "@sideway/pinpoint": ["@sideway/pinpoint@2.0.0", "", {}, "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="], - "@sinclair/typebox": ["@sinclair/typebox@0.27.12", "", {}, "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g=="], - - "@sinonjs/commons": ["@sinonjs/commons@3.0.1", "", { "dependencies": { "type-detect": "4.0.8" } }, "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ=="], - - "@sinonjs/fake-timers": ["@sinonjs/fake-timers@10.3.0", "", { "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA=="], - - "@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="], - "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.9", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA=="], @@ -872,8 +295,6 @@ "@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="], - "@tootallnate/once": ["@tootallnate/once@2.0.1", "", {}, "sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ=="], - "@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="], "@turbo/darwin-64": ["@turbo/darwin-64@2.8.19", "", { "os": "darwin", "cpu": "x64" }, "sha512-WNzpqJV7rO/oKmsxjtSFXuV87fZb/qxzeGWXJng4oHFz7iPOFSWrPb2gTazJgoTvXUfe1kOH2o0F+zFrerwzRw=="], @@ -890,22 +311,10 @@ "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], - "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], - - "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], - - "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], - - "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], - "@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="], - "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], - "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], - "@types/cors": ["@types/cors@2.8.19", "", { "dependencies": { "@types/node": "*" } }, "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg=="], - "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], "@types/d3-contour": ["@types/d3-contour@3.0.6", "", { "dependencies": { "@types/d3-array": "*", "@types/geojson": "*" } }, "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg=="], @@ -922,460 +331,106 @@ "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], - "@types/fs-extra": ["@types/fs-extra@11.0.4", "", { "dependencies": { "@types/jsonfile": "*", "@types/node": "*" } }, "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ=="], - "@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="], - "@types/graceful-fs": ["@types/graceful-fs@4.1.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ=="], - - "@types/hammerjs": ["@types/hammerjs@2.0.46", "", {}, "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw=="], - - "@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="], - - "@types/istanbul-lib-report": ["@types/istanbul-lib-report@3.0.3", "", { "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA=="], - - "@types/istanbul-reports": ["@types/istanbul-reports@3.0.4", "", { "dependencies": { "@types/istanbul-lib-report": "*" } }, "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ=="], - - "@types/jest": ["@types/jest@29.5.14", "", { "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" } }, "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ=="], - - "@types/jsdom": ["@types/jsdom@20.0.1", "", { "dependencies": { "@types/node": "*", "@types/tough-cookie": "*", "parse5": "^7.0.0" } }, "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ=="], - - "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - - "@types/jsonfile": ["@types/jsonfile@6.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ=="], - "@types/node": ["@types/node@25.7.0", "", { "dependencies": { "undici-types": "~7.21.0" } }, "sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg=="], - "@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="], - "@types/qrcode": ["@types/qrcode@1.5.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw=="], - "@types/react": ["@types/react@19.0.14", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-ixLZ7zG7j1fM0DijL9hDArwhwcCb4vqmePgwtV0GfnkHRSCUEv4LvzarcTdhoqgyMznUx/EhoTUv31CKZzkQlw=="], - - "@types/react-test-renderer": ["@types/react-test-renderer@18.3.1", "", { "dependencies": { "@types/react": "^18" } }, "sha512-vAhnk0tG2eGa37lkU9+s5SoroCsRI08xnsWFiAXOuPH2jqzMbcXvKExXViPi1P5fIklDeCvXqyrdmipFaSkZrA=="], - - "@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="], - - "@types/tapable": ["@types/tapable@2.2.7", "", { "dependencies": { "tapable": "^2.2.0" } }, "sha512-D6QzACV9vNX3r8HQQNTOnpG+Bv1rko+yEA82wKs3O9CQ5+XW7HI7TED17/UE7+5dIxyxZIWTxKbsBeF6uKFCwA=="], - - "@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="], - "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], "@types/validator": ["@types/validator@13.15.10", "", {}, "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA=="], - "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - - "@types/yargs": ["@types/yargs@17.0.35", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg=="], - - "@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="], - "@typeschema/class-validator": ["@typeschema/class-validator@0.3.0", "", { "dependencies": { "@typeschema/core": "0.14.0" }, "peerDependencies": { "class-validator": "^0.14.1" }, "optionalPeers": ["class-validator"] }, "sha512-OJSFeZDIQ8EK1HTljKLT5CItM2wsbgczLN8tMEfz3I1Lmhc5TBfkZ0eikFzUC16tI3d1Nag7um6TfCgp2I2Bww=="], "@typeschema/core": ["@typeschema/core@0.14.0", "", { "peerDependencies": { "@types/json-schema": "^7.0.15" }, "optionalPeers": ["@types/json-schema"] }, "sha512-Ia6PtZHcL3KqsAWXjMi5xIyZ7XMH4aSnOQes8mfMLx+wGFGtGRNlwe6Y7cYvX+WfNK67OL0/HSe9t8QDygV0/w=="], "@typescript-eslint/types": ["@typescript-eslint/types@8.57.1", "", {}, "sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ=="], - "@urql/core": ["@urql/core@5.2.0", "", { "dependencies": { "@0no-co/graphql.web": "^1.0.13", "wonka": "^6.3.2" } }, "sha512-/n0ieD0mvvDnVAXEQgX/7qJiVcvYvNkOHeBvkwtylfjydar123caCXcl58PXFY11oU1oquJocVXHxLAbtv4x1A=="], - - "@urql/exchange-retry": ["@urql/exchange-retry@1.3.2", "", { "dependencies": { "@urql/core": "^5.1.2", "wonka": "^6.3.2" } }, "sha512-TQMCz2pFJMfpNxmSfX1VSfTjwUIFx/mL+p1bnfM1xjjdla7Z+KnGMW/EhFbpckp3LyWAH4PgOsMwOMnIN+MBFg=="], - "@valibot/to-json-schema": ["@valibot/to-json-schema@1.7.0", "", { "peerDependencies": { "valibot": "^1.4.0" } }, "sha512-Y3pPVibbIOHzohrlxSINvO7w/bvXkoYS3BQHoImV9ynE+bXKf171bdMucPurV2zp7gdmt0L1HCcNAsbo7cFRQw=="], "@vinejs/compiler": ["@vinejs/compiler@3.0.0", "", {}, "sha512-v9Lsv59nR56+bmy2p0+czjZxsLHwaibJ+SV5iK9JJfehlJMa501jUJQqqz4X/OqKXrxtE3uTQmSqjUqzF3B2mw=="], "@vinejs/vine": ["@vinejs/vine@3.0.1", "", { "dependencies": { "@poppinss/macroable": "^1.0.4", "@types/validator": "^13.12.2", "@vinejs/compiler": "^3.0.0", "camelcase": "^8.0.0", "dayjs": "^1.11.13", "dlv": "^1.1.3", "normalize-url": "^8.0.1", "validator": "^13.12.0" } }, "sha512-ZtvYkYpZOYdvbws3uaOAvTFuvFXoQGAtmzeiXu+XSMGxi5GVsODpoI9Xu9TplEMuD/5fmAtBbKb9cQHkWkLXDQ=="], - "@vue/compiler-core": ["@vue/compiler-core@3.5.41", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/shared": "3.5.41", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg=="], - - "@vue/compiler-dom": ["@vue/compiler-dom@3.5.41", "", { "dependencies": { "@vue/compiler-core": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw=="], - - "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.41", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/compiler-core": "3.5.41", "@vue/compiler-dom": "3.5.41", "@vue/compiler-ssr": "3.5.41", "@vue/shared": "3.5.41", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.19", "source-map-js": "^1.2.1" } }, "sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ=="], - - "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.41", "", { "dependencies": { "@vue/compiler-dom": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A=="], - - "@vue/reactivity": ["@vue/reactivity@3.5.41", "", { "dependencies": { "@vue/shared": "3.5.41" } }, "sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA=="], - - "@vue/runtime-core": ["@vue/runtime-core@3.5.41", "", { "dependencies": { "@vue/reactivity": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg=="], - - "@vue/runtime-dom": ["@vue/runtime-dom@3.5.41", "", { "dependencies": { "@vue/reactivity": "3.5.41", "@vue/runtime-core": "3.5.41", "@vue/shared": "3.5.41", "csstype": "^3.2.3" } }, "sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw=="], - - "@vue/server-renderer": ["@vue/server-renderer@3.5.41", "", { "dependencies": { "@vue/compiler-ssr": "3.5.41", "@vue/runtime-dom": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ=="], - - "@vue/shared": ["@vue/shared@3.5.41", "", {}, "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA=="], - - "@webassemblyjs/ast": ["@webassemblyjs/ast@1.14.1", "", { "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ=="], - - "@webassemblyjs/floating-point-hex-parser": ["@webassemblyjs/floating-point-hex-parser@1.13.2", "", {}, "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA=="], - - "@webassemblyjs/helper-api-error": ["@webassemblyjs/helper-api-error@1.13.2", "", {}, "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ=="], - - "@webassemblyjs/helper-buffer": ["@webassemblyjs/helper-buffer@1.14.1", "", {}, "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA=="], - - "@webassemblyjs/helper-numbers": ["@webassemblyjs/helper-numbers@1.13.2", "", { "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA=="], - - "@webassemblyjs/helper-wasm-bytecode": ["@webassemblyjs/helper-wasm-bytecode@1.13.2", "", {}, "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA=="], - - "@webassemblyjs/helper-wasm-section": ["@webassemblyjs/helper-wasm-section@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/wasm-gen": "1.14.1" } }, "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw=="], - - "@webassemblyjs/ieee754": ["@webassemblyjs/ieee754@1.13.2", "", { "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw=="], - - "@webassemblyjs/leb128": ["@webassemblyjs/leb128@1.13.2", "", { "dependencies": { "@xtuc/long": "4.2.2" } }, "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw=="], - - "@webassemblyjs/utf8": ["@webassemblyjs/utf8@1.13.2", "", {}, "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ=="], - - "@webassemblyjs/wasm-edit": ["@webassemblyjs/wasm-edit@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/helper-wasm-section": "1.14.1", "@webassemblyjs/wasm-gen": "1.14.1", "@webassemblyjs/wasm-opt": "1.14.1", "@webassemblyjs/wasm-parser": "1.14.1", "@webassemblyjs/wast-printer": "1.14.1" } }, "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ=="], - - "@webassemblyjs/wasm-gen": ["@webassemblyjs/wasm-gen@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/ieee754": "1.13.2", "@webassemblyjs/leb128": "1.13.2", "@webassemblyjs/utf8": "1.13.2" } }, "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg=="], - - "@webassemblyjs/wasm-opt": ["@webassemblyjs/wasm-opt@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/wasm-gen": "1.14.1", "@webassemblyjs/wasm-parser": "1.14.1" } }, "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw=="], - - "@webassemblyjs/wasm-parser": ["@webassemblyjs/wasm-parser@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/ieee754": "1.13.2", "@webassemblyjs/leb128": "1.13.2", "@webassemblyjs/utf8": "1.13.2" } }, "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ=="], - - "@webassemblyjs/wast-printer": ["@webassemblyjs/wast-printer@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw=="], - - "@xmldom/xmldom": ["@xmldom/xmldom@0.8.13", "", {}, "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw=="], - "@xrplf/isomorphic": ["@xrplf/isomorphic@1.0.2", "", { "dependencies": { "@noble/hashes": "^2.0.1", "eventemitter3": "5.0.1", "ws": "^8.20.0" } }, "sha512-ncZUdMXr6VlSXtdoiDi0jTH+gBrgGxwVeEidhoegII3PmyErbQsyj6e+j7acmR4LW/lvBkPkzb9QzRfJH0n3rA=="], "@xrplf/secret-numbers": ["@xrplf/secret-numbers@3.0.0", "", { "dependencies": { "@xrplf/isomorphic": "^1.0.2", "ripple-keypairs": "^3.0.0" } }, "sha512-qpGhAZXv5noMDjCtfzq5NK0y5rrdwTVjKhhPcAYSE+a/gogBOgqdpCKyieprVVPCnmVmJnGeRoZKBAqpCGegsA=="], - "@xtuc/ieee754": ["@xtuc/ieee754@1.2.0", "", {}, "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="], - - "@xtuc/long": ["@xtuc/long@4.2.2", "", {}, "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="], - - "abab": ["abab@2.0.6", "", {}, "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA=="], - - "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], - - "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], - "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], - "acorn-globals": ["acorn-globals@7.0.1", "", { "dependencies": { "acorn": "^8.1.0", "acorn-walk": "^8.0.2" } }, "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q=="], - - "acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="], - - "acorn-walk": ["acorn-walk@8.3.4", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g=="], - "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], "ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], - "ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], - - "ajv-keywords": ["ajv-keywords@5.1.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3" }, "peerDependencies": { "ajv": "^8.8.2" } }, "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw=="], - - "anser": ["anser@1.4.10", "", {}, "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww=="], - - "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="], - "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "ansis": ["ansis@4.3.0", "", {}, "sha512-44mvgtPvohuU/70DdY5Oz2AIrLJ9k6/5x4KmoSvPwO+5Moijo0+N9D0fKbbYZQWP1hNm5CpOf+E01jhxG/r8xg=="], - "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], - - "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], - - "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="], - - "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], - "aria-query": ["aria-query@5.3.1", "", {}, "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g=="], "arkregex": ["arkregex@0.0.5", "", { "dependencies": { "@ark/util": "0.56.0" } }, "sha512-ncYjBdLlh5/QnVsAA8De16Tc9EqmYM7y/WU9j+236KcyYNUXogpz3sC4ATIZYzzLxwI+0sEOaQLEmLmRleaEXw=="], "arktype": ["arktype@2.2.0", "", { "dependencies": { "@ark/schema": "0.56.0", "@ark/util": "0.56.0", "arkregex": "0.0.5" } }, "sha512-t54MZ7ti5BhOEvzEkgKnWvqj+UbDfWig+DHr5I34xatymPusKLS0lQpNJd8M6DzmIto2QGszHfNKoFIT8tMCZQ=="], - "array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="], - - "array-timsort": ["array-timsort@1.0.3", "", {}, "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ=="], - - "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], - - "asap": ["asap@2.0.6", "", {}, "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="], - - "asn1.js": ["asn1.js@4.10.1", "", { "dependencies": { "bn.js": "^4.0.0", "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" } }, "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw=="], - - "assert": ["assert@2.1.0", "", { "dependencies": { "call-bind": "^1.0.2", "is-nan": "^1.3.2", "object-is": "^1.1.5", "object.assign": "^4.1.4", "util": "^0.12.5" } }, "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw=="], - "ast-kit": ["ast-kit@2.2.0", "", { "dependencies": { "@babel/parser": "^7.28.5", "pathe": "^2.0.3" } }, "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw=="], "ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="], "async": ["async@2.6.4", "", { "dependencies": { "lodash": "^4.17.14" } }, "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA=="], - "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], - - "async-limiter": ["async-limiter@1.0.1", "", {}, "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="], - "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], - "at-least-node": ["at-least-node@1.0.0", "", {}, "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="], - - "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], - "axios": ["axios@1.16.1", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A=="], "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], - "babel-core": ["babel-core@7.0.0-bridge.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg=="], - - "babel-jest": ["babel-jest@29.7.0", "", { "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "peerDependencies": { "@babel/core": "^7.8.0" } }, "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg=="], - - "babel-plugin-istanbul": ["babel-plugin-istanbul@6.1.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-instrument": "^5.0.4", "test-exclude": "^6.0.0" } }, "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA=="], - - "babel-plugin-jest-hoist": ["babel-plugin-jest-hoist@29.6.3", "", { "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" } }, "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg=="], - - "babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.17", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w=="], - - "babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.13.0", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5", "core-js-compat": "^3.43.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A=="], - - "babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.8", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg=="], - - "babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="], - - "babel-plugin-react-native-web": ["babel-plugin-react-native-web@0.19.13", "", {}, "sha512-4hHoto6xaN23LCyZgL9LJZc3olmAxd7b6jDzlZnKXAh4rRAbZRKNBJoOOdp46OBqgy+K0t0guTj5/mhA8inymQ=="], - - "babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.25.1", "", { "dependencies": { "hermes-parser": "0.25.1" } }, "sha512-IVNpGzboFLfXZUAwkLFcI/bnqVbwky0jP3eBno4HKtqvQJAHBLdgxiG6lQ4to0+Q/YCN3PO0od5NZwIKyY4REQ=="], - - "babel-plugin-transform-flow-enums": ["babel-plugin-transform-flow-enums@0.0.2", "", { "dependencies": { "@babel/plugin-syntax-flow": "^7.12.1" } }, "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ=="], - - "babel-preset-current-node-syntax": ["babel-preset-current-node-syntax@1.2.0", "", { "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg=="], - - "babel-preset-expo": ["babel-preset-expo@12.0.12", "", { "dependencies": { "@babel/plugin-proposal-decorators": "^7.12.9", "@babel/plugin-transform-export-namespace-from": "^7.22.11", "@babel/plugin-transform-object-rest-spread": "^7.12.13", "@babel/plugin-transform-parameters": "^7.22.15", "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.23.0", "@react-native/babel-preset": "0.76.9", "babel-plugin-react-native-web": "~0.19.13", "react-refresh": "^0.14.2" }, "peerDependencies": { "babel-plugin-react-compiler": "^19.0.0-beta-9ee70a1-20241017", "react-compiler-runtime": "^19.0.0-beta-8a03594-20241020" }, "optionalPeers": ["babel-plugin-react-compiler", "react-compiler-runtime"] }, "sha512-qAuaGGZIN//DyQVackP7Czr1SMq5dYb5tpu/uQqL/f1bRARb74r+kBWQRLJGxQ3QujsEw13SyAodCZIOUoD6KQ=="], - - "babel-preset-jest": ["babel-preset-jest@29.6.3", "", { "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA=="], - - "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - - "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - - "base64id": ["base64id@2.0.0", "", {}, "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="], - - "baseline-browser-mapping": ["baseline-browser-mapping@2.11.12", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA=="], - "basic-ftp": ["basic-ftp@5.3.1", "", {}, "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw=="], - "better-opn": ["better-opn@3.0.2", "", { "dependencies": { "open": "^8.0.4" } }, "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ=="], - - "big-integer": ["big-integer@1.6.52", "", {}, "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg=="], - "bignumber.js": ["bignumber.js@10.0.2", "", {}, "sha512-E8Wp9O06QA6lneJ4aRUXKYf/1GIomqUEmUMwtIOMtDxf1U52ffJY+y7JBk/8wRafA8qOIqLnXQGqonYXZdBnFQ=="], - "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], - "birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], "bits-ui": ["bits-ui@2.18.1", "", { "dependencies": { "@floating-ui/core": "^1.7.1", "@floating-ui/dom": "^1.7.1", "esm-env": "^1.1.2", "runed": "^0.35.1", "svelte-toolbelt": "^0.10.6", "tabbable": "^6.2.0" }, "peerDependencies": { "@internationalized/date": "^3.8.1", "svelte": "^5.33.0" } }, "sha512-KkemzKFH4T3gt3H+P86JcnAWExjByv/6vlwjm/BoCwTPHu03yiCdxbghdJLvFReQTe0acCAiRcKfmixxD6XvlA=="], - "bn.js": ["bn.js@5.2.5", "", {}, "sha512-Vq886eXykuP5E6HcKSSStP3bJgrE6In5WKxVUvJ8XGpWWYs2xZHWqUwzCtGgEtBcxyd57KBFDPFoUfNzdaHCNg=="], - - "body-parser": ["body-parser@1.20.3", "", { "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" } }, "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g=="], - - "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], - - "bplist-creator": ["bplist-creator@0.1.0", "", { "dependencies": { "stream-buffers": "2.2.x" } }, "sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg=="], - - "bplist-parser": ["bplist-parser@0.3.2", "", { "dependencies": { "big-integer": "1.6.x" } }, "sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ=="], - - "brace-expansion": ["brace-expansion@2.1.4", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg=="], - - "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - - "brorand": ["brorand@1.1.0", "", {}, "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w=="], - - "browserify-aes": ["browserify-aes@1.2.0", "", { "dependencies": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", "create-hash": "^1.1.0", "evp_bytestokey": "^1.0.3", "inherits": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA=="], - - "browserify-cipher": ["browserify-cipher@1.0.1", "", { "dependencies": { "browserify-aes": "^1.0.4", "browserify-des": "^1.0.0", "evp_bytestokey": "^1.0.0" } }, "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w=="], - - "browserify-des": ["browserify-des@1.0.2", "", { "dependencies": { "cipher-base": "^1.0.1", "des.js": "^1.0.0", "inherits": "^2.0.1", "safe-buffer": "^5.1.2" } }, "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A=="], - - "browserify-rsa": ["browserify-rsa@4.1.1", "", { "dependencies": { "bn.js": "^5.2.1", "randombytes": "^2.1.0", "safe-buffer": "^5.2.1" } }, "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ=="], - - "browserify-sign": ["browserify-sign@4.2.6", "", { "dependencies": { "bn.js": "^5.2.3", "browserify-rsa": "^4.1.1", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", "elliptic": "^6.6.1", "inherits": "^2.0.4", "parse-asn1": "^5.1.9", "readable-stream": "^2.3.8", "safe-buffer": "^5.2.1" } }, "sha512-sd+Q65fjlWCYWtZKXiKfrUc8d+4jtp/8f0W2NkwzLtoW4bI6UDnWusLWIurHnmurW0XShIRxpwiOX4EoPtXUAg=="], - - "browserify-zlib": ["browserify-zlib@0.2.0", "", { "dependencies": { "pako": "~1.0.5" } }, "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA=="], - - "browserslist": ["browserslist@4.28.8", "", { "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", "electron-to-chromium": "^1.5.402", "node-releases": "^2.0.53", "update-browserslist-db": "^1.3.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA=="], - - "browserslist-load-config": ["browserslist-load-config@1.0.3", "", {}, "sha512-boNaPS4KlW6AITZQ60G+1oDJLuxauljDd7QNQFOYpRtldzcTDknMZ8awbwI0BT/8h1/Y/CG4k/tDOLip9lAGcg=="], - - "browserslist-to-es-version": ["browserslist-to-es-version@1.4.2", "", { "dependencies": { "browserslist": "^4.28.2" }, "bin": { "browserslist-to-es-version": "./dist/cli.js" } }, "sha512-3NV13pCv0wmPxxZZcekHAG6vt8rQ94w2c4/UBe3ZU3NDUm5TP+QFK3rjS6XeKWSHWpnPYNfQzlhnljka0BrEOA=="], - - "bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="], - - "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], - - "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], - - "buffer-xor": ["buffer-xor@1.0.3", "", {}, "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ=="], - - "builtin-status-codes": ["builtin-status-codes@3.0.0", "", {}, "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ=="], - "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], - "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], - "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], - "call-bind": ["call-bind@1.0.9", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" } }, "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ=="], - "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], - "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], - - "caller-callsite": ["caller-callsite@2.0.0", "", { "dependencies": { "callsites": "^2.0.0" } }, "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ=="], - - "caller-path": ["caller-path@2.0.0", "", { "dependencies": { "caller-callsite": "^2.0.0" } }, "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A=="], - - "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - "camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="], - "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="], - - "caniuse-api": ["caniuse-api@3.0.0", "", { "dependencies": { "browserslist": "^4.0.0", "caniuse-lite": "^1.0.0", "lodash.memoize": "^4.1.2", "lodash.uniq": "^4.5.0" } }, "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw=="], - - "caniuse-lite": ["caniuse-lite@1.0.30001809", "", {}, "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ=="], - "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "char-regex": ["char-regex@2.0.2", "", {}, "sha512-cbGOjAptfM2LVmWhwRFHEKTPkLwNddVmuqYZQt895yXwAsWsXObCG+YN4DGQ/JBtT4GP1a1lPPdio2z413LmTg=="], - "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], - "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], - - "chrome-launcher": ["chrome-launcher@0.15.2", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^1.0.0" }, "bin": { "print-chrome-path": "bin/print-chrome-path.js" } }, "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ=="], - - "chrome-trace-event": ["chrome-trace-event@1.0.4", "", {}, "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ=="], - - "chromium-edge-launcher": ["chromium-edge-launcher@0.2.0", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^1.0.0", "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg=="], - - "ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], - - "cipher-base": ["cipher-base@1.0.7", "", { "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1", "to-buffer": "^1.2.2" } }, "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA=="], - - "cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="], - "class-validator": ["class-validator@0.14.4", "", { "dependencies": { "@types/validator": "^13.15.3", "libphonenumber-js": "^1.11.1", "validator": "^13.15.22" } }, "sha512-AwNusCCam51q703dW82x95tOqQp6oC9HNUl724KxJJOfnKscI8dOloXFgyez7LbTTKWuRBA37FScqVbJEoq8Yw=="], - "cli-cursor": ["cli-cursor@2.1.0", "", { "dependencies": { "restore-cursor": "^2.0.0" } }, "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw=="], - - "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], - - "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], - "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], - "clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="], - - "clone-deep": ["clone-deep@4.0.1", "", { "dependencies": { "is-plain-object": "^2.0.4", "kind-of": "^6.0.2", "shallow-clone": "^3.0.0" } }, "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ=="], - "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], - "co": ["co@4.6.0", "", {}, "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ=="], - - "collect-v8-coverage": ["collect-v8-coverage@1.0.3", "", {}, "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw=="], - - "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="], - "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - "color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="], - "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], - "comment-json": ["comment-json@4.6.2", "", { "dependencies": { "array-timsort": "^1.0.3", "esprima": "^4.0.1" } }, "sha512-R2rze/hDX30uul4NZoIZ76ImSJLFxn/1/ZxtKC1L77y2X1k+yYu1joKbAtMA2Fg3hZrTOiw0I5mwVMo0cf250w=="], - - "commondir": ["commondir@1.0.1", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="], - - "compressible": ["compressible@2.0.18", "", { "dependencies": { "mime-db": ">= 1.43.0 < 2" } }, "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg=="], - - "compression": ["compression@1.8.1", "", { "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" } }, "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w=="], - - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], - "concurrently": ["concurrently@9.2.1", "", { "dependencies": { "chalk": "4.1.2", "rxjs": "7.8.2", "shell-quote": "1.8.3", "supports-color": "8.1.1", "tree-kill": "1.2.2", "yargs": "17.7.2" }, "bin": { "conc": "dist/bin/concurrently.js", "concurrently": "dist/bin/concurrently.js" } }, "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng=="], - "connect": ["connect@3.7.0", "", { "dependencies": { "debug": "2.6.9", "finalhandler": "1.1.2", "parseurl": "~1.3.3", "utils-merge": "1.0.1" } }, "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ=="], - - "console-browserify": ["console-browserify@1.2.0", "", {}, "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA=="], - - "constants-browserify": ["constants-browserify@1.0.0", "", {}, "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ=="], - - "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], - - "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], - "cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="], - "core-js": ["core-js@3.47.0", "", {}, "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg=="], - - "core-js-compat": ["core-js-compat@3.50.0", "", { "dependencies": { "browserslist": "^4.28.7" } }, "sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q=="], - - "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], - - "cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="], - - "cosmiconfig": ["cosmiconfig@5.2.1", "", { "dependencies": { "import-fresh": "^2.0.0", "is-directory": "^0.3.1", "js-yaml": "^3.13.1", "parse-json": "^4.0.0" } }, "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA=="], - - "create-ecdh": ["create-ecdh@4.0.4", "", { "dependencies": { "bn.js": "^4.1.0", "elliptic": "^6.5.3" } }, "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A=="], - - "create-hash": ["create-hash@1.2.0", "", { "dependencies": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", "md5.js": "^1.3.4", "ripemd160": "^2.0.1", "sha.js": "^2.4.0" } }, "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg=="], - - "create-hmac": ["create-hmac@1.1.7", "", { "dependencies": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", "inherits": "^2.0.1", "ripemd160": "^2.0.0", "safe-buffer": "^5.0.1", "sha.js": "^2.4.8" } }, "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg=="], - - "create-jest": ["create-jest@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-config": "^29.7.0", "jest-util": "^29.7.0", "prompts": "^2.0.1" }, "bin": { "create-jest": "bin/create-jest.js" } }, "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q=="], - - "cross-fetch": ["cross-fetch@3.2.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q=="], - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "cross-spawn-windows-exe": ["cross-spawn-windows-exe@1.2.0", "", { "dependencies": { "@malept/cross-spawn-promise": "^1.1.0", "is-wsl": "^2.2.0", "which": "^2.0.2" } }, "sha512-mkLtJJcYbDCxEG7Js6eUnUNndWjyUZwJ3H7bErmmtOYU/Zb99DyUkpamuIZE0b3bhmJyZ7D90uS6f+CGxRRjOw=="], - "crypto-browserify": ["crypto-browserify@3.12.1", "", { "dependencies": { "browserify-cipher": "^1.0.1", "browserify-sign": "^4.2.3", "create-ecdh": "^4.0.4", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", "diffie-hellman": "^5.0.3", "hash-base": "~3.0.4", "inherits": "^2.0.4", "pbkdf2": "^3.1.2", "public-encrypt": "^4.0.3", "randombytes": "^2.1.0", "randomfill": "^1.0.4" } }, "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ=="], - "crypto-js": ["crypto-js@4.2.0", "", {}, "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="], - "crypto-random-string": ["crypto-random-string@2.0.0", "", {}, "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA=="], - - "css-declaration-sorter": ["css-declaration-sorter@7.4.0", "", { "peerDependencies": { "postcss": "^8.0.9" } }, "sha512-LTuzjPoyA2vMGKKcaOqKSp7Ub2eGrNfKiZH4LpezxpNrsICGCSFvsQOI29psISxNZtaXibkC2CXzrQ5enMeGGw=="], - - "css-in-js-utils": ["css-in-js-utils@3.1.0", "", { "dependencies": { "hyphenate-style-name": "^1.0.3" } }, "sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A=="], - - "css-minimizer-webpack-plugin": ["css-minimizer-webpack-plugin@7.0.2", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "cssnano": "^7.0.4", "jest-worker": "^29.7.0", "postcss": "^8.4.40", "schema-utils": "^4.2.0", "serialize-javascript": "^6.0.2" }, "peerDependencies": { "@parcel/css": "*", "@swc/css": "*", "clean-css": "*", "csso": "*", "esbuild": "*", "lightningcss": "*", "webpack": "^5.0.0" }, "optionalPeers": ["@parcel/css", "@swc/css", "clean-css", "csso", "esbuild", "lightningcss"] }, "sha512-nBRWZtI77PBZQgcXMNqiIXVshiQOVLGSf2qX/WZfG8IQfMbeHUMXaBWQmiiSTmPJUflQxHjZjzAmuyO7tpL2Jg=="], - - "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], - - "css-tree": ["css-tree@1.1.3", "", { "dependencies": { "mdn-data": "2.0.14", "source-map": "^0.6.1" } }, "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q=="], - - "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], - - "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], - - "cssnano": ["cssnano@7.1.9", "", { "dependencies": { "cssnano-preset-default": "^7.0.17", "lilconfig": "^3.1.3" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-uPR75+5Dk/WJ/YSPR1/YDHdwMM9c5FsaARljfKWgeCKLKOtJ0we21xy/RcCjn53fZnD/f6yYEIZ8pu18+GnbNQ=="], - - "cssnano-preset-default": ["cssnano-preset-default@7.0.17", "", { "dependencies": { "browserslist": "^4.28.2", "css-declaration-sorter": "^7.2.0", "cssnano-utils": "^5.0.3", "postcss-calc": "^10.1.1", "postcss-colormin": "^7.0.10", "postcss-convert-values": "^7.0.12", "postcss-discard-comments": "^7.0.8", "postcss-discard-duplicates": "^7.0.4", "postcss-discard-empty": "^7.0.3", "postcss-discard-overridden": "^7.0.3", "postcss-merge-longhand": "^7.0.7", "postcss-merge-rules": "^7.0.11", "postcss-minify-font-values": "^7.0.3", "postcss-minify-gradients": "^7.0.5", "postcss-minify-params": "^7.0.9", "postcss-minify-selectors": "^7.1.2", "postcss-normalize-charset": "^7.0.3", "postcss-normalize-display-values": "^7.0.3", "postcss-normalize-positions": "^7.0.4", "postcss-normalize-repeat-style": "^7.0.4", "postcss-normalize-string": "^7.0.3", "postcss-normalize-timing-functions": "^7.0.3", "postcss-normalize-unicode": "^7.0.9", "postcss-normalize-url": "^7.0.3", "postcss-normalize-whitespace": "^7.0.3", "postcss-ordered-values": "^7.0.4", "postcss-reduce-initial": "^7.0.9", "postcss-reduce-transforms": "^7.0.3", "postcss-svgo": "^7.1.3", "postcss-unique-selectors": "^7.0.7" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-11qO63A+czwguQFJCaTdICvbaxn0pJzz/XghLlv+OT7WyToDxAMR0Xb3/26/l0y0hQJywwNbj/SLSQlGBHE1OA=="], - - "cssnano-utils": ["cssnano-utils@5.0.3", "", { "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-ynIREMICLxkxm7e9bCR9sh75s4Q5drICi0ua1yxo5jH2XPBqSKkl4dOh4EbFqtUmnTMhRffHgYL0EKKkMjtJTg=="], - - "csso": ["csso@5.0.5", "", { "dependencies": { "css-tree": "~2.2.0" } }, "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ=="], - - "cssom": ["cssom@0.5.0", "", {}, "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw=="], - - "cssstyle": ["cssstyle@2.3.0", "", { "dependencies": { "cssom": "~0.3.6" } }, "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A=="], - - "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], "d3-chord": ["d3-chord@3.0.1", "", { "dependencies": { "d3-path": "1 - 3" } }, "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g=="], @@ -1430,14 +485,6 @@ "data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="], - "data-urls": ["data-urls@3.0.2", "", { "dependencies": { "abab": "^2.0.6", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^11.0.0" } }, "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ=="], - - "data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="], - - "data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="], - - "data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="], - "dayjs": ["dayjs@1.11.20", "", {}, "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ=="], "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], @@ -1446,24 +493,8 @@ "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="], - "decode-uri-component": ["decode-uri-component@0.2.2", "", {}, "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="], - - "dedent": ["dedent@1.7.2", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="], - - "deep-eql": ["deep-eql@4.1.4", "", { "dependencies": { "type-detect": "^4.0.0" } }, "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg=="], - - "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], - "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], - "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="], - - "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], - - "define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="], - - "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], - "defu": ["defu@6.1.7", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="], "degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="], @@ -1472,604 +503,154 @@ "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], - "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], - "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], - "des.js": ["des.js@1.1.0", "", { "dependencies": { "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" } }, "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg=="], - - "destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="], - "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "detect-newline": ["detect-newline@3.1.0", "", {}, "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA=="], - "devalue": ["devalue@5.6.4", "", {}, "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA=="], - "didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="], - "diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], - "diff-sequences": ["diff-sequences@29.6.3", "", {}, "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="], - - "diffie-hellman": ["diffie-hellman@5.0.3", "", { "dependencies": { "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", "randombytes": "^2.0.0" } }, "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg=="], - "dijkstrajs": ["dijkstrajs@1.0.3", "", {}, "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="], "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], - "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], - - "domain-browser": ["domain-browser@5.7.0", "", {}, "sha512-edTFu0M/7wO1pXY6GDxVNVW086uqwWYIHP98txhcPyV995X21JIH2DtYp33sQJOupYoXKe9RwTw2Ya2vWaquTQ=="], - - "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], - - "domexception": ["domexception@4.0.0", "", { "dependencies": { "webidl-conversions": "^7.0.0" } }, "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw=="], - - "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], - - "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], - - "dotenv": ["dotenv@16.4.7", "", {}, "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ=="], - - "dotenv-expand": ["dotenv-expand@11.0.7", "", { "dependencies": { "dotenv": "^16.4.5" } }, "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA=="], - "dts-resolver": ["dts-resolver@2.1.3", "", { "peerDependencies": { "oxc-resolver": ">=11.0.0" }, "optionalPeers": ["oxc-resolver"] }, "sha512-bihc7jPC90VrosXNzK0LTE2cuLP6jr0Ro8jk+kMugHReJVLIpHz/xadeq3MhuwyO4TD4OA3L1Q8pBBFRc08Tsw=="], "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], - - "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - "effect": ["effect@3.21.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-rXd2FGDM8KdjSIrc+mqEELo7ScW7xTVxEf1iInmPSpIde9/nyGuFM710cjTo7/EreGXiUX2MOonPpprbz2XHCg=="], "electrobun": ["electrobun@1.18.1", "", { "dependencies": { "@babylonjs/core": "^7.45.0", "@types/bun": "^1.3.8", "png-to-ico": "^2.1.8", "proxy-agent": "^6.5.0", "rcedit": "^4.0.1", "three": "^0.165.0" }, "bin": { "electrobun": "bin/electrobun.cjs" } }, "sha512-tgZ+WKGskn/1/Y5i1mpVCCkgRa1O31Tz7MIArBTK44GfPzT43uOq9c/HvxdQGrLeZV8ZvjK1lQrwqSXCgh6tvA=="], - "electron-to-chromium": ["electron-to-chromium@1.5.402", "", {}, "sha512-/oOpMaPT6Yg+6/1XQhyIPlzgj7Ye9zf+nNM2Uh6OcE2G2oNptWazFa+qB2Pdqqbsc9KnIDzgAntoYN0dbwOXwA=="], - - "elliptic": ["elliptic@6.6.1", "", { "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", "hash.js": "^1.0.0", "hmac-drbg": "^1.0.1", "inherits": "^2.0.4", "minimalistic-assert": "^1.0.1", "minimalistic-crypto-utils": "^1.0.1" } }, "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g=="], - "embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="], "embla-carousel-reactive-utils": ["embla-carousel-reactive-utils@8.6.0", "", { "peerDependencies": { "embla-carousel": "8.6.0" } }, "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A=="], "embla-carousel-svelte": ["embla-carousel-svelte@8.6.0", "", { "dependencies": { "embla-carousel": "8.6.0", "embla-carousel-reactive-utils": "8.6.0" }, "peerDependencies": { "svelte": "^3.49.0 || ^4.0.0 || ^5.0.0" } }, "sha512-ZDsKk8Sdv+AUTygMYcwZjfRd1DTh+JSUzxkOo8b9iKAkYjg+39mzbY/lwHsE3jXSpKxdKWS69hPSNuzlOGtR2Q=="], - "emittery": ["emittery@0.13.1", "", {}, "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ=="], - "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "empathic": ["empathic@2.0.1", "", {}, "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q=="], - "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], - - "engine.io": ["engine.io@6.6.9", "", { "dependencies": { "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "@types/ws": "^8.5.12", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.21.0" } }, "sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg=="], - - "engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="], - "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="], - "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], - - "env-editor": ["env-editor@0.4.2", "", {}, "sha512-ObFo8v4rQJAE59M69QzwloxPZtd33TpYEIjtKD1rrFDcM1Gd7IkDxEBU+HriziN6HSHQnBJi8Dmy+JWkav5HKA=="], - "env-paths": ["env-paths@4.0.0", "", { "dependencies": { "is-safe-filename": "^0.1.0" } }, "sha512-pxP8eL2SwwaTRi/KHYwLYXinDs7gL3jxFcBYmEdYfZmZXbaVDvdppd0XBU8qVz03rDfKZMXg1omHCbsJjZrMsw=="], - "envinfo": ["envinfo@7.14.0", "", { "bin": { "envinfo": "dist/cli.js" } }, "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg=="], - - "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], - - "error-stack-parser": ["error-stack-parser@2.1.4", "", { "dependencies": { "stackframe": "^1.3.4" } }, "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ=="], - - "es-abstract": ["es-abstract@1.24.2", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg=="], - - "es-abstract-get": ["es-abstract-get@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "es-object-atoms": "^1.1.2", "is-callable": "^1.2.7", "object-inspect": "^1.13.4" } }, "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg=="], - "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - "es-module-lexer": ["es-module-lexer@2.3.1", "", {}, "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA=="], - - "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="], + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - "es-to-primitive": ["es-to-primitive@1.3.4", "", { "dependencies": { "es-abstract-get": "^1.0.0", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "is-callable": "^1.2.7", "is-date-object": "^1.1.0", "is-symbol": "^1.1.1" } }, "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw=="], - "esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], - "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], - - "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - "escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="], - "eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="], - "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], "esrap": ["esrap@2.2.4", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15", "@typescript-eslint/types": "^8.2.0" } }, "sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg=="], - "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], - "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], - "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], - "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], - - "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], - "eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], - "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], - - "evp_bytestokey": ["evp_bytestokey@1.0.3", "", { "dependencies": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" } }, "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA=="], - - "execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], - - "exit": ["exit@0.1.2", "", {}, "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ=="], - - "expect": ["expect@29.7.0", "", { "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw=="], - - "expo": ["expo@53.0.27", "", { "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "0.24.24", "@expo/config": "~11.0.13", "@expo/config-plugins": "~10.1.2", "@expo/fingerprint": "0.13.4", "@expo/metro-config": "0.20.18", "@expo/vector-icons": "^14.0.0", "babel-preset-expo": "~13.2.5", "expo-asset": "~11.1.7", "expo-constants": "~17.1.8", "expo-file-system": "~18.1.11", "expo-font": "~13.3.2", "expo-keep-awake": "~14.1.4", "expo-modules-autolinking": "2.1.15", "expo-modules-core": "2.5.0", "react-native-edge-to-edge": "1.6.0", "whatwg-url-without-unicode": "8.0.0-3" }, "peerDependencies": { "@expo/dom-webview": "*", "@expo/metro-runtime": "*", "react": "*", "react-native": "*", "react-native-webview": "*" }, "optionalPeers": ["@expo/dom-webview", "@expo/metro-runtime", "react-native-webview"], "bin": { "expo": "bin/cli", "fingerprint": "bin/fingerprint", "expo-modules-autolinking": "bin/autolinking" } }, "sha512-iQwe2uWLb88opUY4vBYEW1d2GUq3lsa43gsMBEdDV+6pw0Oek93l/4nDLe0ODDdrBRjIJm/rdhKqJC/ehHCUqw=="], - - "expo-asset": ["expo-asset@11.1.7", "", { "dependencies": { "@expo/image-utils": "^0.7.6", "expo-constants": "~17.1.7" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-b5P8GpjUh08fRCf6m5XPVAh7ra42cQrHBIMgH2UXP+xsj4Wufl6pLy6jRF5w6U7DranUMbsXm8TOyq4EHy7ADg=="], - - "expo-barcode-generator": ["expo-barcode-generator@4.0.0", "", { "dependencies": { "jsbarcode": "^3.11.6", "react-native-svg": "15.8.0" } }, "sha512-b7gRqS+uvE66tC/GJYrrEGD6w4Zoma7fFMhMg/3r7C9wvg1gVrQ9FFx9DQnzXzhvIueZkaqx4X1qvScKxiFcJQ=="], - - "expo-camera": ["expo-camera@16.1.11", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-etA5ZKoC6nPBnWWqiTmlX//zoFZ6cWQCCIdmpUHTGHAKd4qZNCkhPvBWbi8o32pDe57lix1V4+TPFgEcvPwsaA=="], - - "expo-clipboard": ["expo-clipboard@7.1.5", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-TCANUGOxouoJXxKBW5ASJl2WlmQLGpuZGemDCL2fO5ZMl57DGTypUmagb0CVUFxDl0yAtFIcESd78UsF9o64aw=="], - - "expo-constants": ["expo-constants@17.0.8", "", { "dependencies": { "@expo/config": "~10.0.11", "@expo/env": "~0.4.2" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-XfWRyQAf1yUNgWZ1TnE8pFBMqGmFP5Gb+SFSgszxDdOoheB/NI5D4p7q86kI2fvGyfTrxAe+D+74nZkfsGvUlg=="], - - "expo-dev-client": ["expo-dev-client@5.2.4", "", { "dependencies": { "expo-dev-launcher": "5.1.16", "expo-dev-menu": "6.1.14", "expo-dev-menu-interface": "1.10.0", "expo-manifests": "~0.16.6", "expo-updates-interface": "~1.1.0" }, "peerDependencies": { "expo": "*" } }, "sha512-s/N/nK5LPo0QZJpV4aPijxyrzV4O49S3dN8D2fljqrX2WwFZzWwFO6dX1elPbTmddxumdcpczsdUPY+Ms8g43g=="], - - "expo-dev-launcher": ["expo-dev-launcher@5.1.16", "", { "dependencies": { "ajv": "8.11.0", "expo-dev-menu": "6.1.14", "expo-manifests": "~0.16.6", "resolve-from": "^5.0.0" }, "peerDependencies": { "expo": "*" } }, "sha512-tbCske9pvbozaEblyxoyo/97D6od9Ma4yAuyUnXtRET1CKAPKYS+c4fiZ+I3B4qtpZwN3JNFUjG3oateN0y6Hg=="], - - "expo-dev-menu": ["expo-dev-menu@6.1.14", "", { "dependencies": { "expo-dev-menu-interface": "1.10.0" }, "peerDependencies": { "expo": "*" } }, "sha512-yonNMg2GHJZtuisVowdl1iQjZfYP85r1D1IO+ar9D9zlrBPBJhq2XEju52jd1rDmDkmDuEhBSbPNhzIcsBNiPg=="], - - "expo-dev-menu-interface": ["expo-dev-menu-interface@1.10.0", "", { "peerDependencies": { "expo": "*" } }, "sha512-NxtM/qot5Rh2cY333iOE87dDg1S8CibW+Wu4WdLua3UMjy81pXYzAGCZGNOeY7k9GpNFqDPNDXWyBSlk9r2pBg=="], - - "expo-file-system": ["expo-file-system@18.1.11", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-HJw/m0nVOKeqeRjPjGdvm+zBi5/NxcdPf8M8P3G2JFvH5Z8vBWqVDic2O58jnT1OFEy0XXzoH9UqFu7cHg9DTQ=="], - - "expo-font": ["expo-font@13.0.4", "", { "dependencies": { "fontfaceobserver": "^2.1.0" }, "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-eAP5hyBgC8gafFtprsz0HMaB795qZfgJWqTmU0NfbSin1wUuVySFMEPMOrTkTgmazU73v4Cb4x7p86jY1XXYUw=="], - - "expo-haptics": ["expo-haptics@14.0.1", "", { "peerDependencies": { "expo": "*" } }, "sha512-V81FZ7xRUfqM6uSI6FA1KnZ+QpEKnISqafob/xEfcx1ymwhm4V3snuLWWFjmAz+XaZQTqlYa8z3QbqEXz7G63w=="], - - "expo-json-utils": ["expo-json-utils@0.15.0", "", {}, "sha512-duRT6oGl80IDzH2LD2yEFWNwGIC2WkozsB6HF3cDYNoNNdUvFk6uN3YiwsTsqVM/D0z6LEAQ01/SlYvN+Fw0JQ=="], - - "expo-keep-awake": ["expo-keep-awake@14.1.4", "", { "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-wU9qOnosy4+U4z/o4h8W9PjPvcFMfZXrlUoKTMBW7F4pLqhkkP/5G4EviPZixv4XWFMjn1ExQ5rV6BX8GwJsWA=="], - - "expo-linking": ["expo-linking@7.0.5", "", { "dependencies": { "expo-constants": "~17.0.5", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-3KptlJtcYDPWohk0MfJU75MJFh2ybavbtcSd84zEPfw9s1q3hjimw3sXnH03ZxP54kiEWldvKmmnGcVffBDB1g=="], - - "expo-local-authentication": ["expo-local-authentication@16.0.5", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "expo": "*" } }, "sha512-JzrUssLBB5tr06ffbZJC074fru2mw6He3ALtwlRnrmL7svMJS0zvm6e5Gx5CSxfHf1DgneBVBnyRlmtWxQzY9Q=="], - - "expo-manifests": ["expo-manifests@0.16.6", "", { "dependencies": { "@expo/config": "~11.0.12", "expo-json-utils": "~0.15.0" }, "peerDependencies": { "expo": "*" } }, "sha512-1A+do6/mLUWF9xd3uCrlXr9QFDbjbfqAYmUy8UDLOjof1lMrOhyeC4Yi6WexA/A8dhZEpIxSMCKfn7G4aHAh4w=="], - - "expo-modules-autolinking": ["expo-modules-autolinking@2.1.15", "", { "dependencies": { "@expo/spawn-async": "^1.7.2", "chalk": "^4.1.0", "commander": "^7.2.0", "find-up": "^5.0.0", "glob": "^10.4.2", "require-from-string": "^2.0.2", "resolve-from": "^5.0.0" }, "bin": { "expo-modules-autolinking": "bin/expo-modules-autolinking.js" } }, "sha512-IUITUERdkgooXjr9bXsX0PmhrZUIGTMyP6NtmQpAxN5+qtf/I7ewbwLx1/rX7tgiAOzaYme+PZOp/o6yqIhFsw=="], - - "expo-modules-core": ["expo-modules-core@2.5.0", "", { "dependencies": { "invariant": "^2.2.4" } }, "sha512-aIbQxZE2vdCKsolQUl6Q9Farlf8tjh/ROR4hfN1qT7QBGPl1XrJGnaOKkcgYaGrlzCPg/7IBe0Np67GzKMZKKQ=="], - - "expo-router": ["expo-router@4.0.22", "", { "dependencies": { "@expo/metro-runtime": "4.0.1", "@expo/server": "^0.5.3", "@radix-ui/react-slot": "1.0.1", "@react-navigation/bottom-tabs": "^7.2.0", "@react-navigation/native": "^7.0.14", "@react-navigation/native-stack": "^7.2.0", "client-only": "^0.0.1", "react-helmet-async": "^1.3.0", "react-native-helmet-async": "2.0.4", "react-native-is-edge-to-edge": "^1.1.6", "schema-utils": "^4.0.1", "semver": "~7.6.3", "server-only": "^0.0.1" }, "peerDependencies": { "@react-navigation/drawer": "^7.1.1", "@testing-library/jest-native": "*", "expo": "*", "expo-constants": "~17.0.8", "expo-linking": "~7.0.5", "react-native-reanimated": "*", "react-native-safe-area-context": "*", "react-native-screens": "*" }, "optionalPeers": ["@react-navigation/drawer", "@testing-library/jest-native", "react-native-reanimated"] }, "sha512-cbl6pWUMMZl5sZaHSMmxhQi+kTC6Yzj0ATaD2j6MiLF6VQyr5IcWes3V0UxPtI08boTX0i+76/fnP5fZ3eGQYQ=="], - - "expo-secure-store": ["expo-secure-store@14.0.1", "", { "peerDependencies": { "expo": "*" } }, "sha512-QUS+j4+UG4jRQalgnpmTvvrFnMVLqPiUZRzYPnG3+JrZ5kwVW2w6YS3WWerPoR7C6g3y/a2htRxRSylsDs+TaQ=="], - - "expo-splash-screen": ["expo-splash-screen@0.29.24", "", { "dependencies": { "@expo/prebuild-config": "~8.2.0" }, "peerDependencies": { "expo": "*" } }, "sha512-k2rdjbb3Qeg4g104Sdz6+qXXYba8QgiuZRSxHX8IpsSYiiTU48BmCCGy12sN+O1B+sD1/+WPL4duCa1Fy6+Y4g=="], - - "expo-status-bar": ["expo-status-bar@2.0.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-AkIPX7jWHRPp83UBZ1iXtVvyr0g+DgBVvIXTtlmPtmUsm8Vq9Bb5IGj86PW8osuFlgoTVAg7HI/+Ok7yEYwiRg=="], - - "expo-system-ui": ["expo-system-ui@4.0.9", "", { "dependencies": { "@react-native/normalize-colors": "0.76.8", "debug": "^4.3.2" }, "peerDependencies": { "expo": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-hqBc0EWeK/BTB8i4H84vqNjje8GgxhapYrcWdg5qriaRA/u+bNNxhmpZXdAjFuhonOP4SmAbF+gjoJJWsTrhUg=="], - - "expo-updates-interface": ["expo-updates-interface@1.1.0", "", { "peerDependencies": { "expo": "*" } }, "sha512-DeB+fRe0hUDPZhpJ4X4bFMAItatFBUPjw/TVSbJsaf3Exeami+2qbbJhWkcTMoYHOB73nOIcaYcWXYJnCJXO0w=="], - - "expo-web-browser": ["expo-web-browser@14.0.2", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-Hncv2yojhTpHbP6SGWARBFdl7P6wBHc1O8IKaNsH0a/IEakq887o1eRhLxZ5IwztPQyRDhpqHdgJ+BjWolOnwA=="], - - "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="], - "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], - "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], - "fast-uri": ["fast-uri@3.1.5", "", {}, "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw=="], - - "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], - - "fb-watchman": ["fb-watchman@2.0.2", "", { "dependencies": { "bser": "2.1.1" } }, "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA=="], - - "fbjs": ["fbjs@3.0.5", "", { "dependencies": { "cross-fetch": "^3.1.5", "fbjs-css-vars": "^1.0.0", "loose-envify": "^1.0.0", "object-assign": "^4.1.0", "promise": "^7.1.1", "setimmediate": "^1.0.5", "ua-parser-js": "^1.0.35" } }, "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg=="], - - "fbjs-css-vars": ["fbjs-css-vars@1.0.2", "", {}, "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ=="], - "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - "filesize": ["filesize@10.1.6", "", {}, "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w=="], - - "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], - - "filter-obj": ["filter-obj@1.1.0", "", {}, "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ=="], - - "finalhandler": ["finalhandler@1.1.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "~2.3.0", "parseurl": "~1.3.3", "statuses": "~1.5.0", "unpipe": "~1.0.0" } }, "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA=="], - - "find-cache-dir": ["find-cache-dir@2.1.0", "", { "dependencies": { "commondir": "^1.0.1", "make-dir": "^2.0.0", "pkg-dir": "^3.0.0" } }, "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ=="], - - "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], - - "flow-enums-runtime": ["flow-enums-runtime@0.0.6", "", {}, "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw=="], - - "flow-estree": ["flow-estree@0.326.0", "", {}, "sha512-43Qv+Ei9qfabhLx8JGEJku4frHGD5zI2EuL73Hs9HrWqPMbTT/DS18Az7bcodGwdQEv1DVIEI2WowkEbMIk4BQ=="], - - "flow-parser": ["flow-parser@0.326.0", "", { "dependencies": { "flow-estree": "0.326.0" } }, "sha512-H/Wqt2SDkQ8GH8wpyAj434zN40zomipZWBbKYlAwQYyNdMIQMKqpXTx82FMnITt2iDQ5zrV80NvSPAgIAafwGA=="], + "find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], "follow-redirects": ["follow-redirects@1.16.0", "", { "peerDependencies": { "debug": "*" }, "optionalPeers": ["debug"] }, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], - "fontfaceobserver": ["fontfaceobserver@2.3.0", "", {}, "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg=="], - - "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], - - "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], - "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], "formsnap": ["formsnap@2.0.1", "", { "dependencies": { "svelte-toolbelt": "^0.5.0" }, "peerDependencies": { "svelte": "^5.0.0", "sveltekit-superforms": "^2.19.0" } }, "sha512-iJSe4YKd/W6WhLwKDVJU9FQeaJRpEFuolhju7ZXlRpUVyDdqFdMP8AUBICgnVvQPyP41IPAlBa/v0Eo35iE6wQ=="], - "freeport-async": ["freeport-async@2.0.0", "", {}, "sha512-K7od3Uw45AJg00XUmy15+Hae2hOcgKcmN3/EF6Y7i01O0gaqiRx8sUSpsb9+BRNL8RPBrhzPsVfy8q9ADlJuWQ=="], - - "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], - - "fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], - - "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - "function.prototype.name": ["function.prototype.name@1.2.0", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2", "hasown": "^2.0.4", "is-callable": "^1.2.7", "is-document.all": "^1.0.0" } }, "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew=="], - - "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="], - - "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], - - "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], - "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], - "get-package-type": ["get-package-type@0.1.0", "", {}, "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q=="], - - "get-port": ["get-port@5.1.1", "", {}, "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ=="], - "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - "get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], - - "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="], - "get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="], "get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="], - "getenv": ["getenv@2.0.0", "", {}, "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ=="], - - "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - - "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], - - "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], - "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], - "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], - - "has-proto": ["has-proto@1.2.0", "", { "dependencies": { "dunder-proto": "^1.0.0" } }, "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ=="], - "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], - "hash-base": ["hash-base@3.0.5", "", { "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1" } }, "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg=="], - - "hash.js": ["hash.js@1.1.7", "", { "dependencies": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" } }, "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA=="], - "hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], - "hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], - - "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], - - "hmac-drbg": ["hmac-drbg@1.0.1", "", { "dependencies": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", "minimalistic-crypto-utils": "^1.0.1" } }, "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg=="], - - "hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="], - "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], - "hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], - - "html-encoding-sniffer": ["html-encoding-sniffer@3.0.0", "", { "dependencies": { "whatwg-encoding": "^2.0.0" } }, "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA=="], - - "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], - - "htmlparser2": ["htmlparser2@10.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.1", "entities": "^6.0.0" } }, "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g=="], - - "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], - "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], - "https-browserify": ["https-browserify@1.0.0", "", {}, "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg=="], - "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], - "human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], - - "hyphenate-style-name": ["hyphenate-style-name@1.1.0", "", {}, "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw=="], - "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], - "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - - "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - - "image-size": ["image-size@1.2.1", "", { "dependencies": { "queue": "6.0.2" }, "bin": { "image-size": "bin/image-size.js" } }, "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw=="], - - "import-fresh": ["import-fresh@2.0.0", "", { "dependencies": { "caller-path": "^2.0.0", "resolve-from": "^3.0.0" } }, "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg=="], - - "import-local": ["import-local@3.2.0", "", { "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" } }, "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA=="], - - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], - - "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], - - "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], - - "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], - "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], - "inline-style-prefixer": ["inline-style-prefixer@7.0.1", "", { "dependencies": { "css-in-js-utils": "^3.1.0" } }, "sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw=="], - - "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], - "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], - "invariant": ["invariant@2.2.4", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA=="], - "ip-address": ["ip-address@10.2.0", "", {}, "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA=="], - "is-arguments": ["is-arguments@1.2.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA=="], - - "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="], - - "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], - - "is-async-function": ["is-async-function@2.1.1", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="], - - "is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="], - - "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], - - "is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="], - - "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], - - "is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="], - - "is-data-view": ["is-data-view@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" } }, "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw=="], - - "is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="], - - "is-directory": ["is-directory@0.3.1", "", {}, "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw=="], - "is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], - "is-document.all": ["is-document.all@1.0.0", "", { "dependencies": { "call-bound": "^1.0.4" } }, "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g=="], - - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - - "is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="], - "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - "is-generator-fn": ["is-generator-fn@2.1.0", "", {}, "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ=="], - - "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="], - - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - - "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], - - "is-nan": ["is-nan@1.3.2", "", { "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" } }, "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w=="], - - "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="], - - "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], - - "is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="], - - "is-plain-object": ["is-plain-object@2.0.4", "", { "dependencies": { "isobject": "^3.0.1" } }, "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og=="], - - "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="], - "is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="], - "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], - "is-safe-filename": ["is-safe-filename@0.1.1", "", {}, "sha512-4SrR7AdnY11LHfDKTZY1u6Ga3RuxZdl3YKWWShO5iyuG5h8QS4GD2tOb04peBJ5I7pXbR+CGBNEhTcwK+FzN3g=="], - "is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="], - - "is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="], - - "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], - - "is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="], - - "is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="], - - "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], - - "is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="], - - "is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="], - - "is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="], - "is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], - "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - "isobject": ["isobject@3.0.1", "", {}, "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg=="], - - "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], - - "istanbul-lib-instrument": ["istanbul-lib-instrument@6.0.3", "", { "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" } }, "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q=="], - - "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="], - - "istanbul-lib-source-maps": ["istanbul-lib-source-maps@4.0.1", "", { "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", "source-map": "^0.6.1" } }, "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw=="], - - "istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="], - - "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - - "jest": ["jest@29.7.0", "", { "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", "import-local": "^3.0.2", "jest-cli": "^29.7.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": { "jest": "bin/jest.js" } }, "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw=="], - - "jest-changed-files": ["jest-changed-files@29.7.0", "", { "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", "p-limit": "^3.1.0" } }, "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w=="], - - "jest-circus": ["jest-circus@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", "jest-each": "^29.7.0", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-runtime": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "p-limit": "^3.1.0", "pretty-format": "^29.7.0", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw=="], - - "jest-cli": ["jest-cli@29.7.0", "", { "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "chalk": "^4.0.0", "create-jest": "^29.7.0", "exit": "^0.1.2", "import-local": "^3.0.2", "jest-config": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "yargs": "^17.3.1" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": { "jest": "bin/jest.js" } }, "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg=="], - - "jest-config": ["jest-config@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", "@jest/types": "^29.6.3", "babel-jest": "^29.7.0", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "jest-circus": "^29.7.0", "jest-environment-node": "^29.7.0", "jest-get-type": "^29.6.3", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-runner": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "micromatch": "^4.0.4", "parse-json": "^5.2.0", "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "peerDependencies": { "@types/node": "*", "ts-node": ">=9.0.0" }, "optionalPeers": ["@types/node", "ts-node"] }, "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ=="], - - "jest-diff": ["jest-diff@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw=="], - - "jest-docblock": ["jest-docblock@29.7.0", "", { "dependencies": { "detect-newline": "^3.0.0" } }, "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g=="], - - "jest-each": ["jest-each@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "jest-util": "^29.7.0", "pretty-format": "^29.7.0" } }, "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ=="], - - "jest-environment-jsdom": ["jest-environment-jsdom@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/jsdom": "^20.0.0", "@types/node": "*", "jest-mock": "^29.7.0", "jest-util": "^29.7.0", "jsdom": "^20.0.0" }, "peerDependencies": { "canvas": "^2.5.0" }, "optionalPeers": ["canvas"] }, "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA=="], - - "jest-environment-node": ["jest-environment-node@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw=="], - - "jest-expo": ["jest-expo@53.0.14", "", { "dependencies": { "@expo/config": "~11.0.13", "@expo/json-file": "^9.1.5", "@jest/create-cache-key-function": "^29.2.1", "@jest/globals": "^29.2.1", "babel-jest": "^29.2.1", "find-up": "^5.0.0", "jest-environment-jsdom": "^29.2.1", "jest-snapshot": "^29.2.1", "jest-watch-select-projects": "^2.0.0", "jest-watch-typeahead": "2.2.1", "json5": "^2.2.3", "lodash": "^4.17.19", "react-test-renderer": "19.0.0", "server-only": "^0.0.1", "stacktrace-js": "^2.0.2" }, "peerDependencies": { "expo": "*", "react-native": "*", "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" }, "optionalPeers": ["react-server-dom-webpack"], "bin": { "jest": "bin/jest.js" } }, "sha512-BwE5ZTjkhTvO+ejJBBJNlwar2YiahWjbcwhSPQ3oYV5UyvVdTrpKvZ+KjFv/7N5OaC3cOhv4/Ve4cTgS6m6vcw=="], - - "jest-get-type": ["jest-get-type@29.6.3", "", {}, "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw=="], - - "jest-haste-map": ["jest-haste-map@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, "optionalDependencies": { "fsevents": "^2.3.2" } }, "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA=="], - - "jest-leak-detector": ["jest-leak-detector@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw=="], - - "jest-matcher-utils": ["jest-matcher-utils@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g=="], - - "jest-message-util": ["jest-message-util@29.7.0", "", { "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w=="], - - "jest-mock": ["jest-mock@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "jest-util": "^29.7.0" } }, "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw=="], - - "jest-pnp-resolver": ["jest-pnp-resolver@1.2.3", "", { "peerDependencies": { "jest-resolve": "*" }, "optionalPeers": ["jest-resolve"] }, "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w=="], - - "jest-regex-util": ["jest-regex-util@29.6.3", "", {}, "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg=="], - - "jest-resolve": ["jest-resolve@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" } }, "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA=="], - - "jest-resolve-dependencies": ["jest-resolve-dependencies@29.7.0", "", { "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.7.0" } }, "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA=="], - - "jest-runner": ["jest-runner@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/environment": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", "jest-docblock": "^29.7.0", "jest-environment-node": "^29.7.0", "jest-haste-map": "^29.7.0", "jest-leak-detector": "^29.7.0", "jest-message-util": "^29.7.0", "jest-resolve": "^29.7.0", "jest-runtime": "^29.7.0", "jest-util": "^29.7.0", "jest-watcher": "^29.7.0", "jest-worker": "^29.7.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" } }, "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ=="], - - "jest-runtime": ["jest-runtime@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/globals": "^29.7.0", "@jest/source-map": "^29.6.3", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" } }, "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ=="], - - "jest-snapshot": ["jest-snapshot@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/types": "^7.3.3", "@jest/expect-utils": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", "expect": "^29.7.0", "graceful-fs": "^4.2.9", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "natural-compare": "^1.4.0", "pretty-format": "^29.7.0", "semver": "^7.5.3" } }, "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw=="], - - "jest-util": ["jest-util@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" } }, "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA=="], - - "jest-validate": ["jest-validate@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "leven": "^3.1.0", "pretty-format": "^29.7.0" } }, "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw=="], - - "jest-watch-select-projects": ["jest-watch-select-projects@2.0.0", "", { "dependencies": { "ansi-escapes": "^4.3.0", "chalk": "^3.0.0", "prompts": "^2.2.1" } }, "sha512-j00nW4dXc2NiCW6znXgFLF9g8PJ0zP25cpQ1xRro/HU2GBfZQFZD0SoXnAlaoKkIY4MlfTMkKGbNXFpvCdjl1w=="], - - "jest-watch-typeahead": ["jest-watch-typeahead@2.2.1", "", { "dependencies": { "ansi-escapes": "^6.0.0", "chalk": "^4.0.0", "jest-regex-util": "^29.0.0", "jest-watcher": "^29.0.0", "slash": "^5.0.0", "string-length": "^5.0.1", "strip-ansi": "^7.0.1" }, "peerDependencies": { "jest": "^27.0.0 || ^28.0.0 || ^29.0.0" } }, "sha512-jYpYmUnTzysmVnwq49TAxlmtOAwp8QIqvZyoofQFn8fiWhEDZj33ZXzg3JA4nGnzWFm1hbWf3ADpteUokvXgFA=="], - - "jest-watcher": ["jest-watcher@29.7.0", "", { "dependencies": { "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", "jest-util": "^29.7.0", "string-length": "^4.0.1" } }, "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g=="], - - "jest-worker": ["jest-worker@29.7.0", "", { "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw=="], - - "jimp-compact": ["jimp-compact@0.16.1", "", {}, "sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww=="], - "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], "joi": ["joi@17.13.3", "", { "dependencies": { "@hapi/hoek": "^9.3.0", "@hapi/topo": "^5.1.0", "@sideway/address": "^4.1.5", "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" } }, "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA=="], - "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - - "js-yaml": ["js-yaml@4.3.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ=="], - - "jsbarcode": ["jsbarcode@3.12.3", "", {}, "sha512-CuHU9hC6dPsHF5oVFMo8NW76uQVjH4L22CsP4hW+dNnGywJHC/B0ThA1CTDVLnxKLrrpYdicBLnd2xsgTfRnvg=="], - - "jsc-safe-url": ["jsc-safe-url@0.2.4", "", {}, "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q=="], - - "jscodeshift": ["jscodeshift@0.14.0", "", { "dependencies": { "@babel/core": "^7.13.16", "@babel/parser": "^7.13.16", "@babel/plugin-proposal-class-properties": "^7.13.0", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", "@babel/plugin-proposal-optional-chaining": "^7.13.12", "@babel/plugin-transform-modules-commonjs": "^7.13.8", "@babel/preset-flow": "^7.13.13", "@babel/preset-typescript": "^7.13.0", "@babel/register": "^7.13.16", "babel-core": "^7.0.0-bridge.0", "chalk": "^4.1.2", "flow-parser": "0.*", "graceful-fs": "^4.2.4", "micromatch": "^4.0.4", "neo-async": "^2.5.0", "node-dir": "^0.1.17", "recast": "^0.21.0", "temp": "^0.8.4", "write-file-atomic": "^2.3.0" }, "peerDependencies": { "@babel/preset-env": "^7.1.6" }, "bin": { "jscodeshift": "bin/jscodeshift.js" } }, "sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA=="], - - "jsdom": ["jsdom@20.0.3", "", { "dependencies": { "abab": "^2.0.6", "acorn": "^8.8.1", "acorn-globals": "^7.0.0", "cssom": "^0.5.0", "cssstyle": "^2.3.0", "data-urls": "^3.0.2", "decimal.js": "^10.4.2", "domexception": "^4.0.0", "escodegen": "^2.0.0", "form-data": "^4.0.0", "html-encoding-sniffer": "^3.0.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", "nwsapi": "^2.2.2", "parse5": "^7.1.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^4.1.2", "w3c-xmlserializer": "^4.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^2.0.0", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^11.0.0", "ws": "^8.11.0", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "canvas": "^2.5.0" }, "optionalPeers": ["canvas"] }, "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ=="], - "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], - "json-cycle": ["json-cycle@1.5.0", "", {}, "sha512-GOehvd5PO2FeZ5T4c+RxobeT5a1PiGpF4u9/3+UvrMU4bhnVqzJY7hm39wg8PDCqkU91fWGH8qjWR4bn+wgq9w=="], - - "json-parse-better-errors": ["json-parse-better-errors@1.0.2", "", {}, "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="], - - "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], - "json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="], "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - "json-stream-stringify": ["json-stream-stringify@3.0.1", "", {}, "sha512-vuxs3G1ocFDiAQ/SX0okcZbtqXwgj1g71qE9+vrjJ2EkjKQlEFDAcUNRxRU8O+GekV4v5cM2qXP0Wyt/EMDBiQ=="], - - "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], - - "jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], - "jsqr": ["jsqr@1.4.0", "", {}, "sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A=="], - "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], - "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], "koins": ["koins@workspace:apps/desktop"], - "koins-mobile": ["koins-mobile@workspace:apps/mobile"], - - "lan-network": ["lan-network@0.1.7", "", { "bin": { "lan-network": "dist/lan-network-cli.js" } }, "sha512-mnIlAEMu4OyEvUNdzco9xpuB9YVcPkQec+QsgycBCtPZvEqWPCDPfbAE4OJMdBBWpZWtpCn1xw9jJYlwjWI5zQ=="], - "layerchart": ["layerchart@2.0.0-next.48", "", { "dependencies": { "@dagrejs/dagre": "^2.0.4", "@layerstack/svelte-actions": "1.0.1-next.18", "@layerstack/svelte-state": "0.1.0-next.23", "@layerstack/tailwind": "2.0.0-next.21", "@layerstack/utils": "2.0.0-next.18", "@types/d3-contour": "^3.0.6", "d3-array": "^3.2.4", "d3-chord": "^3.0.1", "d3-color": "^3.1.0", "d3-contour": "^4.0.2", "d3-delaunay": "^6.0.4", "d3-dsv": "^3.0.1", "d3-force": "^3.0.0", "d3-geo": "^3.1.1", "d3-geo-voronoi": "^2.1.0", "d3-hierarchy": "^3.1.2", "d3-interpolate": "^3.0.1", "d3-interpolate-path": "^2.3.0", "d3-path": "^3.1.0", "d3-quadtree": "^3.0.1", "d3-random": "^3.0.1", "d3-sankey": "^0.12.3", "d3-scale": "^4.0.2", "d3-scale-chromatic": "^3.1.0", "d3-shape": "^3.2.0", "d3-tile": "^1.0.0", "d3-time": "^3.1.0", "memoize": "^10.2.0", "runed": "^0.37.1" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-XoEYBztamA8lMxtF/Jz3aDX0HMk8dI+o4fK9fSl8ecT2Tdx3DQUjtKGtlQAOFdwC/AWifeLmKq5cMTQt9COZPQ=="], - "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="], - "libphonenumber-js": ["libphonenumber-js@1.13.0", "", {}, "sha512-N12qmdu0BM1wVNkMKYOoJR4fTOZDblrKNsOqGbKoUZrYsYLX2zx1O5X+vhK0WJPBU/+/kh9tCr8x0a7t1puGWg=="], - "lighthouse-logger": ["lighthouse-logger@1.4.2", "", { "dependencies": { "debug": "^2.6.9", "marky": "^1.2.2" } }, "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g=="], - "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], @@ -2094,124 +675,32 @@ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], - "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], - - "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], - "locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="], - "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + "locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="], - "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="], - - "lodash.memoize": ["lodash.memoize@4.1.2", "", {}, "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="], - - "lodash.throttle": ["lodash.throttle@4.1.1", "", {}, "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="], - - "lodash.unionby": ["lodash.unionby@4.8.0", "", {}, "sha512-e60kn4GJIunNkw6v9MxRnUuLYI/Tyuanch7ozoCtk/1irJTYBj+qNTxr5B3qVflmJhwStJBv387Cb+9VOfABMg=="], - - "lodash.uniq": ["lodash.uniq@4.5.0", "", {}, "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ=="], - - "log-symbols": ["log-symbols@2.2.0", "", { "dependencies": { "chalk": "^2.0.1" } }, "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg=="], - - "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], - "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], - "lucide-react-native": ["lucide-react-native@1.30.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-native": "*", "react-native-svg": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0" } }, "sha512-rhi4+L7guZuQxCnU01QWQAE6SNBJWH9CDtx4/AZuHHaJjFbb1Pcm2MJrzcheWpDBDxRsnpomHUeEsS31oqMe8A=="], - "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], - - "makeerror": ["makeerror@1.0.12", "", { "dependencies": { "tmpl": "1.0.5" } }, "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg=="], - - "marky": ["marky@1.3.0", "", {}, "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ=="], - "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], - "md5.js": ["md5.js@1.3.5", "", { "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1", "safe-buffer": "^5.1.2" } }, "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg=="], - - "mdn-data": ["mdn-data@2.0.14", "", {}, "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow=="], - - "media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="], - "memoize": ["memoize@10.2.0", "", { "dependencies": { "mimic-function": "^5.0.1" } }, "sha512-DeC6b7QBrZsRs3Y02A6A7lQyzFbsQbqgjI6UW0GigGWV+u1s25TycMr0XHZE4cJce7rY/vyw2ctMQqfDkIhUEA=="], - "memoize-one": ["memoize-one@5.2.1", "", {}, "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="], - "memoize-weak": ["memoize-weak@1.0.2", "", {}, "sha512-gj39xkrjEw7nCn4nJ1M5ms6+MyMlyiGmttzsqAUsAKn6bYKwuTHh/AO3cKPF8IBrTIYTxb0wWXFs3E//Y8VoWQ=="], - "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], - - "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], - - "metro": ["metro@0.82.5", "", { "dependencies": { "@babel/code-frame": "^7.24.7", "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "@babel/types": "^7.25.2", "accepts": "^1.3.7", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.29.1", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.82.5", "metro-cache": "0.82.5", "metro-cache-key": "0.82.5", "metro-config": "0.82.5", "metro-core": "0.82.5", "metro-file-map": "0.82.5", "metro-resolver": "0.82.5", "metro-runtime": "0.82.5", "metro-source-map": "0.82.5", "metro-symbolicate": "0.82.5", "metro-transform-plugins": "0.82.5", "metro-transform-worker": "0.82.5", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-8oAXxL7do8QckID/WZEKaIFuQJFUTLzfVcC48ghkHhNK2RGuQq8Xvf4AVd+TUA0SZtX0q8TGNXZ/eba1ckeGCg=="], - - "metro-babel-transformer": ["metro-babel-transformer@0.82.5", "", { "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", "hermes-parser": "0.29.1", "nullthrows": "^1.1.1" } }, "sha512-W/scFDnwJXSccJYnOFdGiYr9srhbHPdxX9TvvACOFsIXdLilh3XuxQl/wXW6jEJfgIb0jTvoTlwwrqvuwymr6Q=="], - - "metro-cache": ["metro-cache@0.82.5", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", "metro-core": "0.82.5" } }, "sha512-AwHV9607xZpedu1NQcjUkua8v7HfOTKfftl6Vc9OGr/jbpiJX6Gpy8E/V9jo/U9UuVYX2PqSUcVNZmu+LTm71Q=="], - - "metro-cache-key": ["metro-cache-key@0.82.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-qpVmPbDJuRLrT4kcGlUouyqLGssJnbTllVtvIgXfR7ZuzMKf0mGS+8WzcqzNK8+kCyakombQWR0uDd8qhWGJcA=="], - - "metro-config": ["metro-config@0.82.5", "", { "dependencies": { "connect": "^3.6.5", "cosmiconfig": "^5.0.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", "metro": "0.82.5", "metro-cache": "0.82.5", "metro-core": "0.82.5", "metro-runtime": "0.82.5" } }, "sha512-/r83VqE55l0WsBf8IhNmc/3z71y2zIPe5kRSuqA5tY/SL/ULzlHUJEMd1szztd0G45JozLwjvrhAzhDPJ/Qo/g=="], - - "metro-core": ["metro-core@0.82.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.82.5" } }, "sha512-OJL18VbSw2RgtBm1f2P3J5kb892LCVJqMvslXxuxjAPex8OH7Eb8RBfgEo7VZSjgb/LOf4jhC4UFk5l5tAOHHA=="], - - "metro-file-map": ["metro-file-map@0.82.5", "", { "dependencies": { "debug": "^4.4.0", "fb-watchman": "^2.0.0", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "nullthrows": "^1.1.1", "walker": "^1.0.7" } }, "sha512-vpMDxkGIB+MTN8Af5hvSAanc6zXQipsAUO+XUx3PCQieKUfLwdoa8qaZ1WAQYRpaU+CJ8vhBcxtzzo3d9IsCIQ=="], - - "metro-minify-terser": ["metro-minify-terser@0.82.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" } }, "sha512-v6Nx7A4We6PqPu/ta1oGTqJ4Usz0P7c+3XNeBxW9kp8zayS3lHUKR0sY0wsCHInxZlNAEICx791x+uXytFUuwg=="], - - "metro-resolver": ["metro-resolver@0.82.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-kFowLnWACt3bEsuVsaRNgwplT8U7kETnaFHaZePlARz4Fg8tZtmRDUmjaD68CGAwc0rwdwNCkWizLYpnyVcs2g=="], - - "metro-runtime": ["metro-runtime@0.82.5", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-rQZDoCUf7k4Broyw3Ixxlq5ieIPiR1ULONdpcYpbJQ6yQ5GGEyYjtkztGD+OhHlw81LCR2SUAoPvtTus2WDK5g=="], - - "metro-source-map": ["metro-source-map@0.82.5", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.82.5", "nullthrows": "^1.1.1", "ob1": "0.82.5", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-wH+awTOQJVkbhn2SKyaw+0cd+RVSCZ3sHVgyqJFQXIee/yLs3dZqKjjeKKhhVeudgjXo7aE/vSu/zVfcQEcUfw=="], - - "metro-symbolicate": ["metro-symbolicate@0.82.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.82.5", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" } }, "sha512-1u+07gzrvYDJ/oNXuOG1EXSvXZka/0JSW1q2EYBWerVKMOhvv9JzDGyzmuV7hHbF2Hg3T3S2uiM36sLz1qKsiw=="], - - "metro-transform-plugins": ["metro-transform-plugins@0.82.5", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "flow-enums-runtime": "^0.0.6", "nullthrows": "^1.1.1" } }, "sha512-57Bqf3rgq9nPqLrT2d9kf/2WVieTFqsQ6qWHpEng5naIUtc/Iiw9+0bfLLWSAw0GH40iJ4yMjFcFJDtNSYynMA=="], - - "metro-transform-worker": ["metro-transform-worker@0.82.5", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "metro": "0.82.5", "metro-babel-transformer": "0.82.5", "metro-cache": "0.82.5", "metro-cache-key": "0.82.5", "metro-minify-terser": "0.82.5", "metro-source-map": "0.82.5", "metro-transform-plugins": "0.82.5", "nullthrows": "^1.1.1" } }, "sha512-mx0grhAX7xe+XUQH6qoHHlWedI8fhSpDGsfga7CpkO9Lk9W+aPitNtJWNGrW8PfjKEWbT9Uz9O50dkI8bJqigw=="], - - "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - - "miller-rabin": ["miller-rabin@4.0.1", "", { "dependencies": { "bn.js": "^4.0.0", "brorand": "^1.0.1" }, "bin": { "miller-rabin": "bin/miller-rabin" } }, "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA=="], - - "mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], - "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], - "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], - "mini-css-extract-plugin": ["mini-css-extract-plugin@2.10.2", "", { "dependencies": { "schema-utils": "^4.0.0", "tapable": "^2.2.1" }, "peerDependencies": { "webpack": "^5.0.0" } }, "sha512-AOSS0IdEB95ayVkxn5oGzNQwqAi2J0Jb/kKm43t7H73s8+f5873g0yuj0PNvK4dO75mu5DHg4nlgp4k6Kga8eg=="], - - "minimalistic-assert": ["minimalistic-assert@1.0.1", "", {}, "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="], - - "minimalistic-crypto-utils": ["minimalistic-crypto-utils@1.0.1", "", {}, "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg=="], - - "minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], - "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], - "minimizer-webpack-plugin": ["minimizer-webpack-plugin@5.6.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", "terser": "^5.31.1" }, "peerDependencies": { "@minify-html/node": "*", "@swc/core": "*", "@swc/css": "*", "@swc/html": "*", "clean-css": "*", "cssnano": "*", "csso": "*", "esbuild": "*", "html-minifier-terser": "*", "lightningcss": "*", "postcss": "*", "uglify-js": "*", "webpack": "^5.1.0" }, "optionalPeers": ["@minify-html/node", "@swc/core", "@swc/css", "@swc/html", "clean-css", "cssnano", "csso", "esbuild", "html-minifier-terser", "lightningcss", "postcss", "uglify-js"] }, "sha512-DoeAZz8Q1C1znwsUzej1fdoi4jCf7/+Em27ouLqfK/+3m8G+D7yDhUwrc3CNhjSzGUN1kn7Iv4sWmjflQHenpw=="], - - "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], - - "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], - - "mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], - - "mobile-lynx": ["mobile-lynx@workspace:apps/mobile-lynx"], - "mode-watcher": ["mode-watcher@1.1.0", "", { "dependencies": { "runed": "^0.25.0", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.27.0" } }, "sha512-mUT9RRGPDYenk59qJauN1rhsIMKBmWA3xMF+uRwE8MW/tjhaDSCCARqkSuDTq8vr4/2KcAxIGVjACxTjdk5C3g=="], "monero-ts": ["monero-ts@0.11.8", "", { "dependencies": { "@babel/runtime": "^7.23.2", "@types/node": "^20.6.0", "ajv": "^6.12.6", "async": "2.6.4", "axios": "^1.7.4", "crypto-js": "^4.0.0", "decimal.js": "^10.4.3", "net": "^1.0.2", "promise-throttle": "^1.1.2", "serialize-javascript": "^3.1.0", "text-encoding": "^0.7.0", "tls": "0.0.1", "uuid": "3.3.2", "web-worker": "1.3.0" } }, "sha512-+DFw/1ldAuVb7GDnYL9IbagDGJxY8mkHMuKsBqIBrf07Y/fTCZ8SZhJDg4FVFhYwMRdBad0V+nPhjoSeSyNa3Q=="], @@ -2222,87 +711,21 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], - "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - "nativewind": ["nativewind@4.2.6", "", { "dependencies": { "comment-json": "^4.2.5", "debug": "^4.3.7", "react-native-css-interop": "0.2.6" }, "peerDependencies": { "tailwindcss": ">3.3.0" } }, "sha512-kLPRH3Yh/hhIFufXzUZDe39fuK471GwaPBCqYNgcRygILr+obV+wFfDmG+zaJnniVUFFQzYOTCEldem6XktZ+A=="], - - "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], - - "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], - - "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], - - "nested-error-stacks": ["nested-error-stacks@2.0.1", "", {}, "sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A=="], - "net": ["net@1.0.2", "", {}, "sha512-kbhcj2SVVR4caaVnGLJKmlk2+f+oLkjqdKeQlmUtz6nGzOpbcobwVIeSURNgraV/v3tlmGIX82OcPCl0K6RbHQ=="], "netmask": ["netmask@2.1.1", "", {}, "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA=="], - "node-dir": ["node-dir@0.1.17", "", { "dependencies": { "minimatch": "^3.0.2" } }, "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg=="], - - "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], - "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], - "node-forge": ["node-forge@1.4.0", "", {}, "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ=="], - - "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="], - - "node-releases": ["node-releases@2.0.53", "", {}, "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ=="], - - "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], - "normalize-url": ["normalize-url@8.1.1", "", {}, "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ=="], - "npm-package-arg": ["npm-package-arg@11.0.3", "", { "dependencies": { "hosted-git-info": "^7.0.0", "proc-log": "^4.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^5.0.0" } }, "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw=="], - - "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], - - "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], - - "nullthrows": ["nullthrows@1.1.1", "", {}, "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw=="], - - "nwsapi": ["nwsapi@2.2.24", "", {}, "sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A=="], - - "ob1": ["ob1@0.82.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-QyQQ6e66f+Ut/qUVjEce0E/wux5nAGLXYZDn1jr15JWstHsCH3l6VVrg8NKDptW9NEiBXKOJeGF/ydxeSDF3IQ=="], - - "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], - - "object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], - - "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], - - "object-is": ["object-is@1.1.6", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1" } }, "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q=="], - - "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], - - "object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="], - - "object.groupby": ["object.groupby@1.0.3", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2" } }, "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ=="], - "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], - "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], + "p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], - "on-headers": ["on-headers@1.1.0", "", {}, "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A=="], - - "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], - - "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], - - "open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="], - - "ora": ["ora@3.4.0", "", { "dependencies": { "chalk": "^2.4.2", "cli-cursor": "^2.1.0", "cli-spinners": "^2.0.0", "log-symbols": "^2.2.0", "strip-ansi": "^5.2.0", "wcwidth": "^1.0.1" } }, "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg=="], - - "os-browserify": ["os-browserify@0.3.0", "", {}, "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A=="], - - "own-keys": ["own-keys@1.0.2", "", { "dependencies": { "call-bound": "^1.0.4", "get-intrinsic": "^1.3.0", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg=="], - - "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], - - "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + "p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], @@ -2310,298 +733,60 @@ "pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="], - "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], - - "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], - "paneforge": ["paneforge@1.0.2", "", { "dependencies": { "runed": "^0.23.4", "svelte-toolbelt": "^0.9.2" }, "peerDependencies": { "svelte": "^5.29.0" } }, "sha512-KzmIXQH1wCfwZ4RsMohD/IUtEjVhteR+c+ulb/CHYJHX8SuDXoJmChtsc/Xs5Wl8NHS4L5Q7cxL8MG40gSU1bA=="], - "parse-asn1": ["parse-asn1@5.1.9", "", { "dependencies": { "asn1.js": "^4.10.1", "browserify-aes": "^1.2.0", "evp_bytestokey": "^1.0.3", "pbkdf2": "^3.1.5", "safe-buffer": "^5.2.1" } }, "sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg=="], - - "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], - - "parse-png": ["parse-png@2.1.0", "", { "dependencies": { "pngjs": "^3.3.0" } }, "sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ=="], - - "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], - - "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], - - "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], - "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], - "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], - - "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - "pbkdf2": ["pbkdf2@3.1.6", "", { "dependencies": { "create-hash": "^1.2.0", "create-hmac": "^1.1.7", "ripemd160": "^2.0.3", "safe-buffer": "^5.2.1", "sha.js": "^2.4.12", "to-buffer": "^1.2.2" } }, "sha512-BT6eelPB1EyGHo8pC0o9Bl6k6SYVhKO1jEbd3lcTrtr7XHdjP8BW1YpfCV3G9Kwkxgattk+S5q2/RvuttCsS1g=="], - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], - "pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="], - - "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], - - "pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="], - - "plist": ["plist@3.1.1", "", { "dependencies": { "@xmldom/xmldom": "^0.9.10", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA=="], - "png-to-ico": ["png-to-ico@2.1.8", "", { "dependencies": { "@types/node": "^17.0.36", "minimist": "^1.2.6", "pngjs": "^6.0.0" }, "bin": { "png-to-ico": "bin/cli.js" } }, "sha512-Nf+IIn/cZ/DIZVdGveJp86NG5uNib1ZXMiDd/8x32HCTeKSvgpyg6D/6tUBn1QO/zybzoMK0/mc3QRgAyXdv9w=="], "pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="], - "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], - "postcss": ["postcss@8.5.14", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg=="], - "postcss-calc": ["postcss-calc@10.1.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.38" } }, "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw=="], - - "postcss-colormin": ["postcss-colormin@7.0.10", "", { "dependencies": { "@colordx/core": "^5.4.3", "browserslist": "^4.28.2", "caniuse-api": "^3.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-yFr6JezOolHLta/buLE71VKPh2mXursp4saVe98/ol8ZnEWhL+racShqPKlvd/DKWLre/39B6HhcMXf7RZ3hxg=="], - - "postcss-convert-values": ["postcss-convert-values@7.0.12", "", { "dependencies": { "browserslist": "^4.28.2", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-xurKu5qqk4viR3Cp3p4xBR4KfnZm4w4ys6+UBwBmeuBSNkH7+DtLnYOYnOffgtE4yx8sH9S1VZ6RAAvROXzP2Q=="], - - "postcss-discard-comments": ["postcss-discard-comments@7.0.8", "", { "dependencies": { "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-CvvS5S9WrXblFXCEJ9nVo+4z+eA7zSC7Z88V1HEJuwlQhlFnYTIjg1xJY+BCUiG2bvICap2tXii4mP22BD108Q=="], - - "postcss-discard-duplicates": ["postcss-discard-duplicates@7.0.4", "", { "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-VBNn1+EuMZkeGVVtz0gRfbNGtx9IFgAsAV+E2pHtXPrp4qfGBkhTIiAuE/wrb+Y6Pakg9NewAlfTpYIFAWODtw=="], - - "postcss-discard-empty": ["postcss-discard-empty@7.0.3", "", { "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-M2pyjQCU+/7cMHVtL6bKTHjv0lZnPLMpicgr67Dlth7AbuV9gjVTtUqaRwn6Pp6BwSDspUzhz8SaUrRykJU5Dw=="], - - "postcss-discard-overridden": ["postcss-discard-overridden@7.0.3", "", { "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-aNovXo9UsZuRNLzHJtp13lHIvinDPfiXBPePpXkSjCbgp++iU2FqE+YxvjIsg6EdyPZsASFbfu+JcBFVsErXIQ=="], - - "postcss-import": ["postcss-import@15.1.0", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew=="], - - "postcss-js": ["postcss-js@4.1.0", "", { "dependencies": { "camelcase-css": "^2.0.1" }, "peerDependencies": { "postcss": "^8.4.21" } }, "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw=="], - - "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], - - "postcss-merge-longhand": ["postcss-merge-longhand@7.0.7", "", { "dependencies": { "postcss-value-parser": "^4.2.0", "stylehacks": "^7.0.11" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-b3mfYUxR388u5Pt0HPcVIUtUDn/k15UfTY9M+ORW+meCR6JLNxoZffiYvXyOYQoRYQNZyX/UFkMCM/mNHxe1qA=="], - - "postcss-merge-rules": ["postcss-merge-rules@7.0.11", "", { "dependencies": { "browserslist": "^4.28.2", "caniuse-api": "^3.0.0", "cssnano-utils": "^5.0.3", "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-SJUPM18g2BmPhf8BVlbwqWz4aK3pLu6u6xjfwEzra7xL6IBR10sUaiB++EzqcVfadPHrKBSMlNdP+XieykhI+Q=="], - - "postcss-minify-font-values": ["postcss-minify-font-values@7.0.3", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-yilG/VOaNI74IylQvAQQxm3/wZVBkXyYUqNUAdxqwtbWUXPsbK1q8Ms0mL83v+f8YicgcyfYCRZtWACUdYajpA=="], - - "postcss-minify-gradients": ["postcss-minify-gradients@7.0.5", "", { "dependencies": { "@colordx/core": "^5.4.3", "cssnano-utils": "^5.0.3", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-YraROyQRg3BI1+Hg8E05B/JPdnTm8EDSVu4P2BxdM+CRiOyfmou809+chGIqo6fQqwjPGQ947nbGncSjmTU1WQ=="], - - "postcss-minify-params": ["postcss-minify-params@7.0.9", "", { "dependencies": { "browserslist": "^4.28.2", "cssnano-utils": "^5.0.3", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-R8itbB8BhlpoYyBm1ou0dD+vJnQ3F6adQipR4UnkCHUwlo+S9WXJaDRg1RHjC8YVAtIdrQzSWvJl40HnGDTKjA=="], - - "postcss-minify-selectors": ["postcss-minify-selectors@7.1.2", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-api": "^3.0.0", "cssesc": "^3.0.0", "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-aQtrEWKwqafNlExcKHQvPGsXR2+vlUqqJtf5XsCQcgsSb5PL4wlujWBYDJuWsP4UnQX1YHDHU8qRlD+1PzTQ+Q=="], - - "postcss-nested": ["postcss-nested@6.2.0", "", { "dependencies": { "postcss-selector-parser": "^6.1.1" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ=="], - - "postcss-normalize-charset": ["postcss-normalize-charset@7.0.3", "", { "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-NoBfZu8PR4c2NlmjvrqQTzCzLY79hwcSRgNQ3ZiNK0ABzf9kYKloE/jNj+/8GQY1wsm8pRRgANk6ydLH8cwo0Q=="], - - "postcss-normalize-display-values": ["postcss-normalize-display-values@7.0.3", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-ldsCX0QIt05pKIOobZtVQ48wXJecr+czw4+e1/YjVhLMqslShgpVxgPtI2CefURR8oyVoYaU/l829MMwExDMLw=="], - - "postcss-normalize-positions": ["postcss-normalize-positions@7.0.4", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-VEvlpeGd3Ju1Hqa/oN4jaP3+ms4laYwkEL9N9u+B6k54PZjXbW1n6wI+aVprf1BQXlCYpS5+1pl/7/vHiKgARg=="], - - "postcss-normalize-repeat-style": ["postcss-normalize-repeat-style@7.0.4", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-6mPKlY/8cSaDHxX502wERADarJsccwlky6yIrOapHH2ZgfoKAV94SbiTKfKEs4EEpdazuc3J72WsqeYk7hp9+Q=="], - - "postcss-normalize-string": ["postcss-normalize-string@7.0.3", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-HnEQPUchi1eznmDKEYrKUTqrprEq97SrpUYClgUkv7V2zRODD9DFoUsYU+m9ZOetmD5ku7fEMZB/lwy8IT6xVQ=="], - - "postcss-normalize-timing-functions": ["postcss-normalize-timing-functions@7.0.3", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-zmEzHdvpZBZu0OKlbJSfgASQvaayyAoVuWtvyr34IJ/LyS+DaOKvvR3EvFJ9RWWtNIx+CMvO125OVophaxNYew=="], - - "postcss-normalize-unicode": ["postcss-normalize-unicode@7.0.9", "", { "dependencies": { "browserslist": "^4.28.2", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-DRAdWfeh/TjmhLJsw91vdiWCnUod9iwvM7xyS02/nF/sLsCR3A8l3pztrSUrWG8DSBqfX7yEk9FM0USaVJ2mSg=="], - - "postcss-normalize-url": ["postcss-normalize-url@7.0.3", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-CL93wmloq5qsffmFv+bw24MIRbmhHrp53qoh1LDAb/5TtjWEXI/np4xcP/Gw9oWCb2XyWnqHYLDUwiKRoJBA1Q=="], - - "postcss-normalize-whitespace": ["postcss-normalize-whitespace@7.0.3", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-FdHjjn+Ht5Z2ZRjNOmeCbNq6lq09sUYKpmlF/Aq0XjVNSLTL6fmHlA/3swN2wP2caY9GV/tjSDcIIyS7aN7W0A=="], - - "postcss-ordered-values": ["postcss-ordered-values@7.0.4", "", { "dependencies": { "cssnano-utils": "^5.0.3", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-nubSi49hDHQk4E8KIj+IbLY8Bg+8OcSUEhgyolgM+atnOvXjV7EjaR6bac4YGZoFyPa9mWoAF3EaYbWdFkKqVg=="], - - "postcss-reduce-initial": ["postcss-reduce-initial@7.0.9", "", { "dependencies": { "browserslist": "^4.28.2", "caniuse-api": "^3.0.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-ztTNPdIxXTxtBcG03E9u8v44M4ElXbMIRT7pf2onlquGula0Y83nKKxqM22FA/hMgkfCjN7ohevkVlaNwI8iOQ=="], - - "postcss-reduce-transforms": ["postcss-reduce-transforms@7.0.3", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-FXsnN9ZwcZTT8Yf8cAHA8qIGUXcX6WfLd9JoYhrdDfmvsVhhfqkkv7m4AC3rwFOfz+GzkUa87OCKF9dUcicd+g=="], - - "postcss-selector-parser": ["postcss-selector-parser@6.1.4", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ=="], - - "postcss-svgo": ["postcss-svgo@7.1.3", "", { "dependencies": { "postcss-value-parser": "^4.2.0", "svgo": "^4.0.1" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-2QfoFOYMcj8lwcVEf9WeTlkVIAm7u2QvOEhMzkQU3KUhhGX/l8hVV9EtjMv4iq3E9iI3OeeMN0YoMLbGusuigw=="], - - "postcss-unique-selectors": ["postcss-unique-selectors@7.0.7", "", { "dependencies": { "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-d+sCkaRnSefghOUdH8CMJZV9yUQhj2ojpe8Nw/lA+LV1UOfeleGkLTl6XdCFFSai9UJ+DJPb69FFuqthXYsY8w=="], - - "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], - - "preact": ["@hongzhiyuan/preact@10.28.0-fc4af453", "", {}, "sha512-TrM2g079OZN1poroDnU2kQd1gNUB1DMfVoKyz23AE3hZvl9ypRgG2MdgxAJtwT5u3BmYvI4Z0EbdpJTdf428IQ=="], - "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], "prettier-plugin-svelte": ["prettier-plugin-svelte@3.5.1", "", { "peerDependencies": { "prettier": "^3.0.0", "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" } }, "sha512-65+fr5+cgIKWKiqM1Doum4uX6bY8iFCdztvvp2RcF+AJoieaw9kJOFMNcJo/bkmKYsxFaM9OsVZK/gWauG/5mg=="], "prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.7.2", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-hermes": "*", "@prettier/plugin-oxc": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-hermes", "@prettier/plugin-oxc", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-svelte"] }, "sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA=="], - "pretty-bytes": ["pretty-bytes@5.6.0", "", {}, "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg=="], - - "pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="], - - "proc-log": ["proc-log@4.2.0", "", {}, "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA=="], - - "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], - - "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], - - "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], - - "promise": ["promise@8.3.0", "", { "dependencies": { "asap": "~2.0.6" } }, "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg=="], - "promise-throttle": ["promise-throttle@1.1.2", "", {}, "sha512-dij7vjyXNewuuN/gyr+TX2KRjw48mbV5FEtgyXaIoJjGYAKT0au23/voNvy9eS4UNJjx2KUdEcO5Yyfc1h7vWQ=="], - "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], - - "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], - "property-expr": ["property-expr@2.0.6", "", {}, "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA=="], "proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="], "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], - "psl": ["psl@1.15.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w=="], - - "public-encrypt": ["public-encrypt@4.0.3", "", { "dependencies": { "bn.js": "^4.1.0", "browserify-rsa": "^4.0.0", "create-hash": "^1.1.0", "parse-asn1": "^5.0.0", "randombytes": "^2.0.1", "safe-buffer": "^5.1.2" } }, "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q=="], - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], "qrcode": ["qrcode@1.5.4", "", { "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": { "qrcode": "bin/qrcode" } }, "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="], - "qrcode-terminal": ["qrcode-terminal@0.11.0", "", { "bin": { "qrcode-terminal": "./bin/qrcode-terminal.js" } }, "sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ=="], - - "qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="], - "quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="], - "query-string": ["query-string@7.1.3", "", { "dependencies": { "decode-uri-component": "^0.2.2", "filter-obj": "^1.1.0", "split-on-first": "^1.0.0", "strict-uri-encode": "^2.0.0" } }, "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg=="], - - "querystring-es3": ["querystring-es3@0.2.1", "", {}, "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA=="], - - "querystringify": ["querystringify@2.2.0", "", {}, "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="], - - "queue": ["queue@6.0.2", "", { "dependencies": { "inherits": "~2.0.3" } }, "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA=="], - - "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], - "randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="], - "randomfill": ["randomfill@1.0.4", "", { "dependencies": { "randombytes": "^2.0.5", "safe-buffer": "^5.1.0" } }, "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw=="], - - "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], - - "raw-body": ["raw-body@2.5.2", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } }, "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA=="], - - "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], - "rcedit": ["rcedit@4.0.1", "", { "dependencies": { "cross-spawn-windows-exe": "^1.1.0" } }, "sha512-bZdaQi34krFWhrDn+O53ccBDw0MkAT2Vhu75SqhtvhQu4OPyFM4RoVheyYiVQYdjhUi6EJMVWQ0tR6bCIYVkUg=="], - "react": ["react@19.0.0", "", {}, "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ=="], - - "react-devtools-core": ["react-devtools-core@6.1.5", "", { "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" } }, "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA=="], - - "react-dom": ["react-dom@19.0.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="], - - "react-fast-compare": ["react-fast-compare@3.2.2", "", {}, "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="], - - "react-freeze": ["react-freeze@1.0.4", "", { "peerDependencies": { "react": ">=17.0.0" } }, "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA=="], - - "react-helmet-async": ["react-helmet-async@1.3.0", "", { "dependencies": { "@babel/runtime": "^7.12.5", "invariant": "^2.2.4", "prop-types": "^15.7.2", "react-fast-compare": "^3.2.0", "shallowequal": "^1.1.0" }, "peerDependencies": { "react": "^16.6.0 || ^17.0.0 || ^18.0.0", "react-dom": "^16.6.0 || ^17.0.0 || ^18.0.0" } }, "sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg=="], - - "react-is": ["react-is@19.2.8", "", {}, "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ=="], - - "react-native": ["react-native@0.79.0", "", { "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.79.0", "@react-native/codegen": "0.79.0", "@react-native/community-cli-plugin": "0.79.0", "@react-native/gradle-plugin": "0.79.0", "@react-native/js-polyfills": "0.79.0", "@react-native/normalize-colors": "0.79.0", "@react-native/virtualized-lists": "0.79.0", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-jest": "^29.7.0", "babel-plugin-syntax-hermes-parser": "0.25.1", "base64-js": "^1.5.1", "chalk": "^4.0.0", "commander": "^12.0.0", "event-target-shim": "^5.0.1", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", "metro-runtime": "^0.82.0", "metro-source-map": "^0.82.0", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.1.1", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.25.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", "ws": "^6.2.3", "yargs": "^17.6.2" }, "peerDependencies": { "@types/react": "^19.0.0", "react": "^19.0.0" }, "optionalPeers": ["@types/react"], "bin": { "react-native": "cli.js" } }, "sha512-fLG/zl/YF30TWTmp2bbo3flHSFGe4WTyVkb7/wJnMEC39jjXVSCxfDtvSUVavhCc03fA/RTkWWvlmg7NEJk7Vg=="], - - "react-native-css-interop": ["react-native-css-interop@0.2.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.22.15", "@babel/traverse": "^7.23.0", "@babel/types": "^7.23.0", "debug": "^4.3.7", "lightningcss": "~1.27.0", "semver": "^7.6.3" }, "peerDependencies": { "react": ">=18", "react-native": "*", "react-native-reanimated": ">=3.6.2", "react-native-safe-area-context": "*", "react-native-svg": "*", "tailwindcss": "~3" }, "optionalPeers": ["react-native-safe-area-context", "react-native-svg"] }, "sha512-uXad6EWqufupnRjrba2De30tiAx26kLEcwSV3eSLVBwpfYeOnrpDc9k6kHk6SBNbmHHlE5sc9vsfKdVRS9msYg=="], - - "react-native-edge-to-edge": ["react-native-edge-to-edge@1.6.0", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-2WCNdE3Qd6Fwg9+4BpbATUxCLcouF6YRY7K+J36KJ4l3y+tWN6XCqAC4DuoGblAAbb2sLkhEDp4FOlbOIot2Og=="], - - "react-native-gesture-handler": ["react-native-gesture-handler@2.24.0", "", { "dependencies": { "@egjs/hammerjs": "^2.0.17", "hoist-non-react-statics": "^3.3.0", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-ZdWyOd1C8axKJHIfYxjJKCcxjWEpUtUWgTOVY2wynbiveSQDm8X/PDyAKXSer/GOtIpjudUbACOndZXCN3vHsw=="], - - "react-native-helmet-async": ["react-native-helmet-async@2.0.4", "", { "dependencies": { "invariant": "^2.2.4", "react-fast-compare": "^3.2.2", "shallowequal": "^1.1.0" }, "peerDependencies": { "react": "^16.6.0 || ^17.0.0 || ^18.0.0" } }, "sha512-m3CkXWss6B1dd6mCMleLpzDCJJGGaHOLQsUzZv8kAASJmMfmVT4d2fx375iXKTRWT25ThBfae3dECuX5cq/8hg=="], - - "react-native-is-edge-to-edge": ["react-native-is-edge-to-edge@1.3.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-NIXU/iT5+ORyCc7p0z2nnlkouYKX425vuU1OEm6bMMtWWR9yvb+Xg5AZmImTKoF9abxCPqrKC3rOZsKzUYgYZA=="], - - "react-native-qrcode-svg": ["react-native-qrcode-svg@6.3.21", "", { "dependencies": { "prop-types": "^15.8.0", "qrcode": "^1.5.4", "text-encoding": "^0.7.0" }, "peerDependencies": { "react": "*", "react-native": ">=0.63.4", "react-native-svg": ">=14.0.0" } }, "sha512-6vcj4rcdpWedvphDR+NSJcudJykNuLgNGFwm2p4xYjR8RdyTzlrELKI5LkO4ANS9cQUbqsfkpippPv64Q2tUtA=="], - - "react-native-reanimated": ["react-native-reanimated@3.17.5", "", { "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", "@babel/plugin-transform-class-properties": "^7.0.0-0", "@babel/plugin-transform-classes": "^7.0.0-0", "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0", "@babel/plugin-transform-optional-chaining": "^7.0.0-0", "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", "@babel/plugin-transform-template-literals": "^7.0.0-0", "@babel/plugin-transform-unicode-regex": "^7.0.0-0", "@babel/preset-typescript": "^7.16.7", "convert-source-map": "^2.0.0", "invariant": "^2.2.4", "react-native-is-edge-to-edge": "1.1.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0", "react": "*", "react-native": "*" } }, "sha512-SxBK7wQfJ4UoWoJqQnmIC7ZjuNgVb9rcY5Xc67upXAFKftWg0rnkknTw6vgwnjRcvYThrjzUVti66XoZdDJGtw=="], - - "react-native-safe-area-context": ["react-native-safe-area-context@5.4.0", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-JaEThVyJcLhA+vU0NU8bZ0a1ih6GiF4faZ+ArZLqpYbL6j7R3caRqj+mE3lEtKCuHgwjLg3bCxLL1GPUJZVqUA=="], - - "react-native-screens": ["react-native-screens@4.10.0", "", { "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-Tw21NGuXm3PbiUGtZd0AnXirUixaAbPXDjNR0baBH7/WJDaDTTELLcQ7QRXuqAWbmr/EVCrKj1348ei1KFIr8A=="], - - "react-native-svg": ["react-native-svg@15.11.2", "", { "dependencies": { "css-select": "^5.1.0", "css-tree": "^1.1.3", "warn-once": "0.1.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-+YfF72IbWQUKzCIydlijV1fLuBsQNGMT6Da2kFlo1sh+LE3BIm/2Q7AR1zAAR6L0BFLi1WaQPLfFUC9bNZpOmw=="], - - "react-native-web": ["react-native-web@0.20.0", "", { "dependencies": { "@babel/runtime": "^7.18.6", "@react-native/normalize-colors": "^0.74.1", "fbjs": "^3.0.4", "inline-style-prefixer": "^7.0.1", "memoize-one": "^6.0.0", "nullthrows": "^1.1.1", "postcss-value-parser": "^4.2.0", "styleq": "^0.1.3" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-OOSgrw+aON6R3hRosCau/xVxdLzbjEcsLysYedka0ZON4ZZe6n9xgeN9ZkoejhARM36oTlUgHIQqxGutEJ9Wxg=="], - - "react-native-worklets": ["react-native-worklets@0.11.3", "", { "dependencies": { "@babel/generator": "^7.27.1", "@babel/plugin-transform-arrow-functions": "^7.27.1", "@babel/plugin-transform-class-properties": "^7.28.6", "@babel/plugin-transform-classes": "^7.28.6", "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", "@babel/plugin-transform-optional-chaining": "^7.28.6", "@babel/plugin-transform-shorthand-properties": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/preset-typescript": "^7.28.5", "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1", "convert-source-map": "^2.0.0", "semver": "^7.7.4" }, "peerDependencies": { "@babel/core": "*", "@react-native/metro-config": "*", "react": "*", "react-native": "0.83 - 0.86" } }, "sha512-paLRlVFTrrGhkGTi37LHwtaJbrTftA54/zAx1h/ONSBw7XejZn6XUUl1KpM6EOMwsbw7TEGuxJXTAp/wkvuMqQ=="], - - "react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="], - - "react-test-renderer": ["react-test-renderer@19.0.0", "", { "dependencies": { "react-is": "^19.0.0", "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-oX5u9rOQlHzqrE/64CNr0HB0uWxkCQmZNSfozlYvwE71TLVgeZxVf0IjouGEr1v7r1kcDifdAJBeOhdhxsG/DA=="], - - "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="], - - "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], - "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], - "recast": ["recast@0.21.5", "", { "dependencies": { "ast-types": "0.15.2", "esprima": "~4.0.0", "source-map": "~0.6.1", "tslib": "^2.0.1" } }, "sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg=="], - - "reduce-configs": ["reduce-configs@1.1.2", "", {}, "sha512-AgBP55V8FC7NaqoOP2RCbTpu6LE+YuX3LUZkNAoitcfyS3/PIC8Obg/TJrBzTkJ+lDvZv0TTAeDpLkzjTtYlbw=="], - - "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], - - "regenerate": ["regenerate@1.4.2", "", {}, "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A=="], - - "regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], - - "regenerator-runtime": ["regenerator-runtime@0.13.11", "", {}, "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="], - - "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="], - - "regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], - - "regjsgen": ["regjsgen@0.8.0", "", {}, "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q=="], - - "regjsparser": ["regjsparser@0.13.2", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ=="], - "remixicon-svelte": ["remixicon-svelte@0.0.5", "", { "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-a7tnkBeA6P/ItRc2Q/jOgF+Xoh6Fj7hKrr/E6ZUCmOM4XBFi+mBDR95hqdRHWVf2s8sK85ycGXg+AAFZv87s+Q=="], "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], - "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], - "require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="], - "requireg": ["requireg@0.2.2", "", { "dependencies": { "nested-error-stacks": "~2.0.1", "rc": "~1.2.7", "resolve": "~1.7.1" } }, "sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg=="], - - "requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="], - - "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], - - "resolve-cwd": ["resolve-cwd@3.0.0", "", { "dependencies": { "resolve-from": "^5.0.0" } }, "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg=="], - - "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], - "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], - "resolve-workspace-root": ["resolve-workspace-root@2.0.1", "", {}, "sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w=="], - - "resolve.exports": ["resolve.exports@2.0.3", "", {}, "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A=="], - - "restore-cursor": ["restore-cursor@2.0.0", "", { "dependencies": { "onetime": "^2.0.0", "signal-exit": "^3.0.2" } }, "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q=="], - - "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], - - "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], - - "ripemd160": ["ripemd160@2.0.3", "", { "dependencies": { "hash-base": "^3.1.2", "inherits": "^2.0.4" } }, "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA=="], - "ripple-address-codec": ["ripple-address-codec@5.0.1", "", { "dependencies": { "@scure/base": "^2.0.0", "@xrplf/isomorphic": "^1.0.2" } }, "sha512-JQHLKuVJV8lv9Qobmn4aUM2Dpv9WRRLKnNWfM8tN02fAbUtG8mUPsu9q9UYX8P76G4qzytEc5ZKMp/3JggNYmw=="], "ripple-binary-codec": ["ripple-binary-codec@2.8.0", "", { "dependencies": { "@xrplf/isomorphic": "^1.0.2", "bignumber.js": "^10.0.2", "ripple-address-codec": "^5.0.1" } }, "sha512-+NKnOi3hdzjm5dDpoZLUEaYon1jahPlSGnp3YrDoNMSR09ICEqgupN5wpEkPuqJvV75PF/g+W1QUwIXVzbEe7w=="], @@ -2614,14 +799,6 @@ "rolldown-plugin-dts": ["rolldown-plugin-dts@0.17.8", "", { "dependencies": { "@babel/generator": "^7.28.5", "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "ast-kit": "^2.2.0", "birpc": "^2.8.0", "dts-resolver": "^2.1.3", "get-tsconfig": "^4.13.0", "magic-string": "^0.30.21", "obug": "^2.0.0" }, "peerDependencies": { "@ts-macro/tsc": "^0.3.6", "@typescript/native-preview": ">=7.0.0-dev.20250601.1", "rolldown": "^1.0.0-beta.44", "typescript": "^5.0.0", "vue-tsc": "~3.1.0" }, "optionalPeers": ["@ts-macro/tsc", "@typescript/native-preview", "typescript", "vue-tsc"] }, "sha512-76EEBlhF00yeY6M7VpMkWKI4r9WjuoMiOGey7j4D6zf3m0BR+ZrrY9hvSXdueJ3ljxSLq4DJBKFpX/X9+L7EKw=="], - "rsbuild-plugin-tailwindcss": ["rsbuild-plugin-tailwindcss@0.2.4", "", { "peerDependencies": { "@rsbuild/core": "^1.1.0 || ^2.0.0-0", "tailwindcss": "^3.1.0" }, "optionalPeers": ["@rsbuild/core"] }, "sha512-coLf2Rn5QeXkCrW4zlyPej+ezQ1sNFwwagrlpITqo/Yz78bLeNWrMbA1WqHNLtr5IwM0lL/SEc4ZVglw9M13pw=="], - - "rslog": ["rslog@1.3.2", "", {}, "sha512-1YyYXBvN0a2b1MSIDLwDTqqgjDzRKxUg/S/+KO6EAgbtZW1B3fdLHAMhEEtvk1patJYMqcRvlp3HQwnxj7AdGQ=="], - - "rspack-vue-loader": ["rspack-vue-loader@17.6.2", "", { "dependencies": { "@rspack/lite-tapable": "^1.1.0", "chalk": "^4.1.0" }, "peerDependencies": { "@rspack/core": "^1.0.0 || ^2.0.0", "@vue/compiler-sfc": "*", "vue": "*" }, "optionalPeers": ["@rspack/core", "@vue/compiler-sfc", "vue"] }, "sha512-ojnONLyFz0SCLYSDrC8NwHqUBgj/lHFNVmv1hCWk4vaPYjfdBRCLJEGla+P7PtkBkc4eKFgfRSMbSNVyM8EwaQ=="], - - "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], - "runed": ["runed@0.35.1", "", { "dependencies": { "dequal": "^2.0.3", "esm-env": "^1.0.0", "lz-string": "^1.5.0" }, "peerDependencies": { "@sveltejs/kit": "^2.21.0", "svelte": "^5.7.0" }, "optionalPeers": ["@sveltejs/kit"] }, "sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q=="], "rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="], @@ -2630,178 +807,48 @@ "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], - "safe-array-concat": ["safe-array-concat@1.1.4", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "get-intrinsic": "^1.3.0", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg=="], - "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], - "safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="], - - "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], - "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], - "sax": ["sax@1.6.1", "", {}, "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q=="], - - "saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="], - - "scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="], - - "schema-utils": ["schema-utils@4.3.3", "", { "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } }, "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA=="], - "semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="], - "send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="], - - "serialize-error": ["serialize-error@2.1.0", "", {}, "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw=="], - "serialize-javascript": ["serialize-javascript@3.1.0", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg=="], - "serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="], - - "server-only": ["server-only@0.0.1", "", {}, "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA=="], - "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="], "set-cookie-parser": ["set-cookie-parser@3.1.0", "", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="], - "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], - - "set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="], - - "set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="], - - "setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="], - - "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], - - "sf-symbols-typescript": ["sf-symbols-typescript@2.2.0", "", {}, "sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw=="], - - "sha.js": ["sha.js@2.4.12", "", { "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1", "to-buffer": "^1.2.0" }, "bin": { "sha.js": "bin.js" } }, "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w=="], - "shadcn-svelte": ["shadcn-svelte@1.2.7", "", { "dependencies": { "commander": "^14.0.0", "node-fetch-native": "^1.6.4", "postcss": "^8.5.5", "tailwind-merge": "^3.0.0" }, "peerDependencies": { "svelte": "^5.0.0" }, "bin": { "shadcn-svelte": "dist/index.mjs" } }, "sha512-mWuQk4H4gtV+J2wJQ7nEPKNnB/v86AALFryZU0SSM7ChHmJJMZ1kH+qIuxYKrXm9vOOOcSWHRsWzPDB71DnjYA=="], - "shallow-clone": ["shallow-clone@3.0.1", "", { "dependencies": { "kind-of": "^6.0.2" } }, "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA=="], - - "shallowequal": ["shallowequal@1.1.0", "", {}, "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="], - "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="], - "side-channel": ["side-channel@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4", "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ=="], - - "side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="], - - "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], - - "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], - - "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - - "simple-plist": ["simple-plist@1.3.1", "", { "dependencies": { "bplist-creator": "0.1.0", "bplist-parser": "0.3.1", "plist": "^3.0.5" } }, "sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw=="], - - "simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="], - "sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="], - "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], - - "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], - - "slugify": ["slugify@1.6.9", "", {}, "sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg=="], - "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], - "socket.io": ["socket.io@4.8.1", "", { "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" } }, "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg=="], - - "socket.io-adapter": ["socket.io-adapter@2.5.8", "", { "dependencies": { "debug": "~4.4.1", "ws": "~8.21.0" } }, "sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw=="], - - "socket.io-parser": ["socket.io-parser@4.2.7", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1" } }, "sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg=="], - "socks": ["socks@2.8.9", "", { "dependencies": { "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" } }, "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw=="], "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], - "source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="], + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], - - "split-on-first": ["split-on-first@1.1.0", "", {}, "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="], - - "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], - - "stack-generator": ["stack-generator@2.0.10", "", { "dependencies": { "stackframe": "^1.3.4" } }, "sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ=="], - - "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], - - "stackframe": ["stackframe@1.3.4", "", {}, "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="], - - "stacktrace-gps": ["stacktrace-gps@3.1.2", "", { "dependencies": { "source-map": "0.5.6", "stackframe": "^1.3.4" } }, "sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ=="], - - "stacktrace-js": ["stacktrace-js@2.0.2", "", { "dependencies": { "error-stack-parser": "^2.0.6", "stack-generator": "^2.0.5", "stacktrace-gps": "^3.0.4" } }, "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg=="], - - "stacktrace-parser": ["stacktrace-parser@0.1.11", "", { "dependencies": { "type-fest": "^0.7.1" } }, "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg=="], - - "standard-navigation": ["standard-navigation@0.0.8", "", { "peerDependencies": { "react": "*" } }, "sha512-TyVbo7INUDWtsUWDFn8RR7kwR87U0S4xHfLfbbnyeC581TmmyqQ+eM+nPw8rQTSD8QitRVcYfPaSHr/QJiUy1g=="], - - "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], - - "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], - - "stream-browserify": ["stream-browserify@3.0.0", "", { "dependencies": { "inherits": "~2.0.4", "readable-stream": "^3.5.0" } }, "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA=="], - - "stream-buffers": ["stream-buffers@2.2.0", "", {}, "sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg=="], - - "stream-http": ["stream-http@3.2.0", "", { "dependencies": { "builtin-status-codes": "^3.0.0", "inherits": "^2.0.4", "readable-stream": "^3.6.0", "xtend": "^4.0.2" } }, "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A=="], - - "strict-uri-encode": ["strict-uri-encode@2.0.0", "", {}, "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="], - - "string-length": ["string-length@5.0.1", "", { "dependencies": { "char-regex": "^2.0.0", "strip-ansi": "^7.0.1" } }, "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow=="], - "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "string.prototype.trim": ["string.prototype.trim@1.2.11", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.2", "es-object-atoms": "^1.1.2", "has-property-descriptors": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w=="], - - "string.prototype.trimend": ["string.prototype.trimend@1.0.10", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-object-atoms": "^1.1.2" } }, "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw=="], - - "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="], - - "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], - "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-bom": ["strip-bom@4.0.0", "", {}, "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w=="], - - "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], - - "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], - - "structured-headers": ["structured-headers@0.4.1", "", {}, "sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg=="], - "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], - "stylehacks": ["stylehacks@7.0.11", "", { "dependencies": { "browserslist": "^4.28.2", "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-iODNfhXVLqc5LADs+Y6Oh5wJuK5ZcHbVng8aiK3y9pjMQdc5hLrBW0eFU6FtnpNrE6PoEg/MmFTU4waotj5WNg=="], - - "styleq": ["styleq@0.1.3", "", {}, "sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA=="], - - "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], - "superstruct": ["superstruct@2.0.2", "", {}, "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A=="], "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], - "supports-hyperlinks": ["supports-hyperlinks@2.3.0", "", { "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" } }, "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA=="], - - "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], - "sv-router": ["sv-router@0.18.1", "", { "dependencies": { "esm-env": "^1.2.2" }, "peerDependencies": { "svelte": "^5" }, "bin": { "sv-router": "./src/cli/index.js" } }, "sha512-xMJxgDwmcWtLDopU6xdEiBICRN40ucTlK63GUZf6cQk8689LUaUD5FlMrdtTdPFRQ2kfJBeJgoaI1k+XOuIQSQ=="], "svelte": ["svelte@5.55.5", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.6.4", "esm-env": "^1.2.1", "esrap": "^2.2.4", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-2uCs/LZ9us+AktdzYJM8OcxQ8qnPS1kpaO7syGT/MgO+6Qr1Ybl+TqPq+97u7PHqmmMlye5ZkoyXONy5mjjAbw=="], @@ -2814,10 +861,6 @@ "sveltekit-superforms": ["sveltekit-superforms@2.30.1", "", { "dependencies": { "devalue": "^5.6.4", "memoize-weak": "^1.0.2", "ts-deepmerge": "^7.0.3" }, "optionalDependencies": { "@exodus/schemasafe": "^1.3.0", "@standard-schema/spec": "^1.1.0", "@typeschema/class-validator": "^0.3.0", "@valibot/to-json-schema": "^1.6.0", "@vinejs/vine": "^3.0.1", "arktype": "^2.2.0", "class-validator": "^0.14.4", "effect": "^3.21.0", "joi": "^17.13.3", "json-schema-to-ts": "^3.1.1", "superstruct": "^2.0.2", "typebox": "^1.1.6", "valibot": "^1.3.1", "yup": "^1.7.1", "zod": "^4.3.6", "zod-v3-to-json-schema": "^4.0.0" }, "peerDependencies": { "@sveltejs/kit": "1.x || 2.x", "svelte": "3.x || 4.x || >=5.0.0-next.51" } }, "sha512-wBzyqsE0idvEJWuNJ+HCiAtdxa7Z55GZ8jmtlVHJfonrk9bRYC49MoPaloYyFoYuU3QPy6Omna/Qzn1kaIkgew=="], - "svgo": ["svgo@4.0.2", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": "./bin/svgo.js" }, "sha512-ekx94z1rRc5LDi6oSUaeRnYhd0UOJxdtQCL2rF8xpWxD3TPAsISWOrxezqGovqS38GRZOdpDfvQe3ts6F7nsng=="], - - "symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="], - "tabbable": ["tabbable@6.4.0", "", {}, "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg=="], "tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="], @@ -2828,30 +871,10 @@ "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], - "tar": ["tar@7.5.22", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA=="], - - "temp": ["temp@0.8.4", "", { "dependencies": { "rimraf": "~2.6.2" } }, "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg=="], - - "temp-dir": ["temp-dir@2.0.0", "", {}, "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg=="], - - "terminal-link": ["terminal-link@2.1.1", "", { "dependencies": { "ansi-escapes": "^4.2.1", "supports-hyperlinks": "^2.0.0" } }, "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ=="], - - "terser": ["terser@5.49.2", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-rGbJiKeQ4WDe3EXlDAIaQcwftVfv2Q8o1awFNfvXolJYKkb1AuZY1RTOmqx4LJXZENbWZA7eIsYGHuEzHsi1nQ=="], - - "test-exclude": ["test-exclude@6.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w=="], - "text-encoding": ["text-encoding@0.7.0", "", {}, "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA=="], - "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], - - "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], - "three": ["three@0.165.0", "", {}, "sha512-cc96IlVYGydeceu0e5xq70H8/yoVT/tXBxV/W8A/U6uOq7DXc4/s1Mkmnu6SqoYGhSRWWYFOhVwvq6V0VtbplA=="], - "throat": ["throat@5.0.0", "", {}, "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA=="], - - "timers-browserify": ["timers-browserify@2.0.12", "", { "dependencies": { "setimmediate": "^1.0.4" } }, "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ=="], - "tiny-case": ["tiny-case@1.0.3", "", {}, "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q=="], "tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="], @@ -2860,210 +883,70 @@ "tls": ["tls@0.0.1", "", {}, "sha512-GzHpG+hwupY8VMR6rYsnAhTHqT/97zT45PG8WD5eTT1lq+dFE0nN+1PYpsoBcHJgSmTz5ceK2Cv88IkPmIPOtQ=="], - "tmpl": ["tmpl@1.0.5", "", {}, "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw=="], - - "to-buffer": ["to-buffer@1.2.2", "", { "dependencies": { "isarray": "^2.0.5", "safe-buffer": "^5.2.1", "typed-array-buffer": "^1.0.3" } }, "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw=="], - - "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], - - "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], - "toposort": ["toposort@2.0.2", "", {}, "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="], "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], - "tough-cookie": ["tough-cookie@4.1.4", "", { "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", "universalify": "^0.2.0", "url-parse": "^1.5.3" } }, "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag=="], - - "tr46": ["tr46@3.0.0", "", { "dependencies": { "punycode": "^2.1.1" } }, "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA=="], - "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], "ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="], "ts-deepmerge": ["ts-deepmerge@7.0.3", "", {}, "sha512-Du/ZW2RfwV/D4cmA5rXafYjBQVuvu4qGiEEla4EmEHVHgRdx68Gftx7i66jn2bzHPwSVZY36Ae6OuDn9el4ZKA=="], - "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], - "tsdown": ["tsdown@0.15.12", "", { "dependencies": { "ansis": "^4.2.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "debug": "^4.4.3", "diff": "^8.0.2", "empathic": "^2.0.0", "hookable": "^5.5.3", "rolldown": "1.0.0-beta.45", "rolldown-plugin-dts": "^0.17.2", "semver": "^7.7.3", "tinyexec": "^1.0.1", "tinyglobby": "^0.2.15", "tree-kill": "^1.2.2", "unconfig": "^7.3.3" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "publint": "^0.3.0", "typescript": "^5.0.0", "unplugin-lightningcss": "^0.4.0", "unplugin-unused": "^0.5.0", "unrun": "^0.2.1" }, "optionalPeers": ["@arethetypeswrong/core", "publint", "typescript", "unplugin-lightningcss", "unplugin-unused", "unrun"], "bin": { "tsdown": "dist/run.mjs" } }, "sha512-c8VLlQm8/lFrOAg5VMVeN4NAbejZyVQkzd+ErjuaQgJFI/9MhR9ivr0H/CM7UlOF1+ELlF6YaI7sU/4itgGQ8w=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], - "tty-browserify": ["tty-browserify@0.0.1", "", {}, "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw=="], - "turbo": ["turbo@2.8.19", "", { "optionalDependencies": { "@turbo/darwin-64": "2.8.19", "@turbo/darwin-arm64": "2.8.19", "@turbo/linux-64": "2.8.19", "@turbo/linux-arm64": "2.8.19", "@turbo/windows-64": "2.8.19", "@turbo/windows-arm64": "2.8.19" }, "bin": { "turbo": "bin/turbo" } }, "sha512-9NTKvQZ/02XnJCSU4mF5gvAPK+nuLbvCtft/NE8dJeOwaup0bJ14rYx7TxmSXp8JJuAN8nAjqfXWFsWwoQSFHg=="], "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], - "type-detect": ["type-detect@4.0.8", "", {}, "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="], - "type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="], - "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], - "typebox": ["typebox@1.1.38", "", {}, "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA=="], - "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], - - "typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="], - - "typed-array-byte-offset": ["typed-array-byte-offset@1.0.4", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.15", "reflect.getprototypeof": "^1.0.9" } }, "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ=="], - - "typed-array-length": ["typed-array-length@1.0.8", "", { "dependencies": { "call-bind": "^1.0.9", "for-each": "^0.3.5", "gopd": "^1.2.0", "is-typed-array": "^1.1.15", "possible-typed-array-names": "^1.1.0", "reflect.getprototypeof": "^1.0.10" } }, "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g=="], - "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], - "ua-parser-js": ["ua-parser-js@1.0.41", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug=="], - - "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], - "unconfig": ["unconfig@7.5.0", "", { "dependencies": { "@quansync/fs": "^1.0.0", "defu": "^6.1.4", "jiti": "^2.6.1", "quansync": "^1.0.0", "unconfig-core": "7.5.0" } }, "sha512-oi8Qy2JV4D3UQ0PsopR28CzdQ3S/5A1zwsUwp/rosSbfhJ5z7b90bIyTwi/F7hCLD4SGcZVjDzd4XoUQcEanvA=="], "unconfig-core": ["unconfig-core@7.5.0", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w=="], - "undici": ["undici@6.28.0", "", {}, "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA=="], - "undici-types": ["undici-types@7.21.0", "", {}, "sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ=="], - "unicode-canonical-property-names-ecmascript": ["unicode-canonical-property-names-ecmascript@2.0.1", "", {}, "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg=="], - - "unicode-match-property-ecmascript": ["unicode-match-property-ecmascript@2.0.0", "", { "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" } }, "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q=="], - - "unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], - - "unicode-property-aliases-ecmascript": ["unicode-property-aliases-ecmascript@2.2.0", "", {}, "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ=="], - - "unique-string": ["unique-string@2.0.0", "", { "dependencies": { "crypto-random-string": "^2.0.0" } }, "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg=="], - - "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], - - "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], - - "update-browserslist-db": ["update-browserslist-db@1.3.0", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-x/M6q3w4Ybp91CNaS4S69UnliqR3BzRpOT6LWbksjth0S/+jhfaPJsWjt/TewpT8j9eLIojUf5jr29WextHroA=="], - "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - "url": ["url@0.11.4", "", { "dependencies": { "punycode": "^1.4.1", "qs": "^6.12.3" } }, "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg=="], - - "url-parse": ["url-parse@1.5.10", "", { "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ=="], - - "use-latest-callback": ["use-latest-callback@0.2.6", "", { "peerDependencies": { "react": ">=16.8" } }, "sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg=="], - - "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], - - "util": ["util@0.12.5", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="], - - "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], - - "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], - "uuid": ["uuid@3.3.2", "", { "bin": { "uuid": "./bin/uuid" } }, "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="], - "v8-to-istanbul": ["v8-to-istanbul@9.3.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^2.0.0" } }, "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA=="], - "valibot": ["valibot@1.4.0", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-iC/x7fVcSyOwlm/VSt7RlHnzNGLGvR9GnxdifUeWoCJo0q4ZZvrVkIHC6faTlkxG47I2Y4UrFquPuVHCrOnrLg=="], - "validate-npm-package-name": ["validate-npm-package-name@5.0.1", "", {}, "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ=="], - "validator": ["validator@13.15.35", "", {}, "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw=="], - "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], - "vaul-svelte": ["vaul-svelte@1.0.0-next.7", "", { "dependencies": { "runed": "^0.23.2", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-7zN7Bi3dFQixvvbUJY9uGDe7Ws/dGZeBQR2pXdXmzQiakjrxBvWo0QrmsX3HK+VH+SZOltz378cmgmCS9f9rSg=="], "vite": ["vite@8.0.11", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.14", "rolldown": "1.0.0-rc.18", "tinyglobby": "^0.2.16" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Jz1mxtUBR5xTT65VOdJZUUeoyLtqljmFkiUXhPTLZka3RDc9vpi/xXkyrnsdRcm2lIi3l3GPMnAidTsEGIj3Ow=="], "vitefu": ["vitefu@1.1.2", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw=="], - "vlq": ["vlq@1.0.1", "", {}, "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w=="], - - "vm-browserify": ["vm-browserify@1.1.2", "", {}, "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="], - - "vue": ["vue@3.5.41", "", { "dependencies": { "@vue/compiler-dom": "3.5.41", "@vue/compiler-sfc": "3.5.41", "@vue/runtime-dom": "3.5.41", "@vue/server-renderer": "3.5.41", "@vue/shared": "3.5.41" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg=="], - - "vue-lynx": ["vue-lynx@0.5.1", "", { "dependencies": { "@lynx-js/css-extract-webpack-plugin": "^0.7.0", "@lynx-js/react": "^0.116.5", "@lynx-js/runtime-wrapper-webpack-plugin": "^0.1.3", "@lynx-js/template-webpack-plugin": "^0.10.5", "@lynx-js/webpack-dev-transport": "^0.2.0", "@vue/compiler-core": "^3.5.0", "@vue/runtime-core": "^3.5.0" }, "peerDependencies": { "@rsbuild/core": "^1.0.0", "@rsbuild/plugin-vue": "^1.2.6" } }, "sha512-J8qHZHNxmoiEx3Ou+9b3XKFgDsGhRtfoDPrdqQhNcXPHEohUXod92M447gGaxy184pEojzJF8TXiUupWaCB/kA=="], - - "w3c-xmlserializer": ["w3c-xmlserializer@4.0.0", "", { "dependencies": { "xml-name-validator": "^4.0.0" } }, "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw=="], - - "walker": ["walker@1.0.8", "", { "dependencies": { "makeerror": "1.0.12" } }, "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ=="], - - "warn-once": ["warn-once@0.1.1", "", {}, "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q=="], - - "wasm-feature-detect": ["wasm-feature-detect@1.8.0", "", {}, "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ=="], - - "watchpack": ["watchpack@2.5.2", "", { "dependencies": { "graceful-fs": "^4.1.2" } }, "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg=="], - - "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="], - "web-worker": ["web-worker@1.3.0", "", {}, "sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA=="], - "webidl-conversions": ["webidl-conversions@5.0.0", "", {}, "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA=="], - - "webpack": ["webpack@5.109.2", "", { "dependencies": { "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.16.0", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.24.4", "es-module-lexer": "^2.1.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "graceful-fs": "^4.2.11", "mime-db": "^1.54.0", "minimizer-webpack-plugin": "^5.6.1", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", "watchpack": "^2.5.2", "webpack-sources": "^3.5.1" }, "peerDependencies": { "webpack-cli": "*" }, "optionalPeers": ["webpack-cli"], "bin": { "webpack": "bin/webpack.js" } }, "sha512-U9/cvLzxObKNEZ9+TtdqrHM5/9z3lgl2c+c4BzbqGxFQvQvBAq87yql5A8pQ+rrMbS496MZJeF5enVBndIy2hw=="], - - "webpack-sources": ["webpack-sources@3.5.1", "", {}, "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw=="], - - "whatwg-encoding": ["whatwg-encoding@2.0.0", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg=="], - - "whatwg-fetch": ["whatwg-fetch@3.6.20", "", {}, "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg=="], - - "whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], - - "whatwg-url": ["whatwg-url@11.0.0", "", { "dependencies": { "tr46": "^3.0.0", "webidl-conversions": "^7.0.0" } }, "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ=="], - - "whatwg-url-without-unicode": ["whatwg-url-without-unicode@8.0.0-3", "", { "dependencies": { "buffer": "^5.4.3", "punycode": "^2.1.1", "webidl-conversions": "^5.0.0" } }, "sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig=="], - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="], - - "which-builtin-type": ["which-builtin-type@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.1.0", "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", "which-typed-array": "^1.1.16" } }, "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q=="], - - "which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="], - "which-module": ["which-module@2.0.1", "", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="], - "which-typed-array": ["which-typed-array@1.1.22", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw=="], - - "wonka": ["wonka@6.3.6", "", {}, "sha512-MXH+6mDHAZ2GuMpgKS055FR6v0xVP3XwquxIMYXgiW+FejHQlMGlvVRZT4qMCxR+bEo/FCtIdKxwej9WV3YQag=="], - "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], - - "write-file-atomic": ["write-file-atomic@4.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" } }, "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg=="], - - "ws": ["ws@6.2.6", "", { "dependencies": { "async-limiter": "~1.0.0" } }, "sha512-XTrf1gv7kXoVf1hbC3PAyAiPgR8Wz1blcrYIjEsUmr08BLksT41R8KbjmS9408C2ERx7v1JDLD/BkpLEttjfKA=="], - - "xcode": ["xcode@3.0.1", "", { "dependencies": { "simple-plist": "^1.1.0", "uuid": "^7.0.3" } }, "sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA=="], - - "xml-name-validator": ["xml-name-validator@4.0.0", "", {}, "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw=="], - - "xml2js": ["xml2js@0.6.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w=="], - - "xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], - - "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="], + "ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="], "xrpl": ["xrpl@5.0.0", "", { "dependencies": { "@scure/bip32": "^2.0.1", "@scure/bip39": "^2.0.1", "@xrplf/isomorphic": "^1.0.2", "@xrplf/secret-numbers": "^3.0.0", "bignumber.js": "^10.0.2", "eventemitter3": "^5.0.1", "fast-json-stable-stringify": "^2.1.0", "ripple-address-codec": "^5.0.1", "ripple-binary-codec": "^2.8.0", "ripple-keypairs": "^3.0.0" } }, "sha512-YqaTFJUnhOu0mI4bsuHbKGj6w9ATcH8EIgw+gOLnh1rrlLTo5oImLQzhKJixCAPqqWOKnsY7J3jsN+l+zeEWgA=="], - "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], - "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], - "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - - "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], - "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], - "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - "yup": ["yup@1.7.1", "", { "dependencies": { "property-expr": "^2.0.5", "tiny-case": "^1.0.3", "toposort": "^2.0.2", "type-fest": "^2.19.0" } }, "sha512-GKHFX2nXul2/4Dtfxhozv701jLQHdf6J34YDh2cEkpqoo8le5Mg6/LrdseVLrFarmFygZTlfIhHx/QKfb/QWXw=="], "zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="], @@ -3072,154 +955,6 @@ "zod-v3-to-json-schema": ["zod-v3-to-json-schema@4.0.0", "", { "peerDependencies": { "zod": "^3.25 || ^4.0.14" } }, "sha512-KixLrhX/uPmRFnDgsZrzrk4x5SSJA+PmaE5adbfID9+3KPJcdxqRobaHU397EfWBqfQircrjKqvEqZ/mW5QH6w=="], - "zustand": ["zustand@5.0.14", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g=="], - - "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], - - "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "@babel/helper-create-regexp-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "@babel/highlight/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="], - - "@babel/plugin-transform-runtime/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "@babel/preset-env/babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.14.2", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8", "core-js-compat": "^3.48.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g=="], - - "@babel/preset-env/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "@babel/register/make-dir": ["make-dir@2.1.0", "", { "dependencies": { "pify": "^4.0.1", "semver": "^5.6.0" } }, "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA=="], - - "@expo/cli/@expo/env": ["@expo/env@1.0.7", "", { "dependencies": { "chalk": "^4.0.0", "debug": "^4.3.4", "dotenv": "~16.4.5", "dotenv-expand": "~11.0.6", "getenv": "^2.0.0" } }, "sha512-qSTEnwvuYJ3umapO9XJtrb1fAqiPlmUUg78N0IZXXGwQRt+bkp0OBls+Y5Mxw/Owj8waAM0Z3huKKskRADR5ow=="], - - "@expo/cli/@expo/prebuild-config": ["@expo/prebuild-config@9.0.12", "", { "dependencies": { "@expo/config": "~11.0.13", "@expo/config-plugins": "~10.1.2", "@expo/config-types": "^53.0.5", "@expo/image-utils": "^0.7.6", "@expo/json-file": "^9.1.5", "@react-native/normalize-colors": "0.79.6", "debug": "^4.3.1", "resolve-from": "^5.0.0", "semver": "^7.6.0", "xml2js": "0.6.0" } }, "sha512-AKH5Scf+gEMgGxZZaimrJI2wlUJlRoqzDNn7/rkhZa5gUTnO4l6slKak2YdaH+nXlOWCNfAQWa76NnpQIfmv6Q=="], - - "@expo/cli/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - - "@expo/cli/picomatch": ["picomatch@3.0.2", "", {}, "sha512-cfDHL6LStTEKlNilboNtobT/kEa30PtAf2Q1OgszfrG/rpVl1xaFWT9ktfkS306GmHgmnad1Sw4wabhlvFtsTw=="], - - "@expo/cli/ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="], - - "@expo/config/@babel/code-frame": ["@babel/code-frame@7.10.4", "", { "dependencies": { "@babel/highlight": "^7.10.4" } }, "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg=="], - - "@expo/config/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - - "@expo/config/sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="], - - "@expo/config-plugins/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - - "@expo/devcert/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], - - "@expo/env/getenv": ["getenv@1.0.0", "", {}, "sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg=="], - - "@expo/fingerprint/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - - "@expo/json-file/@babel/code-frame": ["@babel/code-frame@7.10.4", "", { "dependencies": { "@babel/highlight": "^7.10.4" } }, "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg=="], - - "@expo/metro-config/@expo/env": ["@expo/env@1.0.7", "", { "dependencies": { "chalk": "^4.0.0", "debug": "^4.3.4", "dotenv": "~16.4.5", "dotenv-expand": "~11.0.6", "getenv": "^2.0.0" } }, "sha512-qSTEnwvuYJ3umapO9XJtrb1fAqiPlmUUg78N0IZXXGwQRt+bkp0OBls+Y5Mxw/Owj8waAM0Z3huKKskRADR5ow=="], - - "@expo/metro-config/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - - "@expo/metro-config/lightningcss": ["lightningcss@1.27.0", "", { "dependencies": { "detect-libc": "^1.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.27.0", "lightningcss-darwin-x64": "1.27.0", "lightningcss-freebsd-x64": "1.27.0", "lightningcss-linux-arm-gnueabihf": "1.27.0", "lightningcss-linux-arm64-gnu": "1.27.0", "lightningcss-linux-arm64-musl": "1.27.0", "lightningcss-linux-x64-gnu": "1.27.0", "lightningcss-linux-x64-musl": "1.27.0", "lightningcss-win32-arm64-msvc": "1.27.0", "lightningcss-win32-x64-msvc": "1.27.0" } }, "sha512-8f7aNmS1+etYSLHht0fQApPc2kNO8qGRutifN5rVIc6Xo6ABsEbqOr758UwI7ALVbTt4x1fllKt0PYgzD9S3yQ=="], - - "@expo/metro-config/postcss": ["postcss@8.4.49", "", { "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA=="], - - "@expo/package-manager/@expo/json-file": ["@expo/json-file@11.0.1", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "json5": "^2.2.3" } }, "sha512-zxHWj4MKKMAL29ZQSY/Fssx4Thluk40JmuGNaeS078wy/NhlFhnVi+rHHunulE3xJAJ0CM73m8VK2+GkF9eRwQ=="], - - "@expo/prebuild-config/@expo/config": ["@expo/config@10.0.11", "", { "dependencies": { "@babel/code-frame": "~7.10.4", "@expo/config-plugins": "~9.0.17", "@expo/config-types": "^52.0.5", "@expo/json-file": "^9.0.2", "deepmerge": "^4.3.1", "getenv": "^1.0.0", "glob": "^10.4.2", "require-from-string": "^2.0.2", "resolve-from": "^5.0.0", "resolve-workspace-root": "^2.0.0", "semver": "^7.6.0", "slugify": "^1.3.4", "sucrase": "3.35.0" } }, "sha512-nociJ4zr/NmbVfMNe9j/+zRlt7wz/siISu7PjdWE4WE+elEGxWWxsGzltdJG0llzrM+khx8qUiFK5aiVcdMBww=="], - - "@expo/prebuild-config/@expo/config-plugins": ["@expo/config-plugins@9.0.17", "", { "dependencies": { "@expo/config-types": "^52.0.5", "@expo/json-file": "~9.0.2", "@expo/plist": "^0.2.2", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", "debug": "^4.3.5", "getenv": "^1.0.0", "glob": "^10.4.2", "resolve-from": "^5.0.0", "semver": "^7.5.4", "slash": "^3.0.0", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, "sha512-m24F1COquwOm7PBl5wRbkT9P9DviCXe0D7S7nQsolfbhdCWuvMkfXeoWmgjtdhy7sDlOyIgBrAdnB6MfsWKqIg=="], - - "@expo/prebuild-config/@expo/config-types": ["@expo/config-types@52.0.5", "", {}, "sha512-AMDeuDLHXXqd8W+0zSjIt7f37vUd/BP8p43k68NHpyAvQO+z8mbQZm3cNQVAMySeayK2XoPigAFB1JF2NFajaA=="], - - "@expo/prebuild-config/@expo/image-utils": ["@expo/image-utils@0.6.5", "", { "dependencies": { "@expo/spawn-async": "^1.7.2", "chalk": "^4.0.0", "fs-extra": "9.0.0", "getenv": "^1.0.0", "jimp-compact": "0.16.1", "parse-png": "^2.1.0", "resolve-from": "^5.0.0", "semver": "^7.6.0", "temp-dir": "~2.0.0", "unique-string": "~2.0.0" } }, "sha512-RsS/1CwJYzccvlprYktD42KjyfWZECH6PPIEowvoSmXfGLfdViwcUEI4RvBfKX5Jli6P67H+6YmHvPTbGOboew=="], - - "@expo/prebuild-config/@react-native/normalize-colors": ["@react-native/normalize-colors@0.76.9", "", {}, "sha512-TUdMG2JGk72M9d8DYbubdOlrzTYjw+YMe/xOnLU4viDgWRHsCbtRS9x0IAxRjs3amj/7zmK3Atm8jUPvdAc8qw=="], - - "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - - "@isaacs/cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], - - "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - - "@istanbuljs/load-nyc-config/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], - - "@istanbuljs/load-nyc-config/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], - - "@istanbuljs/load-nyc-config/js-yaml": ["js-yaml@3.15.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag=="], - - "@jest/reporters/string-length": ["string-length@4.0.2", "", { "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" } }, "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ=="], - - "@lynx-js/css-serializer/css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], - - "@lynx-js/react/@types/react": ["@types/react@18.3.31", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw=="], - - "@lynx-js/tailwind-preset/tailwindcss": ["tailwindcss@3.4.19", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ=="], - - "@lynx-js/template-webpack-plugin/@rspack/lite-tapable": ["@rspack/lite-tapable@1.1.0", "", {}, "sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw=="], - - "@lynx-js/template-webpack-plugin/css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], - - "@react-native/babel-plugin-codegen/@react-native/codegen": ["@react-native/codegen@0.76.9", "", { "dependencies": { "@babel/parser": "^7.25.3", "glob": "^7.1.1", "hermes-parser": "0.23.1", "invariant": "^2.2.4", "jscodeshift": "^0.14.0", "mkdirp": "^0.5.1", "nullthrows": "^1.1.1", "yargs": "^17.6.2" }, "peerDependencies": { "@babel/preset-env": "^7.1.6" } }, "sha512-AzlCHMTKrAVC2709V4ZGtBXmGVtWTpWm3Ruv5vXcd3/anH4mGucfJ4rjbWKdaYQJMpXa3ytGomQrsIsT/s8kgA=="], - - "@react-native/community-cli-plugin/@react-native/dev-middleware": ["@react-native/dev-middleware@0.79.0", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.79.0", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", "debug": "^2.2.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", "ws": "^6.2.3" } }, "sha512-8Mh5L8zJXis2qhgkfXnWMbSmcvb07wrbxQe8KIgIO7C1rS97idg7BBtoPEtmARsaQgmbSGu/wdE7UWFkGYp0OQ=="], - - "@react-native/community-cli-plugin/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], - - "@react-native/dev-middleware/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], - - "@react-native/metro-babel-transformer/@react-native/babel-preset": ["@react-native/babel-preset@0.86.2", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@react-native/babel-plugin-codegen": "0.86.2", "babel-plugin-syntax-hermes-parser": "0.36.0", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-4XKEJ6jKW9lXMB1O5o47gBoGgolde1fbX13gLW/erlcn+1ky+MHHo5UjuM3RWGdPJHIvIzekDDumSUHhB9x5iQ=="], - - "@react-native/metro-babel-transformer/hermes-parser": ["hermes-parser@0.36.0", "", { "dependencies": { "hermes-estree": "0.36.0" } }, "sha512-GdpwMmH5x6IpC1cijvcvYnlPB60Mh6kTSF/NFdYV/j56gYdi+0RIakYs+eqOV+bbO0SW7mgVVGSsTJxyPQfo3w=="], - - "@react-native/metro-config/@react-native/js-polyfills": ["@react-native/js-polyfills@0.86.2", "", {}, "sha512-bIwNcGBaQ74shB5z1mRkxOpjikimuwsnOCEkZSzL67Z1FTyK1ObpENfyd2QvcvVW9Cjl+tHuw9ynpBnb2jPoJQ=="], - - "@react-native/metro-config/metro-config": ["metro-config@0.84.4", "", { "dependencies": { "connect": "^3.6.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", "metro": "0.84.4", "metro-cache": "0.84.4", "metro-core": "0.84.4", "metro-runtime": "0.84.4", "yaml": "^2.6.1" } }, "sha512-PMotGDjXcXLWo2TMRH+VR99phFNgYTwqh4OoieIKK3yTJa1Jmkl+fZJxDO0jfBvNF+WESHciHvpNuBtXaF3B0Q=="], - - "@react-native/metro-config/metro-runtime": ["metro-runtime@0.84.4", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-Jibypds4g7AhzdRKY+kDoj51s5EXMwgyp5ddtlreDAsWefMdOx+agWqgm0H2XSZ/ueanHHVM89fnf5OJnlxa8Q=="], - - "@rsbuild/plugin-check-syntax/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], - - "@rsdoctor/core/enhanced-resolve": ["enhanced-resolve@5.12.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ=="], - - "@rsdoctor/core/fs-extra": ["fs-extra@11.4.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA=="], - - "@rsdoctor/core/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], - - "@rsdoctor/graph/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], - - "@rsdoctor/sdk/dayjs": ["dayjs@1.11.13", "", {}, "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="], - - "@rsdoctor/sdk/fs-extra": ["fs-extra@11.4.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA=="], - - "@rsdoctor/sdk/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], - - "@rsdoctor/sdk/sirv": ["sirv@2.0.4", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ=="], - - "@rsdoctor/sdk/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], - - "@rsdoctor/sdk/tapable": ["tapable@2.2.2", "", {}, "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg=="], - - "@rsdoctor/types/@types/estree": ["@types/estree@1.0.5", "", {}, "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="], - - "@rsdoctor/types/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], - - "@rsdoctor/utils/@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="], - - "@rsdoctor/utils/@types/estree": ["@types/estree@1.0.5", "", {}, "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="], - - "@rsdoctor/utils/fs-extra": ["fs-extra@11.4.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA=="], - - "@rsdoctor/utils/lines-and-columns": ["lines-and-columns@2.0.4", "", {}, "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A=="], - - "@rspack/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" } }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="], - - "@rspack/core/@rspack/lite-tapable": ["@rspack/lite-tapable@1.1.0", "", {}, "sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="], @@ -3234,658 +969,72 @@ "@types/bun/bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], - "@types/react-test-renderer/@types/react": ["@types/react@18.3.31", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw=="], - - "@vue/compiler-sfc/postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="], - - "@xrplf/isomorphic/ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="], - - "acorn-globals/acorn-walk": ["acorn-walk@8.3.5", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw=="], - - "ajv-formats/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], - - "ajv-keywords/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], - - "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], - - "anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], - - "asn1.js/bn.js": ["bn.js@4.12.5", "", {}, "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ=="], - - "ast-kit/@babel/parser": ["@babel/parser@7.29.3", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="], - "axios/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], "axios/proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], - "babel-plugin-istanbul/istanbul-lib-instrument": ["istanbul-lib-instrument@5.2.1", "", { "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" } }, "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg=="], - - "babel-plugin-polyfill-corejs2/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "better-opn/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], - - "body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], - - "body-parser/http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="], - - "body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], - - "browserify-sign/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], - "bun-types/@types/node": ["@types/node@22.19.15", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg=="], - "caller-callsite/callsites": ["callsites@2.0.0", "", {}, "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ=="], - "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - "chromium-edge-launcher/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], - - "compression/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], - - "compression/negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], - - "connect/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], - - "cosmiconfig/js-yaml": ["js-yaml@3.15.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag=="], - - "cosmiconfig/parse-json": ["parse-json@4.0.0", "", { "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" } }, "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw=="], - - "create-ecdh/bn.js": ["bn.js@4.12.5", "", {}, "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ=="], - - "css-minimizer-webpack-plugin/postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="], - - "css-minimizer-webpack-plugin/serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="], - - "css-tree/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - - "csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="], - - "cssstyle/cssom": ["cssom@0.3.8", "", {}, "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="], - "d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], "d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="], "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="], - "diffie-hellman/bn.js": ["bn.js@4.12.5", "", {}, "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ=="], - - "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], - - "domexception/webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="], - - "elliptic/bn.js": ["bn.js@4.12.5", "", {}, "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ=="], - - "engine.io/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], - - "engine.io/ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="], - - "es-abstract/es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], - - "escodegen/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - - "eslint-scope/estraverse": ["estraverse@4.3.0", "", {}, "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="], - - "expo/babel-preset-expo": ["babel-preset-expo@13.2.5", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/plugin-proposal-decorators": "^7.12.9", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-transform-export-namespace-from": "^7.25.9", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.23.0", "@react-native/babel-preset": "0.79.6", "babel-plugin-react-native-web": "~0.19.13", "babel-plugin-syntax-hermes-parser": "^0.25.1", "babel-plugin-transform-flow-enums": "^0.0.2", "debug": "^4.3.4", "react-refresh": "^0.14.2", "resolve-from": "^5.0.0" }, "peerDependencies": { "babel-plugin-react-compiler": "^19.0.0-beta-e993439-20250405" }, "optionalPeers": ["babel-plugin-react-compiler"] }, "sha512-YjVkP1bOLO2OgR2fyCedruYMPR7GFbAtCvvWITBW1UAp6e3ACYZtN6uoqkXgXP6PHQkb6M7qf2vZreBPEZK38A=="], - - "expo/expo-constants": ["expo-constants@17.1.8", "", { "dependencies": { "@expo/config": "~11.0.13", "@expo/env": "~1.0.7" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-sOCeMN/BWLA7hBP6lMwoEQzFNgTopk6YY03sBAmwT216IHyL54TjNseg8CRU1IQQ/+qinJ2fYWCl7blx2TiNcA=="], - - "expo/expo-font": ["expo-font@13.3.2", "", { "dependencies": { "fontfaceobserver": "^2.1.0" }, "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-wUlMdpqURmQ/CNKK/+BIHkDA5nGjMqNlYmW0pJFXY/KE/OG80Qcavdu2sHsL4efAIiNGvYdBS10WztuQYU4X0A=="], - - "expo-asset/expo-constants": ["expo-constants@17.1.8", "", { "dependencies": { "@expo/config": "~11.0.13", "@expo/env": "~1.0.7" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-sOCeMN/BWLA7hBP6lMwoEQzFNgTopk6YY03sBAmwT216IHyL54TjNseg8CRU1IQQ/+qinJ2fYWCl7blx2TiNcA=="], - - "expo-barcode-generator/react-native-svg": ["react-native-svg@15.8.0", "", { "dependencies": { "css-select": "^5.1.0", "css-tree": "^1.1.3", "warn-once": "0.1.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-KHJzKpgOjwj1qeZzsBjxNdoIgv2zNCO9fVcoq2TEhTRsVV5DGTZ9JzUZwybd7q4giT/H3RdtqC3u44dWdO0Ffw=="], - - "expo-constants/@expo/config": ["@expo/config@10.0.11", "", { "dependencies": { "@babel/code-frame": "~7.10.4", "@expo/config-plugins": "~9.0.17", "@expo/config-types": "^52.0.5", "@expo/json-file": "^9.0.2", "deepmerge": "^4.3.1", "getenv": "^1.0.0", "glob": "^10.4.2", "require-from-string": "^2.0.2", "resolve-from": "^5.0.0", "resolve-workspace-root": "^2.0.0", "semver": "^7.6.0", "slugify": "^1.3.4", "sucrase": "3.35.0" } }, "sha512-nociJ4zr/NmbVfMNe9j/+zRlt7wz/siISu7PjdWE4WE+elEGxWWxsGzltdJG0llzrM+khx8qUiFK5aiVcdMBww=="], - - "expo-dev-launcher/ajv": ["ajv@8.11.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } }, "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg=="], - - "expo-modules-autolinking/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], - - "expo-modules-autolinking/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - - "expo-router/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], - - "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - - "fbjs/promise": ["promise@7.3.1", "", { "dependencies": { "asap": "~2.0.3" } }, "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg=="], - - "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], - - "finalhandler/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="], - - "finalhandler/on-finished": ["on-finished@2.3.0", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww=="], - - "finalhandler/statuses": ["statuses@1.5.0", "", {}, "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA=="], - - "find-cache-dir/make-dir": ["make-dir@2.1.0", "", { "dependencies": { "pify": "^4.0.1", "semver": "^5.6.0" } }, "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA=="], - - "find-cache-dir/pkg-dir": ["pkg-dir@3.0.0", "", { "dependencies": { "find-up": "^3.0.0" } }, "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw=="], - - "foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - "formsnap/svelte-toolbelt": ["svelte-toolbelt@0.5.0", "", { "dependencies": { "clsx": "^2.1.1", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0-next.126" } }, "sha512-t3tenZcnfQoIeRuQf/jBU7bvTeT3TGkcEE+1EUr5orp0lR7NEpprflpuie3x9Dn0W9nOKqs3HwKGJeeN5Ok1sQ=="], - "function.prototype.name/hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], - - "get-intrinsic/es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], - - "get-proto/es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], - - "glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - - "hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], - - "hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - - "htmlparser2/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], - - "import-fresh/resolve-from": ["resolve-from@3.0.0", "", {}, "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw=="], - - "istanbul-lib-report/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - - "istanbul-lib-source-maps/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - - "jest-runner/source-map-support": ["source-map-support@0.5.13", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w=="], - - "jest-util/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], - - "jest-validate/camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="], - - "jest-watch-select-projects/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], - - "jest-watch-typeahead/ansi-escapes": ["ansi-escapes@6.2.1", "", {}, "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig=="], - - "jest-watch-typeahead/slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="], - - "jest-watch-typeahead/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], - - "jest-watcher/string-length": ["string-length@4.0.2", "", { "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" } }, "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ=="], - - "jscodeshift/write-file-atomic": ["write-file-atomic@2.4.3", "", { "dependencies": { "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", "signal-exit": "^3.0.2" } }, "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ=="], - - "jsdom/http-proxy-agent": ["http-proxy-agent@5.0.0", "", { "dependencies": { "@tootallnate/once": "2", "agent-base": "6", "debug": "4" } }, "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w=="], - - "jsdom/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], - - "jsdom/webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="], - - "jsdom/ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="], - "koins/typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], - "koins-mobile/tailwindcss": ["tailwindcss@3.4.19", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ=="], - - "koins-mobile/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], - "layerchart/runed": ["runed@0.37.1", "", { "dependencies": { "dequal": "^2.0.3", "esm-env": "^1.0.0", "lz-string": "^1.5.0" }, "peerDependencies": { "@sveltejs/kit": "^2.21.0", "svelte": "^5.7.0", "zod": "^4.1.0" }, "optionalPeers": ["@sveltejs/kit", "zod"] }, "sha512-MeFY73xBW8IueWBm012nNFIGy19WUGPLtknavyUPMpnyt350M47PhGSGrGoSLbidwn+Zlt/O0cp8/OZE3LASWA=="], - "lighthouse-logger/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], - - "log-symbols/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="], - - "metro/ci-info": ["ci-info@2.0.0", "", {}, "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="], - - "metro/hermes-parser": ["hermes-parser@0.29.1", "", { "dependencies": { "hermes-estree": "0.29.1" } }, "sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA=="], - - "metro/ws": ["ws@7.5.13", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA=="], - - "metro-babel-transformer/hermes-parser": ["hermes-parser@0.29.1", "", { "dependencies": { "hermes-estree": "0.29.1" } }, "sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA=="], - - "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], - - "miller-rabin/bn.js": ["bn.js@4.12.5", "", {}, "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ=="], - - "minimizer-webpack-plugin/jest-worker": ["jest-worker@27.5.1", "", { "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg=="], - - "mobile-lynx/tailwindcss": ["tailwindcss@3.4.19", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ=="], - "mode-watcher/runed": ["runed@0.25.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-7+ma4AG9FT2sWQEA0Egf6mb7PBT2vHyuHail1ie8ropfSjvZGtEAx8YTmUjv/APCsdRRxEVvArNjALk9zFSOrg=="], "mode-watcher/svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="], "monero-ts/@types/node": ["@types/node@20.19.41", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ=="], - "node-dir/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - - "node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], - - "ora/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="], - - "ora/strip-ansi": ["strip-ansi@5.2.0", "", { "dependencies": { "ansi-regex": "^4.1.0" } }, "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA=="], - "paneforge/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="], "paneforge/svelte-toolbelt": ["svelte-toolbelt@0.9.3", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.29.0", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.30.2" } }, "sha512-HCSWxCtVmv+c6g1ACb8LTwHVbDqLKJvHpo6J8TaqwUme2hj9ATJCpjCPNISR1OCq2Q4U1KT41if9ON0isINQZw=="], - "parse-png/pngjs": ["pngjs@3.4.0", "", {}, "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w=="], - - "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], - - "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - - "pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], - - "plist/@xmldom/xmldom": ["@xmldom/xmldom@0.9.10", "", {}, "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw=="], - "png-to-ico/@types/node": ["@types/node@17.0.45", "", {}, "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="], "png-to-ico/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="], - "postcss-calc/postcss-selector-parser": ["postcss-selector-parser@7.1.5", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw=="], - - "postcss-discard-comments/postcss-selector-parser": ["postcss-selector-parser@7.1.5", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw=="], - - "postcss-merge-rules/postcss-selector-parser": ["postcss-selector-parser@7.1.5", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw=="], - - "postcss-minify-selectors/postcss-selector-parser": ["postcss-selector-parser@7.1.5", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw=="], - - "postcss-unique-selectors/postcss-selector-parser": ["postcss-selector-parser@7.1.5", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw=="], - - "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], - - "pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], - - "prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], - - "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], - - "public-encrypt/bn.js": ["bn.js@4.12.5", "", {}, "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ=="], - "qrcode/yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="], - "raw-body/http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="], - - "raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], - - "rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], - - "react-devtools-core/ws": ["ws@7.5.13", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA=="], - - "react-native/@react-native/normalize-colors": ["@react-native/normalize-colors@0.79.0", "", {}, "sha512-RmM7Dgb69a4qwdguKR+8MhT0u1IAKa/s0uy8/7JP9b/fm8zjUV9HctMgRgIpZTOELsowEyQodyTnhHQf4HPX0A=="], - - "react-native/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], - - "react-native-css-interop/lightningcss": ["lightningcss@1.27.0", "", { "dependencies": { "detect-libc": "^1.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.27.0", "lightningcss-darwin-x64": "1.27.0", "lightningcss-freebsd-x64": "1.27.0", "lightningcss-linux-arm-gnueabihf": "1.27.0", "lightningcss-linux-arm64-gnu": "1.27.0", "lightningcss-linux-arm64-musl": "1.27.0", "lightningcss-linux-x64-gnu": "1.27.0", "lightningcss-linux-x64-musl": "1.27.0", "lightningcss-win32-arm64-msvc": "1.27.0", "lightningcss-win32-x64-msvc": "1.27.0" } }, "sha512-8f7aNmS1+etYSLHht0fQApPc2kNO8qGRutifN5rVIc6Xo6ABsEbqOr758UwI7ALVbTt4x1fllKt0PYgzD9S3yQ=="], - - "react-native-css-interop/tailwindcss": ["tailwindcss@3.4.19", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ=="], - - "react-native-reanimated/react-native-is-edge-to-edge": ["react-native-is-edge-to-edge@1.1.7", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-EH6i7E8epJGIcu7KpfXYXiV2JFIYITtq+rVS8uEb+92naMRBdxhTuS8Wn2Q7j9sqyO0B+Xbaaf9VdipIAmGW4w=="], - - "react-native-web/@react-native/normalize-colors": ["@react-native/normalize-colors@0.74.89", "", {}, "sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg=="], - - "react-native-web/memoize-one": ["memoize-one@6.0.0", "", {}, "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="], - - "readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], - - "recast/ast-types": ["ast-types@0.15.2", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg=="], - - "recast/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - - "requireg/resolve": ["resolve@1.7.1", "", { "dependencies": { "path-parse": "^1.0.5" } }, "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw=="], - - "restore-cursor/onetime": ["onetime@2.0.1", "", { "dependencies": { "mimic-fn": "^1.0.0" } }, "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ=="], - - "ripemd160/hash-base": ["hash-base@3.1.2", "", { "dependencies": { "inherits": "^2.0.4", "readable-stream": "^2.3.8", "safe-buffer": "^5.2.1", "to-buffer": "^1.2.1" } }, "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg=="], - - "rolldown-plugin-dts/@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], - - "rolldown-plugin-dts/@babel/parser": ["@babel/parser@7.29.3", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="], - - "rolldown-plugin-dts/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], - - "rsbuild-plugin-tailwindcss/tailwindcss": ["tailwindcss@3.4.19", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ=="], - - "safe-array-concat/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], - - "safe-push-apply/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], - - "schema-utils/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], - - "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], - - "simple-plist/bplist-parser": ["bplist-parser@0.3.1", "", { "dependencies": { "big-integer": "1.6.x" } }, "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA=="], - - "simple-swizzle/is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="], - - "socket.io/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], - - "socket.io-adapter/ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="], - - "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - - "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], - - "stacktrace-gps/source-map": ["source-map@0.5.6", "", {}, "sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA=="], - - "stacktrace-parser/type-fest": ["type-fest@0.7.1", "", {}, "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg=="], - - "stream-browserify/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], - - "stream-http/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], - - "string-length/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], - - "stylehacks/postcss-selector-parser": ["postcss-selector-parser@7.1.5", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw=="], - - "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], - - "supports-hyperlinks/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - "svelte-sonner/runed": ["runed@0.28.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ=="], - "svgo/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], - - "svgo/css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], - - "tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], - - "temp/rimraf": ["rimraf@2.6.3", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA=="], - - "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], - - "test-exclude/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - - "to-buffer/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], - - "tough-cookie/universalify": ["universalify@0.2.0", "", {}, "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="], - - "url/punycode": ["punycode@1.4.1", "", {}, "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="], - "vaul-svelte/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="], "vaul-svelte/svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="], "vite/rolldown": ["rolldown@1.0.0-rc.18", "", { "dependencies": { "@oxc-project/types": "=0.128.0", "@rolldown/pluginutils": "1.0.0-rc.18" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.18", "@rolldown/binding-darwin-arm64": "1.0.0-rc.18", "@rolldown/binding-darwin-x64": "1.0.0-rc.18", "@rolldown/binding-freebsd-x64": "1.0.0-rc.18", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.18", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.18", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.18", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.18", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.18", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.18", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.18", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.18", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.18", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.18", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.18" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-phmyKBpuBdRYDf4hgyynGAYn/rDDe+iZXKVJ7WX5b1zQzpLkP5oJRPGsfJuHdzPMlyyEO/4sPW6yfSx2gf7lVg=="], - "webpack/enhanced-resolve": ["enhanced-resolve@5.24.5", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A=="], - - "webpack/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - - "whatwg-url/webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="], - - "which-builtin-type/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], - - "xcode/uuid": ["uuid@7.0.3", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="], - - "xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], - - "@babel/highlight/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], - - "@babel/highlight/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], - - "@babel/highlight/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="], - - "@babel/register/make-dir/pify": ["pify@4.0.1", "", {}, "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="], - - "@babel/register/make-dir/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], - - "@expo/cli/@expo/prebuild-config/@react-native/normalize-colors": ["@react-native/normalize-colors@0.79.6", "", {}, "sha512-0v2/ruY7eeKun4BeKu+GcfO+SHBdl0LJn4ZFzTzjHdWES0Cn+ONqKljYaIv8p9MV2Hx/kcdEvbY4lWI34jC/mQ=="], - - "@expo/config/sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], - - "@expo/metro-config/lightningcss/detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="], - - "@expo/metro-config/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.27.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gl/lqIXY+d+ySmMbgDf0pgaWSqrWYxVHoc88q+Vhf2YNzZ8DwoRzGt5NZDVqqIW5ScpSnmmjcgXP87Dn2ylSSQ=="], - - "@expo/metro-config/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.27.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-0+mZa54IlcNAoQS9E0+niovhyjjQWEMrwW0p2sSdLRhLDc8LMQ/b67z7+B5q4VmjYCMSfnFi3djAAQFIDuj/Tg=="], - - "@expo/metro-config/lightningcss/lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.27.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-n1sEf85fePoU2aDN2PzYjoI8gbBqnmLGEhKq7q0DKLj0UTVmOTwDC7PtLcy/zFxzASTSBlVQYJUhwIStQMIpRA=="], - - "@expo/metro-config/lightningcss/lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.27.0", "", { "os": "linux", "cpu": "arm" }, "sha512-MUMRmtdRkOkd5z3h986HOuNBD1c2lq2BSQA1Jg88d9I7bmPGx08bwGcnB75dvr17CwxjxD6XPi3Qh8ArmKFqCA=="], - - "@expo/metro-config/lightningcss/lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.27.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-cPsxo1QEWq2sfKkSq2Bq5feQDHdUEwgtA9KaB27J5AX22+l4l0ptgjMZZtYtUnteBofjee+0oW1wQ1guv04a7A=="], - - "@expo/metro-config/lightningcss/lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.27.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg=="], - - "@expo/metro-config/lightningcss/lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.27.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A=="], - - "@expo/metro-config/lightningcss/lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.27.0", "", { "os": "linux", "cpu": "x64" }, "sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA=="], - - "@expo/metro-config/lightningcss/lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.27.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ=="], - - "@expo/metro-config/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.27.0", "", { "os": "win32", "cpu": "x64" }, "sha512-/OJLj94Zm/waZShL8nB5jsNj3CfNATLCTyFxZyouilfTmSoLDX7VlVAmhPHoZWVFp4vdmoiEbPEYC8HID3m6yw=="], - - "@expo/prebuild-config/@expo/config/@babel/code-frame": ["@babel/code-frame@7.10.4", "", { "dependencies": { "@babel/highlight": "^7.10.4" } }, "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg=="], - - "@expo/prebuild-config/@expo/config/getenv": ["getenv@1.0.0", "", {}, "sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg=="], - - "@expo/prebuild-config/@expo/config/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - - "@expo/prebuild-config/@expo/config/sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="], - - "@expo/prebuild-config/@expo/config-plugins/@expo/json-file": ["@expo/json-file@9.0.2", "", { "dependencies": { "@babel/code-frame": "~7.10.4", "json5": "^2.2.3", "write-file-atomic": "^2.3.0" } }, "sha512-yAznIUrybOIWp3Uax7yRflB0xsEpvIwIEqIjao9SGi2Gaa+N0OamWfe0fnXBSWF+2zzF4VvqwT4W5zwelchfgw=="], - - "@expo/prebuild-config/@expo/config-plugins/@expo/plist": ["@expo/plist@0.2.2", "", { "dependencies": { "@xmldom/xmldom": "~0.7.7", "base64-js": "^1.2.3", "xmlbuilder": "^14.0.0" } }, "sha512-ZZGvTO6vEWq02UAPs3LIdja+HRO18+LRI5QuDl6Hs3Ps7KX7xU6Y6kjahWKY37Rx2YjNpX07dGpBFzzC+vKa2g=="], - - "@expo/prebuild-config/@expo/config-plugins/getenv": ["getenv@1.0.0", "", {}, "sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg=="], - - "@expo/prebuild-config/@expo/config-plugins/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - - "@expo/prebuild-config/@expo/image-utils/fs-extra": ["fs-extra@9.0.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^1.0.0" } }, "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g=="], - - "@expo/prebuild-config/@expo/image-utils/getenv": ["getenv@1.0.0", "", {}, "sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg=="], - - "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - - "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - - "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - - "@istanbuljs/load-nyc-config/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], - - "@istanbuljs/load-nyc-config/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], - - "@jest/reporters/string-length/char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="], - - "@lynx-js/css-serializer/css-tree/mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], - - "@lynx-js/tailwind-preset/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], - - "@lynx-js/tailwind-preset/tailwindcss/jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="], - - "@lynx-js/template-webpack-plugin/css-tree/mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], - - "@react-native/babel-plugin-codegen/@react-native/codegen/hermes-parser": ["hermes-parser@0.23.1", "", { "dependencies": { "hermes-estree": "0.23.1" } }, "sha512-oxl5h2DkFW83hT4DAUJorpah8ou4yvmweUzLJmmr6YV2cezduCdlil1AvU/a/xSsAFo4WUcNA4GoV5Bvq6JffA=="], - - "@react-native/community-cli-plugin/@react-native/dev-middleware/@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.79.0", "", {}, "sha512-chwKEWAmQMkOKZWwBra+utquuJ/2uFqh+ZgZbJfNX+U0YsBx6AQ3dVbfAaXW3bSLYEJyf9Wb3Opsal4fmcD9Ww=="], - - "@react-native/community-cli-plugin/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - - "@react-native/dev-middleware/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - - "@react-native/metro-babel-transformer/@react-native/babel-preset/@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.86.2", "", { "dependencies": { "@babel/traverse": "^7.29.0", "@react-native/codegen": "0.86.2" } }, "sha512-NNDZqOlNbH5SzgPks1jFDYH3234Rpa5e/nhZymxhIiBH3NcE3uD+rGj/HWXhH7nHF2ToGK6XbUpqy7nmJPeh+g=="], - - "@react-native/metro-babel-transformer/@react-native/babel-preset/babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.36.0", "", { "dependencies": { "hermes-parser": "0.36.0" } }, "sha512-LhD0xdoedDw7ansQgXbB2DADLZIK/LRXuWNBPuVzMc5S2WK5GyT89tCM+cQzxFGO0mGyLK6D5TrVOJJzAoDy8Q=="], - - "@react-native/metro-babel-transformer/hermes-parser/hermes-estree": ["hermes-estree@0.36.0", "", {}, "sha512-A1+8zn5oss2CFP7pKsOaxorQG6FNIz1WU1VDqruLPPZl3LVgeE2C5xfFg8Ow6/Ow4mSslLLtYP1J3n38eKyW9w=="], - - "@react-native/metro-config/metro-config/metro": ["metro@0.84.4", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/core": "^7.25.2", "@babel/generator": "^7.29.1", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "accepts": "^2.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.35.0", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.84.4", "metro-cache": "0.84.4", "metro-cache-key": "0.84.4", "metro-config": "0.84.4", "metro-core": "0.84.4", "metro-file-map": "0.84.4", "metro-resolver": "0.84.4", "metro-runtime": "0.84.4", "metro-source-map": "0.84.4", "metro-symbolicate": "0.84.4", "metro-transform-plugins": "0.84.4", "metro-transform-worker": "0.84.4", "mime-types": "^3.0.1", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-8ETTubqfD6ornDy2zYDvRcKnVDOXdFJsjetYDBsY4oAsb6NJkiwFR+FaMESyGppFmQUyBQA4H4sFGxzcQSGtFA=="], - - "@react-native/metro-config/metro-config/metro-cache": ["metro-cache@0.84.4", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", "metro-core": "0.84.4" } }, "sha512-gpcFQdSLUwUCk71saKoE64jLFbx2nwTfVCcPSULMNT8QYq0p1eZZE29Jvd0HtT/UlhC3ZOutLxJME5xqD2JUZg=="], - - "@react-native/metro-config/metro-config/metro-core": ["metro-core@0.84.4", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.84.4" } }, "sha512-HONpWC5LGXZn3ffkd4Hu6AIrfE7j4Z0g0wMo/goV24WOB3lhuFZ40KgvaDiSw8iyQHloMYay5N/wPX+z8oN/PQ=="], - "@types/bun/bun-types/@types/node": ["@types/node@22.19.15", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg=="], - "@vue/compiler-sfc/postcss/nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="], - - "ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - - "ajv-keywords/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - - "ast-kit/@babel/parser/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], - "axios/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], - "babel-plugin-istanbul/istanbul-lib-instrument/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - - "body-parser/http-errors/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="], - - "browserify-sign/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], - - "browserify-sign/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], - "bun-types/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - "compression/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - - "connect/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - - "cosmiconfig/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], - - "css-minimizer-webpack-plugin/postcss/nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="], - - "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], - "d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="], - "expo-asset/expo-constants/@expo/env": ["@expo/env@1.0.7", "", { "dependencies": { "chalk": "^4.0.0", "debug": "^4.3.4", "dotenv": "~16.4.5", "dotenv-expand": "~11.0.6", "getenv": "^2.0.0" } }, "sha512-qSTEnwvuYJ3umapO9XJtrb1fAqiPlmUUg78N0IZXXGwQRt+bkp0OBls+Y5Mxw/Owj8waAM0Z3huKKskRADR5ow=="], - - "expo-constants/@expo/config/@babel/code-frame": ["@babel/code-frame@7.10.4", "", { "dependencies": { "@babel/highlight": "^7.10.4" } }, "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg=="], - - "expo-constants/@expo/config/@expo/config-plugins": ["@expo/config-plugins@9.0.17", "", { "dependencies": { "@expo/config-types": "^52.0.5", "@expo/json-file": "~9.0.2", "@expo/plist": "^0.2.2", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", "debug": "^4.3.5", "getenv": "^1.0.0", "glob": "^10.4.2", "resolve-from": "^5.0.0", "semver": "^7.5.4", "slash": "^3.0.0", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, "sha512-m24F1COquwOm7PBl5wRbkT9P9DviCXe0D7S7nQsolfbhdCWuvMkfXeoWmgjtdhy7sDlOyIgBrAdnB6MfsWKqIg=="], - - "expo-constants/@expo/config/@expo/config-types": ["@expo/config-types@52.0.5", "", {}, "sha512-AMDeuDLHXXqd8W+0zSjIt7f37vUd/BP8p43k68NHpyAvQO+z8mbQZm3cNQVAMySeayK2XoPigAFB1JF2NFajaA=="], - - "expo-constants/@expo/config/getenv": ["getenv@1.0.0", "", {}, "sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg=="], - - "expo-constants/@expo/config/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - - "expo-constants/@expo/config/sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="], - - "expo-dev-launcher/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - - "expo/babel-preset-expo/@react-native/babel-preset": ["@react-native/babel-preset@0.79.6", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-shorthand-properties": "^7.24.7", "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "0.79.6", "babel-plugin-syntax-hermes-parser": "0.25.1", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-H+FRO+r2Ql6b5IwfE0E7D52JhkxjeGSBSUpCXAI5zQ60zSBJ54Hwh2bBJOohXWl4J+C7gKYSAd2JHMUETu+c/A=="], - - "expo/expo-constants/@expo/env": ["@expo/env@1.0.7", "", { "dependencies": { "chalk": "^4.0.0", "debug": "^4.3.4", "dotenv": "~16.4.5", "dotenv-expand": "~11.0.6", "getenv": "^2.0.0" } }, "sha512-qSTEnwvuYJ3umapO9XJtrb1fAqiPlmUUg78N0IZXXGwQRt+bkp0OBls+Y5Mxw/Owj8waAM0Z3huKKskRADR5ow=="], - - "finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - - "find-cache-dir/make-dir/pify": ["pify@4.0.1", "", {}, "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="], - - "find-cache-dir/make-dir/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], - - "find-cache-dir/pkg-dir/find-up": ["find-up@3.0.0", "", { "dependencies": { "locate-path": "^3.0.0" } }, "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg=="], - - "glob/minimatch/brace-expansion": ["brace-expansion@1.1.18", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw=="], - - "jest-runner/source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - - "jest-watch-select-projects/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - - "jest-watch-typeahead/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - - "jest-watcher/string-length/char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="], - - "jsdom/http-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], - - "jsdom/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], - - "koins-mobile/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], - - "koins-mobile/tailwindcss/jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="], - - "lighthouse-logger/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - - "log-symbols/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], - - "log-symbols/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], - - "log-symbols/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="], - - "metro-babel-transformer/hermes-parser/hermes-estree": ["hermes-estree@0.29.1", "", {}, "sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ=="], - - "metro/hermes-parser/hermes-estree": ["hermes-estree@0.29.1", "", {}, "sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ=="], - - "mobile-lynx/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], - - "mobile-lynx/tailwindcss/jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="], - "mode-watcher/svelte-toolbelt/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="], "monero-ts/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - "node-dir/minimatch/brace-expansion": ["brace-expansion@1.1.18", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw=="], - - "node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], - - "node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], - - "ora/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], - - "ora/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], - - "ora/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="], - - "ora/strip-ansi/ansi-regex": ["ansi-regex@4.1.1", "", {}, "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="], - "paneforge/svelte-toolbelt/runed": ["runed@0.29.2", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-0cq6cA6sYGZwl/FvVqjx9YN+1xEBu9sDDyuWdDW1yWX7JF2wmvmVKfH+hVCZs+csW+P3ARH92MjI3H9QTagOQA=="], - "pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], - "qrcode/yargs/cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="], - "qrcode/yargs/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], - "qrcode/yargs/y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="], "qrcode/yargs/yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], - "raw-body/http-errors/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="], - - "react-native-css-interop/lightningcss/detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="], - - "react-native-css-interop/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.27.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gl/lqIXY+d+ySmMbgDf0pgaWSqrWYxVHoc88q+Vhf2YNzZ8DwoRzGt5NZDVqqIW5ScpSnmmjcgXP87Dn2ylSSQ=="], - - "react-native-css-interop/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.27.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-0+mZa54IlcNAoQS9E0+niovhyjjQWEMrwW0p2sSdLRhLDc8LMQ/b67z7+B5q4VmjYCMSfnFi3djAAQFIDuj/Tg=="], - - "react-native-css-interop/lightningcss/lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.27.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-n1sEf85fePoU2aDN2PzYjoI8gbBqnmLGEhKq7q0DKLj0UTVmOTwDC7PtLcy/zFxzASTSBlVQYJUhwIStQMIpRA=="], - - "react-native-css-interop/lightningcss/lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.27.0", "", { "os": "linux", "cpu": "arm" }, "sha512-MUMRmtdRkOkd5z3h986HOuNBD1c2lq2BSQA1Jg88d9I7bmPGx08bwGcnB75dvr17CwxjxD6XPi3Qh8ArmKFqCA=="], - - "react-native-css-interop/lightningcss/lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.27.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-cPsxo1QEWq2sfKkSq2Bq5feQDHdUEwgtA9KaB27J5AX22+l4l0ptgjMZZtYtUnteBofjee+0oW1wQ1guv04a7A=="], - - "react-native-css-interop/lightningcss/lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.27.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg=="], - - "react-native-css-interop/lightningcss/lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.27.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A=="], - - "react-native-css-interop/lightningcss/lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.27.0", "", { "os": "linux", "cpu": "x64" }, "sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA=="], - - "react-native-css-interop/lightningcss/lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.27.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ=="], - - "react-native-css-interop/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.27.0", "", { "os": "win32", "cpu": "x64" }, "sha512-/OJLj94Zm/waZShL8nB5jsNj3CfNATLCTyFxZyouilfTmSoLDX7VlVAmhPHoZWVFp4vdmoiEbPEYC8HID3m6yw=="], - - "react-native-css-interop/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], - - "react-native-css-interop/tailwindcss/jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="], - - "restore-cursor/onetime/mimic-fn": ["mimic-fn@1.2.0", "", {}, "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="], - - "ripemd160/hash-base/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], - - "rolldown-plugin-dts/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], - - "rolldown-plugin-dts/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], - - "rsbuild-plugin-tailwindcss/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], - - "rsbuild-plugin-tailwindcss/tailwindcss/jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="], - - "schema-utils/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - - "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - - "string-length/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - - "svgo/css-tree/mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], - - "test-exclude/minimatch/brace-expansion": ["brace-expansion@1.1.18", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw=="], - "vite/rolldown/@oxc-project/types": ["@oxc-project/types@0.128.0", "", {}, "sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ=="], "vite/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.18", "", { "os": "android", "cpu": "arm64" }, "sha512-lIDyUAfD7U3+BWKzdxMbJcsYHuqXqmGz40aeRqvuAm3y5TkJSYTBW2RDrn65DJFPQqVjUAUqq5uz8urzQ8aBdQ=="], @@ -3916,160 +1065,10 @@ "vite/rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.18", "", {}, "sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw=="], - "webpack/enhanced-resolve/tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], - - "@babel/highlight/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="], - - "@babel/highlight/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="], - - "@expo/prebuild-config/@expo/config-plugins/@expo/json-file/@babel/code-frame": ["@babel/code-frame@7.10.4", "", { "dependencies": { "@babel/highlight": "^7.10.4" } }, "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg=="], - - "@expo/prebuild-config/@expo/config-plugins/@expo/json-file/write-file-atomic": ["write-file-atomic@2.4.3", "", { "dependencies": { "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", "signal-exit": "^3.0.2" } }, "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ=="], - - "@expo/prebuild-config/@expo/config-plugins/@expo/plist/@xmldom/xmldom": ["@xmldom/xmldom@0.7.13", "", {}, "sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g=="], - - "@expo/prebuild-config/@expo/config-plugins/@expo/plist/xmlbuilder": ["xmlbuilder@14.0.0", "", {}, "sha512-ts+B2rSe4fIckR6iquDjsKbQFK2NlUk6iG5nf14mDEyldgoc2nEKZ3jZWMPTxGQwVgToSjt6VGIho1H8/fNFTg=="], - - "@expo/prebuild-config/@expo/config/sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], - - "@expo/prebuild-config/@expo/image-utils/fs-extra/universalify": ["universalify@1.0.0", "", {}, "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug=="], - - "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], - - "@lynx-js/tailwind-preset/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - - "@lynx-js/tailwind-preset/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], - - "@react-native/babel-plugin-codegen/@react-native/codegen/hermes-parser/hermes-estree": ["hermes-estree@0.23.1", "", {}, "sha512-eT5MU3f5aVhTqsfIReZ6n41X5sYn4IdQL0nvz6yO+MMlPxw49aSARHLg/MSehQftyjnrE8X6bYregzSumqc6cg=="], - - "@react-native/metro-babel-transformer/@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen": ["@react-native/codegen@0.86.2", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.29.0", "hermes-parser": "0.36.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "tinyglobby": "^0.2.15", "yargs": "^17.6.2" } }, "sha512-xKkudsahUJ1n//55g4fXk5BStVqqmZlz8HQveL45ZxcfDnwvhuYe2GymksQANFsSN+slvrarjrfq8kIxJzbceA=="], - - "@react-native/metro-config/metro-config/metro/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], - - "@react-native/metro-config/metro-config/metro/ci-info": ["ci-info@2.0.0", "", {}, "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="], - - "@react-native/metro-config/metro-config/metro/hermes-parser": ["hermes-parser@0.35.0", "", { "dependencies": { "hermes-estree": "0.35.0" } }, "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA=="], - - "@react-native/metro-config/metro-config/metro/metro-babel-transformer": ["metro-babel-transformer@0.84.4", "", { "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", "hermes-parser": "0.35.0", "metro-cache-key": "0.84.4", "nullthrows": "^1.1.1" } }, "sha512-rvCfz8snl9h20VcvpOHxZuHP1SlAkv4HXbzw7nyyVwu6Eqo5PRerbakQ9XmUCOsRy70spJ37O+G1TK8oMzo48g=="], - - "@react-native/metro-config/metro-config/metro/metro-cache-key": ["metro-cache-key@0.84.4", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-wVO79aGrkYImpnaVS4+d5RrRBRPX31QtvKB3wKGBuiNSznduZTQHzsrJZRroFJSwnygrzdsGUtDQPuqqFjFdvw=="], - - "@react-native/metro-config/metro-config/metro/metro-file-map": ["metro-file-map@0.84.4", "", { "dependencies": { "debug": "^4.4.0", "fb-watchman": "^2.0.0", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "nullthrows": "^1.1.1", "walker": "^1.0.7" } }, "sha512-KSVDi/u60hKPx++NLu3MTIvyjzNoJnFAF8PQFxaj1jiSka/wjw+Ua6sNuJ0TDHQv+7AAoFQxeMgaRAe8Yic5wQ=="], - - "@react-native/metro-config/metro-config/metro/metro-resolver": ["metro-resolver@0.84.4", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-1qLgbxQ5ZGhhutuPot1Yp348ofDsATL2WkrHF65TobqTT9K3P9qJXw38bomk7ncp5B7OYMfWwtyBZo1lCV792A=="], - - "@react-native/metro-config/metro-config/metro/metro-source-map": ["metro-source-map@0.84.4", "", { "dependencies": { "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.84.4", "nullthrows": "^1.1.1", "ob1": "0.84.4", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-jbWkPxIesVuo1IWkvezmMJld6iu8nD62GsrZiV6jP37AOdbo4OBq1FJ+qkOg8sV05wAHB//jAbziuW0SlJfW4g=="], - - "@react-native/metro-config/metro-config/metro/metro-symbolicate": ["metro-symbolicate@0.84.4", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.84.4", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" } }, "sha512-OnfpacxUqGPZQ27t8qK9mFa7uqHIlVWeqRqkCbvMvreEBiamEeOn8krKtcwgP5M4cYDPwuSmCTopHMVthqG4zA=="], - - "@react-native/metro-config/metro-config/metro/metro-transform-plugins": ["metro-transform-plugins@0.84.4", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.29.1", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "flow-enums-runtime": "^0.0.6", "nullthrows": "^1.1.1" } }, "sha512-kehr6HbAecqD0/a3xLXobELdPaAmRAl8bel0qagPF4vhZtux93nS8S4eq2kgKt6J2GnQpVjSoW1PXdst04mwow=="], - - "@react-native/metro-config/metro-config/metro/metro-transform-worker": ["metro-transform-worker@0.84.4", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.29.1", "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", "metro": "0.84.4", "metro-babel-transformer": "0.84.4", "metro-cache": "0.84.4", "metro-cache-key": "0.84.4", "metro-minify-terser": "0.84.4", "metro-source-map": "0.84.4", "metro-transform-plugins": "0.84.4", "nullthrows": "^1.1.1" } }, "sha512-W1IYMvvXTu4MxYr7d9h7CeG2vpIr3bmLLIavkPY4O1ilzDrvS8z/NEe6y+pC44Ff7raMXQgYSfdqDUwN/i39gg=="], - - "@react-native/metro-config/metro-config/metro/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], - - "@react-native/metro-config/metro-config/metro/ws": ["ws@7.5.13", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA=="], - - "@react-native/metro-config/metro-config/metro-core/metro-resolver": ["metro-resolver@0.84.4", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-1qLgbxQ5ZGhhutuPot1Yp348ofDsATL2WkrHF65TobqTT9K3P9qJXw38bomk7ncp5B7OYMfWwtyBZo1lCV792A=="], - "@types/bun/bun-types/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - "ast-kit/@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], - - "ast-kit/@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], - - "expo-constants/@expo/config/@expo/config-plugins/@expo/json-file": ["@expo/json-file@9.0.2", "", { "dependencies": { "@babel/code-frame": "~7.10.4", "json5": "^2.2.3", "write-file-atomic": "^2.3.0" } }, "sha512-yAznIUrybOIWp3Uax7yRflB0xsEpvIwIEqIjao9SGi2Gaa+N0OamWfe0fnXBSWF+2zzF4VvqwT4W5zwelchfgw=="], - - "expo-constants/@expo/config/@expo/config-plugins/@expo/plist": ["@expo/plist@0.2.2", "", { "dependencies": { "@xmldom/xmldom": "~0.7.7", "base64-js": "^1.2.3", "xmlbuilder": "^14.0.0" } }, "sha512-ZZGvTO6vEWq02UAPs3LIdja+HRO18+LRI5QuDl6Hs3Ps7KX7xU6Y6kjahWKY37Rx2YjNpX07dGpBFzzC+vKa2g=="], - - "expo-constants/@expo/config/sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], - - "expo/babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.79.6", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.79.6" } }, "sha512-CS5OrgcMPixOyUJ/Sk/HSsKsKgyKT5P7y3CojimOQzWqRZBmoQfxdST4ugj7n1H+ebM2IKqbgovApFbqXsoX0g=="], - - "find-cache-dir/pkg-dir/find-up/locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="], - - "koins-mobile/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - - "koins-mobile/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], - - "log-symbols/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="], - - "log-symbols/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="], - - "mobile-lynx/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - - "mobile-lynx/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], - - "ora/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="], - - "ora/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="], - - "pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], - "qrcode/yargs/cliui/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], - "qrcode/yargs/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], - "qrcode/yargs/yargs-parser/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], - - "react-native-css-interop/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - - "react-native-css-interop/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], - - "ripemd160/hash-base/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], - - "ripemd160/hash-base/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], - - "rsbuild-plugin-tailwindcss/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - - "rsbuild-plugin-tailwindcss/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], - - "@babel/highlight/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], - - "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], - - "@lynx-js/tailwind-preset/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], - - "@react-native/metro-config/metro-config/metro/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], - - "@react-native/metro-config/metro-config/metro/hermes-parser/hermes-estree": ["hermes-estree@0.35.0", "", {}, "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg=="], - - "@react-native/metro-config/metro-config/metro/metro-source-map/ob1": ["ob1@0.84.4", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-eJXMpz4aQHXF/YBB9ddqZDIS+ooO91hObo9FoW/xBkr54/zCwYYCDqT/O54vNo8kOkWs5Ou/y28NgdrV0edQNA=="], - - "@react-native/metro-config/metro-config/metro/metro-transform-worker/metro-minify-terser": ["metro-minify-terser@0.84.4", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" } }, "sha512-5qpbaVOMC7CPitIpuewzVeGw7E+C3ykbv2mqTjQLl85Z3annSVGlSCTcsZjqXZzjupfK4Ztj3dDc4kc44NZwtQ=="], - - "@react-native/metro-config/metro-config/metro/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - - "expo-constants/@expo/config/@expo/config-plugins/@expo/json-file/write-file-atomic": ["write-file-atomic@2.4.3", "", { "dependencies": { "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", "signal-exit": "^3.0.2" } }, "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ=="], - - "expo-constants/@expo/config/@expo/config-plugins/@expo/plist/@xmldom/xmldom": ["@xmldom/xmldom@0.7.13", "", {}, "sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g=="], - - "expo-constants/@expo/config/@expo/config-plugins/@expo/plist/xmlbuilder": ["xmlbuilder@14.0.0", "", {}, "sha512-ts+B2rSe4fIckR6iquDjsKbQFK2NlUk6iG5nf14mDEyldgoc2nEKZ3jZWMPTxGQwVgToSjt6VGIho1H8/fNFTg=="], - - "expo/babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen": ["@react-native/codegen@0.79.6", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.25.3", "glob": "^7.1.1", "hermes-parser": "0.25.1", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" } }, "sha512-iRBX8Lgbqypwnfba7s6opeUwVyaR23mowh9ILw7EcT2oLz3RqMmjJdrbVpWhGSMGq2qkPfqAH7bhO8C7O+xfjQ=="], - - "find-cache-dir/pkg-dir/find-up/locate-path/p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="], - - "find-cache-dir/pkg-dir/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="], - - "koins-mobile/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], - - "log-symbols/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], - - "mobile-lynx/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], - - "ora/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], - - "pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], - - "qrcode/yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], - - "react-native-css-interop/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], - - "rsbuild-plugin-tailwindcss/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], - - "find-cache-dir/pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], - - "qrcode/yargs/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], } } diff --git a/package.json b/package.json index 66b919c..dd8f2c3 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,6 @@ "scripts": { "build": "turbo run build", "dev": "turbo run dev", - "dev:mobile": "turbo run dev:hmr", "lint": "turbo run lint", "format": "prettier --write \"**/*.{ts,tsx,md}\"", "check-types": "turbo run check-types"