Ejemplo n.º 1
0
        private BitcoinUrlBuilder CreateBuilder(string uri)
        {
            var builder = new BitcoinUrlBuilder(uri);

            Assert.Equal(builder.Uri.ToString(), uri);
            builder = new BitcoinUrlBuilder(new Uri(uri, UriKind.Absolute));
            Assert.Equal(builder.ToString(), uri);
            return(builder);
        }
Ejemplo n.º 2
0
        private async Task <PSBT> GetPayjoinProposedTX(BitcoinUrlBuilder bip21, PSBT psbt, DerivationSchemeSettings derivationSchemeSettings, BTCPayNetwork btcPayNetwork, CancellationToken cancellationToken)
        {
            var cloned = psbt.Clone();

            cloned = cloned.Finalize();
            await _broadcaster.Schedule(DateTimeOffset.UtcNow + TimeSpan.FromMinutes(2.0), cloned.ExtractTransaction(), btcPayNetwork);

            return(await _payjoinClient.RequestPayjoin(bip21, new PayjoinWallet(derivationSchemeSettings), psbt, cancellationToken));
        }
Ejemplo n.º 3
0
 public UriClaimDestination(BitcoinUrlBuilder bitcoinUrl)
 {
     ArgumentNullException.ThrowIfNull(bitcoinUrl);
     if (bitcoinUrl.Address is null)
     {
         throw new ArgumentException(nameof(bitcoinUrl));
     }
     _bitcoinUrl = bitcoinUrl;
     Address     = bitcoinUrl.Address ?? throw new FormatException("the bip21 doesn't contain an address");
 }
Ejemplo n.º 4
0
        public async Task <IActionResult> BitpayTranslator(BitpayTranslatorViewModel vm)
        {
            if (!ModelState.IsValid)
            {
                return(View(vm));
            }
            vm.BitpayLink = vm.BitpayLink ?? string.Empty;
            vm.BitpayLink = vm.BitpayLink.Trim();
            if (!vm.BitpayLink.StartsWith("bitcoin:", StringComparison.OrdinalIgnoreCase))
            {
                var invoiceId = vm.BitpayLink.Substring(vm.BitpayLink.LastIndexOf("=", StringComparison.OrdinalIgnoreCase) + 1);
                vm.BitpayLink = $"bitcoin:?r=https://bitpay.com/i/{invoiceId}";
            }

            try
            {
                BitcoinUrlBuilder urlBuilder = new BitcoinUrlBuilder(vm.BitpayLink, Network.Main);
#pragma warning disable CS0618 // Type or member is obsolete
                if (!urlBuilder.PaymentRequestUrl.DnsSafeHost.EndsWith("bitpay.com", StringComparison.OrdinalIgnoreCase))
                {
                    throw new Exception("This tool only work with bitpay");
                }

                var client = HttpClientFactory.CreateClient();
                HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, urlBuilder.PaymentRequestUrl);
#pragma warning restore CS0618 // Type or member is obsolete
                request.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/payment-request"));
                var result = await client.SendAsync(request);

                // {"network":"main","currency":"BTC","requiredFeeRate":29.834,"outputs":[{"amount":255900,"address":"1PgPo5d4swD6pKfCgoXtoW61zqTfX9H7tj"}],"time":"2018-12-03T14:39:47.162Z","expires":"2018-12-03T14:54:47.162Z","memo":"Payment request for BitPay invoice HHfG8cprRMzZG6MErCqbjv for merchant VULTR Holdings LLC","paymentUrl":"https://bitpay.com/i/HHfG8cprRMzZG6MErCqbjv","paymentId":"HHfG8cprRMzZG6MErCqbjv"}
                var str = await result.Content.ReadAsStringAsync();

                try
                {
                    var jobj = JObject.Parse(str);
                    vm.Address = ((JArray)jobj["outputs"])[0]["address"].Value <string>();
                    var amount = Money.Satoshis(((JArray)jobj["outputs"])[0]["amount"].Value <long>());
                    vm.Amount     = amount.ToString();
                    vm.BitcoinUri = $"bitcoin:{vm.Address}?amount={amount.ToString()}";
                }
                catch (JsonReaderException)
                {
                    ModelState.AddModelError(nameof(vm.BitpayLink), $"Invalid or expired bitpay invoice");
                    return(View(vm));
                }
            }
            catch (Exception ex)
            {
                ModelState.AddModelError(nameof(vm.BitpayLink), $"Error while requesting {ex.Message}");
                return(View(vm));
            }
            return(View(vm));
        }
Ejemplo n.º 5
0
 public UriClaimDestination(BitcoinUrlBuilder bitcoinUrl)
 {
     if (bitcoinUrl == null)
     {
         throw new ArgumentNullException(nameof(bitcoinUrl));
     }
     if (bitcoinUrl.Address is null)
     {
         throw new ArgumentException(nameof(bitcoinUrl));
     }
     _bitcoinUrl = bitcoinUrl;
 }
Ejemplo n.º 6
0
 public void CanTalkToPaymentServer()
 {
     using (var server = new PaymentServerTester())
     {
         var uri = server.GetPaymentRequestUri(2);
         BitcoinUrlBuilder btcUri = new BitcoinUrlBuilder(uri);
         var request = btcUri.GetPaymentRequest();
         Assert.True(request.VerifySignature());
         Assert.Equal(2, BitConverter.ToInt32(request.Details.MerchantData, 0));
         var ack = request.CreatePayment().SubmitPayment();
         Assert.NotNull(ack);
     }
 }
Ejemplo n.º 7
0
        public void CanPayUsingBIP70()
        {
            using (var tester = ServerTester.Create())
            {
                tester.Start();
                var user = tester.NewAccount();
                user.GrantAccess();
                user.RegisterDerivationScheme("BTC");
                var invoice = user.BitPay.CreateInvoice(new Invoice()
                {
                    Buyer = new Buyer()
                    {
                        email = "*****@*****.**"
                    },
                    Price    = 5000.0,
                    Currency = "USD",
                    PosData  = "posData",
                    OrderId  = "orderId",
                    //RedirectURL = redirect + "redirect",
                    //NotificationURL = CallbackUri + "/notification",
                    ItemDesc          = "Some description",
                    FullNotifications = true
                }, Facade.Merchant);

                Assert.False(invoice.Refundable);

                var url     = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP72);
                var request = url.GetPaymentRequest();
                var payment = request.CreatePayment();

                Transaction tx = new Transaction();
                tx.Outputs.AddRange(request.Details.Outputs.Select(o => new TxOut(o.Amount, o.Script)));
                var cashCow = tester.ExplorerNode;
                tx = cashCow.FundRawTransaction(tx).Transaction;
                tx = cashCow.SignRawTransaction(tx);

                payment.Transactions.Add(tx);

                payment.RefundTo.Add(new PaymentOutput(Money.Coins(1.0m), new Key().ScriptPubKey));
                var ack = payment.SubmitPayment();
                Assert.NotNull(ack);

                Eventually(() =>
                {
                    var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
                    Assert.Equal("paid", localInvoice.Status);
                    Assert.True(localInvoice.Refundable);
                });
            }
        }
Ejemplo n.º 8
0
        public void CanParsePaymentUrl()
        {
            Assert.Equal("bitcoin:", new BitcoinUrlBuilder().Uri.ToString());

            var url = CreateBuilder("bitcoin:129mVqKUmJ9uwPxKJBnNdABbuaaNfho4Ha");

            Assert.Equal("129mVqKUmJ9uwPxKJBnNdABbuaaNfho4Ha", url.Address.ToString());

            url = CreateBuilder("bitcoin:129mVqKUmJ9uwPxKJBnNdABbuaaNfho4Ha?amount=0.06");
            Assert.Equal("129mVqKUmJ9uwPxKJBnNdABbuaaNfho4Ha", url.Address.ToString());
            Assert.Equal(Money.Parse("0.06"), url.Amount);

            url = new BitcoinUrlBuilder("bitcoin:129mVqKUmJ9uwPxKJBnNdABbuaaNfho4Ha?amount=0.06&label=Tom%20%26%20Jerry");
            Assert.Equal("129mVqKUmJ9uwPxKJBnNdABbuaaNfho4Ha", url.Address.ToString());
            Assert.Equal(Money.Parse("0.06"), url.Amount);
            Assert.Equal("Tom & Jerry", url.Label);
            Assert.Equal(url.ToString(), new BitcoinUrlBuilder(url.ToString()).ToString());

            //Request 50 BTC with message:
            url = new BitcoinUrlBuilder("bitcoin:129mVqKUmJ9uwPxKJBnNdABbuaaNfho4Ha?amount=50&label=Luke-Jr&message=Donation%20for%20project%20xyz");
            Assert.Equal(Money.Parse("50"), url.Amount);
            Assert.Equal("Luke-Jr", url.Label);
            Assert.Equal("Donation for project xyz", url.Message);
            Assert.Equal(url.ToString(), new BitcoinUrlBuilder(url.ToString()).ToString());

            //Some future version that has variables which are (currently) not understood and required and thus invalid:
            url = new BitcoinUrlBuilder("bitcoin:129mVqKUmJ9uwPxKJBnNdABbuaaNfho4Ha?amount=50&label=Luke-Jr&message=Donation%20for%20project%20xyz&unknownparam=lol");

            //Some future version that has variables which are (currently) not understood but not required and thus valid:
            Assert.Throws <FormatException>(() => new BitcoinUrlBuilder("bitcoin:129mVqKUmJ9uwPxKJBnNdABbuaaNfho4Ha?amount=50&label=Luke-Jr&message=Donation%20for%20project%20xyz&req-unknownparam=lol"));
            Assert.Throws <FormatException>(() => new BitcoinUrlBuilder("bitcoin:129mVqKUmJ9uwPxKJBnNdABbuaaNfho4Ha?amount=50&amount=50"));

            url = new BitcoinUrlBuilder("bitcoin:mq7se9wy2egettFxPbmn99cK8v5AFq55Lx?amount=0.11&r=https://merchant.com/pay.php?h%3D2a8628fc2fbe");
            Assert.Equal("bitcoin:mq7se9wy2egettFxPbmn99cK8v5AFq55Lx?amount=0.11&r=https://merchant.com/pay.php?h%3d2a8628fc2fbe", url.ToString());
#pragma warning disable CS0618 // Type or member is obsolete
            Assert.Equal("https://merchant.com/pay.php?h=2a8628fc2fbe", url.PaymentRequestUrl.ToString());
#pragma warning restore CS0618 // Type or member is obsolete
            Assert.Equal(url.ToString(), new BitcoinUrlBuilder(url.ToString()).ToString());

            //Support no address
            url = new BitcoinUrlBuilder("bitcoin:?r=https://merchant.com/pay.php?h%3D2a8628fc2fbe");
#pragma warning disable CS0618 // Type or member is obsolete
            Assert.Equal("https://merchant.com/pay.php?h=2a8628fc2fbe", url.PaymentRequestUrl.ToString());
#pragma warning restore CS0618 // Type or member is obsolete
            Assert.Equal(url.ToString(), new BitcoinUrlBuilder(url.ToString()).ToString());
        }
Ejemplo n.º 9
0
        public static bool TryParse(string text, Network expectedNetwork, out BitcoinUrlBuilder result)
        {
            result = null;
            if (string.IsNullOrWhiteSpace(text) || text.Length > 1000)
            {
                return(false);
            }

            if (TryParseBitcoinAddress(text, expectedNetwork, out BitcoinUrlBuilder addressResult))
            {
                result = addressResult;
                return(true);
            }
            else
            {
                if (TryParseBitcoinUrl(text, expectedNetwork, out BitcoinUrlBuilder urlResult))
                {
                    result = urlResult;
                    return(true);
                }
            }
            return(false);
        }
Ejemplo n.º 10
0
        public static bool TryParseBitcoinUrl(string text, Network expectedNetwork, out BitcoinUrlBuilder url)
        {
            url = null;

            if (text is null || expectedNetwork is null)
            {
                return(false);
            }

            text = text.Trim();

            if (text.Length > 1000 || text.Length < 20)
            {
                return(false);
            }

            try
            {
                if (!text.StartsWith("bitcoin:", true, CultureInfo.InvariantCulture))
                {
                    return(false);
                }

                var bitcoinUrl = new BitcoinUrlBuilder(text, expectedNetwork);
                if (bitcoinUrl?.Address.Network == expectedNetwork)
                {
                    url = bitcoinUrl;
                    return(true);
                }

                return(false);
            }
            catch (FormatException)
            {
                return(false);
            }
        }
Ejemplo n.º 11
0
        internal BitcoinUrlBuilder ParseDestinationString(string destinationString)
        {
            if (destinationString == null)
            {
                return(null);
            }
            destinationString = destinationString.Trim();
            BitcoinUrlBuilder url = null;

            try
            {
                url = new BitcoinUrlBuilder(destinationString, Global.Network);
                if (url.Amount != null)
                {
                    // since AmountText can be altered by hand, we set it instead
                    // of binding to a calculated ObservableAsPropertyHelper
                    AmountText = url.Amount.ToString();
                }

                // we could check url.Label or url.Message for contact, but there is
                // no convention on their use yet so it's hard to say whether they
                // identify the sender or receiver. We care about the recipient only here.
                return(url);
            }
            catch (Exception) { /* invalid bitcoin uri */ }

            try
            {
                BitcoinAddress address = BitcoinAddress.Create(destinationString.Trim(), Global.Network);
                url         = new BitcoinUrlBuilder();
                url.Address = address;
            }
            catch (Exception) { /* invalid bitcoin address */ }

            return(url);
        }
Ejemplo n.º 12
0
        internal bool HandleScan(string scannedIn)
        {
            BitcoinAddress address = null;

            try
            {
                address = BitcoinAddress.Create(scannedIn, Global.Network);
            }
            catch (Exception)
            {
                try
                {
                    address = new BitcoinUrlBuilder(scannedIn, Global.Network).Address;
                }
                catch (Exception) { }
            }

            if (address != null)
            {
                Address = address.ToString();
                return(true);
            }
            return(false);
        }
Ejemplo n.º 13
0
        public async Task ElementsAssetsAreHandledCorrectly()
        {
            using (var tester = CreateServerTester())
            {
                tester.ActivateLBTC();
                await tester.StartAsync();

                var user = tester.NewAccount();
                user.GrantAccess();
                user.RegisterDerivationScheme("LBTC");
                user.RegisterDerivationScheme("USDT");
                user.RegisterDerivationScheme("ETB");
                await tester.LBTCExplorerNode.GenerateAsync(4);

                //no tether on our regtest, lets create it and set it
                var tether           = tester.NetworkProvider.GetNetwork <ElementsBTCPayNetwork>("USDT");
                var lbtc             = tester.NetworkProvider.GetNetwork <ElementsBTCPayNetwork>("LBTC");
                var etb              = tester.NetworkProvider.GetNetwork <ElementsBTCPayNetwork>("ETB");
                var issueAssetResult = await tester.LBTCExplorerNode.SendCommandAsync("issueasset", 100000, 0);

                tether.AssetId = uint256.Parse(issueAssetResult.Result["asset"].ToString());
                ((ElementsBTCPayNetwork)tester.PayTester.GetService <BTCPayWalletProvider>().GetWallet("USDT").Network)
                .AssetId = tether.AssetId;
                Assert.Equal(tether.AssetId, tester.NetworkProvider.GetNetwork <ElementsBTCPayNetwork>("USDT").AssetId);
                Assert.Equal(tether.AssetId, ((ElementsBTCPayNetwork)tester.PayTester.GetService <BTCPayWalletProvider>().GetWallet("USDT").Network).AssetId);

                var issueAssetResult2 = await tester.LBTCExplorerNode.SendCommandAsync("issueasset", 100000, 0);

                etb.AssetId = uint256.Parse(issueAssetResult2.Result["asset"].ToString());
                ((ElementsBTCPayNetwork)tester.PayTester.GetService <BTCPayWalletProvider>().GetWallet("ETB").Network)
                .AssetId = etb.AssetId;


                //test: register 2 assets on the same elements network and make sure paying an invoice on one does not affect the other in any way
                var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(0.1m, "BTC"));

                Assert.Equal(3, invoice.SupportedTransactionCurrencies.Count);
                var ci = invoice.CryptoInfo.Single(info => info.CryptoCode.Equals("LBTC"));
                //1 lbtc = 1 btc
                Assert.Equal(1, ci.Rate);
                var star = await tester.LBTCExplorerNode.SendCommandAsync("sendtoaddress", ci.Address, ci.Due, "", "", false, true,
                                                                          1, "UNSET", lbtc.AssetId);

                TestUtils.Eventually(() =>
                {
                    var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
                    Assert.Equal("paid", localInvoice.Status);
                    Assert.Single(localInvoice.CryptoInfo.Single(info => info.CryptoCode.Equals("LBTC")).Payments);
                });

                invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(0.1m, "BTC"));

                ci = invoice.CryptoInfo.Single(info => info.CryptoCode.Equals("USDT"));
                Assert.Equal(3, invoice.SupportedTransactionCurrencies.Count);
                star = await tester.LBTCExplorerNode.SendCommandAsync("sendtoaddress", ci.Address, ci.Due, "", "", false, true,
                                                                      1, "UNSET", tether.AssetId);

                TestUtils.Eventually(() =>
                {
                    var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
                    Assert.Equal("paid", localInvoice.Status);
                    Assert.Single(localInvoice.CryptoInfo.Single(info => info.CryptoCode.Equals("USDT", StringComparison.InvariantCultureIgnoreCase)).Payments);
                });

                //test precision based on https://github.com/ElementsProject/elements/issues/805#issuecomment-601277606
                var etbBip21 = new BitcoinUrlBuilder(invoice.CryptoInfo.Single(info => info.CryptoCode == "ETB").PaymentUrls.BIP21, etb.NBitcoinNetwork);
                //precision = 2, 1ETB  = 0.00000100
                Assert.Equal(100, etbBip21.Amount.Satoshi);

                var lbtcBip21 = new BitcoinUrlBuilder(invoice.CryptoInfo.Single(info => info.CryptoCode == "LBTC").PaymentUrls.BIP21, lbtc.NBitcoinNetwork);
                //precision = 8, 0.1 = 0.1
                Assert.Equal(0.1m, lbtcBip21.Amount.ToDecimal(MoneyUnit.BTC));
            }
        }
        public async Task <IActionResult> CreateOnChainTransaction(string storeId, string cryptoCode,
                                                                   [FromBody] CreateOnChainTransactionRequest request)
        {
            if (IsInvalidWalletRequest(cryptoCode, out BTCPayNetwork network,
                                       out DerivationSchemeSettings derivationScheme, out IActionResult actionResult))
            {
                return(actionResult);
            }
            if (network.ReadonlyWallet)
            {
                return(this.CreateAPIError("not-available",
                                           $"{cryptoCode} sending services are not currently available"));
            }

            //This API is only meant for hot wallet usage for now. We can expand later when we allow PSBT manipulation.
            if (!(await CanUseHotWallet()).HotWallet)
            {
                return(Unauthorized());
            }

            var explorerClient = _explorerClientProvider.GetExplorerClient(cryptoCode);
            var wallet         = _btcPayWalletProvider.GetWallet(network);

            var utxos = await wallet.GetUnspentCoins(derivationScheme.AccountDerivation);

            if (request.SelectedInputs != null || !utxos.Any())
            {
                utxos = utxos.Where(coin => request.SelectedInputs?.Contains(coin.OutPoint) ?? true)
                        .ToArray();
                if (utxos.Any() is false)
                {
                    //no valid utxos selected
                    request.AddModelError(transactionRequest => transactionRequest.SelectedInputs,
                                          "There are no available utxos based on your request", this);
                }
            }

            var balanceAvailable = utxos.Sum(coin => coin.Value.GetValue(network));

            var subtractFeesOutputsCount = new List <int>();
            var subtractFees             = request.Destinations.Any(o => o.SubtractFromAmount);
            int?payjoinOutputIndex       = null;
            var sum     = 0m;
            var outputs = new List <WalletSendModel.TransactionOutput>();

            for (var index = 0; index < request.Destinations.Count; index++)
            {
                var destination = request.Destinations[index];

                if (destination.SubtractFromAmount)
                {
                    subtractFeesOutputsCount.Add(index);
                }

                BitcoinUrlBuilder bip21 = null;
                var amount = destination.Amount;
                if (amount.GetValueOrDefault(0) <= 0)
                {
                    amount = null;
                }
                var address = string.Empty;
                try
                {
                    destination.Destination = destination.Destination.Replace(network.UriScheme + ":", "bitcoin:", StringComparison.InvariantCultureIgnoreCase);
                    bip21 = new BitcoinUrlBuilder(destination.Destination, network.NBitcoinNetwork);
                    amount ??= bip21.Amount.GetValue(network);
                    address = bip21.Address.ToString();
                    if (destination.SubtractFromAmount)
                    {
                        request.AddModelError(transactionRequest => transactionRequest.Destinations[index],
                                              "You cannot use a BIP21 destination along with SubtractFromAmount", this);
                    }
                }
                catch (FormatException)
                {
                    try
                    {
                        address = BitcoinAddress.Create(destination.Destination, network.NBitcoinNetwork).ToString();
                    }
                    catch (Exception e)
                    {
                        request.AddModelError(transactionRequest => transactionRequest.Destinations[index],
                                              "Destination must be a BIP21 payment link or an address", this);
                    }
                }

                if (amount is null || amount <= 0)
                {
                    request.AddModelError(transactionRequest => transactionRequest.Destinations[index],
                                          "Amount must be specified or destination must be a BIP21 payment link, and greater than 0", this);
                }
                if (request.ProceedWithPayjoin && bip21?.UnknowParameters?.ContainsKey("pj") is true)
                {
                    payjoinOutputIndex = index;
                }

                outputs.Add(new WalletSendModel.TransactionOutput()
                {
                    DestinationAddress     = address,
                    Amount                 = amount,
                    SubtractFeesFromOutput = destination.SubtractFromAmount
                });
                sum += destination.Amount ?? 0;
            }

            if (subtractFeesOutputsCount.Count > 1)
            {
                foreach (var subtractFeesOutput in subtractFeesOutputsCount)
                {
                    request.AddModelError(model => model.Destinations[subtractFeesOutput].SubtractFromAmount,
                                          "You can only subtract fees from one destination", this);
                }
            }

            if (balanceAvailable < sum)
            {
                request.AddModelError(transactionRequest => transactionRequest.Destinations,
                                      "You are attempting to send more than is available", this);
            }
            else if (balanceAvailable == sum && !subtractFees)
            {
                request.AddModelError(transactionRequest => transactionRequest.Destinations,
                                      "You are sending your entire balance, you should subtract the fees from a destination", this);
            }

            var minRelayFee = this._nbXplorerDashboard.Get(network.CryptoCode).Status.BitcoinStatus?.MinRelayTxFee ??
                              new FeeRate(1.0m);

            if (request.FeeRate != null && request.FeeRate < minRelayFee)
            {
                ModelState.AddModelError(nameof(request.FeeRate),
                                         "The fee rate specified is lower than the current minimum relay fee");
            }

            if (!ModelState.IsValid)
            {
                return(this.CreateValidationError(ModelState));
            }

            CreatePSBTResponse psbt;

            try
            {
                psbt = await _walletsController.CreatePSBT(network, derivationScheme,
                                                           new WalletSendModel()
                {
                    SelectedInputs = request.SelectedInputs?.Select(point => point.ToString()),
                    Outputs        = outputs,
                    AlwaysIncludeNonWitnessUTXO = true,
                    InputSelection = request.SelectedInputs?.Any() is true,
                    AllowFeeBump   =
                        !request.RBF.HasValue ? WalletSendModel.ThreeStateBool.Maybe :
                        request.RBF.Value ? WalletSendModel.ThreeStateBool.Yes :
                        WalletSendModel.ThreeStateBool.No,
                    FeeSatoshiPerByte = request.FeeRate?.SatoshiPerByte,
                    NoChange          = request.NoChange
                },
                                                           CancellationToken.None);
            }
Ejemplo n.º 15
0
        public async Task CanUseBIP79()
        {
            using (var tester = ServerTester.Create())
            {
                await tester.StartAsync();

                ////var payJoinStateProvider = tester.PayTester.GetService<PayJoinStateProvider>();
                var btcPayNetwork = tester.NetworkProvider.GetNetwork <BTCPayNetwork>("BTC");
                var btcPayWallet  = tester.PayTester.GetService <BTCPayWalletProvider>().GetWallet(btcPayNetwork);
                var cashCow       = tester.ExplorerNode;
                cashCow.Generate(2); // get some money in case

                var senderUser = tester.NewAccount();
                senderUser.GrantAccess(true);
                senderUser.RegisterDerivationScheme("BTC", true, true);

                var invoice = senderUser.BitPay.CreateInvoice(
                    new Invoice()
                {
                    Price = 100, Currency = "USD", FullNotifications = true
                });
                //payjoin is not enabled by default.
                Assert.DoesNotContain("bpu", invoice.CryptoInfo.First().PaymentUrls.BIP21);
                cashCow.SendToAddress(BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network),
                                      Money.Coins(0.06m));

                var receiverUser = tester.NewAccount();
                receiverUser.GrantAccess(true);
                receiverUser.RegisterDerivationScheme("BTC", true, true);

                await receiverUser.EnablePayJoin();

                // payjoin is enabled, with a segwit wallet, and the keys are available in nbxplorer
                invoice = receiverUser.BitPay.CreateInvoice(
                    new Invoice()
                {
                    Price = 0.02m, Currency = "BTC", FullNotifications = true
                });
                cashCow.SendToAddress(BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network),
                                      Money.Coins(0.06m));
                var receiverWalletId = new WalletId(receiverUser.StoreId, "BTC");

                //give the cow some cash
                await cashCow.GenerateAsync(1);

                //let's get some more utxos first
                await receiverUser.ReceiveUTXO(Money.Coins(0.011m), btcPayNetwork);

                await receiverUser.ReceiveUTXO(Money.Coins(0.012m), btcPayNetwork);

                await receiverUser.ReceiveUTXO(Money.Coins(0.013m), btcPayNetwork);

                await receiverUser.ReceiveUTXO(Money.Coins(0.014m), btcPayNetwork);

                await senderUser.ReceiveUTXO(Money.Coins(0.021m), btcPayNetwork);

                await senderUser.ReceiveUTXO(Money.Coins(0.022m), btcPayNetwork);

                await senderUser.ReceiveUTXO(Money.Coins(0.023m), btcPayNetwork);

                await senderUser.ReceiveUTXO(Money.Coins(0.024m), btcPayNetwork);

                await senderUser.ReceiveUTXO(Money.Coins(0.025m), btcPayNetwork);

                await senderUser.ReceiveUTXO(Money.Coins(0.026m), btcPayNetwork);

                var senderChange = await senderUser.GetNewAddress(btcPayNetwork);

                //Let's start the harassment
                invoice = receiverUser.BitPay.CreateInvoice(
                    new Invoice()
                {
                    Price = 0.02m, Currency = "BTC", FullNotifications = true
                });

                var parsedBip21 = new BitcoinUrlBuilder(invoice.CryptoInfo.First().PaymentUrls.BIP21,
                                                        tester.ExplorerClient.Network.NBitcoinNetwork);

                var invoice2 = receiverUser.BitPay.CreateInvoice(
                    new Invoice()
                {
                    Price = 0.02m, Currency = "BTC", FullNotifications = true
                });
                var secondInvoiceParsedBip21 = new BitcoinUrlBuilder(invoice2.CryptoInfo.First().PaymentUrls.BIP21,
                                                                     tester.ExplorerClient.Network.NBitcoinNetwork);

                var senderStore = await tester.PayTester.StoreRepository.FindStore(senderUser.StoreId);

                var paymentMethodId          = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
                var derivationSchemeSettings = senderStore.GetSupportedPaymentMethods(tester.NetworkProvider)
                                               .OfType <DerivationSchemeSettings>().SingleOrDefault(settings =>
                                                                                                    settings.PaymentId == paymentMethodId);

                ReceivedCoin[] senderCoins = null;
                await TestUtils.EventuallyAsync(async() =>
                {
                    senderCoins = await btcPayWallet.GetUnspentCoins(senderUser.DerivationScheme);
                    Assert.Contains(senderCoins, coin => coin.Value.GetValue(btcPayNetwork) == 0.026m);
                });

                var coin  = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.021m);
                var coin2 = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.022m);
                var coin3 = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.023m);
                var coin4 = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.024m);
                var coin5 = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.025m);
                var coin6 = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.026m);

                var signingKeySettings = derivationSchemeSettings.GetSigningAccountKeySettings();
                signingKeySettings.RootFingerprint =
                    senderUser.GenerateWalletResponseV.MasterHDKey.GetPublicKey().GetHDFingerPrint();

                var extKey =
                    senderUser.GenerateWalletResponseV.MasterHDKey.Derive(signingKeySettings.GetRootedKeyPath()
                                                                          .KeyPath);


                var n             = tester.ExplorerClient.Network.NBitcoinNetwork;
                var Invoice1Coin1 = tester.ExplorerClient.Network.NBitcoinNetwork.CreateTransactionBuilder()
                                    .SetChange(senderChange)
                                    .Send(parsedBip21.Address, parsedBip21.Amount)
                                    .AddCoins(coin.Coin)
                                    .AddKeys(extKey.Derive(coin.KeyPath))
                                    .SendEstimatedFees(new FeeRate(100m))
                                    .BuildTransaction(true);

                var Invoice1Coin2 = tester.ExplorerClient.Network.NBitcoinNetwork.CreateTransactionBuilder()
                                    .SetChange(senderChange)
                                    .Send(parsedBip21.Address, parsedBip21.Amount)
                                    .AddCoins(coin2.Coin)
                                    .AddKeys(extKey.Derive(coin2.KeyPath))
                                    .SendEstimatedFees(new FeeRate(100m))
                                    .BuildTransaction(true);

                var Invoice2Coin1 = tester.ExplorerClient.Network.NBitcoinNetwork.CreateTransactionBuilder()
                                    .SetChange(senderChange)
                                    .Send(secondInvoiceParsedBip21.Address, secondInvoiceParsedBip21.Amount)
                                    .AddCoins(coin.Coin)
                                    .AddKeys(extKey.Derive(coin.KeyPath))
                                    .SendEstimatedFees(new FeeRate(100m))
                                    .BuildTransaction(true);

                var Invoice2Coin2 = tester.ExplorerClient.Network.NBitcoinNetwork.CreateTransactionBuilder()
                                    .SetChange(senderChange)
                                    .Send(secondInvoiceParsedBip21.Address, secondInvoiceParsedBip21.Amount)
                                    .AddCoins(coin2.Coin)
                                    .AddKeys(extKey.Derive(coin2.KeyPath))
                                    .SendEstimatedFees(new FeeRate(100m))
                                    .BuildTransaction(true);

                //Attempt 1: Send a signed tx to invoice 1 that does not pay the invoice at all
                //Result: reject
                // Assert.False((await tester.PayTester.HttpClient.PostAsync(endpoint,
                //     new StringContent(Invoice2Coin1.ToHex(), Encoding.UTF8, "text/plain"))).IsSuccessStatusCode);

                //Attempt 2: Create two transactions using different inputs and send them to the same invoice.
                //Result: Second Tx should be rejected.
                var Invoice1Coin1ResponseTx = await senderUser.SubmitPayjoin(invoice, Invoice1Coin1, btcPayNetwork);

                await senderUser.SubmitPayjoin(invoice, Invoice1Coin1, btcPayNetwork, "already-paid");

                var contributedInputsInvoice1Coin1ResponseTx =
                    Invoice1Coin1ResponseTx.Inputs.Where(txin => coin.OutPoint != txin.PrevOut);
                Assert.Single(contributedInputsInvoice1Coin1ResponseTx);

                //Attempt 3: Send the same inputs from invoice 1 to invoice 2 while invoice 1 tx has not been broadcasted
                //Result: Reject Tx1 but accept tx 2 as its inputs were never accepted by invoice 1
                await senderUser.SubmitPayjoin(invoice2, Invoice2Coin1, btcPayNetwork, "inputs-already-used");

                var Invoice2Coin2ResponseTx = await senderUser.SubmitPayjoin(invoice2, Invoice2Coin2, btcPayNetwork);

                var contributedInputsInvoice2Coin2ResponseTx =
                    Invoice2Coin2ResponseTx.Inputs.Where(txin => coin2.OutPoint != txin.PrevOut);
                Assert.Single(contributedInputsInvoice2Coin2ResponseTx);

                //Attempt 4: Make tx that pays invoice 3 and 4 and submit to both
                //Result: reject on 4: the protocol should not worry about this complexity

                var invoice3 = receiverUser.BitPay.CreateInvoice(
                    new Invoice()
                {
                    Price = 0.01m, Currency = "BTC", FullNotifications = true
                });
                var invoice3ParsedBip21 = new BitcoinUrlBuilder(invoice3.CryptoInfo.First().PaymentUrls.BIP21,
                                                                tester.ExplorerClient.Network.NBitcoinNetwork);


                var invoice4 = receiverUser.BitPay.CreateInvoice(
                    new Invoice()
                {
                    Price = 0.01m, Currency = "BTC", FullNotifications = true
                });
                var invoice4ParsedBip21 = new BitcoinUrlBuilder(invoice4.CryptoInfo.First().PaymentUrls.BIP21,
                                                                tester.ExplorerClient.Network.NBitcoinNetwork);


                var Invoice3AndInvoice4Coin3 = tester.ExplorerClient.Network.NBitcoinNetwork.CreateTransactionBuilder()
                                               .SetChange(senderChange)
                                               .Send(invoice3ParsedBip21.Address, invoice3ParsedBip21.Amount)
                                               .Send(invoice4ParsedBip21.Address, invoice4ParsedBip21.Amount)
                                               .AddCoins(coin3.Coin)
                                               .AddKeys(extKey.Derive(coin3.KeyPath))
                                               .SendEstimatedFees(new FeeRate(100m))
                                               .BuildTransaction(true);

                await senderUser.SubmitPayjoin(invoice3, Invoice3AndInvoice4Coin3, btcPayNetwork);

                await senderUser.SubmitPayjoin(invoice4, Invoice3AndInvoice4Coin3, btcPayNetwork, "already-paid");

                //Attempt 5: Make tx that pays invoice 5 with 2 outputs
                //Result: proposed tx consolidates the outputs

                var invoice5 = receiverUser.BitPay.CreateInvoice(
                    new Invoice()
                {
                    Price = 0.01m, Currency = "BTC", FullNotifications = true
                });
                var invoice5ParsedBip21 = new BitcoinUrlBuilder(invoice5.CryptoInfo.First().PaymentUrls.BIP21,
                                                                tester.ExplorerClient.Network.NBitcoinNetwork);

                var Invoice5Coin4TxBuilder = tester.ExplorerClient.Network.NBitcoinNetwork.CreateTransactionBuilder()
                                             .SetChange(senderChange)
                                             .Send(invoice5ParsedBip21.Address, invoice5ParsedBip21.Amount / 2)
                                             .Send(invoice5ParsedBip21.Address, invoice5ParsedBip21.Amount / 2)
                                             .AddCoins(coin4.Coin)
                                             .AddKeys(extKey.Derive(coin4.KeyPath))
                                             .SendEstimatedFees(new FeeRate(100m));

                var Invoice5Coin4           = Invoice5Coin4TxBuilder.BuildTransaction(true);
                var Invoice5Coin4ResponseTx = await senderUser.SubmitPayjoin(invoice5, Invoice5Coin4, btcPayNetwork);

                Assert.Single(Invoice5Coin4ResponseTx.Outputs.To(invoice5ParsedBip21.Address));

                //Attempt 10: send tx with rbf, broadcast payjoin tx, bump the rbf payjoin , attempt to submit tx again
                //Result: same tx gets sent back

                //give the receiver some more utxos
                Assert.NotNull(await tester.ExplorerNode.SendToAddressAsync(
                                   (await btcPayWallet.ReserveAddressAsync(receiverUser.DerivationScheme)).Address,
                                   new Money(0.1m, MoneyUnit.BTC)));

                var invoice6 = receiverUser.BitPay.CreateInvoice(
                    new Invoice()
                {
                    Price = 0.01m, Currency = "BTC", FullNotifications = true
                });
                var invoice6ParsedBip21 = new BitcoinUrlBuilder(invoice6.CryptoInfo.First().PaymentUrls.BIP21,
                                                                tester.ExplorerClient.Network.NBitcoinNetwork);

                var invoice6Coin5TxBuilder = tester.ExplorerClient.Network.NBitcoinNetwork.CreateTransactionBuilder()
                                             .SetChange(senderChange)
                                             .Send(invoice6ParsedBip21.Address, invoice6ParsedBip21.Amount)
                                             .AddCoins(coin5.Coin)
                                             .AddKeys(extKey.Derive(coin5.KeyPath))
                                             .SendEstimatedFees(new FeeRate(100m))
                                             .SetLockTime(0);

                var invoice6Coin5 = invoice6Coin5TxBuilder
                                    .BuildTransaction(true);

                var Invoice6Coin5Response1Tx = await senderUser.SubmitPayjoin(invoice6, invoice6Coin5, btcPayNetwork);

                var Invoice6Coin5Response1TxSigned = invoice6Coin5TxBuilder.SignTransaction(Invoice6Coin5Response1Tx);
                //broadcast the first payjoin
                await tester.ExplorerClient.BroadcastAsync(Invoice6Coin5Response1TxSigned);

                // invoice6Coin5TxBuilder = invoice6Coin5TxBuilder.SendEstimatedFees(new FeeRate(100m));
                // var invoice6Coin5Bumpedfee = invoice6Coin5TxBuilder
                //     .BuildTransaction(true);
                //
                // var Invoice6Coin5Response3 = await tester.PayTester.HttpClient.PostAsync(invoice6Endpoint,
                //     new StringContent(invoice6Coin5Bumpedfee.ToHex(), Encoding.UTF8, "text/plain"));
                // Assert.True(Invoice6Coin5Response3.IsSuccessStatusCode);
                // var Invoice6Coin5Response3Tx =
                //     Transaction.Parse(await Invoice6Coin5Response3.Content.ReadAsStringAsync(), n);
                // Assert.True(invoice6Coin5Bumpedfee.Inputs.All(txin =>
                //     Invoice6Coin5Response3Tx.Inputs.Any(txin2 => txin2.PrevOut == txin.PrevOut)));

                //Attempt 11:
                //send tx with rbt, broadcast payjoin,
                //create tx spending the original tx inputs with rbf to self,
                //Result: the exposed utxos are priorized in the next p2ep

                //give the receiver some more utxos
                Assert.NotNull(await tester.ExplorerNode.SendToAddressAsync(
                                   (await btcPayWallet.ReserveAddressAsync(receiverUser.DerivationScheme)).Address,
                                   new Money(0.1m, MoneyUnit.BTC)));

                var invoice7 = receiverUser.BitPay.CreateInvoice(
                    new Invoice()
                {
                    Price = 0.01m, Currency = "BTC", FullNotifications = true
                });
                var invoice7ParsedBip21 = new BitcoinUrlBuilder(invoice7.CryptoInfo.First().PaymentUrls.BIP21,
                                                                tester.ExplorerClient.Network.NBitcoinNetwork);

                var invoice7Coin6TxBuilder = tester.ExplorerClient.Network.NBitcoinNetwork.CreateTransactionBuilder()
                                             .SetChange(senderChange)
                                             .Send(invoice7ParsedBip21.Address, invoice7ParsedBip21.Amount)
                                             .AddCoins(coin6.Coin)
                                             .AddKeys(extKey.Derive(coin6.KeyPath))
                                             .SendEstimatedFees(new FeeRate(100m))
                                             .SetLockTime(0);

                var invoice7Coin6Tx = invoice7Coin6TxBuilder
                                      .BuildTransaction(true);

                var invoice7Coin6Response1Tx = await senderUser.SubmitPayjoin(invoice7, invoice7Coin6Tx, btcPayNetwork);

                var Invoice7Coin6Response1TxSigned = invoice7Coin6TxBuilder.SignTransaction(invoice7Coin6Response1Tx);
                var contributedInputsInvoice7Coin6Response1TxSigned =
                    Invoice7Coin6Response1TxSigned.Inputs.Single(txin => coin6.OutPoint != txin.PrevOut);


                ////var receiverWalletPayJoinState = payJoinStateProvider.Get(receiverWalletId);
                ////Assert.Contains(receiverWalletPayJoinState.GetRecords(), item => item.InvoiceId == invoice7.Id);
                //broadcast the payjoin
                var res = (await tester.ExplorerClient.BroadcastAsync(Invoice7Coin6Response1TxSigned));
                Assert.True(res.Success);

                // Paid with coinjoin
                await TestUtils.EventuallyAsync(async() =>
                {
                    var invoiceEntity = await tester.PayTester.GetService <InvoiceRepository>().GetInvoice(invoice7.Id);
                    Assert.Equal(InvoiceStatus.Paid, invoiceEntity.Status);
                    Assert.Contains(invoiceEntity.GetPayments(), p => p.Accounted &&
                                    ((BitcoinLikePaymentData)p.GetCryptoPaymentData()).PayjoinInformation is null);
                });

                ////Assert.Contains(receiverWalletPayJoinState.GetRecords(), item => item.InvoiceId == invoice7.Id && item.TxSeen);

                var invoice7Coin6Tx2 = tester.ExplorerClient.Network.NBitcoinNetwork.CreateTransactionBuilder()
                                       .SetChange(senderChange)
                                       .AddCoins(coin6.Coin)
                                       .SendAll(senderChange)
                                       .SubtractFees()
                                       .AddKeys(extKey.Derive(coin6.KeyPath))
                                       .SendEstimatedFees(new FeeRate(200m))
                                       .SetLockTime(0)
                                       .BuildTransaction(true);

                //broadcast the "rbf cancel" tx
                res = (await tester.ExplorerClient.BroadcastAsync(invoice7Coin6Tx2));
                Assert.True(res.Success);

                // Make a block, this should put back the invoice to new
                var blockhash = tester.ExplorerNode.Generate(1)[0];
                Assert.NotNull(await tester.ExplorerNode.GetRawTransactionAsync(invoice7Coin6Tx2.GetHash(), blockhash));
                Assert.Null(await tester.ExplorerNode.GetRawTransactionAsync(Invoice7Coin6Response1TxSigned.GetHash(), blockhash, false));
                // Now we should return to New
                OutPoint ourOutpoint = null;
                await TestUtils.EventuallyAsync(async() =>
                {
                    var invoiceEntity = await tester.PayTester.GetService <InvoiceRepository>().GetInvoice(invoice7.Id);
                    Assert.Equal(InvoiceStatus.New, invoiceEntity.Status);
                    Assert.True(invoiceEntity.GetPayments().All(p => !p.Accounted));
                    ourOutpoint = invoiceEntity.GetAllBitcoinPaymentData().First().PayjoinInformation.ContributedOutPoints[0];
                });

                var payjoinRepository = tester.PayTester.GetService <PayJoinRepository>();
                // The outpoint should now be available for next pj selection
                Assert.False(await payjoinRepository.TryUnlock(ourOutpoint));
            }
        }
Ejemplo n.º 16
0
        public async Task CanManageWallet()
        {
            using (var s = SeleniumTester.Create())
            {
                await s.StartAsync();

                s.RegisterNewUser(true);
                var storeId = s.CreateNewStore();

                // In this test, we try to spend from a manual seed. We import the xpub 49'/0'/0', then try to use the seed
                // to sign the transaction
                s.GenerateWallet("BTC", "", true, false);

                //let's test quickly the receive wallet page
                s.Driver.FindElement(By.Id("Wallets")).Click();
                s.Driver.FindElement(By.LinkText("Manage")).Click();

                s.Driver.FindElement(By.Id("WalletSend")).Click();
                s.Driver.ScrollTo(By.Id("SendMenu"));
                s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
                //you cant use the Sign with NBX option without saving private keys when generating the wallet.
                Assert.DoesNotContain("nbx-seed", s.Driver.PageSource);

                s.Driver.FindElement(By.Id("WalletReceive")).Click();
                //generate a receiving address
                s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
                Assert.True(s.Driver.FindElement(By.ClassName("qr-container")).Displayed);
                var receiveAddr = s.Driver.FindElement(By.Id("vue-address")).GetAttribute("value");
                //unreserve
                s.Driver.FindElement(By.CssSelector("button[value=unreserve-current-address]")).Click();
                //generate it again, should be the same one as before as nothign got used in the meantime
                s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
                Assert.True(s.Driver.FindElement(By.ClassName("qr-container")).Displayed);
                Assert.Equal(receiveAddr, s.Driver.FindElement(By.Id("vue-address")).GetAttribute("value"));

                //send money to addr and ensure it changed
                var sess = await s.Server.ExplorerClient.CreateWebsocketNotificationSessionAsync();

                sess.ListenAllTrackedSource();
                var nextEvent = sess.NextEventAsync();
                s.Server.ExplorerNode.SendToAddress(BitcoinAddress.Create(receiveAddr, Network.RegTest),
                                                    Money.Parse("0.1"));
                await nextEvent;
                await Task.Delay(200);

                s.Driver.Navigate().Refresh();
                s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
                Assert.NotEqual(receiveAddr, s.Driver.FindElement(By.Id("vue-address")).GetAttribute("value"));
                receiveAddr = s.Driver.FindElement(By.Id("vue-address")).GetAttribute("value");

                //change the wallet and ensure old address is not there and generating a new one does not result in the prev one
                s.GoToStore(storeId.storeId);
                s.GenerateWallet("BTC", "", true, false);
                s.Driver.FindElement(By.Id("Wallets")).Click();
                s.Driver.FindElement(By.LinkText("Manage")).Click();
                s.Driver.FindElement(By.Id("WalletReceive")).Click();
                s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
                Assert.NotEqual(receiveAddr, s.Driver.FindElement(By.Id("vue-address")).GetAttribute("value"));

                var invoiceId = s.CreateInvoice(storeId.storeName);
                var invoice   = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);

                var address = invoice.EntityToDTO().Addresses["BTC"];

                //wallet should have been imported to bitcoin core wallet in watch only mode.
                var result = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));

                Assert.True(result.IsWatchOnly);
                s.GoToStore(storeId.storeId);
                var mnemonic = s.GenerateWallet("BTC", "", true, true);

                //lets import and save private keys
                var root = mnemonic.DeriveExtKey();
                invoiceId = s.CreateInvoice(storeId.storeName);
                invoice   = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);

                address = invoice.EntityToDTO().Addresses["BTC"];
                result  = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));

                //spendable from bitcoin core wallet!
                Assert.False(result.IsWatchOnly);
                var tx = s.Server.ExplorerNode.SendToAddress(BitcoinAddress.Create(address, Network.RegTest), Money.Coins(3.0m));
                s.Server.ExplorerNode.Generate(1);

                s.Driver.FindElement(By.Id("Wallets")).Click();
                s.Driver.FindElement(By.LinkText("Manage")).Click();

                s.ClickOnAllSideMenus();

                // Make sure we can rescan, because we are admin!
                s.Driver.FindElement(By.Id("WalletRescan")).ForceClick();
                Assert.Contains("The batch size make sure", s.Driver.PageSource);

                // We setup the fingerprint and the account key path
                s.Driver.FindElement(By.Id("WalletSettings")).ForceClick();
                //                s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).SendKeys("8bafd160");
                //                s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).SendKeys("m/49'/0'/0'" + Keys.Enter);

                // Check the tx sent earlier arrived
                s.Driver.FindElement(By.Id("WalletTransactions")).ForceClick();
                var walletTransactionLink = s.Driver.Url;
                Assert.Contains(tx.ToString(), s.Driver.PageSource);


                void SignWith(Mnemonic signingSource)
                {
                    // Send to bob
                    s.Driver.FindElement(By.Id("WalletSend")).Click();
                    var bob = new Key().PubKey.Hash.GetAddress(Network.RegTest);

                    SetTransactionOutput(s, 0, bob, 1);
                    s.Driver.ScrollTo(By.Id("SendMenu"));
                    s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
                    s.Driver.FindElement(By.CssSelector("button[value=seed]")).Click();

                    // Input the seed
                    s.Driver.FindElement(By.Id("SeedOrKey")).SendKeys(signingSource.ToString() + Keys.Enter);

                    // Broadcast
                    Assert.Contains(bob.ToString(), s.Driver.PageSource);
                    Assert.Contains("1.00000000", s.Driver.PageSource);
                    s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
                    Assert.Equal(walletTransactionLink, s.Driver.Url);
                }

                SignWith(mnemonic);

                s.Driver.FindElement(By.Id("Wallets")).Click();
                s.Driver.FindElement(By.LinkText("Manage")).Click();
                s.Driver.FindElement(By.Id("WalletSend")).Click();

                var jack = new Key().PubKey.Hash.GetAddress(Network.RegTest);
                SetTransactionOutput(s, 0, jack, 0.01m);
                s.Driver.ScrollTo(By.Id("SendMenu"));
                s.Driver.FindElement(By.Id("SendMenu")).ForceClick();

                s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
                Assert.Contains(jack.ToString(), s.Driver.PageSource);
                Assert.Contains("0.01000000", s.Driver.PageSource);
                s.Driver.FindElement(By.CssSelector("button[value=analyze-psbt]")).ForceClick();
                Assert.EndsWith("psbt", s.Driver.Url);
                s.Driver.FindElement(By.CssSelector("#OtherActions")).ForceClick();
                s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
                Assert.EndsWith("psbt/ready", s.Driver.Url);
                s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
                Assert.Equal(walletTransactionLink, s.Driver.Url);

                var bip21 = invoice.EntityToDTO().CryptoInfo.First().PaymentUrls.BIP21;
                //let's make bip21 more interesting
                bip21 += "&label=Solid Snake&message=Snake? Snake? SNAAAAKE!";
                var parsedBip21 = new BitcoinUrlBuilder(bip21, Network.RegTest);
                s.Driver.FindElement(By.Id("Wallets")).Click();
                s.Driver.FindElement(By.LinkText("Manage")).Click();
                s.Driver.FindElement(By.Id("WalletSend")).Click();
                s.Driver.FindElement(By.Id("bip21parse")).Click();
                s.Driver.SwitchTo().Alert().SendKeys(bip21);
                s.Driver.SwitchTo().Alert().Accept();
                s.AssertHappyMessage(StatusMessageModel.StatusSeverity.Info);
                Assert.Equal(parsedBip21.Amount.ToString(false), s.Driver.FindElement(By.Id($"Outputs_0__Amount")).GetAttribute("value"));
                Assert.Equal(parsedBip21.Address.ToString(), s.Driver.FindElement(By.Id($"Outputs_0__DestinationAddress")).GetAttribute("value"));

                s.GoToWallet(new WalletId(storeId.storeId, "BTC"), WalletsNavPages.Settings);
                var walletUrl = s.Driver.Url;

                s.Driver.FindElement(By.Id("SettingsMenu")).ForceClick();
                s.Driver.FindElement(By.CssSelector("button[value=view-seed]")).Click();

                // Seed backup page
                var recoveryPhrase = s.Driver.FindElements(By.Id("recovery-phrase")).First().GetAttribute("data-mnemonic");
                Assert.Equal(mnemonic.ToString(), recoveryPhrase);
                Assert.Contains("The recovery phrase will also be stored on the server as a hot wallet.", s.Driver.PageSource);

                // No confirmation, just a link to return to the wallet
                Assert.Empty(s.Driver.FindElements(By.Id("confirm")));
                s.Driver.FindElement(By.Id("proceed")).Click();
                Assert.Equal(walletUrl, s.Driver.Url);
            }
        }
 public static bool TryGetPayjoinEndpoint(this BitcoinUrlBuilder bip21, out Uri endpoint)
 {
     endpoint = bip21.UnknownParameters.TryGetValue($"{PayjoinClient.BIP21EndpointKey}", out var uri) ? new Uri(uri, UriKind.Absolute) : null;
     return(endpoint != null);
 }
Ejemplo n.º 18
0
        public static bool TryParseBitcoinAddress(string text, Network expectedNetwork, out BitcoinUrlBuilder url)
        {
            url = null;

            if (text is null || expectedNetwork is null)
            {
                return(false);
            }

            text = text.Trim();

            if (text.Length > 100 || text.Length < 20)
            {
                return(false);
            }

            try
            {
                var bitcoinAddress = BitcoinAddress.Create(text, expectedNetwork);
                url = new BitcoinUrlBuilder($"bitcoin:{bitcoinAddress}", expectedNetwork);
                return(true);
            }
            catch (FormatException)
            {
                return(false);
            }
        }