Skip to content

Commit

Permalink
Merge branch 'main' into feat/keyring-account-scopes
Browse files Browse the repository at this point in the history
  • Loading branch information
ccharly authored Jan 20, 2025
2 parents 0d3dfe4 + 5e95217 commit 19b9874
Show file tree
Hide file tree
Showing 73 changed files with 8,058 additions and 2,392 deletions.
24 changes: 17 additions & 7 deletions .storybook/test-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -1424,17 +1424,27 @@ const state = {
subjects: {
'https://app.uniswap.org': {
permissions: {
eth_accounts: {
invoker: 'https://app.uniswap.org',
parentCapability: 'eth_accounts',
id: 'a7342e4b-beae-4525-a36c-c0635fd03359',
date: 1620710693178,
'endowment:caip25': {
caveats: [
{
type: 'restrictReturnedAccounts',
value: ['0x64a845a5b02460acf8a3d84503b0d68d028b4bb4'],
type: 'authorizedScopes',
value: {
requiredScopes: {},
optionalScopes: {
'eip155:1': {
accounts: [
'eip155:1:0x64a845a5b02460acf8a3d84503b0d68d028b4bb4',
],
},
},
isMultichainOrigin: false,
},
},
],
invoker: 'https://app.uniswap.org',
id: 'a7342e4b-beae-4525-a36c-c0635fd03359',
date: 1620710693178,
parentCapability: 'endowment:caip25',
},
},
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
diff --git a/lib/index.js b/lib/index.js
index f5795884311124b221d91f488ed45750eb6e9c80..e030d6f8d8e85e6d1350c565d36ad48bc49af881 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -25,7 +25,7 @@ class Ptr {
});
return `/${tokens.join("/")}`;
}
- eval(instance) {
+ shmeval(instance) {
for (const token of this.tokens) {
if (instance.hasOwnProperty(token)) {
instance = instance[token];
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
diff --git a/build/resolve-pointer.js b/build/resolve-pointer.js
index d5a8ec7486250cd17572eb0e0449725643fc9842..044e74bb51a46e9bf3547f6d7a84763b93260613 100644
--- a/build/resolve-pointer.js
+++ b/build/resolve-pointer.js
@@ -27,7 +27,7 @@ exports.default = (function (ref, root) {
try {
var withoutHash = ref.replace("#", "");
var pointer = json_pointer_1.default.parse(withoutHash);
- return pointer.eval(root);
+ return pointer.shmeval(root);
}
catch (e) {
throw new InvalidJsonPointerRefError(ref, e.message);
8 changes: 2 additions & 6 deletions app/scripts/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -677,13 +677,9 @@ function emitDappViewedMetricEvent(origin) {
return;
}

const permissions = controller.controllerMessenger.call(
'PermissionController:getPermissions',
origin,
);
const numberOfConnectedAccounts =
permissions?.eth_accounts?.caveats[0]?.value.length;
if (!numberOfConnectedAccounts) {
controller.getPermittedAccounts(origin).length;
if (numberOfConnectedAccounts === 0) {
return;
}

Expand Down
241 changes: 183 additions & 58 deletions app/scripts/controllers/permissions/background-api.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,177 @@
import { nanoid } from 'nanoid';
import {
CaveatTypes,
RestrictedMethods,
} from '../../../../shared/constants/permissions';
import { CaveatFactories, PermissionNames } from './specifications';
MethodNames,
PermissionDoesNotExistError,
} from '@metamask/permission-controller';
import {
Caip25CaveatType,
Caip25EndowmentPermissionName,
getEthAccounts,
setEthAccounts,
getPermittedEthChainIds,
setPermittedEthChainIds,
} from '@metamask/multichain';
import { isSnapId } from '@metamask/snaps-utils';
import { RestrictedMethods } from '../../../../shared/constants/permissions';
import { PermissionNames } from './specifications';

export function getPermissionBackgroundApiMethods({
permissionController,
approvalController,
}) {
// Returns the CAIP-25 caveat or undefined if it does not exist
const getCaip25Caveat = (origin) => {
let caip25Caveat;
try {
caip25Caveat = permissionController.getCaveat(
origin,
Caip25EndowmentPermissionName,
Caip25CaveatType,
);
} catch (err) {
if (err instanceof PermissionDoesNotExistError) {
// suppress expected error in case that the origin
// does not have the target permission yet
} else {
throw err;
}
}
return caip25Caveat;
};

export function getPermissionBackgroundApiMethods(permissionController) {
// To add more than one account when already connected to the dapp
const addMoreAccounts = (origin, accounts) => {
const caveat = CaveatFactories.restrictReturnedAccounts(accounts);
const caip25Caveat = getCaip25Caveat(origin);
if (!caip25Caveat) {
throw new Error(
`Cannot add account permissions for origin "${origin}": no permission currently exists for this origin.`,
);
}

permissionController.grantPermissionsIncremental({
subject: { origin },
approvedPermissions: {
[RestrictedMethods.eth_accounts]: { caveats: [caveat] },
},
});
const ethAccounts = getEthAccounts(caip25Caveat.value);

const updatedEthAccounts = Array.from(
new Set([...ethAccounts, ...accounts]),
);

const updatedCaveatValue = setEthAccounts(
caip25Caveat.value,
updatedEthAccounts,
);

permissionController.updateCaveat(
origin,
Caip25EndowmentPermissionName,
Caip25CaveatType,
updatedCaveatValue,
);
};

const addMoreChains = (origin, chainIds) => {
const caveat = CaveatFactories.restrictNetworkSwitching(chainIds);
const caip25Caveat = getCaip25Caveat(origin);
if (!caip25Caveat) {
throw new Error(
`Cannot add chain permissions for origin "${origin}": no permission currently exists for this origin.`,
);
}

const ethChainIds = getPermittedEthChainIds(caip25Caveat.value);

const updatedEthChainIds = Array.from(
new Set([...ethChainIds, ...chainIds]),
);

const caveatValueWithChains = setPermittedEthChainIds(
caip25Caveat.value,
updatedEthChainIds,
);

// ensure that the list of permitted eth accounts is set for the newly added eth scopes
const ethAccounts = getEthAccounts(caveatValueWithChains);
const caveatValueWithAccountsSynced = setEthAccounts(
caveatValueWithChains,
ethAccounts,
);

permissionController.updateCaveat(
origin,
Caip25EndowmentPermissionName,
Caip25CaveatType,
caveatValueWithAccountsSynced,
);
};

const requestAccountsAndChainPermissions = async (origin, id) => {
// Note that we are purposely requesting an approval from the ApprovalController
// and then manually forming the permission that is then granted via the
// PermissionController rather than calling the PermissionController.requestPermissions()
// directly because the Approval UI is still dependent on the notion of there
// being separate "eth_accounts" and "endowment:permitted-chains" permissions.
// After that depedency is refactored, we can move to requesting "endowment:caip25"
// directly from the PermissionController instead.
const legacyApproval = await approvalController.addAndShowApprovalRequest({
id,
origin,
requestData: {
metadata: {
id,
origin,
},
permissions: {
[RestrictedMethods.eth_accounts]: {},
[PermissionNames.permittedChains]: {},
},
},
type: MethodNames.RequestPermissions,
});

const newCaveatValue = {
requiredScopes: {},
optionalScopes: {},
isMultichainOrigin: false,
};

const caveatValueWithChains = setPermittedEthChainIds(
newCaveatValue,
legacyApproval.approvedChainIds,
);

const caveatValueWithAccounts = setEthAccounts(
caveatValueWithChains,
legacyApproval.approvedAccounts,
);

permissionController.grantPermissionsIncremental({
permissionController.grantPermissions({
subject: { origin },
approvedPermissions: {
[PermissionNames.permittedChains]: { caveats: [caveat] },
[Caip25EndowmentPermissionName]: {
caveats: [
{
type: Caip25CaveatType,
value: caveatValueWithAccounts,
},
],
},
},
});
};

return {
addPermittedAccount: (origin, account) =>
addMoreAccounts(origin, [account]),

addPermittedAccounts: (origin, accounts) =>
addMoreAccounts(origin, accounts),

removePermittedAccount: (origin, account) => {
const { value: existingAccounts } = permissionController.getCaveat(
origin,
RestrictedMethods.eth_accounts,
CaveatTypes.restrictReturnedAccounts,
);
const caip25Caveat = getCaip25Caveat(origin);
if (!caip25Caveat) {
throw new Error(
`Cannot remove account "${account}": No permissions exist for origin "${origin}".`,
);
}

const existingAccounts = getEthAccounts(caip25Caveat.value);

const remainingAccounts = existingAccounts.filter(
(existingAccount) => existingAccount !== account,
Expand All @@ -52,73 +184,66 @@ export function getPermissionBackgroundApiMethods(permissionController) {
if (remainingAccounts.length === 0) {
permissionController.revokePermission(
origin,
RestrictedMethods.eth_accounts,
Caip25EndowmentPermissionName,
);
} else {
const updatedCaveatValue = setEthAccounts(
caip25Caveat.value,
remainingAccounts,
);
permissionController.updateCaveat(
origin,
RestrictedMethods.eth_accounts,
CaveatTypes.restrictReturnedAccounts,
remainingAccounts,
Caip25EndowmentPermissionName,
Caip25CaveatType,
updatedCaveatValue,
);
}
},

addPermittedChain: (origin, chainId) => addMoreChains(origin, [chainId]),

addPermittedChains: (origin, chainIds) => addMoreChains(origin, chainIds),

removePermittedChain: (origin, chainId) => {
const { value: existingChains } = permissionController.getCaveat(
origin,
PermissionNames.permittedChains,
CaveatTypes.restrictNetworkSwitching,
);
const caip25Caveat = getCaip25Caveat(origin);
if (!caip25Caveat) {
throw new Error(
`Cannot remove permission for chainId "${chainId}": No permissions exist for origin "${origin}".`,
);
}

const remainingChains = existingChains.filter(
(existingChain) => existingChain !== chainId,
const existingEthChainIds = getPermittedEthChainIds(caip25Caveat.value);

const remainingChainIds = existingEthChainIds.filter(
(existingChainId) => existingChainId !== chainId,
);

if (remainingChains.length === existingChains.length) {
if (remainingChainIds.length === existingEthChainIds.length) {
return;
}

if (remainingChains.length === 0) {
if (remainingChainIds.length === 0 && !isSnapId(origin)) {
permissionController.revokePermission(
origin,
PermissionNames.permittedChains,
Caip25EndowmentPermissionName,
);
} else {
const updatedCaveatValue = setPermittedEthChainIds(
caip25Caveat.value,
remainingChainIds,
);
permissionController.updateCaveat(
origin,
PermissionNames.permittedChains,
CaveatTypes.restrictNetworkSwitching,
remainingChains,
Caip25EndowmentPermissionName,
Caip25CaveatType,
updatedCaveatValue,
);
}
},

requestAccountsAndChainPermissionsWithId: async (origin) => {
requestAccountsAndChainPermissionsWithId: (origin) => {
const id = nanoid();
permissionController.requestPermissions(
{ origin },
{
[PermissionNames.eth_accounts]: {},
[PermissionNames.permittedChains]: {},
},
{ id },
);
return id;
},

requestAccountsPermissionWithId: async (origin) => {
const id = nanoid();
permissionController.requestPermissions(
{ origin },
{
eth_accounts: {},
},
{ id },
);
requestAccountsAndChainPermissions(origin, id);
return id;
},
};
Expand Down
Loading

0 comments on commit 19b9874

Please sign in to comment.