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