Skip to content

Commit c4cc96e

Browse files
authored
feat: populate verifyContext from core.verify.resolve attestation (#2)
1 parent f17a679 commit c4cc96e

4 files changed

Lines changed: 378 additions & 12 deletions

File tree

packages/auth-client/src/controllers/engine.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
isJsonRpcResponse,
99
isJsonRpcResult,
1010
} from "@exodus/walletconnect-jsonrpc-utils";
11-
import { RelayerTypes, Verify } from "@exodus/walletconnect-types";
11+
import { RelayerTypes } from "@exodus/walletconnect-types";
1212
import { getInternalError, hashKey, TYPE_1 } from "@exodus/walletconnect-utils";
1313
import { AUTH_CLIENT_PUBLIC_KEY_NAME, ENGINE_RPC_OPTS } from "../constants";
1414
import { AuthClientTypes, AuthEngineTypes, IAuthEngine, JsonRpcTypes } from "../types";
@@ -17,6 +17,7 @@ import { verifySignature } from "../utils/signature";
1717
import { getPendingRequest, getPendingRequests } from "../utils/store";
1818
import { isValidRequest, isValidRespond } from "../utils/validators";
1919
import { hashMessage } from "../utils/crypto";
20+
import { buildVerifyContext } from "../utils/verifyContext";
2021

2122
export class AuthEngine extends IAuthEngine {
2223
private initialized = false;
@@ -361,7 +362,7 @@ export class AuthEngine extends IAuthEngine {
361362
});
362363

363364
const hash = hashMessage(JSON.stringify(payload));
364-
const verifyContext = await this.getVerifyContext(hash, this.client.metadata);
365+
const verifyContext = await this.getVerifyContext(hash, requester.metadata);
365366

366367
this.client.emit("auth_request", {
367368
id: payload.id,
@@ -426,15 +427,13 @@ export class AuthEngine extends IAuthEngine {
426427
}
427428
};
428429

429-
private getVerifyContext = async (_hash: string, metadata: AuthClientTypes.Metadata) => {
430-
const context: Verify.Context = {
431-
verified: {
432-
verifyUrl: metadata.verifyUrl || "",
433-
validation: "UNKNOWN",
434-
origin: metadata.url || "",
430+
private getVerifyContext = (hash: string, metadata: AuthClientTypes.Metadata) =>
431+
buildVerifyContext(
432+
{
433+
resolve: (args) => this.client.core.verify.resolve(args),
434+
logError: (e) => this.client.logger.error(e),
435435
},
436-
};
437-
438-
return context;
439-
};
436+
hash,
437+
metadata,
438+
);
440439
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { Verify } from "@exodus/walletconnect-types";
2+
import { AuthClientTypes } from "../types";
3+
4+
export interface BuildVerifyContextDeps {
5+
resolve: (args: {
6+
attestationId: string;
7+
verifyUrl?: string;
8+
}) => Promise<{ origin?: string; isScam?: boolean } | string | undefined>;
9+
logError?: (err: unknown) => void;
10+
}
11+
12+
export const buildVerifyContext = async (
13+
{ resolve, logError }: BuildVerifyContextDeps,
14+
attestationId: string,
15+
metadata: AuthClientTypes.Metadata,
16+
): Promise<Verify.Context> => {
17+
if (!metadata?.url) {
18+
const context: Verify.Context = {
19+
verified: { verifyUrl: "", validation: "UNKNOWN", origin: "" },
20+
};
21+
try {
22+
const attestation = await resolve({ attestationId });
23+
if (attestation) {
24+
const origin =
25+
typeof attestation === "string" ? attestation : (attestation as any).origin;
26+
if (origin && typeof origin === "string") {
27+
// Registered dApps always declare metadata.url; receiving an attested
28+
// origin while the dApp claims nothing is mismatch by definition.
29+
context.verified.origin = origin;
30+
context.verified.validation = "INVALID";
31+
}
32+
if (typeof attestation === "object" && (attestation as any).isScam) {
33+
context.verified.isScam = true;
34+
}
35+
}
36+
} catch (e) {
37+
logError?.(e);
38+
}
39+
return context;
40+
}
41+
42+
const context: Verify.Context = {
43+
verified: {
44+
verifyUrl: metadata.verifyUrl || "",
45+
validation: "UNKNOWN",
46+
origin: metadata.url || "",
47+
},
48+
};
49+
50+
try {
51+
const attestation = await resolve({
52+
attestationId,
53+
verifyUrl: metadata.verifyUrl,
54+
});
55+
if (attestation) {
56+
const origin = typeof attestation === "string" ? attestation : (attestation as any).origin;
57+
if (origin && typeof origin === "string") {
58+
context.verified.origin = origin;
59+
let claimedOrigin = metadata.url || "";
60+
try {
61+
claimedOrigin = new URL(metadata.url).origin;
62+
} catch {}
63+
context.verified.validation = origin === claimedOrigin ? "VALID" : "INVALID";
64+
}
65+
if (typeof attestation === "object" && (attestation as any).isScam) {
66+
context.verified.isScam = true;
67+
}
68+
}
69+
} catch (e) {
70+
logError?.(e);
71+
}
72+
73+
return context;
74+
};
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { expect, describe, it, vi, beforeEach } from "vitest";
2+
import { AuthEngine } from "../src/controllers/engine";
3+
import { AuthClientTypes } from "../src/types";
4+
5+
const WALLET_URL = "https://mywallet.app";
6+
const DAPP_URL = "https://app.uniswap.org";
7+
8+
const walletMetadata: AuthClientTypes.Metadata = {
9+
name: "My Wallet",
10+
description: "",
11+
url: WALLET_URL,
12+
icons: [],
13+
};
14+
15+
const dappMetadata: AuthClientTypes.Metadata = {
16+
name: "Uniswap",
17+
description: "",
18+
url: DAPP_URL,
19+
icons: [],
20+
};
21+
22+
const authRequestPayload = {
23+
id: 1,
24+
jsonrpc: "2.0" as const,
25+
method: "wc_authRequest" as const,
26+
params: {
27+
requester: {
28+
publicKey: "abc123",
29+
metadata: dappMetadata,
30+
},
31+
payloadParams: {
32+
type: "eip4361" as const,
33+
chainId: "eip155:1",
34+
domain: "app.uniswap.org",
35+
aud: "https://app.uniswap.org/login",
36+
version: "1",
37+
nonce: "deadbeef",
38+
iat: new Date().toISOString(),
39+
},
40+
},
41+
};
42+
43+
function createMockClient(verifyResolveReturn: unknown) {
44+
return {
45+
metadata: walletMetadata,
46+
logger: {
47+
info: vi.fn(),
48+
error: vi.fn(),
49+
debug: vi.fn(),
50+
},
51+
requests: {
52+
set: vi.fn().mockResolvedValue(undefined),
53+
},
54+
core: {
55+
verify: {
56+
resolve: vi.fn().mockResolvedValue(verifyResolveReturn),
57+
},
58+
pairing: {
59+
register: vi.fn(),
60+
},
61+
},
62+
emit: vi.fn(),
63+
} as any;
64+
}
65+
66+
describe("AuthEngine.onAuthRequest verifyContext wiring", () => {
67+
it("produces VALID when attested origin matches requester (dApp) URL", async () => {
68+
const mockClient = createMockClient({ origin: DAPP_URL });
69+
const engine = new AuthEngine(mockClient);
70+
71+
await (engine as any).onAuthRequest("topic", authRequestPayload);
72+
73+
const [eventName, eventArgs] = mockClient.emit.mock.calls[0];
74+
expect(eventName).toBe("auth_request");
75+
expect(eventArgs.verifyContext.verified.validation).toBe("VALID");
76+
expect(eventArgs.verifyContext.verified.origin).toBe(DAPP_URL);
77+
});
78+
79+
it("produces INVALID when attested origin matches wallet URL but not requester URL (regression guard)", async () => {
80+
const mockClient = createMockClient({ origin: WALLET_URL });
81+
const engine = new AuthEngine(mockClient);
82+
83+
await (engine as any).onAuthRequest("topic", authRequestPayload);
84+
85+
const [eventName, eventArgs] = mockClient.emit.mock.calls[0];
86+
expect(eventName).toBe("auth_request");
87+
expect(eventArgs.verifyContext.verified.validation).toBe("INVALID");
88+
expect(eventArgs.verifyContext.verified.origin).toBe(WALLET_URL);
89+
});
90+
});

0 commit comments

Comments
 (0)