Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.ixo.world/llms.txt

Use this file to discover all available pages before exploring further.

What identity looks like in code

Two layers — the oracle’s persistent identity, set once at boot via env vars; and per-user identity, validated on every request via UCAN headers.
import { createOracleApp, type OracleConfig } from '@ixo/oracle-runtime';

const config: OracleConfig = {
  name: 'My Oracle',
  org: 'My Org',
  description: 'What this oracle does.',
};

const app = await createOracleApp({ config });
// Runtime fills in `entityDid` from process.env.ORACLE_ENTITY_DID
// and builds the internal OracleIdentity used by every plugin.
The OracleConfig type lives in packages/oracle-runtime/src/plugin-api/types.ts — plugins read the resolved identity off ctx.identity (a OracleIdentity).
LayerWhat it isWhere it comes from
Oracle identityDID + Matrix bot user + on-chain entityOracleConfig (inline) + ORACLE_DID / ORACLE_ENTITY_DID / MATRIX_ORACLE_ADMIN_* env vars
User identityPer-request DID + UCAN capabilitiesHeaders on every incoming request (x-ucan-delegation, x-did, …)

One-time oracle setup

1

Run the CLI to create the oracle's on-chain entity and Matrix account

qiforge create-entity --no-interactive \
  --network devnet \
  --oracle-name "My Oracle" \
  --org-name "My Org" \
  --api-url http://localhost:5678 \
  --model "anthropic/claude-sonnet-4"
This creates a blockchain entity (did:ixo:entity:...), registers linked resources, provisions a Matrix bot account, and writes both oracle.config.json and .env. Run once per deployment — subsequent deploys reuse the identity.See the CLI reference for every flag.
2

Provision the encryption key for per-room secrets

qiforge setup-encryption-key
Sets up the P-256 keyAgreement on the oracle’s Matrix account room. Without this, the secrets read path returns nothing (acceptable degraded mode for routes that don’t need it).
3

Verify .env has every identity var

After CLI setup, .env contains the core identity vars validated by baseEnvSchema:
ORACLE_DID=did:ixo:ixo1...
ORACLE_ENTITY_DID=did:ixo:entity:...
ORACLE_NAME=My Oracle
NETWORK=devnet

MATRIX_BASE_URL=https://matrix.ixo.world
MATRIX_ORACLE_ADMIN_USER_ID=@oracle-bot:ixo.world
MATRIX_ORACLE_ADMIN_PASSWORD=...
MATRIX_ORACLE_ADMIN_ACCESS_TOKEN=...
MATRIX_ACCOUNT_ROOM_ID=...
MATRIX_VALUE_PIN=...
MATRIX_RECOVERY_PHRASE=...

SECP_MNEMONIC=...   # signs UCAN invocations to downstream services
The runtime validates all of these at boot via baseEnvSchema; missing vars fail with a clear message.

What the runtime loads on boot

After Matrix init completes in the background, the runtime reads two further pieces of secret material from the oracle’s Matrix account room:
  1. UCAN signing mnemonic — used by UcanService to mint downstream-service invocations.
  2. P-256 encryption key — used by SecretsService to decrypt per-room secrets.
If a key is missing, boot logs a warning naming the CLI command to fix it. Auth-requiring routes return 401 until provisioned; non-authenticated routes (/health, /docs, any host or plugin authExcludedRoutes) stay reachable.

Per-request user auth

Every chat request carries headers AuthHeaderMiddleware verifies on protected routes.
HeaderRequiredPurpose
x-ucan-delegationyesUCAN envelope authorising this user. Missing → 401 Missing x-ucan-delegation header.
x-didnoThe user’s IXO DID (set by SDK; runtime also derives it from the UCAN).
x-matrix-access-tokennoFor clients that already have a Matrix session.
x-matrix-homeservernoMatrix homeserver for the user.
x-timezonenoPropagates to rtCtx.user.timezone.
x-request-idnoCorrelation ID for logs and traces.
AuthHeaderMiddleware:
1

Validates the UCAN delegation

Calls createUCANValidator with the oracle’s DID as audience. Verifies signature, expiry, and capabilities. Caches the result for 3 minutes (or until the delegation expires, whichever is sooner).
2

Resolves the user's DID

The delegation’s invoker becomes req.authData.did. Plugin tool handlers read it as rtCtx.user.did.
3

Attaches RuntimeUserContext to the request

The next middleware (RuntimeContextBuilder) reads req.authData to build the rtCtx.user field handed to every tool handler — see the Runtime context reference.

Opt routes out of auth

Two mechanisms to expose public routes — host-level and plugin-level. Both merge onto the runtime’s built-in exclusion list (/health, /docs, …).
1

From the host — pass authExcludedRoutes to createOracleApp

import { RequestMethod } from '@nestjs/common';
import { createOracleApp, type AuthExcludedRoute } from '@ixo/oracle-runtime';

const HOST_AUTH_EXCLUDED_ROUTES: AuthExcludedRoute[] = [
  { path: 'version', method: RequestMethod.GET },
];

const app = await createOracleApp({
  config,
  nestModules: [VersionModule],
  authExcludedRoutes: HOST_AUTH_EXCLUDED_ROUTES,
});
Reference: apps/qiforge-example/src/main.ts.
2

From a plugin — implement getAuthExcludedRoutes

override getAuthExcludedRoutes(): AuthExcludedRoute[] {
  return [{ path: 'weather/now', method: RequestMethod.GET }];
}
Reference: weather.plugin.ts.
Any other plugin or host route returns 401 without a UCAN header — exclusion is path-and-method specific.

Mint downstream UCAN invocations

When a plugin calls a downstream service on the user’s behalf, it does not reuse the user’s UCAN — it mints a fresh invocation signed by the oracle’s SECP_MNEMONIC, delegating from the user’s delegation.
handler: async (args, rtCtx: RuntimeContext) => {
  const invocation = await rtCtx.ucan.mintInvocation({
    did: 'did:web:downstream-service',
    capability: 'fetch/read',
  });
  const resp = await fetch('https://downstream-service.example/data', {
    headers: { 'x-ucan-delegation': invocation },
  });
  // ...
}
The full UCAN helper surface on rtCtx.ucan:
MethodPurpose
mintInvocation(target, opts?)Mint a service-targeted invocation. opts.skipCache forces a fresh signature.
requireCapability(resource, action)Throws if the user’s delegation doesn’t include the capability.
hasCapability(resource, action)Returns a boolean — non-throwing variant.
resolveServiceDid(serviceUrl)Resolves a service URL to its did:web:... identifier. Returns null when the DID document is missing or has no id.
See packages/oracle-runtime/src/modules/ucan/ for the service implementation.

What plugins can and cannot do

  • Can: read rtCtx.user.did, rtCtx.user.matrixUserId, rtCtx.user.ucanDelegation, rtCtx.user.timezone.
  • Can: call rtCtx.secrets.getIndex() / getValues() to read per-room secrets.
  • Can: mint downstream invocations via rtCtx.ucan.mintInvocation.
  • Cannot: override the oracle’s identity per request.
  • Cannot: issue UCANs as anyone other than the oracle itself.

API endpoints

Which routes are auth-protected and which are public.

CLI reference

create-entity, setup-encryption-key, and the rest of identity setup.

Runtime context

The full rtCtx.user, rtCtx.ucan, rtCtx.secrets surface.

Environment variables

Every identity-related env var, declared and validated.