-
Notifications
You must be signed in to change notification settings - Fork 570
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
Unblock wallet_switchEthereumChain
#2634
base: main
Are you sure you want to change the base?
Changes from all commits
2d6bce8
0a82e70
525ef12
9473208
26dbd80
fba304e
1dcbeda
736f8f1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,23 @@ import { | |
hexToNumber, | ||
} from '@metamask/utils'; | ||
|
||
import type { PersonalSignParams, SignTypedDataParams } from './types'; | ||
import type { | ||
BaseParams, | ||
PersonalSignParams, | ||
SignTypedDataParams, | ||
} from './types'; | ||
|
||
/** | ||
* Set the active Ethereum chain for the Snap. | ||
* | ||
* @param chainId - The chain ID to switch to. | ||
*/ | ||
async function switchChain(chainId: Hex) { | ||
await ethereum.request({ | ||
method: 'wallet_switchEthereumChain', | ||
params: [{ chainId }], | ||
}); | ||
} | ||
|
||
/** | ||
* Get the current gas price using the `ethereum` global. This is essentially | ||
|
@@ -207,6 +223,9 @@ async function signTypedData(message: string, from: string) { | |
* @see https://docs.metamask.io/snaps/reference/rpc-api/#wallet_invokesnap | ||
*/ | ||
export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { | ||
const { chainId = '0x1' } = (request.params as BaseParams) ?? {}; | ||
await switchChain(chainId); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. probably not a problem, but i think always using mainnet makes it possible in the extension e2e tests that use this snap to try to hit the mainnet infura endpoint, but I think that's probably only the case if something triggers |
||
|
||
switch (request.method) { | ||
case 'getGasPrice': | ||
return await getGasPrice(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
{ | ||
"branches": 92.96, | ||
"branches": 92.97, | ||
"functions": 96.56, | ||
"lines": 98.05, | ||
"statements": 97.77 | ||
"lines": 98.06, | ||
"statements": 97.78 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -103,7 +103,10 @@ | |
waitForStateChange, | ||
} from '../test-utils'; | ||
import { delay } from '../utils'; | ||
import { LEGACY_ENCRYPTION_KEY_DERIVATION_OPTIONS } from './constants'; | ||
import { | ||
LEGACY_ENCRYPTION_KEY_DERIVATION_OPTIONS, | ||
PERMITTED_CHAINS_ENDOWMENT, | ||
} from './constants'; | ||
import { SnapsRegistryStatus } from './registry'; | ||
import type { SnapControllerState } from './SnapController'; | ||
import { | ||
|
@@ -1842,7 +1845,7 @@ | |
}); | ||
|
||
// This isn't stable in CI unfortunately | ||
it.skip('throws if the Snap is terminated while executing', async () => { | ||
const { manifest, sourceCode, svgIcon } = | ||
await getMockSnapFilesWithUpdatedChecksum({ | ||
sourceCode: ` | ||
|
@@ -4877,6 +4880,216 @@ | |
snapController.destroy(); | ||
}); | ||
|
||
it('grants the `endowment:permitted-chains` permission to a Snap with `endowment:ethereum-provider`', async () => { | ||
const rootMessenger = getControllerMessenger(); | ||
const messenger = getSnapControllerMessenger(rootMessenger); | ||
|
||
rootMessenger.registerActionHandler( | ||
'PermissionController:getPermissions', | ||
() => ({}), | ||
); | ||
|
||
rootMessenger.registerActionHandler( | ||
'SelectedNetworkController:getNetworkClientIdForDomain', | ||
() => 'mainnet', | ||
); | ||
|
||
rootMessenger.registerActionHandler( | ||
'NetworkController:getNetworkClientById', | ||
() => ({ | ||
// @ts-expect-error - Partial network client. | ||
configuration: { | ||
chainId: '0x1', | ||
}, | ||
}), | ||
); | ||
|
||
const { manifest } = await getMockSnapFilesWithUpdatedChecksum({ | ||
manifest: getSnapManifest({ | ||
initialPermissions: { | ||
'endowment:page-home': {}, | ||
'endowment:ethereum-provider': {}, | ||
}, | ||
}), | ||
}); | ||
|
||
const snapController = getSnapController( | ||
getSnapControllerOptions({ | ||
messenger, | ||
detectSnapLocation: loopbackDetect({ manifest }), | ||
}), | ||
); | ||
|
||
await snapController.installSnaps(MOCK_ORIGIN, { | ||
[MOCK_SNAP_ID]: {}, | ||
}); | ||
|
||
const approvedPermissions = { | ||
'endowment:page-home': { | ||
caveats: null, | ||
}, | ||
'endowment:ethereum-provider': {}, | ||
[PERMITTED_CHAINS_ENDOWMENT]: { | ||
caveats: [ | ||
{ | ||
type: 'restrictNetworkSwitching', | ||
value: ['0x1'], | ||
}, | ||
], | ||
}, | ||
Comment on lines
+4932
to
+4939
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should now be the CAIP-25 permission type, something like this:
|
||
}; | ||
|
||
expect(messenger.call).toHaveBeenCalledWith( | ||
'PermissionController:grantPermissions', | ||
{ | ||
approvedPermissions, | ||
subject: { origin: MOCK_SNAP_ID }, | ||
requestData: expect.any(Object), | ||
}, | ||
); | ||
|
||
snapController.destroy(); | ||
}); | ||
|
||
it('overrides the `endowment:permitted-chains` permission if the Snap specifies it in its manifest', async () => { | ||
const rootMessenger = getControllerMessenger(); | ||
const messenger = getSnapControllerMessenger(rootMessenger); | ||
|
||
rootMessenger.registerActionHandler( | ||
'PermissionController:getPermissions', | ||
() => ({}), | ||
); | ||
|
||
rootMessenger.registerActionHandler( | ||
'SelectedNetworkController:getNetworkClientIdForDomain', | ||
() => 'mainnet', | ||
); | ||
|
||
rootMessenger.registerActionHandler( | ||
'NetworkController:getNetworkClientById', | ||
() => ({ | ||
// @ts-expect-error - Partial network client. | ||
configuration: { | ||
chainId: '0x1', | ||
}, | ||
}), | ||
); | ||
|
||
const { manifest } = await getMockSnapFilesWithUpdatedChecksum({ | ||
manifest: getSnapManifest({ | ||
initialPermissions: { | ||
'endowment:page-home': {}, | ||
'endowment:ethereum-provider': {}, | ||
[PERMITTED_CHAINS_ENDOWMENT]: { | ||
caveats: [ | ||
{ | ||
type: 'restrictNetworkSwitching', | ||
value: ['0x5'], | ||
}, | ||
], | ||
}, | ||
Comment on lines
+4983
to
+4990
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same |
||
}, | ||
}), | ||
}); | ||
|
||
const snapController = getSnapController( | ||
getSnapControllerOptions({ | ||
messenger, | ||
detectSnapLocation: loopbackDetect({ manifest }), | ||
}), | ||
); | ||
|
||
await snapController.installSnaps(MOCK_ORIGIN, { | ||
[MOCK_SNAP_ID]: {}, | ||
}); | ||
|
||
const approvedPermissions = { | ||
'endowment:page-home': { | ||
caveats: null, | ||
}, | ||
'endowment:ethereum-provider': {}, | ||
[PERMITTED_CHAINS_ENDOWMENT]: { | ||
caveats: [ | ||
{ | ||
type: 'restrictNetworkSwitching', | ||
value: ['0x1'], | ||
}, | ||
], | ||
}, | ||
Comment on lines
+5011
to
+5018
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same re: which permission is now used |
||
}; | ||
|
||
expect(messenger.call).toHaveBeenCalledWith( | ||
'PermissionController:grantPermissions', | ||
{ | ||
approvedPermissions, | ||
subject: { origin: MOCK_SNAP_ID }, | ||
requestData: expect.any(Object), | ||
}, | ||
); | ||
|
||
snapController.destroy(); | ||
}); | ||
|
||
it('does not grant the `endowment:permitted-chains` permission if the Snap does not have the `endowment:ethereum-provider` permission', async () => { | ||
const rootMessenger = getControllerMessenger(); | ||
const messenger = getSnapControllerMessenger(rootMessenger); | ||
|
||
rootMessenger.registerActionHandler( | ||
'PermissionController:getPermissions', | ||
() => ({}), | ||
); | ||
|
||
rootMessenger.registerActionHandler( | ||
'SelectedNetworkController:getNetworkClientIdForDomain', | ||
() => { | ||
throw new Error('This should not be called.'); | ||
}, | ||
); | ||
|
||
rootMessenger.registerActionHandler( | ||
'NetworkController:getNetworkClientById', | ||
() => { | ||
throw new Error('This should not be called.'); | ||
}, | ||
); | ||
|
||
const { manifest } = await getMockSnapFilesWithUpdatedChecksum({ | ||
manifest: getSnapManifest({ | ||
initialPermissions: { | ||
'endowment:page-home': {}, | ||
}, | ||
}), | ||
}); | ||
|
||
const snapController = getSnapController( | ||
getSnapControllerOptions({ | ||
messenger, | ||
detectSnapLocation: loopbackDetect({ manifest }), | ||
}), | ||
); | ||
|
||
await snapController.installSnaps(MOCK_ORIGIN, { | ||
[MOCK_SNAP_ID]: {}, | ||
}); | ||
|
||
const approvedPermissions = { | ||
'endowment:page-home': { | ||
caveats: null, | ||
}, | ||
}; | ||
|
||
expect(messenger.call).toHaveBeenCalledWith( | ||
'PermissionController:grantPermissions', | ||
{ | ||
approvedPermissions, | ||
subject: { origin: MOCK_SNAP_ID }, | ||
requestData: expect.any(Object), | ||
}, | ||
); | ||
|
||
snapController.destroy(); | ||
}); | ||
|
||
it('supports preinstalled snaps', async () => { | ||
const rootMessenger = getControllerMessenger(); | ||
jest.spyOn(rootMessenger, 'call'); | ||
|
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.
i don't know a lot about snaps, but I'm assuming that this type cast has to happen here and can't be moved into the function params type