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