Example #1
0
        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);
        }
        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
            });
        }