public bool Verify(EthereumConfig ethereumConfig) { return(Web3.OfflineTransactionSigner .VerifyTransaction( rlp: RlpEncodedTx, chain: ethereumConfig.Chain)); }
private Task <bool> SignAsync( SecureBytes privateKey, EthereumConfig ethereumConfig) { if (privateKey == null) { throw new ArgumentNullException(nameof(privateKey)); } using var scopedPrivateKey = privateKey.ToUnsecuredBytes(); var chain = ethereumConfig.Chain; RlpEncodedTx = Web3.OfflineTransactionSigner .SignTransaction( privateKey: scopedPrivateKey, chain: chain, to: To, amount: Amount, nonce: Nonce, gasPrice: GasPrice, gasLimit: GasLimit, data: Input); From = Web3.OfflineTransactionSigner .GetSenderAddress(RlpEncodedTx, chain) .ToLowerInvariant(); return(Task.FromResult(true)); }
private async Task <EthereumTransaction> CreateApproveTx( string walletAddress, BigInteger nonce, BigInteger value, decimal gasPrice) { var erc20Config = Erc20Config; var message = new ERC20ApproveFunctionMessage { Spender = erc20Config.SwapContractAddress, Value = value, FromAddress = walletAddress, GasPrice = EthereumConfig.GweiToWei(gasPrice), Nonce = nonce, }; message.Gas = await EstimateGasAsync(message, new BigInteger(erc20Config.ApproveGasLimit)) .ConfigureAwait(false); var txInput = message.CreateTransactionInput(erc20Config.ERC20ContractAddress); return(new EthereumTransaction(erc20Config.Name, txInput) { Type = BlockchainTransactionType.Output | BlockchainTransactionType.TokenApprove }); }
public static ICurrencySwap Create( CurrencyConfig currency, IAccount account) { return(currency switch { BitcoinBasedConfig _ => new BitcoinBasedSwap( account: account.GetCurrencyAccount <BitcoinBasedAccount>(currency.Name), currencies: account.Currencies), Erc20Config _ => new Erc20Swap( account: account.GetCurrencyAccount <Erc20Account>(currency.Name), ethereumAccount: account.GetCurrencyAccount <EthereumAccount>("ETH"), currencies: account.Currencies), EthereumConfig _ => new EthereumSwap( account: account.GetCurrencyAccount <EthereumAccount>(currency.Name), currencies: account.Currencies), Fa12Config _ => new Fa12Swap( account: account.GetCurrencyAccount <Fa12Account>(currency.Name), tezosAccount: account.GetCurrencyAccount <TezosAccount>(TezosConfig.Xtz), currencies: account.Currencies), TezosConfig _ => new TezosSwap( account: account.GetCurrencyAccount <TezosAccount>(currency.Name), currencies: account.Currencies), _ => throw new NotSupportedException($"Not supported currency {currency.Name}") });
public static IWalletViewModel CreateViewModel( IAtomexApp app, IDialogViewer dialogViewer, IMenuSelector menuSelector, IConversionViewModel conversionViewModel, CurrencyConfig currency) { return(currency switch { BitcoinBasedConfig _ or Erc20Config _ or EthereumConfig _ => new WalletViewModel( app: app, dialogViewer: dialogViewer, menuSelector: menuSelector, conversionViewModel: conversionViewModel, currency: currency), Fa12Config _ => new Fa12WalletViewModel( app: app, dialogViewer: dialogViewer, menuSelector: menuSelector, conversionViewModel: conversionViewModel, currency: currency), TezosConfig _ => new TezosWalletViewModel( app: app, dialogViewer: dialogViewer, menuSelector: menuSelector, conversionViewModel: conversionViewModel, currency: currency), _ => throw new NotSupportedException($"Can't create wallet view model for {currency.Name}. This currency is not supported."), });
public static SendViewModel CreateViewModel(IAtomexApp app, CurrencyViewModel currencyViewModel) { return(currencyViewModel.Currency switch { BitcoinBasedConfig _ => (SendViewModel) new BitcoinBasedSendViewModel(app, currencyViewModel), Erc20Config _ => (SendViewModel) new Erc20SendViewModel(app, currencyViewModel), EthereumConfig _ => (SendViewModel) new EthereumSendViewModel(app, currencyViewModel), Fa12Config _ => (SendViewModel) new Fa12SendViewModel(app, currencyViewModel), TezosConfig _ => (SendViewModel) new TezosSendViewModel(app, currencyViewModel), _ => throw new NotSupportedException($"Can't create send view model for {currencyViewModel.Currency.Name}. This currency is not supported."), });
public EthereumTransactionViewModel(EthereumTransaction tx, EthereumConfig ethereumConfig) : base(tx, ethereumConfig, GetAmount(tx), GetFee(tx)) { From = tx.From; To = tx.To; GasPrice = EthereumConfig.WeiToGwei((decimal)tx.GasPrice); GasLimit = (decimal)tx.GasLimit; GasUsed = (decimal)tx.GasUsed; Fee = EthereumConfig.WeiToEth(tx.GasUsed * tx.GasPrice); IsInternal = tx.IsInternal; }
public EthereumERC20TransactionViewModel( EthereumTransaction tx, Erc20Config erc20Config) : base(tx, erc20Config, GetAmount(tx, erc20Config), 0) { From = tx.From; To = tx.To; GasPrice = EthereumConfig.WeiToGwei((decimal)tx.GasPrice); GasLimit = (decimal)tx.GasLimit; GasUsed = (decimal)tx.GasUsed; IsInternal = tx.IsInternal; }
private static decimal GetFee(EthereumTransaction tx) { var result = 0m; if (tx.Type.HasFlag(BlockchainTransactionType.Output)) { result += EthereumConfig.WeiToEth(tx.GasUsed * tx.GasPrice); } tx.InternalTxs?.ForEach(t => result += GetFee(t)); return(result); }
public TetherCurrencyViewModel(CurrencyConfig currency) : base(currency) { ChainCurrency = new EthereumConfig(); Header = Currency.Description; IconBrush = new ImageBrush(new BitmapImage(new Uri(PathToImage("tether_90x90.png")))); IconMaskBrush = new ImageBrush(new BitmapImage(new Uri(PathToImage("tether_mask.png")))); AccentColor = Color.FromRgb(r: 0, g: 162, b: 122); AmountColor = Color.FromRgb(r: 183, g: 208, b: 225); UnselectedIconBrush = Brushes.White; IconPath = PathToImage("tether.png"); LargeIconPath = PathToImage("tether_90x90.png"); FeeName = Resources.SvGasLimit; }
public WbtcCurrencyViewModel(CurrencyConfig currency) : base(currency) { ChainCurrency = new EthereumConfig(); Header = Currency.Description; IconBrush = new ImageBrush(new BitmapImage(new Uri(PathToImage("wbtc_90x90.png")))); IconMaskBrush = new ImageBrush(new BitmapImage(new Uri(PathToImage("wbtc_mask.png")))); AccentColor = Color.FromRgb(r: 7, g: 82, b: 192); AmountColor = Color.FromRgb(r: 188, g: 212, b: 247); UnselectedIconBrush = Brushes.White; IconPath = PathToImage("wbtc.png"); LargeIconPath = PathToImage("wbtc_90x90.png"); FeeName = Resources.SvGasLimit; }
public static CurrencyViewModel CreateViewModel( IAtomexApp app, CurrencyConfig currency, bool loadTransactions = true) { return(currency switch { BitcoinBasedConfig _ or Erc20Config _ or EthereumConfig _ => new CurrencyViewModel(app, currency, loadTransactions), Fa12Config _ => new Fa12CurrencyViewModel(app, currency), TezosConfig _ => new TezosCurrencyViewModel(app, currency), _ => throw new NotSupportedException($"Can't create currency view model for {currency.Name}. This currency is not supported."), });
public override async Task <Result <decimal> > GetBalanceAsync( string address, CancellationToken cancellationToken = default) { var requestUri = $"api?module=account&action=balance&address={address}&apikey={ApiKey}"; await RequestLimitControl .Wait(cancellationToken) .ConfigureAwait(false); return(await HttpHelper.GetAsyncResult <decimal>( baseUri : BaseUrl, requestUri : requestUri, responseHandler : (response, content) => { var json = JsonConvert.DeserializeObject <JObject>(content); return json.ContainsKey("result") ? EthereumConfig.WeiToEth(BigInteger.Parse(json["result"].ToString())) : 0; }, cancellationToken : cancellationToken) .ConfigureAwait(false)); }
protected async Task <EthereumTransaction> CreatePaymentTxAsync( Swap swap, int lockTimeInSeconds, CancellationToken cancellationToken = default) { var erc20Config = Erc20Config; Log.Debug("Create payment transaction from address {@adderss} for swap {@swapId}", swap.FromAddress, swap.Id); var requiredAmountInERC20 = RequiredAmountInTokens(swap, erc20Config); var refundTimeStampUtcInSec = new DateTimeOffset(swap.TimeStamp.ToUniversalTime().AddSeconds(lockTimeInSeconds)).ToUnixTimeSeconds(); var rewardForRedeemInERC20 = swap.PartyRewardForRedeem; var walletAddress = await Erc20Account .GetAddressAsync(swap.FromAddress, cancellationToken) .ConfigureAwait(false); var gasPrice = await EthConfig .GetGasPriceAsync(cancellationToken) .ConfigureAwait(false); var balanceInEth = (await EthereumAccount .GetAddressBalanceAsync( address: walletAddress.Address, cancellationToken: cancellationToken) .ConfigureAwait(false)) .Available; var feeAmountInEth = rewardForRedeemInERC20 == 0 ? erc20Config.InitiateFeeAmount(gasPrice) : erc20Config.InitiateWithRewardFeeAmount(gasPrice); if (balanceInEth < feeAmountInEth) { Log.Error( "Insufficient funds at {@address} for fee. Balance: {@balance}, feeAmount: {@feeAmount}, result: {@result}.", walletAddress.Address, balanceInEth, feeAmountInEth, balanceInEth - feeAmountInEth); return(null); } var balanceInErc20 = walletAddress.Balance; Log.Debug("Available balance: {@balance}", balanceInErc20); if (balanceInErc20 < requiredAmountInERC20) { Log.Error( "Insufficient funds at {@address}. Balance: {@balance}, required: {@result}, missing: {@missing}.", walletAddress.Address, balanceInErc20, requiredAmountInERC20, balanceInErc20 - requiredAmountInERC20); return(null); } var amountInErc20 = AmountHelper.DustProofMin( balanceInErc20, requiredAmountInERC20, erc20Config.DigitsMultiplier, erc20Config.DustDigitsMultiplier); var nonceResult = await((IEthereumBlockchainApi)erc20Config.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 initMessage = new ERC20InitiateFunctionMessage { HashedSecret = swap.SecretHash, ERC20Contract = erc20Config.ERC20ContractAddress, Participant = swap.PartyAddress, RefundTimestamp = refundTimeStampUtcInSec, Countdown = lockTimeInSeconds, Value = erc20Config.TokensToTokenDigits(amountInErc20), RedeemFee = erc20Config.TokensToTokenDigits(rewardForRedeemInERC20), Active = true, FromAddress = walletAddress.Address, GasPrice = EthereumConfig.GweiToWei(gasPrice), Nonce = nonceResult.Value }; var initiateGasLimit = rewardForRedeemInERC20 == 0 ? erc20Config.InitiateGasLimit : erc20Config.InitiateWithRewardGasLimit; initMessage.Gas = await EstimateGasAsync(initMessage, new BigInteger(initiateGasLimit)) .ConfigureAwait(false); txInput = initMessage.CreateTransactionInput(erc20Config.SwapContractAddress); return(new EthereumTransaction(erc20Config.Name, txInput) { Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapPayment }); }
public override Task UpdateBalanceAsync( CancellationToken cancellationToken = default) { return(Task.Run(async() => { try { var eth = EthConfig; var txs = (await DataRepository .GetTransactionsAsync(Currency, eth.TransactionType) .ConfigureAwait(false)) .Cast <EthereumTransaction>() .ToList(); var internalTxs = txs.Aggregate(new List <EthereumTransaction>(), (list, tx) => { if (tx.InternalTxs != null) { list.AddRange(tx.InternalTxs); } return list; }); // calculate balances var totalUnconfirmedIncome = 0m; var totalUnconfirmedOutcome = 0m; var addressBalances = new Dictionary <string, WalletAddress>(); foreach (var tx in txs.Concat(internalTxs)) { var addresses = new HashSet <string>(); var isFromSelf = await IsSelfAddressAsync(tx.From, cancellationToken) .ConfigureAwait(false); if (isFromSelf) { addresses.Add(tx.From); } var isToSelf = await IsSelfAddressAsync(tx.To, cancellationToken) .ConfigureAwait(false); if (isToSelf) { addresses.Add(tx.To); } foreach (var address in addresses) { var isIncome = address == tx.To; var isOutcome = address == tx.From; var isConfirmed = tx.IsConfirmed; var isFailed = tx.State == BlockchainTransactionState.Failed; var income = isIncome && !isFailed ? EthereumConfig.WeiToEth(tx.Amount) : 0; var outcome = isOutcome ? (!isFailed ? -EthereumConfig.WeiToEth(tx.Amount + tx.GasPrice * (tx.GasUsed != 0 ? tx.GasUsed : tx.GasLimit)) : -EthereumConfig.WeiToEth(tx.GasPrice * tx.GasUsed)) : 0; if (addressBalances.TryGetValue(address, out var walletAddress)) { //walletAddress.Balance += isConfirmed ? income + outcome : 0; walletAddress.UnconfirmedIncome += !isConfirmed ? income : 0; walletAddress.UnconfirmedOutcome += !isConfirmed ? outcome : 0; } else { walletAddress = await DataRepository .GetWalletAddressAsync(Currency, address) .ConfigureAwait(false); //walletAddress.Balance = isConfirmed ? income + outcome : 0; walletAddress.UnconfirmedIncome = !isConfirmed ? income : 0; walletAddress.UnconfirmedOutcome = !isConfirmed ? outcome : 0; walletAddress.HasActivity = true; addressBalances.Add(address, walletAddress); } //totalBalance += isConfirmed ? income + outcome : 0; totalUnconfirmedIncome += !isConfirmed ? income : 0; totalUnconfirmedOutcome += !isConfirmed ? outcome : 0; } } var totalBalance = 0m; var api = eth.BlockchainApi; foreach (var wa in addressBalances.Values) { var balanceResult = await api .TryGetBalanceAsync( address: wa.Address, cancellationToken: cancellationToken) .ConfigureAwait(false); if (balanceResult.HasError) { Log.Error("Error while getting balance for {@address} with code {@code} and description {@description}", wa.Address, balanceResult.Error.Code, balanceResult.Error.Description); continue; // todo: may be return? } wa.Balance = balanceResult.Value; totalBalance += wa.Balance; } // upsert addresses await DataRepository .UpsertAddressesAsync(addressBalances.Values) .ConfigureAwait(false); Balance = totalBalance; UnconfirmedIncome = totalUnconfirmedIncome; UnconfirmedOutcome = totalUnconfirmedOutcome; RaiseBalanceUpdated(new CurrencyEventArgs(Currency)); } catch (OperationCanceledException) { Log.Debug($"{Currency} UpdateBalanceAsync canceled."); } catch (Exception e) { Log.Error(e, $"{Currency} UpdateBalanceAsync error."); } }, cancellationToken)); }
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 }); }
public GeoMiddlewareApiController(IOptions <EthereumConfig> config) { _config = config.Value; _contract = new ChannelsContract(_config); }
public override async Task RedeemForPartyAsync( Swap swap, CancellationToken cancellationToken = default) { if (swap.IsInitiator) { var partyRedeemDeadline = swap.TimeStamp.ToUniversalTime().AddSeconds(DefaultAcceptorLockTimeInSeconds) - PartyRedeemTimeReserve; if (DateTime.UtcNow > partyRedeemDeadline) { Log.Error("Party redeem deadline reached for swap {@swap}", swap.Id); return; } } Log.Debug("Create redeem for counterParty for swap {@swapId}", swap.Id); var ethConfig = EthConfig; var gasPrice = await ethConfig .GetGasPriceAsync(cancellationToken) .ConfigureAwait(false); var walletAddress = await _account .GetAddressAsync(swap.FromAddress, cancellationToken) .ConfigureAwait(false); if (walletAddress == null) { Log.Error("Can't get address {@address} for redeem for party from local db", swap.FromAddress); return; } var feeInEth = ethConfig.GetFeeAmount(ethConfig.RedeemGasLimit, gasPrice); if (walletAddress.Balance < feeInEth) { Log.Error("Insufficient funds for redeem for party"); return; } using var addressLock = await EthereumAccount.AddressLocker .GetLockAsync(walletAddress.Address, cancellationToken) .ConfigureAwait(false); var nonceResult = await EthereumNonceManager.Instance .GetNonceAsync(ethConfig, walletAddress.Address, pending : true, cancellationToken) .ConfigureAwait(false); if (nonceResult.HasError) { Log.Error("Nonce getting error with code {@code} and description {@description}", nonceResult.Error.Code, nonceResult.Error.Description); return; } var message = new RedeemFunctionMessage { FromAddress = walletAddress.Address, HashedSecret = swap.SecretHash, Secret = swap.Secret, Nonce = nonceResult.Value, GasPrice = EthereumConfig.GweiToWei(gasPrice), }; message.Gas = await EstimateGasAsync(message, new BigInteger(ethConfig.RedeemGasLimit)) .ConfigureAwait(false); var txInput = message.CreateTransactionInput(ethConfig.SwapContractAddress); var redeemTx = new EthereumTransaction(ethConfig.Name, txInput) { Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapRedeem }; var signResult = await SignTransactionAsync(redeemTx, cancellationToken) .ConfigureAwait(false); if (!signResult) { Log.Error("Transaction signing error"); return; } await BroadcastTxAsync(swap, redeemTx, cancellationToken) .ConfigureAwait(false); }
public override async Task RefundAsync( Swap swap, CancellationToken cancellationToken = default) { var ethConfig = EthConfig; if (swap.StateFlags.HasFlag(SwapStateFlags.IsRefundBroadcast) && swap.RefundTx != null && swap.RefundTx.CreationTime != null && swap.RefundTx.CreationTime.Value.ToUniversalTime() + TimeSpan.FromMinutes(20) > DateTime.UtcNow) { _ = TrackTransactionConfirmationAsync( swap: swap, currency: ethConfig, dataRepository: _account.DataRepository, txId: swap.RefundTx.Id, confirmationHandler: RefundConfirmedEventHandler, cancellationToken: cancellationToken); return; } var lockTimeInSeconds = swap.IsInitiator ? DefaultInitiatorLockTimeInSeconds : DefaultAcceptorLockTimeInSeconds; var lockTime = swap.TimeStamp.ToUniversalTime() + TimeSpan.FromSeconds(lockTimeInSeconds); await RefundTimeDelayAsync(lockTime, cancellationToken) .ConfigureAwait(false); // check swap initiation try { var txResult = await EthereumSwapInitiatedHelper .TryToFindPaymentAsync(swap, ethConfig, cancellationToken) .ConfigureAwait(false); if (!txResult.HasError && txResult.Value == null) { // swap not initiated and must be canceled swap.StateFlags |= SwapStateFlags.IsCanceled; await UpdateSwapAsync(swap, SwapStateFlags.IsCanceled, cancellationToken) .ConfigureAwait(false); return; } } catch (Exception e) { Log.Error(e, $"Can't check {swap.Id} swap initiation for ETH"); } Log.Debug("Create refund for swap {@swap}", swap.Id); var gasPrice = await ethConfig .GetGasPriceAsync(cancellationToken) .ConfigureAwait(false); var walletAddress = await _account .GetAddressAsync(swap.FromAddress, cancellationToken) .ConfigureAwait(false); if (walletAddress == null) { Log.Error("Can't get address {@address} for refund from local db", swap.FromAddress); return; } var feeInEth = ethConfig.GetFeeAmount(ethConfig.RefundGasLimit, gasPrice); if (walletAddress.Balance < feeInEth) { Log.Error("Insufficient funds for refund"); return; } EthereumTransaction refundTx; try { await EthereumAccount.AddressLocker .LockAsync(walletAddress.Address, cancellationToken) .ConfigureAwait(false); var nonceResult = await EthereumNonceManager.Instance .GetNonceAsync(ethConfig, walletAddress.Address, pending : true, cancellationToken) .ConfigureAwait(false); if (nonceResult.HasError) { Log.Error("Nonce getting error with code {@code} and description {@description}", nonceResult.Error.Code, nonceResult.Error.Description); return; } var message = new RefundFunctionMessage { FromAddress = walletAddress.Address, HashedSecret = swap.SecretHash, GasPrice = EthereumConfig.GweiToWei(gasPrice), Nonce = nonceResult.Value, }; message.Gas = await EstimateGasAsync(message, new BigInteger(ethConfig.RefundGasLimit)) .ConfigureAwait(false); var txInput = message.CreateTransactionInput(ethConfig.SwapContractAddress); refundTx = new EthereumTransaction(ethConfig.Name, txInput) { Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapRefund }; var signResult = await SignTransactionAsync(refundTx, cancellationToken) .ConfigureAwait(false); if (!signResult) { Log.Error("Transaction signing error"); return; } swap.RefundTx = refundTx; swap.StateFlags |= SwapStateFlags.IsRefundSigned; await UpdateSwapAsync(swap, SwapStateFlags.IsRefundSigned, cancellationToken) .ConfigureAwait(false); await BroadcastTxAsync(swap, refundTx, cancellationToken) .ConfigureAwait(false); } catch { throw; } finally { EthereumAccount.AddressLocker.Unlock(walletAddress.Address); } swap.RefundTx = refundTx; swap.StateFlags |= SwapStateFlags.IsRefundBroadcast; await UpdateSwapAsync(swap, SwapStateFlags.IsRefundBroadcast, cancellationToken) .ConfigureAwait(false); _ = TrackTransactionConfirmationAsync( swap: swap, currency: ethConfig, dataRepository: _account.DataRepository, txId: refundTx.Id, confirmationHandler: RefundConfirmedEventHandler, cancellationToken: cancellationToken); }
public override async Task RedeemAsync( Swap swap, CancellationToken cancellationToken = default) { var ethConfig = EthConfig; var secretResult = await EthereumSwapRedeemedHelper .IsRedeemedAsync( swap : swap, currency : ethConfig, attempts : MaxRedeemCheckAttempts, attemptIntervalInSec : RedeemCheckAttemptIntervalInSec, cancellationToken : cancellationToken) .ConfigureAwait(false); if (!secretResult.HasError && secretResult.Value != null) { await RedeemConfirmedEventHandler(swap, null, cancellationToken) .ConfigureAwait(false); return; } if (swap.StateFlags.HasFlag(SwapStateFlags.IsRedeemBroadcast) && swap.RedeemTx != null && swap.RedeemTx.CreationTime != null && swap.RedeemTx.CreationTime.Value.ToUniversalTime() + TimeSpan.FromMinutes(30) > DateTime.UtcNow) { // redeem already broadcast _ = TrackTransactionConfirmationAsync( swap: swap, currency: ethConfig, dataRepository: _account.DataRepository, txId: swap.RedeemTx.Id, confirmationHandler: RedeemConfirmedEventHandler, cancellationToken: cancellationToken); return; } if (swap.IsInitiator) { var redeemDeadline = swap.TimeStamp.ToUniversalTime().AddSeconds(DefaultAcceptorLockTimeInSeconds) - RedeemTimeReserve; if (DateTime.UtcNow > redeemDeadline) { Log.Error("Redeem dedline reached for swap {@swap}", swap.Id); return; } } Log.Debug("Create redeem for swap {@swapId}", swap.Id); var gasPrice = await ethConfig .GetGasPriceAsync(cancellationToken) .ConfigureAwait(false); var walletAddress = await _account .GetAddressAsync(swap.RedeemFromAddress, cancellationToken) .ConfigureAwait(false); if (walletAddress == null) { Log.Error("Can't get address {@address} for redeem from local db", swap.RedeemFromAddress); return; } var feeInEth = ethConfig.GetFeeAmount(ethConfig.RedeemGasLimit, gasPrice); if (walletAddress.Balance < feeInEth) { Log.Error("Insufficient funds for redeem"); return; } EthereumTransaction redeemTx; try { await EthereumAccount.AddressLocker .LockAsync(walletAddress.Address, cancellationToken) .ConfigureAwait(false); var nonceResult = await EthereumNonceManager.Instance .GetNonceAsync(ethConfig, walletAddress.Address, pending : true, cancellationToken : cancellationToken) .ConfigureAwait(false); if (nonceResult.HasError) { Log.Error("Nonce getting error with code {@code} and description {@description}", nonceResult.Error.Code, nonceResult.Error.Description); return; } var message = new RedeemFunctionMessage { FromAddress = walletAddress.Address, HashedSecret = swap.SecretHash, Secret = swap.Secret, Nonce = nonceResult.Value, GasPrice = EthereumConfig.GweiToWei(gasPrice), }; message.Gas = await EstimateGasAsync(message, new BigInteger(ethConfig.RedeemGasLimit)) .ConfigureAwait(false); var txInput = message.CreateTransactionInput(ethConfig.SwapContractAddress); redeemTx = new EthereumTransaction(ethConfig.Name, txInput) { Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapRedeem }; var signResult = await SignTransactionAsync(redeemTx, cancellationToken) .ConfigureAwait(false); if (!signResult) { Log.Error("Transaction signing error"); return; } swap.RedeemTx = redeemTx; swap.StateFlags |= SwapStateFlags.IsRedeemSigned; await UpdateSwapAsync(swap, SwapStateFlags.IsRedeemSigned, cancellationToken) .ConfigureAwait(false); await BroadcastTxAsync(swap, redeemTx, cancellationToken) .ConfigureAwait(false); } catch { throw; } finally { EthereumAccount.AddressLocker.Unlock(walletAddress.Address); } swap.RedeemTx = redeemTx; swap.StateFlags |= SwapStateFlags.IsRedeemBroadcast; await UpdateSwapAsync(swap, SwapStateFlags.IsRedeemBroadcast, cancellationToken) .ConfigureAwait(false); _ = TrackTransactionConfirmationAsync( swap: swap, currency: ethConfig, dataRepository: _account.DataRepository, txId: redeemTx.Id, confirmationHandler: RedeemConfirmedEventHandler, cancellationToken: cancellationToken); }
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 EtherScanApi(EthereumConfig currency) { Currency = currency; BaseUrl = currency.BlockchainApiBaseUri; }
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); }
public static IEnumerable <SwapDetailingInfo> GetSwapDetailingInfo(Swap swap, ICurrencies currencies) { var soldCurrencyConfig = currencies.GetByName(swap.SoldCurrency); var purchaseCurrencyConfig = currencies.GetByName(swap.PurchasedCurrency); IList <SwapDetailingInfo> result = new List <SwapDetailingInfo>(); if (swap.StateFlags.HasFlag(SwapStateFlags.HasSecretHash) && swap.Status.HasFlag(SwapStatus.Initiated) && swap.Status.HasFlag(SwapStatus.Accepted)) { result.Add(new SwapDetailingInfo { Status = SwapDetailingStatus.Initialization, IsCompleted = true, Description = "Orders matched" }); result.Add(new SwapDetailingInfo { Status = SwapDetailingStatus.Initialization, IsCompleted = true, Description = "Credentials exchanged" }); } else { if (swap.IsCanceled) { result.Add(new SwapDetailingInfo { Status = SwapDetailingStatus.Initialization, IsCompleted = false, Description = "Error during orders matching and credentials exchanging" }); return(result); } result.Add(new SwapDetailingInfo { Status = SwapDetailingStatus.Initialization, IsCompleted = false, Description = "Waiting while orders are matched and credentials exchanged" }); return(result); } if (swap.StateFlags.HasFlag(SwapStateFlags.HasPartyPayment) && swap.StateFlags.HasFlag(SwapStateFlags.IsPartyPaymentConfirmed)) { var swapDetailingStep = new SwapDetailingInfo { Status = SwapDetailingStatus.Exchanging, IsCompleted = false, Description = $"{swap.PurchasedCurrency} counterparty payment", ExplorerLink = new DetailsLink { Text = "transaction confirmed", Url = purchaseCurrencyConfig switch { EthereumConfig ethereumConfig => $"{ethereumConfig.AddressExplorerUri}{ethereumConfig.SwapContractAddress}", TezosConfig tezosConfig => $"{tezosConfig.AddressExplorerUri}{tezosConfig.SwapContractAddress}", _ => null } } }; result.Add(swapDetailingStep); } else { if (swap.IsCanceled) { var swapDetailingStep = new SwapDetailingInfo { Status = SwapDetailingStatus.Exchanging, IsCompleted = false, Description = $"Counterparty {swap.PurchasedCurrency} payment", ExplorerLink = new DetailsLink { Text = "transaction failed", Url = purchaseCurrencyConfig switch { EthereumConfig ethereumConfig => $"{ethereumConfig.AddressExplorerUri}{ethereumConfig.SwapContractAddress}", TezosConfig tezosConfig => $"{tezosConfig.AddressExplorerUri}{tezosConfig.SwapContractAddress}", _ => null } } }; result.Add(swapDetailingStep); return(result); } else { var swapDetailingStep = new SwapDetailingInfo { Status = SwapDetailingStatus.Exchanging, IsCompleted = false, Description = $"Waiting for confirmation counterparty {swap.PurchasedCurrency}", ExplorerLink = new DetailsLink { Text = "payment transaction", Url = purchaseCurrencyConfig switch { EthereumConfig ethereumConfig => $"{ethereumConfig.AddressExplorerUri}{ethereumConfig.SwapContractAddress}", TezosConfig tezosConfig => $"{tezosConfig.AddressExplorerUri}{tezosConfig.SwapContractAddress}", _ => null } } }; result.Add(swapDetailingStep); return(result); } } if (swap.StateFlags.HasFlag(SwapStateFlags.IsPaymentConfirmed) || swap.IsComplete || swap.IsRefunded || swap.IsUnsettled || swap.IsCanceled && swap.StateFlags.HasFlag(SwapStateFlags.HasSecret)) { var swapDetailingStep = new SwapDetailingInfo { Status = SwapDetailingStatus.Exchanging, IsCompleted = true, Description = $"Your {swap.SoldCurrency} payment", ExplorerLink = new DetailsLink { Text = "transaction confirmed", Url = soldCurrencyConfig switch { EthereumConfig ethereumConfig => $"{ethereumConfig.AddressExplorerUri}{ethereumConfig.SwapContractAddress}", TezosConfig tezosConfig => $"{tezosConfig.AddressExplorerUri}{tezosConfig.SwapContractAddress}", _ => null } } }; result.Add(swapDetailingStep); } else { if (swap.IsCanceled) { var swapDetailingStep = new SwapDetailingInfo { Status = SwapDetailingStatus.Exchanging, IsCompleted = false, Description = $"Your {swap.SoldCurrency} payment", ExplorerLink = new DetailsLink { Text = "transaction failed", Url = soldCurrencyConfig switch { EthereumConfig ethereumConfig => $"{ethereumConfig.AddressExplorerUri}{ethereumConfig.SwapContractAddress}", TezosConfig tezosConfig => $"{tezosConfig.AddressExplorerUri}{tezosConfig.SwapContractAddress}", _ => null } } }; result.Add(swapDetailingStep); return(result); } // your payment broadcasted but not confirmed. if (swap.StateFlags.HasFlag(SwapStateFlags.IsPaymentBroadcast)) { var swapDetailingStep = new SwapDetailingInfo { Status = SwapDetailingStatus.Exchanging, IsCompleted = false, Description = $"Waiting for confirmation your {swap.SoldCurrency}", ExplorerLink = new DetailsLink { Text = "payment transaction", Url = soldCurrencyConfig switch { EthereumConfig ethereumConfig => $"{ethereumConfig.AddressExplorerUri}{ethereumConfig.SwapContractAddress}", TezosConfig tezosConfig => $"{tezosConfig.AddressExplorerUri}{tezosConfig.SwapContractAddress}", _ => null } } }; result.Add(swapDetailingStep); return(result); } // your payment not yet created. result.Add(new SwapDetailingInfo { Status = SwapDetailingStatus.Exchanging, IsCompleted = false, Description = $"Creating your {swap.SoldCurrency} payment transaction." }); return(result); } if (swap.StateFlags.HasFlag(SwapStateFlags.HasSecret)) { var swapDetailingStep = new SwapDetailingInfo { Status = SwapDetailingStatus.Completion, IsCompleted = false, Description = $"Counterparty {swap.SoldCurrency} redeem", ExplorerLink = new DetailsLink { Text = "transaction confirmed", Url = soldCurrencyConfig switch { EthereumConfig ethereumConfig => $"{ethereumConfig.AddressExplorerUri}{ethereumConfig.SwapContractAddress}", TezosConfig tezosConfig => $"{tezosConfig.AddressExplorerUri}{tezosConfig.SwapContractAddress}", _ => null } } }; result.Add(swapDetailingStep); } else { if (swap.IsCanceled) { var swapDetailingStep = new SwapDetailingInfo { Status = SwapDetailingStatus.Completion, IsCompleted = false, Description = $"Counterparty {swap.SoldCurrency} redeem", ExplorerLink = new DetailsLink { Text = "transaction failed", Url = soldCurrencyConfig switch { EthereumConfig ethereumConfig => $"{ethereumConfig.AddressExplorerUri}{ethereumConfig.SwapContractAddress}", TezosConfig tezosConfig => $"{tezosConfig.AddressExplorerUri}{tezosConfig.SwapContractAddress}", _ => null } } }; result.Add(swapDetailingStep); return(result); } else { var swapDetailingStep = new SwapDetailingInfo { Status = SwapDetailingStatus.Completion, IsCompleted = false, Description = $"Waiting for confirmation counterparty {swap.SoldCurrency}", ExplorerLink = new DetailsLink { Text = "redeem transaction", Url = soldCurrencyConfig switch { EthereumConfig ethereumConfig => $"{ethereumConfig.AddressExplorerUri}{ethereumConfig.SwapContractAddress}", TezosConfig tezosConfig => $"{tezosConfig.AddressExplorerUri}{tezosConfig.SwapContractAddress}", _ => null } } }; result.Add(swapDetailingStep); return(result); } } if (swap.StateFlags.HasFlag(SwapStateFlags.IsRedeemConfirmed)) { var swapDetailingStep = new SwapDetailingInfo { Status = SwapDetailingStatus.Completion, IsCompleted = true, Description = $"Your {swap.PurchasedCurrency} redeem", ExplorerLink = new DetailsLink { Text = "transaction confirmed", Url = purchaseCurrencyConfig switch { EthereumConfig ethereumConfig => $"{ethereumConfig.AddressExplorerUri}{ethereumConfig.SwapContractAddress}", TezosConfig tezosConfig => $"{tezosConfig.AddressExplorerUri}{tezosConfig.SwapContractAddress}", _ => null } } }; result.Add(swapDetailingStep); } else { if (swap.StateFlags.HasFlag(SwapStateFlags.IsRefundConfirmed)) { var swapDetailingStep = new SwapDetailingInfo { Status = SwapDetailingStatus.Completion, IsCompleted = true, Description = $"Your {swap.SoldCurrency} refund", ExplorerLink = new DetailsLink { Text = "transaction confirmed", Url = soldCurrencyConfig switch { EthereumConfig ethereumConfig => $"{ethereumConfig.AddressExplorerUri}{ethereumConfig.SwapContractAddress}", TezosConfig tezosConfig => $"{tezosConfig.AddressExplorerUri}{tezosConfig.SwapContractAddress}", _ => null } } }; result.Add(swapDetailingStep); } else if (swap.StateFlags.HasFlag(SwapStateFlags.IsRefundBroadcast)) { var swapDetailingStep = new SwapDetailingInfo { Status = SwapDetailingStatus.Completion, IsCompleted = true, Description = $"Waiting for confirmation your {swap.SoldCurrency}", ExplorerLink = new DetailsLink { Text = "refund transaction", Url = soldCurrencyConfig switch { EthereumConfig ethereumConfig => $"{ethereumConfig.AddressExplorerUri}{ethereumConfig.SwapContractAddress}", TezosConfig tezosConfig => $"{tezosConfig.AddressExplorerUri}{tezosConfig.SwapContractAddress}", _ => null } } }; result.Add(swapDetailingStep); } else { if (swap.IsCanceled) { var swapDetailingStep = new SwapDetailingInfo { Status = SwapDetailingStatus.Completion, IsCompleted = false, Description = $"Your {swap.PurchasedCurrency} redeem", ExplorerLink = new DetailsLink { Text = "transaction failed", Url = purchaseCurrencyConfig switch { EthereumConfig ethereumConfig => $"{ethereumConfig.AddressExplorerUri}{ethereumConfig.SwapContractAddress}", TezosConfig tezosConfig => $"{tezosConfig.AddressExplorerUri}{tezosConfig.SwapContractAddress}", _ => null } } }; result.Add(swapDetailingStep); } else { var swapDetailingStep = new SwapDetailingInfo { Status = SwapDetailingStatus.Completion, IsCompleted = false, Description = $"Waiting for confirmation your {swap.PurchasedCurrency}", ExplorerLink = new DetailsLink { Text = "redeem transaction", Url = purchaseCurrencyConfig switch { EthereumConfig ethereumConfig => $"{ethereumConfig.AddressExplorerUri}{ethereumConfig.SwapContractAddress}", TezosConfig tezosConfig => $"{tezosConfig.AddressExplorerUri}{tezosConfig.SwapContractAddress}", _ => null } } }; result.Add(swapDetailingStep); } } } return(result); }
public override Task UpdateBalanceAsync( string address, CancellationToken cancellationToken = default) { return(Task.Run(async() => { try { var eth = EthConfig; var walletAddress = await DataRepository .GetWalletAddressAsync(Currency, address) .ConfigureAwait(false); if (walletAddress == null) { return; } var balanceResult = await eth.BlockchainApi .TryGetBalanceAsync(address, cancellationToken: cancellationToken) .ConfigureAwait(false); if (balanceResult.HasError) { Log.Error("Error while balance update for {@address} with code {@code} and description {@description}", address, balanceResult.Error.Code, balanceResult.Error.Description); return; } var balance = balanceResult.Value; // calculate unconfirmed balances var unconfirmedTxs = (await DataRepository .GetUnconfirmedTransactionsAsync(Currency, eth.TransactionType) .ConfigureAwait(false)) .Cast <EthereumTransaction>() .ToList(); var unconfirmedInternalTxs = unconfirmedTxs.Aggregate(new List <EthereumTransaction>(), (list, tx) => { if (tx.InternalTxs != null) { list.AddRange(tx.InternalTxs); } return list; }); var unconfirmedIncome = 0m; var unconfirmedOutcome = 0m; foreach (var utx in unconfirmedTxs.Concat(unconfirmedInternalTxs)) { var isFailed = utx.State == BlockchainTransactionState.Failed; unconfirmedIncome += address == utx.To && !isFailed ? EthereumConfig.WeiToEth(utx.Amount) : 0; unconfirmedOutcome += address == utx.From && !isFailed ? -EthereumConfig.WeiToEth(utx.Amount + utx.GasPrice * (utx.GasUsed != 0 ? utx.GasUsed : utx.GasLimit)) : 0; } var balanceDifference = balance - walletAddress.Balance; var unconfirmedIncomeDifference = unconfirmedIncome - walletAddress.UnconfirmedIncome; var unconfirmedOutcomeDifference = unconfirmedOutcome - walletAddress.UnconfirmedOutcome; if (balanceDifference != 0 || unconfirmedIncomeDifference != 0 || unconfirmedOutcomeDifference != 0) { walletAddress.Balance = balance; walletAddress.UnconfirmedIncome = unconfirmedIncome; walletAddress.UnconfirmedOutcome = unconfirmedOutcome; walletAddress.HasActivity = true; await DataRepository.UpsertAddressAsync(walletAddress) .ConfigureAwait(false); Balance += balanceDifference; UnconfirmedIncome += unconfirmedIncomeDifference; UnconfirmedOutcome += unconfirmedOutcomeDifference; RaiseBalanceUpdated(new CurrencyEventArgs(Currency)); } } catch (OperationCanceledException) { Log.Debug("UpdateBalanceAsync canceled."); } catch (Exception e) { Log.Error(e, "UpdateBalanceAsync error."); } }, cancellationToken)); }
public ChannelsContract(EthereumConfig config) { _config = config; _account = new Account(_config.PrivateKey); _web3 = new Web3(_account, _config.RpcAddress); }