Building Holochain Apps
Let users prove their Flowsta identity on your Holochain app's DHT.
When users link their Flowsta identity to your app, a cryptographic attestation (IsSamePersonEntry) is committed to your DHT. Anyone on your network can verify the link using Ed25519 cryptography - no shared DNA or API dependency required.
Prerequisites
- A Holochain application with its own DNA
- A registered app at dev.flowsta.com (for
client_id) - Users have Flowsta Vault installed on their desktop
Works with any framework
This guide applies to Tauri, Electron, and any other desktop framework. The @flowsta/holochain SDK communicates with Flowsta Vault via its localhost IPC server and works in any JavaScript environment — no framework-specific adapter needed.
Step 1: Add Agent-Linking Zomes
Add the flowsta-agent-linking crate to your DNA:
# integrity/Cargo.toml
[dependencies]
flowsta-agent-linking-integrity = { git = "https://github.com/WeAreFlowsta/flowsta-agent-linking" }# coordinator/Cargo.toml
[dependencies]
flowsta-agent-linking-coordinator = { git = "https://github.com/WeAreFlowsta/flowsta-agent-linking" }Reference them in your dna.yaml:
integrity:
zomes:
- name: agent_linking_integrity
bundled: ../../target/.../flowsta_agent_linking_integrity.wasm
coordinator:
zomes:
- name: agent_linking
bundled: ../../target/.../flowsta_agent_linking_coordinator.wasm
dependencies:
- name: agent_linking_integrityStep 2: Install the SDK
npm install @flowsta/holochainStep 3: Configure Scopes
In your app's settings at dev.flowsta.com, select the scopes your app needs. Scopes control which Flowsta profile fields are exposed to your app via the Vault's local IPC server (GET /status). The user sees the scope list in the Vault approval dialog before they approve.
| Scope | What you receive | Typical use |
|---|---|---|
openid | Basic identity (auto-included) | Always present — not shown to the user |
did | User's decentralized identifier | Unique identity across your app |
public_key | Vault's Holochain agent public key | Agent linking ceremony |
holochain | Holochain identity access | Required for agent linking |
display_name | User's display name | Profile UI |
username | User's @username | Profile UI |
profile_picture | Avatar URL | Profile UI |
Fields for scopes you haven't selected are returned as null from /status, even if the user has that data in their Vault. Scope changes at dev.flowsta.com take effect immediately — no app rebuild needed.
Step 4: Request Identity Linking
import { linkFlowstaIdentity } from '@flowsta/holochain';
// Request link from Vault
const result = await linkFlowstaIdentity({
appName: 'ChessChain',
clientId: 'flowsta_app_abc123', // from dev.flowsta.com
localAgentPubKey: myAgentKey, // uhCAk... format
});
// result.payload contains:
// - vaultAgentPubKey: the Vault's agent key
// - vaultSignature: Ed25519 signature of the 78-byte linking payloadStep 5: Commit to Your DHT
import { decodeHashFromBase64 } from '@holochain/client';
await appWebsocket.callZome({
role_name: 'my-role',
zome_name: 'agent_linking',
fn_name: 'create_direct_link',
payload: {
other_agent: decodeHashFromBase64(result.payload.vaultAgentPubKey),
other_signature: base64ToSignature(result.payload.vaultSignature),
},
});The create_direct_link function:
- Verifies the Vault's Ed25519 signature
- Creates a local signature from your app's agent key
- Commits an
IsSamePersonEntrywith both signatures - Creates lookup links for querying
Step 6: Query Linked Agents
import { getFlowstaIdentity } from '@flowsta/holochain';
const linkedAgents = await getFlowstaIdentity({
appWebsocket,
roleName: 'my-role',
agentPubKey: someAgentKey,
});
// linkedAgents is Uint8Array[] - array of linked agent public keys
if (linkedAgents.length > 0) {
console.log(`Linked to ${linkedAgents.length} Flowsta identities`);
}Or call the zome directly:
const linkedAgents = await appWebsocket.callZome({
role_name: 'my-role',
zome_name: 'agent_linking',
fn_name: 'get_linked_agents',
payload: myAgentKey,
});Error Handling
Handle Vault availability gracefully:
import { getVaultStatus, linkFlowstaIdentity } from '@flowsta/holochain';
// Check if Vault is available first
const status = await getVaultStatus();
if (!status.running) {
showMessage('Please install and start Flowsta Vault');
return;
}
if (!status.unlocked) {
showMessage('Please unlock your Flowsta Vault');
return;
}
try {
const result = await linkFlowstaIdentity({ /* ... */ });
} catch (error) {
if (error.name === 'UserDeniedError') {
showMessage('Identity linking was cancelled');
} else if (error.name === 'InvalidClientIdError') {
showMessage('App registration error');
}
}See the full error reference in Agent Linking.
User Data Backups & Reinstall Recovery
If your app stores user data on Holochain, integrate the canonical-shape backup pipeline so your users never lose data on reinstall, device wipe, or move to a new machine.
You write two small Tauri commands — decode_record_for_export (decode an entry to plain JSON for the user's data export) and restore_record (re-create an entry by calling the matching zome function). When you add a new entry type, you add one match arm in each. The SDK handles the rest: it captures the user's source chain, runs your decoder per record, posts the canonical-shape payload to Vault. On reinstall, the SDK's restoreFromVault walks the backup and calls your restore_record once per entry. Vault provides the encryption, storage, the Your Data UI, and the CAL §4.2.1-compliant data export with the user's cryptographic keys included.
Auto-backups run on every write (debounced 30s) plus a 30-minute heartbeat retry. They work even when the Vault is locked, as long as it has been unlocked at least once in the current session.
If your app stores encrypted entries on the public DHT, decrypt them when you build the human_readable view so the user's export remains readable — the Vault encrypts the backup at rest, so no double-encryption is needed.
Encrypted Private Data
Your app can store private data on the public DHT using client-side encryption. Entries are encrypted with lair's xsalsa20poly1305 crypto_box (256-bit) before being committed. Peers replicate the ciphertext for backup resilience, but only the author can decrypt.
Use a generic "private" hint on all encrypted entries — don't leak metadata about what type of private data is stored.
See Encrypted Entries on Public DHT for the full pattern, and ProofPoll for a working implementation.
Next Steps
- Agent Linking — Detailed attestation mechanics and API reference
- Encrypted Entries — Private data on public DHT
- @flowsta/holochain SDK — Backup API reference
- IPC Endpoints — Raw IPC API reference