@@ -439,6 +439,29 @@ void encodeForMultiSigning() throws JsonProcessingException {
439439 assertThat (encoder .encodeForMultiSigning (json , signerAccountId )).isEqualTo (expected );
440440 }
441441
442+ @ Test
443+ void encodeForMultiSigningWithNonObjectJsonThrowsException () {
444+ String signerAccountId = "rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN" ;
445+
446+ // Test with JSON array
447+ String jsonArray = "[\" value1\" , \" value2\" ]" ;
448+ Assertions .assertThatThrownBy (() -> encoder .encodeForMultiSigning (jsonArray , signerAccountId ))
449+ .isInstanceOf (IllegalArgumentException .class )
450+ .hasMessage ("JSON object required for signing" );
451+
452+ // Test with JSON primitive (string)
453+ String jsonString = "\" just a string\" " ;
454+ Assertions .assertThatThrownBy (() -> encoder .encodeForMultiSigning (jsonString , signerAccountId ))
455+ .isInstanceOf (IllegalArgumentException .class )
456+ .hasMessage ("JSON object required for signing" );
457+
458+ // Test with JSON primitive (number)
459+ String jsonNumber = "12345" ;
460+ Assertions .assertThatThrownBy (() -> encoder .encodeForMultiSigning (jsonNumber , signerAccountId ))
461+ .isInstanceOf (IllegalArgumentException .class )
462+ .hasMessage ("JSON object required for signing" );
463+ }
464+
442465 @ Test
443466 public void encodePaymentWithSigners () throws JsonProcessingException {
444467 String json = "{\" Account\" :\" rGs8cFHMfJanAXVtn6e8Lz2iH8FtnGdexw\" ,\" Fee\" :\" 30\" ," +
@@ -602,6 +625,158 @@ void encodeForBatchSigningNullBatchInnerThrowsException() {
602625 .isInstanceOf (NullPointerException .class );
603626 }
604627
628+ ///////////////////
629+ // encodeForBatchInnerMultiSigning
630+ ///////////////////
631+
632+ @ Test
633+ void encodeForBatchInnerMultiSigningWithNullBatch () {
634+ Address signerAddress = Address .of ("rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN" );
635+ Assertions .assertThatThrownBy (() -> encoder .encodeForBatchInnerMultiSigning (null , signerAddress ))
636+ .isInstanceOf (NullPointerException .class );
637+ }
638+
639+ @ Test
640+ void encodeForBatchInnerMultiSigningWithNullSignerAddress () throws JsonProcessingException {
641+ List <RawTransactionWrapper > innerTransactions = createInnerPayments (2 );
642+ Batch batch = createBatch (BatchFlags .ALL_OR_NOTHING , innerTransactions );
643+
644+ Assertions .assertThatThrownBy (() -> encoder .encodeForBatchInnerMultiSigning (batch , null ))
645+ .isInstanceOf (NullPointerException .class );
646+ }
647+
648+ @ Test
649+ void encodeForBatchInnerMultiSigningWithValidBatch () throws JsonProcessingException {
650+ // Create two inner payment transactions
651+ Payment innerPayment1 = createInnerPayment (XrpCurrencyAmount .ofDrops (1000 ), UnsignedInteger .ONE );
652+ Payment innerPayment2 = createInnerPayment (XrpCurrencyAmount .ofDrops (2000 ), UnsignedInteger .valueOf (2 ));
653+
654+ // Create a Batch transaction
655+ Batch batch = Batch .builder ()
656+ .account (BATCH_ACCOUNT )
657+ .fee (XrpCurrencyAmount .ofDrops (100 ))
658+ .sequence (UnsignedInteger .ONE )
659+ .flags (BatchFlags .ALL_OR_NOTHING )
660+ .signingPublicKey (BATCH_ACCOUNT_SIGNING_PUB_KEY )
661+ .addRawTransactions (
662+ RawTransactionWrapper .of (innerPayment1 ),
663+ RawTransactionWrapper .of (innerPayment2 )
664+ )
665+ .build ();
666+
667+ Address signerAddress = Address .of ("rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN" );
668+ UnsignedByteArray result = encoder .encodeForBatchInnerMultiSigning (batch , signerAddress );
669+
670+ // Verify the result starts with the batch signature prefix "BCH\0" = 0x42434800
671+ assertThat (result .hexValue ()).startsWith ("42434800" );
672+
673+ // Verify the structure: prefix (4 bytes) + flags (4 bytes) + count (4 bytes) + tx IDs (32 bytes * 2) + account ID (20 bytes)
674+ // Total: 4 + 4 + 4 + (32 * 2) + 20 = 96 bytes = 192 hex chars
675+ assertThat (result .hexValue ()).hasSize (192 );
676+
677+ // Verify flags are encoded correctly (ALL_OR_NOTHING = 0x00010000)
678+ String flagsHex = result .hexValue ().substring (8 , 16 );
679+ assertThat (flagsHex ).isEqualTo ("00010000" );
680+
681+ // Verify count is 2
682+ String countHex = result .hexValue ().substring (16 , 24 );
683+ assertThat (countHex ).isEqualTo ("00000002" );
684+
685+ // Verify the account ID suffix is appended (last 40 hex chars = 20 bytes)
686+ String accountIdSuffix = result .hexValue ().substring (152 , 192 );
687+ assertThat (accountIdSuffix ).hasSize (40 );
688+ }
689+
690+ @ Test
691+ void encodeForBatchInnerMultiSigningWithDifferentSigners () throws JsonProcessingException {
692+ List <RawTransactionWrapper > innerTransactions = createInnerPayments (2 );
693+ Batch batch = createBatch (BatchFlags .ALL_OR_NOTHING , innerTransactions );
694+
695+ Address signer1 = Address .of ("rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN" );
696+ Address signer2 = Address .of ("rDgZZ3wyprx4ZqrGQUkquE9Fs2Xs8XBcdw" );
697+
698+ UnsignedByteArray result1 = encoder .encodeForBatchInnerMultiSigning (batch , signer1 );
699+ UnsignedByteArray result2 = encoder .encodeForBatchInnerMultiSigning (batch , signer2 );
700+
701+ // The batch serialization part should be the same (first 152 hex chars)
702+ assertThat (result1 .hexValue ().substring (0 , 152 )).isEqualTo (result2 .hexValue ().substring (0 , 152 ));
703+
704+ // But the account ID suffix should be different (last 40 hex chars)
705+ assertThat (result1 .hexValue ().substring (152 )).isNotEqualTo (result2 .hexValue ().substring (152 ));
706+ }
707+
708+ ///////////////////
709+ // encodeForSigningClaim
710+ ///////////////////
711+
712+ @ Test
713+ void encodeForSigningClaimWithNonObjectJson () {
714+ // Test with JSON array
715+ String jsonArray = "[\" value1\" , \" value2\" ]" ;
716+ Assertions .assertThatThrownBy (() -> encoder .encodeForSigningClaim (jsonArray ))
717+ .isInstanceOf (IllegalArgumentException .class )
718+ .hasMessage ("JSON object required for signing" );
719+
720+ // Test with JSON primitive
721+ String jsonString = "\" just a string\" " ;
722+ Assertions .assertThatThrownBy (() -> encoder .encodeForSigningClaim (jsonString ))
723+ .isInstanceOf (IllegalArgumentException .class )
724+ .hasMessage ("JSON object required for signing" );
725+ }
726+
727+ @ Test
728+ void encodeForSigningClaimWithMissingChannel () {
729+ String jsonMissingChannel = "{\" Amount\" :\" 1000\" }" ;
730+ Assertions .assertThatThrownBy (() -> encoder .encodeForSigningClaim (jsonMissingChannel ))
731+ .isInstanceOf (IllegalArgumentException .class )
732+ .hasMessage ("Unsigned claims must have Channel and Amount fields." );
733+ }
734+
735+ @ Test
736+ void encodeForSigningClaimWithMissingAmount () {
737+ String jsonMissingAmount = "{\" Channel\" :\" 5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3\" }" ;
738+ Assertions .assertThatThrownBy (() -> encoder .encodeForSigningClaim (jsonMissingAmount ))
739+ .isInstanceOf (IllegalArgumentException .class )
740+ .hasMessage ("Unsigned claims must have Channel and Amount fields." );
741+ }
742+
743+ @ Test
744+ void encodeForSigningClaimWithValidData () throws JsonProcessingException {
745+ String channel = "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3" ;
746+ String amount = "1000" ;
747+ String json = "{\" Channel\" :\" " + channel + "\" ,\" Amount\" :\" " + amount + "\" }" ;
748+
749+ String result = encoder .encodeForSigningClaim (json );
750+
751+ // Verify the result starts with the payment channel claim signature prefix "CLM\0" = 0x434C4D00
752+ assertThat (result ).startsWith ("434C4D00" );
753+
754+ // Verify the structure: prefix (4 bytes) + channel (32 bytes) + amount (8 bytes)
755+ // Total: 4 + 32 + 8 = 44 bytes = 88 hex chars (prefix is included in the total)
756+ assertThat (result ).hasSize (88 );
757+
758+ // Verify the channel is encoded correctly (after the prefix)
759+ String encodedChannel = result .substring (8 , 72 );
760+ assertThat (encodedChannel ).isEqualTo (channel );
761+ }
762+
763+ @ Test
764+ void encodeForSigningClaimWithDifferentAmounts () throws JsonProcessingException {
765+ String channel = "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3" ;
766+
767+ String json1 = "{\" Channel\" :\" " + channel + "\" ,\" Amount\" :\" 1000\" }" ;
768+ String json2 = "{\" Channel\" :\" " + channel + "\" ,\" Amount\" :\" 2000\" }" ;
769+
770+ String result1 = encoder .encodeForSigningClaim (json1 );
771+ String result2 = encoder .encodeForSigningClaim (json2 );
772+
773+ // The prefix and channel should be the same
774+ assertThat (result1 .substring (0 , 72 )).isEqualTo (result2 .substring (0 , 72 ));
775+
776+ // But the amount should be different
777+ assertThat (result1 .substring (72 )).isNotEqualTo (result2 .substring (72 ));
778+ }
779+
605780 // Helper method to create inner payment transactions
606781 private Payment createInnerPayment (CurrencyAmount amount , UnsignedInteger sequence ) {
607782 return Payment .builder ()
0 commit comments