Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
6e2d16cb94 |
@ -1,78 +0,0 @@
|
||||
#if ALTCOINS
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public partial class BTCPayNetworkProvider
|
||||
{
|
||||
public void InitEthereum()
|
||||
{
|
||||
Add(new EthereumBTCPayNetwork()
|
||||
{
|
||||
CryptoCode = "ETH",
|
||||
DisplayName = "Ethereum",
|
||||
DefaultRateRules = new[] {"ETH_X = ETH_BTC * BTC_X", "ETH_BTC = kraken(ETH_BTC)"},
|
||||
BlockExplorerLink =
|
||||
NetworkType == ChainName.Mainnet
|
||||
? "https://etherscan.io/address/{0}"
|
||||
: "https://ropsten.etherscan.io/address/{0}",
|
||||
CryptoImagePath = "/imlegacy/eth.png",
|
||||
ShowSyncSummary = true,
|
||||
CoinType = NetworkType == ChainName.Mainnet? 60 : 1,
|
||||
ChainId = NetworkType == ChainName.Mainnet ? 1 : 3,
|
||||
Divisibility = 18,
|
||||
});
|
||||
}
|
||||
|
||||
public void InitERC20()
|
||||
{
|
||||
if (NetworkType != ChainName.Mainnet)
|
||||
{
|
||||
Add(new ERC20BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = "FAU",
|
||||
DisplayName = "Faucet Token",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"FAU_X = FAU_BTC * BTC_X",
|
||||
"FAU_BTC = 0.01",
|
||||
},
|
||||
BlockExplorerLink = "https://ropsten.etherscan.io/address/{0}#tokentxns",
|
||||
ShowSyncSummary = false,
|
||||
CoinType = 1,
|
||||
ChainId = 3,
|
||||
//use https://erc20faucet.com for testnet
|
||||
SmartContractAddress = "0xFab46E002BbF0b4509813474841E0716E6730136",
|
||||
Divisibility = 18,
|
||||
CryptoImagePath = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Add(new ERC20BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = "USDT20",
|
||||
DisplayName = "Tether USD (ERC20)",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"USDT20_UST = 1",
|
||||
"USDT20_X = USDT20_BTC * BTC_X",
|
||||
"USDT20_BTC = bitfinex(UST_BTC)",
|
||||
},
|
||||
BlockExplorerLink =
|
||||
NetworkType == ChainName.Mainnet
|
||||
? "https://etherscan.io/address/{0}#tokentxns"
|
||||
: "https://ropsten.etherscan.io/address/{0}#tokentxns",
|
||||
CryptoImagePath = "/imlegacy/liquid-tether.svg",
|
||||
ShowSyncSummary = false,
|
||||
CoinType = NetworkType == ChainName.Mainnet? 60 : 1,
|
||||
ChainId = NetworkType == ChainName.Mainnet ? 1 : 3,
|
||||
SmartContractAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
Divisibility = 6
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,20 +0,0 @@
|
||||
#if ALTCOINS
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class EthereumBTCPayNetwork : BTCPayNetworkBase
|
||||
{
|
||||
public int ChainId { get; set; }
|
||||
public int CoinType { get; set; }
|
||||
|
||||
public string GetDefaultKeyPath()
|
||||
{
|
||||
return $"m/44'/{CoinType}'/0'/0/x";
|
||||
}
|
||||
}
|
||||
|
||||
public class ERC20BTCPayNetwork : EthereumBTCPayNetwork
|
||||
{
|
||||
public string SmartContractAddress { get; set; }
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,20 +0,0 @@
|
||||
#if ALTCOINS
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public static class EthereumExtensions
|
||||
{
|
||||
|
||||
public static IEnumerable<string> GetAllEthereumSubChains(this BTCPayNetworkProvider networkProvider, BTCPayNetworkProvider unfiltered)
|
||||
{
|
||||
var ethBased = networkProvider.GetAll().OfType<EthereumBTCPayNetwork>();
|
||||
var chainId = ethBased.Select(network => network.ChainId).Distinct();
|
||||
return unfiltered.GetAll().OfType<EthereumBTCPayNetwork>()
|
||||
.Where(network => chainId.Contains(network.ChainId))
|
||||
.Select(network => network.CryptoCode.ToUpperInvariant());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -59,8 +59,6 @@ namespace BTCPayServer
|
||||
InitChaincoin();
|
||||
// InitArgoneum();//their rate source is down 9/15/20.
|
||||
InitMonetaryUnit();
|
||||
InitEthereum();
|
||||
InitERC20();
|
||||
|
||||
// Assume that electrum mappings are same as BTC if not specified
|
||||
foreach (var network in _Networks.Values.OfType<BTCPayNetwork>())
|
||||
|
@ -1,90 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Configuration.Memory;
|
||||
using NBitcoin;
|
||||
using OpenQA.Selenium;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public class EthereumTests : UnitTestBase
|
||||
{
|
||||
public const int TestTimeout = 60_000;
|
||||
|
||||
public EthereumTests(ITestOutputHelper helper) : base(helper)
|
||||
{
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Altcoins", "Altcoins")]
|
||||
public async Task CanUseEthereum()
|
||||
{
|
||||
using var s = CreateSeleniumTester("ETHEREUM", true);
|
||||
s.Server.ActivateETH();
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser(true);
|
||||
|
||||
IWebElement syncSummary = null;
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
syncSummary = s.Driver.FindElement(By.Id("modalDialog"));
|
||||
Assert.True(syncSummary.Displayed);
|
||||
});
|
||||
var web3Link = syncSummary.FindElement(By.LinkText("Configure Web3"));
|
||||
web3Link.Click();
|
||||
s.Driver.FindElement(By.Id("Web3ProviderUrl")).SendKeys("https://ropsten-rpc.linkpool.io");
|
||||
s.Driver.FindElement(By.Id("saveButton")).Click();
|
||||
s.FindAlertMessage();
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
s.Driver.Navigate().Refresh();
|
||||
s.Driver.AssertElementNotFound(By.Id("modalDialog"));
|
||||
});
|
||||
|
||||
var store = s.CreateNewStore();
|
||||
s.Driver.FindElement(By.LinkText("Ethereum")).Click();
|
||||
|
||||
var seed = new Mnemonic(Wordlist.English);
|
||||
s.Driver.FindElement(By.Id("ModifyETH")).Click();
|
||||
s.Driver.FindElement(By.Id("Seed")).SendKeys(seed.ToString());
|
||||
s.Driver.SetCheckbox(By.Id("StoreSeed"), true);
|
||||
s.Driver.SetCheckbox(By.Id("Enabled"), true);
|
||||
s.Driver.FindElement(By.Id("SaveButton")).Click();
|
||||
s.FindAlertMessage();
|
||||
s.Driver.FindElement(By.Id("ModifyUSDT20")).Click();
|
||||
s.Driver.FindElement(By.Id("Seed")).SendKeys(seed.ToString());
|
||||
s.Driver.SetCheckbox(By.Id("StoreSeed"), true);
|
||||
s.Driver.SetCheckbox(By.Id("Enabled"), true);
|
||||
s.Driver.FindElement(By.Id("SaveButton")).Click();
|
||||
s.FindAlertMessage();
|
||||
|
||||
var invoiceId = s.CreateInvoice(store.storeName, 10);
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
var currencyDropdownButton = s.Driver.FindElement(By.ClassName("payment__currencies"));
|
||||
Assert.Contains("ETH", currencyDropdownButton.Text);
|
||||
s.Driver.FindElement(By.Id("copy-tab")).Click();
|
||||
|
||||
var ethAddress = s.Driver.FindElements(By.ClassName("copySectionBox"))
|
||||
.Single(element => element.FindElement(By.TagName("label")).Text
|
||||
.Contains("Address", StringComparison.InvariantCultureIgnoreCase)).FindElement(By.TagName("input"))
|
||||
.GetAttribute("value");
|
||||
currencyDropdownButton.Click();
|
||||
var elements = s.Driver.FindElement(By.ClassName("vex-content")).FindElements(By.ClassName("vexmenuitem"));
|
||||
Assert.Equal(2, elements.Count);
|
||||
|
||||
elements.Single(element => element.Text.Contains("USDT20")).Click();
|
||||
s.Driver.FindElement(By.Id("copy-tab")).Click();
|
||||
var usdtAddress = s.Driver.FindElements(By.ClassName("copySectionBox"))
|
||||
.Single(element => element.FindElement(By.TagName("label")).Text
|
||||
.Contains("Address", StringComparison.InvariantCultureIgnoreCase)).FindElement(By.TagName("input"))
|
||||
.GetAttribute("value");
|
||||
Assert.Equal(usdtAddress, ethAddress);
|
||||
}
|
||||
}
|
||||
}
|
@ -1442,13 +1442,11 @@ namespace BTCPayServer.Tests
|
||||
new MemoryConfigurationProvider(new MemoryConfigurationSource()
|
||||
{
|
||||
InitialData = new[] {
|
||||
new KeyValuePair<string, string>("chains", "usdt20,usdt")}
|
||||
new KeyValuePair<string, string>("chains", "usdt")}
|
||||
})
|
||||
});
|
||||
|
||||
var networkProvider = config.ConfigureNetworkProvider(BTCPayLogs);
|
||||
Assert.NotNull(networkProvider.GetNetwork("ETH"));
|
||||
Assert.NotNull(networkProvider.GetNetwork("USDT20"));
|
||||
Assert.NotNull(networkProvider.GetNetwork("LBTC"));
|
||||
Assert.NotNull(networkProvider.GetNetwork("USDT"));
|
||||
}
|
||||
|
@ -37,18 +37,10 @@
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="bundleconfig.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(Altcoins)' == 'true'">
|
||||
<PackageReference Include="Nethereum.ABI" Version="4.1.0" />
|
||||
<PackageReference Include="Nethereum.HdWallet" Version="4.1.0" />
|
||||
<PackageReference Include="Nethereum.StandardTokenEIP20" Version="4.1.0" />
|
||||
<PackageReference Include="Nethereum.Web3" Version="4.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(Altcoins)' != 'true'">
|
||||
<Content Remove="Services\Altcoins\**\*" />
|
||||
<Content Remove="Views\EthereumLikeStore\**\*" />
|
||||
<Content Remove="Views\MoneroLikeStore\**\*" />
|
||||
<Content Remove="Views\Shared\Ethereum\**\*" />
|
||||
<Content Remove="Views\Shared\Monero\**\*" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -468,12 +468,18 @@ namespace BTCPayServer
|
||||
var supportedChains = configuration.GetOrDefault<string>("chains", "btc")
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(t => t.ToUpperInvariant()).ToHashSet();
|
||||
|
||||
foreach (var c in supportedChains.ToList())
|
||||
{
|
||||
if (new[] { "ETH", "USDT20", "FAU" }.Contains(c, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
logs.Configuration.LogWarning($"'{c}' is not anymore supported, please remove it from 'chains'");
|
||||
supportedChains.Remove(c);
|
||||
}
|
||||
}
|
||||
var networkProvider = new BTCPayNetworkProvider(_networkType);
|
||||
var filtered = networkProvider.Filter(supportedChains.ToArray());
|
||||
#if ALTCOINS
|
||||
supportedChains.AddRange(filtered.GetAllElementsSubChains(networkProvider));
|
||||
supportedChains.AddRange(filtered.GetAllEthereumSubChains(networkProvider));
|
||||
#endif
|
||||
#if !ALTCOINS
|
||||
var onlyBTC = supportedChains.Count == 1 && supportedChains.First() == "BTC";
|
||||
|
@ -59,7 +59,6 @@ using Serilog;
|
||||
using NBitcoin.RPC;
|
||||
#if ALTCOINS
|
||||
using BTCPayServer.Services.Altcoins.Monero;
|
||||
using BTCPayServer.Services.Altcoins.Ethereum;
|
||||
#endif
|
||||
namespace BTCPayServer.Hosting
|
||||
{
|
||||
@ -90,7 +89,6 @@ namespace BTCPayServer.Hosting
|
||||
services.AddPayJoinServices();
|
||||
#if ALTCOINS
|
||||
services.AddMoneroLike();
|
||||
services.AddEthereumLike();
|
||||
#endif
|
||||
services.TryAddSingleton<SettingsRepository>();
|
||||
services.TryAddSingleton<ISettingsRepository>(provider => provider.GetService<SettingsRepository>());
|
||||
|
@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
#if ALTCOINS
|
||||
using BTCPayServer.Services.Altcoins.Ethereum.Payments;
|
||||
using BTCPayServer.Services.Altcoins.Monero.Payments;
|
||||
#endif
|
||||
using BTCPayServer.Services.Invoices;
|
||||
@ -20,7 +19,6 @@ namespace BTCPayServer.Payments
|
||||
BTCLike, LightningLike, LNURLPay,
|
||||
#if ALTCOINS
|
||||
MoneroLike,
|
||||
EthereumPaymentType.Instance
|
||||
#endif
|
||||
};
|
||||
/// <summary>
|
||||
|
@ -86,7 +86,7 @@
|
||||
"BTCPAY_ALLOW-ADMIN-REGISTRATION": "true",
|
||||
"BTCPAY_DISABLE-REGISTRATION": "false",
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"BTCPAY_CHAINS": "btc,ltc,lbtc,eth",
|
||||
"BTCPAY_CHAINS": "btc,ltc,lbtc",
|
||||
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver",
|
||||
"BTCPAY_EXTERNALSERVICES": "totoservice:totolink;",
|
||||
"BTCPAY_SSHCONNECTION": "root@127.0.0.1:21622",
|
||||
|
@ -1,29 +0,0 @@
|
||||
#if ALTCOINS
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Ethereum.Configuration
|
||||
{
|
||||
public class EthereumLikeConfiguration
|
||||
{
|
||||
public static string SettingsKey(int chainId)
|
||||
{
|
||||
return $"{nameof(EthereumLikeConfiguration)}_{chainId}";
|
||||
}
|
||||
public int ChainId { get; set; }
|
||||
[Display(Name = "Web3 provider url")]
|
||||
public string Web3ProviderUrl { get; set; }
|
||||
|
||||
[Display(Name = "Web3 provider username (can be left blank)")]
|
||||
public string Web3ProviderUsername { get; set; }
|
||||
|
||||
[Display(Name = "Web3 provider password (can be left blank)")]
|
||||
public string Web3ProviderPassword { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -1,43 +0,0 @@
|
||||
#if ALTCOINS
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Services;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services.Altcoins.Ethereum.Payments;
|
||||
using BTCPayServer.Services.Altcoins.Ethereum.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Ethereum
|
||||
{
|
||||
public static class EthereumLikeExtensions
|
||||
{
|
||||
public const string EthereumInvoiceCheckHttpClient = "EthereumCheck";
|
||||
public const string EthereumInvoiceCreateHttpClient = "EthereumCreate";
|
||||
public static IServiceCollection AddEthereumLike(this IServiceCollection serviceCollection)
|
||||
{
|
||||
serviceCollection.AddSingleton<EthereumService>();
|
||||
serviceCollection.AddSingleton<IHostedService, EthereumService>(provider => provider.GetService<EthereumService>());
|
||||
serviceCollection.AddSingleton<EthereumLikePaymentMethodHandler>();
|
||||
serviceCollection.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<EthereumLikePaymentMethodHandler>());
|
||||
|
||||
serviceCollection.AddSingleton<IUIExtension>(new UIExtension("Ethereum/StoreNavEthereumExtension", "store-nav"));
|
||||
serviceCollection.AddTransient<NoRedirectHttpClientHandler>();
|
||||
serviceCollection.AddSingleton<ISyncSummaryProvider, EthereumSyncSummaryProvider>();
|
||||
serviceCollection.AddHttpClient(EthereumInvoiceCreateHttpClient)
|
||||
.ConfigurePrimaryHttpMessageHandler<NoRedirectHttpClientHandler>();
|
||||
return serviceCollection;
|
||||
}
|
||||
}
|
||||
|
||||
public class NoRedirectHttpClientHandler : HttpClientHandler
|
||||
{
|
||||
public NoRedirectHttpClientHandler()
|
||||
{
|
||||
this.AllowAutoRedirect = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,26 +0,0 @@
|
||||
#if ALTCOINS
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Ethereum.Filters
|
||||
{
|
||||
public class OnlyIfSupportEthAttribute : Attribute, IAsyncActionFilter
|
||||
{
|
||||
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
var options = context.HttpContext.RequestServices.GetService<BTCPayNetworkProvider>();
|
||||
if (!options.GetAll().OfType<EthereumBTCPayNetwork>().Any())
|
||||
{
|
||||
context.Result = new NotFoundResult();
|
||||
return;
|
||||
}
|
||||
|
||||
await next();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,44 +0,0 @@
|
||||
#if ALTCOINS
|
||||
using BTCPayServer.Payments;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Ethereum.Payments
|
||||
{
|
||||
public class EthereumLikeOnChainPaymentMethodDetails : IPaymentMethodDetails
|
||||
{
|
||||
public PaymentType GetPaymentType()
|
||||
{
|
||||
return EthereumPaymentType.Instance;
|
||||
}
|
||||
|
||||
public string GetPaymentDestination()
|
||||
{
|
||||
return DepositAddress;
|
||||
}
|
||||
|
||||
public decimal GetNextNetworkFee()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public decimal GetFeeRate()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void SetPaymentDetails(IPaymentMethodDetails newPaymentMethodDetails)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public void SetPaymentDestination(string newPaymentDestination)
|
||||
{
|
||||
DepositAddress = newPaymentDestination;
|
||||
}
|
||||
|
||||
public bool Activated { get; set; }
|
||||
public long Index { get; set; }
|
||||
public string XPub { get; set; }
|
||||
public string DepositAddress { get; set; }
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,77 +0,0 @@
|
||||
#if ALTCOINS
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Nethereum.Hex.HexTypes;
|
||||
using Nethereum.Web3;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Ethereum.Payments
|
||||
{
|
||||
public class EthereumLikePaymentData : CryptoPaymentData
|
||||
{
|
||||
public long Amount { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public string Address { get; set; }
|
||||
public long AccountIndex { get; set; }
|
||||
public string XPub { get; set; }
|
||||
public long ConfirmationCount { get; set; }
|
||||
public BTCPayNetworkBase Network { get; set; }
|
||||
public long? BlockNumber { get; set; }
|
||||
|
||||
public string GetPaymentId()
|
||||
{
|
||||
return GetPaymentId(CryptoCode,Address, Amount);
|
||||
}
|
||||
|
||||
public static string GetPaymentId(string cryptoCode, string address, long amount)
|
||||
{
|
||||
return $"{cryptoCode}#{address}#{amount}";
|
||||
}
|
||||
|
||||
public string[] GetSearchTerms()
|
||||
{
|
||||
return new[] {Address};
|
||||
}
|
||||
|
||||
public decimal GetValue()
|
||||
{
|
||||
return decimal.Parse(Web3.Convert.FromWeiToBigDecimal(Amount, Network.Divisibility).ToString(),
|
||||
CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public bool PaymentCompleted(PaymentEntity entity)
|
||||
{
|
||||
return ConfirmationCount >= 25;
|
||||
}
|
||||
|
||||
public bool PaymentConfirmed(PaymentEntity entity, SpeedPolicy speedPolicy)
|
||||
{
|
||||
switch (speedPolicy)
|
||||
{
|
||||
case SpeedPolicy.HighSpeed:
|
||||
return ConfirmationCount >= 2;
|
||||
case SpeedPolicy.MediumSpeed:
|
||||
return ConfirmationCount >= 6;
|
||||
case SpeedPolicy.LowMediumSpeed:
|
||||
return ConfirmationCount >= 12;
|
||||
case SpeedPolicy.LowSpeed:
|
||||
return ConfirmationCount >= 20;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public PaymentType GetPaymentType()
|
||||
{
|
||||
return EthereumPaymentType.Instance;
|
||||
}
|
||||
|
||||
public string GetDestination()
|
||||
{
|
||||
return Address;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,133 +0,0 @@
|
||||
#if ALTCOINS
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services.Altcoins.Ethereum.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Ethereum.Payments
|
||||
{
|
||||
public class
|
||||
EthereumLikePaymentMethodHandler : PaymentMethodHandlerBase<EthereumSupportedPaymentMethod,
|
||||
EthereumBTCPayNetwork>
|
||||
{
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
private readonly EthereumService _ethereumService;
|
||||
|
||||
public EthereumLikePaymentMethodHandler(BTCPayNetworkProvider networkProvider, EthereumService ethereumService)
|
||||
{
|
||||
_networkProvider = networkProvider;
|
||||
_ethereumService = ethereumService;
|
||||
}
|
||||
|
||||
public override PaymentType PaymentType => EthereumPaymentType.Instance;
|
||||
|
||||
public override async Task<IPaymentMethodDetails> CreatePaymentMethodDetails(InvoiceLogs logs,
|
||||
EthereumSupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod,
|
||||
StoreData store, EthereumBTCPayNetwork network, object preparePaymentObject)
|
||||
{
|
||||
if (preparePaymentObject is null)
|
||||
{
|
||||
return new EthereumLikeOnChainPaymentMethodDetails()
|
||||
{
|
||||
Activated = false
|
||||
};
|
||||
}
|
||||
if (!_ethereumService.IsAvailable(network.CryptoCode, out var error))
|
||||
throw new PaymentMethodUnavailableException(error??$"Not configured yet");
|
||||
var invoice = paymentMethod.ParentEntity;
|
||||
if (!(preparePaymentObject is Prepare ethPrepare)) throw new ArgumentException();
|
||||
var address = await ethPrepare.ReserveAddress(invoice.Id);
|
||||
if (address is null || address.Failed)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException($"could not generate address");
|
||||
}
|
||||
|
||||
return new EthereumLikeOnChainPaymentMethodDetails()
|
||||
{
|
||||
DepositAddress = address.Address, Index = address.Index, XPub = address.XPub, Activated = true
|
||||
};
|
||||
}
|
||||
|
||||
public override object PreparePayment(EthereumSupportedPaymentMethod supportedPaymentMethod, StoreData store,
|
||||
BTCPayNetworkBase network)
|
||||
{
|
||||
return new Prepare()
|
||||
{
|
||||
ReserveAddress = s =>
|
||||
_ethereumService.ReserveNextAddress(
|
||||
new EthereumService.ReserveEthereumAddress()
|
||||
{
|
||||
StoreId = store.Id, CryptoCode = network.CryptoCode
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
class Prepare
|
||||
{
|
||||
public Func<string, Task<EthereumService.ReserveEthereumAddressResponse>> ReserveAddress;
|
||||
}
|
||||
|
||||
public override void PreparePaymentModel(PaymentModel model, InvoiceResponse invoiceResponse,
|
||||
StoreBlob storeBlob, IPaymentMethod paymentMethod)
|
||||
{
|
||||
var paymentMethodId = paymentMethod.GetId();
|
||||
var cryptoInfo = invoiceResponse.CryptoInfo.First(o => o.GetpaymentMethodId() == paymentMethodId);
|
||||
var network = _networkProvider.GetNetwork<EthereumBTCPayNetwork>(model.CryptoCode);
|
||||
model.PaymentMethodName = GetPaymentMethodName(network);
|
||||
model.CryptoImage = GetCryptoImage(network);
|
||||
model.InvoiceBitcoinUrl = "";
|
||||
model.InvoiceBitcoinUrlQR = cryptoInfo.Address ?? "";
|
||||
}
|
||||
|
||||
public override string GetCryptoImage(PaymentMethodId paymentMethodId)
|
||||
{
|
||||
var network = _networkProvider.GetNetwork<EthereumBTCPayNetwork>(paymentMethodId.CryptoCode);
|
||||
return GetCryptoImage(network);
|
||||
}
|
||||
|
||||
public override string GetPaymentMethodName(PaymentMethodId paymentMethodId)
|
||||
{
|
||||
var network = _networkProvider.GetNetwork<EthereumBTCPayNetwork>(paymentMethodId.CryptoCode);
|
||||
return GetPaymentMethodName(network);
|
||||
}
|
||||
|
||||
public override IEnumerable<PaymentMethodId> GetSupportedPaymentMethods()
|
||||
{
|
||||
return _networkProvider.GetAll().OfType<EthereumBTCPayNetwork>()
|
||||
.Select(network => new PaymentMethodId(network.CryptoCode, PaymentType));
|
||||
}
|
||||
|
||||
public override CheckoutUIPaymentMethodSettings GetCheckoutUISettings()
|
||||
{
|
||||
return new CheckoutUIPaymentMethodSettings()
|
||||
{
|
||||
ExtensionPartial = "Ethereum/EthereumLikeMethodCheckout",
|
||||
CheckoutBodyVueComponentName = "EthereumLikeMethodCheckout",
|
||||
CheckoutHeaderVueComponentName = "EthereumLikeMethodCheckoutHeader",
|
||||
NoScriptPartialName = "Bitcoin/BitcoinLikeMethodCheckoutNoScript"
|
||||
};
|
||||
}
|
||||
|
||||
private string GetCryptoImage(EthereumBTCPayNetwork network)
|
||||
{
|
||||
return network.CryptoImagePath;
|
||||
}
|
||||
|
||||
|
||||
private string GetPaymentMethodName(EthereumBTCPayNetwork network)
|
||||
{
|
||||
return $"{network.DisplayName}";
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,75 +0,0 @@
|
||||
#if ALTCOINS
|
||||
using System.Globalization;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Ethereum.Payments
|
||||
{
|
||||
public class EthereumPaymentType: PaymentType
|
||||
{
|
||||
public static EthereumPaymentType Instance { get; } = new EthereumPaymentType();
|
||||
public override string ToPrettyString() => "On-Chain";
|
||||
|
||||
public override string GetId()=> "EthereumLike";
|
||||
|
||||
|
||||
public override CryptoPaymentData DeserializePaymentData(BTCPayNetworkBase network, string str)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<EthereumLikePaymentData>(str);
|
||||
}
|
||||
|
||||
public override string SerializePaymentData(BTCPayNetworkBase network, CryptoPaymentData paymentData)
|
||||
{
|
||||
return JsonConvert.SerializeObject(paymentData);
|
||||
}
|
||||
|
||||
public override IPaymentMethodDetails DeserializePaymentMethodDetails(BTCPayNetworkBase network, string str)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<EthereumLikeOnChainPaymentMethodDetails>(str);
|
||||
}
|
||||
|
||||
public override string SerializePaymentMethodDetails(BTCPayNetworkBase network, IPaymentMethodDetails details)
|
||||
{
|
||||
return JsonConvert.SerializeObject(details);
|
||||
}
|
||||
|
||||
public override ISupportedPaymentMethod DeserializeSupportedPaymentMethod(BTCPayNetworkBase network, JToken value)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<EthereumSupportedPaymentMethod>(value.ToString());
|
||||
}
|
||||
|
||||
public override string GetTransactionLink(BTCPayNetworkBase network, string txId)
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink, txId);
|
||||
}
|
||||
|
||||
public override string GetPaymentLink(BTCPayNetworkBase network, IPaymentMethodDetails paymentMethodDetails, Money cryptoInfoDue,
|
||||
string serverUri)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
public override string InvoiceViewPaymentPartialName { get; }= "Ethereum/ViewEthereumLikePaymentData";
|
||||
public override object GetGreenfieldData(ISupportedPaymentMethod supportedPaymentMethod, bool canModifyStore)
|
||||
{
|
||||
if (supportedPaymentMethod is EthereumSupportedPaymentMethod ethereumSupportedPaymentMethod)
|
||||
{
|
||||
return new
|
||||
{
|
||||
ethereumSupportedPaymentMethod.XPub
|
||||
//no clue what all those properties saved are and don't care.
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void PopulateCryptoInfo(PaymentMethod details, InvoiceCryptoInfo invoiceCryptoInfo, string serverUrl)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,41 +0,0 @@
|
||||
#if ALTCOINS
|
||||
using System;
|
||||
using BTCPayServer.Payments;
|
||||
using NBitcoin;
|
||||
using Nethereum.HdWallet;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Ethereum.Payments
|
||||
{
|
||||
public class EthereumSupportedPaymentMethod : ISupportedPaymentMethod
|
||||
{
|
||||
public string CryptoCode { get; set; }
|
||||
public string Seed { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string XPub { get; set; }
|
||||
public PaymentMethodId PaymentId => new PaymentMethodId(CryptoCode, EthereumPaymentType.Instance);
|
||||
public long CurrentIndex { get; set; }
|
||||
public string KeyPath { get; set; }
|
||||
|
||||
public Func<int, string> GetWalletDerivator()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(XPub))
|
||||
{
|
||||
try
|
||||
{
|
||||
return new PublicWallet(XPub).GetAddress;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return new PublicWallet(new BitcoinExtPubKey(XPub, Network.Main).ExtPubKey).GetAddress;
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(XPub))
|
||||
{
|
||||
return i => new Wallet(Seed, Password, KeyPath).GetAccount(i).Address;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,317 +0,0 @@
|
||||
#if ALTCOINS
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Services.Altcoins.Ethereum.Payments;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Altcoins.Ethereum.Configuration;
|
||||
using BTCPayServer.Services.Altcoins.Ethereum.UI;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Ethereum.Services
|
||||
{
|
||||
public class EthereumService : EventHostedServiceBase
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly SettingsRepository _settingsRepository;
|
||||
private readonly InvoiceRepository _invoiceRepository;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly PaymentService _paymentService;
|
||||
private readonly Dictionary<int, EthereumWatcher> _chainHostedServices = new Dictionary<int, EthereumWatcher>();
|
||||
|
||||
private readonly Dictionary<int, CancellationTokenSource> _chainHostedServiceCancellationTokenSources =
|
||||
new Dictionary<int, CancellationTokenSource>();
|
||||
|
||||
public EthereumService(
|
||||
IHttpClientFactory httpClientFactory,
|
||||
EventAggregator eventAggregator,
|
||||
StoreRepository storeRepository,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
SettingsRepository settingsRepository,
|
||||
InvoiceRepository invoiceRepository,
|
||||
IConfiguration configuration,
|
||||
PaymentService paymentService,
|
||||
Logs logs) : base(eventAggregator, logs)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_eventAggregator = eventAggregator;
|
||||
_storeRepository = storeRepository;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_settingsRepository = settingsRepository;
|
||||
_invoiceRepository = invoiceRepository;
|
||||
_configuration = configuration;
|
||||
_paymentService = paymentService;
|
||||
}
|
||||
|
||||
public override async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var chainIds = _btcPayNetworkProvider.GetAll().OfType<EthereumBTCPayNetwork>()
|
||||
.Select(network => network.ChainId).Distinct().ToList();
|
||||
if (!chainIds.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await base.StartAsync(cancellationToken);
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_eventAggregator.Publish(new CheckWatchers());
|
||||
await Task.Delay(IsAllAvailable() ? TimeSpan.FromDays(1) : TimeSpan.FromSeconds(5),
|
||||
cancellationToken);
|
||||
}
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
private static bool First = true;
|
||||
|
||||
private async Task LoopThroughChainWatchers(CancellationToken cancellationToken)
|
||||
{
|
||||
var chainIds = _btcPayNetworkProvider.GetAll().OfType<EthereumBTCPayNetwork>()
|
||||
.Select(network => network.ChainId).Distinct().ToList();
|
||||
foreach (var chainId in chainIds)
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = await _settingsRepository.GetSettingAsync<EthereumLikeConfiguration>(
|
||||
EthereumLikeConfiguration.SettingsKey(chainId));
|
||||
if (settings is null || string.IsNullOrEmpty(settings.Web3ProviderUrl))
|
||||
{
|
||||
var val = _configuration.GetValue<string>($"chain{chainId}_web3", null);
|
||||
var valUser = _configuration.GetValue<string>($"chain{chainId}_web3_user", null);
|
||||
var valPass = _configuration.GetValue<string>($"chain{chainId}_web3_password", null);
|
||||
if (val != null && First)
|
||||
{
|
||||
Logs.PayServer.LogInformation($"Setting eth chain {chainId} web3 to {val}");
|
||||
settings ??= new EthereumLikeConfiguration()
|
||||
{
|
||||
ChainId = chainId,
|
||||
Web3ProviderUrl = val,
|
||||
Web3ProviderPassword = valPass,
|
||||
Web3ProviderUsername = valUser
|
||||
};
|
||||
await _settingsRepository.UpdateSetting(settings,
|
||||
EthereumLikeConfiguration.SettingsKey(chainId));
|
||||
}
|
||||
}
|
||||
|
||||
var currentlyRunning = _chainHostedServices.ContainsKey(chainId);
|
||||
if (!currentlyRunning || (currentlyRunning))
|
||||
{
|
||||
await HandleChainWatcher(settings, cancellationToken);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
First = false;
|
||||
}
|
||||
|
||||
public override Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var chainHostedService in _chainHostedServices.Values)
|
||||
{
|
||||
_ = chainHostedService.StopAsync(cancellationToken);
|
||||
}
|
||||
|
||||
return base.StopAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override void SubscribeToEvents()
|
||||
{
|
||||
base.SubscribeToEvents();
|
||||
|
||||
Subscribe<ReserveEthereumAddress>();
|
||||
Subscribe<SettingsChanged<EthereumLikeConfiguration>>();
|
||||
Subscribe<CheckWatchers>();
|
||||
}
|
||||
|
||||
protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken)
|
||||
{
|
||||
if (evt is ReserveEthereumAddress reserveEthereumAddress)
|
||||
{
|
||||
await HandleReserveNextAddress(reserveEthereumAddress);
|
||||
}
|
||||
|
||||
if (evt is SettingsChanged<EthereumLikeConfiguration> settingsChangedEthConfig)
|
||||
{
|
||||
await HandleChainWatcher(settingsChangedEthConfig.Settings, cancellationToken);
|
||||
}
|
||||
|
||||
if (evt is CheckWatchers)
|
||||
{
|
||||
await LoopThroughChainWatchers(cancellationToken);
|
||||
}
|
||||
|
||||
await base.ProcessEvent(evt, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task HandleChainWatcher(EthereumLikeConfiguration ethereumLikeConfiguration,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (ethereumLikeConfiguration is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_chainHostedServiceCancellationTokenSources.ContainsKey(ethereumLikeConfiguration.ChainId))
|
||||
{
|
||||
_chainHostedServiceCancellationTokenSources[ethereumLikeConfiguration.ChainId].Cancel();
|
||||
_chainHostedServiceCancellationTokenSources.Remove(ethereumLikeConfiguration.ChainId);
|
||||
}
|
||||
|
||||
if (_chainHostedServices.ContainsKey(ethereumLikeConfiguration.ChainId))
|
||||
{
|
||||
await _chainHostedServices[ethereumLikeConfiguration.ChainId].StopAsync(cancellationToken);
|
||||
_chainHostedServices.Remove(ethereumLikeConfiguration.ChainId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(ethereumLikeConfiguration.Web3ProviderUrl))
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
_chainHostedServiceCancellationTokenSources.AddOrReplace(ethereumLikeConfiguration.ChainId, cts);
|
||||
_chainHostedServices.AddOrReplace(ethereumLikeConfiguration.ChainId,
|
||||
new EthereumWatcher(ethereumLikeConfiguration.ChainId, ethereumLikeConfiguration,
|
||||
_btcPayNetworkProvider, _eventAggregator, _invoiceRepository, _paymentService, Logs));
|
||||
await _chainHostedServices[ethereumLikeConfiguration.ChainId].StartAsync(CancellationTokenSource
|
||||
.CreateLinkedTokenSource(cancellationToken, cts.Token).Token);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleReserveNextAddress(ReserveEthereumAddress reserveEthereumAddress)
|
||||
{
|
||||
var store = await _storeRepository.FindStore(reserveEthereumAddress.StoreId);
|
||||
var ethereumSupportedPaymentMethod = store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
||||
.OfType<EthereumSupportedPaymentMethod>()
|
||||
.SingleOrDefault(method => method.PaymentId.CryptoCode == reserveEthereumAddress.CryptoCode);
|
||||
if (ethereumSupportedPaymentMethod == null)
|
||||
{
|
||||
_eventAggregator.Publish(new ReserveEthereumAddressResponse()
|
||||
{
|
||||
OpId = reserveEthereumAddress.OpId, Failed = true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
ethereumSupportedPaymentMethod.CurrentIndex++;
|
||||
var address = ethereumSupportedPaymentMethod.GetWalletDerivator()?
|
||||
.Invoke((int)ethereumSupportedPaymentMethod.CurrentIndex);
|
||||
|
||||
if (string.IsNullOrEmpty(address))
|
||||
{
|
||||
_eventAggregator.Publish(new ReserveEthereumAddressResponse()
|
||||
{
|
||||
OpId = reserveEthereumAddress.OpId, Failed = true
|
||||
});
|
||||
return;
|
||||
}
|
||||
store.SetSupportedPaymentMethod(ethereumSupportedPaymentMethod.PaymentId,
|
||||
ethereumSupportedPaymentMethod);
|
||||
await _storeRepository.UpdateStore(store);
|
||||
_eventAggregator.Publish(new ReserveEthereumAddressResponse()
|
||||
{
|
||||
Address = address,
|
||||
Index = ethereumSupportedPaymentMethod.CurrentIndex,
|
||||
CryptoCode = ethereumSupportedPaymentMethod.CryptoCode,
|
||||
OpId = reserveEthereumAddress.OpId,
|
||||
StoreId = reserveEthereumAddress.StoreId,
|
||||
XPub = ethereumSupportedPaymentMethod.XPub
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<ReserveEthereumAddressResponse> ReserveNextAddress(ReserveEthereumAddress address)
|
||||
{
|
||||
address.OpId = string.IsNullOrEmpty(address.OpId) ? Guid.NewGuid().ToString() : address.OpId;
|
||||
var tcs = new TaskCompletionSource<ReserveEthereumAddressResponse>();
|
||||
var subscription = _eventAggregator.Subscribe<ReserveEthereumAddressResponse>(response =>
|
||||
{
|
||||
if (response.OpId == address.OpId)
|
||||
{
|
||||
tcs.SetResult(response);
|
||||
}
|
||||
});
|
||||
_eventAggregator.Publish(address);
|
||||
|
||||
if (tcs.Task.Wait(TimeSpan.FromSeconds(60)))
|
||||
{
|
||||
subscription?.Dispose();
|
||||
return await tcs.Task;
|
||||
}
|
||||
|
||||
subscription?.Dispose();
|
||||
return null;
|
||||
}
|
||||
|
||||
public class CheckWatchers
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public class ReserveEthereumAddressResponse
|
||||
{
|
||||
public string StoreId { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public string Address { get; set; }
|
||||
public long Index { get; set; }
|
||||
public string OpId { get; set; }
|
||||
public string XPub { get; set; }
|
||||
public bool Failed { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Reserved {CryptoCode} address {Address} for store {StoreId}";
|
||||
}
|
||||
}
|
||||
|
||||
public class ReserveEthereumAddress
|
||||
{
|
||||
public string StoreId { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public string OpId { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Reserving {CryptoCode} address for store {StoreId}";
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAllAvailable()
|
||||
{
|
||||
return _btcPayNetworkProvider.GetAll().OfType<EthereumBTCPayNetwork>()
|
||||
.All(network => IsAvailable(network.CryptoCode, out _));
|
||||
}
|
||||
|
||||
public bool IsAvailable(string networkCryptoCode, out string error)
|
||||
{
|
||||
error = null;
|
||||
var chainId = _btcPayNetworkProvider.GetNetwork<EthereumBTCPayNetwork>(networkCryptoCode)?.ChainId;
|
||||
if (chainId != null && _chainHostedServices.TryGetValue(chainId.Value, out var watcher))
|
||||
{
|
||||
error = watcher.GlobalError;
|
||||
return string.IsNullOrEmpty(watcher.GlobalError);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,45 +0,0 @@
|
||||
#if ALTCOINS
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Ethereum.Services
|
||||
{
|
||||
public class EthereumSyncSummaryProvider : ISyncSummaryProvider
|
||||
{
|
||||
private readonly EthereumService _ethereumService;
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
|
||||
public EthereumSyncSummaryProvider(EthereumService ethereumService, BTCPayNetworkProvider btcPayNetworkProvider)
|
||||
{
|
||||
_ethereumService = ethereumService;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
}
|
||||
|
||||
public bool AllAvailable()
|
||||
{
|
||||
return _ethereumService.IsAllAvailable();
|
||||
}
|
||||
|
||||
public string Partial { get; } = "Ethereum/ETHSyncSummary";
|
||||
public IEnumerable<ISyncStatus> GetStatuses()
|
||||
{
|
||||
return _btcPayNetworkProvider
|
||||
.GetAll()
|
||||
.OfType<EthereumBTCPayNetwork>()
|
||||
.Where(network => !(network is ERC20BTCPayNetwork))
|
||||
.Select(network => network.CryptoCode).Select(network => new SyncStatus()
|
||||
{
|
||||
CryptoCode = network,
|
||||
Available = _ethereumService.IsAvailable(network, out _)
|
||||
});
|
||||
}
|
||||
|
||||
public class SyncStatus : ISyncStatus
|
||||
{
|
||||
public string CryptoCode { get; set; }
|
||||
public bool Available { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,381 +0,0 @@
|
||||
#if ALTCOINS
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services.Altcoins.Ethereum.Configuration;
|
||||
using BTCPayServer.Services.Altcoins.Ethereum.Payments;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin.Logging;
|
||||
using Nethereum.RPC.Eth.DTOs;
|
||||
using Nethereum.StandardTokenEIP20.ContractDefinition;
|
||||
using Nethereum.Web3;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Ethereum.Services
|
||||
{
|
||||
public class EthereumWatcher : EventHostedServiceBase
|
||||
{
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly InvoiceRepository _invoiceRepository;
|
||||
private readonly PaymentService _paymentService;
|
||||
private int ChainId { get; }
|
||||
private readonly HashSet<PaymentMethodId> PaymentMethods;
|
||||
|
||||
private readonly Web3 Web3;
|
||||
private readonly List<EthereumBTCPayNetwork> Networks;
|
||||
public string GlobalError { get; private set; } = "The chain watcher is still starting.";
|
||||
|
||||
public override async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Logs.PayServer.LogInformation($"Starting EthereumWatcher for chain {ChainId}");
|
||||
var result = await Web3.Eth.ChainId.SendRequestAsync();
|
||||
if (result.Value != ChainId)
|
||||
{
|
||||
GlobalError =
|
||||
$"The web3 client is connected to a different chain id. Expected {ChainId} but Web3 returned {result.Value}";
|
||||
return;
|
||||
}
|
||||
|
||||
await base.StartAsync(cancellationToken);
|
||||
_eventAggregator.Publish(new CatchUp());
|
||||
GlobalError = null;
|
||||
}
|
||||
|
||||
protected override void SubscribeToEvents()
|
||||
{
|
||||
Subscribe<EthereumService.ReserveEthereumAddressResponse>();
|
||||
Subscribe<EthereumAddressBalanceFetched>();
|
||||
Subscribe<CatchUp>();
|
||||
base.SubscribeToEvents();
|
||||
}
|
||||
|
||||
protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken)
|
||||
{
|
||||
if (evt is CatchUp)
|
||||
{
|
||||
DateTimeOffset start = DateTimeOffset.Now;
|
||||
await UpdateAnyPendingEthLikePaymentAndAddressWatchList(cancellationToken);
|
||||
|
||||
TimeSpan diff = start - DateTimeOffset.Now;
|
||||
if (diff.TotalSeconds < 5)
|
||||
{
|
||||
_ = Task.Delay(TimeSpan.FromSeconds(5 - diff.TotalSeconds), cancellationToken).ContinueWith(task =>
|
||||
{
|
||||
_eventAggregator.Publish(new CatchUp());
|
||||
return Task.CompletedTask;
|
||||
}, cancellationToken, TaskContinuationOptions.None, TaskScheduler.Current);
|
||||
}
|
||||
}
|
||||
|
||||
if (evt is EthereumAddressBalanceFetched response)
|
||||
{
|
||||
if (response.ChainId != ChainId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var network = Networks.SingleOrDefault(payNetwork =>
|
||||
payNetwork.CryptoCode.Equals(response.CryptoCode, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
if (network is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var invoice = response.InvoiceEntity;
|
||||
if (invoice is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var existingPayment = response.MatchedExistingPayment;
|
||||
|
||||
if (existingPayment is null && response.Amount > 0)
|
||||
{
|
||||
//new payment
|
||||
var paymentData = new EthereumLikePaymentData()
|
||||
{
|
||||
Address = response.Address,
|
||||
CryptoCode = response.CryptoCode,
|
||||
Amount = response.Amount,
|
||||
Network = network,
|
||||
BlockNumber =
|
||||
response.BlockParameter.ParameterType == BlockParameter.BlockParameterType.blockNumber
|
||||
? (long?)response.BlockParameter.BlockNumber.Value
|
||||
: (long?)null,
|
||||
ConfirmationCount = 0,
|
||||
AccountIndex = response.PaymentMethodDetails.Index,
|
||||
XPub = response.PaymentMethodDetails.XPub
|
||||
};
|
||||
var payment = await _paymentService.AddPayment(invoice.Id, DateTimeOffset.UtcNow,
|
||||
paymentData, network, true);
|
||||
if (payment != null) ReceivedPayment(invoice, payment);
|
||||
}
|
||||
else if (existingPayment != null)
|
||||
{
|
||||
var cd = (EthereumLikePaymentData)existingPayment.GetCryptoPaymentData();
|
||||
//existing payment amount was changed. Set to unaccounted and register as a new payment.
|
||||
if (response.Amount == 0 || response.Amount != cd.Amount)
|
||||
{
|
||||
existingPayment.Accounted = false;
|
||||
|
||||
await _paymentService.UpdatePayments(new List<PaymentEntity>() {existingPayment});
|
||||
if (response.Amount > 0)
|
||||
{
|
||||
var paymentData = new EthereumLikePaymentData()
|
||||
{
|
||||
Address = response.Address,
|
||||
CryptoCode = response.CryptoCode,
|
||||
Amount = response.Amount,
|
||||
Network = network,
|
||||
BlockNumber =
|
||||
response.BlockParameter.ParameterType ==
|
||||
BlockParameter.BlockParameterType.blockNumber
|
||||
? (long?)response.BlockParameter.BlockNumber.Value
|
||||
: null,
|
||||
ConfirmationCount =
|
||||
response.BlockParameter.ParameterType ==
|
||||
BlockParameter.BlockParameterType.blockNumber
|
||||
? 1
|
||||
: 0,
|
||||
|
||||
AccountIndex = cd.AccountIndex,
|
||||
XPub = cd.XPub
|
||||
};
|
||||
var payment = await _paymentService.AddPayment(invoice.Id, DateTimeOffset.UtcNow,
|
||||
paymentData, network, true);
|
||||
if (payment != null) ReceivedPayment(invoice, payment);
|
||||
}
|
||||
}
|
||||
else if (response.Amount == cd.Amount)
|
||||
{
|
||||
//transition from pending to 1 confirmed
|
||||
if (cd.BlockNumber is null && response.BlockParameter.ParameterType ==
|
||||
BlockParameter.BlockParameterType.blockNumber)
|
||||
{
|
||||
cd.ConfirmationCount = 1;
|
||||
cd.BlockNumber = (long?)response.BlockParameter.BlockNumber.Value;
|
||||
|
||||
existingPayment.SetCryptoPaymentData(cd);
|
||||
await _paymentService.UpdatePayments(new List<PaymentEntity>() {existingPayment});
|
||||
|
||||
_eventAggregator.Publish(new Events.InvoiceNeedUpdateEvent(invoice.Id));
|
||||
}
|
||||
//increment confirm count
|
||||
else if (response.BlockParameter.ParameterType ==
|
||||
BlockParameter.BlockParameterType.blockNumber)
|
||||
{
|
||||
if (response.BlockParameter.BlockNumber.Value > cd.BlockNumber.Value)
|
||||
{
|
||||
cd.ConfirmationCount =
|
||||
(long)(response.BlockParameter.BlockNumber.Value - cd.BlockNumber.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
cd.BlockNumber = (long?)response.BlockParameter.BlockNumber.Value;
|
||||
cd.ConfirmationCount = 1;
|
||||
}
|
||||
|
||||
existingPayment.SetCryptoPaymentData(cd);
|
||||
await _paymentService.UpdatePayments(new List<PaymentEntity>() {existingPayment});
|
||||
|
||||
_eventAggregator.Publish(new Events.InvoiceNeedUpdateEvent(invoice.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CatchUp
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public override Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Logs.PayServer.LogInformation($"Stopping EthereumWatcher for chain {ChainId}");
|
||||
return base.StopAsync(cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
private async Task UpdateAnyPendingEthLikePaymentAndAddressWatchList(CancellationToken cancellationToken)
|
||||
{
|
||||
var invoiceIds = await _invoiceRepository.GetPendingInvoices();
|
||||
if (!invoiceIds.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var invoices = await _invoiceRepository.GetInvoices(new InvoiceQuery() {InvoiceId = invoiceIds});
|
||||
invoices = invoices
|
||||
.Where(entity => PaymentMethods.Any(id => entity.GetPaymentMethod(id)?.GetPaymentMethodDetails()?.Activated is true))
|
||||
.ToArray();
|
||||
|
||||
await UpdatePaymentStates(invoices, cancellationToken);
|
||||
}
|
||||
|
||||
private long? LastBlock = null;
|
||||
|
||||
private async Task UpdatePaymentStates(InvoiceEntity[] invoices, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!invoices.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var currentBlock = await Web3.Eth.Blocks.GetBlockNumber.SendRequestAsync();
|
||||
|
||||
foreach (var network in Networks)
|
||||
{
|
||||
var erc20Network = network as ERC20BTCPayNetwork;
|
||||
var paymentMethodId = new PaymentMethodId(network.CryptoCode, EthereumPaymentType.Instance);
|
||||
var expandedInvoices = invoices
|
||||
.Select(entity => (
|
||||
Invoice: entity,
|
||||
PaymentMethodDetails: entity.GetPaymentMethods().TryGet(paymentMethodId),
|
||||
ExistingPayments: entity.GetPayments(network, true).Select(paymentEntity => (Payment: paymentEntity,
|
||||
PaymentData: (EthereumLikePaymentData)paymentEntity.GetCryptoPaymentData(),
|
||||
Invoice: entity))
|
||||
)).Where(tuple => tuple.PaymentMethodDetails?.GetPaymentMethodDetails()?.Activated is true).ToList();
|
||||
|
||||
var existingPaymentData = expandedInvoices.SelectMany(tuple =>
|
||||
tuple.ExistingPayments.Where(valueTuple => valueTuple.Payment.Accounted)).ToList();
|
||||
|
||||
var noAccountedPaymentInvoices = expandedInvoices.Where(tuple =>
|
||||
tuple.ExistingPayments.All(valueTuple => !valueTuple.Payment.Accounted)).ToList();
|
||||
|
||||
var tasks = new List<Task>();
|
||||
if (existingPaymentData.Any() && currentBlock.Value != LastBlock)
|
||||
{
|
||||
Logs.PayServer.LogInformation(
|
||||
$"Checking {existingPaymentData.Count} existing payments on {expandedInvoices.Count} invoices on {network.CryptoCode}");
|
||||
var blockParameter = new BlockParameter(currentBlock);
|
||||
|
||||
tasks.Add(Task.WhenAll(existingPaymentData.Select(async tuple =>
|
||||
{
|
||||
var bal = await GetBalance(network, blockParameter, tuple.PaymentData.Address);
|
||||
_eventAggregator.Publish(new EthereumAddressBalanceFetched()
|
||||
{
|
||||
Address = tuple.PaymentData.Address,
|
||||
CryptoCode = network.CryptoCode,
|
||||
Amount = bal,
|
||||
MatchedExistingPayment = tuple.Payment,
|
||||
BlockParameter = blockParameter,
|
||||
ChainId = ChainId,
|
||||
InvoiceEntity = tuple.Invoice,
|
||||
});
|
||||
})).ContinueWith(task =>
|
||||
{
|
||||
LastBlock = (long?)currentBlock.Value;
|
||||
}, TaskScheduler.Current));
|
||||
}
|
||||
|
||||
if (noAccountedPaymentInvoices.Any())
|
||||
{
|
||||
Logs.PayServer.LogInformation(
|
||||
$"Checking {noAccountedPaymentInvoices.Count} addresses for new payments on {network.CryptoCode}");
|
||||
var blockParameter = BlockParameter.CreatePending();
|
||||
tasks.AddRange(noAccountedPaymentInvoices.Select(async tuple =>
|
||||
{
|
||||
var bal = await GetBalance(network, blockParameter,
|
||||
tuple.PaymentMethodDetails.GetPaymentMethodDetails().GetPaymentDestination());
|
||||
_eventAggregator.Publish(new EthereumAddressBalanceFetched()
|
||||
{
|
||||
Address = tuple.PaymentMethodDetails.GetPaymentMethodDetails().GetPaymentDestination(),
|
||||
CryptoCode = network.CryptoCode,
|
||||
Amount = bal,
|
||||
MatchedExistingPayment = null,
|
||||
BlockParameter = blockParameter,
|
||||
ChainId = ChainId,
|
||||
InvoiceEntity = tuple.Invoice,
|
||||
PaymentMethodDetails = (EthereumLikeOnChainPaymentMethodDetails) tuple.PaymentMethodDetails.GetPaymentMethodDetails()
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
}
|
||||
|
||||
public class EthereumAddressBalanceFetched
|
||||
{
|
||||
public BlockParameter BlockParameter { get; set; }
|
||||
public int ChainId { get; set; }
|
||||
public string Address { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public long Amount { get; set; }
|
||||
public InvoiceEntity InvoiceEntity { get; set; }
|
||||
public PaymentEntity MatchedExistingPayment { get; set; }
|
||||
public EthereumLikeOnChainPaymentMethodDetails PaymentMethodDetails { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private void ReceivedPayment(InvoiceEntity invoice, PaymentEntity payment)
|
||||
{
|
||||
_eventAggregator.Publish(
|
||||
new InvoiceEvent(invoice, InvoiceEvent.ReceivedPayment) {Payment = payment});
|
||||
}
|
||||
|
||||
private async Task<long> GetBalance(EthereumBTCPayNetwork network, BlockParameter blockParameter,
|
||||
string address)
|
||||
{
|
||||
if (network is ERC20BTCPayNetwork erc20BTCPayNetwork)
|
||||
{
|
||||
return (long)(await Web3.Eth.GetContractHandler(erc20BTCPayNetwork.SmartContractAddress)
|
||||
.QueryAsync<BalanceOfFunction, BigInteger>(new BalanceOfFunction() {Owner = address}));
|
||||
}
|
||||
else
|
||||
{
|
||||
return (long)(await Web3.Eth.GetBalance.SendRequestAsync(address, blockParameter)).Value;
|
||||
}
|
||||
}
|
||||
|
||||
public EthereumWatcher(int chainId, EthereumLikeConfiguration config,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
EventAggregator eventAggregator, InvoiceRepository invoiceRepository, PaymentService paymentService,
|
||||
BTCPayServer.Logging.Logs logs) :
|
||||
base(eventAggregator, logs)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
_invoiceRepository = invoiceRepository;
|
||||
_paymentService = paymentService;
|
||||
ChainId = chainId;
|
||||
AuthenticationHeaderValue headerValue = null;
|
||||
if (!string.IsNullOrEmpty(config.Web3ProviderUsername))
|
||||
{
|
||||
var val = config.Web3ProviderUsername;
|
||||
if (!string.IsNullOrEmpty(config.Web3ProviderUsername))
|
||||
{
|
||||
val += $":{config.Web3ProviderUsername}";
|
||||
}
|
||||
|
||||
headerValue = new AuthenticationHeaderValue(
|
||||
"Basic", Convert.ToBase64String(
|
||||
System.Text.Encoding.ASCII.GetBytes(val)));
|
||||
}
|
||||
Web3 = new Web3(config.Web3ProviderUrl, null, headerValue);
|
||||
Networks = btcPayNetworkProvider.GetAll()
|
||||
.OfType<EthereumBTCPayNetwork>()
|
||||
.Where(network => network.ChainId == chainId)
|
||||
.ToList();
|
||||
PaymentMethods = Networks
|
||||
.Select(network => new PaymentMethodId(network.CryptoCode, EthereumPaymentType.Instance))
|
||||
.ToHashSet();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,89 +0,0 @@
|
||||
#if ALTCOINS
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Altcoins.Ethereum.Configuration;
|
||||
using BTCPayServer.Services.Altcoins.Ethereum.Filters;
|
||||
using BTCPayServer.Services.Altcoins.Ethereum.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using Nethereum.Hex.HexConvertors.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Ethereum.UI
|
||||
{
|
||||
[Route("ethconfig")]
|
||||
[OnlyIfSupportEth]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Authorize(Policy = Policies.CanModifyServerSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public class EthereumConfigController : Controller
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly SettingsRepository _settingsRepository;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
|
||||
public EthereumConfigController(IHttpClientFactory httpClientFactory, SettingsRepository settingsRepository,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
EventAggregator eventAggregator)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_settingsRepository = settingsRepository;
|
||||
_userManager = userManager;
|
||||
_eventAggregator = eventAggregator;
|
||||
}
|
||||
|
||||
[HttpGet("{chainId}")]
|
||||
public async Task<IActionResult> UpdateChainConfig(int chainId)
|
||||
{
|
||||
return View("Ethereum/UpdateChainConfig",
|
||||
(await _settingsRepository.GetSettingAsync<EthereumLikeConfiguration>(
|
||||
EthereumLikeConfiguration.SettingsKey(chainId))) ?? new EthereumLikeConfiguration()
|
||||
{
|
||||
ChainId = chainId, Web3ProviderUrl = ""
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("{chainId}/cb")]
|
||||
public IActionResult Callback(int chainId)
|
||||
{
|
||||
_eventAggregator.Publish(new EthereumService.CheckWatchers());
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "If the invoice was paid successfully and confirmed, the system will be enabled momentarily"
|
||||
});
|
||||
return RedirectToAction("UpdateChainConfig", new {chainId});
|
||||
}
|
||||
|
||||
[HttpPost("{chainId}")]
|
||||
public async Task<IActionResult> UpdateChainConfig(int chainId, EthereumLikeConfiguration vm)
|
||||
{
|
||||
var current = await _settingsRepository.GetSettingAsync<EthereumLikeConfiguration>(
|
||||
EthereumLikeConfiguration.SettingsKey(chainId));
|
||||
if (current?.Web3ProviderUrl != vm.Web3ProviderUrl)
|
||||
{
|
||||
vm.ChainId = chainId;
|
||||
await _settingsRepository.UpdateSetting(vm, EthereumLikeConfiguration.SettingsKey(chainId));
|
||||
}
|
||||
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Success, Message = $"Chain {chainId} updated"
|
||||
});
|
||||
return RedirectToAction(nameof(UpdateChainConfig));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,281 +0,0 @@
|
||||
#if ALTCOINS
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Altcoins.Ethereum.Filters;
|
||||
using BTCPayServer.Services.Altcoins.Ethereum.Payments;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using Nethereum.HdWallet;
|
||||
using Nethereum.Hex.HexConvertors.Extensions;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Ethereum.UI
|
||||
{
|
||||
[Route("stores/{storeId}/ethlike")]
|
||||
[OnlyIfSupportEth]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public class EthereumLikeStoreController : Controller
|
||||
{
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
|
||||
public EthereumLikeStoreController(StoreRepository storeRepository,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider)
|
||||
{
|
||||
_storeRepository = storeRepository;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
}
|
||||
|
||||
private StoreData StoreData => HttpContext.GetStoreData();
|
||||
|
||||
[HttpGet()]
|
||||
public IActionResult GetStoreEthereumLikePaymentMethods()
|
||||
{
|
||||
var eth = StoreData.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
||||
.OfType<EthereumSupportedPaymentMethod>();
|
||||
|
||||
var excludeFilters = StoreData.GetStoreBlob().GetExcludedPaymentMethods();
|
||||
var ethNetworks = _btcPayNetworkProvider.GetAll().OfType<EthereumBTCPayNetwork>();
|
||||
|
||||
var vm = new ViewEthereumStoreOptionsViewModel() { };
|
||||
|
||||
foreach (var network in ethNetworks)
|
||||
{
|
||||
var paymentMethodId = new PaymentMethodId(network.CryptoCode, EthereumPaymentType.Instance);
|
||||
var matchedPaymentMethod = eth.SingleOrDefault(method =>
|
||||
method.PaymentId == paymentMethodId);
|
||||
vm.Items.Add(new ViewEthereumStoreOptionItemViewModel()
|
||||
{
|
||||
CryptoCode = network.CryptoCode,
|
||||
Enabled = matchedPaymentMethod != null && !excludeFilters.Match(paymentMethodId),
|
||||
IsToken = network is ERC20BTCPayNetwork,
|
||||
RootAddress = matchedPaymentMethod?.GetWalletDerivator()?.Invoke(0) ?? "not configured"
|
||||
});
|
||||
}
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[HttpGet("{cryptoCode}")]
|
||||
public IActionResult GetStoreEthereumLikePaymentMethod(string cryptoCode)
|
||||
{
|
||||
var eth = StoreData.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
||||
.OfType<EthereumSupportedPaymentMethod>();
|
||||
|
||||
var network = _btcPayNetworkProvider.GetNetwork<EthereumBTCPayNetwork>(cryptoCode);
|
||||
if (network is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var excludeFilters = StoreData.GetStoreBlob().GetExcludedPaymentMethods();
|
||||
var paymentMethodId = new PaymentMethodId(network.CryptoCode, EthereumPaymentType.Instance);
|
||||
var matchedPaymentMethod = eth.SingleOrDefault(method =>
|
||||
method.PaymentId == paymentMethodId);
|
||||
|
||||
return View(new EditEthereumPaymentMethodViewModel()
|
||||
{
|
||||
Enabled = !excludeFilters.Match(paymentMethodId),
|
||||
XPub = matchedPaymentMethod?.XPub,
|
||||
Index = matchedPaymentMethod?.CurrentIndex ?? 0,
|
||||
Passphrase = matchedPaymentMethod?.Password,
|
||||
Seed = matchedPaymentMethod?.Seed,
|
||||
StoreSeed = !string.IsNullOrEmpty(matchedPaymentMethod?.Seed),
|
||||
OriginalIndex = matchedPaymentMethod?.CurrentIndex ?? 0,
|
||||
KeyPath = string.IsNullOrEmpty(matchedPaymentMethod?.KeyPath)
|
||||
? network.GetDefaultKeyPath()
|
||||
: matchedPaymentMethod?.KeyPath
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("{cryptoCode}")]
|
||||
public async Task<IActionResult> GetStoreEthereumLikePaymentMethod(string cryptoCode,
|
||||
EditEthereumPaymentMethodViewModel viewModel)
|
||||
{
|
||||
var network = _btcPayNetworkProvider.GetNetwork<EthereumBTCPayNetwork>(cryptoCode);
|
||||
if (network is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var store = StoreData;
|
||||
var blob = StoreData.GetStoreBlob();
|
||||
var paymentMethodId = new PaymentMethodId(network.CryptoCode, EthereumPaymentType.Instance);
|
||||
|
||||
var currentPaymentMethod = StoreData.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
||||
.OfType<EthereumSupportedPaymentMethod>().SingleOrDefault(method =>
|
||||
method.PaymentId == paymentMethodId);
|
||||
|
||||
if (currentPaymentMethod != null && currentPaymentMethod.CurrentIndex != viewModel.Index &&
|
||||
viewModel.OriginalIndex == viewModel.Index)
|
||||
{
|
||||
viewModel.Index = currentPaymentMethod.CurrentIndex;
|
||||
viewModel.OriginalIndex = currentPaymentMethod.CurrentIndex;
|
||||
}
|
||||
else if (currentPaymentMethod != null && currentPaymentMethod.CurrentIndex != viewModel.Index &&
|
||||
viewModel.OriginalIndex != currentPaymentMethod.CurrentIndex)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.Index),
|
||||
$"You tried to update the index (to {viewModel.Index}) but new derivations in the background updated the index (to {currentPaymentMethod.CurrentIndex}) ");
|
||||
viewModel.Index = currentPaymentMethod.CurrentIndex;
|
||||
viewModel.OriginalIndex = currentPaymentMethod.CurrentIndex;
|
||||
}
|
||||
|
||||
Wallet wallet = null;
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(viewModel.Seed))
|
||||
{
|
||||
wallet = new Wallet(viewModel.Seed, viewModel.Passphrase,
|
||||
string.IsNullOrEmpty(viewModel.KeyPath) ? network.GetDefaultKeyPath() : viewModel.KeyPath);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.Seed), $"seed was incorrect");
|
||||
}
|
||||
|
||||
if (wallet != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
wallet.GetAccount(0);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.KeyPath), $"keypath was incorrect");
|
||||
}
|
||||
}
|
||||
|
||||
PublicWallet publicWallet = null;
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(viewModel.XPub))
|
||||
{
|
||||
try
|
||||
{
|
||||
publicWallet = new PublicWallet(viewModel.XPub);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
publicWallet = new PublicWallet(new BitcoinExtPubKey(viewModel.XPub, Network.Main).ExtPubKey);
|
||||
}
|
||||
|
||||
if (wallet != null && !publicWallet.ExtPubKey.Equals(wallet.GetMasterPublicWallet().ExtPubKey))
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.XPub),
|
||||
$"The xpub does not match the seed/pass/key path provided");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.XPub), $"xpub was incorrect");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(viewModel.AddressCheck))
|
||||
{
|
||||
int index = -1;
|
||||
if (wallet != null)
|
||||
{
|
||||
index = Array.IndexOf(wallet.GetAddresses(1000), viewModel.AddressCheck);
|
||||
}
|
||||
else if (publicWallet != null)
|
||||
{
|
||||
index = Array.IndexOf(publicWallet.GetAddresses(1000), viewModel.AddressCheck);
|
||||
}
|
||||
|
||||
if (viewModel.AddressCheckLastUsed && index > -1)
|
||||
{
|
||||
viewModel.Index = index;
|
||||
}
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.AddressCheck),
|
||||
"Could not confirm address belongs to configured wallet");
|
||||
}
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
currentPaymentMethod ??= new EthereumSupportedPaymentMethod();
|
||||
currentPaymentMethod.Password = viewModel.StoreSeed ? viewModel.Passphrase : "";
|
||||
currentPaymentMethod.Seed = viewModel.StoreSeed ? viewModel.Seed : "";
|
||||
currentPaymentMethod.XPub = string.IsNullOrEmpty(viewModel.XPub) && wallet != null
|
||||
? wallet.GetMasterPublicWallet().ExtPubKey.ToBytes().ToHex()
|
||||
: viewModel.XPub;
|
||||
currentPaymentMethod.CryptoCode = cryptoCode;
|
||||
currentPaymentMethod.KeyPath = string.IsNullOrEmpty(viewModel.KeyPath)
|
||||
? network.GetDefaultKeyPath()
|
||||
: viewModel.KeyPath;
|
||||
currentPaymentMethod.CurrentIndex = viewModel.Index;
|
||||
|
||||
blob.SetExcluded(paymentMethodId, !viewModel.Enabled);
|
||||
store.SetSupportedPaymentMethod(currentPaymentMethod);
|
||||
store.SetStoreBlob(blob);
|
||||
await _storeRepository.UpdateStore(store);
|
||||
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = $"updated {cryptoCode}", Severity = StatusMessageModel.StatusSeverity.Success
|
||||
});
|
||||
|
||||
return RedirectToAction("GetStoreEthereumLikePaymentMethods", new {storeId = store.Id});
|
||||
}
|
||||
}
|
||||
|
||||
public class EditEthereumPaymentMethodViewModel
|
||||
{
|
||||
public string XPub { get; set; }
|
||||
public string Seed { get; set; }
|
||||
public string Passphrase { get; set; }
|
||||
|
||||
public string KeyPath { get; set; }
|
||||
public long OriginalIndex { get; set; }
|
||||
|
||||
[Display(Name = "Current address index")]
|
||||
|
||||
public long Index { get; set; }
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
[Display(Name = "Hot wallet")] public bool StoreSeed { get; set; }
|
||||
|
||||
[Display(Name ="Address Check")]
|
||||
public string AddressCheck { get; set; }
|
||||
|
||||
public bool AddressCheckLastUsed { get; set; }
|
||||
}
|
||||
|
||||
public class ViewEthereumStoreOptionsViewModel
|
||||
{
|
||||
public List<ViewEthereumStoreOptionItemViewModel> Items { get; set; } =
|
||||
new List<ViewEthereumStoreOptionItemViewModel>();
|
||||
}
|
||||
|
||||
public class ViewEthereumStoreOptionItemViewModel
|
||||
{
|
||||
public string CryptoCode { get; set; }
|
||||
public bool IsToken { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
public string RootAddress { get; set; }
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,19 +0,0 @@
|
||||
#if ALTCOINS
|
||||
using System;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Ethereum.UI
|
||||
{
|
||||
public class EthereumPaymentViewModel
|
||||
{
|
||||
public string Crypto { get; set; }
|
||||
public string Confirmations { get; set; }
|
||||
public string DepositAddress { get; set; }
|
||||
public string Amount { get; set; }
|
||||
public DateTimeOffset ReceivedTime { get; set; }
|
||||
public long? BlockNumber { get; set; }
|
||||
public string BalanceLink { get; set; }
|
||||
public bool Replaced { get; set; }
|
||||
public long Index { get; set; }
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,92 +0,0 @@
|
||||
@using BTCPayServer.Views.Stores
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@model BTCPayServer.Services.Altcoins.Ethereum.UI.EditEthereumPaymentMethodViewModel
|
||||
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData["NavPartialName"] = "../Stores/_Nav";
|
||||
ViewData.SetActivePageAndTitle(StoreNavPages.ActivePage, $"{Context.GetRouteValue("cryptoCode")} Settings", Context.GetStoreData().StoreName);
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="alert alert-warning">DO NOT USE THE WALLET TO ACCEPT PAYMENTS OUTSIDE OF THIS STORE.<br/>If you spend funds received on invoices which have not been marked complete yet, the invoice will be marked as unpaid.
|
||||
</div>
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<form method="post" asp-action="GetStoreEthereumLikePaymentMethod"
|
||||
asp-route-storeId="@Context.GetRouteValue("storeId")"
|
||||
asp-route-cryptoCode="@Context.GetRouteValue("cryptoCode")"
|
||||
class="mt-4" enctype="multipart/form-data">
|
||||
|
||||
<input type="hidden" asp-for="OriginalIndex"/>
|
||||
<div class="form-group">
|
||||
<label asp-for="Seed" class="form-label"></label>
|
||||
<input asp-for="Seed" type="text" class="form-control"/>
|
||||
<span asp-validation-for="Seed" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Passphrase" class="form-label"></label>
|
||||
<input type="text" asp-for="Passphrase" class="form-control" autocomplete="off"/>
|
||||
<span asp-validation-for="Passphrase" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label asp-for="KeyPath" class="form-label"></label>
|
||||
<input asp-for="KeyPath" class="form-control"/>
|
||||
<span class="text-muted">Please see <a href="https://medium.com/myetherwallet/hd-wallets-and-derivation-paths-explained-865a643c7bf2" target="_blank" rel="noreferrer noopener">this article.</a></span>
|
||||
<span asp-validation-for="KeyPath" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group form-check">
|
||||
<input asp-for="StoreSeed" type="checkbox" class="form-check-input"/>
|
||||
<label asp-for="StoreSeed" class="form-check-label"></label>
|
||||
<span class="text-muted">Store the seed/password on server if provided. If not checked, will generate the xpub and erase the seed/pass from server</span>
|
||||
<span asp-validation-for="StoreSeed" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label asp-for="XPub" class="form-label"></label>
|
||||
<input asp-for="XPub" class="form-control"/>
|
||||
<span class="text-muted">The public master key derived from a seed/pass/keypath. This allows you to generate addresses without private keys on the server.</span>
|
||||
<span asp-validation-for="XPub" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label asp-for="Index" class="form-label"></label>
|
||||
<input asp-for="Index" type="number" min="0" class="form-control"/>
|
||||
<span class="text-muted">The index to generate the next address from. If you are using a wallet that you have used before, be sure to set this to the last index +1</span>
|
||||
<span asp-validation-for="Index" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="AddressCheck" class="form-label"></label>
|
||||
<div class="input-group">
|
||||
<input asp-for="AddressCheck" type="text" class="form-control"/>
|
||||
<div class="input-group-text">
|
||||
<input asp-for="AddressCheckLastUsed" type="checkbox" data-toggle="tooltip" class="form-check" title="Use this address to set the last used index for the wallet"/>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-muted">Check wallet by providing an address it can generate within the first 1000 indexes</span>
|
||||
<span asp-validation-for="AddressCheck" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group form-check">
|
||||
<input asp-for="Enabled" type="checkbox" class="form-check-input"/>
|
||||
<label asp-for="Enabled" class="form-check-label"></label>
|
||||
<span asp-validation-for="Enabled" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary" id="SaveButton">Save</button>
|
||||
|
||||
<a class="btn btn-secondary" asp-action="GetStoreEthereumLikePaymentMethods"
|
||||
asp-route-storeId="@Context.GetRouteValue("storeId")"
|
||||
asp-route-cryptoCode="@Context.GetRouteValue("cryptoCode")"
|
||||
asp-controller="EthereumLikeStore">
|
||||
Back to list
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section PageFootContent {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
|
||||
@using BTCPayServer.Views.Stores
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@model BTCPayServer.Services.Altcoins.Ethereum.UI.ViewEthereumStoreOptionsViewModel
|
||||
@inject SignInManager<ApplicationUser> SignInManager;
|
||||
@inject BTCPayNetworkProvider BTCPayNetworkProvider;
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData.SetActivePageAndTitle(StoreNavPages.ActivePage, "Ethereum Settings", Context.GetStoreData().StoreName);
|
||||
ViewData["NavPartialName"] = "../Stores/_Nav";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div class="form-group">
|
||||
<table class="table table-hover table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Crypto</th>
|
||||
<th>Root address</th>
|
||||
<th class="text-center">Enabled</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Model.Items)
|
||||
{
|
||||
<tr>
|
||||
<td>@item.CryptoCode</td>
|
||||
<td>@item.RootAddress</td>
|
||||
<td class="text-center">
|
||||
@if (item.Enabled)
|
||||
{
|
||||
<span class="text-success fa fa-check"></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-danger fa fa-times"></span>
|
||||
}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<a id="Modify@(item.CryptoCode)" asp-action="GetStoreEthereumLikePaymentMethod"
|
||||
asp-route-storeId="@Context.GetRouteValue("storeId")"
|
||||
asp-route-cryptoCode="@item.CryptoCode">
|
||||
Modify
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (SignInManager.IsSignedIn(User) && User.IsInRole(Roles.ServerAdmin))
|
||||
{
|
||||
var chains = BTCPayNetworkProvider.GetAll().OfType<EthereumBTCPayNetwork>().Select(network => network.ChainId).Distinct();
|
||||
foreach (var chain in chains)
|
||||
{
|
||||
<a asp-action="UpdateChainConfig" asp-controller="EthereumConfig" asp-route-chainId="@chain">Configure Web3 for chain @chain</a>
|
||||
}
|
||||
}
|
||||
@section PageFootContent {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
@using BTCPayServer.Services.Altcoins.Ethereum.Services
|
||||
@inject BTCPayNetworkProvider BTCPayNetworkProvider
|
||||
@inject EthereumService EthereumService;
|
||||
|
||||
@inject SignInManager<ApplicationUser> SignInManager;
|
||||
@{
|
||||
var networks = BTCPayNetworkProvider.GetAll().OfType<EthereumBTCPayNetwork>().OrderBy(network => network.ChainId).Where(network => network.ShowSyncSummary);
|
||||
}
|
||||
|
||||
@foreach (var network in networks)
|
||||
{
|
||||
<h4>@network.CryptoCode (Chain ID: @network.ChainId) @(network is ERC20BTCPayNetwork is true ? "(ERC20)" : "")</h4>
|
||||
<ul>
|
||||
@if (!EthereumService.IsAvailable(network.CryptoCode, out var error))
|
||||
{
|
||||
<li>
|
||||
@(error??"Web3 has not yet been configured")
|
||||
|
||||
|
||||
@if (SignInManager.IsSignedIn(User) && User.IsInRole(Roles.ServerAdmin))
|
||||
{
|
||||
<a asp-action="UpdateChainConfig" asp-controller="EthereumConfig" asp-route-chainId="@network.ChainId">Configure Web3</a>
|
||||
}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
@ -1,224 +0,0 @@
|
||||
@model BTCPayServer.Models.InvoicingModels.PaymentModel
|
||||
@inject BTCPayNetworkProvider BTCPayNetworkProvider
|
||||
@{
|
||||
var chains = BTCPayNetworkProvider.GetAll().OfType<EthereumBTCPayNetwork>().ToDictionary(network => network.CryptoCode.ToLowerInvariant(), network => new
|
||||
{
|
||||
ChainId = network.ChainId,
|
||||
SmartContractAddress = (network as ERC20BTCPayNetwork)?.SmartContractAddress,
|
||||
Divisibility = network.Divisibility
|
||||
});
|
||||
}
|
||||
<script type="text/x-template" id="ethereum-method-checkout-template">
|
||||
<div>
|
||||
<div class="bp-view payment scan" id="scan" v-bind:class="{ 'active': currentTab == 'scan'}">
|
||||
|
||||
<div class="payment__scan">
|
||||
<img v-bind:src="srvModel.cryptoImage" class="qr_currency_icon" v-if="scanDisplayQr"/>
|
||||
<qrcode v-bind:value="scanDisplayQr" :options="{ width: 256, margin: 1, color: {dark:'#000', light:'#f5f5f7'} }" tag="svg" v-if="scanDisplayQr"></qrcode>
|
||||
<div class="payment__spinner qr_currency_icon" style="padding-right: 20px;">
|
||||
<partial name="Checkout-Spinner"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="payment__details__instruction__open-wallet" v-if="hasWeb3">
|
||||
<button :disabled="web3Loading" class="payment__details__instruction__open-wallet__btn action-button" target="_top" v-on:click="payWithWeb3">
|
||||
<span>{{$t("Open in wallet")}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-view payment manual-flow" id="copy" v-bind:class="{ 'active': currentTab == 'copy'}">
|
||||
<div class="manual__step-two__instructions">
|
||||
<span v-html="$t('CompletePay_Body', srvModel)"></span>
|
||||
</div>
|
||||
<div class="copyLabelPopup">
|
||||
<span>{{$t("Copied")}}</span>
|
||||
</div>
|
||||
<nav class="copyBox">
|
||||
<div class="copySectionBox bottomBorder">
|
||||
<label>{{$t("Amount")}}</label>
|
||||
<div class="copyAmountText copy-cursor _copySpan">
|
||||
<span>{{dueAmount}}</span> {{ srvModel.cryptoCode }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="separatorGem"></div>
|
||||
<div class="copySectionBox bottomBorder">
|
||||
<label>{{$t("Address")}}</label>
|
||||
<div class="inputWithIcon _copyInput">
|
||||
<input type="text" class="checkoutTextbox" v-bind:value="srvModel.btcAddress" readonly="readonly"/>
|
||||
<img v-bind:src="srvModel.cryptoImage"/>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
@await Component.InvokeAsync("UiExtensionPoint" , new { location="checkout-ethereum-post-content", model = Model})
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-template" id="ethereum-method-checkout-header-template">
|
||||
<div class="payment-tabs">
|
||||
<div class="payment-tabs__tab " id="scan-tab" v-on:click="switchTab('scan')" v-bind:class="{ 'active': currentTab == 'scan'}" >
|
||||
<span>{{$t("Scan")}}</span>
|
||||
</div>
|
||||
<div class="payment-tabs__tab" id="copy-tab" v-on:click="switchTab('copy')" v-bind:class="{ 'active': currentTab == 'copy'}" >
|
||||
<span>{{$t("Copy")}}</span>
|
||||
</div>
|
||||
@await Component.InvokeAsync("UiExtensionPoint" , new { location="checkout-ethereum-post-tabs", model = Model})
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
Vue.component('EthereumLikeMethodCheckout',
|
||||
{
|
||||
props: ["srvModel"],
|
||||
template: "#ethereum-method-checkout-template",
|
||||
components: {
|
||||
qrcode: VueQrcode
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
cryptoChains: @Safe.Json(chains),
|
||||
selectedThirdPartyProcessor: "",
|
||||
currentTab: "scan",
|
||||
hasWeb3: false,
|
||||
web3Loading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dueAmount: function(){
|
||||
var r = this.srvModel.btcDue;
|
||||
while(r.charAt(r.length-1)=='0') {
|
||||
r = r.substring(0,r.length-1);
|
||||
}
|
||||
return r;
|
||||
},
|
||||
scanDisplayQr: function() {
|
||||
return this.srvModel.invoiceBitcoinUrlQR;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
payWithWeb3: function() {
|
||||
var self = this;
|
||||
self.web3Loading = true;
|
||||
var chainData = self.cryptoChains[self.srvModel.cryptoCode.toLowerCase()];
|
||||
var ptio = self.dueAmount.indexOf('.');
|
||||
var amt = self.dueAmount.substr(0,ptio) + web3.padRight(self.dueAmount.substr(ptio+1), chainData.divisibility);
|
||||
if(chainData.smartContractAddress){
|
||||
var abi = [
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{ name: "_to", type: "address" },
|
||||
{ name: "_value", type: "uint256" },
|
||||
],
|
||||
name: "transfer",
|
||||
outputs: [{ name: "", type: "bool" }],
|
||||
type: "function",
|
||||
},
|
||||
];
|
||||
var contract = web3.eth.contract(abi).at(chainData.smartContractAddress);
|
||||
contract.transfer(self.srvModel.btcAddress, amt, (error, txHash) => {
|
||||
if(error){
|
||||
console.error(error);
|
||||
}
|
||||
self.web3Loading = false;
|
||||
});
|
||||
}else {
|
||||
web3.eth.sendTransaction({
|
||||
from : 0,
|
||||
to : self.srvModel.btcAddress,
|
||||
value: amt,
|
||||
},
|
||||
(error, txID) => {
|
||||
|
||||
if(error){
|
||||
console.error(error);
|
||||
}
|
||||
self.web3Loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted: function() {
|
||||
var self = this;
|
||||
eventBus.$on("tab-switched",
|
||||
function(tab) {
|
||||
self.currentTab = tab;
|
||||
});
|
||||
if (window.ethereum) {
|
||||
window.web3 = new Web3(ethereum);
|
||||
}
|
||||
|
||||
var chainData = self.cryptoChains[self.srvModel.cryptoCode.toLowerCase()];
|
||||
if (typeof web3 !== "undefined"){
|
||||
if(ethereum && ethereum.chainId){
|
||||
self.hasWeb3 = chainData.chainId == parseInt(ethereum.chainId, 16);
|
||||
}else if (web3.eth.getChainId){
|
||||
web3.eth.getChainId(function(chainId){
|
||||
self.hasWeb3 = chainData.chainId == chainId;
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Vue.component('EthereumLikeMethodCheckoutHeader', {
|
||||
props: ["srvModel"],
|
||||
template: "#ethereum-method-checkout-header-template",
|
||||
data: function() {
|
||||
return {
|
||||
currentTab: "scan"
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
switchTab: function(tab) {
|
||||
this.currentTab = tab;
|
||||
eventBus.$emit("tab-switched", tab);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
// Clipboard Copy
|
||||
var copySpan = new Clipboard('._copySpan', {
|
||||
target: function(trigger) {
|
||||
return copyElement(trigger, 0, 65).firstChild;
|
||||
}
|
||||
});
|
||||
var copyInput = new Clipboard('._copyInput', {
|
||||
target: function(trigger) {
|
||||
return copyElement(trigger, 4, 65).firstChild;
|
||||
}
|
||||
});
|
||||
|
||||
function copyElement(trigger, popupLeftModifier, popupTopModifier) {
|
||||
var elm = $(trigger);
|
||||
var position = elm.offset();
|
||||
position.top -= popupLeftModifier + $(window).scrollTop();
|
||||
position.left += (elm.width() / 2) - popupTopModifier;
|
||||
$(".copyLabelPopup").css(position).addClass("copied");
|
||||
elm.removeClass("copy-cursor").addClass("clipboardCopied");
|
||||
setTimeout(clearSelection, 100);
|
||||
setTimeout(function() {
|
||||
elm.removeClass("clipboardCopied").addClass("copy-cursor");
|
||||
$(".copyLabelPopup").removeClass("copied");
|
||||
},
|
||||
1000);
|
||||
return trigger;
|
||||
}
|
||||
|
||||
function clearSelection() {
|
||||
if (window.getSelection) {
|
||||
window.getSelection().removeAllRanges();
|
||||
} else if (document.selection) {
|
||||
document.selection.empty();
|
||||
}
|
||||
}
|
||||
// Disable enter key
|
||||
$(document).keypress(
|
||||
function(event) {
|
||||
if (event.which === '13') {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
</script>
|
@ -1,11 +0,0 @@
|
||||
@using BTCPayServer.Services.Altcoins.Ethereum.UI
|
||||
@inject SignInManager<ApplicationUser> SignInManager;
|
||||
@inject BTCPayNetworkProvider BTCPayNetworkProvider;
|
||||
@{
|
||||
var controller = ViewContext.RouteData.Values["Controller"].ToString();
|
||||
var isEthereum = controller.Equals(nameof(EthereumLikeStoreController), StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
@if (SignInManager.IsSignedIn(User) && BTCPayNetworkProvider.GetAll().OfType<EthereumBTCPayNetwork>().Any())
|
||||
{
|
||||
<a class="nav-link @(isEthereum ? "active" : string.Empty)" asp-route-storeId="@this.Context.GetRouteValue("storeId")" asp-action="GetStoreEthereumLikePaymentMethods" asp-controller="EthereumLikeStore">Ethereum</a>
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
@using BTCPayServer.Views.Server
|
||||
@using System.Net.Http
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@model BTCPayServer.Services.Altcoins.Ethereum.Configuration.EthereumLikeConfiguration
|
||||
@inject BTCPayNetworkProvider BTCPayNetworkProvider;
|
||||
@inject IHttpClientFactory HttpClientFactory;
|
||||
@{
|
||||
Layout = "../_NavLayout.cshtml";
|
||||
ViewData["NavPartialName"] = "../Server/_Nav";
|
||||
ViewBag.MainTitle = "Server settings";
|
||||
ViewData.SetActivePageAndTitle(ServerNavPages.Policies, $"ETH Chain {Model.ChainId} Configuration");
|
||||
}
|
||||
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
}
|
||||
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<input type="hidden" asp-for="ChainId"/>
|
||||
<div class="form-check">
|
||||
<label asp-for="Web3ProviderUrl" class="form-label"></label>
|
||||
<input asp-for="Web3ProviderUrl" type="text" class="form-control"/>
|
||||
<span asp-validation-for="Web3ProviderUrl" class="text-danger"></span>
|
||||
<div>
|
||||
Possible free options are
|
||||
<ul>
|
||||
<li><a href="https://medium.com/linkpool/release-of-public-ethereum-rpcs-f5dd57455d2e" target="_blank" rel="noreferrer noopener">linkpool.io</a> - Free, just set the url to <code>https://main-rpc.linkpool.io</code></li>
|
||||
<li><a href="https://chainstack.com/" target="_blank" rel="noreferrer noopener">chainstack.com</a> - Free plan, choose shared public node</li>
|
||||
<li><a href="https://infura.io/" target="_blank" rel="noreferrer noopener">infura.io</a> - Free tier but limited calls per day</li>
|
||||
<li>Your own geth/openethereum node</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<label asp-for="Web3ProviderUsername" class="form-label"></label>
|
||||
<input asp-for="Web3ProviderUsername" type="text" class="form-control"/>
|
||||
<span asp-validation-for="Web3ProviderUsername" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<label asp-for="Web3ProviderPassword" class="form-label"></label>
|
||||
<input asp-for="Web3ProviderPassword" type="text" class="form-control"/>
|
||||
<span asp-validation-for="Web3ProviderPassword" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" name="command" value="Save" id="saveButton">Save</button>
|
||||
</form>
|
@ -1,64 +0,0 @@
|
||||
@using System.Globalization
|
||||
@using BTCPayServer.Services.Altcoins.Ethereum.Payments
|
||||
@using BTCPayServer.Services.Altcoins.Ethereum.UI
|
||||
@model IEnumerable<BTCPayServer.Services.Invoices.PaymentEntity>
|
||||
|
||||
@{
|
||||
var onchainPayments = Model.Where(entity => entity.GetPaymentMethodId().PaymentType == EthereumPaymentType.Instance).Select(payment =>
|
||||
{
|
||||
var m = new EthereumPaymentViewModel();
|
||||
var onChainPaymentData = payment.GetCryptoPaymentData() as EthereumLikePaymentData;
|
||||
m.Crypto = payment.GetPaymentMethodId().CryptoCode;
|
||||
m.DepositAddress = onChainPaymentData.GetDestination();
|
||||
|
||||
m.Amount = onChainPaymentData.GetValue().ToString(CultureInfo.InvariantCulture);
|
||||
m.Confirmations = onChainPaymentData.BlockNumber.HasValue ? $"{onChainPaymentData.ConfirmationCount} (block {onChainPaymentData.BlockNumber})" : "pending";
|
||||
m.Amount = onChainPaymentData.GetValue().ToString(CultureInfo.InvariantCulture);
|
||||
m.BlockNumber = onChainPaymentData.BlockNumber;
|
||||
m.ReceivedTime = payment.ReceivedTime;
|
||||
m.BalanceLink = string.Format(CultureInfo.InvariantCulture, payment.Network.BlockExplorerLink, m.DepositAddress);
|
||||
m.Replaced = !payment.Accounted;
|
||||
m.Index = onChainPaymentData.AccountIndex;
|
||||
return m;
|
||||
});
|
||||
}
|
||||
|
||||
@if (onchainPayments.Any())
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-md-12 invoice-payments">
|
||||
<h3>Ethereum/ERC-20 payments</h3>
|
||||
<table class="table table-hover table-responsive-lg">
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th>Crypto</th>
|
||||
<th>Amount</th>
|
||||
<th>Address</th>
|
||||
<th>Index</th>
|
||||
<th class="text-right">Confirmations</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var payment in onchainPayments)
|
||||
{
|
||||
|
||||
<tr style="@(payment.Replaced ? "text-decoration: line-through" : "")">
|
||||
<td>@payment.Crypto</td>
|
||||
<td>@payment.Amount</td>
|
||||
<td>
|
||||
<div class="wraptextAuto">
|
||||
<a href="@payment.BalanceLink" target="_blank" rel="noreferrer noopener">
|
||||
|
||||
@payment.DepositAddress
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
<td>@payment.Index</td>
|
||||
<td class="text-right">@payment.Confirmations</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 26 KiB |
Reference in New Issue
Block a user