-
-
Notifications
You must be signed in to change notification settings - Fork 246
feat: added rewards controller and rewards data service messenger #6493
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition to the requested changes:
-
please review the spacing inside the functions, it's a bit out of place
-
There's a significant amount of logging, especially in
catch
blocks. I wonder if we can reduce the logging and throw regular errors instead.
try { | ||
// Generate timestamp and sign the message | ||
const timestamp = Math.floor(Date.now() / 1000); | ||
|
||
let signature; | ||
try { | ||
signature = await this.#signRewardsMessage(internalAccount, timestamp); | ||
} catch (signError) { | ||
log('RewardsController: Failed to generate signature:', signError); | ||
|
||
// Check if the error is due to locked keyring | ||
if ( | ||
signError && | ||
typeof signError === 'object' && | ||
'message' in signError | ||
) { | ||
const errorMessage = (signError as Error).message; | ||
if (errorMessage.includes('controller is locked')) { | ||
log('RewardsController: Keyring is locked, skipping silent auth'); | ||
return; // Exit silently when keyring is locked | ||
} | ||
} | ||
|
||
throw signError; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we avoid the nested try...catch
blocks ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not fixed
packages/rewards-controller/src/messenger/RewardsControllerMessenger.ts
Outdated
Show resolved
Hide resolved
packages/rewards-controller/src/messenger/RewardsControllerMessenger.ts
Outdated
Show resolved
Hide resolved
packages/rewards-controller/src/services/rewards-data-service.ts
Outdated
Show resolved
Hide resolved
packages/rewards-controller/src/services/rewards-data-service.ts
Outdated
Show resolved
Hide resolved
|
||
constructor({ | ||
messenger, | ||
fetch: fetchFunction, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we have to pass fetch in the constructor ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually the fetch is just a function for handing the fetch process and url is formed in a function makeRequest.
As in rewards case the fetch handling is similar for all calls we are sending a fetch implementation in the constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no need to pass fetch as it's already present as a global in the versions of node.js we use. I don't fully get your comment, it doesn't answer my question.
My review was an initial pass on this PR, do not expect it to be a thorough review as this PR is pretty big. In the future please try to keep the PR size down. This could have been split up in multiple PRs. |
Co-authored-by: Guillaume Roux <guillaumeroux123@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition, there's still a very significant amount of logging in errors
isHardwareAccount: boolean, | ||
): boolean { | ||
// Skip for hardware and Solana accounts | ||
if (isHardwareAccount || isSolanaAddress(address)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no need to bring a package (@solana/addresses
) to validate an address with isSolanaAddress
as the CAIP-10 ID contains the chain ID that could be use to determine if it's a solana account.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You shouldn't be completely mocking the messenger but rather creating a real restricted messenger with mocked actions inside it
mockMessenger = { | ||
subscribe: jest.fn(), | ||
call: jest.fn(), | ||
registerActionHandler: jest.fn(), | ||
unregisterActionHandler: jest.fn(), | ||
publish: jest.fn(), | ||
clearEventSubscriptions: jest.fn(), | ||
registerInitialEventPayload: jest.fn(), | ||
unsubscribe: jest.fn(), | ||
} as unknown as jest.Mocked<RewardsControllerMessenger>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You shouldn't be mocking completely the messenger but rather creating a real messenger with mocked actions inside it. You can look at other controllers to see how it's done.
} as unknown as jest.Mocked<RewardsControllerMessenger>; | ||
|
||
// Reset feature flag to enabled by default | ||
mockGetRewardsFeatureFlag.mockReturnValue(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should get rid of this mock and instead do a partial implementation of the feature flag action in the test messenger.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be deleted now that it exists in the controller file
__logSpy: jest.Mock; | ||
}; | ||
|
||
let mockMessenger: jest.Mocked<RewardsDataServiceMessenger>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't mock the entire messenger.
fetchImpl = jest | ||
.fn() | ||
.mockResolvedValue( | ||
okJsonResponse({ sessionId: 'sess', subscription: { id: 'sub' } }), | ||
), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't manually mock fetch
, we rely on nock
to provide fetch mocks.
mockMessenger = { | ||
registerActionHandler: jest.fn(), | ||
call: jest.fn(), | ||
} as unknown as jest.Mocked<RewardsDataServiceMessenger>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't directly mock the messenger
|
||
constructor({ | ||
messenger, | ||
fetch: fetchFunction, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no need to pass fetch as it's already present as a global in the versions of node.js we use. I don't fully get your comment, it doesn't answer my question.
|
||
readonly #environment: EnvironmentType; | ||
|
||
readonly #getSubscriptionToken: GetSubscriptionToken; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does this function comes from ? If it's available through a messenger action please use the messenger rather than passing it as a parameter.
lastPerpsDiscountRateFetched: number | null; | ||
}; | ||
|
||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment seems to be unnecessary, there is no lint error here
}; | ||
|
||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions | ||
export type RewardsControllerState = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add descriptions to these properties? It's hard to tell what they are for
Explanation
This pull request adds Rewards Controller and messenger feature, which is used for silent login in the MetaMask Mobile app. The implementation enables automatic authentication for users when they switch accounts or unlock their wallet.
The RewardsController should:
Automatically authenticates a user through a silent signature from KeyringController if their EVM address owns a rewards profile
Skips auth for Solana and hardware wallets for now
Skips auth for 10 minutes of grace period
Note: This controller was part of metamask-mobile and moved to core as it can be used for metamask-extension too.
References
Checklist