Beispiel #1
0
        private async Task TransferOneDirection(TransactionBuilder builder, TransactionBuildContext context,
                                                BitcoinAddress @from, decimal amount, IAsset asset, BitcoinAddress to, bool addDust = true, bool sendDust = false)
        {
            var fromStr = from.ToString();

            if (OpenAssetsHelper.IsBitcoin(asset.Id))
            {
                var coins   = (await _bitcoinOutputsService.GetUncoloredUnspentOutputs(fromStr)).ToList();
                var balance = coins.Cast <Coin>().Select(o => o.Amount).DefaultIfEmpty().Sum(o => o?.ToDecimal(MoneyUnit.BTC) ?? 0);
                if (sendDust && balance > amount &&
                    balance - amount < new TxOut(Money.Zero, from).GetDustThreshold(builder.StandardTransactionPolicy.MinRelayTxFee).ToDecimal(MoneyUnit.BTC))
                {
                    amount = balance;
                }
                await _transactionBuildHelper.SendWithChange(builder, context, coins, to, new Money(amount, MoneyUnit.BTC),
                                                             from, addDust);
            }
            else
            {
                var assetIdObj  = new BitcoinAssetId(asset.BlockChainAssetId, _connectionParams.Network).AssetId;
                var assetAmount = new AssetMoney(assetIdObj, amount, asset.MultiplierPower);

                var coins = (await _bitcoinOutputsService.GetColoredUnspentOutputs(fromStr, assetIdObj)).ToList();
                _transactionBuildHelper.SendAssetWithChange(builder, context, coins, to, assetAmount, @from);
            }
        }
        private ICoin FindCoin(Transaction tr, string multisig, string walletRedeemScript, decimal amount, IAsset asset)
        {
            if (OpenAssetsHelper.IsBitcoin(asset.Id))
            {
                var money = new Money(amount, MoneyUnit.BTC);
                return(tr.Outputs.AsCoins().FirstOrDefault(o => o.Amount == money &&
                                                           o.ScriptPubKey.GetDestinationAddress(_connectionParams.Network).ToString() == multisig));
            }
            var  assetMoney = new AssetMoney(new BitcoinAssetId(asset.BlockChainAssetId), amount, asset.MultiplierPower);
            uint markerPosition;
            var  marker = ColorMarker.Get(tr, out markerPosition);
            var  found  = tr.Outputs.AsIndexedOutputs()
                          .FirstOrDefault(o => o.TxOut.ScriptPubKey.GetDestinationAddress(_connectionParams.Network)?.ToString() == multisig &&
                                          o.N > markerPosition && marker.Quantities[o.N - markerPosition - 1] == (ulong)assetMoney.Quantity);

            return(found?.ToCoin().ToScriptCoin(new Script(walletRedeemScript)).ToColoredCoin(assetMoney));
        }
        private async Task <decimal> SendToMultisig(BitcoinAddress @from, BitcoinAddress toMultisig, IAsset assetEntity, TransactionBuilder builder, TransactionBuildContext context, decimal amount)
        {
            if (OpenAssetsHelper.IsBitcoin(assetEntity.Id))
            {
                Money sendAmount;
                var   unspentOutputs = (await _bitcoinOutputsService.GetUncoloredUnspentOutputs(from.ToString())).ToList();

                if (amount < 0)
                {
                    sendAmount = unspentOutputs.OfType <Coin>().DefaultIfEmpty().Sum(o => o.Amount);
                }
                else
                {
                    sendAmount = Money.FromUnit(amount, MoneyUnit.BTC);
                }

                if (sendAmount > 0)
                {
                    _transactionBuildHelper.SendWithChange(builder, context, unspentOutputs, toMultisig, sendAmount, from);
                }

                return(sendAmount.ToDecimal(MoneyUnit.BTC));
            }
            else
            {
                var  asset = new BitcoinAssetId(assetEntity.BlockChainAssetId, _connectionParams.Network).AssetId;
                long sendAmount;

                var unspentOutputs = (await _bitcoinOutputsService.GetColoredUnspentOutputs(from.ToString(), asset)).ToList();
                if (amount < 0)
                {
                    sendAmount = unspentOutputs.Sum(o => o.Amount.Quantity);
                }
                else
                {
                    sendAmount = new AssetMoney(asset, amount, assetEntity.MultiplierPower).Quantity;
                }
                if (sendAmount > 0)
                {
                    _transactionBuildHelper.SendAssetWithChange(builder, context, unspentOutputs,
                                                                toMultisig, new AssetMoney(asset, sendAmount), @from);
                }

                return(new AssetMoney(asset, sendAmount).ToDecimal(assetEntity.MultiplierPower));
            }
        }
Beispiel #4
0
        public async Task <IActionResult> Cashout([FromBody] CashoutRequest model)
        {
            model.DestinationAddress = model.DestinationAddress.Trim('\n', ' ', '\t');

            if (model.Amount <= 0)
            {
                throw new BackendException("Amount can't be less or equal to zero", ErrorCode.BadInputParameter);
            }

            await ValidateAddress(model.DestinationAddress, false);

            var asset = await _assetRepository.GetItemAsync(model.Asset);

            if (asset == null)
            {
                throw new BackendException("Provided asset is missing in database", ErrorCode.AssetNotFound);
            }

            var transactionId = await _builder.AddTransactionId(model.TransactionId, $"Cashout: {model.ToJson()}");

            if (OpenAssetsHelper.IsBitcoin(model.Asset))
            {
                await _cashoutRequestRepository.CreateCashoutRequest(transactionId, model.Amount, model.DestinationAddress);
            }
            else
            {
                var assetSetting = await _assetSettingCache.GetItemAsync(asset.Id);

                var hotWallet = !string.IsNullOrEmpty(assetSetting.ChangeWallet)
                    ? assetSetting.ChangeWallet
                    : assetSetting.HotWallet;

                await _transactionQueueWriter.AddCommand(transactionId, TransactionCommandType.Transfer, new TransferCommand
                {
                    Amount             = model.Amount,
                    SourceAddress      = hotWallet,
                    Asset              = model.Asset,
                    DestinationAddress = model.DestinationAddress
                }.ToJson());
            }
            return(Ok(new TransactionIdResponse
            {
                TransactionId = transactionId
            }));
        }
        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);
            });
        }
        private async Task TransferOneDirection(TransactionBuilder builder, TransactionBuildContext context,
                                                BitcoinAddress @from, decimal amount, IAsset asset, BitcoinAddress to)
        {
            var fromStr = from.ToString();

            if (OpenAssetsHelper.IsBitcoin(asset.Id))
            {
                var coins = (await _bitcoinOutputsService.GetUncoloredUnspentOutputs(fromStr)).ToList();
                _transactionBuildHelper.SendWithChange(builder, context, coins, to, new Money(amount, MoneyUnit.BTC),
                                                       from);
            }
            else
            {
                var assetIdObj  = new BitcoinAssetId(asset.BlockChainAssetId, _connectionParams.Network).AssetId;
                var assetAmount = new AssetMoney(assetIdObj, amount, asset.MultiplierPower);

                var coins = (await _bitcoinOutputsService.GetColoredUnspentOutputs(fromStr, assetIdObj)).ToList();
                _transactionBuildHelper.SendAssetWithChange(builder, context, coins, to, assetAmount, @from);
            }
        }
        public async Task <CommandHandlingResult> Handle(StartCashoutCommand command,
                                                         IEventPublisher eventPublisher)
        {
            var address = command.Address.Trim('\n', ' ', '\t');

            try
            {
                var transactionId = await _builder.AddTransactionId(command.Id, $"Cashout: {command.ToJson()}");

                if (OpenAssetsHelper.IsBitcoin(command.AssetId))
                {
                    await _cashoutRequestRepository.CreateCashoutRequest(transactionId, command.Amount, address);
                }
                else
                {
                    var assetSetting = await _assetSettingCache.GetItemAsync(command.AssetId);

                    var hotWallet = !string.IsNullOrEmpty(assetSetting.ChangeWallet)
                        ? assetSetting.ChangeWallet
                        : assetSetting.HotWallet;

                    await _transactionQueueWriter.AddCommand(transactionId, TransactionCommandType.Transfer, new TransferCommand
                    {
                        Amount             = command.Amount,
                        SourceAddress      = hotWallet,
                        Asset              = command.AssetId,
                        DestinationAddress = command.Address
                    }.ToJson());
                }
            }
            catch (BackendException ex) when(ex.Code == ErrorCode.DuplicateTransactionId)
            {
                _logger.WriteWarning(nameof(CashinCommandHandler), nameof(Handle), $"Duplicated id: {command.Id}");
            }

            return(CommandHandlingResult.Ok());
        }
Beispiel #8
0
        private async Task GenerateIssueAllowedCoins()
        {
            foreach (var asset in await _assetRepostory.Values())
            {
                if (OpenAssetsHelper.IsBitcoin(asset.Id) || OpenAssetsHelper.IsLkk(asset.Id) || !asset.IssueAllowed)
                {
                    continue;
                }

                try
                {
                    var setting = await GetAssetSetting(asset.Id);

                    if (setting.HotWallet != setting.ChangeWallet)
                    {
                        continue;
                    }

                    var hotWallet = OpenAssetsHelper.ParseAddress(setting.HotWallet);
                    var assetId   = new BitcoinAssetId(asset.BlockChainAssetId).AssetId;

                    var coins = await _bitcoinOutputsService.GetColoredUnspentOutputs(setting.HotWallet, assetId);

                    var outputSize = new AssetMoney(assetId, setting.OutputSize, asset.MultiplierPower);

                    await _logger.WriteInfoAsync("GenerateOffchainOutputsFunction", "GenerateIssueAllowedCoins", "AssetId " + asset.Id, "Start process");

                    var existingCoinsCount = coins.Count(o => o.Amount <= outputSize && o.Amount * 2 > outputSize);

                    if (existingCoinsCount > setting.MinOutputsCount)
                    {
                        continue;
                    }

                    var generateCnt = setting.MaxOutputsCount - existingCoinsCount;
                    var generated   = 0;
                    while (generated < generateCnt)
                    {
                        var outputsCount = Math.Min(setting.MaxOutputsCountInTx, generateCnt - generated);

                        var context = _transactionBuildContextFactory.Create(_connectionParams.Network);

                        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)
                                };

                                builder.AddCoins(issueCoin);

                                for (var i = 0; i < outputsCount; i++)
                                {
                                    builder.IssueAsset(hotWallet, outputSize);
                                }
                                context.IssueAsset(assetId);
                                await _transactionBuildHelper.AddFee(builder, context);

                                var tr = builder.BuildTransaction(true);

                                await SignAndBroadcastTransaction(tr, context);

                                return("");
                            }
                            catch (Exception)
                            {
                                await queue.EnqueueOutputs(coin);

                                throw;
                            }
                        });

                        generated += outputsCount;
                    }
                }
                catch (Exception ex)
                {
                    await _logger.WriteWarningAsync("GenerateOffchainOutputsFunction", "GenerateIssueAllowedCoins", "AssetId " + asset.Id, ex);
                }
                finally
                {
                    await _logger.WriteInfoAsync("GenerateOffchainOutputsFunction", "GenerateIssueAllowedCoins", "AssetId " + asset.Id, "End process");
                }
            }
        }
Beispiel #9
0
        public async Task <IActionResult> CreateCashout([FromBody] TransferRequest model)
        {
            if (model.Amount <= 0)
            {
                throw new BackendException("Amount can't be less or equal to zero", ErrorCode.BadInputParameter);
            }

            var sourceAddress = OpenAssetsHelper.ParseAddress(model.SourceAddress);

            if (sourceAddress == null)
            {
                throw new BackendException("Invalid source address provided", ErrorCode.InvalidAddress);
            }

            var destAddress = OpenAssetsHelper.ParseAddress(model.DestinationAddress);

            if (destAddress == null)
            {
                throw new BackendException("Invalid destination address provided", ErrorCode.InvalidAddress);
            }

            var asset = await _assetRepository.GetAssetById(model.Asset);

            if (asset == null)
            {
                throw new BackendException("Provided asset is missing in database", ErrorCode.AssetNotFound);
            }

            if (model.Fee.GetValueOrDefault() < 0)
            {
                throw new BackendException("Fee must be greater than or equal to zero", ErrorCode.BadInputParameter);
            }

            if (model.Amount <= model.Fee.GetValueOrDefault())
            {
                throw new BackendException("Amount is less than fee", ErrorCode.BadInputParameter);
            }

            var transactionId = await _builder.AddTransactionId(model.TransactionId, model.ToJson());

            CreateTransactionResponse createTransactionResponse;

            if (OpenAssetsHelper.IsBitcoin(asset.Id) && model.Fee.HasValue)
            {
                createTransactionResponse = await _builder.GetPrivateTransferTransaction(sourceAddress, destAddress, model.Amount,
                                                                                         model.Fee.Value, transactionId);

                await _transactionSignRequestRepository.DoNotSign(transactionId);
            }
            else
            {
                createTransactionResponse = await _builder.GetTransferTransaction(sourceAddress, destAddress, model.Amount, asset, transactionId, true, true);
            }

            await _transactionBlobStorage.AddOrReplaceTransaction(transactionId, TransactionBlobType.Initial, createTransactionResponse.Transaction);

            return(Ok(new TransactionResponse
            {
                Transaction = createTransactionResponse.Transaction,
                TransactionId = createTransactionResponse.TransactionId,
                Fee = (createTransactionResponse as PrivateTransferResponse)?.Fee ?? 0
            }));
        }
        private CreationCommitmentResult CreateCommitmentTransaction(IWalletAddress wallet, PubKey lockedPubKey, PubKey unlockedPubKey, PubKey revokePubKey, PubKey multisigPairPubKey,
                                                                     IAsset asset, decimal lockedAmount, decimal unlockedAmount, string channelTr)
        {
            var multisig  = new BitcoinScriptAddress(wallet.MultisigAddress, _connectionParams.Network);
            var channel   = new Transaction(channelTr);
            var spendCoin = FindCoin(channel, multisig.ToString(), wallet.RedeemScript, lockedAmount + unlockedAmount, asset);

            if (spendCoin == null)
            {
                throw new BackendException($"Not found output in setup channel with amount {lockedAmount + unlockedAmount}", ErrorCode.NoCoinsFound);
            }

            TransactionBuilder builder = new TransactionBuilder();

            builder.AddCoins(spendCoin);
            long additionalBtc = 0;
            var  script        = CreateOffchainScript(multisigPairPubKey, revokePubKey, lockedPubKey);

            var unlockedAddress = unlockedPubKey.GetAddress(_connectionParams.Network);
            var lockedAddress   = script.GetScriptAddress(_connectionParams.Network);

            if (OpenAssetsHelper.IsBitcoin(asset.Id))
            {
                if (unlockedAmount > 0)
                {
                    builder.Send(unlockedAddress, new Money(unlockedAmount, MoneyUnit.BTC));
                }
                if (lockedAmount > 0)
                {
                    builder.Send(lockedAddress, new Money(lockedAmount, MoneyUnit.BTC));
                }
            }
            else
            {
                var sendAmount = ((ColoredCoin)spendCoin).Bearer.Amount;
                var dustAmount = 0L;
                var assetId    = new BitcoinAssetId(asset.BlockChainAssetId).AssetId;
                if (unlockedAmount > 0)
                {
                    builder.SendAsset(unlockedAddress, new AssetMoney(assetId, unlockedAmount, asset.MultiplierPower));
                    dustAmount += new TxOut(Money.Zero, unlockedAddress.ScriptPubKey).GetDustThreshold(builder.StandardTransactionPolicy.MinRelayTxFee);
                }
                if (lockedAmount > 0)
                {
                    builder.Send(lockedAddress, new AssetMoney(assetId, lockedAmount, asset.MultiplierPower));
                    dustAmount += new TxOut(Money.Zero, lockedAddress.ScriptPubKey).GetDustThreshold(builder.StandardTransactionPolicy.MinRelayTxFee);
                }
                additionalBtc = dustAmount - sendAmount;
            }

            var fakeFee = new Money(1, MoneyUnit.BTC);

            var fakeAmount = additionalBtc + fakeFee;

            builder.SendFees(fakeFee);

            _transactionBuildHelper.AddFakeInput(builder, fakeAmount);
            var tr = builder.BuildTransaction(true);

            _transactionBuildHelper.RemoveFakeInput(tr);
            return(new CreationCommitmentResult(tr, lockedAddress.ToString(), script.ToHex()));
        }