Ejemplo n.º 1
0
        private async Task <decimal> FeeByType(
            BlockchainTransactionType type,
            string from,
            CancellationToken cancellationToken = default)
        {
            var xtz = Config;

            var isRevealed = await IsRevealedSourceAsync(from, cancellationToken)
                             .ConfigureAwait(false);

            var revealFeeInTez = !isRevealed
                ? xtz.RevealFee.ToTez()
                : 0;

            if (type.HasFlag(BlockchainTransactionType.SwapPayment))
            {
                return(xtz.InitiateFee.ToTez() + revealFeeInTez);
            }

            if (type.HasFlag(BlockchainTransactionType.SwapRefund))
            {
                return(xtz.RefundFee.ToTez() + revealFeeInTez);
            }

            if (type.HasFlag(BlockchainTransactionType.SwapRedeem))
            {
                return(xtz.RedeemFee.ToTez() + revealFeeInTez);
            }

            return(xtz.Fee.ToTez() + revealFeeInTez);
        }
Ejemplo n.º 2
0
        private async Task <decimal> FeeByType(
            BlockchainTransactionType type,
            string from,
            bool isFirstTx,
            CancellationToken cancellationToken = default)
        {
            var xtz = Xtz;

            var isRevealed = await IsRevealedSourceAsync(from, cancellationToken)
                             .ConfigureAwait(false);

            if (type.HasFlag(BlockchainTransactionType.SwapPayment) && isFirstTx)
            {
                return(xtz.InitiateFee.ToTez() + (isRevealed ? 0 : xtz.RevealFee.ToTez()));
            }
            if (type.HasFlag(BlockchainTransactionType.SwapPayment) && !isFirstTx)
            {
                return(xtz.AddFee.ToTez() + (isRevealed ? 0 : xtz.RevealFee.ToTez()));
            }
            if (type.HasFlag(BlockchainTransactionType.SwapRefund))
            {
                return(xtz.RefundFee.ToTez() + (isRevealed ? 0 : xtz.RevealFee.ToTez()));
            }
            if (type.HasFlag(BlockchainTransactionType.SwapRedeem))
            {
                return(xtz.RedeemFee.ToTez() + (isRevealed ? 0 : xtz.RevealFee.ToTez()));
            }

            return(xtz.Fee.ToTez() + (isRevealed ? 0 : xtz.RevealFee.ToTez()));
        }
Ejemplo n.º 3
0
        private decimal StorageFeeByType(BlockchainTransactionType type)
        {
            var fa12 = Fa12Config;

            if (type.HasFlag(BlockchainTransactionType.TokenApprove))
            {
                return(fa12.ApproveStorageLimit.ToTez());
            }

            if (type.HasFlag(BlockchainTransactionType.SwapPayment))
            {
                return(((fa12.ApproveStorageLimit * 2 + fa12.InitiateStorageLimit) * fa12.StorageFeeMultiplier).ToTez());
            }

            if (type.HasFlag(BlockchainTransactionType.SwapRefund))
            {
                return(((fa12.RefundStorageLimit - fa12.ActivationStorage) * fa12.StorageFeeMultiplier).ToTez());
            }

            if (type.HasFlag(BlockchainTransactionType.SwapRedeem))
            {
                return(((fa12.RedeemStorageLimit - fa12.ActivationStorage) * fa12.StorageFeeMultiplier).ToTez());
            }

            return(((fa12.TransferStorageLimit - fa12.ActivationStorage) * fa12.StorageFeeMultiplier).ToTez());
        }
Ejemplo n.º 4
0
        private decimal GasLimitByType(BlockchainTransactionType type)
        {
            var erc20 = Erc20Config;

            if (type.HasFlag(BlockchainTransactionType.TokenApprove))
            {
                return(erc20.ApproveGasLimit);
            }

            if (type.HasFlag(BlockchainTransactionType.SwapPayment)) // todo: recheck
            {
                return(erc20.ApproveGasLimit * 2 + erc20.InitiateWithRewardGasLimit);
            }

            if (type.HasFlag(BlockchainTransactionType.SwapRefund))
            {
                return(erc20.RefundGasLimit);
            }

            if (type.HasFlag(BlockchainTransactionType.SwapRedeem))
            {
                return(erc20.RedeemGasLimit);
            }

            return(erc20.TransferGasLimit);
        }
Ejemplo n.º 5
0
        private async Task <decimal> FeeByType(
            BlockchainTransactionType type,
            string from,
            CancellationToken cancellationToken = default)
        {
            var fa2 = FA2;

            var isRevealed = await IsRevealedSourceAsync(from, cancellationToken)
                             .ConfigureAwait(false);

            if (type.HasFlag(BlockchainTransactionType.TokenApprove))
            {
                return(fa2.ApproveFee.ToTez());
            }
            if (type.HasFlag(BlockchainTransactionType.SwapPayment))
            {
                return(fa2.ApproveFee.ToTez() + fa2.InitiateFee.ToTez() + (isRevealed ? 0 : fa2.RevealFee.ToTez()));
            }
            if (type.HasFlag(BlockchainTransactionType.SwapRefund))
            {
                return(fa2.RefundFee.ToTez() + (isRevealed ? 0 : fa2.RevealFee.ToTez()));
            }
            if (type.HasFlag(BlockchainTransactionType.SwapRedeem))
            {
                return(fa2.RedeemFee.ToTez() + (isRevealed ? 0 : fa2.RevealFee.ToTez()));
            }

            return(fa2.TransferFee.ToTez() + (isRevealed ? 0 : fa2.RevealFee.ToTez()));
        }
Ejemplo n.º 6
0
        private decimal StorageFeeByTypeAsync(
            BlockchainTransactionType type,
            bool isFirstTx)
        {
            var fa12 = Fa12;

            if (type.HasFlag(BlockchainTransactionType.TokenApprove))
            {
                return(fa12.ApproveStorageLimit);
            }
            if (type.HasFlag(BlockchainTransactionType.SwapPayment) && isFirstTx)
            {
                return((fa12.ApproveStorageLimit * 2 + fa12.InitiateStorageLimit) / fa12.StorageFeeMultiplier);
            }
            if (type.HasFlag(BlockchainTransactionType.SwapPayment) && !isFirstTx)
            {
                return((fa12.ApproveStorageLimit * 2 + fa12.AddStorageLimit) / fa12.StorageFeeMultiplier);
            }
            if (type.HasFlag(BlockchainTransactionType.SwapRefund))
            {
                return((fa12.RefundStorageLimit - fa12.ActivationStorage) / fa12.StorageFeeMultiplier);
            }
            if (type.HasFlag(BlockchainTransactionType.SwapRedeem))
            {
                return((fa12.RedeemStorageLimit - fa12.ActivationStorage) / fa12.StorageFeeMultiplier);
            }

            return((fa12.TransferStorageLimit - fa12.ActivationStorage) / fa12.StorageFeeMultiplier);
        }
Ejemplo n.º 7
0
        public override async Task <decimal?> EstimateFeeAsync(
            string to,
            decimal amount,
            BlockchainTransactionType type,
            decimal fee      = 0,
            decimal feePrice = 0,
            CancellationToken cancellationToken = default)
        {
            var unspentAddresses = (await DataRepository
                                    .GetUnspentAddressesAsync(Currency)
                                    .ConfigureAwait(false))
                                   .ToList();

            if (!type.HasFlag(BlockchainTransactionType.SwapRedeem) &&
                !type.HasFlag(BlockchainTransactionType.SwapRefund))
            {
                unspentAddresses = unspentAddresses
                                   .Where(w => w.Address != to)
                                   .ToList();
            }

            if (!unspentAddresses.Any())
            {
                return(null); // insufficient funds
            }
            var selectedAddresses = (await SelectUnspentAddressesAsync(
                                         from: unspentAddresses,
                                         to: to,
                                         amount: amount,
                                         fee: fee,
                                         feePrice: 0,
                                         feeUsagePolicy: fee == 0 ? FeeUsagePolicy.EstimatedFee : FeeUsagePolicy.FeeForAllTransactions,
                                         addressUsagePolicy: AddressUsagePolicy.UseMinimalBalanceFirst,
                                         transactionType: type,
                                         cancellationToken: cancellationToken)
                                     .ConfigureAwait(false))
                                    .ToList();

            if (!selectedAddresses.Any())
            {
                return(null); // insufficient funds
            }
            return(selectedAddresses.Sum(s => s.UsedFee));
        }
Ejemplo n.º 8
0
        private decimal GasLimitByType(BlockchainTransactionType type)
        {
            var eth = EthConfig;

            if (type.HasFlag(BlockchainTransactionType.SwapPayment))
            {
                return(eth.InitiateWithRewardGasLimit);
            }

            if (type.HasFlag(BlockchainTransactionType.SwapRefund))
            {
                return(eth.RefundGasLimit);
            }

            if (type.HasFlag(BlockchainTransactionType.SwapRedeem))
            {
                return(eth.RedeemGasLimit);
            }

            return(eth.GasLimit);
        }
Ejemplo n.º 9
0
        public static TransactionType GetType(BlockchainTransactionType type)
        {
            if (type.HasFlag(BlockchainTransactionType.SwapPayment))
            {
                return(TransactionType.SwapPayment);
            }

            if (type.HasFlag(BlockchainTransactionType.SwapRedeem))
            {
                return(TransactionType.SwapRedeem);
            }

            if (type.HasFlag(BlockchainTransactionType.SwapRefund))
            {
                return(TransactionType.SwapRefund);
            }

            if (type.HasFlag(BlockchainTransactionType.SwapCall))
            {
                return(TransactionType.SwapCall);
            }

            if (type.HasFlag(BlockchainTransactionType.TokenCall))
            {
                return(TransactionType.TokenCall);
            }

            if (type.HasFlag(BlockchainTransactionType.TokenApprove))
            {
                return(TransactionType.TokenApprove);
            }

            if (type.HasFlag(BlockchainTransactionType.Input) &&
                type.HasFlag(BlockchainTransactionType.Output))
            {
                return(TransactionType.Output);
            }

            if (type.HasFlag(BlockchainTransactionType.Input))
            {
                return(TransactionType.Input);
            }

            return(TransactionType.Output);
        }
Ejemplo n.º 10
0
        public override async Task <IEnumerable <WalletAddress> > GetUnspentAddressesAsync(
            string toAddress,
            decimal amount,
            decimal fee,
            decimal feePrice,
            FeeUsagePolicy feeUsagePolicy,
            AddressUsagePolicy addressUsagePolicy,
            BlockchainTransactionType transactionType,
            CancellationToken cancellationToken = default)
        {
            var unspentAddresses = (await DataRepository
                                    .GetUnspentAddressesAsync(Currency)
                                    .ConfigureAwait(false))
                                   .ToList();

            if (!transactionType.HasFlag(BlockchainTransactionType.SwapRedeem) &&
                !transactionType.HasFlag(BlockchainTransactionType.SwapRefund))
            {
                unspentAddresses = unspentAddresses
                                   .Where(w => w.Address != toAddress)
                                   .ToList();
            }

            var selectedAddresses = await SelectUnspentAddressesAsync(
                from : unspentAddresses,
                to : toAddress,
                amount : amount,
                fee : fee,
                feePrice : 0,
                feeUsagePolicy : feeUsagePolicy,
                addressUsagePolicy : addressUsagePolicy,
                transactionType : transactionType,
                cancellationToken : cancellationToken)
                                    .ConfigureAwait(false);

            return(ResolvePublicKeys(selectedAddresses
                                     .Select(w => w.WalletAddress)
                                     .ToList()));
        }
Ejemplo n.º 11
0
        private async Task <decimal> StorageFeeByTypeAsync(
            BlockchainTransactionType type,
            string to,
            bool isFirstTx,
            CancellationToken cancellationToken = default)
        {
            var xtz = Xtz;

            var isActive = to != null
                ? await IsAllocatedDestinationAsync(type, to, cancellationToken)
                           .ConfigureAwait(false)
                : false;

            if (type.HasFlag(BlockchainTransactionType.SwapPayment) && isFirstTx)
            {
                return(xtz.InitiateStorageLimit / xtz.StorageFeeMultiplier);
            }
            if (type.HasFlag(BlockchainTransactionType.SwapPayment) && !isFirstTx)
            {
                return(xtz.AddStorageLimit / xtz.StorageFeeMultiplier);
            }
            if (type.HasFlag(BlockchainTransactionType.SwapRefund))
            {
                return(isActive
                    ? Math.Max((xtz.RefundStorageLimit - xtz.ActivationStorage) / xtz.StorageFeeMultiplier, 0) // without activation storage fee
                    : xtz.RefundStorageLimit / xtz.StorageFeeMultiplier);
            }
            if (type.HasFlag(BlockchainTransactionType.SwapRedeem))
            {
                return(isActive
                    ? Math.Max((xtz.RedeemStorageLimit - xtz.ActivationStorage) / xtz.StorageFeeMultiplier, 0) // without activation storage fee
                    : xtz.RedeemStorageLimit / xtz.StorageFeeMultiplier);
            }

            return(isActive || !isFirstTx
                ? Math.Max((xtz.StorageLimit - xtz.ActivationStorage) / xtz.StorageFeeMultiplier, 0) // without activation storage fee
                : xtz.StorageLimit / xtz.StorageFeeMultiplier);
        }
Ejemplo n.º 12
0
        private decimal GasLimitByType(BlockchainTransactionType type, bool isFirstTx)
        {
            var eth = Eth;

            if (type.HasFlag(BlockchainTransactionType.SwapPayment) && isFirstTx)
            {
                return(eth.InitiateWithRewardGasLimit);
            }
            if (type.HasFlag(BlockchainTransactionType.SwapPayment) && !isFirstTx)
            {
                return(eth.AddGasLimit);
            }
            if (type.HasFlag(BlockchainTransactionType.SwapRefund))
            {
                return(eth.RefundGasLimit);
            }
            if (type.HasFlag(BlockchainTransactionType.SwapRedeem))
            {
                return(eth.RedeemGasLimit);
            }

            return(eth.GasLimit);
        }
Ejemplo n.º 13
0
        private async Task <decimal> FeeByType(
            BlockchainTransactionType type,
            string from,
            CancellationToken cancellationToken = default)
        {
            var fa12 = Fa12Config;

            var isRevealed = from != null && await _tezosAccount
                             .IsRevealedSourceAsync(from, cancellationToken)
                             .ConfigureAwait(false);

            var revealFeeInTez = !isRevealed
                ? fa12.RevealFee.ToTez()
                : 0;

            if (type.HasFlag(BlockchainTransactionType.TokenApprove))
            {
                return(fa12.ApproveFee.ToTez());
            }

            if (type.HasFlag(BlockchainTransactionType.SwapPayment))
            {
                return(fa12.ApproveFee.ToTez() * 2 + fa12.InitiateFee.ToTez() + revealFeeInTez);
            }

            if (type.HasFlag(BlockchainTransactionType.SwapRefund))
            {
                return(fa12.RefundFee.ToTez() + revealFeeInTez);
            }

            if (type.HasFlag(BlockchainTransactionType.SwapRedeem))
            {
                return(fa12.RedeemFee.ToTez() + revealFeeInTez);
            }

            return(fa12.TransferFee.ToTez() + revealFeeInTez);
        }
Ejemplo n.º 14
0
        private decimal StorageFeeByTypeAsync(
            BlockchainTransactionType type)
        {
            var nyx = NYX;

            if (type.HasFlag(BlockchainTransactionType.TokenApprove))
            {
                return(nyx.ApproveStorageLimit);
            }
            if (type.HasFlag(BlockchainTransactionType.SwapPayment))
            {
                return((nyx.ApproveStorageLimit + nyx.InitiateStorageLimit) / nyx.StorageFeeMultiplier);
            }
            if (type.HasFlag(BlockchainTransactionType.SwapRefund))
            {
                return((nyx.RefundStorageLimit - nyx.ActivationStorage) / nyx.StorageFeeMultiplier);
            }
            if (type.HasFlag(BlockchainTransactionType.SwapRedeem))
            {
                return((nyx.RedeemStorageLimit - nyx.ActivationStorage) / nyx.StorageFeeMultiplier);
            }

            return((nyx.TransferStorageLimit - nyx.ActivationStorage) / nyx.StorageFeeMultiplier);
        }
Ejemplo n.º 15
0
        private async Task <decimal> StorageFeeByTypeAsync(
            BlockchainTransactionType type,
            string to,
            CancellationToken cancellationToken = default)
        {
            var xtz = Config;

            var isActive = await IsAllocatedDestinationAsync(to, cancellationToken)
                           .ConfigureAwait(false);

            if (type.HasFlag(BlockchainTransactionType.SwapPayment))
            {
                return((xtz.InitiateStorageLimit * xtz.StorageFeeMultiplier).ToTez());
            }

            if (type.HasFlag(BlockchainTransactionType.SwapRefund))
            {
                return((isActive
                    ? Math.Max((xtz.RefundStorageLimit - xtz.ActivationStorage) * xtz.StorageFeeMultiplier, 0) // without activation storage fee
                    : xtz.RefundStorageLimit *xtz.StorageFeeMultiplier)
                       .ToTez());
            }

            if (type.HasFlag(BlockchainTransactionType.SwapRedeem))
            {
                return((isActive
                    ? Math.Max((xtz.RedeemStorageLimit - xtz.ActivationStorage) * xtz.StorageFeeMultiplier, 0) // without activation storage fee
                    : xtz.RedeemStorageLimit *xtz.StorageFeeMultiplier)
                       .ToTez());
            }

            return((isActive
                ? Math.Max((xtz.StorageLimit - xtz.ActivationStorage) * xtz.StorageFeeMultiplier, 0) // without activation storage fee
                : xtz.StorageLimit *xtz.StorageFeeMultiplier)
                   .ToTez());
        }
Ejemplo n.º 16
0
 public static string GetDescription(
     BlockchainTransactionType type,
     decimal amount,
     decimal netAmount,
     int amountDigits,
     string currencyCode)
 {
     if (type.HasFlag(BlockchainTransactionType.SwapPayment))
     {
         return($"Swap payment {Math.Abs(amount).ToString("0." + new string('#', amountDigits))} {currencyCode}");
     }
     else if (type.HasFlag(BlockchainTransactionType.SwapRefund))
     {
         return($"Swap refund {Math.Abs(netAmount).ToString("0." + new string('#', amountDigits))} {currencyCode}");
     }
     else if (type.HasFlag(BlockchainTransactionType.SwapRedeem))
     {
         return($"Swap redeem {Math.Abs(netAmount).ToString("0." + new string('#', amountDigits))} {currencyCode}");
     }
     else if (type.HasFlag(BlockchainTransactionType.TokenApprove))
     {
         return($"Token approve");
     }
     else if (type.HasFlag(BlockchainTransactionType.TokenCall))
     {
         return($"Token call");
     }
     else if (type.HasFlag(BlockchainTransactionType.SwapCall))
     {
         return($"Token swap call");
     }
     else if (amount <= 0)
     {
         return($"Sent {Math.Abs(netAmount).ToString("0." + new string('#', amountDigits))} {currencyCode}");
     }
     else if (amount > 0)
     {
         return($"Received {Math.Abs(netAmount).ToString("0." + new string('#', amountDigits))} {currencyCode}");
     }
     else
     {
         return("Unknown transaction");
     }
 }
Ejemplo n.º 17
0
        public override async Task <(decimal, decimal, decimal)> EstimateMaxAmountToSendAsync(
            string to,
            BlockchainTransactionType type,
            decimal fee      = 0,
            decimal feePrice = 0,
            bool reserve     = false,
            CancellationToken cancellationToken = default)
        {
            var xtz = Xtz;

            var unspentAddresses = (await DataRepository
                                    .GetUnspentAddressesAsync(Currency)
                                    .ConfigureAwait(false))
                                   .ToList();

            if (!type.HasFlag(BlockchainTransactionType.SwapRedeem) &&
                !type.HasFlag(BlockchainTransactionType.SwapRefund))
            {
                unspentAddresses = unspentAddresses
                                   .Where(w => w.Address != to)
                                   .ToList();
            }

            if (!unspentAddresses.Any())
            {
                return(0m, 0m, 0m);
            }

            // minimum balance first
            unspentAddresses = unspentAddresses
                               .ToList()
                               .SortList(new AvailableBalanceAscending());

            // use only max balance for swap payment
            if (type.HasFlag(BlockchainTransactionType.SwapPayment))
            {
                unspentAddresses.RemoveRange(0, unspentAddresses.Count - 1);
            }

            var amount    = 0m;
            var feeAmount = 0m;

            var reserveFee = ReserveFee();

            foreach (var address in unspentAddresses)
            {
                var xtzAddress = await DataRepository
                                 .GetWalletAddressAsync(xtz.Name, address.Address)
                                 .ConfigureAwait(false);

                if (xtzAddress == null)
                {
                    continue;
                }

                var availableBalanceInTez = xtzAddress.AvailableBalance();

                var feeInTez = await FeeByType(
                    type : type,
                    from : address.Address,
                    cancellationToken : cancellationToken)
                               .ConfigureAwait(false);

                var storageFeeInTez = StorageFeeByTypeAsync(
                    type: type);

                availableBalanceInTez = availableBalanceInTez - feeInTez - storageFeeInTez - ((reserve && address == unspentAddresses.Last()) ? reserveFee : 0) - xtz.MicroTezReserve.ToTez();

                if (availableBalanceInTez < 0)
                {
                    continue;
                }

                amount    += address.AvailableBalance();
                feeAmount += fee == 0 ? feeInTez : availableBalanceInTez + feeInTez;
            }

            return(amount, feeAmount, reserveFee);
        }
Ejemplo n.º 18
0
        public override async Task <(decimal, decimal, decimal)> EstimateMaxAmountToSendAsync(
            string to,
            BlockchainTransactionType type,
            decimal feePerTx = 0,
            decimal feePrice = 0,
            bool reserve     = false,
            CancellationToken cancellationToken = default)
        {
            var eth = Eth;

            var unspentAddresses = (await DataRepository
                                    .GetUnspentAddressesAsync(Currency)
                                    .ConfigureAwait(false))
                                   .ToList();

            if (!type.HasFlag(BlockchainTransactionType.SwapRedeem) &&
                !type.HasFlag(BlockchainTransactionType.SwapRefund))
            {
                unspentAddresses = unspentAddresses
                                   .Where(w => w.Address != to)
                                   .ToList();
            }

            if (!unspentAddresses.Any())
            {
                return(0m, 0m, 0m);
            }

            // minimum balance first
            unspentAddresses = unspentAddresses
                               .ToList()
                               .SortList(new AvailableBalanceAscending());

            var isFirstTx = true;
            var amount    = 0m;
            var fee       = 0m;

            var reserveFeeInEth = ReserveFee();

            foreach (var address in unspentAddresses)
            {
                var feeInEth = eth.GetFeeAmount(feePerTx == 0 ? GasLimitByType(type, isFirstTx) : feePerTx, feePrice == 0 ? eth.GasPriceInGwei : feePrice);

                var usedAmountInEth = Math.Max(address.AvailableBalance() - feeInEth - (reserve && address == unspentAddresses.Last() ? reserveFeeInEth : 0), 0);

                if (usedAmountInEth <= 0)
                {
                    continue;
                }

                amount += usedAmountInEth;
                fee    += feeInEth;

                if (isFirstTx)
                {
                    isFirstTx = false;
                }
            }

            return(amount, fee, 0m);
        }
Ejemplo n.º 19
0
        public override async Task <(decimal, decimal, decimal)> EstimateMaxAmountToSendAsync(
            string to,
            BlockchainTransactionType type,
            decimal feeAmount = 0,
            decimal feePrice  = 0,
            bool reserve      = false,
            CancellationToken cancellationToken = default)
        {
            var unspentAddresses = (await DataRepository
                                    .GetUnspentAddressesAsync(Currency)
                                    .ConfigureAwait(false))
                                   .ToList();

            if (!type.HasFlag(BlockchainTransactionType.SwapRedeem) &&
                !type.HasFlag(BlockchainTransactionType.SwapRefund))
            {
                unspentAddresses = unspentAddresses
                                   .Where(w => w.Address != to)
                                   .ToList();
            }

            if (!unspentAddresses.Any())
            {
                return(0m, 0m, 0m);
            }

            // minimum balance first
            unspentAddresses = unspentAddresses
                               .ToList()
                               .SortList(new AvailableBalanceAscending());

            var isFirstTx = true;
            var amount    = 0m;
            var fee       = 0m;

            var reserveFee = ReserveFee();

            foreach (var address in unspentAddresses)
            {
                var feeInTez = await FeeByType(
                    type : type,
                    from : address.Address,
                    isFirstTx : isFirstTx,
                    cancellationToken : cancellationToken)
                               .ConfigureAwait(false);

                var storageFeeInTez = await StorageFeeByTypeAsync(
                    type : type,
                    to : to,
                    isFirstTx : isFirstTx,
                    cancellationToken : cancellationToken)
                                      .ConfigureAwait(false);

                var usedAmountInTez = Math.Max(address.AvailableBalance() - feeInTez - storageFeeInTez - (reserve && address == unspentAddresses.Last() ? reserveFee : 0) - Xtz.MicroTezReserve.ToTez(), 0);

                if (usedAmountInTez <= 0)
                {
                    continue;
                }

                amount += usedAmountInTez;
                fee    += feeInTez;

                if (isFirstTx)
                {
                    isFirstTx = false;
                }
            }

            return(amount, fee, reserveFee);
        }