mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-13 11:35:51 +01:00
Support wasabi file format input (#1671)
* Support wasabi file format input This adds support to importing the wasabi file format which seems to be used more for imports by Wasabi,ColdCard,BlueWallet & Cobo Vault (and way better than electrum anyway) * fixes * add test
This commit is contained in:
parent
e751be21ea
commit
0dd1b668cd
6 changed files with 149 additions and 53 deletions
|
@ -2248,13 +2248,41 @@ namespace BTCPayServer.Tests
|
|||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller
|
||||
.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model;
|
||||
Assert.True(derivationVM.Confirmation);
|
||||
|
||||
// Can we upload coldcard settings? (Should fail, we are giving a mainnet file to a testnet network)
|
||||
|
||||
|
||||
//cobo vault file
|
||||
var content = "{\"ExtPubKey\":\"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\"MasterFingerprint\":\"7a7563b5\",\"DerivationPath\":\"M\\/84'\\/0'\\/0'\",\"CoboVaultFirmwareVersion\":\"1.2.0(BTC-Only)\"}";
|
||||
derivationVM = (DerivationSchemeViewModel)Assert
|
||||
.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
string content =
|
||||
derivationVM.WalletFile = TestUtils.GetFormFile("wallet3.json", content);
|
||||
derivationVM.Enabled = true;
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller
|
||||
.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model;
|
||||
Assert.True(derivationVM.Confirmation);
|
||||
Assert.IsType<RedirectToActionResult>(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC")
|
||||
.GetAwaiter().GetResult());
|
||||
|
||||
//wasabi wallet file
|
||||
content =
|
||||
"{\r\n \"EncryptedSecret\": \"6PYWBQ1zsukowsnTNA57UUx791aBuJusm7E4egXUmF5WGw3tcdG3cmTL57\",\r\n \"ChainCode\": \"waSIVbn8HaoovoQg/0t8IS1+ZCxGsJRGFT21i06nWnc=\",\r\n \"MasterFingerprint\": \"7a7563b5\",\r\n \"ExtPubKey\": \"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\r\n \"PasswordVerified\": false,\r\n \"MinGapLimit\": 21,\r\n \"AccountKeyPath\": \"84'/0'/0'\",\r\n \"BlockchainState\": {\r\n \"Network\": \"RegTest\",\r\n \"Height\": \"0\"\r\n },\r\n \"HdPubKeys\": []\r\n}";
|
||||
|
||||
derivationVM = (DerivationSchemeViewModel)Assert
|
||||
.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
derivationVM.WalletFile = TestUtils.GetFormFile("wallet4.json", content);
|
||||
derivationVM.Enabled = true;
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller
|
||||
.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model;
|
||||
Assert.True(derivationVM.Confirmation);
|
||||
Assert.IsType<RedirectToActionResult>(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC")
|
||||
.GetAwaiter().GetResult());
|
||||
|
||||
|
||||
// Can we upload coldcard settings? (Should fail, we are giving a mainnet file to a testnet network)
|
||||
derivationVM = (DerivationSchemeViewModel)Assert
|
||||
.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
content =
|
||||
"{\"keystore\": {\"ckcc_xpub\": \"xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw\", \"xpub\": \"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}";
|
||||
derivationVM.ElectrumWalletFile = TestUtils.GetFormFile("wallet.json", content);
|
||||
derivationVM.WalletFile = TestUtils.GetFormFile("wallet.json", content);
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller
|
||||
.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model;
|
||||
Assert.False(derivationVM
|
||||
|
@ -2265,19 +2293,20 @@ namespace BTCPayServer.Tests
|
|||
"{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"upub5DBYp1qGgsTrkzCptMGZc2x18pquLwGrBw6nS59T4NViZ4cni1mGowQzziy85K8vzkp1jVtWrSkLhqk9KDfvrGeB369wGNYf39kX8rQfiLn\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}";
|
||||
derivationVM = (DerivationSchemeViewModel)Assert
|
||||
.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
derivationVM.ElectrumWalletFile = TestUtils.GetFormFile("wallet2.json", content);
|
||||
derivationVM.WalletFile = TestUtils.GetFormFile("wallet2.json", content);
|
||||
derivationVM.Enabled = true;
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller
|
||||
.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model;
|
||||
Assert.True(derivationVM.Confirmation);
|
||||
Assert.IsType<RedirectToActionResult>(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC")
|
||||
.GetAwaiter().GetResult());
|
||||
|
||||
|
||||
|
||||
// Now let's check that no data has been lost in the process
|
||||
var store = tester.PayTester.StoreRepository.FindStore(user.StoreId).GetAwaiter().GetResult();
|
||||
var onchainBTC = store.GetSupportedPaymentMethods(tester.PayTester.Networks)
|
||||
.OfType<DerivationSchemeSettings>().First(o => o.PaymentId.IsBTCOnChain);
|
||||
DerivationSchemeSettings.TryParseFromElectrumWallet(content, onchainBTC.Network, out var expected);
|
||||
DerivationSchemeSettings.TryParseFromWalletFile(content, onchainBTC.Network, out var expected);
|
||||
Assert.Equal(expected.ToJson(), onchainBTC.ToJson());
|
||||
|
||||
// Let's check that the root hdkey and account key path are taken into account when making a PSBT
|
||||
|
@ -3581,7 +3610,7 @@ normal:
|
|||
var root = new Mnemonic(
|
||||
"usage fever hen zero slide mammal silent heavy donate budget pulse say brain thank sausage brand craft about save attract muffin advance illegal cabbage")
|
||||
.DeriveExtKey();
|
||||
Assert.True(DerivationSchemeSettings.TryParseFromElectrumWallet(
|
||||
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(
|
||||
"{\"keystore\": {\"ckcc_xpub\": \"xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw\", \"xpub\": \"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}",
|
||||
mainnet, out var settings));
|
||||
Assert.Equal(root.GetPublicKey().GetHDFingerPrint(), settings.AccountKeySettings[0].RootFingerprint);
|
||||
|
@ -3598,20 +3627,20 @@ normal:
|
|||
var testnet = new BTCPayNetworkProvider(NetworkType.Testnet).GetNetwork<BTCPayNetwork>("BTC");
|
||||
|
||||
// Should be legacy
|
||||
Assert.True(DerivationSchemeSettings.TryParseFromElectrumWallet(
|
||||
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(
|
||||
"{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"tpubDDWYqT3P24znfsaGX7kZcQhNc5LAjnQiKQvUCHF2jS6dsgJBRtymopEU5uGpMaR5YChjuiExZG1X2aTbqXkp82KqH5qnqwWHp6EWis9ZvKr\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/44'/1'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}",
|
||||
testnet, out settings));
|
||||
Assert.True(settings.AccountDerivation is DirectDerivationStrategy s && !s.Segwit);
|
||||
|
||||
// Should be segwit p2sh
|
||||
Assert.True(DerivationSchemeSettings.TryParseFromElectrumWallet(
|
||||
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(
|
||||
"{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"upub5DSddA9NoRUyJrQ4p86nsCiTSY7kLHrSxx3joEJXjHd4HPARhdXUATuk585FdWPVC2GdjsMePHb6BMDmf7c6KG4K4RPX6LVqBLtDcWpQJmh\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/1'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}",
|
||||
testnet, out settings));
|
||||
Assert.True(settings.AccountDerivation is P2SHDerivationStrategy p &&
|
||||
p.Inner is DirectDerivationStrategy s2 && s2.Segwit);
|
||||
|
||||
// Should be segwit
|
||||
Assert.True(DerivationSchemeSettings.TryParseFromElectrumWallet(
|
||||
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(
|
||||
"{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"vpub5YjYxTemJ39tFRnuAhwduyxG2tKGjoEpmvqVQRPqdYrqa6YGoeSzBtHXaJUYB19zDbXs3JjbEcVWERjQBPf9bEfUUMZNMv1QnMyHV8JPqyf\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/84'/1'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}",
|
||||
testnet, out settings));
|
||||
Assert.True(settings.AccountDerivation is DirectDerivationStrategy s3 && s3.Segwit);
|
||||
|
|
|
@ -110,14 +110,14 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
}
|
||||
|
||||
if (vm.ElectrumWalletFile != null)
|
||||
if (vm.WalletFile != null)
|
||||
{
|
||||
if (!DerivationSchemeSettings.TryParseFromElectrumWallet(await ReadAllText(vm.ElectrumWalletFile), network, out strategy))
|
||||
if (!DerivationSchemeSettings.TryParseFromWalletFile(await ReadAllText(vm.WalletFile), network, out strategy))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = "Electrum wallet/Air-gapped hardware wallet file was not in the correct format"
|
||||
Message = "Wallet file was not in the correct format"
|
||||
});
|
||||
vm.Confirmation = false;
|
||||
return View(nameof(AddDerivationScheme),vm);
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Payments;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
@ -38,12 +40,12 @@ namespace BTCPayServer
|
|||
return strategy != null;
|
||||
}
|
||||
|
||||
private static bool TryParseXpub(string xpub, DerivationSchemeParser derivationSchemeParser, ref DerivationSchemeSettings derivationSchemeSettings)
|
||||
private static bool TryParseXpub(string xpub, DerivationSchemeParser derivationSchemeParser, ref DerivationSchemeSettings derivationSchemeSettings, bool electrum = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
derivationSchemeSettings.AccountOriginal = xpub.Trim();
|
||||
derivationSchemeSettings.AccountDerivation = derivationSchemeParser.ParseElectrum(derivationSchemeSettings.AccountOriginal);
|
||||
derivationSchemeSettings.AccountDerivation = electrum ? derivationSchemeParser.ParseElectrum(derivationSchemeSettings.AccountOriginal) : derivationSchemeParser.Parse(derivationSchemeSettings.AccountOriginal);
|
||||
derivationSchemeSettings.AccountKeySettings = new AccountKeySettings[1];
|
||||
derivationSchemeSettings.AccountKeySettings[0] = new AccountKeySettings();
|
||||
derivationSchemeSettings.AccountKeySettings[0].AccountKey = derivationSchemeSettings.AccountDerivation.GetExtPubKeys().Single().GetWif(derivationSchemeParser.Network);
|
||||
|
@ -57,58 +59,122 @@ namespace BTCPayServer
|
|||
}
|
||||
}
|
||||
|
||||
public static bool TryParseFromElectrumWallet(string coldcardExport, BTCPayNetwork network, out DerivationSchemeSettings settings)
|
||||
public static bool TryParseFromWalletFile(string fileContents, BTCPayNetwork network, out DerivationSchemeSettings settings)
|
||||
{
|
||||
settings = null;
|
||||
if (coldcardExport == null)
|
||||
throw new ArgumentNullException(nameof(coldcardExport));
|
||||
if (fileContents == null)
|
||||
throw new ArgumentNullException(nameof(fileContents));
|
||||
if (network == null)
|
||||
throw new ArgumentNullException(nameof(network));
|
||||
var result = new DerivationSchemeSettings();
|
||||
result.Source = "Electrum/Airgap hardware wallet";
|
||||
var derivationSchemeParser = new DerivationSchemeParser(network);
|
||||
JObject jobj = null;
|
||||
try
|
||||
{
|
||||
jobj = JObject.Parse(coldcardExport);
|
||||
jobj = (JObject)jobj["keystore"];
|
||||
jobj = JObject.Parse(fileContents);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return TryParseXpub(coldcardExport, derivationSchemeParser, ref result);
|
||||
result.Source = "GenericFile";
|
||||
return TryParseXpub(fileContents, derivationSchemeParser, ref result);
|
||||
}
|
||||
|
||||
if (!jobj.ContainsKey("xpub") ||
|
||||
!TryParseXpub(jobj["xpub"].Value<string>(), derivationSchemeParser, ref result))
|
||||
//electrum
|
||||
if (jobj.ContainsKey("keystore"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (jobj.ContainsKey("label"))
|
||||
{
|
||||
try
|
||||
result.Source = "ElectrumFile";
|
||||
jobj = (JObject)jobj["keystore"];
|
||||
|
||||
if (!jobj.ContainsKey("xpub") ||
|
||||
!TryParseXpub(jobj["xpub"].Value<string>(), derivationSchemeParser, ref result))
|
||||
{
|
||||
result.Label = jobj["label"].Value<string>();
|
||||
return false;
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
|
||||
if (jobj.ContainsKey("ckcc_xfp"))
|
||||
{
|
||||
try
|
||||
if (jobj.ContainsKey("label"))
|
||||
{
|
||||
result.AccountKeySettings[0].RootFingerprint = new HDFingerprint(jobj["ckcc_xfp"].Value<uint>());
|
||||
try
|
||||
{
|
||||
result.Label = jobj["label"].Value<string>();
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
|
||||
if (jobj.ContainsKey("derivation"))
|
||||
{
|
||||
try
|
||||
if (jobj.ContainsKey("ckcc_xfp"))
|
||||
{
|
||||
result.AccountKeySettings[0].AccountKeyPath = new KeyPath(jobj["derivation"].Value<string>());
|
||||
try
|
||||
{
|
||||
result.AccountKeySettings[0].RootFingerprint = new HDFingerprint(jobj["ckcc_xfp"].Value<uint>());
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
|
||||
if (jobj.ContainsKey("derivation"))
|
||||
{
|
||||
try
|
||||
{
|
||||
result.AccountKeySettings[0].AccountKeyPath = new KeyPath(jobj["derivation"].Value<string>());
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Source = "WasabiFile";
|
||||
//wasabi format
|
||||
if (!jobj.ContainsKey("ExtPubKey") ||
|
||||
!TryParseXpub(jobj["ExtPubKey"].Value<string>(), derivationSchemeParser, ref result, false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (jobj.ContainsKey("MasterFingerprint"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var mfpString = jobj["MasterFingerprint"].ToString().Trim();
|
||||
// https://github.com/zkSNACKs/WalletWasabi/pull/1663#issuecomment-508073066
|
||||
|
||||
if(uint.TryParse(mfpString, out var fingerprint))
|
||||
{
|
||||
result.AccountKeySettings[0].RootFingerprint = new HDFingerprint(fingerprint);
|
||||
}
|
||||
else
|
||||
{
|
||||
var shouldReverseMfp = jobj.ContainsKey("ColdCardFirmwareVersion") &&
|
||||
jobj["ColdCardFirmwareVersion"].ToString() == "2.1.0";
|
||||
var bytes = Encoders.Hex.DecodeData(mfpString);
|
||||
result.AccountKeySettings[0].RootFingerprint = shouldReverseMfp ? new HDFingerprint(bytes.Reverse().ToArray()) : new HDFingerprint(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
catch { return false; }
|
||||
}
|
||||
if (jobj.ContainsKey("AccountKeyPath"))
|
||||
{
|
||||
try
|
||||
{
|
||||
result.AccountKeySettings[0].AccountKeyPath = new KeyPath(jobj["AccountKeyPath"].Value<string>());
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
if (jobj.ContainsKey("DerivationPath"))
|
||||
{
|
||||
try
|
||||
{
|
||||
result.AccountKeySettings[0].AccountKeyPath = new KeyPath(jobj["DerivationPath"].Value<string>().ToLowerInvariant());
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
|
||||
if (jobj.ContainsKey("ColdCardFirmwareVersion"))
|
||||
{
|
||||
result.Source = "ColdCard";
|
||||
}
|
||||
|
||||
if (jobj.ContainsKey("CoboVaultFirmwareVersion"))
|
||||
{
|
||||
result.Source = "CoboVault";
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
settings = result;
|
||||
settings.Network = network;
|
||||
|
|
|
@ -35,8 +35,8 @@ namespace BTCPayServer.Models.StoreViewModels
|
|||
|
||||
public KeyPath RootKeyPath { get; set; }
|
||||
|
||||
[Display(Name = "Electrum Wallet File")]
|
||||
public IFormFile ElectrumWalletFile{ get; set; }
|
||||
[Display(Name = "Electrum/Hardware Wallet File")]
|
||||
public IFormFile WalletFile{ get; set; }
|
||||
public string Config { get; set; }
|
||||
public string Source { get; set; }
|
||||
public string DerivationSchemeFormat { get; set; }
|
||||
|
|
|
@ -85,7 +85,7 @@
|
|||
{
|
||||
<button class="dropdown-item check-for-vault" type="button">... a hardware wallet</button>
|
||||
}
|
||||
<button class="dropdown-item" type="button" data-toggle="modal" data-target="#electrumimport">... Electrum/Air-gapped hardware wallet file</button>
|
||||
<button class="dropdown-item" type="button" data-toggle="modal" data-target="#electrumimport">... a wallet file (Electrum, Wasabi, Cobo Vault, ColdCard)</button>
|
||||
@if (Model.CanUseHotWallet)
|
||||
{
|
||||
<button class="dropdown-item" data-toggle="modal" data-target="#nbxplorergeneratewallet" type="button" id="nbxplorergeneratewalletbtn">... a new/existing seed.</button>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<div class="modal-dialog modal-lg" role="document">
|
||||
<form class="modal-content" form method="post" enctype="multipart/form-data">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="electrumimportLabel">Import Electrum/Air-gapped Hardware Wallet</h5>
|
||||
<h5 class="modal-title" id="electrumimportLabel">Import Wallet from file</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
|
@ -19,14 +19,15 @@
|
|||
<div class="modal-body">
|
||||
<p>You may import your air-gapped hardware wallet (such as ColdCard, Cobo Vault) by exporting a file and uploading it here.</p>
|
||||
<ul >
|
||||
<li>Cobo Vault - <kbd>∙∙∙->Create Watch-Only Wallet in Electrum->export via microSD</kbd></li>
|
||||
<li>ColdCard - <kbd>Advanced->MicroSD Card->Electrum Wallet</kbd></li>
|
||||
<li>Cobo Vault - <kbd>Settings->Watch-Only Wallet->Wasabi Wallet/BTCPay->Export Wallet</kbd></li>
|
||||
<li>ColdCard - <kbd>Advanced->MicroSD Card->Electrum Wallet</kbd> or <kbd>Advanced->MicroSD Card->Wasabi Wallet</kbd></li>
|
||||
<li>Electrum - <kbd>File->Save Copy</kbd></li>
|
||||
<li>Wasabi - <kbd>Tools->Wallet Manager->Open Wallets Folder</kbd></li>
|
||||
</ul>
|
||||
<div class="form-group">
|
||||
<label asp-for="ElectrumWalletFile"></label>
|
||||
<label asp-for="WalletFile"></label>
|
||||
|
||||
<input type="file" class="form-control-file" asp-for="ElectrumWalletFile" required>
|
||||
<input type="file" class="form-control-file" asp-for="WalletFile" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
|
Loading…
Add table
Reference in a new issue