public async Task <SplitTransactionResponse> GetSplitTransaction([FromQuery] string multisig, [FromQuery] string clientDestination, [FromQuery] string hubDestination) { var result = await _bccTransactionService.CreateSplitTransaction(multisig, OpenAssetsHelper.ParseAddress(clientDestination), OpenAssetsHelper.ParseAddress(hubDestination)); return(new SplitTransactionResponse(result)); }
public async Task SpendMultisigWitOverP2ShOutput() { var pk1 = Key.Parse("cMahea7zqjxrtgAbB7LSGbcZDo359LNtib5kYpwbiSqBqvs6cqPV"); var pk2 = Key.Parse("cQDux9gANFC1mPiwPpx7feHpiZu9xKn8RyV8yLErazuzWt146oY1"); var pubKey1 = new PubKey("03ebcc2d675d17c5b5e250307cb0189bfc5adf6809bfd3c2823a2884dbbcaec58b").Compress(); var pubKey2 = new PubKey("02235060021d06f6c4e766574b0374dde8d050a0a036ee52cde04608a87eebc3e1").Compress(); var redeem = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(2, pubKey1, pubKey2); var coin = new Coin(new OutPoint(uint256.Parse("2b2e47cd0ba3be2a013fd8658f24e66aae5d492837423ccee76e4e670e980b6f"), 0), new TxOut(Money.FromUnit(1, MoneyUnit.BTC), "a914d388706006374eb728135014c9cad72e5dcd72fe87".ToScript())); var scriptCoin = coin.ToScriptCoin(redeem); var builder = new TransactionBuilder(); builder.AddCoins(scriptCoin); builder.Send(OpenAssetsHelper.ParseAddress("mj5FEqrC2P4FjFNfX8q3eZ4UABWUcRNy9r"), Money.FromUnit(0.5M, MoneyUnit.BTC)); builder.SetChange(coin.ScriptPubKey); builder.SendFees(Money.FromUnit(0.0001M, MoneyUnit.BTC)); builder.AddKeys(pk1, pk2); var tr = builder.BuildTransaction(true); //await Broadcast(tr); }
public async Task SpendSegwitOutput() { var pk1 = Key.Parse("cMahea7zqjxrtgAbB7LSGbcZDo359LNtib5kYpwbiSqBqvs6cqPV"); var pk2 = Key.Parse("cQDux9gANFC1mPiwPpx7feHpiZu9xKn8RyV8yLErazuzWt146oY1"); var pubKey1 = new PubKey("03ebcc2d675d17c5b5e250307cb0189bfc5adf6809bfd3c2823a2884dbbcaec58b").Compress(); var pubKey2 = new PubKey("02235060021d06f6c4e766574b0374dde8d050a0a036ee52cde04608a87eebc3e1").Compress(); var redeem = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(2, pubKey1, pubKey2); var coin = new Coin(new OutPoint(uint256.Parse("9aff0ea55be499f0c664e447a93f7a38a87cc34f24f464642c7553b40fd18958"), 1), new TxOut(Money.FromUnit(0.2M, MoneyUnit.BTC), redeem.WitHash.ScriptPubKey)); var scriptCoin = coin.ToScriptCoin(redeem); var builder = new TransactionBuilder(); builder.AddCoins(scriptCoin); builder.Send(OpenAssetsHelper.ParseAddress("mj5FEqrC2P4FjFNfX8q3eZ4UABWUcRNy9r"), Money.FromUnit(0.1M, MoneyUnit.BTC)); builder.SetChange(coin.ScriptPubKey); builder.SendFees(Money.FromUnit(0.0001M, MoneyUnit.BTC)); builder.AddKeys(pk1, pk2); var tr = builder.BuildTransaction(true); //await Broadcast(tr); }
public async Task <PrivateBccTransferResponse> GetPrivateTransfer([FromQuery] string sourceAddress, [FromQuery] string destinationAddress, [FromQuery] decimal fee) { var result = await _bccTransactionService.CreatePrivateTransfer(OpenAssetsHelper.ParseAddress(sourceAddress), OpenAssetsHelper.ParseAddress(destinationAddress), fee); return(new PrivateBccTransferResponse { Transaction = result.TransactionHex, Outputs = result.Outputs }); }
public async Task Process(SpendCommitmentMonitorindMessage message, QueueTriggeringContext context) { if (DateTime.UtcNow - message.LastTryTime.GetValueOrDefault() < MessageProcessDelay) { MoveToEnd(context, message); return; } message.LastTryTime = DateTime.UtcNow; var tr = await _qBitNinjaApiCaller.GetTransaction(message.TransactionHash); if (tr?.Block == null || tr.Block.Confirmations < OffchainService.OneDayDelay) { MoveToEnd(context, message); return; } var commitment = await _commitmentRepository.GetCommitment(message.CommitmentId); var lockedAddr = OpenAssetsHelper.ParseAddress(commitment.LockedAddress); var coin = tr.ReceivedCoins .FirstOrDefault(o => o.TxOut.ScriptPubKey.GetDestinationAddress(_connectionParams.Network) == lockedAddr); if (coin == null) { throw new Exception("Not found coin for spending for " + message.ToJson()); } if (coin is Coin) { coin = ((Coin)coin).ToScriptCoin(commitment.LockedScript.ToScript()); } else { var colored = coin as ColoredCoin; coin = colored.Bearer.ToScriptCoin(commitment.LockedScript.ToScript()).ToColoredCoin(colored.Amount); } var assetSettings = await _offchainService.GetAssetSetting(commitment.AssetId); try { var hash = await _offchainService.SpendCommitmemtByPubkey(commitment, coin, !string.IsNullOrEmpty(assetSettings.ChangeWallet)?assetSettings.ChangeWallet : assetSettings.HotWallet); await _log.WriteInfoAsync(nameof(SpendBroadcastedCommitmentFunction), nameof(Process), message.ToJson(), "Spent commitment by transaction" + hash); } catch (Exception ex) { await _log.WriteWarningAsync(nameof(SpendBroadcastedCommitmentFunction), nameof(Process), message.ToJson(), ex); MoveToEnd(context, message); } }
private async Task ValidateAddress(string address, bool checkOffchain = true) { var bitcoinAddres = OpenAssetsHelper.ParseAddress(address); if (bitcoinAddres == null) { throw new BackendException($"Invalid Address provided: {address}", ErrorCode.InvalidAddress); } if (checkOffchain && await _offchainService.HasChannel(address)) { throw new BackendException("Address was used in offchain", ErrorCode.AddressUsedInOffchain); } }
public async Task Save(string address, IEnumerable <ICoin> coins) { try { var str = Serializer.ToString(coins, OpenAssetsHelper.ParseAddress(address).Network); await _storage.SaveBlobAsync(BlobContainer, $"{address}_{DateTime.UtcNow:yyyy-MM-dd_HH-mm-ss.fff}.txt", Encoding.UTF8.GetBytes(str)); } // ReSharper disable once EmptyGeneralCatchClause catch (Exception) { } }
private Task GenerateColorOutputs(string assetId) { return(Retry.Try(async() => { var asset = await _assetRepostory.GetItemAsync(assetId); var setting = await GetAssetSetting(assetId); var hotWallet = OpenAssetsHelper.ParseAddress(setting.HotWallet); var assetIdObj = new BitcoinAssetId(asset.BlockChainAssetId).AssetId; var outputs = await _bitcoinOutputsService.GetColoredUnspentOutputs(setting.HotWallet, assetIdObj, 0, false); var balance = outputs.Aggregate(new AssetMoney(assetIdObj, 0), (accum, coin) => accum + coin.Amount); var outputSize = new AssetMoney(assetIdObj, setting.OutputSize, asset.MultiplierPower); var changeBalance = new AssetMoney(assetIdObj); if (setting.ChangeWallet != setting.HotWallet && !string.IsNullOrEmpty(setting.ChangeWallet)) { var changeOutputs = await _bitcoinOutputsService.GetColoredUnspentOutputs(setting.ChangeWallet, assetIdObj, 0, false); changeBalance = changeOutputs.Aggregate(new AssetMoney(assetIdObj, 0), (accum, coin) => accum + coin.Amount); } if ((balance + changeBalance).ToDecimal(asset.MultiplierPower) < setting.MinBalance) { await SendBalanceNotifications(assetId, setting.HotWallet, setting.MinBalance); } var existingCoinsCount = outputs.Count(o => o.Amount <= outputSize && o.Amount.Quantity > outputSize.Quantity / 2); if (existingCoinsCount > setting.MinOutputsCount) { return; } var generateCnt = setting.MaxOutputsCount - existingCoinsCount; var coins = outputs.Where(o => o.Amount > outputSize * 2).ToList(); balance = coins.Aggregate(new AssetMoney(assetIdObj, 0), (accum, coin) => accum + coin.Amount); generateCnt = Math.Min(generateCnt, (int)(balance.Quantity / outputSize.Quantity)); if (generateCnt == 0) { return; } await GenerateOutputs(generateCnt, coins, hotWallet, outputSize, asset, setting); }, exception => (exception as BackendException)?.Code == ErrorCode.TransactionConcurrentInputsProblem, 3, _logger)); }
public async Task <CreateTransactionResponse> GetTransferFromSegwitWallet(BitcoinAddress source, Guid transactionId) { var assetSetting = await _assetSettingCache.GetItemAsync("BTC"); var asset = await _assetRepository.GetItemAsync("BTC"); var hotWallet = OpenAssetsHelper.ParseAddress(!string.IsNullOrEmpty(assetSetting.ChangeWallet) ? assetSetting.ChangeWallet : assetSetting.HotWallet); var context = _transactionBuildContextFactory.Create(_connectionParams.Network); var lowVolume = new Money((decimal)asset.LowVolumeAmount, MoneyUnit.BTC); return(await context.Build(async() => { var outputs = (await _bitcoinOutputsService.GetUncoloredUnspentOutputs(source.ToString())).OfType <Coin>() .Where(o => o.Amount >= lowVolume).ToList(); if (!outputs.Any()) { throw new BackendException($"Address {source} has not unspent outputs", ErrorCode.NoCoinsFound); } var totalAmount = new Money(outputs.Sum(o => o.Amount)); var builder = new TransactionBuilder(); builder.DustPrevention = false; builder.AddCoins(outputs); builder.Send(hotWallet, totalAmount); builder.SubtractFees(); builder.SendEstimatedFees(await _feeProvider.GetFeeRate()); builder.SetChange(hotWallet); var tx = builder.BuildTransaction(true); _transactionBuildHelper.AggregateOutputs(tx); if (tx.Outputs[0].Value <= tx.Outputs[0].GetDustThreshold(builder.StandardTransactionPolicy.MinRelayTxFee)) { throw new BackendException($"Address {source} balance is too low", ErrorCode.NoCoinsFound); } await _spentOutputService.SaveSpentOutputs(transactionId, tx); await SaveNewOutputs(transactionId, tx, context); return new CreateTransactionResponse(tx.ToHex(), transactionId); })); }
private Task GenerateBtcOutputs() { return(Retry.Try(async() => { var setting = await GetAssetSetting("BTC"); var hotWallet = OpenAssetsHelper.ParseAddress(setting.HotWallet); var outputs = (await _bitcoinOutputsService.GetUncoloredUnspentOutputs(setting.HotWallet, 0, false)).Cast <Coin>().ToList(); var balance = new Money(outputs.DefaultIfEmpty().Sum(o => o?.Amount ?? Money.Zero)); var outputSize = new Money(setting.OutputSize, MoneyUnit.BTC); Money changeBalance = Money.Zero; if (setting.ChangeWallet != setting.HotWallet && !string.IsNullOrEmpty(setting.ChangeWallet)) { var changeOutputs = (await _bitcoinOutputsService.GetUncoloredUnspentOutputs(setting.ChangeWallet, 0, false)).Cast <Coin>().ToList(); changeBalance = new Money(changeOutputs.DefaultIfEmpty().Sum(o => o?.Amount ?? Money.Zero)); } if ((balance + changeBalance).ToDecimal(MoneyUnit.BTC) < setting.MinBalance) { await SendBalanceNotifications("BTC", setting.HotWallet, setting.MinBalance); } var existingCoinsCount = outputs.Count(o => o.Amount <= outputSize && o.Amount > outputSize / 2); if (existingCoinsCount > setting.MinOutputsCount) { return; } var generateCnt = setting.MaxOutputsCount - existingCoinsCount; var coins = outputs.Where(o => o.Amount > outputSize * 2).ToList(); balance = coins.DefaultIfEmpty().Sum(o => o?.Amount ?? Money.Zero); generateCnt = Math.Min(generateCnt, (int)(balance / outputSize)); if (generateCnt == 0) { return; } await GenerateOutputs(generateCnt, coins, hotWallet, outputSize, await _assetRepostory.GetItemAsync("BTC"), setting); }, exception => (exception as BackendException)?.Code == ErrorCode.TransactionConcurrentInputsProblem, 3, _logger)); }
public async Task SendToWitnessAddress() { var sourceAddr = OpenAssetsHelper.ParseAddress("mj5FEqrC2P4FjFNfX8q3eZ4UABWUcRNy9r"); var addr = OpenAssetsHelper.ParseAddress("tb1q2xvc2c503h95sm8nu5wyj68xee23su5wt46au5ztdsa9neqs424qh8kxal"); var coin = new Coin(new OutPoint(uint256.Parse("1fcedb863194c3c31f133e09c509e54b2cb40ef0a651a41a23d534301eb57af0"), 1), new TxOut(Money.FromUnit(0.5M, MoneyUnit.BTC), sourceAddr)); var key = Key.Parse("93586ks3uwSAgJ6q3He4CkuXeVg1N4syvszP514TitfcA9mXjVo"); var builder = new TransactionBuilder(); builder.AddCoins(coin); builder.SetChange(sourceAddr); builder.SendFees(Money.FromUnit(0.0001M, MoneyUnit.BTC)); builder.Send(addr, Money.FromUnit(0.2M, MoneyUnit.BTC)); builder.AddKeys(key); var tr = builder.BuildTransaction(true); //await Broadcast(tr); }
public async Task <IActionResult> CreateMultipleTransfer([FromBody] MultipleTransferRequest model) { foreach (var source in model.Sources) { await ValidateAddress(source.Address); } if (model.FixedFee.GetValueOrDefault() < 0) { throw new BackendException("Fixed fee must be greater than or equal to zero", ErrorCode.BadInputParameter); } var destAddress = OpenAssetsHelper.ParseAddress(model.Destination); 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); } var transactionId = await _builder.AddTransactionId(model.TransactionId, $"MultipleTransfer: {model.ToJson()}"); var response = await _builder.GetMultipleTransferTransaction(destAddress, asset, model.Sources.ToDictionary(x => x.Address, x => x.Amount), model.FeeRate, model.FixedFee.GetValueOrDefault(), transactionId); var fullSignedHex = await _signatureApiProvider.SignTransaction(response.Transaction); await _transactionBlobStorage.AddOrReplaceTransaction(transactionId, TransactionBlobType.Signed, fullSignedHex); var fullSigned = new Transaction(fullSignedHex); await _broadcastService.BroadcastTransaction(transactionId, fullSigned, useHandlers : false); return(Ok(new TransactionIdAndHashResponse { TransactionId = transactionId, Hash = fullSigned.GetHash().ToString() })); }
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"); } } }
public Task <CreateMultiCashoutTransactionResult> GetMultipleCashoutTransaction(List <ICashoutRequest> cashoutRequests, Guid transactionId) { return(Retry.Try(async() => { var context = _transactionBuildContextFactory.Create(_connectionParams.Network); var assetSetting = await _assetSettingCache.GetItemAsync("BTC"); var hotWallet = OpenAssetsHelper.ParseAddress(assetSetting.HotWallet); var changeWallet = OpenAssetsHelper.ParseAddress(string.IsNullOrWhiteSpace(assetSetting.ChangeWallet) ? assetSetting.HotWallet : assetSetting.ChangeWallet); return await context.Build(async() => { var builder = new TransactionBuilder(); var hotWalletOutputs = (await _bitcoinOutputsService.GetUncoloredUnspentOutputs(hotWallet.ToString())).OfType <Coin>().ToList(); var hotWalletBalance = new Money(hotWalletOutputs.Select(o => o.Amount).DefaultIfEmpty().Sum(o => o?.Satoshi ?? 0)); var maxFeeForTransaction = Money.FromUnit(0.099M, MoneyUnit.BTC); var selectedCoins = new List <Coin>(); var maxInputsCount = maxFeeForTransaction.Satoshi / (await _feeProvider.GetFeeRate()).GetFee(Constants.InputSize).Satoshi; var tryCount = 100; do { if (selectedCoins.Count > maxInputsCount && cashoutRequests.Count > 1) { cashoutRequests = cashoutRequests.Take(cashoutRequests.Count - 1).ToList(); selectedCoins.Clear(); } else if (selectedCoins.Count > 0) { break; } var totalAmount = Money.FromUnit(cashoutRequests.Select(o => o.Amount).Sum(), MoneyUnit.BTC); if (hotWalletBalance < totalAmount + maxFeeForTransaction) { var changeBalance = Money.Zero; List <Coin> changeWalletOutputs = new List <Coin>(); if (hotWallet != changeWallet) { changeWalletOutputs = (await _bitcoinOutputsService.GetUncoloredUnspentOutputs(changeWallet.ToString())) .OfType <Coin>().ToList(); changeBalance = new Money(changeWalletOutputs.Select(o => o.Amount).DefaultIfEmpty().Sum(o => o?.Satoshi ?? 0)); } if (changeBalance + hotWalletBalance >= totalAmount + maxFeeForTransaction) { selectedCoins.AddRange(hotWalletOutputs); selectedCoins.AddRange(OpenAssetsHelper .CoinSelect(changeWalletOutputs, totalAmount + maxFeeForTransaction - hotWalletBalance).OfType <Coin>()); } else { selectedCoins.AddRange(hotWalletOutputs); selectedCoins.AddRange(changeWalletOutputs); int cashoutsUsedCount = 0; var cashoutsAmount = await _transactionBuildHelper.CalcFee(selectedCoins.Count, cashoutRequests.Count + 1); foreach (var cashoutRequest in cashoutRequests) { cashoutsAmount += Money.FromUnit(cashoutRequest.Amount, MoneyUnit.BTC); if (cashoutsAmount > hotWalletBalance + changeBalance) { break; } cashoutsUsedCount++; } if (cashoutsUsedCount == 0) { throw new BackendException("Not enough bitcoin available", ErrorCode.NotEnoughBitcoinAvailable); } cashoutRequests = cashoutRequests.Take(cashoutsUsedCount).ToList(); } if (changeWallet != hotWallet) { if (assetSetting.Asset == Constants.DefaultAssetSetting) { assetSetting = await CreateAssetSetting("BTC", assetSetting); } if (assetSetting.Asset != Constants.DefaultAssetSetting) { await _assetSettingRepository.UpdateHotWallet(assetSetting.Asset, assetSetting.ChangeWallet); } } } else { selectedCoins.AddRange(OpenAssetsHelper.CoinSelect(hotWalletOutputs, totalAmount + maxFeeForTransaction).OfType <Coin>()); } } while (tryCount-- > 0); var selectedCoinsAmount = new Money(selectedCoins.Sum(o => o.Amount)); var sendAmount = new Money(cashoutRequests.Sum(o => o.Amount), MoneyUnit.BTC); builder.AddCoins(selectedCoins); foreach (var cashout in cashoutRequests) { var amount = Money.FromUnit(cashout.Amount, MoneyUnit.BTC); builder.Send(OpenAssetsHelper.ParseAddress(cashout.DestinationAddress), amount); } builder.Send(changeWallet, selectedCoinsAmount - sendAmount); builder.SubtractFees(); builder.SendEstimatedFees(await _feeProvider.GetFeeRate()); builder.SetChange(changeWallet); var tx = builder.BuildTransaction(true); _transactionBuildHelper.AggregateOutputs(tx); await _broadcastedOutputRepository.InsertOutputs( tx.Outputs.AsCoins().Where(o => o.ScriptPubKey == changeWallet.ScriptPubKey).Select(o => new BroadcastedOutput(o, transactionId, _connectionParams.Network)).ToList()); await _spentOutputService.SaveSpentOutputs(transactionId, tx); return new CreateMultiCashoutTransactionResult { Transaction = tx, UsedRequests = cashoutRequests }; }); }, exception => (exception as BackendException)?.Code == ErrorCode.TransactionConcurrentInputsProblem, 3, _log)); }
public Task <CreateTransactionResponse> GetMultipleTransferTransaction(BitcoinAddress destination, IAsset asset, Dictionary <string, decimal> transferAddresses, int feeRate, decimal fixedFee, Guid transactionId) { return(Retry.Try(async() => { var context = _transactionBuildContextFactory.Create(_connectionParams.Network); return await context.Build(async() => { var builder = new TransactionBuilder(); builder.SetChange(destination, ChangeType.Uncolored); foreach (var transferAddress in transferAddresses) { var source = OpenAssetsHelper.ParseAddress(transferAddress.Key); await TransferOneDirection(builder, context, source, transferAddress.Value, asset, destination); } Transaction buildedTransaction; try { buildedTransaction = builder.BuildTransaction(true); } catch (NotEnoughFundsException ex) { if (ex.Missing is Money) { var missingAmount = ((Money)ex.Missing).Satoshi; _transactionBuildHelper.AddFakeInput(builder, new Money(missingAmount, MoneyUnit.Satoshi)); buildedTransaction = builder.BuildTransaction(true); } else { throw; } } _transactionBuildHelper.RemoveFakeInput(buildedTransaction); _transactionBuildHelper.AggregateOutputs(buildedTransaction); var fee = fixedFee > 0 ? Money.FromUnit(fixedFee, MoneyUnit.BTC) : await _transactionBuildHelper.CalcFee(buildedTransaction, feeRate); foreach (var output in buildedTransaction.Outputs) { if (output.ScriptPubKey.GetDestinationAddress(_connectionParams.Network) == destination) { if (output.Value <= fee) { throw new BackendException("Amount is lower than fee", ErrorCode.NotEnoughBitcoinAvailable); } output.Value -= fee; break; } } await _spentOutputService.SaveSpentOutputs(transactionId, buildedTransaction); await SaveNewOutputs(transactionId, buildedTransaction, context); return new CreateTransactionResponse(buildedTransaction.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 = _transactionBuildContextFactory.Create(_connectionParams.Network); var channels = await _offchainService.GetCurrentChannels(from.ToString()); var assets = await _assetRepository.Values(); 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); } async Task <IDestination> GetChangeWallet(string asset) { var assetSetting = await _offchainService.GetAssetSetting(asset); return OpenAssetsHelper.ParseAddress(!string.IsNullOrEmpty(assetSetting.ChangeWallet) ? assetSetting.ChangeWallet : assetSetting.HotWallet); }; if (uncoloredCoins.Count > 0) { var hubAmount = Money.Zero; IDestination hubAmountAddress = null; var channel = channels.FirstOrDefault(o => o.Asset == "BTC"); if (channel != null) { hubAmount = Money.FromUnit(channel.HubAmount, MoneyUnit.BTC); hubAmountAddress = await GetChangeWallet("BTC"); } builder.AddCoins(uncoloredCoins); context.AddCoins(uncoloredCoins); builder.Send(to, uncoloredCoins.Sum(o => o.TxOut.Value) - hubAmount); if (hubAmount > 0) { builder.Send(hubAmountAddress, hubAmount); } } foreach (var assetGroup in coloredCoins.GroupBy(o => o.AssetId)) { var asset = assets.First(o => o.BlockChainAssetId == assetGroup.Key.GetWif(_connectionParams.Network).ToString()); var channel = channels.FirstOrDefault(o => o.Asset == asset.Id); var sum = new AssetMoney(assetGroup.Key); foreach (var coloredCoin in assetGroup) { sum += coloredCoin.Amount; } var hubAmount = new AssetMoney(assetGroup.Key); IDestination hubAmountAddress = null; if (channel != null) { hubAmount = new AssetMoney(assetGroup.Key, channel.HubAmount, asset.MultiplierPower); hubAmountAddress = await GetChangeWallet(asset.Id); } builder.AddCoins(assetGroup.ToList()); context.AddCoins(assetGroup.ToList()); builder.SendAsset(to, sum - hubAmount); if (hubAmount.Quantity > 0) { builder.SendAsset(hubAmountAddress, hubAmount); } } await _transactionBuildHelper.AddFee(builder, context); var buildedTransaction = builder.BuildTransaction(true); await _spentOutputService.SaveSpentOutputs(transactionId, buildedTransaction); await SaveNewOutputs(transactionId, buildedTransaction, context); foreach (var offchainChannel in channels) { await _offchainService.RemoveChannel(offchainChannel); } return new CreateTransactionResponse(buildedTransaction.ToHex(), transactionId); }); }, exception => (exception as BackendException)?.Code == ErrorCode.TransactionConcurrentInputsProblem, 3, _log)); }
public Task OnMessage(BccTransferCommand command) { return(_bccTransactionService.Transfer(OpenAssetsHelper.ParseAddress(command.SourceAddress), OpenAssetsHelper.ParseAddress(command.DestinationAddress), command.Amount)); }
public async Task Test() { var hotWallet = OpenAssetsHelper.ParseAddress("mj5FEqrC2P4FjFNfX8q3eZ4UABWUcRNy9r"); var changeWallet = OpenAssetsHelper.ParseAddress("minog49vnNVuWK5kSs5Ut1iPyNZcR1i7he"); var hotWalletOutputs = GenerateOutputs(5); var hotWalletBalance = new Money(hotWalletOutputs.Select(o => o.Amount).DefaultIfEmpty().Sum(o => o?.Satoshi ?? 0)); var maxFeeForTransaction = Money.FromUnit(0.099M, MoneyUnit.BTC); var selectedCoins = new List <Coin>(); var _feeProvider = Config.Services.GetService <IFeeProvider>(); var _transactionBuildHelper = Config.Services.GetService <ITransactionBuildHelper>(); var cashoutRequests = (GenerateCashoutRequest(200)).ToList(); var maxInputsCount = maxFeeForTransaction.Satoshi / (await _feeProvider.GetFeeRate()).GetFee(Constants.InputSize).Satoshi; do { if (selectedCoins.Count > maxInputsCount && cashoutRequests.Count > 1) { cashoutRequests = cashoutRequests.Take(cashoutRequests.Count - 1).ToList(); selectedCoins.Clear(); } else if (selectedCoins.Count > 0) { break; } var totalAmount = Money.FromUnit(cashoutRequests.Select(o => o.Amount).Sum(), MoneyUnit.BTC); if (hotWalletBalance < totalAmount + maxFeeForTransaction) { var changeBalance = Money.Zero; List <Coin> changeWalletOutputs = new List <Coin>(); if (hotWallet != changeWallet) { changeWalletOutputs = GenerateOutputs(1).ToList(); changeBalance = new Money(changeWalletOutputs.Select(o => o.Amount).DefaultIfEmpty().Sum(o => o?.Satoshi ?? 0)); } if (changeBalance + hotWalletBalance >= totalAmount + maxFeeForTransaction) { selectedCoins.AddRange(hotWalletOutputs); selectedCoins.AddRange(OpenAssetsHelper .CoinSelect(changeWalletOutputs, totalAmount + maxFeeForTransaction - hotWalletBalance).OfType <Coin>()); } else { selectedCoins.AddRange(hotWalletOutputs); selectedCoins.AddRange(changeWalletOutputs); int cashoutsUsedCount = 0; var cashoutsAmount = await _transactionBuildHelper.CalcFee(selectedCoins.Count, cashoutRequests.Count + 1); foreach (var cashoutRequest in cashoutRequests) { cashoutsAmount += Money.FromUnit(cashoutRequest.Amount, MoneyUnit.BTC); if (cashoutsAmount > hotWalletBalance + changeBalance) { break; } cashoutsUsedCount++; } if (cashoutsUsedCount == 0) { throw new BackendException("Not enough bitcoin available", ErrorCode.NotEnoughBitcoinAvailable); } cashoutRequests = cashoutRequests.Take(cashoutsUsedCount).ToList(); } } else { selectedCoins.AddRange(OpenAssetsHelper.CoinSelect(hotWalletOutputs, totalAmount + maxFeeForTransaction).OfType <Coin>()); } } while (true); var selectedCoinsAmount = new Money(selectedCoins.Sum(o => o.Amount)); var sendAmount = new Money(cashoutRequests.Sum(o => o.Amount), MoneyUnit.BTC); var builder = new TransactionBuilder(); builder.AddCoins(selectedCoins); foreach (var cashout in cashoutRequests) { var amount = Money.FromUnit(cashout.Amount, MoneyUnit.BTC); builder.Send(OpenAssetsHelper.ParseAddress(cashout.DestinationAddress), amount); } builder.Send(changeWallet, selectedCoinsAmount - sendAmount); builder.SubtractFees(); builder.SendEstimatedFees(await _feeProvider.GetFeeRate()); builder.SetChange(changeWallet); var tx = builder.BuildTransaction(true); _transactionBuildHelper.AggregateOutputs(tx); }
private IEnumerable <Coin> GenerateOutputs(int cnt) { var rand = new Random(rndINit); for (int i = 0; i < cnt; i++) { yield return(new Coin(new OutPoint(uint256.One, _N++), new TxOut(rand.Next(2700, 500000000), OpenAssetsHelper.ParseAddress("mj5FEqrC2P4FjFNfX8q3eZ4UABWUcRNy9r")))); } }
public async Task ProcessMessage(TransactionQueueMessage message, QueueTriggeringContext context) { CreateTransactionResponse transactionResponse; try { var request = await _signRequestRepository.GetSignRequest(message.TransactionId); if (request?.Invalidated == true) { context.MoveMessageToPoison(message.ToJson()); return; } switch (message.Type) { case TransactionCommandType.Issue: var issue = message.Command.DeserializeJson <IssueCommand>(); transactionResponse = await _lykkeTransactionBuilderService.GetIssueTransaction( OpenAssetsHelper.ParseAddress(issue.Address), issue.Amount, await _assetRepository.GetAssetById(issue.Asset), message.TransactionId); break; case TransactionCommandType.Transfer: var transfer = message.Command.DeserializeJson <TransferCommand>(); transactionResponse = await _lykkeTransactionBuilderService.GetTransferTransaction( OpenAssetsHelper.ParseAddress(transfer.SourceAddress), OpenAssetsHelper.ParseAddress(transfer.DestinationAddress), transfer.Amount, await _assetRepository.GetAssetById(transfer.Asset), message.TransactionId); break; case TransactionCommandType.TransferAll: var transferAll = message.Command.DeserializeJson <TransferAllCommand>(); transactionResponse = await _lykkeTransactionBuilderService.GetTransferAllTransaction( OpenAssetsHelper.ParseAddress(transferAll.SourceAddress), OpenAssetsHelper.ParseAddress(transferAll.DestinationAddress), message.TransactionId); break; case TransactionCommandType.Swap: var swap = message.Command.DeserializeJson <SwapCommand>(); transactionResponse = await _lykkeTransactionBuilderService.GetSwapTransaction( OpenAssetsHelper.ParseAddress(swap.MultisigCustomer1), swap.Amount1, await _assetRepository.GetAssetById(swap.Asset1), OpenAssetsHelper.ParseAddress(swap.MultisigCustomer2), swap.Amount2, await _assetRepository.GetAssetById(swap.Asset2), message.TransactionId); break; case TransactionCommandType.Destroy: var destroy = message.Command.DeserializeJson <DestroyCommand>(); transactionResponse = await _lykkeTransactionBuilderService.GetDestroyTransaction( OpenAssetsHelper.ParseAddress(destroy.Address), destroy.Amount, await _assetRepository.GetAssetById(destroy.Asset), message.TransactionId); break; case TransactionCommandType.SegwitTransferToHotwallet: var segwitTransfer = message.Command.DeserializeJson <SegwitTransferCommand>(); transactionResponse = await _lykkeTransactionBuilderService.GetTransferFromSegwitWallet( OpenAssetsHelper.ParseAddress(segwitTransfer.SourceAddress), message.TransactionId); break; default: throw new ArgumentOutOfRangeException(); } } catch (BackendException e) when(e.Code == ErrorCode.NoCoinsFound) { if (message.Type == TransactionCommandType.SegwitTransferToHotwallet) { _cqrsEngine.PublishEvent(new CashinCompletedEvent { OperationId = message.TransactionId }, BitcoinBoundedContext.Name); } return; } catch (BackendException e) { if (e.Text != message.LastError) { await _logger.WriteWarningAsync("TransactionBuildFunction", "ProcessMessage", $"Id: [{message.TransactionId}], cmd: [{message.Command}]", e.Text); } message.LastError = e.Text; if (message.DequeueCount >= _settings.MaxDequeueCount) { context.MoveMessageToPoison(message.ToJson()); } else { message.DequeueCount++; context.MoveMessageToEnd(message.ToJson()); context.SetCountQueueBasedDelay(_settings.MaxQueueDelay, 200); } return; } await _transactionBlobStorage.AddOrReplaceTransaction(message.TransactionId, TransactionBlobType.Initial, transactionResponse.Transaction); await _queueFactory(Constants.BroadcastingQueue).PutRawMessageAsync(new BroadcastingTransaction { TransactionCommandType = message.Type, TransactionId = message.TransactionId }.ToJson()); }
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 })); }