Integration Example
The snippet below is a minimal React host that mounts Web3Kit, captures connectionId, requests a signing message, signs it with MetaMask, submits the signed payload, and handles AUTH_DATA / AUTH_FAILED responses.
It uses a small inline constant for the message type names. If the host lives in this monorepo you can import { WEB3_KIT_MESSAGE_TYPES } from 'src/pages/Web3KitPage/Web3KitPage' directly. If it lives in another codebase, keep the constants in sync manually (or factor them into a shared package).
import { useEffect, useRef, useState } from 'react';
const TYPES = {
INIT: 'WEB3_KIT_INIT',
GET_SIGNATURE_MSG: 'WEB3_KIT_GET_SIGNATURE_MSG',
SIGNATURE_MSG: 'WEB3_KIT_SIGNATURE_MSG',
AUTH_BY_WALLET: 'WEB3_KIT_AUTH_BY_WALLET',
AUTH_DATA: 'WEB3_KIT_AUTH_DATA',
AUTH_FAILED: 'WEB3_KIT_AUTH_FAILED',
} as const;
const WEB3KIT_ORIGIN = 'https://auth.example.com';
// Replace these with whatever wallet stack your app uses (wagmi / web3modal / ethers).
declare function connectMetaMask(): Promise<`0x${string}`>;
declare function signMessage(message: string, account: `0x${string}`): Promise<string>;
interface AuthData { token: string; refreshToken: string; isNew: boolean }
interface AuthFailed { reason: 'banned' | 'deleted' | 'unknown'; message?: string }
export function Web3KitFrame({ authToken }: { authToken: string }) {
const iframeRef = useRef<HTMLIFrameElement>(null);
const pendingSig = useRef<((m: { nonce: string; message: string }) => void) | null>(null);
const [connectionId, setConnectionId] = useState<string | null>(null);
const [authData, setAuthData] = useState<AuthData | null>(null);
const [authFailed, setAuthFailed] = useState<AuthFailed | null>(null);
useEffect(() => {
const onMessage = (e: MessageEvent) => {
if (e.origin !== WEB3KIT_ORIGIN) return;
const data = e.data;
if (!data || typeof data !== 'object') return;
// INIT — capture connectionId
if (data.type === TYPES.INIT) {
setConnectionId(data.payload.connectionId);
return;
}
// For every subsequent message validate connectionId
if (!connectionId || data.payload?.connectionId !== connectionId) return;
if (data.type === TYPES.SIGNATURE_MSG) {
pendingSig.current?.({
nonce: data.payload.nonce,
message: data.payload.message,
});
pendingSig.current = null;
}
if (data.type === TYPES.AUTH_DATA) {
setAuthData({
token: data.payload.token,
refreshToken: data.payload.refreshToken,
isNew: data.payload.isNew,
});
// Branch host UX: onboarding for new users, app for returning ones.
// if (data.payload.isNew) router.push('/welcome'); else router.push('/app');
}
if (data.type === TYPES.AUTH_FAILED) {
setAuthFailed({
reason: data.payload.reason,
message: data.payload.message,
});
}
};
window.addEventListener('message', onMessage);
return () => window.removeEventListener('message', onMessage);
}, [connectionId]);
const connect = async () => {
if (!connectionId) return;
const account = await connectMetaMask();
const sigPromise = new Promise<{ nonce: string; message: string }>((resolve) => {
pendingSig.current = resolve;
});
iframeRef.current?.contentWindow?.postMessage(
{
type: TYPES.GET_SIGNATURE_MSG,
payload: { connectionId, network: 'eth' },
},
WEB3KIT_ORIGIN,
);
const { nonce, message } = await sigPromise;
const signature = await signMessage(message, account);
iframeRef.current?.contentWindow?.postMessage(
{
type: TYPES.AUTH_BY_WALLET,
payload: {
connectionId,
nonce,
payload: { address: account, signature },
cryptoProvider: 'meta_mask',
network: 'eth',
},
},
WEB3KIT_ORIGIN,
);
};
return (
<>
<iframe
ref={iframeRef}
src={`${WEB3KIT_ORIGIN}/oauth/${authToken}/web3-kit`}
title="web3-kit"
style={{ border: 0, width: '100%', height: '100%' }}
/>
<button type="button" onClick={connect} disabled={!connectionId}>
Connect MetaMask
</button>
{authData && <pre>{JSON.stringify(authData, null, 2)}</pre>}
{authFailed && <pre>{JSON.stringify(authFailed, null, 2)}</pre>}
</>
);
}
A worked-out version of the same component using the project's own wagmi setup lives at src/pages/DemoWeb3KitTestPage/DemoWeb3KitTestPage.tsx. It exercises the full flow end-to-end and is the recommended starting point for adapting the integration to your codebase.