Example #1
0
        private async Task InitiateSwapAsync(
            Swap swap,
            CancellationToken cancellationToken = default)
        {
            Log.Debug("Initiate swap {@swapId}", swap.Id);

            var soldCurrency = _account.Currencies.GetByName(swap.SoldCurrency);

            if (swap.Secret == null)
            {
                var secret = _account.Wallet
                             .GetDeterministicSecret(soldCurrency, swap.TimeStamp);

                swap.Secret = secret.SubArray(0, CurrencySwap.DefaultSecretSize);

                await UpdateSwapAsync(swap, SwapStateFlags.HasSecret, cancellationToken)
                .ConfigureAwait(false);

                secret.Clear();
            }

            if (swap.SecretHash == null)
            {
                swap.SecretHash = CurrencySwap.CreateSwapSecretHash(swap.Secret);

                await UpdateSwapAsync(swap, SwapStateFlags.HasSecretHash, cancellationToken)
                .ConfigureAwait(false);
            }

            var redeemFromWalletAddress = swap.RedeemFromAddress != null
                ? await _account
                                          .GetAddressAsync(swap.PurchasedCurrency, swap.RedeemFromAddress, cancellationToken)
                                          .ConfigureAwait(false)
                : null;

            var purchasedCurrency = _account.Currencies.GetByName(swap.PurchasedCurrency);

            swap.RewardForRedeem = await RewardForRedeemHelper
                                   .EstimateAsync(
                account : _account,
                quotesProvider : _quotesProvider,
                feeCurrencyQuotesProvider : symbol => _marketDataRepository
                ?.OrderBookBySymbol(symbol)
                ?.TopOfBook(),
                redeemableCurrency : purchasedCurrency,
                redeemFromAddress : redeemFromWalletAddress,
                cancellationToken : cancellationToken)
                                   .ConfigureAwait(false);

            // select refund address for bitcoin based currency
            if (soldCurrency is BitcoinBasedConfig && swap.RefundAddress == null)
            {
                swap.RefundAddress = (await _account
                                      .GetCurrencyAccount <BitcoinBasedAccount>(soldCurrency.Name)
                                      .GetRefundAddressAsync(cancellationToken)
                                      .ConfigureAwait(false))
                                     ?.Address;
            }

            await UpdateSwapAsync(swap, SwapStateFlags.Empty, cancellationToken)
            .ConfigureAwait(false);

            _swapClient.SwapInitiateAsync(swap);
        }
Example #2
0
        private async Task <Error> HandleInitiateAsync(
            Swap swap,
            Swap receivedSwap,
            CancellationToken cancellationToken = default)
        {
            if (DateTime.UtcNow > swap.TimeStamp.ToUniversalTime() + DefaultCredentialsExchangeTimeout)
            {
                Log.Error("Handle initiate after swap {@swap} timeout", swap.Id);

                swap.StateFlags |= SwapStateFlags.IsCanceled;

                await UpdateSwapAsync(swap, SwapStateFlags.IsCanceled, cancellationToken)
                .ConfigureAwait(false);

                return(null); // no error
            }

            // check secret hash
            if (swap.SecretHash != null &&
                !swap.SecretHash.SequenceEqual(receivedSwap.SecretHash))
            {
                return(new Error(Errors.InvalidSecretHash, $"Secret hash does not match the one already received for swap {swap.Id}"));
            }

            if (receivedSwap.SecretHash == null ||
                receivedSwap.SecretHash.Length != CurrencySwap.DefaultSecretHashSize)
            {
                return(new Error(Errors.InvalidSecretHash, $"Incorrect secret hash length for swap {swap.Id}"));
            }

            Log.Debug("Secret hash {@hash} successfully received", receivedSwap.SecretHash.ToHexString());

            swap.SecretHash = receivedSwap.SecretHash;

            await UpdateSwapAsync(swap, SwapStateFlags.HasSecretHash, cancellationToken)
            .ConfigureAwait(false);

            // check party address
            if (receivedSwap.PartyAddress == null)
            {
                return(new Error(Errors.InvalidWallets, $"Incorrect party address for swap {swap.Id}"));
            }

            // check party reward for redeem
            if (receivedSwap.RewardForRedeem < 0)
            {
                return(new Error(Errors.InvalidRewardForRedeem, $"Incorrect reward for redeem for swap {swap.Id}"));
            }

            if (swap.PartyAddress == null)
            {
                swap.PartyAddress = receivedSwap.PartyAddress;
            }

            if (swap.PartyRewardForRedeem == 0 && receivedSwap.PartyRewardForRedeem > 0)
            {
                swap.PartyRewardForRedeem = receivedSwap.PartyRewardForRedeem;
            }

            if (swap.PartyRefundAddress == null)
            {
                swap.PartyRefundAddress = receivedSwap.PartyRefundAddress;
            }

            var redeemFromWalletAddress = swap.RedeemFromAddress != null
                ? await _account
                                          .GetAddressAsync(swap.PurchasedCurrency, swap.RedeemFromAddress, cancellationToken)
                                          .ConfigureAwait(false)
                : null;

            var purchasedCurrency = _account.Currencies.GetByName(swap.PurchasedCurrency);

            swap.RewardForRedeem = await RewardForRedeemHelper
                                   .EstimateAsync(
                account : _account,
                quotesProvider : _quotesProvider,
                feeCurrencyQuotesProvider : symbol => _marketDataRepository
                ?.OrderBookBySymbol(symbol)
                ?.TopOfBook(),
                redeemableCurrency : purchasedCurrency,
                redeemFromAddress : redeemFromWalletAddress,
                cancellationToken : cancellationToken)
                                   .ConfigureAwait(false);

            var soldCurrency = _account.Currencies.GetByName(swap.SoldCurrency);

            // select refund address for bitcoin based currency
            if (soldCurrency is BitcoinBasedConfig && swap.RefundAddress == null)
            {
                swap.RefundAddress = (await _account
                                      .GetCurrencyAccount <BitcoinBasedAccount>(soldCurrency.Name)
                                      .GetRefundAddressAsync(cancellationToken)
                                      .ConfigureAwait(false))
                                     ?.Address;
            }

            await UpdateSwapAsync(swap, SwapStateFlags.Empty, cancellationToken)
            .ConfigureAwait(false);

            // send "accept" to other side
            _swapClient.SwapAcceptAsync(swap);

            await GetCurrencySwap(swap.PurchasedCurrency)
            .StartPartyPaymentControlAsync(swap, cancellationToken)
            .ConfigureAwait(false);

            return(null); // no error
        }
Example #3
0
        public static Task <SwapParams> EstimateSwapParamsAsync(
            IFromSource from,
            decimal fromAmount,
            string redeemFromAddress,
            CurrencyConfig fromCurrency,
            CurrencyConfig toCurrency,
            IAccount account,
            IAtomexClient atomexClient,
            ISymbolsProvider symbolsProvider,
            ICurrencyQuotesProvider quotesProvider,
            CancellationToken cancellationToken = default)
        {
            return(Task.Run(async() =>
            {
                if (fromCurrency == null)
                {
                    return null;
                }

                if (toCurrency == null)
                {
                    return null;
                }

                var redeemFromWalletAddress = redeemFromAddress != null
                    ? await account
                                              .GetAddressAsync(toCurrency.Name, redeemFromAddress, cancellationToken)
                                              .ConfigureAwait(false)
                    : null;

                // estimate redeem fee
                var estimatedRedeemFee = await toCurrency
                                         .GetEstimatedRedeemFeeAsync(redeemFromWalletAddress, withRewardForRedeem: false)
                                         .ConfigureAwait(false);

                // estimate reward for redeem
                var rewardForRedeem = await RewardForRedeemHelper.EstimateAsync(
                    account: account,
                    quotesProvider: quotesProvider,
                    feeCurrencyQuotesProvider: symbol => atomexClient?.GetOrderBook(symbol)?.TopOfBook(),
                    redeemableCurrency: toCurrency,
                    redeemFromAddress: redeemFromWalletAddress,
                    cancellationToken: cancellationToken);

                // get amount reserved for active swaps
                var reservedForSwapsAmount = await GetAmountReservedForSwapsAsync(
                    from: from,
                    account: account,
                    currency: fromCurrency)
                                             .ConfigureAwait(false);

                // estimate maker network fee
                var estimatedMakerNetworkFee = await EstimateMakerNetworkFeeAsync(
                    fromCurrency: fromCurrency,
                    toCurrency: toCurrency,
                    account: account,
                    atomexClient: atomexClient,
                    symbolsProvider: symbolsProvider,
                    cancellationToken: cancellationToken)
                                               .ConfigureAwait(false);

                var fromCurrencyAccount = account
                                          .GetCurrencyAccount(fromCurrency.Name) as IEstimatable;

                // estimate payment fee
                var estimatedPaymentFee = await fromCurrencyAccount
                                          .EstimateSwapPaymentFeeAsync(
                    from: from,
                    amount: fromAmount,
                    cancellationToken: cancellationToken)
                                          .ConfigureAwait(false);

                // estimate max amount and max fee
                var maxAmountEstimation = await fromCurrencyAccount
                                          .EstimateMaxSwapPaymentAmountAsync(
                    from: from,
                    reserve: true,
                    cancellationToken: cancellationToken)
                                          .ConfigureAwait(false);

                if (maxAmountEstimation.Error != null)
                {
                    return new SwapParams
                    {
                        Amount = 0m,
                        PaymentFee = estimatedPaymentFee.Value,
                        RedeemFee = estimatedRedeemFee,
                        RewardForRedeem = rewardForRedeem,
                        MakerNetworkFee = estimatedMakerNetworkFee,
                        ReservedForSwaps = reservedForSwapsAmount,
                        Error = maxAmountEstimation.Error
                    };
                }

                var maxNetAmount = Math.Max(maxAmountEstimation.Amount - reservedForSwapsAmount - estimatedMakerNetworkFee, 0m);

                if (maxNetAmount == 0m) // insufficient funds
                {
                    return new SwapParams
                    {
                        Amount = 0m,
                        PaymentFee = maxAmountEstimation.Fee,
                        RedeemFee = estimatedRedeemFee,
                        RewardForRedeem = rewardForRedeem,
                        MakerNetworkFee = estimatedMakerNetworkFee,
                        ReservedForSwaps = reservedForSwapsAmount,
                        Error = new Error(
                            code: Errors.InsufficientFunds,
                            description: Resources.InsufficientFundsToCoverMakerNetworkFee,
                            details: string.Format(Resources.InsufficientFundsToCoverMakerNetworkFeeDetails,
                                                   estimatedMakerNetworkFee,                             // required
                                                   fromCurrency.Name,                                    // currency code
                                                   maxAmountEstimation.Amount - reservedForSwapsAmount)) // available
                    };
                }

                if (fromAmount > maxNetAmount) // amount greater than max net amount => use max amount params
                {
                    return new SwapParams
                    {
                        Amount = Math.Max(maxNetAmount, 0m),
                        PaymentFee = maxAmountEstimation.Fee,
                        RedeemFee = estimatedRedeemFee,
                        RewardForRedeem = rewardForRedeem,
                        MakerNetworkFee = estimatedMakerNetworkFee,
                        ReservedForSwaps = reservedForSwapsAmount,
                        Error = new Error(
                            code: Errors.InsufficientFunds,
                            description: Resources.InsufficientFunds,
                            details: string.Format(Resources.InsufficientFundsToSendAmountDetails,
                                                   fromAmount,        // required
                                                   fromCurrency.Name, // currency code
                                                   maxNetAmount))     // available
                    };
                }

                return new SwapParams
                {
                    Amount = fromAmount,
                    PaymentFee = estimatedPaymentFee.Value,
                    RedeemFee = estimatedRedeemFee,
                    RewardForRedeem = rewardForRedeem,
                    MakerNetworkFee = estimatedMakerNetworkFee,
                    ReservedForSwaps = reservedForSwapsAmount,
                    Error = null
                };
            }, cancellationToken));
        }