2020-06-29 04:44:35 +02:00
using System ;
2019-05-12 04:13:04 +02:00
using System.Collections.Generic ;
using System.Linq ;
using System.Threading ;
using System.Threading.Tasks ;
2020-11-17 13:46:23 +01:00
using BTCPayServer.Abstractions.Extensions ;
using BTCPayServer.Abstractions.Models ;
2020-04-29 09:09:16 +02:00
using BTCPayServer.HostedServices ;
2019-05-12 04:13:04 +02:00
using BTCPayServer.ModelBinders ;
2020-01-06 13:57:32 +01:00
using BTCPayServer.Models ;
2019-05-12 04:13:04 +02:00
using BTCPayServer.Models.WalletViewModels ;
2021-03-01 14:44:53 +01:00
using BTCPayServer.Payments.PayJoin.Sender ;
2020-04-06 06:19:51 +02:00
using BTCPayServer.Services ;
2019-05-12 04:13:04 +02:00
using Microsoft.AspNetCore.Mvc ;
using NBitcoin ;
2021-03-01 14:44:53 +01:00
using BTCPayServer.BIP78.Sender ;
2020-06-17 14:43:56 +02:00
using NBitcoin.Payment ;
2020-01-21 09:33:12 +01:00
using NBXplorer ;
2019-05-12 04:13:04 +02:00
using NBXplorer.Models ;
namespace BTCPayServer.Controllers
{
public partial class WalletsController
{
[NonAction]
public async Task < CreatePSBTResponse > CreatePSBT ( BTCPayNetwork network , DerivationSchemeSettings derivationSettings , WalletSendModel sendModel , CancellationToken cancellationToken )
{
var nbx = ExplorerClientProvider . GetExplorerClient ( network ) ;
CreatePSBTRequest psbtRequest = new CreatePSBTRequest ( ) ;
2020-03-19 09:44:47 +01:00
if ( sendModel . InputSelection )
{
2020-06-28 10:55:27 +02:00
psbtRequest . IncludeOnlyOutpoints = sendModel . SelectedInputs ? . Select ( OutPoint . Parse ) ? . ToList ( ) ? ? new List < OutPoint > ( ) ;
2020-03-19 09:44:47 +01:00
}
2019-05-21 10:10:07 +02:00
foreach ( var transactionOutput in sendModel . Outputs )
{
var psbtDestination = new CreatePSBTDestination ( ) ;
psbtRequest . Destinations . Add ( psbtDestination ) ;
psbtDestination . Destination = BitcoinAddress . Create ( transactionOutput . DestinationAddress , network . NBitcoinNetwork ) ;
psbtDestination . Amount = Money . Coins ( transactionOutput . Amount . Value ) ;
psbtDestination . SubstractFees = transactionOutput . SubtractFeesFromOutput ;
}
2020-06-28 10:55:27 +02:00
2019-05-12 04:13:04 +02:00
if ( network . SupportRBF )
{
2020-05-24 14:11:33 +02:00
if ( sendModel . AllowFeeBump is WalletSendModel . ThreeStateBool . Yes )
psbtRequest . RBF = true ;
if ( sendModel . AllowFeeBump is WalletSendModel . ThreeStateBool . No )
psbtRequest . RBF = false ;
2019-05-12 04:13:04 +02:00
}
2020-06-12 13:58:55 +02:00
psbtRequest . AlwaysIncludeNonWitnessUTXO = sendModel . AlwaysIncludeNonWitnessUTXO ;
2020-06-28 10:55:27 +02:00
2019-05-12 04:13:04 +02:00
psbtRequest . FeePreference = new FeePreference ( ) ;
2020-05-05 12:06:59 +02:00
if ( sendModel . FeeSatoshiPerByte is decimal v & &
v > decimal . Zero )
{
2020-09-03 10:27:48 +02:00
psbtRequest . FeePreference . ExplicitFeeRate = new FeeRate ( v ) ;
2020-05-05 12:06:59 +02:00
}
2019-05-12 04:13:04 +02:00
if ( sendModel . NoChange )
{
2019-05-21 10:10:07 +02:00
psbtRequest . ExplicitChangeAddress = psbtRequest . Destinations . First ( ) . Destination ;
2019-05-12 04:13:04 +02:00
}
2020-06-28 10:55:27 +02:00
2019-05-12 04:13:04 +02:00
var psbt = ( await nbx . CreatePSBTAsync ( derivationSettings . AccountDerivation , psbtRequest , cancellationToken ) ) ;
if ( psbt = = null )
throw new NotSupportedException ( "You need to update your version of NBXplorer" ) ;
2019-07-25 12:38:29 +02:00
// Not supported by coldcard, remove when they do support it
psbt . PSBT . GlobalXPubs . Clear ( ) ;
2019-05-12 04:13:04 +02:00
return psbt ;
}
2021-06-14 07:06:56 +02:00
[HttpGet("{walletId}/psbt")]
2019-05-19 16:27:18 +02:00
public async Task < IActionResult > WalletPSBT ( [ ModelBinder ( typeof ( WalletIdModelBinder ) ) ]
2019-05-15 08:00:09 +02:00
WalletId walletId , WalletPSBTViewModel vm )
2019-05-12 04:13:04 +02:00
{
2019-05-29 11:43:50 +02:00
var network = NetworkProvider . GetNetwork < BTCPayNetwork > ( walletId . CryptoCode ) ;
2019-12-03 10:57:07 +01:00
vm . CryptoCode = network . CryptoCode ;
2021-01-16 11:48:05 +01:00
var derivationSchemeSettings = GetDerivationSchemeSettings ( walletId ) ;
if ( derivationSchemeSettings = = null )
return NotFound ( ) ;
2020-01-21 09:33:12 +01:00
vm . NBXSeedAvailable = await CanUseHotWallet ( ) & & ! string . IsNullOrEmpty ( await ExplorerClientProvider . GetExplorerClient ( network )
2021-01-16 11:48:05 +01:00
. GetMetadataAsync < string > ( derivationSchemeSettings . AccountDerivation , WellknownMetadataKeys . Mnemonic ) ) ;
2019-05-19 16:27:18 +02:00
if ( await vm . GetPSBT ( network . NBitcoinNetwork ) is PSBT psbt )
2019-05-15 08:00:09 +02:00
{
2019-05-19 16:27:18 +02:00
vm . Decoded = psbt . ToString ( ) ;
vm . PSBT = psbt . ToBase64 ( ) ;
2020-10-21 14:03:11 +02:00
vm . PSBTHex = psbt . ToHex ( ) ;
2019-05-15 08:00:09 +02:00
}
2020-06-28 10:55:27 +02:00
2020-02-13 14:06:00 +01:00
return View ( nameof ( WalletPSBT ) , vm ? ? new WalletPSBTViewModel ( ) { CryptoCode = walletId . CryptoCode } ) ;
2019-05-12 04:13:04 +02:00
}
2021-06-14 07:06:56 +02:00
[HttpPost("{walletId}/psbt")]
2019-05-12 06:13:52 +02:00
public async Task < IActionResult > WalletPSBT (
2019-05-12 04:13:04 +02:00
[ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId ,
2019-05-12 06:13:52 +02:00
WalletPSBTViewModel vm , string command = null )
2019-05-12 04:13:04 +02:00
{
2019-11-08 12:21:33 +01:00
if ( command = = null )
return await WalletPSBT ( walletId , vm ) ;
2019-05-29 11:43:50 +02:00
var network = NetworkProvider . GetNetwork < BTCPayNetwork > ( walletId . CryptoCode ) ;
2019-12-03 10:57:07 +01:00
vm . CryptoCode = network . CryptoCode ;
2021-01-16 11:48:05 +01:00
var derivationSchemeSettings = GetDerivationSchemeSettings ( walletId ) ;
if ( derivationSchemeSettings = = null )
return NotFound ( ) ;
2020-04-23 15:02:26 +02:00
vm . NBXSeedAvailable = await CanUseHotWallet ( ) & & ! string . IsNullOrEmpty ( await ExplorerClientProvider . GetExplorerClient ( network )
2021-01-16 11:48:05 +01:00
. GetMetadataAsync < string > ( derivationSchemeSettings . AccountDerivation , WellknownMetadataKeys . Mnemonic ) ) ;
2019-05-19 16:27:18 +02:00
var psbt = await vm . GetPSBT ( network . NBitcoinNetwork ) ;
2019-05-12 06:13:52 +02:00
if ( psbt = = null )
{
ModelState . AddModelError ( nameof ( vm . PSBT ) , "Invalid PSBT" ) ;
return View ( vm ) ;
}
2020-10-21 14:03:11 +02:00
vm . PSBTHex = psbt . ToHex ( ) ;
2021-06-14 07:06:56 +02:00
var routeBack = new Dictionary < string , string >
{
{ "action" , nameof ( WalletPSBT ) } , { "walletId" , walletId . ToString ( ) }
} ;
var res = await TryHandleSigningCommands ( walletId , psbt , command , vm . SigningContext , routeBack ) ;
2020-05-23 21:31:21 +02:00
if ( res ! = null )
{
return res ;
}
2019-05-14 18:03:48 +02:00
switch ( command )
2019-05-12 04:13:04 +02:00
{
2019-11-08 12:21:33 +01:00
case "decode" :
2019-05-14 18:03:48 +02:00
vm . Decoded = psbt . ToString ( ) ;
2019-05-19 16:27:18 +02:00
ModelState . Remove ( nameof ( vm . PSBT ) ) ;
ModelState . Remove ( nameof ( vm . FileName ) ) ;
ModelState . Remove ( nameof ( vm . UploadedPSBTFile ) ) ;
vm . PSBT = psbt . ToBase64 ( ) ;
2020-10-21 14:03:11 +02:00
vm . PSBTHex = psbt . ToHex ( ) ;
2019-05-19 16:27:18 +02:00
vm . FileName = vm . UploadedPSBTFile ? . FileName ;
2019-05-14 18:03:48 +02:00
return View ( vm ) ;
2020-05-23 21:31:21 +02:00
2019-05-30 16:16:05 +02:00
case "update" :
2020-03-29 17:28:22 +02:00
psbt = await ExplorerClientProvider . UpdatePSBT ( derivationSchemeSettings , psbt ) ;
2019-05-30 16:16:05 +02:00
if ( psbt = = null )
{
2019-05-30 17:00:20 +02:00
ModelState . AddModelError ( nameof ( vm . PSBT ) , "You need to update your version of NBXplorer" ) ;
2019-05-30 16:16:05 +02:00
return View ( vm ) ;
}
2019-10-31 04:29:59 +01:00
TempData [ WellKnownTempData . SuccessMessage ] = "PSBT updated!" ;
2021-06-14 07:06:56 +02:00
return RedirectToWalletPSBT ( new WalletPSBTViewModel
2020-05-24 21:55:28 +02:00
{
PSBT = psbt . ToBase64 ( ) ,
2020-05-25 00:05:01 +02:00
FileName = vm . FileName
2020-05-24 21:55:28 +02:00
} ) ;
2020-01-23 14:02:37 +01:00
2019-05-14 18:03:48 +02:00
case "broadcast" :
2020-05-24 21:55:28 +02:00
{
2021-06-14 07:06:56 +02:00
return RedirectToWalletPSBTReady ( new WalletPSBTReadyViewModel
2020-06-28 10:55:27 +02:00
{
SigningContext = new SigningContextModel ( psbt )
} ) ;
}
2019-05-14 18:03:48 +02:00
case "combine" :
ModelState . Remove ( nameof ( vm . PSBT ) ) ;
2021-06-14 07:06:56 +02:00
return View ( nameof ( WalletPSBTCombine ) , new WalletPSBTCombineViewModel { OtherPSBT = psbt . ToBase64 ( ) } ) ;
2019-05-14 18:03:48 +02:00
case "save-psbt" :
return FilePSBT ( psbt , vm . FileName ) ;
default :
return View ( vm ) ;
2019-05-12 04:13:04 +02:00
}
}
2020-06-17 14:43:56 +02:00
private async Task < PSBT > GetPayjoinProposedTX ( BitcoinUrlBuilder bip21 , PSBT psbt , DerivationSchemeSettings derivationSchemeSettings , BTCPayNetwork btcPayNetwork , CancellationToken cancellationToken )
2020-01-06 13:57:32 +01:00
{
2020-04-06 06:19:51 +02:00
var cloned = psbt . Clone ( ) ;
cloned = cloned . Finalize ( ) ;
2020-04-28 17:23:51 +02:00
await _broadcaster . Schedule ( DateTimeOffset . UtcNow + TimeSpan . FromMinutes ( 2.0 ) , cloned . ExtractTransaction ( ) , btcPayNetwork ) ;
2021-03-01 14:44:53 +01:00
return await _payjoinClient . RequestPayjoin ( bip21 , new PayjoinWallet ( derivationSchemeSettings ) , psbt , cancellationToken ) ;
2020-01-06 13:57:32 +01:00
}
2020-06-28 10:55:27 +02:00
2021-06-14 07:06:56 +02:00
[HttpGet("{walletId}/psbt/ready")]
2019-05-15 08:00:09 +02:00
public async Task < IActionResult > WalletPSBTReady (
2019-05-12 04:13:04 +02:00
[ModelBinder(typeof(WalletIdModelBinder))]
2020-05-24 21:55:28 +02:00
WalletId walletId ,
WalletPSBTReadyViewModel vm )
2019-05-15 08:00:09 +02:00
{
2020-05-24 21:55:28 +02:00
if ( vm is null )
return NotFound ( ) ;
2019-05-29 11:43:50 +02:00
var network = NetworkProvider . GetNetwork < BTCPayNetwork > ( walletId . CryptoCode ) ;
2019-10-12 13:35:30 +02:00
var derivationSchemeSettings = GetDerivationSchemeSettings ( walletId ) ;
2019-05-27 14:50:08 +02:00
if ( derivationSchemeSettings = = null )
return NotFound ( ) ;
2019-07-12 05:57:56 +02:00
try
{
await FetchTransactionDetails ( derivationSchemeSettings , vm , network ) ;
}
catch { return BadRequest ( ) ; }
2019-05-15 08:00:09 +02:00
return View ( nameof ( WalletPSBTReady ) , vm ) ;
}
2019-05-30 16:16:05 +02:00
private async Task FetchTransactionDetails ( DerivationSchemeSettings derivationSchemeSettings , WalletPSBTReadyViewModel vm , BTCPayNetwork network )
2019-05-12 04:13:04 +02:00
{
2020-05-24 23:27:01 +02:00
var psbtObject = PSBT . Parse ( vm . SigningContext . PSBT , network . NBitcoinNetwork ) ;
2019-11-11 06:22:04 +01:00
if ( ! psbtObject . IsAllFinalized ( ) )
2020-03-29 17:28:22 +02:00
psbtObject = await ExplorerClientProvider . UpdatePSBT ( derivationSchemeSettings , psbtObject ) ? ? psbtObject ;
2019-05-15 08:00:09 +02:00
IHDKey signingKey = null ;
RootedKeyPath signingKeyPath = null ;
try
{
signingKey = new BitcoinExtPubKey ( vm . SigningKey , network . NBitcoinNetwork ) ;
}
catch { }
try
{
signingKey = signingKey ? ? new BitcoinExtKey ( vm . SigningKey , network . NBitcoinNetwork ) ;
}
catch { }
try
{
signingKeyPath = RootedKeyPath . Parse ( vm . SigningKeyPath ) ;
}
catch { }
if ( signingKey = = null | | signingKeyPath = = null )
{
2019-05-19 16:27:18 +02:00
var signingKeySettings = derivationSchemeSettings . GetSigningAccountKeySettings ( ) ;
2019-05-15 08:00:09 +02:00
if ( signingKey = = null )
{
signingKey = signingKeySettings . AccountKey ;
vm . SigningKey = signingKey . ToString ( ) ;
}
if ( vm . SigningKeyPath = = null )
{
signingKeyPath = signingKeySettings . GetRootedKeyPath ( ) ;
vm . SigningKeyPath = signingKeyPath ? . ToString ( ) ;
}
}
2019-05-19 16:27:18 +02:00
if ( psbtObject . IsAllFinalized ( ) )
{
vm . CanCalculateBalance = false ;
}
else
{
var balanceChange = psbtObject . GetBalance ( derivationSchemeSettings . AccountDerivation , signingKey , signingKeyPath ) ;
vm . BalanceChange = ValueToString ( balanceChange , network ) ;
vm . CanCalculateBalance = true ;
vm . Positive = balanceChange > = Money . Zero ;
}
2020-02-13 14:06:00 +01:00
vm . Inputs = new List < WalletPSBTReadyViewModel . InputViewModel > ( ) ;
2019-05-30 17:23:23 +02:00
foreach ( var input in psbtObject . Inputs )
{
var inputVm = new WalletPSBTReadyViewModel . InputViewModel ( ) ;
vm . Inputs . Add ( inputVm ) ;
var mine = input . HDKeysFor ( derivationSchemeSettings . AccountDerivation , signingKey , signingKeyPath ) . Any ( ) ;
var balanceChange2 = input . GetTxOut ( ) ? . Value ? ? Money . Zero ;
if ( mine )
balanceChange2 = - balanceChange2 ;
inputVm . BalanceChange = ValueToString ( balanceChange2 , network ) ;
inputVm . Positive = balanceChange2 > = Money . Zero ;
inputVm . Index = ( int ) input . Index ;
}
2020-02-13 14:06:00 +01:00
vm . Destinations = new List < WalletPSBTReadyViewModel . DestinationViewModel > ( ) ;
2019-05-15 08:00:09 +02:00
foreach ( var output in psbtObject . Outputs )
{
var dest = new WalletPSBTReadyViewModel . DestinationViewModel ( ) ;
vm . Destinations . Add ( dest ) ;
2019-05-19 16:27:18 +02:00
var mine = output . HDKeysFor ( derivationSchemeSettings . AccountDerivation , signingKey , signingKeyPath ) . Any ( ) ;
2019-05-15 08:00:09 +02:00
var balanceChange2 = output . Value ;
if ( ! mine )
balanceChange2 = - balanceChange2 ;
dest . Balance = ValueToString ( balanceChange2 , network ) ;
dest . Positive = balanceChange2 > = Money . Zero ;
dest . Destination = output . ScriptPubKey . GetDestinationAddress ( network . NBitcoinNetwork ) ? . ToString ( ) ? ? output . ScriptPubKey . ToString ( ) ;
}
if ( psbtObject . TryGetFee ( out var fee ) )
{
2019-05-19 16:27:18 +02:00
vm . Destinations . Add ( new WalletPSBTReadyViewModel . DestinationViewModel ( )
{
Positive = false ,
2020-06-28 10:55:27 +02:00
Balance = ValueToString ( - fee , network ) ,
2019-05-19 16:27:18 +02:00
Destination = "Mining fees"
} ) ;
2019-05-15 08:00:09 +02:00
}
2019-05-16 05:56:06 +02:00
if ( psbtObject . TryGetEstimatedFeeRate ( out var feeRate ) )
{
vm . FeeRate = feeRate . ToString ( ) ;
}
2019-05-19 16:27:18 +02:00
2019-05-30 17:23:23 +02:00
var sanityErrors = psbtObject . CheckSanity ( ) ;
if ( sanityErrors . Count ! = 0 )
{
vm . SetErrors ( sanityErrors ) ;
}
else if ( ! psbtObject . IsAllFinalized ( ) & & ! psbtObject . TryFinalize ( out var errors ) )
2019-05-19 16:27:18 +02:00
{
vm . SetErrors ( errors ) ;
}
2019-05-12 04:13:04 +02:00
}
2021-06-14 07:06:56 +02:00
[HttpPost("{walletId}/psbt/ready")]
2019-05-12 04:13:04 +02:00
public async Task < IActionResult > WalletPSBTReady (
[ModelBinder(typeof(WalletIdModelBinder))]
2020-03-29 17:28:22 +02:00
WalletId walletId , WalletPSBTReadyViewModel vm , string command = null , CancellationToken cancellationToken = default )
2019-05-12 04:13:04 +02:00
{
2019-11-08 12:21:33 +01:00
if ( command = = null )
2020-05-24 21:55:28 +02:00
return await WalletPSBTReady ( walletId , vm ) ;
2019-05-12 04:13:04 +02:00
PSBT psbt = null ;
2019-05-29 11:43:50 +02:00
var network = NetworkProvider . GetNetwork < BTCPayNetwork > ( walletId . CryptoCode ) ;
2020-03-30 08:31:30 +02:00
DerivationSchemeSettings derivationSchemeSettings = null ;
2019-05-12 04:13:04 +02:00
try
{
2020-05-24 23:27:01 +02:00
psbt = PSBT . Parse ( vm . SigningContext . PSBT , network . NBitcoinNetwork ) ;
2020-03-30 08:31:30 +02:00
derivationSchemeSettings = GetDerivationSchemeSettings ( walletId ) ;
2019-05-27 14:50:08 +02:00
if ( derivationSchemeSettings = = null )
return NotFound ( ) ;
await FetchTransactionDetails ( derivationSchemeSettings , vm , network ) ;
2019-05-12 04:13:04 +02:00
}
catch
{
2019-05-19 16:27:18 +02:00
vm . GlobalError = "Invalid PSBT" ;
2020-06-28 10:55:27 +02:00
return View ( nameof ( WalletPSBTReady ) , vm ) ;
2019-05-12 04:13:04 +02:00
}
2020-06-28 10:55:27 +02:00
2020-03-30 08:31:30 +02:00
switch ( command )
2019-05-12 04:13:04 +02:00
{
2020-03-30 08:31:30 +02:00
case "payjoin" :
2020-04-06 06:19:51 +02:00
string error = null ;
try
2020-03-30 08:31:30 +02:00
{
2020-06-17 14:43:56 +02:00
var proposedPayjoin = await GetPayjoinProposedTX ( new BitcoinUrlBuilder ( vm . SigningContext . PayJoinBIP21 , network . NBitcoinNetwork ) , psbt ,
2020-04-06 06:19:51 +02:00
derivationSchemeSettings , network , cancellationToken ) ;
2020-03-30 08:31:30 +02:00
try
{
2021-07-29 13:29:34 +02:00
proposedPayjoin . Settings . SigningOptions = new SigningOptions ( )
{
EnforceLowR = ! ( vm . SigningContext ? . EnforceLowR is false )
} ;
2020-03-30 08:31:30 +02:00
var extKey = ExtKey . Parse ( vm . SigningKey , network . NBitcoinNetwork ) ;
2020-03-29 17:28:22 +02:00
proposedPayjoin = proposedPayjoin . SignAll ( derivationSchemeSettings . AccountDerivation ,
extKey ,
2021-07-29 13:29:34 +02:00
RootedKeyPath . Parse ( vm . SigningKeyPath ) ) ;
2020-05-24 23:27:01 +02:00
vm . SigningContext . PSBT = proposedPayjoin . ToBase64 ( ) ;
vm . SigningContext . OriginalPSBT = psbt . ToBase64 ( ) ;
2020-04-06 06:19:51 +02:00
proposedPayjoin . Finalize ( ) ;
2020-04-29 09:09:16 +02:00
var hash = proposedPayjoin . ExtractTransaction ( ) . GetHash ( ) ;
2020-06-01 06:10:21 +02:00
_EventAggregator . Publish ( new UpdateTransactionLabel ( walletId , hash , UpdateTransactionLabel . PayjoinLabelTemplate ( ) ) ) ;
2020-04-06 06:19:51 +02:00
TempData . SetStatusMessageModel ( new StatusMessageModel ( )
{
Severity = StatusMessageModel . StatusSeverity . Success ,
AllowDismiss = false ,
Html = $"The payjoin transaction has been successfully broadcasted ({proposedPayjoin.ExtractTransaction().GetHash()})"
} ) ;
2020-03-30 08:31:30 +02:00
return await WalletPSBTReady ( walletId , vm , "broadcast" ) ;
}
2020-03-29 17:28:22 +02:00
catch ( Exception )
2020-03-26 15:42:54 +01:00
{
TempData . SetStatusMessageModel ( new StatusMessageModel ( )
{
2020-04-06 06:19:51 +02:00
Severity = StatusMessageModel . StatusSeverity . Warning ,
2020-03-26 15:42:54 +01:00
AllowDismiss = false ,
2020-03-30 08:31:30 +02:00
Html =
2020-04-06 06:19:51 +02:00
$"This transaction has been coordinated between the receiver and you to create a <a href='https://en.bitcoin.it/wiki/PayJoin' target='_blank'>payjoin transaction</a> by adding inputs from the receiver.<br/>" +
$"The amount being sent may appear higher but is in fact almost same.<br/><br/>" +
2020-04-26 22:18:36 +02:00
$"If you cancel or refuse to sign this transaction, the payment will proceed without payjoin"
2020-03-26 15:42:54 +01:00
} ) ;
2020-05-24 23:27:01 +02:00
vm . SigningContext . PSBT = proposedPayjoin . ToBase64 ( ) ;
vm . SigningContext . OriginalPSBT = psbt . ToBase64 ( ) ;
return ViewVault ( walletId , vm . SigningContext ) ;
2020-03-26 15:42:54 +01:00
}
2020-03-30 08:31:30 +02:00
}
2020-04-06 06:19:51 +02:00
catch ( PayjoinReceiverException ex )
{
error = $"The payjoin receiver could not complete the payjoin: {ex.Message}" ;
}
catch ( PayjoinSenderException ex )
{
error = $"We rejected the receiver's payjoin proposal: {ex.Message}" ;
}
catch ( Exception ex )
{
error = $"Unexpected payjoin error: {ex.Message}" ;
}
2020-06-28 10:55:27 +02:00
2020-04-06 06:19:51 +02:00
//we possibly exposed the tx to the receiver, so we need to broadcast straight away
psbt . Finalize ( ) ;
TempData . SetStatusMessageModel ( new StatusMessageModel ( )
{
Severity = StatusMessageModel . StatusSeverity . Warning ,
AllowDismiss = false ,
Html = $"The payjoin transaction could not be created.<br/>" +
$"The original transaction was broadcasted instead. ({psbt.ExtractTransaction().GetHash()})<br/><br/>" +
$"{error}"
} ) ;
return await WalletPSBTReady ( walletId , vm , "broadcast" ) ;
2020-03-30 08:31:30 +02:00
case "broadcast" when ! psbt . IsAllFinalized ( ) & & ! psbt . TryFinalize ( out var errors ) :
vm . SetErrors ( errors ) ;
2020-06-28 10:55:27 +02:00
return View ( nameof ( WalletPSBTReady ) , vm ) ;
2020-03-30 08:31:30 +02:00
case "broadcast" :
{
2020-06-28 10:55:27 +02:00
var transaction = psbt . ExtractTransaction ( ) ;
try
2020-03-30 08:31:30 +02:00
{
2020-06-28 10:55:27 +02:00
var broadcastResult = await ExplorerClientProvider . GetExplorerClient ( network ) . BroadcastAsync ( transaction ) ;
if ( ! broadcastResult . Success )
2020-03-30 08:31:30 +02:00
{
2020-06-28 10:55:27 +02:00
if ( ! string . IsNullOrEmpty ( vm . SigningContext . OriginalPSBT ) )
2020-03-30 08:31:30 +02:00
{
2020-06-28 10:55:27 +02:00
TempData . SetStatusMessageModel ( new StatusMessageModel ( )
{
Severity = StatusMessageModel . StatusSeverity . Warning ,
AllowDismiss = false ,
Html = $"The payjoin transaction could not be broadcasted.<br/>({broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}).<br/>The transaction has been reverted back to its original format and has been broadcast."
} ) ;
vm . SigningContext . PSBT = vm . SigningContext . OriginalPSBT ;
vm . SigningContext . OriginalPSBT = null ;
return await WalletPSBTReady ( walletId , vm , "broadcast" ) ;
}
vm . GlobalError = $"RPC Error while broadcasting: {broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}" ;
return View ( nameof ( WalletPSBTReady ) , vm ) ;
2020-03-30 08:31:30 +02:00
}
}
2020-06-28 10:55:27 +02:00
catch ( Exception ex )
{
vm . GlobalError = "Error while broadcasting: " + ex . Message ;
return View ( nameof ( WalletPSBTReady ) , vm ) ;
}
2020-04-06 06:19:51 +02:00
2020-06-28 10:55:27 +02:00
if ( ! TempData . HasStatusMessage ( ) )
{
TempData [ WellKnownTempData . SuccessMessage ] = $"Transaction broadcasted successfully ({transaction.GetHash()})" ;
}
return RedirectToWalletTransaction ( walletId , transaction ) ;
2020-04-06 06:19:51 +02:00
}
2020-03-30 08:31:30 +02:00
case "analyze-psbt" :
2020-05-24 21:55:28 +02:00
return RedirectToWalletPSBT ( new WalletPSBTViewModel ( )
{
2020-05-25 00:05:01 +02:00
PSBT = psbt . ToBase64 ( )
2020-05-24 21:55:28 +02:00
} ) ;
2020-03-30 08:31:30 +02:00
default :
vm . GlobalError = "Unknown command" ;
2020-06-28 10:55:27 +02:00
return View ( nameof ( WalletPSBTReady ) , vm ) ;
2019-05-12 04:13:04 +02:00
}
}
private IActionResult FilePSBT ( PSBT psbt , string fileName )
{
return File ( psbt . ToBytes ( ) , "application/octet-stream" , fileName ) ;
}
2019-05-12 06:13:52 +02:00
2021-06-14 07:06:56 +02:00
[HttpPost("{walletId}/psbt/combine")]
2019-05-12 06:13:52 +02:00
public async Task < IActionResult > WalletPSBTCombine ( [ ModelBinder ( typeof ( WalletIdModelBinder ) ) ]
WalletId walletId , WalletPSBTCombineViewModel vm )
{
2019-05-29 11:43:50 +02:00
var network = NetworkProvider . GetNetwork < BTCPayNetwork > ( walletId . CryptoCode ) ;
2019-05-12 06:13:52 +02:00
var psbt = await vm . GetPSBT ( network . NBitcoinNetwork ) ;
if ( psbt = = null )
{
ModelState . AddModelError ( nameof ( vm . PSBT ) , "Invalid PSBT" ) ;
return View ( vm ) ;
}
var sourcePSBT = vm . GetSourcePSBT ( network . NBitcoinNetwork ) ;
if ( sourcePSBT = = null )
{
ModelState . AddModelError ( nameof ( vm . OtherPSBT ) , "Invalid PSBT" ) ;
return View ( vm ) ;
}
sourcePSBT = sourcePSBT . Combine ( psbt ) ;
2019-10-31 04:29:59 +01:00
TempData [ WellKnownTempData . SuccessMessage ] = "PSBT Successfully combined!" ;
2020-05-24 21:55:28 +02:00
return RedirectToWalletPSBT ( new WalletPSBTViewModel ( )
{
PSBT = sourcePSBT . ToBase64 ( )
} ) ;
2019-05-12 06:13:52 +02:00
}
2020-05-23 21:31:21 +02:00
private async Task < IActionResult > TryHandleSigningCommands ( WalletId walletId , PSBT psbt , string command ,
2021-06-14 07:06:56 +02:00
SigningContextModel signingContext , Dictionary < string , string > routeBack )
2020-05-23 21:31:21 +02:00
{
2020-05-24 23:27:01 +02:00
signingContext . PSBT = psbt . ToBase64 ( ) ;
2020-06-28 10:55:27 +02:00
switch ( command )
2020-05-23 21:31:21 +02:00
{
2021-06-14 07:06:56 +02:00
case "sign" :
return View ( "WalletSigningOptions" , new WalletSigningOptionsModel ( signingContext , routeBack ) ) ;
2020-05-23 21:31:21 +02:00
case "vault" :
2020-05-24 23:27:01 +02:00
return ViewVault ( walletId , signingContext ) ;
2020-05-23 21:31:21 +02:00
case "seed" :
2020-05-24 23:27:01 +02:00
return SignWithSeed ( walletId , signingContext ) ;
2020-05-23 21:31:21 +02:00
case "nbx-seed" :
if ( await CanUseHotWallet ( ) )
{
var derivationScheme = GetDerivationSchemeSettings ( walletId ) ;
2020-06-07 02:38:15 +02:00
if ( derivationScheme . IsHotWallet )
{
var extKey = await ExplorerClientProvider . GetExplorerClient ( walletId . CryptoCode )
. GetMetadataAsync < string > ( derivationScheme . AccountDerivation ,
WellknownMetadataKeys . MasterHDKey ) ;
return SignWithSeed ( walletId ,
2021-06-14 07:06:56 +02:00
new SignWithSeedViewModel { SeedOrKey = extKey , SigningContext = signingContext } ) ;
2020-06-07 02:38:15 +02:00
}
2020-05-23 21:31:21 +02:00
}
2021-06-14 07:06:56 +02:00
TempData . SetStatusMessageModel ( new StatusMessageModel
2020-05-23 21:31:21 +02:00
{
Severity = StatusMessageModel . StatusSeverity . Error ,
Message = "NBX seed functionality is not available"
} ) ;
break ;
}
return null ;
}
2019-05-12 04:13:04 +02:00
}
}