Пример #1
0
        public virtual async Task UpsertTransactionAsync(
            IBlockchainTransaction tx,
            bool updateBalance                  = false,
            bool notifyIfUnconfirmed            = true,
            bool notifyIfBalanceUpdated         = true,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            await ResolveTransactionTypeAsync(tx, cancellationToken)
            .ConfigureAwait(false);

            // todo: optimize, if tx already added in data repository
            var result = await DataRepository
                         .UpsertTransactionAsync(tx)
                         .ConfigureAwait(false);

            if (!result)
            {
                return; // todo: error or message?
            }
            if (updateBalance)
            {
                await UpdateBalanceAsync(cancellationToken)
                .ConfigureAwait(false);
            }

            if (notifyIfUnconfirmed && !tx.IsConfirmed())
            {
                RaiseUnconfirmedTransactionAdded(new TransactionEventArgs(tx));
            }

            if (updateBalance && notifyIfBalanceUpdated)
            {
                RaiseBalanceUpdated(new CurrencyEventArgs(tx.Currency));
            }
        }
Пример #2
0
        public override async Task <Result <string> > BroadcastAsync(
            IBlockchainTransaction transaction,
            CancellationToken cancellationToken = default)
        {
            await RequestLimitControl
            .Wait(cancellationToken)
            .ConfigureAwait(false);

            var tx    = (IBitcoinBasedTransaction)transaction;
            var txHex = tx.ToBytes().ToHexString();

            tx.State = BlockchainTransactionState.Pending;

            const string requestUri = "api/tx/send";
            var          parameters = new Dictionary <string, string> {
                { "rawtx", txHex }
            };

            using var requestContent = new FormUrlEncodedContent(parameters);

            return(await HttpHelper.PostAsyncResult <string>(
                       baseUri : BaseUri,
                       requestUri : requestUri,
                       content : requestContent,
                       responseHandler : (response, responseContent) => JsonConvert.DeserializeObject <SendTxId>(responseContent).TxId,
                       cancellationToken : cancellationToken)
                   .ConfigureAwait(false));
        }
Пример #3
0
        public async Task <string> BroadcastAsync(
            IBlockchainTransaction transaction,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            var tx    = (IBitcoinBasedTransaction)transaction;
            var txHex = tx.ToBytes().ToHexString();

            var requestUri = $"api/tx/send"; //?rawtx={txHex}";

            var parameters = new Dictionary <string, string> {
                { "rawtx", txHex }
            };

            var content = new FormUrlEncodedContent(parameters);

            return(await HttpHelper.PostAsync(
                       baseUri : BaseUri,
                       requestUri : requestUri,
                       content : content,
                       responseHandler : responseContent => JsonConvert.DeserializeObject <SendTxId>(responseContent).TxId,
                       requestLimitChecker : RequestLimitChecker,
                       maxAttempts : MaxRequestAttemptsCount,
                       cancellationToken : cancellationToken)
                   .ConfigureAwait(false));
        }
Пример #4
0
        private async void PaymentConfirmedEventHandler(
            Swap swap,
            IBlockchainTransaction tx,
            CancellationToken cancellationToken = default)
        {
            Log.Debug("Handle {@currency} payment confirmed event for swap {@swapId}",
                      Currency,
                      swap.Id);

            try
            {
                swap.StateFlags |= SwapStateFlags.IsPaymentConfirmed;
                RaiseSwapUpdated(swap, SwapStateFlags.IsPaymentConfirmed);

                await _account
                .UpsertTransactionAsync(
                    tx : tx,
                    updateBalance : true)
                .ConfigureAwait(false);
            }
            catch (Exception e)
            {
                Log.Error(e, "Error while handle payment tx confirmed event");
            }

            if (swap.IsInitiator)
            {
                RaiseInitiatorPaymentConfirmed(swap);
            }
            else
            {
                RaiseAcceptorPaymentConfirmed(swap);
            }
        }
Пример #5
0
        public TransactionViewModel(IBlockchainTransaction tx, CurrencyConfig currencyConfig, decimal amount, decimal fee)
        {
            Transaction = tx ?? throw new ArgumentNullException(nameof(tx));
            Id          = Transaction.Id;
            Currency    = currencyConfig;
            State       = Transaction.State;
            Type        = GetType(Transaction.Type);
            //Type = Transaction.Type;
            Amount = amount;

            TxExplorerUri = $"{Currency.TxExplorerUri}{Id}";

            ToastService = DependencyService.Get <IToastService>();

            var netAmount = amount + fee;

            AmountFormat = currencyConfig.Format;
            CurrencyCode = currencyConfig.Name;
            Time         = tx.CreationTime ?? DateTime.UtcNow;
            CanBeRemoved = tx.State == BlockchainTransactionState.Failed ||
                           tx.State == BlockchainTransactionState.Pending ||
                           tx.State == BlockchainTransactionState.Unknown ||
                           tx.State == BlockchainTransactionState.Unconfirmed;

            Description = GetDescription(
                type: tx.Type,
                amount: Amount,
                netAmount: netAmount,
                amountDigits: currencyConfig.Digits,
                currencyCode: currencyConfig.Name);
        }
Пример #6
0
        private async void PartyPaymentConfirmedHandler(
            Swap swap,
            IBlockchainTransaction tx,
            CancellationToken cancellationToken = default)
        {
            Log.Debug("Handle party's payment confirmed event for swap {@swapId}", swap.Id);

            swap.StateFlags |= SwapStateFlags.IsPartyPaymentConfirmed;
            RaiseSwapUpdated(swap, SwapStateFlags.IsPartyPaymentConfirmed);

            try
            {
                if (swap.IsInitiator)
                {
                    await RedeemAsync(swap)
                    .ConfigureAwait(false);
                }
            }
            catch (Exception e)
            {
                Log.Error(e, "Error while handle counterParty's payment tx confirmed event");
            }

            if (swap.IsInitiator)
            {
                RaiseAcceptorPaymentConfirmed(swap);
            }
            else
            {
                RaiseInitiatorPaymentConfirmed(swap);
            }
        }
Пример #7
0
 public async Task <string> BroadcastAsync(
     IBlockchainTransaction transaction,
     CancellationToken cancellationToken = default(CancellationToken))
 {
     return(await _web3.BroadcastAsync(transaction, cancellationToken)
            .ConfigureAwait(false));
 }
Пример #8
0
        public override async Task <Result <string> > BroadcastAsync(
            IBlockchainTransaction transaction,
            CancellationToken cancellationToken = default)
        {
            await RequestLimitControl
            .Wait(cancellationToken)
            .ConfigureAwait(false);

            var tx    = (IBitcoinBasedTransaction)transaction;
            var txHex = tx.ToBytes().ToHexString();

            tx.State = BlockchainTransactionState.Pending;

            var requestUri     = $"api/{Currency.Name}/{Currency.Network.ToString().ToLower()}/tx/send";
            var requestContent = JsonConvert.SerializeObject(new RawTx {
                RawTxHex = txHex
            });

            return(await HttpHelper.PostAsyncResult <string>(
                       baseUri : BaseUri,
                       requestUri : requestUri,
                       content : new StringContent(requestContent, Encoding.UTF8, "application/json"),
                       responseHandler : (response, content) => JsonConvert.DeserializeObject <SendTxId>(content).TxId,
                       cancellationToken : cancellationToken)
                   .ConfigureAwait(false));
        }
        public override async Task <Result <string> > BroadcastAsync(
            IBlockchainTransaction transaction,
            CancellationToken cancellationToken = default)
        {
            try
            {
                if (!(transaction is EthereumTransaction ethTx))
                {
                    throw new NotSupportedException("Not supported transaction type");
                }

                transaction.State = BlockchainTransactionState.Pending;

                var web3 = new Web3(_uri);

                var txId = await web3.Eth.Transactions
                           .SendRawTransaction
                           .SendRequestAsync("0x" + ethTx.RlpEncodedTx)
                           .ConfigureAwait(false);

                ethTx.Id = txId; // todo: wtf?

                return(txId);
            }
            catch (Nethereum.JsonRpc.Client.RpcResponseException e)
            {
                return(new Error(Errors.RpcResponseError, e.RpcError?.Message));
            }
            catch (Exception e)
            {
                return(new Error(Errors.RequestError, e.Message));
            }
        }
Пример #10
0
        public override async Task <Result <string> > BroadcastAsync(
            IBlockchainTransaction transaction,
            CancellationToken cancellationToken = default)
        {
            if (!(transaction is EthereumTransaction ethTx))
            {
                return(new Error(Errors.TransactionBroadcastError, "Invalid transaction type."));
            }

            var requestUri = $"api?module=proxy&action=eth_sendRawTransaction&hex=0x{ethTx.RlpEncodedTx}&apikey={ApiKey}";

            await RequestLimitControl
            .Wait(cancellationToken)
            .ConfigureAwait(false);

            var txId = await HttpHelper.PostAsyncResult <string>(
                baseUri : BaseUrl,
                requestUri : requestUri,
                content : null,
                responseHandler : (response, content) =>
            {
                var json = JsonConvert.DeserializeObject <JObject>(content);

                return(json.ContainsKey("result")
                           ? json["result"].Value <string>()
                           : null);
            },
                cancellationToken : cancellationToken)
                       .ConfigureAwait(false);

            ethTx.Id = txId.Value;

            return(txId);
        }
Пример #11
0
        private async Task BroadcastRedeemAsync(
            Swap swap,
            IBlockchainTransaction redeemTx,
            CancellationToken cancellationToken = default)
        {
            var currency = Currencies.GetByName(swap.PurchasedCurrency);

            var broadcastResult = await currency.BlockchainApi
                                  .TryBroadcastAsync(redeemTx, cancellationToken : cancellationToken)
                                  .ConfigureAwait(false);

            if (broadcastResult.HasError)
            {
                throw new Exception($"Error while broadcast transaction. Code: {broadcastResult.Error.Code}. Description: {broadcastResult.Error.Description}");
            }

            var txId = broadcastResult.Value;

            if (txId == null)
            {
                throw new Exception("Transaction Id is null");
            }

            Log.Debug("Redeem tx {@txId} successfully broadcast for swap {@swapId}", txId, swap.Id);
        }
Пример #12
0
        public async Task <string> BroadcastAsync(
            IBlockchainTransaction transaction,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            var tx = (IBitcoinBasedTransaction)transaction;

            var requestUri = $"api/v2/send_tx/{NetworkAcronym}";

            var txHex = tx.ToBytes().ToHexString();

            Log.Debug("TxHex: {@txHex}", txHex);

            using (var content = new StringContent(
                       content: JsonConvert.SerializeObject(new SendTx(txHex)),
                       encoding: Encoding.UTF8,
                       mediaType: "application/json"))
            {
                return(await HttpHelper.PostAsync(
                           baseUri : BaseUrl,
                           requestUri : requestUri,
                           content : content,
                           responseHandler : responseContent => JsonConvert.DeserializeObject <Response <SendTxId> >(responseContent).Data.TxId,
                           requestLimitChecker : RequestLimitChecker,
                           maxAttempts : MaxRequestAttemptsCount,
                           cancellationToken : cancellationToken)
                       .ConfigureAwait(false));
            }
        }
Пример #13
0
        protected override async Task <bool> ResolveTransactionTypeAsync(
            IBlockchainTransaction tx,
            CancellationToken cancellationToken = default)
        {
            var currency = BtcBasedCurrency;

            var oldTx = await DataRepository
                        .GetTransactionByIdAsync(Currency, tx.Id, currency.TransactionType)
                        .ConfigureAwait(false);

            if (oldTx != null && oldTx.IsConfirmed)
            {
                return(false);
            }

            var outputs = await DataRepository
                          .GetOutputsAsync(Currency, currency.OutputType())
                          .ConfigureAwait(false);

            var indexedOutputs = outputs.ToDictionary(o => $"{o.TxId}:{o.Index}");

            var btcBasedTx = (IBitcoinBasedTransaction)tx;

            var selfInputs = btcBasedTx.Inputs
                             .Where(i => indexedOutputs.ContainsKey($"{i.Hash}:{i.Index}"))
                             .Select(i => indexedOutputs[$"{i.Hash}:{i.Index}"])
                             .ToList();

            if (selfInputs.Any())
            {
                btcBasedTx.Type |= BlockchainTransactionType.Output;
            }

            var sentAmount = selfInputs.Sum(i => i.Value);

            // todo: recognize swap refund/redeem

            var selfOutputs = btcBasedTx.Outputs
                              .Where(o => indexedOutputs.ContainsKey($"{o.TxId}:{o.Index}"))
                              .ToList();

            if (selfOutputs.Any())
            {
                btcBasedTx.Type |= BlockchainTransactionType.Input;
            }

            var receivedAmount = selfOutputs.Sum(o => o.Value);

            btcBasedTx.Amount = receivedAmount - sentAmount;

            // todo: recognize swap payment

            if (oldTx != null)
            {
                btcBasedTx.Type |= oldTx.Type;
            }

            return(true);
        }
Пример #14
0
 private void RefundConfirmedEventHandler(
     Swap swap,
     IBlockchainTransaction tx,
     CancellationToken cancellationToken = default)
 {
     swap.StateFlags |= SwapStateFlags.IsRefundConfirmed;
     RaiseSwapUpdated(swap, SwapStateFlags.IsRefundConfirmed);
 }
 public static Task <Result <ConfirmationCheckResult> > IsTransactionConfirmed(
     this IBlockchainTransaction tx,
     CancellationToken cancellationToken = default)
 {
     return(IsTransactionConfirmed(
                currency: tx.Currency,
                txId: tx.Id,
                cancellationToken: cancellationToken));
 }
Пример #16
0
 public async Task <Result <string> > TryBroadcastAsync(
     IBlockchainTransaction transaction,
     int attempts           = 3,
     int attemptsIntervalMs = 1000,
     CancellationToken cancellationToken = default)
 {
     return(await ResultHelper.TryDo((c) => BroadcastAsync(transaction, c), attempts, attemptsIntervalMs, cancellationToken)
            .ConfigureAwait(false) ?? new Error(Errors.RequestError, $"Connection error while getting transaction after {attempts} attempts"));
 }
        public virtual Task <bool> UpsertTransactionAsync(IBlockchainTransaction tx)
        {
            lock (_sync)
            {
                _transactions[tx.Id] = tx; // todo: copy?

                return(Task.FromResult(true));
            }
        }
Пример #18
0
        protected async Task RefundConfirmedEventHandler(
            Swap swap,
            IBlockchainTransaction tx,
            CancellationToken cancellationToken = default)
        {
            swap.StateFlags |= SwapStateFlags.IsRefundConfirmed;

            await UpdateSwapAsync(swap, SwapStateFlags.IsRefundConfirmed, cancellationToken)
            .ConfigureAwait(false);
        }
Пример #19
0
        public override async Task <Result <string> > BroadcastAsync(
            IBlockchainTransaction transaction,
            CancellationToken cancellationToken = default)
        {
            try
            {
                var tx = (TezosTransaction)transaction;
                tx.State = BlockchainTransactionState.Pending;

                var rpc = new Rpc(_rpcNodeUri);

                var opResults = await rpc
                                .PreApplyOperations(tx.Head, tx.Operations, tx.SignedMessage.EncodedSignature)
                                .ConfigureAwait(false);

                if (!opResults.Any())
                {
                    return(new Error(Errors.EmptyPreApplyOperations, "Empty pre apply operations"));
                }

                string txId = null;

                foreach (var opResult in opResults)
                {
                    Log.Debug("OperationResult {@result}: {@opResult}", opResult.Succeeded, opResult.Data.ToString());
                }

                if (opResults.Any() && opResults.All(op => op.Succeeded))
                {
                    var injectedOperation = await rpc
                                            .InjectOperations(tx.SignedMessage.SignedBytes)
                                            .ConfigureAwait(false);

                    txId = injectedOperation.ToString();
                }

                if (txId == null)
                {
                    return(new Error(Errors.NullTxId, "Null tx id"));
                }

                tx.Id = txId;

                return(tx.Id);
            }
            catch (Exception e)
            {
                return(new Error(Errors.RequestError, e.Message));
            }
        }
        public static Task <string> ForceBroadcast(
            this IBlockchainTransaction tx,
            IBlockchainApi blockchainApi,
            Swap swap,
            TimeSpan interval,
            Action <Swap, string, CancellationToken> completionHandler = null,
            CancellationToken cancellationToken = default)
        {
            return(Task.Run(async() =>
            {
                try
                {
                    while (!cancellationToken.IsCancellationRequested)
                    {
                        var broadcastResult = await blockchainApi
                                              .TryBroadcastAsync(tx, cancellationToken: cancellationToken)
                                              .ConfigureAwait(false);

                        if (!broadcastResult.HasError)
                        {
                            if (broadcastResult.Value != null)
                            {
                                completionHandler?.Invoke(swap, broadcastResult.Value, cancellationToken);
                                return broadcastResult.Value;
                            }
                        }
                        else
                        {
                            Log.Error("Error while broadcast {@currency} tx with. Code: {@code}. Description: {@desc}",
                                      tx.Currency,
                                      broadcastResult.Error.Code,
                                      broadcastResult.Error.Description);
                        }

                        await Task.Delay(interval, cancellationToken)
                        .ConfigureAwait(false);
                    }
                }
                catch (OperationCanceledException)
                {
                    Log.Debug("ForceBroadcast canceled.");
                }
                catch (Exception e)
                {
                    Log.Error(e, "Error while broadcast {@currency} tx.", tx.Currency);
                }

                return null;
            }, cancellationToken));
        }
Пример #21
0
        public override async Task UpsertTransactionAsync(
            IBlockchainTransaction tx,
            bool updateBalance                  = false,
            bool notifyIfUnconfirmed            = true,
            bool notifyIfBalanceUpdated         = true,
            CancellationToken cancellationToken = default)
        {
            if (!(tx is IBitcoinBasedTransaction btcBasedTx))
            {
                throw new NotSupportedException("Transaction has incorrect type");
            }

            await UpsertOutputsAsync(
                tx : btcBasedTx,
                cancellationToken : cancellationToken)
            .ConfigureAwait(false);

            var result = await ResolveTransactionTypeAsync(tx, cancellationToken)
                         .ConfigureAwait(false);

            if (result == false)
            {
                return;
            }

            result = await DataRepository
                     .UpsertTransactionAsync(tx)
                     .ConfigureAwait(false);

            if (!result)
            {
                return; // TODO: return result
            }
            if (updateBalance)
            {
                await UpdateBalanceAsync(cancellationToken)
                .ConfigureAwait(false);
            }

            if (notifyIfUnconfirmed && !tx.IsConfirmed)
            {
                RaiseUnconfirmedTransactionAdded(new TransactionEventArgs(tx));
            }

            if (updateBalance && notifyIfBalanceUpdated)
            {
                RaiseBalanceUpdated(new CurrencyEventArgs(tx.Currency.Name));
            }
        }
Пример #22
0
 public static TransactionViewModel CreateViewModel(
     IBlockchainTransaction tx,
     CurrencyConfig currencyConfig)
 {
     return(tx.Currency switch
     {
         "BTC" => (TransactionViewModel) new BitcoinBasedTransactionViewModel(tx as IBitcoinBasedTransaction, currencyConfig as BitcoinBasedConfig),
         "LTC" => new BitcoinBasedTransactionViewModel(tx as IBitcoinBasedTransaction, currencyConfig as BitcoinBasedConfig),
         "USDT" => new EthereumERC20TransactionViewModel(tx as EthereumTransaction, currencyConfig as Erc20Config),
         "TBTC" => new EthereumERC20TransactionViewModel(tx as EthereumTransaction, currencyConfig as Erc20Config),
         "WBTC" => new EthereumERC20TransactionViewModel(tx as EthereumTransaction, currencyConfig as Erc20Config),
         "ETH" => new EthereumTransactionViewModel(tx as EthereumTransaction, currencyConfig as EthereumConfig),
         "XTZ" => new TezosTransactionViewModel(tx as TezosTransaction, currencyConfig as TezosConfig),
         _ => throw new NotSupportedException("Not supported transaction type."),
     });
Пример #23
0
        private async Task ProcessNewTransaction(string clientId, string address, IBlockchainTransaction tx, bool segwit)
        {
            var balanceChangeTx = BalanceChangeTransaction.Create(tx, clientId, address, segwit);

            var shouldBeProcessed = await _balanceChangeTransactionsRepository.InsertIfNotExistsAsync(balanceChangeTx);

            await _log.WriteInfoAsync(nameof(WalletsScannerFunctions), nameof(ProcessNewTransaction),
                                      $"ClientId: {balanceChangeTx.ClientId}, tx hash: {balanceChangeTx.Hash}",
                                      $"Got transaction; shouldBeProcessed: {shouldBeProcessed}");

            if (shouldBeProcessed)
            {
                await HandleDetectedTransaction(address, tx, balanceChangeTx);
            }
        }
        private async Task BroadcastRedeemAsync(ClientSwap swap, IBlockchainTransaction redeemTx)
        {
            var currency = swap.PurchasedCurrency;

            var txId = await currency.BlockchainApi
                       .BroadcastAsync(redeemTx)
                       .ConfigureAwait(false);

            if (txId == null)
            {
                throw new Exception("Transaction Id is null");
            }

            Log.Debug("Redeem tx {@txId} successfully broadcast for swap {@swapId}", txId, swap.Id);
        }
Пример #25
0
 public Task UpsertTransactionAsync(
     IBlockchainTransaction tx,
     bool updateBalance                  = false,
     bool notifyIfUnconfirmed            = true,
     bool notifyIfBalanceUpdated         = true,
     CancellationToken cancellationToken = default)
 {
     return(GetCurrencyAccount(tx.Currency.Name)
            .UpsertTransactionAsync(
                tx: tx,
                updateBalance: updateBalance,
                notifyIfUnconfirmed: notifyIfUnconfirmed,
                notifyIfBalanceUpdated: notifyIfBalanceUpdated,
                cancellationToken: cancellationToken));
 }
        public static bool TryParseTransaction(Currency currency, byte[] txBytes, out IBlockchainTransaction tx)
        {
            tx = null;

            try
            {
                tx = ParseTransaction(currency, txBytes);
                return(true);
            }
            catch (Exception e)
            {
                Log.Error(e, "Transaction parse error: {@message}", e.Message);
            }

            return(false);
        }
Пример #27
0
        private async Task HandleDetectedTransaction(string address, IBlockchainTransaction tx, IBalanceChangeTransaction balanceChangeTx)
        {
            var internalOperation = await _internalOperationsRepository.GetAsync(tx.Hash);

            if (internalOperation?.CommandType == BitCoinCommands.Transfer ||
                IsExternalCashIn(address, tx, internalOperation) ||
                IsOtherClientsCashOut(address, tx, internalOperation))
            {
                var processTransactionCommand = new ProcessTransactionCommand
                {
                    TransactionHash = balanceChangeTx.Hash
                };

                _cqrsEngine.SendCommand(processTransactionCommand, "transactions", "transactions");
            }
        }
Пример #28
0
        public async Task <string> BroadcastAsync(
            IBlockchainTransaction transaction,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            var tx = (IBitcoinBasedTransaction)transaction;

            var txHex = tx.ToBytes().ToHexString();

            Log.Debug("TxHex: {@txHex}", txHex);

            await new TransactionPusher()
            .PushTransactionAsync(txHex)
            .ConfigureAwait(false);

            return(tx.Id); // todo: receive id from network!!!
        }
        public TransactionViewModel CreateViewModel(IBlockchainTransaction tx)
        {
            switch (tx.Currency)
            {
            case BitcoinBasedCurrency _:
                return(new BitcoinBasedTransactionViewModel((IInOutTransaction)tx, IndexedOutputs));

            case Ethereum _:
                return(new EthereumTransactionViewModel((IAddressBasedTransaction)tx));

            case Tezos _:
                return(new TezosTransactionViewModel((IAddressBasedTransaction)tx));
            }

            return(null);
        }
 public static IBalanceChangeTransaction Create(IBlockchainTransaction blockchainTx, string clientId, string multisig, bool isSegwit)
 {
     return(new BalanceChangeTransaction
     {
         ClientId = clientId,
         Confirmations = blockchainTx.Confirmations,
         Hash = blockchainTx.Hash,
         ReceivedCoins = blockchainTx.ReceivedCoins,
         SpentCoins = blockchainTx.SpentCoins,
         Multisig = multisig,
         BlockId = blockchainTx.BlockId,
         Height = blockchainTx.Height,
         DetectDt = DateTime.UtcNow,
         IsSegwit = isSegwit
     });
 }