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(); var selectedAddresses = (await SelectUnspentAddresses( from: unspentAddresses, amount: amount, fee: fee, feePrice: feePrice, feeUsagePolicy: feeUsagePolicy, addressUsagePolicy: addressUsagePolicy, transactionType: transactionType) .ConfigureAwait(false)) .ToList(); if (toAddress != null && selectedAddresses.Any(a => a.WalletAddress.Address == toAddress)) { selectedAddresses.RemoveAll(a => a.WalletAddress.Address != toAddress); } return(ResolvePublicKeys(selectedAddresses .Select(w => w.WalletAddress) .ToList())); }
protected IEnumerable <WalletAddress> ApplyAddressUsagePolicy( List <WalletAddress> addresses, decimal amount, decimal fee, decimal feePrice, AddressUsagePolicy addressUsagePolicy) { var currency = Currencies.GetByName(Currency); switch (addressUsagePolicy) { case AddressUsagePolicy.UseMinimalBalanceFirst: addresses = addresses.SortList(new AvailableBalanceAscending()); break; case AddressUsagePolicy.UseMaximumBalanceFirst: addresses = addresses.SortList(new AvailableBalanceDescending()); break; case AddressUsagePolicy.UseOnlyOneAddress: var walletAddress = addresses .FirstOrDefault(w => w.AvailableBalance() >= amount + currency.GetFeeAmount(fee, feePrice)); return(walletAddress != null ? new List <WalletAddress> { walletAddress } : Enumerable.Empty <WalletAddress>()); default: throw new Exception("Address usage policy not supported"); } return(addresses); }
public override async Task <IEnumerable <WalletAddress> > GetUnspentAddressesAsync( decimal amount, decimal fee, decimal feePrice, bool isFeePerTransaction, AddressUsagePolicy addressUsagePolicy, CancellationToken cancellationToken = default(CancellationToken)) { var unspentAddresses = (await DataRepository .GetUnspentAddressesAsync(Currency) .ConfigureAwait(false)) .ToList(); var selectedAddresses = await SelectUnspentAddressesAsync( from : unspentAddresses, to : null, amount : amount, fee : fee, isFeePerTransaction : isFeePerTransaction, addressUsagePolicy : addressUsagePolicy, cancellationToken : cancellationToken) .ConfigureAwait(false); return(ResolvePublicKeys(selectedAddresses .Select(w => w.WalletAddress) .ToList())); }
protected IEnumerable <WalletAddress> ApplyAddressUsagePolicy( List <WalletAddress> addresses, decimal amount, decimal fee, decimal feePrice, AddressUsagePolicy addressUsagePolicy) { switch (addressUsagePolicy) { case AddressUsagePolicy.UseMinimalBalanceFirst: addresses = addresses .SortList(comparison: (a, b) => a.AvailableBalance().CompareTo(b.AvailableBalance())); break; case AddressUsagePolicy.UseMaximumBalanceFirst: addresses = addresses .SortList(comparison: (a, b) => b.AvailableBalance().CompareTo(a.AvailableBalance())); break; case AddressUsagePolicy.UseOnlyOneAddress: var walletAddress = addresses .FirstOrDefault(w => w.AvailableBalance() >= amount + Currency.GetFeeAmount(fee, feePrice)); return(walletAddress != null ? new List <WalletAddress> { walletAddress } : Enumerable.Empty <WalletAddress>()); default: throw new Exception("Address usage policy not supported"); } return(addresses); }
public abstract Task <IEnumerable <WalletAddress> > GetUnspentAddressesAsync( decimal amount, decimal fee, decimal feePrice, bool isFeePerTransaction, AddressUsagePolicy addressUsagePolicy, CancellationToken cancellationToken = default(CancellationToken));
public abstract Task <IEnumerable <WalletAddress> > GetUnspentAddressesAsync( string toAddress, decimal amount, decimal fee, decimal feePrice, FeeUsagePolicy feeUsagePolicy, AddressUsagePolicy addressUsagePolicy, BlockchainTransactionType transactionType, CancellationToken cancellationToken = default);
public override async Task <IEnumerable <WalletAddress> > GetUnspentAddressesAsync( decimal amount, decimal fee, decimal feePrice, bool isFeePerTransaction, AddressUsagePolicy addressUsagePolicy, CancellationToken cancellationToken = default(CancellationToken)) { // todo: unspent address using policy (transaction count minimization?) var unspentAddresses = (await DataRepository .GetUnspentAddressesAsync(Currency) .ConfigureAwait(false)) .ToList(); unspentAddresses = ApplyAddressUsagePolicy( addresses: unspentAddresses, amount: amount, fee: fee, feePrice: feePrice, addressUsagePolicy: addressUsagePolicy) .ToList(); if (unspentAddresses.Count == 0) { return(unspentAddresses); } var requiredAmount = amount + fee; var usedAddresses = new List <WalletAddress>(); var usedAmount = 0m; foreach (var walletAddress in unspentAddresses) { if (usedAmount >= requiredAmount) { break; } usedAddresses.Add(walletAddress); usedAmount += walletAddress.AvailableBalance(); } if (requiredAmount > 0 && usedAmount < requiredAmount) { return(Enumerable.Empty <WalletAddress>()); } return(ResolvePublicKeys(usedAddresses)); }
public Task <IEnumerable <WalletAddress> > GetUnspentAddressesAsync( Currency currency, decimal amount, decimal fee, decimal feePrice, bool isFeePerTransaction, AddressUsagePolicy addressUsagePolicy, CancellationToken cancellationToken = default(CancellationToken)) { return(GetAccountByCurrency(currency) .GetUnspentAddressesAsync( amount: amount, fee: fee, feePrice: feePrice, isFeePerTransaction: isFeePerTransaction, addressUsagePolicy: addressUsagePolicy, cancellationToken: cancellationToken)); }
public Task <IEnumerable <WalletAddress> > GetUnspentAddressesAsync( string currency, string toAddress, decimal amount, decimal fee, decimal feePrice, FeeUsagePolicy feeUsagePolicy, AddressUsagePolicy addressUsagePolicy, BlockchainTransactionType transactionType, CancellationToken cancellationToken = default) { return(GetCurrencyAccount(currency) .GetUnspentAddressesAsync( toAddress: toAddress, amount: amount, fee: fee, feePrice: feePrice, feeUsagePolicy: feeUsagePolicy, addressUsagePolicy: addressUsagePolicy, transactionType: transactionType, cancellationToken: cancellationToken)); }
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())); }
public override async Task <IEnumerable <WalletAddress> > GetUnspentAddressesAsync( string toAddress, decimal amount, decimal fee, decimal feePrice, FeeUsagePolicy feeUsagePolicy, AddressUsagePolicy addressUsagePolicy, BlockchainTransactionType transactionType, CancellationToken cancellationToken = default) { if (feeUsagePolicy == FeeUsagePolicy.EstimatedFee) { var estimatedFee = await EstimateFeeAsync( to : toAddress, amount : amount, type : transactionType, cancellationToken : cancellationToken) .ConfigureAwait(false); if (estimatedFee == null) { return(Enumerable.Empty <WalletAddress>()); } fee = estimatedFee.Value; } var unspentAddresses = (await DataRepository .GetUnspentAddressesAsync(Currency) .ConfigureAwait(false)) .ToList(); unspentAddresses = ApplyAddressUsagePolicy( addresses: unspentAddresses, amount: amount, fee: fee, feePrice: feePrice, addressUsagePolicy: addressUsagePolicy) .ToList(); if (unspentAddresses.Count == 0) { return(unspentAddresses); } var requiredAmount = amount + fee; var usedAddresses = new List <WalletAddress>(); var usedAmount = 0m; foreach (var walletAddress in unspentAddresses) { if (usedAmount >= requiredAmount) { break; } usedAddresses.Add(walletAddress); usedAmount += walletAddress.AvailableBalance(); } if (requiredAmount > 0 && usedAmount < requiredAmount) { return(Enumerable.Empty <WalletAddress>()); } return(ResolvePublicKeys(usedAddresses)); }
private async Task <IEnumerable <SelectedWalletAddress> > SelectUnspentAddressesAsync( IList <WalletAddress> from, string to, decimal amount, decimal fee, FeeUsagePolicy feeUsagePolicy, AddressUsagePolicy addressUsagePolicy, BlockchainTransactionType transactionType, CancellationToken cancellationToken = default) { var xtz = Xtz; if (addressUsagePolicy == AddressUsagePolicy.UseMinimalBalanceFirst) { from = from.ToList().SortList(new AvailableBalanceAscending()); } else if (addressUsagePolicy == AddressUsagePolicy.UseMaximumBalanceFirst) { from = from.ToList().SortList(new AvailableBalanceDescending()); } else if (addressUsagePolicy == AddressUsagePolicy.UseMaximumChainBalanceFirst) { var xtzUnspentAddresses = (await DataRepository .GetUnspentAddressesAsync(xtz.Name) .ConfigureAwait(false)) .ToList(); if (!xtzUnspentAddresses.Any()) { Log.Debug("Unsufficient XTZ ammount for FA2 token processing"); return(Enumerable.Empty <SelectedWalletAddress>()); } xtzUnspentAddresses = xtzUnspentAddresses.ToList().SortList(new AvailableBalanceDescending()); from = xtzUnspentAddresses.FindAll(a => from.Select(b => b.Address).ToList().Contains(a.Address)); } else if (addressUsagePolicy == AddressUsagePolicy.UseOnlyOneAddress) { var xtzUnspentAddresses = (await DataRepository .GetUnspentAddressesAsync(xtz.Name) .ConfigureAwait(false)) .ToList(); xtzUnspentAddresses = xtzUnspentAddresses.ToList().SortList(new AvailableBalanceAscending()); if (!xtzUnspentAddresses.Any()) { Log.Debug("Unsufficient XTZ ammount for FA2 token processing"); return(Enumerable.Empty <SelectedWalletAddress>()); } from = from.ToList() .Concat(xtzUnspentAddresses.FindAll(a => from.Select(b => b.Address).ToList().Contains(a.Address) == false)) .ToList(); //"to" used for redeem and refund - using the destination address is prefered from = from.Where(a => a.Address == to).Concat(from.Where(a => a.Address != to)).ToList(); } var result = new List <SelectedWalletAddress>(); var requiredAmount = amount; var isFirstTx = true; var completed = false; foreach (var address in from) { var availableBalanceInTokens = address.AvailableBalance(); var xtzAddress = await DataRepository .GetWalletAddressAsync(xtz.Name, address.Address) .ConfigureAwait(false); var availableBalanceInTez = xtzAddress != null?xtzAddress.AvailableBalance() : 0m; var txFeeInTez = feeUsagePolicy == FeeUsagePolicy.FeePerTransaction ? fee : await FeeByType( type : transactionType, from : address.Address, cancellationToken : cancellationToken) .ConfigureAwait(false); var storageFeeInTez = StorageFeeByTypeAsync( type: transactionType); var leftBalanceInTez = availableBalanceInTez - txFeeInTez - storageFeeInTez - xtz.MicroTezReserve.ToTez(); if (leftBalanceInTez < 0) // ignore address with balance less than fee { Log.Debug("Unsufficient XTZ ammount for FA2 token processing on address {@address} with available balance {@balance} and needed amount {@amount}", address.Address, availableBalanceInTez, txFeeInTez + storageFeeInTez + xtz.MicroTezReserve.ToTez()); continue; } if (addressUsagePolicy == AddressUsagePolicy.UseOnlyOneAddress) { if (Math.Min(availableBalanceInTokens, requiredAmount) == requiredAmount) { return new List <SelectedWalletAddress> { new SelectedWalletAddress { WalletAddress = address, UsedAmount = amount, UsedFee = txFeeInTez, UsedStorageFee = storageFeeInTez } } } ; continue; } decimal amountToUse = 0; amountToUse = Math.Min(availableBalanceInTokens, requiredAmount); requiredAmount -= amountToUse; if (amountToUse > 0) { result.Add(new SelectedWalletAddress { WalletAddress = address, UsedAmount = amountToUse, UsedFee = txFeeInTez, UsedStorageFee = storageFeeInTez }); } if (requiredAmount <= 0) { completed = true; if (feeUsagePolicy == FeeUsagePolicy.FeeForAllTransactions) { var estimatedFee = result.Sum(s => s.UsedFee); var remainingFee = fee - estimatedFee; var extraFee = 0m; if (remainingFee > 0) { foreach (var s in result) { xtzAddress = await DataRepository .GetWalletAddressAsync(xtz.Name, s.WalletAddress.Address) .ConfigureAwait(false); extraFee = s == result.Last() ? Math.Min(xtzAddress.AvailableBalance() - s.UsedFee - s.UsedStorageFee - xtz.MicroTezReserve.ToTez(), remainingFee) : Math.Min(xtzAddress.AvailableBalance() - s.UsedFee - s.UsedStorageFee - xtz.MicroTezReserve.ToTez(), Math.Round(remainingFee * s.UsedFee / estimatedFee, xtz.Digits)); remainingFee -= extraFee; estimatedFee -= s.UsedFee; s.UsedFee += extraFee; if (remainingFee <= 0) { break; } } } else if (remainingFee < 0) //todo: delete { Log.Error("Error fee is too small for transaction, fee is {@fee} with estimated fee {@estimatedFee}", fee, estimatedFee); return(Enumerable.Empty <SelectedWalletAddress>()); } } break; } if (isFirstTx) { isFirstTx = false; } } if (completed) { return(result); } if (feeUsagePolicy == FeeUsagePolicy.FeeForAllTransactions) //todo: delete { Log.Error("Error fee is too big for transaction, fee is {@fee} with estimated fee {@estimatedFee}", fee, result.Sum(s => s.UsedFee)); } return(Enumerable.Empty <SelectedWalletAddress>()); }
private async Task <IEnumerable <SelectedWalletAddress> > SelectUnspentAddressesAsync( IList <WalletAddress> from, string to, decimal amount, decimal fee, bool isFeePerTransaction, AddressUsagePolicy addressUsagePolicy, CancellationToken cancellationToken = default(CancellationToken)) { var activationFeeTez = 0m; if (to != null) { var api = (ITezosBlockchainApi)Xtz.BlockchainApi; var isActive = await api .IsActiveAddress(to, cancellationToken) .ConfigureAwait(false); activationFeeTez = !isActive ? Xtz.ActivationFee.ToTez() : 0; } if (addressUsagePolicy == AddressUsagePolicy.UseMinimalBalanceFirst) { from = from.ToList().SortList((a, b) => a.AvailableBalance().CompareTo(b.AvailableBalance())); } else if (addressUsagePolicy == AddressUsagePolicy.UseMaximumBalanceFirst) { from = from.ToList().SortList((a, b) => b.AvailableBalance().CompareTo(a.AvailableBalance())); } else if (addressUsagePolicy == AddressUsagePolicy.UseOnlyOneAddress) { var address = from.FirstOrDefault(w => w.AvailableBalance() >= amount + fee + activationFeeTez); return(address != null ? new List <SelectedWalletAddress> { new SelectedWalletAddress { WalletAddress = address, UsedAmount = amount, UsedFee = fee + activationFeeTez } } : Enumerable.Empty <SelectedWalletAddress>()); } for (var txCount = 1; txCount <= from.Count; ++txCount) { var result = new List <SelectedWalletAddress>(); var requiredAmount = amount; var feePerTx = isFeePerTransaction ? fee : fee / txCount; var firstTx = true; var completed = false; foreach (var address in from) { var availableBalance = address.AvailableBalance(); var txFee = firstTx ? feePerTx + activationFeeTez : feePerTx; if (availableBalance <= txFee) // ignore address with balance less than fee { continue; } var amountToUse = Math.Min(Math.Max(availableBalance - txFee, 0), requiredAmount); result.Add(new SelectedWalletAddress { WalletAddress = address, UsedAmount = amountToUse, UsedFee = txFee }); requiredAmount -= amountToUse; if (requiredAmount <= 0) { completed = true; break; } if (result.Count == txCount) // will need more transactions { break; } if (firstTx) { firstTx = false; } } if (completed) { return(result); } } return(Enumerable.Empty <SelectedWalletAddress>()); }
public override Task <IEnumerable <SelectedWalletAddress> > SelectUnspentAddressesAsync( IList <WalletAddress> from, string to, decimal amount, decimal fee, decimal feePrice, FeeUsagePolicy feeUsagePolicy, AddressUsagePolicy addressUsagePolicy, BlockchainTransactionType transactionType, CancellationToken cancellationToken = default) { var eth = Eth; if (addressUsagePolicy == AddressUsagePolicy.UseMinimalBalanceFirst) { from = from.ToList().SortList(new AvailableBalanceAscending()); } else if (addressUsagePolicy == AddressUsagePolicy.UseMaximumBalanceFirst) { from = from.ToList().SortList(new AvailableBalanceDescending()); } else if (addressUsagePolicy == AddressUsagePolicy.UseOnlyOneAddress) { var feeInEth = feeUsagePolicy == FeeUsagePolicy.EstimatedFee ? eth.GetFeeAmount(GasLimitByType(transactionType, isFirstTx: true), eth.GasPriceInGwei) : eth.GetFeeAmount(fee, feePrice); var address = from.FirstOrDefault(w => w.AvailableBalance() >= amount + feeInEth); return(address != null ? Task.FromResult <IEnumerable <SelectedWalletAddress> >(new List <SelectedWalletAddress> { new SelectedWalletAddress { WalletAddress = address, UsedAmount = amount, UsedFee = feeInEth } }) : Task.FromResult(Enumerable.Empty <SelectedWalletAddress>())); } for (var txCount = 1; txCount <= from.Count; ++txCount) { var result = new List <SelectedWalletAddress>(); var requiredAmount = amount; var isFirstTx = true; var completed = false; foreach (var address in from) { var availableBalance = address.AvailableBalance(); var txFee = feeUsagePolicy == FeeUsagePolicy.EstimatedFee ? eth.GetFeeAmount(GasLimitByType(transactionType, isFirstTx), eth.GasPriceInGwei) : feeUsagePolicy == FeeUsagePolicy.FeeForAllTransactions ? Math.Round(eth.GetFeeAmount(fee, feePrice) / txCount, eth.Digits) : eth.GetFeeAmount(fee, feePrice); if (availableBalance <= txFee) // ignore address with balance less than fee { if (result.Count + from.Count - from.IndexOf(address) <= txCount) { break; } else { continue; } } var amountToUse = Math.Min(Math.Max(availableBalance - txFee, 0), requiredAmount); result.Add(new SelectedWalletAddress { WalletAddress = address, UsedAmount = amountToUse, UsedFee = txFee }); requiredAmount -= amountToUse; if (requiredAmount <= 0) { completed = true; break; } if (result.Count == txCount) // will need more transactions { break; } if (isFirstTx) { isFirstTx = false; } } if (completed) { return(Task.FromResult <IEnumerable <SelectedWalletAddress> >(result)); } } return(Task.FromResult(Enumerable.Empty <SelectedWalletAddress>())); }
private async Task <IEnumerable <SelectedWalletAddress> > SelectUnspentAddresses( List <WalletAddress> from, decimal amount, decimal fee, decimal feePrice, FeeUsagePolicy feeUsagePolicy, AddressUsagePolicy addressUsagePolicy, BlockchainTransactionType transactionType) { var erc20 = Erc20; var eth = Eth; if (addressUsagePolicy == AddressUsagePolicy.UseMinimalBalanceFirst) { from = from.ToList().SortList((a, b) => a.AvailableBalance().CompareTo(b.AvailableBalance())); } else if (addressUsagePolicy == AddressUsagePolicy.UseMaximumBalanceFirst) { from = from.ToList().SortList((a, b) => b.AvailableBalance().CompareTo(a.AvailableBalance())); } else if (addressUsagePolicy == AddressUsagePolicy.UseMaximumChainBalanceFirst) { var ethUnspentAddresses = (await DataRepository .GetUnspentAddressesAsync(eth.Name) .ConfigureAwait(false)) .ToList(); if (!ethUnspentAddresses.Any()) { Log.Debug("Unsufficient ETH ammount for ERC20 token processing"); return(Enumerable.Empty <SelectedWalletAddress>()); } ethUnspentAddresses = ethUnspentAddresses.SortList((a, b) => b.AvailableBalance().CompareTo(a.AvailableBalance())); from = from.FindAll( a => ethUnspentAddresses.Select(b => b.Address) .ToList() .Contains(a.Address)); } else if (addressUsagePolicy == AddressUsagePolicy.UseOnlyOneAddress) { var result = new List <SelectedWalletAddress>(); var feeInEth = feeUsagePolicy == FeeUsagePolicy.EstimatedFee ? erc20.GetFeeAmount(GasLimitByType(transactionType, isFirstTx: true), erc20.GasPriceInGwei) : erc20.GetFeeAmount(fee, feePrice); //take erc20 non zero addresses first foreach (var address in from.TakeWhile(x => x.AvailableBalance() >= amount)) { var ethAddress = await DataRepository .GetWalletAddressAsync(eth.Name, address.Address) .ConfigureAwait(false); if (ethAddress == null || ethAddress.AvailableBalance() < feeInEth) { Log.Debug("Unsufficient ETH ammount for ERC20 token processing on address {@address} with available balance {@balance} and needed amount {@amount}", ethAddress.Address, ethAddress.AvailableBalance(), feeInEth); continue; } result.Add(new SelectedWalletAddress { WalletAddress = address, UsedAmount = amount, UsedFee = feeInEth }); } if (result.Any() || amount != 0m) { return(result); } //take non zero eth addresses var ethUnspentAddresses = (await DataRepository .GetUnspentAddressesAsync(eth.Name) .ConfigureAwait(false)) .ToList(); if (!ethUnspentAddresses.Any()) { Log.Debug("Unsufficient ETH ammount for ERC20 token processing"); return(Enumerable.Empty <SelectedWalletAddress>()); } ethUnspentAddresses = ethUnspentAddresses.FindAll(a => a.AvailableBalance() > feeInEth); ethUnspentAddresses = ethUnspentAddresses.SortList((a, b) => a.AvailableBalance().CompareTo(b.AvailableBalance())); foreach (var address in ethUnspentAddresses) { result.Add(new SelectedWalletAddress { WalletAddress = address, UsedAmount = amount, UsedFee = feeInEth }); } return(result); } for (var txCount = 1; txCount <= from.Count; ++txCount) { var result = new List <SelectedWalletAddress>(); var requiredAmount = amount; var isFirstTx = true; var completed = false; foreach (var address in from) { var availableBalance = address.AvailableBalance(); if (!(availableBalance > 0)) { continue; } var ethAddress = await DataRepository .GetWalletAddressAsync(eth.Name, address.Address) .ConfigureAwait(false); var ethAvailableBalance = ethAddress != null?ethAddress.AvailableBalance() : 0; var txFee = feeUsagePolicy == FeeUsagePolicy.EstimatedFee ? eth.GetFeeAmount(GasLimitByType(transactionType, isFirstTx), erc20.GasPriceInGwei) : feeUsagePolicy == FeeUsagePolicy.FeeForAllTransactions ? Math.Round(eth.GetFeeAmount(fee, feePrice) / txCount, eth.Digits) : eth.GetFeeAmount(fee, feePrice); if (ethAvailableBalance < txFee) // ignore address with balance less than fee { Log.Debug("Unsufficient ETH ammount for ERC20 token processing on address {@address} with available balance {@balance} and needed amount {@amount}", ethAddress.Address, ethAddress.AvailableBalance(), txFee); if (result.Count + from.Count - from.IndexOf(address) <= txCount) { break; } else { continue; } } var amountToUse = Math.Min(availableBalance, requiredAmount); result.Add(new SelectedWalletAddress { WalletAddress = address, UsedAmount = amountToUse, UsedFee = txFee }); requiredAmount -= amountToUse; if (requiredAmount <= 0) { completed = true; break; } if (result.Count == txCount) // will need more transactions { break; } if (isFirstTx) { isFirstTx = false; } } if (completed) { return(result); } } return(Enumerable.Empty <SelectedWalletAddress>()); }
private IEnumerable <(WalletAddress, decimal)> SelectUnspentAddresses( IList <WalletAddress> from, decimal amount, decimal fee, decimal feePrice, bool isFeePerTransaction, AddressUsagePolicy addressUsagePolicy) { if (addressUsagePolicy == AddressUsagePolicy.UseMinimalBalanceFirst) { from = from.ToList().SortList((a, b) => a.AvailableBalance().CompareTo(b.AvailableBalance())); } else if (addressUsagePolicy == AddressUsagePolicy.UseMaximumBalanceFirst) { from = from.ToList().SortList((a, b) => b.AvailableBalance().CompareTo(a.AvailableBalance())); } else if (addressUsagePolicy == AddressUsagePolicy.UseOnlyOneAddress) { var address = from.FirstOrDefault(w => w.AvailableBalance() >= amount + Currency.GetFeeAmount(fee, feePrice)); return(address != null ? new List <(WalletAddress, decimal)> { (address, amount + Currency.GetFeeAmount(fee, feePrice)) } : Enumerable.Empty <(WalletAddress, decimal)>()); } for (var txCount = 1; txCount <= from.Count; ++txCount) { var result = new List <(WalletAddress, decimal)>(); var requiredAmount = amount; var feePerTx = isFeePerTransaction ? Currency.GetFeeAmount(fee, feePrice) : Currency.GetFeeAmount(fee, feePrice) / txCount; var completed = false; foreach (var address in from) { var availableBalance = address.AvailableBalance(); if (availableBalance <= feePerTx) // ignore address with balance less than fee { continue; } var amountToUse = Math.Min(Math.Max(availableBalance - feePerTx, 0), requiredAmount); result.Add((address, amountToUse)); requiredAmount -= amountToUse; if (requiredAmount <= 0) { completed = true; break; } if (result.Count == txCount) // will need more transactions { break; } } if (completed) { return(result); } } return(Enumerable.Empty <(WalletAddress, decimal)>()); }
public override async Task <IEnumerable <SelectedWalletAddress> > SelectUnspentAddressesAsync( IList <WalletAddress> from, string to, decimal amount, decimal fee, decimal feePrice, FeeUsagePolicy feeUsagePolicy, AddressUsagePolicy addressUsagePolicy, BlockchainTransactionType transactionType, CancellationToken cancellationToken = default) { var xtz = Xtz; if (addressUsagePolicy == AddressUsagePolicy.UseMinimalBalanceFirst) { from = from.ToList().SortList(new AvailableBalanceAscending()); } else if (addressUsagePolicy == AddressUsagePolicy.UseMaximumBalanceFirst) { from = from.ToList().SortList(new AvailableBalanceDescending()); } else if (addressUsagePolicy == AddressUsagePolicy.UseOnlyOneAddress) { //"to" used for redeem and refund - using the destination address is prefered from = from.Where(a => a.Address == to).Concat(from.Where(a => a.Address != to)).ToList(); } var result = new List <SelectedWalletAddress>(); var requiredAmount = amount; if (feeUsagePolicy == FeeUsagePolicy.FeeForAllTransactions) { requiredAmount += fee; } var isFirstTx = true; var completed = false; foreach (var address in from) { var availableBalanceInTez = address.AvailableBalance(); var txFeeInTez = feeUsagePolicy == FeeUsagePolicy.FeePerTransaction ? fee : await FeeByType( type : transactionType, from : address.Address, isFirstTx : isFirstTx, cancellationToken : cancellationToken) .ConfigureAwait(false); var storageFeeInTez = await StorageFeeByTypeAsync( type : transactionType, to : to, isFirstTx : isFirstTx, cancellationToken : cancellationToken) .ConfigureAwait(false); var netAvailableBalanceInTez = availableBalanceInTez - txFeeInTez - storageFeeInTez - xtz.MicroTezReserve.ToTez(); if (netAvailableBalanceInTez <= 0) // ignore address with balance less than fee { continue; } if (addressUsagePolicy == AddressUsagePolicy.UseOnlyOneAddress) { if (Math.Min(netAvailableBalanceInTez, requiredAmount) == requiredAmount) { return new List <SelectedWalletAddress> { new SelectedWalletAddress { WalletAddress = address, UsedAmount = amount, UsedFee = txFeeInTez, UsedStorageFee = storageFeeInTez } } } ; continue; } decimal amountToUse = 0; if (feeUsagePolicy == FeeUsagePolicy.EstimatedFee) { amountToUse = Math.Min(netAvailableBalanceInTez, requiredAmount); requiredAmount -= amountToUse; } else if (feeUsagePolicy == FeeUsagePolicy.FeeForAllTransactions) { amountToUse = Math.Min(netAvailableBalanceInTez, requiredAmount - txFeeInTez); requiredAmount -= (amountToUse + txFeeInTez); } if (amountToUse > 0) { result.Add(new SelectedWalletAddress { WalletAddress = address, UsedAmount = amountToUse, UsedFee = txFeeInTez, UsedStorageFee = storageFeeInTez }); } if (requiredAmount <= 0) { completed = true; if (feeUsagePolicy == FeeUsagePolicy.FeeForAllTransactions) { requiredAmount = amount; var estimatedFee = result.Sum(s => s.UsedFee); var remainingFee = fee - estimatedFee; var extraFee = 0m; if (remainingFee > 0) { var res = result.ToList(); result = new List <SelectedWalletAddress>(); foreach (var s in res) { extraFee = Math.Round(remainingFee * s.UsedFee / estimatedFee, xtz.Digits); if (extraFee + requiredAmount <= s.UsedAmount) { s.UsedAmount = requiredAmount; s.UsedFee += extraFee; result.Add(s); break; } if (s == res.Last()) { s.UsedAmount = requiredAmount; s.UsedFee += Math.Min(s.WalletAddress.AvailableBalance() - s.UsedAmount - s.UsedFee - s.UsedStorageFee - xtz.MicroTezReserve.ToTez(), remainingFee); if (s.WalletAddress.AvailableBalance() - s.UsedAmount - s.UsedFee - s.UsedStorageFee - xtz.MicroTezReserve.ToTez() < 0) //check if possible { Log.Error("Error in fee distribution for transactions, fee is {@fee} with used fee {@usedFee}", fee, s.UsedFee + s.UsedStorageFee); } } else { if (extraFee >= s.UsedAmount) { //remainingFee -= s.UsedAmount; //todo: use it when replacing allocation fee //estimatedFee -= s.UsedFee; //continue; extraFee = Math.Min(s.UsedAmount - 1 / xtz.DigitsMultiplier, extraFee); } remainingFee -= extraFee; estimatedFee -= s.UsedFee; s.UsedAmount -= extraFee; s.UsedFee += extraFee; } requiredAmount -= Math.Min(requiredAmount, s.UsedAmount); result.Add(s); if (requiredAmount <= 0) { break; } } } else //todo: delete { Log.Error("Error fee is too small for transaction, fee is {@fee} with estimated fee {@estimatedFee}", fee, estimatedFee); } } break; } if (isFirstTx) { isFirstTx = false; } } if (completed) { return(result); } if (feeUsagePolicy == FeeUsagePolicy.FeeForAllTransactions) //todo: delete { Log.Error("Error fee is too big for transaction, fee is {@fee} with estimated fee {@estimatedFee}", fee, result.Sum(s => s.UsedFee)); } return(Enumerable.Empty <SelectedWalletAddress>()); } #endregion Addresses }
public override Task <IEnumerable <SelectedWalletAddress> > SelectUnspentAddressesAsync(IList <WalletAddress> from, string to, decimal amount, decimal fee, decimal feePrice, FeeUsagePolicy feeUsagePolicy, AddressUsagePolicy addressUsagePolicy, BlockchainTransactionType transactionType, CancellationToken cancellationToken = default) { throw new NotImplementedException(); }