Skip to content

Agent Linking ​

Link your Holochain app's agent key with the user's Flowsta Vault identity.

Flowsta Vault acts as a local identity provider for Holochain apps. When a user approves a link request, the Vault signs a cryptographic attestation that is committed to your app's DHT as an IsSamePersonEntry. Anyone on your DHT can verify the link using Ed25519 cryptography - no shared DNA, no API dependency, no one in between.

How It Works ​

Agent linking flow between Your App, Flowsta Vault, and Your DHT

The 78-Byte Payload ​

Vault constructs the signing payload itself (apps cannot influence what gets signed):

BytesContent
0-38First agent public key (39 bytes: 3-byte prefix + 32-byte key + 4-byte checksum)
39-77Second agent public key (39 bytes, same format)

The two 39-byte Holochain AgentPubKeys are sorted lexicographically to produce a canonical 78-byte payload, and both agents sign the raw 78 bytes (no MessagePack encoding). The dual-signature design means:

  • The app agent attests "I am the same person as this Vault agent"
  • The Vault agent attests "I am the same person as this app agent"
  • Anyone can verify both signatures using the public keys in the payload

Because the attestation is a public claim that two keys are one person, one app agent links to one Flowsta identity. A second live link from the same app agent to a different identity would claim that two people are one; the zome refuses it, and so does the Vault. See Identity switching.

Prerequisites ​

  1. Register your app at dev.flowsta.com to get a client_id
  2. Add the flowsta-agent-linking zomes to your DNA (see Integration Guide)
  3. Install the SDK: npm install @flowsta/holochain

Quick Start ​

typescript
import { linkFlowstaIdentity, getFlowstaIdentity } from '@flowsta/holochain';
import { decodeHashFromBase64 } from '@holochain/client';

// Link identity
const result = await linkFlowstaIdentity({
  appName: 'ChessChain',
  clientId: 'flowsta_app_abc123...', // from dev.flowsta.com
  localAgentPubKey: myAgentKey,      // uhCAk... format
});

// The Vault's signature arrives base64-encoded; the zome wants the raw 64 bytes.
const signatureBytes = (b64: string) =>
  Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));

// Commit to your DHT
await appWebsocket.callZome({
  role_name: 'chess',
  zome_name: 'agent_linking',
  fn_name: 'create_external_link',
  payload: {
    external_agent: decodeHashFromBase64(result.payload.vaultAgentPubKey),
    external_signature: signatureBytes(result.payload.vaultSignature),
  },
});

// Query linked identities
const linked = await getFlowstaIdentity({
  appWebsocket,
  roleName: 'chess',
  agentPubKey: myAgentKey,
});

Integration Guide ​

1. Add zomes to your DNA ​

Add the flowsta-agent-linking crates (MIT) to your DNA's integrity and coordinator zomes, pinned to a release tag:

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

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

Tag v0.3.0 (integrity 0.2.0, coordinator 0.3.0) targets Holochain 0.6. The crates pin exact HDK versions, so your DNA's hdi and hdk must be on the same line - hdi = "=0.7.0" and hdk = "=0.6.0" for this tag. Build with RUSTFLAGS='--cfg getrandom_backend="custom"' cargo build --release --target wasm32-unknown-unknown.

Reference them in your dna.yaml:

yaml
integrity:
  zomes:
    - name: agent_linking_integrity
      bundled: ../../target/wasm32-unknown-unknown/release/flowsta_agent_linking_integrity.wasm
coordinator:
  zomes:
    - name: agent_linking
      bundled: ../../target/wasm32-unknown-unknown/release/flowsta_agent_linking_coordinator.wasm
      dependencies:
        - name: agent_linking_integrity

2. Install SDK ​

bash
npm install @flowsta/holochain

3. Register your app ​

Go to dev.flowsta.com and create an app to get a client_id.

API Reference ​

SDK Functions ​

FunctionDescription
linkFlowstaIdentity(options)Request identity link from Vault. Also binds the app to that identity (v3)
getFlowstaIdentity(options)Query linked agents on your DHT
getVaultStatus(ipcUrl?)Check if Vault is running/unlocked; did (v3.6.0), activeIdentity, identityEpoch, instanceId (v3.5.0)
revokeFlowstaIdentity(options)Notify Vault of revocation (removes the app from the Vault's connected list; the DHT entry is revoked with revoke_link)
getFlowstaLinkStatus(options)Check link status - three-state result (linked/unlinked/offline). Recommended. (v2.3.0)
checkFlowstaLinkStatus(options)Check if Vault still considers agent linked. Deprecated since v2.3.0; use getFlowstaLinkStatus.
getVaultIdentity(ipcUrl?)The unlocked agent key or null, one status read (v3.4.0)
onIdentityChanged(cb, opts?)Poll for an identity switch in the Vault; compares the identity epoch on Vault 1.5.0+ (v3.0.0, epoch v3.5.0)
reconnectIdentity(options)After a switch: rebind silently when the identity now in the Vault already links this app, else approval_needed (v3.4.0)
partitionKeyFor(agentPubKey)Folder-safe key to keep one identity's data apart from another's (v3.3.0)
resolveVaultUrl(ipcUrl?)Probe ports 27777-27779 in parallel and pick the best Vault (v3.0.0, parallel since v3.4.0)

The full list, with the backup and signing functions, is on the SDK page.

Zome Functions ​

These are the Holochain zome calls available after adding the flowsta-agent-linking zomes to your DNA:

FunctionInputOutputDescription
create_external_linkExternalLinkInputActionHashCommit attestation to DHT. Refuses when the calling agent already holds a live link to a different external agent
get_linked_agentsAgentPubKeyVec<AgentPubKey>Get all linked agents (non-deleted)
are_agents_linkedAgentPairboolCheck if two agents are linked
revoke_linkActionHashActionHashRevoke a link. Called by the app-side agent on your DHT; the integrity zome rejects a delete by anyone but the two agents named in the entry

ExternalLinkInput ​

typescript
{
  external_agent: Uint8Array,     // 39-byte Holochain AgentPubKey
  external_signature: Uint8Array, // 64-byte Ed25519 signature
}

create_external_link first refuses if your agent already holds a live link to a different external agent (linking the same one again is allowed - reinstall, restore; revoke_link the old entry to move an agent on purpose). It then verifies the external agent's signature over the 78-byte payload, adds a local signature from your app's agent key, commits the IsSamePersonEntry with both signatures, and creates lookup links for querying.

What the integrity zome enforces on every peer (a modified coordinator cannot get around it): an entry carries two different keys in canonical order, its author is one of them, and both raw Ed25519 signatures verify over the sorted 78-byte pair; only one of the two agents can delete (revoke) it; a lookup link must start from one of the two agents and be written by one of them. The "one agent, one external identity" refusal is a coordinator rule, since it has to read the DHT.

Error Handling ​

ErrorCauseSuggested UX
VaultNotFoundErrorVault not running or not installed"Install or start Flowsta Vault"
VaultBlockedError (v3.1.0)The browser refused the loopback request (Chrome 142+ Local Network Access permission); the Vault may be running"Allow local network access for this site in the browser"
VaultLockedErrorVault is locked (password required)"Please unlock your Flowsta Vault"
UserDeniedErrorUser rejected the approval dialog"Identity linking canceled"
InvalidClientIdErrorclient_id not registered, disabled, or invalidDeveloper error - check registration
MissingClientIdErrorNo client_id providedDeveloper error
ApiUnreachableErrorCan't reach Flowsta API to verify app (the first link needs internet)"Check internet connection"
IdentityMismatchError (v3.0.0)Thrown by later calls (backups, signDocument, authenticateWithVault) when the Vault holds a different identity than the one this app linked withStop. "Your Vault is signed in as someone else" - offer switch back / open as / disconnect, never re-link
FlowstaHolochainError with code: 'agent_linked_elsewhere'Vault 1.5.0+ answered 409: another identity in this Vault already links this app agentShow error.description (it names the identity); the person opens the app as that identity or disconnects it there
FlowstaHolochainError with code: 'timeout'The user did not answer the Vault dialog within 60 seconds (408)"No answer from the Vault - try again"

Data Backups ​

Once linked, your app can back up user data to Flowsta Vault's encrypted local storage. Users can view, export, or delete their backups at any time from the Vault UI.

If your app is licensed under the Cryptographic Autonomy License (CAL), §4.2.1 requires that users can get a copy of their own data and the keys needed to use it. Flowsta Vault handles the key export - your app just needs to back up the user's own data (not the entire DHT).

See the Backup functions in the SDK reference for implementation details and examples.

Security ​

  • User-custodied keys: Private keys never leave the user's device (Flowsta Vault)
  • Purpose-specific signatures: Vault computes the signing payload itself, preventing apps from tricking users into signing arbitrary data
  • User approval required: Every link request shows an approval dialog in Vault
  • Verifiable on-chain: Anyone on your DHT can verify the attestation using Ed25519 public key cryptography
  • One app agent, one identity: A second attestation from the same agent to another identity is a public claim that two people are one - the zome and the Vault both refuse it
  • Revocable: The app-side agent can revoke a link on your DHT at any time (revoke_link); revokeFlowstaIdentity tells the Vault so the app leaves the user's connected list
  • No shared infrastructure: Verification requires no API calls or shared DNAs - no one in between

Next Steps ​

Documentation licensed under CC BY-SA 4.0.