protected override async Task <IEnumerable <EthereumTransaction> > CreatePaymentTxsAsync( Swap swap, int lockTimeInSeconds, CancellationToken cancellationToken = default) { var erc20 = Erc20; Log.Debug("Create payment transactions for swap {@swapId}", swap.Id); var requiredAmountInERC20 = AmountHelper.QtyToAmount(swap.Side, swap.Qty, swap.Price, erc20.DigitsMultiplier); var refundTimeStampUtcInSec = new DateTimeOffset(swap.TimeStamp.ToUniversalTime().AddSeconds(lockTimeInSeconds)).ToUnixTimeSeconds(); var isInitTx = true; var rewardForRedeemInERC20 = swap.PartyRewardForRedeem; var unspentAddresses = (await Erc20Account .GetUnspentAddressesAsync(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 EthereumAccount .GetAddressBalanceAsync( address: walletAddress.Address, cancellationToken: cancellationToken) .ConfigureAwait(false)) .Available; var balanceInERC20 = (await Erc20Account .GetAddressBalanceAsync( address: walletAddress.Address, cancellationToken: cancellationToken) .ConfigureAwait(false)) .Available; Log.Debug("Available balance: {@balance}", balanceInERC20); var feeAmountInEth = (isInitTx ? rewardForRedeemInERC20 == 0 ? erc20.InitiateFeeAmount : erc20.InitiateWithRewardFeeAmount : erc20.AddFeeAmount) + erc20.ApproveFeeAmount; if (balanceInEth - feeAmountInEth <= 0) { Log.Warning( "Insufficient funds at {@address}. Balance: {@balance}, feeAmount: {@feeAmount}, result: {@result}.", walletAddress.Address, balanceInEth, feeAmountInEth, balanceInEth - feeAmountInEth); continue; } var amountInERC20 = requiredAmountInERC20 > 0 ? AmountHelper.DustProofMin(balanceInERC20, requiredAmountInERC20, erc20.DigitsMultiplier, erc20.DustDigitsMultiplier) : 0; requiredAmountInERC20 -= amountInERC20; var nonceResult = await EthereumNonceManager.Instance .GetNonceAsync(erc20, 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); } var nonce = nonceResult.Value; var allowanceMessage = new ERC20AllowanceFunctionMessage() { Owner = walletAddress.Address, Spender = erc20.SwapContractAddress, FromAddress = walletAddress.Address }; var allowance = await((IEthereumBlockchainApi)erc20.BlockchainApi) .GetERC20AllowanceAsync( erc20: erc20, tokenAddress: erc20.ERC20ContractAddress, allowanceMessage: allowanceMessage, cancellationToken: cancellationToken) .ConfigureAwait(false); if (allowance.Value > 0) { transactions.Add(await CreateApproveTx(walletAddress, nonceResult.Value, 0) .ConfigureAwait(false)); nonce += 1; } else { transactions.Add(new EthereumTransaction()); } transactions.Add(await CreateApproveTx(walletAddress, nonce, erc20.TokensToTokenDigits(amountInERC20)) .ConfigureAwait(false)); nonce += 1; TransactionInput txInput; //actual transfer if (isInitTx) { var initMessage = new ERC20InitiateFunctionMessage { HashedSecret = swap.SecretHash, ERC20Contract = erc20.ERC20ContractAddress, Participant = swap.PartyAddress, RefundTimestamp = refundTimeStampUtcInSec, Countdown = lockTimeInSeconds, Value = erc20.TokensToTokenDigits(amountInERC20), RedeemFee = erc20.TokensToTokenDigits(rewardForRedeemInERC20), Active = true, FromAddress = walletAddress.Address, GasPrice = Atomex.Ethereum.GweiToWei(erc20.GasPriceInGwei), Nonce = nonce }; var initiateGasLimit = rewardForRedeemInERC20 == 0 ? erc20.InitiateGasLimit : erc20.InitiateWithRewardGasLimit; initMessage.Gas = await EstimateGasAsync(initMessage, new BigInteger(initiateGasLimit)) .ConfigureAwait(false); txInput = initMessage.CreateTransactionInput(erc20.SwapContractAddress); } else { var addMessage = new ERC20AddFunctionMessage { HashedSecret = swap.SecretHash, Value = erc20.TokensToTokenDigits(amountInERC20), FromAddress = walletAddress.Address, GasPrice = Atomex.Ethereum.GweiToWei(erc20.GasPriceInGwei), Nonce = nonce }; addMessage.Gas = await EstimateGasAsync(addMessage, new BigInteger(erc20.AddGasLimit)) .ConfigureAwait(false); txInput = addMessage.CreateTransactionInput(erc20.SwapContractAddress); } transactions.Add(new EthereumTransaction(erc20, txInput) { Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapPayment }); if (isInitTx) { isInitTx = false; } if (requiredAmountInERC20 <= 0) { break; } } if (requiredAmountInERC20 > 0) { Log.Warning("Insufficient ERC20 or Eth funds (left {@requiredAmount}).", requiredAmountInERC20); return(Enumerable.Empty <EthereumTransaction>()); } return(transactions); }
private async Task <IList <EthereumTransaction> > CreateApproveTxsAsync( Swap swap, EthereumTransaction paymentTx, CancellationToken cancellationToken = default) { var erc20 = Erc20Config; Log.Debug("Create approve transactions for swap {@swapId}", swap.Id); var transactions = new List <EthereumTransaction>(); var walletAddress = await Erc20Account .GetAddressAsync(paymentTx.From, cancellationToken) .ConfigureAwait(false); var gasPrice = await EthConfig .GetGasPriceAsync(cancellationToken) .ConfigureAwait(false); var allowanceMessage = new ERC20AllowanceFunctionMessage { Owner = walletAddress.Address, Spender = erc20.SwapContractAddress, FromAddress = walletAddress.Address }; var allowance = await((IEthereumBlockchainApi)erc20.BlockchainApi) .GetERC20AllowanceAsync( erc20: erc20, tokenAddress: erc20.ERC20ContractAddress, allowanceMessage: allowanceMessage, cancellationToken: cancellationToken) .ConfigureAwait(false); var nonceResult = await((IEthereumBlockchainApi)erc20.BlockchainApi) .GetTransactionCountAsync(walletAddress.Address, pending: false, cancellationToken) .ConfigureAwait(false); if (nonceResult.HasError) { Log.Error($"Getting nonce error: {nonceResult.Error.Description}"); return(new List <EthereumTransaction>()); } if (allowance.Value > 0) { var tx = await CreateApproveTx( walletAddress : walletAddress.Address, nonce : nonceResult.Value, value : 0, gasPrice : gasPrice) .ConfigureAwait(false); transactions.Add(tx); } var requiredAmountInErc20 = RequiredAmountInTokens(swap, erc20); var amountInErc20 = AmountHelper.DustProofMin( walletAddress.Balance, requiredAmountInErc20, erc20.DigitsMultiplier, erc20.DustDigitsMultiplier); var approveTx = await CreateApproveTx( walletAddress : walletAddress.Address, nonce : nonceResult.Value, value : erc20.TokensToTokenDigits(amountInErc20), gasPrice : gasPrice) .ConfigureAwait(false); transactions.Add(approveTx); return(transactions); }