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();
|
InitGroestlcoin();
|
||||||
InitViacoin();
|
InitViacoin();
|
||||||
InitMonero();
|
InitMonero();
|
||||||
|
InitZcash();
|
||||||
InitPolis();
|
InitPolis();
|
||||||
InitChaincoin();
|
InitChaincoin();
|
||||||
// InitArgoneum();//their rate source is down 9/15/20.
|
// InitArgoneum();//their rate source is down 9/15/20.
|
||||||
|
@ -1259,6 +1259,20 @@
|
|||||||
"symbol":null,
|
"symbol":null,
|
||||||
"crypto":true
|
"crypto":true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name":"YEC",
|
||||||
|
"code":"YEC",
|
||||||
|
"divisibility":8,
|
||||||
|
"symbol":null,
|
||||||
|
"crypto":true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"ZEC",
|
||||||
|
"code":"ZEC",
|
||||||
|
"divisibility":8,
|
||||||
|
"symbol":null,
|
||||||
|
"crypto":true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name":"POLIS",
|
"name":"POLIS",
|
||||||
"code":"POLIS",
|
"code":"POLIS",
|
||||||
|
@ -385,6 +385,7 @@ services:
|
|||||||
- "19444:19444"
|
- "19444:19444"
|
||||||
volumes:
|
volumes:
|
||||||
- "elementsd_liquid_datadir:/data"
|
- "elementsd_liquid_datadir:/data"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
sshd_datadir:
|
sshd_datadir:
|
||||||
bitcoin_datadir:
|
bitcoin_datadir:
|
||||||
|
@ -40,7 +40,9 @@
|
|||||||
<ItemGroup Condition="'$(Altcoins)' != 'true'">
|
<ItemGroup Condition="'$(Altcoins)' != 'true'">
|
||||||
<Content Remove="Services\Altcoins\**\*" />
|
<Content Remove="Services\Altcoins\**\*" />
|
||||||
<Content Remove="Views\UIMoneroLikeStore\**\*" />
|
<Content Remove="Views\UIMoneroLikeStore\**\*" />
|
||||||
|
<Content Remove="Views\UIZcashLikeStore\**\*" />
|
||||||
<Content Remove="Views\Shared\Monero\**\*" />
|
<Content Remove="Views\Shared\Monero\**\*" />
|
||||||
|
<Content Remove="Views\Shared\Zcash\**\*" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -59,6 +59,7 @@ using NicolasDorier.RateLimits;
|
|||||||
using Serilog;
|
using Serilog;
|
||||||
#if ALTCOINS
|
#if ALTCOINS
|
||||||
using BTCPayServer.Services.Altcoins.Monero;
|
using BTCPayServer.Services.Altcoins.Monero;
|
||||||
|
using BTCPayServer.Services.Altcoins.Zcash;
|
||||||
#endif
|
#endif
|
||||||
namespace BTCPayServer.Hosting
|
namespace BTCPayServer.Hosting
|
||||||
{
|
{
|
||||||
@ -90,6 +91,8 @@ namespace BTCPayServer.Hosting
|
|||||||
#if ALTCOINS
|
#if ALTCOINS
|
||||||
if (configuration.SupportChain("xmr"))
|
if (configuration.SupportChain("xmr"))
|
||||||
services.AddMoneroLike();
|
services.AddMoneroLike();
|
||||||
|
if (configuration.SupportChain("yec") || configuration.SupportChain("zec"))
|
||||||
|
services.AddZcashLike();
|
||||||
#endif
|
#endif
|
||||||
services.TryAddSingleton<SettingsRepository>();
|
services.TryAddSingleton<SettingsRepository>();
|
||||||
services.TryAddSingleton<ISettingsRepository>(provider => provider.GetService<SettingsRepository>());
|
services.TryAddSingleton<ISettingsRepository>(provider => provider.GetService<SettingsRepository>());
|
||||||
|
@ -80,6 +80,8 @@ namespace BTCPayServer.Payments
|
|||||||
#if ALTCOINS
|
#if ALTCOINS
|
||||||
if (CryptoCode == "XMR" && PaymentType == PaymentTypes.MoneroLike)
|
if (CryptoCode == "XMR" && PaymentType == PaymentTypes.MoneroLike)
|
||||||
return CryptoCode;
|
return CryptoCode;
|
||||||
|
if ((CryptoCode == "YEC" || CryptoCode == "ZEC") && PaymentType == PaymentTypes.ZcashLike)
|
||||||
|
return CryptoCode;
|
||||||
#endif
|
#endif
|
||||||
return $"{CryptoCode}-{PaymentType.ToStringNormalized()}";
|
return $"{CryptoCode}-{PaymentType.ToStringNormalized()}";
|
||||||
}
|
}
|
||||||
@ -105,6 +107,8 @@ namespace BTCPayServer.Payments
|
|||||||
#if ALTCOINS
|
#if ALTCOINS
|
||||||
if (parts[0].ToUpperInvariant() == "XMR")
|
if (parts[0].ToUpperInvariant() == "XMR")
|
||||||
type = PaymentTypes.MoneroLike;
|
type = PaymentTypes.MoneroLike;
|
||||||
|
if (parts[0].ToUpperInvariant() == "ZEC")
|
||||||
|
type = PaymentTypes.ZcashLike;
|
||||||
#endif
|
#endif
|
||||||
if (parts.Length == 2)
|
if (parts.Length == 2)
|
||||||
{
|
{
|
||||||
|
@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
#if ALTCOINS
|
#if ALTCOINS
|
||||||
using BTCPayServer.Services.Altcoins.Monero.Payments;
|
using BTCPayServer.Services.Altcoins.Monero.Payments;
|
||||||
|
using BTCPayServer.Services.Altcoins.Zcash.Payments;
|
||||||
#endif
|
#endif
|
||||||
using BTCPayServer.Services.Invoices;
|
using BTCPayServer.Services.Invoices;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
@ -18,7 +19,7 @@ namespace BTCPayServer.Payments
|
|||||||
{
|
{
|
||||||
BTCLike, LightningLike, LNURLPay,
|
BTCLike, LightningLike, LNURLPay,
|
||||||
#if ALTCOINS
|
#if ALTCOINS
|
||||||
MoneroLike,
|
MoneroLike, ZcashLike,
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -39,6 +40,10 @@ namespace BTCPayServer.Payments
|
|||||||
/// Monero payment
|
/// Monero payment
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static MoneroPaymentType MoneroLike => MoneroPaymentType.Instance;
|
public static MoneroPaymentType MoneroLike => MoneroPaymentType.Instance;
|
||||||
|
/// <summary>
|
||||||
|
/// Zcash payment
|
||||||
|
/// </summary>
|
||||||
|
public static ZcashPaymentType ZcashLike => ZcashPaymentType.Instance;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public static bool TryParse(string paymentType, out PaymentType type)
|
public static bool TryParse(string paymentType, out PaymentType type)
|
||||||
|
@ -98,7 +98,7 @@
|
|||||||
"BTCPAY_RECOMMENDED-PLUGINS": "BTCPayServer.Plugins.Test",
|
"BTCPAY_RECOMMENDED-PLUGINS": "BTCPayServer.Plugins.Test",
|
||||||
"BTCPAY_CHEATMODE": "true"
|
"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