Ejemplo n.º 1
0
        public TezosTransactionViewModel(TezosTransaction tx)
            : base(tx, GetAmount(tx), GetFee(tx))
        {
            From       = tx.From;
            To         = tx.To;
            GasLimit   = tx.GasLimit;
            Fee        = Tezos.MtzToTz(tx.Fee);
            IsInternal = tx.IsInternal;

            if (!string.IsNullOrEmpty(tx.Alias))
            {
                Alias = tx.Alias;
            }
            else
            {
                if (Amount <= 0)
                {
                    Alias = tx.To;
                }

                if (Amount > 0)
                {
                    Alias = tx.From;
                }
            }
        }
Ejemplo n.º 2
0
        public static bool IsSwapRedeem(TezosTransaction tx, byte[] secretHash)
        {
            try
            {
                if (tx.Params == null)
                {
                    return(false);
                }

                var entrypoint = tx.Params?["entrypoint"]?.ToString();

                var paramSecretHex = entrypoint switch
                {
                    "default" => GetSecret(tx.Params?["value"]?["args"]?[0]?["args"]?[0]),
                    "withdraw" => GetSecret(tx.Params?["value"]?["args"]?[0]),
                    "redeem" => GetSecret(tx.Params?["value"]),
                    _ => ""
                };

                var paramSecretBytes     = Hex.FromString(paramSecretHex);
                var paramSecretHashBytes = CurrencySwap.CreateSwapSecretHash(paramSecretBytes);

                return(paramSecretHashBytes.SequenceEqual(secretHash));
            }
            catch (Exception)
            {
                return(false);
            }
        }
        private static decimal GetAmount(TezosTransaction tx)
        {
            var result = 0m;

            if (tx.Type.HasFlag(BlockchainTransactionType.SwapRedeem) ||
                tx.Type.HasFlag(BlockchainTransactionType.SwapRefund))
            {
                result += tx.Amount.FromTokenDigits(tx.Currency.DigitsMultiplier);
            }
            else
            {
                if (tx.Type.HasFlag(BlockchainTransactionType.Input))
                {
                    result += tx.Amount.FromTokenDigits(tx.Currency.DigitsMultiplier);
                }
                if (tx.Type.HasFlag(BlockchainTransactionType.Output))
                {
                    result += -tx.Amount.FromTokenDigits(tx.Currency.DigitsMultiplier);
                }
            }

            tx.InternalTxs?.ForEach(t => result += GetAmount(t));

            return(result);
        }
Ejemplo n.º 4
0
        private async Task BroadcastTxAsync(
            ClientSwap swap,
            TezosTransaction tx,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            var txId = await Xtz.BlockchainApi
                       .BroadcastAsync(tx, cancellationToken)
                       .ConfigureAwait(false);

            if (txId == null)
            {
                throw new Exception("Transaction Id is null");
            }

            Log.Debug("TxId {@id} for swap {@swapId}", txId, swap.Id);

            // account new unconfirmed transaction
            await Account
            .UpsertTransactionAsync(
                tx : tx,
                updateBalance : true,
                notifyIfUnconfirmed : true,
                notifyIfBalanceUpdated : true,
                cancellationToken : cancellationToken)
            .ConfigureAwait(false);

            // todo: transaction receipt status control
        }
Ejemplo n.º 5
0
        private async Task BroadcastTxAsync(
            Swap swap,
            TezosTransaction tx,
            CancellationToken cancellationToken = default)
        {
            var broadcastResult = await XtzConfig.BlockchainApi
                                  .TryBroadcastAsync(tx, cancellationToken : cancellationToken)
                                  .ConfigureAwait(false);

            if (broadcastResult.HasError)
            {
                throw new Exception($"Error while broadcast transaction with code {broadcastResult.Error.Code} and description {broadcastResult.Error.Description}");
            }

            var txId = broadcastResult.Value;

            if (txId == null)
            {
                throw new Exception("Transaction Id is null");
            }

            Log.Debug("TxId {@id} for swap {@swapId}", txId, swap.Id);

            // account new unconfirmed transaction
            await _account
            .UpsertTransactionAsync(
                tx : tx,
                updateBalance : true,
                notifyIfUnconfirmed : true,
                notifyIfBalanceUpdated : true,
                cancellationToken : cancellationToken)
            .ConfigureAwait(false);

            // todo: transaction receipt status control
        }
 public TezosTransactionViewModel(TezosTransaction tx, TezosConfig tezosConfig)
     : base(tx, tezosConfig, GetAmount(tx, tezosConfig), GetFee(tx))
 {
     From       = tx.From;
     To         = tx.To;
     GasLimit   = tx.GasLimit;
     Fee        = TezosConfig.MtzToTz(tx.Fee);
     IsInternal = tx.IsInternal;
 }
Ejemplo n.º 7
0
        private async Task BroadcastTxAsync(
            Swap swap,
            TezosTransaction tx,
            CancellationToken cancellationToken = default,
            bool updateBalance          = true,
            bool notifyIfUnconfirmed    = true,
            bool notifyIfBalanceUpdated = true)
        {
            var broadcastResult = await Xtz.BlockchainApi
                                  .TryBroadcastAsync(tx, cancellationToken : cancellationToken)
                                  .ConfigureAwait(false);

            if (broadcastResult.HasError)
            {
                throw new Exception($"Error while broadcast transaction with code {broadcastResult.Error.Code} and description {broadcastResult.Error.Description}");
            }

            var txId = broadcastResult.Value;

            if (txId == null)
            {
                throw new Exception("Transaction Id is null");
            }

            Log.Debug("TxId {@id} for swap {@swapId}", txId, swap.Id);

            // account new unconfirmed transaction
            await Fa12Account
            .UpsertTransactionAsync(
                tx : tx,
                updateBalance : updateBalance,
                notifyIfUnconfirmed : notifyIfUnconfirmed,
                notifyIfBalanceUpdated : notifyIfBalanceUpdated,
                cancellationToken : cancellationToken)
            .ConfigureAwait(false);

            var xtzTx = tx.Clone();

            xtzTx.Currency = Xtz;
            xtzTx.Amount   = 0;
            xtzTx.Type     = BlockchainTransactionType.Output | (tx.Type.HasFlag(BlockchainTransactionType.TokenApprove)
                ? BlockchainTransactionType.TokenCall
                : BlockchainTransactionType.SwapCall);

            await TezosAccount
            .UpsertTransactionAsync(
                tx : xtzTx,
                updateBalance : updateBalance,
                notifyIfUnconfirmed : notifyIfUnconfirmed,
                notifyIfBalanceUpdated : notifyIfBalanceUpdated,
                cancellationToken : cancellationToken)
            .ConfigureAwait(false);

            // todo: transaction receipt status control
        }
Ejemplo n.º 8
0
 public static bool IsSwapAdd(TezosTransaction tx, byte[] secretHash)
 {
     try
     {
         return(tx.Params["value"]["args"][0]["args"][0]["bytes"].ToString().Equals(secretHash.ToHexString()));
     }
     catch (Exception)
     {
         return(false);
     }
 }
Ejemplo n.º 9
0
 public static bool IsSwapInit(TezosTransaction tx, long refundTimestamp, byte[] secretHash, string participant)
 {
     try
     {
         return(tx.Params["value"]["args"][0]["args"][0]["args"][1]["args"][0]["args"][0]["bytes"].ToString().Equals(secretHash.ToHexString()) &&
                tx.Params["value"]["args"][0]["args"][0]["args"][1]["args"][0]["args"][1]["int"].ToObject <long>() == refundTimestamp &&
                tx.Params["value"]["args"][0]["args"][0]["args"][0]["string"].ToString().Equals(participant));
     }
     catch (Exception)
     {
         return(false);
     }
 }
Ejemplo n.º 10
0
        private static decimal GetFee(TezosTransaction tx)
        {
            var result = 0m;

            if (tx.Type.HasFlag(BlockchainTransactionType.Output))
            {
                result += Tezos.MtzToTz(tx.Fee);
            }

            tx.InternalTxs?.ForEach(t => result += GetFee(t));

            return(result);
        }
Ejemplo n.º 11
0
        public static bool IsSwapRefund(TezosTransaction tx, byte[] secretHash)
        {
            try
            {
                var secretHashBytes = Hex.FromString(tx.Params["value"]["bytes"].ToString());

                return(secretHashBytes.SequenceEqual(secretHash));
            }
            catch (Exception)
            {
                return(false);
            }
        }
        public static bool IsSwapRedeem(TezosTransaction tx, byte[] secretHash)
        {
            try
            {
                var secretBytes     = Hex.FromString(tx.Params["value"]["args"][0]["args"][0]["bytes"].ToString());
                var secretHashBytes = CurrencySwap.CreateSwapSecretHash(secretBytes);

                return(secretHashBytes.SequenceEqual(secretHash));
            }
            catch (Exception)
            {
                return(false);
            }
        }
Ejemplo n.º 13
0
        public static byte[] GetSecret(TezosTransaction tx)
        {
            var entrypoint = tx.Params?["entrypoint"]?.ToString();

            var secretInHex = entrypoint switch
            {
                "default" => GetSecret(tx.Params?["value"]?["args"]?[0]?["args"]?[0]),
                "withdraw" => GetSecret(tx.Params?["value"]?["args"]?[0]),
                "redeem" => GetSecret(tx.Params?["value"]),
                _ => ""
            };

            return(Hex.FromString(secretInHex));
        }
    }
Ejemplo n.º 14
0
        private async Task <bool> SignTransactionAsync(
            TezosTransaction tx,
            CancellationToken cancellationToken = default)
        {
            var walletAddress = await Fa12Account
                                .GetAddressAsync(
                address : tx.From,
                cancellationToken : cancellationToken)
                                .ConfigureAwait(false);

            return(await Fa12Account.Wallet
                   .SignAsync(
                       tx : tx,
                       address : walletAddress,
                       cancellationToken : cancellationToken)
                   .ConfigureAwait(false));
        }
Ejemplo n.º 15
0
        private TezosTransaction ResolveFA12TransactionType(
            TezosTransaction tx)
        {
            if (tx.Params["entrypoint"].ToString().Equals("initiate") ||
                tx.Params["entrypoint"].ToString().Equals("redeem") ||
                tx.Params["entrypoint"].ToString().Equals("refund"))
            {
                tx.Type |= BlockchainTransactionType.SwapCall;
            }
            else if (tx.Params["entrypoint"].ToString().Equals("transfer") ||
                     tx.Params["entrypoint"].ToString().Equals("approve"))
            {
                tx.Type |= BlockchainTransactionType.TokenCall;
            }

            return(tx);
        }
Ejemplo n.º 16
0
        public override async Task PartyRedeemAsync(ClientSwap swap)
        {
            Log.Debug("Create redeem for acceptor for swap {@swapId}", swap.Id);

            var walletAddress = (await Account
                                 .GetUnspentAddressesAsync(
                                     currency: Currency,
                                     amount: 0,
                                     fee: Xtz.RedeemFee.ToTez() + Xtz.RedeemStorageLimit.ToTez(),
                                     feePrice: 0,
                                     isFeePerTransaction: false,
                                     addressUsagePolicy: AddressUsagePolicy.UseOnlyOneAddress)
                                 .ConfigureAwait(false))
                                .FirstOrDefault();

            if (walletAddress == null)
            {
                Log.Error("Insufficient balance for party redeem. Cannot find the address containing the required amount of funds.");
                return;
            }

            var redeemTx = new TezosTransaction(Xtz)
            {
                From         = walletAddress.Address,
                To           = Xtz.SwapContractAddress,
                Amount       = 0,
                Fee          = Xtz.RedeemFee,
                GasLimit     = Xtz.RedeemGasLimit,
                StorageLimit = Xtz.RedeemStorageLimit,
                Params       = RedeemParams(swap),
                Type         = TezosTransaction.OutputTransaction
            };

            var signResult = await SignTransactionAsync(redeemTx)
                             .ConfigureAwait(false);

            if (!signResult)
            {
                Log.Error("Transaction signing error");
                return;
            }

            await BroadcastTxAsync(swap, redeemTx)
            .ConfigureAwait(false);
        }
Ejemplo n.º 17
0
        public static void ResolveTezosTxAlias(TezosTransaction tx)
        {
            var ALIAS_DELIMETER = '/';

            if (string.IsNullOrEmpty(tx.Alias) || tx.Alias.IndexOf(ALIAS_DELIMETER) == -1)
            {
                return;
            }

            if (tx.Type.HasFlag(BlockchainTransactionType.Input))
            {
                tx.Alias = tx.Alias.Split(ALIAS_DELIMETER)[0];
            }
            else if (tx.Type.HasFlag(BlockchainTransactionType.Output))
            {
                tx.Alias = tx.Alias.Split(ALIAS_DELIMETER)[1];
            }
        }
 public static bool IsSwapInit(
     TezosTransaction tx,
     string tokenContractAddress,
     byte[] secretHash,
     string participant)
 {
     try
     {
         return(tx.Params["entrypoint"].ToString().Equals("initiate") &&
                tx.Params["value"]["args"][0]["args"][0]["args"][0]["bytes"].ToString().Equals(secretHash.ToHexString()) &&
                tx.Params["value"]["args"][0]["args"][0]["args"][1]["string"].ToString().Equals(participant) &&
                tx.Params["value"]["args"][1]["args"][0]["string"].ToString().Equals(tokenContractAddress));
     }
     catch (Exception)
     {
         return(false);
     }
 }
Ejemplo n.º 19
0
        private async Task <bool> SignTransactionAsync(
            TezosTransaction tx,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            var walletAddress = await Account
                                .ResolveAddressAsync(
                currency : tx.Currency,
                address : tx.From,
                cancellationToken : cancellationToken)
                                .ConfigureAwait(false);

            return(await Account.Wallet
                   .SignAsync(
                       tx : tx,
                       address : walletAddress,
                       cancellationToken : cancellationToken)
                   .ConfigureAwait(false));
        }
        public static bool IsSwapInit(TezosTransaction tx, long refundTimestamp, byte[] secretHash, string participant)
        {
            try
            {
                if (tx.Params == null)
                {
                    return(false);
                }

                var entrypoint = tx.Params?["entrypoint"]?.ToString();

                return(entrypoint switch
                {
                    "default" => IsSwapInit(tx.Params?["value"]?["args"]?[0]?["args"]?[0], secretHash.ToHexString(), participant, refundTimestamp),
                    "fund" => IsSwapInit(tx.Params?["value"]?["args"]?[0], secretHash.ToHexString(), participant, refundTimestamp),
                    "initiate" => IsSwapInit(tx.Params?["value"], secretHash.ToHexString(), participant, refundTimestamp),
                    _ => false
                });
            }
Ejemplo n.º 21
0
        private static decimal GetAmount(TezosTransaction tx)
        {
            var result = 0m;

            if (tx.Type.HasFlag(BlockchainTransactionType.Input))
            {
                result += tx.Amount / tx.Currency.DigitsMultiplier;
            }

            var includeFee = tx.Currency.Name == tx.Currency.FeeCurrencyName;
            var fee        = includeFee ? tx.Fee : 0;

            if (tx.Type.HasFlag(BlockchainTransactionType.Output))
            {
                result += -(tx.Amount + fee) / tx.Currency.DigitsMultiplier;
            }

            tx.InternalTxs?.ForEach(t => result += GetAmount(t));

            return(result);
        }
Ejemplo n.º 22
0
        public static bool IsSwapRefund(TezosTransaction tx, byte[] secretHash)
        {
            try
            {
                if (tx.Params == null)
                {
                    return(false);
                }

                var entrypoint = tx.Params?["entrypoint"]?.ToString();

                if (entrypoint == "default" && tx.Params?["value"]?["prim"]?.Value <string>() != "Right")
                {
                    return(false);
                }

                var paramSecretHashInHex = entrypoint switch
                {
                    "default" => GetSecretHash(tx.Params?["value"]?["args"]?[0]?["args"]?[0]),
                    "withdraw" => GetSecretHash(tx.Params?["value"]?["args"]?[0]),
                    "refund" => GetSecretHash(tx.Params?["value"]),
                    _ => ""
                };

                if (paramSecretHashInHex == null)
                {
                    return(false);
                }

                var paramSecretHashBytes = Hex.FromString(paramSecretHashInHex);

                return(paramSecretHashBytes.SequenceEqual(secretHash));
            }
            catch (Exception)
            {
                return(false);
            }
        }
        public static bool IsSwapInit(
            TezosTransaction tx,
            string secretHash,
            string tokenContractAddress,
            string participant,
            long refundTimeStamp)
        {
            try
            {
                if (tx.Params == null)
                {
                    return(false);
                }

                var entrypoint = tx.Params?["entrypoint"]?.ToString();

                return(entrypoint switch
                {
                    "default" => IsSwapInit(tx.Params?["value"]?["args"]?[0]?["args"]?[0], secretHash, tokenContractAddress, participant, refundTimeStamp),
                    "initiate" => IsSwapInit(tx.Params?["value"], secretHash, tokenContractAddress, participant, refundTimeStamp),
                    _ => false
                });
            }
        private async void Send()
        {
            var wallet     = (HdWallet)App.Account.Wallet;
            var keyStorage = wallet.KeyStorage;
            var tezos      = Currency;

            var tezosAccount = App.Account
                               .GetCurrencyAccount <TezosAccount>("XTZ");

            try
            {
                DialogViewer.PushPage(Dialogs.Delegate, Pages.Delegating);

                await tezosAccount.AddressLocker
                .LockAsync(WalletAddress.Address);

                var tx = new TezosTransaction
                {
                    StorageLimit = Currency.StorageLimit,
                    GasLimit     = Currency.GasLimit,
                    From         = WalletAddress.Address,
                    To           = To,
                    Fee          = Fee.ToMicroTez(),
                    Currency     = Currency.Name,
                    CreationTime = DateTime.UtcNow,

                    UseRun            = true,
                    UseOfflineCounter = true,
                    OperationType     = OperationType.Delegation
                };

                using var securePublicKey = App.Account.Wallet.GetPublicKey(
                          currency: Currency,
                          keyIndex: WalletAddress.KeyIndex,
                          keyType: WalletAddress.KeyType);

                var _ = await tx.FillOperationsAsync(
                    securePublicKey : securePublicKey,
                    tezosConfig : Currency,
                    headOffset : TezosConfig.HeadOffset);

                var signResult = await tx
                                 .SignAsync(keyStorage, WalletAddress, Currency);

                if (!signResult)
                {
                    Log.Error("Transaction signing error");

                    DialogViewer.PushPage(Dialogs.Delegate, Pages.Message, MessageViewModel.Error(
                                              text: "Transaction signing error",
                                              backAction: BackToConfirmation));

                    return;
                }

                var result = await tezos.BlockchainApi
                             .TryBroadcastAsync(tx);

                if (result.Error != null)
                {
                    DialogViewer.PushPage(Dialogs.Delegate, Pages.Message, MessageViewModel.Error(
                                              text: result.Error.Description,
                                              backAction: BackToConfirmation));

                    return;
                }

                DialogViewer.PushPage(Dialogs.Delegate, Pages.Message, MessageViewModel.Success(
                                          text: $"Successful delegation!",
                                          tezos.TxExplorerUri,
                                          result.Value,
                                          nextAction: () =>
                {
                    DialogViewer.HideDialog(Dialogs.Delegate);

                    _onDelegate?.Invoke();
                }));
            }
            catch (Exception e)
            {
                DialogViewer.PushPage(Dialogs.Delegate, Pages.Message, MessageViewModel.Error(
                                          text: "An error has occurred while delegation.",
                                          backAction: BackToConfirmation));

                Log.Error(e, "delegation send error.");
            }
            finally
            {
                tezosAccount.AddressLocker.Unlock(WalletAddress.Address);
            }
        }
Ejemplo n.º 25
0
        public override async Task <Error> SendAsync(
            IEnumerable <WalletAddress> from,
            string to,
            decimal amount,
            decimal fee,
            decimal feePrice,
            bool useDefaultFee = true,
            CancellationToken cancellationToken = default)
        {
            var fa2 = FA2;

            var fromAddresses = from
                                .Where(w => w.Address != to) // filter self address usage
                                .ToList();

            var selectedAddresses = (await SelectUnspentAddressesAsync(
                                         from: fromAddresses,
                                         to: to,
                                         amount: amount,
                                         fee: fee,
                                         feeUsagePolicy: useDefaultFee ? FeeUsagePolicy.EstimatedFee : FeeUsagePolicy.FeeForAllTransactions,
                                         addressUsagePolicy: AddressUsagePolicy.UseMinimalBalanceFirst,
                                         transactionType: BlockchainTransactionType.Output,
                                         cancellationToken: cancellationToken)
                                     .ConfigureAwait(false))
                                    .ToList();

            if (!selectedAddresses.Any())
            {
                return(new Error(
                           code: Errors.InsufficientFunds,
                           description: "Insufficient funds"));
            }

            // todo: min fee control
            var isFirstTx = true;

            foreach (var selectedAddress in selectedAddresses)
            {
                var addressAmountInDigits = selectedAddress.UsedAmount.ToTokenDigits(fa2.DigitsMultiplier);

                Log.Debug("Send {@amount} tokens from address {@address} with available balance {@balance}",
                          addressAmountInDigits,
                          selectedAddress.WalletAddress.Address,
                          selectedAddress.WalletAddress.AvailableBalance());

                var storageLimit = Math.Max(fa2.TransferStorageLimit - fa2.ActivationStorage, 0); // without activation storage fee

                var tx = new TezosTransaction
                {
                    Currency      = fa2,
                    CreationTime  = DateTime.UtcNow,
                    From          = selectedAddress.WalletAddress.Address,
                    To            = fa2.TokenContractAddress,
                    Fee           = selectedAddress.UsedFee.ToMicroTez(),
                    GasLimit      = fa2.TransferGasLimit,
                    StorageLimit  = storageLimit,
                    Params        = TransferParams(fa2.TokenID, selectedAddress.WalletAddress.Address, to, Math.Round(addressAmountInDigits, 0)),
                    UseDefaultFee = useDefaultFee,
                    Type          = BlockchainTransactionType.Output
                };

                var signResult = await Wallet
                                 .SignAsync(tx, selectedAddress.WalletAddress, cancellationToken)
                                 .ConfigureAwait(false);

                if (!signResult)
                {
                    return(new Error(
                               code: Errors.TransactionSigningError,
                               description: "Transaction signing error"));
                }

                var broadcastResult = await fa2.BlockchainApi
                                      .TryBroadcastAsync(tx, cancellationToken : cancellationToken)
                                      .ConfigureAwait(false);

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

                var txId = broadcastResult.Value;

                if (txId == null)
                {
                    return(new Error(
                               code: Errors.TransactionBroadcastError,
                               description: "Transaction Id is null"));
                }

                Log.Debug("Transaction successfully sent with txId: {@id}", txId);

                tx.Amount = Math.Round(addressAmountInDigits, 0);

                await UpsertTransactionAsync(
                    tx : tx,
                    updateBalance : false,
                    notifyIfUnconfirmed : true,
                    notifyIfBalanceUpdated : false,
                    cancellationToken : cancellationToken)
                .ConfigureAwait(false);

                var xtzTx = tx.Clone();
                xtzTx.Currency = Xtz;
                xtzTx.Amount   = 0;
                xtzTx.Type     = BlockchainTransactionType.TokenCall;

                await UpsertTransactionAsync(
                    tx : xtzTx,
                    updateBalance : false,
                    notifyIfUnconfirmed : true,
                    notifyIfBalanceUpdated : false,
                    cancellationToken : cancellationToken)
                .ConfigureAwait(false);

                if (isFirstTx)
                {
                    isFirstTx = false;
                }
            }

            UpdateBalanceAsync(cancellationToken)
            .FireAndForget();

            return(null);
        }
Ejemplo n.º 26
0
 public static byte[] GetSecret(TezosTransaction tx)
 {
     return(Hex.FromString(tx.Params["value"]["bytes"].ToString()));
 }
Ejemplo n.º 27
0
 public TezosNYXTransactionViewModel(TezosTransaction tx)
     : base(tx)
 {
 }
Ejemplo n.º 28
0
        public async Task <Error> SendAsync(
            string from,
            string to,
            decimal amount,
            string tokenContract,
            int tokenId,
            int fee,
            bool useDefaultFee = true,
            CancellationToken cancellationToken = default)
        {
            var fa2Config = Fa2Config;
            var xtzConfig = XtzConfig;

            var fromAddress = await DataRepository
                              .GetTezosTokenAddressAsync(TokenType, _tokenContract, _tokenId, from)
                              .ConfigureAwait(false);

            var digitsMultiplier = (decimal)Math.Pow(10, fromAddress.TokenBalance.Decimals);

            var availableBalance = fromAddress.AvailableBalance() * digitsMultiplier;

            if (availableBalance < amount)
            {
                return(new Error(
                           code: Errors.InsufficientFunds,
                           description: $"Insufficient tokens. " +
                           $"Available: {fromAddress.AvailableBalance()}. " +
                           $"Required: {amount}."));
            }

            var xtzAddress = await DataRepository
                             .GetWalletAddressAsync(xtzConfig.Name, from)
                             .ConfigureAwait(false);

            var isRevealed = await _tezosAccount
                             .IsRevealedSourceAsync(from, cancellationToken)
                             .ConfigureAwait(false);

            var storageFeeInMtz = (fa2Config.TransferStorageLimit - fa2Config.ActivationStorage) * fa2Config.StorageFeeMultiplier;

            var feeInMtz = useDefaultFee
                ? fa2Config.TransferFee + (isRevealed ? 0 : fa2Config.RevealFee) + storageFeeInMtz
                : fee;

            var availableBalanceInTz = xtzAddress.AvailableBalance().ToMicroTez() - feeInMtz - xtzConfig.MicroTezReserve;

            if (availableBalanceInTz < 0)
            {
                return(new Error(
                           code: Errors.InsufficientFunds,
                           description: $"Insufficient funds to pay fee for address {from}. " +
                           $"Available: {xtzAddress.AvailableBalance()}. " +
                           $"Required: {feeInMtz + xtzConfig.MicroTezReserve}"));
            }

            Log.Debug("Send {@amount} tokens from address {@address} with available balance {@balance}",
                      amount,
                      from,
                      fromAddress.AvailableBalance());

            var storageLimit = Math.Max(fa2Config.TransferStorageLimit - fa2Config.ActivationStorage, 0); // without activation storage fee

            var tx = new TezosTransaction
            {
                Currency     = xtzConfig.Name,
                CreationTime = DateTime.UtcNow,
                From         = from,
                To           = tokenContract,
                Fee          = feeInMtz,
                GasLimit     = fa2Config.TransferGasLimit,
                StorageLimit = storageLimit,
                Params       = CreateTransferParams(tokenId, from, to, amount),
                Type         = BlockchainTransactionType.Output | BlockchainTransactionType.TokenCall,

                UseRun = useDefaultFee,
                UseSafeStorageLimit = true,
                UseOfflineCounter   = true
            };

            using var addressLock = await _tezosAccount.AddressLocker
                                    .GetLockAsync(from, cancellationToken)
                                    .ConfigureAwait(false);

            using var securePublicKey = Wallet.GetPublicKey(
                      currency: xtzConfig,
                      keyIndex: fromAddress.KeyIndex,
                      keyType: fromAddress.KeyType);

            // fill operation
            var(fillResult, isRunSuccess, hasReveal) = await tx
                                                       .FillOperationsAsync(
                securePublicKey : securePublicKey,
                tezosConfig : xtzConfig,
                headOffset : TezosConfig.HeadOffset,
                cancellationToken : cancellationToken)
                                                       .ConfigureAwait(false);

            var signResult = await Wallet
                             .SignAsync(tx, xtzAddress, xtzConfig, cancellationToken)
                             .ConfigureAwait(false);

            if (!signResult)
            {
                return(new Error(
                           code: Errors.TransactionSigningError,
                           description: "Transaction signing error"));
            }

            var broadcastResult = await xtzConfig.BlockchainApi
                                  .TryBroadcastAsync(tx, cancellationToken : cancellationToken)
                                  .ConfigureAwait(false);

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

            var txId = broadcastResult.Value;

            if (txId == null)
            {
                return(new Error(
                           code: Errors.TransactionBroadcastError,
                           description: "Transaction Id is null"));
            }

            Log.Debug("Transaction successfully sent with txId: {@id}", txId);

            await _tezosAccount
            .UpsertTransactionAsync(
                tx : tx,
                updateBalance : false,
                notifyIfUnconfirmed : true,
                notifyIfBalanceUpdated : false,
                cancellationToken : cancellationToken)
            .ConfigureAwait(false);

            return(null);
        }
Ejemplo n.º 29
0
        public override async Task <Error> SendAsync(
            IEnumerable <WalletAddress> from,
            string to,
            decimal amount,
            decimal fee,
            decimal feePrice,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            var selectedAddresses = (await SelectUnspentAddressesAsync(
                                         from: from.ToList(),
                                         to: to,
                                         amount: amount,
                                         fee: fee,
                                         isFeePerTransaction: false,
                                         addressUsagePolicy: AddressUsagePolicy.UseMinimalBalanceFirst,
                                         cancellationToken: cancellationToken)
                                     .ConfigureAwait(false))
                                    .ToList();

            if (!selectedAddresses.Any())
            {
                return(new Error(
                           code: Errors.InsufficientFunds,
                           description: "Insufficient funds"));
            }

            var feePerTxInMtz = Math.Round(fee.ToMicroTez() / selectedAddresses.Count);

            foreach (var selectedAddress in selectedAddresses)
            {
                var addressAmountMtz = selectedAddress.UsedAmount.ToMicroTez();

                Log.Debug("Send {@amount} XTZ from address {@address} with available balance {@balance}",
                          addressAmountMtz,
                          selectedAddress.WalletAddress.Address,
                          selectedAddress.WalletAddress.AvailableBalance());

                var tx = new TezosTransaction(Xtz)
                {
                    From         = selectedAddress.WalletAddress.Address,
                    To           = to,
                    Amount       = Math.Round(addressAmountMtz, 0),
                    Fee          = feePerTxInMtz,
                    GasLimit     = Xtz.GasLimit,
                    StorageLimit = Xtz.StorageLimit,
                    Type         = TezosTransaction.OutputTransaction
                };

                var signResult = await Wallet
                                 .SignAsync(tx, selectedAddress.WalletAddress, cancellationToken)
                                 .ConfigureAwait(false);

                if (!signResult)
                {
                    return(new Error(
                               code: Errors.TransactionSigningError,
                               description: "Transaction signing error"));
                }

                var txId = await Currency.BlockchainApi
                           .BroadcastAsync(tx, cancellationToken)
                           .ConfigureAwait(false);

                if (txId == null)
                {
                    return(new Error(
                               code: Errors.TransactionBroadcastError,
                               description: "Transaction Id is null"));
                }

                Log.Debug("Transaction successfully sent with txId: {@id}", txId);

                await UpsertTransactionAsync(
                    tx : tx,
                    updateBalance : false,
                    notifyIfUnconfirmed : true,
                    notifyIfBalanceUpdated : false,
                    cancellationToken : cancellationToken)
                .ConfigureAwait(false);
            }

            await UpdateBalanceAsync(cancellationToken)
            .ConfigureAwait(false);

            return(null);
        }
Ejemplo n.º 30
0
        private async Task RefundAsync(
            ClientSwap swap,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            Log.Debug("Create refund for swap {@swap}", swap.Id);

            var walletAddress = (await Account.GetUnspentAddressesAsync(
                                     currency: Currency,
                                     amount: 0,
                                     fee: Xtz.RefundFee.ToTez() + Xtz.RefundStorageLimit.ToTez(),
                                     feePrice: 0,
                                     isFeePerTransaction: true,
                                     addressUsagePolicy: AddressUsagePolicy.UseOnlyOneAddress,
                                     cancellationToken: cancellationToken)
                                 .ConfigureAwait(false))
                                .FirstOrDefault();

            if (walletAddress == null)
            {
                Log.Error("Insufficient funds for refund");
                return;
            }

            var refundTx = new TezosTransaction(Xtz)
            {
                From         = walletAddress.Address,
                To           = Xtz.SwapContractAddress,
                Fee          = Xtz.RefundFee,
                GasLimit     = Xtz.RefundGasLimit,
                StorageLimit = Xtz.RefundStorageLimit,
                Params       = RefundParams(swap),
                Type         = TezosTransaction.OutputTransaction
            };

            var signResult = await SignTransactionAsync(refundTx, cancellationToken)
                             .ConfigureAwait(false);

            if (!signResult)
            {
                Log.Error("Transaction signing error");
                return;
            }

            swap.RefundTx = refundTx;
            swap.SetRefundSigned();
            RaiseSwapUpdated(swap, SwapStateFlags.IsRefundSigned);

            await BroadcastTxAsync(swap, refundTx, cancellationToken)
            .ConfigureAwait(false);

            swap.RefundTx = refundTx;
            swap.SetRefundBroadcast();
            RaiseSwapUpdated(swap, SwapStateFlags.IsRefundBroadcast);

            TaskPerformer.EnqueueTask(new TransactionConfirmationCheckTask
            {
                Currency        = Currency,
                Swap            = swap,
                Interval        = DefaultConfirmationCheckInterval,
                TxId            = refundTx.Id,
                CompleteHandler = RefundConfirmedEventHandler
            });
        }