TruthID

Smart Contracts

Addresses, ABIs, function reference, and real gas costs for TruthID's six Solidity contracts on Base.

TruthID is six Solidity contracts on Base — no backend, no database. Everything described below is on the public chain and independently checkable; this page exists for integrators who want to read on-chain data directly, write their own SDK, or just verify what's actually deployed.

All six contracts are verified on Basescan and carry no upgrade proxy — a bug can't be hot-patched in the deployed bytecode. That's not the same as the deployed addresses being permanent, though: TruthIDAccount and TruthIDAccountFactory both have an owner-controlled updateRegistries function specifically so existing smart accounts can be redirected after a migration to a new deployment, which has happened more than once as issues were found and fixed (see Security Model → Audit status). The addresses below are current as of the latest deployment.

Addresses

Base Mainnet (production, chain ID 8453)

Base Sepolia (testnet, chain ID 84532)

Use testnet while developing — see Networks in the SDK reference for how to point a client at Sepolia. VaultRegistry isn't deployed on Sepolia today (Mainnet only). TruthIDAccount itself isn't listed here — every identity gets its own instance, deployed on demand by the factory; see Smart Account & Gas for how that address is computed.

Getting the ABI

You probably don't need this — the SDKs already wrap every contract call behind plain functions. Reach for the raw ABI only if you're reading state from a language without an official SDK, or auditing the deployment yourself.

There's no published npm/PyPI/RubyGems package containing the full ABI JSON — the SDKs only embed the minimal fragment each function needs internally. To generate the full artifact yourself: git clone the repo, then cd contracts && forge build (output lands in the gitignored out/ directory).

Contract reference

A quick map of what each contract does and who's allowed to call what. Full behavior (commit-reveal, guardian thresholds, session hashing) is covered in How TruthID Works and Security Model — this is just the function list.

IdentityRegistry

Username ↔ controller wallet mapping. The root of every other contract — DeviceRegistry, RecoveryManager, and SessionRegistry all resolve a caller's identity by asking this contract who controls which username.

FunctionCallerPurpose
createIdentity(username)anyone (becomes controller)Mint a new identity
transferController(username, newController)current controllerMove control to another wallet
setRecoveryManager(rm)deploy owner, onceWire up the RecoveryManager address
recoverController(username, newController)RecoveryManager onlyChange controller via social recovery
getIdentity(username) / getUsernameByController(addr) / isUsernameTaken(username) / totalIdentities()anyone, freeReads

DeviceRegistry

Device public keys per identity, registered via commit-reveal to prevent front-running. Caps at MAX_DEVICES = 50 per identity.

FunctionCallerPurpose
commitDevice(commitment)the future device ownerStep 1/2 — commit a hidden hash
registerDevice(devicePubKey, label, salt, encryptedVaultKey)same caller as the commitStep 2/2 — reveal and register. encryptedVaultKey is optional (pass empty bytes) — when set, it's the Vault's AES key, ECIES-encrypted to this device's public key, so the device can decrypt the Vault without needing the owner wallet again
revokeDevice(devicePubKey)the identity's controllerDeactivate a device
updateDeviceVaultKey(devicePubKey, newEncryptedVaultKey)the identity's controllerRotate the encrypted Vault key stored for an active device (used when the Vault's key itself rotates after revoking a different device)
revokeAllDevices(identityId)RecoveryManager onlyDeactivate every device on an identity at once — run automatically during social recovery so the old controller's devices can't keep authenticating
isDeviceActive(devicePubKey) / getDevice(devicePubKey) / getDevicesByIdentity(id) / getDevicesByIdentityPaginated(id, offset, limit) / deviceCount(id)anyone, freeReads

RecoveryManager

M-of-N guardian recovery with a 7-day timelock (TIMELOCK constant) and a 20-guardian cap (MAX_GUARDIANS constant).

FunctionCallerPurpose
configureGuardians(username, guardians[], threshold)the identity's controllerSet or replace the guardian set
proposeRecovery(username, newController)a configured guardianStart a recovery
approveRecovery(username)a configured guardianVote on the active proposal
executeRecovery(username)anyone, after threshold + 7 daysApply the new controller
cancelRecovery(username)the current controllerAbort during the timelock window
getGuardianConfig(username) / getProposal(username) / hasGuardianApproved(username, guardian)anyone, freeReads

SessionRegistry

Stores only a keccak256 hash per session — never the data it represents.

FunctionCallerPurpose
createSession(hash, identityId, devicePubKey, r, s, v)anyone can submit, but requires the device's own ECDSA signature over hashRecord a session hash
revokeSession(hash)the session's identity controllerRevoke one session
revokeAllSessions()the identity's controllerRevoke every session up to now, in one O(1) call
isSessionRevoked(hash) / getSession(hash) / getSessionsByIdentity(id) / getRevokedBefore(id)anyone, freeReads

TruthIDAccount

The ERC-4337 smart account that controls an identity — one instance per owner, deployed via TruthIDAccountFactory. Full explanation of the two signer tiers, setup flow, and cost is in Smart Account & Gas; this is just the function list.

FunctionCallerPurpose
validateUserOp(userOp, userOpHash, missingFunds)EntryPoint onlyVerify a UserOperation's signature and pay the prefund
execute(dest, value, data) / executeBatch(dest[], value[], data[])EntryPoint, the owner, or the account itselfPerform one or more calls — the only entry point available to device-tier signers
addDevice(device) / removeDevice(device)owner (directly or via a UserOp)Authorize or deauthorize a device-tier signer
blockDestinationForDevices(dest) / unblockDestinationForDevices(dest)ownerExtend or shrink the set of contracts devices can't target via execute
emergencyWithdraw(recipient)RecoveryManager onlyMove the account's ETH balance out during social recovery
updateRegistries(deviceRegistry, identityRegistry, recoveryManager)owner (directly or via a UserOp)Repoint the account at a new deployment of the three registries after a migration — also re-syncs blockedForDevices so device-tier signers can't call either the old or the new registries directly
receive()anyonePlain ETH transfers (funding the account)
owner() / authorizedDevices(addr) / blockedForDevices(addr)anyone, freeReads

TruthIDAccountFactory

Deploys TruthIDAccount instances at deterministic CREATE2 addresses, one per owner (or per owner + index, for multiple accounts).

FunctionCallerPurpose
createAccount(owner, index)anyoneDeploy the account if it doesn't exist yet; idempotent — returns the existing address otherwise
getAddress(owner, index)anyone, freePredict the CREATE2 address before deployment
updateRegistries(deviceRegistry, identityRegistry, recoveryManager)deployer/ownerChanges what the next createAccount call deploys accounts pointed at — doesn't affect already-deployed accounts, which each update their own pointers individually via TruthIDAccount.updateRegistries above

VaultRegistry

Stores a pointer to the user's encrypted Vault blob — never the Vault's contents. The cid field name is a holdover from when this always held an IPFS CID; today it typically holds an ar://<transaction-id> reference instead (see Cross-Device & Storage for why). Content integrity is checked against contentHash, a keccak256 of the encrypted blob.

FunctionCallerPurpose
updateVault(cid, contentHash)the identity's controllerPublish a new Vault version — reverts on an empty cid or zero contentHash
getVault(id) / hasVault(id) / getVaultHistory(id) / getVaultHistoryPaginated(id, offset, limit)anyone, freeReads. getVaultHistory can revert for very long histories (gas cap on eth_call results) — use the paginated version for identities with MAX_HISTORY = 1000 (the cap) worth of updates

Cost per operation

Real gas numbers from the project's own test suite (forge test --gas-report), not estimates — IdentityRegistry/DeviceRegistry/RecoveryManager/SessionRegistry have 140 tests passing; TruthIDAccount/TruthIDAccountFactory (rows below revokeAllSessions) have their own 62. VaultRegistry's operations aren't in this table — updateVault is the only state-changing call, and its cost is dominated by the size of cid/contentHash you pass in rather than fixed contract logic. Read functions (view) are free when called the normal way — directly via an RPC eth_call, exactly how every SDK reads them. The "free" label below stops applying only if another contract calls them mid-transaction, which no part of TruthID does today.

OperationMin gasMedian gasMax gas
createIdentity22,153141,483142,832
commitDevice44,21144,21144,211
registerDevice23,757195,037218,286
revokeDevice24,41140,76740,767
configureGuardians37,894340,3291,028,006
proposeRecovery22,532162,839162,839
approveRecovery39,18771,78788,887
executeRecovery39,349126,801126,801
cancelRecovery36,92943,95143,951
createSession27,061183,605183,605
revokeSession24,50143,15745,501
revokeAllSessions27,96154,44654,446
TruthIDAccountFactory.createAccount (already deployed)24,996——
TruthIDAccountFactory.createAccount (first deploy)——1,774,511
TruthIDAccount.execute23,13571,51971,544
TruthIDAccount.addDevice21,92445,81245,812
TruthIDAccount.removeDevice22,14823,85424,573

Why configureGuardians has such a wide range

Gas scales with the size of the guardians array. The low end is a couple of guardians; the high end in this table comes from a test that exercises the full MAX_GUARDIANS cap of 20. A typical 3-of-5 setup lands well below the median shown here.

What that costs in ETH: at the ~0.011 gwei gas price observed during the mainnet deploy, the heaviest operation in this table (registerDevice, ~195k gas) comes out to roughly 0.0000021 ETH — a small fraction of a cent. TruthIDAccountFactory.createAccount's first-deploy row is heavier still (it deploys a whole contract via CREATE2, not just a state update) — see Smart Account & Gas → Cost for that conversion. Gas price moves with network demand; check Base's live gas tracker for the current rate before assuming these numbers hold exactly.

Audit status

See Security Model → Audit status for the manual review findings and what's not yet covered by a third-party audit.

Next steps

  • How TruthID Works — the protocol walkthrough behind these functions
  • Smart Account & Gas — how the ERC-4337 account works, setup flow, and funding
  • Security Model — threat model and what TruthID does and doesn't protect against
  • SDK Reference — the typed, documented way to call these contracts from TypeScript, Python, or Ruby

On this page