Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion app/components/Views/QRScanner/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { useAnalytics } from '../../../components/hooks/useAnalytics/useAnalytic
import { MetaMetricsEvents } from '../../../core/Analytics';
import { QRType, QRScannerEventProperties, ScanResult } from './constants';
import { getQRType } from './utils';
import Logger from '../../../util/Logger';

const frameImage = require('../../../images/frame.png'); // eslint-disable-line import-x/no-commonjs

Expand Down Expand Up @@ -177,22 +178,36 @@ const QRScanner = ({

const onBarCodeRead = useCallback(
async (codes: Code[]) => {
Logger.log('[wc][scan] onBarCodeRead invoked', {
codesCount: codes.length,
shouldRead: shouldReadBarCodeRef.current,
mounted: mountedRef.current,
origin,
});
// Early exit if no codes detected
if (!codes.length) return;
if (!codes.length) {
Logger.log('[wc][scan] exit: no codes');
return;
}

/**
* Barcode read triggers multiple times
* shouldReadBarCodeRef controls how often the logic below runs
* Think of this as a allow or disallow bar code reading
*/
if (!shouldReadBarCodeRef.current || !mountedRef.current) {
Logger.log('[wc][scan] exit: read blocked by refs', {
shouldRead: shouldReadBarCodeRef.current,
mounted: mountedRef.current,
});
return;
}

const response = { data: codes[0].value };
let content = response.data;

if (!content) {
Logger.log('[wc][scan] exit: empty code value');
return;
}

Expand Down Expand Up @@ -552,6 +567,23 @@ const QRScanner = ({
return;
}

const looksLikeWalletConnect =
content.startsWith('wc:') ||
content.startsWith('wc://') ||
content.includes('wc:');

if (looksLikeWalletConnect) {
Logger.log(
'[wc][QRScanner] parsing scanned WalletConnect QR content',
{
origin,
contentPreview: `${content.slice(0, 120)}${
content.length > 120 ? '...' : ''
}`,
},
);
}

const handledByDeeplink = await SharedDeeplinkManager.parse(content, {
origin: AppConstants.DEEPLINKS.ORIGIN_QR_CODE,
onHandled: () => {
Expand All @@ -563,6 +595,9 @@ const QRScanner = ({
});

if (handledByDeeplink) {
if (looksLikeWalletConnect) {
Logger.log('[wc][QRScanner] SharedDeeplinkManager handled content');
}
trackEvent(
createEventBuilder(MetaMetricsEvents.QR_SCANNED)
.addProperties({
Expand Down
72 changes: 59 additions & 13 deletions app/core/DeeplinkManager/handlers/legacy/connectWithWC.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { INTERNAL_ORIGINS } from '../../../../constants/transaction';
import WC2Manager from '../../../WalletConnect/WalletConnectV2';
import WC2Manager, {
isWC2Enabled,
} from '../../../WalletConnect/WalletConnectV2';
import DevLogger from '../../../SDKConnect/utils/DevLogger';
import extractURLParams from '../../utils/extractURLParams';

export function connectWithWC({
export async function connectWithWC({
handled,
wcURL,
origin,
Expand All @@ -14,21 +17,64 @@ export function connectWithWC({
params: ReturnType<typeof extractURLParams>['params'];
}) {
handled();

const preview = wcURL.length > 160 ? `${wcURL.slice(0, 160)}...` : wcURL;
DevLogger.log('[wc][connectWithWC] connect called:', {
origin,
wcURLPreview: preview,
redirect: params?.redirect,
});

if (params.channelId && INTERNAL_ORIGINS.includes(params.channelId)) {
throw new Error('External transactions cannot use internal origins');
DevLogger.log(
'[wc][connectWithWC] rejected wc connection due to internal channelId',
{ channelId: params.channelId },
);
return;
}

try {
if (!isWC2Enabled) {
DevLogger.log(
'[wc][connectWithWC] WC2 is not enabled (missing/empty WALLET_CONNECT_PROJECT_ID)',
);
return;
}
DevLogger.log('[wc][connectWithWC] ensuring WC2Manager is initialized');
// Must pass an options object; the init signature destructures params.
await WC2Manager.init({});
} catch (err) {
DevLogger.log(
'[wc][connectWithWC] WC2Manager.init() failed or skipped',
err,
);
}

WC2Manager.getInstance()
.then((instance) =>
instance.connect({
wcUri: wcURL,
origin,
redirectUrl: params?.redirect,
}),
)
.catch((err) => {
console.warn(`connectWithWC failed`, err);
try {
const instance = await Promise.race([
WC2Manager.getInstance(),
new Promise<never>((_, reject) =>
setTimeout(
() =>
reject(
new Error(
'Timed out waiting for WC2Manager.getInstance() to resolve',
),
),
5000,
),
),
]);

DevLogger.log('[wc][connectWithWC] instance ready, calling connect');
await instance.connect({
wcUri: wcURL,
origin,
redirectUrl: params?.redirect,
});
} catch (err) {
DevLogger.log('[wc][connectWithWC] failed', err);
}
}

export default connectWithWC;
8 changes: 7 additions & 1 deletion app/core/DeeplinkManager/utils/parseDeeplink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,15 @@ async function parseDeeplink({
});
break;
}
case PROTOCOLS.WC:
case PROTOCOLS.WC: {
const preview = url.length > 160 ? `${url.slice(0, 160)}...` : url;
DevLogger.log('[wc][parseDeeplink] detected wc:', {
origin,
preview,
});
connectWithWC({ handled, wcURL, origin, params });
break;
}
case PROTOCOLS.ETHEREUM:
handled();
handleEthereumUrl({
Expand Down
51 changes: 32 additions & 19 deletions app/core/Permissions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
parseCaipAccountId,
parseCaipChainId,
} from '@metamask/utils';
///: BEGIN:ONLY_INCLUDE_IF(tron)
import { TrxScope } from '@metamask/keyring-api';
///: END:ONLY_INCLUDE_IF
import { InternalAccount } from '@metamask/keyring-internal-api';
import {
Caip25CaveatType,
Expand All @@ -16,7 +19,7 @@
getAllScopesFromCaip25CaveatValue,
getCaipAccountIdsFromCaip25CaveatValue,
getEthAccounts,
getPermittedEthChainIds,

Check warning on line 22 in app/core/Permissions/index.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused import of 'getPermittedEthChainIds'.

See more on https://sonarcloud.io/project/issues?id=metamask-mobile&issues=AZ1EaTcNPU4l3pg_ECoJ&open=AZ1EaTcNPU4l3pg_ECoJ&pullRequest=28172
isInternalAccountInPermittedAccountIds,
setChainIdsInCaip25CaveatValue,
setNonSCACaipAccountIdsInCaip25CaveatValue,
Expand Down Expand Up @@ -297,18 +300,25 @@

/**
* Returns a default CAIP-25 caveat value.
* Each chain appends its optional scope below (feature-flagged).
* @returns Default {@link Caip25CaveatValue}
*/
export const getDefaultCaip25CaveatValue = (): Caip25CaveatValue => ({
requiredScopes: {},
optionalScopes: {
'wallet:eip155': {
accounts: [],
},
},
sessionProperties: {},
isMultichainOrigin: false,
});
export const getDefaultCaip25CaveatValue = (): Caip25CaveatValue => {
const optionalScopes: Caip25CaveatValue['optionalScopes'] = {
'wallet:eip155': { accounts: [] },
};

///: BEGIN:ONLY_INCLUDE_IF(tron)
optionalScopes[TrxScope.Mainnet] = { accounts: [] };
///: END:ONLY_INCLUDE_IF

return {
requiredScopes: {},
optionalScopes,
sessionProperties: {},
isMultichainOrigin: false,
};
};

// Returns the CAIP-25 caveat or undefined if it does not exist
export const getCaip25Caveat = (origin: string) => {
Expand Down Expand Up @@ -624,10 +634,11 @@
};

/**
* Get permitted chains for the given the host.
* Get permitted chains for the given the host, across all namespaces.
* Returns all non-wallet scopes stored in the CAIP-25 caveat.
*
* @param hostname - Subject to check if permissions exists. Ex: A Dapp is a subject.
* @returns An array containing permitted chains for the specified host.
* @returns An array containing permitted CAIP chain IDs for the specified host.
*/
export const getPermittedChains = async (
hostname: string,
Expand All @@ -640,14 +651,16 @@
);

if (caveat) {
const chains = getPermittedEthChainIds(caveat.value).map(
(chainId: string) =>
`${KnownCaipNamespace.Eip155.toString()}:${parseInt(
chainId,
)}` as CaipChainId,
return getAllScopesFromCaip25CaveatValue(caveat.value).filter(
(caipChainId: CaipChainId) => {
try {
const { namespace } = parseCaipChainId(caipChainId);
return namespace !== KnownCaipNamespace.Wallet;
} catch {
return false;
}
},
);

return chains;
}

return [];
Expand Down
Loading
Loading