IAM Badge

A drop-in React component to display on-chain proof-of-humanity status.

Tip: Use a wallet from your devnet testing to see the verified state.

Preview

Enter a valid address to preview

1. Usage

Import and use the component anywhere in your app. Pass the wallet address and ideally your active Solana connection.

import { IAMBadge } from "@/components/ui/iam-badge";
import { useConnection } from "@solana/wallet-adapter-react";
export function ProfileHeader({ walletAddress }) {
const { connection } = useConnection();
return (
<div className="flex items-center gap-4">
<h2 className="text-xl font-bold">{walletAddress}</h2>
<IAMBadge walletAddress={walletAddress} connection={connection} />
</div>
);
}

2. Component Source

Copy this code into your project at components/ui/iam-badge.tsx. It requires the @iam-protocol/pulse-sdk and @solana/web3.js packages.

"use client";
import { useEffect, useState } from "react";
import { Connection, PublicKey } from "@solana/web3.js";
import { PROGRAM_IDS } from "@iam-protocol/pulse-sdk";
import { Loader2 } from "lucide-react";
import { cn } from "@/lib/utils";
const EXPECTED_SIZE = 62;
const IAM_PROGRAM_ID = new PublicKey(PROGRAM_IDS.iamAnchor);
interface IAMBadgeProps {
walletAddress: string;
connection?: Connection;
className?: string;
}
export function IAMBadge({ walletAddress, connection, className }: IAMBadgeProps) {
const [loading, setLoading] = useState(true);
const [trustScore, setTrustScore] = useState<number | null>(null);
const [invalid, setInvalid] = useState(false);
useEffect(() => {
if (!walletAddress) {
setTrustScore(null);
setInvalid(false);
setLoading(false);
return;
}
let pubkey: PublicKey;
try {
pubkey = new PublicKey(walletAddress);
setInvalid(false);
} catch (err) {
setInvalid(true);
setTrustScore(null);
setLoading(false);
return;
}
setLoading(true);
let isMounted = true;
const timeoutId = setTimeout(() => {
const fetchIdentity = async () => {
try {
const [identityPda] = PublicKey.findProgramAddressSync(
[new TextEncoder().encode("identity"), pubkey.toBuffer()],
IAM_PROGRAM_ID
);
// If no connection prop is passed, use a default devnet connection
const conn = connection || new Connection("https://api.devnet.solana.com", "confirmed");
const account = await conn.getAccountInfo(identityPda);
if (isMounted) {
if (!account || account.data.length < EXPECTED_SIZE) {
setTrustScore(null);
} else {
const data = account.data;
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
// Trust score is a u16 at offset 60
const score = view.getUint16(60, true);
setTrustScore(score);
}
}
} catch (err) {
if (isMounted) {
setTrustScore(null);
}
} finally {
if (isMounted) {
setLoading(false);
}
}
};
fetchIdentity();
}, 300);
return () => {
isMounted = false;
clearTimeout(timeoutId);
};
}, [walletAddress, connection]);
return (
<div
className={cn(
"inline-flex items-center gap-2 rounded-full border px-3 py-1.5 font-mono text-xs transition-colors",
invalid
? "border-danger/30 bg-danger/10 text-danger"
: loading
? "border-border bg-surface/30 text-muted"
: trustScore !== null
? "border-cyan/30 bg-cyan/10 text-cyan"
: "border-danger/30 bg-danger/10 text-danger",
className
)}
>
{invalid ? (
<>
<span className="h-2 w-2 rounded-full bg-danger/50" />
<span>Invalid Address</span>
</>
) : loading ? (
<>
<Loader2 className="h-3.5 w-3.5 animate-spin text-muted" />
<span>Verifying IAM...</span>
</>
) : trustScore !== null ? (
<>
<span className="h-2 w-2 rounded-full bg-cyan animate-pulse" />
<span>
Verified <span className="text-cyan/50">·</span> Trust:{" "}
<span className="font-bold">{trustScore}</span>
</span>
</>
) : (
<>
<span className="h-2 w-2 rounded-full bg-danger/50" />
<span>Not Verified</span>
</>
)}
</div>
);
}