Skip to content

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

  1. A Holochain application with its own DNA
  2. A registered app at dev.flowsta.com (for client_id)
  3. 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:

toml
# integrity/Cargo.toml
[dependencies]
flowsta-agent-linking-integrity = { git = "https://github.com/WeAreFlowsta/flowsta-agent-linking" }
toml
# coordinator/Cargo.toml
[dependencies]
flowsta-agent-linking-coordinator = { git = "https://github.com/WeAreFlowsta/flowsta-agent-linking" }

Reference them in your dna.yaml:

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_integrity

Step 2: Install the SDK

bash
npm install @flowsta/holochain

Step 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.

ScopeWhat you receiveTypical use
openidBasic identity (auto-included)Always present — not shown to the user
didUser's decentralized identifierUnique identity across your app
public_keyVault's Holochain agent public keyAgent linking ceremony
holochainHolochain identity accessRequired for agent linking
display_nameUser's display nameProfile UI
usernameUser's @usernameProfile UI
profile_pictureAvatar URLProfile 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

typescript
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 payload

Step 5: Commit to Your DHT

typescript
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:

  1. Verifies the Vault's Ed25519 signature
  2. Creates a local signature from your app's agent key
  3. Commits an IsSamePersonEntry with both signatures
  4. Creates lookup links for querying

Step 6: Query Linked Agents

typescript
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:

typescript
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:

typescript
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

If your app stores user data on Holochain, integrate auto-backups to store data securely in users' Vaults. Backups work while the Vault is locked and create timestamped snapshots (up to 10 per app).

Include both public and private data in your backups. If your app uses encrypted entries, decrypt them before including in the backup — the Vault encrypts the backup at rest.

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

Documentation licensed under CC BY-SA 4.0.