TruthID
SDK Reference

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-sdk

Requires 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)

FieldTypeRequiredDescription
network"base-sepolia" or "base-mainnet"No — defaults to "base-mainnet"Which network to read contracts from
rpc_urlOptional[str]NoCustom 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

NameTypeDescription
originstrYour 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:

  1. User approved (not rejected)
  2. Challenge is within TTL (default: 30 seconds)
  3. Nonce matches the original challenge
  4. Cryptographic signature is valid
  5. Device is registered and active on the blockchain
  6. Retrieves the identity ID linked to this device

Parameters

NameTypeDescription
challengeAuthChallengeThe challenge you created
responseAuthResponseThe response received from the phone
ttl_ms (optional)intMax 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

reasonCause
"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

NameTypeDescription
session_hashstrbytes32 hex string (0x...)

Returns SessionInfo

session = truthid.verify_session(session_hash)
if session.exists and not session.revoked:
    pass  # still logged in

check_device_status(device_pub_key)

Looks up a device's current status on the blockchain.

Parameters

NameTypeDescription
device_pub_keystrEthereum 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

NameTypeDescription
ledger_addressstrThe owning hardware wallet / EOA address
index (optional)intAccount 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: str

AuthResponse

@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 signature

VerifyAuthResult

@dataclass
class VerifyAuthResult:
    valid: bool
    identity_id: Optional[int] = None
    device_address: Optional[str] = None
    reason: Optional[str] = None

SessionInfo

@dataclass
class SessionInfo:
    exists: bool
    revoked: bool
    identity_id: Optional[int] = None
    device_pub_key: Optional[str] = None
    created_at: Optional[datetime] = None

DeviceStatus

@dataclass
class DeviceStatus:
    exists: bool
    active: bool
    label: Optional[str] = None
    identity_id: Optional[int] = None
    added_at: Optional[datetime] = None

Security 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 verify

TTL

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

NetworkChain IDDescription
"base-sepolia"84532Testnet — for development
"base-mainnet"8453Production (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-chain

Next steps

On this page