Exemple #1
0
        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);
        }
        public override async Task <Error> SendAsync(
            IEnumerable <WalletAddress> from,
            string to,
            decimal amount,
            decimal feePerTx   = 0,
            decimal feePrice   = 0,
            bool useDefaultFee = false,
            CancellationToken cancellationToken = default)
        {
            var erc20 = Erc20;

            var fromAddresses = from
                                .Where(w => w.Address != to) // filter self address usage
                                .ToList();

            var selectedAddresses = (await SelectUnspentAddresses(
                                         from: fromAddresses,
                                         amount: amount,
                                         fee: feePerTx,
                                         feePrice: feePrice,
                                         feeUsagePolicy: useDefaultFee ? FeeUsagePolicy.EstimatedFee : FeeUsagePolicy.FeePerTransaction,
                                         addressUsagePolicy: AddressUsagePolicy.UseMinimalBalanceFirst,
                                         transactionType: BlockchainTransactionType.Output)
                                     .ConfigureAwait(false))
                                    .ToList();

            if (!selectedAddresses.Any())
            {
                return(new Error(
                           code: Errors.InsufficientFunds,
                           description: "Insufficient funds"));
            }

            if (feePerTx < erc20.TransferGasLimit)
            {
                return(new Error(
                           code: Errors.InsufficientGas,
                           description: "Insufficient gas"));
            }

            var feeAmount = erc20.GetFeeAmount(feePerTx, feePrice);

            Log.Debug("Fee per transaction {@feePerTransaction}. Fee Amount {@feeAmount}",
                      feePerTx,
                      feeAmount);

            foreach (var selectedAddress in selectedAddresses)
            {
                Log.Debug("Send {@amount} of {@currency} from address {@address} with available balance {@balance}",
                          selectedAddress.UsedAmount,
                          erc20.Name,
                          selectedAddress.WalletAddress.Address,
                          selectedAddress.WalletAddress.AvailableBalance());

                var nonceResult = await EthereumNonceManager.Instance
                                  .GetNonceAsync(Eth, selectedAddress.WalletAddress.Address)
                                  .ConfigureAwait(false);

                if (nonceResult.HasError)
                {
                    return(nonceResult.Error);
                }

                TransactionInput txInput;

                var message = new ERC20TransferFunctionMessage
                {
                    To          = to.ToLowerInvariant(),
                    Value       = erc20.TokensToTokenDigits(selectedAddress.UsedAmount),
                    FromAddress = selectedAddress.WalletAddress.Address,
                    Gas         = new BigInteger(feePerTx),
                    GasPrice    = new BigInteger(Atomex.Ethereum.GweiToWei(feePrice)),
                    Nonce       = nonceResult.Value
                };

                txInput = message.CreateTransactionInput(erc20.ERC20ContractAddress);

                var tx = new EthereumTransaction(erc20, txInput)
                {
                    Type = BlockchainTransactionType.Output
                };

                var signResult = await Wallet
                                 .SignAsync(tx, selectedAddress.WalletAddress, cancellationToken)
                                 .ConfigureAwait(false);

                if (!signResult)
                {
                    return(new Error(
                               code: Errors.TransactionSigningError,
                               description: "Transaction signing error"));
                }

                if (!tx.Verify())
                {
                    return(new Error(
                               code: Errors.TransactionVerificationError,
                               description: "Transaction verification error"));
                }

                var broadcastResult = await erc20.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 = erc20.TokensToTokenDigits(selectedAddress.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 = Eth;
                ethTx.Amount   = 0;
                ethTx.Type     = BlockchainTransactionType.TokenCall;

                await UpsertTransactionAsync(
                    tx : ethTx,
                    updateBalance : false,
                    notifyIfUnconfirmed : true,
                    notifyIfBalanceUpdated : false,
                    cancellationToken : cancellationToken)
                .ConfigureAwait(false);
            }

            UpdateBalanceAsync(cancellationToken)
            .FireAndForget();

            return(null);
        }