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)); } }
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)); }
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)); }
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); } }
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); }
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); } }
public async Task <string> BroadcastAsync( IBlockchainTransaction transaction, CancellationToken cancellationToken = default(CancellationToken)) { return(await _web3.BroadcastAsync(transaction, cancellationToken) .ConfigureAwait(false)); }
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)); } }
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); }
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); }
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)); } }
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); }
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)); }
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)); } }
protected async Task RefundConfirmedEventHandler( Swap swap, IBlockchainTransaction tx, CancellationToken cancellationToken = default) { swap.StateFlags |= SwapStateFlags.IsRefundConfirmed; await UpdateSwapAsync(swap, SwapStateFlags.IsRefundConfirmed, cancellationToken) .ConfigureAwait(false); }
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)); }
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)); } }
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."), });
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); }
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); }
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"); } }
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 }); }