Ejemplo n.º 1
0
        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);
        }
Ejemplo n.º 2
0
        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
            });
        }