Skip to main content
Core Insight: There are no “modes.” Self-sovereign vs SaaS is an emergent property of how you configure the same system. The only things that vary are who is the Account Owner (TEE-derived wallet vs SAFE vs EOA) and what scopes the API keys have. The contracts don’t know or care.

Entity Boundaries

The system spans four distinct trust boundaries. Understanding what lives where is essential to reasoning about security.

User / External

EntityDescription
Account OwnerTop-level identity. An address on Base that can do everything. Can be an EOA, TEE-derived wallet (Stytch auth), or SAFE/governance contract. Ultimate authority.
API Key (Private Key)User holds the private key locally. Sent to TEE over HTTPS per-request. TEE derives the address and checks scopes on-chain.
SAFE / GovernanceOptional. Multisig, timelocks, voting. Submits txs directly to chain for structural changes — TEE not involved.

TEE Enclave (Phala / dstack)

EntityDescription
Root KeyMaster secret managed by Phala’s KMS. Only approved TEE build images can derive from it. Never leaves the enclave.
Key DerivationSigning key + symmetric encryption key derived transiently from root key using derivation path ID. Never persisted.
Auth VerificationDerives address from API key → reads on-chain scopes → checks group membership of action CID + PKP → allows or rejects.
Sandbox ExecutionFetches Lit Action from IPFS, runs in sandboxed JS environment with access to derived key material. Returns result to caller.
TX RelayConvenience relay for management operations. TEE checks scopes, then signs and submits tx to Base on user’s behalf.

On-Chain (Base)

EntityDescription
Account ContractRegisters the owner address. All permissions flow from this. Requires msg.sender == owner.
API Key RegistryOn-chain mapping of key addresses → scopes. Includes per-group scope bindings. Owner-managed.
PKP RegistryList of PKP derivation path IDs owned by the account. PKPs are created here, then referenced by Groups.
GroupsPermission policies binding {PKP IDs, Action CIDs}. “Who can execute” is on the API key, not the group.

IPFS (Content-addressed)

EntityDescription
Lit ActionsImmutable JS on IPFS, referenced by CID. Not owned by anyone — public, reusable, content-addressed. Like npm packages.

Entity Relationships

USER / EXTERNAL                ON-CHAIN (BASE)             TEE ENCLAVE
─────────────────────────────  ──────────────────────────  ──────────────────────────────
Account Owner                  Account Contract             Root Key
 EOA / SAFE / TEE-derived   ──▶  owner address registered    master secret, never exported
      │ owns                                                      │ derives
      │                        API Key Registry             Auth + Key Derivation
API Key (private key)       ──▶  address → scopes mapping ◀──    verify scopes, derive keys
 Held by user, sent/request                                       │ provides keys
      │ sent over HTTPS            │ reads                        │
      └──────────────────────────▶ TEE                    Sandbox Execution
                                   │                        runs Lit Actions w/ key material
                                  PKP Registry                    │ fetched from IPFS
                                   derivation path IDs            ▼
                                      │ referenced         Lit Actions (IPFS)
                                  Groups               ◀──   Immutable JS, public CIDs
                                   {PKP refs, ACIDs}
                                      │ CID ref
                                  TX Relay
                                   signs + submits mgmt txs

Execution Flow (Inside the TEE)

StepActionWho
1User sends API key (private key) + "run action QmABC with pkp_001" over HTTPSuser → tee
2TEE derives the public address from the provided private keyinside tee
3TEE reads the API Key Registry on Base — does this address have execute scope? On which groups?tee → chain read
4TEE checks: is there a group this key can execute on where QmABC is a listed action AND pkp_001 is a listed PKP?tee → chain read
5If authorized → derive pkp_001 key material from root key → fetch QmABC from IPFS → execute in sandbox with key material access → return resultinside tee + ipfs fetch
✕ RejectIf any check fails → reject the request. No key material is derived.

Management Paths

There are two paths for making structural changes (creating groups, adding PKPs, updating scopes):

Path A: Via TEE Relay (convenience)

TEE checks scopes and submits the transaction on the user’s behalf.
User + API Key  →  TEE (verify scopes)  →  Permissions Contract

Path B: Direct to Chain (self-sovereign)

Owner submits transactions directly — TEE is not involved.
SAFE / EOA  →  Permissions Contract

API Key Scopes

ScopeAllowsType
executeInvoke Lit Actions with PKPsper-group
pkp:createCreate new PKPs in the account’s registryaccount-wide
group:createCreate new groupsaccount-wide
group:deleteDelete groupsaccount-wide
group:manageActionsAdd / remove action CIDs in a groupper-group
group:addPkpAdd PKP references to a groupper-group
group:removePkpRemove PKP references from a groupper-group

Permission Matrix

OperationOwnerAPI KeyScope Required
Invoke action + PKPexecute(group_id)
Create PKPpkp:create
Create groupgroup:create
Delete groupgroup:delete
Add/remove actionsgroup:manageActions(group_id)
Add PKPs to groupgroup:addPkp(group_id)
Remove PKPs from groupgroup:removePkp(group_id)
Add / revoke API keyowner only
Update API key scopesowner only
Transfer ownershipowner only

Configuration Comparison

The same system, two very different security postures — determined entirely by who the owner is and what scopes the keys carry.

SaaS Mode

Fast iteration, broad scopes, Stytch recovery
OwnerTEE-derived wallet from Stytch auth (email / passkey / OAuth)
API Keydev_key with all scopes: execute(*), pkp:create, group:create, group:delete, group:manageActions(*), group:addPkp(*), group:removePkp(*)
EffectDeveloper does everything via HTTP. Only API key management and ownership transfer require Stytch-authenticated dashboard. Recovery = Stytch re-auth.

Self-Sovereign Mode

Auditable governance, restricted scopes, SAFE multisig
Owner3-of-5 SAFE multisig on Base
API Keysserver_keyexecute(group_1) only. onboard_keypkp:create + group:addPkp(group_1) only. No structural scopes granted.
EffectDay-to-day ops via purpose-built keys. All structural changes (groups, actions, PKP removal) require SAFE vote. Leaked key blast radius is minimal.