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
| Export | Type | Purpose |
|---|---|---|
PulseSDK | class | High-level entry point. Construct once with config, call verify() per session. |
PulseSession | class | Lower-level session for fine-grained capture control. |
Constants
| Export | Value | Purpose |
|---|---|---|
PROGRAM_IDS | { entrosAnchor, entrosVerifier, entrosRegistry } | Devnet program IDs |
MIN_AUDIO_SAMPLES | 16000 | Minimum audio samples per capture |
MIN_MOTION_SAMPLES | 10 | Minimum motion samples |
MIN_TOUCH_SAMPLES | 10 | Minimum touch samples |
MIN_CAPTURE_MS | 2000 | Minimum capture duration |
MAX_CAPTURE_MS | 60000 | Maximum capture duration |
DEFAULT_CAPTURE_MS | 12000 | Default capture duration |
DEFAULT_THRESHOLD | 96 | Default Hamming distance threshold |
DEFAULT_MIN_DISTANCE | 3 | Default minimum distance for replay defense |
FINGERPRINT_BITS | 256 | Behavioral fingerprint width in bits |
SPEAKER_FEATURE_COUNT | 170 | Number of voice features extracted (44 legacy + 72 MFCC + 24 LPC + 16 formant trajectories + 9 voice quality + 5 pitch contour DCT) |
MOTION_FEATURE_COUNT | 81 | Number of motion features extracted (54 legacy + 27 v2 additions: FFT band energy, tremor peak, cross-axis covariance, magnitude autocorrelation) |
TOUCH_FEATURE_COUNT | 57 | Number 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
| Export | Signature | Purpose |
|---|---|---|
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
| Export | Signature | Purpose |
|---|---|---|
simhash | (features) => TemporalFingerprint | 256-bit SimHash of the feature vector |
hammingDistance | (fp1, fp2) => number | Bit-distance between two fingerprints |
generateTBH | (fingerprint) => Promise<TBH> | Temporal Behavioral Hash with Poseidon commitment |
generateSalt | () => bigint | Cryptographic salt for the commitment |
packBits | (fingerprint) => PackedFingerprint | Pack the 256-bit fingerprint for circuit input |
computeCommitment | (fingerprint, salt) => bigint | Poseidon commitment over BN254 |
bigintToBytes32 | (n) => Uint8Array | BigInt to 32-byte big-endian array |
Proof generation
| Export | Signature | Purpose |
|---|---|---|
prepareCircuitInput | (tbhNew, tbhPrev, threshold?, minDistance?) => CircuitInput | Format the inputs for the Hamming circuit |
generateProof | (input, wasmUrl, zkeyUrl) => Promise<ProofResult> | Run Groth16 proving |
serializeProof | (proof, publicSignals) => SolanaProof | Serialize to the verifier program's expected layout |
toBigEndian32 | (decStr) => Uint8Array | Decimal-string to big-endian bytes |
Submission
| Export | Signature | Purpose |
|---|---|---|
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
| Export | Signature | Purpose |
|---|---|---|
generatePhrase | (wordCount?) => string | Random phrase prompt for voice capture |
generatePhraseSequence | (count) => string[] | Multiple phrase prompts |
randomLissajousParams | () => LissajousParams | Lissajous 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
| Export | Signature | Purpose |
|---|---|---|
encodeAudioAsBase64 | (samples) => string | Base64-encode audio for transport (relayer mode) |
Attestation and on-chain reads
| Export | Signature | Purpose |
|---|---|---|
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".
| Reason | Disposition | Source |
|---|---|---|
variance_floor, entropy_bounds, temporal_coupling_low, phrase_content_mismatch, captcha_required | retry | validator |
rate_limited, ip_rate_limited, cross_wallet_cooldown | wait | executor, with retryAfterSec |
payload_too_large | fatal | executor |
validation_unavailable, validation_timeout | retry | SDK, 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
- Programs reference—what the SDK talks to on-chain
- Costs—fee and compute-unit numbers
- Changelog—version history