Skip to content
Merged
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
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