2020-07-29 12:11:54 +02:00
using System ;
using System.Collections.Generic ;
using System.Globalization ;
using System.IO ;
using System.Linq ;
using System.Net ;
using System.Net.Http ;
using System.Runtime.CompilerServices ;
using System.Security ;
using System.Text ;
using System.Text.RegularExpressions ;
using System.Threading ;
using System.Threading.Tasks ;
using BTCPayServer.Client ;
using BTCPayServer.Client.Models ;
using BTCPayServer.Configuration ;
using BTCPayServer.Controllers ;
using BTCPayServer.Data ;
using BTCPayServer.Events ;
using BTCPayServer.HostedServices ;
using BTCPayServer.Lightning ;
using BTCPayServer.Models ;
using BTCPayServer.Models.AccountViewModels ;
using BTCPayServer.Models.AppViewModels ;
using BTCPayServer.Models.InvoicingModels ;
using BTCPayServer.Models.ServerViewModels ;
using BTCPayServer.Models.StoreViewModels ;
using BTCPayServer.Models.WalletViewModels ;
using BTCPayServer.Payments ;
using BTCPayServer.Payments.Bitcoin ;
using BTCPayServer.Payments.Lightning ;
using BTCPayServer.Rating ;
using BTCPayServer.Security.Bitpay ;
using BTCPayServer.Services ;
using BTCPayServer.Services.Apps ;
using BTCPayServer.Services.Invoices ;
using BTCPayServer.Services.Rates ;
using BTCPayServer.Tests.Logging ;
using BTCPayServer.U2F.Models ;
using BTCPayServer.Validation ;
using ExchangeSharp ;
using Microsoft.AspNetCore.Mvc ;
using Microsoft.EntityFrameworkCore ;
using NBitcoin ;
using NBitcoin.DataEncoders ;
using NBitcoin.Payment ;
2021-01-11 03:22:42 +01:00
using NBitcoin.Scripting.Parser ;
2020-07-29 12:11:54 +02:00
using NBitpayClient ;
using NBXplorer.DerivationStrategy ;
using NBXplorer.Models ;
using Newtonsoft.Json ;
using Newtonsoft.Json.Linq ;
using Newtonsoft.Json.Schema ;
using OpenQA.Selenium ;
using Xunit ;
using Xunit.Abstractions ;
using Xunit.Sdk ;
using RatesViewModel = BTCPayServer . Models . StoreViewModels . RatesViewModel ;
namespace BTCPayServer.Tests
{
public class AltcoinTests
{
public const int TestTimeout = 60_000 ;
2020-07-29 13:18:01 +02:00
public AltcoinTests ( ITestOutputHelper helper )
{
Logs . Tester = new XUnitLog ( helper ) { Name = "Tests" } ;
Logs . LogProvider = new XUnitLogProvider ( helper ) ;
}
2020-07-29 12:11:54 +02:00
[Fact]
[Trait("Integration", "Integration")]
[Trait("Altcoins", "Altcoins")]
[Trait("Lightning", "Lightning")]
public async Task CanAddDerivationSchemes ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . ActivateLTC ( ) ;
tester . ActivateLightning ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
user . RegisterDerivationScheme ( "LTC" ) ;
user . RegisterLightningNode ( "BTC" , LightningConnectionType . CLightning ) ;
var btcNetwork = tester . PayTester . Networks . GetNetwork < BTCPayNetwork > ( "BTC" ) ;
var invoice = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 1.5 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
Assert . Equal ( 3 , invoice . CryptoInfo . Length ) ;
var controller = user . GetController < StoresController > ( ) ;
var lightningVM =
( LightningNodeViewModel ) Assert . IsType < ViewResult > ( controller . AddLightningNode ( user . StoreId , "BTC" ) )
. Model ;
Assert . True ( lightningVM . Enabled ) ;
lightningVM . Enabled = false ;
controller . AddLightningNode ( user . StoreId , lightningVM , "save" , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ;
lightningVM =
( LightningNodeViewModel ) Assert . IsType < ViewResult > ( controller . AddLightningNode ( user . StoreId , "BTC" ) )
. Model ;
Assert . False ( lightningVM . Enabled ) ;
// Only Enabling/Disabling the payment method must redirect to store page
var derivationVM = ( DerivationSchemeViewModel ) Assert
. IsType < ViewResult > ( await controller . AddDerivationScheme ( user . StoreId , "BTC" ) ) . Model ;
Assert . True ( derivationVM . Enabled ) ;
derivationVM . Enabled = false ;
Assert . IsType < RedirectToActionResult > ( controller . AddDerivationScheme ( user . StoreId , derivationVM , "BTC" )
. GetAwaiter ( ) . GetResult ( ) ) ;
derivationVM = ( DerivationSchemeViewModel ) Assert
. IsType < ViewResult > ( await controller . AddDerivationScheme ( user . StoreId , "BTC" ) ) . Model ;
Assert . False ( derivationVM . Enabled ) ;
// Clicking next without changing anything should send to the confirmation screen
derivationVM = ( DerivationSchemeViewModel ) Assert
. IsType < ViewResult > ( await controller . AddDerivationScheme ( user . StoreId , "BTC" ) ) . Model ;
derivationVM = ( DerivationSchemeViewModel ) Assert . IsType < ViewResult > ( controller
. AddDerivationScheme ( user . StoreId , derivationVM , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ) . Model ;
Assert . True ( derivationVM . Confirmation ) ;
invoice = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 1.5 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
Assert . Single ( invoice . CryptoInfo ) ;
Assert . Equal ( "LTC" , invoice . CryptoInfo [ 0 ] . CryptoCode ) ;
// Removing the derivation scheme, should redirect to store page
var oldScheme = derivationVM . DerivationScheme ;
derivationVM = ( DerivationSchemeViewModel ) Assert
. IsType < ViewResult > ( await controller . AddDerivationScheme ( user . StoreId , "BTC" ) ) . Model ;
derivationVM . DerivationScheme = null ;
Assert . IsType < RedirectToActionResult > ( controller . AddDerivationScheme ( user . StoreId , derivationVM , "BTC" )
. GetAwaiter ( ) . GetResult ( ) ) ;
// Setting it again should redirect to the confirmation page
derivationVM = ( DerivationSchemeViewModel ) Assert
. IsType < ViewResult > ( await controller . AddDerivationScheme ( user . StoreId , "BTC" ) ) . Model ;
derivationVM . DerivationScheme = oldScheme ;
derivationVM = ( DerivationSchemeViewModel ) Assert . IsType < ViewResult > ( controller
. AddDerivationScheme ( user . StoreId , derivationVM , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ) . Model ;
Assert . True ( derivationVM . Confirmation ) ;
//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 ;
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 . WalletFile = TestUtils . GetFormFile ( "wallet.json" , content ) ;
derivationVM = ( DerivationSchemeViewModel ) Assert . IsType < ViewResult > ( controller
. AddDerivationScheme ( user . StoreId , derivationVM , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ) . Model ;
Assert . False ( derivationVM
. Confirmation ) ; // Should fail, we are giving a mainnet file to a testnet network
// And with a good file? (upub)
content =
"{\"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 . 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 )
#pragma warning disable CS0618 // Type or member is obsolete
. OfType < DerivationSchemeSettings > ( ) . First ( o = > o . PaymentId . IsBTCOnChain ) ;
#pragma warning restore CS0618 // Type or member is obsolete
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
invoice = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 1.5 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
tester . ExplorerNode . Generate ( 1 ) ;
var invoiceAddress = BitcoinAddress . Create ( invoice . CryptoInfo . First ( c = > c . CryptoCode = = "BTC" ) . Address ,
tester . ExplorerNode . Network ) ;
tester . ExplorerNode . SendToAddress ( invoiceAddress , Money . Coins ( 1 m ) ) ;
TestUtils . Eventually ( ( ) = >
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( "paid" , invoice . Status ) ;
} ) ;
var wallet = tester . PayTester . GetController < WalletsController > ( ) ;
var psbt = wallet . CreatePSBT ( btcNetwork , onchainBTC ,
new WalletSendModel ( )
{
Outputs = new List < WalletSendModel . TransactionOutput > ( )
{
new WalletSendModel . TransactionOutput ( )
{
Amount = 0.5 m ,
DestinationAddress = new Key ( ) . PubKey . GetAddress ( ScriptPubKeyType . Legacy , btcNetwork . NBitcoinNetwork )
. ToString ( ) ,
}
} ,
FeeSatoshiPerByte = 1
} , default ) . GetAwaiter ( ) . GetResult ( ) ;
Assert . NotNull ( psbt ) ;
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 ( ) . AsHDKeyCache ( ) ;
var account = root . Derive ( new KeyPath ( "m/49'/0'/0'" ) ) ;
Assert . All ( psbt . PSBT . Inputs , input = >
{
var keyPath = input . HDKeyPaths . Single ( ) ;
Assert . False ( keyPath . Value . KeyPath . IsHardened ) ;
Assert . Equal ( account . Derive ( keyPath . Value . KeyPath ) . GetPublicKey ( ) , keyPath . Key ) ;
Assert . Equal ( keyPath . Value . MasterFingerprint ,
onchainBTC . AccountKeySettings [ 0 ] . AccountKey . GetPublicKey ( ) . GetHDFingerPrint ( ) ) ;
} ) ;
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
[Trait("Altcoins", "Altcoins")]
[Trait("Lightning", "Lightning")]
public async Task CanCreateInvoiceWithSpecificPaymentMethods ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . ActivateLightning ( ) ;
tester . ActivateLTC ( ) ;
await tester . StartAsync ( ) ;
await tester . EnsureChannelsSetup ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterLightningNode ( "BTC" , LightningConnectionType . Charge ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
user . RegisterDerivationScheme ( "LTC" ) ;
var invoice = await user . BitPay . CreateInvoiceAsync ( new Invoice ( 100 , "BTC" ) ) ;
Assert . Equal ( 2 , invoice . SupportedTransactionCurrencies . Count ) ;
invoice = await user . BitPay . CreateInvoiceAsync ( new Invoice ( 100 , "BTC" )
{
SupportedTransactionCurrencies = new Dictionary < string , InvoiceSupportedTransactionCurrency > ( )
{
{ "BTC" , new InvoiceSupportedTransactionCurrency ( ) { Enabled = true } }
}
} ) ;
Assert . Single ( invoice . SupportedTransactionCurrencies ) ;
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
[Trait("Altcoins", "Altcoins")]
public async Task CanHaveLTCOnlyStore ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . ActivateLTC ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "LTC" ) ;
// First we try payment with a merchant having only BTC
var invoice = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 500 ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
Assert . Single ( invoice . CryptoInfo ) ;
Assert . Equal ( "LTC" , invoice . CryptoInfo [ 0 ] . CryptoCode ) ;
Assert . True ( invoice . PaymentCodes . ContainsKey ( "LTC" ) ) ;
Assert . True ( invoice . SupportedTransactionCurrencies . ContainsKey ( "LTC" ) ) ;
Assert . True ( invoice . SupportedTransactionCurrencies [ "LTC" ] . Enabled ) ;
Assert . True ( invoice . PaymentSubtotals . ContainsKey ( "LTC" ) ) ;
Assert . True ( invoice . PaymentTotals . ContainsKey ( "LTC" ) ) ;
var cashCow = tester . LTCExplorerNode ;
var invoiceAddress = BitcoinAddress . Create ( invoice . CryptoInfo [ 0 ] . Address , cashCow . Network ) ;
var firstPayment = Money . Coins ( 0.1 m ) ;
cashCow . SendToAddress ( invoiceAddress , firstPayment ) ;
TestUtils . Eventually ( ( ) = >
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( firstPayment , invoice . CryptoInfo [ 0 ] . Paid ) ;
} ) ;
Assert . Single ( invoice . CryptoInfo ) ; // Only BTC should be presented
var controller = tester . PayTester . GetController < InvoiceController > ( null ) ;
var checkout =
( Models . InvoicingModels . PaymentModel ) ( ( JsonResult ) controller . GetStatus ( invoice . Id , null )
. GetAwaiter ( ) . GetResult ( ) ) . Value ;
Assert . Single ( checkout . AvailableCryptos ) ;
Assert . Equal ( "LTC" , checkout . CryptoCode ) ;
//////////////////////
// Despite it is called BitcoinAddress it should be LTC because BTC is not available
Assert . Null ( invoice . BitcoinAddress ) ;
Assert . NotEqual ( 1.0 m , invoice . Rate ) ;
Assert . NotEqual ( invoice . BtcDue , invoice . CryptoInfo [ 0 ] . Due ) ; // Should be BTC rate
cashCow . SendToAddress ( invoiceAddress , invoice . CryptoInfo [ 0 ] . Due ) ;
TestUtils . Eventually ( ( ) = >
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( "paid" , invoice . Status ) ;
checkout = ( Models . InvoicingModels . PaymentModel ) ( ( JsonResult ) controller . GetStatus ( invoice . Id , null )
. GetAwaiter ( ) . GetResult ( ) ) . Value ;
Assert . Equal ( "paid" , checkout . Status ) ;
} ) ;
}
}
[Fact]
[Trait("Selenium", "Selenium")]
[Trait("Altcoins", "Altcoins")]
public async Task CanCreateRefunds ( )
{
using ( var s = SeleniumTester . Create ( ) )
{
s . Server . ActivateLTC ( ) ;
await s . StartAsync ( ) ;
var user = s . Server . NewAccount ( ) ;
await user . GrantAccessAsync ( ) ;
s . GoToLogin ( ) ;
s . Login ( user . RegisterDetails . Email , user . RegisterDetails . Password ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
await s . Server . ExplorerNode . GenerateAsync ( 1 ) ;
foreach ( var multiCurrency in new [ ] { false , true } )
{
if ( multiCurrency )
user . RegisterDerivationScheme ( "LTC" ) ;
foreach ( var rateSelection in new [ ] { "FiatText" , "CurrentRateText" , "RateThenText" } )
await CanCreateRefundsCore ( s , user , multiCurrency , rateSelection ) ;
}
}
}
private static async Task CanCreateRefundsCore ( SeleniumTester s , TestAccount user , bool multiCurrency , string rateSelection )
{
s . GoToHome ( ) ;
s . Server . PayTester . ChangeRate ( "BTC_USD" , new Rating . BidAsk ( 5000.0 m , 5100.0 m ) ) ;
var invoice = await user . BitPay . CreateInvoiceAsync ( new NBitpayClient . Invoice ( )
{
Currency = "USD" ,
Price = 5000.0 m
} ) ;
var info = invoice . CryptoInfo . First ( o = > o . CryptoCode = = "BTC" ) ;
var totalDue = decimal . Parse ( info . TotalDue , CultureInfo . InvariantCulture ) ;
var paid = totalDue + 0.1 m ;
await s . Server . ExplorerNode . SendToAddressAsync ( BitcoinAddress . Create ( info . Address , Network . RegTest ) , Money . Coins ( paid ) ) ;
await s . Server . ExplorerNode . GenerateAsync ( 1 ) ;
await TestUtils . EventuallyAsync ( async ( ) = >
{
invoice = await user . BitPay . GetInvoiceAsync ( invoice . Id ) ;
Assert . Equal ( "confirmed" , invoice . Status ) ;
} ) ;
// BTC crash by 50%
s . Server . PayTester . ChangeRate ( "BTC_USD" , new Rating . BidAsk ( 5000.0 m / 2.0 m , 5100.0 m / 2.0 m ) ) ;
s . GoToInvoice ( invoice . Id ) ;
s . Driver . FindElement ( By . Id ( "refundlink" ) ) . Click ( ) ;
if ( multiCurrency )
{
s . Driver . FindElement ( By . Id ( "SelectedPaymentMethod" ) ) . SendKeys ( "BTC" + Keys . Enter ) ;
s . Driver . FindElement ( By . Id ( "ok" ) ) . Click ( ) ;
}
Assert . Contains ( "$5,500.00" , s . Driver . PageSource ) ; // Should propose reimburse in fiat
Assert . Contains ( "1.10000000 â‚¿" , s . Driver . PageSource ) ; // Should propose reimburse in BTC at the rate of before
Assert . Contains ( "2.20000000 â‚¿" , s . Driver . PageSource ) ; // Should propose reimburse in BTC at the current rate
s . Driver . FindElement ( By . Id ( rateSelection ) ) . Click ( ) ;
s . Driver . FindElement ( By . Id ( "ok" ) ) . Click ( ) ;
Assert . Contains ( "pull-payments" , s . Driver . Url ) ;
if ( rateSelection = = "FiatText" )
Assert . Contains ( "$5,500.00" , s . Driver . PageSource ) ;
if ( rateSelection = = "CurrentRateText" )
Assert . Contains ( "2.20000000 â‚¿" , s . Driver . PageSource ) ;
if ( rateSelection = = "RateThenText" )
Assert . Contains ( "1.10000000 â‚¿" , s . Driver . PageSource ) ;
s . GoToHome ( ) ;
s . GoToInvoices ( ) ;
s . GoToInvoice ( invoice . Id ) ;
s . Driver . FindElement ( By . Id ( "refundlink" ) ) . Click ( ) ;
Assert . Contains ( "pull-payments" , s . Driver . Url ) ;
}
[Fact(Timeout = TestTimeout)]
[Trait("Altcoins", "Altcoins")]
[Trait("Lightning", "Lightning")]
public async Task CanUsePaymentMethodDropdown ( )
{
using ( var s = SeleniumTester . Create ( ) )
{
s . Server . ActivateLTC ( ) ;
s . Server . ActivateLightning ( ) ;
await s . StartAsync ( ) ;
s . GoToRegister ( ) ;
s . RegisterNewUser ( ) ;
var store = s . CreateNewStore ( ) ;
s . AddDerivationScheme ( "BTC" ) ;
//check that there is no dropdown since only one payment method is set
var invoiceId = s . CreateInvoice ( store . storeName , 10 , "USD" , "a@g.com" ) ;
s . GoToInvoiceCheckout ( invoiceId ) ;
s . Driver . FindElement ( By . ClassName ( "payment__currencies_noborder" ) ) ;
s . GoToHome ( ) ;
s . GoToStore ( store . storeId ) ;
s . AddDerivationScheme ( "LTC" ) ;
s . AddLightningNode ( "BTC" , LightningConnectionType . CLightning ) ;
//there should be three now
invoiceId = s . CreateInvoice ( store . storeName , 10 , "USD" , "a@g.com" ) ;
s . GoToInvoiceCheckout ( invoiceId ) ;
2021-01-22 17:49:26 +01:00
var currencyDropdownButton = s . Driver . FindElement ( By . ClassName ( "payment__currencies" ) ) ;
2020-07-29 12:11:54 +02:00
Assert . Contains ( "BTC" , currencyDropdownButton . Text ) ;
currencyDropdownButton . Click ( ) ;
var elements = s . Driver . FindElement ( By . ClassName ( "vex-content" ) ) . FindElements ( By . ClassName ( "vexmenuitem" ) ) ;
Assert . Equal ( 3 , elements . Count ) ;
elements . Single ( element = > element . Text . Contains ( "LTC" ) ) . Click ( ) ;
2021-01-22 17:49:26 +01:00
currencyDropdownButton = s . Driver . FindElement ( By . ClassName ( "payment__currencies" ) ) ;
2020-07-29 12:11:54 +02:00
Assert . Contains ( "LTC" , currencyDropdownButton . Text ) ;
currencyDropdownButton . Click ( ) ;
elements = s . Driver . FindElement ( By . ClassName ( "vex-content" ) ) . FindElements ( By . ClassName ( "vexmenuitem" ) ) ;
elements . Single ( element = > element . Text . Contains ( "Lightning" ) ) . Click ( ) ;
2021-01-22 17:49:26 +01:00
currencyDropdownButton = s . Driver . FindElement ( By . ClassName ( "payment__currencies" ) ) ;
2020-07-29 12:11:54 +02:00
Assert . Contains ( "Lightning" , currencyDropdownButton . Text ) ;
s . Driver . Quit ( ) ;
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
[Trait("Altcoins", "Altcoins")]
public async Task CanPayWithTwoCurrencies ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . ActivateLTC ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
// First we try payment with a merchant having only BTC
var invoice = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 5000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
var cashCow = tester . ExplorerNode ;
cashCow . Generate ( 2 ) ; // get some money in case
var invoiceAddress = BitcoinAddress . Create ( invoice . BitcoinAddress , cashCow . Network ) ;
var firstPayment = Money . Coins ( 0.04 m ) ;
cashCow . SendToAddress ( invoiceAddress , firstPayment ) ;
TestUtils . Eventually ( ( ) = >
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . True ( invoice . BtcPaid = = firstPayment ) ;
} ) ;
Assert . Single ( invoice . CryptoInfo ) ; // Only BTC should be presented
var controller = tester . PayTester . GetController < InvoiceController > ( null ) ;
var checkout =
( Models . InvoicingModels . PaymentModel ) ( ( JsonResult ) controller . GetStatus ( invoice . Id , null )
. GetAwaiter ( ) . GetResult ( ) ) . Value ;
Assert . Single ( checkout . AvailableCryptos ) ;
Assert . Equal ( "BTC" , checkout . CryptoCode ) ;
Assert . Single ( invoice . PaymentCodes ) ;
Assert . Single ( invoice . SupportedTransactionCurrencies ) ;
Assert . Single ( invoice . SupportedTransactionCurrencies ) ;
Assert . Single ( invoice . PaymentSubtotals ) ;
Assert . Single ( invoice . PaymentTotals ) ;
Assert . True ( invoice . PaymentCodes . ContainsKey ( "BTC" ) ) ;
Assert . True ( invoice . SupportedTransactionCurrencies . ContainsKey ( "BTC" ) ) ;
Assert . True ( invoice . SupportedTransactionCurrencies [ "BTC" ] . Enabled ) ;
Assert . True ( invoice . PaymentSubtotals . ContainsKey ( "BTC" ) ) ;
Assert . True ( invoice . PaymentTotals . ContainsKey ( "BTC" ) ) ;
//////////////////////
// Retry now with LTC enabled
user . RegisterDerivationScheme ( "LTC" ) ;
invoice = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 5000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
cashCow = tester . ExplorerNode ;
invoiceAddress = BitcoinAddress . Create ( invoice . BitcoinAddress , cashCow . Network ) ;
firstPayment = Money . Coins ( 0.04 m ) ;
cashCow . SendToAddress ( invoiceAddress , firstPayment ) ;
Logs . Tester . LogInformation ( "First payment sent to " + invoiceAddress ) ;
TestUtils . Eventually ( ( ) = >
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . True ( invoice . BtcPaid = = firstPayment ) ;
} ) ;
cashCow = tester . LTCExplorerNode ;
var ltcCryptoInfo = invoice . CryptoInfo . FirstOrDefault ( c = > c . CryptoCode = = "LTC" ) ;
Assert . NotNull ( ltcCryptoInfo ) ;
invoiceAddress = BitcoinAddress . Create ( ltcCryptoInfo . Address , cashCow . Network ) ;
var secondPayment = Money . Coins ( decimal . Parse ( ltcCryptoInfo . Due , CultureInfo . InvariantCulture ) ) ;
cashCow . Generate ( 4 ) ; // LTC is not worth a lot, so just to make sure we have money...
cashCow . SendToAddress ( invoiceAddress , secondPayment ) ;
Logs . Tester . LogInformation ( "Second payment sent to " + invoiceAddress ) ;
TestUtils . Eventually ( ( ) = >
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( Money . Zero , invoice . BtcDue ) ;
var ltcPaid = invoice . CryptoInfo . First ( c = > c . CryptoCode = = "LTC" ) ;
Assert . Equal ( Money . Zero , ltcPaid . Due ) ;
Assert . Equal ( secondPayment , ltcPaid . CryptoPaid ) ;
Assert . Equal ( "paid" , invoice . Status ) ;
Assert . False ( ( bool ) ( ( JValue ) invoice . ExceptionStatus ) . Value ) ;
} ) ;
controller = tester . PayTester . GetController < InvoiceController > ( null ) ;
checkout = ( Models . InvoicingModels . PaymentModel ) ( ( JsonResult ) controller . GetStatus ( invoice . Id , "LTC" )
. GetAwaiter ( ) . GetResult ( ) ) . Value ;
Assert . Equal ( 2 , checkout . AvailableCryptos . Count ) ;
Assert . Equal ( "LTC" , checkout . CryptoCode ) ;
Assert . Equal ( 2 , invoice . PaymentCodes . Count ( ) ) ;
Assert . Equal ( 2 , invoice . SupportedTransactionCurrencies . Count ( ) ) ;
Assert . Equal ( 2 , invoice . SupportedTransactionCurrencies . Count ( ) ) ;
Assert . Equal ( 2 , invoice . PaymentSubtotals . Count ( ) ) ;
Assert . Equal ( 2 , invoice . PaymentTotals . Count ( ) ) ;
Assert . True ( invoice . PaymentCodes . ContainsKey ( "LTC" ) ) ;
Assert . True ( invoice . SupportedTransactionCurrencies . ContainsKey ( "LTC" ) ) ;
Assert . True ( invoice . SupportedTransactionCurrencies [ "LTC" ] . Enabled ) ;
Assert . True ( invoice . PaymentSubtotals . ContainsKey ( "LTC" ) ) ;
Assert . True ( invoice . PaymentTotals . ContainsKey ( "LTC" ) ) ;
// Check if we can disable LTC
invoice = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 5000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true ,
SupportedTransactionCurrencies = new Dictionary < string , InvoiceSupportedTransactionCurrency > ( )
{
{ "BTC" , new InvoiceSupportedTransactionCurrency ( ) { Enabled = true } }
}
} , Facade . Merchant ) ;
Assert . Single ( invoice . CryptoInfo . Where ( c = > c . CryptoCode = = "BTC" ) ) ;
Assert . Empty ( invoice . CryptoInfo . Where ( c = > c . CryptoCode = = "LTC" ) ) ;
}
}
[Fact]
[Trait("Integration", "Integration")]
[Trait("Altcoins", "Altcoins")]
public async Task CanUsePoSApp ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . ActivateLTC ( ) ;
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
user . RegisterDerivationScheme ( "LTC" ) ;
var apps = user . GetController < AppsController > ( ) ;
var vm = Assert . IsType < CreateAppViewModel > ( Assert . IsType < ViewResult > ( apps . CreateApp ( ) . Result ) . Model ) ;
vm . Name = "test" ;
vm . SelectedAppType = AppType . PointOfSale . ToString ( ) ;
Assert . IsType < RedirectToActionResult > ( apps . CreateApp ( vm ) . Result ) ;
var appId = Assert . IsType < ListAppsViewModel > ( Assert . IsType < ViewResult > ( apps . ListApps ( ) . Result ) . Model )
. Apps [ 0 ] . Id ;
var vmpos = Assert . IsType < UpdatePointOfSaleViewModel > ( Assert
. IsType < ViewResult > ( apps . UpdatePointOfSale ( appId ) . Result ) . Model ) ;
vmpos . Title = "hello" ;
vmpos . Currency = "CAD" ;
vmpos . ButtonText = "{0} Purchase" ;
vmpos . CustomButtonText = "Nicolas Sexy Hair" ;
vmpos . CustomTipText = "Wanna tip?" ;
vmpos . CustomTipPercentages = "15,18,20" ;
vmpos . Template = @ "
apple :
price : 5.0
title : good apple
orange :
price : 10.0
donation :
price : 1.02
custom : true
";
Assert . IsType < RedirectToActionResult > ( apps . UpdatePointOfSale ( appId , vmpos ) . Result ) ;
vmpos = Assert . IsType < UpdatePointOfSaleViewModel > ( Assert
. IsType < ViewResult > ( apps . UpdatePointOfSale ( appId ) . Result ) . Model ) ;
Assert . Equal ( "hello" , vmpos . Title ) ;
var publicApps = user . GetController < AppsPublicController > ( ) ;
var vmview =
Assert . IsType < ViewPointOfSaleViewModel > ( Assert
. IsType < ViewResult > ( publicApps . ViewPointOfSale ( appId , PosViewType . Cart ) . Result ) . Model ) ;
Assert . Equal ( "hello" , vmview . Title ) ;
Assert . Equal ( 3 , vmview . Items . Length ) ;
Assert . Equal ( "good apple" , vmview . Items [ 0 ] . Title ) ;
Assert . Equal ( "orange" , vmview . Items [ 1 ] . Title ) ;
Assert . Equal ( 10.0 m , vmview . Items [ 1 ] . Price . Value ) ;
Assert . Equal ( "$5.00" , vmview . Items [ 0 ] . Price . Formatted ) ;
Assert . Equal ( "{0} Purchase" , vmview . ButtonText ) ;
Assert . Equal ( "Nicolas Sexy Hair" , vmview . CustomButtonText ) ;
Assert . Equal ( "Wanna tip?" , vmview . CustomTipText ) ;
Assert . Equal ( "15,18,20" , string . Join ( ',' , vmview . CustomTipPercentages ) ) ;
Assert . IsType < RedirectToActionResult > ( publicApps
. ViewPointOfSale ( appId , PosViewType . Cart , 0 , null , null , null , null , "orange" ) . Result ) ;
//
var invoices = user . BitPay . GetInvoices ( ) ;
var orangeInvoice = invoices . First ( ) ;
Assert . Equal ( 10.00 m , orangeInvoice . Price ) ;
Assert . Equal ( "CAD" , orangeInvoice . Currency ) ;
Assert . Equal ( "orange" , orangeInvoice . ItemDesc ) ;
Assert . IsType < RedirectToActionResult > ( publicApps
. ViewPointOfSale ( appId , PosViewType . Cart , 0 , null , null , null , null , "apple" ) . Result ) ;
invoices = user . BitPay . GetInvoices ( ) ;
var appleInvoice = invoices . SingleOrDefault ( invoice = > invoice . ItemCode . Equals ( "apple" ) ) ;
Assert . NotNull ( appleInvoice ) ;
Assert . Equal ( "good apple" , appleInvoice . ItemDesc ) ;
// testing custom amount
var action = Assert . IsType < RedirectToActionResult > ( publicApps
. ViewPointOfSale ( appId , PosViewType . Cart , 6.6 m , null , null , null , null , "donation" ) . Result ) ;
Assert . Equal ( nameof ( InvoiceController . Checkout ) , action . ActionName ) ;
invoices = user . BitPay . GetInvoices ( ) ;
var donationInvoice = invoices . Single ( i = > i . Price = = 6.6 m ) ;
Assert . NotNull ( donationInvoice ) ;
Assert . Equal ( "CAD" , donationInvoice . Currency ) ;
Assert . Equal ( "donation" , donationInvoice . ItemDesc ) ;
foreach ( var test in new [ ]
{
( Code : "EUR" , ExpectedSymbol : "€" , ExpectedDecimalSeparator : "," , ExpectedDivisibility : 2 ,
ExpectedThousandSeparator : "\xa0" , ExpectedPrefixed : false , ExpectedSymbolSpace : true ) ,
( Code : "INR" , ExpectedSymbol : "₹" , ExpectedDecimalSeparator : "." , ExpectedDivisibility : 2 ,
ExpectedThousandSeparator : "," , ExpectedPrefixed : true , ExpectedSymbolSpace : true ) ,
( Code : "JPY" , ExpectedSymbol : "Â¥" , ExpectedDecimalSeparator : "." , ExpectedDivisibility : 0 ,
ExpectedThousandSeparator : "," , ExpectedPrefixed : true , ExpectedSymbolSpace : false ) ,
( Code : "BTC" , ExpectedSymbol : "â‚¿" , ExpectedDecimalSeparator : "." , ExpectedDivisibility : 8 ,
ExpectedThousandSeparator : "," , ExpectedPrefixed : false , ExpectedSymbolSpace : true ) ,
} )
{
Logs . Tester . LogInformation ( $"Testing for {test.Code}" ) ;
vmpos = Assert . IsType < UpdatePointOfSaleViewModel > ( Assert
. IsType < ViewResult > ( apps . UpdatePointOfSale ( appId ) . Result ) . Model ) ;
vmpos . Title = "hello" ;
vmpos . Currency = test . Item1 ;
vmpos . ButtonText = "{0} Purchase" ;
vmpos . CustomButtonText = "Nicolas Sexy Hair" ;
vmpos . CustomTipText = "Wanna tip?" ;
vmpos . Template = @ "
apple :
price : 1000.0
title : good apple
orange :
price : 10.0
donation :
price : 1.02
custom : true
";
Assert . IsType < RedirectToActionResult > ( apps . UpdatePointOfSale ( appId , vmpos ) . Result ) ;
publicApps = user . GetController < AppsPublicController > ( ) ;
vmview = Assert . IsType < ViewPointOfSaleViewModel > ( Assert
. IsType < ViewResult > ( publicApps . ViewPointOfSale ( appId , PosViewType . Cart ) . Result ) . Model ) ;
Assert . Equal ( test . Code , vmview . CurrencyCode ) ;
Assert . Equal ( test . ExpectedSymbol ,
vmview . CurrencySymbol . Replace ( "ï¿¥" , "Â¥" ) ) ; // Hack so JPY test pass on linux as well);
Assert . Equal ( test . ExpectedSymbol ,
vmview . CurrencyInfo . CurrencySymbol
. Replace ( "ï¿¥" , "Â¥" ) ) ; // Hack so JPY test pass on linux as well);
Assert . Equal ( test . ExpectedDecimalSeparator , vmview . CurrencyInfo . DecimalSeparator ) ;
Assert . Equal ( test . ExpectedThousandSeparator , vmview . CurrencyInfo . ThousandSeparator ) ;
Assert . Equal ( test . ExpectedPrefixed , vmview . CurrencyInfo . Prefixed ) ;
Assert . Equal ( test . ExpectedDivisibility , vmview . CurrencyInfo . Divisibility ) ;
Assert . Equal ( test . ExpectedSymbolSpace , vmview . CurrencyInfo . SymbolSpace ) ;
}
//test inventory related features
vmpos = Assert . IsType < UpdatePointOfSaleViewModel > ( Assert
. IsType < ViewResult > ( apps . UpdatePointOfSale ( appId ) . Result ) . Model ) ;
vmpos . Title = "hello" ;
vmpos . Currency = "BTC" ;
vmpos . Template = @ "
inventoryitem :
price : 1.0
title : good apple
inventory : 1
noninventoryitem :
price : 10.0 ";
Assert . IsType < RedirectToActionResult > ( apps . UpdatePointOfSale ( appId , vmpos ) . Result ) ;
//inventoryitem has 1 item available
2020-12-08 10:02:03 +01:00
await tester . WaitForEvent < AppInventoryUpdaterHostedService . UpdateAppInventory > ( ( ) = >
2020-09-24 09:43:39 +02:00
{
Assert . IsType < RedirectToActionResult > ( publicApps
. ViewPointOfSale ( appId , PosViewType . Cart , 1 , null , null , null , null , "inventoryitem" ) . Result ) ;
return Task . CompletedTask ;
} ) ;
2020-07-29 12:11:54 +02:00
//we already bought all available stock so this should fail
await Task . Delay ( 100 ) ;
Assert . IsType < RedirectToActionResult > ( publicApps
. ViewPointOfSale ( appId , PosViewType . Cart , 1 , null , null , null , null , "inventoryitem" ) . Result ) ;
//inventoryitem has unlimited items available
Assert . IsType < RedirectToActionResult > ( publicApps
. ViewPointOfSale ( appId , PosViewType . Cart , 1 , null , null , null , null , "noninventoryitem" ) . Result ) ;
Assert . IsType < RedirectToActionResult > ( publicApps
. ViewPointOfSale ( appId , PosViewType . Cart , 1 , null , null , null , null , "noninventoryitem" ) . Result ) ;
//verify invoices where created
invoices = user . BitPay . GetInvoices ( ) ;
Assert . Equal ( 2 , invoices . Count ( invoice = > invoice . ItemCode . Equals ( "noninventoryitem" ) ) ) ;
var inventoryItemInvoice =
Assert . Single ( invoices . Where ( invoice = > invoice . ItemCode . Equals ( "inventoryitem" ) ) ) ;
Assert . NotNull ( inventoryItemInvoice ) ;
//let's mark the inventoryitem invoice as invalid, thsi should return the item to back in stock
var controller = tester . PayTester . GetController < InvoiceController > ( user . UserId , user . StoreId ) ;
var appService = tester . PayTester . GetService < AppService > ( ) ;
var eventAggregator = tester . PayTester . GetService < EventAggregator > ( ) ;
Assert . IsType < JsonResult > ( await controller . ChangeInvoiceState ( inventoryItemInvoice . Id , "invalid" ) ) ;
//check that item is back in stock
TestUtils . Eventually ( ( ) = >
{
vmpos = Assert . IsType < UpdatePointOfSaleViewModel > ( Assert
. IsType < ViewResult > ( apps . UpdatePointOfSale ( appId ) . Result ) . Model ) ;
Assert . Equal ( 1 ,
appService . Parse ( vmpos . Template , "BTC" ) . Single ( item = > item . Id = = "inventoryitem" ) . Inventory ) ;
} , 10000 ) ;
//test payment methods option
vmpos = Assert . IsType < UpdatePointOfSaleViewModel > ( Assert
. IsType < ViewResult > ( apps . UpdatePointOfSale ( appId ) . Result ) . Model ) ;
vmpos . Title = "hello" ;
vmpos . Currency = "BTC" ;
vmpos . Template = @ "
btconly :
price : 1.0
title : good apple
payment_methods :
- BTC
normal :
price : 1.0 ";
Assert . IsType < RedirectToActionResult > ( apps . UpdatePointOfSale ( appId , vmpos ) . Result ) ;
Assert . IsType < RedirectToActionResult > ( publicApps
. ViewPointOfSale ( appId , PosViewType . Cart , 1 , null , null , null , null , "btconly" ) . Result ) ;
Assert . IsType < RedirectToActionResult > ( publicApps
. ViewPointOfSale ( appId , PosViewType . Cart , 1 , null , null , null , null , "normal" ) . Result ) ;
invoices = user . BitPay . GetInvoices ( ) ;
var normalInvoice = invoices . Single ( invoice = > invoice . ItemCode = = "normal" ) ;
var btcOnlyInvoice = invoices . Single ( invoice = > invoice . ItemCode = = "btconly" ) ;
Assert . Single ( btcOnlyInvoice . CryptoInfo ) ;
Assert . Equal ( "BTC" ,
btcOnlyInvoice . CryptoInfo . First ( ) . CryptoCode ) ;
Assert . Equal ( PaymentTypes . BTCLike . ToString ( ) ,
btcOnlyInvoice . CryptoInfo . First ( ) . PaymentType ) ;
Assert . Equal ( 2 , normalInvoice . CryptoInfo . Length ) ;
Assert . Contains (
normalInvoice . CryptoInfo ,
s = > PaymentTypes . BTCLike . ToString ( ) = = s . PaymentType & & new [ ] { "BTC" , "LTC" } . Contains (
s . CryptoCode ) ) ;
}
}
2020-07-29 13:01:51 +02:00
[Fact]
[Trait("Fast", "Fast")]
[Trait("Altcoins", "Altcoins")]
public void CanCalculateCryptoDue2 ( )
{
#pragma warning disable CS0618
var dummy = new Key ( ) . PubKey . GetAddress ( ScriptPubKeyType . Legacy , Network . RegTest ) . ToString ( ) ;
2021-01-27 06:39:38 +01:00
var networkProvider = new BTCPayNetworkProvider ( ChainName . Regtest ) ;
2020-07-29 13:01:51 +02:00
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary ( new IPaymentMethodHandler [ ]
{
new BitcoinLikePaymentHandler ( null , networkProvider , null , null , null ) ,
2020-11-06 11:09:17 +01:00
new LightningLikePaymentHandler ( null , null , networkProvider , null , null ) ,
2020-07-29 13:01:51 +02:00
} ) ;
var networkBTC = networkProvider . GetNetwork ( "BTC" ) ;
var networkLTC = networkProvider . GetNetwork ( "LTC" ) ;
InvoiceEntity invoiceEntity = new InvoiceEntity ( ) ;
invoiceEntity . Networks = networkProvider ;
invoiceEntity . Payments = new System . Collections . Generic . List < PaymentEntity > ( ) ;
2020-08-26 14:24:37 +02:00
invoiceEntity . Price = 100 ;
2020-07-29 13:01:51 +02:00
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary ( ) ;
paymentMethods . Add ( new PaymentMethod ( ) { Network = networkBTC , CryptoCode = "BTC" , Rate = 10513.44 m , }
. SetPaymentMethodDetails (
new BTCPayServer . Payments . Bitcoin . BitcoinLikeOnChainPaymentMethod ( )
{
NextNetworkFee = Money . Coins ( 0.00000100 m ) ,
DepositAddress = dummy
} ) ) ;
paymentMethods . Add ( new PaymentMethod ( ) { Network = networkLTC , CryptoCode = "LTC" , Rate = 216.79 m }
. SetPaymentMethodDetails (
new BTCPayServer . Payments . Bitcoin . BitcoinLikeOnChainPaymentMethod ( )
{
NextNetworkFee = Money . Coins ( 0.00010000 m ) ,
DepositAddress = dummy
} ) ) ;
invoiceEntity . SetPaymentMethods ( paymentMethods ) ;
var btc = invoiceEntity . GetPaymentMethod ( new PaymentMethodId ( "BTC" , PaymentTypes . BTCLike ) ) ;
var accounting = btc . Calculate ( ) ;
invoiceEntity . Payments . Add (
new PaymentEntity ( )
{
Accounted = true ,
CryptoCode = "BTC" ,
NetworkFee = 0.00000100 m ,
Network = networkProvider . GetNetwork ( "BTC" ) ,
}
. SetCryptoPaymentData ( new BitcoinLikePaymentData ( )
{
Network = networkProvider . GetNetwork ( "BTC" ) ,
Output = new TxOut ( ) { Value = Money . Coins ( 0.00151263 m ) }
} ) ) ;
accounting = btc . Calculate ( ) ;
invoiceEntity . Payments . Add (
new PaymentEntity ( )
{
Accounted = true ,
CryptoCode = "BTC" ,
NetworkFee = 0.00000100 m ,
Network = networkProvider . GetNetwork ( "BTC" )
}
. SetCryptoPaymentData ( new BitcoinLikePaymentData ( )
{
Network = networkProvider . GetNetwork ( "BTC" ) ,
Output = new TxOut ( ) { Value = accounting . Due }
} ) ) ;
accounting = btc . Calculate ( ) ;
Assert . Equal ( Money . Zero , accounting . Due ) ;
Assert . Equal ( Money . Zero , accounting . DueUncapped ) ;
var ltc = invoiceEntity . GetPaymentMethod ( new PaymentMethodId ( "LTC" , PaymentTypes . BTCLike ) ) ;
accounting = ltc . Calculate ( ) ;
Assert . Equal ( Money . Zero , accounting . Due ) ;
// LTC might have over paid due to BTC paying above what it should (round 1 satoshi up)
Assert . True ( accounting . DueUncapped < Money . Zero ) ;
var paymentMethod = InvoiceWatcher . GetNearestClearedPayment ( paymentMethods , out var accounting2 ) ;
Assert . Equal ( btc . CryptoCode , paymentMethod . CryptoCode ) ;
#pragma warning restore CS0618
}
[Fact]
[Trait("Fast", "Fast")]
[Trait("Altcoins", "Altcoins")]
public void CanParseDerivationScheme ( )
{
2021-01-27 06:39:38 +01:00
var testnetNetworkProvider = new BTCPayNetworkProvider ( ChainName . Testnet ) ;
var regtestNetworkProvider = new BTCPayNetworkProvider ( ChainName . Regtest ) ;
var mainnetNetworkProvider = new BTCPayNetworkProvider ( ChainName . Mainnet ) ;
2020-07-29 13:01:51 +02:00
var testnetParser = new DerivationSchemeParser ( testnetNetworkProvider . GetNetwork < BTCPayNetwork > ( "BTC" ) ) ;
var mainnetParser = new DerivationSchemeParser ( mainnetNetworkProvider . GetNetwork < BTCPayNetwork > ( "BTC" ) ) ;
NBXplorer . DerivationStrategy . DerivationStrategyBase result ;
// Passing electrum stuff
// Passing a native segwit from mainnet to a testnet parser, means the testnet parser will try to convert it into segwit
result = testnetParser . Parse (
"zpub6nL6PUGurpU3DfPDSZaRS6WshpbNc9ctCFFzrCn54cssnheM31SZJZUcFHKtjJJNhAueMbh6ptFMfy1aeiMQJr3RJ4DDt1hAPx7sMTKV48t" ) ;
Assert . Equal (
"tpubD93CJNkmGjLXnsBqE2zGDqfEh1Q8iJ8wueordy3SeWt1RngbbuxXCsqASuVWFywmfoCwUE1rSfNJbaH4cBNcbp8WcyZgPiiRSTazLGL8U9w" ,
result . ToString ( ) ) ;
result = mainnetParser . Parse (
"zpub6nL6PUGurpU3DfPDSZaRS6WshpbNc9ctCFFzrCn54cssnheM31SZJZUcFHKtjJJNhAueMbh6ptFMfy1aeiMQJr3RJ4DDt1hAPx7sMTKV48t" ) ;
Assert . Equal (
"xpub68fZn8w5ZTP5X4zymr1B1vKsMtJUiudtN2DZHQzJJc87gW1tXh7S4SALCsQijUzXstg2reVyuZYFuPnTDKXNiNgDZNpNiC4BrVzaaGEaRHj" ,
result . ToString ( ) ) ;
// P2SH
result = testnetParser . Parse (
"upub57Wa4MvRPNyAipy1MCpERxcFpHR2ZatyikppkyeWkoRL6QJvLVMo39jYdcaJVxyvBURyRVmErBEA5oGicKBgk1j72GAXSPFH5tUDoGZ8nEu" ) ;
Assert . Equal (
"tpubD6NzVbkrYhZ4YWjDJUACG9E8fJx2NqNY1iynTiPKEjJrzzRKAgha3nNnwGXr2BtvCJKJHW4nmG7rRqc2AGGy2AECgt16seMyV2FZivUmaJg-[p2sh]" ,
result . ToString ( ) ) ;
result = mainnetParser . Parse (
"ypub6QqdH2c5z79681jUgdxjGJzGW9zpL4ryPCuhtZE4GpvrJoZqM823XQN6iSQeVbbbp2uCRQ9UgpeMcwiyV6qjvxTWVcxDn2XEAnioMUwsrQ5" ) ;
Assert . Equal (
"xpub661MyMwAqRbcGiYMrHB74DtmLBrNPSsUU6PV7ALAtpYyFhkc6TrUuLhxhET4VgwgQPnPfvYvEAHojf7QmQRj8imudHFoC7hju4f9xxri8wR-[p2sh]" ,
result . ToString ( ) ) ;
// if prefix not recognize, assume it is segwit
result = testnetParser . Parse (
"xpub661MyMwAqRbcGeVGU5e5KBcau1HHEUGf9Wr7k4FyLa8yRPNQrrVa7Ndrgg8Afbe2UYXMSL6tJBFd2JewwWASsePPLjkcJFL1tTVEs3UQ23X" ) ;
Assert . Equal (
"tpubD6NzVbkrYhZ4YSg7vGdAX6wxE8NwDrmih9SR6cK7gUtsAg37w5LfFpJgviCxC6bGGT4G3uckqH5fiV9ZLN1gm5qgQLVuymzFUR5ed7U7ksu" ,
result . ToString ( ) ) ;
////////////////
var tpub =
"tpubD6NzVbkrYhZ4Wc65tjhmcKdWFauAo7bGLRTxvggygkNyp6SMGutJp7iociwsinU33jyNBp1J9j2hJH5yQsayfiS3LEU2ZqXodAcnaygra8o" ;
result = testnetParser . Parse ( tpub ) ;
Assert . Equal ( tpub , result . ToString ( ) ) ;
testnetParser . HintScriptPubKey = BitcoinAddress
. Create ( "tb1q4s33amqm8l7a07zdxcunqnn3gcsjcfz3xc573l" , testnetParser . Network ) . ScriptPubKey ;
result = testnetParser . Parse ( tpub ) ;
Assert . Equal ( tpub , result . ToString ( ) ) ;
testnetParser . HintScriptPubKey = BitcoinAddress
. Create ( "2N2humNio3YTApSfY6VztQ9hQwDnhDvaqFQ" , testnetParser . Network ) . ScriptPubKey ;
result = testnetParser . Parse ( tpub ) ;
Assert . Equal ( $"{tpub}-[p2sh]" , result . ToString ( ) ) ;
testnetParser . HintScriptPubKey = BitcoinAddress
. Create ( "mwD8bHS65cdgUf6rZUUSoVhi3wNQFu1Nfi" , testnetParser . Network ) . ScriptPubKey ;
result = testnetParser . Parse ( tpub ) ;
Assert . Equal ( $"{tpub}-[legacy]" , result . ToString ( ) ) ;
testnetParser . HintScriptPubKey = BitcoinAddress
. Create ( "2N2humNio3YTApSfY6VztQ9hQwDnhDvaqFQ" , testnetParser . Network ) . ScriptPubKey ;
result = testnetParser . Parse ( $"{tpub}-[legacy]" ) ;
Assert . Equal ( $"{tpub}-[p2sh]" , result . ToString ( ) ) ;
result = testnetParser . Parse ( tpub ) ;
Assert . Equal ( $"{tpub}-[p2sh]" , result . ToString ( ) ) ;
var regtestParser = new DerivationSchemeParser ( regtestNetworkProvider . GetNetwork < BTCPayNetwork > ( "BTC" ) ) ;
var parsed =
regtestParser . Parse (
"xpub6DG1rMYXiQtCc6CfdLFD9CtxqhzzRh7j6Sq6EdE9abgYy3cfDRrniLLv2AdwqHL1exiLnnKR5XXcaoiiexf3Y9R6J6rxkJtqJHzNzMW9QMZ-[p2sh]" ) ;
Assert . Equal (
"tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[p2sh]" ,
parsed . ToString ( ) ) ;
// Let's make sure we can't generate segwit with dogecoin
regtestParser = new DerivationSchemeParser ( regtestNetworkProvider . GetNetwork < BTCPayNetwork > ( "DOGE" ) ) ;
parsed = regtestParser . Parse (
"xpub6DG1rMYXiQtCc6CfdLFD9CtxqhzzRh7j6Sq6EdE9abgYy3cfDRrniLLv2AdwqHL1exiLnnKR5XXcaoiiexf3Y9R6J6rxkJtqJHzNzMW9QMZ-[p2sh]" ) ;
Assert . Equal (
"tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[legacy]" ,
parsed . ToString ( ) ) ;
regtestParser = new DerivationSchemeParser ( regtestNetworkProvider . GetNetwork < BTCPayNetwork > ( "DOGE" ) ) ;
parsed = regtestParser . Parse (
"tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[p2sh]" ) ;
Assert . Equal (
"tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[legacy]" ,
parsed . ToString ( ) ) ;
2021-01-11 03:22:42 +01:00
//let's test output descriptor parsing support
//we don't support every descriptor, only the ones which represent an HD wallet with stndard derivation paths
Assert . Throws < FormatException > ( ( ) = > mainnetParser . ParseOutputDescriptor ( "pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)" ) ) ;
Assert . Throws < FormatException > ( ( ) = > mainnetParser . ParseOutputDescriptor ( "pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)" ) ) ;
Assert . Throws < FormatException > ( ( ) = > mainnetParser . ParseOutputDescriptor ( "wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)" ) ) ;
Assert . Throws < FormatException > ( ( ) = > mainnetParser . ParseOutputDescriptor ( "sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))" ) ) ;
Assert . Throws < FormatException > ( ( ) = > mainnetParser . ParseOutputDescriptor ( "combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)" ) ) ;
Assert . Throws < FormatException > ( ( ) = > mainnetParser . ParseOutputDescriptor ( "sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))" ) ) ;
Assert . Throws < FormatException > ( ( ) = > mainnetParser . ParseOutputDescriptor ( "multi(1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)" ) ) ;
Assert . Throws < FormatException > ( ( ) = > mainnetParser . ParseOutputDescriptor ( "sh(multi(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))" ) ) ;
Assert . Throws < FormatException > ( ( ) = > mainnetParser . ParseOutputDescriptor ( "sh(sortedmulti(2,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01))" ) ) ;
//let's see what we actually support now
//standard legacy hd wallet
var parsedDescriptor = mainnetParser . ParseOutputDescriptor (
"pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)" ) ;
Assert . Equal ( KeyPath . Parse ( "44'/0'/0'" ) , Assert . Single ( parsedDescriptor . Item2 ) . KeyPath ) ;
Assert . Equal ( HDFingerprint . Parse ( "d34db33f" ) , Assert . Single ( parsedDescriptor . Item2 ) . MasterFingerprint ) ;
Assert . Equal ( "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]" , parsedDescriptor . Item1 . ToString ( ) ) ;
//masterfingerprint and key path are optional
parsedDescriptor = mainnetParser . ParseOutputDescriptor (
"pkh([d34db33f]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)" ) ;
Assert . Equal ( "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]" , parsedDescriptor . Item1 . ToString ( ) ) ;
//a master fingerprint must always be present if youre providing rooted path
Assert . Throws < ParsingException > ( ( ) = > mainnetParser . ParseOutputDescriptor ( "pkh([44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)" ) ) ;
parsedDescriptor = mainnetParser . ParseOutputDescriptor (
"pkh(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)" ) ;
Assert . Equal ( "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]" , parsedDescriptor . Item1 . ToString ( ) ) ;
//but a different deriv path from standard (0/*) is not supported
Assert . Throws < FormatException > ( ( ) = > mainnetParser . ParseOutputDescriptor ( "pkh(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)" ) ) ;
//p2sh-segwit hd wallet
parsedDescriptor = mainnetParser . ParseOutputDescriptor (
"sh(wpkh([d34db33f/49'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*))" ) ;
Assert . Equal ( KeyPath . Parse ( "49'/0'/0'" ) , Assert . Single ( parsedDescriptor . Item2 ) . KeyPath ) ;
Assert . Equal ( HDFingerprint . Parse ( "d34db33f" ) , Assert . Single ( parsedDescriptor . Item2 ) . MasterFingerprint ) ;
Assert . Equal ( "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[p2sh]" , parsedDescriptor . Item1 . ToString ( ) ) ;
//segwit hd wallet
parsedDescriptor = mainnetParser . ParseOutputDescriptor (
"wpkh([d34db33f/84'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)" ) ;
Assert . Equal ( KeyPath . Parse ( "84'/0'/0'" ) , Assert . Single ( parsedDescriptor . Item2 ) . KeyPath ) ;
Assert . Equal ( HDFingerprint . Parse ( "d34db33f" ) , Assert . Single ( parsedDescriptor . Item2 ) . MasterFingerprint ) ;
Assert . Equal ( "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL" , parsedDescriptor . Item1 . ToString ( ) ) ;
//multisig tests
//legacy
parsedDescriptor = mainnetParser . ParseOutputDescriptor (
"sh(multi(1,[d34db33f/45'/0]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,[d34db33f/45'/0]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*))" ) ;
Assert . Equal ( 2 , parsedDescriptor . Item2 . Length ) ;
var strat = Assert . IsType < MultisigDerivationStrategy > ( Assert . IsType < P2SHDerivationStrategy > ( parsedDescriptor . Item1 ) . Inner ) ;
Assert . True ( strat . IsLegacy ) ;
Assert . Equal ( 1 , strat . RequiredSignatures ) ;
Assert . Equal ( 2 , strat . Keys . Count ( ) ) ;
Assert . False ( strat . LexicographicOrder ) ;
Assert . Equal ( "1-of-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]-[keeporder]" , parsedDescriptor . Item1 . ToString ( ) ) ;
//segwit
parsedDescriptor = mainnetParser . ParseOutputDescriptor (
"wsh(multi(1,[d34db33f/48'/0'/0'/2']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,[d34db33f/48'/0'/0'/2']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*))" ) ;
Assert . Equal ( 2 , parsedDescriptor . Item2 . Length ) ;
strat = Assert . IsType < MultisigDerivationStrategy > ( Assert . IsType < P2WSHDerivationStrategy > ( parsedDescriptor . Item1 ) . Inner ) ;
Assert . False ( strat . IsLegacy ) ;
Assert . Equal ( 1 , strat . RequiredSignatures ) ;
Assert . Equal ( 2 , strat . Keys . Count ( ) ) ;
Assert . False ( strat . LexicographicOrder ) ;
Assert . Equal ( "1-of-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[keeporder]" , parsedDescriptor . Item1 . ToString ( ) ) ;
//segwit-p2sh
parsedDescriptor = mainnetParser . ParseOutputDescriptor (
"sh(wsh(multi(1,[d34db33f/48'/0'/0'/2']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,[d34db33f/48'/0'/0'/2']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)))" ) ;
Assert . Equal ( 2 , parsedDescriptor . Item2 . Length ) ;
strat = Assert . IsType < MultisigDerivationStrategy > ( Assert . IsType < P2WSHDerivationStrategy > ( Assert . IsType < P2SHDerivationStrategy > ( parsedDescriptor . Item1 ) . Inner ) . Inner ) ;
Assert . False ( strat . IsLegacy ) ;
Assert . Equal ( 1 , strat . RequiredSignatures ) ;
Assert . Equal ( 2 , strat . Keys . Count ( ) ) ;
Assert . False ( strat . LexicographicOrder ) ;
Assert . Equal ( "1-of-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[keeporder]-[p2sh]" , parsedDescriptor . Item1 . ToString ( ) ) ;
//sorted
parsedDescriptor = mainnetParser . ParseOutputDescriptor (
"sh(sortedmulti(1,[d34db33f/48'/0'/0'/1']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,[d34db33f/48'/0'/0'/1']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*))" ) ;
Assert . Equal ( "1-of-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]" , parsedDescriptor . Item1 . ToString ( ) ) ;
2020-07-29 13:01:51 +02:00
}
2020-07-29 12:11:54 +02:00
}
}