@oma3/omatrust 0.1.0-alpha.7 → 0.1.0-alpha.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,195 @@
1
+ 'use strict';
2
+
3
+ // src/widgets/protocol.ts
4
+ var OMATRUST_READY = "omatrust:ready";
5
+ var OMATRUST_HOST_READY = "omatrust:hostReady";
6
+ var OMATRUST_SIGN_TYPED_DATA = "omatrust:signTypedData";
7
+ var OMATRUST_SIGNATURE = "omatrust:signature";
8
+ var OMATRUST_SIGNATURE_ERROR = "omatrust:signatureError";
9
+
10
+ // src/widgets/trust-policy.ts
11
+ var DEFAULT_TRUST_POLICY_URL = "https://api.omatrust.org/v1/trust-policy";
12
+ var TRUST_POLICY_URL = DEFAULT_TRUST_POLICY_URL;
13
+ var cachedPolicy = null;
14
+ var cacheTimestamp = 0;
15
+ var CACHE_TTL_MS = 5 * 60 * 1e3;
16
+ async function fetchTrustPolicy(url) {
17
+ const now = Date.now();
18
+ if (cachedPolicy && now - cacheTimestamp < CACHE_TTL_MS) {
19
+ return cachedPolicy;
20
+ }
21
+ const res = await fetch(url ?? TRUST_POLICY_URL);
22
+ if (!res.ok) {
23
+ throw new Error(`Failed to fetch trust policy: ${res.status} ${res.statusText}`);
24
+ }
25
+ const policy = await res.json();
26
+ if (!policy.version || !policy.chains || typeof policy.chains !== "object") {
27
+ throw new Error("Invalid trust policy format");
28
+ }
29
+ cachedPolicy = policy;
30
+ cacheTimestamp = now;
31
+ return policy;
32
+ }
33
+ function extractAllowlists(policy) {
34
+ const contracts = [];
35
+ const schemas = [];
36
+ for (const chain of Object.values(policy.chains)) {
37
+ if (chain.easContract) contracts.push(chain.easContract);
38
+ if (chain.schemas) schemas.push(...chain.schemas);
39
+ }
40
+ return {
41
+ allowedContracts: [...new Set(contracts)],
42
+ allowedSchemas: [...new Set(schemas)]
43
+ };
44
+ }
45
+
46
+ // src/widgets/bridge.ts
47
+ function getTrustedBaseDomain() {
48
+ try {
49
+ const hostname = new URL(TRUST_POLICY_URL).hostname;
50
+ const parts = hostname.split(".");
51
+ return parts.length >= 2 ? parts.slice(-2).join(".") : hostname;
52
+ } catch {
53
+ return "omatrust.org";
54
+ }
55
+ }
56
+ function isOriginTrusted(messageOrigin, baseDomain, policyOrigins, devOverride) {
57
+ if (devOverride && messageOrigin === devOverride) return true;
58
+ try {
59
+ const hostname = new URL(messageOrigin).hostname;
60
+ if (hostname === baseDomain || hostname.endsWith(`.${baseDomain}`)) return true;
61
+ } catch {
62
+ }
63
+ if (policyOrigins.includes(messageOrigin)) return true;
64
+ return false;
65
+ }
66
+ var HEX_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
67
+ var BYTES32_RE = /^0x[a-fA-F0-9]{64}$/;
68
+ function validateEasSigningRequest(data, allowedSchemas, allowedContracts) {
69
+ const { id, domain, types, message } = data;
70
+ if (!id || typeof id !== "string") {
71
+ return { valid: false, reason: "Missing or invalid request id" };
72
+ }
73
+ if (!domain || typeof domain !== "object") {
74
+ return { valid: false, reason: "Missing domain object" };
75
+ }
76
+ const d = domain;
77
+ if (d.name !== "EAS") {
78
+ return { valid: false, reason: `Unexpected domain name: "${d.name}" (expected "EAS")` };
79
+ }
80
+ if (d.version !== "1.4.0") {
81
+ return { valid: false, reason: `Unexpected domain version: "${d.version}" (expected "1.4.0")` };
82
+ }
83
+ const chainId = Number(d.chainId);
84
+ if (!Number.isInteger(chainId) || chainId <= 0) {
85
+ return { valid: false, reason: `Invalid domain chainId: ${d.chainId}` };
86
+ }
87
+ if (typeof d.verifyingContract !== "string" || !HEX_ADDRESS_RE.test(d.verifyingContract)) {
88
+ return { valid: false, reason: `Invalid verifyingContract: ${d.verifyingContract}` };
89
+ }
90
+ const contractLower = d.verifyingContract.toLowerCase();
91
+ if (!allowedContracts.some((c) => c.toLowerCase() === contractLower)) {
92
+ return { valid: false, reason: `Contract ${d.verifyingContract} is not in the OMA3 trust policy` };
93
+ }
94
+ if (!types || typeof types !== "object") {
95
+ return { valid: false, reason: "Missing types object" };
96
+ }
97
+ if (!message || typeof message !== "object") {
98
+ return { valid: false, reason: "Missing message object" };
99
+ }
100
+ const m = message;
101
+ if (typeof m.schema !== "string" || !BYTES32_RE.test(m.schema)) {
102
+ return { valid: false, reason: `Invalid schema UID: ${m.schema}` };
103
+ }
104
+ const schemaLower = m.schema.toLowerCase();
105
+ if (!allowedSchemas.some((s) => s.toLowerCase() === schemaLower)) {
106
+ return { valid: false, reason: `Schema ${m.schema} is not in the OMA3 trust policy` };
107
+ }
108
+ if (typeof m.attester !== "string" || !HEX_ADDRESS_RE.test(m.attester)) {
109
+ return { valid: false, reason: `Invalid attester address: ${m.attester}` };
110
+ }
111
+ const deadline = Number(m.deadline);
112
+ if (!Number.isFinite(deadline) || deadline <= 0) {
113
+ return { valid: false, reason: `Invalid deadline: ${m.deadline}` };
114
+ }
115
+ const now = Math.floor(Date.now() / 1e3);
116
+ if (deadline <= now) {
117
+ return { valid: false, reason: `Deadline has passed: ${deadline} <= ${now}` };
118
+ }
119
+ return { valid: true };
120
+ }
121
+ async function createSigningBridge(options) {
122
+ const { iframeId, signTypedData, devOriginOverride } = options;
123
+ const policy = await fetchTrustPolicy();
124
+ const { allowedContracts, allowedSchemas } = extractAllowlists(policy);
125
+ if (allowedContracts.length === 0 || allowedSchemas.length === 0) {
126
+ throw new Error("Trust policy contains no allowed contracts or schemas");
127
+ }
128
+ const baseDomain = getTrustedBaseDomain();
129
+ const policyOrigins = policy.widgetOrigins ?? [];
130
+ async function handleMessage(event) {
131
+ const data = event.data;
132
+ if (!data || typeof data !== "object" || typeof data.type !== "string") return;
133
+ if (!String(data.type).startsWith("omatrust:")) return;
134
+ if (!isOriginTrusted(event.origin, baseDomain, policyOrigins, devOriginOverride)) {
135
+ return;
136
+ }
137
+ const iframe = document.getElementById(iframeId);
138
+ if (!iframe) return;
139
+ if (event.source !== iframe.contentWindow) {
140
+ return;
141
+ }
142
+ const source = event.source;
143
+ const replyOrigin = devOriginOverride ?? (() => {
144
+ try {
145
+ return new URL(iframe.src).origin;
146
+ } catch {
147
+ return "*";
148
+ }
149
+ })();
150
+ function reply(msg) {
151
+ source.postMessage(msg, replyOrigin);
152
+ }
153
+ if (data.type === OMATRUST_READY) {
154
+ reply({ type: OMATRUST_HOST_READY });
155
+ return;
156
+ }
157
+ if (data.type === OMATRUST_SIGN_TYPED_DATA) {
158
+ const { id, domain, types, message } = data;
159
+ const validation = validateEasSigningRequest(data, allowedSchemas, allowedContracts);
160
+ if (!validation.valid) {
161
+ reply({
162
+ type: OMATRUST_SIGNATURE_ERROR,
163
+ id: id ?? "unknown",
164
+ error: `Signing request rejected: ${validation.reason}`
165
+ });
166
+ return;
167
+ }
168
+ try {
169
+ const signature = await signTypedData(domain, types, message);
170
+ reply({ type: OMATRUST_SIGNATURE, id, signature });
171
+ } catch (err) {
172
+ const error = err instanceof Error ? err.message : "Signing failed";
173
+ reply({ type: OMATRUST_SIGNATURE_ERROR, id, error });
174
+ }
175
+ }
176
+ }
177
+ window.addEventListener("message", handleMessage);
178
+ return {
179
+ destroy() {
180
+ window.removeEventListener("message", handleMessage);
181
+ }
182
+ };
183
+ }
184
+
185
+ exports.OMATRUST_HOST_READY = OMATRUST_HOST_READY;
186
+ exports.OMATRUST_READY = OMATRUST_READY;
187
+ exports.OMATRUST_SIGNATURE = OMATRUST_SIGNATURE;
188
+ exports.OMATRUST_SIGNATURE_ERROR = OMATRUST_SIGNATURE_ERROR;
189
+ exports.OMATRUST_SIGN_TYPED_DATA = OMATRUST_SIGN_TYPED_DATA;
190
+ exports.TRUST_POLICY_URL = TRUST_POLICY_URL;
191
+ exports.createSigningBridge = createSigningBridge;
192
+ exports.extractAllowlists = extractAllowlists;
193
+ exports.fetchTrustPolicy = fetchTrustPolicy;
194
+ //# sourceMappingURL=index.cjs.map
195
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/widgets/protocol.ts","../../src/widgets/trust-policy.ts","../../src/widgets/bridge.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * postMessage protocol constants and types for the OMATrust widget bridge.\n *\n * Widget (iframe) → Host:\n * omatrust:ready — widget loaded, requesting handshake\n * omatrust:signTypedData — widget requests EIP-712 signature\n *\n * Host → Widget (iframe):\n * omatrust:hostReady — host acknowledges the widget\n * omatrust:signature — host returns a signature\n * omatrust:signatureError — host reports a signing failure\n */\n\nexport const OMATRUST_READY = \"omatrust:ready\" as const;\nexport const OMATRUST_HOST_READY = \"omatrust:hostReady\" as const;\nexport const OMATRUST_SIGN_TYPED_DATA = \"omatrust:signTypedData\" as const;\nexport const OMATRUST_SIGNATURE = \"omatrust:signature\" as const;\nexport const OMATRUST_SIGNATURE_ERROR = \"omatrust:signatureError\" as const;\n\nexport type OmaTrustReadyMessage = {\n type: typeof OMATRUST_READY;\n};\n\nexport type OmaTrustHostReadyMessage = {\n type: typeof OMATRUST_HOST_READY;\n};\n\nexport type OmaTrustSignTypedDataMessage = {\n type: typeof OMATRUST_SIGN_TYPED_DATA;\n id: string;\n domain: Record<string, unknown>;\n types: Record<string, unknown>;\n message: Record<string, unknown>;\n};\n\nexport type OmaTrustSignatureMessage = {\n type: typeof OMATRUST_SIGNATURE;\n id: string;\n signature: string;\n};\n\nexport type OmaTrustSignatureErrorMessage = {\n type: typeof OMATRUST_SIGNATURE_ERROR;\n id: string;\n error: string;\n};\n\nexport type OmaTrustMessage =\n | OmaTrustReadyMessage\n | OmaTrustHostReadyMessage\n | OmaTrustSignTypedDataMessage\n | OmaTrustSignatureMessage\n | OmaTrustSignatureErrorMessage;\n","/**\n * Trust policy: fetches the OMA3 allowlist of EAS contracts and schemas.\n *\n * The bridge uses this to validate signing requests automatically.\n * Developers don't need to maintain their own allowlists.\n */\n\nconst DEFAULT_TRUST_POLICY_URL = \"https://api.omatrust.org/v1/trust-policy\";\n\nexport const TRUST_POLICY_URL = DEFAULT_TRUST_POLICY_URL;\n\nexport type ChainPolicy = {\n name: string;\n easContract: string;\n schemas: string[];\n};\n\nexport type TrustPolicy = {\n version: number;\n updatedAt: string;\n widgetOrigins?: string[];\n chains: Record<string, ChainPolicy>;\n};\n\nlet cachedPolicy: TrustPolicy | null = null;\nlet cacheTimestamp = 0;\nconst CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes\n\n/**\n * Fetch the trust policy from the OMA3 API gateway.\n * Caches the result for 5 minutes.\n *\n * @param url Override the trust policy URL (for testing or custom deployments)\n */\nexport async function fetchTrustPolicy(url?: string): Promise<TrustPolicy> {\n const now = Date.now();\n if (cachedPolicy && now - cacheTimestamp < CACHE_TTL_MS) {\n return cachedPolicy;\n }\n\n const res = await fetch(url ?? TRUST_POLICY_URL);\n if (!res.ok) {\n throw new Error(`Failed to fetch trust policy: ${res.status} ${res.statusText}`);\n }\n\n const policy: TrustPolicy = await res.json();\n\n if (!policy.version || !policy.chains || typeof policy.chains !== \"object\") {\n throw new Error(\"Invalid trust policy format\");\n }\n\n cachedPolicy = policy;\n cacheTimestamp = now;\n return policy;\n}\n\n/**\n * Extract the allowed contracts and schemas from a trust policy.\n * Returns all contracts and schemas across all chains.\n */\nexport function extractAllowlists(policy: TrustPolicy): {\n allowedContracts: string[];\n allowedSchemas: string[];\n} {\n const contracts: string[] = [];\n const schemas: string[] = [];\n\n for (const chain of Object.values(policy.chains)) {\n if (chain.easContract) contracts.push(chain.easContract);\n if (chain.schemas) schemas.push(...chain.schemas);\n }\n\n return {\n allowedContracts: [...new Set(contracts)],\n allowedSchemas: [...new Set(schemas)],\n };\n}\n","/**\n * Host-side signing bridge for OMATrust widget iframes.\n *\n * Handles the postMessage protocol between a host page and an embedded\n * OMATrust widget. Validates incoming EAS signing requests against the\n * OMA3 trust policy before forwarding to the host's wallet.\n *\n * The bridge resolves the iframe element lazily by ID when messages arrive,\n * avoiding React ref timing issues with conditionally rendered iframes.\n *\n * Usage:\n * import { createSigningBridge } from \"@oma3/omatrust/widgets\";\n *\n * const bridge = await createSigningBridge({\n * iframeId: \"omatrust-widget\",\n * signTypedData: async (domain, types, message) => {\n * return await signer.signTypedData(domain, types, message);\n * },\n * });\n *\n * bridge.destroy();\n */\n\nimport {\n OMATRUST_READY,\n OMATRUST_HOST_READY,\n OMATRUST_SIGN_TYPED_DATA,\n OMATRUST_SIGNATURE,\n OMATRUST_SIGNATURE_ERROR,\n} from \"./protocol\";\nimport { fetchTrustPolicy, extractAllowlists, TRUST_POLICY_URL } from \"./trust-policy\";\n\nexport type SigningBridgeOptions = {\n /**\n * The ID of the iframe element containing the widget.\n * The bridge looks up the element by ID when messages arrive,\n * so the iframe doesn't need to exist when the bridge is created.\n */\n iframeId: string;\n\n /**\n * Callback to sign EIP-712 typed data using the host's wallet.\n * Must return the hex-encoded signature string.\n */\n signTypedData: (\n domain: Record<string, unknown>,\n types: Record<string, unknown>,\n message: Record<string, unknown>\n ) => Promise<string>;\n\n /**\n * Override the allowed widget origin for local development.\n * In production, the origin is derived from the trust policy domain\n * (*.omatrust.org) plus any widgetOrigins in the policy.\n * Only set this for local dev (e.g., \"http://localhost:3000\").\n */\n devOriginOverride?: string;\n};\n\nexport type SigningBridge = {\n /** Remove all event listeners and stop the bridge. */\n destroy: () => void;\n};\n\n// ---------------------------------------------------------------------------\n// Origin resolution\n// ---------------------------------------------------------------------------\n\nfunction getTrustedBaseDomain(): string {\n try {\n const hostname = new URL(TRUST_POLICY_URL).hostname;\n const parts = hostname.split(\".\");\n return parts.length >= 2 ? parts.slice(-2).join(\".\") : hostname;\n } catch {\n return \"omatrust.org\";\n }\n}\n\nfunction isOriginTrusted(\n messageOrigin: string,\n baseDomain: string,\n policyOrigins: string[],\n devOverride?: string\n): boolean {\n if (devOverride && messageOrigin === devOverride) return true;\n\n try {\n const hostname = new URL(messageOrigin).hostname;\n if (hostname === baseDomain || hostname.endsWith(`.${baseDomain}`)) return true;\n } catch {\n // Invalid origin\n }\n\n if (policyOrigins.includes(messageOrigin)) return true;\n\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// EAS request validation\n// ---------------------------------------------------------------------------\n\nconst HEX_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;\nconst BYTES32_RE = /^0x[a-fA-F0-9]{64}$/;\n\ntype ValidationResult =\n | { valid: true }\n | { valid: false; reason: string };\n\nfunction validateEasSigningRequest(\n data: Record<string, unknown>,\n allowedSchemas: string[],\n allowedContracts: string[]\n): ValidationResult {\n const { id, domain, types, message } = data;\n\n if (!id || typeof id !== \"string\") {\n return { valid: false, reason: \"Missing or invalid request id\" };\n }\n if (!domain || typeof domain !== \"object\") {\n return { valid: false, reason: \"Missing domain object\" };\n }\n\n const d = domain as Record<string, unknown>;\n\n if (d.name !== \"EAS\") {\n return { valid: false, reason: `Unexpected domain name: \"${d.name}\" (expected \"EAS\")` };\n }\n if (d.version !== \"1.4.0\") {\n return { valid: false, reason: `Unexpected domain version: \"${d.version}\" (expected \"1.4.0\")` };\n }\n const chainId = Number(d.chainId);\n if (!Number.isInteger(chainId) || chainId <= 0) {\n return { valid: false, reason: `Invalid domain chainId: ${d.chainId}` };\n }\n if (typeof d.verifyingContract !== \"string\" || !HEX_ADDRESS_RE.test(d.verifyingContract)) {\n return { valid: false, reason: `Invalid verifyingContract: ${d.verifyingContract}` };\n }\n\n const contractLower = (d.verifyingContract as string).toLowerCase();\n if (!allowedContracts.some(c => c.toLowerCase() === contractLower)) {\n return { valid: false, reason: `Contract ${d.verifyingContract} is not in the OMA3 trust policy` };\n }\n\n if (!types || typeof types !== \"object\") {\n return { valid: false, reason: \"Missing types object\" };\n }\n if (!message || typeof message !== \"object\") {\n return { valid: false, reason: \"Missing message object\" };\n }\n\n const m = message as Record<string, unknown>;\n\n if (typeof m.schema !== \"string\" || !BYTES32_RE.test(m.schema)) {\n return { valid: false, reason: `Invalid schema UID: ${m.schema}` };\n }\n\n const schemaLower = m.schema.toLowerCase();\n if (!allowedSchemas.some(s => s.toLowerCase() === schemaLower)) {\n return { valid: false, reason: `Schema ${m.schema} is not in the OMA3 trust policy` };\n }\n\n if (typeof m.attester !== \"string\" || !HEX_ADDRESS_RE.test(m.attester)) {\n return { valid: false, reason: `Invalid attester address: ${m.attester}` };\n }\n\n const deadline = Number(m.deadline);\n if (!Number.isFinite(deadline) || deadline <= 0) {\n return { valid: false, reason: `Invalid deadline: ${m.deadline}` };\n }\n const now = Math.floor(Date.now() / 1000);\n if (deadline <= now) {\n return { valid: false, reason: `Deadline has passed: ${deadline} <= ${now}` };\n }\n\n return { valid: true };\n}\n\n// ---------------------------------------------------------------------------\n// Bridge implementation\n// ---------------------------------------------------------------------------\n\n/**\n * Create a signing bridge between the host page and an OMATrust widget iframe.\n *\n * The bridge resolves the iframe element by ID when messages arrive, not at\n * creation time. This avoids React ref timing issues — the bridge can be\n * created before the iframe is in the DOM.\n *\n * Fetches the OMA3 trust policy on creation. Fails closed if unavailable.\n */\nexport async function createSigningBridge(options: SigningBridgeOptions): Promise<SigningBridge> {\n const { iframeId, signTypedData, devOriginOverride } = options;\n\n // Fetch the trust policy — fail closed if unavailable\n const policy = await fetchTrustPolicy();\n const { allowedContracts, allowedSchemas } = extractAllowlists(policy);\n\n if (allowedContracts.length === 0 || allowedSchemas.length === 0) {\n throw new Error(\"Trust policy contains no allowed contracts or schemas\");\n }\n\n const baseDomain = getTrustedBaseDomain();\n const policyOrigins = policy.widgetOrigins ?? [];\n\n async function handleMessage(event: MessageEvent) {\n const data = event.data;\n if (!data || typeof data !== \"object\" || typeof data.type !== \"string\") return;\n if (!String(data.type).startsWith(\"omatrust:\")) return;\n\n // Origin check\n if (!isOriginTrusted(event.origin, baseDomain, policyOrigins, devOriginOverride)) {\n return;\n }\n\n // Resolve the iframe element lazily by ID.\n // This works even if the iframe was mounted after the bridge was created.\n const iframe = document.getElementById(iframeId) as HTMLIFrameElement | null;\n if (!iframe) return;\n\n // Source check — must come from this specific iframe\n if (event.source !== iframe.contentWindow) {\n return;\n }\n\n const source = event.source as WindowProxy;\n\n // Determine reply origin from the iframe's current src\n const replyOrigin = devOriginOverride ?? (() => {\n try { return new URL(iframe.src).origin; }\n catch { return \"*\"; }\n })();\n\n function reply(msg: Record<string, unknown>) {\n source.postMessage(msg, replyOrigin);\n }\n\n // Handshake\n if (data.type === OMATRUST_READY) {\n reply({ type: OMATRUST_HOST_READY });\n return;\n }\n\n // Signing request\n if (data.type === OMATRUST_SIGN_TYPED_DATA) {\n const { id, domain, types, message } = data;\n\n const validation = validateEasSigningRequest(data, allowedSchemas, allowedContracts);\n if (!validation.valid) {\n reply({\n type: OMATRUST_SIGNATURE_ERROR,\n id: id ?? \"unknown\",\n error: `Signing request rejected: ${validation.reason}`,\n });\n return;\n }\n\n try {\n const signature = await signTypedData(domain, types, message);\n reply({ type: OMATRUST_SIGNATURE, id, signature });\n } catch (err) {\n const error = err instanceof Error ? err.message : \"Signing failed\";\n reply({ type: OMATRUST_SIGNATURE_ERROR, id, error });\n }\n }\n }\n\n window.addEventListener(\"message\", handleMessage);\n\n return {\n destroy() {\n window.removeEventListener(\"message\", handleMessage);\n },\n };\n}\n"]}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Host-side signing bridge for OMATrust widget iframes.
3
+ *
4
+ * Handles the postMessage protocol between a host page and an embedded
5
+ * OMATrust widget. Validates incoming EAS signing requests against the
6
+ * OMA3 trust policy before forwarding to the host's wallet.
7
+ *
8
+ * The bridge resolves the iframe element lazily by ID when messages arrive,
9
+ * avoiding React ref timing issues with conditionally rendered iframes.
10
+ *
11
+ * Usage:
12
+ * import { createSigningBridge } from "@oma3/omatrust/widgets";
13
+ *
14
+ * const bridge = await createSigningBridge({
15
+ * iframeId: "omatrust-widget",
16
+ * signTypedData: async (domain, types, message) => {
17
+ * return await signer.signTypedData(domain, types, message);
18
+ * },
19
+ * });
20
+ *
21
+ * bridge.destroy();
22
+ */
23
+ type SigningBridgeOptions = {
24
+ /**
25
+ * The ID of the iframe element containing the widget.
26
+ * The bridge looks up the element by ID when messages arrive,
27
+ * so the iframe doesn't need to exist when the bridge is created.
28
+ */
29
+ iframeId: string;
30
+ /**
31
+ * Callback to sign EIP-712 typed data using the host's wallet.
32
+ * Must return the hex-encoded signature string.
33
+ */
34
+ signTypedData: (domain: Record<string, unknown>, types: Record<string, unknown>, message: Record<string, unknown>) => Promise<string>;
35
+ /**
36
+ * Override the allowed widget origin for local development.
37
+ * In production, the origin is derived from the trust policy domain
38
+ * (*.omatrust.org) plus any widgetOrigins in the policy.
39
+ * Only set this for local dev (e.g., "http://localhost:3000").
40
+ */
41
+ devOriginOverride?: string;
42
+ };
43
+ type SigningBridge = {
44
+ /** Remove all event listeners and stop the bridge. */
45
+ destroy: () => void;
46
+ };
47
+ /**
48
+ * Create a signing bridge between the host page and an OMATrust widget iframe.
49
+ *
50
+ * The bridge resolves the iframe element by ID when messages arrive, not at
51
+ * creation time. This avoids React ref timing issues — the bridge can be
52
+ * created before the iframe is in the DOM.
53
+ *
54
+ * Fetches the OMA3 trust policy on creation. Fails closed if unavailable.
55
+ */
56
+ declare function createSigningBridge(options: SigningBridgeOptions): Promise<SigningBridge>;
57
+
58
+ /**
59
+ * Trust policy: fetches the OMA3 allowlist of EAS contracts and schemas.
60
+ *
61
+ * The bridge uses this to validate signing requests automatically.
62
+ * Developers don't need to maintain their own allowlists.
63
+ */
64
+ declare const TRUST_POLICY_URL = "https://api.omatrust.org/v1/trust-policy";
65
+ type ChainPolicy = {
66
+ name: string;
67
+ easContract: string;
68
+ schemas: string[];
69
+ };
70
+ type TrustPolicy = {
71
+ version: number;
72
+ updatedAt: string;
73
+ widgetOrigins?: string[];
74
+ chains: Record<string, ChainPolicy>;
75
+ };
76
+ /**
77
+ * Fetch the trust policy from the OMA3 API gateway.
78
+ * Caches the result for 5 minutes.
79
+ *
80
+ * @param url Override the trust policy URL (for testing or custom deployments)
81
+ */
82
+ declare function fetchTrustPolicy(url?: string): Promise<TrustPolicy>;
83
+ /**
84
+ * Extract the allowed contracts and schemas from a trust policy.
85
+ * Returns all contracts and schemas across all chains.
86
+ */
87
+ declare function extractAllowlists(policy: TrustPolicy): {
88
+ allowedContracts: string[];
89
+ allowedSchemas: string[];
90
+ };
91
+
92
+ /**
93
+ * postMessage protocol constants and types for the OMATrust widget bridge.
94
+ *
95
+ * Widget (iframe) → Host:
96
+ * omatrust:ready — widget loaded, requesting handshake
97
+ * omatrust:signTypedData — widget requests EIP-712 signature
98
+ *
99
+ * Host → Widget (iframe):
100
+ * omatrust:hostReady — host acknowledges the widget
101
+ * omatrust:signature — host returns a signature
102
+ * omatrust:signatureError — host reports a signing failure
103
+ */
104
+ declare const OMATRUST_READY: "omatrust:ready";
105
+ declare const OMATRUST_HOST_READY: "omatrust:hostReady";
106
+ declare const OMATRUST_SIGN_TYPED_DATA: "omatrust:signTypedData";
107
+ declare const OMATRUST_SIGNATURE: "omatrust:signature";
108
+ declare const OMATRUST_SIGNATURE_ERROR: "omatrust:signatureError";
109
+ type OmaTrustReadyMessage = {
110
+ type: typeof OMATRUST_READY;
111
+ };
112
+ type OmaTrustHostReadyMessage = {
113
+ type: typeof OMATRUST_HOST_READY;
114
+ };
115
+ type OmaTrustSignTypedDataMessage = {
116
+ type: typeof OMATRUST_SIGN_TYPED_DATA;
117
+ id: string;
118
+ domain: Record<string, unknown>;
119
+ types: Record<string, unknown>;
120
+ message: Record<string, unknown>;
121
+ };
122
+ type OmaTrustSignatureMessage = {
123
+ type: typeof OMATRUST_SIGNATURE;
124
+ id: string;
125
+ signature: string;
126
+ };
127
+ type OmaTrustSignatureErrorMessage = {
128
+ type: typeof OMATRUST_SIGNATURE_ERROR;
129
+ id: string;
130
+ error: string;
131
+ };
132
+ type OmaTrustMessage = OmaTrustReadyMessage | OmaTrustHostReadyMessage | OmaTrustSignTypedDataMessage | OmaTrustSignatureMessage | OmaTrustSignatureErrorMessage;
133
+
134
+ export { type ChainPolicy, OMATRUST_HOST_READY, OMATRUST_READY, OMATRUST_SIGNATURE, OMATRUST_SIGNATURE_ERROR, OMATRUST_SIGN_TYPED_DATA, type OmaTrustHostReadyMessage, type OmaTrustMessage, type OmaTrustReadyMessage, type OmaTrustSignTypedDataMessage, type OmaTrustSignatureErrorMessage, type OmaTrustSignatureMessage, type SigningBridge, type SigningBridgeOptions, TRUST_POLICY_URL, type TrustPolicy, createSigningBridge, extractAllowlists, fetchTrustPolicy };
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Host-side signing bridge for OMATrust widget iframes.
3
+ *
4
+ * Handles the postMessage protocol between a host page and an embedded
5
+ * OMATrust widget. Validates incoming EAS signing requests against the
6
+ * OMA3 trust policy before forwarding to the host's wallet.
7
+ *
8
+ * The bridge resolves the iframe element lazily by ID when messages arrive,
9
+ * avoiding React ref timing issues with conditionally rendered iframes.
10
+ *
11
+ * Usage:
12
+ * import { createSigningBridge } from "@oma3/omatrust/widgets";
13
+ *
14
+ * const bridge = await createSigningBridge({
15
+ * iframeId: "omatrust-widget",
16
+ * signTypedData: async (domain, types, message) => {
17
+ * return await signer.signTypedData(domain, types, message);
18
+ * },
19
+ * });
20
+ *
21
+ * bridge.destroy();
22
+ */
23
+ type SigningBridgeOptions = {
24
+ /**
25
+ * The ID of the iframe element containing the widget.
26
+ * The bridge looks up the element by ID when messages arrive,
27
+ * so the iframe doesn't need to exist when the bridge is created.
28
+ */
29
+ iframeId: string;
30
+ /**
31
+ * Callback to sign EIP-712 typed data using the host's wallet.
32
+ * Must return the hex-encoded signature string.
33
+ */
34
+ signTypedData: (domain: Record<string, unknown>, types: Record<string, unknown>, message: Record<string, unknown>) => Promise<string>;
35
+ /**
36
+ * Override the allowed widget origin for local development.
37
+ * In production, the origin is derived from the trust policy domain
38
+ * (*.omatrust.org) plus any widgetOrigins in the policy.
39
+ * Only set this for local dev (e.g., "http://localhost:3000").
40
+ */
41
+ devOriginOverride?: string;
42
+ };
43
+ type SigningBridge = {
44
+ /** Remove all event listeners and stop the bridge. */
45
+ destroy: () => void;
46
+ };
47
+ /**
48
+ * Create a signing bridge between the host page and an OMATrust widget iframe.
49
+ *
50
+ * The bridge resolves the iframe element by ID when messages arrive, not at
51
+ * creation time. This avoids React ref timing issues — the bridge can be
52
+ * created before the iframe is in the DOM.
53
+ *
54
+ * Fetches the OMA3 trust policy on creation. Fails closed if unavailable.
55
+ */
56
+ declare function createSigningBridge(options: SigningBridgeOptions): Promise<SigningBridge>;
57
+
58
+ /**
59
+ * Trust policy: fetches the OMA3 allowlist of EAS contracts and schemas.
60
+ *
61
+ * The bridge uses this to validate signing requests automatically.
62
+ * Developers don't need to maintain their own allowlists.
63
+ */
64
+ declare const TRUST_POLICY_URL = "https://api.omatrust.org/v1/trust-policy";
65
+ type ChainPolicy = {
66
+ name: string;
67
+ easContract: string;
68
+ schemas: string[];
69
+ };
70
+ type TrustPolicy = {
71
+ version: number;
72
+ updatedAt: string;
73
+ widgetOrigins?: string[];
74
+ chains: Record<string, ChainPolicy>;
75
+ };
76
+ /**
77
+ * Fetch the trust policy from the OMA3 API gateway.
78
+ * Caches the result for 5 minutes.
79
+ *
80
+ * @param url Override the trust policy URL (for testing or custom deployments)
81
+ */
82
+ declare function fetchTrustPolicy(url?: string): Promise<TrustPolicy>;
83
+ /**
84
+ * Extract the allowed contracts and schemas from a trust policy.
85
+ * Returns all contracts and schemas across all chains.
86
+ */
87
+ declare function extractAllowlists(policy: TrustPolicy): {
88
+ allowedContracts: string[];
89
+ allowedSchemas: string[];
90
+ };
91
+
92
+ /**
93
+ * postMessage protocol constants and types for the OMATrust widget bridge.
94
+ *
95
+ * Widget (iframe) → Host:
96
+ * omatrust:ready — widget loaded, requesting handshake
97
+ * omatrust:signTypedData — widget requests EIP-712 signature
98
+ *
99
+ * Host → Widget (iframe):
100
+ * omatrust:hostReady — host acknowledges the widget
101
+ * omatrust:signature — host returns a signature
102
+ * omatrust:signatureError — host reports a signing failure
103
+ */
104
+ declare const OMATRUST_READY: "omatrust:ready";
105
+ declare const OMATRUST_HOST_READY: "omatrust:hostReady";
106
+ declare const OMATRUST_SIGN_TYPED_DATA: "omatrust:signTypedData";
107
+ declare const OMATRUST_SIGNATURE: "omatrust:signature";
108
+ declare const OMATRUST_SIGNATURE_ERROR: "omatrust:signatureError";
109
+ type OmaTrustReadyMessage = {
110
+ type: typeof OMATRUST_READY;
111
+ };
112
+ type OmaTrustHostReadyMessage = {
113
+ type: typeof OMATRUST_HOST_READY;
114
+ };
115
+ type OmaTrustSignTypedDataMessage = {
116
+ type: typeof OMATRUST_SIGN_TYPED_DATA;
117
+ id: string;
118
+ domain: Record<string, unknown>;
119
+ types: Record<string, unknown>;
120
+ message: Record<string, unknown>;
121
+ };
122
+ type OmaTrustSignatureMessage = {
123
+ type: typeof OMATRUST_SIGNATURE;
124
+ id: string;
125
+ signature: string;
126
+ };
127
+ type OmaTrustSignatureErrorMessage = {
128
+ type: typeof OMATRUST_SIGNATURE_ERROR;
129
+ id: string;
130
+ error: string;
131
+ };
132
+ type OmaTrustMessage = OmaTrustReadyMessage | OmaTrustHostReadyMessage | OmaTrustSignTypedDataMessage | OmaTrustSignatureMessage | OmaTrustSignatureErrorMessage;
133
+
134
+ export { type ChainPolicy, OMATRUST_HOST_READY, OMATRUST_READY, OMATRUST_SIGNATURE, OMATRUST_SIGNATURE_ERROR, OMATRUST_SIGN_TYPED_DATA, type OmaTrustHostReadyMessage, type OmaTrustMessage, type OmaTrustReadyMessage, type OmaTrustSignTypedDataMessage, type OmaTrustSignatureErrorMessage, type OmaTrustSignatureMessage, type SigningBridge, type SigningBridgeOptions, TRUST_POLICY_URL, type TrustPolicy, createSigningBridge, extractAllowlists, fetchTrustPolicy };
@@ -0,0 +1,185 @@
1
+ // src/widgets/protocol.ts
2
+ var OMATRUST_READY = "omatrust:ready";
3
+ var OMATRUST_HOST_READY = "omatrust:hostReady";
4
+ var OMATRUST_SIGN_TYPED_DATA = "omatrust:signTypedData";
5
+ var OMATRUST_SIGNATURE = "omatrust:signature";
6
+ var OMATRUST_SIGNATURE_ERROR = "omatrust:signatureError";
7
+
8
+ // src/widgets/trust-policy.ts
9
+ var DEFAULT_TRUST_POLICY_URL = "https://api.omatrust.org/v1/trust-policy";
10
+ var TRUST_POLICY_URL = DEFAULT_TRUST_POLICY_URL;
11
+ var cachedPolicy = null;
12
+ var cacheTimestamp = 0;
13
+ var CACHE_TTL_MS = 5 * 60 * 1e3;
14
+ async function fetchTrustPolicy(url) {
15
+ const now = Date.now();
16
+ if (cachedPolicy && now - cacheTimestamp < CACHE_TTL_MS) {
17
+ return cachedPolicy;
18
+ }
19
+ const res = await fetch(url ?? TRUST_POLICY_URL);
20
+ if (!res.ok) {
21
+ throw new Error(`Failed to fetch trust policy: ${res.status} ${res.statusText}`);
22
+ }
23
+ const policy = await res.json();
24
+ if (!policy.version || !policy.chains || typeof policy.chains !== "object") {
25
+ throw new Error("Invalid trust policy format");
26
+ }
27
+ cachedPolicy = policy;
28
+ cacheTimestamp = now;
29
+ return policy;
30
+ }
31
+ function extractAllowlists(policy) {
32
+ const contracts = [];
33
+ const schemas = [];
34
+ for (const chain of Object.values(policy.chains)) {
35
+ if (chain.easContract) contracts.push(chain.easContract);
36
+ if (chain.schemas) schemas.push(...chain.schemas);
37
+ }
38
+ return {
39
+ allowedContracts: [...new Set(contracts)],
40
+ allowedSchemas: [...new Set(schemas)]
41
+ };
42
+ }
43
+
44
+ // src/widgets/bridge.ts
45
+ function getTrustedBaseDomain() {
46
+ try {
47
+ const hostname = new URL(TRUST_POLICY_URL).hostname;
48
+ const parts = hostname.split(".");
49
+ return parts.length >= 2 ? parts.slice(-2).join(".") : hostname;
50
+ } catch {
51
+ return "omatrust.org";
52
+ }
53
+ }
54
+ function isOriginTrusted(messageOrigin, baseDomain, policyOrigins, devOverride) {
55
+ if (devOverride && messageOrigin === devOverride) return true;
56
+ try {
57
+ const hostname = new URL(messageOrigin).hostname;
58
+ if (hostname === baseDomain || hostname.endsWith(`.${baseDomain}`)) return true;
59
+ } catch {
60
+ }
61
+ if (policyOrigins.includes(messageOrigin)) return true;
62
+ return false;
63
+ }
64
+ var HEX_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
65
+ var BYTES32_RE = /^0x[a-fA-F0-9]{64}$/;
66
+ function validateEasSigningRequest(data, allowedSchemas, allowedContracts) {
67
+ const { id, domain, types, message } = data;
68
+ if (!id || typeof id !== "string") {
69
+ return { valid: false, reason: "Missing or invalid request id" };
70
+ }
71
+ if (!domain || typeof domain !== "object") {
72
+ return { valid: false, reason: "Missing domain object" };
73
+ }
74
+ const d = domain;
75
+ if (d.name !== "EAS") {
76
+ return { valid: false, reason: `Unexpected domain name: "${d.name}" (expected "EAS")` };
77
+ }
78
+ if (d.version !== "1.4.0") {
79
+ return { valid: false, reason: `Unexpected domain version: "${d.version}" (expected "1.4.0")` };
80
+ }
81
+ const chainId = Number(d.chainId);
82
+ if (!Number.isInteger(chainId) || chainId <= 0) {
83
+ return { valid: false, reason: `Invalid domain chainId: ${d.chainId}` };
84
+ }
85
+ if (typeof d.verifyingContract !== "string" || !HEX_ADDRESS_RE.test(d.verifyingContract)) {
86
+ return { valid: false, reason: `Invalid verifyingContract: ${d.verifyingContract}` };
87
+ }
88
+ const contractLower = d.verifyingContract.toLowerCase();
89
+ if (!allowedContracts.some((c) => c.toLowerCase() === contractLower)) {
90
+ return { valid: false, reason: `Contract ${d.verifyingContract} is not in the OMA3 trust policy` };
91
+ }
92
+ if (!types || typeof types !== "object") {
93
+ return { valid: false, reason: "Missing types object" };
94
+ }
95
+ if (!message || typeof message !== "object") {
96
+ return { valid: false, reason: "Missing message object" };
97
+ }
98
+ const m = message;
99
+ if (typeof m.schema !== "string" || !BYTES32_RE.test(m.schema)) {
100
+ return { valid: false, reason: `Invalid schema UID: ${m.schema}` };
101
+ }
102
+ const schemaLower = m.schema.toLowerCase();
103
+ if (!allowedSchemas.some((s) => s.toLowerCase() === schemaLower)) {
104
+ return { valid: false, reason: `Schema ${m.schema} is not in the OMA3 trust policy` };
105
+ }
106
+ if (typeof m.attester !== "string" || !HEX_ADDRESS_RE.test(m.attester)) {
107
+ return { valid: false, reason: `Invalid attester address: ${m.attester}` };
108
+ }
109
+ const deadline = Number(m.deadline);
110
+ if (!Number.isFinite(deadline) || deadline <= 0) {
111
+ return { valid: false, reason: `Invalid deadline: ${m.deadline}` };
112
+ }
113
+ const now = Math.floor(Date.now() / 1e3);
114
+ if (deadline <= now) {
115
+ return { valid: false, reason: `Deadline has passed: ${deadline} <= ${now}` };
116
+ }
117
+ return { valid: true };
118
+ }
119
+ async function createSigningBridge(options) {
120
+ const { iframeId, signTypedData, devOriginOverride } = options;
121
+ const policy = await fetchTrustPolicy();
122
+ const { allowedContracts, allowedSchemas } = extractAllowlists(policy);
123
+ if (allowedContracts.length === 0 || allowedSchemas.length === 0) {
124
+ throw new Error("Trust policy contains no allowed contracts or schemas");
125
+ }
126
+ const baseDomain = getTrustedBaseDomain();
127
+ const policyOrigins = policy.widgetOrigins ?? [];
128
+ async function handleMessage(event) {
129
+ const data = event.data;
130
+ if (!data || typeof data !== "object" || typeof data.type !== "string") return;
131
+ if (!String(data.type).startsWith("omatrust:")) return;
132
+ if (!isOriginTrusted(event.origin, baseDomain, policyOrigins, devOriginOverride)) {
133
+ return;
134
+ }
135
+ const iframe = document.getElementById(iframeId);
136
+ if (!iframe) return;
137
+ if (event.source !== iframe.contentWindow) {
138
+ return;
139
+ }
140
+ const source = event.source;
141
+ const replyOrigin = devOriginOverride ?? (() => {
142
+ try {
143
+ return new URL(iframe.src).origin;
144
+ } catch {
145
+ return "*";
146
+ }
147
+ })();
148
+ function reply(msg) {
149
+ source.postMessage(msg, replyOrigin);
150
+ }
151
+ if (data.type === OMATRUST_READY) {
152
+ reply({ type: OMATRUST_HOST_READY });
153
+ return;
154
+ }
155
+ if (data.type === OMATRUST_SIGN_TYPED_DATA) {
156
+ const { id, domain, types, message } = data;
157
+ const validation = validateEasSigningRequest(data, allowedSchemas, allowedContracts);
158
+ if (!validation.valid) {
159
+ reply({
160
+ type: OMATRUST_SIGNATURE_ERROR,
161
+ id: id ?? "unknown",
162
+ error: `Signing request rejected: ${validation.reason}`
163
+ });
164
+ return;
165
+ }
166
+ try {
167
+ const signature = await signTypedData(domain, types, message);
168
+ reply({ type: OMATRUST_SIGNATURE, id, signature });
169
+ } catch (err) {
170
+ const error = err instanceof Error ? err.message : "Signing failed";
171
+ reply({ type: OMATRUST_SIGNATURE_ERROR, id, error });
172
+ }
173
+ }
174
+ }
175
+ window.addEventListener("message", handleMessage);
176
+ return {
177
+ destroy() {
178
+ window.removeEventListener("message", handleMessage);
179
+ }
180
+ };
181
+ }
182
+
183
+ export { OMATRUST_HOST_READY, OMATRUST_READY, OMATRUST_SIGNATURE, OMATRUST_SIGNATURE_ERROR, OMATRUST_SIGN_TYPED_DATA, TRUST_POLICY_URL, createSigningBridge, extractAllowlists, fetchTrustPolicy };
184
+ //# sourceMappingURL=index.js.map
185
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/widgets/protocol.ts","../../src/widgets/trust-policy.ts","../../src/widgets/bridge.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * postMessage protocol constants and types for the OMATrust widget bridge.\n *\n * Widget (iframe) → Host:\n * omatrust:ready — widget loaded, requesting handshake\n * omatrust:signTypedData — widget requests EIP-712 signature\n *\n * Host → Widget (iframe):\n * omatrust:hostReady — host acknowledges the widget\n * omatrust:signature — host returns a signature\n * omatrust:signatureError — host reports a signing failure\n */\n\nexport const OMATRUST_READY = \"omatrust:ready\" as const;\nexport const OMATRUST_HOST_READY = \"omatrust:hostReady\" as const;\nexport const OMATRUST_SIGN_TYPED_DATA = \"omatrust:signTypedData\" as const;\nexport const OMATRUST_SIGNATURE = \"omatrust:signature\" as const;\nexport const OMATRUST_SIGNATURE_ERROR = \"omatrust:signatureError\" as const;\n\nexport type OmaTrustReadyMessage = {\n type: typeof OMATRUST_READY;\n};\n\nexport type OmaTrustHostReadyMessage = {\n type: typeof OMATRUST_HOST_READY;\n};\n\nexport type OmaTrustSignTypedDataMessage = {\n type: typeof OMATRUST_SIGN_TYPED_DATA;\n id: string;\n domain: Record<string, unknown>;\n types: Record<string, unknown>;\n message: Record<string, unknown>;\n};\n\nexport type OmaTrustSignatureMessage = {\n type: typeof OMATRUST_SIGNATURE;\n id: string;\n signature: string;\n};\n\nexport type OmaTrustSignatureErrorMessage = {\n type: typeof OMATRUST_SIGNATURE_ERROR;\n id: string;\n error: string;\n};\n\nexport type OmaTrustMessage =\n | OmaTrustReadyMessage\n | OmaTrustHostReadyMessage\n | OmaTrustSignTypedDataMessage\n | OmaTrustSignatureMessage\n | OmaTrustSignatureErrorMessage;\n","/**\n * Trust policy: fetches the OMA3 allowlist of EAS contracts and schemas.\n *\n * The bridge uses this to validate signing requests automatically.\n * Developers don't need to maintain their own allowlists.\n */\n\nconst DEFAULT_TRUST_POLICY_URL = \"https://api.omatrust.org/v1/trust-policy\";\n\nexport const TRUST_POLICY_URL = DEFAULT_TRUST_POLICY_URL;\n\nexport type ChainPolicy = {\n name: string;\n easContract: string;\n schemas: string[];\n};\n\nexport type TrustPolicy = {\n version: number;\n updatedAt: string;\n widgetOrigins?: string[];\n chains: Record<string, ChainPolicy>;\n};\n\nlet cachedPolicy: TrustPolicy | null = null;\nlet cacheTimestamp = 0;\nconst CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes\n\n/**\n * Fetch the trust policy from the OMA3 API gateway.\n * Caches the result for 5 minutes.\n *\n * @param url Override the trust policy URL (for testing or custom deployments)\n */\nexport async function fetchTrustPolicy(url?: string): Promise<TrustPolicy> {\n const now = Date.now();\n if (cachedPolicy && now - cacheTimestamp < CACHE_TTL_MS) {\n return cachedPolicy;\n }\n\n const res = await fetch(url ?? TRUST_POLICY_URL);\n if (!res.ok) {\n throw new Error(`Failed to fetch trust policy: ${res.status} ${res.statusText}`);\n }\n\n const policy: TrustPolicy = await res.json();\n\n if (!policy.version || !policy.chains || typeof policy.chains !== \"object\") {\n throw new Error(\"Invalid trust policy format\");\n }\n\n cachedPolicy = policy;\n cacheTimestamp = now;\n return policy;\n}\n\n/**\n * Extract the allowed contracts and schemas from a trust policy.\n * Returns all contracts and schemas across all chains.\n */\nexport function extractAllowlists(policy: TrustPolicy): {\n allowedContracts: string[];\n allowedSchemas: string[];\n} {\n const contracts: string[] = [];\n const schemas: string[] = [];\n\n for (const chain of Object.values(policy.chains)) {\n if (chain.easContract) contracts.push(chain.easContract);\n if (chain.schemas) schemas.push(...chain.schemas);\n }\n\n return {\n allowedContracts: [...new Set(contracts)],\n allowedSchemas: [...new Set(schemas)],\n };\n}\n","/**\n * Host-side signing bridge for OMATrust widget iframes.\n *\n * Handles the postMessage protocol between a host page and an embedded\n * OMATrust widget. Validates incoming EAS signing requests against the\n * OMA3 trust policy before forwarding to the host's wallet.\n *\n * The bridge resolves the iframe element lazily by ID when messages arrive,\n * avoiding React ref timing issues with conditionally rendered iframes.\n *\n * Usage:\n * import { createSigningBridge } from \"@oma3/omatrust/widgets\";\n *\n * const bridge = await createSigningBridge({\n * iframeId: \"omatrust-widget\",\n * signTypedData: async (domain, types, message) => {\n * return await signer.signTypedData(domain, types, message);\n * },\n * });\n *\n * bridge.destroy();\n */\n\nimport {\n OMATRUST_READY,\n OMATRUST_HOST_READY,\n OMATRUST_SIGN_TYPED_DATA,\n OMATRUST_SIGNATURE,\n OMATRUST_SIGNATURE_ERROR,\n} from \"./protocol\";\nimport { fetchTrustPolicy, extractAllowlists, TRUST_POLICY_URL } from \"./trust-policy\";\n\nexport type SigningBridgeOptions = {\n /**\n * The ID of the iframe element containing the widget.\n * The bridge looks up the element by ID when messages arrive,\n * so the iframe doesn't need to exist when the bridge is created.\n */\n iframeId: string;\n\n /**\n * Callback to sign EIP-712 typed data using the host's wallet.\n * Must return the hex-encoded signature string.\n */\n signTypedData: (\n domain: Record<string, unknown>,\n types: Record<string, unknown>,\n message: Record<string, unknown>\n ) => Promise<string>;\n\n /**\n * Override the allowed widget origin for local development.\n * In production, the origin is derived from the trust policy domain\n * (*.omatrust.org) plus any widgetOrigins in the policy.\n * Only set this for local dev (e.g., \"http://localhost:3000\").\n */\n devOriginOverride?: string;\n};\n\nexport type SigningBridge = {\n /** Remove all event listeners and stop the bridge. */\n destroy: () => void;\n};\n\n// ---------------------------------------------------------------------------\n// Origin resolution\n// ---------------------------------------------------------------------------\n\nfunction getTrustedBaseDomain(): string {\n try {\n const hostname = new URL(TRUST_POLICY_URL).hostname;\n const parts = hostname.split(\".\");\n return parts.length >= 2 ? parts.slice(-2).join(\".\") : hostname;\n } catch {\n return \"omatrust.org\";\n }\n}\n\nfunction isOriginTrusted(\n messageOrigin: string,\n baseDomain: string,\n policyOrigins: string[],\n devOverride?: string\n): boolean {\n if (devOverride && messageOrigin === devOverride) return true;\n\n try {\n const hostname = new URL(messageOrigin).hostname;\n if (hostname === baseDomain || hostname.endsWith(`.${baseDomain}`)) return true;\n } catch {\n // Invalid origin\n }\n\n if (policyOrigins.includes(messageOrigin)) return true;\n\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// EAS request validation\n// ---------------------------------------------------------------------------\n\nconst HEX_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;\nconst BYTES32_RE = /^0x[a-fA-F0-9]{64}$/;\n\ntype ValidationResult =\n | { valid: true }\n | { valid: false; reason: string };\n\nfunction validateEasSigningRequest(\n data: Record<string, unknown>,\n allowedSchemas: string[],\n allowedContracts: string[]\n): ValidationResult {\n const { id, domain, types, message } = data;\n\n if (!id || typeof id !== \"string\") {\n return { valid: false, reason: \"Missing or invalid request id\" };\n }\n if (!domain || typeof domain !== \"object\") {\n return { valid: false, reason: \"Missing domain object\" };\n }\n\n const d = domain as Record<string, unknown>;\n\n if (d.name !== \"EAS\") {\n return { valid: false, reason: `Unexpected domain name: \"${d.name}\" (expected \"EAS\")` };\n }\n if (d.version !== \"1.4.0\") {\n return { valid: false, reason: `Unexpected domain version: \"${d.version}\" (expected \"1.4.0\")` };\n }\n const chainId = Number(d.chainId);\n if (!Number.isInteger(chainId) || chainId <= 0) {\n return { valid: false, reason: `Invalid domain chainId: ${d.chainId}` };\n }\n if (typeof d.verifyingContract !== \"string\" || !HEX_ADDRESS_RE.test(d.verifyingContract)) {\n return { valid: false, reason: `Invalid verifyingContract: ${d.verifyingContract}` };\n }\n\n const contractLower = (d.verifyingContract as string).toLowerCase();\n if (!allowedContracts.some(c => c.toLowerCase() === contractLower)) {\n return { valid: false, reason: `Contract ${d.verifyingContract} is not in the OMA3 trust policy` };\n }\n\n if (!types || typeof types !== \"object\") {\n return { valid: false, reason: \"Missing types object\" };\n }\n if (!message || typeof message !== \"object\") {\n return { valid: false, reason: \"Missing message object\" };\n }\n\n const m = message as Record<string, unknown>;\n\n if (typeof m.schema !== \"string\" || !BYTES32_RE.test(m.schema)) {\n return { valid: false, reason: `Invalid schema UID: ${m.schema}` };\n }\n\n const schemaLower = m.schema.toLowerCase();\n if (!allowedSchemas.some(s => s.toLowerCase() === schemaLower)) {\n return { valid: false, reason: `Schema ${m.schema} is not in the OMA3 trust policy` };\n }\n\n if (typeof m.attester !== \"string\" || !HEX_ADDRESS_RE.test(m.attester)) {\n return { valid: false, reason: `Invalid attester address: ${m.attester}` };\n }\n\n const deadline = Number(m.deadline);\n if (!Number.isFinite(deadline) || deadline <= 0) {\n return { valid: false, reason: `Invalid deadline: ${m.deadline}` };\n }\n const now = Math.floor(Date.now() / 1000);\n if (deadline <= now) {\n return { valid: false, reason: `Deadline has passed: ${deadline} <= ${now}` };\n }\n\n return { valid: true };\n}\n\n// ---------------------------------------------------------------------------\n// Bridge implementation\n// ---------------------------------------------------------------------------\n\n/**\n * Create a signing bridge between the host page and an OMATrust widget iframe.\n *\n * The bridge resolves the iframe element by ID when messages arrive, not at\n * creation time. This avoids React ref timing issues — the bridge can be\n * created before the iframe is in the DOM.\n *\n * Fetches the OMA3 trust policy on creation. Fails closed if unavailable.\n */\nexport async function createSigningBridge(options: SigningBridgeOptions): Promise<SigningBridge> {\n const { iframeId, signTypedData, devOriginOverride } = options;\n\n // Fetch the trust policy — fail closed if unavailable\n const policy = await fetchTrustPolicy();\n const { allowedContracts, allowedSchemas } = extractAllowlists(policy);\n\n if (allowedContracts.length === 0 || allowedSchemas.length === 0) {\n throw new Error(\"Trust policy contains no allowed contracts or schemas\");\n }\n\n const baseDomain = getTrustedBaseDomain();\n const policyOrigins = policy.widgetOrigins ?? [];\n\n async function handleMessage(event: MessageEvent) {\n const data = event.data;\n if (!data || typeof data !== \"object\" || typeof data.type !== \"string\") return;\n if (!String(data.type).startsWith(\"omatrust:\")) return;\n\n // Origin check\n if (!isOriginTrusted(event.origin, baseDomain, policyOrigins, devOriginOverride)) {\n return;\n }\n\n // Resolve the iframe element lazily by ID.\n // This works even if the iframe was mounted after the bridge was created.\n const iframe = document.getElementById(iframeId) as HTMLIFrameElement | null;\n if (!iframe) return;\n\n // Source check — must come from this specific iframe\n if (event.source !== iframe.contentWindow) {\n return;\n }\n\n const source = event.source as WindowProxy;\n\n // Determine reply origin from the iframe's current src\n const replyOrigin = devOriginOverride ?? (() => {\n try { return new URL(iframe.src).origin; }\n catch { return \"*\"; }\n })();\n\n function reply(msg: Record<string, unknown>) {\n source.postMessage(msg, replyOrigin);\n }\n\n // Handshake\n if (data.type === OMATRUST_READY) {\n reply({ type: OMATRUST_HOST_READY });\n return;\n }\n\n // Signing request\n if (data.type === OMATRUST_SIGN_TYPED_DATA) {\n const { id, domain, types, message } = data;\n\n const validation = validateEasSigningRequest(data, allowedSchemas, allowedContracts);\n if (!validation.valid) {\n reply({\n type: OMATRUST_SIGNATURE_ERROR,\n id: id ?? \"unknown\",\n error: `Signing request rejected: ${validation.reason}`,\n });\n return;\n }\n\n try {\n const signature = await signTypedData(domain, types, message);\n reply({ type: OMATRUST_SIGNATURE, id, signature });\n } catch (err) {\n const error = err instanceof Error ? err.message : \"Signing failed\";\n reply({ type: OMATRUST_SIGNATURE_ERROR, id, error });\n }\n }\n }\n\n window.addEventListener(\"message\", handleMessage);\n\n return {\n destroy() {\n window.removeEventListener(\"message\", handleMessage);\n },\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oma3/omatrust",
3
- "version": "0.1.0-alpha.7",
3
+ "version": "0.1.0-alpha.9",
4
4
  "description": "Framework-agnostic TypeScript SDK for OMATrust",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -44,6 +44,14 @@
44
44
  "import": "./dist/app-registry/index.js",
45
45
  "require": "./dist/app-registry/index.cjs"
46
46
  },
47
+ "./widgets": {
48
+ "types": {
49
+ "import": "./dist/widgets/index.d.ts",
50
+ "require": "./dist/widgets/index.d.cts"
51
+ },
52
+ "import": "./dist/widgets/index.js",
53
+ "require": "./dist/widgets/index.cjs"
54
+ },
47
55
  "./package.json": "./package.json"
48
56
  },
49
57
  "files": [
@@ -56,7 +64,8 @@
56
64
  "build": "tsup",
57
65
  "clean": "rm -rf dist",
58
66
  "typecheck": "tsc --noEmit",
59
- "test": "vitest run"
67
+ "test": "vitest run",
68
+ "test:ci": "npm run typecheck && npm run test"
60
69
  },
61
70
  "dependencies": {
62
71
  "canonicalize": "^2.1.0"
@@ -73,6 +82,7 @@
73
82
  "devDependencies": {
74
83
  "@ethereum-attestation-service/eas-sdk": "^2.9.0",
75
84
  "@types/node": "^22.10.2",
85
+ "@vitest/coverage-v8": "^2.1.9",
76
86
  "ethers": "^6.16.0",
77
87
  "tsup": "^8.3.5",
78
88
  "typescript": "^5.7.2",