BONP-1 · Draft · v1.0.0

Biometric Oracle
Network Protocol

A standard for wrapping biometric device data as cryptographically attested settlement oracles in prediction markets and commitment contracts.

TypeScript SDK Ed25519 signatures Whoop · Oura · Apple Health Replay protection Dispute resolution

Why BONP?

Prediction markets that settle on biometric data all solve the same problem independently: fetch from a device API, prove the data is real, handle disputes. BONP extracts that into a standard. Every adapter speaks the same envelope format. Every verifier runs the same checks.

Standard envelope

One typed format across all providers. Whoop, Oura, and Apple Health all produce the same BiometricEnvelope.

Replay protection

Every envelope carries a 32-byte nonce and a signed fetch timestamp. Stale or replayed data is rejected at verification.

Dispute pathway

Counter-evidence envelopes, heuristic adjudication, and optional on-chain arbitration — baked in from day one.

Quick Start

pnpm add @bonp/sdk

Settle a market with Whoop recovery

import { WhoopAdapter, AdapterRegistry, verifyEnvelope, generateStalenessProof } from "@bonp/sdk";

const registry = new AdapterRegistry();
registry.register(new WhoopAdapter());

const adapter = registry.get("whoop")!;

const envelope = await adapter.fetchMetric(
  userId,
  "recovery_score",
  { start: new Date("2026-04-19"), end: new Date("2026-04-20") },
  { access_token: whoopToken }
);

const { valid, errors } = await verifyEnvelope(envelope);
if (!valid) throw new Error(errors.map(e => e.code).join(", "));

const staleness = generateStalenessProof(envelope);
if (!staleness.is_fresh) throw new Error(`Stale: ${staleness.lag_ms}ms`);

console.log(envelope.claim.value); // e.g. 74 (recovery score)

BiometricEnvelope

The core BONP data structure. Every adapter produces one; every verifier consumes one.

{
  "version": "BONP-1.0",
  "claim": {
    "metric": "recovery_score",
    "value": 74,
    "unit": "percent",
    "timestamp": "2026-04-19T08:32:00.000Z",
    "window": {
      "start": "2026-04-19T00:00:00.000Z",
      "end":   "2026-04-20T00:00:00.000Z"
    },
    "device": {
      "provider": "whoop",
      "device_id": "a3f9c2...",   // SHA-256 hashed
      "model": "WHOOP 4.0"
    },
    "subject": {
      "id": "7b2e1d...",           // SHA-256 of user ID
      "data_hash": "c8f0a4..."    // SHA-256 of raw API response
    }
  },
  "attestation": {
    "provider_signature": "3a7c...",
    "provider_public_key": "d9e1...",
    "adapter_id": "bonp-whoop-v1",
    "adapter_version": "1.0.0",
    "fetch_timestamp": "2026-04-19T09:01:14.322Z",
    "nonce": "8f4a1b2c..."
  },
  "settlement": {
    "staleness_window_ms": 43200000,
    "confidence_score": 0.95,
    "dispute_window_ms": 86400000
  }
}

Settlement Lifecycle

sequenceDiagram
  participant M as Market Contract
  participant O as Oracle (Adapter)
  participant P as Device Provider API
  participant V as Verifier
  M->>O: fetchMetric(userId, metric, range)
  O->>P: GET /recovery?start=&end=
  P-->>O: { records: [...] }
  O->>O: sign(canonicalize(claim), adapter_key)
  O-->>M: BiometricEnvelope
  M->>V: verifyEnvelope(envelope)
  V->>V: check version + fields
  V->>V: check nonce registry
  V->>V: check staleness_window_ms
  V->>V: check confidence_score >= 0.5
  V-->>M: { valid: true, errors: [] }
  M->>M: generateStalenessProof(envelope)
  Note over M: dispute_window_ms begins
  alt No dispute
    M->>M: Settlement final
  else Dispute opened
    M->>V: openDispute + submitEvidence
    V->>V: adjudicate(dispute, original)
    V-->>M: DisputeRecord resolved
  end
            
  1. 1
    Fetch

    Oracle calls adapter.fetchMetric(). The adapter hits the provider API, wraps the response in a signed BiometricEnvelope.

  2. 2
    Verify

    Settlement engine calls verifyEnvelope(). Checks version, nonce, staleness, confidence floor, and signature.

  3. 3
    Challenge period

    dispute_window_ms elapses. During this window, any party can open a dispute with counter-evidence.

  4. 4
    Resolve

    No dispute → settlement final. Disputed → heuristic adjudication or human arbiter resolves.

Error Codes

Code Meaning Recovery
BONP-001 Invalid envelope format Fix malformed fields
BONP-002 Signature verification failed Re-fetch with valid adapter keypair
BONP-003 Stale data outside window Re-fetch with current timestamp
BONP-004 Replay detected — nonce reuse Generate new nonce per envelope
BONP-005 Provider authentication failed Refresh OAuth token
BONP-006 Metric not supported by adapter Check getCapabilities().metrics
BONP-007 Dispute window expired No remedy; settlement is final
BONP-008 Confidence score too low Use higher-confidence adapter

Whoop Adapter

Hits the Whoop Developer API v1. Requires a Bearer access token from Whoop OAuth.

Metric Endpoint Field
recovery_score GET /v1/recovery score.recovery_score
hrv_rmssd GET /v1/recovery score.hrv_rmssd_milli
resting_heart_rate GET /v1/recovery score.resting_heart_rate
spo2 GET /v1/recovery score.spo2_percentage
sleep_performance GET /v1/sleep score.sleep_performance_percentage
sleep_hours GET /v1/sleep score.total_in_bed_time_milli ÷ 3.6M
strain GET /v1/cycle score.strain
active_calories GET /v1/cycle score.kilojoule × 0.239
import { WhoopAdapter } from "@bonp/sdk";

const adapter = new WhoopAdapter();
const envelope = await adapter.fetchMetric(
  userId, "recovery_score",
  { start: new Date(), end: new Date(Date.now() + 86400000) },
  { access_token: "whoop_oauth_token" }
);

Oura Adapter

Hits the Oura Ring API v2. Requires a Personal Access Token or OAuth Bearer token.

Metric Endpoint Field
readiness_score GET /v2/usercollection/daily_readiness data[0].score
sleep_hours GET /v2/usercollection/sleep total_sleep_duration ÷ 3600
sleep_performance GET /v2/usercollection/sleep efficiency
step_count GET /v2/usercollection/daily_activity data[0].steps
active_calories GET /v2/usercollection/daily_activity data[0].active_calories

Apple Health Adapter

Bridge adapter for HealthKit export XML or iOS push data. No live API — works offline.

import { AppleHealthAdapter } from "@bonp/sdk";

const adapter = new AppleHealthAdapter();
adapter.loadExport("/path/to/export.xml");

const envelope = await adapter.fetchMetric(
  userId, "step_count",
  { start: new Date("2026-04-19"), end: new Date("2026-04-20") }
);

To use with a live iOS push: pass the parsed HealthKitReading[] array directly to loadExport().

Dispute Flow

flowchart TD
  A[Settlement submitted] --> B{Within dispute window?}
  B -- No --> C[Settlement final]
  B -- Yes --> D[openDispute]
  D --> E[submitEvidence with counter-envelopes]
  E --> F{adjudicate}
  F -- No evidence --> G[resolved_for_claimant]
  F -- Different provider --> H[open · needs manual review]
  F -- Same provider, higher confidence --> I[resolved_for_challenger]
  F -- Same provider, lower confidence --> G
  H --> J[resolveDispute manual]
            
import { openDispute, submitEvidence, adjudicate, resolveDispute } from "@bonp/sdk";

const dispute = openDispute(originalEnvelope, "challenger-user-id");
const withEvidence = submitEvidence(dispute, [counterEnvelope]);

const result = adjudicate(withEvidence, originalEnvelope);
// "resolved_for_claimant" | "resolved_for_challenger" | "open"

const resolved = resolveDispute(withEvidence, result, "reason text");

Staleness Proofs

A StalenessProof documents why a specific envelope is fresh or stale. It is serializable — store it alongside the settlement for audit purposes.

import { generateStalenessProof, checkFreshness, freshestValidEnvelope } from "@bonp/sdk";

const proof = generateStalenessProof(envelope);
// {
//   envelope_id: "c8f0...",
//   claimed_timestamp: "2026-04-19T08:32:00Z",
//   fetch_timestamp:   "2026-04-19T09:01:14Z",
//   max_age_ms: 43200000,
//   is_fresh: true,
//   lag_ms: 1754000
// }

// When you have multiple envelopes, pick the freshest valid one:
const best = freshestValidEnvelope([env1, env2, env3]);

Security Considerations

Concern Mitigation
Adapter key compromise Store adapter keys in HSM/KMS. Rotate quarterly. Per-adapter key isolation means one compromise doesn't affect other providers.
OAuth token exposure Tokens must not be logged or persisted. Adapters hold tokens only for the duration of the fetch call.
Replay attacks 32-byte nonce per envelope, tracked in a registry with TTL ≥ dispute_window_ms. Use Redis in production.
Oracle collusion Use multiple independent oracle operators for high-value markets. data_hash enables anyone with the raw API response to verify the claim.
Subject re-identification Add a per-market salt: SHA-256(userId + marketId + staticSalt) for subject.id.