@@ -193,6 +193,42 @@ describe('IsWalletAddress codec tests', function () {
193193 assert . strictEqual ( decoded . format , 'hex' ) ;
194194 } ) ;
195195
196+ it ( 'should validate body with derivedFromParentWithSeed for SMC wallets' , function ( ) {
197+ const validBody = {
198+ address : '0xa33f0975f53cdcfcc0cb564d25fb5be03b0651cf' ,
199+ baseAddress : '0xc012041dac143a59fa491db3a2b67b69bd78b685' ,
200+ keychains : [
201+ {
202+ pub : 'user_pub' ,
203+ commonKeychain :
204+ '033b02aac4f038fef5118350b77d302ec6202931ca2e7122aad88994ffefcbc70a6069e662436236abb1619195232c41580204cb202c22357ed8f53e69eac5c69e' ,
205+ } ,
206+ {
207+ pub : 'backup_pub' ,
208+ commonKeychain :
209+ '033b02aac4f038fef5118350b77d302ec6202931ca2e7122aad88994ffefcbc70a6069e662436236abb1619195232c41580204cb202c22357ed8f53e69eac5c69e' ,
210+ } ,
211+ {
212+ pub : 'bitgo_pub' ,
213+ commonKeychain :
214+ '033b02aac4f038fef5118350b77d302ec6202931ca2e7122aad88994ffefcbc70a6069e662436236abb1619195232c41580204cb202c22357ed8f53e69eac5c69e' ,
215+ } ,
216+ ] ,
217+ walletVersion : 6 ,
218+ index : 7 ,
219+ coinSpecific : {
220+ forwarderVersion : 5 ,
221+ feeAddress : '0xb1e725186990b86ca8efed08a3ccda9c9f400f09' ,
222+ } ,
223+ derivedFromParentWithSeed : 'my-unique-smc-seed-123' ,
224+ } ;
225+
226+ const decoded = assertDecode ( t . type ( IsWalletAddressBody ) , validBody ) ;
227+ assert . strictEqual ( decoded . address , validBody . address ) ;
228+ assert . strictEqual ( decoded . derivedFromParentWithSeed , 'my-unique-smc-seed-123' ) ;
229+ assert . strictEqual ( decoded . walletVersion , 6 ) ;
230+ } ) ;
231+
196232 it ( 'should reject body with missing address' , function ( ) {
197233 const invalidBody = {
198234 keychains : [ { pub : 'xpub1...' } ] ,
@@ -255,6 +291,18 @@ describe('IsWalletAddress codec tests', function () {
255291 const decodedString = assertDecode ( t . type ( IsWalletAddressBody ) , validBodyWithString ) ;
256292 assert . strictEqual ( decodedString . index , '7' ) ;
257293 } ) ;
294+
295+ it ( 'should reject body with invalid derivedFromParentWithSeed type' , function ( ) {
296+ const invalidBody = {
297+ address : '0x6069a4baf2360bf67a6d02a7fc43d8f3910016ae' ,
298+ keychains : [ { pub : 'xpub1...' } ] ,
299+ derivedFromParentWithSeed : 12345 , // Should be string, not number
300+ } ;
301+
302+ assert . throws ( ( ) => {
303+ assertDecode ( t . type ( IsWalletAddressBody ) , invalidBody ) ;
304+ } ) ;
305+ } ) ;
258306 } ) ;
259307
260308 describe ( 'IsWalletAddressResponse' , function ( ) {
@@ -640,6 +688,157 @@ describe('IsWalletAddress codec tests', function () {
640688 } ) ;
641689 } ) ;
642690
691+ describe ( 'SMC (Self-Managed Cold) TSS Wallet Address Verification' , function ( ) {
692+ const commonKeychain =
693+ '033b02aac4f038fef5118350b77d302ec6202931ca2e7122aad88994ffefcbc70a6069e662436236abb1619195232c41580204cb202c22357ed8f53e69eac5c69e' ;
694+
695+ it ( 'should verify SMC wallet address with derivedFromParentWithSeed' , async function ( ) {
696+ const requestBody = {
697+ address : '0xa33f0975f53cdcfcc0cb564d25fb5be03b0651cf' ,
698+ baseAddress : '0xc012041dac143a59fa491db3a2b67b69bd78b685' ,
699+ coinSpecific : {
700+ forwarderVersion : 5 ,
701+ feeAddress : '0xb1e725186990b86ca8efed08a3ccda9c9f400f09' ,
702+ } ,
703+ keychains : [
704+ { pub : 'user_pub' , commonKeychain } ,
705+ { pub : 'backup_pub' , commonKeychain } ,
706+ { pub : 'bitgo_pub' , commonKeychain } ,
707+ ] ,
708+ index : 7 ,
709+ walletVersion : 6 ,
710+ derivedFromParentWithSeed : 'my-unique-smc-seed-abc123' ,
711+ } ;
712+
713+ const isWalletAddressStub = sinon . stub ( ) . resolves ( true ) ;
714+ const mockWallet = {
715+ baseCoin : {
716+ isWalletAddress : isWalletAddressStub ,
717+ } ,
718+ } ;
719+ const walletsGetStub = sinon . stub ( ) . resolves ( mockWallet ) ;
720+ const mockWallets = {
721+ get : walletsGetStub ,
722+ } ;
723+ const mockCoin = {
724+ wallets : sinon . stub ( ) . returns ( mockWallets ) ,
725+ } ;
726+ sinon . stub ( BitGo . prototype , 'coin' ) . returns ( mockCoin as any ) ;
727+
728+ const result = await agent
729+ . post ( '/api/v2/hteth/wallet/test-wallet-id/iswalletaddress' )
730+ . set ( 'Authorization' , 'Bearer test_access_token_12345' )
731+ . set ( 'Content-Type' , 'application/json' )
732+ . send ( requestBody ) ;
733+
734+ assert . strictEqual ( result . status , 200 ) ;
735+ assert . strictEqual ( result . body , true ) ;
736+
737+ // Verify that the derivedFromParentWithSeed was passed to isWalletAddress
738+ sinon . assert . calledOnce ( isWalletAddressStub ) ;
739+ const callArgs = isWalletAddressStub . firstCall . args [ 0 ] ;
740+ assert . strictEqual ( callArgs . derivedFromParentWithSeed , 'my-unique-smc-seed-abc123' ) ;
741+ } ) ;
742+
743+ it ( 'should verify SMC wallet base address with derivedFromParentWithSeed' , async function ( ) {
744+ const baseAddress = '0xc012041dac143a59fa491db3a2b67b69bd78b685' ;
745+ const requestBody = {
746+ address : baseAddress ,
747+ baseAddress : baseAddress ,
748+ coinSpecific : {
749+ salt : '0x0' ,
750+ forwarderVersion : 5 ,
751+ feeAddress : '0xb1e725186990b86ca8efed08a3ccda9c9f400f09' ,
752+ } ,
753+ keychains : [
754+ { pub : 'user_pub' , commonKeychain } ,
755+ { pub : 'backup_pub' , commonKeychain } ,
756+ { pub : 'bitgo_pub' , commonKeychain } ,
757+ ] ,
758+ index : 0 ,
759+ walletVersion : 6 ,
760+ derivedFromParentWithSeed : 'another-smc-seed-xyz789' ,
761+ } ;
762+
763+ const isWalletAddressStub = sinon . stub ( ) . resolves ( true ) ;
764+ const mockWallet = {
765+ baseCoin : {
766+ isWalletAddress : isWalletAddressStub ,
767+ } ,
768+ } ;
769+ const walletsGetStub = sinon . stub ( ) . resolves ( mockWallet ) ;
770+ const mockWallets = {
771+ get : walletsGetStub ,
772+ } ;
773+ const mockCoin = {
774+ wallets : sinon . stub ( ) . returns ( mockWallets ) ,
775+ } ;
776+ sinon . stub ( BitGo . prototype , 'coin' ) . returns ( mockCoin as any ) ;
777+
778+ const result = await agent
779+ . post ( '/api/v2/hteth/wallet/test-wallet-id/iswalletaddress' )
780+ . set ( 'Authorization' , 'Bearer test_access_token_12345' )
781+ . set ( 'Content-Type' , 'application/json' )
782+ . send ( requestBody ) ;
783+
784+ assert . strictEqual ( result . status , 200 ) ;
785+ assert . strictEqual ( result . body , true ) ;
786+
787+ // Verify that the derivedFromParentWithSeed was passed to isWalletAddress
788+ sinon . assert . calledOnce ( isWalletAddressStub ) ;
789+ const callArgs = isWalletAddressStub . firstCall . args [ 0 ] ;
790+ assert . strictEqual ( callArgs . derivedFromParentWithSeed , 'another-smc-seed-xyz789' ) ;
791+ } ) ;
792+
793+ it ( 'should work without derivedFromParentWithSeed for non-SMC wallets' , async function ( ) {
794+ const requestBody = {
795+ address : '0xa33f0975f53cdcfcc0cb564d25fb5be03b0651cf' ,
796+ baseAddress : '0xc012041dac143a59fa491db3a2b67b69bd78b685' ,
797+ coinSpecific : {
798+ forwarderVersion : 5 ,
799+ feeAddress : '0xb1e725186990b86ca8efed08a3ccda9c9f400f09' ,
800+ } ,
801+ keychains : [
802+ { pub : 'user_pub' , commonKeychain } ,
803+ { pub : 'backup_pub' , commonKeychain } ,
804+ { pub : 'bitgo_pub' , commonKeychain } ,
805+ ] ,
806+ index : 7 ,
807+ walletVersion : 6 ,
808+ // No derivedFromParentWithSeed - this is a regular TSS wallet
809+ } ;
810+
811+ const isWalletAddressStub = sinon . stub ( ) . resolves ( true ) ;
812+ const mockWallet = {
813+ baseCoin : {
814+ isWalletAddress : isWalletAddressStub ,
815+ } ,
816+ } ;
817+ const walletsGetStub = sinon . stub ( ) . resolves ( mockWallet ) ;
818+ const mockWallets = {
819+ get : walletsGetStub ,
820+ } ;
821+ const mockCoin = {
822+ wallets : sinon . stub ( ) . returns ( mockWallets ) ,
823+ } ;
824+ sinon . stub ( BitGo . prototype , 'coin' ) . returns ( mockCoin as any ) ;
825+
826+ const result = await agent
827+ . post ( '/api/v2/hteth/wallet/test-wallet-id/iswalletaddress' )
828+ . set ( 'Authorization' , 'Bearer test_access_token_12345' )
829+ . set ( 'Content-Type' , 'application/json' )
830+ . send ( requestBody ) ;
831+
832+ assert . strictEqual ( result . status , 200 ) ;
833+ assert . strictEqual ( result . body , true ) ;
834+
835+ // Verify that derivedFromParentWithSeed is undefined for non-SMC wallets
836+ sinon . assert . calledOnce ( isWalletAddressStub ) ;
837+ const callArgs = isWalletAddressStub . firstCall . args [ 0 ] ;
838+ assert . strictEqual ( callArgs . derivedFromParentWithSeed , undefined ) ;
839+ } ) ;
840+ } ) ;
841+
643842 describe ( 'Invalid Address Cases' , function ( ) {
644843 it ( 'should return false for wrong address' , async function ( ) {
645844 const requestBody = {
@@ -797,6 +996,23 @@ describe('IsWalletAddress codec tests', function () {
797996 assert . ok ( Array . isArray ( result . body ) ) ;
798997 } ) ;
799998
999+ it ( 'should return 400 for invalid derivedFromParentWithSeed type' , async function ( ) {
1000+ const requestBody = {
1001+ address : '0x6069a4baf2360bf67a6d02a7fc43d8f3910016ae' ,
1002+ keychains : [ { pub : 'xpub1...' } ] ,
1003+ derivedFromParentWithSeed : 12345 , // Should be string, not number
1004+ } ;
1005+
1006+ const result = await agent
1007+ . post ( '/api/v2/hteth/wallet/test-wallet-id/iswalletaddress' )
1008+ . set ( 'Authorization' , 'Bearer test_access_token_12345' )
1009+ . set ( 'Content-Type' , 'application/json' )
1010+ . send ( requestBody ) ;
1011+
1012+ assert . strictEqual ( result . status , 400 ) ;
1013+ assert . ok ( Array . isArray ( result . body ) ) ;
1014+ } ) ;
1015+
8001016 it ( 'should handle isWalletAddress throwing InvalidAddressError' , async function ( ) {
8011017 const requestBody = {
8021018 address : '0xinvalid' ,
0 commit comments