@@ -752,4 +752,110 @@ public async Task CanRestoreSwapsFromBoltz()
752752 var swapsAfterRestore = await swapStorage . GetSwaps ( walletIds : [ testingPrerequisite . walletIdentifier ] ) ;
753753 Assert . That ( swapsAfterRestore , Has . Count . GreaterThanOrEqualTo ( 1 ) ) ;
754754 }
755+
756+ // BOLT 12 submarine swap test.
757+ //
758+ // Nigiri ships a CLN container so the offer can be generated with:
759+ // docker exec cln lightning-cli --network=regtest offer any "nark bolt12 e2e"
760+ //
761+ // Pass the returned `bolt12` value via BOLT12_OFFER when running this test:
762+ // BOLT12_OFFER=lno1... dotnet test --filter CanPayBolt12OfferWithArkadeUsingBoltz
763+ //
764+ // The test is [Explicit] because Boltz must also be configured to use a BOLT12-capable
765+ // Lightning backend (CLN or LDK). The default regtest stack connects Boltz only to LND,
766+ // which does not support BOLT12.
767+ private const string Bolt12OfferEnv = "BOLT12_OFFER" ;
768+ private const long Bolt12TestAmountSats = 2_000 ;
769+
770+ [ Test ]
771+ [ Explicit ( "Requires BOLT12_OFFER env var and Boltz configured with a BOLT12-capable Lightning backend (CLN/LDK)" ) ]
772+ [ Category ( "Bolt12" ) ]
773+ public async Task CanPayBolt12OfferWithArkadeUsingBoltz ( )
774+ {
775+ var offer = Environment . GetEnvironmentVariable ( Bolt12OfferEnv )
776+ ?? await DockerHelper . CreateClnBolt12Offer ( amountSats : 0 , description : "nark bolt12 e2e" ) ;
777+
778+ var testingPrerequisite = await FundedWalletHelper . GetFundedWallet ( ) ;
779+ var swapStorage = TestStorage . CreateSwapStorage ( ) ;
780+ var boltzClient = new BoltzClient ( new HttpClient ( ) ,
781+ new OptionsWrapper < BoltzClientOptions > ( new BoltzClientOptions
782+ {
783+ BoltzUrl = SharedSwapInfrastructure . BoltzEndpoint . ToString ( ) ,
784+ WebsocketUrl = SharedSwapInfrastructure . BoltzWsEndpoint . ToString ( )
785+ } ) ) ;
786+ var intentStorage = TestStorage . CreateIntentStorage ( ) ;
787+ var chainTimeProvider = new NBXplorerBlockchain ( Network . RegTest , SharedArkInfrastructure . NbxplorerEndpoint ) ;
788+ var coinService = new CoinService (
789+ testingPrerequisite . clientTransport ,
790+ testingPrerequisite . contracts ,
791+ [
792+ new PaymentContractTransformer ( testingPrerequisite . walletProvider ) ,
793+ new HashLockedContractTransformer ( testingPrerequisite . walletProvider )
794+ ] ) ;
795+ var spendingService = new SpendingService (
796+ testingPrerequisite . vtxoStorage ,
797+ testingPrerequisite . contracts ,
798+ testingPrerequisite . walletProvider ,
799+ coinService ,
800+ testingPrerequisite . contractService ,
801+ testingPrerequisite . clientTransport ,
802+ new DefaultCoinSelector ( ) ,
803+ testingPrerequisite . safetyService ,
804+ intentStorage ) ;
805+ var boltzProvider = new BoltzSwapProvider (
806+ boltzClient ,
807+ new BoltzLimitsValidator ( new CachedBoltzClient (
808+ new HttpClient ( ) ,
809+ new OptionsWrapper < BoltzClientOptions > ( new BoltzClientOptions
810+ {
811+ BoltzUrl = SharedSwapInfrastructure . BoltzEndpoint . ToString ( ) ,
812+ WebsocketUrl = SharedSwapInfrastructure . BoltzWsEndpoint . ToString ( )
813+ } ) ) ) ,
814+ testingPrerequisite . clientTransport ,
815+ testingPrerequisite . vtxoStorage ,
816+ testingPrerequisite . walletProvider ,
817+ swapStorage ,
818+ testingPrerequisite . contractService ,
819+ testingPrerequisite . contracts ,
820+ testingPrerequisite . safetyService ,
821+ spendingService ,
822+ intentStorage ,
823+ chainTimeProvider ) ;
824+
825+ await using var swapMgr = new SwapsManagementService (
826+ new ISwapProvider [ ] { boltzProvider } ,
827+ spendingService ,
828+ testingPrerequisite . clientTransport ,
829+ testingPrerequisite . vtxoStorage ,
830+ testingPrerequisite . walletProvider ,
831+ swapStorage ,
832+ testingPrerequisite . contractService ,
833+ testingPrerequisite . contracts ,
834+ testingPrerequisite . safetyService ,
835+ intentStorage ,
836+ chainTimeProvider ) ;
837+
838+ var settledSwapTcs = new TaskCompletionSource ( ) ;
839+ swapStorage . SwapsChanged += ( _ , swap ) =>
840+ {
841+ if ( swap . Status == ArkSwapStatus . Settled )
842+ settledSwapTcs . TrySetResult ( ) ;
843+ } ;
844+
845+ await swapMgr . StartAsync ( CancellationToken . None ) ;
846+
847+ await swapMgr . InitiateSubmarineSwapBolt12 (
848+ testingPrerequisite . walletIdentifier ,
849+ offer ,
850+ amountSats : Bolt12TestAmountSats ,
851+ autoPay : true ,
852+ cancellationToken : CancellationToken . None ) ;
853+
854+ await settledSwapTcs . Task . WaitAsync ( TimeSpan . FromMinutes ( 2 ) ) ;
855+
856+ var swaps = ( await swapStorage . GetSwaps ( walletIds : [ testingPrerequisite . walletIdentifier ] ) ) . ToList ( ) ;
857+ Assert . That ( swaps , Has . Count . EqualTo ( 1 ) ) ;
858+ Assert . That ( swaps [ 0 ] . Status , Is . EqualTo ( ArkSwapStatus . Settled ) ) ;
859+ Assert . That ( swaps [ 0 ] . Invoice , Does . StartWith ( "lni" ) ) ;
860+ }
755861}
0 commit comments