mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2026-01-31 03:44:29 +01:00
Remove ZCash and Monero from core code
This commit is contained in:
@@ -58,7 +58,6 @@ services:
|
||||
- merchant_lnd
|
||||
- sshd
|
||||
- tor
|
||||
- monero_wallet
|
||||
|
||||
sshd:
|
||||
build:
|
||||
@@ -317,28 +316,6 @@ services:
|
||||
- "torrcdir:/usr/local/etc/tor"
|
||||
- "tor_servicesdir:/var/lib/tor/hidden_services"
|
||||
|
||||
monerod:
|
||||
image: btcpayserver/monero:0.18.3.3
|
||||
restart: unless-stopped
|
||||
container_name: xmr_monerod
|
||||
entrypoint: monerod --fixed-difficulty 200 --rpc-bind-ip=0.0.0.0 --confirm-external-bind --rpc-bind-port=18081 --block-notify="/bin/sh ./scripts/notifier.sh -k -X GET https://host.docker.internal:14142/monerolikedaemoncallback/block?cryptoCode=xmr&hash=%s" --testnet --no-igd --hide-my-port --offline --non-interactive
|
||||
volumes:
|
||||
- "monero_data:/home/monero/.bitmonero"
|
||||
ports:
|
||||
- "18081:18081"
|
||||
|
||||
monero_wallet:
|
||||
image: btcpayserver/monero:0.18.3.3
|
||||
restart: unless-stopped
|
||||
container_name: xmr_wallet_rpc
|
||||
entrypoint: monero-wallet-rpc --testnet --rpc-bind-ip=0.0.0.0 --disable-rpc-login --confirm-external-bind --rpc-bind-port=18082 --non-interactive --trusted-daemon --daemon-address=monerod:18081 --wallet-dir=/wallet --tx-notify="/bin/sh ./scripts/notifier.sh -k -X GET https://host.docker.internal:14142/monerolikedaemoncallback/tx?cryptoCode=xmr&hash=%s"
|
||||
ports:
|
||||
- "18082:18082"
|
||||
volumes:
|
||||
- "./monero_wallet:/wallet"
|
||||
depends_on:
|
||||
- monerod
|
||||
|
||||
litecoind:
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/litecoin:0.18.1
|
||||
@@ -400,7 +377,6 @@ volumes:
|
||||
tor_datadir:
|
||||
torrcdir:
|
||||
tor_servicesdir:
|
||||
monero_data:
|
||||
|
||||
networks:
|
||||
default:
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BTCPayServer.Filters
|
||||
{
|
||||
public class OnlyIfSupportAttribute : Attribute, IAsyncActionFilter
|
||||
{
|
||||
private readonly string _paymentMethodId;
|
||||
|
||||
public OnlyIfSupportAttribute(string paymentMethodId)
|
||||
{
|
||||
_paymentMethodId = paymentMethodId;
|
||||
}
|
||||
|
||||
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
var handlers = context.HttpContext.RequestServices.GetService<PaymentMethodHandlerDictionary>();
|
||||
if (!handlers.Support(PaymentMethodId.Parse(_paymentMethodId)))
|
||||
{
|
||||
context.Result = new NotFoundResult();
|
||||
return;
|
||||
}
|
||||
await next();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,10 +52,6 @@ namespace BTCPayServer.Plugins.Altcoins
|
||||
InitDash(services);
|
||||
if (selectedChains.Contains("GRS"))
|
||||
InitGroestlcoin(services);
|
||||
if (selectedChains.Contains("XMR"))
|
||||
InitMonero(services);
|
||||
if (selectedChains.Contains("ZEC"))
|
||||
InitZcash(services);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Services;
|
||||
using System.Net.Http;
|
||||
using System.Net;
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Services.Altcoins.Monero.Configuration;
|
||||
using BTCPayServer.Services.Altcoins.Monero.Payments;
|
||||
using BTCPayServer.Services.Altcoins.Monero.Services;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NBitcoin;
|
||||
using BTCPayServer.Configuration;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace BTCPayServer.Plugins.Altcoins;
|
||||
|
||||
public partial class AltcoinsPlugin
|
||||
{
|
||||
public void InitMonero(IServiceCollection services)
|
||||
{
|
||||
var network = new MoneroLikeSpecificBtcPayNetwork()
|
||||
{
|
||||
CryptoCode = "XMR",
|
||||
DisplayName = "Monero",
|
||||
Divisibility = 12,
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"XMR_X = XMR_BTC * BTC_X",
|
||||
"XMR_BTC = kraken(XMR_BTC)"
|
||||
},
|
||||
CryptoImagePath = "/imlegacy/monero.svg",
|
||||
UriScheme = "monero"
|
||||
};
|
||||
var blockExplorerLink = ChainName == ChainName.Mainnet
|
||||
? "https://www.exploremonero.com/transaction/{0}"
|
||||
: "https://testnet.xmrchain.net/tx/{0}";
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId("XMR");
|
||||
services.AddDefaultPrettyName(pmi, network.DisplayName);
|
||||
services.AddBTCPayNetwork(network)
|
||||
.AddTransactionLinkProvider(pmi, new SimpleTransactionLinkProvider(blockExplorerLink));
|
||||
|
||||
|
||||
services.AddSingleton(provider =>
|
||||
ConfigureMoneroLikeConfiguration(provider));
|
||||
services.AddHttpClient("XMRclient")
|
||||
.ConfigurePrimaryHttpMessageHandler(provider =>
|
||||
{
|
||||
var configuration = provider.GetRequiredService<MoneroLikeConfiguration>();
|
||||
if (!configuration.MoneroLikeConfigurationItems.TryGetValue("XMR", out var xmrConfig) || xmrConfig.Username is null || xmrConfig.Password is null)
|
||||
{
|
||||
return new HttpClientHandler();
|
||||
}
|
||||
return new HttpClientHandler
|
||||
{
|
||||
Credentials = new NetworkCredential(xmrConfig.Username, xmrConfig.Password),
|
||||
PreAuthenticate = true
|
||||
};
|
||||
});
|
||||
services.AddSingleton<MoneroRPCProvider>();
|
||||
services.AddHostedService<MoneroLikeSummaryUpdaterHostedService>();
|
||||
services.AddHostedService<MoneroListener>();
|
||||
services.AddSingleton<IPaymentMethodHandler>(provider =>
|
||||
(IPaymentMethodHandler)ActivatorUtilities.CreateInstance(provider, typeof(MoneroLikePaymentMethodHandler), new object[] { network }));
|
||||
services.AddSingleton<IPaymentLinkExtension>(provider =>
|
||||
(IPaymentLinkExtension)ActivatorUtilities.CreateInstance(provider, typeof(MoneroPaymentLinkExtension), new object[] { network, pmi }));
|
||||
services.AddSingleton<ICheckoutModelExtension>(provider =>
|
||||
(ICheckoutModelExtension)ActivatorUtilities.CreateInstance(provider, typeof(MoneroCheckoutModelExtension), new object[] { network, pmi }));
|
||||
|
||||
services.AddUIExtension("store-nav", "Monero/StoreNavMoneroExtension");
|
||||
services.AddUIExtension("store-wallets-nav", "Monero/StoreWalletsNavMoneroExtension");
|
||||
services.AddUIExtension("store-invoices-payments", "Monero/ViewMoneroLikePaymentData");
|
||||
services.AddSingleton<ISyncSummaryProvider, MoneroSyncSummaryProvider>();
|
||||
}
|
||||
private static MoneroLikeConfiguration ConfigureMoneroLikeConfiguration(IServiceProvider serviceProvider)
|
||||
{
|
||||
var configuration = serviceProvider.GetService<IConfiguration>();
|
||||
var btcPayNetworkProvider = serviceProvider.GetService<BTCPayNetworkProvider>();
|
||||
var result = new MoneroLikeConfiguration();
|
||||
|
||||
var supportedNetworks = btcPayNetworkProvider.GetAll()
|
||||
.OfType<MoneroLikeSpecificBtcPayNetwork>();
|
||||
|
||||
foreach (var moneroLikeSpecificBtcPayNetwork in supportedNetworks)
|
||||
{
|
||||
var daemonUri =
|
||||
configuration.GetOrDefault<Uri>($"{moneroLikeSpecificBtcPayNetwork.CryptoCode}_daemon_uri",
|
||||
null);
|
||||
var walletDaemonUri =
|
||||
configuration.GetOrDefault<Uri>(
|
||||
$"{moneroLikeSpecificBtcPayNetwork.CryptoCode}_wallet_daemon_uri", null);
|
||||
var walletDaemonWalletDirectory =
|
||||
configuration.GetOrDefault<string>(
|
||||
$"{moneroLikeSpecificBtcPayNetwork.CryptoCode}_wallet_daemon_walletdir", null);
|
||||
var daemonUsername =
|
||||
configuration.GetOrDefault<string>(
|
||||
$"{moneroLikeSpecificBtcPayNetwork.CryptoCode}_daemon_username", null);
|
||||
var daemonPassword =
|
||||
configuration.GetOrDefault<string>(
|
||||
$"{moneroLikeSpecificBtcPayNetwork.CryptoCode}_daemon_password", null);
|
||||
if (daemonUri == null || walletDaemonUri == null || walletDaemonWalletDirectory == null)
|
||||
{
|
||||
throw new ConfigException($"{moneroLikeSpecificBtcPayNetwork.CryptoCode} is misconfigured");
|
||||
}
|
||||
|
||||
result.MoneroLikeConfigurationItems.Add(moneroLikeSpecificBtcPayNetwork.CryptoCode, new MoneroLikeConfigurationItem()
|
||||
{
|
||||
DaemonRpcUri = daemonUri,
|
||||
Username = daemonUsername,
|
||||
Password = daemonPassword,
|
||||
InternalWalletRpcUri = walletDaemonUri,
|
||||
WalletDirectory = walletDaemonWalletDirectory
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace BTCPayServer.Plugins.Altcoins;
|
||||
|
||||
public class MoneroLikeSpecificBtcPayNetwork : BTCPayNetworkBase
|
||||
{
|
||||
public int MaxTrackedConfirmation = 10;
|
||||
public string UriScheme { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC
|
||||
{
|
||||
public class JsonRpcClient
|
||||
{
|
||||
private readonly Uri _address;
|
||||
private readonly string _username;
|
||||
private readonly string _password;
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public JsonRpcClient(Uri address, string username, string password, HttpClient client = null)
|
||||
{
|
||||
_address = address;
|
||||
_username = username;
|
||||
_password = password;
|
||||
_httpClient = client ?? new HttpClient();
|
||||
}
|
||||
|
||||
|
||||
public async Task<TResponse> SendCommandAsync<TRequest, TResponse>(string method, TRequest data,
|
||||
CancellationToken cts = default(CancellationToken))
|
||||
{
|
||||
var jsonSerializer = new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
};
|
||||
var httpRequest = new HttpRequestMessage()
|
||||
{
|
||||
Method = HttpMethod.Post,
|
||||
RequestUri = new Uri(_address, "json_rpc"),
|
||||
Content = new StringContent(
|
||||
JsonConvert.SerializeObject(new JsonRpcCommand<TRequest>(method, data), jsonSerializer),
|
||||
Encoding.UTF8, "application/json")
|
||||
};
|
||||
httpRequest.Headers.Accept.Clear();
|
||||
httpRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Basic",
|
||||
Convert.ToBase64String(Encoding.Default.GetBytes($"{_username}:{_password}")));
|
||||
|
||||
HttpResponseMessage rawResult = await _httpClient.SendAsync(httpRequest, cts);
|
||||
rawResult.EnsureSuccessStatusCode();
|
||||
var rawJson = await rawResult.Content.ReadAsStringAsync();
|
||||
|
||||
JsonRpcResult<TResponse> response;
|
||||
try
|
||||
{
|
||||
response = JsonConvert.DeserializeObject<JsonRpcResult<TResponse>>(rawJson, jsonSerializer);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.Message);
|
||||
Console.WriteLine(rawJson);
|
||||
throw;
|
||||
}
|
||||
|
||||
if (response.Error != null)
|
||||
{
|
||||
throw new JsonRpcApiException()
|
||||
{
|
||||
Error = response.Error
|
||||
};
|
||||
}
|
||||
|
||||
return response.Result;
|
||||
}
|
||||
|
||||
public class NoRequestModel
|
||||
{
|
||||
public static NoRequestModel Instance = new NoRequestModel();
|
||||
}
|
||||
|
||||
internal class JsonRpcApiException : Exception
|
||||
{
|
||||
public JsonRpcResultError Error { get; set; }
|
||||
|
||||
public override string Message => Error?.Message;
|
||||
}
|
||||
|
||||
public class JsonRpcResultError
|
||||
{
|
||||
[JsonProperty("code")] public int Code { get; set; }
|
||||
[JsonProperty("message")] public string Message { get; set; }
|
||||
[JsonProperty("data")] dynamic Data { get; set; }
|
||||
}
|
||||
internal class JsonRpcResult<T>
|
||||
{
|
||||
|
||||
|
||||
[JsonProperty("result")] public T Result { get; set; }
|
||||
[JsonProperty("error")] public JsonRpcResultError Error { get; set; }
|
||||
[JsonProperty("id")] public string Id { get; set; }
|
||||
}
|
||||
|
||||
internal class JsonRpcCommand<T>
|
||||
{
|
||||
[JsonProperty("jsonRpc")] public string JsonRpc { get; set; } = "2.0";
|
||||
[JsonProperty("id")] public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
[JsonProperty("method")] public string Method { get; set; }
|
||||
|
||||
[JsonProperty("params")] public T Parameters { get; set; }
|
||||
|
||||
public JsonRpcCommand()
|
||||
{
|
||||
}
|
||||
|
||||
public JsonRpcCommand(string method, T parameters)
|
||||
{
|
||||
Method = method;
|
||||
Parameters = parameters;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public partial class CreateAccountRequest
|
||||
{
|
||||
[JsonProperty("label")] public string Label { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public partial class CreateAccountResponse
|
||||
{
|
||||
[JsonProperty("account_index")] public long AccountIndex { get; set; }
|
||||
[JsonProperty("address")] public string Address { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public partial class CreateAddressRequest
|
||||
{
|
||||
[JsonProperty("account_index")] public long AccountIndex { get; set; }
|
||||
[JsonProperty("label")] public string Label { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public partial class CreateAddressResponse
|
||||
{
|
||||
[JsonProperty("address")] public string Address { get; set; }
|
||||
[JsonProperty("address_index")] public long AddressIndex { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public partial class GetAccountsRequest
|
||||
{
|
||||
[JsonProperty("tag")] public string Tag { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public partial class GetAccountsResponse
|
||||
{
|
||||
[JsonProperty("subaddress_accounts")] public List<SubaddressAccount> SubaddressAccounts { get; set; }
|
||||
[JsonProperty("total_balance")] public decimal TotalBalance { get; set; }
|
||||
|
||||
[JsonProperty("total_unlocked_balance")]
|
||||
public decimal TotalUnlockedBalance { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public class GetFeeEstimateRequest
|
||||
{
|
||||
[JsonProperty("grace_blocks")] public int? GraceBlocks { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public class GetFeeEstimateResponse
|
||||
{
|
||||
[JsonProperty("fee")] public long Fee { get; set; }
|
||||
[JsonProperty("status")] public string Status { get; set; }
|
||||
[JsonProperty("untrusted")] public bool Untrusted { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public partial class GetHeightResponse
|
||||
{
|
||||
[JsonProperty("height")] public long Height { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public partial class GetInfoResponse
|
||||
{
|
||||
[JsonProperty("height")] public long Height { get; set; }
|
||||
[JsonProperty("busy_syncing")] public bool BusySyncing { get; set; }
|
||||
[JsonProperty("status")] public string Status { get; set; }
|
||||
[JsonProperty("target_height")] public long? TargetHeight { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public class GetTransferByTransactionIdRequest
|
||||
{
|
||||
[JsonProperty("txid")] public string TransactionId { get; set; }
|
||||
|
||||
[JsonProperty("account_index", DefaultValueHandling = DefaultValueHandling.Ignore)] public long? AccountIndex { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public partial class GetTransferByTransactionIdResponse
|
||||
{
|
||||
[JsonProperty("transfer")] public TransferItem Transfer { get; set; }
|
||||
[JsonProperty("transfers")] public IEnumerable<TransferItem> Transfers { get; set; }
|
||||
|
||||
public partial class TransferItem
|
||||
{
|
||||
[JsonProperty("address")] public string Address { get; set; }
|
||||
[JsonProperty("amount")] public long Amount { get; set; }
|
||||
[JsonProperty("confirmations")] public long Confirmations { get; set; }
|
||||
[JsonProperty("double_spend_seen")] public bool DoubleSpendSeen { get; set; }
|
||||
[JsonProperty("height")] public long Height { get; set; }
|
||||
[JsonProperty("note")] public string Note { get; set; }
|
||||
[JsonProperty("payment_id")] public string PaymentId { get; set; }
|
||||
[JsonProperty("subaddr_index")] public SubaddrIndex SubaddrIndex { get; set; }
|
||||
|
||||
[JsonProperty("suggested_confirmations_threshold")]
|
||||
public long SuggestedConfirmationsThreshold { get; set; }
|
||||
|
||||
[JsonProperty("timestamp")] public long Timestamp { get; set; }
|
||||
[JsonProperty("txid")] public string Txid { get; set; }
|
||||
[JsonProperty("type")] public string Type { get; set; }
|
||||
[JsonProperty("unlock_time")] public long UnlockTime { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public partial class GetTransfersRequest
|
||||
{
|
||||
[JsonProperty("in")] public bool In { get; set; }
|
||||
[JsonProperty("out")] public bool Out { get; set; }
|
||||
[JsonProperty("pending")] public bool Pending { get; set; }
|
||||
[JsonProperty("failed")] public bool Failed { get; set; }
|
||||
[JsonProperty("pool")] public bool Pool { get; set; }
|
||||
[JsonProperty("filter_by_height ")] public bool FilterByHeight { get; set; }
|
||||
[JsonProperty("min_height")] public long MinHeight { get; set; }
|
||||
[JsonProperty("max_height")] public long MaxHeight { get; set; }
|
||||
[JsonProperty("account_index")] public long AccountIndex { get; set; }
|
||||
[JsonProperty("subaddr_indices")] public List<long> SubaddrIndices { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public partial class GetTransfersResponse
|
||||
{
|
||||
[JsonProperty("in")] public List<GetTransfersResponseItem> In { get; set; }
|
||||
[JsonProperty("out")] public List<GetTransfersResponseItem> Out { get; set; }
|
||||
[JsonProperty("pending")] public List<GetTransfersResponseItem> Pending { get; set; }
|
||||
[JsonProperty("failed")] public List<GetTransfersResponseItem> Failed { get; set; }
|
||||
[JsonProperty("pool")] public List<GetTransfersResponseItem> Pool { get; set; }
|
||||
|
||||
public partial class GetTransfersResponseItem
|
||||
|
||||
{
|
||||
[JsonProperty("address")] public string Address { get; set; }
|
||||
[JsonProperty("amount")] public long Amount { get; set; }
|
||||
[JsonProperty("confirmations")] public long Confirmations { get; set; }
|
||||
[JsonProperty("double_spend_seen")] public bool DoubleSpendSeen { get; set; }
|
||||
[JsonProperty("height")] public long Height { get; set; }
|
||||
[JsonProperty("note")] public string Note { get; set; }
|
||||
[JsonProperty("payment_id")] public string PaymentId { get; set; }
|
||||
[JsonProperty("subaddr_index")] public SubaddrIndex SubaddrIndex { get; set; }
|
||||
|
||||
[JsonProperty("suggested_confirmations_threshold")]
|
||||
public long SuggestedConfirmationsThreshold { get; set; }
|
||||
|
||||
[JsonProperty("timestamp")] public long Timestamp { get; set; }
|
||||
[JsonProperty("txid")] public string Txid { get; set; }
|
||||
[JsonProperty("type")] public string Type { get; set; }
|
||||
[JsonProperty("unlock_time")] public long UnlockTime { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public partial class Info
|
||||
{
|
||||
[JsonProperty("address")] public string Address { get; set; }
|
||||
[JsonProperty("avg_download")] public long AvgDownload { get; set; }
|
||||
[JsonProperty("avg_upload")] public long AvgUpload { get; set; }
|
||||
[JsonProperty("connection_id")] public string ConnectionId { get; set; }
|
||||
[JsonProperty("current_download")] public long CurrentDownload { get; set; }
|
||||
[JsonProperty("current_upload")] public long CurrentUpload { get; set; }
|
||||
[JsonProperty("height")] public long Height { get; set; }
|
||||
[JsonProperty("host")] public string Host { get; set; }
|
||||
[JsonProperty("incoming")] public bool Incoming { get; set; }
|
||||
[JsonProperty("ip")] public string Ip { get; set; }
|
||||
[JsonProperty("live_time")] public long LiveTime { get; set; }
|
||||
[JsonProperty("local_ip")] public bool LocalIp { get; set; }
|
||||
[JsonProperty("localhost")] public bool Localhost { get; set; }
|
||||
[JsonProperty("peer_id")] public string PeerId { get; set; }
|
||||
|
||||
[JsonProperty("port")]
|
||||
[JsonConverter(typeof(ParseStringConverter))]
|
||||
public long Port { get; set; }
|
||||
|
||||
[JsonProperty("recv_count")] public long RecvCount { get; set; }
|
||||
[JsonProperty("recv_idle_time")] public long RecvIdleTime { get; set; }
|
||||
[JsonProperty("send_count")] public long SendCount { get; set; }
|
||||
[JsonProperty("send_idle_time")] public long SendIdleTime { get; set; }
|
||||
[JsonProperty("state")] public string State { get; set; }
|
||||
[JsonProperty("support_flags")] public long SupportFlags { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public partial class MakeUriRequest
|
||||
{
|
||||
[JsonProperty("address")] public string Address { get; set; }
|
||||
[JsonProperty("amount")] public long Amount { get; set; }
|
||||
[JsonProperty("payment_id")] public string PaymentId { get; set; }
|
||||
[JsonProperty("tx_description")] public string TxDescription { get; set; }
|
||||
[JsonProperty("recipient_name")] public string RecipientName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public partial class MakeUriResponse
|
||||
{
|
||||
[JsonProperty("uri")] public string Uri { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public partial class OpenWalletErrorResponse
|
||||
{
|
||||
[JsonProperty("code")] public int Code { get; set; }
|
||||
[JsonProperty("message")] public string Message { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public partial class OpenWalletRequest
|
||||
{
|
||||
[JsonProperty("filename")] public string Filename { get; set; }
|
||||
[JsonProperty("password")] public string Password { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public partial class OpenWalletResponse
|
||||
{
|
||||
[JsonProperty("id")] public string Id { get; set; }
|
||||
[JsonProperty("jsonrpc")] public string Jsonrpc { get; set; }
|
||||
[JsonProperty("result")] public object Result { get; set; }
|
||||
[JsonProperty("error")] public OpenWalletErrorResponse Error { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
internal class ParseStringConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type t) => t == typeof(long) || t == typeof(long?);
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
return null;
|
||||
var value = serializer.Deserialize<string>(reader);
|
||||
long l;
|
||||
if (Int64.TryParse(value, out l))
|
||||
{
|
||||
return l;
|
||||
}
|
||||
|
||||
throw new Exception("Cannot unmarshal type long");
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
|
||||
{
|
||||
if (untypedValue == null)
|
||||
{
|
||||
serializer.Serialize(writer, null);
|
||||
return;
|
||||
}
|
||||
|
||||
var value = (long)untypedValue;
|
||||
serializer.Serialize(writer, value.ToString(CultureInfo.InvariantCulture));
|
||||
return;
|
||||
}
|
||||
|
||||
public static readonly ParseStringConverter Singleton = new ParseStringConverter();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public partial class Peer
|
||||
{
|
||||
[JsonProperty("info")] public Info Info { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public partial class SubaddrIndex
|
||||
{
|
||||
[JsonProperty("major")] public long Major { get; set; }
|
||||
[JsonProperty("minor")] public long Minor { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
|
||||
{
|
||||
public partial class SubaddressAccount
|
||||
{
|
||||
[JsonProperty("account_index")] public long AccountIndex { get; set; }
|
||||
[JsonProperty("balance")] public decimal Balance { get; set; }
|
||||
[JsonProperty("base_address")] public string BaseAddress { get; set; }
|
||||
[JsonProperty("label")] public string Label { get; set; }
|
||||
[JsonProperty("tag")] public string Tag { get; set; }
|
||||
[JsonProperty("unlocked_balance")] public decimal UnlockedBalance { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.Utils
|
||||
{
|
||||
public class MoneroMoney
|
||||
{
|
||||
public static decimal Convert(long piconero)
|
||||
{
|
||||
var amt = piconero.ToString(CultureInfo.InvariantCulture).PadLeft(12, '0');
|
||||
amt = amt.Length == 12 ? $"0.{amt}" : amt.Insert(amt.Length - 12, ".");
|
||||
|
||||
return decimal.Parse(amt, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public static long Convert(decimal monero)
|
||||
{
|
||||
return System.Convert.ToInt64(monero * 1000000000000);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
#nullable enable
|
||||
using BTCPayServer.Services;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using NBitcoin;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Services;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Payments;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Services;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Configuration;
|
||||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace BTCPayServer.Plugins.Altcoins;
|
||||
|
||||
public partial class AltcoinsPlugin
|
||||
{
|
||||
// Change this if you want another zcash coin
|
||||
public void InitZcash(IServiceCollection services)
|
||||
{
|
||||
var network = new ZcashLikeSpecificBtcPayNetwork()
|
||||
{
|
||||
CryptoCode = "ZEC",
|
||||
DisplayName = "Zcash",
|
||||
Divisibility = 8,
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"ZEC_X = ZEC_BTC * BTC_X",
|
||||
"ZEC_BTC = kraken(ZEC_BTC)"
|
||||
},
|
||||
CryptoImagePath = "/imlegacy/zcash.png",
|
||||
UriScheme = "zcash"
|
||||
};
|
||||
var blockExplorerLink = ChainName == ChainName.Mainnet
|
||||
? "https://www.exploreZcash.com/transaction/{0}"
|
||||
: "https://testnet.xmrchain.net/tx/{0}";
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId("ZEC");
|
||||
services.AddDefaultPrettyName(pmi, network.DisplayName);
|
||||
services.AddBTCPayNetwork(network)
|
||||
.AddTransactionLinkProvider(pmi, new SimpleTransactionLinkProvider(blockExplorerLink));
|
||||
|
||||
|
||||
services.AddSingleton(provider =>
|
||||
ConfigureZcashLikeConfiguration(provider));
|
||||
services.AddSingleton<ZcashRPCProvider>();
|
||||
services.AddHostedService<ZcashLikeSummaryUpdaterHostedService>();
|
||||
services.AddHostedService<ZcashListener>();
|
||||
|
||||
|
||||
services.AddSingleton<IPaymentMethodHandler>(provider =>
|
||||
(IPaymentMethodHandler)ActivatorUtilities.CreateInstance(provider, typeof(ZcashLikePaymentMethodHandler), new object[] { network }));
|
||||
services.AddSingleton<IPaymentLinkExtension>(provider =>
|
||||
(IPaymentLinkExtension)ActivatorUtilities.CreateInstance(provider, typeof(ZcashPaymentLinkExtension), new object[] { network, pmi }));
|
||||
services.AddSingleton<ICheckoutModelExtension>(provider =>
|
||||
(ICheckoutModelExtension)ActivatorUtilities.CreateInstance(provider, typeof(ZcashCheckoutModelExtension), new object[] { network, pmi }));
|
||||
|
||||
services.AddSingleton<ZcashLikePaymentMethodHandler>();
|
||||
services.AddSingleton<IPaymentMethodHandler>(provider => provider.GetRequiredService<ZcashLikePaymentMethodHandler>());
|
||||
services.AddUIExtension("store-nav", "Zcash/StoreNavZcashExtension");
|
||||
services.AddUIExtension("store-invoices-payments", "Zcash/ViewZcashLikePaymentData");
|
||||
services.AddSingleton<ISyncSummaryProvider, ZcashSyncSummaryProvider>();
|
||||
|
||||
}
|
||||
static ZcashLikeConfiguration ConfigureZcashLikeConfiguration(IServiceProvider serviceProvider)
|
||||
{
|
||||
var configuration = serviceProvider.GetService<IConfiguration>();
|
||||
var btcPayNetworkProvider = serviceProvider.GetRequiredService<BTCPayNetworkProvider>();
|
||||
var result = new ZcashLikeConfiguration();
|
||||
|
||||
var supportedNetworks = btcPayNetworkProvider.GetAll()
|
||||
.OfType<ZcashLikeSpecificBtcPayNetwork>();
|
||||
|
||||
foreach (var ZcashLikeSpecificBtcPayNetwork in supportedNetworks)
|
||||
{
|
||||
var daemonUri =
|
||||
configuration.GetOrDefault<Uri?>($"{ZcashLikeSpecificBtcPayNetwork.CryptoCode}_daemon_uri",
|
||||
null);
|
||||
var walletDaemonUri =
|
||||
configuration.GetOrDefault<Uri?>(
|
||||
$"{ZcashLikeSpecificBtcPayNetwork.CryptoCode}_wallet_daemon_uri", null);
|
||||
var walletDaemonWalletDirectory =
|
||||
configuration.GetOrDefault<string?>(
|
||||
$"{ZcashLikeSpecificBtcPayNetwork.CryptoCode}_wallet_daemon_walletdir", null);
|
||||
if (daemonUri == null || walletDaemonUri == null || walletDaemonWalletDirectory == null)
|
||||
{
|
||||
throw new ConfigException($"{ZcashLikeSpecificBtcPayNetwork.CryptoCode} is misconfigured");
|
||||
}
|
||||
|
||||
result.ZcashLikeConfigurationItems.Add(ZcashLikeSpecificBtcPayNetwork.CryptoCode, new ZcashLikeConfigurationItem()
|
||||
{
|
||||
DaemonRpcUri = daemonUri,
|
||||
InternalWalletRpcUri = walletDaemonUri,
|
||||
WalletDirectory = walletDaemonWalletDirectory
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
class SimpleTransactionLinkProvider : DefaultTransactionLinkProvider
|
||||
{
|
||||
public SimpleTransactionLinkProvider(string blockExplorerLink) : base(blockExplorerLink)
|
||||
{
|
||||
}
|
||||
|
||||
public override string? GetTransactionLink(string paymentId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(BlockExplorerLink))
|
||||
return null;
|
||||
return string.Format(CultureInfo.InvariantCulture, BlockExplorerLink, paymentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC
|
||||
{
|
||||
public class JsonRpcClient
|
||||
{
|
||||
private readonly Uri _address;
|
||||
private readonly string _username;
|
||||
private readonly string _password;
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public JsonRpcClient(Uri address, string username, string password, HttpClient client = null)
|
||||
{
|
||||
_address = address;
|
||||
_username = username;
|
||||
_password = password;
|
||||
_httpClient = client ?? new HttpClient();
|
||||
}
|
||||
|
||||
|
||||
public async Task<TResponse> SendCommandAsync<TRequest, TResponse>(string method, TRequest data,
|
||||
CancellationToken cts = default(CancellationToken))
|
||||
{
|
||||
var jsonSerializer = new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
};
|
||||
var httpRequest = new HttpRequestMessage()
|
||||
{
|
||||
Method = HttpMethod.Post,
|
||||
RequestUri = new Uri(_address, method),
|
||||
Content = new StringContent(
|
||||
JsonConvert.SerializeObject(data, jsonSerializer),
|
||||
Encoding.UTF8, "application/json")
|
||||
};
|
||||
// httpRequest.Headers.Accept.Clear();
|
||||
// httpRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
// httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Basic",
|
||||
// Convert.ToBase64String(Encoding.Default.GetBytes($"{_username}:{_password}")));
|
||||
|
||||
var rawResult = await _httpClient.SendAsync(httpRequest, cts);
|
||||
|
||||
var rawJson = await rawResult.Content.ReadAsStringAsync();
|
||||
rawResult.EnsureSuccessStatusCode();
|
||||
var response = JsonConvert.DeserializeObject<TResponse>(rawJson, jsonSerializer);
|
||||
return response;
|
||||
}
|
||||
|
||||
public class NoRequestModel
|
||||
{
|
||||
public static NoRequestModel Instance = new NoRequestModel();
|
||||
}
|
||||
|
||||
internal class JsonRpcApiException : Exception
|
||||
{
|
||||
public JsonRpcResultError Error { get; set; }
|
||||
|
||||
public override string Message => Error?.Message;
|
||||
}
|
||||
|
||||
public class JsonRpcResultError
|
||||
{
|
||||
[JsonProperty("code")] public int Code { get; set; }
|
||||
[JsonProperty("message")] public string Message { get; set; }
|
||||
[JsonProperty("data")] dynamic Data { get; set; }
|
||||
}
|
||||
internal class JsonRpcResult<T>
|
||||
{
|
||||
|
||||
|
||||
[JsonProperty("result")] public T Result { get; set; }
|
||||
[JsonProperty("error")] public JsonRpcResultError Error { get; set; }
|
||||
[JsonProperty("id")] public string Id { get; set; }
|
||||
}
|
||||
|
||||
internal class JsonRpcCommand<T>
|
||||
{
|
||||
[JsonProperty("jsonRpc")] public string JsonRpc { get; set; } = "2.0";
|
||||
[JsonProperty("id")] public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
[JsonProperty("method")] public string Method { get; set; }
|
||||
|
||||
[JsonProperty("params")] public T Parameters { get; set; }
|
||||
|
||||
public JsonRpcCommand()
|
||||
{
|
||||
}
|
||||
|
||||
public JsonRpcCommand(string method, T parameters)
|
||||
{
|
||||
Method = method;
|
||||
Parameters = parameters;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public partial class CreateAccountRequest
|
||||
{
|
||||
[JsonProperty("label")] public string Label { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public partial class CreateAccountResponse
|
||||
{
|
||||
[JsonProperty("account_index")] public long AccountIndex { get; set; }
|
||||
[JsonProperty("address")] public string Address { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public partial class CreateAddressRequest
|
||||
{
|
||||
[JsonProperty("account_index")] public long AccountIndex { get; set; }
|
||||
[JsonProperty("label")] public string Label { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public partial class CreateAddressResponse
|
||||
{
|
||||
[JsonProperty("address")] public string Address { get; set; }
|
||||
[JsonProperty("address_index")] public long AddressIndex { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public partial class GetAccountsRequest
|
||||
{
|
||||
[JsonProperty("tag")] public string Tag { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public partial class GetAccountsResponse
|
||||
{
|
||||
[JsonProperty("subaddress_accounts")] public List<SubaddressAccount> SubaddressAccounts { get; set; }
|
||||
[JsonProperty("total_balance")] public decimal TotalBalance { get; set; }
|
||||
|
||||
[JsonProperty("total_unlocked_balance")]
|
||||
public decimal TotalUnlockedBalance { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public class GetFeeEstimateRequest
|
||||
{
|
||||
[JsonProperty("grace_blocks")] public int? GraceBlocks { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public class GetFeeEstimateResponse
|
||||
{
|
||||
[JsonProperty("fee")] public long Fee { get; set; }
|
||||
[JsonProperty("status")] public string Status { get; set; }
|
||||
[JsonProperty("untrusted")] public bool Untrusted { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public partial class GetHeightResponse
|
||||
{
|
||||
[JsonProperty("height")] public long Height { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public class GetTransferByTransactionIdRequest
|
||||
{
|
||||
[JsonProperty("txid")] public string TransactionId { get; set; }
|
||||
|
||||
[JsonProperty("account_index")] public long AccountIndex { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public partial class GetTransferByTransactionIdResponse
|
||||
{
|
||||
[JsonProperty("transfer")] public TransferItem Transfer { get; set; }
|
||||
[JsonProperty("transfers")] public IEnumerable<TransferItem> Transfers { get; set; }
|
||||
|
||||
public partial class TransferItem
|
||||
{
|
||||
[JsonProperty("address")] public string Address { get; set; }
|
||||
[JsonProperty("amount")] public long Amount { get; set; }
|
||||
[JsonProperty("confirmations")] public long Confirmations { get; set; }
|
||||
[JsonProperty("double_spend_seen")] public bool DoubleSpendSeen { get; set; }
|
||||
[JsonProperty("height")] public long Height { get; set; }
|
||||
[JsonProperty("note")] public string Note { get; set; }
|
||||
[JsonProperty("payment_id")] public string PaymentId { get; set; }
|
||||
[JsonProperty("subaddr_index")] public SubaddrIndex SubaddrIndex { get; set; }
|
||||
|
||||
[JsonProperty("suggested_confirmations_threshold")]
|
||||
public long SuggestedConfirmationsThreshold { get; set; }
|
||||
|
||||
[JsonProperty("timestamp")] public long Timestamp { get; set; }
|
||||
[JsonProperty("txid")] public string Txid { get; set; }
|
||||
[JsonProperty("type")] public string Type { get; set; }
|
||||
[JsonProperty("unlock_time")] public long UnlockTime { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public partial class GetTransfersRequest
|
||||
{
|
||||
[JsonProperty("in")] public bool In { get; set; }
|
||||
[JsonProperty("out")] public bool Out { get; set; }
|
||||
[JsonProperty("pending")] public bool Pending { get; set; }
|
||||
[JsonProperty("failed")] public bool Failed { get; set; }
|
||||
[JsonProperty("pool")] public bool Pool { get; set; }
|
||||
[JsonProperty("filter_by_height ")] public bool FilterByHeight { get; set; }
|
||||
[JsonProperty("min_height")] public long MinHeight { get; set; }
|
||||
[JsonProperty("max_height")] public long MaxHeight { get; set; }
|
||||
[JsonProperty("account_index")] public long AccountIndex { get; set; }
|
||||
[JsonProperty("subaddr_indices")] public List<long> SubaddrIndices { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public partial class GetTransfersResponse
|
||||
{
|
||||
[JsonProperty("in")] public List<GetTransfersResponseItem> In { get; set; }
|
||||
[JsonProperty("out")] public List<GetTransfersResponseItem> Out { get; set; }
|
||||
[JsonProperty("pending")] public List<GetTransfersResponseItem> Pending { get; set; }
|
||||
[JsonProperty("failed")] public List<GetTransfersResponseItem> Failed { get; set; }
|
||||
[JsonProperty("pool")] public List<GetTransfersResponseItem> Pool { get; set; }
|
||||
|
||||
public partial class GetTransfersResponseItem
|
||||
|
||||
{
|
||||
[JsonProperty("address")] public string Address { get; set; }
|
||||
[JsonProperty("amount")] public long Amount { get; set; }
|
||||
[JsonProperty("confirmations")] public long Confirmations { get; set; }
|
||||
[JsonProperty("double_spend_seen")] public bool DoubleSpendSeen { get; set; }
|
||||
[JsonProperty("height")] public long Height { get; set; }
|
||||
[JsonProperty("note")] public string Note { get; set; }
|
||||
[JsonProperty("payment_id")] public string PaymentId { get; set; }
|
||||
[JsonProperty("subaddr_index")] public SubaddrIndex SubaddrIndex { get; set; }
|
||||
|
||||
[JsonProperty("suggested_confirmations_threshold")]
|
||||
public long SuggestedConfirmationsThreshold { get; set; }
|
||||
|
||||
[JsonProperty("timestamp")] public long Timestamp { get; set; }
|
||||
[JsonProperty("txid")] public string Txid { get; set; }
|
||||
[JsonProperty("type")] public string Type { get; set; }
|
||||
[JsonProperty("unlock_time")] public long UnlockTime { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public partial class Info
|
||||
{
|
||||
[JsonProperty("address")] public string Address { get; set; }
|
||||
[JsonProperty("avg_download")] public long AvgDownload { get; set; }
|
||||
[JsonProperty("avg_upload")] public long AvgUpload { get; set; }
|
||||
[JsonProperty("connection_id")] public string ConnectionId { get; set; }
|
||||
[JsonProperty("current_download")] public long CurrentDownload { get; set; }
|
||||
[JsonProperty("current_upload")] public long CurrentUpload { get; set; }
|
||||
[JsonProperty("height")] public long Height { get; set; }
|
||||
[JsonProperty("host")] public string Host { get; set; }
|
||||
[JsonProperty("incoming")] public bool Incoming { get; set; }
|
||||
[JsonProperty("ip")] public string Ip { get; set; }
|
||||
[JsonProperty("live_time")] public long LiveTime { get; set; }
|
||||
[JsonProperty("local_ip")] public bool LocalIp { get; set; }
|
||||
[JsonProperty("localhost")] public bool Localhost { get; set; }
|
||||
[JsonProperty("peer_id")] public string PeerId { get; set; }
|
||||
|
||||
[JsonProperty("port")]
|
||||
[JsonConverter(typeof(ParseStringConverter))]
|
||||
public long Port { get; set; }
|
||||
|
||||
[JsonProperty("recv_count")] public long RecvCount { get; set; }
|
||||
[JsonProperty("recv_idle_time")] public long RecvIdleTime { get; set; }
|
||||
[JsonProperty("send_count")] public long SendCount { get; set; }
|
||||
[JsonProperty("send_idle_time")] public long SendIdleTime { get; set; }
|
||||
[JsonProperty("state")] public string State { get; set; }
|
||||
[JsonProperty("support_flags")] public long SupportFlags { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public partial class MakeUriRequest
|
||||
{
|
||||
[JsonProperty("address")] public string Address { get; set; }
|
||||
[JsonProperty("amount")] public long Amount { get; set; }
|
||||
[JsonProperty("payment_id")] public string PaymentId { get; set; }
|
||||
[JsonProperty("tx_description")] public string TxDescription { get; set; }
|
||||
[JsonProperty("recipient_name")] public string RecipientName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public partial class MakeUriResponse
|
||||
{
|
||||
[JsonProperty("uri")] public string Uri { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
internal class ParseStringConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type t) => t == typeof(long) || t == typeof(long?);
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
return null;
|
||||
var value = serializer.Deserialize<string>(reader);
|
||||
long l;
|
||||
if (Int64.TryParse(value, out l))
|
||||
{
|
||||
return l;
|
||||
}
|
||||
|
||||
throw new Exception("Cannot unmarshal type long");
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
|
||||
{
|
||||
if (untypedValue == null)
|
||||
{
|
||||
serializer.Serialize(writer, null);
|
||||
return;
|
||||
}
|
||||
|
||||
var value = (long)untypedValue;
|
||||
serializer.Serialize(writer, value.ToString(CultureInfo.InvariantCulture));
|
||||
return;
|
||||
}
|
||||
|
||||
public static readonly ParseStringConverter Singleton = new ParseStringConverter();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public partial class Peer
|
||||
{
|
||||
[JsonProperty("info")] public Info Info { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public partial class SubaddrIndex
|
||||
{
|
||||
[JsonProperty("major")] public long Major { get; set; }
|
||||
[JsonProperty("minor")] public long Minor { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public partial class SubaddressAccount
|
||||
{
|
||||
[JsonProperty("account_index")] public long AccountIndex { get; set; }
|
||||
[JsonProperty("balance")] public decimal Balance { get; set; }
|
||||
[JsonProperty("base_address")] public string BaseAddress { get; set; }
|
||||
[JsonProperty("label")] public string Label { get; set; }
|
||||
[JsonProperty("tag")] public string Tag { get; set; }
|
||||
[JsonProperty("unlocked_balance")] public decimal UnlockedBalance { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public partial class SyncInfoResponse
|
||||
{
|
||||
[JsonProperty("height")] public long Height { get; set; }
|
||||
[JsonProperty("peers")] public List<Peer> Peers { get; set; }
|
||||
[JsonProperty("status")] public string Status { get; set; }
|
||||
[JsonProperty("target_height")] public long? TargetHeight { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.Utils
|
||||
{
|
||||
public class ZcashMoney
|
||||
{
|
||||
public static decimal Convert(long zat)
|
||||
{
|
||||
var amt = zat.ToString(CultureInfo.InvariantCulture).PadLeft(8, '0');
|
||||
amt = amt.Length == 8 ? $"0.{amt}" : amt.Insert(amt.Length - 8, ".");
|
||||
|
||||
return decimal.Parse(amt, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public static long Convert(decimal Zcash)
|
||||
{
|
||||
return System.Convert.ToInt64(Zcash * 100000000);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace BTCPayServer.Plugins.Altcoins;
|
||||
|
||||
public class ZcashLikeSpecificBtcPayNetwork : BTCPayNetworkBase
|
||||
{
|
||||
public int MaxTrackedConfirmation = 10;
|
||||
public string UriScheme { get; set; }
|
||||
}
|
||||
@@ -181,8 +181,7 @@
|
||||
"BTCPAY_CHEATMODE": "true",
|
||||
"BTCPAY_EXPLORERPOSTGRES": "User ID=postgres;Include Error Detail=true;Host=127.0.0.1;Port=39372;Database=nbxplorer",
|
||||
"BTCPAY_XMR_DAEMON_URI": "http://127.0.0.1:18081",
|
||||
"BTCPAY_XMR_WALLET_DAEMON_URI": "http://127.0.0.1:18082",
|
||||
"BTCPAY_XMR_WALLET_DAEMON_WALLETDIR": "/path/to/monero_wallet"
|
||||
"BTCPAY_XMR_WALLET_DAEMON_URI": "http://127.0.0.1:18082"
|
||||
},
|
||||
"applicationUrl": "https://localhost:14142/"
|
||||
},
|
||||
@@ -221,10 +220,7 @@
|
||||
"BTCPAY_DOCKERDEPLOYMENT": "true",
|
||||
"BTCPAY_RECOMMENDED-PLUGINS": "",
|
||||
"BTCPAY_CHEATMODE": "true",
|
||||
"BTCPAY_EXPLORERPOSTGRES": "User ID=postgres;Include Error Detail=true;Host=127.0.0.1;Port=39372;Database=nbxplorer",
|
||||
"BTCPAY_XMR_DAEMON_URI": "http://127.0.0.1:18081",
|
||||
"BTCPAY_XMR_WALLET_DAEMON_URI": "http://127.0.0.1:18082",
|
||||
"BTCPAY_XMR_WALLET_DAEMON_WALLETDIR": "/path/to/monero_wallet"
|
||||
"BTCPAY_EXPLORERPOSTGRES": "User ID=postgres;Include Error Detail=true;Host=127.0.0.1;Port=39372;Database=nbxplorer"
|
||||
},
|
||||
"applicationUrl": "https://localhost:14142/"
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.Configuration
|
||||
{
|
||||
public class MoneroLikeConfiguration
|
||||
{
|
||||
public Dictionary<string, MoneroLikeConfigurationItem> MoneroLikeConfigurationItems { get; set; } =
|
||||
new Dictionary<string, MoneroLikeConfigurationItem>();
|
||||
}
|
||||
|
||||
public class MoneroLikeConfigurationItem
|
||||
{
|
||||
public Uri DaemonRpcUri { get; set; }
|
||||
public Uri InternalWalletRpcUri { get; set; }
|
||||
public string WalletDirectory { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Services.Altcoins.Monero.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.Payments
|
||||
{
|
||||
public class MoneroCheckoutModelExtension : ICheckoutModelExtension
|
||||
{
|
||||
private readonly BTCPayNetworkBase _network;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly IPaymentLinkExtension paymentLinkExtension;
|
||||
|
||||
public MoneroCheckoutModelExtension(
|
||||
PaymentMethodId paymentMethodId,
|
||||
IEnumerable<IPaymentLinkExtension> paymentLinkExtensions,
|
||||
BTCPayNetworkBase network,
|
||||
PaymentMethodHandlerDictionary handlers)
|
||||
{
|
||||
PaymentMethodId = paymentMethodId;
|
||||
_network = network;
|
||||
_handlers = handlers;
|
||||
paymentLinkExtension = paymentLinkExtensions.Single(p => p.PaymentMethodId == PaymentMethodId);
|
||||
}
|
||||
public PaymentMethodId PaymentMethodId { get; }
|
||||
|
||||
public string Image => _network.CryptoImagePath;
|
||||
public string Badge => "";
|
||||
|
||||
public void ModifyCheckoutModel(CheckoutModelContext context)
|
||||
{
|
||||
if (context is not { Handler: MoneroLikePaymentMethodHandler handler })
|
||||
return;
|
||||
context.Model.CheckoutBodyComponentName = BitcoinCheckoutModelExtension.CheckoutBodyComponentName;
|
||||
var details = context.InvoiceEntity.GetPayments(true)
|
||||
.Select(p => p.GetDetails<MoneroLikePaymentData>(handler))
|
||||
.Where(p => p is not null)
|
||||
.FirstOrDefault();
|
||||
if (details is not null)
|
||||
{
|
||||
context.Model.ReceivedConfirmations = details.ConfirmationCount;
|
||||
context.Model.RequiredConfirmations = (int)MoneroListener.ConfirmationsRequired(details, context.InvoiceEntity.SpeedPolicy);
|
||||
}
|
||||
|
||||
context.Model.InvoiceBitcoinUrl = paymentLinkExtension.GetPaymentLink(context.Prompt, context.UrlHelper);
|
||||
context.Model.InvoiceBitcoinUrlQR = context.Model.InvoiceBitcoinUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using BTCPayServer.Payments;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.Payments
|
||||
{
|
||||
public class MoneroLikeOnChainPaymentMethodDetails
|
||||
{
|
||||
public long AccountIndex { get; set; }
|
||||
public long AddressIndex { get; set; }
|
||||
public long? InvoiceSettledConfirmationThreshold { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Plugins.Altcoins;
|
||||
using BTCPayServer.Services.Altcoins.Monero.Utils;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.Payments
|
||||
{
|
||||
public class MoneroLikePaymentData
|
||||
{
|
||||
public long SubaddressIndex { get; set; }
|
||||
public long SubaccountIndex { get; set; }
|
||||
public long BlockHeight { get; set; }
|
||||
public long ConfirmationCount { get; set; }
|
||||
public string TransactionId { get; set; }
|
||||
public long? InvoiceSettledConfirmationThreshold { get; set; }
|
||||
|
||||
public long LockTime { get; set; } = 0;
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Dom;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.BIP78.Sender;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Plugins.Altcoins;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services.Altcoins.Monero.RPC.Models;
|
||||
using BTCPayServer.Services.Altcoins.Monero.Services;
|
||||
using BTCPayServer.Services.Altcoins.Monero.Utils;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Payments;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.Payments
|
||||
{
|
||||
public class MoneroLikePaymentMethodHandler : IPaymentMethodHandler
|
||||
{
|
||||
private readonly MoneroLikeSpecificBtcPayNetwork _network;
|
||||
public MoneroLikeSpecificBtcPayNetwork Network => _network;
|
||||
public JsonSerializer Serializer { get; }
|
||||
private readonly MoneroRPCProvider _moneroRpcProvider;
|
||||
|
||||
public PaymentMethodId PaymentMethodId { get; }
|
||||
|
||||
public MoneroLikePaymentMethodHandler(MoneroLikeSpecificBtcPayNetwork network, MoneroRPCProvider moneroRpcProvider)
|
||||
{
|
||||
PaymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode);
|
||||
_network = network;
|
||||
Serializer = BlobSerializer.CreateSerializer().Serializer;
|
||||
_moneroRpcProvider = moneroRpcProvider;
|
||||
}
|
||||
|
||||
public Task BeforeFetchingRates(PaymentMethodContext context)
|
||||
{
|
||||
context.Prompt.Currency = _network.CryptoCode;
|
||||
context.Prompt.Divisibility = _network.Divisibility;
|
||||
if (context.Prompt.Activated)
|
||||
{
|
||||
var supportedPaymentMethod = ParsePaymentMethodConfig(context.PaymentMethodConfig);
|
||||
var walletClient = _moneroRpcProvider.WalletRpcClients[_network.CryptoCode];
|
||||
var daemonClient = _moneroRpcProvider.DaemonRpcClients[_network.CryptoCode];
|
||||
context.State = new Prepare()
|
||||
{
|
||||
GetFeeRate = daemonClient.SendCommandAsync<GetFeeEstimateRequest, GetFeeEstimateResponse>("get_fee_estimate", new GetFeeEstimateRequest()),
|
||||
ReserveAddress = s => walletClient.SendCommandAsync<CreateAddressRequest, CreateAddressResponse>("create_address", new CreateAddressRequest() { Label = $"btcpay invoice #{s}", AccountIndex = supportedPaymentMethod.AccountIndex }),
|
||||
AccountIndex = supportedPaymentMethod.AccountIndex
|
||||
};
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task ConfigurePrompt(PaymentMethodContext context)
|
||||
{
|
||||
if (!_moneroRpcProvider.IsAvailable(_network.CryptoCode))
|
||||
throw new PaymentMethodUnavailableException($"Node or wallet not available");
|
||||
var invoice = context.InvoiceEntity;
|
||||
Prepare moneroPrepare = (Prepare)context.State;
|
||||
var feeRatePerKb = await moneroPrepare.GetFeeRate;
|
||||
var address = await moneroPrepare.ReserveAddress(invoice.Id);
|
||||
|
||||
var feeRatePerByte = feeRatePerKb.Fee / 1024;
|
||||
var details = new MoneroLikeOnChainPaymentMethodDetails()
|
||||
{
|
||||
AccountIndex = moneroPrepare.AccountIndex,
|
||||
AddressIndex = address.AddressIndex,
|
||||
InvoiceSettledConfirmationThreshold = ParsePaymentMethodConfig(context.PaymentMethodConfig).InvoiceSettledConfirmationThreshold
|
||||
};
|
||||
context.Prompt.Destination = address.Address;
|
||||
context.Prompt.PaymentMethodFee = MoneroMoney.Convert(feeRatePerByte * 100);
|
||||
context.Prompt.Details = JObject.FromObject(details, Serializer);
|
||||
context.TrackedDestinations.Add(address.Address);
|
||||
}
|
||||
private MoneroPaymentPromptDetails ParsePaymentMethodConfig(JToken config)
|
||||
{
|
||||
return config.ToObject<MoneroPaymentPromptDetails>(Serializer) ?? throw new FormatException($"Invalid {nameof(MoneroLikePaymentMethodHandler)}");
|
||||
}
|
||||
object IPaymentMethodHandler.ParsePaymentMethodConfig(JToken config)
|
||||
{
|
||||
return ParsePaymentMethodConfig(config);
|
||||
}
|
||||
|
||||
class Prepare
|
||||
{
|
||||
public Task<GetFeeEstimateResponse> GetFeeRate;
|
||||
public Func<string, Task<CreateAddressResponse>> ReserveAddress;
|
||||
|
||||
public long AccountIndex { get; internal set; }
|
||||
}
|
||||
|
||||
public MoneroLikeOnChainPaymentMethodDetails ParsePaymentPromptDetails(Newtonsoft.Json.Linq.JToken details)
|
||||
{
|
||||
return details.ToObject<MoneroLikeOnChainPaymentMethodDetails>(Serializer);
|
||||
}
|
||||
object IPaymentMethodHandler.ParsePaymentPromptDetails(Newtonsoft.Json.Linq.JToken details)
|
||||
{
|
||||
return ParsePaymentPromptDetails(details);
|
||||
}
|
||||
|
||||
public MoneroLikePaymentData ParsePaymentDetails(JToken details)
|
||||
{
|
||||
return details.ToObject<MoneroLikePaymentData>(Serializer) ?? throw new FormatException($"Invalid {nameof(MoneroLikePaymentMethodHandler)}");
|
||||
}
|
||||
object IPaymentMethodHandler.ParsePaymentDetails(JToken details)
|
||||
{
|
||||
return ParsePaymentDetails(details);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Globalization;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Plugins.Altcoins;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.Payments
|
||||
{
|
||||
public class MoneroPaymentLinkExtension : IPaymentLinkExtension
|
||||
{
|
||||
private readonly MoneroLikeSpecificBtcPayNetwork _network;
|
||||
|
||||
public MoneroPaymentLinkExtension(PaymentMethodId paymentMethodId, MoneroLikeSpecificBtcPayNetwork network)
|
||||
{
|
||||
PaymentMethodId = paymentMethodId;
|
||||
_network = network;
|
||||
}
|
||||
public PaymentMethodId PaymentMethodId { get; }
|
||||
|
||||
public string? GetPaymentLink(PaymentPrompt prompt, IUrlHelper? urlHelper)
|
||||
{
|
||||
var due = prompt.Calculate().Due;
|
||||
return $"{_network.UriScheme}:{prompt.Destination}?tx_amount={due.ToString(CultureInfo.InvariantCulture)}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using BTCPayServer.Payments;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.Payments
|
||||
{
|
||||
public class MoneroPaymentPromptDetails
|
||||
{
|
||||
public long AccountIndex { get; set; }
|
||||
public long? InvoiceSettledConfirmationThreshold { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using BTCPayServer.Filters;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC
|
||||
{
|
||||
[Route("[controller]")]
|
||||
[OnlyIfSupportAttribute("XMR-CHAIN")]
|
||||
public class MoneroLikeDaemonCallbackController : Controller
|
||||
{
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
|
||||
public MoneroLikeDaemonCallbackController(EventAggregator eventAggregator)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
}
|
||||
[HttpGet("block")]
|
||||
public IActionResult OnBlockNotify(string hash, string cryptoCode)
|
||||
{
|
||||
_eventAggregator.Publish(new MoneroEvent()
|
||||
{
|
||||
BlockHash = hash,
|
||||
CryptoCode = cryptoCode.ToUpperInvariant()
|
||||
});
|
||||
return Ok();
|
||||
}
|
||||
[HttpGet("tx")]
|
||||
public IActionResult OnTransactionNotify(string hash, string cryptoCode)
|
||||
{
|
||||
_eventAggregator.Publish(new MoneroEvent()
|
||||
{
|
||||
TransactionHash = hash,
|
||||
CryptoCode = cryptoCode.ToUpperInvariant()
|
||||
});
|
||||
return Ok();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.RPC
|
||||
{
|
||||
public class MoneroEvent
|
||||
{
|
||||
public string BlockHash { get; set; }
|
||||
public string TransactionHash { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return
|
||||
$"{CryptoCode}: {(string.IsNullOrEmpty(TransactionHash) ? string.Empty : "Tx Update")}{(string.IsNullOrEmpty(BlockHash) ? string.Empty : "New Block")} ({TransactionHash ?? string.Empty}{BlockHash ?? string.Empty})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Services.Altcoins.Monero.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.Services
|
||||
{
|
||||
public class MoneroLikeSummaryUpdaterHostedService : IHostedService
|
||||
{
|
||||
private readonly MoneroRPCProvider _MoneroRpcProvider;
|
||||
private readonly MoneroLikeConfiguration _moneroLikeConfiguration;
|
||||
|
||||
public Logs Logs { get; }
|
||||
|
||||
private CancellationTokenSource _Cts;
|
||||
public MoneroLikeSummaryUpdaterHostedService(MoneroRPCProvider moneroRpcProvider, MoneroLikeConfiguration moneroLikeConfiguration, Logs logs)
|
||||
{
|
||||
_MoneroRpcProvider = moneroRpcProvider;
|
||||
_moneroLikeConfiguration = moneroLikeConfiguration;
|
||||
Logs = logs;
|
||||
}
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_Cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
foreach (var moneroLikeConfigurationItem in _moneroLikeConfiguration.MoneroLikeConfigurationItems)
|
||||
{
|
||||
_ = StartLoop(_Cts.Token, moneroLikeConfigurationItem.Key);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task StartLoop(CancellationToken cancellation, string cryptoCode)
|
||||
{
|
||||
Logs.PayServer.LogInformation($"Starting listening Monero-like daemons ({cryptoCode})");
|
||||
try
|
||||
{
|
||||
while (!cancellation.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _MoneroRpcProvider.UpdateSummary(cryptoCode);
|
||||
if (_MoneroRpcProvider.IsAvailable(cryptoCode))
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(1), cancellation);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(10), cancellation);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (!cancellation.IsCancellationRequested)
|
||||
{
|
||||
Logs.PayServer.LogError(ex, $"Unhandled exception in Summary updater ({cryptoCode})");
|
||||
await Task.Delay(TimeSpan.FromSeconds(10), cancellation);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch when (cancellation.IsCancellationRequested) { }
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_Cts?.Cancel();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,401 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Plugins.Altcoins;
|
||||
using BTCPayServer.Services.Altcoins.Monero.Configuration;
|
||||
using BTCPayServer.Services.Altcoins.Monero.Payments;
|
||||
using BTCPayServer.Services.Altcoins.Monero.RPC;
|
||||
using BTCPayServer.Services.Altcoins.Monero.RPC.Models;
|
||||
using BTCPayServer.Services.Altcoins.Monero.Utils;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Utils;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.Services
|
||||
{
|
||||
public class MoneroListener : EventHostedServiceBase
|
||||
{
|
||||
private readonly InvoiceRepository _invoiceRepository;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly MoneroRPCProvider _moneroRpcProvider;
|
||||
private readonly MoneroLikeConfiguration _MoneroLikeConfiguration;
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
private readonly ILogger<MoneroListener> _logger;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly InvoiceActivator _invoiceActivator;
|
||||
private readonly PaymentService _paymentService;
|
||||
|
||||
public MoneroListener(InvoiceRepository invoiceRepository,
|
||||
EventAggregator eventAggregator,
|
||||
MoneroRPCProvider moneroRpcProvider,
|
||||
MoneroLikeConfiguration moneroLikeConfiguration,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
ILogger<MoneroListener> logger,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
InvoiceActivator invoiceActivator,
|
||||
PaymentService paymentService) : base(eventAggregator, logger)
|
||||
{
|
||||
_invoiceRepository = invoiceRepository;
|
||||
_eventAggregator = eventAggregator;
|
||||
_moneroRpcProvider = moneroRpcProvider;
|
||||
_MoneroLikeConfiguration = moneroLikeConfiguration;
|
||||
_networkProvider = networkProvider;
|
||||
_logger = logger;
|
||||
_handlers = handlers;
|
||||
_invoiceActivator = invoiceActivator;
|
||||
_paymentService = paymentService;
|
||||
}
|
||||
|
||||
protected override void SubscribeToEvents()
|
||||
{
|
||||
base.SubscribeToEvents();
|
||||
Subscribe<MoneroEvent>();
|
||||
Subscribe<MoneroRPCProvider.MoneroDaemonStateChange>();
|
||||
}
|
||||
|
||||
protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken)
|
||||
{
|
||||
if (evt is MoneroRPCProvider.MoneroDaemonStateChange stateChange)
|
||||
{
|
||||
if (_moneroRpcProvider.IsAvailable(stateChange.CryptoCode))
|
||||
{
|
||||
_logger.LogInformation($"{stateChange.CryptoCode} just became available");
|
||||
_ = UpdateAnyPendingMoneroLikePayment(stateChange.CryptoCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation($"{stateChange.CryptoCode} just became unavailable");
|
||||
}
|
||||
}
|
||||
else if (evt is MoneroEvent moneroEvent)
|
||||
{
|
||||
if (!_moneroRpcProvider.IsAvailable(moneroEvent.CryptoCode))
|
||||
return;
|
||||
|
||||
if (!string.IsNullOrEmpty(moneroEvent.BlockHash))
|
||||
{
|
||||
await OnNewBlock(moneroEvent.CryptoCode);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(moneroEvent.TransactionHash))
|
||||
{
|
||||
await OnTransactionUpdated(moneroEvent.CryptoCode, moneroEvent.TransactionHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReceivedPayment(InvoiceEntity invoice, PaymentEntity payment)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
$"Invoice {invoice.Id} received payment {payment.Value} {payment.Currency} {payment.Id}");
|
||||
|
||||
var prompt = invoice.GetPaymentPrompt(payment.PaymentMethodId);
|
||||
|
||||
if (prompt != null &&
|
||||
prompt.Activated &&
|
||||
prompt.Destination == payment.Destination &&
|
||||
prompt.Calculate().Due > 0.0m)
|
||||
{
|
||||
await _invoiceActivator.ActivateInvoicePaymentMethod(invoice.Id, payment.PaymentMethodId, true);
|
||||
invoice = await _invoiceRepository.GetInvoice(invoice.Id);
|
||||
}
|
||||
|
||||
_eventAggregator.Publish(
|
||||
new InvoiceEvent(invoice, InvoiceEvent.ReceivedPayment) { Payment = payment });
|
||||
}
|
||||
|
||||
private async Task UpdatePaymentStates(string cryptoCode, InvoiceEntity[] invoices)
|
||||
{
|
||||
if (!invoices.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var moneroWalletRpcClient = _moneroRpcProvider.WalletRpcClients[cryptoCode];
|
||||
var network = _networkProvider.GetNetwork(cryptoCode);
|
||||
var paymentId = PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode);
|
||||
var handler = (MoneroLikePaymentMethodHandler)_handlers[paymentId];
|
||||
|
||||
//get all the required data in one list (invoice, its existing payments and the current payment method details)
|
||||
var expandedInvoices = invoices.Select(entity => (Invoice: entity,
|
||||
ExistingPayments: GetAllMoneroLikePayments(entity, cryptoCode),
|
||||
Prompt: entity.GetPaymentPrompt(paymentId),
|
||||
PaymentMethodDetails: handler.ParsePaymentPromptDetails(entity.GetPaymentPrompt(paymentId).Details)))
|
||||
.Select(tuple => (
|
||||
tuple.Invoice,
|
||||
tuple.PaymentMethodDetails,
|
||||
tuple.Prompt,
|
||||
ExistingPayments: tuple.ExistingPayments.Select(entity =>
|
||||
(Payment: entity, PaymentData: handler.ParsePaymentDetails(entity.Details),
|
||||
tuple.Invoice))
|
||||
));
|
||||
|
||||
var existingPaymentData = expandedInvoices.SelectMany(tuple => tuple.ExistingPayments);
|
||||
|
||||
var accountToAddressQuery = new Dictionary<long, List<long>>();
|
||||
//create list of subaddresses to account to query the monero wallet
|
||||
foreach (var expandedInvoice in expandedInvoices)
|
||||
{
|
||||
var addressIndexList =
|
||||
accountToAddressQuery.GetValueOrDefault(expandedInvoice.PaymentMethodDetails.AccountIndex,
|
||||
new List<long>());
|
||||
|
||||
addressIndexList.AddRange(
|
||||
expandedInvoice.ExistingPayments.Select(tuple => tuple.PaymentData.SubaddressIndex));
|
||||
addressIndexList.Add(expandedInvoice.PaymentMethodDetails.AddressIndex);
|
||||
accountToAddressQuery.AddOrReplace(expandedInvoice.PaymentMethodDetails.AccountIndex, addressIndexList);
|
||||
}
|
||||
|
||||
var tasks = accountToAddressQuery.ToDictionary(datas => datas.Key,
|
||||
datas => moneroWalletRpcClient.SendCommandAsync<GetTransfersRequest, GetTransfersResponse>(
|
||||
"get_transfers",
|
||||
new GetTransfersRequest()
|
||||
{
|
||||
AccountIndex = datas.Key,
|
||||
In = true,
|
||||
SubaddrIndices = datas.Value.Distinct().ToList()
|
||||
}));
|
||||
|
||||
await Task.WhenAll(tasks.Values);
|
||||
|
||||
|
||||
var transferProcessingTasks = new List<Task>();
|
||||
|
||||
var updatedPaymentEntities = new List<(PaymentEntity Payment, InvoiceEntity invoice)>();
|
||||
foreach (var keyValuePair in tasks)
|
||||
{
|
||||
var transfers = keyValuePair.Value.Result.In;
|
||||
if (transfers == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
transferProcessingTasks.AddRange(transfers.Select(transfer =>
|
||||
{
|
||||
InvoiceEntity invoice = null;
|
||||
var existingMatch = existingPaymentData.SingleOrDefault(tuple =>
|
||||
tuple.Payment.Destination == transfer.Address &&
|
||||
tuple.PaymentData.TransactionId == transfer.Txid);
|
||||
|
||||
if (existingMatch.Invoice != null)
|
||||
{
|
||||
invoice = existingMatch.Invoice;
|
||||
}
|
||||
else
|
||||
{
|
||||
var newMatch = expandedInvoices.SingleOrDefault(tuple =>
|
||||
tuple.Prompt.Destination == transfer.Address);
|
||||
|
||||
if (newMatch.Invoice == null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
invoice = newMatch.Invoice;
|
||||
}
|
||||
|
||||
|
||||
return HandlePaymentData(cryptoCode, transfer.Address, transfer.Amount, transfer.SubaddrIndex.Major,
|
||||
transfer.SubaddrIndex.Minor, transfer.Txid, transfer.Confirmations, transfer.Height, transfer.UnlockTime,invoice,
|
||||
updatedPaymentEntities);
|
||||
}));
|
||||
}
|
||||
|
||||
transferProcessingTasks.Add(
|
||||
_paymentService.UpdatePayments(updatedPaymentEntities.Select(tuple => tuple.Item1).ToList()));
|
||||
await Task.WhenAll(transferProcessingTasks);
|
||||
foreach (var valueTuples in updatedPaymentEntities.GroupBy(entity => entity.Item2))
|
||||
{
|
||||
if (valueTuples.Any())
|
||||
{
|
||||
_eventAggregator.Publish(new Events.InvoiceNeedUpdateEvent(valueTuples.Key.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnNewBlock(string cryptoCode)
|
||||
{
|
||||
await UpdateAnyPendingMoneroLikePayment(cryptoCode);
|
||||
_eventAggregator.Publish(new NewBlockEvent() { PaymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode) });
|
||||
}
|
||||
|
||||
private async Task OnTransactionUpdated(string cryptoCode, string transactionHash)
|
||||
{
|
||||
var paymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode);
|
||||
var transfer = await GetTransferByTxId(cryptoCode, transactionHash, this.CancellationToken);
|
||||
|
||||
var paymentsToUpdate = new List<(PaymentEntity Payment, InvoiceEntity invoice)>();
|
||||
|
||||
//group all destinations of the tx together and loop through the sets
|
||||
foreach (var destination in transfer.Transfers.GroupBy(destination => destination.Address))
|
||||
{
|
||||
//find the invoice corresponding to this address, else skip
|
||||
var invoice = await _invoiceRepository.GetInvoiceFromAddress(paymentMethodId, destination.Key);
|
||||
if (invoice == null)
|
||||
continue;
|
||||
|
||||
var index = destination.First().SubaddrIndex;
|
||||
|
||||
await HandlePaymentData(cryptoCode,
|
||||
destination.Key,
|
||||
destination.Sum(destination1 => destination1.Amount),
|
||||
index.Major,
|
||||
index.Minor,
|
||||
transfer.Transfer.Txid,
|
||||
transfer.Transfer.Confirmations,
|
||||
transfer.Transfer.Height
|
||||
, transfer.Transfer.UnlockTime,invoice, paymentsToUpdate);
|
||||
}
|
||||
|
||||
if (paymentsToUpdate.Any())
|
||||
{
|
||||
await _paymentService.UpdatePayments(paymentsToUpdate.Select(tuple => tuple.Payment).ToList());
|
||||
foreach (var valueTuples in paymentsToUpdate.GroupBy(entity => entity.invoice))
|
||||
{
|
||||
if (valueTuples.Any())
|
||||
{
|
||||
_eventAggregator.Publish(new Events.InvoiceNeedUpdateEvent(valueTuples.Key.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<GetTransferByTransactionIdResponse> GetTransferByTxId(string cryptoCode,
|
||||
string transactionHash, CancellationToken cancellationToken)
|
||||
{
|
||||
var accounts = await _moneroRpcProvider.WalletRpcClients[cryptoCode].SendCommandAsync<GetAccountsRequest, GetAccountsResponse>("get_accounts", new GetAccountsRequest(), cancellationToken);
|
||||
var accountIndexes = accounts
|
||||
.SubaddressAccounts
|
||||
.Select(a => new long?(a.AccountIndex))
|
||||
.ToList();
|
||||
if (accountIndexes.Count is 0)
|
||||
accountIndexes.Add(null);
|
||||
var req = accountIndexes
|
||||
.Select(i => GetTransferByTxId(cryptoCode, transactionHash, i))
|
||||
.ToArray();
|
||||
foreach (var task in req)
|
||||
{
|
||||
var result = await task;
|
||||
if (result != null)
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<GetTransferByTransactionIdResponse> GetTransferByTxId(string cryptoCode, string transactionHash, long? accountIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _moneroRpcProvider.WalletRpcClients[cryptoCode]
|
||||
.SendCommandAsync<GetTransferByTransactionIdRequest, GetTransferByTransactionIdResponse>(
|
||||
"get_transfer_by_txid",
|
||||
new GetTransferByTransactionIdRequest()
|
||||
{
|
||||
TransactionId = transactionHash,
|
||||
AccountIndex = accountIndex
|
||||
});
|
||||
return result;
|
||||
}
|
||||
catch (JsonRpcClient.JsonRpcApiException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandlePaymentData(string cryptoCode, string address, long totalAmount, long subaccountIndex,
|
||||
long subaddressIndex,
|
||||
string txId, long confirmations, long blockHeight, long locktime, InvoiceEntity invoice,
|
||||
List<(PaymentEntity Payment, InvoiceEntity invoice)> paymentsToUpdate)
|
||||
{
|
||||
var network = _networkProvider.GetNetwork(cryptoCode);
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode);
|
||||
var handler = (MoneroLikePaymentMethodHandler)_handlers[pmi];
|
||||
var promptDetails = handler.ParsePaymentPromptDetails(invoice.GetPaymentPrompt(pmi).Details);
|
||||
var details = new MoneroLikePaymentData()
|
||||
{
|
||||
SubaccountIndex = subaccountIndex,
|
||||
SubaddressIndex = subaddressIndex,
|
||||
TransactionId = txId,
|
||||
ConfirmationCount = confirmations,
|
||||
BlockHeight = blockHeight,
|
||||
LockTime = locktime,
|
||||
InvoiceSettledConfirmationThreshold = promptDetails.InvoiceSettledConfirmationThreshold
|
||||
};
|
||||
var status = GetStatus(details, invoice.SpeedPolicy) ? PaymentStatus.Settled : PaymentStatus.Processing;
|
||||
var paymentData = new Data.PaymentData()
|
||||
{
|
||||
Status = status,
|
||||
Amount = MoneroMoney.Convert(totalAmount),
|
||||
Created = DateTimeOffset.UtcNow,
|
||||
Id = $"{txId}#{subaccountIndex}#{subaddressIndex}",
|
||||
Currency = network.CryptoCode,
|
||||
InvoiceDataId = invoice.Id,
|
||||
}.Set(invoice, handler, details);
|
||||
|
||||
|
||||
//check if this tx exists as a payment to this invoice already
|
||||
var alreadyExistingPaymentThatMatches = GetAllMoneroLikePayments(invoice, cryptoCode)
|
||||
.SingleOrDefault(c => c.Id == paymentData.Id && c.PaymentMethodId == pmi);
|
||||
|
||||
//if it doesnt, add it and assign a new monerolike address to the system if a balance is still due
|
||||
if (alreadyExistingPaymentThatMatches == null)
|
||||
{
|
||||
var payment = await _paymentService.AddPayment(paymentData, [txId]);
|
||||
if (payment != null)
|
||||
await ReceivedPayment(invoice, payment);
|
||||
}
|
||||
else
|
||||
{
|
||||
//else update it with the new data
|
||||
alreadyExistingPaymentThatMatches.Status = status;
|
||||
alreadyExistingPaymentThatMatches.Details = JToken.FromObject(details, handler.Serializer);
|
||||
paymentsToUpdate.Add((alreadyExistingPaymentThatMatches, invoice));
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetStatus(MoneroLikePaymentData details, SpeedPolicy speedPolicy)
|
||||
=> ConfirmationsRequired(details, speedPolicy) <= details.ConfirmationCount;
|
||||
|
||||
public static long ConfirmationsRequired(MoneroLikePaymentData details, SpeedPolicy speedPolicy)
|
||||
=> (details, speedPolicy) switch
|
||||
{
|
||||
(_, _) when details.ConfirmationCount < details.LockTime => details.LockTime - details.ConfirmationCount,
|
||||
({ InvoiceSettledConfirmationThreshold: long v }, _) => v,
|
||||
(_, SpeedPolicy.HighSpeed) => 0,
|
||||
(_, SpeedPolicy.MediumSpeed) => 1,
|
||||
(_, SpeedPolicy.LowMediumSpeed) => 2,
|
||||
(_, SpeedPolicy.LowSpeed) => 6,
|
||||
_ => 6,
|
||||
};
|
||||
|
||||
|
||||
private async Task UpdateAnyPendingMoneroLikePayment(string cryptoCode)
|
||||
{
|
||||
var paymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode);
|
||||
var invoices = await _invoiceRepository.GetMonitoredInvoices(paymentMethodId);
|
||||
if (!invoices.Any())
|
||||
return;
|
||||
invoices = invoices.Where(entity => entity.GetPaymentPrompt(paymentMethodId)?.Activated is true).ToArray();
|
||||
await UpdatePaymentStates(cryptoCode, invoices);
|
||||
}
|
||||
|
||||
private IEnumerable<PaymentEntity> GetAllMoneroLikePayments(InvoiceEntity invoice, string cryptoCode)
|
||||
{
|
||||
return invoice.GetPayments(false)
|
||||
.Where(p => p.PaymentMethodId == PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Immutable;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services.Altcoins.Monero.Configuration;
|
||||
using BTCPayServer.Services.Altcoins.Monero.RPC;
|
||||
using BTCPayServer.Services.Altcoins.Monero.RPC.Models;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.Services
|
||||
{
|
||||
public class MoneroRPCProvider
|
||||
{
|
||||
private readonly MoneroLikeConfiguration _moneroLikeConfiguration;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
public ImmutableDictionary<string, JsonRpcClient> DaemonRpcClients;
|
||||
public ImmutableDictionary<string, JsonRpcClient> WalletRpcClients;
|
||||
|
||||
private readonly ConcurrentDictionary<string, MoneroLikeSummary> _summaries =
|
||||
new ConcurrentDictionary<string, MoneroLikeSummary>();
|
||||
|
||||
public ConcurrentDictionary<string, MoneroLikeSummary> Summaries => _summaries;
|
||||
|
||||
public MoneroRPCProvider(MoneroLikeConfiguration moneroLikeConfiguration, EventAggregator eventAggregator, IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_moneroLikeConfiguration = moneroLikeConfiguration;
|
||||
_eventAggregator = eventAggregator;
|
||||
DaemonRpcClients =
|
||||
_moneroLikeConfiguration.MoneroLikeConfigurationItems.ToImmutableDictionary(pair => pair.Key,
|
||||
pair => new JsonRpcClient(pair.Value.DaemonRpcUri, pair.Value.Username, pair.Value.Password, httpClientFactory.CreateClient($"{pair.Key}client")));
|
||||
WalletRpcClients =
|
||||
_moneroLikeConfiguration.MoneroLikeConfigurationItems.ToImmutableDictionary(pair => pair.Key,
|
||||
pair => new JsonRpcClient(pair.Value.InternalWalletRpcUri, "", "", httpClientFactory.CreateClient($"{pair.Key}client")));
|
||||
}
|
||||
|
||||
public bool IsAvailable(string cryptoCode)
|
||||
{
|
||||
cryptoCode = cryptoCode.ToUpperInvariant();
|
||||
return _summaries.ContainsKey(cryptoCode) && IsAvailable(_summaries[cryptoCode]);
|
||||
}
|
||||
|
||||
private bool IsAvailable(MoneroLikeSummary summary)
|
||||
{
|
||||
return summary.Synced &&
|
||||
summary.WalletAvailable;
|
||||
}
|
||||
|
||||
public async Task<MoneroLikeSummary> UpdateSummary(string cryptoCode)
|
||||
{
|
||||
if (!DaemonRpcClients.TryGetValue(cryptoCode.ToUpperInvariant(), out var daemonRpcClient) ||
|
||||
!WalletRpcClients.TryGetValue(cryptoCode.ToUpperInvariant(), out var walletRpcClient))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var summary = new MoneroLikeSummary();
|
||||
try
|
||||
{
|
||||
var daemonResult =
|
||||
await daemonRpcClient.SendCommandAsync<JsonRpcClient.NoRequestModel, GetInfoResponse>("get_info",
|
||||
JsonRpcClient.NoRequestModel.Instance);
|
||||
summary.TargetHeight = daemonResult.TargetHeight.GetValueOrDefault(0);
|
||||
summary.CurrentHeight = daemonResult.Height;
|
||||
summary.TargetHeight = summary.TargetHeight == 0 ? summary.CurrentHeight : summary.TargetHeight;
|
||||
summary.Synced = !daemonResult.BusySyncing;
|
||||
summary.UpdatedAt = DateTime.UtcNow;
|
||||
summary.DaemonAvailable = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
summary.DaemonAvailable = false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var walletResult =
|
||||
await walletRpcClient.SendCommandAsync<JsonRpcClient.NoRequestModel, GetHeightResponse>(
|
||||
"get_height", JsonRpcClient.NoRequestModel.Instance);
|
||||
|
||||
summary.WalletHeight = walletResult.Height;
|
||||
summary.WalletAvailable = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
summary.WalletAvailable = false;
|
||||
}
|
||||
|
||||
var changed = !_summaries.ContainsKey(cryptoCode) || IsAvailable(cryptoCode) != IsAvailable(summary);
|
||||
|
||||
_summaries.AddOrReplace(cryptoCode, summary);
|
||||
if (changed)
|
||||
{
|
||||
_eventAggregator.Publish(new MoneroDaemonStateChange() { Summary = summary, CryptoCode = cryptoCode });
|
||||
}
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
|
||||
public class MoneroDaemonStateChange
|
||||
{
|
||||
public string CryptoCode { get; set; }
|
||||
public MoneroLikeSummary Summary { get; set; }
|
||||
}
|
||||
|
||||
public class MoneroLikeSummary
|
||||
{
|
||||
public bool Synced { get; set; }
|
||||
public long CurrentHeight { get; set; }
|
||||
public long WalletHeight { get; set; }
|
||||
public long TargetHeight { get; set; }
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
public bool DaemonAvailable { get; set; }
|
||||
public bool WalletAvailable { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Payments;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.Services
|
||||
{
|
||||
public class MoneroSyncSummaryProvider : ISyncSummaryProvider
|
||||
{
|
||||
private readonly MoneroRPCProvider _moneroRpcProvider;
|
||||
|
||||
public MoneroSyncSummaryProvider(MoneroRPCProvider moneroRpcProvider)
|
||||
{
|
||||
_moneroRpcProvider = moneroRpcProvider;
|
||||
}
|
||||
|
||||
public bool AllAvailable()
|
||||
{
|
||||
return _moneroRpcProvider.Summaries.All(pair => pair.Value.WalletAvailable);
|
||||
}
|
||||
|
||||
public string Partial { get; } = "Monero/MoneroSyncSummary";
|
||||
public IEnumerable<ISyncStatus> GetStatuses()
|
||||
{
|
||||
return _moneroRpcProvider.Summaries.Select(pair => new MoneroSyncStatus()
|
||||
{
|
||||
Summary = pair.Value, PaymentMethodId = PaymentMethodId.Parse(pair.Key).ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class MoneroSyncStatus: SyncStatus, ISyncStatus
|
||||
{
|
||||
public override bool Available
|
||||
{
|
||||
get
|
||||
{
|
||||
return Summary?.WalletAvailable ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
public MoneroRPCProvider.MoneroLikeSummary Summary { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,393 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
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.Filters;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services.Altcoins.Monero.Configuration;
|
||||
using BTCPayServer.Services.Altcoins.Monero.Payments;
|
||||
using BTCPayServer.Services.Altcoins.Monero.RPC.Models;
|
||||
using BTCPayServer.Services.Altcoins.Monero.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.UI
|
||||
{
|
||||
[Route("stores/{storeId}/monerolike")]
|
||||
[OnlyIfSupportAttribute("XMR-CHAIN")]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Authorize(Policy = Policies.CanModifyServerSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public class UIMoneroLikeStoreController : Controller
|
||||
{
|
||||
private readonly MoneroLikeConfiguration _MoneroLikeConfiguration;
|
||||
private readonly StoreRepository _StoreRepository;
|
||||
private readonly MoneroRPCProvider _MoneroRpcProvider;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private IStringLocalizer StringLocalizer { get; }
|
||||
|
||||
public UIMoneroLikeStoreController(MoneroLikeConfiguration moneroLikeConfiguration,
|
||||
StoreRepository storeRepository, MoneroRPCProvider moneroRpcProvider,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
IStringLocalizer stringLocalizer)
|
||||
{
|
||||
_MoneroLikeConfiguration = moneroLikeConfiguration;
|
||||
_StoreRepository = storeRepository;
|
||||
_MoneroRpcProvider = moneroRpcProvider;
|
||||
_handlers = handlers;
|
||||
StringLocalizer = stringLocalizer;
|
||||
}
|
||||
|
||||
public StoreData StoreData => HttpContext.GetStoreData();
|
||||
|
||||
[HttpGet()]
|
||||
public async Task<IActionResult> GetStoreMoneroLikePaymentMethods()
|
||||
{
|
||||
return View(await GetVM(StoreData));
|
||||
}
|
||||
[NonAction]
|
||||
public async Task<MoneroLikePaymentMethodListViewModel> GetVM(StoreData storeData)
|
||||
{
|
||||
var excludeFilters = storeData.GetStoreBlob().GetExcludedPaymentMethods();
|
||||
|
||||
var accountsList = _MoneroLikeConfiguration.MoneroLikeConfigurationItems.ToDictionary(pair => pair.Key,
|
||||
pair => GetAccounts(pair.Key));
|
||||
|
||||
await Task.WhenAll(accountsList.Values);
|
||||
return new MoneroLikePaymentMethodListViewModel()
|
||||
{
|
||||
Items = _MoneroLikeConfiguration.MoneroLikeConfigurationItems.Select(pair =>
|
||||
GetMoneroLikePaymentMethodViewModel(storeData, pair.Key, excludeFilters,
|
||||
accountsList[pair.Key].Result))
|
||||
};
|
||||
}
|
||||
|
||||
private Task<GetAccountsResponse> GetAccounts(string cryptoCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_MoneroRpcProvider.Summaries.TryGetValue(cryptoCode, out var summary) && summary.WalletAvailable)
|
||||
{
|
||||
|
||||
return _MoneroRpcProvider.WalletRpcClients[cryptoCode].SendCommandAsync<GetAccountsRequest, GetAccountsResponse>("get_accounts", new GetAccountsRequest());
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return Task.FromResult<GetAccountsResponse>(null);
|
||||
}
|
||||
|
||||
private MoneroLikePaymentMethodViewModel GetMoneroLikePaymentMethodViewModel(
|
||||
StoreData storeData, string cryptoCode,
|
||||
IPaymentFilter excludeFilters, GetAccountsResponse accountsResponse)
|
||||
{
|
||||
var monero = storeData.GetPaymentMethodConfigs(_handlers)
|
||||
.Where(s => s.Value is MoneroPaymentPromptDetails)
|
||||
.Select(s => (PaymentMethodId: s.Key, Details: (MoneroPaymentPromptDetails)s.Value));
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode);
|
||||
var settings = monero.Where(method => method.PaymentMethodId == pmi).Select(m => m.Details).SingleOrDefault();
|
||||
_MoneroRpcProvider.Summaries.TryGetValue(cryptoCode, out var summary);
|
||||
_MoneroLikeConfiguration.MoneroLikeConfigurationItems.TryGetValue(cryptoCode,
|
||||
out var configurationItem);
|
||||
var fileAddress = Path.Combine(configurationItem.WalletDirectory, "wallet");
|
||||
var accounts = accountsResponse?.SubaddressAccounts?.Select(account =>
|
||||
new SelectListItem(
|
||||
$"{account.AccountIndex} - {(string.IsNullOrEmpty(account.Label) ? "No label" : account.Label)}",
|
||||
account.AccountIndex.ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
var settlementThresholdChoice = MoneroLikeSettlementThresholdChoice.StoreSpeedPolicy;
|
||||
if (settings != null && settings.InvoiceSettledConfirmationThreshold is { } confirmations)
|
||||
{
|
||||
settlementThresholdChoice = confirmations switch
|
||||
{
|
||||
0 => MoneroLikeSettlementThresholdChoice.ZeroConfirmation,
|
||||
1 => MoneroLikeSettlementThresholdChoice.AtLeastOne,
|
||||
10 => MoneroLikeSettlementThresholdChoice.AtLeastTen,
|
||||
_ => MoneroLikeSettlementThresholdChoice.Custom
|
||||
};
|
||||
}
|
||||
|
||||
return new MoneroLikePaymentMethodViewModel()
|
||||
{
|
||||
WalletFileFound = System.IO.File.Exists(fileAddress),
|
||||
Enabled =
|
||||
settings != null &&
|
||||
!excludeFilters.Match(PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode)),
|
||||
Summary = summary,
|
||||
CryptoCode = cryptoCode,
|
||||
AccountIndex = settings?.AccountIndex ?? accountsResponse?.SubaddressAccounts?.FirstOrDefault()?.AccountIndex ?? 0,
|
||||
Accounts = accounts == null ? null : new SelectList(accounts, nameof(SelectListItem.Value),
|
||||
nameof(SelectListItem.Text)),
|
||||
SettlementConfirmationThresholdChoice = settlementThresholdChoice,
|
||||
CustomSettlementConfirmationThreshold =
|
||||
settings != null &&
|
||||
settlementThresholdChoice is MoneroLikeSettlementThresholdChoice.Custom
|
||||
? settings.InvoiceSettledConfirmationThreshold
|
||||
: null
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{cryptoCode}")]
|
||||
public async Task<IActionResult> GetStoreMoneroLikePaymentMethod(string cryptoCode)
|
||||
{
|
||||
cryptoCode = cryptoCode.ToUpperInvariant();
|
||||
if (!_MoneroLikeConfiguration.MoneroLikeConfigurationItems.ContainsKey(cryptoCode))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var vm = GetMoneroLikePaymentMethodViewModel(StoreData, cryptoCode,
|
||||
StoreData.GetStoreBlob().GetExcludedPaymentMethods(), await GetAccounts(cryptoCode));
|
||||
return View(nameof(GetStoreMoneroLikePaymentMethod), vm);
|
||||
}
|
||||
|
||||
[HttpPost("{cryptoCode}")]
|
||||
[DisableRequestSizeLimit]
|
||||
public async Task<IActionResult> GetStoreMoneroLikePaymentMethod(MoneroLikePaymentMethodViewModel viewModel, string command, string cryptoCode)
|
||||
{
|
||||
cryptoCode = cryptoCode.ToUpperInvariant();
|
||||
if (!_MoneroLikeConfiguration.MoneroLikeConfigurationItems.TryGetValue(cryptoCode,
|
||||
out var configurationItem))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (command == "add-account")
|
||||
{
|
||||
try
|
||||
{
|
||||
var newAccount = await _MoneroRpcProvider.WalletRpcClients[cryptoCode].SendCommandAsync<CreateAccountRequest, CreateAccountResponse>("create_account", new CreateAccountRequest()
|
||||
{
|
||||
Label = viewModel.NewAccountLabel
|
||||
});
|
||||
viewModel.AccountIndex = newAccount.AccountIndex;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.AccountIndex), StringLocalizer["Could not create a new account."]);
|
||||
}
|
||||
|
||||
}
|
||||
else if (command == "upload-wallet")
|
||||
{
|
||||
var valid = true;
|
||||
if (viewModel.WalletFile == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.WalletFile), StringLocalizer["Please select the view-only wallet file"]);
|
||||
valid = false;
|
||||
}
|
||||
if (viewModel.WalletKeysFile == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.WalletKeysFile), StringLocalizer["Please select the view-only wallet keys file"]);
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (valid)
|
||||
{
|
||||
if (_MoneroRpcProvider.Summaries.TryGetValue(cryptoCode, out var summary))
|
||||
{
|
||||
if (summary.WalletAvailable)
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = StringLocalizer["There is already an active wallet configured for {0}. Replacing it would break any existing invoices!", cryptoCode].Value
|
||||
});
|
||||
return RedirectToAction(nameof(GetStoreMoneroLikePaymentMethod),
|
||||
new { cryptoCode });
|
||||
}
|
||||
}
|
||||
|
||||
var fileAddress = Path.Combine(configurationItem.WalletDirectory, "wallet");
|
||||
using (var fileStream = new FileStream(fileAddress, FileMode.Create))
|
||||
{
|
||||
await viewModel.WalletFile.CopyToAsync(fileStream);
|
||||
try
|
||||
{
|
||||
Exec($"chmod 666 {fileAddress}");
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
fileAddress = Path.Combine(configurationItem.WalletDirectory, "wallet.keys");
|
||||
using (var fileStream = new FileStream(fileAddress, FileMode.Create))
|
||||
{
|
||||
await viewModel.WalletKeysFile.CopyToAsync(fileStream);
|
||||
try
|
||||
{
|
||||
Exec($"chmod 666 {fileAddress}");
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fileAddress = Path.Combine(configurationItem.WalletDirectory, "password");
|
||||
using (var fileStream = new StreamWriter(fileAddress, false))
|
||||
{
|
||||
await fileStream.WriteAsync(viewModel.WalletPassword);
|
||||
try
|
||||
{
|
||||
Exec($"chmod 666 {fileAddress}");
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = await _MoneroRpcProvider.WalletRpcClients[cryptoCode].SendCommandAsync<OpenWalletRequest, OpenWalletResponse>("open_wallet", new OpenWalletRequest
|
||||
{
|
||||
Filename = "wallet",
|
||||
Password = viewModel.WalletPassword
|
||||
});
|
||||
if (response?.Error != null)
|
||||
{
|
||||
throw new Exception(response.Error.Message);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.AccountIndex), StringLocalizer["Could not open the wallet: {0}", ex.Message]);
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Info,
|
||||
Message = StringLocalizer["View-only wallet files uploaded. The wallet will soon become available."].Value
|
||||
});
|
||||
return RedirectToAction(nameof(GetStoreMoneroLikePaymentMethod), new { cryptoCode });
|
||||
}
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
|
||||
var vm = GetMoneroLikePaymentMethodViewModel(StoreData, cryptoCode,
|
||||
StoreData.GetStoreBlob().GetExcludedPaymentMethods(), await GetAccounts(cryptoCode));
|
||||
|
||||
vm.Enabled = viewModel.Enabled;
|
||||
vm.NewAccountLabel = viewModel.NewAccountLabel;
|
||||
vm.AccountIndex = viewModel.AccountIndex;
|
||||
vm.SettlementConfirmationThresholdChoice = viewModel.SettlementConfirmationThresholdChoice;
|
||||
vm.CustomSettlementConfirmationThreshold = viewModel.CustomSettlementConfirmationThreshold;
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
var storeData = StoreData;
|
||||
var blob = storeData.GetStoreBlob();
|
||||
storeData.SetPaymentMethodConfig(_handlers[PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode)], new MoneroPaymentPromptDetails()
|
||||
{
|
||||
AccountIndex = viewModel.AccountIndex,
|
||||
InvoiceSettledConfirmationThreshold = viewModel.SettlementConfirmationThresholdChoice switch
|
||||
{
|
||||
MoneroLikeSettlementThresholdChoice.ZeroConfirmation => 0,
|
||||
MoneroLikeSettlementThresholdChoice.AtLeastOne => 1,
|
||||
MoneroLikeSettlementThresholdChoice.AtLeastTen => 10,
|
||||
MoneroLikeSettlementThresholdChoice.Custom when viewModel.CustomSettlementConfirmationThreshold is { } custom => custom,
|
||||
_ => null
|
||||
}
|
||||
});
|
||||
|
||||
blob.SetExcluded(PaymentTypes.CHAIN.GetPaymentMethodId(viewModel.CryptoCode), !viewModel.Enabled);
|
||||
storeData.SetStoreBlob(blob);
|
||||
await _StoreRepository.UpdateStore(storeData);
|
||||
return RedirectToAction("GetStoreMoneroLikePaymentMethods",
|
||||
new { StatusMessage = $"{cryptoCode} settings updated successfully", storeId = StoreData.Id });
|
||||
}
|
||||
|
||||
private void Exec(string cmd)
|
||||
{
|
||||
|
||||
var escapedArgs = cmd.Replace("\"", "\\\"", StringComparison.InvariantCulture);
|
||||
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
FileName = "/bin/sh",
|
||||
Arguments = $"-c \"{escapedArgs}\""
|
||||
}
|
||||
};
|
||||
|
||||
#pragma warning disable CA1416 // Validate platform compatibility
|
||||
process.Start();
|
||||
#pragma warning restore CA1416 // Validate platform compatibility
|
||||
process.WaitForExit();
|
||||
}
|
||||
|
||||
public class MoneroLikePaymentMethodListViewModel
|
||||
{
|
||||
public IEnumerable<MoneroLikePaymentMethodViewModel> Items { get; set; }
|
||||
}
|
||||
|
||||
public class MoneroLikePaymentMethodViewModel : IValidatableObject
|
||||
{
|
||||
public MoneroRPCProvider.MoneroLikeSummary Summary { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public string NewAccountLabel { get; set; }
|
||||
public long AccountIndex { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public IEnumerable<SelectListItem> Accounts { get; set; }
|
||||
public bool WalletFileFound { get; set; }
|
||||
[Display(Name = "View-Only Wallet File")]
|
||||
public IFormFile WalletFile { get; set; }
|
||||
[Display(Name = "Wallet Keys File")]
|
||||
public IFormFile WalletKeysFile { get; set; }
|
||||
[Display(Name = "Wallet Password")]
|
||||
public string WalletPassword { get; set; }
|
||||
[Display(Name = "Consider the invoice settled when the payment transaction …")]
|
||||
public MoneroLikeSettlementThresholdChoice SettlementConfirmationThresholdChoice { get; set; }
|
||||
[Display(Name = "Required Confirmations"), Range(0, 100)]
|
||||
public long? CustomSettlementConfirmationThreshold { get; set; }
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
if (SettlementConfirmationThresholdChoice is MoneroLikeSettlementThresholdChoice.Custom
|
||||
&& CustomSettlementConfirmationThreshold is null)
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
"You must specify the number of required confirmations when using a custom threshold.",
|
||||
new[] { nameof(CustomSettlementConfirmationThreshold) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum MoneroLikeSettlementThresholdChoice
|
||||
{
|
||||
[Display(Name = "Store Speed Policy", Description = "Use the store's speed policy")]
|
||||
StoreSpeedPolicy,
|
||||
[Display(Name = "Zero Confirmation", Description = "Is unconfirmed")]
|
||||
ZeroConfirmation,
|
||||
[Display(Name = "At Least One", Description = "Has at least 1 confirmation")]
|
||||
AtLeastOne,
|
||||
[Display(Name = "At Least Ten", Description = "Has at least 10 confirmations")]
|
||||
AtLeastTen,
|
||||
[Display(Name = "Custom", Description = "Custom")]
|
||||
Custom
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using System;
|
||||
using BTCPayServer.Payments;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Monero.UI
|
||||
{
|
||||
public class MoneroPaymentViewModel
|
||||
{
|
||||
public PaymentMethodId PaymentMethodId { get; set; }
|
||||
public string Confirmations { get; set; }
|
||||
public string DepositAddress { get; set; }
|
||||
public string Amount { get; set; }
|
||||
public string TransactionId { get; set; }
|
||||
public DateTimeOffset ReceivedTime { get; set; }
|
||||
public string TransactionLink { get; set; }
|
||||
public string Currency { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.Configuration
|
||||
{
|
||||
public class ZcashLikeConfiguration
|
||||
{
|
||||
public Dictionary<string, ZcashLikeConfigurationItem> ZcashLikeConfigurationItems { get; set; } =
|
||||
new Dictionary<string, ZcashLikeConfigurationItem>();
|
||||
}
|
||||
|
||||
public class ZcashLikeConfigurationItem
|
||||
{
|
||||
public Uri DaemonRpcUri { get; set; }
|
||||
public Uri InternalWalletRpcUri { get; set; }
|
||||
public string WalletDirectory { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Services.Altcoins.Monero.Payments;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.Payments
|
||||
{
|
||||
public class ZcashCheckoutModelExtension : ICheckoutModelExtension
|
||||
{
|
||||
private readonly BTCPayNetworkBase _network;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly IPaymentLinkExtension paymentLinkExtension;
|
||||
|
||||
public ZcashCheckoutModelExtension(
|
||||
PaymentMethodId paymentMethodId,
|
||||
IEnumerable<IPaymentLinkExtension> paymentLinkExtensions,
|
||||
BTCPayNetworkBase network,
|
||||
PaymentMethodHandlerDictionary handlers)
|
||||
{
|
||||
PaymentMethodId = paymentMethodId;
|
||||
_network = network;
|
||||
_handlers = handlers;
|
||||
paymentLinkExtension = paymentLinkExtensions.Single(p => p.PaymentMethodId == PaymentMethodId);
|
||||
}
|
||||
public PaymentMethodId PaymentMethodId { get; }
|
||||
|
||||
public string Image => _network.CryptoImagePath;
|
||||
public string Badge => "";
|
||||
|
||||
public void ModifyCheckoutModel(CheckoutModelContext context)
|
||||
{
|
||||
if (context is not { Handler: ZcashLikePaymentMethodHandler handler })
|
||||
return;
|
||||
context.Model.CheckoutBodyComponentName = BitcoinCheckoutModelExtension.CheckoutBodyComponentName;
|
||||
var details = context.InvoiceEntity.GetPayments(true)
|
||||
.Select(p => p.GetDetails<ZcashLikePaymentData>(handler))
|
||||
.Where(p => p is not null)
|
||||
.FirstOrDefault();
|
||||
if (details is not null)
|
||||
{
|
||||
context.Model.ReceivedConfirmations = details.ConfirmationCount;
|
||||
context.Model.RequiredConfirmations = (int)ZcashListener.ConfirmationsRequired(context.InvoiceEntity.SpeedPolicy);
|
||||
}
|
||||
context.Model.InvoiceBitcoinUrl = paymentLinkExtension.GetPaymentLink(context.Prompt, context.UrlHelper);
|
||||
context.Model.InvoiceBitcoinUrlQR = context.Model.InvoiceBitcoinUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Plugins.Altcoins;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Utils;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.Payments
|
||||
{
|
||||
public class ZcashLikePaymentData
|
||||
{
|
||||
public long SubaddressIndex { get; set; }
|
||||
public long SubaccountIndex { get; set; }
|
||||
public long BlockHeight { get; set; }
|
||||
public long ConfirmationCount { get; set; }
|
||||
public string TransactionId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Dom;
|
||||
using BTCPayServer.Common;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Plugins.Altcoins;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.RPC.Models;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Services;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Utils;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.Payments
|
||||
{
|
||||
public class ZcashLikePaymentMethodHandler : IPaymentMethodHandler
|
||||
{
|
||||
private readonly ZcashLikeSpecificBtcPayNetwork _network;
|
||||
public ZcashLikeSpecificBtcPayNetwork Network => _network;
|
||||
public JsonSerializer Serializer { get; }
|
||||
private readonly ZcashRPCProvider _ZcashRpcProvider;
|
||||
public PaymentMethodId PaymentMethodId { get; }
|
||||
public ZcashLikePaymentMethodHandler(BTCPayNetworkBase network, ZcashRPCProvider ZcashRpcProvider)
|
||||
{
|
||||
_network = (ZcashLikeSpecificBtcPayNetwork)network;
|
||||
PaymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(_network.CryptoCode);
|
||||
Serializer = BlobSerializer.CreateSerializer().Serializer;
|
||||
_ZcashRpcProvider = ZcashRpcProvider;
|
||||
}
|
||||
public Task BeforeFetchingRates(PaymentMethodContext context)
|
||||
{
|
||||
context.Prompt.Currency = _network.CryptoCode;
|
||||
context.Prompt.Divisibility = _network.Divisibility;
|
||||
if (context.Prompt.Activated)
|
||||
{
|
||||
var walletClient = _ZcashRpcProvider.WalletRpcClients[_network.CryptoCode];
|
||||
var daemonClient = _ZcashRpcProvider.DaemonRpcClients[_network.CryptoCode];
|
||||
var config = ParsePaymentMethodConfig(context.PaymentMethodConfig);
|
||||
context.State = new Prepare()
|
||||
{
|
||||
GetFeeRate = daemonClient.SendCommandAsync<GetFeeEstimateRequest, GetFeeEstimateResponse>("get_fee_estimate", new GetFeeEstimateRequest()),
|
||||
ReserveAddress = s => walletClient.SendCommandAsync<CreateAddressRequest, CreateAddressResponse>("create_address", new CreateAddressRequest() { Label = $"btcpay invoice #{s}", AccountIndex = config.AccountIndex }),
|
||||
AccountIndex = config.AccountIndex
|
||||
};
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public async Task ConfigurePrompt(PaymentMethodContext context)
|
||||
{
|
||||
if (!_ZcashRpcProvider.IsAvailable(_network.CryptoCode))
|
||||
throw new PaymentMethodUnavailableException($"Node or wallet not available");
|
||||
var invoice = context.InvoiceEntity;
|
||||
var ZcashPrepare = (Prepare)context.State;
|
||||
var feeRatePerKb = await ZcashPrepare.GetFeeRate;
|
||||
var address = await ZcashPrepare.ReserveAddress(invoice.Id);
|
||||
|
||||
var feeRatePerByte = feeRatePerKb.Fee / 1024;
|
||||
|
||||
context.Prompt.PaymentMethodFee = ZcashMoney.Convert(feeRatePerByte * 100);
|
||||
context.Prompt.Details = JObject.FromObject(new ZcashPaymentPromptDetails()
|
||||
{
|
||||
AccountIndex = ZcashPrepare.AccountIndex,
|
||||
AddressIndex = address.AddressIndex,
|
||||
DepositAddress = address.Address
|
||||
}, Serializer);
|
||||
context.TrackedDestinations.Add(address.Address);
|
||||
}
|
||||
|
||||
public ZcashPaymentPromptDetails ParsePaymentPromptDetails(Newtonsoft.Json.Linq.JToken details)
|
||||
{
|
||||
return details.ToObject<ZcashPaymentPromptDetails>(Serializer);
|
||||
}
|
||||
object IPaymentMethodHandler.ParsePaymentPromptDetails(Newtonsoft.Json.Linq.JToken details)
|
||||
{
|
||||
return ParsePaymentPromptDetails(details);
|
||||
}
|
||||
object IPaymentMethodHandler.ParsePaymentMethodConfig(JToken config)
|
||||
{
|
||||
return ParsePaymentMethodConfig(config);
|
||||
}
|
||||
public ZcashPaymentMethodConfig ParsePaymentMethodConfig(JToken config)
|
||||
{
|
||||
return config.ToObject<ZcashPaymentMethodConfig>(Serializer) ?? throw new FormatException($"Invalid {nameof(ZcashPaymentMethodConfig)}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
class Prepare
|
||||
{
|
||||
public Task<GetFeeEstimateResponse> GetFeeRate;
|
||||
public Func<string, Task<CreateAddressResponse>> ReserveAddress;
|
||||
public long AccountIndex { get; internal set; }
|
||||
}
|
||||
|
||||
public ZcashLikePaymentData ParsePaymentDetails(JToken details)
|
||||
{
|
||||
return details.ToObject<ZcashLikePaymentData>(Serializer) ?? throw new FormatException($"Invalid {nameof(ZcashLikePaymentData)}");
|
||||
}
|
||||
object IPaymentMethodHandler.ParsePaymentDetails(JToken details)
|
||||
{
|
||||
return ParsePaymentDetails(details);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Globalization;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Plugins.Altcoins;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.Payments
|
||||
{
|
||||
public class ZcashPaymentLinkExtension : IPaymentLinkExtension
|
||||
{
|
||||
private readonly ZcashLikeSpecificBtcPayNetwork _network;
|
||||
|
||||
public ZcashPaymentLinkExtension(PaymentMethodId paymentMethodId, ZcashLikeSpecificBtcPayNetwork network)
|
||||
{
|
||||
PaymentMethodId = paymentMethodId;
|
||||
_network = network;
|
||||
}
|
||||
public PaymentMethodId PaymentMethodId { get; }
|
||||
|
||||
public string? GetPaymentLink(PaymentPrompt prompt, IUrlHelper? urlHelper)
|
||||
{
|
||||
var due = prompt.Calculate().Due;
|
||||
return $"{_network.UriScheme}:{prompt.Destination}?amount={due.ToString(CultureInfo.InvariantCulture)}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using BTCPayServer.Payments;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.Payments
|
||||
{
|
||||
public class ZcashPaymentMethodConfig
|
||||
{
|
||||
public long AccountIndex { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using BTCPayServer.Payments;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.Payments
|
||||
{
|
||||
public class ZcashPaymentPromptDetails
|
||||
{
|
||||
public long AccountIndex { get; set; }
|
||||
public long AddressIndex { get; set; }
|
||||
public string DepositAddress { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using BTCPayServer.Filters;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC
|
||||
{
|
||||
[Route("[controller]")]
|
||||
[OnlyIfSupportAttribute("ZEC-CHAIN")]
|
||||
public class ZcashLikeDaemonCallbackController : Controller
|
||||
{
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
|
||||
public ZcashLikeDaemonCallbackController(EventAggregator eventAggregator)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
}
|
||||
[HttpGet("block")]
|
||||
public IActionResult OnBlockNotify(string hash, string cryptoCode)
|
||||
{
|
||||
_eventAggregator.Publish(new ZcashEvent()
|
||||
{
|
||||
BlockHash = hash,
|
||||
CryptoCode = cryptoCode.ToUpperInvariant()
|
||||
});
|
||||
return Ok();
|
||||
}
|
||||
[HttpGet("tx")]
|
||||
public IActionResult OnTransactionNotify(string hash, string cryptoCode)
|
||||
{
|
||||
_eventAggregator.Publish(new ZcashEvent()
|
||||
{
|
||||
TransactionHash = hash,
|
||||
CryptoCode = cryptoCode.ToUpperInvariant()
|
||||
});
|
||||
return Ok();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC
|
||||
{
|
||||
public class ZcashEvent
|
||||
{
|
||||
public string BlockHash { get; set; }
|
||||
public string TransactionHash { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return
|
||||
$"{CryptoCode}: {(string.IsNullOrEmpty(TransactionHash) ? string.Empty : "Tx Update")}{(string.IsNullOrEmpty(BlockHash) ? string.Empty : "New Block")} ({TransactionHash ?? string.Empty}{BlockHash ?? string.Empty})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.Services
|
||||
{
|
||||
public class ZcashLikeSummaryUpdaterHostedService : IHostedService
|
||||
{
|
||||
private readonly ZcashRPCProvider _ZcashRpcProvider;
|
||||
private readonly ZcashLikeConfiguration _ZcashLikeConfiguration;
|
||||
private CancellationTokenSource _Cts;
|
||||
|
||||
public Logs Logs { get; }
|
||||
|
||||
public ZcashLikeSummaryUpdaterHostedService(ZcashRPCProvider ZcashRpcProvider, ZcashLikeConfiguration ZcashLikeConfiguration, Logs logs)
|
||||
{
|
||||
_ZcashRpcProvider = ZcashRpcProvider;
|
||||
_ZcashLikeConfiguration = ZcashLikeConfiguration;
|
||||
Logs = logs;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_Cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
foreach (var ZcashLikeConfigurationItem in _ZcashLikeConfiguration.ZcashLikeConfigurationItems)
|
||||
{
|
||||
_ = StartLoop(_Cts.Token, ZcashLikeConfigurationItem.Key);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task StartLoop(CancellationToken cancellation, string cryptoCode)
|
||||
{
|
||||
Logs.PayServer.LogInformation($"Starting listening Zcash-like daemons ({cryptoCode})");
|
||||
try
|
||||
{
|
||||
while (!cancellation.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _ZcashRpcProvider.UpdateSummary(cryptoCode);
|
||||
if (_ZcashRpcProvider.IsAvailable(cryptoCode))
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(1), cancellation);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(10), cancellation);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (!cancellation.IsCancellationRequested)
|
||||
{
|
||||
Logs.PayServer.LogError(ex, $"Unhandled exception in Summary updater ({cryptoCode})");
|
||||
await Task.Delay(TimeSpan.FromSeconds(10), cancellation);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch when (cancellation.IsCancellationRequested) { }
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_Cts?.Cancel();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,356 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Plugins.Altcoins;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Configuration;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Payments;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.RPC;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.RPC.Models;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Utils;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using NBXplorer;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using static BTCPayServer.Client.Models.InvoicePaymentMethodDataModel;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.Services
|
||||
{
|
||||
public class ZcashListener : EventHostedServiceBase
|
||||
{
|
||||
private readonly InvoiceRepository _invoiceRepository;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly ZcashRPCProvider _ZcashRpcProvider;
|
||||
private readonly ZcashLikeConfiguration _ZcashLikeConfiguration;
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
private readonly ILogger<ZcashListener> _logger;
|
||||
private readonly PaymentService _paymentService;
|
||||
private readonly InvoiceActivator _invoiceActivator;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
|
||||
public ZcashListener(InvoiceRepository invoiceRepository,
|
||||
EventAggregator eventAggregator,
|
||||
ZcashRPCProvider ZcashRpcProvider,
|
||||
ZcashLikeConfiguration ZcashLikeConfiguration,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
ILogger<ZcashListener> logger,
|
||||
PaymentService paymentService,
|
||||
InvoiceActivator invoiceActivator,
|
||||
PaymentMethodHandlerDictionary handlers) : base(eventAggregator, logger)
|
||||
{
|
||||
_invoiceRepository = invoiceRepository;
|
||||
_eventAggregator = eventAggregator;
|
||||
_ZcashRpcProvider = ZcashRpcProvider;
|
||||
_ZcashLikeConfiguration = ZcashLikeConfiguration;
|
||||
_networkProvider = networkProvider;
|
||||
_logger = logger;
|
||||
_paymentService = paymentService;
|
||||
_invoiceActivator = invoiceActivator;
|
||||
_handlers = handlers;
|
||||
}
|
||||
|
||||
protected override void SubscribeToEvents()
|
||||
{
|
||||
base.SubscribeToEvents();
|
||||
Subscribe<ZcashEvent>();
|
||||
Subscribe<ZcashRPCProvider.ZcashDaemonStateChange>();
|
||||
}
|
||||
|
||||
protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken)
|
||||
{
|
||||
if (evt is ZcashRPCProvider.ZcashDaemonStateChange stateChanged)
|
||||
{
|
||||
if (_ZcashRpcProvider.IsAvailable(stateChanged.CryptoCode))
|
||||
{
|
||||
_logger.LogInformation($"{stateChanged.CryptoCode} just became available");
|
||||
_ = UpdateAnyPendingZcashLikePayment(stateChanged.CryptoCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation($"{stateChanged.CryptoCode} just became unavailable");
|
||||
}
|
||||
}
|
||||
else if (evt is ZcashEvent zcashEvent)
|
||||
{
|
||||
if (!_ZcashRpcProvider.IsAvailable(zcashEvent.CryptoCode))
|
||||
return;
|
||||
|
||||
if (!string.IsNullOrEmpty(zcashEvent.BlockHash))
|
||||
{
|
||||
await OnNewBlock(zcashEvent.CryptoCode);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(zcashEvent.TransactionHash))
|
||||
{
|
||||
await OnTransactionUpdated(zcashEvent.CryptoCode, zcashEvent.TransactionHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReceivedPayment(InvoiceEntity invoice, PaymentEntity payment)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
$"Invoice {invoice.Id} received payment {payment.Value} {payment.Currency} {payment.Id}");
|
||||
|
||||
|
||||
var prompt = invoice.GetPaymentPrompt(payment.PaymentMethodId);
|
||||
|
||||
if (prompt != null &&
|
||||
prompt.Activated &&
|
||||
prompt.Destination == payment.Destination &&
|
||||
prompt.Calculate().Due > 0.0m)
|
||||
{
|
||||
await _invoiceActivator.ActivateInvoicePaymentMethod(invoice.Id, payment.PaymentMethodId, true);
|
||||
invoice = await _invoiceRepository.GetInvoice(invoice.Id);
|
||||
}
|
||||
|
||||
_eventAggregator.Publish(
|
||||
new InvoiceEvent(invoice, InvoiceEvent.ReceivedPayment) { Payment = payment });
|
||||
}
|
||||
|
||||
private async Task UpdatePaymentStates(string cryptoCode, InvoiceEntity[] invoices)
|
||||
{
|
||||
if (!invoices.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var ZcashWalletRpcClient = _ZcashRpcProvider.WalletRpcClients[cryptoCode];
|
||||
var network = _networkProvider.GetNetwork(cryptoCode);
|
||||
|
||||
var paymentId = PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode);
|
||||
var handler = (ZcashLikePaymentMethodHandler)_handlers[paymentId];
|
||||
|
||||
//get all the required data in one list (invoice, its existing payments and the current payment method details)
|
||||
var expandedInvoices = invoices.Select(entity => (Invoice: entity,
|
||||
ExistingPayments: GetAllZcashLikePayments(entity, cryptoCode),
|
||||
Prompt: entity.GetPaymentPrompt(paymentId),
|
||||
PaymentMethodDetails: handler.ParsePaymentPromptDetails(entity.GetPaymentPrompt(paymentId).Details)))
|
||||
.Select(tuple => (
|
||||
tuple.Invoice,
|
||||
tuple.PaymentMethodDetails,
|
||||
tuple.Prompt,
|
||||
ExistingPayments: tuple.ExistingPayments.Select(entity =>
|
||||
(Payment: entity, PaymentData: handler.ParsePaymentDetails(entity.Details),
|
||||
tuple.Invoice))
|
||||
));
|
||||
|
||||
var existingPaymentData = expandedInvoices.SelectMany(tuple => tuple.ExistingPayments);
|
||||
|
||||
var accountToAddressQuery = new Dictionary<long, List<long>>();
|
||||
//create list of subaddresses to account to query the Zcash wallet
|
||||
foreach (var expandedInvoice in expandedInvoices)
|
||||
{
|
||||
var addressIndexList =
|
||||
accountToAddressQuery.GetValueOrDefault(expandedInvoice.PaymentMethodDetails.AccountIndex,
|
||||
new List<long>());
|
||||
|
||||
addressIndexList.AddRange(
|
||||
expandedInvoice.ExistingPayments.Select(tuple => tuple.PaymentData.SubaddressIndex));
|
||||
addressIndexList.Add(expandedInvoice.PaymentMethodDetails.AddressIndex);
|
||||
accountToAddressQuery.AddOrReplace(expandedInvoice.PaymentMethodDetails.AccountIndex, addressIndexList);
|
||||
}
|
||||
|
||||
var tasks = accountToAddressQuery.ToDictionary(datas => datas.Key,
|
||||
datas => ZcashWalletRpcClient.SendCommandAsync<GetTransfersRequest, GetTransfersResponse>(
|
||||
"get_transfers",
|
||||
new GetTransfersRequest()
|
||||
{
|
||||
AccountIndex = datas.Key,
|
||||
In = true,
|
||||
SubaddrIndices = datas.Value.Distinct().ToList()
|
||||
}));
|
||||
|
||||
await Task.WhenAll(tasks.Values);
|
||||
|
||||
|
||||
var transferProcessingTasks = new List<Task>();
|
||||
|
||||
var updatedPaymentEntities = new List<(PaymentEntity Payment, InvoiceEntity invoice)>();
|
||||
foreach (var keyValuePair in tasks)
|
||||
{
|
||||
var transfers = keyValuePair.Value.Result.In;
|
||||
if (transfers == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
transferProcessingTasks.AddRange(transfers.Select(transfer =>
|
||||
{
|
||||
InvoiceEntity invoice = null;
|
||||
var existingMatch = existingPaymentData.SingleOrDefault(tuple =>
|
||||
tuple.Payment.Destination == transfer.Address &&
|
||||
tuple.PaymentData.TransactionId == transfer.Txid);
|
||||
|
||||
if (existingMatch.Invoice != null)
|
||||
{
|
||||
invoice = existingMatch.Invoice;
|
||||
}
|
||||
else
|
||||
{
|
||||
var newMatch = expandedInvoices.SingleOrDefault(tuple =>
|
||||
tuple.Prompt.Destination == transfer.Address);
|
||||
|
||||
if (newMatch.Invoice == null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
invoice = newMatch.Invoice;
|
||||
}
|
||||
|
||||
|
||||
return HandlePaymentData(cryptoCode, transfer.Address, transfer.Amount, transfer.SubaddrIndex.Major,
|
||||
transfer.SubaddrIndex.Minor, transfer.Txid, transfer.Confirmations, transfer.Height, invoice,
|
||||
updatedPaymentEntities);
|
||||
}));
|
||||
}
|
||||
|
||||
transferProcessingTasks.Add(
|
||||
_paymentService.UpdatePayments(updatedPaymentEntities.Select(tuple => tuple.Item1).ToList()));
|
||||
await Task.WhenAll(transferProcessingTasks);
|
||||
foreach (var valueTuples in updatedPaymentEntities.GroupBy(entity => entity.Item2))
|
||||
{
|
||||
if (valueTuples.Any())
|
||||
{
|
||||
_eventAggregator.Publish(new Events.InvoiceNeedUpdateEvent(valueTuples.Key.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnNewBlock(string cryptoCode)
|
||||
{
|
||||
await UpdateAnyPendingZcashLikePayment(cryptoCode);
|
||||
_eventAggregator.Publish(new NewBlockEvent() { PaymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode) });
|
||||
}
|
||||
|
||||
private async Task OnTransactionUpdated(string cryptoCode, string transactionHash)
|
||||
{
|
||||
var paymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode);
|
||||
var transfer = await _ZcashRpcProvider.WalletRpcClients[cryptoCode]
|
||||
.SendCommandAsync<GetTransferByTransactionIdRequest, GetTransferByTransactionIdResponse>(
|
||||
"get_transfer_by_txid",
|
||||
new GetTransferByTransactionIdRequest() { TransactionId = transactionHash });
|
||||
|
||||
var paymentsToUpdate = new List<(PaymentEntity Payment, InvoiceEntity invoice)>();
|
||||
|
||||
//group all destinations of the tx together and loop through the sets
|
||||
foreach (var destination in transfer.Transfers.GroupBy(destination => destination.Address))
|
||||
{
|
||||
//find the invoice corresponding to this address, else skip
|
||||
var invoice = await _invoiceRepository.GetInvoiceFromAddress(paymentMethodId, destination.Key);
|
||||
if (invoice == null)
|
||||
continue;
|
||||
|
||||
var index = destination.First().SubaddrIndex;
|
||||
|
||||
await HandlePaymentData(cryptoCode,
|
||||
destination.Key,
|
||||
destination.Sum(destination1 => destination1.Amount),
|
||||
index.Major,
|
||||
index.Minor,
|
||||
transfer.Transfer.Txid,
|
||||
transfer.Transfer.Confirmations,
|
||||
transfer.Transfer.Height
|
||||
, invoice, paymentsToUpdate);
|
||||
}
|
||||
|
||||
if (paymentsToUpdate.Any())
|
||||
{
|
||||
await _paymentService.UpdatePayments(paymentsToUpdate.Select(tuple => tuple.Payment).ToList());
|
||||
foreach (var valueTuples in paymentsToUpdate.GroupBy(entity => entity.invoice))
|
||||
{
|
||||
if (valueTuples.Any())
|
||||
{
|
||||
_eventAggregator.Publish(new Events.InvoiceNeedUpdateEvent(valueTuples.Key.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandlePaymentData(string cryptoCode, string address, long totalAmount, long subaccountIndex,
|
||||
long subaddressIndex,
|
||||
string txId, long confirmations, long blockHeight, InvoiceEntity invoice,
|
||||
List<(PaymentEntity Payment, InvoiceEntity invoice)> paymentsToUpdate)
|
||||
{
|
||||
var network = _networkProvider.GetNetwork(cryptoCode);
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode);
|
||||
var handler = (ZcashLikePaymentMethodHandler)_handlers[pmi];
|
||||
|
||||
var details = new ZcashLikePaymentData()
|
||||
{
|
||||
SubaccountIndex = subaccountIndex,
|
||||
SubaddressIndex = subaddressIndex,
|
||||
TransactionId = txId,
|
||||
ConfirmationCount = confirmations,
|
||||
BlockHeight = blockHeight
|
||||
};
|
||||
var status = GetStatus(details, invoice.SpeedPolicy) ? PaymentStatus.Settled : PaymentStatus.Processing;
|
||||
var paymentData = new Data.PaymentData()
|
||||
{
|
||||
Status = status,
|
||||
Amount = ZcashMoney.Convert(totalAmount),
|
||||
Created = DateTimeOffset.UtcNow,
|
||||
Id = $"{txId}#{subaccountIndex}#{subaddressIndex}",
|
||||
Currency = network.CryptoCode
|
||||
}.Set(invoice, handler, details);
|
||||
|
||||
|
||||
var alreadyExistingPaymentThatMatches = GetAllZcashLikePayments(invoice, cryptoCode)
|
||||
.SingleOrDefault(c => c.Id == paymentData.Id && c.PaymentMethodId == pmi);
|
||||
|
||||
//if it doesnt, add it and assign a new Zcashlike address to the system if a balance is still due
|
||||
if (alreadyExistingPaymentThatMatches == null)
|
||||
{
|
||||
var payment = await _paymentService.AddPayment(paymentData, [txId]);
|
||||
if (payment != null)
|
||||
await ReceivedPayment(invoice, payment);
|
||||
}
|
||||
else
|
||||
{
|
||||
//else update it with the new data
|
||||
alreadyExistingPaymentThatMatches.Status = status;
|
||||
alreadyExistingPaymentThatMatches.Details = JToken.FromObject(details, handler.Serializer);
|
||||
paymentsToUpdate.Add((alreadyExistingPaymentThatMatches, invoice));
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetStatus(ZcashLikePaymentData details, SpeedPolicy speedPolicy)
|
||||
=> details.ConfirmationCount >= ConfirmationsRequired(speedPolicy);
|
||||
public static int ConfirmationsRequired(SpeedPolicy speedPolicy)
|
||||
=> speedPolicy switch
|
||||
{
|
||||
SpeedPolicy.HighSpeed => 0,
|
||||
SpeedPolicy.MediumSpeed => 1,
|
||||
SpeedPolicy.LowMediumSpeed => 2,
|
||||
SpeedPolicy.LowSpeed => 6,
|
||||
_ => 6,
|
||||
};
|
||||
|
||||
private async Task UpdateAnyPendingZcashLikePayment(string cryptoCode)
|
||||
{
|
||||
var paymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode);
|
||||
var invoices = await _invoiceRepository.GetMonitoredInvoices(paymentMethodId);
|
||||
if (!invoices.Any())
|
||||
return;
|
||||
invoices = invoices.Where(entity => entity.GetPaymentPrompt(paymentMethodId).Activated).ToArray();
|
||||
await UpdatePaymentStates(cryptoCode, invoices);
|
||||
}
|
||||
|
||||
private IEnumerable<PaymentEntity> GetAllZcashLikePayments(InvoiceEntity invoice, string cryptoCode)
|
||||
{
|
||||
return invoice.GetPayments(false)
|
||||
.Where(p => p.PaymentMethodId == PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Configuration;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.RPC;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.RPC.Models;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.Services
|
||||
{
|
||||
public class ZcashRPCProvider
|
||||
{
|
||||
private readonly ZcashLikeConfiguration _ZcashLikeConfiguration;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
public ImmutableDictionary<string, JsonRpcClient> DaemonRpcClients;
|
||||
public ImmutableDictionary<string, JsonRpcClient> WalletRpcClients;
|
||||
|
||||
private readonly ConcurrentDictionary<string, ZcashLikeSummary> _summaries =
|
||||
new ConcurrentDictionary<string, ZcashLikeSummary>();
|
||||
|
||||
public ConcurrentDictionary<string, ZcashLikeSummary> Summaries => _summaries;
|
||||
|
||||
public ZcashRPCProvider(ZcashLikeConfiguration ZcashLikeConfiguration, EventAggregator eventAggregator, IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_ZcashLikeConfiguration = ZcashLikeConfiguration;
|
||||
_eventAggregator = eventAggregator;
|
||||
DaemonRpcClients =
|
||||
_ZcashLikeConfiguration.ZcashLikeConfigurationItems.ToImmutableDictionary(pair => pair.Key,
|
||||
pair => new JsonRpcClient(pair.Value.DaemonRpcUri, "", "", httpClientFactory.CreateClient()));
|
||||
WalletRpcClients =
|
||||
_ZcashLikeConfiguration.ZcashLikeConfigurationItems.ToImmutableDictionary(pair => pair.Key,
|
||||
pair => new JsonRpcClient(pair.Value.InternalWalletRpcUri, "", "", httpClientFactory.CreateClient()));
|
||||
}
|
||||
|
||||
public bool IsAvailable(string cryptoCode)
|
||||
{
|
||||
cryptoCode = cryptoCode.ToUpperInvariant();
|
||||
return _summaries.ContainsKey(cryptoCode) && IsAvailable(_summaries[cryptoCode]);
|
||||
}
|
||||
|
||||
private bool IsAvailable(ZcashLikeSummary summary)
|
||||
{
|
||||
return summary.Synced &&
|
||||
summary.WalletAvailable;
|
||||
}
|
||||
|
||||
public async Task<ZcashLikeSummary> UpdateSummary(string cryptoCode)
|
||||
{
|
||||
if (!DaemonRpcClients.TryGetValue(cryptoCode.ToUpperInvariant(), out var daemonRpcClient) ||
|
||||
!WalletRpcClients.TryGetValue(cryptoCode.ToUpperInvariant(), out var walletRpcClient))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var summary = new ZcashLikeSummary();
|
||||
try
|
||||
{
|
||||
var daemonResult =
|
||||
await daemonRpcClient.SendCommandAsync<JsonRpcClient.NoRequestModel, SyncInfoResponse>("sync_info",
|
||||
JsonRpcClient.NoRequestModel.Instance);
|
||||
|
||||
summary.TargetHeight = daemonResult.TargetHeight.GetValueOrDefault(0);
|
||||
summary.CurrentHeight = daemonResult.Height;
|
||||
summary.TargetHeight = summary.TargetHeight == 0 ? summary.CurrentHeight : summary.TargetHeight;
|
||||
summary.Synced = daemonResult.Height >= summary.TargetHeight && summary.CurrentHeight > 0;
|
||||
summary.UpdatedAt = DateTime.Now;
|
||||
summary.DaemonAvailable = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.Message);
|
||||
summary.DaemonAvailable = false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var walletResult =
|
||||
await walletRpcClient.SendCommandAsync<JsonRpcClient.NoRequestModel, GetHeightResponse>(
|
||||
"get_height", JsonRpcClient.NoRequestModel.Instance);
|
||||
|
||||
summary.WalletHeight = walletResult.Height;
|
||||
summary.WalletAvailable = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
summary.WalletAvailable = false;
|
||||
}
|
||||
|
||||
var changed = !_summaries.ContainsKey(cryptoCode) || IsAvailable(cryptoCode) != IsAvailable(summary);
|
||||
|
||||
_summaries.AddOrReplace(cryptoCode, summary);
|
||||
if (changed)
|
||||
{
|
||||
_eventAggregator.Publish(new ZcashDaemonStateChange() { Summary = summary, CryptoCode = cryptoCode });
|
||||
}
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
|
||||
public class ZcashDaemonStateChange
|
||||
{
|
||||
public string CryptoCode { get; set; }
|
||||
public ZcashLikeSummary Summary { get; set; }
|
||||
}
|
||||
|
||||
public class ZcashLikeSummary
|
||||
{
|
||||
public bool Synced { get; set; }
|
||||
public long CurrentHeight { get; set; }
|
||||
public long WalletHeight { get; set; }
|
||||
public long TargetHeight { get; set; }
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
public bool DaemonAvailable { get; set; }
|
||||
public bool WalletAvailable { get; set; }
|
||||
|
||||
public override String ToString() { return String.Format(CultureInfo.InvariantCulture, "{0} {1} {2} {3} {4} {5}", Synced, CurrentHeight, TargetHeight, WalletHeight, DaemonAvailable, WalletAvailable); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Payments;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.Services
|
||||
{
|
||||
public class ZcashSyncSummaryProvider : ISyncSummaryProvider
|
||||
{
|
||||
private readonly ZcashRPCProvider _ZcashRpcProvider;
|
||||
|
||||
public ZcashSyncSummaryProvider(ZcashRPCProvider ZcashRpcProvider)
|
||||
{
|
||||
_ZcashRpcProvider = ZcashRpcProvider;
|
||||
}
|
||||
|
||||
public bool AllAvailable()
|
||||
{
|
||||
return _ZcashRpcProvider.Summaries.All(pair => pair.Value.WalletAvailable);
|
||||
}
|
||||
|
||||
public string Partial { get; } = "Zcash/ZcashSyncSummary";
|
||||
public IEnumerable<ISyncStatus> GetStatuses()
|
||||
{
|
||||
return _ZcashRpcProvider.Summaries.Select(pair => new ZcashSyncStatus()
|
||||
{
|
||||
Summary = pair.Value, PaymentMethodId = PaymentMethodId.Parse(pair.Key).ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class ZcashSyncStatus: SyncStatus, ISyncStatus
|
||||
{
|
||||
public override bool Available
|
||||
{
|
||||
get
|
||||
{
|
||||
return Summary?.WalletAvailable ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
public ZcashRPCProvider.ZcashLikeSummary Summary { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,311 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
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.Filters;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Configuration;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Payments;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.RPC.Models;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.UI
|
||||
{
|
||||
[Route("stores/{storeId}/Zcashlike")]
|
||||
[OnlyIfSupportAttribute("ZEC-CHAIN")]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Authorize(Policy = Policies.CanModifyServerSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public class UIZcashLikeStoreController : Controller
|
||||
{
|
||||
private readonly ZcashLikeConfiguration _ZcashLikeConfiguration;
|
||||
private readonly StoreRepository _StoreRepository;
|
||||
private readonly ZcashRPCProvider _ZcashRpcProvider;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private IStringLocalizer StringLocalizer { get; }
|
||||
|
||||
public UIZcashLikeStoreController(ZcashLikeConfiguration ZcashLikeConfiguration,
|
||||
StoreRepository storeRepository, ZcashRPCProvider ZcashRpcProvider,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
IStringLocalizer stringLocalizer)
|
||||
{
|
||||
_ZcashLikeConfiguration = ZcashLikeConfiguration;
|
||||
_StoreRepository = storeRepository;
|
||||
_ZcashRpcProvider = ZcashRpcProvider;
|
||||
_handlers = handlers;
|
||||
StringLocalizer = stringLocalizer;
|
||||
}
|
||||
|
||||
public StoreData StoreData => HttpContext.GetStoreData();
|
||||
|
||||
[HttpGet()]
|
||||
public async Task<IActionResult> GetStoreZcashLikePaymentMethods()
|
||||
{
|
||||
var Zcash = StoreData.GetPaymentMethodConfigs<ZcashPaymentMethodConfig>(_handlers);
|
||||
|
||||
var excludeFilters = StoreData.GetStoreBlob().GetExcludedPaymentMethods();
|
||||
|
||||
var accountsList = _ZcashLikeConfiguration.ZcashLikeConfigurationItems.ToDictionary(pair => pair.Key,
|
||||
pair => GetAccounts(pair.Key));
|
||||
|
||||
await Task.WhenAll(accountsList.Values);
|
||||
return View(new ZcashLikePaymentMethodListViewModel()
|
||||
{
|
||||
Items = _ZcashLikeConfiguration.ZcashLikeConfigurationItems.Select(pair =>
|
||||
GetZcashLikePaymentMethodViewModel(StoreData, pair.Key, excludeFilters,
|
||||
accountsList[pair.Key].Result))
|
||||
});
|
||||
}
|
||||
|
||||
private Task<GetAccountsResponse> GetAccounts(string cryptoCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_ZcashRpcProvider.Summaries.TryGetValue(cryptoCode, out var summary) && summary.WalletAvailable)
|
||||
{
|
||||
|
||||
return _ZcashRpcProvider.WalletRpcClients[cryptoCode].SendCommandAsync<GetAccountsRequest, GetAccountsResponse>("get_accounts", new GetAccountsRequest());
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return Task.FromResult<GetAccountsResponse>(null);
|
||||
}
|
||||
|
||||
private ZcashLikePaymentMethodViewModel GetZcashLikePaymentMethodViewModel(
|
||||
StoreData store, string cryptoCode,
|
||||
IPaymentFilter excludeFilters, GetAccountsResponse accountsResponse)
|
||||
{
|
||||
var Zcash = store.GetPaymentMethodConfigs<ZcashPaymentMethodConfig>(_handlers);
|
||||
var settings = Zcash.SingleOrDefault(method => ((IHasNetwork)_handlers[method.Key]).Network.CryptoCode == cryptoCode).Value;
|
||||
_ZcashRpcProvider.Summaries.TryGetValue(cryptoCode, out var summary);
|
||||
_ZcashLikeConfiguration.ZcashLikeConfigurationItems.TryGetValue(cryptoCode,
|
||||
out var configurationItem);
|
||||
var fileAddress = Path.Combine(configurationItem.WalletDirectory, "wallet");
|
||||
var accounts = accountsResponse?.SubaddressAccounts?.Select(account =>
|
||||
new SelectListItem(
|
||||
$"{account.AccountIndex} - {(string.IsNullOrEmpty(account.Label) ? "No label" : account.Label)}",
|
||||
account.AccountIndex.ToString(CultureInfo.InvariantCulture)));
|
||||
return new ZcashLikePaymentMethodViewModel()
|
||||
{
|
||||
WalletFileFound = System.IO.File.Exists(fileAddress),
|
||||
Enabled =
|
||||
settings != null &&
|
||||
!excludeFilters.Match(PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode)),
|
||||
Summary = summary,
|
||||
CryptoCode = cryptoCode,
|
||||
AccountIndex = settings?.AccountIndex ?? accountsResponse?.SubaddressAccounts?.FirstOrDefault()?.AccountIndex ?? 0,
|
||||
Accounts = accounts == null ? null : new SelectList(accounts, nameof(SelectListItem.Value),
|
||||
nameof(SelectListItem.Text))
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{cryptoCode}")]
|
||||
public async Task<IActionResult> GetStoreZcashLikePaymentMethod(string cryptoCode)
|
||||
{
|
||||
cryptoCode = cryptoCode.ToUpperInvariant();
|
||||
if (!_ZcashLikeConfiguration.ZcashLikeConfigurationItems.ContainsKey(cryptoCode))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var vm = GetZcashLikePaymentMethodViewModel(StoreData, cryptoCode,
|
||||
StoreData.GetStoreBlob().GetExcludedPaymentMethods(), await GetAccounts(cryptoCode));
|
||||
return View(nameof(GetStoreZcashLikePaymentMethod), vm);
|
||||
}
|
||||
|
||||
[DisableRequestSizeLimit]
|
||||
[HttpPost("{cryptoCode}")]
|
||||
public async Task<IActionResult> GetStoreZcashLikePaymentMethod(ZcashLikePaymentMethodViewModel viewModel, string command, string cryptoCode)
|
||||
{
|
||||
cryptoCode = cryptoCode.ToUpperInvariant();
|
||||
if (!_ZcashLikeConfiguration.ZcashLikeConfigurationItems.TryGetValue(cryptoCode,
|
||||
out var configurationItem))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (command == "add-account")
|
||||
{
|
||||
try
|
||||
{
|
||||
var newAccount = await _ZcashRpcProvider.WalletRpcClients[cryptoCode].SendCommandAsync<CreateAccountRequest, CreateAccountResponse>("create_account", new CreateAccountRequest()
|
||||
{
|
||||
Label = viewModel.NewAccountLabel
|
||||
});
|
||||
viewModel.AccountIndex = newAccount.AccountIndex;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.AccountIndex), StringLocalizer["Could not create new account."]);
|
||||
}
|
||||
|
||||
}
|
||||
else if (command == "upload-wallet")
|
||||
{
|
||||
var valid = true;
|
||||
if (viewModel.WalletFile == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.WalletFile), StringLocalizer["Please select the wallet file"]);
|
||||
valid = false;
|
||||
}
|
||||
if (viewModel.WalletKeysFile == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.WalletKeysFile), StringLocalizer["Please select the wallet.keys file"]);
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (valid)
|
||||
{
|
||||
if (_ZcashRpcProvider.Summaries.TryGetValue(cryptoCode, out var summary))
|
||||
{
|
||||
if (summary.WalletAvailable)
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = StringLocalizer["There is already an active wallet configured for {0}. Replacing it would break any existing invoices!", cryptoCode].Value
|
||||
});
|
||||
return RedirectToAction(nameof(GetStoreZcashLikePaymentMethod),
|
||||
new { cryptoCode });
|
||||
}
|
||||
}
|
||||
|
||||
var fileAddress = Path.Combine(configurationItem.WalletDirectory, "wallet");
|
||||
using (var fileStream = new FileStream(fileAddress, FileMode.Create))
|
||||
{
|
||||
await viewModel.WalletFile.CopyToAsync(fileStream);
|
||||
try
|
||||
{
|
||||
Exec($"chmod 666 {fileAddress}");
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
fileAddress = Path.Combine(configurationItem.WalletDirectory, "wallet.keys");
|
||||
using (var fileStream = new FileStream(fileAddress, FileMode.Create))
|
||||
{
|
||||
await viewModel.WalletKeysFile.CopyToAsync(fileStream);
|
||||
try
|
||||
{
|
||||
Exec($"chmod 666 {fileAddress}");
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fileAddress = Path.Combine(configurationItem.WalletDirectory, "password");
|
||||
using (var fileStream = new StreamWriter(fileAddress, false))
|
||||
{
|
||||
await fileStream.WriteAsync(viewModel.WalletPassword);
|
||||
try
|
||||
{
|
||||
Exec($"chmod 666 {fileAddress}");
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(GetStoreZcashLikePaymentMethod), new
|
||||
{
|
||||
cryptoCode,
|
||||
StatusMessage = "Wallet files uploaded. If it was valid, the wallet will become available soon"
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
|
||||
var vm = GetZcashLikePaymentMethodViewModel(StoreData, cryptoCode,
|
||||
StoreData.GetStoreBlob().GetExcludedPaymentMethods(), await GetAccounts(cryptoCode));
|
||||
|
||||
vm.Enabled = viewModel.Enabled;
|
||||
vm.NewAccountLabel = viewModel.NewAccountLabel;
|
||||
vm.AccountIndex = viewModel.AccountIndex;
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
var storeData = StoreData;
|
||||
var blob = storeData.GetStoreBlob();
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode);
|
||||
storeData.SetPaymentMethodConfig(_handlers[pmi], new ZcashPaymentMethodConfig()
|
||||
{
|
||||
AccountIndex = viewModel.AccountIndex
|
||||
});
|
||||
|
||||
blob.SetExcluded(PaymentTypes.CHAIN.GetPaymentMethodId(viewModel.CryptoCode), !viewModel.Enabled);
|
||||
storeData.SetStoreBlob(blob);
|
||||
await _StoreRepository.UpdateStore(storeData);
|
||||
return RedirectToAction("GetStoreZcashLikePaymentMethods",
|
||||
new { StatusMessage = $"{cryptoCode} settings updated successfully", storeId = StoreData.Id });
|
||||
}
|
||||
|
||||
private void Exec(string cmd)
|
||||
{
|
||||
|
||||
var escapedArgs = cmd.Replace("\"", "\\\"", StringComparison.InvariantCulture);
|
||||
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
FileName = "/bin/sh",
|
||||
Arguments = $"-c \"{escapedArgs}\""
|
||||
}
|
||||
};
|
||||
|
||||
#pragma warning disable CA1416 // Validate platform compatibility
|
||||
process.Start();
|
||||
#pragma warning restore CA1416 // Validate platform compatibility
|
||||
process.WaitForExit();
|
||||
}
|
||||
|
||||
public class ZcashLikePaymentMethodListViewModel
|
||||
{
|
||||
public IEnumerable<ZcashLikePaymentMethodViewModel> Items { get; set; }
|
||||
}
|
||||
|
||||
public class ZcashLikePaymentMethodViewModel
|
||||
{
|
||||
public ZcashRPCProvider.ZcashLikeSummary Summary { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public string NewAccountLabel { get; set; }
|
||||
public long AccountIndex { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public IEnumerable<SelectListItem> Accounts { get; set; }
|
||||
public bool WalletFileFound { get; set; }
|
||||
[Display(Name = "View-Only Wallet File")]
|
||||
public IFormFile WalletFile { get; set; }
|
||||
public IFormFile WalletKeysFile { get; set; }
|
||||
public string WalletPassword { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using System;
|
||||
using BTCPayServer.Payments;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.UI
|
||||
{
|
||||
public class ZcashPaymentViewModel
|
||||
{
|
||||
public PaymentMethodId PaymentMethodId { get; set; }
|
||||
public string Confirmations { get; set; }
|
||||
public string DepositAddress { get; set; }
|
||||
public string Amount { get; set; }
|
||||
public string TransactionId { get; set; }
|
||||
public DateTimeOffset ReceivedTime { get; set; }
|
||||
public string TransactionLink { get; set; }
|
||||
public string Currency { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
@using BTCPayServer.Services.Altcoins.Monero.Services
|
||||
@inject MoneroRPCProvider MoneroRpcProvider
|
||||
@inject SignInManager<ApplicationUser> SignInManager;
|
||||
|
||||
@if (SignInManager.IsSignedIn(User) && User.IsInRole(Roles.ServerAdmin) && MoneroRpcProvider.Summaries.Any())
|
||||
{
|
||||
@foreach (var summary in MoneroRpcProvider.Summaries)
|
||||
{
|
||||
@if (summary.Value != null)
|
||||
{
|
||||
var status = summary.Value.DaemonAvailable
|
||||
? summary.Value.Synced ? "enabled" : "pending"
|
||||
: "disabled";
|
||||
<h5 class="d-flex align-items-center fw-semibold">
|
||||
<span class="me-2 btcpay-status btcpay-status--@status"></span>
|
||||
@summary.Key
|
||||
</h5>
|
||||
<ul>
|
||||
<li>Node available: @summary.Value.DaemonAvailable</li>
|
||||
<li>Wallet available: @summary.Value.WalletAvailable</li>
|
||||
<li>Last updated: @summary.Value.UpdatedAt</li>
|
||||
<li>Synced: @summary.Value.Synced (@summary.Value.CurrentHeight / @summary.Value.TargetHeight)</li>
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
@using BTCPayServer.Services.Altcoins.Monero.Configuration
|
||||
@using BTCPayServer.Services.Altcoins.Monero.UI
|
||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@using BTCPayServer.Abstractions.Contracts
|
||||
@inject SignInManager<ApplicationUser> SignInManager;
|
||||
@inject MoneroLikeConfiguration MoneroLikeConfiguration;
|
||||
@inject IScopeProvider ScopeProvider
|
||||
@{
|
||||
var storeId = ScopeProvider.GetCurrentStoreId();
|
||||
var isActive = !string.IsNullOrEmpty(storeId) && ViewContext.RouteData.Values.TryGetValue("Controller", out var controller) && controller is not null &&
|
||||
nameof(UIMoneroLikeStoreController).StartsWith(controller.ToString() ?? string.Empty, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
@if (SignInManager.IsSignedIn(User) && User.IsInRole(Roles.ServerAdmin) && MoneroLikeConfiguration.MoneroLikeConfigurationItems.Any())
|
||||
{
|
||||
<a class="nav-link @(isActive ? "active" : string.Empty)" asp-route-storeId="@storeId" asp-action="GetStoreMoneroLikePaymentMethods" asp-controller="UIMoneroLikeStore">Monero</a>
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
@using BTCPayServer.Services.Altcoins.Monero.Configuration
|
||||
@using BTCPayServer.Services.Altcoins.Monero.UI
|
||||
@using BTCPayServer.Abstractions.Contracts
|
||||
@inject SignInManager<ApplicationUser> SignInManager;
|
||||
@inject MoneroLikeConfiguration MoneroLikeConfiguration;
|
||||
@inject IScopeProvider ScopeProvider
|
||||
@inject UIMoneroLikeStoreController UIMoneroLikeStore;
|
||||
@{
|
||||
var storeId = ScopeProvider.GetCurrentStoreId();
|
||||
|
||||
}
|
||||
@if (SignInManager.IsSignedIn(User) && User.IsInRole(Roles.ServerAdmin) && MoneroLikeConfiguration.MoneroLikeConfigurationItems.Any())
|
||||
{
|
||||
var store = Context.GetStoreData();
|
||||
var result = await UIMoneroLikeStore.GetVM(store);
|
||||
|
||||
foreach (var item in result.Items)
|
||||
{
|
||||
|
||||
var isActive = !string.IsNullOrEmpty(storeId) && ViewContext.RouteData.Values.TryGetValue("Controller", out var controller) && controller is not null &&
|
||||
nameof(UIMoneroLikeStoreController).StartsWith(controller.ToString() ?? string.Empty, StringComparison.InvariantCultureIgnoreCase) &&
|
||||
ViewContext.RouteData.Values.TryGetValue("cryptoCode", out var cryptoCode) && cryptoCode is not null && cryptoCode.ToString() == item.CryptoCode;
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @(isActive? "active" : "")"
|
||||
asp-route-cryptoCode="@item.CryptoCode"
|
||||
asp-route-storeId="@storeId"
|
||||
asp-action="GetStoreMoneroLikePaymentMethod"
|
||||
asp-controller="UIMoneroLikeStore">
|
||||
<span class="me-2 btcpay-status btcpay-status--@(item.Enabled ? "enabled" : "pending")"></span>
|
||||
<span>@item.CryptoCode Wallet</span>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
@using System.Globalization
|
||||
@using BTCPayServer.Plugins.Altcoins;
|
||||
@using BTCPayServer.Services
|
||||
@using BTCPayServer.Services.Altcoins.Monero.Payments
|
||||
@using BTCPayServer.Services.Altcoins.Monero.Services
|
||||
@using BTCPayServer.Services.Altcoins.Monero.UI
|
||||
@using BTCPayServer.Services.Invoices
|
||||
@inject DisplayFormatter DisplayFormatter
|
||||
@model InvoiceDetailsModel
|
||||
@inject TransactionLinkProviders TransactionLinkProviders
|
||||
@inject PaymentMethodHandlerDictionary handlers
|
||||
|
||||
@{
|
||||
var payments = Model.Payments.Select(payment =>
|
||||
{
|
||||
if (!handlers.TryGetValue(payment.PaymentMethodId, out var h) || h is not MoneroLikePaymentMethodHandler handler)
|
||||
return null;
|
||||
var m = new MoneroPaymentViewModel();
|
||||
var onChainPaymentData = handler.ParsePaymentDetails(payment.Details);
|
||||
m.PaymentMethodId = handler.PaymentMethodId;
|
||||
m.DepositAddress = payment.Destination;
|
||||
m.Amount = payment.Value.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
var confReq = MoneroListener.ConfirmationsRequired(onChainPaymentData, payment.InvoiceEntity.SpeedPolicy);
|
||||
var confCount = onChainPaymentData.ConfirmationCount;
|
||||
confCount = Math.Min(confReq, confCount);
|
||||
m.Confirmations = $"{confCount} / {confReq}";
|
||||
|
||||
m.TransactionId = onChainPaymentData.TransactionId;
|
||||
m.ReceivedTime = payment.ReceivedTime;
|
||||
if (onChainPaymentData.TransactionId != null)
|
||||
m.TransactionLink = TransactionLinkProviders.GetTransactionLink(m.PaymentMethodId, onChainPaymentData.TransactionId);
|
||||
m.Currency = payment.Currency;
|
||||
return m;
|
||||
}).Where(c => c != null).ToList();
|
||||
}
|
||||
|
||||
@if (payments.Any())
|
||||
{
|
||||
<section>
|
||||
<h5>Monero Payments</h5>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-75px">Payment Method</th>
|
||||
<th class="w-175px">Destination</th>
|
||||
<th class="text-nowrap">Payment Proof</th>
|
||||
<th class="text-end">Confirmations</th>
|
||||
<th class="w-150px text-end">Paid</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var payment in payments)
|
||||
{
|
||||
<tr >
|
||||
<td>@payment.PaymentMethodId</td>
|
||||
<td><vc:truncate-center text="@payment.DepositAddress" classes="truncate-center-id" /></td>
|
||||
<td><vc:truncate-center text="@payment.TransactionId" link="@payment.TransactionLink" classes="truncate-center-id" /></td>
|
||||
<td class="text-end">@payment.Confirmations</td>
|
||||
<td class="payment-value text-end text-nowrap">
|
||||
<span data-sensitive class="text-success">@DisplayFormatter.Currency(payment.Amount, payment.Currency)</span>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
@using BTCPayServer.Services.Altcoins.Zcash.Configuration
|
||||
@using BTCPayServer.Services.Altcoins.Zcash.UI
|
||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@using BTCPayServer.Abstractions.Contracts
|
||||
@inject SignInManager<ApplicationUser> SignInManager;
|
||||
@inject ZcashLikeConfiguration ZcashLikeConfiguration;
|
||||
@inject IScopeProvider ScopeProvider
|
||||
@{
|
||||
var storeId = ScopeProvider.GetCurrentStoreId();
|
||||
var isActive = !string.IsNullOrEmpty(storeId) && ViewContext.RouteData.Values.TryGetValue("Controller", out var controller) && controller is not null &&
|
||||
nameof(UIZcashLikeStoreController).StartsWith(controller.ToString() ?? string.Empty, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
@if (SignInManager.IsSignedIn(User) && User.IsInRole(Roles.ServerAdmin) && ZcashLikeConfiguration.ZcashLikeConfigurationItems.Any())
|
||||
{
|
||||
<a class="nav-link @(isActive ? "active" : string.Empty)" asp-route-storeId="@storeId" asp-action="GetStoreZcashLikePaymentMethods" asp-controller="UIZcashLikeStore">Zcash</a>
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
@using System.Globalization
|
||||
@using BTCPayServer.Plugins.Altcoins;
|
||||
@using BTCPayServer.Components.TruncateCenter
|
||||
@using BTCPayServer.Services
|
||||
@using BTCPayServer.Services.Altcoins.Zcash.Payments
|
||||
@using BTCPayServer.Services.Altcoins.Zcash.Services
|
||||
@using BTCPayServer.Services.Altcoins.Zcash.UI
|
||||
@using BTCPayServer.Services.Invoices
|
||||
@inject DisplayFormatter DisplayFormatter
|
||||
@model InvoiceDetailsModel
|
||||
@inject TransactionLinkProviders TransactionLinkProviders
|
||||
@inject PaymentMethodHandlerDictionary handlers
|
||||
|
||||
@{
|
||||
var payments = Model.Payments.Select(payment =>
|
||||
{
|
||||
if (!handlers.TryGetValue(payment.PaymentMethodId, out var h) || h is not ZcashLikePaymentMethodHandler handler)
|
||||
return null;
|
||||
var m = new ZcashPaymentViewModel();
|
||||
var onChainPaymentData = handler.ParsePaymentDetails(payment.Details);
|
||||
m.PaymentMethodId = handler.PaymentMethodId;
|
||||
m.DepositAddress = payment.Destination;
|
||||
m.Amount = payment.Value.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
var confReq = ZcashListener.ConfirmationsRequired(payment.InvoiceEntity.SpeedPolicy);
|
||||
var confCount = onChainPaymentData.ConfirmationCount;
|
||||
confCount = Math.Min(confReq, confCount);
|
||||
m.Confirmations = $"{confCount} / {confReq}";
|
||||
|
||||
m.TransactionId = onChainPaymentData.TransactionId;
|
||||
m.ReceivedTime = payment.ReceivedTime;
|
||||
if (onChainPaymentData.TransactionId != null)
|
||||
m.TransactionLink = TransactionLinkProviders.GetTransactionLink(m.PaymentMethodId, onChainPaymentData.TransactionId);
|
||||
m.Currency = payment.Currency;
|
||||
return m;
|
||||
}).Where(c => c != null).ToList();
|
||||
}
|
||||
|
||||
@if (payments.Any())
|
||||
{
|
||||
<section>
|
||||
<h5>Zcash Payments</h5>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-75px">Payment Method</th>
|
||||
<th class="w-175px">Destination</th>
|
||||
<th class="text-nowrap">Payment Proof</th>
|
||||
<th class="text-end">Confirmations</th>
|
||||
<th class="w-150px text-end">Paid</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var payment in payments)
|
||||
{
|
||||
<tr >
|
||||
<td>@payment.PaymentMethodId</td>
|
||||
<td><vc:truncate-center text="@payment.DepositAddress" classes="truncate-center-id" /></td>
|
||||
<td><vc:truncate-center text="@payment.TransactionId" link="@payment.TransactionLink" classes="truncate-center-id" /></td>
|
||||
<td class="text-end">@payment.Confirmations</td>
|
||||
<td class="payment-value text-end text-nowrap">
|
||||
<span data-sensitive class="text-success">@DisplayFormatter.Currency(payment.Amount, payment.Currency)</span>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
@using BTCPayServer.Services.Altcoins.Zcash.Services
|
||||
@inject ZcashRPCProvider ZcashRpcProvider
|
||||
@inject SignInManager<ApplicationUser> SignInManager;
|
||||
|
||||
@if (SignInManager.IsSignedIn(User) && User.IsInRole(Roles.ServerAdmin) && ZcashRpcProvider.Summaries.Any())
|
||||
{
|
||||
@foreach (var summary in ZcashRpcProvider.Summaries)
|
||||
{
|
||||
@if (summary.Value != null)
|
||||
{
|
||||
var status = summary.Value.DaemonAvailable
|
||||
? summary.Value.Synced ? "enabled" : "pending"
|
||||
: "disabled";
|
||||
<h5 class="d-flex align-items-center fw-semibold">
|
||||
<span class="me-2 btcpay-status btcpay-status--@status"></span>
|
||||
@summary.Key
|
||||
</h5>
|
||||
<ul>
|
||||
<li>Node available: @summary.Value.DaemonAvailable</li>
|
||||
<li>Wallet available: @summary.Value.WalletAvailable</li>
|
||||
<li>Last updated: @summary.Value.UpdatedAt</li>
|
||||
<li>Synced: @summary.Value.Synced (@summary.Value.CurrentHeight / @summary.Value.TargetHeight)</li>
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
@using MoneroLikePaymentMethodViewModel = BTCPayServer.Services.Altcoins.Monero.UI.UIMoneroLikeStoreController.MoneroLikePaymentMethodViewModel
|
||||
@using MoneroLikeSettlementThresholdChoice = BTCPayServer.Services.Altcoins.Monero.UI.UIMoneroLikeStoreController.MoneroLikeSettlementThresholdChoice;
|
||||
@model MoneroLikePaymentMethodViewModel
|
||||
|
||||
@{
|
||||
ViewData.SetActivePage(Model.CryptoCode, StringLocalizer["{0} Settings", Model.CryptoCode], Model.CryptoCode);
|
||||
}
|
||||
|
||||
<partial name="_StatusMessage" />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
@if (Model.Summary != null)
|
||||
{
|
||||
<div class="card">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">Node available: @Model.Summary.DaemonAvailable</li>
|
||||
<li class="list-group-item">Wallet available: @Model.Summary.WalletAvailable (@(Model.WalletFileFound ? "Wallet file present" : "Wallet file not found"))</li>
|
||||
<li class="list-group-item">Last updated: @Model.Summary.UpdatedAt</li>
|
||||
<li class="list-group-item">Synced: @Model.Summary.Synced (@Model.Summary.CurrentHeight / @Model.Summary.TargetHeight)</li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!Model.WalletFileFound || Model.Summary.WalletHeight == default)
|
||||
{
|
||||
<form method="post" asp-action="GetStoreMoneroLikePaymentMethod"
|
||||
asp-route-storeId="@Context.GetRouteValue("storeId")"
|
||||
asp-route-cryptoCode="@Context.GetRouteValue("cryptoCode")"
|
||||
class="mt-4" enctype="multipart/form-data">
|
||||
|
||||
<div class="card my-2">
|
||||
<h3 class="card-title p-2">Upload Wallet</h3>
|
||||
<div class="form-group p-2">
|
||||
<label asp-for="WalletFile" class="form-label"></label>
|
||||
<input class="form-control" asp-for="WalletFile" required>
|
||||
<span asp-validation-for="WalletFile" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group p-2">
|
||||
<label asp-for="WalletKeysFile" class="form-label"></label>
|
||||
<input class="form-control" asp-for="WalletKeysFile" required>
|
||||
<span asp-validation-for="WalletKeysFile" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group p-2">
|
||||
<label asp-for="WalletPassword" class="form-label"></label>
|
||||
<input class="form-control" asp-for="WalletPassword">
|
||||
<span asp-validation-for="WalletPassword" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="card-footer text-right">
|
||||
<button name="command" value="upload-wallet" class="btn btn-secondary" type="submit">Upload</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
<form method="post" asp-action="GetStoreMoneroLikePaymentMethod"
|
||||
asp-route-storeId="@Context.GetRouteValue("storeId")"
|
||||
asp-route-cryptoCode="@Context.GetRouteValue("cryptoCode")"
|
||||
class="mt-4" enctype="multipart/form-data">
|
||||
|
||||
<input type="hidden" asp-for="CryptoCode"/>
|
||||
@if (!Model.WalletFileFound || Model.Summary.WalletHeight == default)
|
||||
{
|
||||
<input type="hidden" asp-for="AccountIndex"/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="AccountIndex" class="control-label"></label>
|
||||
@if (@Model.Accounts != null && Model.Accounts.Any())
|
||||
{
|
||||
<select asp-for="AccountIndex" asp-items="Model.Accounts" class="form-control"></select>
|
||||
<span asp-validation-for="AccountIndex" class="text-danger"></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>No accounts available on the current wallet</span>
|
||||
<input type="hidden" asp-for="AccountIndex"/>
|
||||
}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-group my-3">
|
||||
<input type="text" class="form-control" placeholder="@StringLocalizer["New account label"]" asp-for="NewAccountLabel">
|
||||
<button name="command" value="add-account" class="input-group-text btn btn-secondary" type="submit">Add account</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="form-group">
|
||||
<label asp-for="Enabled"></label>
|
||||
<input asp-for="Enabled" type="checkbox" class="form-check"/>
|
||||
<span asp-validation-for="Enabled" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label asp-for="SettlementConfirmationThresholdChoice" class="form-label"></label>
|
||||
<a href="https://docs.btcpayserver.org/FAQ/Stores/#consider-the-invoice-confirmed-when-the-payment-transaction" target="_blank" rel="noreferrer noopener" title="@StringLocalizer["More information..."]">
|
||||
<vc:icon symbol="info" />
|
||||
</a>
|
||||
<select
|
||||
asp-for="SettlementConfirmationThresholdChoice"
|
||||
asp-items="Html.GetEnumSelectList<MoneroLikeSettlementThresholdChoice>()"
|
||||
class="form-select w-auto"
|
||||
onchange="
|
||||
document.getElementById('unconfirmed-warning').hidden = this.value !== '@((int)MoneroLikeSettlementThresholdChoice.ZeroConfirmation)';
|
||||
document.getElementById('custom-confirmation-value').hidden = this.value !== '@((int)MoneroLikeSettlementThresholdChoice.Custom)';">
|
||||
</select>
|
||||
<span asp-validation-for="SettlementConfirmationThresholdChoice" class="text-danger"></span>
|
||||
<p class="info-note my-3 text-warning" id="unconfirmed-warning" role="alert" hidden="@(Model.SettlementConfirmationThresholdChoice is not MoneroLikeSettlementThresholdChoice.ZeroConfirmation)">
|
||||
<vc:icon symbol="warning" />
|
||||
<span text-translate="true">Choosing to accept an unconfirmed invoice can lead to double-spending and is strongly discouraged.</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="custom-confirmation-value" hidden="@(Model.SettlementConfirmationThresholdChoice is not MoneroLikeSettlementThresholdChoice.Custom)">
|
||||
<label asp-for="CustomSettlementConfirmationThreshold" class="form-label"></label>
|
||||
<input
|
||||
asp-for="CustomSettlementConfirmationThreshold"
|
||||
type="number"
|
||||
value="@(Model.CustomSettlementConfirmationThreshold)"
|
||||
class="form-control w-auto"
|
||||
min="0"
|
||||
max="100"
|
||||
pattern="\d+"
|
||||
/>
|
||||
<span asp-validation-for="CustomSettlementConfirmationThreshold" 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="GetStoreMoneroLikePaymentMethods"
|
||||
asp-route-storeId="@Context.GetRouteValue("storeId")"
|
||||
asp-route-cryptoCode="@Context.GetRouteValue("cryptoCode")"
|
||||
asp-controller="UIMoneroLikeStore">
|
||||
Back to list
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section PageFootContent {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
@model BTCPayServer.Services.Altcoins.Monero.UI.UIMoneroLikeStoreController.MoneroLikePaymentMethodListViewModel
|
||||
|
||||
@{
|
||||
ViewData.SetActivePage("Monero Settings", StringLocalizer["{0} Settings", "Monero"], "Monero Settings");
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All"></div>
|
||||
}
|
||||
<div class="table-responsive-md">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th text-translate="true">Crypto</th>
|
||||
<th text-translate="true">Account Index</th>
|
||||
<th class="text-center" text-translate="true">Enabled</th>
|
||||
<th class="text-right" text-translate="true">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Model.Items)
|
||||
{
|
||||
<tr>
|
||||
<td>@item.CryptoCode</td>
|
||||
<td>@item.AccountIndex</td>
|
||||
<td class="text-center">
|
||||
@if (item.Enabled)
|
||||
{
|
||||
<vc:icon symbol="checkmark" css-class="text-success" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<vc:icon symbol="cross" css-class="text-danger" />
|
||||
}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<a id="Modify" asp-action="GetStoreMoneroLikePaymentMethod"
|
||||
asp-route-storeId="@Context.GetRouteValue("storeId")"
|
||||
asp-route-cryptoCode="@item.CryptoCode"
|
||||
text-translate="true">
|
||||
Modify
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section PageFootContent {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
@using BTCPayServer.Views.Stores
|
||||
@model BTCPayServer.Services.Altcoins.Zcash.UI.UIZcashLikeStoreController.ZcashLikePaymentMethodViewModel
|
||||
|
||||
@{
|
||||
ViewData.SetActivePage(StoreNavPages.OnchainSettings, StringLocalizer["{0} Settings", Model.CryptoCode], $"{Context.GetStoreData().Id}-{Model.CryptoCode}");
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
}
|
||||
@if (Model.Summary != null)
|
||||
{
|
||||
<div class="card">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">Node available: @Model.Summary.DaemonAvailable</li>
|
||||
<li class="list-group-item">Wallet available: @Model.Summary.WalletAvailable (@(Model.WalletFileFound ? "Wallet file present" : "Wallet file not found"))</li>
|
||||
<li class="list-group-item">Last updated: @Model.Summary.UpdatedAt</li>
|
||||
<li class="list-group-item">Synced: @Model.Summary.Synced (@Model.Summary.CurrentHeight / @Model.Summary.TargetHeight)</li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!Model.WalletFileFound || Model.Summary.WalletHeight == default)
|
||||
{
|
||||
<form method="post" asp-action="GetStoreZcashLikePaymentMethod"
|
||||
asp-route-storeId="@Context.GetRouteValue("storeId")"
|
||||
asp-route-cryptoCode="@Context.GetRouteValue("cryptoCode")"
|
||||
class="mt-4" enctype="multipart/form-data">
|
||||
|
||||
<div class="card my-2">
|
||||
<h3 class="card-title p-2" text-translate="true">Upload Wallet</h3>
|
||||
<div class="form-group p-2">
|
||||
<label asp-for="WalletFile" class="form-label"></label>
|
||||
<input class="form-control" asp-for="WalletFile" required>
|
||||
<span asp-validation-for="WalletFile" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group p-2">
|
||||
<label asp-for="WalletKeysFile" class="form-label"></label>
|
||||
<input class="form-control" asp-for="WalletKeysFile" required>
|
||||
<span asp-validation-for="WalletKeysFile" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group p-2">
|
||||
<label asp-for="WalletPassword" class="form-label"></label>
|
||||
<input class="form-control" asp-for="WalletPassword">
|
||||
<span asp-validation-for="WalletPassword" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="card-footer text-right">
|
||||
<button name="command" value="upload-wallet" class="btn btn-secondary" type="submit" text-translate="true">Upload</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
<form method="post" asp-action="GetStoreZcashLikePaymentMethod"
|
||||
asp-route-storeId="@Context.GetRouteValue("storeId")"
|
||||
asp-route-cryptoCode="@Context.GetRouteValue("cryptoCode")"
|
||||
class="mt-4" enctype="multipart/form-data">
|
||||
|
||||
<input type="hidden" asp-for="CryptoCode"/>
|
||||
@if (!Model.WalletFileFound || Model.Summary.WalletHeight == default)
|
||||
{
|
||||
<input type="hidden" asp-for="AccountIndex"/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="AccountIndex" class="control-label"></label>
|
||||
@if (@Model.Accounts != null && Model.Accounts.Any())
|
||||
{
|
||||
<select asp-for="AccountIndex" asp-items="Model.Accounts" class="form-control"></select>
|
||||
<span asp-validation-for="AccountIndex" class="text-danger"></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span text-translate="true">No accounts available on the current wallet</span>
|
||||
<input type="hidden" asp-for="AccountIndex"/>
|
||||
}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-group my-3">
|
||||
<input type="text" class="form-control" placeholder="@StringLocalizer["New account label"]" asp-for="NewAccountLabel">
|
||||
<button name="command" value="add-account" class="input-group-text btn btn-secondary" type="submit" text-translate="true">Add account</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="form-group">
|
||||
<label asp-for="Enabled"></label>
|
||||
<input asp-for="Enabled" type="checkbox" class="form-check"/>
|
||||
<span asp-validation-for="Enabled" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary" id="SaveButton" text-translate="true">Save</button>
|
||||
|
||||
<a class="btn btn-secondary" asp-action="GetStoreZcashLikePaymentMethods"
|
||||
asp-route-storeId="@Context.GetRouteValue("storeId")"
|
||||
asp-route-cryptoCode="@Context.GetRouteValue("cryptoCode")"
|
||||
asp-controller="UIZcashLikeStore"
|
||||
text-translate="true">
|
||||
Back to list
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section PageFootContent {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
@using BTCPayServer.Views.Stores
|
||||
@model BTCPayServer.Services.Altcoins.Zcash.UI.UIZcashLikeStoreController.ZcashLikePaymentMethodListViewModel
|
||||
|
||||
@{
|
||||
ViewData.SetActivePage(StoreNavPages.OnchainSettings, StringLocalizer["{0} Settings", "Zcash"], $"{Context.GetStoreData().Id}-ZEC");
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
}
|
||||
<div class="table-responsive-md">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th text-translate="true">Crypto</th>
|
||||
<th text-translate="true">Account Index</th>
|
||||
<th text-translate="true" class="text-center">Enabled</th>
|
||||
<th text-translate="true" class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Model.Items)
|
||||
{
|
||||
<tr>
|
||||
<td>@item.CryptoCode</td>
|
||||
<td>@item.AccountIndex</td>
|
||||
<td class="text-center">
|
||||
@if (item.Enabled)
|
||||
{
|
||||
<vc:icon symbol="checkmark" css-class="text-success" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<vc:icon symbol="cross" css-class="text-danger" />
|
||||
}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<a id="Modify" asp-action="GetStoreZcashLikePaymentMethod"
|
||||
asp-route-storeId="@Context.GetRouteValue("storeId")"
|
||||
asp-route-cryptoCode="@item.CryptoCode"
|
||||
text-translate="true">
|
||||
Modify
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section PageFootContent {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<circle style="fill:#F0EFEB;" cx="256" cy="256" r="256"/>
|
||||
<path style="fill:#4C4C4C;" d="M364.2,393.163h107.979c-45.411,71.439-125.262,118.836-216.178,118.836
|
||||
S85.235,464.603,39.824,393.163h107.969V257.328l108.209,108.146L364.2,257.328V393.163z"/>
|
||||
<path style="fill:#FF6600;" d="M512,256.001c0,28.599-4.692,56.1-13.343,81.784H421.21V122.537L256.002,286.062L90.794,122.537
|
||||
v215.248H13.346C4.694,312.102,0.003,284.6,0.003,256.001c0-141.384,114.614-255.998,255.998-255.998S512,114.616,512,256.001z"/>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 995 B |
Binary file not shown.
|
Before Width: | Height: | Size: 121 KiB |
Reference in New Issue
Block a user