Entros_docs
Reference

SDK reference

Public API surface of @entros/pulse-sdk.

@entros/pulse-sdk is the client SDK. It captures behavioral signals on-device, extracts statistical features, hashes the result, generates a zero-knowledge proof, and submits to Solana. This page covers the exports most integrations use, grouped by purpose. The package's type declarations are the full surface.

The current published version is 3.16.0. Install with npm install @entros/pulse-sdk. See the changelog for the upgrade path from earlier versions.

Top-level

ExportTypePurpose
PulseSDKclassHigh-level entry point. Construct once with config, call verify() per session.
PulseSessionclassLower-level session for fine-grained capture control.

Constants

ExportValuePurpose
PROGRAM_IDS{ entrosAnchor, entrosVerifier, entrosRegistry }Devnet program IDs
MIN_AUDIO_SAMPLES16000Minimum audio samples per capture
MIN_MOTION_SAMPLES10Minimum motion samples
MIN_TOUCH_SAMPLES10Minimum touch samples
MIN_CAPTURE_MS2000Minimum capture duration
MAX_CAPTURE_MS60000Maximum capture duration
DEFAULT_CAPTURE_MS12000Default capture duration
DEFAULT_THRESHOLD96Default Hamming distance threshold
DEFAULT_MIN_DISTANCE3Default minimum distance for replay defense
FINGERPRINT_BITS256Behavioral fingerprint width in bits
SPEAKER_FEATURE_COUNT170Number of voice features extracted (44 legacy + 72 MFCC + 24 LPC + 16 formant trajectories + 9 voice quality + 5 pitch contour DCT)
MOTION_FEATURE_COUNT81Number of motion features extracted (54 legacy + 27 v2 additions: FFT band energy, tremor peak, cross-axis covariance, magnitude autocorrelation)
TOUCH_FEATURE_COUNT57Number of touch features extracted (36 legacy + 21 v2 additions: pressure derivatives, contact aspect ratio, curvature, gap distribution, path efficiency)

The fused vector emitted by the SDK to the validator has dimension SPEAKER_FEATURE_COUNT + MOTION_FEATURE_COUNT + TOUCH_FEATURE_COUNT = 308.

Capture and feature extraction

ExportSignaturePurpose
extractSpeakerFeatures(audio) => Promise<number[]>170 voice features: F0 statistics, jitter, shimmer, HNR, MFCCs and delta-MFCCs, LPC coefficients, formant trajectories (F1/F2/F3), voice quality (CPP, spectral tilt, H1-H2, sub-bands), pitch contour DCT, LTAS
extractSpeakerFeaturesDetailed(audio) => Promise<{ features, f0Contour }>Voice features plus pitch contour
extractMotionFeatures(samples) => number[]81 motion features from IMU samples (jerk/jounce, FFT band energy, tremor peak, cross-axis covariance, magnitude autocorrelation)
extractTouchFeatures(samples) => number[]57 touch features (velocity, acceleration, pressure derivatives, contact aspect ratio, curvature, gap distribution, path efficiency, per-stroke length stats)
extractMouseDynamics(samples) => number[]Pointer-based touch dynamics
extractAccelerationMagnitude(samples, frameCount) => number[]Frame-aligned acceleration magnitudes
fuseFeatures(audio, motion, touch) => number[]Concatenate the three feature streams
fuseRawFeatures(audio, motion, touch) => number[]Same, without pre-aggregation

Hashing

ExportSignaturePurpose
simhash(features) => TemporalFingerprint256-bit SimHash of the feature vector
hammingDistance(fp1, fp2) => numberBit-distance between two fingerprints
generateTBH(fingerprint) => Promise<TBH>Temporal Behavioral Hash with Poseidon commitment
generateSalt() => bigintCryptographic salt for the commitment
packBits(fingerprint) => PackedFingerprintPack the 256-bit fingerprint for circuit input
computeCommitment(fingerprint, salt) => bigintPoseidon commitment over BN254
bigintToBytes32(n) => Uint8ArrayBigInt to 32-byte big-endian array

Proof generation

ExportSignaturePurpose
prepareCircuitInput(tbhNew, tbhPrev, threshold?, minDistance?) => CircuitInputFormat the inputs for the Hamming circuit
generateProof(input, wasmUrl, zkeyUrl) => Promise<ProofResult>Run Groth16 proving
serializeProof(proof, publicSignals) => SolanaProofSerialize to the verifier program's expected layout
toBigEndian32(decStr) => Uint8ArrayDecimal-string to big-endian bytes

Submission

ExportSignaturePurpose
submitViaWallet(proof, commitment, options) => Promise<SubmissionResult>Submit through a connected wallet
submitResetViaWallet(commitment, options) => Promise<SubmissionResult>Submit a recovery reset
submitViaRelayer(proof, commitment, options) => Promise<SubmissionResult>Submit through the executor's relayer. Used by the no-wallet capture preview on entros.io/verify; production integrations use submitViaWallet.

Challenge generation

ExportSignaturePurpose
generatePhrase(wordCount?) => stringRandom phrase prompt for voice capture
generatePhraseSequence(count) => string[]Multiple phrase prompts
randomLissajousParams() => LissajousParamsLissajous figure parameters for touch capture
generateLissajousPoints(params) => Point2D[]Render the curve
generateLissajousSequence(count) => Point2D[][]Multiple curves
fetchChallenge(executorUrl, walletAddress, apiKey?) => Promise<ChallengeResponse>Fetch a challenge from the executor

Audio encoding

ExportSignaturePurpose
encodeAudioAsBase64(samples) => stringBase64-encode audio for transport (relayer mode)

Attestation and on-chain reads

ExportSignaturePurpose
verifyEntrosAttestation(walletAddress, connection) => Promise<EntrosAttestation | null>Read a wallet's SAS attestation
attestAgentOperator(agentAsset: string, options: { wallet, connection, cluster? }) => Promise<{ success: boolean; signature?: string; error?: string }>Write the human-operator metadata for a registered agent
getAgentHumanOperator(agentAsset: string, connection?, cluster?) => Promise<AgentHumanOperator | null>Read the human-operator metadata. AgentHumanOperator fields: { anchorPda, trustScore, verifiedAt, wallet }
fetchIdentityState(wallet, connection) => Promise<IdentityState | null>Read the full Anchor PDA
storeVerificationData(data, walletAddress?) => Promise<void>Persist verification state to local storage. Pass the wallet address to keep multi-wallet state separate.
loadVerificationData(walletAddress?) => Promise<StoredVerificationData | null>Load persisted state for a wallet

Configuration

PulseSDK is constructed with a PulseConfig:

type PulseConfig = {
  cluster: "devnet" | "mainnet-beta" | "localnet";
  rpcEndpoint?: string;
  relayerUrl?: string;
  relayerApiKey?: string;
  zkeyUrl?: string;
  wasmUrl?: string;
  threshold?: number;
  debug?: boolean;
  // Crypto-unavailable browsers (iOS Safari private mode, Brave shields,
  // Firefox Total Cookie Protection) hit this callback. Return true to allow
  // plaintext localStorage; false to keep storage in-memory only (data lost
  // on reload). Without the callback, the SDK defaults to in-memory only.
  onPrivacyFallback?: () => Promise<boolean>;
};

Verification result reasons

VerificationResult.reason (when success: false) carries a label when one is safe to reveal. Classify it with reasonDisposition rather than matching strings:

import { reasonDisposition } from "@entros/pulse-sdk";

switch (reasonDisposition(result.reason)) {
  case "retry":  // offer another attempt now
  case "wait":   // blocked until result.retryAfterSec elapses
  case "fatal":  // retrying changes nothing
}

Three dispositions rather than a retryable boolean, because a rate limit is not "not retryable", it is "not retryable yet".

ReasonDispositionSource
variance_floor, entropy_bounds, temporal_coupling_low, phrase_content_mismatch, captcha_requiredretryvalidator
rate_limited, ip_rate_limited, cross_wallet_cooldownwaitexecutor, with retryAfterSec
payload_too_largefatalexecutor
validation_unavailable, validation_timeoutretrySDK, before any server saw the capture

reasonDisposition returns fatal for a reason it does not recognise, so a newer server cannot hand an older client a retry it does not understand.

reason is absent on most rejections and its absence never means success. The validator withholds a label on attack-signal rejections so the detection layers stay opaque to probing, and there is no label on on-chain submission failures or data-quality rejections. Read success first, always.

isClientOriginReason(result.reason) is true when the SDK gave up before a server rendered a verdict. Hosts metering attempts should not charge the user for those.

Verification phases

VerificationResult.failedAt names the stage that failed: capture, extraction, validation, baseline, proving, signing, submission, confirmation. Route on it rather than on the wording of error.

import { phaseChargesAttempt, phaseSpend } from "@entros/pulse-sdk";

phaseChargesAttempt(result.failedAt); // only `validation` judged the capture
phaseSpend(result.failedAt);          // "none" | "possible" | "certain"

opaque: true means the cause must not be described. It is a second axis over failedAt, never derivable from it, because three failures in three phases have to render identically or the difference tells an attacker which layer caught them. Match a specific user-actionable condition first if you have one, then fall back to your generic rejection copy rather than to error.

baselineRecovery says why an on-chain baseline could not be restored, when failedAt is baseline. no-encrypted-baseline means the anchor predates on-chain baseline storage and one reset makes it portable.

portableBaseline: false on a successful result means the verification landed but wrote no portable copy, because the wallet could not sign the key derivation. Say so. Left silent, the user learns it on their next device.

Timeouts

The SDK bounds the wallet signature at SIGNATURE_TIMEOUT_MS and confirmation at CONFIRMATION_TIMEOUT_MS. If you keep your own backstop timer over complete(), set it above MAX_VERIFICATION_MS, which is derived from those clocks plus the validate deadline. A backstop below it pre-empts the SDK's per-step clocks and reports the failure against whichever step its own message happens to name.

import { MAX_VERIFICATION_MS } from "@entros/pulse-sdk";

Neither timeout cancels the work it bounds, because nothing in a wallet adapter or in web3.js can be cancelled. A prompt approved after the clock expires still broadcasts, which is why a timeout is reported as submission rather than as a failure to send.

Peer dependencies

The SDK declares the following as optional peers:

  • @coral-xyz/anchor ^0.32.1
  • @solana/wallet-adapter-base ^0.9.0
  • @solana/web3.js ^1.98.0
  • @solana/spl-token ^0.4.0

Install whichever you need for your integration path. For pure read flows (verifyEntrosAttestation, fetchIdentityState), only @solana/web3.js is required.

Where to look next

On this page