Python SDK
Full API reference for truthid-sdk on PyPI.
Full API reference for truthid-sdk on PyPI. New to TruthID? Start with the Quickstart — this page is the detailed reference for every method and type once you're integrating for real.
Installation
pip install truthid-sdkRequires Python 3.10+.
TruthIDClient
from truthid import TruthIDClient
truthid = TruthIDClient() # defaults to network="base-mainnet"Constructor
TruthIDClient(network: str = "base-mainnet", rpc_url: Optional[str] = None)
| Field | Type | Required | Description |
|---|---|---|---|
network | "base-sepolia" or "base-mainnet" | No — defaults to "base-mainnet" | Which network to read contracts from |
rpc_url | Optional[str] | No | Custom RPC endpoint. Defaults to the public Base RPC for the chosen network |
Unlike the TypeScript SDK, network has a default here — you only need to pass it to override or to use the testnet.
Methods
create_challenge(origin)
Creates a one-time challenge to embed in the QR code shown to the user.
Parameters
| Name | Type | Description |
|---|---|---|
origin | str | Your site's domain, e.g. "yoursite.com" |
Returns AuthChallenge
challenge = truthid.create_challenge("yoursite.com")
# AuthChallenge(type="challenge", nonce="3f2e1a4b-...", issuedAt=1718000000000, origin="yoursite.com")Store it, then delete it
Keep the challenge server-side, keyed by nonce, until verify_auth_response runs — then delete it immediately. See Nonce invalidation below.
Building the QR code — the mobile app expects this exact shape:
{
"action": "truthid-auth",
"challenge": { "type": "challenge", "nonce": "...", "issuedAt": 1718000000000, "origin": "yoursite.com" },
"callbackUrl": "https://yoursite.com/auth/verify"
}callbackUrl must use https:// — the mobile app refuses to send the signed response to a plain http:// URL.
verify_auth_response(challenge, response, ttl_ms=30_000)
Verifies the signed response received from the user's phone. Runs six checks in sequence and stops at the first failure:
- User approved (not rejected)
- Challenge is within TTL (default: 30 seconds)
- Nonce matches the original challenge
- Cryptographic signature is valid
- Device is registered and active on the blockchain
- Retrieves the identity ID linked to this device
Parameters
| Name | Type | Description |
|---|---|---|
challenge | AuthChallenge | The challenge you created |
response | AuthResponse | The response received from the phone |
ttl_ms (optional) | int | Max challenge age in ms. Default: 30_000 |
Returns VerifyAuthResult
AuthResponse has no from_dict helper — build it field by field from the parsed JSON body. Its fields are camelCase (matching the wire format), not snake_case:
from truthid import AuthResponse
data = request.json # { "approved": ..., "nonce": ..., "signature": ..., "deviceAddress": ... }
response = AuthResponse(
approved=data["approved"],
nonce=data["nonce"],
signature=data["signature"],
deviceAddress=data["deviceAddress"],
)
result = truthid.verify_auth_response(challenge, response)
if result.valid:
print(f"Authenticated! Identity ID: {result.identity_id}")
else:
print(f"Failed: {result.reason}")Failure reasons
reason | Cause |
|---|---|
"User rejected the login request" | User tapped "Reject" on their phone |
"Challenge expired" | More than ttl_ms ms have passed since issuedAt |
"Nonce mismatch" | Response nonce doesn't match the challenge |
"Invalid signature format" | Signature is malformed |
"Signature does not match device address" | Signature was not made by deviceAddress |
"Device is not active or has been revoked" | Device was revoked by the identity owner |
verify_session(session_hash)
Checks whether a session hash is still valid (not revoked). Call this on subsequent requests after login to confirm the session hasn't been revoked from another device.
Parameters
| Name | Type | Description |
|---|---|---|
session_hash | str | bytes32 hex string (0x...) |
Returns SessionInfo
session = truthid.verify_session(session_hash)
if session.exists and not session.revoked:
pass # still logged incheck_device_status(device_pub_key)
Looks up a device's current status on the blockchain.
Parameters
| Name | Type | Description |
|---|---|---|
device_pub_key | str | Ethereum address of the device (0x...) |
Returns DeviceStatus
status = truthid.check_device_status(device_pub_key)compute_smart_account_address(ledger_address, index=0)
Predicts a user's smart account (controller) address via CREATE2 — pure local computation, no RPC call and no server involved. Useful for showing a deposit address, or checking a balance, before the account has ever been used on-chain.
Parameters
| Name | Type | Description |
|---|---|---|
ledger_address | str | The owning hardware wallet / EOA address |
index (optional) | int | Account index, for multiple smart accounts per owner. Default: 0 |
Returns str — the checksummed smart account address.
smart_account = truthid.compute_smart_account_address(ledger_address)A standalone function is also exported for when you don't need a full client instance:
from truthid import compute_smart_account_address
smart_account = compute_smart_account_address(ledger_address, network="base-mainnet")Types
All of the following are exported from truthid. AuthChallenge and AuthResponse use camelCase field names because they mirror the JSON shape the mobile app sends and signs directly — every other type follows normal Python snake_case, since it never crosses the wire.
AuthChallenge
@dataclass
class AuthChallenge:
type: str
nonce: str
issuedAt: int # Unix timestamp in ms
origin: strAuthResponse
@dataclass
class AuthResponse:
approved: bool
nonce: str
signature: str # secp256k1 signature, hex ("0x...")
deviceAddress: str # Ethereum address of the device key
sessionSignature: Optional[str] = None # personal_sign over keccak256(nonce) — always sent alongside signatureVerifyAuthResult
@dataclass
class VerifyAuthResult:
valid: bool
identity_id: Optional[int] = None
device_address: Optional[str] = None
reason: Optional[str] = NoneSessionInfo
@dataclass
class SessionInfo:
exists: bool
revoked: bool
identity_id: Optional[int] = None
device_pub_key: Optional[str] = None
created_at: Optional[datetime] = NoneDeviceStatus
@dataclass
class DeviceStatus:
exists: bool
active: bool
label: Optional[str] = None
identity_id: Optional[int] = None
added_at: Optional[datetime] = NoneSecurity notes
Nonce invalidation
Delete the challenge from your store before calling verify_auth_response, not after — deleting after leaves a race condition where the same signed response can be submitted twice within the TTL window.
# Correct order
del pending_challenges[response.nonce] # delete first
result = truthid.verify_auth_response(...) # then verifyTTL
The default is 30 seconds, matching the mobile app's own challenge expiry. Lowering it is fine; raising it above 30 seconds has no effect, since the phone will already refuse an older challenge.
HTTPS only
The phone POSTs the signed response directly to your callback URL — the mobile app refuses non-https:// URLs, and your endpoint still needs a valid TLS certificate for the connection to succeed.
Networks
| Network | Chain ID | Description |
|---|---|---|
"base-sepolia" | 84532 | Testnet — for development |
"base-mainnet" | 8453 | Production (default) |
# Testnet during development
truthid = TruthIDClient(network="base-sepolia")
# Custom RPC instead of the public endpoint
truthid = TruthIDClient(network="base-mainnet", rpc_url="https://your-private-rpc.example.com")Contract addresses for both networks are in Smart contracts.
Session visibility — nothing to do
Completed logins already show up in the user's TruthID mobile/desktop apps and can be individually revoked from there. The phone registers the session on-chain itself (via a UserOperation) before it ever calls your callback URL — there's no separate registration step for you to call.
If you want to double-check a session landed on-chain, derive its hash from the nonce and call verify_session:
from web3 import Web3
session_hash = "0x" + Web3.keccak(text=response.nonce).hex()
session = truthid.verify_session(session_hash)
if session.exists and not session.revoked:
pass # confirmed on-chainNext steps
- Quickstart — full walkthrough from install to first login
- Full Flask example with session tokens and a protected route
- TypeScript SDK reference
- Ruby SDK reference