public async Task BroadcastTransaction(NeoModules.NEP6.Transactions.Transaction signedTransaction, OperationAggregate aggregate) { var txHash = signedTransaction.Hash.ToString().Substring(2); var lastBlockHeight = await _blockchainProvider.GetHeightAsync(); try { var isSuccess = await _neoRawTransactionSender.SendRequestAsync(signedTransaction.ToHexString()); if (!isSuccess) { throw new Exception("Unknown error while broadcasting the tx"); } } catch (RpcResponseException e) when(e.RpcError.Code == -501) { throw new TransactionAlreadyBroadcastedException(e); } await _observableOperationRepository.InsertOrReplace(ObervableOperation.Create(aggregate, BroadcastStatus.InProgress, txHash, (int)lastBlockHeight)); await _unconfirmedTransactionRepository.InsertOrReplace( UnconfirmedTransaction.Create(aggregate.OperationId, txHash)); await _transactionOutputsService.CompleteTxOutputs(aggregate.OperationId, signedTransaction); }
public async Task <IActionResult> BuildClaim([FromBody] BuildClaimTransactionRequest request) { if (request == null) { return(BadRequest(ErrorResponse.Create("Unable to deserialize request"))); } var addressValid = _addressValidator.IsAddressValid(request.Address); if (!addressValid) { return(BadRequest(ErrorResponse.Create("Invalid address"))); } var built = await _transactionBuilder.BuildClaimTransactions(request.Address); var aggregate = await _operationRepository.GetOrInsert(request.OperationId, () => OperationAggregate.StartNew(request.OperationId, fromAddress: request.Address, toAddress: request.Address, amount: built.availiableGas, assetId: Constants.Assets.Gas.AssetId, fee: 0, includeFee: false)); if (aggregate.IsBroadcasted) { return(Conflict()); } if (!built.tx.Claims.Any()) { return(Accepted(new BuiltClaimTransactionResponse { ClaimedGas = MoneyConversionHelper.ToContract(built.availiableGas, Constants.Assets.Gas.AssetId), AllGas = MoneyConversionHelper.ToContract(built.unclaimedGas, Constants.Assets.Gas.AssetId), TransactionContext = TransactionSerializer.Serialize(built.tx, TransactionType.ClaimTransaction) })); } return(Ok(new BuiltClaimTransactionResponse { ClaimedGas = MoneyConversionHelper.ToContract(built.availiableGas, Constants.Assets.Gas.AssetId), AllGas = MoneyConversionHelper.ToContract(built.unclaimedGas, Constants.Assets.Gas.AssetId), TransactionContext = TransactionSerializer.Serialize(built.tx, TransactionType.ClaimTransaction) })); }
public static ObervableOperation Create(OperationAggregate operation, BroadcastStatus status, string txHash, int updatedAtBlockHeight, DateTime?updated = null) { return(new ObervableOperation { OperationId = operation.OperationId, Amount = operation.Amount, AssetId = operation.AssetId, FromAddress = operation.FromAddress, IncludeFee = operation.IncludeFee, ToAddress = operation.ToAddress, Status = status, TxHash = txHash, Updated = updated ?? DateTime.UtcNow, Fee = operation.Fee, UpdatedAtBlockHeight = updatedAtBlockHeight }); }
public static OperationEntity FromDomain(OperationAggregate aggregate) { return(new OperationEntity { ETag = string.IsNullOrEmpty(aggregate.Version) ? "*" : aggregate.Version, Fee = aggregate.Fee, Amount = aggregate.Amount, AssetId = aggregate.AssetId, DetectedOnBlockchain = aggregate.DetectedOnBlockchain, BroadcastedAt = aggregate.BroadcastedAt, FromAddress = aggregate.FromAddress, IncludeFee = aggregate.IncludeFee, InsertedAt = aggregate.InsertedAt, OperationId = aggregate.OperationId, ToAddress = aggregate.ToAddress, PartitionKey = GeneratePartitionKey(aggregate.OperationId), RowKey = GenerateRowKey() }); }
public async Task <IActionResult> BuildSingle([FromBody] BuildSingleTransactionRequest request) { if (request == null) { return(BadRequest(ErrorResponse.Create("Unable to deserialize request"))); } if (!new [] { Constants.Assets.Neo.AssetId, Constants.Assets.Gas.AssetId }.Contains(request.AssetId)) { return(BadRequest(ErrorResponse.Create("Invalid assetId"))); } var amount = MoneyConversionHelper.FromContract(request.Amount, request.AssetId); if (amount <= 0) { return(BadRequest(ErrorResponse.Create($"Amount can't be less or equal to zero: {amount}"))); } if (request.AssetId == Constants.Assets.Neo.AssetId && amount % 1 != 0) { return(BadRequest($"The minimum unit of NEO is 1 and tokens cannot be subdivided.: {amount}")); } var toAddressValid = _addressValidator.IsAddressValid(request.ToAddress); if (!toAddressValid) { return(BadRequest(ErrorResponse.Create("Invalid toAddress"))); } var fromAddressValid = _addressValidator.IsAddressValid(request.FromAddress); if (!fromAddressValid) { return(BadRequest(ErrorResponse.Create("Invalid fromAddress"))); } if (!ModelState.IsValidOperationId(request.OperationId)) { return(BadRequest(ErrorResponseFactory.Create(ModelState))); } if ((await _operationRepository.GetOrDefault(request.OperationId))?.IsBroadcasted ?? false) { return(Conflict()); } Transaction tx; decimal fee = 0; switch (request.AssetId) { case Constants.Assets.Neo.AssetId: (tx, fee) = await _transactionBuilder.BuildNeoContractTransactionAsync(request.FromAddress, request.ToAddress, amount, request.IncludeFee); break; case Constants.Assets.Gas.AssetId: tx = await _transactionBuilder.BuildGasTransactionAsync(request.FromAddress, request.ToAddress, amount); break; default: throw new ArgumentException("Unknown switch", nameof(request.AssetId)); } await _operationRepository.GetOrInsert(request.OperationId, () => OperationAggregate.StartNew(request.OperationId, fromAddress: request.FromAddress, toAddress: request.ToAddress, amount: amount, assetId: request.AssetId, fee: fee, includeFee: request.IncludeFee)); return(Ok(new BuildTransactionResponse { TransactionContext = TransactionSerializer.Serialize(tx, TransactionType.ContractTransaction) })); }
public Task Save(OperationAggregate aggregate) { return(_storage.ReplaceAsync(OperationEntity.FromDomain(aggregate))); }
public Task Insert(OperationAggregate aggregate) { return(_storage.InsertAsync(OperationEntity.FromDomain(aggregate))); }