React Native focused SDK for Zill/Nocturne. It re-exports the shared protocol logic from `@zill-protocol/client` and adds React Native helpers for sync, storage, and proving.
npm install @zill-protocol/rn-sdkReact Native focused SDK for Zill/Nocturne. It re-exports the shared protocol
logic from @zill-protocol/client and adds React Native helpers for sync,
storage, and proving.
This package is internal-only for now and is opinionated toward the Zill stack.
/v0/sync and maintains local note state.createHttpSyncAdapter polls /v0/sync and yields insertions + nullifiers.NocturneDB.prepareOperation builds a pre-sign operation from local notes.signOperation and proveOperation generate a submittable operation.``ts
import { createReactNativeClientFromConfig } from "@zill-protocol/rn-sdk";
const { client } = createReactNativeClientFromConfig({
namespace: walletId,
syncBaseUrl: "https://insertion-writer.up.railway.app",
bundlerBaseUrl: "https://bundler.up.railway.app",
viewer,
provider,
config: "citrea-testnet",
tokenConverter,
syncOptions: {
expectedChainId: "5115",
expectedHandlerAddress: "0x...",
},
});
`
`ts`
await client.sync();
const latestIndex = await client.getLatestSyncedMerkleIndex();
createHttpSyncAdapter ships with a built-in MMKV cursor store for nullifiernullifierCursorStore
pagination. If you omit , it persists the cursor under (default namespace is default).
Retry/backoff is supported via the retry option on the sync adapter. Chain andexpectedChainId
handler validation can be enforced via andexpectedHandlerAddress.
(required): sync API base URL.
- fetch: custom fetch implementation.
- insertionLimit / nullifierLimit: page sizes.
- pollIntervalMs: sleep interval when idle.
- namespace: used by the built-in cursor store.
- nullifierCursorKey: override the cursor key name.
- nullifierCursorStore: custom store implementation.
- expectedChainId / expectedHandlerAddress: fail fast on misconfigured URLs.
- retry: retry/backoff settings for transient failures.$3
`ts
{
maxRetries: 3,
baseDelayMs: 500,
maxDelayMs: 5000,
jitterRatio: 0.2,
retryOnStatuses: [429, 502, 503, 504],
timeoutMs: 10000,
}
`$3
`ts
const syncAdapter = createHttpSyncAdapter({
baseUrl: "...",
nullifierCursorStore: {
get: async () => Number(await storage.getItem("nfCursor") ?? 0),
set: async (cursor) => storage.setItem("nfCursor", cursor.toString()),
},
});
`Operation flow (prepare -> sign -> prove)
`ts
import { createHttpBundlerClient } from "@zill-protocol/rn-sdk";
import { signOperation, proveOperation } from "@zill-protocol/client";const preSignOp = await client.prepareOperation(opRequest, 1.1);
const signedOp = signOperation(nocturneSigner, preSignOp);
const provenOp = await proveOperation(joinSplitProver, signedOp);
const bundler = createHttpBundlerClient("https://bundler.up.railway.app");
const { id } = await bundler.submitOperation(provenOp);
const status = await bundler.getOperationStatus(id);
`Notes:
-
joinSplitProver is typically ReactNativeJoinSplitProver.
- You can submit via createHttpBundlerClient or your own network layer.Submitting operations
Bundler accepts a JSON payload of the form
{ operation } at POST /relay and
returns { id }, where id is the operation digest string. The SDK provides a
simple client wrapper:`ts
import { createHttpBundlerClient } from "@zill-protocol/rn-sdk";const bundler = createHttpBundlerClient("https://bundler.up.railway.app");
const { id } = await bundler.submitOperation(provenOp);
// Track status with the digest returned by the bundler.
const status = await bundler.getOperationStatus(id);
`Practical guidance:
- Keep the returned
id to reconcile status updates.
- A 400 response indicates validation failure (nullifier conflict, revert, gas).
- A 500 response indicates bundler failure; retry with backoff.Bundler client
createHttpBundlerClient wraps the bundler HTTP API:
- submitOperation(op) → POST /relay
- getOperationStatus(id) → GET /operations/:id
- checkNullifier(nf) → GET /nullifiers/:nullifierActivity and tx status
`ts
import { createHttpActivityClient, createHttpTxStatusClient } from "@zill-protocol/rn-sdk";const activityClient = createHttpActivityClient("https://insertion-writer.up.railway.app");
const page = await activityClient.fetchActivity({ limit: 100 });
const txClient = createHttpTxStatusClient("https://bundler.up.railway.app");
const status = await txClient.getOperationStatus(opDigest);
`Storage adapter
The SDK ships an MMKV-backed
KVStore adapter and a helper that wires both the
DB and the sparse Merkle prover.`ts
import { createReactNativeStorage } from "@zill-protocol/rn-sdk";const { db, merkleProver } = createReactNativeStorage({
namespace: walletId,
});
`createReactNativeStorage accepts optional overrides for MMKV ids, encryption
keys, or custom MMKV instances. You can also instantiate MMKVStore directly
via createMMKVStore.JoinSplit artifact manager
JoinSplit proving requires three artifacts:
- proving key (
.zkey)
- witness calculator graph (.wcd)
- verification key (base64)The SDK ships
JoinSplitArtifactManager to download, verify, and cache these
artifacts in the app sandbox. You bring a storage adapter (e.g. RNFS) and the
R2/CDN manifest (version + SHA256 hashes).`ts
import RNFS from "react-native-fs";
import {
JoinSplitArtifactManager,
ReactNativeJoinSplitProver,
} from "@zill-protocol/rn-sdk";const storage = {
artifactDir:
${RNFS.DocumentDirectoryPath}/nocturne-artifacts,
ensureDir: () => RNFS.mkdir(${RNFS.DocumentDirectoryPath}/nocturne-artifacts),
exists: (path: string) => RNFS.exists(path),
readText: async (path: string) =>
(await RNFS.exists(path)) ? RNFS.readFile(path, "utf8") : null,
writeText: (path: string, contents: string) =>
RNFS.writeFile(path, contents, "utf8"),
readFileBase64: (path: string) => RNFS.readFile(path, "base64"),
writeFileBase64: (path: string, base64: string) =>
RNFS.writeFile(path, base64, "base64"),
};const artifactManager = new JoinSplitArtifactManager(storage, {
baseUrl: "https://r2.example.com/nocturne/joinsplit/",
manifest: {
version: "v1",
zkey: {
path: "joinsplit.zkey",
sha256: "0x...",
},
wcd: {
path: "joinsplit.wcd",
sha256: "0x...",
},
verificationKeyBase64: "base64-vkey",
},
// Optional: add signed headers or tokens for private buckets.
// getHeaders: async () => ({ Authorization:
Bearer ${token} }),
});const proverConfig = await artifactManager.ensureArtifacts();
const prover = new ReactNativeJoinSplitProver(proverConfig);
`If you want the SDK to fetch the manifest directly:
`ts
import {
createReactNativeJoinSplitProverFromManifestUrl,
} from "@zill-protocol/rn-sdk";const { prover } = await createReactNativeJoinSplitProverFromManifestUrl({
storage,
manifestUrl: "https://bucket.example.com/joinsplit/joinsplit-artifacts.json",
});
`React Native prover
ReactNativeJoinSplitProver is backed by:
- @iden3/react-native-circom-witnesscalc
- @iden3/react-native-rapidsnark`