mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-22 06:21:44 +01:00
GreenField: Generate Store OnChain Wallet (#2708)
* GreenField: Generate Store OnChain Wallet * Greenfield: Do not generate wallet if already configured
This commit is contained in:
parent
80483ba76f
commit
c59798e9c4
12 changed files with 505 additions and 12 deletions
|
@ -78,5 +78,17 @@ namespace BTCPayServer.Client
|
|||
method: HttpMethod.Get), token);
|
||||
return await HandleResponse<OnChainPaymentMethodPreviewResultData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<OnChainPaymentMethodDataWithSensitiveData> GenerateOnChainWallet(string storeId,
|
||||
string cryptoCode, GenerateOnChainWalletRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/generate",
|
||||
bodyPayload: request,
|
||||
method: HttpMethod.Post), token);
|
||||
return await HandleResponse<OnChainPaymentMethodDataWithSensitiveData>(response);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
31
BTCPayServer.Client/JsonConverters/MnemonicJsonConverter.cs
Normal file
31
BTCPayServer.Client/JsonConverters/MnemonicJsonConverter.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using NBitcoin;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.JsonConverters
|
||||
{
|
||||
public class MnemonicJsonConverter : JsonConverter<Mnemonic>
|
||||
{
|
||||
public override Mnemonic ReadJson(JsonReader reader, Type objectType, Mnemonic existingValue, bool hasExistingValue,
|
||||
JsonSerializer serializer)
|
||||
{
|
||||
return reader.TokenType switch
|
||||
{
|
||||
JsonToken.String => new Mnemonic((string)reader.Value),
|
||||
JsonToken.Null => null,
|
||||
_ => throw new JsonObjectException(reader.Path, "Mnemonic must be a json string")
|
||||
};
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, Mnemonic value, JsonSerializer serializer)
|
||||
{
|
||||
if (value != null)
|
||||
writer.WriteValue(value.ToString());
|
||||
else
|
||||
{
|
||||
writer.WriteNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
59
BTCPayServer.Client/JsonConverters/WordcountJsonConverter.cs
Normal file
59
BTCPayServer.Client/JsonConverters/WordcountJsonConverter.cs
Normal file
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
public class WordcountJsonConverter : JsonConverter
|
||||
{
|
||||
static WordcountJsonConverter()
|
||||
{
|
||||
_Wordcount = new Dictionary<long, WordCount>()
|
||||
{
|
||||
{18, WordCount.Eighteen},
|
||||
{15, WordCount.Fifteen},
|
||||
{12, WordCount.Twelve},
|
||||
{24, WordCount.TwentyFour},
|
||||
{21, WordCount.TwentyOne}
|
||||
};
|
||||
_WordcountReverse = _Wordcount.ToDictionary(kv => kv.Value, kv => kv.Key);
|
||||
}
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return typeof(NBitcoin.WordCount).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo()) ||
|
||||
typeof(NBitcoin.WordCount?).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
return default;
|
||||
if (reader.TokenType != JsonToken.Integer)
|
||||
throw new NBitcoin.JsonConverters.JsonObjectException(
|
||||
$"Unexpected json token type, expected Integer, actual {reader.TokenType}", reader);
|
||||
if (!_Wordcount.TryGetValue((long)reader.Value, out var result))
|
||||
throw new NBitcoin.JsonConverters.JsonObjectException(
|
||||
$"Invalid WordCount, possible values {string.Join(", ", _Wordcount.Keys.ToArray())} (default: 12)",
|
||||
reader);
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
if (value is WordCount wc)
|
||||
writer.WriteValue(_WordcountReverse[wc]);
|
||||
}
|
||||
|
||||
readonly static Dictionary<long, WordCount> _Wordcount = new Dictionary<long, WordCount>()
|
||||
{
|
||||
{18, WordCount.Eighteen},
|
||||
{15, WordCount.Fifteen},
|
||||
{12, WordCount.Twelve},
|
||||
{24, WordCount.TwentyFour},
|
||||
{21, WordCount.TwentyOne}
|
||||
};
|
||||
|
||||
readonly static Dictionary<WordCount, long> _WordcountReverse;
|
||||
}
|
55
BTCPayServer.Client/JsonConverters/WordlistJsonConverter.cs
Normal file
55
BTCPayServer.Client/JsonConverters/WordlistJsonConverter.cs
Normal file
|
@ -0,0 +1,55 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
public class WordlistJsonConverter : JsonConverter
|
||||
{
|
||||
static WordlistJsonConverter()
|
||||
{
|
||||
|
||||
_Wordlists = new Dictionary<string, Wordlist>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{"English", Wordlist.English},
|
||||
{"Japanese", Wordlist.Japanese},
|
||||
{"Spanish", Wordlist.Spanish},
|
||||
{"ChineseSimplified", Wordlist.ChineseSimplified},
|
||||
{"ChineseTraditional", Wordlist.ChineseTraditional},
|
||||
{"French", Wordlist.French},
|
||||
{"PortugueseBrazil", Wordlist.PortugueseBrazil},
|
||||
{"Czech", Wordlist.Czech}
|
||||
};
|
||||
|
||||
_WordlistsReverse = _Wordlists.ToDictionary(kv => kv.Value, kv => kv.Key);
|
||||
}
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return typeof(Wordlist).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
return null;
|
||||
if (reader.TokenType != JsonToken.String)
|
||||
throw new NBitcoin.JsonConverters.JsonObjectException(
|
||||
$"Unexpected json token type, expected String, actual {reader.TokenType}", reader);
|
||||
if (!_Wordlists.TryGetValue((string)reader.Value, out var result))
|
||||
throw new NBitcoin.JsonConverters.JsonObjectException(
|
||||
$"Invalid wordlist, possible values {string.Join(", ", _Wordlists.Keys.ToArray())} (default: English)",
|
||||
reader);
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
if (value is Wordlist wl)
|
||||
writer.WriteValue(_WordlistsReverse[wl]);
|
||||
}
|
||||
|
||||
readonly static Dictionary<string, Wordlist> _Wordlists;
|
||||
readonly static Dictionary<Wordlist, string> _WordlistsReverse;
|
||||
}
|
25
BTCPayServer.Client/Models/GenerateOnChainWalletRequest.cs
Normal file
25
BTCPayServer.Client/Models/GenerateOnChainWalletRequest.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using BTCPayServer.Client.JsonConverters;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public class GenerateOnChainWalletRequest
|
||||
{
|
||||
public int AccountNumber { get; set; } = 0;
|
||||
[JsonConverter(typeof(MnemonicJsonConverter))]
|
||||
public Mnemonic ExistingMnemonic { get; set; }
|
||||
[JsonConverter(typeof(WordlistJsonConverter))]
|
||||
public NBitcoin.Wordlist WordList { get; set; }
|
||||
|
||||
[JsonConverter(typeof(WordcountJsonConverter))]
|
||||
public NBitcoin.WordCount? WordCount { get; set; } = NBitcoin.WordCount.Twelve;
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public NBitcoin.ScriptPubKeyType ScriptPubKeyType { get; set; } = ScriptPubKeyType.Segwit;
|
||||
public string Passphrase { get; set; }
|
||||
public bool ImportKeysToRPC { get; set; }
|
||||
public bool SavePrivateKeys { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class OnChainPaymentMethodData : OnChainPaymentMethodBaseData
|
||||
|
@ -17,9 +19,11 @@ namespace BTCPayServer.Client.Models
|
|||
|
||||
}
|
||||
|
||||
public OnChainPaymentMethodData(string cryptoCode, string derivationScheme, bool enabled)
|
||||
public OnChainPaymentMethodData(string cryptoCode, string derivationScheme, bool enabled, string label, RootedKeyPath accountKeyPath)
|
||||
{
|
||||
Enabled = enabled;
|
||||
Label = label;
|
||||
AccountKeyPath = accountKeyPath;
|
||||
CryptoCode = cryptoCode;
|
||||
DerivationScheme = derivationScheme;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
using BTCPayServer.Client.JsonConverters;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class OnChainPaymentMethodDataWithSensitiveData : OnChainPaymentMethodData
|
||||
{
|
||||
public OnChainPaymentMethodDataWithSensitiveData()
|
||||
{
|
||||
}
|
||||
|
||||
public OnChainPaymentMethodDataWithSensitiveData(string cryptoCode, string derivationScheme, bool enabled,
|
||||
string label, RootedKeyPath accountKeyPath, Mnemonic mnemonic) : base(cryptoCode, derivationScheme, enabled,
|
||||
label, accountKeyPath)
|
||||
{
|
||||
Mnemonic = mnemonic;
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(MnemonicJsonConverter))]
|
||||
public Mnemonic Mnemonic { get; set; }
|
||||
}
|
||||
}
|
|
@ -1427,8 +1427,12 @@ namespace BTCPayServer.Tests
|
|||
using var tester = ServerTester.Create();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
var user2 = tester.NewAccount();
|
||||
await user.GrantAccessAsync(true);
|
||||
await user2.GrantAccessAsync(false);
|
||||
|
||||
var client = await user.CreateClient(Policies.CanModifyStoreSettings);
|
||||
var client2 = await user2.CreateClient(Policies.CanModifyStoreSettings);
|
||||
var viewOnlyClient = await user.CreateClient(Policies.CanViewStoreSettings);
|
||||
|
||||
var store = await client.CreateStore(new CreateStoreRequest() { Name = "test store" });
|
||||
|
@ -1438,8 +1442,9 @@ namespace BTCPayServer.Tests
|
|||
{
|
||||
await viewOnlyClient.UpdateStoreOnChainPaymentMethod(store.Id, "BTC", new OnChainPaymentMethodData() { });
|
||||
});
|
||||
|
||||
var xpriv = new Mnemonic("all all all all all all all all all all all all").DeriveExtKey()
|
||||
.Derive(KeyPath.Parse("m/84'/0'/0'"));
|
||||
.Derive(KeyPath.Parse("m/84'/1'/0'"));
|
||||
var xpub = xpriv.Neuter().ToString(Network.RegTest);
|
||||
var firstAddress = xpriv.Derive(KeyPath.Parse("0/0")).Neuter().GetPublicKey().GetAddress(ScriptPubKeyType.Segwit, Network.RegTest).ToString();
|
||||
await AssertHttpError(404, async () =>
|
||||
|
@ -1475,6 +1480,60 @@ namespace BTCPayServer.Tests
|
|||
{
|
||||
await client.GetStoreOnChainPaymentMethod(store.Id, "BTC");
|
||||
});
|
||||
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnlyClient.GenerateOnChainWallet(store.Id, "BTC", new GenerateOnChainWalletRequest() { });
|
||||
});
|
||||
|
||||
await AssertValidationError(new []{"SavePrivateKeys", "ImportKeysToRPC"}, async () =>
|
||||
{
|
||||
await client2.GenerateOnChainWallet(user2.StoreId, "BTC", new GenerateOnChainWalletRequest()
|
||||
{
|
||||
SavePrivateKeys = true,
|
||||
ImportKeysToRPC = true
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
var allMnemonic = new Mnemonic("all all all all all all all all all all all all");
|
||||
|
||||
|
||||
await client.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
|
||||
var generateResponse = await client.GenerateOnChainWallet(store.Id, "BTC",
|
||||
new GenerateOnChainWalletRequest() {ExistingMnemonic = allMnemonic,});
|
||||
|
||||
Assert.Equal(generateResponse.Mnemonic.ToString(), allMnemonic.ToString());
|
||||
Assert.Equal(generateResponse.DerivationScheme, xpub);
|
||||
|
||||
await AssertAPIError("already-configured", async () =>
|
||||
{
|
||||
await client.GenerateOnChainWallet(store.Id, "BTC",
|
||||
new GenerateOnChainWalletRequest() {ExistingMnemonic = allMnemonic,});
|
||||
});
|
||||
|
||||
await client.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
|
||||
generateResponse = await client.GenerateOnChainWallet(store.Id, "BTC",
|
||||
new GenerateOnChainWalletRequest() {});
|
||||
Assert.NotEqual(generateResponse.Mnemonic.ToString(), allMnemonic.ToString());
|
||||
Assert.Equal(generateResponse.Mnemonic.DeriveExtKey().Derive(KeyPath.Parse("m/84'/1'/0'")).Neuter().ToString(Network.RegTest), generateResponse.DerivationScheme);
|
||||
|
||||
await client.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
|
||||
generateResponse = await client.GenerateOnChainWallet(store.Id, "BTC",
|
||||
new GenerateOnChainWalletRequest() { ExistingMnemonic = allMnemonic, AccountNumber = 1});
|
||||
|
||||
Assert.Equal(generateResponse.Mnemonic.ToString(), allMnemonic.ToString());
|
||||
|
||||
Assert.Equal(new Mnemonic("all all all all all all all all all all all all").DeriveExtKey()
|
||||
.Derive(KeyPath.Parse("m/84'/1'/1'")).Neuter().ToString(Network.RegTest), generateResponse.DerivationScheme);
|
||||
|
||||
await client.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
|
||||
generateResponse = await client.GenerateOnChainWallet(store.Id, "BTC",
|
||||
new GenerateOnChainWalletRequest() { WordList = Wordlist.Japanese, WordCount = WordCount.TwentyFour});
|
||||
|
||||
Assert.Equal(24,generateResponse.Mnemonic.Words.Length);
|
||||
Assert.Equal(Wordlist.Japanese,generateResponse.Mnemonic.WordList);
|
||||
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
|
@ -1883,7 +1942,7 @@ namespace BTCPayServer.Tests
|
|||
|
||||
var randK = new Mnemonic(Wordlist.English, WordCount.Twelve).DeriveExtKey().Neuter().ToString(Network.RegTest);
|
||||
await adminClient.UpdateStoreOnChainPaymentMethod(admin.StoreId, "BTC",
|
||||
new OnChainPaymentMethodData("BTC", randK, true));
|
||||
new OnChainPaymentMethodData("BTC", randK, true, "testing", null));
|
||||
|
||||
void VerifyOnChain(Dictionary<string, GenericPaymentMethodData> dictionary)
|
||||
{
|
||||
|
|
|
@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Identity;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBXplorer.Models;
|
||||
using YamlDotNet.Core.Tokens;
|
||||
using InvoiceData = BTCPayServer.Client.Models.InvoiceData;
|
||||
using Language = BTCPayServer.Client.Models.Language;
|
||||
|
@ -883,5 +884,21 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
{
|
||||
return Task.FromResult(GetFromActionResult(_storePaymentMethodsController.GetStorePaymentMethods(storeId, enabled)));
|
||||
}
|
||||
|
||||
public override async Task<OnChainPaymentMethodDataWithSensitiveData> GenerateOnChainWallet(string storeId, string cryptoCode, GenerateOnChainWalletRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<OnChainPaymentMethodDataWithSensitiveData>(await _chainPaymentMethodsController.GenerateOnChainWallet(storeId, cryptoCode, new GenerateWalletRequest()
|
||||
{
|
||||
Passphrase = request.Passphrase,
|
||||
AccountNumber = request.AccountNumber,
|
||||
ExistingMnemonic = request.ExistingMnemonic?.ToString(),
|
||||
WordCount = request.WordCount,
|
||||
WordList = request.WordList,
|
||||
SavePrivateKeys = request.SavePrivateKeys,
|
||||
ScriptPubKeyType = request.ScriptPubKeyType,
|
||||
ImportKeysToRPC = request.ImportKeysToRPC
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Payments;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBXplorer.Models;
|
||||
|
||||
namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
public partial class StoreOnChainPaymentMethodsController
|
||||
{
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPost("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/generate")]
|
||||
public async Task<IActionResult> GenerateOnChainWallet(string storeId, string cryptoCode,
|
||||
GenerateWalletRequest request)
|
||||
{
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
if (network is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (!_walletProvider.IsAvailable(network))
|
||||
{
|
||||
return this.CreateAPIError("not-available",
|
||||
$"{cryptoCode} services are not currently available");
|
||||
}
|
||||
|
||||
var method = GetExistingBtcLikePaymentMethod(cryptoCode);
|
||||
if (method != null)
|
||||
{
|
||||
return this.CreateAPIError("already-configured",
|
||||
$"{cryptoCode} wallet is already configured for this store");
|
||||
}
|
||||
|
||||
var canUseHotWallet = await CanUseHotWallet();
|
||||
if (request.SavePrivateKeys && !canUseHotWallet.HotWallet)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.SavePrivateKeys),
|
||||
"This instance forbids non-admins from having a hot wallet for your store.");
|
||||
}
|
||||
|
||||
if (request.ImportKeysToRPC && !canUseHotWallet.RPCImport)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.ImportKeysToRPC),
|
||||
"This instance forbids non-admins from having importing the wallet addresses/keys to the underlying node.");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
var client = _explorerClientProvider.GetExplorerClient(network);
|
||||
GenerateWalletResponse response;
|
||||
try
|
||||
{
|
||||
response = await client.GenerateWalletAsync(request);
|
||||
if (response == null)
|
||||
{
|
||||
return this.CreateAPIError("not-available",
|
||||
$"{cryptoCode} services are not currently available");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return this.CreateAPIError("not-available",
|
||||
$"{cryptoCode} error: {e.Message}");
|
||||
}
|
||||
|
||||
var derivationSchemeSettings = new DerivationSchemeSettings(response.DerivationScheme, network);
|
||||
|
||||
derivationSchemeSettings.Source =
|
||||
string.IsNullOrEmpty(request.ExistingMnemonic) ? "NBXplorerGenerated" : "ImportedSeed";
|
||||
derivationSchemeSettings.IsHotWallet = request.SavePrivateKeys;
|
||||
|
||||
var accountSettings = derivationSchemeSettings.GetSigningAccountKeySettings();
|
||||
accountSettings.AccountKeyPath = response.AccountKeyPath.KeyPath;
|
||||
accountSettings.RootFingerprint = response.AccountKeyPath.MasterFingerprint;
|
||||
derivationSchemeSettings.AccountOriginal = response.DerivationScheme.ToString();
|
||||
|
||||
var store = Store;
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
store.SetSupportedPaymentMethod(new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike),
|
||||
derivationSchemeSettings);
|
||||
store.SetStoreBlob(storeBlob);
|
||||
await _storeRepository.UpdateStore(store);
|
||||
var rawResult = GetExistingBtcLikePaymentMethod(cryptoCode, store);
|
||||
var result = new OnChainPaymentMethodDataWithSensitiveData(rawResult.CryptoCode, rawResult.DerivationScheme,
|
||||
rawResult.Enabled, rawResult.Label, rawResult.AccountKeyPath, response.GetMnemonic());
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
private async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet()
|
||||
{
|
||||
return await _authorizationService.CanUseHotWallet(await _settingsRepository.GetPolicies(), User);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,9 +2,11 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
|
@ -12,27 +14,36 @@ using Microsoft.AspNetCore.Authorization;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public class StoreOnChainPaymentMethodsController : ControllerBase
|
||||
public partial class StoreOnChainPaymentMethodsController : ControllerBase
|
||||
{
|
||||
private StoreData Store => HttpContext.GetStoreData();
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly BTCPayWalletProvider _walletProvider;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly ISettingsRepository _settingsRepository;
|
||||
private readonly ExplorerClientProvider _explorerClientProvider;
|
||||
|
||||
public StoreOnChainPaymentMethodsController(
|
||||
StoreRepository storeRepository,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
BTCPayWalletProvider walletProvider)
|
||||
BTCPayWalletProvider walletProvider,
|
||||
IAuthorizationService authorizationService,
|
||||
ExplorerClientProvider explorerClientProvider, ISettingsRepository settingsRepository)
|
||||
{
|
||||
_storeRepository = storeRepository;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_walletProvider = walletProvider;
|
||||
_authorizationService = authorizationService;
|
||||
_explorerClientProvider = explorerClientProvider;
|
||||
_settingsRepository = settingsRepository;
|
||||
}
|
||||
|
||||
public static IEnumerable<OnChainPaymentMethodData> GetOnChainPaymentMethods(StoreData store,
|
||||
|
@ -46,7 +57,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
.OfType<DerivationSchemeSettings>()
|
||||
.Select(strategy =>
|
||||
new OnChainPaymentMethodData(strategy.PaymentId.CryptoCode,
|
||||
strategy.AccountDerivation.ToString(), !excludedPaymentMethods.Match(strategy.PaymentId)))
|
||||
strategy.AccountDerivation.ToString(), !excludedPaymentMethods.Match(strategy.PaymentId), strategy.Label, strategy.GetSigningAccountKeySettings().GetRootedKeyPath()))
|
||||
.Where((result) => enabled is null || enabled == result.Enabled)
|
||||
.ToList();
|
||||
}
|
||||
|
@ -279,11 +290,8 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
return paymentMethod == null
|
||||
? null
|
||||
: new OnChainPaymentMethodData(paymentMethod.PaymentId.CryptoCode,
|
||||
paymentMethod.AccountDerivation.ToString(), !excluded)
|
||||
{
|
||||
Label = paymentMethod.Label,
|
||||
AccountKeyPath = paymentMethod.GetSigningAccountKeySettings().GetRootedKeyPath()
|
||||
};
|
||||
paymentMethod.AccountDerivation.ToString(), !excluded, paymentMethod.Label,
|
||||
paymentMethod.GetSigningAccountKeySettings().GetRootedKeyPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -405,6 +405,23 @@
|
|||
"$ref": "#/components/schemas/OnChainPaymentMethodData"
|
||||
}
|
||||
},
|
||||
"OnChainPaymentMethodDataWithSensitiveData": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/OnChainPaymentMethodData"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"mnemonic": {
|
||||
"type": "string",
|
||||
"description": "The mnemonic used to generate the wallet",
|
||||
"nullable": false
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"OnChainPaymentMethodBaseData": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
|
@ -467,6 +484,86 @@
|
|||
"description": "The address generated at the key path"
|
||||
}
|
||||
}
|
||||
},
|
||||
"GenerateOnChainWalletRequest": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"existingMnemonic": {
|
||||
"type": "string",
|
||||
"description": "An existing BIP39 mnemonic seed to generate the wallet with"
|
||||
},
|
||||
"passphrase": {
|
||||
"type": "string",
|
||||
"description": "A passphrase for the BIP39 mnemonic seed"
|
||||
},
|
||||
"accountNumber": {
|
||||
"type": "number",
|
||||
"default": 0,
|
||||
"description": "The account to derive from the BIP39 mnemonic seed"
|
||||
},
|
||||
"savePrivateKeys": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether to store the seed inside BTCPay Server to enable some additional services. IF `false` AND `existingMnemonic` IS NOT SPECIFIED, BE SURE TO SECURELY STORE THE SEED IN THE RESPONSE!"
|
||||
},
|
||||
"importKeysToRPC": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether to import all addresses generated via BTCPay Server into the underlying node wallet. (Private keys will also be imported if `savePrivateKeys` is set to true."
|
||||
},
|
||||
"wordList": {
|
||||
"type": "string",
|
||||
"description": "If `existingMnemonic` is not set, a mnemonic is generated using the specified wordList.",
|
||||
"default": "English",
|
||||
"x-enumNames": [
|
||||
"English",
|
||||
"Japanese",
|
||||
"Spanish",
|
||||
"ChineseSimplified",
|
||||
"ChineseTraditional",
|
||||
"French",
|
||||
"PortugueseBrazil",
|
||||
"Czech"
|
||||
],
|
||||
"enum": [
|
||||
"English",
|
||||
"Japanese",
|
||||
"Spanish",
|
||||
"ChineseSimplified",
|
||||
"ChineseTraditional",
|
||||
"French",
|
||||
"PortugueseBrazil",
|
||||
"Czech"
|
||||
]
|
||||
},
|
||||
"wordCount": {
|
||||
"type": "number",
|
||||
"description": "If `existingMnemonic` is not set, a mnemonic is generated using the specified wordCount.",
|
||||
"default": 12,
|
||||
"x-enumNames": [
|
||||
12,15,18,21,24
|
||||
],
|
||||
"enum": [
|
||||
12,15,18,21,24
|
||||
]
|
||||
},
|
||||
"scriptPubKeyType": {
|
||||
"type": "string",
|
||||
"description": "the type of wallet to generate",
|
||||
"default": "Segwit",
|
||||
"x-enumNames": [
|
||||
"Legacy",
|
||||
"Segwit",
|
||||
"SegwitP2SH"
|
||||
],
|
||||
"enum": [
|
||||
"Legacy",
|
||||
"Segwit",
|
||||
"SegwitP2SH"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Add table
Reference in a new issue