private async Task <IBitcoinBasedTransaction> CreatePaymentTxAsync(
            Swap swap,
            string refundAddress,
            DateTimeOffset lockTime)
        {
            var currency = Currencies.Get <BitcoinBasedConfig>(swap.SoldCurrency);

            Log.Debug("Create swap payment {@currency} tx for swap {@swapId}",
                      currency.Name,
                      swap.Id);

            var amountInSatoshi = currency.CoinToSatoshi(
                AmountHelper.QtyToSellAmount(
                    swap.Side,
                    swap.Qty,
                    swap.Price,
                    currency.DigitsMultiplier));

            // maker network fee
            if (swap.MakerNetworkFee > 0)
            {
                var makerNetworkFeeInSatoshi = currency.CoinToSatoshi(swap.MakerNetworkFee);

                if (makerNetworkFeeInSatoshi < amountInSatoshi) // network fee size check
                {
                    amountInSatoshi += makerNetworkFeeInSatoshi;
                }
            }

            var tx = await _transactionFactory
                     .CreateSwapPaymentTxAsync(
                fromOutputs : swap.FromOutputs,
                amount : amountInSatoshi,
                refundAddress : refundAddress,
                toAddress : swap.PartyAddress,
                lockTime : lockTime,
                secretHash : swap.SecretHash,
                secretSize : DefaultSecretSize,
                currencyConfig : currency)
                     .ConfigureAwait(false);

            if (tx == null)
            {
                throw new InternalException(
                          code: Errors.TransactionCreationError,
                          description: $"Payment tx creation error for swap {swap.Id}");
            }

            tx.Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapPayment;

            Log.Debug("Payment tx successfully created for swap {@swapId}", swap.Id);

            return(tx);
        }
Exemplo n.º 2
0
        private decimal RequiredAmountInTokens(Swap swap, Erc20Config erc20)
        {
            var requiredAmountInERC20 = AmountHelper.QtyToSellAmount(swap.Side, swap.Qty, swap.Price, erc20.DigitsMultiplier);

            // maker network fee
            if (swap.MakerNetworkFee > 0 && swap.MakerNetworkFee < requiredAmountInERC20) // network fee size check
            {
                requiredAmountInERC20 += AmountHelper.RoundDown(swap.MakerNetworkFee, erc20.DigitsMultiplier);
            }

            return(requiredAmountInERC20);
        }
        private async Task <IBitcoinBasedTransaction> CreateRedeemTxAsync(
            Swap swap,
            IBitcoinBasedTransaction paymentTx,
            string redeemAddress,
            byte[] redeemScript,
            bool increaseSequenceNumber = false)
        {
            Log.Debug("Create redeem tx for swap {@swapId}", swap.Id);

            var currency = Currencies.Get <BitcoinBasedConfig>(Currency);

            var amountInSatoshi = currency.CoinToSatoshi(
                AmountHelper.QtyToSellAmount(
                    swap.Side.Opposite(),
                    swap.Qty,
                    swap.Price,
                    currency.DigitsMultiplier));

            var sequenceNumber = 0u;

            if (increaseSequenceNumber)
            {
                var previousSequenceNumber = (swap?.RedeemTx as IBitcoinBasedTransaction)?.GetSequenceNumber(0) ?? 0;

                sequenceNumber = previousSequenceNumber == 0
                    ? Sequence.SEQUENCE_FINAL - 1024
                    : (previousSequenceNumber == Sequence.SEQUENCE_FINAL
                        ? Sequence.SEQUENCE_FINAL
                        : previousSequenceNumber + 1);
            }

            var tx = await _transactionFactory
                     .CreateSwapRedeemTxAsync(
                paymentTx : paymentTx,
                amount : amountInSatoshi,
                redeemAddress : redeemAddress,
                redeemScript : redeemScript,
                currency : currency,
                sequenceNumber : sequenceNumber)
                     .ConfigureAwait(false);

            if (tx == null)
            {
                throw new InternalException(
                          code: Errors.TransactionCreationError,
                          description: $"Redeem tx creation error for swap {swap.Id}");
            }

            tx.Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapRedeem;

            return(tx);
        }
        private async Task <IBitcoinBasedTransaction> CreateRefundTxAsync(
            Swap swap,
            IBitcoinBasedTransaction paymentTx,
            string refundAddress,
            DateTimeOffset lockTime,
            byte[] redeemScript)
        {
            Log.Debug("Create refund tx for swap {@swapId}", swap.Id);

            var currency = Currencies.Get <BitcoinBasedConfig>(Currency);

            var amountInSatoshi = currency.CoinToSatoshi(
                AmountHelper.QtyToSellAmount(
                    swap.Side,
                    swap.Qty,
                    swap.Price,
                    currency.DigitsMultiplier));

            var tx = await _transactionFactory
                     .CreateSwapRefundTxAsync(
                paymentTx : paymentTx,
                amount : amountInSatoshi,
                refundAddress : refundAddress,
                redeemScript : redeemScript,
                lockTime : lockTime,
                currency : currency)
                     .ConfigureAwait(false);

            if (tx == null)
            {
                throw new InternalException(
                          code: Errors.TransactionCreationError,
                          description: $"Refund tx creation error for swap {swap.Id}");
            }

            tx.Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapRefund;

            Log.Debug("Refund tx successfully created for swap {@swapId}", swap.Id);

            return(tx);
        }
Exemplo n.º 5
0
        protected virtual async Task <EthereumTransaction> CreatePaymentTxAsync(
            Swap swap,
            int lockTimeInSeconds,
            CancellationToken cancellationToken = default)
        {
            var ethConfig = EthConfig;

            Log.Debug("Create payment transaction from address {@address} for swap {@swapId}", swap.FromAddress, swap.Id);

            var requiredAmountInEth = AmountHelper.QtyToSellAmount(swap.Side, swap.Qty, swap.Price, ethConfig.DigitsMultiplier);

            // maker network fee
            if (swap.MakerNetworkFee > 0 && swap.MakerNetworkFee < requiredAmountInEth) // network fee size check
            {
                requiredAmountInEth += AmountHelper.RoundDown(swap.MakerNetworkFee, ethConfig.DigitsMultiplier);
            }

            var refundTimeStampUtcInSec = new DateTimeOffset(swap.TimeStamp.ToUniversalTime().AddSeconds(lockTimeInSeconds)).ToUnixTimeSeconds();

            var rewardForRedeemInEth = swap.PartyRewardForRedeem;

            var walletAddress = await _account
                                .GetAddressAsync(swap.FromAddress, cancellationToken)
                                .ConfigureAwait(false);

            var gasPrice = await ethConfig
                           .GetGasPriceAsync(cancellationToken)
                           .ConfigureAwait(false);

            var balanceInEth = walletAddress.Balance;

            Log.Debug("Available balance: {@balance}", balanceInEth);

            var feeAmountInEth = rewardForRedeemInEth == 0
                ? ethConfig.InitiateFeeAmount(gasPrice)
                : ethConfig.InitiateWithRewardFeeAmount(gasPrice);

            if (balanceInEth < feeAmountInEth + requiredAmountInEth)
            {
                Log.Warning(
                    "Insufficient funds at {@address}. Balance: {@balance}, required: {@required}, " +
                    "feeAmount: {@feeAmount}, missing: {@result}.",
                    walletAddress.Address,
                    balanceInEth,
                    requiredAmountInEth,
                    feeAmountInEth,
                    balanceInEth - feeAmountInEth - requiredAmountInEth);

                return(null);
            }

            var nonceResult = await((IEthereumBlockchainApi)ethConfig.BlockchainApi)
                              .GetTransactionCountAsync(walletAddress.Address, pending: false, cancellationToken)
                              .ConfigureAwait(false);

            if (nonceResult.HasError)
            {
                Log.Error($"Getting nonce error: {nonceResult.Error.Description}");
                return(null);
            }

            TransactionInput txInput;

            var message = new InitiateFunctionMessage
            {
                HashedSecret    = swap.SecretHash,
                Participant     = swap.PartyAddress,
                RefundTimestamp = refundTimeStampUtcInSec,
                AmountToSend    = EthereumConfig.EthToWei(requiredAmountInEth),
                FromAddress     = walletAddress.Address,
                GasPrice        = EthereumConfig.GweiToWei(gasPrice),
                Nonce           = nonceResult.Value,
                RedeemFee       = EthereumConfig.EthToWei(rewardForRedeemInEth)
            };

            var initiateGasLimit = rewardForRedeemInEth == 0
                ? ethConfig.InitiateGasLimit
                : ethConfig.InitiateWithRewardGasLimit;

            message.Gas = await EstimateGasAsync(message, new BigInteger(initiateGasLimit))
                          .ConfigureAwait(false);

            txInput = message.CreateTransactionInput(ethConfig.SwapContractAddress);

            return(new EthereumTransaction(ethConfig.Name, txInput)
            {
                Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapPayment
            });
        }
        public static Task StartSwapSpentControlAsync(
            Swap swap,
            CurrencyConfig currency,
            DateTime refundTimeUtc,
            TimeSpan interval,
            Func <Swap, ITxPoint, CancellationToken, Task> completionHandler = null,
            Func <Swap, CancellationToken, Task> refundTimeReachedHandler    = null,
            CancellationToken cancellationToken = default)
        {
            return(Task.Run(async() =>
            {
                try
                {
                    var bitcoinBased = (BitcoinBasedConfig)currency;

                    var side = swap.Symbol
                               .OrderSideForBuyCurrency(swap.PurchasedCurrency);

                    var requiredAmount = AmountHelper.QtyToSellAmount(side, swap.Qty, swap.Price, bitcoinBased.DigitsMultiplier);
                    var requiredAmountInSatoshi = bitcoinBased.CoinToSatoshi(requiredAmount);

                    var lockTimeInSeconds = swap.IsInitiator
                        ? CurrencySwap.DefaultInitiatorLockTimeInSeconds
                        : CurrencySwap.DefaultAcceptorLockTimeInSeconds;

                    var refundTimeUtcInSec = new DateTimeOffset(swap.TimeStamp.ToUniversalTime().AddSeconds(lockTimeInSeconds))
                                             .ToUnixTimeSeconds();

                    var redeemScript = swap.RefundAddress == null && swap.RedeemScript != null
                        ? new Script(Convert.FromBase64String(swap.RedeemScript))
                        : BitcoinBasedSwapTemplate
                                       .GenerateHtlcP2PkhSwapPayment(
                        aliceRefundAddress: swap.RefundAddress,
                        bobAddress: swap.PartyAddress,
                        lockTimeStamp: refundTimeUtcInSec,
                        secretHash: swap.SecretHash,
                        secretSize: CurrencySwap.DefaultSecretSize,
                        expectedNetwork: bitcoinBased.Network);

                    var swapOutput = ((IBitcoinBasedTransaction)swap.PaymentTx)
                                     .Outputs
                                     .Cast <BitcoinBasedTxOutput>()
                                     .FirstOrDefault(o => o.IsPayToScriptHash(redeemScript) && o.Value >= requiredAmountInSatoshi);

                    if (swapOutput == null)
                    {
                        throw new InternalException(
                            code: Errors.SwapError,
                            description: "Payment tx have not swap output");
                    }

                    while (!cancellationToken.IsCancellationRequested)
                    {
                        Log.Debug("Output spent control for {@currency} swap {@swapId}", currency.Name, swap.Id);

                        var result = await currency
                                     .GetSpentPointAsync(
                            hash: swap.PaymentTxId,
                            index: swapOutput.Index,
                            cancellationToken: cancellationToken)
                                     .ConfigureAwait(false);

                        if (result != null && !result.HasError)
                        {
                            if (result.Value != null)
                            {
                                await completionHandler.Invoke(swap, result.Value, cancellationToken)
                                .ConfigureAwait(false);

                                break;
                            }
                        }

                        if (DateTime.UtcNow >= refundTimeUtc)
                        {
                            await refundTimeReachedHandler.Invoke(swap, cancellationToken)
                            .ConfigureAwait(false);

                            break;
                        }

                        await Task.Delay(interval, cancellationToken)
                        .ConfigureAwait(false);
                    }
                }
                catch (OperationCanceledException)
                {
                    Log.Debug("StartSwapSpentControlAsync canceled.");
                }
                catch (Exception e)
                {
                    Log.Error(e, "StartSwapSpentControlAsync error");
                }
            }, cancellationToken));
        }
        public static async Task <Result <bool> > IsInitiatedAsync(
            Swap swap,
            CurrencyConfig currency,
            TezosConfig tezos,
            long refundTimeStamp,
            CancellationToken cancellationToken = default)
        {
            try
            {
                Log.Debug("Tezos FA12: check initiated event");

                var fa12 = (Fa12Config)currency;

                var side = swap.Symbol
                           .OrderSideForBuyCurrency(swap.PurchasedCurrency)
                           .Opposite();

                var requiredAmountInTokenDigits = AmountHelper
                                                  .QtyToSellAmount(side, swap.Qty, swap.Price, fa12.DigitsMultiplier)
                                                  .ToTokenDigits(fa12.DigitsMultiplier);

                var requiredRewardForRedeemInTokenDigits = swap.IsAcceptor ? swap.RewardForRedeem.ToTokenDigits(fa12.DigitsMultiplier) : 0;

                var contractAddress                      = fa12.SwapContractAddress;
                var detectedAmountInTokenDigits          = 0m;
                var detectedRedeemFeeAmountInTokenDigits = 0m;

                long detectedRefundTimestamp = 0;

                var blockchainApi = (ITezosBlockchainApi)tezos.BlockchainApi;

                var txsResult = await blockchainApi
                                .TryGetTransactionsAsync(contractAddress, cancellationToken : cancellationToken)
                                .ConfigureAwait(false);

                if (txsResult == null)
                {
                    return(new Error(Errors.RequestError, $"Connection error while getting txs from contract {contractAddress}"));
                }

                if (txsResult.HasError)
                {
                    Log.Error("Error while get transactions from contract {@contract}. Code: {@code}. Description: {@desc}",
                              contractAddress,
                              txsResult.Error.Code,
                              txsResult.Error.Description);

                    return(txsResult.Error);
                }

                var txs = txsResult.Value
                          ?.Cast <TezosTransaction>()
                          .ToList();

                if (txs == null || !txs.Any())
                {
                    return(false);
                }

                foreach (var tx in txs)
                {
                    if (tx.IsConfirmed && tx.To == contractAddress)
                    {
                        var detectedPayment = false;

                        if (IsSwapInit(tx, swap.SecretHash.ToHexString(), fa12.TokenContractAddress, swap.ToAddress, refundTimeStamp))
                        {
                            // init payment to secret hash!
                            detectedPayment                      = true;
                            detectedAmountInTokenDigits         += GetAmount(tx);
                            detectedRedeemFeeAmountInTokenDigits = GetRedeemFee(tx);
                            detectedRefundTimestamp              = GetRefundTimestamp(tx);
                        }
                        ///not implemented
                        //else if (IsSwapAdd(tx, swap.SecretHash))
                        //{
                        //    detectedPayment = true;
                        //    detectedAmountInMtz += tx.Amount;
                        //}

                        if (detectedPayment && detectedAmountInTokenDigits >= requiredAmountInTokenDigits)
                        {
                            if (swap.IsAcceptor && detectedRedeemFeeAmountInTokenDigits != requiredRewardForRedeemInTokenDigits)
                            {
                                Log.Debug(
                                    "Invalid redeem fee in initiated event. Expected value is {@expected}, actual is {@actual}",
                                    requiredRewardForRedeemInTokenDigits,
                                    detectedRedeemFeeAmountInTokenDigits);

                                return(new Error(
                                           code: Errors.InvalidRewardForRedeem,
                                           description: $"Invalid redeem fee in initiated event. Expected value is {requiredRewardForRedeemInTokenDigits}, actual is {detectedRedeemFeeAmountInTokenDigits}"));
                            }

                            if (detectedRefundTimestamp != refundTimeStamp)
                            {
                                Log.Debug(
                                    "Invalid refund timestamp in initiated event. Expected value is {@expected}, actual is {@actual}",
                                    refundTimeStamp,
                                    detectedRefundTimestamp);

                                return(new Error(
                                           code: Errors.InvalidRewardForRedeem,
                                           description: $"Invalid refund timestamp in initiated event. Expected value is {refundTimeStamp}, actual is {detectedRefundTimestamp}"));
                            }

                            return(true);   // todo: check also token contract transfers
                        }
                    }

                    if (tx.BlockInfo?.BlockTime == null)
                    {
                        continue;
                    }

                    var blockTimeUtc = tx.BlockInfo.BlockTime.Value.ToUniversalTime();
                    var swapTimeUtc  = swap.TimeStamp.ToUniversalTime();

                    if (blockTimeUtc < swapTimeUtc)
                    {
                        return(false);
                    }
                }
            }
            catch (Exception e)
            {
                Log.Error(e, "Tezos token swap initiated control task error");

                return(new Error(Errors.InternalError, e.Message));
            }

            return(false);
        }
Exemplo n.º 8
0
        protected virtual async Task <TezosTransaction> CreatePaymentTxAsync(
            Swap swap,
            int lockTimeSeconds,
            CancellationToken cancellationToken = default)
        {
            var xtzConfig = XtzConfig;

            Log.Debug("Create {@currency} payment transaction from address {@address} for swap {@swapId}",
                      Currency,
                      swap.FromAddress,
                      swap.Id);

            var requiredAmountInMtz = AmountHelper
                                      .QtyToSellAmount(swap.Side, swap.Qty, swap.Price, xtzConfig.DigitsMultiplier)
                                      .ToMicroTez();

            // maker network fee
            if (swap.MakerNetworkFee > 0)
            {
                var makerNetworkFeeInMtz = swap.MakerNetworkFee.ToMicroTez();

                if (makerNetworkFeeInMtz < requiredAmountInMtz) // network fee size check
                {
                    requiredAmountInMtz += makerNetworkFeeInMtz;
                }
            }

            var refundTimeStampUtcInSec = new DateTimeOffset(swap.TimeStamp.ToUniversalTime().AddSeconds(lockTimeSeconds)).ToUnixTimeSeconds();

            var rewardForRedeemInMtz = swap.IsInitiator
                ? swap.PartyRewardForRedeem.ToMicroTez()
                : 0;

            var walletAddress = await _account
                                .GetAddressAsync(swap.FromAddress, cancellationToken)
                                .ConfigureAwait(false);

            Log.Debug("Available balance: {@balance}", walletAddress.Balance);

            var balanceInMtz = walletAddress.Balance.ToMicroTez();

            var isRevealed = await _account
                             .IsRevealedSourceAsync(walletAddress.Address, cancellationToken)
                             .ConfigureAwait(false);

            var feeAmountInMtz    = xtzConfig.InitiateFee + (isRevealed ? 0 : xtzConfig.RevealFee);
            var storageLimitInMtz = xtzConfig.InitiateStorageLimit * xtzConfig.StorageFeeMultiplier;

            if (balanceInMtz < feeAmountInMtz + storageLimitInMtz + requiredAmountInMtz)
            {
                Log.Error(
                    "Insufficient funds at {@address}. Balance: {@balance}, required: {@required}, " +
                    "feeAmount: {@feeAmount}, storageLimit: {@storageLimit}, missing: {@result}.",
                    walletAddress.Address,
                    balanceInMtz,
                    requiredAmountInMtz,
                    feeAmountInMtz,
                    storageLimitInMtz,
                    balanceInMtz - feeAmountInMtz - storageLimitInMtz - requiredAmountInMtz);

                return(null);
            }

            return(new TezosTransaction
            {
                Currency = xtzConfig.Name,
                CreationTime = DateTime.UtcNow,
                From = walletAddress.Address,
                To = xtzConfig.SwapContractAddress,
                Amount = Math.Round(requiredAmountInMtz, 0),
                Fee = feeAmountInMtz,
                GasLimit = xtzConfig.InitiateGasLimit,
                StorageLimit = xtzConfig.InitiateStorageLimit,
                Params = CreateInitParams(swap, refundTimeStampUtcInSec, (long) rewardForRedeemInMtz),
                Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapPayment,

                UseRun = true,
                UseSafeStorageLimit = true,
                UseOfflineCounter = true
            });
        }
Exemplo n.º 9
0
        public async Task <IBlockchainTransaction> CreateSwapPaymentTxTest()
        {
            var bitcoinApi = new Mock <IInOutBlockchainApi>();

            bitcoinApi.Setup(a => a.GetUnspentOutputsAsync(It.IsAny <string>(), null, new CancellationToken()))
            .Returns(Task.FromResult(GetTestOutputs(Common.Alice.PubKey, NBitcoin.Network.TestNet)));

            var litecoinApi = new Mock <IInOutBlockchainApi>();

            litecoinApi.Setup(a => a.GetUnspentOutputsAsync(It.IsAny <string>(), null, new CancellationToken()))
            .Returns(Task.FromResult(GetTestOutputs(Common.Bob.PubKey, AltNetworkSets.Litecoin.Testnet)));

            var tempCurrencies = new CurrenciesProvider(Common.CurrenciesConfigurationString)
                                 .GetCurrencies(Atomex.Core.Network.TestNet);

            var bitcoin = tempCurrencies.Get <BitcoinConfig>("BTC");

            bitcoin.BlockchainApi = bitcoinApi.Object;

            var litecoin = tempCurrencies.Get <LitecoinConfig>("LTC");

            litecoin.BlockchainApi = litecoinApi.Object;

            var aliceBtcAddress = Common.Alice.PubKey
                                  .GetAddress(ScriptPubKeyType.Legacy, bitcoin.Network)
                                  .ToString();

            var bobBtcAddress = Common.Bob.PubKey
                                .GetAddress(ScriptPubKeyType.Legacy, bitcoin.Network)
                                .ToString();

            const decimal lastPrice = 0.000001m;
            const decimal lastQty   = 10m;

            var swap = new Swap
            {
                Symbol = "LTC/BTC",
                Side   = Side.Buy,
                Price  = lastPrice,
                Qty    = lastQty
            };

            var amountInSatoshi = bitcoin.CoinToSatoshi(AmountHelper.QtyToSellAmount(swap.Side, swap.Qty, swap.Price, bitcoin.DigitsMultiplier));

            var outputs = (await new BlockchainTxOutputSource(bitcoin)
                           .GetAvailableOutputsAsync(new[] { aliceBtcAddress }))
                          .Cast <BitcoinBasedTxOutput>();

            var tx = await new BitcoinBasedSwapTransactionFactory()
                     .CreateSwapPaymentTxAsync(
                fromOutputs: outputs,
                amount: amountInSatoshi,
                refundAddress: aliceBtcAddress,
                toAddress: bobBtcAddress,
                lockTime: DateTimeOffset.UtcNow.AddHours(1),
                secretHash: Common.SecretHash,
                secretSize: Common.Secret.Length,
                currencyConfig: bitcoin)
                     .ConfigureAwait(false);

            Assert.NotNull(tx);
            //Assert.NotNull(redeemScript);

            return(tx);
        }
Exemplo n.º 10
0
        public static async Task <Result <bool> > IsInitiatedAsync(
            Swap swap,
            CurrencyConfig currency,
            long refundTimeStamp,
            CancellationToken cancellationToken = default)
        {
            try
            {
                Log.Debug("Ethereum: check initiated event");

                var ethereum = (Atomex.EthereumConfig)currency;

                var sideOpposite = swap.Symbol
                                   .OrderSideForBuyCurrency(swap.PurchasedCurrency)
                                   .Opposite();

                var requiredAmountInEth          = AmountHelper.QtyToSellAmount(sideOpposite, swap.Qty, swap.Price, ethereum.DigitsMultiplier);
                var requiredAmountInWei          = Atomex.EthereumConfig.EthToWei(requiredAmountInEth);
                var requiredRewardForRedeemInWei = Atomex.EthereumConfig.EthToWei(swap.RewardForRedeem);

                var api = new EtherScanApi(ethereum);

                var initiateEventsResult = await api
                                           .GetContractEventsAsync(
                    address : ethereum.SwapContractAddress,
                    fromBlock : ethereum.SwapContractBlockNumber,
                    toBlock : ulong.MaxValue,
                    topic0 : EventSignatureExtractor.GetSignatureHash <InitiatedEventDTO>(),
                    topic1 : "0x" + swap.SecretHash.ToHexString(),
                    topic2 : "0x000000000000000000000000" + swap.ToAddress.Substring(2),
                    cancellationToken : cancellationToken)
                                           .ConfigureAwait(false);

                if (initiateEventsResult == null)
                {
                    return(new Error(Errors.RequestError, $"Connection error while getting contract {ethereum.SwapContractAddress} initiate event"));
                }

                if (initiateEventsResult.HasError)
                {
                    return(initiateEventsResult.Error);
                }

                var events = initiateEventsResult.Value?.ToList();

                if (events == null || !events.Any())
                {
                    return(false);
                }

                var initiatedEvent = events.First().ParseInitiatedEvent();

                if (initiatedEvent.Value >= requiredAmountInWei - requiredRewardForRedeemInWei)
                {
                    if (initiatedEvent.RefundTimestamp != refundTimeStamp)
                    {
                        Log.Debug(
                            "Invalid refund time in initiated event. Expected value is {@expected}, actual is {@actual}",
                            refundTimeStamp,
                            (long)initiatedEvent.RefundTimestamp);

                        return(new Error(
                                   code: Errors.InvalidRefundLockTime,
                                   description: $"Invalid refund time in initiated event. Expected value is {refundTimeStamp}, actual is {(long)initiatedEvent.RefundTimestamp}"));
                    }

                    if (swap.IsAcceptor)
                    {
                        if (initiatedEvent.RedeemFee != requiredRewardForRedeemInWei)
                        {
                            Log.Debug(
                                "Invalid redeem fee in initiated event. Expected value is {@expected}, actual is {@actual}",
                                requiredRewardForRedeemInWei,
                                (long)initiatedEvent.RedeemFee);

                            return(new Error(
                                       code: Errors.InvalidRewardForRedeem,
                                       description: $"Invalid redeem fee in initiated event. Expected value is {requiredRewardForRedeemInWei}, actual is {(long)initiatedEvent.RedeemFee}"));
                        }
                    }

                    return(true);
                }

                Log.Debug(
                    "Eth value is not enough. Expected value is {@expected}. Actual value is {@actual}",
                    (decimal)(requiredAmountInWei - requiredRewardForRedeemInWei),
                    (decimal)initiatedEvent.Value);

                var addEventsResult = await api
                                      .GetContractEventsAsync(
                    address : ethereum.SwapContractAddress,
                    fromBlock : ethereum.SwapContractBlockNumber,
                    toBlock : ulong.MaxValue,
                    topic0 : EventSignatureExtractor.GetSignatureHash <AddedEventDTO>(),
                    topic1 : "0x" + swap.SecretHash.ToHexString(),
                    cancellationToken : cancellationToken)
                                      .ConfigureAwait(false);

                if (addEventsResult == null)
                {
                    return(new Error(Errors.RequestError, $"Connection error while getting contract {ethereum.SwapContractAddress} add event"));
                }

                if (addEventsResult.HasError)
                {
                    return(addEventsResult.Error);
                }

                events = addEventsResult.Value?.ToList();

                if (events == null || !events.Any())
                {
                    return(false);
                }

                foreach (var @event in events.Select(e => e.ParseAddedEvent()))
                {
                    if (@event.Value >= requiredAmountInWei - requiredRewardForRedeemInWei)
                    {
                        return(true);
                    }

                    Log.Debug(
                        "Eth value is not enough. Expected value is {@expected}. Actual value is {@actual}",
                        requiredAmountInWei - requiredRewardForRedeemInWei,
                        (long)@event.Value);
                }
            }
            catch (Exception e)
            {
                Log.Error(e, "Ethereum swap initiated control task error");

                return(new Error(Errors.InternalError, e.Message));
            }

            return(false);
        }
Exemplo n.º 11
0
        public static async Task <Result <bool> > IsInitiatedAsync(
            Swap swap,
            CurrencyConfig currency,
            long lockTimeInSec,
            CancellationToken cancellationToken = default)
        {
            try
            {
                Log.Debug("Ethereum ERC20: check initiated event");

                var erc20 = (Erc20Config)currency;

                var side = swap.Symbol
                           .OrderSideForBuyCurrency(swap.PurchasedCurrency)
                           .Opposite();

                var refundTimeStamp                   = new DateTimeOffset(swap.TimeStamp.ToUniversalTime().AddSeconds(lockTimeInSec)).ToUnixTimeSeconds();
                var requiredAmountInERC20             = AmountHelper.QtyToSellAmount(side, swap.Qty, swap.Price, erc20.DigitsMultiplier);
                var requiredAmountInDecimals          = erc20.TokensToTokenDigits(requiredAmountInERC20);
                var receivedAmountInDecimals          = new BigInteger(0);
                var requiredRewardForRedeemInDecimals = swap.IsAcceptor
                    ? erc20.TokensToTokenDigits(swap.RewardForRedeem)
                    : 0;

                var api = new EtherScanApi(erc20);

                var initiateEventsResult = await api
                                           .GetContractEventsAsync(
                    address : erc20.SwapContractAddress,
                    fromBlock : erc20.SwapContractBlockNumber,
                    toBlock : ulong.MaxValue,
                    topic0 : EventSignatureExtractor.GetSignatureHash <ERC20InitiatedEventDTO>(),
                    topic1 : "0x" + swap.SecretHash.ToHexString(),
                    topic2 : "0x000000000000000000000000" + erc20.ERC20ContractAddress.Substring(2),     //??
                    topic3 : "0x000000000000000000000000" + swap.ToAddress.Substring(2),
                    cancellationToken : cancellationToken)
                                           .ConfigureAwait(false);

                if (initiateEventsResult == null)
                {
                    return(new Error(Errors.RequestError, $"Connection error while trying to get contract {erc20.SwapContractAddress} initiate event"));
                }

                if (initiateEventsResult.HasError)
                {
                    return(initiateEventsResult.Error);
                }

                var events = initiateEventsResult.Value?.ToList();

                if (events == null || !events.Any())
                {
                    return(false);
                }

                var contractInitEvent = events.Last();

                var initiatedEvent = contractInitEvent.ParseERC20InitiatedEvent();

                if (initiatedEvent.RefundTimestamp != refundTimeStamp)
                {
                    Log.Debug(
                        "Invalid refund time in initiated event. Expected value is {@expected}, actual is {@actual}",
                        refundTimeStamp,
                        (long)initiatedEvent.RefundTimestamp);

                    return(new Error(
                               code: Errors.InvalidRefundLockTime,
                               description: $"Invalid refund time in initiated event. Expected value is {refundTimeStamp}, actual is {(long)initiatedEvent.RefundTimestamp}"));
                }

                if (initiatedEvent.Countdown != lockTimeInSec)  //todo: use it
                {
                    Log.Debug(
                        "Invalid countdown in initiated event. Expected value is {@expected}, actual is {@actual}",
                        lockTimeInSec,
                        (long)initiatedEvent.Countdown);

                    return(new Error(
                               code: Errors.InvalidRewardForRedeem,
                               description: $"Invalid countdown in initiated event. Expected value is {lockTimeInSec}, actual is {(long)initiatedEvent.Countdown}"));
                }

                if (initiatedEvent.RedeemFee != requiredRewardForRedeemInDecimals)
                {
                    Log.Debug(
                        "Invalid redeem fee in initiated event. Expected value is {@expected}, actual is {@actual}",
                        requiredRewardForRedeemInDecimals,
                        (long)initiatedEvent.RedeemFee);

                    return(new Error(
                               code: Errors.InvalidRewardForRedeem,
                               description: $"Invalid redeem fee in initiated event. Expected value is {requiredRewardForRedeemInDecimals}, actual is {(long)initiatedEvent.RedeemFee}"));
                }

                if (!initiatedEvent.Active)
                {
                    Log.Debug(
                        "Invalid active value in initiated event. Expected value is {@expected}, actual is {@actual}",
                        true,
                        initiatedEvent.Active);

                    return(new Error(
                               code: Errors.InvalidRewardForRedeem,
                               description: $"Invalid active value in initiated event. Expected value is {true}, actual is {initiatedEvent.Active}"));
                }

                var erc20TransferValues = await GetTransferValuesAsync(
                    currency : currency,
                    from : initiatedEvent.Initiator.Substring(2),
                    to : erc20.SwapContractAddress.Substring(2),
                    blockNumber : contractInitEvent.HexBlockNumber,
                    cancellationToken : cancellationToken)
                                          .ConfigureAwait(false);

                if (!erc20TransferValues.Contains(initiatedEvent.Value + initiatedEvent.RedeemFee))
                {
                    var actualTransferValue = string.Join(", ", erc20TransferValues.Select(v => v.ToString()));

                    Log.Debug(
                        "Invalid transfer value in erc20 initiated event. Expected value is {@expected}, actual is {@actual}",
                        initiatedEvent.Value.ToString(),
                        actualTransferValue);

                    return(new Error(
                               code: Errors.InvalidSwapPaymentTx,
                               description: $"Invalid transfer value in erc20 initiated event. Expected value is {initiatedEvent.Value}, actual is {actualTransferValue}"));
                }

                receivedAmountInDecimals = initiatedEvent.Value;

                if (receivedAmountInDecimals >= requiredAmountInDecimals - requiredRewardForRedeemInDecimals)
                {
                    return(true);
                }

                Log.Debug(
                    "Ethereum ERC20 value is not enough. Expected value is {@expected}. Actual value is {@actual}",
                    (decimal)(requiredAmountInDecimals - requiredRewardForRedeemInDecimals),
                    (decimal)initiatedEvent.Value);

                var addEventsResult = await api
                                      .GetContractEventsAsync(
                    address : erc20.SwapContractAddress,
                    fromBlock : erc20.SwapContractBlockNumber,
                    toBlock : ulong.MaxValue,
                    topic0 : EventSignatureExtractor.GetSignatureHash <ERC20AddedEventDTO>(),
                    topic1 : "0x" + swap.SecretHash.ToHexString(),
                    cancellationToken : cancellationToken)
                                      .ConfigureAwait(false);

                if (addEventsResult == null)
                {
                    return(new Error(Errors.RequestError, $"Connection error while trying to get contract {erc20.SwapContractAddress} add event"));
                }

                if (addEventsResult.HasError)
                {
                    return(addEventsResult.Error);
                }

                events = addEventsResult.Value?.ToList();

                if (events == null || !events.Any())
                {
                    return(false);
                }

                foreach (var @event in events.Select(e => e.ParseERC20AddedEvent()))
                {
                    erc20TransferValues = await GetTransferValuesAsync(
                        currency : currency,
                        from : @event.Initiator.Substring(2),
                        to : erc20.SwapContractAddress.Substring(2),
                        blockNumber : contractInitEvent.HexBlockNumber,
                        cancellationToken : cancellationToken)
                                          .ConfigureAwait(false);

                    if (!erc20TransferValues.Contains(@event.Value - receivedAmountInDecimals))
                    {
                        var actualTransferValue = string.Join(", ", erc20TransferValues.Select(v => v.ToString()));

                        Log.Debug(
                            "Invalid transfer value in added event. Expected value is {@expected}, actual is {@actual}",
                            (@event.Value - receivedAmountInDecimals).ToString(),
                            actualTransferValue);

                        return(new Error(
                                   code: Errors.InvalidSwapPaymentTx,
                                   description: $"Invalid transfer value in initiated event. Expected value is {@event.Value - receivedAmountInDecimals}, actual is {actualTransferValue}"));
                    }

                    receivedAmountInDecimals = @event.Value;

                    if (receivedAmountInDecimals >= requiredAmountInDecimals - requiredRewardForRedeemInDecimals)
                    {
                        return(true);
                    }

                    Log.Debug(
                        "Ethereum ERC20 value is not enough. Expected value is {@expected}. Actual value is {@actual}",
                        requiredAmountInDecimals - requiredRewardForRedeemInDecimals,
                        (long)@event.Value);
                }
            }
            catch (Exception e)
            {
                Log.Error(e, "Ethereum ERC20 swap initiated control task error");

                return(new Error(Errors.InternalError, e.Message));
            }

            return(false);
        }
Exemplo n.º 12
0
        public static async Task <Result <IBlockchainTransaction> > TryToFindPaymentAsync(
            Swap swap,
            CurrencyConfig currency,
            Side side,
            string toAddress,
            string refundAddress,
            long refundTimeStamp,
            string redeemScriptBase64           = null,
            CancellationToken cancellationToken = default)
        {
            try
            {
                Log.Debug("BitcoinBased: try to find payment tx");

                var bitcoinBased = (BitcoinBasedConfig)currency;

                var requiredAmount          = AmountHelper.QtyToSellAmount(side, swap.Qty, swap.Price, bitcoinBased.DigitsMultiplier);
                var requiredAmountInSatoshi = bitcoinBased.CoinToSatoshi(requiredAmount);

                var redeemScript = refundAddress == null && redeemScriptBase64 != null
                    ? new Script(Convert.FromBase64String(redeemScriptBase64))
                    : BitcoinBasedSwapTemplate
                                   .GenerateHtlcP2PkhSwapPayment(
                    aliceRefundAddress: refundAddress,
                    bobAddress: toAddress,
                    lockTimeStamp: refundTimeStamp,
                    secretHash: swap.SecretHash,
                    secretSize: CurrencySwap.DefaultSecretSize,
                    expectedNetwork: bitcoinBased.Network);

                var redeemScriptAddress = redeemScript
                                          .PaymentScript
                                          .GetDestinationAddress(bitcoinBased.Network)
                                          .ToString();

                var api = bitcoinBased.BlockchainApi as BitcoinBasedBlockchainApi;

                var outputsResult = await api
                                    .GetOutputsAsync(redeemScriptAddress, null, cancellationToken)
                                    .ConfigureAwait(false);

                if (outputsResult == null)
                {
                    return(new Error(Errors.RequestError, $"Connection error while getting outputs for {redeemScriptAddress} address"));
                }

                if (outputsResult.HasError)
                {
                    return(outputsResult.Error);
                }

                foreach (var output in outputsResult.Value)
                {
                    var o = output as BitcoinBasedTxOutput;

                    var outputScriptHex = o.Coin.TxOut.ScriptPubKey.ToHex();

                    if (redeemScript.PaymentScript.ToHex() != outputScriptHex)
                    {
                        continue;
                    }

                    if (o.Value < requiredAmountInSatoshi)
                    {
                        continue;
                    }

                    var txResult = await api
                                   .GetTransactionAsync(o.TxId, cancellationToken)
                                   .ConfigureAwait(false);

                    if (txResult == null)
                    {
                        return(new Error(Errors.RequestError, $"Connection error while getting tx {o.TxId}"));
                    }

                    if (txResult.HasError)
                    {
                        return(txResult.Error);
                    }

                    if (txResult.Value == null)
                    {
                        continue;
                    }

                    return(txResult.Value as BitcoinBasedTransaction);
                }

                return(new Result <IBlockchainTransaction>((IBitcoinBasedTransaction)null));
            }
            catch (Exception e)
            {
                Log.Error(e, "BitcoinBased swap initiated control task error");

                return(new Error(Errors.InternalError, e.Message));
            }
        }