private T ParseJObject <T>(JObject requestObj, NBXplorerNetwork network)
 {
     if (requestObj == null)
     {
         return(default);
示例#2
0
        public async Task <IActionResult> CreatePSBT(
            [ModelBinder(BinderType = typeof(NetworkModelBinder))]
            NBXplorerNetwork network,
            [ModelBinder(BinderType = typeof(DerivationStrategyModelBinder))]
            DerivationStrategyBase strategy,
            [FromBody]
            JObject body)
        {
            if (body == null)
            {
                throw new ArgumentNullException(nameof(body));
            }
            CreatePSBTRequest request = ParseJObject <CreatePSBTRequest>(body, network);

            if (strategy == null)
            {
                throw new ArgumentNullException(nameof(strategy));
            }

            var repo      = RepositoryProvider.GetRepository(network);
            var txBuilder = request.Seed is int s?network.NBitcoinNetwork.CreateTransactionBuilder(s)
                                : network.NBitcoinNetwork.CreateTransactionBuilder();

            CreatePSBTSuggestions suggestions = null;

            if (!(request.DisableFingerprintRandomization is true) &&
                fingerprintService.GetDistribution(network) is FingerprintDistribution distribution)
            {
                suggestions ??= new CreatePSBTSuggestions();
                var known = new List <(Fingerprint feature, bool value)>();
                if (request.RBF is bool rbf)
                {
                    known.Add((Fingerprint.RBF, rbf));
                }
                if (request.DiscourageFeeSniping is bool feeSnipping)
                {
                    known.Add((Fingerprint.FeeSniping, feeSnipping));
                }
                if (request.LockTime is LockTime l)
                {
                    if (l == LockTime.Zero)
                    {
                        known.Add((Fingerprint.TimelockZero, true));
                    }
                }
                if (request.Version is uint version)
                {
                    if (version == 1)
                    {
                        known.Add((Fingerprint.V1, true));
                    }
                    if (version == 2)
                    {
                        known.Add((Fingerprint.V2, true));
                    }
                }
                known.Add((Fingerprint.SpendFromMixed, false));
                known.Add((Fingerprint.SequenceMixed, false));
                if (strategy is DirectDerivationStrategy direct)
                {
                    if (direct.Segwit)
                    {
                        known.Add((Fingerprint.SpendFromP2WPKH, true));
                    }
                    else
                    {
                        known.Add((Fingerprint.SpendFromP2PKH, true));
                    }
                }
                else
                {
                    // TODO: What if multisig? For now we consider it p2wpkh
                    known.Add((Fingerprint.SpendFromP2SHP2WPKH, true));
                }

                Fingerprint fingerprint = distribution.PickFingerprint(txBuilder.ShuffleRandom);
                try
                {
                    fingerprint = distribution.KnowingThat(known.ToArray())
                                  .PickFingerprint(txBuilder.ShuffleRandom);
                }
                catch (InvalidOperationException)
                {
                }

                request.RBF ??= fingerprint.HasFlag(Fingerprint.RBF);
                request.DiscourageFeeSniping ??= fingerprint.HasFlag(Fingerprint.FeeSniping);
                if (request.LockTime is null && fingerprint.HasFlag(Fingerprint.TimelockZero))
                {
                    request.LockTime = new LockTime(0);
                }
                if (request.Version is null && fingerprint.HasFlag(Fingerprint.V1))
                {
                    request.Version = 1;
                }
                if (request.Version is null && fingerprint.HasFlag(Fingerprint.V2))
                {
                    request.Version = 2;
                }
                suggestions.ShouldEnforceLowR = fingerprint.HasFlag(Fingerprint.LowR);
            }

            var waiter = Waiters.GetWaiter(network);

            if (waiter.NetworkInfo?.GetRelayFee() is FeeRate feeRate)
            {
                txBuilder.StandardTransactionPolicy.MinRelayTxFee = feeRate;
            }

            txBuilder.OptInRBF = !(request.RBF is false);
            if (request.LockTime is LockTime lockTime)
            {
                txBuilder.SetLockTime(lockTime);
            }
            // Discourage fee sniping.
            //
            // For a large miner the value of the transactions in the best block and
            // the mempool can exceed the cost of deliberately attempting to mine two
            // blocks to orphan the current best block. By setting nLockTime such that
            // only the next block can include the transaction, we discourage this
            // practice as the height restricted and limited blocksize gives miners
            // considering fee sniping fewer options for pulling off this attack.
            //
            // A simple way to think about this is from the wallet's point of view we
            // always want the blockchain to move forward. By setting nLockTime this
            // way we're basically making the statement that we only want this
            // transaction to appear in the next block; we don't want to potentially
            // encourage reorgs by allowing transactions to appear at lower heights
            // than the next block in forks of the best chain.
            //
            // Of course, the subsidy is high enough, and transaction volume low
            // enough, that fee sniping isn't a problem yet, but by implementing a fix
            // now we ensure code won't be written that makes assumptions about
            // nLockTime that preclude a fix later.
            else if (!(request.DiscourageFeeSniping is false))
            {
                if (waiter.State is BitcoinDWaiterState.Ready)
                {
                    int blockHeight = ChainProvider.GetChain(network).Height;
                    // Secondly occasionally randomly pick a nLockTime even further back, so
                    // that transactions that are delayed after signing for whatever reason,
                    // e.g. high-latency mix networks and some CoinJoin implementations, have
                    // better privacy.
                    if (txBuilder.ShuffleRandom.Next(0, 10) == 0)
                    {
                        blockHeight = Math.Max(0, blockHeight - txBuilder.ShuffleRandom.Next(0, 100));
                    }
                    txBuilder.SetLockTime(new LockTime(blockHeight));
                }
                else
                {
                    txBuilder.SetLockTime(new LockTime(0));
                }
            }
            var utxos = (await GetUTXOs(network.CryptoCode, strategy, null)).As <UTXOChanges>().GetUnspentUTXOs(request.MinConfirmations);
            var availableCoinsByOutpoint = utxos.ToDictionary(o => o.Outpoint);

            if (request.IncludeOnlyOutpoints != null)
            {
                var includeOnlyOutpoints = request.IncludeOnlyOutpoints.ToHashSet();
                availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => includeOnlyOutpoints.Contains(c.Key)).ToDictionary(o => o.Key, o => o.Value);
            }

            if (request.ExcludeOutpoints?.Any() is true)
            {
                var excludedOutpoints = request.ExcludeOutpoints.ToHashSet();
                availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => !excludedOutpoints.Contains(c.Key)).ToDictionary(o => o.Key, o => o.Value);
            }

            if (request.MinValue != null)
            {
                availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => request.MinValue >= (Money)c.Value.Value).ToDictionary(o => o.Key, o => o.Value);
            }

            ICoin[] coins = null;
            if (strategy.GetDerivation().Redeem != null)
            {
                // We need to add the redeem script to the coins
                var hdKeys = strategy.AsHDRedeemScriptPubKey().AsHDKeyCache();
                var arr    = availableCoinsByOutpoint.Values.ToArray();
                coins = new ICoin[arr.Length];
                // Can be very intense CPU wise
                Parallel.For(0, coins.Length, i =>
                {
                    coins[i] = ((Coin)arr[i].AsCoin()).ToScriptCoin(hdKeys.Derive(arr[i].KeyPath).ScriptPubKey);
                });
            }
            else
            {
                coins = availableCoinsByOutpoint.Values.Select(v => v.AsCoin()).ToArray();
            }
            txBuilder.AddCoins(coins);

            foreach (var dest in request.Destinations)
            {
                if (dest.SweepAll)
                {
                    try
                    {
                        txBuilder.SendAll(dest.Destination);
                    }
                    catch
                    {
                        throw new NBXplorerException(new NBXplorerError(400, "not-enough-funds", "You can't sweep funds, because you don't have any."));
                    }
                }
                else
                {
                    txBuilder.Send(dest.Destination, dest.Amount);
                    if (dest.SubstractFees)
                    {
                        try
                        {
                            txBuilder.SubtractFees();
                        }
                        catch
                        {
                            throw new NBXplorerException(new NBXplorerError(400, "not-enough-funds", "You can't substract fee on this destination, because not enough money was sent to it"));
                        }
                    }
                }
            }
            (Script ScriptPubKey, KeyPath KeyPath)change = (null, null);
            bool hasChange = false;

            if (request.ExplicitChangeAddress == null)
            {
                var keyInfo = await repo.GetUnused(strategy, DerivationFeature.Change, 0, false);

                change = (keyInfo.ScriptPubKey, keyInfo.KeyPath);
            }
            else
            {
                // The provided explicit change might have a known keyPath, let's change for it
                KeyPath keyPath  = null;
                var     keyInfos = await repo.GetKeyInformations(new[] { request.ExplicitChangeAddress.ScriptPubKey });

                if (keyInfos.TryGetValue(request.ExplicitChangeAddress.ScriptPubKey, out var kis))
                {
                    keyPath = kis.FirstOrDefault(k => k.DerivationStrategy == strategy)?.KeyPath;
                }
                change = (request.ExplicitChangeAddress.ScriptPubKey, keyPath);
            }
            txBuilder.SetChange(change.ScriptPubKey);
            PSBT psbt = null;

            try
            {
                if (request.FeePreference?.ExplicitFeeRate is FeeRate explicitFeeRate)
                {
                    txBuilder.SendEstimatedFees(explicitFeeRate);
                }
                else if (request.FeePreference?.BlockTarget is int blockTarget)
                {
                    try
                    {
                        var rate = await GetFeeRate(blockTarget, network.CryptoCode);

                        txBuilder.SendEstimatedFees(rate.FeeRate);
                    }
                    catch (NBXplorerException e) when(e.Error.Code == "fee-estimation-unavailable" && request.FeePreference?.FallbackFeeRate is FeeRate fallbackFeeRate)
                    {
                        txBuilder.SendEstimatedFees(fallbackFeeRate);
                    }
                }
                else if (request.FeePreference?.ExplicitFee is Money explicitFee)
                {
                    txBuilder.SendFees(explicitFee);
                }
                else
                {
                    try
                    {
                        var rate = await GetFeeRate(1, network.CryptoCode);

                        txBuilder.SendEstimatedFees(rate.FeeRate);
                    }
                    catch (NBXplorerException e) when(e.Error.Code == "fee-estimation-unavailable" && request.FeePreference?.FallbackFeeRate is FeeRate fallbackFeeRate)
                    {
                        txBuilder.SendEstimatedFees(fallbackFeeRate);
                    }
                }
                psbt      = txBuilder.BuildPSBT(false);
                hasChange = psbt.Outputs.Any(o => o.ScriptPubKey == change.ScriptPubKey);
            }
            catch (NotEnoughFundsException)
            {
                throw new NBXplorerException(new NBXplorerError(400, "not-enough-funds", "Not enough funds for doing this transaction"));
            }
            // We made sure we can build the PSBT, so now we can reserve the change address if we need to
            if (hasChange && request.ExplicitChangeAddress == null && request.ReserveChangeAddress)
            {
                var derivation = await repo.GetUnused(strategy, DerivationFeature.Change, 0, true);

                // In most of the time, this is the same as previously, so no need to rebuild PSBT
                if (derivation.ScriptPubKey != change.ScriptPubKey)
                {
                    change = (derivation.ScriptPubKey, derivation.KeyPath);
                    txBuilder.SetChange(change.ScriptPubKey);
                    psbt = txBuilder.BuildPSBT(false);
                }
            }

            var tx = psbt.GetOriginalTransaction();

            if (request.Version is uint v)
            {
                tx.Version = v;
            }
            psbt = txBuilder.CreatePSBTFrom(tx, false, SigHash.All);

            var update = new UpdatePSBTRequest()
            {
                DerivationScheme            = strategy,
                PSBT                        = psbt,
                RebaseKeyPaths              = request.RebaseKeyPaths,
                AlwaysIncludeNonWitnessUTXO = request.AlwaysIncludeNonWitnessUTXO,
                IncludeGlobalXPub           = request.IncludeGlobalXPub
            };

            await UpdatePSBTCore(update, network);

            var resp = new CreatePSBTResponse()
            {
                PSBT          = update.PSBT,
                ChangeAddress = hasChange ? change.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork) : null,
                Suggestions   = suggestions
            };

            return(Json(resp, network.JsonSerializerSettings));
        }
        public async Task <IActionResult> CreatePSBT(
            [ModelBinder(BinderType = typeof(NetworkModelBinder))]
            NBXplorerNetwork network,
            [ModelBinder(BinderType = typeof(DerivationStrategyModelBinder))]
            DerivationStrategyBase strategy,
            [FromBody]
            JObject body)
        {
            if (body == null)
            {
                throw new ArgumentNullException(nameof(body));
            }
            CreatePSBTRequest request = ParseJObject <CreatePSBTRequest>(body, network);

            if (strategy == null)
            {
                throw new ArgumentNullException(nameof(strategy));
            }
            var repo      = RepositoryProvider.GetRepository(network);
            var txBuilder = request.Seed is int s?network.NBitcoinNetwork.CreateTransactionBuilder(s)
                                : network.NBitcoinNetwork.CreateTransactionBuilder();

            if (Waiters.GetWaiter(network).NetworkInfo?.GetRelayFee() is FeeRate feeRate)
            {
                txBuilder.StandardTransactionPolicy.MinRelayTxFee = feeRate;
            }

            txBuilder.OptInRBF = request.RBF;
            if (request.LockTime is LockTime lockTime)
            {
                txBuilder.SetLockTime(lockTime);
                txBuilder.OptInRBF = true;
            }
            var utxos = (await GetUTXOs(network.CryptoCode, strategy, null)).As <UTXOChanges>().GetUnspentCoins(request.MinConfirmations);
            var availableCoinsByOutpoint = utxos.ToDictionary(o => o.Outpoint);

            if (request.IncludeOnlyOutpoints != null)
            {
                var includeOnlyOutpoints = request.IncludeOnlyOutpoints.ToHashSet();
                availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => includeOnlyOutpoints.Contains(c.Key)).ToDictionary(o => o.Key, o => o.Value);
            }

            if (request.ExcludeOutpoints?.Any() is true)
            {
                var excludedOutpoints = request.ExcludeOutpoints.ToHashSet();
                availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => !excludedOutpoints.Contains(c.Key)).ToDictionary(o => o.Key, o => o.Value);
            }

            if (request.MinValue != null)
            {
                availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => request.MinValue >= c.Value.Amount).ToDictionary(o => o.Key, o => o.Value);
            }
            txBuilder.AddCoins(availableCoinsByOutpoint.Values);

            foreach (var dest in request.Destinations)
            {
                if (dest.SweepAll)
                {
                    try
                    {
                        txBuilder.SendAll(dest.Destination);
                    }
                    catch
                    {
                        throw new NBXplorerException(new NBXplorerError(400, "not-enough-funds", "You can't sweep funds, because you don't have any."));
                    }
                }
                else
                {
                    txBuilder.Send(dest.Destination, dest.Amount);
                    if (dest.SubstractFees)
                    {
                        try
                        {
                            txBuilder.SubtractFees();
                        }
                        catch
                        {
                            throw new NBXplorerException(new NBXplorerError(400, "not-enough-funds", "You can't substract fee on this destination, because not enough money was sent to it"));
                        }
                    }
                }
            }
            (Script ScriptPubKey, KeyPath KeyPath)change = (null, null);
            bool hasChange = false;

            if (request.ExplicitChangeAddress == null)
            {
                var keyInfo = await repo.GetUnused(strategy, DerivationFeature.Change, 0, false);

                change = (keyInfo.ScriptPubKey, keyInfo.KeyPath);
            }
            else
            {
                // The provided explicit change might have a known keyPath, let's change for it
                KeyPath keyPath  = null;
                var     keyInfos = await repo.GetKeyInformations(new[] { request.ExplicitChangeAddress.ScriptPubKey });

                if (keyInfos.TryGetValue(request.ExplicitChangeAddress.ScriptPubKey, out var kis))
                {
                    keyPath = kis.FirstOrDefault(k => k.DerivationStrategy == strategy)?.KeyPath;
                }
                change = (request.ExplicitChangeAddress.ScriptPubKey, keyPath);
            }
            txBuilder.SetChange(change.ScriptPubKey);
            PSBT psbt = null;

            try
            {
                if (request.FeePreference?.ExplicitFeeRate is FeeRate explicitFeeRate)
                {
                    txBuilder.SendEstimatedFees(explicitFeeRate);
                }
                else if (request.FeePreference?.BlockTarget is int blockTarget)
                {
                    try
                    {
                        var rate = await GetFeeRate(blockTarget, network.CryptoCode);

                        txBuilder.SendEstimatedFees(rate.FeeRate);
                    }
                    catch (NBXplorerException e) when(e.Error.Code == "fee-estimation-unavailable" && request.FeePreference?.FallbackFeeRate is FeeRate fallbackFeeRate)
                    {
                        txBuilder.SendEstimatedFees(fallbackFeeRate);
                    }
                }
                else if (request.FeePreference?.ExplicitFee is Money explicitFee)
                {
                    txBuilder.SendFees(explicitFee);
                }
                else
                {
                    try
                    {
                        var rate = await GetFeeRate(1, network.CryptoCode);

                        txBuilder.SendEstimatedFees(rate.FeeRate);
                    }
                    catch (NBXplorerException e) when(e.Error.Code == "fee-estimation-unavailable" && request.FeePreference?.FallbackFeeRate is FeeRate fallbackFeeRate)
                    {
                        txBuilder.SendEstimatedFees(fallbackFeeRate);
                    }
                }
                psbt      = txBuilder.BuildPSBT(false);
                hasChange = psbt.Outputs.Any(o => o.ScriptPubKey == change.ScriptPubKey);
            }
            catch (NotEnoughFundsException)
            {
                throw new NBXplorerException(new NBXplorerError(400, "not-enough-funds", "Not enough funds for doing this transaction"));
            }
            // We made sure we can build the PSBT, so now we can reserve the change address if we need to
            if (hasChange && request.ExplicitChangeAddress == null && request.ReserveChangeAddress)
            {
                var derivation = await repo.GetUnused(strategy, DerivationFeature.Change, 0, true);

                // In most of the time, this is the same as previously, so no need to rebuild PSBT
                if (derivation.ScriptPubKey != change.ScriptPubKey)
                {
                    change = (derivation.ScriptPubKey, derivation.KeyPath);
                    txBuilder.SetChange(change.ScriptPubKey);
                    psbt = txBuilder.BuildPSBT(false);
                }
            }

            var tx = psbt.GetOriginalTransaction();

            if (request.Version is uint v)
            {
                tx.Version = v;
            }
            psbt = txBuilder.CreatePSBTFrom(tx, false, SigHash.All);

            await UpdatePSBTCore(new UpdatePSBTRequest()
            {
                DerivationScheme = strategy,
                PSBT             = psbt,
                RebaseKeyPaths   = request.RebaseKeyPaths
            }, network);

            var resp = new CreatePSBTResponse()
            {
                PSBT          = psbt,
                ChangeAddress = hasChange ? change.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork) : null
            };

            return(Json(resp, network.JsonSerializerSettings));
        }
示例#4
0
        public async Task <IActionResult> CreatePSBT(
            [ModelBinder(BinderType = typeof(NetworkModelBinder))]
            NBXplorerNetwork network,
            [ModelBinder(BinderType = typeof(DerivationStrategyModelBinder))]
            DerivationStrategyBase strategy,
            [FromBody]
            JObject body)
        {
            if (body == null)
            {
                throw new ArgumentNullException(nameof(body));
            }
            CreatePSBTRequest request = ParseJObject <CreatePSBTRequest>(body, network);

            if (strategy == null)
            {
                throw new ArgumentNullException(nameof(strategy));
            }
            var repo      = RepositoryProvider.GetRepository(network);
            var txBuilder = request.Seed is int s?network.NBitcoinNetwork.CreateTransactionBuilder(s)
                                : network.NBitcoinNetwork.CreateTransactionBuilder();

            if (Waiters.GetWaiter(network).NetworkInfo?.GetRelayFee() is FeeRate feeRate)
            {
                txBuilder.StandardTransactionPolicy.MinRelayTxFee = feeRate;
            }

            txBuilder.OptInRBF = request.RBF;
            if (request.LockTime is LockTime lockTime)
            {
                txBuilder.SetLockTime(lockTime);
                txBuilder.OptInRBF = true;
            }
            var utxos = (await GetUTXOs(network.CryptoCode, strategy, null)).GetUnspentCoins(request.MinConfirmations);
            var availableCoinsByOutpoint = utxos.ToDictionary(o => o.Outpoint);

            if (request.IncludeOnlyOutpoints != null)
            {
                var includeOnlyOutpoints = request.IncludeOnlyOutpoints.ToHashSet();
                availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => includeOnlyOutpoints.Contains(c.Key)).ToDictionary(o => o.Key, o => o.Value);
            }

            if (request.ExcludeOutpoints?.Any() is true)
            {
                var excludedOutpoints = request.ExcludeOutpoints.ToHashSet();
                availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => !excludedOutpoints.Contains(c.Key)).ToDictionary(o => o.Key, o => o.Value);
            }
            txBuilder.AddCoins(availableCoinsByOutpoint.Values);

            foreach (var dest in request.Destinations)
            {
                if (dest.SweepAll)
                {
                    txBuilder.SendAll(dest.Destination);
                }
                else
                {
                    txBuilder.Send(dest.Destination, dest.Amount);
                    if (dest.SubstractFees)
                    {
                        txBuilder.SubtractFees();
                    }
                }
            }
            (Script ScriptPubKey, KeyPath KeyPath)change = (null, null);
            bool hasChange = false;

            // We first build the transaction with a change which keep the length of the expected change scriptPubKey
            // This allow us to detect if there is a change later in the constructed transaction.
            // This defend against bug which can happen if one of the destination is the same as the expected change
            // This assume that a script with only 0 can't be created from a strategy, nor by passing any data to explicitChangeAddress
            if (request.ExplicitChangeAddress == null)
            {
                // The dummyScriptPubKey is necessary to know the size of the change
                var dummyScriptPubKey = utxos.FirstOrDefault()?.ScriptPubKey ?? strategy.GetDerivation(0).ScriptPubKey;
                change = (Script.FromBytesUnsafe(new byte[dummyScriptPubKey.Length]), null);
            }
            else
            {
                change = (Script.FromBytesUnsafe(new byte[request.ExplicitChangeAddress.ScriptPubKey.Length]), null);
            }
            txBuilder.SetChange(change.ScriptPubKey);
            PSBT psbt = null;

            try
            {
                if (request.FeePreference?.ExplicitFeeRate is FeeRate explicitFeeRate)
                {
                    txBuilder.SendEstimatedFees(explicitFeeRate);
                }
                else if (request.FeePreference?.BlockTarget is int blockTarget)
                {
                    try
                    {
                        var rate = await GetFeeRate(blockTarget, network.CryptoCode);

                        txBuilder.SendEstimatedFees(rate.FeeRate);
                    }
                    catch (NBXplorerException e) when(e.Error.Code == "fee-estimation-unavailable" && request.FeePreference?.FallbackFeeRate is FeeRate fallbackFeeRate)
                    {
                        txBuilder.SendEstimatedFees(fallbackFeeRate);
                    }
                }
                else if (request.FeePreference?.ExplicitFee is Money explicitFee)
                {
                    txBuilder.SendFees(explicitFee);
                }
                else
                {
                    try
                    {
                        var rate = await GetFeeRate(1, network.CryptoCode);

                        txBuilder.SendEstimatedFees(rate.FeeRate);
                    }
                    catch (NBXplorerException e) when(e.Error.Code == "fee-estimation-unavailable" && request.FeePreference?.FallbackFeeRate is FeeRate fallbackFeeRate)
                    {
                        txBuilder.SendEstimatedFees(fallbackFeeRate);
                    }
                }
                psbt      = txBuilder.BuildPSBT(false);
                hasChange = psbt.Outputs.Any(o => o.ScriptPubKey == change.ScriptPubKey);
            }
            catch (NotEnoughFundsException)
            {
                throw new NBXplorerException(new NBXplorerError(400, "not-enough-funds", "Not enough funds for doing this transaction"));
            }
            if (hasChange)             // We need to reserve an address, so we need to build again the psbt
            {
                if (request.ExplicitChangeAddress == null)
                {
                    var derivation = await repo.GetUnused(strategy, DerivationFeature.Change, 0, request.ReserveChangeAddress);

                    change = (derivation.ScriptPubKey, derivation.KeyPath);
                }
                else
                {
                    change = (request.ExplicitChangeAddress.ScriptPubKey, null);
                }
                txBuilder.SetChange(change.ScriptPubKey);
                psbt = txBuilder.BuildPSBT(false);
            }

            var tx = psbt.GetOriginalTransaction();

            if (request.Version is uint v)
            {
                tx.Version = v;
            }
            psbt = txBuilder.CreatePSBTFrom(tx, false, SigHash.All);

            // Maybe it is a change that we know about, let's search in the DB
            if (hasChange && change.KeyPath == null)
            {
                var keyInfos = await repo.GetKeyInformations(new[] { request.ExplicitChangeAddress.ScriptPubKey });

                if (keyInfos.TryGetValue(request.ExplicitChangeAddress.ScriptPubKey, out var kis))
                {
                    var ki = kis.FirstOrDefault(k => k.DerivationStrategy == strategy);
                    if (ki != null)
                    {
                        change = (change.ScriptPubKey, kis.First().KeyPath);
                    }
                }
            }

            await UpdatePSBTCore(new UpdatePSBTRequest()
            {
                DerivationScheme = strategy,
                PSBT             = psbt,
                RebaseKeyPaths   = request.RebaseKeyPaths
            }, network);

            var resp = new CreatePSBTResponse()
            {
                PSBT          = psbt,
                ChangeAddress = hasChange ? change.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork) : null
            };

            return(Json(resp, network.JsonSerializerSettings));
        }
 public bool Supports(NBXplorerNetwork network)
 {
     return(ChainConfigurations.Any(c => network.CryptoCode == c.CryptoCode));
 }
示例#6
0
 internal NRustLightningNetwork(INetworkSet networkSet, NetworkType networkType, NBXplorerNetwork nbXplorerNetwork, KeyPath baseKeyPath, string bolt11InvoicePrefix)
 {
     NbXplorerNetwork    = nbXplorerNetwork;
     BaseKeyPath         = baseKeyPath;
     BOLT11InvoicePrefix = bolt11InvoicePrefix;
     NBitcoinNetwork     = networkSet.GetNetwork(networkType);
     CryptoCode          = networkSet.CryptoCode.ToLowerInvariant();
 }
        private void StartNBXplorer()
        {
            var port = CustomServer.FreeTcpPort();
            List <(string key, string value)> keyValues = new List <(string key, string value)>();

            keyValues.Add(("conf", Path.Combine(datadir, "settings.config")));
            keyValues.Add(("datadir", datadir));
            keyValues.Add(("port", port.ToString()));
            keyValues.Add(("network", "regtest"));
            keyValues.Add(("instancename", Caller));
            keyValues.Add(("chains", CryptoCode.ToLowerInvariant()));
            keyValues.Add(("verbose", "1"));
            keyValues.Add(($"{CryptoCode.ToLowerInvariant()}rpcauth", Explorer.GetRPCAuth()));
            keyValues.Add(($"{CryptoCode.ToLowerInvariant()}rpcurl", Explorer.CreateRPCClient().Address.AbsoluteUri));
            keyValues.Add(("cachechain", "0"));
            keyValues.Add(("exposerpc", "1"));
            keyValues.Add(("rpcnotest", "1"));
            keyValues.Add(("trimevents", TrimEvents.ToString()));
            keyValues.Add(("mingapsize", "3"));
            keyValues.Add(("maxgapsize", "8"));
            keyValues.Add(($"{CryptoCode.ToLowerInvariant()}startheight", Explorer.CreateRPCClient().GetBlockCount().ToString()));
            keyValues.Add(($"{CryptoCode.ToLowerInvariant()}nodeendpoint", $"{Explorer.Endpoint.Address}:{Explorer.Endpoint.Port}"));
            keyValues.Add(("asbcnstr", AzureServiceBusTestConfig.ConnectionString));
            keyValues.Add(("asbblockq", AzureServiceBusTestConfig.NewBlockQueue));
            keyValues.Add(("asbtranq", AzureServiceBusTestConfig.NewTransactionQueue));
            keyValues.Add(("asbblockt", AzureServiceBusTestConfig.NewBlockTopic));
            keyValues.Add(("asbtrant", AzureServiceBusTestConfig.NewTransactionTopic));
            if (UseRabbitMQ)
            {
                keyValues.Add(("rmqhost", RabbitMqTestConfig.RabbitMqHostName));
                keyValues.Add(("rmqvirtual", RabbitMqTestConfig.RabbitMqVirtualHost));
                keyValues.Add(("rmquser", RabbitMqTestConfig.RabbitMqUsername));
                keyValues.Add(("rmqpass", RabbitMqTestConfig.RabbitMqPassword));
                keyValues.Add(("rmqtranex", RabbitMqTestConfig.RabbitMqTransactionExchange));
                keyValues.Add(("rmqblockex", RabbitMqTestConfig.RabbitMqBlockExchange));
            }
            var args = keyValues.SelectMany(kv => new[] { $"--{kv.key}", kv.value }
                                            .Concat(new[] { $"--{CryptoCode.ToLowerInvariant()}hastxindex" })).ToArray();

            Host = new WebHostBuilder()
                   .UseConfiguration(new DefaultConfiguration().CreateConfiguration(args))
                   .UseKestrel()
                   .ConfigureLogging(l =>
            {
                l.SetMinimumLevel(LogLevel.Information)
                .AddFilter("System.Net.Http.HttpClient", LogLevel.Error)
                .AddFilter("Microsoft", LogLevel.Error)
                .AddFilter("Hangfire", LogLevel.Error)
                .AddFilter("NBXplorer.Authentication.BasicAuthenticationHandler", LogLevel.Critical)
                .AddProvider(Logs.LogProvider);
            })
                   .UseStartup <Startup>()
                   .Build();

            RPC = ((RPCClientProvider)Host.Services.GetService(typeof(RPCClientProvider))).GetRPCClient(CryptoCode);
            NBXplorerNetwork = ((NBXplorerNetworkProvider)Host.Services.GetService(typeof(NBXplorerNetworkProvider))).GetFromCryptoCode(CryptoCode);
            var conf = (ExplorerConfiguration)Host.Services.GetService(typeof(ExplorerConfiguration));

            Host.Start();
            Configuration          = conf;
            _Client                = NBXplorerNetwork.CreateExplorerClient(Address);
            HttpClient             = ((IHttpClientFactory)Host.Services.GetService(typeof(IHttpClientFactory))).CreateClient();
            HttpClient.BaseAddress = Address;
            _Client.SetCookieAuth(Path.Combine(conf.DataDir, ".cookie"));
            Notifications = _Client.CreateLongPollingNotificationSession();
        }
示例#8
0
 public static bool TryParse(ReadOnlySpan <char> strSpan, out DerivationSchemeTrackedSource derivationSchemeTrackedSource, NBXplorerNetwork network)
 {
     if (strSpan == null)
     {
         throw new ArgumentNullException(nameof(strSpan));
     }
     if (network == null)
     {
         throw new ArgumentNullException(nameof(network));
     }
     derivationSchemeTrackedSource = null;
     if (!strSpan.StartsWith("DERIVATIONSCHEME:".AsSpan(), StringComparison.Ordinal))
     {
         return(false);
     }
     try
     {
         var factory          = network.DerivationStrategyFactory;
         var derivationScheme = factory.Parse(strSpan.Slice("DERIVATIONSCHEME:".Length).ToString());
         derivationSchemeTrackedSource = new DerivationSchemeTrackedSource(derivationScheme);
         return(true);
     }
     catch { return(false); }
 }
 public FingerprintDistribution GetDistribution(NBXplorerNetwork network)
 {
     return(data[network].Distribution ?? data[network].DefaultDistribution);
 }
示例#10
0
        public KeyPathInformation(KeyPathTemplates keyPathTemplates, KeyPath keyPath, DerivationStrategyBase derivationStrategy, NBXplorerNetwork network)
        {
            var derivation = derivationStrategy.GetDerivation(keyPath);

            ScriptPubKey       = derivation.ScriptPubKey;
            Redeem             = derivation.Redeem;
            TrackedSource      = new DerivationSchemeTrackedSource(derivationStrategy);
            DerivationStrategy = derivationStrategy;
            Feature            = keyPathTemplates.GetDerivationFeature(keyPath);
            KeyPath            = keyPath;
            Address            = network.CreateAddress(derivationStrategy, keyPath, ScriptPubKey);
        }
        private void SetEnvironment()
        {
            //CryptoCode = "LTC";
            //nodeDownloadData = NodeDownloadData.Litecoin.v0_16_3;
            //Network = NBitcoin.Altcoins.Litecoin.Instance.Regtest;

            //CryptoCode = "BCH";
            //nodeDownloadData = NodeDownloadData.BCash.v0_16_2;
            //Network = NBitcoin.Altcoins.BCash.Instance.Regtest;

            //Tests of DOGE are broken because it outpoint locking seems to work differently
            //CryptoCode = "DOGE";
            //nodeDownloadData = NodeDownloadData.Dogecoin.v1_10_0;
            //Network = NBitcoin.Altcoins.Dogecoin.Instance.Regtest;
            //RPCStringAmount = false;

            //CryptoCode = "DASH";
            //nodeDownloadData = NodeDownloadData.Dash.v0_12_2;
            //Network = NBitcoin.Altcoins.Dash.Instance.Regtest;

            //CryptoCode = "POLIS";
            //nodeDownloadData = NodeDownloadData.Polis.v1_3_1;
            //Network = NBitcoin.Altcoins.Polis.Instance.Regtest;

            //CryptoCode = "BTG";
            //nodeDownloadData = NodeDownloadData.BGold.v0_15_0;
            //Network = NBitcoin.Altcoins.BGold.Instance.Regtest;

            //CryptoCode = "MONA";
            //nodeDownloadData = NodeDownloadData.Monacoin.v0_15_1;
            //Network = NBitcoin.Altcoins.Monacoin.Instance.Regtest;

            //CryptoCode = "FTC";
            //nodeDownloadData = NodeDownloadData.Feathercoin.v0_16_0;
            //Network = NBitcoin.Altcoins.Feathercoin.Instance.Regtest;

            //CryptoCode = "UFO";
            //nodeDownloadData = NodeDownloadData.Ufo.v0_16_0;
            //Network = NBitcoin.Altcoins.Ufo.Instance.Regtest;

            //CryptoCode = "VIA";
            //nodeDownloadData = NodeDownloadData.Viacoin.v0_15_1;
            //Network = NBitcoin.Altcoins.Viacoin.Instance.Regtest;

            //CryptoCode = "GRS";
            //nodeDownloadData = NodeDownloadData.Groestlcoin.v2_16_0;
            //Network = NBitcoin.Altcoins.Groestlcoin.Instance.Regtest;

            //CryptoCode = "BTX";
            //nodeDownloadData = NodeDownloadData.Bitcore.v0_15_2;
            //Network = NBitcoin.Altcoins.Bitcore.Instance.Regtest;

            //CryptoCode = "XMCC";
            //nodeDownloadData = NodeDownloadData.Monoeci.v0_12_2_3;
            //Network = NBitcoin.Altcoins.Monoeci.Instance.Regtest;
            //RPCSupportSegwit = false;

            //CryptoCode = "GBX";
            //nodeDownloadData = NodeDownloadData.Gobyte.v0_12_2_4;
            //Network = NBitcoin.Altcoins.Gobyte.Instance.Regtest;
            //RPCSupportSegwit = false;

            //CryptoCode = "COLX";
            //nodeDownloadData = NodeDownloadData.Colossus.v1_1_1;
            //Network = NBitcoin.Altcoins.Colossus.Instance.Regtest;
            //RPCSupportSegwit = false;

            //CryptoCode = "LBTC";
            //nodeDownloadData = NodeDownloadData.Elements.v0_18_1_1;
            //NBXplorerNetwork = new NBXplorerNetwork(NBitcoin.Altcoins.Liquid.Instance, NetworkType.Regtest);
            //
            CryptoCode       = "BTC";
            nodeDownloadData = NodeDownloadData.Bitcoin.v0_18_0;
            NBXplorerNetwork = new NBXplorerNetwork(Network.RegTest.NetworkSet, NetworkType.Regtest);
        }
示例#12
0
        public static async Task <RPCCapabilities> TestRPCAsync(NBXplorerNetwork networkInfo, RPCClient rpcClient, CancellationToken cancellation)
        {
            var network = networkInfo.NBitcoinNetwork;

            Logs.Configuration.LogInformation($"{networkInfo.CryptoCode}: Testing RPC connection to " + rpcClient.Address.AbsoluteUri);

            RPCResponse blockchainInfo = null;

            try
            {
                int time = 0;
retry:
                try
                {
                    blockchainInfo = await rpcClient.SendCommandAsync("getblockchaininfo");

                    blockchainInfo.ThrowIfError();
                }
                catch (RPCException ex) when(IsTransient(ex))
                {
                    Logs.Configuration.LogInformation($"{networkInfo.CryptoCode}: Transient error '{ex.Message}', retrying soon...");
                    time++;
                    await Task.Delay(Math.Min(1000 * time, 10000), cancellation);

                    goto retry;
                }
            }
            catch (ConfigException)
            {
                throw;
            }
            catch (RPCException ex)
            {
                Logs.Configuration.LogError($"{networkInfo.CryptoCode}: Invalid response from RPC server " + ex.Message);
                throw new ConfigException();
            }
            catch (Exception ex)
            {
                Logs.Configuration.LogError($"{networkInfo.CryptoCode}: Error connecting to RPC server " + ex.Message);
                throw new ConfigException();
            }

            Logs.Configuration.LogInformation($"{networkInfo.CryptoCode}: RPC connection successful");
            if (blockchainInfo?.Result["chain"]?.Value <string>() is string rpcChain)
            {
                List <string> expectedNames = new List <string>();
                expectedNames.Add(network.ChainName.ToString());
                expectedNames.Add(network.ChainName.ToString().Replace("net", string.Empty, StringComparison.OrdinalIgnoreCase));
                if (!expectedNames.Any(e => rpcChain.Equals(e, StringComparison.OrdinalIgnoreCase)))
                {
                    Logs.Configuration.LogError($"{networkInfo.CryptoCode}: NBXplorer expected chain is '{network.ChainName}', but the RPC node is running '{rpcChain}'");
                    throw new ConfigException();
                }
            }

            var capabilities = await rpcClient.ScanRPCCapabilitiesAsync();

            if (capabilities.Version < networkInfo.MinRPCVersion)
            {
                Logs.Configuration.LogError($"{networkInfo.CryptoCode}: The minimum node version required is {networkInfo.MinRPCVersion} (detected: {capabilities.Version})");
                throw new ConfigException();
            }
            Logs.Configuration.LogInformation($"{networkInfo.CryptoCode}: Full node version detected: {capabilities.Version}");
            return(capabilities);
        }
示例#13
0
 public KeyPathInformation(Derivation derivation, DerivationSchemeTrackedSource derivationStrategy, DerivationFeature feature, KeyPath keyPath, NBXplorerNetwork network)
 {
     ScriptPubKey       = derivation.ScriptPubKey;
     Redeem             = derivation.Redeem;
     TrackedSource      = derivationStrategy;
     DerivationStrategy = derivationStrategy.DerivationStrategy;
     Feature            = feature;
     KeyPath            = keyPath;
     Address            = network.CreateAddress(derivationStrategy.DerivationStrategy, keyPath, ScriptPubKey);
 }
 public TrackedSourceJsonConverter(NBXplorerNetwork network)
 {
     Network = network;
 }
示例#15
0
        public async Task <IActionResult> CreatePSBT(
            [ModelBinder(BinderType = typeof(NetworkModelBinder))]
            NBXplorerNetwork network,
            [ModelBinder(BinderType = typeof(DerivationStrategyModelBinder))]
            DerivationStrategyBase strategy,
            [FromBody]
            JObject body)
        {
            if (body == null)
            {
                throw new ArgumentNullException(nameof(body));
            }
            CreatePSBTRequest request = ParseJObject <CreatePSBTRequest>(body, network);

            if (strategy == null)
            {
                throw new ArgumentNullException(nameof(strategy));
            }
            var repo  = RepositoryProvider.GetRepository(network);
            var utxos = await GetUTXOs(network.CryptoCode, strategy, null);

            var txBuilder = request.Seed is int s?network.NBitcoinNetwork.CreateTransactionBuilder(s)
                                : network.NBitcoinNetwork.CreateTransactionBuilder();

            if (Waiters.GetWaiter(network).NetworkInfo?.GetRelayFee() is FeeRate feeRate)
            {
                txBuilder.StandardTransactionPolicy.MinRelayTxFee = feeRate;
            }

            txBuilder.OptInRBF = request.RBF;
            if (request.LockTime is LockTime lockTime)
            {
                txBuilder.SetLockTime(lockTime);
                txBuilder.OptInRBF = true;
            }

            var availableCoinsByOutpoint = utxos.GetUnspentCoins(request.MinConfirmations).ToDictionary(o => o.Outpoint);

            if (request.IncludeOnlyOutpoints != null)
            {
                var includeOnlyOutpoints = request.IncludeOnlyOutpoints.ToHashSet();
                availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => includeOnlyOutpoints.Contains(c.Key)).ToDictionary(o => o.Key, o => o.Value);
            }

            if (request.ExcludeOutpoints?.Any() is true)
            {
                var excludedOutpoints = request.ExcludeOutpoints.ToHashSet();
                availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => !excludedOutpoints.Contains(c.Key)).ToDictionary(o => o.Key, o => o.Value);
            }
            txBuilder.AddCoins(availableCoinsByOutpoint.Values);

            foreach (var dest in request.Destinations)
            {
                if (dest.SweepAll)
                {
                    txBuilder.SendAll(dest.Destination);
                }
                else
                {
                    txBuilder.Send(dest.Destination, dest.Amount);
                    if (dest.SubstractFees)
                    {
                        txBuilder.SubtractFees();
                    }
                }
            }
            (Script ScriptPubKey, KeyPath KeyPath)change = (null, null);
            bool hasChange = false;

            // We first build the transaction with a change which keep the length of the expected change scriptPubKey
            // This allow us to detect if there is a change later in the constructed transaction.
            // This defend against bug which can happen if one of the destination is the same as the expected change
            // This assume that a script with only 0 can't be created from a strategy, nor by passing any data to explicitChangeAddress
            if (request.ExplicitChangeAddress == null)
            {
                // The dummyScriptPubKey is necessary to know the size of the change
                var dummyScriptPubKey = utxos.Unconfirmed.UTXOs.FirstOrDefault()?.ScriptPubKey ??
                                        utxos.Confirmed.UTXOs.FirstOrDefault()?.ScriptPubKey ?? strategy.Derive(0).ScriptPubKey;
                change = (Script.FromBytesUnsafe(new byte[dummyScriptPubKey.Length]), null);
            }
            else
            {
                change = (Script.FromBytesUnsafe(new byte[request.ExplicitChangeAddress.ScriptPubKey.Length]), null);
            }
            txBuilder.SetChange(change.ScriptPubKey);
            PSBT psbt = null;

            try
            {
                if (request.FeePreference?.ExplicitFeeRate is FeeRate explicitFeeRate)
                {
                    txBuilder.SendEstimatedFees(explicitFeeRate);
                }
                else if (request.FeePreference?.BlockTarget is int blockTarget)
                {
                    try
                    {
                        var rate = await GetFeeRate(blockTarget, network.CryptoCode);

                        txBuilder.SendEstimatedFees(rate.FeeRate);
                    }
                    catch (NBXplorerException e) when(e.Error.Code == "fee-estimation-unavailable" && request.FeePreference?.FallbackFeeRate is FeeRate fallbackFeeRate)
                    {
                        txBuilder.SendEstimatedFees(fallbackFeeRate);
                    }
                }
                else if (request.FeePreference?.ExplicitFee is Money explicitFee)
                {
                    txBuilder.SendFees(explicitFee);
                }
                else
                {
                    try
                    {
                        var rate = await GetFeeRate(1, network.CryptoCode);

                        txBuilder.SendEstimatedFees(rate.FeeRate);
                    }
                    catch (NBXplorerException e) when(e.Error.Code == "fee-estimation-unavailable" && request.FeePreference?.FallbackFeeRate is FeeRate fallbackFeeRate)
                    {
                        txBuilder.SendEstimatedFees(fallbackFeeRate);
                    }
                }
                psbt      = txBuilder.BuildPSBT(false);
                hasChange = psbt.Outputs.Any(o => o.ScriptPubKey == change.ScriptPubKey);
            }
            catch (NotEnoughFundsException)
            {
                throw new NBXplorerException(new NBXplorerError(400, "not-enough-funds", "Not enough funds for doing this transaction"));
            }
            if (hasChange)             // We need to reserve an address, so we need to build again the psbt
            {
                if (request.ExplicitChangeAddress == null)
                {
                    var derivation = await repo.GetUnused(strategy, DerivationFeature.Change, 0, request.ReserveChangeAddress);

                    change = (derivation.ScriptPubKey, derivation.KeyPath);
                }
                else
                {
                    change = (request.ExplicitChangeAddress.ScriptPubKey, null);
                }
                txBuilder.SetChange(change.ScriptPubKey);
                psbt = txBuilder.BuildPSBT(false);
            }

            var tx = psbt.GetOriginalTransaction();

            if (request.Version is uint v)
            {
                tx.Version = v;
            }
            psbt = txBuilder.CreatePSBTFrom(tx, false, SigHash.All);
            var outputsKeyInformations = repo.GetKeyInformations(psbt.Outputs.Where(o => !o.HDKeyPaths.Any()).Select(o => o.ScriptPubKey).ToArray());
            var utxosByOutpoint        = utxos.GetUnspentUTXOs().ToDictionary(u => u.Outpoint);

            // Maybe it is a change that we know about, let's search in the DB
            if (hasChange && change.KeyPath == null)
            {
                var keyInfos = await repo.GetKeyInformations(new[] { request.ExplicitChangeAddress.ScriptPubKey });

                if (keyInfos.TryGetValue(request.ExplicitChangeAddress.ScriptPubKey, out var kis))
                {
                    var ki = kis.FirstOrDefault(k => k.DerivationStrategy == strategy);
                    if (ki != null)
                    {
                        change = (change.ScriptPubKey, kis.First().KeyPath);
                    }
                }
            }


            var pubkeys  = strategy.GetExtPubKeys().Select(p => p.AsHDKeyCache()).ToArray();
            var keyPaths = psbt.Inputs.Select(i => utxosByOutpoint[i.PrevOut].KeyPath).ToList();

            if (hasChange && change.KeyPath != null)
            {
                keyPaths.Add(change.KeyPath);
            }
            var fps = new Dictionary <PubKey, HDFingerprint>();

            foreach (var pubkey in pubkeys)
            {
                // We derive everything the fastest way possible on multiple cores
                pubkey.Derive(keyPaths.ToArray());
                fps.TryAdd(pubkey.GetPublicKey(), pubkey.GetPublicKey().GetHDFingerPrint());
            }

            foreach (var input in psbt.Inputs)
            {
                var utxo = utxosByOutpoint[input.PrevOut];
                foreach (var pubkey in pubkeys)
                {
                    var childPubKey = pubkey.Derive(utxo.KeyPath);
                    NBitcoin.Extensions.TryAdd(input.HDKeyPaths, childPubKey.GetPublicKey(), Tuple.Create(fps[pubkey.GetPublicKey()], utxo.KeyPath));
                }
            }

            await Task.WhenAll(psbt.Inputs
                               .Select(async(input) =>
            {
                if (input.WitnessUtxo == null)                         // We need previous tx
                {
                    var prev = await repo.GetSavedTransactions(input.PrevOut.Hash);
                    if (prev?.Any() is true)
                    {
                        input.NonWitnessUtxo = prev[0].Transaction;
                    }
                }
            }).ToArray());

            var outputsKeyInformationsResult = await outputsKeyInformations;

            foreach (var output in psbt.Outputs)
            {
                foreach (var keyInfo in outputsKeyInformationsResult[output.ScriptPubKey].Where(o => o.DerivationStrategy == strategy))
                {
                    foreach (var pubkey in pubkeys)
                    {
                        var childPubKey = pubkey.Derive(keyInfo.KeyPath);
                        NBitcoin.Extensions.TryAdd(output.HDKeyPaths, childPubKey.GetPublicKey(), Tuple.Create(fps[pubkey.GetPublicKey()], keyInfo.KeyPath));
                    }
                }
            }

            if (request.RebaseKeyPaths != null)
            {
                foreach (var rebase in request.RebaseKeyPaths)
                {
                    if (rebase.AccountKeyPath == null)
                    {
                        throw new NBXplorerException(new NBXplorerError(400, "missing-parameter", "rebaseKeyPaths[].accountKeyPath is missing"));
                    }
                    if (rebase.AccountKey == null)
                    {
                        throw new NBXplorerException(new NBXplorerError(400, "missing-parameter", "rebaseKeyPaths[].accountKey is missing"));
                    }
                    psbt.RebaseKeyPaths(rebase.AccountKey, rebase.AccountKeyPath, rebase.MasterFingerprint);
                }
            }

            var resp = new CreatePSBTResponse()
            {
                PSBT          = psbt,
                ChangeAddress = hasChange ? change.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork) : null
            };

            return(Json(resp, network.JsonSerializerSettings));
        }
示例#16
0
 public BitcoinDStateChangedEvent(NBXplorerNetwork network, BitcoinDWaiterState oldState, BitcoinDWaiterState newState)
 {
     OldState = oldState;
     NewState = newState;
     Network  = network;
 }