mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Allow import of coldcard wallet
This commit is contained in:
@@ -1,12 +1,14 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Models;
|
||||||
using BTCPayServer.Models.StoreViewModels;
|
using BTCPayServer.Models.StoreViewModels;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
@@ -111,10 +113,13 @@ namespace BTCPayServer.Controllers
|
|||||||
.FirstOrDefault(d => d.PaymentId == id);
|
.FirstOrDefault(d => d.PaymentId == id);
|
||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("{storeId}/derivations/{cryptoCode}")]
|
[Route("{storeId}/derivations/{cryptoCode}")]
|
||||||
public async Task<IActionResult> AddDerivationScheme(string storeId, DerivationSchemeViewModel vm, string cryptoCode)
|
public async Task<IActionResult> AddDerivationScheme(string storeId, DerivationSchemeViewModel vm,
|
||||||
|
string cryptoCode)
|
||||||
{
|
{
|
||||||
vm.CryptoCode = cryptoCode;
|
vm.CryptoCode = cryptoCode;
|
||||||
var store = HttpContext.GetStoreData();
|
var store = HttpContext.GetStoreData();
|
||||||
@@ -126,45 +131,70 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.RootKeyPath = network.GetRootKeyPath();
|
vm.RootKeyPath = network.GetRootKeyPath();
|
||||||
|
DerivationSchemeSettings strategy = null;
|
||||||
|
|
||||||
|
PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
|
||||||
|
var exisingStrategy = store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||||
|
.Where(c => c.PaymentId == paymentMethodId)
|
||||||
|
.OfType<DerivationSchemeSettings>()
|
||||||
|
.Select(c => c.AccountDerivation.ToString())
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
var wallet = _WalletProvider.GetWallet(network);
|
var wallet = _WalletProvider.GetWallet(network);
|
||||||
if (wallet == null)
|
if (wallet == null)
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
|
if (vm.ColdcardPublicFile != null)
|
||||||
var exisingStrategy = store.GetSupportedPaymentMethods(_NetworkProvider)
|
|
||||||
.Where(c => c.PaymentId == paymentMethodId)
|
|
||||||
.OfType<DerivationSchemeSettings>()
|
|
||||||
.Select(c => c.AccountDerivation.ToString())
|
|
||||||
.FirstOrDefault();
|
|
||||||
DerivationSchemeSettings strategy = null;
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(vm.DerivationScheme))
|
using (var stream = new StreamReader(vm.ColdcardPublicFile.OpenReadStream()))
|
||||||
{
|
{
|
||||||
strategy = ParseDerivationStrategy(vm.DerivationScheme, null, network);
|
var fileContent = await stream.ReadToEndAsync();
|
||||||
vm.DerivationScheme = strategy.ToString();
|
if (
|
||||||
|
!DerivationSchemeSettings.TryParseFromColdcard(fileContent, network, out strategy))
|
||||||
|
{
|
||||||
|
vm.StatusMessage = new StatusMessageModel()
|
||||||
|
{
|
||||||
|
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||||
|
Message = "Coldcard public file was not in the correct format"
|
||||||
|
}.ToString();
|
||||||
|
vm.Confirmation = false;
|
||||||
|
return View(vm);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
else
|
||||||
{
|
{
|
||||||
ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid Derivation Scheme");
|
try
|
||||||
vm.Confirmation = false;
|
{
|
||||||
return View(vm);
|
if (!string.IsNullOrEmpty(vm.DerivationScheme))
|
||||||
|
{
|
||||||
|
strategy = ParseDerivationStrategy(vm.DerivationScheme, null, network);
|
||||||
|
vm.DerivationScheme = strategy.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid Derivation Scheme");
|
||||||
|
vm.Confirmation = false;
|
||||||
|
return View(vm);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var storeBlob = store.GetStoreBlob();
|
var storeBlob = store.GetStoreBlob();
|
||||||
var wasExcluded = storeBlob.GetExcludedPaymentMethods().Match(paymentMethodId);
|
var wasExcluded = storeBlob.GetExcludedPaymentMethods().Match(paymentMethodId);
|
||||||
var willBeExcluded = !vm.Enabled;
|
var willBeExcluded = !vm.Enabled;
|
||||||
|
|
||||||
var showAddress = // Show addresses if:
|
var showAddress = // Show addresses if:
|
||||||
// - If the user is testing the hint address in confirmation screen
|
// - If the user is testing the hint address in confirmation screen
|
||||||
(vm.Confirmation && !string.IsNullOrWhiteSpace(vm.HintAddress)) ||
|
(vm.Confirmation && !string.IsNullOrWhiteSpace(vm.HintAddress)) ||
|
||||||
// - The user is setting a new derivation scheme
|
// - The user is setting a new derivation scheme
|
||||||
(!vm.Confirmation && strategy != null && exisingStrategy != strategy.AccountDerivation.ToString()) ||
|
(!vm.Confirmation && strategy != null && exisingStrategy != strategy.AccountDerivation.ToString()) ||
|
||||||
// - The user is clicking on continue without changing anything
|
// - The user is clicking on continue without changing anything
|
||||||
(!vm.Confirmation && willBeExcluded == wasExcluded);
|
(!vm.Confirmation && willBeExcluded == wasExcluded);
|
||||||
|
|
||||||
showAddress = showAddress && strategy != null;
|
showAddress = showAddress && strategy != null;
|
||||||
if (!showAddress)
|
if (!showAddress)
|
||||||
@@ -186,7 +216,7 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
await _Repo.UpdateStore(store);
|
await _Repo.UpdateStore(store);
|
||||||
StatusMessage = $"Derivation scheme for {network.CryptoCode} has been modified.";
|
StatusMessage = $"Derivation scheme for {network.CryptoCode} has been modified.";
|
||||||
return RedirectToAction(nameof(UpdateStore), new { storeId = storeId });
|
return RedirectToAction(nameof(UpdateStore), new {storeId = storeId});
|
||||||
}
|
}
|
||||||
else if (!string.IsNullOrEmpty(vm.HintAddress))
|
else if (!string.IsNullOrEmpty(vm.HintAddress))
|
||||||
{
|
{
|
||||||
@@ -210,13 +240,17 @@ namespace BTCPayServer.Controllers
|
|||||||
ModelState.AddModelError(nameof(vm.HintAddress), "Impossible to find a match with this address");
|
ModelState.AddModelError(nameof(vm.HintAddress), "Impossible to find a match with this address");
|
||||||
return ShowAddresses(vm, strategy);
|
return ShowAddresses(vm, strategy);
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.HintAddress = "";
|
vm.HintAddress = "";
|
||||||
vm.StatusMessage = "Address successfully found, please verify that the rest is correct and click on \"Confirm\"";
|
vm.StatusMessage =
|
||||||
|
"Address successfully found, please verify that the rest is correct and click on \"Confirm\"";
|
||||||
ModelState.Remove(nameof(vm.HintAddress));
|
ModelState.Remove(nameof(vm.HintAddress));
|
||||||
ModelState.Remove(nameof(vm.DerivationScheme));
|
ModelState.Remove(nameof(vm.DerivationScheme));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ShowAddresses(vm, strategy);
|
return ShowAddresses(vm, strategy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private IActionResult ShowAddresses(DerivationSchemeViewModel vm, DerivationSchemeSettings strategy)
|
private IActionResult ShowAddresses(DerivationSchemeViewModel vm, DerivationSchemeSettings strategy)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
|
|
||||||
@@ -32,5 +33,8 @@ namespace BTCPayServer.Models.StoreViewModels
|
|||||||
|
|
||||||
public string StatusMessage { get; internal set; }
|
public string StatusMessage { get; internal set; }
|
||||||
public KeyPath RootKeyPath { get; set; }
|
public KeyPath RootKeyPath { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Coldcard Wallet File")]
|
||||||
|
public IFormFile ColdcardPublicFile{ get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,33 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
|
|
||||||
|
<div class="modal fade" id="coldcardimport" tabindex="-1" role="dialog" aria-labelledby="coldcardimport" aria-hidden="true">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<form class="modal-content" form method="post" enctype="multipart/form-data">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="exampleModalLabel">Import Coldcard Wallet</h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>You may import your Coldcard wallet by exporting the public details from <kbd>Advanced->MicroSD Card->Electrum Wallet</kbd> and uploading it here.</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label asp-for="ColdcardPublicFile"></label>
|
||||||
|
|
||||||
|
<input type="file" class="form-control-file" asp-for="ColdcardPublicFile" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
@if (!Model.Confirmation)
|
@if (!Model.Confirmation)
|
||||||
{
|
{
|
||||||
@@ -36,9 +63,12 @@
|
|||||||
<p id="no-ledger-info" class="form-text text-muted" style="display: none;">
|
<p id="no-ledger-info" class="form-text text-muted" style="display: none;">
|
||||||
No ledger wallet detected. If you own one, use chrome, open the app, and refresh this page.
|
No ledger wallet detected. If you own one, use chrome, open the app, and refresh this page.
|
||||||
</p>
|
</p>
|
||||||
<div id="ledger-info" class="form-text text-muted" style="display: none;">
|
<div id="ledger-info" class="form-text text-muted display-when-ledger-connected" >
|
||||||
<span>A ledger wallet is detected, which account do you want to use? No need to paste manually xpub if your ledger device was detected. Just select derivation scheme from the list bellow and xpub will automatically populate.</span>
|
<span>A ledger wallet is detected, which account do you want to use? No need to paste manually xpub if your ledger device was detected. Just select derivation scheme from the list bellow and xpub will automatically populate.</span>
|
||||||
<div class="dropdown">
|
|
||||||
|
</div>
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="dropdown display-when-ledger-connected mr-2">
|
||||||
<button class="btn btn-primary dropdown-toggle" type="button" id="ledgerAccountsDropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<button class="btn btn-primary dropdown-toggle" type="button" id="ledgerAccountsDropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
Select ledger wallet account
|
Select ledger wallet account
|
||||||
</button>
|
</button>
|
||||||
@@ -49,7 +79,13 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#coldcardimport">
|
||||||
|
Import Coldcard wallet
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<span>BTCPay format memo</span>
|
<span>BTCPay format memo</span>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
$("#ledger-loading").css("display", id === "ledger-loading" ? "block" : "none");
|
$("#ledger-loading").css("display", id === "ledger-loading" ? "block" : "none");
|
||||||
$("#no-ledger-info").css("display", id === "no-ledger-info" ? "block" : "none");
|
$("#no-ledger-info").css("display", id === "no-ledger-info" ? "block" : "none");
|
||||||
$("#ledger-validate").css("display", id === "ledger-validate" ? "block" : "none");
|
$("#ledger-validate").css("display", id === "ledger-validate" ? "block" : "none");
|
||||||
$("#ledger-info").css("display", id === "ledger-info" ? "block" : "none");
|
$(".display-when-ledger-connected").css("display", id === "ledger-info" ? "block" : "none");
|
||||||
}
|
}
|
||||||
function Write(prefix, type, message) {
|
function Write(prefix, type, message) {
|
||||||
if (type === "error") {
|
if (type === "error") {
|
||||||
|
|||||||
@@ -19,3 +19,7 @@
|
|||||||
.only-for-js, .input-group-clear{
|
.only-for-js, .input-group-clear{
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.display-when-ledger-connected{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user