public async Task <Error> SendAsync( string from, string to, decimal amount, decimal gasLimit, decimal gasPrice, bool useDefaultFee = false, CancellationToken cancellationToken = default) { //if (from == to) // return new Error( // code: Errors.SendingAndReceivingAddressesAreSame, // description: "Sending and receiving addresses are the same."); var ethConfig = EthConfig; if (useDefaultFee) { gasLimit = GasLimitByType(BlockchainTransactionType.Output); gasPrice = Math.Floor(await ethConfig .GetGasPriceAsync(cancellationToken) .ConfigureAwait(false)); } var addressFeeUsage = await CalculateFundsUsageAsync( from : from, amount : amount, fee : gasLimit, feePrice : gasPrice, cancellationToken : cancellationToken) .ConfigureAwait(false); if (addressFeeUsage == null) { return(new Error( code: Errors.InsufficientFunds, description: "Insufficient funds")); } if (gasLimit < ethConfig.GasLimit) { return(new Error( code: Errors.InsufficientGas, description: "Insufficient gas")); } Log.Debug("Try to send {@amount} ETH with fee {@fee} from address {@address} with available balance {@balance}", addressFeeUsage.UsedAmount, addressFeeUsage.UsedFee, addressFeeUsage.WalletAddress.Address, addressFeeUsage.WalletAddress.AvailableBalance()); // lock address to prevent nonce races using var addressLock = await AddressLocker .GetLockAsync(addressFeeUsage.WalletAddress.Address, cancellationToken) .ConfigureAwait(false); var nonceAsyncResult = await EthereumNonceManager.Instance .GetNonceAsync(ethConfig, addressFeeUsage.WalletAddress.Address) .ConfigureAwait(false); if (nonceAsyncResult.HasError) { return(nonceAsyncResult.Error); } var tx = new EthereumTransaction { Currency = ethConfig.Name, Type = BlockchainTransactionType.Output, CreationTime = DateTime.UtcNow, To = to.ToLowerInvariant(), Amount = EthereumConfig.EthToWei(addressFeeUsage.UsedAmount), Nonce = nonceAsyncResult.Value, GasPrice = new BigInteger(EthereumConfig.GweiToWei(gasPrice)), GasLimit = new BigInteger(gasLimit), }; var signResult = await Wallet .SignAsync(tx, addressFeeUsage.WalletAddress, ethConfig, cancellationToken) .ConfigureAwait(false); if (!signResult) { return(new Error( code: Errors.TransactionSigningError, description: "Transaction signing error")); } if (!tx.Verify(ethConfig)) { return(new Error( code: Errors.TransactionVerificationError, description: "Transaction verification error")); } var broadcastResult = await ethConfig.BlockchainApi .TryBroadcastAsync(tx, cancellationToken : cancellationToken) .ConfigureAwait(false); if (broadcastResult.HasError) { return(broadcastResult.Error); } var txId = broadcastResult.Value; if (txId == null) { return(new Error( code: Errors.TransactionBroadcastError, description: "Transaction Id is null")); } Log.Debug("Transaction successfully sent with txId: {@id}", txId); await UpsertTransactionAsync( tx : tx, updateBalance : false, notifyIfUnconfirmed : true, notifyIfBalanceUpdated : false, cancellationToken : cancellationToken) .ConfigureAwait(false); _ = UpdateBalanceAsync(cancellationToken); return(null); }
protected virtual async Task <EthereumTransaction> CreatePaymentTxAsync( Swap swap, int lockTimeInSeconds, CancellationToken cancellationToken = default) { var ethConfig = EthConfig; Log.Debug("Create payment transaction from address {@address} for swap {@swapId}", swap.FromAddress, swap.Id); var requiredAmountInEth = AmountHelper.QtyToSellAmount(swap.Side, swap.Qty, swap.Price, ethConfig.DigitsMultiplier); // maker network fee if (swap.MakerNetworkFee > 0 && swap.MakerNetworkFee < requiredAmountInEth) // network fee size check { requiredAmountInEth += AmountHelper.RoundDown(swap.MakerNetworkFee, ethConfig.DigitsMultiplier); } var refundTimeStampUtcInSec = new DateTimeOffset(swap.TimeStamp.ToUniversalTime().AddSeconds(lockTimeInSeconds)).ToUnixTimeSeconds(); var rewardForRedeemInEth = swap.PartyRewardForRedeem; var walletAddress = await _account .GetAddressAsync(swap.FromAddress, cancellationToken) .ConfigureAwait(false); var gasPrice = await ethConfig .GetGasPriceAsync(cancellationToken) .ConfigureAwait(false); var balanceInEth = walletAddress.Balance; Log.Debug("Available balance: {@balance}", balanceInEth); var feeAmountInEth = rewardForRedeemInEth == 0 ? ethConfig.InitiateFeeAmount(gasPrice) : ethConfig.InitiateWithRewardFeeAmount(gasPrice); if (balanceInEth < feeAmountInEth + requiredAmountInEth) { Log.Warning( "Insufficient funds at {@address}. Balance: {@balance}, required: {@required}, " + "feeAmount: {@feeAmount}, missing: {@result}.", walletAddress.Address, balanceInEth, requiredAmountInEth, feeAmountInEth, balanceInEth - feeAmountInEth - requiredAmountInEth); return(null); } var nonceResult = await((IEthereumBlockchainApi)ethConfig.BlockchainApi) .GetTransactionCountAsync(walletAddress.Address, pending: false, cancellationToken) .ConfigureAwait(false); if (nonceResult.HasError) { Log.Error($"Getting nonce error: {nonceResult.Error.Description}"); return(null); } TransactionInput txInput; var message = new InitiateFunctionMessage { HashedSecret = swap.SecretHash, Participant = swap.PartyAddress, RefundTimestamp = refundTimeStampUtcInSec, AmountToSend = EthereumConfig.EthToWei(requiredAmountInEth), FromAddress = walletAddress.Address, GasPrice = EthereumConfig.GweiToWei(gasPrice), Nonce = nonceResult.Value, RedeemFee = EthereumConfig.EthToWei(rewardForRedeemInEth) }; var initiateGasLimit = rewardForRedeemInEth == 0 ? ethConfig.InitiateGasLimit : ethConfig.InitiateWithRewardGasLimit; message.Gas = await EstimateGasAsync(message, new BigInteger(initiateGasLimit)) .ConfigureAwait(false); txInput = message.CreateTransactionInput(ethConfig.SwapContractAddress); return(new EthereumTransaction(ethConfig.Name, txInput) { Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapPayment }); }