private async Task <IEnumerable <EthereumTransaction> > CreatePaymentTxsAsync( ClientSwap swap, int lockTimeInSeconds, CancellationToken cancellationToken = default(CancellationToken)) { Log.Debug("Create payment transactions for swap {@swapId}", swap.Id); var requiredAmountInEth = AmountHelper.QtyToAmount(swap.Side, swap.Qty, swap.Price); var refundTimeStampUtcInSec = new DateTimeOffset(swap.TimeStamp.ToUniversalTime().AddSeconds(lockTimeInSeconds)).ToUnixTimeSeconds(); var isInitTx = true; var rewardForRedeemInEth = swap.PartyRewardForRedeem; var unspentAddresses = (await Account .GetUnspentAddressesAsync(Eth, cancellationToken) .ConfigureAwait(false)) .ToList() .SortList((a, b) => a.AvailableBalance().CompareTo(b.AvailableBalance())); var transactions = new List <EthereumTransaction>(); foreach (var walletAddress in unspentAddresses) { Log.Debug("Create swap payment tx from address {@address} for swap {@swapId}", walletAddress.Address, swap.Id); var balanceInEth = (await Account .GetAddressBalanceAsync( currency: Eth, address: walletAddress.Address, cancellationToken: cancellationToken) .ConfigureAwait(false)) .Available; Log.Debug("Available balance: {@balance}", balanceInEth); var feeAmountInEth = isInitTx ? (rewardForRedeemInEth == 0 ? Eth.InitiateFeeAmount : Eth.InitiateWithRewardFeeAmount) : Eth.AddFeeAmount; var amountInEth = Math.Min(balanceInEth - feeAmountInEth, requiredAmountInEth); if (amountInEth <= 0) { Log.Warning( "Insufficient funds at {@address}. Balance: {@balance}, feeAmount: {@feeAmount}, result: {@result}.", walletAddress.Address, balanceInEth, feeAmountInEth, amountInEth); continue; } requiredAmountInEth -= amountInEth; var nonce = await EthereumNonceManager.Instance .GetNonce(Eth, walletAddress.Address) .ConfigureAwait(false); TransactionInput txInput; if (isInitTx) { var message = new InitiateFunctionMessage { HashedSecret = swap.SecretHash, Participant = swap.PartyAddress, RefundTimestamp = refundTimeStampUtcInSec, AmountToSend = Atomix.Ethereum.EthToWei(amountInEth), FromAddress = walletAddress.Address, GasPrice = Atomix.Ethereum.GweiToWei(Eth.GasPriceInGwei), Nonce = nonce, RedeemFee = Atomix.Ethereum.EthToWei(rewardForRedeemInEth) }; var initiateGasLimit = rewardForRedeemInEth == 0 ? Eth.InitiateGasLimit : Eth.InitiateWithRewardGasLimit; message.Gas = await EstimateGasAsync(message, new BigInteger(initiateGasLimit)) .ConfigureAwait(false); txInput = message.CreateTransactionInput(Eth.SwapContractAddress); } else { var message = new AddFunctionMessage { HashedSecret = swap.SecretHash, AmountToSend = Atomix.Ethereum.EthToWei(amountInEth), FromAddress = walletAddress.Address, GasPrice = Atomix.Ethereum.GweiToWei(Eth.GasPriceInGwei), Nonce = nonce, }; message.Gas = await EstimateGasAsync(message, new BigInteger(Eth.AddGasLimit)) .ConfigureAwait(false); txInput = message.CreateTransactionInput(Eth.SwapContractAddress); } transactions.Add(new EthereumTransaction(Eth, txInput) { Type = EthereumTransaction.OutputTransaction }); if (isInitTx) { isInitTx = false; } if (requiredAmountInEth == 0) { break; } } if (requiredAmountInEth > 0) { Log.Warning("Insufficient funds (left {@requredAmount}).", requiredAmountInEth); return(Enumerable.Empty <EthereumTransaction>()); } return(transactions); }
protected virtual async Task <IEnumerable <EthereumTransaction> > CreatePaymentTxsAsync( Swap swap, int lockTimeInSeconds, CancellationToken cancellationToken = default) { var eth = Eth; Log.Debug("Create payment transactions for swap {@swapId}", swap.Id); var requiredAmountInEth = AmountHelper.QtyToAmount(swap.Side, swap.Qty, swap.Price, eth.DigitsMultiplier); var refundTimeStampUtcInSec = new DateTimeOffset(swap.TimeStamp.ToUniversalTime().AddSeconds(lockTimeInSeconds)).ToUnixTimeSeconds(); var isInitTx = true; var rewardForRedeemInEth = swap.PartyRewardForRedeem; var unspentAddresses = (await _account .GetUnspentAddressesAsync(cancellationToken) .ConfigureAwait(false)) .ToList() .SortList(new AvailableBalanceAscending()); var transactions = new List <EthereumTransaction>(); foreach (var walletAddress in unspentAddresses) { Log.Debug("Create swap payment tx from address {@address} for swap {@swapId}", walletAddress.Address, swap.Id); var balanceInEth = (await _account .GetAddressBalanceAsync( address: walletAddress.Address, cancellationToken: cancellationToken) .ConfigureAwait(false)) .Available; Log.Debug("Available balance: {@balance}", balanceInEth); var feeAmountInEth = isInitTx ? rewardForRedeemInEth == 0 ? eth.InitiateFeeAmount : eth.InitiateWithRewardFeeAmount : eth.AddFeeAmount; var amountInEth = Math.Min(balanceInEth - feeAmountInEth, requiredAmountInEth); if (amountInEth <= 0) { Log.Warning( "Insufficient funds at {@address}. Balance: {@balance}, feeAmount: {@feeAmount}, result: {@result}.", walletAddress.Address, balanceInEth, feeAmountInEth, amountInEth); continue; } requiredAmountInEth -= amountInEth; var nonceResult = await EthereumNonceManager.Instance .GetNonceAsync(eth, walletAddress.Address) .ConfigureAwait(false); if (nonceResult.HasError) { Log.Error("Nonce getting error with code {@code} and description {@description}", nonceResult.Error.Code, nonceResult.Error.Description); return(null); } TransactionInput txInput; if (isInitTx) { var message = new InitiateFunctionMessage { HashedSecret = swap.SecretHash, Participant = swap.PartyAddress, RefundTimestamp = refundTimeStampUtcInSec, AmountToSend = Atomex.Ethereum.EthToWei(amountInEth), FromAddress = walletAddress.Address, GasPrice = Atomex.Ethereum.GweiToWei(eth.GasPriceInGwei), Nonce = nonceResult.Value, RedeemFee = Atomex.Ethereum.EthToWei(rewardForRedeemInEth) }; var initiateGasLimit = rewardForRedeemInEth == 0 ? eth.InitiateGasLimit : eth.InitiateWithRewardGasLimit; message.Gas = await EstimateGasAsync(message, new BigInteger(initiateGasLimit)) .ConfigureAwait(false); txInput = message.CreateTransactionInput(eth.SwapContractAddress); } else { var message = new AddFunctionMessage { HashedSecret = swap.SecretHash, AmountToSend = Atomex.Ethereum.EthToWei(amountInEth), FromAddress = walletAddress.Address, GasPrice = Atomex.Ethereum.GweiToWei(Eth.GasPriceInGwei), Nonce = nonceResult.Value, }; message.Gas = await EstimateGasAsync(message, new BigInteger(eth.AddGasLimit)) .ConfigureAwait(false); txInput = message.CreateTransactionInput(eth.SwapContractAddress); } transactions.Add(new EthereumTransaction(eth, txInput) { Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapPayment }); if (isInitTx) { isInitTx = false; } if (requiredAmountInEth == 0) { break; } } if (requiredAmountInEth > 0) { Log.Warning("Insufficient funds (left {@requiredAmount}).", requiredAmountInEth); return(Enumerable.Empty <EthereumTransaction>()); } return(transactions); }