Advanced API
Low-level primitives exposed by
@embarkai/ui-kitfor apps that need direct control outside the standard hooks flow.
Most apps use the hooks under Hooks and never need this page. The APIs below exist for imperative scripts, custom flows (background tasks, non-React integrations), and advanced verification.
Error Handling
The UI kit exposes a small error hierarchy so you can discriminate user actions from protocol failures:
import {
AccountAbstractionError,
UserRejectedError,
ErrorCodes,
createAccountAbstractionError,
} from '@embarkai/ui-kit'| Symbol | Kind | Description |
|---|---|---|
AccountAbstractionError | class extends Error | Base class. Has a string code field for programmatic handling. |
UserRejectedError | class extends AccountAbstractionError | Thrown when the user cancels a signing or transaction prompt. code === 'USER_REJECTED'. |
ErrorCodes | const | Frozen map of stable error codes: MPC_SIGNING_ERROR, USER_REJECTED. |
createAccountAbstractionError({ message, code? }) | function | Reconstructs a typed error from a serialized { message, code } payload. Used internally to rehydrate errors that cross the iframe postMessage boundary. |
Discriminating errors
import { sendUserOperation, UserRejectedError } from '@embarkai/ui-kit'
try {
await sendUserOperation(session, { to, value })
} catch (err) {
if (err instanceof UserRejectedError) {
toast('You cancelled the transaction.')
return
}
throw err // unexpected — re-raise
}UserOperationTimeoutError
Thrown by waitForUserOperationReceipt(...) when the receipt does not appear before the configured timeout. Import from @embarkai/ui-kit:
import { waitForUserOperationReceipt, UserOperationTimeoutError } from '@embarkai/ui-kit'
try {
const receipt = await waitForUserOperationReceipt(userOpHash, { timeoutMs: 60_000 })
} catch (err) {
if (err instanceof UserOperationTimeoutError) {
// receipt not available yet — poll later
}
}Account Operations
Imperative counterparts to the React hooks. Use them from non-component code (web workers, event listeners, setup scripts) where hooks aren’t available.
import {
sendUserOperation,
prepareUserOperation,
signTypedData,
deployAccount,
getAllSmartAccounts,
getSmartAccountForChain,
getUserOperationByHash,
getUserOperationReceipt,
waitForUserOperationReceipt,
} from '@embarkai/ui-kit'
import type {
AccountSession,
SendTransactionParams,
SignTypedDataParams,
SmartAccountEntry,
RegisterSmartAccountResult,
} from '@embarkai/ui-kit'All transaction functions take an AccountSession (obtained from useAccountSession() or the session store) and accept an optional chainId — if omitted, the active chain from the session store is used.
prepareUserOperation(session, params)
Builds and signs a UserOperation without submitting it to the bundler. Returns the signed UserOperationV07 and its hash — useful when you want to verify or persist the op before broadcasting it.
const { userOp, userOpHash } = await prepareUserOperation(session, {
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
value: '1000000000000000000',
data: '0x',
feeType: 'standard', // 'economy' | 'standard' (default) | 'fast'
chainId: 994873017, // optional
})sendUserOperation(session, params)
Prepares, signs, and submits a UserOperation. Returns the user-op hash. Same params shape as prepareUserOperation.
signTypedData(session, params)
EIP-712 typed-data signing routed through the MPC iframe:
const signature = await signTypedData(session, {
domain: { name: 'MyApp', version: '1', chainId: 994873017, verifyingContract: '0x...' },
types: { /* ... */ },
primaryType: 'Order',
message: { /* ... */ },
chainId: 994873017, // optional — must match domain.chainId if both are set
})deployAccount(session, feeType?, options?)
Deploys the user’s smart account on a chain. Checks on-chain first and skips deployment if already deployed (unless options.force === true). Returns the deploy UserOp hash, or null if already deployed.
await deployAccount(session, 'economy', { chainId: 994873017, force: false })getAllSmartAccounts() / getSmartAccountForChain(chainId)
Read the list of smart-account addresses the current user has registered with the TSS backend, across chains. Useful for multi-chain dashboards.
const accounts: SmartAccountEntry[] = await getAllSmartAccounts()
// → [{ chainId, address, factoryAddress, registeredAt }, ...]
const lumiaEntry = await getSmartAccountForChain(994873017)UserOperation status helpers
const op = await getUserOperationByHash(userOpHash) // may be null
const receipt = await getUserOperationReceipt(userOpHash) // may be null
const final = await waitForUserOperationReceipt(userOpHash, {
timeoutMs: 60_000,
pollIntervalMs: 2_000,
})Throws UserOperationTimeoutError from the wait* variant on timeout. See Error Handling above.
Profile API
Manage the authenticated user’s profile (display name, avatar, linked providers).
import { getUserProfile, updateUserProfile } from '@embarkai/ui-kit'
import type { UserProfile, UpdateProfileRequest, ProviderDetail } from '@embarkai/ui-kit'getUserProfile()
Fetches the full profile from /api/auth/profile, including linked provider details.
const profile: UserProfile = await getUserProfile()
console.log(profile.userId, profile.displayName, profile.avatar)
for (const p of profile.providers) {
console.log(p.provider, p.externalId, p.verified, p.lastUsedAt)
}ProviderDetail fields:
| Field | Type | Description |
|---|---|---|
id | string | Composite provider:externalId. Stable key. |
provider | string | 'email' | 'telegram' | 'passkey' | 'wallet' | … |
verified | boolean | Provider-side verification status. |
linkedAt | string | ISO-8601. |
lastUsedAt | string | ISO-8601. |
externalId | string | Email address, Telegram user ID, credential ID, etc. |
meta | Record<string, any>? | Provider-specific extras. |
updateUserProfile(updates)
Patches mutable profile fields. Currently only displayName is writable.
await updateUserProfile({ displayName: 'Alice' })useLinkedProfiles()
React hook — thin reactive wrapper over getUserProfile().providers. Prefer the hook in components; use getUserProfile() in imperative code.
Nickname Fingerprint Verification
When resolving a nickname through the TSS service, the backend returns both the owner address and a short XXXX-XXXX fingerprint derived from that address. Always verify the fingerprint locally before showing the resolved address to the user — this protects against a compromised backend swapping addresses silently.
import {
generateFingerprint,
verifyFingerprint,
verifyFingerprintDetailed,
} from '@embarkai/ui-kit'
import type { FingerprintVerificationResult } from '@embarkai/ui-kit'generateFingerprint(ownerAddress)
Deterministic function that mirrors the backend algorithm: keccak256 of the checksummed address, first 5 bytes, base32 (Crockford-like, 0/O/I/L excluded), split into XXXX-XXXX.
const fp = generateFingerprint('0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb')
// → e.g. "A3BK-9XYZ"verifyFingerprint(ownerAddress, receivedFingerprint) → boolean
Quick boolean check. Use this at the call site of any nickname resolution:
import { useNicknameResolve, verifyFingerprint } from '@embarkai/ui-kit'
const { data } = useNicknameResolve('alice')
if (data && !verifyFingerprint(data.address, data.fingerprint)) {
throw new Error('Nickname resolution fingerprint mismatch — do not use this address.')
}verifyFingerprintDetailed(...)
Same check, but returns a FingerprintVerificationResult so you can surface diagnostic info to the user or logs:
const result: FingerprintVerificationResult = verifyFingerprintDetailed(addr, received)
// { isValid, computedFingerprint, receivedFingerprint, error? }Never skip verification. A
falseresult means the server’s claim is inconsistent with the cryptographic derivation — treat it as a security incident, not a UX annoyance.
See Also
- Hooks — the idiomatic React way to consume most of these APIs
- Provider Setup — obtaining the
AccountSessionused by the imperative APIs above - Core SDK → Auth — for backend / non-React auth flows