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