mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 01:43:50 +01:00
parent
44e84b46b8
commit
f4153ade92
@ -0,0 +1,29 @@
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public partial class BTCPayNetworkProvider
|
||||
{
|
||||
// Change this if you want another zcash coin
|
||||
public void InitZcash()
|
||||
{
|
||||
Add(new ZcashLikeSpecificBtcPayNetwork()
|
||||
{
|
||||
CryptoCode = "ZEC",
|
||||
DisplayName = "Zcash",
|
||||
Divisibility = 8,
|
||||
BlockExplorerLink =
|
||||
NetworkType == ChainName.Mainnet
|
||||
? "https://www.exploreZcash.com/transaction/{0}"
|
||||
: "https://testnet.xmrchain.net/tx/{0}",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"ZEC_X = ZEC_BTC * BTC_X",
|
||||
"ZEC_BTC = kraken(ZEC_BTC)"
|
||||
},
|
||||
CryptoImagePath = "/imlegacy/zcash.png",
|
||||
UriScheme = "zcash"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
102
BTCPayServer.Common/Altcoins/Zcash/RPC/JsonRpcClient.cs
Normal file
102
BTCPayServer.Common/Altcoins/Zcash/RPC/JsonRpcClient.cs
Normal file
@ -0,0 +1,102 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public partial class CreateAccountRequest
|
||||
{
|
||||
[JsonProperty("label")] public string Label { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
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; }
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
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; }
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
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; }
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public partial class GetAccountsRequest
|
||||
{
|
||||
[JsonProperty("tag")] public string Tag { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
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; }
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public class GetFeeEstimateRequest
|
||||
{
|
||||
[JsonProperty("grace_blocks")] public int? GraceBlocks { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
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; }
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public partial class GetHeightResponse
|
||||
{
|
||||
[JsonProperty("height")] public long Height { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
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; }
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
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; }
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
33
BTCPayServer.Common/Altcoins/Zcash/RPC/Models/Info.cs
Normal file
33
BTCPayServer.Common/Altcoins/Zcash/RPC/Models/Info.cs
Normal file
@ -0,0 +1,33 @@
|
||||
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; }
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
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; }
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public partial class MakeUriResponse
|
||||
{
|
||||
[JsonProperty("uri")] public string Uri { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
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();
|
||||
}
|
||||
}
|
9
BTCPayServer.Common/Altcoins/Zcash/RPC/Models/Peer.cs
Normal file
9
BTCPayServer.Common/Altcoins/Zcash/RPC/Models/Peer.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC.Models
|
||||
{
|
||||
public partial class Peer
|
||||
{
|
||||
[JsonProperty("info")] public Info Info { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
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; }
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
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; }
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
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; }
|
||||
}
|
||||
}
|
20
BTCPayServer.Common/Altcoins/Zcash/Utils/ZcashMoney.cs
Normal file
20
BTCPayServer.Common/Altcoins/Zcash/Utils/ZcashMoney.cs
Normal file
@ -0,0 +1,20 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class ZcashLikeSpecificBtcPayNetwork : BTCPayNetworkBase
|
||||
{
|
||||
public int MaxTrackedConfirmation = 10;
|
||||
public string UriScheme { get; set; }
|
||||
}
|
||||
}
|
@ -55,6 +55,7 @@ namespace BTCPayServer
|
||||
InitGroestlcoin();
|
||||
InitViacoin();
|
||||
InitMonero();
|
||||
InitZcash();
|
||||
InitPolis();
|
||||
InitChaincoin();
|
||||
// InitArgoneum();//their rate source is down 9/15/20.
|
||||
|
@ -1259,6 +1259,20 @@
|
||||
"symbol":null,
|
||||
"crypto":true
|
||||
},
|
||||
{
|
||||
"name":"YEC",
|
||||
"code":"YEC",
|
||||
"divisibility":8,
|
||||
"symbol":null,
|
||||
"crypto":true
|
||||
},
|
||||
{
|
||||
"name":"ZEC",
|
||||
"code":"ZEC",
|
||||
"divisibility":8,
|
||||
"symbol":null,
|
||||
"crypto":true
|
||||
},
|
||||
{
|
||||
"name":"POLIS",
|
||||
"code":"POLIS",
|
||||
|
@ -385,6 +385,7 @@ services:
|
||||
- "19444:19444"
|
||||
volumes:
|
||||
- "elementsd_liquid_datadir:/data"
|
||||
|
||||
volumes:
|
||||
sshd_datadir:
|
||||
bitcoin_datadir:
|
||||
|
@ -40,7 +40,9 @@
|
||||
<ItemGroup Condition="'$(Altcoins)' != 'true'">
|
||||
<Content Remove="Services\Altcoins\**\*" />
|
||||
<Content Remove="Views\UIMoneroLikeStore\**\*" />
|
||||
<Content Remove="Views\UIZcashLikeStore\**\*" />
|
||||
<Content Remove="Views\Shared\Monero\**\*" />
|
||||
<Content Remove="Views\Shared\Zcash\**\*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -59,6 +59,7 @@ using NicolasDorier.RateLimits;
|
||||
using Serilog;
|
||||
#if ALTCOINS
|
||||
using BTCPayServer.Services.Altcoins.Monero;
|
||||
using BTCPayServer.Services.Altcoins.Zcash;
|
||||
#endif
|
||||
namespace BTCPayServer.Hosting
|
||||
{
|
||||
@ -90,6 +91,8 @@ namespace BTCPayServer.Hosting
|
||||
#if ALTCOINS
|
||||
if (configuration.SupportChain("xmr"))
|
||||
services.AddMoneroLike();
|
||||
if (configuration.SupportChain("yec") || configuration.SupportChain("zec"))
|
||||
services.AddZcashLike();
|
||||
#endif
|
||||
services.TryAddSingleton<SettingsRepository>();
|
||||
services.TryAddSingleton<ISettingsRepository>(provider => provider.GetService<SettingsRepository>());
|
||||
|
@ -80,6 +80,8 @@ namespace BTCPayServer.Payments
|
||||
#if ALTCOINS
|
||||
if (CryptoCode == "XMR" && PaymentType == PaymentTypes.MoneroLike)
|
||||
return CryptoCode;
|
||||
if ((CryptoCode == "YEC" || CryptoCode == "ZEC") && PaymentType == PaymentTypes.ZcashLike)
|
||||
return CryptoCode;
|
||||
#endif
|
||||
return $"{CryptoCode}-{PaymentType.ToStringNormalized()}";
|
||||
}
|
||||
@ -105,6 +107,8 @@ namespace BTCPayServer.Payments
|
||||
#if ALTCOINS
|
||||
if (parts[0].ToUpperInvariant() == "XMR")
|
||||
type = PaymentTypes.MoneroLike;
|
||||
if (parts[0].ToUpperInvariant() == "ZEC")
|
||||
type = PaymentTypes.ZcashLike;
|
||||
#endif
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
|
@ -2,6 +2,7 @@ using System;
|
||||
using System.Linq;
|
||||
#if ALTCOINS
|
||||
using BTCPayServer.Services.Altcoins.Monero.Payments;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Payments;
|
||||
#endif
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using NBitcoin;
|
||||
@ -18,7 +19,7 @@ namespace BTCPayServer.Payments
|
||||
{
|
||||
BTCLike, LightningLike, LNURLPay,
|
||||
#if ALTCOINS
|
||||
MoneroLike,
|
||||
MoneroLike, ZcashLike,
|
||||
#endif
|
||||
};
|
||||
/// <summary>
|
||||
@ -39,6 +40,10 @@ namespace BTCPayServer.Payments
|
||||
/// Monero payment
|
||||
/// </summary>
|
||||
public static MoneroPaymentType MoneroLike => MoneroPaymentType.Instance;
|
||||
/// <summary>
|
||||
/// Zcash payment
|
||||
/// </summary>
|
||||
public static ZcashPaymentType ZcashLike => ZcashPaymentType.Instance;
|
||||
#endif
|
||||
|
||||
public static bool TryParse(string paymentType, out PaymentType type)
|
||||
|
@ -98,7 +98,7 @@
|
||||
"BTCPAY_RECOMMENDED-PLUGINS": "BTCPayServer.Plugins.Test",
|
||||
"BTCPAY_CHEATMODE": "true"
|
||||
},
|
||||
"applicationUrl": "https://localhost:14142/"
|
||||
"applicationUrl": "https://localhost:14142/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,20 @@
|
||||
#if ALTCOINS
|
||||
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; }
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,34 @@
|
||||
#if ALTCOINS
|
||||
using BTCPayServer.Payments;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.Payments
|
||||
{
|
||||
public class ZcashLikeOnChainPaymentMethodDetails : IPaymentMethodDetails
|
||||
{
|
||||
public PaymentType GetPaymentType()
|
||||
{
|
||||
return ZcashPaymentType.Instance;
|
||||
}
|
||||
|
||||
public string GetPaymentDestination()
|
||||
{
|
||||
return DepositAddress;
|
||||
}
|
||||
|
||||
public decimal GetNextNetworkFee()
|
||||
{
|
||||
return NextNetworkFee;
|
||||
}
|
||||
|
||||
public decimal GetFeeRate()
|
||||
{
|
||||
return 0.0m;
|
||||
}
|
||||
public bool Activated { get; set; } = true;
|
||||
public long AccountIndex { get; set; }
|
||||
public long AddressIndex { get; set; }
|
||||
public string DepositAddress { get; set; }
|
||||
public decimal NextNetworkFee { get; set; }
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,69 @@
|
||||
#if ALTCOINS
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Utils;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.Payments
|
||||
{
|
||||
public class ZcashLikePaymentData : CryptoPaymentData
|
||||
{
|
||||
public long Amount { get; set; }
|
||||
public string Address { get; set; }
|
||||
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 BTCPayNetworkBase Network { get; set; }
|
||||
|
||||
public string GetPaymentId()
|
||||
{
|
||||
return $"{TransactionId}#{SubaccountIndex}#{SubaddressIndex}";
|
||||
}
|
||||
|
||||
public string[] GetSearchTerms()
|
||||
{
|
||||
return new[] { TransactionId };
|
||||
}
|
||||
|
||||
public decimal GetValue()
|
||||
{
|
||||
return ZcashMoney.Convert(Amount);
|
||||
}
|
||||
|
||||
public bool PaymentCompleted(PaymentEntity entity)
|
||||
{
|
||||
return ConfirmationCount >= (Network as ZcashLikeSpecificBtcPayNetwork).MaxTrackedConfirmation;
|
||||
}
|
||||
|
||||
public bool PaymentConfirmed(PaymentEntity entity, SpeedPolicy speedPolicy)
|
||||
{
|
||||
switch (speedPolicy)
|
||||
{
|
||||
case SpeedPolicy.HighSpeed:
|
||||
return ConfirmationCount >= 0;
|
||||
case SpeedPolicy.MediumSpeed:
|
||||
return ConfirmationCount >= 1;
|
||||
case SpeedPolicy.LowMediumSpeed:
|
||||
return ConfirmationCount >= 2;
|
||||
case SpeedPolicy.LowSpeed:
|
||||
return ConfirmationCount >= 6;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public PaymentType GetPaymentType()
|
||||
{
|
||||
return ZcashPaymentType.Instance;
|
||||
}
|
||||
|
||||
public string GetDestination()
|
||||
{
|
||||
return Address;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,136 @@
|
||||
#if ALTCOINS
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services.Altcoins.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;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.Payments
|
||||
{
|
||||
public class ZcashLikePaymentMethodHandler : PaymentMethodHandlerBase<ZcashSupportedPaymentMethod, ZcashLikeSpecificBtcPayNetwork>
|
||||
{
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
private readonly ZcashRPCProvider _ZcashRpcProvider;
|
||||
|
||||
public ZcashLikePaymentMethodHandler(BTCPayNetworkProvider networkProvider, ZcashRPCProvider ZcashRpcProvider)
|
||||
{
|
||||
_networkProvider = networkProvider;
|
||||
_ZcashRpcProvider = ZcashRpcProvider;
|
||||
}
|
||||
public override PaymentType PaymentType => ZcashPaymentType.Instance;
|
||||
|
||||
public override async Task<IPaymentMethodDetails> CreatePaymentMethodDetails(InvoiceLogs logs, ZcashSupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod,
|
||||
StoreData store, ZcashLikeSpecificBtcPayNetwork network, object preparePaymentObject)
|
||||
{
|
||||
|
||||
if (preparePaymentObject is null)
|
||||
{
|
||||
return new ZcashLikeOnChainPaymentMethodDetails()
|
||||
{
|
||||
Activated = false
|
||||
};
|
||||
}
|
||||
|
||||
if (!_ZcashRpcProvider.IsAvailable(network.CryptoCode))
|
||||
throw new PaymentMethodUnavailableException($"Node or wallet not available");
|
||||
var invoice = paymentMethod.ParentEntity;
|
||||
if (!(preparePaymentObject is Prepare ZcashPrepare))
|
||||
throw new ArgumentException();
|
||||
var feeRatePerKb = await ZcashPrepare.GetFeeRate;
|
||||
var address = await ZcashPrepare.ReserveAddress(invoice.Id);
|
||||
|
||||
var feeRatePerByte = feeRatePerKb.Fee / 1024;
|
||||
return new ZcashLikeOnChainPaymentMethodDetails()
|
||||
{
|
||||
NextNetworkFee = ZcashMoney.Convert(feeRatePerByte * 100),
|
||||
AccountIndex = supportedPaymentMethod.AccountIndex,
|
||||
AddressIndex = address.AddressIndex,
|
||||
DepositAddress = address.Address,
|
||||
Activated = true
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public override object PreparePayment(ZcashSupportedPaymentMethod supportedPaymentMethod, StoreData store,
|
||||
BTCPayNetworkBase network)
|
||||
{
|
||||
|
||||
var walletClient = _ZcashRpcProvider.WalletRpcClients[supportedPaymentMethod.CryptoCode];
|
||||
var daemonClient = _ZcashRpcProvider.DaemonRpcClients[supportedPaymentMethod.CryptoCode];
|
||||
return 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 })
|
||||
};
|
||||
}
|
||||
|
||||
class Prepare
|
||||
{
|
||||
public Task<GetFeeEstimateResponse> GetFeeRate;
|
||||
public Func<string, Task<CreateAddressResponse>> ReserveAddress;
|
||||
}
|
||||
|
||||
public override void PreparePaymentModel(PaymentModel model, InvoiceResponse invoiceResponse,
|
||||
StoreBlob storeBlob, IPaymentMethod paymentMethod)
|
||||
{
|
||||
var paymentMethodId = paymentMethod.GetId();
|
||||
var network = _networkProvider.GetNetwork<ZcashLikeSpecificBtcPayNetwork>(model.CryptoCode);
|
||||
model.PaymentMethodName = GetPaymentMethodName(network);
|
||||
model.CryptoImage = GetCryptoImage(network);
|
||||
if (model.Activated)
|
||||
{
|
||||
var cryptoInfo = invoiceResponse.CryptoInfo.First(o => o.GetpaymentMethodId() == paymentMethodId);
|
||||
model.InvoiceBitcoinUrl = ZcashPaymentType.Instance.GetPaymentLink(network,
|
||||
new ZcashLikeOnChainPaymentMethodDetails() {DepositAddress = cryptoInfo.Address}, cryptoInfo.Due,
|
||||
null);
|
||||
model.InvoiceBitcoinUrlQR = model.InvoiceBitcoinUrl;
|
||||
}
|
||||
else
|
||||
{
|
||||
model.InvoiceBitcoinUrl = "";
|
||||
model.InvoiceBitcoinUrlQR = "";
|
||||
}
|
||||
}
|
||||
public override string GetCryptoImage(PaymentMethodId paymentMethodId)
|
||||
{
|
||||
var network = _networkProvider.GetNetwork<ZcashLikeSpecificBtcPayNetwork>(paymentMethodId.CryptoCode);
|
||||
return GetCryptoImage(network);
|
||||
}
|
||||
|
||||
public override string GetPaymentMethodName(PaymentMethodId paymentMethodId)
|
||||
{
|
||||
var network = _networkProvider.GetNetwork<ZcashLikeSpecificBtcPayNetwork>(paymentMethodId.CryptoCode);
|
||||
return GetPaymentMethodName(network);
|
||||
}
|
||||
public override IEnumerable<PaymentMethodId> GetSupportedPaymentMethods()
|
||||
{
|
||||
return _networkProvider.GetAll()
|
||||
.Where(network => network is ZcashLikeSpecificBtcPayNetwork)
|
||||
.Select(network => new PaymentMethodId(network.CryptoCode, PaymentType));
|
||||
}
|
||||
|
||||
private string GetCryptoImage(ZcashLikeSpecificBtcPayNetwork network)
|
||||
{
|
||||
return network.CryptoImagePath;
|
||||
}
|
||||
|
||||
|
||||
private string GetPaymentMethodName(ZcashLikeSpecificBtcPayNetwork network)
|
||||
{
|
||||
return $"{network.DisplayName}";
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,79 @@
|
||||
#if ALTCOINS
|
||||
using System.Globalization;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.Payments
|
||||
{
|
||||
public class ZcashPaymentType : PaymentType
|
||||
{
|
||||
public static ZcashPaymentType Instance { get; } = new ZcashPaymentType();
|
||||
public override string ToPrettyString() => "On-Chain";
|
||||
|
||||
public override string GetId() => "ZcashLike";
|
||||
public override string ToStringNormalized()
|
||||
{
|
||||
return "Zcash";
|
||||
}
|
||||
|
||||
public override CryptoPaymentData DeserializePaymentData(BTCPayNetworkBase network, string str)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<ZcashLikePaymentData>(str);
|
||||
}
|
||||
|
||||
public override string SerializePaymentData(BTCPayNetworkBase network, CryptoPaymentData paymentData)
|
||||
{
|
||||
return JsonConvert.SerializeObject(paymentData);
|
||||
}
|
||||
|
||||
public override IPaymentMethodDetails DeserializePaymentMethodDetails(BTCPayNetworkBase network, string str)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<ZcashLikeOnChainPaymentMethodDetails>(str);
|
||||
}
|
||||
|
||||
public override string SerializePaymentMethodDetails(BTCPayNetworkBase network, IPaymentMethodDetails details)
|
||||
{
|
||||
return JsonConvert.SerializeObject(details);
|
||||
}
|
||||
|
||||
public override ISupportedPaymentMethod DeserializeSupportedPaymentMethod(BTCPayNetworkBase network, JToken value)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<ZcashSupportedPaymentMethod>(value.ToString());
|
||||
}
|
||||
|
||||
public override string GetTransactionLink(BTCPayNetworkBase network, string txId)
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink, txId);
|
||||
}
|
||||
|
||||
public override string GetPaymentLink(BTCPayNetworkBase network, IPaymentMethodDetails paymentMethodDetails, Money cryptoInfoDue, string serverUri)
|
||||
{
|
||||
return paymentMethodDetails.Activated
|
||||
? $"{(network as ZcashLikeSpecificBtcPayNetwork).UriScheme}:{paymentMethodDetails.GetPaymentDestination()}?amount={cryptoInfoDue.ToDecimal(MoneyUnit.BTC)}"
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
public override string InvoiceViewPaymentPartialName { get; } = "Zcash/ViewZcashLikePaymentData";
|
||||
public override object GetGreenfieldData(ISupportedPaymentMethod supportedPaymentMethod, bool canModifyStore)
|
||||
{
|
||||
if (supportedPaymentMethod is ZcashSupportedPaymentMethod ZcashSupportedPaymentMethod)
|
||||
{
|
||||
return new
|
||||
{
|
||||
ZcashSupportedPaymentMethod.AccountIndex,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void PopulateCryptoInfo(PaymentMethod details, InvoiceCryptoInfo invoiceCryptoInfo, string serverUrl)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,14 @@
|
||||
#if ALTCOINS
|
||||
using BTCPayServer.Payments;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.Payments
|
||||
{
|
||||
public class ZcashSupportedPaymentMethod : ISupportedPaymentMethod
|
||||
{
|
||||
|
||||
public string CryptoCode { get; set; }
|
||||
public long AccountIndex { get; set; }
|
||||
public PaymentMethodId PaymentId => new PaymentMethodId(CryptoCode, ZcashPaymentType.Instance);
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,40 @@
|
||||
#if ALTCOINS
|
||||
using BTCPayServer.Filters;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.RPC
|
||||
{
|
||||
[Route("[controller]")]
|
||||
[OnlyIfSupportAttribute("ZEC")]
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
17
BTCPayServer/Services/Altcoins/Zcash/RPC/ZcashEvent.cs
Normal file
17
BTCPayServer/Services/Altcoins/Zcash/RPC/ZcashEvent.cs
Normal file
@ -0,0 +1,17 @@
|
||||
#if ALTCOINS
|
||||
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})";
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,73 @@
|
||||
#if ALTCOINS
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
378
BTCPayServer/Services/Altcoins/Zcash/Services/ZcashListener.cs
Normal file
378
BTCPayServer/Services/Altcoins/Zcash/Services/ZcashListener.cs
Normal file
@ -0,0 +1,378 @@
|
||||
#if ALTCOINS
|
||||
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.Events;
|
||||
using BTCPayServer.Payments;
|
||||
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.Invoices;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.Services
|
||||
{
|
||||
public class ZcashListener : IHostedService
|
||||
{
|
||||
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 CompositeDisposable leases = new CompositeDisposable();
|
||||
private readonly Channel<Func<Task>> _requests = Channel.CreateUnbounded<Func<Task>>();
|
||||
private CancellationTokenSource _Cts;
|
||||
|
||||
public ZcashListener(InvoiceRepository invoiceRepository,
|
||||
EventAggregator eventAggregator,
|
||||
ZcashRPCProvider ZcashRpcProvider,
|
||||
ZcashLikeConfiguration ZcashLikeConfiguration,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
ILogger<ZcashListener> logger,
|
||||
PaymentService paymentService)
|
||||
{
|
||||
_invoiceRepository = invoiceRepository;
|
||||
_eventAggregator = eventAggregator;
|
||||
_ZcashRpcProvider = ZcashRpcProvider;
|
||||
_ZcashLikeConfiguration = ZcashLikeConfiguration;
|
||||
_networkProvider = networkProvider;
|
||||
_logger = logger;
|
||||
_paymentService = paymentService;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_ZcashLikeConfiguration.ZcashLikeConfigurationItems.Any())
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
_Cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
|
||||
leases.Add(_eventAggregator.Subscribe<ZcashEvent>(OnZcashEvent));
|
||||
leases.Add(_eventAggregator.Subscribe<ZcashRPCProvider.ZcashDaemonStateChange>(e =>
|
||||
{
|
||||
if (_ZcashRpcProvider.IsAvailable(e.CryptoCode))
|
||||
{
|
||||
_logger.LogInformation($"{e.CryptoCode} just became available");
|
||||
_ = UpdateAnyPendingZcashLikePayment(e.CryptoCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation($"{e.CryptoCode} just became unavailable");
|
||||
}
|
||||
}));
|
||||
_ = WorkThroughQueue(_Cts.Token);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task WorkThroughQueue(CancellationToken token)
|
||||
{
|
||||
while (await _requests.Reader.WaitToReadAsync(token) && _requests.Reader.TryRead(out var action)) {
|
||||
token.ThrowIfCancellationRequested();
|
||||
try {
|
||||
await action.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError($"error with action item {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnZcashEvent(ZcashEvent obj)
|
||||
{
|
||||
if (!_ZcashRpcProvider.IsAvailable(obj.CryptoCode))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(obj.BlockHash))
|
||||
{
|
||||
if (!_requests.Writer.TryWrite(() => OnNewBlock(obj.CryptoCode))) {
|
||||
_logger.LogWarning($"Failed to write new block task to channel");
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(obj.TransactionHash))
|
||||
{
|
||||
if (!_requests.Writer.TryWrite(() => OnTransactionUpdated(obj.CryptoCode, obj.TransactionHash))) {
|
||||
_logger.LogWarning($"Failed to write new tx task to channel");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReceivedPayment(InvoiceEntity invoice, PaymentEntity payment)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
$"Invoice {invoice.Id} received payment {payment.GetCryptoPaymentData().GetValue()} {payment.GetCryptoCode()} {payment.GetCryptoPaymentData().GetPaymentId()}");
|
||||
var paymentData = (ZcashLikePaymentData)payment.GetCryptoPaymentData();
|
||||
var paymentMethod = invoice.GetPaymentMethod(payment.Network, ZcashPaymentType.Instance);
|
||||
if (paymentMethod != null &&
|
||||
paymentMethod.GetPaymentMethodDetails() is ZcashLikeOnChainPaymentMethodDetails Zcash &&
|
||||
Zcash.Activated &&
|
||||
Zcash.GetPaymentDestination() == paymentData.GetDestination() &&
|
||||
paymentMethod.Calculate().Due > Money.Zero)
|
||||
{
|
||||
var walletClient = _ZcashRpcProvider.WalletRpcClients[payment.GetCryptoCode()];
|
||||
|
||||
var address = await walletClient.SendCommandAsync<CreateAddressRequest, CreateAddressResponse>(
|
||||
"create_address",
|
||||
new CreateAddressRequest()
|
||||
{
|
||||
Label = $"btcpay invoice #{invoice.Id}",
|
||||
AccountIndex = Zcash.AccountIndex
|
||||
});
|
||||
Zcash.DepositAddress = address.Address;
|
||||
Zcash.AddressIndex = address.AddressIndex;
|
||||
await _invoiceRepository.NewPaymentDetails(invoice.Id, Zcash, payment.Network);
|
||||
_eventAggregator.Publish(
|
||||
new InvoiceNewPaymentDetailsEvent(invoice.Id, Zcash, payment.GetPaymentMethodId()));
|
||||
paymentMethod.SetPaymentMethodDetails(Zcash);
|
||||
invoice.SetPaymentMethod(paymentMethod);
|
||||
}
|
||||
|
||||
_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);
|
||||
|
||||
|
||||
//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),
|
||||
PaymentMethodDetails: entity.GetPaymentMethod(network, ZcashPaymentType.Instance)
|
||||
.GetPaymentMethodDetails() as ZcashLikeOnChainPaymentMethodDetails))
|
||||
.Select(tuple => (
|
||||
tuple.Invoice,
|
||||
tuple.PaymentMethodDetails,
|
||||
ExistingPayments: tuple.ExistingPayments.Select(entity =>
|
||||
(Payment: entity, PaymentData: (ZcashLikePaymentData)entity.GetCryptoPaymentData(),
|
||||
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 BlockingCollection<(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.PaymentData.Address == transfer.Address &&
|
||||
tuple.PaymentData.TransactionId == transfer.Txid);
|
||||
|
||||
if (existingMatch.Invoice != null)
|
||||
{
|
||||
invoice = existingMatch.Invoice;
|
||||
}
|
||||
else
|
||||
{
|
||||
var newMatch = expandedInvoices.SingleOrDefault(tuple =>
|
||||
tuple.PaymentMethodDetails.GetPaymentDestination() == 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
leases.Dispose();
|
||||
_Cts?.Cancel();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task OnNewBlock(string cryptoCode)
|
||||
{
|
||||
await UpdateAnyPendingZcashLikePayment(cryptoCode);
|
||||
_eventAggregator.Publish(new NewBlockEvent() { CryptoCode = cryptoCode });
|
||||
}
|
||||
|
||||
private async Task OnTransactionUpdated(string cryptoCode, string transactionHash)
|
||||
{
|
||||
var paymentMethodId = new PaymentMethodId(cryptoCode, ZcashPaymentType.Instance);
|
||||
var transfer = await _ZcashRpcProvider.WalletRpcClients[cryptoCode]
|
||||
.SendCommandAsync<GetTransferByTransactionIdRequest, GetTransferByTransactionIdResponse>(
|
||||
"get_transfer_by_txid",
|
||||
new GetTransferByTransactionIdRequest() { TransactionId = transactionHash });
|
||||
|
||||
var paymentsToUpdate = new BlockingCollection<(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 address = destination.Key + "#" + paymentMethodId;
|
||||
var invoice = (await _invoiceRepository.GetInvoicesFromAddresses(new[] { address })).FirstOrDefault();
|
||||
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,
|
||||
BlockingCollection<(PaymentEntity Payment, InvoiceEntity invoice)> paymentsToUpdate)
|
||||
{
|
||||
//construct the payment data
|
||||
var paymentData = new ZcashLikePaymentData()
|
||||
{
|
||||
Address = address,
|
||||
SubaccountIndex = subaccountIndex,
|
||||
SubaddressIndex = subaddressIndex,
|
||||
TransactionId = txId,
|
||||
ConfirmationCount = confirmations,
|
||||
Amount = totalAmount,
|
||||
BlockHeight = blockHeight,
|
||||
Network = _networkProvider.GetNetwork(cryptoCode)
|
||||
};
|
||||
|
||||
//check if this tx exists as a payment to this invoice already
|
||||
var alreadyExistingPaymentThatMatches = GetAllZcashLikePayments(invoice, cryptoCode)
|
||||
.Select(entity => (Payment: entity, PaymentData: entity.GetCryptoPaymentData()))
|
||||
.SingleOrDefault(c => c.PaymentData.GetPaymentId() == paymentData.GetPaymentId());
|
||||
|
||||
//if it doesnt, add it and assign a new Zcashlike address to the system if a balance is still due
|
||||
if (alreadyExistingPaymentThatMatches.Payment == null)
|
||||
{
|
||||
var payment = await _paymentService.AddPayment(invoice.Id, DateTimeOffset.UtcNow,
|
||||
paymentData, _networkProvider.GetNetwork<ZcashLikeSpecificBtcPayNetwork>(cryptoCode), true);
|
||||
if (payment != null)
|
||||
await ReceivedPayment(invoice, payment);
|
||||
}
|
||||
else
|
||||
{
|
||||
//else update it with the new data
|
||||
alreadyExistingPaymentThatMatches.PaymentData = paymentData;
|
||||
alreadyExistingPaymentThatMatches.Payment.SetCryptoPaymentData(paymentData);
|
||||
paymentsToUpdate.Add((alreadyExistingPaymentThatMatches.Payment, invoice));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateAnyPendingZcashLikePayment(string cryptoCode)
|
||||
{
|
||||
var invoiceIds = await _invoiceRepository.GetPendingInvoices();
|
||||
if (!invoiceIds.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var invoices = await _invoiceRepository.GetInvoices(new InvoiceQuery() { InvoiceId = invoiceIds });
|
||||
invoices = invoices.Where(entity => entity.GetPaymentMethod(new PaymentMethodId(cryptoCode, ZcashPaymentType.Instance))
|
||||
?.GetPaymentMethodDetails().Activated is true).ToArray();
|
||||
_logger.LogInformation($"Updating pending payments for {cryptoCode} in {string.Join(',', invoiceIds)}");
|
||||
await UpdatePaymentStates(cryptoCode, invoices);
|
||||
}
|
||||
|
||||
private IEnumerable<PaymentEntity> GetAllZcashLikePayments(InvoiceEntity invoice, string cryptoCode)
|
||||
{
|
||||
return invoice.GetPayments(false)
|
||||
.Where(p => p.GetPaymentMethodId() == new PaymentMethodId(cryptoCode, ZcashPaymentType.Instance));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,124 @@
|
||||
#if ALTCOINS
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Immutable;
|
||||
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("{0} {1} {2} {3} {4} {5}", Synced, CurrentHeight, TargetHeight, WalletHeight, DaemonAvailable, WalletAvailable); }
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,46 @@
|
||||
#if ALTCOINS
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
||||
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, CryptoCode = pair.Key
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class ZcashSyncStatus: SyncStatus, ISyncStatus
|
||||
{
|
||||
public override bool Available
|
||||
{
|
||||
get
|
||||
{
|
||||
return Summary?.WalletAvailable ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
public ZcashRPCProvider.ZcashLikeSummary Summary { get; set; }
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,309 @@
|
||||
#if ALTCOINS
|
||||
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.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Security;
|
||||
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.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.UI
|
||||
{
|
||||
[Route("stores/{storeId}/Zcashlike")]
|
||||
[OnlyIfSupportAttribute("ZEC")]
|
||||
[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 BTCPayNetworkProvider _BtcPayNetworkProvider;
|
||||
|
||||
public UIZcashLikeStoreController(ZcashLikeConfiguration ZcashLikeConfiguration,
|
||||
StoreRepository storeRepository, ZcashRPCProvider ZcashRpcProvider,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider)
|
||||
{
|
||||
_ZcashLikeConfiguration = ZcashLikeConfiguration;
|
||||
_StoreRepository = storeRepository;
|
||||
_ZcashRpcProvider = ZcashRpcProvider;
|
||||
_BtcPayNetworkProvider = btcPayNetworkProvider;
|
||||
}
|
||||
|
||||
public StoreData StoreData => HttpContext.GetStoreData();
|
||||
|
||||
[HttpGet()]
|
||||
public async Task<IActionResult> GetStoreZcashLikePaymentMethods()
|
||||
{
|
||||
var Zcash = StoreData.GetSupportedPaymentMethods(_BtcPayNetworkProvider)
|
||||
.OfType<ZcashSupportedPaymentMethod>();
|
||||
|
||||
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(Zcash, 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(
|
||||
IEnumerable<ZcashSupportedPaymentMethod> Zcash, string cryptoCode,
|
||||
IPaymentFilter excludeFilters, GetAccountsResponse accountsResponse)
|
||||
{
|
||||
var settings = Zcash.SingleOrDefault(method => method.CryptoCode == cryptoCode);
|
||||
_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(new PaymentMethodId(cryptoCode, ZcashPaymentType.Instance)),
|
||||
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.GetSupportedPaymentMethods(_BtcPayNetworkProvider)
|
||||
.OfType<ZcashSupportedPaymentMethod>(), cryptoCode,
|
||||
StoreData.GetStoreBlob().GetExcludedPaymentMethods(), await GetAccounts(cryptoCode));
|
||||
return View(nameof(GetStoreZcashLikePaymentMethod), vm);
|
||||
}
|
||||
|
||||
[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), "Could not create new account.");
|
||||
}
|
||||
|
||||
}
|
||||
else if (command == "upload-wallet")
|
||||
{
|
||||
var valid = true;
|
||||
if (viewModel.WalletFile == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.WalletFile), "Please select the wallet file");
|
||||
valid = false;
|
||||
}
|
||||
if (viewModel.WalletKeysFile == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.WalletKeysFile), "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 = $"There is already an active wallet configured for {cryptoCode}. Replacing it would break any existing invoices"
|
||||
});
|
||||
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
|
||||
.GetSupportedPaymentMethods(_BtcPayNetworkProvider)
|
||||
.OfType<ZcashSupportedPaymentMethod>(), 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();
|
||||
storeData.SetSupportedPaymentMethod(new ZcashSupportedPaymentMethod()
|
||||
{
|
||||
AccountIndex = viewModel.AccountIndex,
|
||||
CryptoCode = viewModel.CryptoCode
|
||||
});
|
||||
|
||||
blob.SetExcluded(new PaymentMethodId(viewModel.CryptoCode, ZcashPaymentType.Instance), !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}\""
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,17 @@
|
||||
#if ALTCOINS
|
||||
using System;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash.UI
|
||||
{
|
||||
public class ZcashPaymentViewModel
|
||||
{
|
||||
public string Crypto { 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; }
|
||||
}
|
||||
}
|
||||
#endif
|
73
BTCPayServer/Services/Altcoins/Zcash/ZcashLikeExtensions.cs
Normal file
73
BTCPayServer/Services/Altcoins/Zcash/ZcashLikeExtensions.cs
Normal file
@ -0,0 +1,73 @@
|
||||
#if ALTCOINS
|
||||
using System;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Services;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Configuration;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Payments;
|
||||
using BTCPayServer.Services.Altcoins.Zcash.Services;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BTCPayServer.Services.Altcoins.Zcash
|
||||
{
|
||||
public static class ZcashLikeExtensions
|
||||
{
|
||||
public static IServiceCollection AddZcashLike(this IServiceCollection serviceCollection)
|
||||
{
|
||||
serviceCollection.AddSingleton(provider =>
|
||||
provider.ConfigureZcashLikeConfiguration());
|
||||
serviceCollection.AddSingleton<ZcashRPCProvider>();
|
||||
serviceCollection.AddHostedService<ZcashLikeSummaryUpdaterHostedService>();
|
||||
serviceCollection.AddHostedService<ZcashListener>();
|
||||
serviceCollection.AddSingleton<ZcashLikePaymentMethodHandler>();
|
||||
serviceCollection.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<ZcashLikePaymentMethodHandler>());
|
||||
serviceCollection.AddSingleton<IUIExtension>(new UIExtension("Zcash/StoreNavZcashExtension", "store-nav"));
|
||||
serviceCollection.AddSingleton<ISyncSummaryProvider, ZcashSyncSummaryProvider>();
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
|
||||
private static ZcashLikeConfiguration ConfigureZcashLikeConfiguration(this IServiceProvider serviceProvider)
|
||||
{
|
||||
var configuration = serviceProvider.GetService<IConfiguration>();
|
||||
var btcPayNetworkProvider = serviceProvider.GetService<BTCPayNetworkProvider>();
|
||||
var result = new ZcashLikeConfiguration();
|
||||
|
||||
var supportedChains = configuration.GetOrDefault<string>("chains", string.Empty)
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(t => t.ToUpperInvariant());
|
||||
|
||||
var supportedNetworks = btcPayNetworkProvider.Filter(supportedChains.ToArray()).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)
|
||||
{
|
||||
throw new ConfigException($"{ZcashLikeSpecificBtcPayNetwork.CryptoCode} is misconfigured");
|
||||
}
|
||||
|
||||
result.ZcashLikeConfigurationItems.Add(ZcashLikeSpecificBtcPayNetwork.CryptoCode, new ZcashLikeConfigurationItem()
|
||||
{
|
||||
DaemonRpcUri = daemonUri,
|
||||
InternalWalletRpcUri = walletDaemonUri,
|
||||
WalletDirectory = walletDaemonWalletDirectory
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,12 @@
|
||||
@using BTCPayServer.Services.Altcoins.Zcash.Configuration
|
||||
@using BTCPayServer.Services.Altcoins.Zcash.UI
|
||||
@inject SignInManager<ApplicationUser> SignInManager;
|
||||
@inject ZcashLikeConfiguration ZcashLikeConfiguration;
|
||||
@{
|
||||
var controller = ViewContext.RouteData.Values["Controller"].ToString();
|
||||
var isZcash = controller.Equals(nameof(UIZcashLikeStoreController), StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
@if (SignInManager.IsSignedIn(User) && User.IsInRole(Roles.ServerAdmin) && ZcashLikeConfiguration.ZcashLikeConfigurationItems.Any())
|
||||
{
|
||||
<a class="nav-link @(isZcash ? "active" : string.Empty)" asp-route-storeId="@this.Context.GetRouteValue("storeId")" asp-action="GetStoreZcashLikePaymentMethods" asp-controller="UIZcashLikeStore">Zcash</a>
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
@using System.Globalization
|
||||
@using BTCPayServer.Services.Altcoins.Zcash.Payments
|
||||
@using BTCPayServer.Services.Altcoins.Zcash.UI
|
||||
@model IEnumerable<BTCPayServer.Services.Invoices.PaymentEntity>
|
||||
|
||||
@{
|
||||
var onchainPayments = Model.Where(entity => entity.GetPaymentMethodId().PaymentType == ZcashPaymentType.Instance).Select(payment =>
|
||||
{
|
||||
var m = new ZcashPaymentViewModel();
|
||||
var onChainPaymentData = payment.GetCryptoPaymentData() as ZcashLikePaymentData;
|
||||
m.Crypto = payment.GetPaymentMethodId().CryptoCode;
|
||||
m.DepositAddress = onChainPaymentData.GetDestination();
|
||||
m.Amount = onChainPaymentData.GetValue().ToString(CultureInfo.InvariantCulture);
|
||||
var confirmationCount = onChainPaymentData.ConfirmationCount;
|
||||
var network = payment.Network as ZcashLikeSpecificBtcPayNetwork;
|
||||
if (confirmationCount >= network.MaxTrackedConfirmation)
|
||||
{
|
||||
m.Confirmations = "At least " + (network.MaxTrackedConfirmation);
|
||||
}
|
||||
else
|
||||
{
|
||||
m.Confirmations = confirmationCount.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
m.TransactionId = onChainPaymentData.TransactionId;
|
||||
m.ReceivedTime = payment.ReceivedTime;
|
||||
m.TransactionLink = string.Format(CultureInfo.InvariantCulture, payment.Network.BlockExplorerLink, m.TransactionId);
|
||||
return m;
|
||||
});
|
||||
}
|
||||
|
||||
@if (onchainPayments.Any())
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-md-12 invoice-payments">
|
||||
<h3>Zcash payments</h3>
|
||||
<table class="table table-hover table-responsive-lg">
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th>Crypto</th>
|
||||
<th>Deposit address</th>
|
||||
<th>Amount</th>
|
||||
<th>Transaction Id</th>
|
||||
<th class="text-right">Confirmations</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var payment in onchainPayments)
|
||||
{
|
||||
<tr >
|
||||
<td>@payment.Crypto</td>
|
||||
<td>@payment.DepositAddress</td>
|
||||
<td>@payment.Amount</td>
|
||||
<td>
|
||||
<div class="wraptextAuto">
|
||||
<a href="@payment.TransactionLink" target="_blank" rel="noreferrer noopener">
|
||||
@payment.TransactionId
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right">@payment.Confirmations</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
20
BTCPayServer/Views/Shared/Zcash/ZcashSyncSummary.cshtml
Normal file
20
BTCPayServer/Views/Shared/Zcash/ZcashSyncSummary.cshtml
Normal file
@ -0,0 +1,20 @@
|
||||
@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)
|
||||
{
|
||||
<h4>@summary.Key</h4>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
@using BTCPayServer.Views.Stores
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@model BTCPayServer.Services.Altcoins.Zcash.UI.UIZcashLikeStoreController.ZcashLikePaymentMethodViewModel
|
||||
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData["NavPartialName"] = "../UIStores/_Nav";
|
||||
ViewData.SetActivePage(StoreNavPages.OnchainSettings, $"{Model.CryptoCode} Settings");
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<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">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="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>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="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">
|
||||
<button type="submit" class="btn btn-primary" id="SaveButton">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">
|
||||
Back to list
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section PageFootContent {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
@using BTCPayServer.Views.Stores
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@model BTCPayServer.Services.Altcoins.Zcash.UI.UIZcashLikeStoreController.ZcashLikePaymentMethodListViewModel
|
||||
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData.SetActivePage(StoreNavPages.OnchainSettings, "Zcash Settings");
|
||||
ViewData["NavPartialName"] = "../UIStores/_Nav";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<div class="form-group">
|
||||
<table class="table table-hover table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Crypto</th>
|
||||
<th>Account Index</th>
|
||||
<th class="text-center">Enabled</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Model.Items)
|
||||
{
|
||||
<tr>
|
||||
<td>@item.CryptoCode</td>
|
||||
<td>@item.AccountIndex</td>
|
||||
<td class="text-center">
|
||||
@if (item.Enabled)
|
||||
{
|
||||
<span class="text-success fa fa-check"></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-danger fa fa-times"></span>
|
||||
}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<a id="Modify" asp-action="GetStoreZcashLikePaymentMethod"
|
||||
asp-route-storeId="@Context.GetRouteValue("storeId")"
|
||||
asp-route-cryptoCode="@item.CryptoCode">
|
||||
Modify
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section PageFootContent {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
}
|
BIN
BTCPayServer/wwwroot/imlegacy/ycash.png
Normal file
BIN
BTCPayServer/wwwroot/imlegacy/ycash.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
BIN
BTCPayServer/wwwroot/imlegacy/zcash.png
Normal file
BIN
BTCPayServer/wwwroot/imlegacy/zcash.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 121 KiB |
Loading…
Reference in New Issue
Block a user