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));
        }
예제 #3
0
        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
            });
        }
예제 #4
0
        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."),
            });
예제 #6
0
 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."),
     });
예제 #7
0
 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;
 }
예제 #9
0
        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."),
            });
예제 #13
0
        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));
        }
예제 #14
0
        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
            });
        }
예제 #15
0
        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));
        }
예제 #16
0
        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);
 }
예제 #18
0
        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);
        }
예제 #19
0
        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);
        }
예제 #20
0
        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);
        }
예제 #21
0
        public async Task <Error> SendAsync(
            string from,
            string to,
            decimal amount,
            decimal gasLimit   = 0,
            decimal gasPrice   = 0,
            bool useDefaultFee = false,
            CancellationToken cancellationToken = default)
        {
            //if (from == to)
            //    return new Error(
            //        code: Errors.SendingAndReceivingAddressesAreSame,
            //        description: "Sending and receiving addresses are the same.");

            var erc20Config = Erc20Config;

            if (useDefaultFee)
            {
                gasLimit = GasLimitByType(BlockchainTransactionType.Output);

                gasPrice = await erc20Config
                           .GetGasPriceAsync()
                           .ConfigureAwait(false);
            }

            var addressFeeUsage = await SelectUnspentAddressesAsync(
                from : from,
                amount : amount,
                fee : gasLimit,
                feePrice : gasPrice,
                cancellationToken : cancellationToken)
                                  .ConfigureAwait(false);

            if (addressFeeUsage == null)
            {
                return(new Error(
                           code: Errors.InsufficientFunds,
                           description: "Insufficient funds"));
            }

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

            var feeAmount = erc20Config.GetFeeAmount(gasLimit, gasPrice);

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

            Log.Debug("Send {@amount} of {@currency} from address {@address} with available balance {@balance}",
                      addressFeeUsage.UsedAmount,
                      erc20Config.Name,
                      addressFeeUsage.WalletAddress.Address,
                      addressFeeUsage.WalletAddress.AvailableBalance());

            using var addressLock = await EthereumAccount.AddressLocker
                                    .GetLockAsync(addressFeeUsage.WalletAddress.Address, cancellationToken)
                                    .ConfigureAwait(false);

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

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

            TransactionInput txInput;

            var message = new ERC20TransferFunctionMessage
            {
                To          = to.ToLowerInvariant(),
                Value       = erc20Config.TokensToTokenDigits(addressFeeUsage.UsedAmount),
                FromAddress = addressFeeUsage.WalletAddress.Address,
                Gas         = new BigInteger(gasLimit),
                GasPrice    = new BigInteger(EthereumConfig.GweiToWei(gasPrice)),
                Nonce       = nonceResult.Value
            };

            txInput = message.CreateTransactionInput(erc20Config.ERC20ContractAddress);

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

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

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

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

            var broadcastResult = await erc20Config.BlockchainApi
                                  .BroadcastAsync(tx, cancellationToken)
                                  .ConfigureAwait(false);

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

            var txId = broadcastResult.Value;

            if (txId == null)
            {
                return(new Error(
                           code: Errors.TransactionBroadcastError,
                           description: "Transaction Id is null"));
            }

            Log.Debug("Transaction successfully sent with txId: {@id}", txId);

            tx.Amount = erc20Config.TokensToTokenDigits(addressFeeUsage.UsedAmount);
            tx.To     = to.ToLowerInvariant();

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

            var ethTx = tx.Clone();

            ethTx.Currency = EthConfig.Name;
            ethTx.Amount   = 0;
            ethTx.Type     = BlockchainTransactionType.TokenCall;

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

            _ = UpdateBalanceAsync(cancellationToken);

            return(null);
        }
예제 #22
0
 public EtherScanApi(EthereumConfig currency)
 {
     Currency = currency;
     BaseUrl  = currency.BlockchainApiBaseUri;
 }
예제 #23
0
        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);
        }
예제 #24
0
        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);
        }
예제 #25
0
        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));
        }
예제 #26
0
 public ChannelsContract(EthereumConfig config)
 {
     _config  = config;
     _account = new Account(_config.PrivateKey);
     _web3    = new Web3(_account, _config.RpcAddress);
 }