private async Task <EthereumTransaction> CreateApproveTx( string walletAddress, BigInteger nonce, BigInteger value, decimal gasPrice) { var erc20Config = Erc20Config; var message = new ERC20ApproveFunctionMessage { Spender = erc20Config.SwapContractAddress, Value = value, FromAddress = walletAddress, GasPrice = EthereumConfig.GweiToWei(gasPrice), Nonce = nonce, }; message.Gas = await EstimateGasAsync(message, new BigInteger(erc20Config.ApproveGasLimit)) .ConfigureAwait(false); var txInput = message.CreateTransactionInput(erc20Config.ERC20ContractAddress); return(new EthereumTransaction(erc20Config.Name, txInput) { Type = BlockchainTransactionType.Output | BlockchainTransactionType.TokenApprove }); }
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 }); }
public override async Task RefundAsync( Swap swap, CancellationToken cancellationToken = default) { var ethConfig = EthConfig; if (swap.StateFlags.HasFlag(SwapStateFlags.IsRefundBroadcast) && swap.RefundTx != null && swap.RefundTx.CreationTime != null && swap.RefundTx.CreationTime.Value.ToUniversalTime() + TimeSpan.FromMinutes(20) > DateTime.UtcNow) { _ = TrackTransactionConfirmationAsync( swap: swap, currency: ethConfig, dataRepository: _account.DataRepository, txId: swap.RefundTx.Id, confirmationHandler: RefundConfirmedEventHandler, cancellationToken: cancellationToken); return; } var lockTimeInSeconds = swap.IsInitiator ? DefaultInitiatorLockTimeInSeconds : DefaultAcceptorLockTimeInSeconds; var lockTime = swap.TimeStamp.ToUniversalTime() + TimeSpan.FromSeconds(lockTimeInSeconds); await RefundTimeDelayAsync(lockTime, cancellationToken) .ConfigureAwait(false); // check swap initiation try { var txResult = await EthereumSwapInitiatedHelper .TryToFindPaymentAsync(swap, ethConfig, cancellationToken) .ConfigureAwait(false); if (!txResult.HasError && txResult.Value == null) { // swap not initiated and must be canceled swap.StateFlags |= SwapStateFlags.IsCanceled; await UpdateSwapAsync(swap, SwapStateFlags.IsCanceled, cancellationToken) .ConfigureAwait(false); return; } } catch (Exception e) { Log.Error(e, $"Can't check {swap.Id} swap initiation for ETH"); } Log.Debug("Create refund for swap {@swap}", swap.Id); var gasPrice = await ethConfig .GetGasPriceAsync(cancellationToken) .ConfigureAwait(false); var walletAddress = await _account .GetAddressAsync(swap.FromAddress, cancellationToken) .ConfigureAwait(false); if (walletAddress == null) { Log.Error("Can't get address {@address} for refund from local db", swap.FromAddress); return; } var feeInEth = ethConfig.GetFeeAmount(ethConfig.RefundGasLimit, gasPrice); if (walletAddress.Balance < feeInEth) { Log.Error("Insufficient funds for refund"); return; } EthereumTransaction refundTx; try { await EthereumAccount.AddressLocker .LockAsync(walletAddress.Address, cancellationToken) .ConfigureAwait(false); var nonceResult = await EthereumNonceManager.Instance .GetNonceAsync(ethConfig, walletAddress.Address, pending : true, cancellationToken) .ConfigureAwait(false); if (nonceResult.HasError) { Log.Error("Nonce getting error with code {@code} and description {@description}", nonceResult.Error.Code, nonceResult.Error.Description); return; } var message = new RefundFunctionMessage { FromAddress = walletAddress.Address, HashedSecret = swap.SecretHash, GasPrice = EthereumConfig.GweiToWei(gasPrice), Nonce = nonceResult.Value, }; message.Gas = await EstimateGasAsync(message, new BigInteger(ethConfig.RefundGasLimit)) .ConfigureAwait(false); var txInput = message.CreateTransactionInput(ethConfig.SwapContractAddress); refundTx = new EthereumTransaction(ethConfig.Name, txInput) { Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapRefund }; var signResult = await SignTransactionAsync(refundTx, cancellationToken) .ConfigureAwait(false); if (!signResult) { Log.Error("Transaction signing error"); return; } swap.RefundTx = refundTx; swap.StateFlags |= SwapStateFlags.IsRefundSigned; await UpdateSwapAsync(swap, SwapStateFlags.IsRefundSigned, cancellationToken) .ConfigureAwait(false); await BroadcastTxAsync(swap, refundTx, cancellationToken) .ConfigureAwait(false); } catch { throw; } finally { EthereumAccount.AddressLocker.Unlock(walletAddress.Address); } swap.RefundTx = refundTx; swap.StateFlags |= SwapStateFlags.IsRefundBroadcast; await UpdateSwapAsync(swap, SwapStateFlags.IsRefundBroadcast, cancellationToken) .ConfigureAwait(false); _ = TrackTransactionConfirmationAsync( swap: swap, currency: ethConfig, dataRepository: _account.DataRepository, txId: refundTx.Id, confirmationHandler: RefundConfirmedEventHandler, cancellationToken: cancellationToken); }
public override async Task RedeemForPartyAsync( Swap swap, CancellationToken cancellationToken = default) { if (swap.IsInitiator) { var partyRedeemDeadline = swap.TimeStamp.ToUniversalTime().AddSeconds(DefaultAcceptorLockTimeInSeconds) - PartyRedeemTimeReserve; if (DateTime.UtcNow > partyRedeemDeadline) { Log.Error("Party redeem deadline reached for swap {@swap}", swap.Id); return; } } Log.Debug("Create redeem for counterParty for swap {@swapId}", swap.Id); var ethConfig = EthConfig; var gasPrice = await ethConfig .GetGasPriceAsync(cancellationToken) .ConfigureAwait(false); var walletAddress = await _account .GetAddressAsync(swap.FromAddress, cancellationToken) .ConfigureAwait(false); if (walletAddress == null) { Log.Error("Can't get address {@address} for redeem for party from local db", swap.FromAddress); return; } var feeInEth = ethConfig.GetFeeAmount(ethConfig.RedeemGasLimit, gasPrice); if (walletAddress.Balance < feeInEth) { Log.Error("Insufficient funds for redeem for party"); return; } using var addressLock = await EthereumAccount.AddressLocker .GetLockAsync(walletAddress.Address, cancellationToken) .ConfigureAwait(false); var nonceResult = await EthereumNonceManager.Instance .GetNonceAsync(ethConfig, walletAddress.Address, pending : true, cancellationToken) .ConfigureAwait(false); if (nonceResult.HasError) { Log.Error("Nonce getting error with code {@code} and description {@description}", nonceResult.Error.Code, nonceResult.Error.Description); return; } var message = new RedeemFunctionMessage { FromAddress = walletAddress.Address, HashedSecret = swap.SecretHash, Secret = swap.Secret, Nonce = nonceResult.Value, GasPrice = EthereumConfig.GweiToWei(gasPrice), }; message.Gas = await EstimateGasAsync(message, new BigInteger(ethConfig.RedeemGasLimit)) .ConfigureAwait(false); var txInput = message.CreateTransactionInput(ethConfig.SwapContractAddress); var redeemTx = new EthereumTransaction(ethConfig.Name, txInput) { Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapRedeem }; var signResult = await SignTransactionAsync(redeemTx, cancellationToken) .ConfigureAwait(false); if (!signResult) { Log.Error("Transaction signing error"); return; } await BroadcastTxAsync(swap, redeemTx, cancellationToken) .ConfigureAwait(false); }
public override async Task RedeemAsync( Swap swap, CancellationToken cancellationToken = default) { var ethConfig = EthConfig; var secretResult = await EthereumSwapRedeemedHelper .IsRedeemedAsync( swap : swap, currency : ethConfig, attempts : MaxRedeemCheckAttempts, attemptIntervalInSec : RedeemCheckAttemptIntervalInSec, cancellationToken : cancellationToken) .ConfigureAwait(false); if (!secretResult.HasError && secretResult.Value != null) { await RedeemConfirmedEventHandler(swap, null, cancellationToken) .ConfigureAwait(false); return; } if (swap.StateFlags.HasFlag(SwapStateFlags.IsRedeemBroadcast) && swap.RedeemTx != null && swap.RedeemTx.CreationTime != null && swap.RedeemTx.CreationTime.Value.ToUniversalTime() + TimeSpan.FromMinutes(30) > DateTime.UtcNow) { // redeem already broadcast _ = TrackTransactionConfirmationAsync( swap: swap, currency: ethConfig, dataRepository: _account.DataRepository, txId: swap.RedeemTx.Id, confirmationHandler: RedeemConfirmedEventHandler, cancellationToken: cancellationToken); return; } if (swap.IsInitiator) { var redeemDeadline = swap.TimeStamp.ToUniversalTime().AddSeconds(DefaultAcceptorLockTimeInSeconds) - RedeemTimeReserve; if (DateTime.UtcNow > redeemDeadline) { Log.Error("Redeem dedline reached for swap {@swap}", swap.Id); return; } } Log.Debug("Create redeem for swap {@swapId}", swap.Id); var gasPrice = await ethConfig .GetGasPriceAsync(cancellationToken) .ConfigureAwait(false); var walletAddress = await _account .GetAddressAsync(swap.RedeemFromAddress, cancellationToken) .ConfigureAwait(false); if (walletAddress == null) { Log.Error("Can't get address {@address} for redeem from local db", swap.RedeemFromAddress); return; } var feeInEth = ethConfig.GetFeeAmount(ethConfig.RedeemGasLimit, gasPrice); if (walletAddress.Balance < feeInEth) { Log.Error("Insufficient funds for redeem"); return; } EthereumTransaction redeemTx; try { await EthereumAccount.AddressLocker .LockAsync(walletAddress.Address, cancellationToken) .ConfigureAwait(false); var nonceResult = await EthereumNonceManager.Instance .GetNonceAsync(ethConfig, walletAddress.Address, pending : true, cancellationToken : cancellationToken) .ConfigureAwait(false); if (nonceResult.HasError) { Log.Error("Nonce getting error with code {@code} and description {@description}", nonceResult.Error.Code, nonceResult.Error.Description); return; } var message = new RedeemFunctionMessage { FromAddress = walletAddress.Address, HashedSecret = swap.SecretHash, Secret = swap.Secret, Nonce = nonceResult.Value, GasPrice = EthereumConfig.GweiToWei(gasPrice), }; message.Gas = await EstimateGasAsync(message, new BigInteger(ethConfig.RedeemGasLimit)) .ConfigureAwait(false); var txInput = message.CreateTransactionInput(ethConfig.SwapContractAddress); redeemTx = new EthereumTransaction(ethConfig.Name, txInput) { Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapRedeem }; var signResult = await SignTransactionAsync(redeemTx, cancellationToken) .ConfigureAwait(false); if (!signResult) { Log.Error("Transaction signing error"); return; } swap.RedeemTx = redeemTx; swap.StateFlags |= SwapStateFlags.IsRedeemSigned; await UpdateSwapAsync(swap, SwapStateFlags.IsRedeemSigned, cancellationToken) .ConfigureAwait(false); await BroadcastTxAsync(swap, redeemTx, cancellationToken) .ConfigureAwait(false); } catch { throw; } finally { EthereumAccount.AddressLocker.Unlock(walletAddress.Address); } swap.RedeemTx = redeemTx; swap.StateFlags |= SwapStateFlags.IsRedeemBroadcast; await UpdateSwapAsync(swap, SwapStateFlags.IsRedeemBroadcast, cancellationToken) .ConfigureAwait(false); _ = TrackTransactionConfirmationAsync( swap: swap, currency: ethConfig, dataRepository: _account.DataRepository, txId: redeemTx.Id, confirmationHandler: RedeemConfirmedEventHandler, cancellationToken: cancellationToken); }
public async Task <Error> SendAsync( string from, string to, decimal amount, decimal gasLimit = 0, decimal gasPrice = 0, bool useDefaultFee = false, CancellationToken cancellationToken = default) { //if (from == to) // return new Error( // code: Errors.SendingAndReceivingAddressesAreSame, // description: "Sending and receiving addresses are the same."); var erc20Config = Erc20Config; if (useDefaultFee) { gasLimit = GasLimitByType(BlockchainTransactionType.Output); gasPrice = await erc20Config .GetGasPriceAsync() .ConfigureAwait(false); } var addressFeeUsage = await SelectUnspentAddressesAsync( 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 < erc20Config.TransferGasLimit) { return(new Error( code: Errors.InsufficientGas, description: "Insufficient gas")); } var feeAmount = erc20Config.GetFeeAmount(gasLimit, gasPrice); Log.Debug("Fee per transaction {@feePerTransaction}. Fee Amount {@feeAmount}", gasLimit, feeAmount); Log.Debug("Send {@amount} of {@currency} from address {@address} with available balance {@balance}", addressFeeUsage.UsedAmount, erc20Config.Name, addressFeeUsage.WalletAddress.Address, addressFeeUsage.WalletAddress.AvailableBalance()); using var addressLock = await EthereumAccount.AddressLocker .GetLockAsync(addressFeeUsage.WalletAddress.Address, cancellationToken) .ConfigureAwait(false); var nonceResult = await EthereumNonceManager.Instance .GetNonceAsync(EthConfig, addressFeeUsage.WalletAddress.Address) .ConfigureAwait(false); if (nonceResult.HasError) { return(nonceResult.Error); } TransactionInput txInput; var message = new ERC20TransferFunctionMessage { To = to.ToLowerInvariant(), Value = erc20Config.TokensToTokenDigits(addressFeeUsage.UsedAmount), FromAddress = addressFeeUsage.WalletAddress.Address, Gas = new BigInteger(gasLimit), GasPrice = new BigInteger(EthereumConfig.GweiToWei(gasPrice)), Nonce = nonceResult.Value }; txInput = message.CreateTransactionInput(erc20Config.ERC20ContractAddress); var tx = new EthereumTransaction(erc20Config.Name, txInput) { Type = BlockchainTransactionType.Output }; var signResult = await Wallet .SignAsync(tx, addressFeeUsage.WalletAddress, erc20Config, cancellationToken) .ConfigureAwait(false); if (!signResult) { return(new Error( code: Errors.TransactionSigningError, description: "Transaction signing error")); } if (!tx.Verify(erc20Config)) { return(new Error( code: Errors.TransactionVerificationError, description: "Transaction verification error")); } var broadcastResult = await erc20Config.BlockchainApi .BroadcastAsync(tx, 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); tx.Amount = erc20Config.TokensToTokenDigits(addressFeeUsage.UsedAmount); tx.To = to.ToLowerInvariant(); await UpsertTransactionAsync( tx : tx, updateBalance : false, notifyIfUnconfirmed : true, notifyIfBalanceUpdated : false, cancellationToken : cancellationToken) .ConfigureAwait(false); var ethTx = tx.Clone(); ethTx.Currency = EthConfig.Name; ethTx.Amount = 0; ethTx.Type = BlockchainTransactionType.TokenCall; await UpsertTransactionAsync( tx : ethTx, updateBalance : false, notifyIfUnconfirmed : true, notifyIfBalanceUpdated : false, cancellationToken : cancellationToken) .ConfigureAwait(false); _ = UpdateBalanceAsync(cancellationToken); return(null); }
protected async Task <EthereumTransaction> CreatePaymentTxAsync( Swap swap, int lockTimeInSeconds, CancellationToken cancellationToken = default) { var erc20Config = Erc20Config; Log.Debug("Create payment transaction from address {@adderss} for swap {@swapId}", swap.FromAddress, swap.Id); var requiredAmountInERC20 = RequiredAmountInTokens(swap, erc20Config); var refundTimeStampUtcInSec = new DateTimeOffset(swap.TimeStamp.ToUniversalTime().AddSeconds(lockTimeInSeconds)).ToUnixTimeSeconds(); var rewardForRedeemInERC20 = swap.PartyRewardForRedeem; var walletAddress = await Erc20Account .GetAddressAsync(swap.FromAddress, cancellationToken) .ConfigureAwait(false); var gasPrice = await EthConfig .GetGasPriceAsync(cancellationToken) .ConfigureAwait(false); var balanceInEth = (await EthereumAccount .GetAddressBalanceAsync( address: walletAddress.Address, cancellationToken: cancellationToken) .ConfigureAwait(false)) .Available; var feeAmountInEth = rewardForRedeemInERC20 == 0 ? erc20Config.InitiateFeeAmount(gasPrice) : erc20Config.InitiateWithRewardFeeAmount(gasPrice); if (balanceInEth < feeAmountInEth) { Log.Error( "Insufficient funds at {@address} for fee. Balance: {@balance}, feeAmount: {@feeAmount}, result: {@result}.", walletAddress.Address, balanceInEth, feeAmountInEth, balanceInEth - feeAmountInEth); return(null); } var balanceInErc20 = walletAddress.Balance; Log.Debug("Available balance: {@balance}", balanceInErc20); if (balanceInErc20 < requiredAmountInERC20) { Log.Error( "Insufficient funds at {@address}. Balance: {@balance}, required: {@result}, missing: {@missing}.", walletAddress.Address, balanceInErc20, requiredAmountInERC20, balanceInErc20 - requiredAmountInERC20); return(null); } var amountInErc20 = AmountHelper.DustProofMin( balanceInErc20, requiredAmountInERC20, erc20Config.DigitsMultiplier, erc20Config.DustDigitsMultiplier); var nonceResult = await((IEthereumBlockchainApi)erc20Config.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 initMessage = new ERC20InitiateFunctionMessage { HashedSecret = swap.SecretHash, ERC20Contract = erc20Config.ERC20ContractAddress, Participant = swap.PartyAddress, RefundTimestamp = refundTimeStampUtcInSec, Countdown = lockTimeInSeconds, Value = erc20Config.TokensToTokenDigits(amountInErc20), RedeemFee = erc20Config.TokensToTokenDigits(rewardForRedeemInERC20), Active = true, FromAddress = walletAddress.Address, GasPrice = EthereumConfig.GweiToWei(gasPrice), Nonce = nonceResult.Value }; var initiateGasLimit = rewardForRedeemInERC20 == 0 ? erc20Config.InitiateGasLimit : erc20Config.InitiateWithRewardGasLimit; initMessage.Gas = await EstimateGasAsync(initMessage, new BigInteger(initiateGasLimit)) .ConfigureAwait(false); txInput = initMessage.CreateTransactionInput(erc20Config.SwapContractAddress); return(new EthereumTransaction(erc20Config.Name, txInput) { Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapPayment }); }