public Task <CreateTransactionResponse> GetTransferTransaction(BitcoinAddress sourceAddress,
                                                                       BitcoinAddress destAddress, decimal amount, IAsset asset, Guid transactionId, bool shouldReserveFee = false)
        {
            return(Retry.Try(async() =>
            {
                var context = new TransactionBuildContext(_connectionParams.Network, _pregeneratedOutputsQueueFactory);

                return await context.Build(async() =>
                {
                    var builder = new TransactionBuilder();

                    await TransferOneDirection(builder, context, sourceAddress, amount, asset, destAddress);

                    await _transactionBuildHelper.AddFee(builder, context);

                    var buildedTransaction = builder.BuildTransaction(true);

                    await SaveSpentOutputs(transactionId, buildedTransaction);

                    await _signRequestRepository.InsertTransactionId(transactionId);

                    await SaveNewOutputs(transactionId, buildedTransaction, context);

                    if (shouldReserveFee)
                    {
                        await _feeReserveMonitoringWriter.AddTransactionFeeReserve(transactionId, context.FeeCoins);
                    }

                    return new CreateTransactionResponse(buildedTransaction.ToHex(), transactionId);
                });
            }, exception => (exception as BackendException)?.Code == ErrorCode.TransactionConcurrentInputsProblem, 3, _log));
        }
        public Task <CreateTransactionResponse> GetSwapTransaction(BitcoinAddress address1, decimal amount1,
                                                                   IAsset asset1, BitcoinAddress address2, decimal amount2, IAsset asset2, Guid transactionId)
        {
            return(Retry.Try(async() =>
            {
                var context = new TransactionBuildContext(_connectionParams.Network, _pregeneratedOutputsQueueFactory);

                return await context.Build(async() =>
                {
                    var builder = new TransactionBuilder();
                    await TransferOneDirection(builder, context, address1, amount1, asset1, address2);
                    await TransferOneDirection(builder, context, address2, amount2, asset2, address1);

                    await _transactionBuildHelper.AddFee(builder, context);

                    var buildedTransaction = builder.BuildTransaction(true);

                    await SaveSpentOutputs(transactionId, buildedTransaction);

                    await _signRequestRepository.InsertTransactionId(transactionId);

                    await SaveNewOutputs(transactionId, buildedTransaction, context);

                    return new CreateTransactionResponse(buildedTransaction.ToHex(), transactionId);
                });
            }, exception => (exception as BackendException)?.Code == ErrorCode.TransactionConcurrentInputsProblem, 3, _log));
        }
        public async Task <string> BroadcastCommitment(string clientPubKey, IAsset asset, string transactionHex)
        {
            var address = await _multisigService.GetMultisig(clientPubKey);

            if (address == null)
            {
                throw new BackendException($"Client {clientPubKey} is not registered", ErrorCode.BadInputParameter);
            }

            var channel = await _offchainChannelRepository.GetChannel(address.MultisigAddress, asset.Id);

            if (channel == null)
            {
                throw new BackendException("Channel is not found", ErrorCode.ShouldOpenNewChannel);
            }
            if (!channel.IsBroadcasted)
            {
                throw new BackendException("Channel is not finalized", ErrorCode.ChannelNotFinalized);
            }

            var commitment = await _commitmentRepository.GetCommitment(address.MultisigAddress, asset.Id, transactionHex);

            if (commitment == null)
            {
                throw new BackendException("Commitment is not found", ErrorCode.CommitmentNotFound);
            }
            if (commitment.ChannelId != channel.ChannelId)
            {
                throw new BackendException("Commitment is expired", ErrorCode.CommitmentExpired);
            }

            var lastCommitment = await _commitmentRepository.GetLastCommitment(address.MultisigAddress, asset.Id, commitment.Type);

            if (commitment.CommitmentId != lastCommitment.CommitmentId)
            {
                throw new BackendException("Commitment is expired", ErrorCode.CommitmentExpired);
            }

            TransactionBuildContext context = new TransactionBuildContext(_connectionParams.Network, _pregeneratedOutputsQueueFactory);

            return(await context.Build(async() =>
            {
                var transaction = new Transaction(transactionHex);
                await _transactionBuildHelper.AddFee(transaction, context);

                var signed = await _signatureApiProvider.SignTransaction(transaction.ToHex());
                var signedTr = new Transaction(signed);

                await _rpcBitcoinClient.BroadcastTransaction(signedTr, commitment.CommitmentId);

                await CloseChannel(commitment);

                return signedTr.GetHash().ToString();
            }));
        }
        public Task <CreateTransactionResponse> GetDestroyTransaction(BitcoinAddress bitcoinAddres, decimal modelAmount, IAsset asset, Guid transactionId)
        {
            return(Retry.Try(async() =>
            {
                var context = new TransactionBuildContext(_connectionParams.Network, _pregeneratedOutputsQueueFactory);

                return await context.Build(async() =>
                {
                    var builder = new TransactionBuilder();

                    var assetId = new BitcoinAssetId(asset.BlockChainAssetId, _connectionParams.Network).AssetId;
                    var coins =
                        (await _bitcoinOutputsService.GetColoredUnspentOutputs(bitcoinAddres.ToString(), assetId)).ToList();

                    builder.SetChange(bitcoinAddres, ChangeType.Colored);
                    builder.AddCoins(coins);

                    var assetMoney = new AssetMoney(assetId, modelAmount, asset.MultiplierPower);

                    var changeAddress = BitcoinAddress.Create(_baseSettings.ChangeAddress, _connectionParams.Network);

                    _transactionBuildHelper.SendAssetWithChange(builder, context, coins, changeAddress, assetMoney, bitcoinAddres);

                    await _transactionBuildHelper.AddFee(builder, context);

                    var tx = builder.BuildTransaction(true);

                    uint markerPosition;
                    var colorMarker = ColorMarker.Get(tx, out markerPosition);

                    for (var i = 0; i < colorMarker.Quantities.Length; i++)
                    {
                        if ((long)colorMarker.Quantities[i] == assetMoney.Quantity &&
                            tx.Outputs[i + 1].ScriptPubKey.GetDestinationAddress(_connectionParams.Network) == changeAddress)
                        {
                            colorMarker.Quantities[i] = 0;
                            break;
                        }
                    }

                    tx.Outputs[markerPosition].ScriptPubKey = colorMarker.GetScript();

                    await SaveSpentOutputs(transactionId, tx);

                    await _signRequestRepository.InsertTransactionId(transactionId);

                    await SaveNewOutputs(transactionId, tx, context);

                    return new CreateTransactionResponse(tx.ToHex(), transactionId);
                });
            }, exception => (exception as BackendException)?.Code == ErrorCode.TransactionConcurrentInputsProblem, 3, _log));
        }
        public Task <CreateTransactionResponse> GetTransferAllTransaction(BitcoinAddress @from, BitcoinAddress to, Guid transactionId)
        {
            return(Retry.Try(async() =>
            {
                var context = new TransactionBuildContext(_connectionParams.Network, _pregeneratedOutputsQueueFactory);

                return await context.Build(async() =>
                {
                    var builder = new TransactionBuilder();
                    var uncoloredCoins = (await _bitcoinOutputsService.GetUncoloredUnspentOutputs(from.ToString())).ToList();
                    var coloredCoins = (await _bitcoinOutputsService.GetColoredUnspentOutputs(from.ToString())).ToList();

                    if (uncoloredCoins.Count == 0 && coloredCoins.Count == 0)
                    {
                        throw new BackendException("Address has no unspent outputs", ErrorCode.NoCoinsFound);
                    }

                    if (uncoloredCoins.Count > 0)
                    {
                        _transactionBuildHelper.SendWithChange(builder, context, uncoloredCoins, to, uncoloredCoins.Sum(o => o.TxOut.Value), from);
                    }
                    foreach (var assetGroup in coloredCoins.GroupBy(o => o.AssetId))
                    {
                        var sum = new AssetMoney(assetGroup.Key);
                        foreach (var coloredCoin in assetGroup)
                        {
                            sum += coloredCoin.Amount;
                        }

                        _transactionBuildHelper.SendAssetWithChange(builder, context, assetGroup.ToList(), to, sum, from);
                    }
                    await _transactionBuildHelper.AddFee(builder, context);

                    var buildedTransaction = builder.BuildTransaction(true);

                    await SaveSpentOutputs(transactionId, buildedTransaction);

                    await _signRequestRepository.InsertTransactionId(transactionId);

                    await SaveNewOutputs(transactionId, buildedTransaction, context);

                    return new CreateTransactionResponse(buildedTransaction.ToHex(), transactionId);
                });
            }, exception => (exception as BackendException)?.Code == ErrorCode.TransactionConcurrentInputsProblem, 3, _log));
        }
        public Task <CreateTransactionResponse> GetIssueTransaction(BitcoinAddress bitcoinAddres, decimal amount, IAsset asset, Guid transactionId)
        {
            return(Retry.Try(async() =>
            {
                var context = new TransactionBuildContext(_connectionParams.Network, _pregeneratedOutputsQueueFactory);

                return await context.Build(async() =>
                {
                    var builder = new TransactionBuilder();
                    var queue = _pregeneratedOutputsQueueFactory.Create(asset.BlockChainAssetId);

                    var coin = await queue.DequeueCoin();

                    try
                    {
                        var issueCoin = new IssuanceCoin(coin)
                        {
                            DefinitionUrl = new Uri(asset.DefinitionUrl)
                        };

                        var assetId = new BitcoinAssetId(asset.BlockChainAssetId, _connectionParams.Network).AssetId;

                        builder.AddCoins(issueCoin)
                        .IssueAsset(bitcoinAddres, new AssetMoney(assetId, amount, asset.MultiplierPower));
                        context.IssueAsset(assetId);

                        await _transactionBuildHelper.AddFee(builder, context);

                        var buildedTransaction = builder.BuildTransaction(true);

                        await SaveSpentOutputs(transactionId, buildedTransaction);

                        await SaveNewOutputs(transactionId, buildedTransaction, context);

                        return new CreateTransactionResponse(buildedTransaction.ToHex(), transactionId);
                    }
                    catch (Exception)
                    {
                        await queue.EnqueueOutputs(coin);
                        throw;
                    }
                });
            }, exception => (exception as BackendException)?.Code == ErrorCode.TransactionConcurrentInputsProblem, 3, _log));
        }
        public async Task SpendCommitmemtByMultisig(ICommitment commitment, ICoin spendingCoin, string destination)
        {
            TransactionBuildContext context = new TransactionBuildContext(_connectionParams.Network, _pregeneratedOutputsQueueFactory);

            var destinationAddress = BitcoinAddress.Create(destination);

            await context.Build(async() =>
            {
                TransactionBuilder builder = new TransactionBuilder();
                builder.AddCoins(spendingCoin);
                if (OpenAssetsHelper.IsBitcoin(commitment.AssetId))
                {
                    builder.Send(destinationAddress, spendingCoin.Amount);
                }
                else
                {
                    builder.SendAsset(destinationAddress, ((ColoredCoin)spendingCoin).Amount);
                }
                await _transactionBuildHelper.AddFee(builder, context);

                var tr = builder.BuildTransaction(false);

                var scriptParams = new OffchainScriptParams
                {
                    IsMultisig   = true,
                    RedeemScript = commitment.LockedScript.ToScript().ToBytes(),
                    Pushes       = new[] { new byte[0], new byte[0], new byte[0] }
                };

                tr.Inputs[0].ScriptSig = OffchainScriptCommitmentTemplate.GenerateScriptSig(scriptParams);

                var signed = await _signatureApiProvider.SignTransaction(tr.ToHex());

                var signedTr = new Transaction(signed);
                var id       = Guid.NewGuid();
                await _rpcBitcoinClient.BroadcastTransaction(signedTr, id);

                await _lykkeTransactionBuilderService.SaveSpentOutputs(id, signedTr);

                return(Task.CompletedTask);
            });
        }
        public async Task <string> CreateUnsignedChannel(string clientPubKey, string hotWalletPubKey, decimal clientAmount, decimal hubAmount, IAsset asset)
        {
            var address = await _multisigService.GetMultisig(clientPubKey);

            if (address == null)
            {
                throw new BackendException($"Client {clientPubKey} is not registered", ErrorCode.BadInputParameter);
            }

            var multisig = new BitcoinScriptAddress(address.MultisigAddress, _connectionParams.Network);

            var clientAddress    = new PubKey(clientPubKey).GetAddress(_connectionParams.Network);
            var hotWalletAddress = new PubKey(hotWalletPubKey).GetAddress(_connectionParams.Network);

            TransactionBuildContext context = new TransactionBuildContext(_connectionParams.Network, _pregeneratedOutputsQueueFactory);

            var currentChannel = await _offchainChannelRepository.GetChannel(address.MultisigAddress, asset.Id);

            if (currentChannel != null && !currentChannel.IsBroadcasted)
            {
                throw new BackendException("There is another pending channel setup", ErrorCode.AnotherChannelSetupExists);
            }

            return(await context.Build(async() =>
            {
                var builder = new TransactionBuilder();

                var multisigAmount = await SendToMultisig(multisig, multisig, asset, builder, context, -1);
                decimal clientChannelAmount, hubChannelAmount;
                if (currentChannel == null)
                {
                    clientChannelAmount = Math.Max(0, clientAmount - multisigAmount);
                    hubChannelAmount = hubAmount;

                    await SendToMultisig(clientAddress, multisig, asset, builder, context, clientChannelAmount);
                    await SendToMultisig(hotWalletAddress, multisig, asset, builder, context, hubAmount);

                    clientChannelAmount += multisigAmount;
                }
                else
                {
                    clientChannelAmount = Math.Max(0, clientAmount - currentChannel.ClientAmount);
                    hubChannelAmount = Math.Max(0, hubAmount - currentChannel.HubAmount);

                    await SendToMultisig(clientAddress, multisig, asset, builder, context, clientChannelAmount);
                    await SendToMultisig(hotWalletAddress, multisig, asset, builder, context, hubChannelAmount);

                    clientChannelAmount += currentChannel.ClientAmount;
                    hubChannelAmount += currentChannel.HubAmount;
                }

                await _transactionBuildHelper.AddFee(builder, context);
                var tr = builder.BuildTransaction(true);

                _transactionBuildHelper.AggregateOutputs(tr);

                var hex = tr.ToHex();
                var channel = await _offchainChannelRepository.CreateChannel(multisig.ToString(), asset.Id, hex, clientChannelAmount, hubChannelAmount);

                await _broadcastedOutputRepository.InsertOutputs(OpenAssetsHelper.OrderBasedColoringOutputs(tr, context)
                                                                 .Select(o => new BroadcastedOutput(o, channel.ChannelId, _connectionParams.Network)));

                return hex;
            }));
        }