Ruby SDK
Full API reference for truthid-sdk on RubyGems.
Full API reference for truthid-sdk on RubyGems. 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
gem install truthid-sdkRequires Ruby 3.0+.
TruthID::Client
require "truthid"
truthid = TruthID::Client.new # defaults to network: "base-mainnet"There's also a factory function, if you prefer it:
truthid = TruthID.new_client # equivalent to TruthID::Client.newConstructor
TruthID::Client.new(network: "base-mainnet", rpc_url: nil)
| Field | Type | Required | Description |
|---|---|---|---|
network | "base-sepolia" or "base-mainnet" | No — defaults to "base-mainnet" | Which network to read contracts from |
rpc_url | String or nil | No | Custom RPC endpoint. Defaults to the public Base RPC for the chosen network |
Like Python, network has a default here — you only need to pass it to override or to use the testnet. (The TypeScript SDK requires it explicitly, with no default.)
Methods
create_challenge(origin)
Creates a one-time challenge to embed in the QR code shown to the user.
Parameters
| Name | Type | Description |
|---|---|---|
origin | String | Your site's domain, e.g. "yoursite.com" |
Returns AuthChallenge
challenge = truthid.create_challenge("yoursite.com")
challenge.nonce #=> "3f2e1a4b-..."
challenge.issued_at #=> 1718000000000Store 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 (challenge.to_h already produces the right camelCase keys):
{
"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) | Integer | Max challenge age in ms. Default: 30_000 |
Returns VerifyAuthResult
AuthResponse.from_hash builds it straight from the parsed JSON body — unlike the Python SDK, no manual field mapping needed:
data = JSON.parse(request.body.read) # { "approved" => ..., "nonce" => ..., "signature" => ..., "deviceAddress" => ... }
response = TruthID::AuthResponse.from_hash(data)
result = truthid.verify_auth_response(challenge, response)
if result.valid
puts "Authenticated! Identity ID: #{result.identity_id}"
else
puts "Failed: #{result.reason}"
endFailure reasons
reason | Cause |
|---|---|
"User rejected the login request" | User tapped "Reject" on their phone |
"Challenge expired" | More than ttl_ms ms have passed since issued_at |
"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 device_address |
"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 | String | bytes32 hex string (0x...) |
Returns SessionInfo
session = truthid.verify_session(session_hash)
if session.exists && !session.revoked
# still logged in
endcheck_device_status(device_pub_key)
Looks up a device's current status on the blockchain.
Parameters
| Name | Type | Description |
|---|---|---|
device_pub_key | String | 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 | String | The owning hardware wallet / EOA address |
index: (optional) | Integer | Account index, for multiple smart accounts per owner. Default: 0 |
Returns String — the checksummed smart account address.
smart_account = truthid.compute_smart_account_address(ledger_address)A module-level function is also available for when you don't need a full client instance:
smart_account = TruthID.compute_smart_account_address(ledger_address, network: "base-mainnet")Types
All of the following are in the TruthID module. AuthChallenge and AuthResponse are plain classes — their attributes follow normal Ruby snake_case (issued_at, device_address), and each has a conversion method (to_h / from_hash) at the boundary where it meets the camelCase JSON the mobile app actually sends and signs. VerifyAuthResult, SessionInfo, and DeviceStatus are Structs, since they never cross the wire.
AuthChallenge
class AuthChallenge
attr_reader :type, :nonce, :issued_at, :origin
# to_h => { "type" => ..., "nonce" => ..., "issuedAt" => ..., "origin" => ... }
endAuthResponse
class AuthResponse
attr_reader :approved, :nonce, :signature, :device_address, :session_signature
# self.from_hash(h) reads h["deviceAddress"] into device_address, etc.
# session_signature: personal_sign over keccak256(nonce) — always sent alongside signature
endVerifyAuthResult
VerifyAuthResult = Struct.new(:valid, :identity_id, :device_address, :reason, keyword_init: true)SessionInfo
SessionInfo = Struct.new(:exists, :revoked, :identity_id, :device_pub_key, :created_at, keyword_init: true)DeviceStatus
DeviceStatus = Struct.new(:exists, :active, :label, :identity_id, :added_at, keyword_init: true)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
pending_challenges.delete(response.nonce) # delete first
result = truthid.verify_auth_response(challenge, 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 = TruthID::Client.new(network: "base-sepolia")
# Custom RPC instead of the public endpoint
truthid = TruthID::Client.new(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:
session_hash = "0x" + Eth::Util.keccak256(response.nonce).unpack1("H*")
session = truthid.verify_session(session_hash)
if session.exists && !session.revoked
# confirmed on-chain
endNext steps
- Quickstart — full walkthrough from install to first login
- Full Sinatra example with session tokens and a protected route
- TypeScript SDK reference
- Python SDK reference