public IActionResult Get([Required, FromQuery] int take, [FromQuery] string continuation)
        {
            if (take < 1)
            {
                return(BadRequest(ErrorResponse.Create("Invalid parameter").AddModelError("take", "Must be positive non zero integer")));
            }
            if (!string.IsNullOrEmpty(continuation))
            {
                return(BadRequest(ErrorResponse.Create("Invalid parameter").AddModelError("continuation", "Continuation token not supported")));
            }

            var assets = new [] { _blockchainAssetsService.GetNativeAsset().ToAssetResponse() };

            return(Ok(PaginationResponse.From("", assets)));
        }
        public async Task <AddressBalance> GetAddressBalanceAsync(string address, Fees fees = null)
        {
            var baseAddress = GetBaseAddress(address);
            var result      = new AddressBalance
            {
                Address = baseAddress
            };

            var accountDetails = await _horizonService.GetAccountDetails(baseAddress);

            if (accountDetails == null)
            {
                // address not found
                return(result);
            }
            result.Sequence = accountDetails.SequenceNumber;

            var addressExtension = GetPublicAddressExtension(address);

            if (string.IsNullOrEmpty(addressExtension))
            {
                var nativeBalance = accountDetails.Balances.Single(b => _blockchainAssetsService.GetNativeAsset().TypeName.Equals(b.AssetType, StringComparison.OrdinalIgnoreCase));
                result.Balance = Convert.ToInt64(decimal.Parse(nativeBalance.BalanceString, CultureInfo.InvariantCulture) * One.Value);
            }
            else
            {
                var walletBalance = await _walletBalanceRepository.GetAsync(_blockchainAssetsService.GetNativeAsset().Id, address);

                if (walletBalance != null)
                {
                    result.Balance = walletBalance.Balance;
                }
            }

            if (fees == null)
            {
                return(result);
            }

            var entries    = accountDetails.Signers.Length + accountDetails.SubentryCount;
            var minBalance = (2 + entries) * fees.BaseReserve;

            result.MinBalance = Convert.ToInt64(minBalance);

            return(result);
        }
Esempio n. 3
0
        private async Task <int> QueryAndProcessTransactions(string address, TransactionContext context, Func <TxDirectionType, TxHistory, Task <bool> > process)
        {
            var transactions = await _horizonService.GetTransactions(address, OrderDirection.ASC, context.Cursor);

            var count = 0;

            context.Cursor = null;
            foreach (var transaction in transactions)
            {
                try
                {
                    context.Cursor = transaction.PagingToken;
                    count++;

                    var xdr        = Convert.FromBase64String(transaction.EnvelopeXdr);
                    var reader     = new XdrDataInputStream(xdr);
                    var txEnvelope = TransactionEnvelope.Decode(reader);
                    var tx         = txEnvelope.V1;
                    var operations = txEnvelope?.V1?.Tx?.Operations ?? txEnvelope.V0.Tx.Operations;

                    for (short i = 0; i < operations.Length; i++)
                    {
                        var operation     = operations[i];
                        var operationType = operation.Body.Discriminant.InnerValue;

                        DateTime.TryParse(transaction.CreatedAt, out var createdAt);
                        var history = new TxHistory
                        {
                            FromAddress    = transaction.SourceAccount,
                            AssetId        = _blockchainAssetsService.GetNativeAsset().Id,
                            Hash           = transaction.Hash,
                            OperationIndex = i,
                            PagingToken    = transaction.PagingToken,
                            CreatedAt      = createdAt,
                            Memo           = _horizonService.GetMemo(transaction)
                        };

                        // ReSharper disable once SwitchStatementMissingSomeCases
                        switch (operationType)
                        {
                        case OperationType.OperationTypeEnum.CREATE_ACCOUNT:
                        {
                            var op      = operation.Body.CreateAccountOp;
                            var keyPair = KeyPair.FromXdrPublicKey(op.Destination.InnerValue);
                            history.ToAddress   = keyPair.Address;
                            history.Amount      = op.StartingBalance.InnerValue;
                            history.PaymentType = PaymentType.CreateAccount;
                            break;
                        }

                        case OperationType.OperationTypeEnum.PAYMENT:
                        {
                            var op = operation.Body.PaymentOp;
                            if (op.Asset.Discriminant.InnerValue == AssetType.AssetTypeEnum.ASSET_TYPE_NATIVE)
                            {
                                var keyPair = KeyPair.FromPublicKey(op.Destination.Ed25519.InnerValue);
                                history.ToAddress   = keyPair.Address;
                                history.Amount      = op.Amount.InnerValue;
                                history.PaymentType = PaymentType.Payment;
                            }
                            break;
                        }

                        case OperationType.OperationTypeEnum.ACCOUNT_MERGE:
                        {
                            var op      = operation.Body;
                            var keyPair = KeyPair.FromPublicKey(op.Destination.Ed25519.InnerValue);
                            history.ToAddress   = keyPair.Address;
                            history.Amount      = _horizonService.GetAccountMergeAmount(transaction.ResultXdr, i);
                            history.PaymentType = PaymentType.AccountMerge;
                            break;
                        }

                        case OperationType.OperationTypeEnum.PATH_PAYMENT_STRICT_RECEIVE:
                        {
                            var op = operation.Body.PathPaymentStrictReceiveOp;
                            if (op.DestAsset.Discriminant.InnerValue == AssetType.AssetTypeEnum.ASSET_TYPE_NATIVE)
                            {
                                var keyPair = KeyPair.FromPublicKey(op.Destination.Ed25519.InnerValue);
                                history.ToAddress   = keyPair.Address;
                                history.Amount      = op.DestAmount.InnerValue;
                                history.PaymentType = PaymentType.PathPayment;
                            }
                            break;
                        }

                        default:
                            continue;
                        }

                        if (!ForbiddenCharacterAzureStorageUtils.IsValidRowKey(history.Memo))
                        {
                            await _log.WriteErrorAsync(nameof(TransactionHistoryService),
                                                       nameof(QueryAndProcessTransactions),
                                                       history.Memo,
                                                       new Exception("Possible cashin skipped. It has forbiddden characters in memo."));

                            continue;
                        }

                        var cancel = false;
                        if (address.Equals(history.ToAddress, StringComparison.OrdinalIgnoreCase))
                        {
                            cancel = await process(TxDirectionType.Incoming, history);
                        }
                        if (address.Equals(history.FromAddress, StringComparison.OrdinalIgnoreCase))
                        {
                            cancel = await process(TxDirectionType.Outgoing, history);
                        }
                        if (cancel)
                        {
                            return(count);
                        }
                    }
                }
                catch (Exception ex)
                {
                    throw new BusinessException($"Failed to process transaction. hash={transaction?.Hash}", ex);
                }
            }

            return(count);
        }
        public async Task <IActionResult> BuildSingle([Required, FromBody] BuildSingleTransactionRequest request)
        {
            if (request == null || request.OperationId.Equals(Guid.Empty))
            {
                return(BadRequest(ErrorResponse.Create("Invalid parameter").AddModelError(nameof(request.OperationId), "Must be valid guid")));
            }

            string xdrBase64;
            var    broadcast = await _transactionService.GetTxBroadcastAsync(request.OperationId);

            if (broadcast != null)
            {
                return(new StatusCodeResult(StatusCodes.Status409Conflict));
            }
            var build = await _transactionService.GetTxBuildAsync(request.OperationId);

            if (build != null)
            {
                xdrBase64 = build.XdrBase64;
            }
            else
            {
                string memo = null;

                if (!_balanceService.IsAddressValid(request.FromAddress, out var fromAddressHasExtension))
                {
                    return(BadRequest(ErrorResponse.Create($"{nameof(request.FromAddress)} is not a valid")));
                }
                if (!_balanceService.IsAddressValid(request.ToAddress, out var toAddressHasExtension))
                {
                    return(BadRequest(ErrorResponse.Create($"{nameof(request.ToAddress)} is not a valid")));
                }

                if (fromAddressHasExtension)
                {
                    if (!_balanceService.IsDepositBaseAddress(request.FromAddress))
                    {
                        return(BadRequest(ErrorResponse.Create($"{nameof(request.FromAddress)} is not a valid. Public address extension allowed for deposit base address only!")));
                    }

                    if (!_balanceService.IsDepositBaseAddress(request.ToAddress) || toAddressHasExtension)
                    {
                        return(BadRequest(ErrorResponse.Create($"{nameof(request.ToAddress)} is not a valid. Only deposit base address allowed as destination, when sending from address with public address extension!")));
                    }

                    memo = _balanceService.GetPublicAddressExtension(request.FromAddress);
                }
                var toBaseAddress = _balanceService.GetBaseAddress(request.ToAddress);
                if (toAddressHasExtension)
                {
                    memo = _balanceService.GetPublicAddressExtension(request.ToAddress);
                }

                if (request.AssetId != _blockchainAssetsService.GetNativeAsset().Id)
                {
                    return(BadRequest(ErrorResponse.Create($"{nameof(request.AssetId)} was not found")));
                }

                long amount;
                try
                {
                    amount = long.Parse(request.Amount);
                }
                catch (FormatException)
                {
                    // too small (e.g. 0.1)
                    return(BadRequest(StellarErrorResponse.Create($"Amount is too small. min=1, amount={request.Amount}", BlockchainErrorCode.AmountIsTooSmall)));
                }

                var fees = new Fees();
                if (!fromAddressHasExtension)
                {
                    fees = await _transactionService.GetFeesAsync();
                }
                var fromAddressBalance = await _balanceService.GetAddressBalanceAsync(request.FromAddress, fees);

                long requiredBalance;
                if (request.IncludeFee)
                {
                    requiredBalance = amount;
                    amount         -= fees.BaseFee;
                }
                else
                {
                    requiredBalance = amount + fees.BaseFee;
                }
                var availableBalance = fromAddressBalance.Balance;
                if (requiredBalance > availableBalance)
                {
                    return(BadRequest(StellarErrorResponse.Create($"Not enough balance to create transaction. required={requiredBalance}, available={availableBalance}",
                                                                  BlockchainErrorCode.NotEnoughBalance)));
                }

                xdrBase64 = await _transactionService.BuildTransactionAsync(request.OperationId, fromAddressBalance, toBaseAddress, memo, amount);
            }

            return(Ok(new BuildTransactionResponse
            {
                TransactionContext = xdrBase64
            }));
        }
Esempio n. 5
0
        private async Task <bool> ProcessDwToHwTransaction(Guid operationId, stellar_dotnet_sdk.xdr.Transaction tx)
        {
            var fromKeyPair = KeyPair.FromPublicKey(tx.SourceAccount.Ed25519.InnerValue);

            if (!_balanceService.IsDepositBaseAddress(fromKeyPair.Address) || tx.Operations.Length != 1 ||
                tx.Operations[0].Body.PaymentOp == null || string.IsNullOrWhiteSpace(tx.Memo.Text))
            {
                return(false);
            }

            var toKeyPair = KeyPair.FromPublicKey(tx.Operations[0].Body.PaymentOp.Destination.Ed25519.InnerValue);

            if (!_balanceService.IsDepositBaseAddress(toKeyPair.Address))
            {
                return(false);
            }

            var fromAddress = $"{fromKeyPair.Address}{Constants.PublicAddressExtension.Separator}{tx.Memo.Text}";
            var amount      = tx.Operations[0].Body.PaymentOp.Amount.InnerValue;

            // Use our guid-ed OperationId as transaction hash, as it uniquely identifies the transaction,
            // just without dashes to look more hash-y.
            var hash = operationId.ToString("N");

            // While we have only single action within DW->HW transaction,
            // we can use any value to identify action within transaction.
            // Use hashed operation ID to add more diversity.
            var opId = operationId.ToString("N").CalculateHash64();

            var ledger = await _horizonService.GetLatestLedger();

            var updateLedger = (ledger.Sequence * 10) + 1;
            var broadcast    = new TxBroadcast
            {
                OperationId = operationId,
                Amount      = amount,
                Fee         = 0,
                Hash        = hash,
                // ReSharper disable once ArrangeRedundantParentheses
                Ledger    = updateLedger,
                CreatedAt = DateTime.UtcNow
            };

            var assetId = _blockchainAssetsService.GetNativeAsset().Id;
            var balance = await _balanceRepository.GetAsync(assetId, fromAddress);

            if (balance.Balance < amount)
            {
                broadcast.State     = TxBroadcastState.Failed;
                broadcast.Error     = "Not enough balance!";
                broadcast.ErrorCode = TxExecutionError.NotEnoughBalance;
            }
            else
            {
                await _balanceRepository.RecordOperationAsync(assetId, fromAddress, updateLedger, opId, hash, (-1) *amount);

                await _balanceRepository.RefreshBalance(assetId, fromAddress);

                broadcast.State = TxBroadcastState.Completed;
            }

            _chaos.Meow(nameof(ProcessDwToHwTransaction));

            // update state
            await _broadcastRepository.InsertOrReplaceAsync(broadcast);

            return(true);
        }