private DerivationStrategyBase FindMatch(HashSet <string> hintLabels, DerivationStrategyBase result) { var facto = new DerivationStrategyFactory(Network); var firstKeyPath = new KeyPath("0/0"); if (HintScriptPubKey == null) { return(result); } if (HintScriptPubKey == result.GetDerivation(firstKeyPath).ScriptPubKey) { return(result); } if (result is MultisigDerivationStrategy) { hintLabels.Add("keeporder"); } var resultNoLabels = result.ToString(); resultNoLabels = string.Join('-', resultNoLabels.Split('-').Where(p => !IsLabel(p))); foreach (var labels in ItemCombinations(hintLabels.ToList())) { var hinted = facto.Parse(resultNoLabels + '-' + string.Join('-', labels.Select(l => $"[{l}]").ToArray())); if (HintScriptPubKey == hinted.GetDerivation(firstKeyPath).ScriptPubKey) { return(hinted); } } throw new FormatException("Could not find any match"); }
public override DerivationStrategyBase Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.Null) { return(null); } try { var result = Factory?.Parse(reader.GetString()); if (result == null) { throw new JsonException("invalid derivation strategy"); } if (!typeToConvert.GetTypeInfo().IsAssignableFrom(result.GetType().GetTypeInfo())) { throw new JsonException( $"Invalid derivation strategy expected {typeToConvert.Name}, actual {result.GetType().Name}"); } return(result); } catch (FormatException ex) { throw new JsonException($"Invalid derivation strategy ", ex); } }
private async Task <(bool NeedSave, UTXOChanges Changes)> UpdateInvoice(UTXOChanges changes, InvoiceEntity invoice) { bool needSave = false; //Fetch unknown payments var strategy = _DerivationFactory.Parse(invoice.DerivationStrategy); changes = await _ExplorerClient.SyncAsync(strategy, changes, !LongPollingMode, _Cts.Token).ConfigureAwait(false); var utxos = changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs).ToArray(); List <Coin> receivedCoins = new List <Coin>(); foreach (var received in utxos) { if (invoice.AvailableAddressHashes.Contains(received.Output.ScriptPubKey.Hash.ToString())) { receivedCoins.Add(new Coin(received.Outpoint, received.Output)); } } var alreadyAccounted = new HashSet <OutPoint>(invoice.Payments.Select(p => p.Outpoint)); bool dirtyAddress = false; foreach (var coin in receivedCoins.Where(c => !alreadyAccounted.Contains(c.Outpoint))) { var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin).ConfigureAwait(false); invoice.Payments.Add(payment); dirtyAddress = true; } ////// if (invoice.Status == "new" && invoice.ExpirationTime < DateTimeOffset.UtcNow) { needSave = true; await _InvoiceRepository.UnaffectAddress(invoice.Id); invoice.Status = "expired"; if (invoice.FullNotifications) { _NotificationManager.Notify(invoice); } } if (invoice.Status == "new" || invoice.Status == "expired") { var totalPaid = (await GetPaymentsWithTransaction(invoice)).Select(p => p.Payment.Output.Value).Sum(); if (totalPaid >= invoice.GetTotalCryptoDue()) { if (invoice.Status == "new") { invoice.Status = "paid"; if (invoice.FullNotifications) { _NotificationManager.Notify(invoice); } invoice.ExceptionStatus = null; await _InvoiceRepository.UnaffectAddress(invoice.Id); needSave = true; } else if (invoice.Status == "expired") { invoice.ExceptionStatus = "paidLate"; needSave = true; } } if (totalPaid > invoice.GetTotalCryptoDue() && invoice.ExceptionStatus != "paidOver") { invoice.ExceptionStatus = "paidOver"; await _InvoiceRepository.UnaffectAddress(invoice.Id); needSave = true; } if (totalPaid < invoice.GetTotalCryptoDue() && invoice.Payments.Count != 0 && invoice.ExceptionStatus != "paidPartial") { Logs.PayServer.LogInformation("Paid to " + invoice.DepositAddress); invoice.ExceptionStatus = "paidPartial"; needSave = true; if (dirtyAddress) { var address = await _Wallet.ReserveAddressAsync(_DerivationFactory.Parse(invoice.DerivationStrategy)); Logs.PayServer.LogInformation("Generate new " + address); await _InvoiceRepository.NewAddress(invoice.Id, address); } } } if (invoice.Status == "paid") { var transactions = await GetPaymentsWithTransaction(invoice); var chainConfirmedTransactions = transactions.Where(t => t.Confirmations >= 1); if (invoice.SpeedPolicy == SpeedPolicy.HighSpeed) { transactions = transactions.Where(t => !t.Transaction.RBF); } else if (invoice.SpeedPolicy == SpeedPolicy.MediumSpeed) { transactions = transactions.Where(t => t.Confirmations >= 1); } else if (invoice.SpeedPolicy == SpeedPolicy.LowSpeed) { transactions = transactions.Where(t => t.Confirmations >= 6); } var chainTotalConfirmed = chainConfirmedTransactions.Select(t => t.Payment.Output.Value).Sum(); if (// Is after the monitoring deadline (invoice.MonitoringExpiration < DateTimeOffset.UtcNow) && // And not enough amount confirmed (chainTotalConfirmed < invoice.GetTotalCryptoDue())) { await _InvoiceRepository.UnaffectAddress(invoice.Id); invoice.Status = "invalid"; needSave = true; if (invoice.FullNotifications) { _NotificationManager.Notify(invoice); } } else { var totalConfirmed = transactions.Select(t => t.Payment.Output.Value).Sum(); if (totalConfirmed >= invoice.GetTotalCryptoDue()) { await _InvoiceRepository.UnaffectAddress(invoice.Id); invoice.Status = "confirmed"; _NotificationManager.Notify(invoice); needSave = true; } } } if (invoice.Status == "confirmed") { var transactions = await GetPaymentsWithTransaction(invoice); transactions = transactions.Where(t => t.Confirmations >= 6); var totalConfirmed = transactions.Select(t => t.Payment.Output.Value).Sum(); if (totalConfirmed >= invoice.GetTotalCryptoDue()) { invoice.Status = "complete"; if (invoice.FullNotifications) { _NotificationManager.Notify(invoice); } needSave = true; } } return(needSave, changes); }
public DerivationStrategyBase Parse(DerivationStrategyFactory derivationStrategyFactory, string str) { if (str == null) { throw new ArgumentNullException(nameof(str)); } str = str.Trim(); HashSet <string> hintedLabels = new HashSet <string>(); var hintDestination = HintScriptPubKey?.GetDestination(); if (hintDestination != null) { if (hintDestination is KeyId) { hintedLabels.Add("legacy"); } if (hintDestination is ScriptId) { hintedLabels.Add("p2sh"); } } if (!derivationStrategyFactory.Network.Consensus.SupportSegwit) { hintedLabels.Add("legacy"); } try { var result = derivationStrategyFactory.Parse(str); return(FindMatch(hintedLabels, result, derivationStrategyFactory)); } catch { } Dictionary <uint, string[]> electrumMapping = new Dictionary <uint, string[]>(); //Source https://github.com/spesmilo/electrum/blob/9edffd17542de5773e7284a8c8a2673c766bb3c3/lib/bitcoin.py var standard = 0x0488b21eU; electrumMapping.Add(standard, new[] { "legacy" }); var p2wpkh_p2sh = 0x049d7cb2U; electrumMapping.Add(p2wpkh_p2sh, new string[] { "p2sh" }); var p2wpkh = 0x4b24746U; electrumMapping.Add(p2wpkh, Array.Empty <string>()); var parts = str.Split('-'); bool hasLabel = false; for (int i = 0; i < parts.Length; i++) { if (IsLabel(parts[i])) { if (!hasLabel) { hintedLabels.Clear(); if (!derivationStrategyFactory.Network.Consensus.SupportSegwit) { hintedLabels.Add("legacy"); } } hasLabel = true; hintedLabels.Add(parts[i].Substring(1, parts[i].Length - 2).ToLowerInvariant()); continue; } try { var data = derivationStrategyFactory.Network.GetBase58CheckEncoder().DecodeData(parts[i]); if (data.Length < 4) { continue; } var prefix = Utils.ToUInt32(data, false); var standardPrefix = Utils.ToBytes( derivationStrategyFactory.Network.NetworkType == NetworkType.Mainnet ? 0x0488b21eU : 0x043587cf, false); for (int ii = 0; ii < 4; ii++) { data[ii] = standardPrefix[ii]; } var derivationScheme = new BitcoinExtPubKey(derivationStrategyFactory.Network.GetBase58CheckEncoder().EncodeData(data), derivationStrategyFactory.Network).ToString(); electrumMapping.TryGetValue(prefix, out string[] labels); if (labels != null) { foreach (var label in labels) { hintedLabels.Add(label.ToLowerInvariant()); } } parts[i] = derivationScheme; } catch { continue; } } if (hintDestination != null) { if (hintDestination is WitKeyId) { hintedLabels.Remove("legacy"); hintedLabels.Remove("p2sh"); } } str = string.Join("-", parts.Where(p => !IsLabel(p))); foreach (var label in hintedLabels) { str = $"{str}-[{label}]"; } return(FindMatch(hintedLabels, derivationStrategyFactory.Parse(str), derivationStrategyFactory)); }
private async Task <(bool NeedSave, UTXOChanges Changes)> UpdateInvoice(UTXOChanges changes, InvoiceEntity invoice) { bool needSave = false; if (invoice.Status != "invalid" && invoice.ExpirationTime < DateTimeOffset.UtcNow && (invoice.Status == "new" || invoice.Status == "paidPartial")) { needSave = true; invoice.Status = "invalid"; } if (invoice.Status == "invalid" || invoice.Status == "new" || invoice.Status == "paidPartial") { var strategy = _DerivationFactory.Parse(invoice.DerivationStrategy); changes = await _ExplorerClient.SyncAsync(strategy, changes, !LongPollingMode, _Cts.Token).ConfigureAwait(false); var utxos = changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs).ToArray(); var invoiceIds = utxos.Select(u => _Wallet.GetInvoiceId(u.Output.ScriptPubKey)).ToArray(); utxos = utxos .Where((u, i) => invoiceIds[i].GetAwaiter().GetResult() == invoice.Id) .ToArray(); List <Coin> receivedCoins = new List <Coin>(); foreach (var received in utxos) { if (received.Output.ScriptPubKey == invoice.DepositAddress.ScriptPubKey) { receivedCoins.Add(new Coin(received.Outpoint, received.Output)); } } var alreadyAccounted = new HashSet <OutPoint>(invoice.Payments.Select(p => p.Outpoint)); foreach (var coin in receivedCoins.Where(c => !alreadyAccounted.Contains(c.Outpoint))) { var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin).ConfigureAwait(false); invoice.Payments.Add(payment); if (invoice.Status == "new") { invoice.Status = "paidPartial"; needSave = true; } } } if (invoice.Status == "paidPartial") { var totalPaid = invoice.Payments.Select(p => p.Output.Value).Sum(); if (totalPaid == invoice.GetTotalCryptoDue()) { invoice.Status = "paid"; if (invoice.FullNotifications) { _NotificationManager.Notify(invoice); } invoice.ExceptionStatus = null; needSave = true; } if (totalPaid > invoice.GetTotalCryptoDue()) { invoice.Status = "paidOver"; invoice.ExceptionStatus = "paidOver"; needSave = true; } if (totalPaid < invoice.GetTotalCryptoDue() && invoice.ExceptionStatus == null) { invoice.ExceptionStatus = "paidPartial"; needSave = true; } } if (invoice.Status == "paid" || invoice.Status == "paidOver") { var getTransactions = invoice.Payments.Select(o => o.Outpoint.Hash).Select(o => _ExplorerClient.GetTransactionAsync(o, _Cts.Token)).ToArray(); await Task.WhenAll(getTransactions).ConfigureAwait(false); var transactions = getTransactions.Select(c => c.GetAwaiter().GetResult()).ToArray(); bool confirmed = false; var minConf = transactions.Select(t => t.Confirmations).Min(); if (invoice.SpeedPolicy == SpeedPolicy.HighSpeed) { if (minConf > 0) { confirmed = true; } else { confirmed = !transactions.Any(t => t.Transaction.RBF); } } else if (invoice.SpeedPolicy == SpeedPolicy.MediumSpeed) { confirmed = minConf >= 1; } else if (invoice.SpeedPolicy == SpeedPolicy.LowSpeed) { confirmed = minConf >= 6; } if (confirmed) { invoice.Status = "confirmed"; _NotificationManager.Notify(invoice); needSave = true; } } if (invoice.Status == "confirmed") { var getTransactions = invoice.Payments.Select(o => o.Outpoint.Hash).Select(o => _ExplorerClient.GetTransactionAsync(o, _Cts.Token)).ToArray(); await Task.WhenAll(getTransactions).ConfigureAwait(false); var transactions = getTransactions.Select(c => c.GetAwaiter().GetResult()).ToArray(); var minConf = transactions.Select(t => t.Confirmations).Min(); if (minConf >= 6) { invoice.Status = "complete"; if (invoice.FullNotifications) { _NotificationManager.Notify(invoice); } needSave = true; } } return(needSave, changes); }
public async Task <IActionResult> UpdateStore(string storeId, StoreViewModel model, string command) { if (!ModelState.IsValid) { return(View(model)); } var store = await _Repo.FindStore(storeId, GetUserId()); if (store == null) { return(NotFound()); } if (command == "Save") { bool needUpdate = false; if (store.SpeedPolicy != model.SpeedPolicy) { needUpdate = true; store.SpeedPolicy = model.SpeedPolicy; } if (store.StoreName != model.StoreName) { needUpdate = true; store.StoreName = model.StoreName; } if (store.StoreWebsite != model.StoreWebsite) { needUpdate = true; store.StoreWebsite = model.StoreWebsite; } if (store.DerivationStrategy != model.DerivationScheme) { needUpdate = true; try { var strategy = ParseDerivationStrategy(model.DerivationScheme); await _Wallet.TrackAsync(strategy); await _CallbackController.RegisterCallbackUriAsync(strategy, Request); store.DerivationStrategy = model.DerivationScheme; } catch { ModelState.AddModelError(nameof(model.DerivationScheme), "Invalid Derivation Scheme"); return(View(model)); } } if (needUpdate) { await _Repo.UpdateStore(store); StatusMessage = "Store successfully updated"; } return(RedirectToAction(nameof(UpdateStore), new { storeId = storeId })); } else { var facto = new DerivationStrategyFactory(_Network); var scheme = facto.Parse(model.DerivationScheme); var line = scheme.GetLineFor(DerivationFeature.Deposit); for (int i = 0; i < 10; i++) { var address = line.Derive((uint)i); model.AddressSamples.Add((line.Path.Derive((uint)i).ToString(), address.ScriptPubKey.GetDestinationAddress(_Network).ToString())); } return(View(model)); } }