Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
7 changes: 7 additions & 0 deletions app/components/Views/AccountConnect/AccountConnect.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,7 @@ describe('AccountConnect', () => {
const mockStateWithoutWC2 = {
...mockInitialState,
sdk: {
v2Connections: {},
wc2Metadata: { id: '' }, // Empty to avoid WalletConnect branch
},
};
Expand Down Expand Up @@ -1072,6 +1073,7 @@ describe('AccountConnect', () => {
const mockStateWithWC2 = {
...mockInitialState,
sdk: {
v2Connections: {},
wc2Metadata: { id: 'mock-wc2-id' }, // Non-empty to trigger WalletConnect branch
},
};
Expand Down Expand Up @@ -1118,6 +1120,7 @@ describe('AccountConnect', () => {
const mockStateWithoutWC2 = {
...mockInitialState,
sdk: {
v2Connections: {},
wc2Metadata: { id: '' }, // Empty to avoid WalletConnect branch
},
};
Expand Down Expand Up @@ -1158,6 +1161,7 @@ describe('AccountConnect', () => {
const mockMaliciousState = {
...mockInitialState,
sdk: {
v2Connections: {},
wc2Metadata: {
id: 'mock-wc2-id',
url: 'https://malicious-dapp.com',
Expand Down Expand Up @@ -1278,6 +1282,7 @@ describe('AccountConnect', () => {
const cleanState = {
...mockInitialState,
sdk: {
v2Connections: {},
wc2Metadata: {
id: 'mock-wc2-id',
url: 'https://clean-dapp.com',
Expand Down Expand Up @@ -1325,6 +1330,7 @@ describe('AccountConnect', () => {
const verifiedState = {
...mockInitialState,
sdk: {
v2Connections: {},
wc2Metadata: {
id: 'mock-wc2-id',
url: 'https://safe-dapp.com',
Expand Down Expand Up @@ -1354,6 +1360,7 @@ describe('AccountConnect', () => {
const nonWcState = {
...mockInitialState,
sdk: {
v2Connections: {},
wc2Metadata: {
id: '',
url: '',
Expand Down
56 changes: 55 additions & 1 deletion app/components/hooks/useOriginSource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ jest.mock('react-redux', () => ({
wc2Metadata: {
id: '',
},
v2Connections: {},
},
} as RootState),
),
Expand Down Expand Up @@ -43,6 +44,7 @@ describe('useOriginSource', () => {
wc2Metadata: {
id: '',
},
v2Connections: {},
},
} as RootState;

Expand All @@ -67,6 +69,58 @@ describe('useOriginSource', () => {
expect(result.current).toBe(SourceType.SDK);
});

it('should return SDK_CONNECT_V2 source for UUID origin present in v2Connections', () => {
const v2Uuid = 'aabbccdd-1122-3344-5566-778899aabbcc';
const v2State = {
...mockState,
sdk: {
...mockState.sdk,
v2Connections: {
[v2Uuid]: { id: v2Uuid, isV2: true },
},
},
} as unknown as RootState;

mockSelector.mockImplementation((selector: (state: RootState) => unknown) =>
selector(v2State),
);

const { result } = renderHook(() => useOriginSource({ origin: v2Uuid }));
expect(result.current).toBe(SourceType.SDK_CONNECT_V2);
});

it('should prefer SDK_CONNECT_V2 over SDK when UUID exists in both stores', () => {
// UUID '123e4567-...' is recognized by the V1 SDKConnect mock above.
// When it also appears in v2Connections, V2 should win.
const sharedUuid = '123e4567-e89b-12d3-a456-426614174000';
const v2State = {
...mockState,
sdk: {
...mockState.sdk,
v2Connections: {
[sharedUuid]: { id: sharedUuid, isV2: true },
},
},
} as unknown as RootState;

mockSelector.mockImplementation((selector: (state: RootState) => unknown) =>
selector(v2State),
);

const { result } = renderHook(() =>
useOriginSource({ origin: sharedUuid }),
);
expect(result.current).toBe(SourceType.SDK_CONNECT_V2);
});

it('should not return SDK_CONNECT_V2 for UUID origin not in v2Connections', () => {
const unknownUuid = 'aabbccdd-1122-3344-5566-000000000000';
const { result } = renderHook(() =>
useOriginSource({ origin: unknownUuid }),
);
expect(result.current).toBe(SourceType.IN_APP_BROWSER);
});

it('should return SDK source for SDK_REMOTE_ORIGIN', () => {
const { result } = renderHook(() =>
useOriginSource({
Expand All @@ -77,10 +131,10 @@ describe('useOriginSource', () => {
});

it('should return WALLET_CONNECT source when WC metadata is present', () => {
// Mock WalletConnect metadata
const wcState = {
...mockState,
sdk: {
...mockState.sdk,
wc2Metadata: {
id: 'some-wc-id',
},
Expand Down
58 changes: 39 additions & 19 deletions app/components/hooks/useOriginSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,59 @@ interface UseOriginSourceProps {

type SourceTypeValue = (typeof SourceType)[keyof typeof SourceType];

/**
* Determines the connection source type for analytics from a permission-request origin.
*
* The origin value varies by connection type:
* - SDK v2 (MWP): bare UUID (the connection/session ID)
* - SDK v1: bare UUID, or prefixed with "MMSDKREMOTE::"
* - WalletConnect: the dapp URL (no special prefix)
* - In-app browser: the dapp URL
*
* We check in priority order: SDK v2 → SDK v1 → WalletConnect → browser.
*/
export const useOriginSource = ({
origin,
}: UseOriginSourceProps): SourceTypeValue | undefined => {
const { wc2Metadata } = useSelector((state: RootState) => state.sdk);
const { wc2Metadata, v2Connections } = useSelector(
(state: RootState) => state.sdk,
);

// Return undefined if origin is undefined
if (!origin) {
return undefined;
}

// Check for V2 connections
if (origin.startsWith(AppConstants.MM_SDK.SDK_CONNECT_V2_ORIGIN)) {
// --- SDK v2 (MWP) ---
// V2 connections use the bare session UUID as the permission-system origin.
// Look it up in the v2Connections store (populated by
// HostApplicationAdapter.syncConnectionList, keyed by connection ID).
if (isUUID(origin) && v2Connections?.[origin]) {
return SourceType.SDK_CONNECT_V2;
}

// Check if origin is a UUID (SDK channel ID format) or starts with SDK_REMOTE_ORIGIN
const isChannelId = isUUID(origin);
const isSDKRemoteOrigin = origin.startsWith(
AppConstants.MM_SDK.SDK_REMOTE_ORIGIN,
);

const sdkConnection = isChannelId
? SDKConnect.getInstance().getConnection({ channelId: origin })
: undefined;

// Check if it's SDK (either by UUID connection or remote origin)
if (sdkConnection || isSDKRemoteOrigin) {
// --- SDK v1 ---
// V1 origins are either a bare UUID (channel ID) found in the SDKConnect
// singleton, or prefixed with "MMSDKREMOTE::" (used as the connection host
// in approved-hosts, display logic, etc.).
if (origin.startsWith(AppConstants.MM_SDK.SDK_REMOTE_ORIGIN)) {
return SourceType.SDK;
}
if (isUUID(origin)) {
const sdkConnection = SDKConnect.getInstance().getConnection({
channelId: origin,
});
if (sdkConnection) {
return SourceType.SDK;
}
}

// Check if origin matches WalletConnect metadata
const isWalletConnect = wc2Metadata?.id && wc2Metadata.id.length > 0;
if (isWalletConnect) {
// --- WalletConnect ---
// wc2Metadata is a single Redux slot holding the *most recent* WC proposal
// metadata (set on session_proposal, cleared after approval/rejection).
// It is not keyed by origin — we rely on the WC proposal flow being
// serialized (via proposalLock in WalletConnectV2) so that during the
// approval window, a non-empty id implies *this* origin is from WC.
Comment on lines +61 to +65
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😭 😭 😭

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created a ticket to improve this here: https://consensyssoftware.atlassian.net/browse/WAPI-1383

if (wc2Metadata?.id && wc2Metadata.id.length > 0) {
return SourceType.WALLET_CONNECT;
}

Expand Down
Loading