This guide covers WebAuth wallet integration, user profiles, and KYC/identity verification.
WebAuth is XPR Network's native wallet supporting:
- WebAuthn authentication - Face ID, fingerprint, security keys
- Account abstraction - Human-readable accounts
- No seed phrases - Hardware-backed keys
- Cross-device - Mobile app and web wallet
| Wallet | Type | Use Case |
|---|---|---|
| WebAuth Mobile | iOS/Android app | Primary consumer wallet |
| webauth.com | Web wallet | Browser-based, no install |
| Anchor | Desktop app | Power users, developers |
User profiles are stored in the eosio.proton contract.
interface UserInfo {
acc: string; // Account name
name: string; // Display name
avatar: string; // Avatar URL or base64
verified: boolean; // Blue checkmark
date: number; // Registration date
verifiedon: number; // Verification timestamp
verifier: string; // Account that verified
raccs: string[]; // Recovery accounts
aacts: string[]; // Authorized apps/accounts
ac: object[]; // Account containers
kyc: KYCProvider[]; // KYC verifications
}
interface KYCProvider {
kyc_provider: string; // Provider account
kyc_level: string; // Verification level
kyc_date: number; // Verification date
}async function getUserProfile(account: string) {
const { rows } = await rpc.get_table_rows({
code: 'eosio.proton',
scope: 'eosio.proton',
table: 'usersinfo',
lower_bound: account,
upper_bound: account,
limit: 1
});
return rows[0] ?? null;
}
// Example response
{
"acc": "alice",
"name": "Alice",
"avatar": "https://gateway.pinata.cloud/ipfs/Qm...",
"verified": true,
"date": 1704567890,
"verifiedon": 1705123456,
"verifier": "eosio.proton",
"raccs": [],
"aacts": [],
"ac": [],
"kyc": [
{
"kyc_provider": "eosio.proton",
"kyc_level": "2",
"kyc_date": 1705123456
}
]
}async function isKYCVerified(account: string): Promise<boolean> {
const profile = await getUserProfile(account);
if (!profile) return false;
// Check for any KYC entry
return profile.kyc && profile.kyc.length > 0;
}
async function getKYCLevel(account: string): Promise<number> {
const profile = await getUserProfile(account);
if (!profile || !profile.kyc || profile.kyc.length === 0) {
return 0; // Not KYC'd
}
// Get highest level
return Math.max(...profile.kyc.map(k => parseInt(k.kyc_level)));
}| Level | Description |
|---|---|
| 0 | Not verified |
| 1 | Basic verification (email) |
| 2 | Identity verified (ID document) |
| 3 | Enhanced verification (additional docs) |
The eosio.proton contract uses two separate actions for updating profile fields:
setusernameto update the display namesetuseravato update the avatar
Both can be submitted in a single transaction:
async function updateProfile(
session: any,
name: string,
avatar: string
) {
return session.transact({
actions: [
{
account: 'eosio.proton',
name: 'setusername',
authorization: [session.auth],
data: {
acc: session.auth.actor,
name: name
}
},
{
account: 'eosio.proton',
name: 'setuserava',
authorization: [session.auth],
data: {
acc: session.auth.actor,
ava: avatar
}
}
]
}, { broadcast: true });
}Local initials fallback — no third-party CDN, no per-render request leaking account names to an external service. Account name hashes to a deterministic hue so each user gets a consistent color.
interface AvatarProps {
account: string;
avatar?: string;
size?: number;
}
// Cheap, deterministic string → hue.
function hueFromAccount(account: string): number {
let hash = 0;
for (let i = 0; i < account.length; i++) {
hash = (hash << 5) - hash + account.charCodeAt(i);
hash |= 0; // force int32
}
return Math.abs(hash) % 360;
}
export function Avatar({ account, avatar, size = 40 }: AvatarProps) {
const [error, setError] = useState(false);
const showImage = avatar && !error;
if (showImage) {
return (
<img
src={avatar}
alt={account}
width={size}
height={size}
style={{ borderRadius: '50%', objectFit: 'cover' }}
onError={() => setError(true)}
/>
);
}
// Initials fallback — first character of the account, in a deterministic-color circle.
const hue = hueFromAccount(account);
return (
<div
role="img"
aria-label={account}
style={{
width: size,
height: size,
borderRadius: '50%',
background: `hsl(${hue}, 60%, 50%)`,
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: size * 0.45,
fontWeight: 600,
textTransform: 'uppercase',
userSelect: 'none',
}}
>
{account.charAt(0)}
</div>
);
}interface UserCardProps {
account: string;
}
export function UserCard({ account }: UserCardProps) {
const [profile, setProfile] = useState<UserInfo | null>(null);
useEffect(() => {
async function load() {
const p = await getUserProfile(account);
setProfile(p);
}
load();
}, [account]);
const isKYC = profile?.kyc?.length > 0;
return (
<div className="user-card">
<Avatar account={account} avatar={profile?.avatar} size={60} />
<div>
<h3>{profile?.name || account}</h3>
<span className="account">@{account}</span>
{profile?.verified && <span className="checkmark">✓ Verified</span>}
{isKYC && <span className="kyc-badge">KYC</span>}
</div>
</div>
);
}import ProtonWebSDK from '@proton/web-sdk';
async function login() {
const { link, session } = await ProtonWebSDK({
linkOptions: {
chainId: '384da888112027f0321850a169f737c33e53b388aad48b5adace4bab97f437e0',
endpoints: ['https://proton.eosusa.io'],
restoreSession: false
},
transportOptions: {
requestAccount: 'myapp'
},
selectorOptions: {
appName: 'My App',
appLogo: 'https://myapp.com/logo.png',
enabledWalletTypes: ['proton', 'webauth', 'anchor']
}
});
// Get user profile after login
const profile = await getUserProfile(session.auth.actor);
return {
session,
profile,
isKYC: profile?.kyc?.length > 0
};
}async function requireKYC(session: any) {
const profile = await getUserProfile(session.auth.actor);
if (!profile?.kyc?.length) {
throw new Error('KYC verification required. Please verify your identity in WebAuth.');
}
return true;
}
// Usage
async function performKYCAction(session: any, data: any) {
await requireKYC(session);
// Proceed with action
return session.transact({
actions: [/* ... */]
});
}// Redirect to WebAuth for KYC
function redirectToKYC() {
window.location.href = 'https://webauth.com/verify';
}
// Open specific account in WebAuth
function openInWebAuth(account: string) {
window.location.href = `https://webauth.com/wallet/${account}`;
}import { Name, TableStore, check } from 'proton-tsc';
// Define the usersinfo table structure
@table("usersinfo", noabigen)
class UserInfo extends Table {
constructor(
public acc: Name = new Name(),
public name: string = "",
public avatar: string = "",
public verified: boolean = false
// kyc array handling requires custom deserialization
) { super(); }
@primary
get primary(): u64 { return this.acc.N; }
}
@action("kycaction")
kycRequiredAction(user: Name): void {
requireAuth(user);
// Query eosio.proton usersinfo
const usersTable = new TableStore<UserInfo>(
Name.fromString("eosio.proton"),
Name.fromString("eosio.proton").N
);
const userInfo = usersTable.get(user.N);
check(userInfo != null, "User not found");
check(userInfo.verified, "User must be verified");
// Proceed with action...
}// Only request what you need
const profile = await getUserProfile(account);
// Don't store KYC details
const publicProfile = {
account: profile.acc,
displayName: profile.name,
avatar: profile.avatar,
verified: profile.verified
};function VerificationRequest() {
return (
<div>
<p>This action requires identity verification.</p>
<p>We will check your verification status with WebAuth.</p>
<p>We do not store your personal information.</p>
<button onClick={checkKYC}>Continue</button>
</div>
);
}# Get user profile
proton table eosio.proton usersinfo -l ACCOUNT -u ACCOUNT| Field | Description |
|---|---|
acc |
Account name |
name |
Display name |
avatar |
Avatar URL/base64 |
verified |
Blue checkmark |
kyc |
Array of KYC verifications |
const profile = await getUserProfile(account);
const isKYC = profile?.kyc?.length > 0;
const kycLevel = Math.max(...(profile?.kyc?.map(k => parseInt(k.kyc_level)) ?? [0]));