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); }
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 }
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)); }