React Native Pro SDK

Complete SDK for blockchain-verified content registration with StarkNet wallet management, on-device zero-knowledge proofs, offline queue, and enterprise telemetry.

📦 Current Version: 5.0.0

Full on-device processing — images never leave the device. Native Poseidon hash, watermark, MobileCLIP embedding, and .facti build via blockfact-core. On-device ZKP via Mopro/Arkworks (<1s). Optional IPFS pinning via uploadToIPFS(). Backend is a thin registration relay (~3.5s).

Installation

npm install @blockfact/react-native-facti-pro blockfact-core mopro-ffi

Native Setup

Run the setup script to install native ZKP libraries, circuit files, and the MobileCLIP embedding model (82MB, downloaded from CDN):

npx @blockfact/setup

See the Native Module Setup Guide for platform-specific details.

Peer Dependencies

npm install react-native-get-random-values
npm install react-native-keychain
npm install react-native-encrypted-storage
npm install react-native-blob-util
npm install @react-native-community/netinfo
npm install react-native-device-info

iOS Setup

cd ios && pod install && cd ..

Quick Start

1. Wrap Your App

import { BlockFactProvider } from '@blockfact/react-native-facti-pro';

export default function App() {
  return (
    <BlockFactProvider apiBase="https://api.blockfact.io">
      <YourApp />
    </BlockFactProvider>
  );
}

2. Create a Wallet

Two modes — standalone (no Web3Auth needed) or Web3Auth:

import { useBlockFact } from '@blockfact/react-native-facti-pro';

function WalletScreen() {
  const { wallet, createWallet, hasWallet } = useBlockFact();

  // Standalone — generates keypair, deploys on-chain
  const handleStandalone = () => createWallet();

  // Or with Web3Auth
  const handleWeb3Auth = () => createWallet({ idToken: web3authToken });

  if (!hasWallet) {
    return <Button title="Create Wallet" onPress={handleStandalone} />;
  }

  return <Text>Wallet: {wallet.address}</Text>;
}

3. Register Content

function CameraScreen() {
  const { registerContent } = useBlockFact();

  const handleCapture = async (photo) => {
    const { jobId, status } = await registerContent({
      filePath: photo.uri,
      filename: photo.fileName || 'photo.jpg',  // from camera response
      mime: photo.type || 'image/jpeg',          // defaults to image/jpeg
      latitude: position.coords.latitude,
      longitude: position.coords.longitude,
    });

    // Job is queued — processes automatically
    // Survives app kills, resumes on relaunch
    // Auto-retries when network returns
    console.log('Queued:', jobId);
  };
}

📡 Offline Resilient

Jobs are persisted to encrypted storage. If the app is killed or goes offline, jobs resume automatically on relaunch or when network returns.

API Reference

BlockFactProvider

<BlockFactProvider
  apiBase="https://api.blockfact.io"  // Required
  modelUrl="https://cdn.blockfact.io/models/mobileclip-s1.onnx"  // Optional — custom model CDN
  onModelDownloadProgress={(progress) => {           // Optional — 0-100
    console.log('Model download:', progress + '%');
  }}
  telemetry={{                              // Optional — enterprise only
    enabled: true,
    apiKey: 'ent_live_abc123',
    tags: { organizationId: 'acme-corp', sourceId: 'tablet-001' }
  }}
>
  {children}
</BlockFactProvider>

🔒 Images Never Leave the Device

In v5, all image processing (hashing, watermarking, embedding, .facti build) happens on-device via native Rust modules. Only a few KB of metadata (hash + proof + embedding vector) are sent to the backend for blockchain registration.

useBlockFact()

const {
  // State
  wallet,           // { address, mode, userId, publicKey? }
  loading,          // boolean
  error,            // string | null
  blocked,          // boolean — true if SDK version is too old
  sdkVersion,       // string — e.g. "5.0.0"
  hasWallet,        // boolean
  queue,            // UploadQueue instance

  // Functions
  createWallet,     // (params?) => Promise<Wallet>
  registerContent,  // (params) => Promise<{ jobId, status }>
  deleteWallet,     // () => Promise<void>
  uploadToIPFS,     // (factiPath: string) => Promise<{ cid, url }>
  ensureEmbeddingModel, // () => Promise<void> — pre-downloads MobileCLIP model
} = useBlockFact();

createWallet(params?)

// Standalone mode — no Web3Auth dependency
const wallet = await createWallet();
// or with custom user ID
const wallet = await createWallet({ userId: 'user-123' });

// Web3Auth mode
const wallet = await createWallet({ idToken: web3authJWT });

// Returns:
{
  address: string,      // StarkNet address (0x...)
  mode: 'standalone' | 'web3auth',
  userId: string,
  publicKey?: string,   // Only in standalone mode
}

🔐 Security

Private keys are stored in iOS Keychain / Android Keystore with biometric authentication (BIOMETRY_ANY + WHEN_UNLOCKED_THIS_DEVICE_ONLY). Keys never leave the device.

registerContent(params)

const { jobId, status, factiLocalPath } = await registerContent({
  filePath: string,       // Required — local file path
  filename?: string,      // Default: 'photo.jpg'
  mime?: string,          // Default: 'image/jpeg'
  latitude?: number,      // GPS latitude
  longitude?: number,     // GPS longitude
  metadata?: object,      // Custom metadata
});

// Returns immediately — job processes in background
// {
//   jobId: 'abc123',
//   status: 'queued',
//   factiLocalPath: '/path/to/Documents/facti/abc123.facti'  // NEW in v5
// }

// To optionally pin to IPFS:
const { cid, url } = await uploadToIPFS(factiLocalPath);

Upload Queue

const { queue } = useBlockFact();

// Monitor jobs
queue.jobs          // All jobs
queue.pending       // Jobs not yet completed

// Subscribe to changes
const unsub = queue.subscribe(() => {
  console.log('Queue updated:', queue.jobs);
});

// Remove a job
await queue.removeJob(jobId);

// Job statuses:
// 'queued' → 'processing' → 'completed'
//                          → 'failed'
//                          → 'waiting_network' (auto-resumes)

Processing Pipeline

  1. 1. On-Device Poseidon Hash + Watermark (<1s)
    • • Computes Poseidon hash from wallet + GPS + timestamp (native Rust)
    • • Embeds hash as steganographic watermark into image pixels (native Rust)
    • • Image never leaves the device
  2. 2. Native ZKP Generation (<1s)
    • • Generates Groth16 proof natively via Mopro/Arkworks (Rust)
    • • Proof cryptographically attests device knows the private inputs
    • • No server round-trip — proof generated entirely on-device
  3. 3. MobileCLIP Embedding (~80ms)
    • • Generates 512-dim image embedding on-device via MobileCLIP-S1 ONNX model
    • • Enables reverse image verification on blockfact.io/verify
    • • Model downloaded on first use, cached permanently
  4. 4. Blockchain Registration (~3.5s)
    • • Sends hash + proof + embedding (~few KB) to thin backend relay
    • • Backend registers on StarkNet with Garaga on-chain verification
    • • Returns tx_hash
  5. 5. Local .facti Build (instant)
    • • Builds .facti file on-device from watermarked image + metadata
    • • Saved to local storage — available immediately
    • • Optional: call uploadToIPFS(factiLocalPath) to pin to IPFS

🔐 Learn More

See the ZKP Architecture Guide for a deep dive into the Poseidon circuit, Groth16 proving, and the on-chain verification flow.

Enterprise Telemetry

Enterprise customers can enable telemetry for fleet monitoring, anomaly detection, and audit trails. Free users are completely unaffected — no API key means no telemetry.

<BlockFactProvider
  apiBase="https://api.blockfact.io"
  telemetry={{
    enabled: true,
    apiKey: 'ent_live_abc123',  // From enterprise dashboard
    tags: {
      organizationId: 'acme-corp',
      projectId: 'field-ops',
      sourceId: 'tablet-001',   // For fleet tracking
    }
  }}
>

Events tracked automatically:

  • source.sdk_initialized — SDK startup with device info
  • source.health_check — Every 5 minutes (queue depth, network status)
  • source.sdk_outdated — When newer version available
  • content.captured — When registerContent() is called
  • content.registered — Successful blockchain registration
  • content.registration_failed — Failed registration

Version Gate

The SDK checks the minimum required version on startup. If your version is too old, blocked will be true and all operations will throw with an upgrade message.

const { blocked, error, sdkVersion } = useBlockFact();

if (blocked) {
  return <Text>{error}</Text>;
  // "SDK v2.5.1 is blocked. Minimum required: v3.0.0. Please upgrade to v3.2.0."
}

Standalone Verification

import { verifyContent } from '@blockfact/react-native-facti-pro';

const result = await verifyContent('https://ipfs.io/ipfs/Qm.../file.facti');
// { valid: true, metadata: { ... } }

Error Handling

try {
  const { jobId } = await registerContent({...});
} catch (error) {
  if (error.message.includes('blocked')) {
    // SDK version too old — must upgrade
  } else if (error.message.includes('No wallet')) {
    await createWallet();
  } else if (error.message.includes('filePath')) {
    // Missing required param
  }
}

Security Features

  • On-Device Processing: Images never leave the device — hash, watermark, embedding, .facti all built locally
  • Native ZKP: Groth16 proofs generated on-device via Mopro/Arkworks in <1s
  • Biometric Protection: Face ID, Touch ID, Fingerprint for key access
  • Encrypted Storage: Queue and wallet data encrypted at rest
  • Device Attestation: iOS DeviceCheck / Android Play Integrity (when available)
  • Idempotency: Duplicate registrations prevented via content-derived keys
  • Version Gate: Server-controlled minimum SDK version enforcement
  • Blockchain Verification: Immutable StarkNet registration
  • IPFS Storage: Decentralized .facti file hosting via Pinata

Migration from v4.x

⚠️ Breaking Changes in v5.0.0

  • New peer dependency: blockfact-core — on-device Poseidon hash, watermark, .facti builder, MobileCLIP embedding
  • Return type changed: registerContent() now returns factiLocalPath instead of factiUrl — .facti files are built on-device, not uploaded to IPFS
  • New exports: uploadToIPFS(factiPath) for optional IPFS pinning, ensureEmbeddingModel() to pre-download MobileCLIP
  • Removed props: skipZkp and wsUrl on BlockFactProvider
  • New props: modelUrl and onModelDownloadProgress on BlockFactProvider
  • Setup CLI: npx @blockfact/setup now also downloads the MobileCLIP ONNX model (82MB)
  • Images never leave device: No S3 upload, no server-side watermarking — all processing is on-device

See the full v5 Migration Guide for step-by-step instructions.

Migration from v3.x

⚠️ Breaking Changes in v4.0.0

  • Native ZKP: Replaced JS snarkjs/circomlibjs with native mopro-ffi — requires native module linking
  • New dependency: mopro-ffi (optional peer dep — falls back to server-side ZKP if not installed)
  • Setup script: Run npx @blockfact/setup to install native libs + circuit files
  • Expo: Expo Go no longer supported — use dev client or npx expo prebuild
  • Removed: snarkjs and circomlibjs dependencies
  • No API changes: registerContent() and createWallet() work the same way

Migration from v2.x

⚠️ Breaking Changes in v3.0.0

  • BlockFactProvider now requires apiBase prop
  • registerContent() takes filePath instead of imageUri, returns { jobId, status }
  • createWallet() accepts optional { idToken } or { userId }
  • • Replaced async-storage with react-native-encrypted-storage
  • • Removed buffer polyfill requirement
  • • Added react-native-blob-util, @react-native-community/netinfo, react-native-device-info as dependencies

Support