npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@moon-x/react-native-sdk

v0.3.0

Published

MoonX React Native SDK — WebView-backed transport, react-native-passkeys, AsyncStorage. Mobile parity with @moon-x/react-sdk + @moon-x/core-sdk.

Readme

@moon-x/react-native-sdk

React Native SDK for MoonX — feature parity with @moon-x/react-sdk on mobile. WebView-backed transport, native passkeys, AsyncStorage.

Install

# bare RN
pnpm add @moon-x/react-native-sdk \
  react-native-webview react-native-passkeys \
  @react-native-async-storage/async-storage \
  react-native-get-random-values react-native-url-polyfill \
  buffer \
  viem bs58

# Expo
pnpm add @moon-x/react-native-sdk \
  react-native-webview react-native-passkeys \
  @react-native-async-storage/async-storage \
  react-native-get-random-values react-native-url-polyfill \
  buffer \
  expo-web-browser expo-linking \
  viem bs58

The SDK declares everything except @moon-x/core as peer dependencies. Privy follows the same pattern; their docs are the canonical install reference and apply 1:1 here:

  • https://docs.privy.io/basics/react-native/installation
  • https://docs.privy.io/basics/react-native/advanced/setup-passkeys

Polyfills (order matters)

The SDK touches several globals that RN doesn't ship in every config. Import the polyfills at the top of your app entry, before any other code, so they're set up before anything else loads:

// index.js / index.ts — must run first.

// crypto.getRandomValues — needed by passkey challenge generation,
// MPC keygen, and viem.
import "react-native-get-random-values";

// URL / URLSearchParams — needed by viem and the OAuth deep-link
// helpers.
import "react-native-url-polyfill/auto";

// Buffer — needed by bs58, ethers, viem's RLP path, and the SDK's
// Solana broadcast helper. RN does not provide a Buffer global; on
// Expo it's polyfilled implicitly but you should set it explicitly so
// bare-RN consumers and any later eject-from-Expo users don't break.
// Same pattern Privy documents.
import { Buffer } from "buffer";
(global as any).Buffer = (global as any).Buffer || Buffer;

import { registerRootComponent } from "expo";
import App from "./App";
registerRootComponent(App);

atob / btoa and TextEncoder are built into Hermes (RN's default JS engine since 0.71). The peerDep react-native: ">=0.78.0" covers this — but if you're targeting an older runtime or have explicitly opted into JSC, add text-encoding and base-64 polyfills here too.

Provider

import { MoonKeyProvider } from "@moon-x/react-native-sdk";

export default function Root() {
  return (
    <MoonKeyProvider
      publishableKey={process.env.EXPO_PUBLIC_MOONKEY_PUBLISHABLE_KEY!}
      config={{
        iframeUrl: "https://iframe.moonx-dev.com",
        oauthRedirectUri: "myapp://oauth-callback",
      }}
    >
      <App />
    </MoonKeyProvider>
  );
}

config.iframeUrl is required and explicit — the RN SDK never reads env vars to discover it. Use the same URL the web SDK points at. The iframe-app detects WebView mode at runtime via window.ReactNativeWebView and routes replies through the bridge automatically; no iframe-app changes needed per integration.

Security

Every sensitive op (sign / send / create / import / export / addPasskey / removePasskey) drives the same server-verified presence ceremony per call:

  1. Server-issued WebAuthn challenge.
  2. Fresh biometric assertion (stripping response.userHandle before anything crosses the bridge to MoonX — DEK hygiene).
  3. Server mints short-lived (30s) single-use JWTs scoped to the specific endpoint set this op needs.
  4. Each scoped JWT travels as X-MoonX-Presence on its matching gated endpoint and is burned (platform.app_presence_jti_used) on first use.

What this closes: captured userHandle + session JWT alone no longer unlocks DEK material — every gated endpoint also needs a fresh WebAuthn signature MoonX verifies against the credential's stored public key. See apps/platform/docs/notes/passkeys/presence-tokens.md in the backend repo for the full threat model.

The previously-configurable config.security.assertionCacheTtlMs and per-call requireFreshAssertion flag were removed entirely when presence-token gating shipped — they are no longer on the public TypeScript surface. Every op is always-fresh by construction.

Note on RN-side migration status: as of this writing the RN add-passkey (sdk-passkey-methods.ts) and export-key (use-export-key.ts) flows still use the older parent-only assertion path. The web SDK is fully migrated to the presence-token orchestrator; RN parity is on the to-do list.

Hooks

Authentication & user state

| Hook | What it does | |---|---| | useMoonKey() | The big one. { ready, isAuthenticated, user, start, logout, setAppearance, getSessionTokens, refreshUser, ... } + every SDK method on the same instance. | | useUser() | Just { user, refreshUser }. Re-subscribes to user changes. | | useLoginWithEmail({ onComplete?, onError? }) | Headless email-OTP. Returns { state, sendCode, loginWithCode, reset }. state is a discriminated union: idle / sending / awaiting-code / verifying / complete / error. | | useLoginWithOAuth() | Google + Apple flows via expo-web-browser. Returns { state, loginWithOAuth, reset }. | | useLogout() | { logout } — also clears AsyncStorage + WebView session. | | useAttachOAuth() / useDetachOAuth() | Link / unlink an OAuth provider on an existing user. |

Passkeys

| Hook | What it does | |---|---| | usePasskeyStatus() | { status, refresh }. status.passkeys lists the user's enrolled passkeys with provider labels. | | useRegisterPasskey() | First-time passkey enrollment via the native ceremony (iOS ASAuthorization, Android Credential Manager). | | useAddPasskey() | Add an additional passkey to an authenticated user. | | useRemovePasskey() | Remove a passkey by its credential ID. |

Wallets

| Hook | What it does | |---|---| | useWallets() | { wallets, loading } — both Ethereum and Solana, fetched once on mount. | | useCreateWallet() | Mint a new MPC wallet. Pass { walletType: "ethereum" \| "solana" }. | | useImportKey() | Bring-your-own-key flow. | | useExportKey() | Show the user their private key via the visible-WebView modal pattern (see below). Plaintext never leaves the WebView's DOM. |

Per-chain signing — /ethereum and /solana subpaths

Chain-aware hooks live under subpaths so they don't pull in the other chain's adapters if you only use one — same pattern as @moon-x/react-sdk:

import { useSignMessage, useSignTransaction, useSendTransaction } from "@moon-x/react-native-sdk/ethereum";
import { useSignMessage as useSignSolanaMessage } from "@moon-x/react-native-sdk/solana";

Ethereum hooks:

  • useSignMessage — EIP-191 personal_sign. Returns { signature: "0x..." }.
  • useSignTransaction — Signs an EIP-1559 tx. Returns { signature, serializedSigned, hash }.
  • useSignTypedData — EIP-712. Returns { signature: "0x..." }.
  • useSignHash — Raw ECDSA digest sign (Privy parity — secp256k1_sign).
  • useSign7702Authorization — EIP-7702 delegation auth.
  • useSendTransaction — Sign + broadcast via the configured RPC.
  • useGetBalance — Native token balance.

Solana hooks:

  • useSignMessage — Ed25519 signature.
  • useSignTransaction — Signs a serialized base58 tx. Returns { signedTransaction: Uint8Array }.
  • useSendTransaction — Sign + broadcast.
  • useGetBalance — Lamport balance.

Same return shapes as the web SDK — the wire contract is identical.

Mobile-specific notes

  • No useConnectWallet. External-wallet connection (MetaMask, Phantom, WalletConnect) is web-only — MoonX wallets are the only thing you can sign with from the RN SDK.
  • useExportKey is a top-level hook on RN. On web the same operation hangs off useMoonKey().exportKey() inside a parent-side modal; on RN it toggles the always-mounted WebView into a full-screen visible state so the iframe-app renders the key UI itself.

Visible-WebView pattern (sensitive flows)

useExportKey (and future useImportKey, useAddPasskey) toggle the WebView from off-screen to a full-screen modal so the iframe-app can render the secret-sequestration UI. The plaintext key never leaves the WebView's DOM — RN-side JS only sees the rendered pixels.

The hook surface is still headless from the consumer's perspective:

const { exportKey } = useExportKey();
await exportKey(wallet); // shows modal, resolves on user-dismiss

Native config

The SDK ships an Expo config plugin (app.plugin.js) that writes the iOS and Android edits described below at expo prebuild time. The plugin is Expo-only — bare React Native projects (react-native init, no expo dependency) ignore it entirely and must apply the same edits by hand. Pick the path that matches your project.

Path A: Expo (recommended)

Add the plugin to app.json:

{
  "expo": {
    "scheme": "myapp",
    "plugins": [
      ["@moon-x/react-native-sdk", {
        "passkeyDomains": ["myapp.com"],
        "oauthRedirectScheme": "myapp"
      }]
    ]
  }
}

Then run pnpm prebuild (or pnpm prebuild --clean to regenerate native code from scratch). The plugin produces:

  • iOSwebcredentials:<domain> Associated Domains entitlement for each passkeyDomains entry.
  • iOSITSAppUsesNonExemptEncryption = false (App Store export compliance).
  • Android<meta-data android:name="asset_statements"> inside <application> in AndroidManifest.xml, plus a matching <string name="asset_statements"> resource in res/values/strings.xml pointing at https://<passkeyDomain>/.well-known/assetlinks.json for every entry in passkeyDomains. Required by Credential Manager — without it, [50152] RP ID cannot be validated.
  • Android<intent-filter> on MainActivity for the oauthRedirectScheme deep link.

Re-run pnpm prebuild whenever you change the plugin options.

Path B: Bare React Native (no Expo prebuild)

The plugin does nothing here — app.json and app.plugin.js aren't consulted. Apply each edit manually:

ios/<App>/<App>.entitlements — add:

<key>com.apple.developer.associated-domains</key>
<array>
  <string>webcredentials:myapp.com</string>
</array>

Also ensure https://myapp.com/.well-known/apple-app-site-association declares webcredentials for your bundle ID.

ios/<App>/Info.plist — add:

<key>ITSAppUsesNonExemptEncryption</key>
<false/>

android/app/src/main/AndroidManifest.xml — inside the existing <application> element add:

<meta-data
    android:name="asset_statements"
    android:resource="@string/asset_statements" />

Inside <activity android:name=".MainActivity"> add an additional intent filter for your OAuth deep link (alongside the default LAUNCHER filter, do not replace it):

<intent-filter android:autoVerify="false">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="myapp" />
</intent-filter>

android/app/src/main/res/values/strings.xml — add:

<string name="asset_statements" translatable="false">
[{ \"include\": \"https://myapp.com/.well-known/assetlinks.json\" }]
</string>

For multiple passkeyDomains, comma-separate the include objects inside the same JSON array (escape every " as \"):

<string name="asset_statements" translatable="false">
[{ \"include\": \"https://myapp.com/.well-known/assetlinks.json\" }, { \"include\": \"https://staging.myapp.com/.well-known/assetlinks.json\" }]
</string>

Android passkeys: Digital Asset Links

Android Credential Manager has no developer-mode bypass like iOS Associated Domains does — every WebAuthn ceremony is validated against https://<rpId>/.well-known/assetlinks.json, which must list your app's package name + signing-cert SHA-256 fingerprint.

1. Publish assetlinks.json at the rpId apex

If passkeyDomains is myapp.com, host at https://myapp.com/.well-known/assetlinks.json. Both relation strings below are recommended per the official Android docs:

[
  {
    "relation": [
      "delegate_permission/common.handle_all_urls",
      "delegate_permission/common.get_login_creds"
    ],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.myapp",
      "sha256_cert_fingerprints": [
        "<debug-keystore-sha-256>",
        "<upload-keystore-sha-256>",
        "<play-app-signing-sha-256>"
      ]
    }
  }
]

The file must be served as Content-Type: application/json over HTTPS with a valid certificate.

2. Get every signing fingerprint you need

cd android && ./gradlew signingReport

Copy the SHA-256 line from each Variant: block. Three flavors to include:

  • Debug: every developer's local builds. If you don't commit a shared debug.keystore, every developer's machine generates a different one — fix is to commit a single keystore to the repo (debug keystores are public-secret per the spec; password is android). See apps/rn-demo/.gitignore for the negation pattern that lets android/app/debug.keystore survive expo prebuild. React Native's template ships a community-default debug.keystore with SHA-256 FA:C6:17:45:DC:09:03:78:6F:B9:ED:E6:2A:96:2B:39:9F:73:48:F0:BB:6F:89:9B:83:32:66:75:91:03:3B:9C — if you copy that file in (it's in react-native-passkey, react-native-fast-image, etc.), every dev's debug build matches a single SHA.
  • Release / upload key: builds you upload to Play Console. Local release builds are signed with this until Google takes over.
  • Play App Signing key: once your first release uploads, Google strips your upload key and re-signs with their managed key. The resulting fingerprint is the one users actually run. Find it in Play Console → Setup → App integrity → App signing key certificate. Add this SHA-256 the moment your first internal-track upload completes — without it, GPM rejects validation on installs shipped through Play.

3. Verify before debugging

Before reinstalling and re-attempting the ceremony, sanity-check the file with Google's official Digital Asset Links validator:

curl 'https://digitalassetlinks.googleapis.com/v1/assetlinks:check?source.web.site=https://myapp.com&relation=delegate_permission/common.get_login_creds&target.android_app.package_name=com.example.myapp&target.android_app.certificate.sha256_fingerprint=<SHA-WITH-COLONS>'

Expected: { "linked": true, ... }. If this returns linked: false, the on-device validator will also fail — fix the file first.

4. Register the Android fingerprint with the moon-x backend

The backend needs to know which Android signing certs are allowed to act as the relying party for your app — Credential Manager substitutes android:apk-key-hash:<base64url(sha256_of_signing_cert)> for the WebAuthn clientDataJSON.origin instead of the HTTPS origin a browser sends, and the platform does an exact-match check.

Set the webauthn.android_apk_key_hashes app setting to a JSON array of every Android signing cert SHA-256 (hex, with or without colons — both forms accepted; same values you put in assetlinks.json):

["FA:C6:17:45:DC:09:...", "AA:BB:CC:..."]

Symptom when missing: passkey ceremony succeeds at the OS level (you see and pass the biometric prompt), then the backend's verify step rejects with error validating origin.

5. Troubleshooting

| Symptom | Likely cause | Fix | |---|---|---| | Native dialog opens, user taps, response never returns; logcat shows [50152] RP ID cannot be validated. | assetlinks.json not reachable, wrong package_name, wrong SHA, OR a stale negative DAL cache from an earlier failure | Verify file with Google's validator (step 3); uninstall and reinstall the app — clearing com.google.android.gms data does NOT evict the per-package DAL cache, only a reinstall does. | | error validating origin from the backend after the OS ceremony succeeds | Android signing fingerprint not registered with the backend | Add the SHA to webauthn.android_apk_key_hashes (step 4). | | Spec-correct setup that worked in dev fails for users installing from Play | Play App Signing key SHA missing | Add the Play App Signing fingerprint from Play Console to assetlinks.json AND webauthn.android_apk_key_hashes. | | androidx.credentials.exceptions.domerrors.DataError@<hash> with no detail | Older react-native-passkeys doesn't surface the underlying DOM error | Watch logcat directly: adb logcat \| grep -E "Auth.Api.Credentials\|Fido" — the real error code (e.g. [50152]) is visible there. | | Persistent failure even though everything looks correct | DAL cache from a prior bad attempt | adb uninstall <package> then reinstall. Do not rely on adb shell pm clear com.google.android.gms — it logs the user out of Google but leaves the per-package DAL cache. |

6. Min versions / known issues

  • Google Password Manager validates more strictly than the WebAuthn spec requires. Some debug-keystore + assetlinks setups that pass Google's centralized DAL validator still fail GPM's on-device check on first install. Reinstall after publishing assetlinks.json is the most reliable workaround.
  • Real device testing is recommended: emulator + GMS sometimes exhibits subtly different validation behaviour.
  • Android 13+ is what we test against. The Credential Manager backport on 9-12 works for most flows but isn't continuously exercised here.

Min OS versions

  • iOS 16+ (passkeys via ASAuthorization)
  • Android 9+ (passkeys via Credential Manager)
  • React Native 0.78+ (React 19 support)
  • Hermes is the supported JS engine. JSC will work for non-passkey flows but isn't tested.

What's deferred from v1

  • WalletConnect / external-wallet connectors
  • "WithUI" SDK method variants (web-only — RN is headless)
  • Iframe-app emitting EXPORT_COMPLETE one-way postMessage on user-dismiss (currently the modal's close button resolves the promise; the post-back contract is a planned follow-up)
  • RN-mode bundle-ID origin whitelist on the iframe-app (window.ReactNativeWebView-mode currently bypasses origin whitelisting; publishableKey + session JWT are the gates in v1)

Reference

  • Web parity: @moon-x/react-sdk
  • Wire protocol: packages/core/src/utils/post-message.ts
  • Architecture: packages/react-native-sdk/AGENTS.md