private async Task BroadcastTxAsync( ClientSwap swap, EthereumTransaction tx, CancellationToken cancellationToken = default(CancellationToken)) { var txId = await Eth.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 }
private async Task BroadcastTxAsync( Swap swap, EthereumTransaction tx, CancellationToken cancellationToken = default, bool updateBalance = true, bool notifyIfUnconfirmed = true, bool notifyIfBalanceUpdated = true) { var broadcastResult = await Erc20.BlockchainApi .BroadcastAsync(tx, cancellationToken) .ConfigureAwait(false); if (broadcastResult.HasError) { throw new InternalException(broadcastResult.Error); } var txId = broadcastResult.Value; if (txId == null) { throw new Exception("Transaction Id is null"); } Log.Debug("TxId {@id} for swap {@swapId}", txId, swap.Id); if (tx.Type.HasFlag(BlockchainTransactionType.SwapPayment)) { tx = tx.ParseERC20Input(); } // account new unconfirmed transaction await Erc20Account .UpsertTransactionAsync( tx : tx, updateBalance : updateBalance, notifyIfUnconfirmed : notifyIfUnconfirmed, notifyIfBalanceUpdated : notifyIfBalanceUpdated, cancellationToken : cancellationToken) .ConfigureAwait(false); var ethTx = tx.Clone(); ethTx.Currency = Eth; ethTx.Amount = 0; ethTx.Type = BlockchainTransactionType.Output | (ethTx.Type.HasFlag(BlockchainTransactionType.TokenApprove) ? BlockchainTransactionType.TokenCall : BlockchainTransactionType.SwapCall); await EthereumAccount .UpsertTransactionAsync( tx : ethTx, updateBalance : updateBalance, notifyIfUnconfirmed : notifyIfUnconfirmed, notifyIfBalanceUpdated : notifyIfBalanceUpdated, cancellationToken : cancellationToken) .ConfigureAwait(false); // todo: transaction receipt status control }
private async Task BroadcastTxAsync( Swap swap, EthereumTransaction tx, CancellationToken cancellationToken = default) { var broadcastResult = await EthConfig.BlockchainApi .TryBroadcastAsync(tx, cancellationToken : cancellationToken) .ConfigureAwait(false); if (broadcastResult.HasError) { throw new InternalException(broadcastResult.Error); } 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); }
public static decimal GetAmount( EthereumTransaction tx, Erc20Config erc20Config) { var result = 0m; if (tx.Type.HasFlag(BlockchainTransactionType.SwapRedeem) || tx.Type.HasFlag(BlockchainTransactionType.SwapRefund)) { result += erc20Config.TokenDigitsToTokens(tx.Amount); } else { if (tx.Type.HasFlag(BlockchainTransactionType.Input)) { result += erc20Config.TokenDigitsToTokens(tx.Amount); } if (tx.Type.HasFlag(BlockchainTransactionType.Output)) { result += -erc20Config.TokenDigitsToTokens(tx.Amount); } } tx.InternalTxs?.ForEach(t => result += GetAmount(t, erc20Config)); return(result); }
public static decimal GetAmount(EthereumTransaction tx) { var Erc20 = tx.Currency as EthereumTokens.ERC20; var result = 0m; if (tx.Type.HasFlag(BlockchainTransactionType.SwapRedeem) || tx.Type.HasFlag(BlockchainTransactionType.SwapRefund)) { result += Erc20.TokenDigitsToTokens(tx.Amount); } else { if (tx.Type.HasFlag(BlockchainTransactionType.Input)) { result += Erc20.TokenDigitsToTokens(tx.Amount); } if (tx.Type.HasFlag(BlockchainTransactionType.Output)) { result += -Erc20.TokenDigitsToTokens(tx.Amount); } } tx.InternalTxs?.ForEach(t => result += GetAmount(t)); return(result); }
public static EthereumTransaction ParseERC20TransactionType( this EthereumTransaction transaction) { if (transaction.Input == "0x") { return(transaction); } if (transaction.Currency.Name == "ETH") { if (transaction.IsERC20TransferTransaction() || transaction.IsERC20ApproveTransaction()) { transaction.Type |= BlockchainTransactionType.TokenCall; } else if (transaction.IsERC20InitiateTransaction() || transaction.IsERC20RedeemTransaction() || transaction.IsERC20RefundTransaction()) { transaction.Type |= BlockchainTransactionType.SwapCall; } } return(transaction); }
public static EthereumTransaction ParseERC20AddInput( this EthereumTransaction transaction) { var input = transaction.Input.Substring(transaction.Input.Length % InputItemSizeInHex); transaction.Amount = new HexBigInteger(input.Substring(InputItemSizeInHex * 1, InputItemSizeInHex)).Value; return(transaction); }
public override async Task PartyRedeemAsync(ClientSwap swap) { Log.Debug("Create redeem for counterParty for swap {@swapId}", swap.Id); var walletAddress = (await Account .GetUnspentAddressesAsync( currency: Currency, amount: 0, fee: Eth.RedeemGasLimit, feePrice: Eth.GasPriceInGwei, 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 nonce = await EthereumNonceManager.Instance .GetNonce(Eth, walletAddress.Address) .ConfigureAwait(false); var message = new RedeemFunctionMessage { FromAddress = walletAddress.Address, HashedSecret = swap.SecretHash, Secret = swap.Secret, Nonce = nonce, GasPrice = Atomix.Ethereum.GweiToWei(Eth.GasPriceInGwei), }; message.Gas = await EstimateGasAsync(message, new BigInteger(Eth.RedeemGasLimit)) .ConfigureAwait(false); var txInput = message.CreateTransactionInput(Eth.SwapContractAddress); var redeemTx = new EthereumTransaction(Eth, txInput) { Type = EthereumTransaction.OutputTransaction }; var signResult = await SignTransactionAsync(redeemTx) .ConfigureAwait(false); if (!signResult) { Log.Error("Transaction signing error"); return; } await BroadcastTxAsync(swap, redeemTx) .ConfigureAwait(false); }
public static EthereumTransaction ParseERC20TransferInput( this EthereumTransaction transaction) { var input = transaction.Input.Substring(transaction.Input.Length % InputItemSizeInHex); transaction.To = $"0x{input.Substring(InputItemSizeInHex - AddressLengthInHex, AddressLengthInHex)}"; transaction.Amount = new HexBigInteger(input.Substring(InputItemSizeInHex, InputItemSizeInHex)).Value; return(transaction); }
public async Task <long> StartAsync(string hash, long userId, EthereumTransactionEntityType entityType, long entityId, EthereumTransactionType transactionType) { var transaction = new EthereumTransaction(userId, hash, _clock.UtcNow, entityType, entityId, transactionType); _repository.Add(transaction); await _repository.SaveChangesAsync(); return(transaction.Id); }
public EthereumTransactionViewModel(EthereumTransaction tx, EthereumConfig ethereumConfig) : base(tx, ethereumConfig, GetAmount(tx), GetFee(tx)) { From = tx.From; To = tx.To; GasPrice = EthereumConfig.WeiToGwei((decimal)tx.GasPrice); GasLimit = (decimal)tx.GasLimit; GasUsed = (decimal)tx.GasUsed; Fee = EthereumConfig.WeiToEth(tx.GasUsed * tx.GasPrice); IsInternal = tx.IsInternal; }
public void MapTransactionToDton_EmptySource_ShouldSucceed() { var mapper = new EthereumTransactionMapper(); var source = new EthereumTransaction(); // act var actual = mapper.Map(source); // assert Assert.NotNull(actual); }
public EthereumERC20TransactionViewModel( EthereumTransaction tx, Erc20Config erc20Config) : base(tx, erc20Config, GetAmount(tx, erc20Config), 0) { From = tx.From; To = tx.To; GasPrice = EthereumConfig.WeiToGwei((decimal)tx.GasPrice); GasLimit = (decimal)tx.GasLimit; GasUsed = (decimal)tx.GasUsed; IsInternal = tx.IsInternal; }
public static EthereumTransaction TransformTransferEvent( this EtherScanApi.ContractEvent contractEvent, string address, EthereumTokens.ERC20 erc20, long lastBlockNumber) { if (!contractEvent.IsERC20TransferEvent()) { return(null); } var transferEvent = contractEvent.ParseERC20TransferEvent(); var tx = new EthereumTransaction() //todo: make a refactoring { Currency = erc20, Id = contractEvent.HexTransactionHash, Type = transferEvent.From == address ? transferEvent.To == erc20.SwapContractAddress.ToLowerInvariant() //todo: change to erc20.SwapContractAddress after currencies.json update ? BlockchainTransactionType.Output | BlockchainTransactionType.SwapPayment : BlockchainTransactionType.Output : BlockchainTransactionType.Input, //todo: recognize redeem&refund State = BlockchainTransactionState.Confirmed, //todo: check if true in 100% cases CreationTime = contractEvent.HexTimeStamp.Substring(PrefixOffset).FromHexString(), From = transferEvent.From, To = transferEvent.To, Amount = transferEvent.Value.ToHexBigInteger(), ////Nonce GasPrice = new HexBigInteger(contractEvent.HexGasPrice).Value, ////GasLimit GasLimit = new HexBigInteger(contractEvent.HexGasUsed).Value, ReceiptStatus = true, IsInternal = transferEvent.From == erc20.SwapContractAddress.ToLowerInvariant() || transferEvent.To == erc20.SwapContractAddress.ToLowerInvariant(), InternalIndex = 0, BlockInfo = new BlockInfo { Confirmations = 1 + (int)(lastBlockNumber - long.Parse(contractEvent.HexBlockNumber.Substring(PrefixOffset), System.Globalization.NumberStyles.HexNumber)), // //Confirmations = txReceipt.Status != null // //? (int)txReceipt.Status.Value // //: 0, // //BlockHash = tx.BlockHash, BlockHeight = long.Parse(contractEvent.HexBlockNumber.Substring(PrefixOffset), System.Globalization.NumberStyles.HexNumber), // //BlockTime = blockTimeStamp, // //FirstSeen = blockTimeStamp } }; return(tx); }
private static decimal GetFee(EthereumTransaction tx) { var result = 0m; if (tx.Type.HasFlag(BlockchainTransactionType.Output)) { result += EthereumConfig.WeiToEth(tx.GasUsed * tx.GasPrice); } tx.InternalTxs?.ForEach(t => result += GetFee(t)); return(result); }
public static EthereumTransactionResponse Create(EthereumTransaction transaction) { return(new EthereumTransactionResponse { Id = transaction.Id, UserId = transaction.UserId, Hash = transaction.Hash, EntityId = transaction.EntityId, EntityType = transaction.EntityType, TransactionType = transaction.TransactionType, Status = transaction.Status, Created = transaction.Created }); }
public void MapTransactionToDto_BlockNumber_ShouldSucceed() { var mapper = new EthereumTransactionMapper(); var source = new EthereumTransaction { BlockNumber = "12345" }; // act var actual = mapper.Map(source); // assert Assert.NotNull(actual); Assert.Equal("12345", actual.BlockNumber); }
private async Task <bool> SignTransactionAsync( EthereumTransaction tx, CancellationToken cancellationToken = default) { var walletAddress = await _account .GetAddressAsync( address : tx.From, cancellationToken : cancellationToken) .ConfigureAwait(false); return(await _account.Wallet .SignAsync( tx : tx, address : walletAddress, cancellationToken : cancellationToken) .ConfigureAwait(false)); }
public static EthereumTransaction TransformApprovalEvent( this EtherScanApi.ContractEvent contractEvent, EthereumTokens.ERC20 erc20, long lastBlockNumber) { if (!contractEvent.IsERC20ApprovalEvent()) { return(null); } var approvalEvent = contractEvent.ParseERC20ApprovalEvent(); var tx = new EthereumTransaction() //todo: make a refactoring { Currency = erc20, Id = contractEvent.HexTransactionHash, Type = BlockchainTransactionType.Output | BlockchainTransactionType.TokenApprove, State = BlockchainTransactionState.Confirmed, //todo: check if true in 100% cases CreationTime = contractEvent.HexTimeStamp.Substring(PrefixOffset).FromHexString(), From = approvalEvent.Owner, To = approvalEvent.Spender, Amount = 0, ////Nonce GasPrice = new HexBigInteger(contractEvent.HexGasPrice).Value, ////GasLimit GasLimit = new HexBigInteger(contractEvent.HexGasUsed).Value, ReceiptStatus = true, IsInternal = false, InternalIndex = 0, BlockInfo = new BlockInfo { Confirmations = 1 + (int)(lastBlockNumber - long.Parse(contractEvent.HexBlockNumber.Substring(PrefixOffset), System.Globalization.NumberStyles.HexNumber)), // //Confirmations = txReceipt.Status != null // //? (int)txReceipt.Status.Value // //: 0, // //BlockHash = tx.BlockHash, BlockHeight = long.Parse(contractEvent.HexBlockNumber.Substring(PrefixOffset), System.Globalization.NumberStyles.HexNumber), // //BlockTime = blockTimeStamp, // //FirstSeen = blockTimeStamp } }; return(tx); }
public EthereumERC20TransactionViewModel(EthereumTransaction tx) : base(tx, GetAmount(tx), 0) { From = tx.From; To = tx.To; GasPrice = Ethereum.WeiToGwei((decimal)tx.GasPrice); GasLimit = (decimal)tx.GasLimit; GasUsed = (decimal)tx.GasUsed; IsInternal = tx.IsInternal; if (Amount <= 0) { Alias = tx.To; } if (Amount > 0) { Alias = tx.From; } }
public EthereumTransactionDto Map(EthereumTransaction source) { var result = new EthereumTransactionDto(); if (source == null) { return(result); } result.BlockHash = source.BlockHash; result.BlockNumber = source.BlockNumber; result.Gas = source.Gas; result.Hash = source.Hash; result.From = source.From; result.To = source.To; //result.Value = Convert.ToInt64(source.Value, 16); result.Value = source.Value; return(result); }
public static EthereumTransaction ParseERC20Input( this EthereumTransaction transaction) { if (transaction.Input == "0x") { return(transaction); } if (transaction.IsERC20TransferTransaction()) { return(transaction.ParseERC20TransferInput()); } else if (transaction.IsERC20InitiateTransaction()) { return(transaction.ParseERC20InitiateInput()); } else if (transaction.IsERC20AddTransaction()) { return(transaction.ParseERC20AddInput()); } return(transaction); }
public async void AddEthereumTransactionTest() { var repository = new LiteDbAccountDataRepository( pathToDb: PathToDb, password: Password, currencies: Common.CurrenciesTestNet, network: Network.TestNet); var id = "abcdefgh"; var tx = new EthereumTransaction { Id = id, Currency = Common.EthTestNet.Name, InternalTxs = new List <EthereumTransaction> { new EthereumTransaction { Currency = Common.EthTestNet.Name } } }; var result = await repository .UpsertTransactionAsync(tx) .ConfigureAwait(false); Assert.True(result); var readTx = await repository .GetTransactionByIdAsync(Common.EthTestNet.Name, id, Common.EthTestNet.TransactionType) .ConfigureAwait(false) as EthereumTransaction; Assert.NotNull(readTx); Assert.NotNull(readTx.InternalTxs); Assert.Equal(id, readTx.Id); }
public async Task <Error> SendAsync( string from, string to, decimal amount, decimal gasLimit, decimal gasPrice, bool useDefaultFee = false, CancellationToken cancellationToken = default) { //if (from == to) // return new Error( // code: Errors.SendingAndReceivingAddressesAreSame, // description: "Sending and receiving addresses are the same."); var ethConfig = EthConfig; if (useDefaultFee) { gasLimit = GasLimitByType(BlockchainTransactionType.Output); gasPrice = Math.Floor(await ethConfig .GetGasPriceAsync(cancellationToken) .ConfigureAwait(false)); } var addressFeeUsage = await CalculateFundsUsageAsync( from : from, amount : amount, fee : gasLimit, feePrice : gasPrice, cancellationToken : cancellationToken) .ConfigureAwait(false); if (addressFeeUsage == null) { return(new Error( code: Errors.InsufficientFunds, description: "Insufficient funds")); } if (gasLimit < ethConfig.GasLimit) { return(new Error( code: Errors.InsufficientGas, description: "Insufficient gas")); } Log.Debug("Try to send {@amount} ETH with fee {@fee} from address {@address} with available balance {@balance}", addressFeeUsage.UsedAmount, addressFeeUsage.UsedFee, addressFeeUsage.WalletAddress.Address, addressFeeUsage.WalletAddress.AvailableBalance()); // lock address to prevent nonce races using var addressLock = await AddressLocker .GetLockAsync(addressFeeUsage.WalletAddress.Address, cancellationToken) .ConfigureAwait(false); var nonceAsyncResult = await EthereumNonceManager.Instance .GetNonceAsync(ethConfig, addressFeeUsage.WalletAddress.Address) .ConfigureAwait(false); if (nonceAsyncResult.HasError) { return(nonceAsyncResult.Error); } var tx = new EthereumTransaction { Currency = ethConfig.Name, Type = BlockchainTransactionType.Output, CreationTime = DateTime.UtcNow, To = to.ToLowerInvariant(), Amount = EthereumConfig.EthToWei(addressFeeUsage.UsedAmount), Nonce = nonceAsyncResult.Value, GasPrice = new BigInteger(EthereumConfig.GweiToWei(gasPrice)), GasLimit = new BigInteger(gasLimit), }; var signResult = await Wallet .SignAsync(tx, addressFeeUsage.WalletAddress, ethConfig, cancellationToken) .ConfigureAwait(false); if (!signResult) { return(new Error( code: Errors.TransactionSigningError, description: "Transaction signing error")); } if (!tx.Verify(ethConfig)) { return(new Error( code: Errors.TransactionVerificationError, description: "Transaction verification error")); } var broadcastResult = await ethConfig.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 UpsertTransactionAsync( tx : tx, updateBalance : false, notifyIfUnconfirmed : true, notifyIfBalanceUpdated : false, cancellationToken : cancellationToken) .ConfigureAwait(false); _ = UpdateBalanceAsync(cancellationToken); return(null); }
public override async Task RefundAsync( Swap swap, CancellationToken cancellationToken = default) { var ethConfig = EthConfig; if (swap.StateFlags.HasFlag(SwapStateFlags.IsRefundBroadcast) && swap.RefundTx != null && swap.RefundTx.CreationTime != null && swap.RefundTx.CreationTime.Value.ToUniversalTime() + TimeSpan.FromMinutes(20) > DateTime.UtcNow) { _ = TrackTransactionConfirmationAsync( swap: swap, currency: ethConfig, dataRepository: _account.DataRepository, txId: swap.RefundTx.Id, confirmationHandler: RefundConfirmedEventHandler, cancellationToken: cancellationToken); return; } var lockTimeInSeconds = swap.IsInitiator ? DefaultInitiatorLockTimeInSeconds : DefaultAcceptorLockTimeInSeconds; var lockTime = swap.TimeStamp.ToUniversalTime() + TimeSpan.FromSeconds(lockTimeInSeconds); await RefundTimeDelayAsync(lockTime, cancellationToken) .ConfigureAwait(false); // check swap initiation try { var txResult = await EthereumSwapInitiatedHelper .TryToFindPaymentAsync(swap, ethConfig, cancellationToken) .ConfigureAwait(false); if (!txResult.HasError && txResult.Value == null) { // swap not initiated and must be canceled swap.StateFlags |= SwapStateFlags.IsCanceled; await UpdateSwapAsync(swap, SwapStateFlags.IsCanceled, cancellationToken) .ConfigureAwait(false); return; } } catch (Exception e) { Log.Error(e, $"Can't check {swap.Id} swap initiation for ETH"); } Log.Debug("Create refund for swap {@swap}", swap.Id); var gasPrice = await ethConfig .GetGasPriceAsync(cancellationToken) .ConfigureAwait(false); var walletAddress = await _account .GetAddressAsync(swap.FromAddress, cancellationToken) .ConfigureAwait(false); if (walletAddress == null) { Log.Error("Can't get address {@address} for refund from local db", swap.FromAddress); return; } var feeInEth = ethConfig.GetFeeAmount(ethConfig.RefundGasLimit, gasPrice); if (walletAddress.Balance < feeInEth) { Log.Error("Insufficient funds for refund"); return; } EthereumTransaction refundTx; try { await EthereumAccount.AddressLocker .LockAsync(walletAddress.Address, cancellationToken) .ConfigureAwait(false); var nonceResult = await EthereumNonceManager.Instance .GetNonceAsync(ethConfig, walletAddress.Address, pending : true, cancellationToken) .ConfigureAwait(false); if (nonceResult.HasError) { Log.Error("Nonce getting error with code {@code} and description {@description}", nonceResult.Error.Code, nonceResult.Error.Description); return; } var message = new RefundFunctionMessage { FromAddress = walletAddress.Address, HashedSecret = swap.SecretHash, GasPrice = EthereumConfig.GweiToWei(gasPrice), Nonce = nonceResult.Value, }; message.Gas = await EstimateGasAsync(message, new BigInteger(ethConfig.RefundGasLimit)) .ConfigureAwait(false); var txInput = message.CreateTransactionInput(ethConfig.SwapContractAddress); refundTx = new EthereumTransaction(ethConfig.Name, txInput) { Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapRefund }; var signResult = await SignTransactionAsync(refundTx, cancellationToken) .ConfigureAwait(false); if (!signResult) { Log.Error("Transaction signing error"); return; } swap.RefundTx = refundTx; swap.StateFlags |= SwapStateFlags.IsRefundSigned; await UpdateSwapAsync(swap, SwapStateFlags.IsRefundSigned, cancellationToken) .ConfigureAwait(false); await BroadcastTxAsync(swap, refundTx, cancellationToken) .ConfigureAwait(false); } catch { throw; } finally { EthereumAccount.AddressLocker.Unlock(walletAddress.Address); } swap.RefundTx = refundTx; swap.StateFlags |= SwapStateFlags.IsRefundBroadcast; await UpdateSwapAsync(swap, SwapStateFlags.IsRefundBroadcast, cancellationToken) .ConfigureAwait(false); _ = TrackTransactionConfirmationAsync( swap: swap, currency: ethConfig, dataRepository: _account.DataRepository, txId: refundTx.Id, confirmationHandler: RefundConfirmedEventHandler, cancellationToken: cancellationToken); }
public override async Task RedeemForPartyAsync( Swap swap, CancellationToken cancellationToken = default) { if (swap.IsInitiator) { var partyRedeemDeadline = swap.TimeStamp.ToUniversalTime().AddSeconds(DefaultAcceptorLockTimeInSeconds) - PartyRedeemTimeReserve; if (DateTime.UtcNow > partyRedeemDeadline) { Log.Error("Party redeem deadline reached for swap {@swap}", swap.Id); return; } } Log.Debug("Create redeem for counterParty for swap {@swapId}", swap.Id); var ethConfig = EthConfig; var gasPrice = await ethConfig .GetGasPriceAsync(cancellationToken) .ConfigureAwait(false); var walletAddress = await _account .GetAddressAsync(swap.FromAddress, cancellationToken) .ConfigureAwait(false); if (walletAddress == null) { Log.Error("Can't get address {@address} for redeem for party from local db", swap.FromAddress); return; } var feeInEth = ethConfig.GetFeeAmount(ethConfig.RedeemGasLimit, gasPrice); if (walletAddress.Balance < feeInEth) { Log.Error("Insufficient funds for redeem for party"); return; } using var addressLock = await EthereumAccount.AddressLocker .GetLockAsync(walletAddress.Address, cancellationToken) .ConfigureAwait(false); var nonceResult = await EthereumNonceManager.Instance .GetNonceAsync(ethConfig, walletAddress.Address, pending : true, cancellationToken) .ConfigureAwait(false); if (nonceResult.HasError) { Log.Error("Nonce getting error with code {@code} and description {@description}", nonceResult.Error.Code, nonceResult.Error.Description); return; } var message = new RedeemFunctionMessage { FromAddress = walletAddress.Address, HashedSecret = swap.SecretHash, Secret = swap.Secret, Nonce = nonceResult.Value, GasPrice = EthereumConfig.GweiToWei(gasPrice), }; message.Gas = await EstimateGasAsync(message, new BigInteger(ethConfig.RedeemGasLimit)) .ConfigureAwait(false); var txInput = message.CreateTransactionInput(ethConfig.SwapContractAddress); var redeemTx = new EthereumTransaction(ethConfig.Name, txInput) { Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapRedeem }; var signResult = await SignTransactionAsync(redeemTx, cancellationToken) .ConfigureAwait(false); if (!signResult) { Log.Error("Transaction signing error"); return; } await BroadcastTxAsync(swap, redeemTx, cancellationToken) .ConfigureAwait(false); }
public override async Task RedeemAsync( Swap swap, CancellationToken cancellationToken = default) { var ethConfig = EthConfig; var secretResult = await EthereumSwapRedeemedHelper .IsRedeemedAsync( swap : swap, currency : ethConfig, attempts : MaxRedeemCheckAttempts, attemptIntervalInSec : RedeemCheckAttemptIntervalInSec, cancellationToken : cancellationToken) .ConfigureAwait(false); if (!secretResult.HasError && secretResult.Value != null) { await RedeemConfirmedEventHandler(swap, null, cancellationToken) .ConfigureAwait(false); return; } if (swap.StateFlags.HasFlag(SwapStateFlags.IsRedeemBroadcast) && swap.RedeemTx != null && swap.RedeemTx.CreationTime != null && swap.RedeemTx.CreationTime.Value.ToUniversalTime() + TimeSpan.FromMinutes(30) > DateTime.UtcNow) { // redeem already broadcast _ = TrackTransactionConfirmationAsync( swap: swap, currency: ethConfig, dataRepository: _account.DataRepository, txId: swap.RedeemTx.Id, confirmationHandler: RedeemConfirmedEventHandler, cancellationToken: cancellationToken); return; } if (swap.IsInitiator) { var redeemDeadline = swap.TimeStamp.ToUniversalTime().AddSeconds(DefaultAcceptorLockTimeInSeconds) - RedeemTimeReserve; if (DateTime.UtcNow > redeemDeadline) { Log.Error("Redeem dedline reached for swap {@swap}", swap.Id); return; } } Log.Debug("Create redeem for swap {@swapId}", swap.Id); var gasPrice = await ethConfig .GetGasPriceAsync(cancellationToken) .ConfigureAwait(false); var walletAddress = await _account .GetAddressAsync(swap.RedeemFromAddress, cancellationToken) .ConfigureAwait(false); if (walletAddress == null) { Log.Error("Can't get address {@address} for redeem from local db", swap.RedeemFromAddress); return; } var feeInEth = ethConfig.GetFeeAmount(ethConfig.RedeemGasLimit, gasPrice); if (walletAddress.Balance < feeInEth) { Log.Error("Insufficient funds for redeem"); return; } EthereumTransaction redeemTx; try { await EthereumAccount.AddressLocker .LockAsync(walletAddress.Address, cancellationToken) .ConfigureAwait(false); var nonceResult = await EthereumNonceManager.Instance .GetNonceAsync(ethConfig, walletAddress.Address, pending : true, cancellationToken : cancellationToken) .ConfigureAwait(false); if (nonceResult.HasError) { Log.Error("Nonce getting error with code {@code} and description {@description}", nonceResult.Error.Code, nonceResult.Error.Description); return; } var message = new RedeemFunctionMessage { FromAddress = walletAddress.Address, HashedSecret = swap.SecretHash, Secret = swap.Secret, Nonce = nonceResult.Value, GasPrice = EthereumConfig.GweiToWei(gasPrice), }; message.Gas = await EstimateGasAsync(message, new BigInteger(ethConfig.RedeemGasLimit)) .ConfigureAwait(false); var txInput = message.CreateTransactionInput(ethConfig.SwapContractAddress); redeemTx = new EthereumTransaction(ethConfig.Name, txInput) { Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapRedeem }; var signResult = await SignTransactionAsync(redeemTx, cancellationToken) .ConfigureAwait(false); if (!signResult) { Log.Error("Transaction signing error"); return; } swap.RedeemTx = redeemTx; swap.StateFlags |= SwapStateFlags.IsRedeemSigned; await UpdateSwapAsync(swap, SwapStateFlags.IsRedeemSigned, cancellationToken) .ConfigureAwait(false); await BroadcastTxAsync(swap, redeemTx, cancellationToken) .ConfigureAwait(false); } catch { throw; } finally { EthereumAccount.AddressLocker.Unlock(walletAddress.Address); } swap.RedeemTx = redeemTx; swap.StateFlags |= SwapStateFlags.IsRedeemBroadcast; await UpdateSwapAsync(swap, SwapStateFlags.IsRedeemBroadcast, cancellationToken) .ConfigureAwait(false); _ = TrackTransactionConfirmationAsync( swap: swap, currency: ethConfig, dataRepository: _account.DataRepository, txId: redeemTx.Id, confirmationHandler: RedeemConfirmedEventHandler, cancellationToken: cancellationToken); }
public async Task <Error> SendAsync( string from, string to, decimal amount, decimal gasLimit = 0, decimal gasPrice = 0, bool useDefaultFee = false, CancellationToken cancellationToken = default) { //if (from == to) // return new Error( // code: Errors.SendingAndReceivingAddressesAreSame, // description: "Sending and receiving addresses are the same."); var erc20Config = Erc20Config; if (useDefaultFee) { gasLimit = GasLimitByType(BlockchainTransactionType.Output); gasPrice = await erc20Config .GetGasPriceAsync() .ConfigureAwait(false); } var addressFeeUsage = await SelectUnspentAddressesAsync( from : from, amount : amount, fee : gasLimit, feePrice : gasPrice, cancellationToken : cancellationToken) .ConfigureAwait(false); if (addressFeeUsage == null) { return(new Error( code: Errors.InsufficientFunds, description: "Insufficient funds")); } if (gasLimit < erc20Config.TransferGasLimit) { return(new Error( code: Errors.InsufficientGas, description: "Insufficient gas")); } var feeAmount = erc20Config.GetFeeAmount(gasLimit, gasPrice); Log.Debug("Fee per transaction {@feePerTransaction}. Fee Amount {@feeAmount}", gasLimit, feeAmount); Log.Debug("Send {@amount} of {@currency} from address {@address} with available balance {@balance}", addressFeeUsage.UsedAmount, erc20Config.Name, addressFeeUsage.WalletAddress.Address, addressFeeUsage.WalletAddress.AvailableBalance()); using var addressLock = await EthereumAccount.AddressLocker .GetLockAsync(addressFeeUsage.WalletAddress.Address, cancellationToken) .ConfigureAwait(false); var nonceResult = await EthereumNonceManager.Instance .GetNonceAsync(EthConfig, addressFeeUsage.WalletAddress.Address) .ConfigureAwait(false); if (nonceResult.HasError) { return(nonceResult.Error); } TransactionInput txInput; var message = new ERC20TransferFunctionMessage { To = to.ToLowerInvariant(), Value = erc20Config.TokensToTokenDigits(addressFeeUsage.UsedAmount), FromAddress = addressFeeUsage.WalletAddress.Address, Gas = new BigInteger(gasLimit), GasPrice = new BigInteger(EthereumConfig.GweiToWei(gasPrice)), Nonce = nonceResult.Value }; txInput = message.CreateTransactionInput(erc20Config.ERC20ContractAddress); var tx = new EthereumTransaction(erc20Config.Name, txInput) { Type = BlockchainTransactionType.Output }; var signResult = await Wallet .SignAsync(tx, addressFeeUsage.WalletAddress, erc20Config, cancellationToken) .ConfigureAwait(false); if (!signResult) { return(new Error( code: Errors.TransactionSigningError, description: "Transaction signing error")); } if (!tx.Verify(erc20Config)) { return(new Error( code: Errors.TransactionVerificationError, description: "Transaction verification error")); } var broadcastResult = await erc20Config.BlockchainApi .BroadcastAsync(tx, 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 = erc20Config.TokensToTokenDigits(addressFeeUsage.UsedAmount); tx.To = to.ToLowerInvariant(); await UpsertTransactionAsync( tx : tx, updateBalance : false, notifyIfUnconfirmed : true, notifyIfBalanceUpdated : false, cancellationToken : cancellationToken) .ConfigureAwait(false); var ethTx = tx.Clone(); ethTx.Currency = EthConfig.Name; ethTx.Amount = 0; ethTx.Type = BlockchainTransactionType.TokenCall; await UpsertTransactionAsync( tx : ethTx, updateBalance : false, notifyIfUnconfirmed : true, notifyIfBalanceUpdated : false, cancellationToken : cancellationToken) .ConfigureAwait(false); _ = UpdateBalanceAsync(cancellationToken); return(null); }
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, // todo: account storage fee fee: Eth.RefundGasLimit, feePrice: Eth.GasPriceInGwei, isFeePerTransaction: true, addressUsagePolicy: AddressUsagePolicy.UseOnlyOneAddress) .ConfigureAwait(false)) .FirstOrDefault(); if (walletAddress == null) { Log.Error("Insufficient funds for refund"); return; } var nonce = await EthereumNonceManager.Instance .GetNonce(Eth, walletAddress.Address) .ConfigureAwait(false); var message = new RefundFunctionMessage { FromAddress = walletAddress.Address, HashedSecret = swap.SecretHash, GasPrice = Atomix.Ethereum.GweiToWei(Eth.GasPriceInGwei), Nonce = nonce, }; message.Gas = await EstimateGasAsync(message, new BigInteger(Eth.RefundGasLimit)) .ConfigureAwait(false); var txInput = message.CreateTransactionInput(Eth.SwapContractAddress); var refundTx = new EthereumTransaction(Eth, txInput) { Type = EthereumTransaction.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 }); }
public override async Task RefundAsync( Swap swap, CancellationToken cancellationToken = default) { var eth = Eth; if (swap.StateFlags.HasFlag(SwapStateFlags.IsRefundBroadcast) && swap.RefundTx != null && swap.RefundTx.CreationTime != null && swap.RefundTx.CreationTime.Value.ToUniversalTime() + TimeSpan.FromMinutes(5) > DateTime.UtcNow) { TrackTransactionConfirmationAsync( swap: swap, currency: eth, txId: swap.RefundTx.Id, confirmationHandler: RefundConfirmedEventHandler, cancellationToken: cancellationToken) .FireAndForget(); return; } Log.Debug("Create refund for swap {@swap}", swap.Id); var walletAddress = (await _account .GetUnspentAddressesAsync( toAddress: null, // get refund address amount: 0, fee: 0, feePrice: 0, feeUsagePolicy: FeeUsagePolicy.EstimatedFee, addressUsagePolicy: AddressUsagePolicy.UseOnlyOneAddress, transactionType: BlockchainTransactionType.SwapRefund, cancellationToken: cancellationToken) .ConfigureAwait(false)) .FirstOrDefault(); if (walletAddress == null) { Log.Error("Insufficient funds for refund"); return; } var nonceResult = await EthereumNonceManager.Instance .GetNonceAsync(eth, 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; } var message = new RefundFunctionMessage { FromAddress = walletAddress.Address, HashedSecret = swap.SecretHash, GasPrice = Atomex.Ethereum.GweiToWei(eth.GasPriceInGwei), Nonce = nonceResult.Value, }; message.Gas = await EstimateGasAsync(message, new BigInteger(eth.RefundGasLimit)) .ConfigureAwait(false); var txInput = message.CreateTransactionInput(eth.SwapContractAddress); var refundTx = new EthereumTransaction(eth, txInput) { Type = BlockchainTransactionType.Output | BlockchainTransactionType.SwapRefund }; var signResult = await SignTransactionAsync(refundTx, cancellationToken) .ConfigureAwait(false); if (!signResult) { Log.Error("Transaction signing error"); return; } swap.RefundTx = refundTx; swap.StateFlags |= SwapStateFlags.IsRefundSigned; RaiseSwapUpdated(swap, SwapStateFlags.IsRefundSigned); await BroadcastTxAsync(swap, refundTx, cancellationToken) .ConfigureAwait(false); swap.RefundTx = refundTx; swap.StateFlags |= SwapStateFlags.IsRefundBroadcast; RaiseSwapUpdated(swap, SwapStateFlags.IsRefundBroadcast); TrackTransactionConfirmationAsync( swap: swap, currency: eth, txId: refundTx.Id, confirmationHandler: RefundConfirmedEventHandler, cancellationToken: cancellationToken) .FireAndForget(); }