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, constructed as new AccountAbstractionError(message, code). Has a readonly string code for programmatic handling. |
UserRejectedError | class extends AccountAbstractionError | Thrown when the user cancels a signing or transaction prompt. code === 'USER_REJECTED'. |
SessionExpiredError | class extends AccountAbstractionError | Thrown when the session is no longer valid. code === 'SESSION_EXPIRED'; check with isSessionExpiredError(err). |
ErrorCodes | const | Frozen map of stable error codes: MPC_SIGNING_ERROR, USER_REJECTED, SESSION_EXPIRED. |
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, isSessionExpiredError } from '@embarkai/ui-kit'
import type { AccountSession } from '@embarkai/ui-kit'
async function send(session: AccountSession) {
try {
return await sendUserOperation(session, {
to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
value: '1000000000000000000',
})
} catch (err) {
if (err instanceof UserRejectedError) {
console.log('You cancelled the transaction.')
return null
}
if (isSessionExpiredError(err)) {
console.log('Please sign in again.')
return null
}
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'
const userOpHash = '0x…' as `0x${string}`
try {
const receipt = await waitForUserOperationReceipt(userOpHash, { maxPollTimeMs: 60_000 })
console.log(receipt.success, receipt.receipt.transactionHash)
} catch (err) {
if (err instanceof UserOperationTimeoutError) {
console.log(`No receipt for ${err.userOpHash} after ${err.timeoutMs} ms`)
}
}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,
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.
import { prepareUserOperation } from '@embarkai/ui-kit'
import type { AccountSession } from '@embarkai/ui-kit'
async function prepare(session: AccountSession) {
const { userOp, userOpHash } = await prepareUserOperation(session, {
to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
value: '1000000000000000000',
data: '0x',
feeType: 'standard', // 'economy' | 'standard' (default) | 'fast'
chainId: 994873017, // optional
})
return { userOp, userOpHash }
}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:
import { signTypedData } from '@embarkai/ui-kit'
import type { AccountSession } from '@embarkai/ui-kit'
async function signOrder(session: AccountSession) {
return signTypedData(session, {
domain: {
name: 'MyApp',
version: '1',
chainId: 994873017,
verifyingContract: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
},
types: {
Order: [
{ name: 'maker', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
},
primaryType: 'Order',
message: { maker: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', amount: 1n },
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.
import { deployAccount } from '@embarkai/ui-kit'
import type { AccountSession } from '@embarkai/ui-kit'
async function ensureDeployed(session: AccountSession) {
const hash = await deployAccount(session, 'economy', { chainId: 994873017, force: false })
return hash // null if the account was already deployed
}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.
import { getAllSmartAccounts, getSmartAccountForChain } from '@embarkai/ui-kit'
import type { SmartAccountEntry } from '@embarkai/ui-kit'
const accounts: SmartAccountEntry[] = await getAllSmartAccounts()
// → [{ chainId, address, factoryAddress, registeredAt }, ...]
const lumiaEntry = await getSmartAccountForChain(994873017)UserOperation status helpers
import {
getUserOperationByHash,
getUserOperationReceipt,
waitForUserOperationReceipt,
} from '@embarkai/ui-kit'
const userOpHash = '0x…' as `0x${string}`
const op = await getUserOperationByHash(userOpHash) // may be null
const receipt = await getUserOperationReceipt(userOpHash) // may be null
const final = await waitForUserOperationReceipt(userOpHash, {
maxPollTimeMs: 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.
import { getUserProfile } from '@embarkai/ui-kit'
import type { UserProfile } from '@embarkai/ui-kit'
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.
import { updateUserProfile } from '@embarkai/ui-kit'
await updateUserProfile({ displayName: 'Alice' })useLinkedProfiles()
React hook that returns { profiles, avatar, isLoading, isFetching, error, refresh } built from the profile’s 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.
import { generateFingerprint } from '@embarkai/ui-kit'
const fp = generateFingerprint('0x742d35Cc6634C0532925a3b844Bc454e4438f44e')
// → e.g. "A3BK-9XYZ"verifyFingerprint(ownerAddress, receivedFingerprint) → boolean
Quick boolean check. useNicknameResolve already performs it and exposes the result as isFingerprintVerified; call verifyFingerprint yourself when you resolve nicknames through your own backend:
import { useActiveChainId, useNicknameResolve, verifyFingerprint } from '@embarkai/ui-kit'
function ResolvedOwner({ nickname }: { nickname: string }) {
const chainId = useActiveChainId()
const { data, isFingerprintVerified } = useNicknameResolve(nickname, { chainId })
if (!data?.owner || !data.fingerprint) return null
if (!verifyFingerprint(data.owner, data.fingerprint)) {
throw new Error('Nickname resolution fingerprint mismatch — do not use this address.')
}
return <p>{data.owner} {isFingerprintVerified ? '✓' : ''}</p>
}verifyFingerprintDetailed(...)
Same check, but returns a FingerprintVerificationResult so you can surface diagnostic info to the user or logs:
import { verifyFingerprintDetailed } from '@embarkai/ui-kit'
import type { FingerprintVerificationResult } from '@embarkai/ui-kit'
const result: FingerprintVerificationResult = verifyFingerprintDetailed(
'0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
'A3BK-9XYZ',
)
// { 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