示例#1
0
        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");
        }
示例#2
0
        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);
            }
        }
示例#3
0
        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));
        }
示例#5
0
        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);
        }
示例#6
0
        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));
            }
        }