@@ -132,6 +132,7 @@ function getEarnControllerMessenger(
132132 events : [
133133 'NetworkController:networkDidChange' ,
134134 'AccountTreeController:selectedAccountGroupChange' ,
135+ 'AccountTreeController:stateChange' ,
135136 'TransactionController:transactionConfirmed' ,
136137 ] ,
137138 } ) ;
@@ -869,6 +870,108 @@ describe('EarnController', () => {
869870 mockedEarnApiService ?. pooledStaking ?. getPooledStakes ,
870871 ) . toHaveBeenCalledTimes ( 2 ) ; // 2 chains (ETH + HOODI) from the first init()
871872 } ) ;
873+
874+ describe ( 'when no EVM account is available at init time' , ( ) => {
875+ // Minimal AccountTreeControllerState shape used to trigger the stateChange event
876+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
877+ const mockAccountTreeStateWithGroup : any = {
878+ selectedAccountGroup : 'keyring:test/0' ,
879+ accountTree : { wallets : { } } ,
880+ isAccountTreeSyncingInProgress : false ,
881+ hasAccountTreeSyncingSyncedAtLeastOnce : false ,
882+ accountGroupsMetadata : { } ,
883+ accountWalletsMetadata : { } ,
884+ } ;
885+
886+ it ( 'defers portfolio refresh until AccountTreeController:stateChange fires with a non-empty selectedAccountGroup' , async ( ) => {
887+ const mockGetAccounts = jest
888+ . fn ( )
889+ . mockReturnValueOnce ( [ ] ) // No account during init
890+ . mockReturnValue ( [ mockInternalAccount1 ] ) ; // Account available after stateChange
891+
892+ const { messenger } = await setupController ( {
893+ mockGetAccountsFromSelectedAccountGroup : mockGetAccounts ,
894+ } ) ;
895+
896+ // No eligibility or staking refresh should have happened during init
897+ expect (
898+ mockedEarnApiService ?. pooledStaking ?. getPooledStakingEligibility ,
899+ ) . not . toHaveBeenCalled ( ) ;
900+ expect (
901+ mockedEarnApiService ?. pooledStaking ?. getPooledStakes ,
902+ ) . not . toHaveBeenCalled ( ) ;
903+
904+ messenger . publish (
905+ 'AccountTreeController:stateChange' ,
906+ mockAccountTreeStateWithGroup ,
907+ [ ] ,
908+ ) ;
909+ await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
910+
911+ expect (
912+ mockedEarnApiService ?. pooledStaking ?. getPooledStakingEligibility ,
913+ ) . toHaveBeenCalledWith ( [ mockAccount1Address ] ) ;
914+ expect (
915+ mockedEarnApiService ?. pooledStaking ?. getPooledStakes ,
916+ ) . toHaveBeenCalled ( ) ;
917+ } ) ;
918+
919+ it ( 'does not trigger portfolio refresh when selectedAccountGroup is empty' , async ( ) => {
920+ const { messenger } = await setupController ( {
921+ mockGetAccountsFromSelectedAccountGroup : jest . fn ( ( ) => [ ] ) ,
922+ } ) ;
923+
924+ messenger . publish (
925+ 'AccountTreeController:stateChange' ,
926+ {
927+ ...mockAccountTreeStateWithGroup ,
928+ selectedAccountGroup : '' ,
929+ } ,
930+ [ ] ,
931+ ) ;
932+ await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
933+
934+ expect (
935+ mockedEarnApiService ?. pooledStaking ?. getPooledStakingEligibility ,
936+ ) . not . toHaveBeenCalled ( ) ;
937+ } ) ;
938+
939+ it ( 'unsubscribes after the first non-empty selectedAccountGroup event' , async ( ) => {
940+ const mockGetAccounts = jest
941+ . fn ( )
942+ . mockReturnValueOnce ( [ ] ) // No account during init
943+ . mockReturnValue ( [ mockInternalAccount1 ] ) ; // Account available after stateChange
944+
945+ const { messenger } = await setupController ( {
946+ mockGetAccountsFromSelectedAccountGroup : mockGetAccounts ,
947+ } ) ;
948+
949+ // First publish triggers the deferred refresh
950+ messenger . publish (
951+ 'AccountTreeController:stateChange' ,
952+ mockAccountTreeStateWithGroup ,
953+ [ ] ,
954+ ) ;
955+ await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
956+
957+ const eligibilityCallCount = (
958+ mockedEarnApiService ?. pooledStaking
959+ ?. getPooledStakingEligibility as jest . Mock
960+ ) . mock . calls . length ;
961+
962+ // Second publish should be ignored – handler was already unsubscribed
963+ messenger . publish (
964+ 'AccountTreeController:stateChange' ,
965+ mockAccountTreeStateWithGroup ,
966+ [ ] ,
967+ ) ;
968+ await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
969+
970+ expect (
971+ mockedEarnApiService ?. pooledStaking ?. getPooledStakingEligibility ,
972+ ) . toHaveBeenCalledTimes ( eligibilityCallCount ) ;
973+ } ) ;
974+ } ) ;
872975 } ) ;
873976
874977 describe ( 'SDK initialization' , ( ) => {
@@ -1051,7 +1154,7 @@ describe('EarnController', () => {
10511154 const { controller } = await setupController ( ) ;
10521155
10531156 await expect ( controller . refreshPooledStakingData ( ) ) . rejects . toThrow (
1054- 'Failed to refresh some staking data: API Error getPooledStakingEligibility, API Error getPooledStakes, API Error getVaultData, API Error getVaultDailyApys, API Error getVaultApyAverages, API Error getPooledStakes, API Error getVaultData, API Error getVaultDailyApys, API Error getVaultApyAverages' ,
1157+ 'Failed to refresh some staking data: API Error getPooledStakes, API Error getVaultData, API Error getVaultDailyApys, API Error getVaultApyAverages, API Error getPooledStakes, API Error getVaultData, API Error getVaultDailyApys, API Error getVaultApyAverages' ,
10551158 ) ;
10561159 expect ( consoleErrorSpy ) . toHaveBeenCalled ( ) ;
10571160 consoleErrorSpy . mockRestore ( ) ;
@@ -1223,7 +1326,19 @@ describe('EarnController', () => {
12231326 // Assertion on second call since the first is part of controller setup.
12241327 expect (
12251328 mockedEarnApiService ?. pooledStaking ?. getPooledStakingEligibility ,
1226- ) . toHaveBeenNthCalledWith ( 3 , [ mockAccount2Address ] ) ;
1329+ ) . toHaveBeenNthCalledWith ( 2 , [ mockAccount2Address ] ) ;
1330+ } ) ;
1331+
1332+ it ( 'returns early without fetching when no address is available' , async ( ) => {
1333+ const { controller } = await setupController ( {
1334+ mockGetAccountsFromSelectedAccountGroup : jest . fn ( ( ) => [ ] ) ,
1335+ } ) ;
1336+
1337+ await controller . refreshEarnEligibility ( ) ;
1338+
1339+ expect (
1340+ mockedEarnApiService ?. pooledStaking ?. getPooledStakingEligibility ,
1341+ ) . not . toHaveBeenCalled ( ) ;
12271342 } ) ;
12281343 } ) ;
12291344
@@ -1624,31 +1739,6 @@ describe('EarnController', () => {
16241739 } ) ;
16251740
16261741 describe ( 'Lending' , ( ) => {
1627- describe ( 'refreshLendingEligibility' , ( ) => {
1628- it ( 'fetches lending eligibility using active account (default)' , async ( ) => {
1629- const { controller } = await setupController ( ) ;
1630-
1631- await controller . refreshLendingEligibility ( ) ;
1632-
1633- // Assertion on third call since the first and second calls are part of controller setup.
1634- expect (
1635- mockedEarnApiService ?. pooledStaking ?. getPooledStakingEligibility ,
1636- ) . toHaveBeenNthCalledWith ( 3 , [ mockAccount1Address ] ) ;
1637- } ) ;
1638-
1639- it ( 'fetches lending eligibility using options.address override' , async ( ) => {
1640- const { controller } = await setupController ( ) ;
1641- await controller . refreshLendingEligibility ( {
1642- address : mockAccount2Address ,
1643- } ) ;
1644-
1645- // Assertion on third call since the first and second calls are part of controller setup.
1646- expect (
1647- mockedEarnApiService ?. pooledStaking ?. getPooledStakingEligibility ,
1648- ) . toHaveBeenNthCalledWith ( 3 , [ mockAccount2Address ] ) ;
1649- } ) ;
1650- } ) ;
1651-
16521742 describe ( 'refreshLendingPositions' , ( ) => {
16531743 it ( 'fetches using active account (default)' , async ( ) => {
16541744 const { controller } = await setupController ( ) ;
@@ -1671,6 +1761,18 @@ describe('EarnController', () => {
16711761 mockedEarnApiService ?. lending ?. getPositions ,
16721762 ) . toHaveBeenNthCalledWith ( 2 , mockAccount2Address ) ;
16731763 } ) ;
1764+
1765+ it ( 'returns early without fetching when no address is available' , async ( ) => {
1766+ const { controller } = await setupController ( {
1767+ mockGetAccountsFromSelectedAccountGroup : jest . fn ( ( ) => [ ] ) ,
1768+ } ) ;
1769+
1770+ await controller . refreshLendingPositions ( ) ;
1771+
1772+ expect (
1773+ mockedEarnApiService ?. lending ?. getPositions ,
1774+ ) . not . toHaveBeenCalled ( ) ;
1775+ } ) ;
16741776 } ) ;
16751777
16761778 describe ( 'refreshLendingMarkets' , ( ) => {
@@ -1697,9 +1799,6 @@ describe('EarnController', () => {
16971799 expect (
16981800 mockedEarnApiService ?. lending ?. getPositions ,
16991801 ) . toHaveBeenCalledTimes ( 2 ) ;
1700- expect (
1701- mockedEarnApiService ?. pooledStaking ?. getPooledStakingEligibility ,
1702- ) . toHaveBeenCalledTimes ( 3 ) ; // Additionally called once in controller setup by refreshPooledStakingData
17031802 } ) ;
17041803 } ) ;
17051804
0 commit comments