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); }