Exemplo n.º 1
0
        protected override async Task <IEnumerable <TezosTransaction> > CreatePaymentTxsAsync(
            Swap swap,
            int lockTimeSeconds,
            CancellationToken cancellationToken = default)
        {
            Log.Debug("Create payment transactions for swap {@swapId}", swap.Id);

            var fa12    = Fa12;
            var fa12Api = fa12.BlockchainApi as ITokenBlockchainApi;

            var requiredAmountInTokens = AmountHelper.QtyToAmount(swap.Side, swap.Qty, swap.Price, fa12.DigitsMultiplier);

            var refundTimeStampUtcInSec = new DateTimeOffset(swap.TimeStamp.ToUniversalTime().AddSeconds(lockTimeSeconds)).ToUnixTimeSeconds();
            var isInitTx = true;
            var rewardForRedeemInTokenDigits = swap.IsInitiator
                ? swap.PartyRewardForRedeem.ToTokenDigits(fa12.DigitsMultiplier)
                : 0;

            var unspentAddresses = (await Fa12Account
                                    .GetUnspentAddressesAsync(cancellationToken)
                                    .ConfigureAwait(false))
                                   .ToList()
                                   .SortList(new AvailableBalanceAscending());

            var transactions = new List <TezosTransaction>();

            foreach (var walletAddress in unspentAddresses)
            {
                Log.Debug("Create swap payment tx from address {@address} for swap {@swapId}",
                          walletAddress.Address,
                          swap.Id);

                var balanceInTz = (await TezosAccount
                                   .GetAddressBalanceAsync(
                                       address: walletAddress.Address,
                                       cancellationToken: cancellationToken)
                                   .ConfigureAwait(false))
                                  .Available;

                var balanceInTokens = (await Fa12Account
                                       .GetAddressBalanceAsync(
                                           address: walletAddress.Address,
                                           cancellationToken: cancellationToken)
                                       .ConfigureAwait(false))
                                      .Available;

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

                var balanceInMtz         = balanceInTz.ToMicroTez();
                var balanceInTokenDigits = balanceInTokens.ToTokenDigits(fa12.DigitsMultiplier);

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

                var feeAmountInMtz = fa12.ApproveFee * 2 +
                                     (isInitTx ? fa12.InitiateFee : fa12.AddFee) +
                                     (isRevealed ? 0 : fa12.RevealFee);

                var storageLimitInMtz = (fa12.ApproveStorageLimit * 2 +
                                         (isInitTx ? fa12.InitiateStorageLimit : fa12.AddStorageLimit)) *
                                        fa12.StorageFeeMultiplier;

                if (balanceInMtz - feeAmountInMtz - storageLimitInMtz - Xtz.MicroTezReserve <= 0)
                {
                    Log.Warning(
                        "Insufficient funds at {@address}. Balance: {@balance}, " +
                        "feeAmount: {@feeAmount}, storageLimit: {@storageLimit}.",
                        walletAddress.Address,
                        balanceInMtz,
                        feeAmountInMtz,
                        storageLimitInMtz);

                    continue;
                }

                var amountInTokens = requiredAmountInTokens > 0
                    ? AmountHelper.DustProofMin(balanceInTokens, requiredAmountInTokens, fa12.DigitsMultiplier, fa12.DustDigitsMultiplier)
                    : 0;

                if (amountInTokens == 0)
                {
                    break;
                }

                requiredAmountInTokens -= amountInTokens;

                using var callingAddressPublicKey = new SecureBytes((await Fa12Account.GetAddressAsync(walletAddress.Address)
                                                                     .ConfigureAwait(false))
                                                                    .PublicKeyBytes());

                var allowanceResult = await fa12Api
                                      .TryGetTokenAllowanceAsync(
                    holderAddress : walletAddress.Address,
                    spenderAddress : fa12.SwapContractAddress,
                    callingAddress : walletAddress.Address,
                    securePublicKey : callingAddressPublicKey,
                    cancellationToken : cancellationToken)
                                      .ConfigureAwait(false);

                if (allowanceResult.HasError)
                {
                    Log.Error("Error while getting token allowance for {@address} with code {@code} and description {@description}",
                              walletAddress.Address,
                              allowanceResult.Error.Code,
                              allowanceResult.Error.Description);

                    continue; // todo: maybe add approve 0
                }

                if (allowanceResult.Value > 0)
                {
                    transactions.Add(new TezosTransaction
                    {
                        Currency      = fa12,
                        CreationTime  = DateTime.UtcNow,
                        From          = walletAddress.Address,
                        To            = fa12.TokenContractAddress,
                        Fee           = fa12.ApproveFee,
                        GasLimit      = fa12.ApproveGasLimit,
                        StorageLimit  = fa12.ApproveStorageLimit,
                        Params        = ApproveParams(fa12.SwapContractAddress, 0),
                        UseDefaultFee = true,
                        Type          = BlockchainTransactionType.TokenApprove
                    });
                }

                transactions.Add(new TezosTransaction
                {
                    Currency      = fa12,
                    CreationTime  = DateTime.UtcNow,
                    From          = walletAddress.Address,
                    To            = fa12.TokenContractAddress,
                    Fee           = fa12.ApproveFee,
                    GasLimit      = fa12.ApproveGasLimit,
                    StorageLimit  = fa12.ApproveStorageLimit,
                    Params        = ApproveParams(fa12.SwapContractAddress, amountInTokens.ToTokenDigits(fa12.DigitsMultiplier)),
                    UseDefaultFee = true,
                    Type          = BlockchainTransactionType.TokenApprove
                });

                if (isInitTx)
                {
                    transactions.Add(new TezosTransaction
                    {
                        Currency      = fa12,
                        CreationTime  = DateTime.UtcNow,
                        From          = walletAddress.Address,
                        To            = fa12.SwapContractAddress,
                        Fee           = feeAmountInMtz,
                        GasLimit      = fa12.InitiateGasLimit,
                        StorageLimit  = fa12.InitiateStorageLimit,
                        Params        = InitParams(swap, fa12.TokenContractAddress, amountInTokens.ToTokenDigits(fa12.DigitsMultiplier), refundTimeStampUtcInSec, (long)rewardForRedeemInTokenDigits),
                        UseDefaultFee = true,
                        Type          = BlockchainTransactionType.Output | BlockchainTransactionType.SwapPayment
                    });
                }
                //else
                //{
                //    transactions.Add(new TezosTransaction
                //    {
                //        Currency = Xtz,
                //        CreationTime = DateTime.UtcNow,
                //        From = walletAddress.Address,
                //        To = Xtz.SwapContractAddress,
                //        Fee = feeAmountInMtz,
                //        GasLimit = Xtz.AddGasLimit,
                //        StorageLimit = Xtz.AddStorageLimit,
                //        UseDefaultFee = true,
                //        Params = AddParams(swap),
                //        Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapPayment
                //    });
                //}

                if (isInitTx)
                {
                    isInitTx = false;
                }

                if (requiredAmountInTokens <= 0)
                {
                    break;
                }
            }

            if (requiredAmountInTokens > 0)
            {
                Log.Warning("Insufficient funds (left {@requredAmount}).", requiredAmountInTokens);
                return(Enumerable.Empty <TezosTransaction>());
            }

            return(transactions);
        }
Exemplo n.º 2
0
        private async Task <IList <EthereumTransaction> > CreateApproveTxsAsync(
            Swap swap,
            EthereumTransaction paymentTx,
            CancellationToken cancellationToken = default)
        {
            var erc20 = Erc20Config;

            Log.Debug("Create approve transactions for swap {@swapId}", swap.Id);

            var transactions = new List <EthereumTransaction>();

            var walletAddress = await Erc20Account
                                .GetAddressAsync(paymentTx.From, cancellationToken)
                                .ConfigureAwait(false);

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

            var allowanceMessage = new ERC20AllowanceFunctionMessage
            {
                Owner       = walletAddress.Address,
                Spender     = erc20.SwapContractAddress,
                FromAddress = walletAddress.Address
            };

            var allowance = await((IEthereumBlockchainApi)erc20.BlockchainApi)
                            .GetERC20AllowanceAsync(
                erc20: erc20,
                tokenAddress: erc20.ERC20ContractAddress,
                allowanceMessage: allowanceMessage,
                cancellationToken: cancellationToken)
                            .ConfigureAwait(false);

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

            if (nonceResult.HasError)
            {
                Log.Error($"Getting nonce error: {nonceResult.Error.Description}");
                return(new List <EthereumTransaction>());
            }

            if (allowance.Value > 0)
            {
                var tx = await CreateApproveTx(
                    walletAddress : walletAddress.Address,
                    nonce : nonceResult.Value,
                    value : 0,
                    gasPrice : gasPrice)
                         .ConfigureAwait(false);

                transactions.Add(tx);
            }

            var requiredAmountInErc20 = RequiredAmountInTokens(swap, erc20);

            var amountInErc20 = AmountHelper.DustProofMin(
                walletAddress.Balance,
                requiredAmountInErc20,
                erc20.DigitsMultiplier,
                erc20.DustDigitsMultiplier);

            var approveTx = await CreateApproveTx(
                walletAddress : walletAddress.Address,
                nonce : nonceResult.Value,
                value : erc20.TokensToTokenDigits(amountInErc20),
                gasPrice : gasPrice)
                            .ConfigureAwait(false);

            transactions.Add(approveTx);

            return(transactions);
        }
Exemplo n.º 3
0
        protected override async Task <IEnumerable <EthereumTransaction> > CreatePaymentTxsAsync(
            Swap swap,
            int lockTimeInSeconds,
            CancellationToken cancellationToken = default)
        {
            var erc20 = Erc20;

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

            var requiredAmountInERC20   = AmountHelper.QtyToAmount(swap.Side, swap.Qty, swap.Price, erc20.DigitsMultiplier);
            var refundTimeStampUtcInSec = new DateTimeOffset(swap.TimeStamp.ToUniversalTime().AddSeconds(lockTimeInSeconds)).ToUnixTimeSeconds();
            var isInitTx = true;
            var rewardForRedeemInERC20 = swap.PartyRewardForRedeem;

            var unspentAddresses = (await Erc20Account
                                    .GetUnspentAddressesAsync(cancellationToken)
                                    .ConfigureAwait(false))
                                   .ToList()
                                   .SortList((a, b) => a.AvailableBalance().CompareTo(b.AvailableBalance()));

            var transactions = new List <EthereumTransaction>();

            foreach (var walletAddress in unspentAddresses)
            {
                Log.Debug("Create swap payment tx from address {@address} for swap {@swapId}", walletAddress.Address, swap.Id);

                var balanceInEth = (await EthereumAccount
                                    .GetAddressBalanceAsync(
                                        address: walletAddress.Address,
                                        cancellationToken: cancellationToken)
                                    .ConfigureAwait(false))
                                   .Available;

                var balanceInERC20 = (await Erc20Account
                                      .GetAddressBalanceAsync(
                                          address: walletAddress.Address,
                                          cancellationToken: cancellationToken)
                                      .ConfigureAwait(false))
                                     .Available;

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

                var feeAmountInEth = (isInitTx
                    ? rewardForRedeemInERC20 == 0
                        ? erc20.InitiateFeeAmount
                        : erc20.InitiateWithRewardFeeAmount
                    : erc20.AddFeeAmount) + erc20.ApproveFeeAmount;

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

                    continue;
                }

                var amountInERC20 = requiredAmountInERC20 > 0
                    ? AmountHelper.DustProofMin(balanceInERC20, requiredAmountInERC20, erc20.DigitsMultiplier, erc20.DustDigitsMultiplier)
                    : 0;

                requiredAmountInERC20 -= amountInERC20;

                var nonceResult = await EthereumNonceManager.Instance
                                  .GetNonceAsync(erc20, walletAddress.Address)
                                  .ConfigureAwait(false);

                if (nonceResult.HasError)
                {
                    Log.Error("Nonce getting error with code {@code} and description {@description}",
                              nonceResult.Error.Code,
                              nonceResult.Error.Description);

                    return(null);
                }

                var nonce = nonceResult.Value;

                var allowanceMessage = new ERC20AllowanceFunctionMessage()
                {
                    Owner       = walletAddress.Address,
                    Spender     = erc20.SwapContractAddress,
                    FromAddress = walletAddress.Address
                };

                var allowance = await((IEthereumBlockchainApi)erc20.BlockchainApi)
                                .GetERC20AllowanceAsync(
                    erc20: erc20,
                    tokenAddress: erc20.ERC20ContractAddress,
                    allowanceMessage: allowanceMessage,
                    cancellationToken: cancellationToken)
                                .ConfigureAwait(false);

                if (allowance.Value > 0)
                {
                    transactions.Add(await CreateApproveTx(walletAddress, nonceResult.Value, 0)
                                     .ConfigureAwait(false));
                    nonce += 1;
                }
                else
                {
                    transactions.Add(new EthereumTransaction());
                }

                transactions.Add(await CreateApproveTx(walletAddress, nonce, erc20.TokensToTokenDigits(amountInERC20))
                                 .ConfigureAwait(false));
                nonce += 1;

                TransactionInput txInput;

                //actual transfer
                if (isInitTx)
                {
                    var initMessage = new ERC20InitiateFunctionMessage
                    {
                        HashedSecret    = swap.SecretHash,
                        ERC20Contract   = erc20.ERC20ContractAddress,
                        Participant     = swap.PartyAddress,
                        RefundTimestamp = refundTimeStampUtcInSec,
                        Countdown       = lockTimeInSeconds,
                        Value           = erc20.TokensToTokenDigits(amountInERC20),
                        RedeemFee       = erc20.TokensToTokenDigits(rewardForRedeemInERC20),
                        Active          = true,
                        FromAddress     = walletAddress.Address,
                        GasPrice        = Atomex.Ethereum.GweiToWei(erc20.GasPriceInGwei),
                        Nonce           = nonce
                    };

                    var initiateGasLimit = rewardForRedeemInERC20 == 0
                        ? erc20.InitiateGasLimit
                        : erc20.InitiateWithRewardGasLimit;

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

                    txInput = initMessage.CreateTransactionInput(erc20.SwapContractAddress);
                }
                else
                {
                    var addMessage = new ERC20AddFunctionMessage
                    {
                        HashedSecret = swap.SecretHash,
                        Value        = erc20.TokensToTokenDigits(amountInERC20),
                        FromAddress  = walletAddress.Address,
                        GasPrice     = Atomex.Ethereum.GweiToWei(erc20.GasPriceInGwei),
                        Nonce        = nonce
                    };

                    addMessage.Gas = await EstimateGasAsync(addMessage, new BigInteger(erc20.AddGasLimit))
                                     .ConfigureAwait(false);

                    txInput = addMessage.CreateTransactionInput(erc20.SwapContractAddress);
                }

                transactions.Add(new EthereumTransaction(erc20, txInput)
                {
                    Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapPayment
                });

                if (isInitTx)
                {
                    isInitTx = false;
                }

                if (requiredAmountInERC20 <= 0)
                {
                    break;
                }
            }

            if (requiredAmountInERC20 > 0)
            {
                Log.Warning("Insufficient ERC20 or Eth funds (left {@requiredAmount}).", requiredAmountInERC20);
                return(Enumerable.Empty <EthereumTransaction>());
            }

            return(transactions);
        }
Exemplo n.º 4
0
        protected async Task <EthereumTransaction> CreatePaymentTxAsync(
            Swap swap,
            int lockTimeInSeconds,
            CancellationToken cancellationToken = default)
        {
            var erc20Config = Erc20Config;

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

            var requiredAmountInERC20 = RequiredAmountInTokens(swap, erc20Config);

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

            var rewardForRedeemInERC20 = swap.PartyRewardForRedeem;

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

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

            var balanceInEth = (await EthereumAccount
                                .GetAddressBalanceAsync(
                                    address: walletAddress.Address,
                                    cancellationToken: cancellationToken)
                                .ConfigureAwait(false))
                               .Available;

            var feeAmountInEth = rewardForRedeemInERC20 == 0
                ? erc20Config.InitiateFeeAmount(gasPrice)
                : erc20Config.InitiateWithRewardFeeAmount(gasPrice);

            if (balanceInEth < feeAmountInEth)
            {
                Log.Error(
                    "Insufficient funds at {@address} for fee. Balance: {@balance}, feeAmount: {@feeAmount}, result: {@result}.",
                    walletAddress.Address,
                    balanceInEth,
                    feeAmountInEth,
                    balanceInEth - feeAmountInEth);

                return(null);
            }

            var balanceInErc20 = walletAddress.Balance;

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

            if (balanceInErc20 < requiredAmountInERC20)
            {
                Log.Error(
                    "Insufficient funds at {@address}. Balance: {@balance}, required: {@result}, missing: {@missing}.",
                    walletAddress.Address,
                    balanceInErc20,
                    requiredAmountInERC20,
                    balanceInErc20 - requiredAmountInERC20);

                return(null);
            }

            var amountInErc20 = AmountHelper.DustProofMin(
                balanceInErc20,
                requiredAmountInERC20,
                erc20Config.DigitsMultiplier,
                erc20Config.DustDigitsMultiplier);

            var nonceResult = await((IEthereumBlockchainApi)erc20Config.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 initMessage = new ERC20InitiateFunctionMessage
            {
                HashedSecret    = swap.SecretHash,
                ERC20Contract   = erc20Config.ERC20ContractAddress,
                Participant     = swap.PartyAddress,
                RefundTimestamp = refundTimeStampUtcInSec,
                Countdown       = lockTimeInSeconds,
                Value           = erc20Config.TokensToTokenDigits(amountInErc20),
                RedeemFee       = erc20Config.TokensToTokenDigits(rewardForRedeemInERC20),
                Active          = true,
                FromAddress     = walletAddress.Address,
                GasPrice        = EthereumConfig.GweiToWei(gasPrice),
                Nonce           = nonceResult.Value
            };

            var initiateGasLimit = rewardForRedeemInERC20 == 0
                ? erc20Config.InitiateGasLimit
                : erc20Config.InitiateWithRewardGasLimit;

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

            txInput = initMessage.CreateTransactionInput(erc20Config.SwapContractAddress);

            return(new EthereumTransaction(erc20Config.Name, txInput)
            {
                Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapPayment
            });
        }