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 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 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); }
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); } }
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); } }
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 }); }
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); })); }
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); }
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)); } }
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)); }
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)); }
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 <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() })); }
public async Task <decimal> SendWithChange(TransactionBuilder builder, TransactionBuildContext context, List <ICoin> coins, IDestination destination, Money amount, IDestination changeDestination, bool addDust = true) { if (amount.Satoshi <= 0) { throw new BackendException("Amount can't be less or equal to zero", ErrorCode.BadInputParameter); } Action throwError = () => { throw new BackendException($"The sum of total applicable outputs is less than the required: {amount.Satoshi} satoshis.", ErrorCode.NotEnoughBitcoinAvailable); }; var selectedCoins = OpenAssetsHelper.CoinSelect(coins, amount); if (selectedCoins == null) { throwError(); } var orderedCoins = selectedCoins.OrderBy(o => o.Amount).ToList(); var sendAmount = Money.Zero; var cnt = 0; while (sendAmount < amount && cnt < orderedCoins.Count) { sendAmount += orderedCoins[cnt].TxOut.Value; cnt++; } if (sendAmount < amount) { throwError(); } context.AddCoins(orderedCoins.Take(cnt)); builder.AddCoins(orderedCoins.Take(cnt)); var sent = await Send(builder, context, destination, amount, addDust); if (sendAmount - amount > 0) { await Send(builder, context, changeDestination, sendAmount - amount, addDust); } return(sent); }
public void SendAssetWithChange(TransactionBuilder builder, TransactionBuildContext context, List <ColoredCoin> coins, IDestination destination, AssetMoney amount, IDestination changeDestination) { if (amount.Quantity <= 0) { throw new BackendException("Amount can't be less or equal to zero", ErrorCode.BadInputParameter); } Action throwError = () => { throw new BackendException($"The sum of total applicable outputs is less than the required: {amount.Quantity} {amount.Id}.", ErrorCode.NotEnoughAssetAvailable); }; var selectedCoins = OpenAssetsHelper.CoinSelect(coins, amount); if (selectedCoins == null) { throwError(); } var orderedCounts = selectedCoins.Cast <ColoredCoin>().OrderBy(o => o.Amount).ToList(); var sendAmount = new AssetMoney(amount.Id); var cnt = 0; while (sendAmount < amount && cnt < orderedCounts.Count) { sendAmount += orderedCounts[cnt].Amount; cnt++; } if (sendAmount < amount) { throwError(); } builder.AddCoins(orderedCounts.Take(cnt)); context.AddCoins(orderedCounts.Take(cnt)); builder.SendAsset(destination, amount); if ((sendAmount - amount).Quantity > 0) { builder.SendAsset(changeDestination, sendAmount - amount); } }
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()); }
public Task <CreateTransactionResponse> GetDestroyTransaction(BitcoinAddress bitcoinAddres, decimal modelAmount, IAsset asset, Guid transactionId) { return(Retry.Try(async() => { var context = _transactionBuildContextFactory.Create(_connectionParams.Network); 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); OpenAssetsHelper.DestroyColorCoin(tx, assetMoney, changeAddress, _connectionParams.Network); await _spentOutputService.SaveSpentOutputs(transactionId, tx); await SaveNewOutputs(transactionId, tx, context); return new CreateTransactionResponse(tx.ToHex(), transactionId); }); }, exception => (exception as BackendException)?.Code == ErrorCode.TransactionConcurrentInputsProblem, 3, _log)); }
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 async Task SaveNewOutputs(Guid transactionId, Transaction tr, TransactionBuildContext context) { var coloredOutputs = OpenAssetsHelper.OrderBasedColoringOutputs(tr, context); await _broadcastedOutputRepository.InsertOutputs( coloredOutputs.Select(o => new BroadcastedOutput(o, transactionId, _connectionParams.Network)).ToList()); }
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 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()); }
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")))); } }