{"_id":"@bkey-inc/bmoni_embedded_sdk","name":"@bkey-inc/bmoni_embedded_sdk","dist-tags":{"latest":"0.0.1"},"versions":{"0.0.1":{"name":"@bkey-inc/bmoni_embedded_sdk","version":"0.0.1","description":"Bmoni Embedded SDK for React Native — Ethereum wallet provisioning and signing backed by Android Keystore / iOS Secure Enclave.","main":"./lib/module/index.js","types":"./lib/typescript/src/index.d.ts","exports":{".":{"bmoni_embedded_sdk-source":"./src/index.tsx","types":"./lib/typescript/src/index.d.ts","default":"./lib/module/index.js"},"./package.json":"./package.json"},"scripts":{"example":"yarn workspace bmoni_embedded_sdk-example","clean":"del-cli android/build example/android/build example/android/app/build example/ios/build lib","prepare":"bob build","typecheck":"tsc","lint":"eslint \"**/*.{js,ts,tsx}\"","test":"jest"},"keywords":["react-native","ios","android","ethereum","wallet","signer","web3","secure-storage"],"repository":{"type":"git","url":"git+https://github.com/bkey-inc/bmoni_embedded_reactnative_sdk.git"},"author":{"name":"Bkey Inc.","email":"developers@bkey.me","url":"https://bkey.me"},"license":"Apache-2.0","bugs":{"url":"https://github.com/bkey-inc/bmoni_embedded_reactnative_sdk/issues"},"homepage":"https://github.com/bkey-inc/bmoni_embedded_reactnative_sdk#readme","publishConfig":{"registry":"https://registry.npmjs.org/","access":"public"},"devDependencies":{"@eslint/compat":"^2.1.0","@eslint/eslintrc":"^3.3.5","@eslint/js":"^10.0.1","@react-native/babel-preset":"0.85.0","@react-native/eslint-config":"0.85.0","@types/jest":"^30.0.0","@types/react":"^19.2.0","babel-jest":"^30.0.0","del-cli":"^7.0.0","eslint":"^9.39.4","eslint-config-prettier":"^10.1.8","eslint-plugin-ft-flow":"^3.0.11","eslint-plugin-prettier":"^5.5.6","jest":"^30.0.0","prettier":"^3.8.3","react":"19.2.3","react-native":"0.85.0","react-native-builder-bob":"^0.43.0","turbo":"^2.9.16","typescript":"^6.0.3"},"peerDependencies":{"react":"*","react-native":"*"},"workspaces":["example"],"packageManager":"yarn@4.11.0","react-native-builder-bob":{"source":"src","output":"lib","targets":[["module",{"esm":true}],["typescript",{"project":"tsconfig.build.json"}]]},"codegenConfig":{"name":"BmoniEmbeddedSdkSpec","type":"modules","jsSrcsDir":"src","android":{"javaPackageName":"com.bmoniembeddedsdk"}},"prettier":{"quoteProps":"consistent","singleQuote":true,"tabWidth":2,"trailingComma":"es5","useTabs":false},"create-react-native-library":{"type":"turbo-module","languages":"kotlin-objc","tools":["eslint","jest"],"version":"0.63.0"},"gitHead":"850b818426a3f3d00180e30ddcbe79617880e2c0","_id":"@bkey-inc/bmoni_embedded_sdk@0.0.1","_nodeVersion":"24.15.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-ogEvfyTYVDSc60xU4NBboEJFt5Bdv61pdA8ne44gWKsDRoWL3wUHu5DAy8q8UoXZJIumIFtdJcXQXvdN3YaMHg==","shasum":"caa9d16af6462bd467b517a4e1d88efa844a6f30","tarball":"https://registry.npmjs.org/@bkey-inc/bmoni_embedded_sdk/-/bmoni_embedded_sdk-0.0.1.tgz","fileCount":56,"unpackedSize":146497,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIGjsEsJByVIyymFwjrsae/MBmObRfVVbP7QOfiqtRZrNAiEA4R143U+14iH+KoHuQnQL3NGRWaGIx7Ud9iNKEBJz+Pk="}]},"_npmUser":{"name":"bright.sunu","email":"bright.etornam@bkey.me"},"directories":{},"maintainers":[{"name":"hhkweku","email":"harold.williams@bkey.me"},{"name":"bright.sunu","email":"bright.etornam@bkey.me"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/bmoni_embedded_sdk_0.0.1_1785254515047_0.8612458048997531"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-28T16:01:54.945Z","0.0.1":"2026-07-28T16:01:55.237Z","modified":"2026-07-28T16:01:55.494Z"},"maintainers":[{"name":"hhkweku","email":"harold.williams@bkey.me"},{"name":"bright.sunu","email":"bright.etornam@bkey.me"}],"description":"Bmoni Embedded SDK for React Native — Ethereum wallet provisioning and signing backed by Android Keystore / iOS Secure Enclave.","homepage":"https://github.com/bkey-inc/bmoni_embedded_reactnative_sdk#readme","keywords":["react-native","ios","android","ethereum","wallet","signer","web3","secure-storage"],"repository":{"type":"git","url":"git+https://github.com/bkey-inc/bmoni_embedded_reactnative_sdk.git"},"author":{"name":"Bkey Inc.","email":"developers@bkey.me","url":"https://bkey.me"},"bugs":{"url":"https://github.com/bkey-inc/bmoni_embedded_reactnative_sdk/issues"},"license":"Apache-2.0","readme":"# @bkey-inc/bmoni_embedded_sdk\n\n[![npm](https://img.shields.io/npm/v/@bkey-inc/bmoni_embedded_sdk)](https://www.npmjs.com/package/@bkey-inc/bmoni_embedded_sdk)\n[![Platform Support](https://img.shields.io/badge/Platform-Android%20%7C%20iOS-blue)](#-platform-support)\n[![License](https://img.shields.io/badge/License-Apache%202.0-green)](LICENSE)\n\nA React Native library that exposes the **BMONISigner** native SDKs for Ethereum wallet provisioning and transaction/message signing on Android and iOS.\n\n> **Security First:** Private keys are generated on-device, encrypted with a platform-managed wrapping key (Android Keystore on Android, Secure Enclave on iOS), and persisted only as ciphertext. Plaintext keys never leave the secure boundary and are zeroized in RAM after each operation.\n\n---\n\n## ✨ Features\n\n- **Customizable PIN policies:** `initialize({ pinLength, requirePin })` — opt into a custom PIN length (default `6`) and toggle whether sign/delete operations verify the PIN before invoking the native module (default `true`).\n- **One-tap Provisioning:** `initWallet()` — provision a fresh secp256k1 wallet and return its EIP-55 checksummed address. The address is persisted in the SDK's secure-storage cache so subsequent launches can read it back via `walletAddress()` / `hasWallet()`.\n- **PIN Management:** `setPin`, `changePin`, `removePin`, `matchPin`, `hasPin`. The PIN is persisted as a salted PBKDF2-HMAC-SHA256 digest in platform secure storage (Android Keystore-wrapped preferences / iOS Keychain).\n- **Robust Signing:**\n  - `signTransactionHash(hashHex, pin?)` — sign a pre-computed 32-byte digest (ERC-4337 `userOpHash`, EIP-712 digest, raw transaction hash, etc.).\n  - `signMessage(message, pin?)` — sign a UTF-8 message with the EIP-191 `personal_sign` prefix (SIWE, login challenges, etc.).\n- **Lifecycle Management:** `deleteWallet(pin?)` — remove the encrypted private key from device storage. Idempotent at the native layer.\n- **Typed Errors:** `BmoniSignerError` carries the native or SDK-level error code for easy branching.\n\n*Note: All signatures are returned as `0x`-prefixed 130-character hex strings in recoverable `r(32) || s(32) || v(1)` format with `v ∈ {27, 28}` and low-s normalized (EIP-2 compliant), so they can be verified server-side with `ecrecover`.*\n\n---\n\n## 🚀 Getting Started\n\n```sh\nnpm install @bkey-inc/bmoni_embedded_sdk\n# or\nyarn add @bkey-inc/bmoni_embedded_sdk\n```\n\nThen install the iOS pods:\n\n```sh\ncd ios && pod install\n```\n\nNo manual linking is required — the library ships a TurboModule and is picked up by React Native autolinking on both platforms, and `pod install` downloads the pinned `BMONISigner.xcframework`.\n\n### Android: declare Bkey's Maven repository\n\nThe native `BMONISigner` AAR is published to Bkey's own Maven repository, and your app resolves it transitively, so **the repository has to be declared by the app**. Add it to your root `android/build.gradle`:\n\n```groovy android/build.gradle\nallprojects {\n    repositories {\n        maven {\n            url \"https://bkey-inc.github.io/package-distribution/maven\"\n            content { includeGroup \"me.bkey.ip\" }\n        }\n    }\n}\n```\n\n<!-- The library cannot do this for you: a repository declared in a library\n     module only applies to that module's own resolutions, while\n     `me.bkey.ip:bmonisigner` is resolved on the app's runtime classpath. -->\n\nWithout it the build fails with:\n\n```\nCould not find me.bkey.ip:bmonisigner:1.0.0.\nRequired by: project ':app' > project :bkey-inc_bmoni_embedded_sdk\n```\n\n---\n\n## 💻 Usage\n\n`BmoniEmbeddedSdk` is a static facade — call its methods directly without instantiating it. Configure it once as your app starts:\n\n```ts\nimport { BmoniEmbeddedSdk } from '@bkey-inc/bmoni_embedded_sdk';\n\n// pinLength defaults to 6. requirePin defaults to true.\nBmoniEmbeddedSdk.initialize({ pinLength: 6, requirePin: true });\n```\n\n### Basic Wallet Flow\n\n```ts\nimport {\n  BmoniEmbeddedSdk,\n  BmoniSignerError,\n  BmoniSignerErrorCode,\n} from '@bkey-inc/bmoni_embedded_sdk';\n\ntry {\n  // 1. Provision a wallet (one-time per device). The returned address is\n  //    also cached in secure storage; subsequent launches can read it back\n  //    without re-provisioning.\n  const address =\n    (await BmoniEmbeddedSdk.walletAddress()) ??\n    (await BmoniEmbeddedSdk.initWallet());\n  console.log('Wallet address:', address);\n\n  // 2. Set the PIN that gates future signing operations. PINs are exactly\n  //    `BmoniEmbeddedSdk.pinLength` (default 6) characters; other lengths\n  //    throw `pinInvalid`.\n  if (!(await BmoniEmbeddedSdk.hasPin())) {\n    await BmoniEmbeddedSdk.setPin('123456');\n  }\n\n  // 3. Sign a personal message (EIP-191) — requires a matching PIN when\n  //    requirePin is true.\n  const messageSig = await BmoniEmbeddedSdk.signMessage(\n    'Welcome to BMONI!',\n    '123456'\n  );\n\n  // 4. Sign a 32-byte digest (e.g. ERC-4337 userOpHash).\n  const hashSig = await BmoniEmbeddedSdk.signTransactionHash(\n    '0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8',\n    '123456'\n  );\n} catch (error) {\n  if (!(error instanceof BmoniSignerError)) throw error;\n\n  switch (error.errorCode) {\n    case BmoniSignerErrorCode.walletAlreadyExists:\n      // Re-provision flow — destructive, the on-chain address becomes\n      // unrecoverable from this device.\n      await BmoniEmbeddedSdk.deleteWallet('123456');\n      await BmoniEmbeddedSdk.initWallet();\n      break;\n    case BmoniSignerErrorCode.pinMismatch:\n    case BmoniSignerErrorCode.pinNotSet:\n      // Prompt the user to (re)enter / set their PIN.\n      break;\n  }\n}\n```\n\n### ⚙️ Configuration\n\n`BmoniEmbeddedSdk.initialize(...)` accepts:\n\n| Option | Type | Default | Effect |\n| --- | --- | --- | --- |\n| `pinLength` | `number` | `6` | Number of characters required for a valid PIN. Enforced by `setPin` / `changePin`. |\n| `requirePin` | `boolean` | `true` | Whether `signMessage` / `signTransactionHash` / `deleteWallet` verify a supplied PIN against the stored digest before forwarding to the native module. |\n\nThe active configuration is exposed via `BmoniEmbeddedSdk.config` (and the convenience getters `BmoniEmbeddedSdk.pinLength` / `BmoniEmbeddedSdk.requirePin`) so app-side UI can adapt — e.g. render a PIN input of the right length, or hide PIN flows entirely when gating is off.\n\n#### `requirePin: false` mode\n\nWhen the developer chooses to manage authentication elsewhere (biometrics, OS lockscreen, server-side challenge, …), pass `requirePin: false`. In that mode:\n\n- `signMessage`, `signTransactionHash` and `deleteWallet` forward straight to the native module and **ignore** any supplied `pin` argument.\n- The PIN management methods (`setPin`, `changePin`, …) keep working — toggling `requirePin` only changes whether a stored PIN is enforced as a gate.\n\n```ts\nBmoniEmbeddedSdk.initialize({ requirePin: false });\n\nawait BmoniEmbeddedSdk.initWallet();\nconst sig = await BmoniEmbeddedSdk.signMessage('hi'); // no pin required\n```\n\n### 🔍 Reading the Wallet Address Back\n\nThe native BMONISigner SDK only returns the address from the call that originally provisioned the wallet — there is no `getAddress()` on the native side. The TypeScript facade transparently caches the address in platform secure storage after `initWallet()` succeeds and wipes it on `deleteWallet()`, so you can recover it at any time:\n\n```ts\nconst address = await BmoniEmbeddedSdk.walletAddress();\nif (address !== null) {\n  // Render the wallet UI, fetch on-chain state, etc.\n} else {\n  // Show the \"create wallet\" flow → call BmoniEmbeddedSdk.initWallet().\n}\n```\n\n### 🔐 PIN Management\n\nThe SDK enforces a fixed-length PIN equal to `BmoniEmbeddedSdk.pinLength`. Calling `setPin` / `changePin` with any other length throws `BmoniSignerError` with `errorCode: pinInvalid`. The PIN is persisted as a salted PBKDF2-HMAC-SHA256 digest (100 000 iterations) in platform secure storage; the raw PIN never touches disk.\n\n```ts\n// One-time setup.\nawait BmoniEmbeddedSdk.setPin('123456');\n\n// Rotate.\nawait BmoniEmbeddedSdk.changePin({ currentPin: '123456', newPin: '654321' });\n\n// Verify without throwing — useful for UI prompts.\nconst ok = await BmoniEmbeddedSdk.matchPin('654321');\n\n// Tear down (e.g. on logout).\nawait BmoniEmbeddedSdk.removePin('654321');\n```\n\n### 🛡️ Server-Side Verification\n\nSignatures are ECDSA recoverable, so verifiers only need the address returned by `initWallet()`:\n\n```solidity\naddress recovered = ecrecover(hash, v, r, s);\nrequire(recovered == expectedAddress, \"invalid signature\");\n```\n\n---\n\n## 🛑 Error Handling\n\nNative failures surface as a [`BmoniSignerError`](src/BmoniSignerError.ts) carrying both a numeric `errorCode` and a human-readable `message`. Compare against the [`BmoniSignerErrorCode`](src/BmoniSignerError.ts) constants:\n\n| Constant | Hex | Meaning |\n| --- | --- | --- |\n| `walletAlreadyExists` | `0x30010010` | `initWallet` called while a wallet is already on disk. |\n| `signInvalidMessage` | `0x30010001` | The supplied message could not be processed. |\n| `signInvalidPrivateKey` | `0x30010002` | Stored key could not be recovered / decrypted. |\n| `signInvalidHash` | `0x30010003` | Hash argument was not a valid 32-byte hex string. |\n| `signProcess` | `0x30010004` | Generic ECDSA signing failure. |\n| `signKeygen` | `0x30010005` | secp256k1 keypair generation failed. |\n| `signEip55` | `0x30010006` | EIP-55 checksum derivation failed. |\n| `pinNotSet` | `0x40000001` | A PIN-gated call was attempted but no PIN exists. |\n| `pinAlreadySet` | `0x40000002` | `setPin` called while a PIN already exists. |\n| `pinMismatch` | `0x40000003` | The supplied PIN did not match the stored digest. |\n| `pinInvalid` | `0x40000004` | The supplied PIN was the wrong length / missing. |\n| `unexpectedNativeNull` | `0x50000001` | A native method that promised a non-null result did not deliver one (native-bridge bug). |\n| `walletAddressCacheFailed` | `0x50000002` | A wallet was provisioned but its address could not be cached. **The error message carries the address — persist it, or the wallet becomes unreachable from this device.** |\n\n- **`0x3001xxxx`** codes originate in the native BMONISigner SDK, and are the same on both platforms.\n- **Storage failures** (key creation, encrypt, decrypt) also come through unmapped, but the native SDK uses a *different* range per platform: **`0x3000xxxx`** on iOS (Secure Enclave) and **`0x3002xxxx`** on Android (Keystore). Read `errorCodeHex` rather than matching one prefix or expecting a named constant.\n- **`0x4xxxxxxx`** codes are SDK-level (PIN gating, etc.).\n- **`0x5xxxxxxx`** codes come from the TypeScript ↔ native bridge.\n\nFailures that are *not* BMONISigner errors (a keychain problem, an unavailable native module) reject with their original React Native error, so `instanceof BmoniSignerError` is the reliable discriminator.\n\n---\n\n## 📱 Platform Support\n\n| Android | iOS |\n|---------|-----|\n| ✅      | ✅  |\n\n## 📋 Requirements\n\n- React Native with the New Architecture enabled (default since `0.76`). Built and verified against React Native `0.85`.\n- Android `minSdk 24+`\n- iOS `15.1+`\n- **Android ABI:** the BMONISigner AAR ships an `arm64-v8a` slice only. Build for `arm64-v8a` (set `reactNativeArchitectures=arm64-v8a` in `android/gradle.properties`) and run on an arm64 device or emulator.\n\n---\n\n## 💡 Example\n\nSee the [`example`](example) directory for a working demo covering wallet provisioning, PIN management, message signing, and hash signing.\n\n```sh\nyarn\nyarn example ios      # or: yarn example android\n```\n\n---\n\n## 🤝 Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md).\n\n---\n\n## 📄 License\n\nCopyright 2026 Bkey, Inc.\n\nLicensed under the [Apache License, Version 2.0](LICENSE) — you may use, modify, and distribute the SDK (including in proprietary applications) provided you preserve the copyright and license notices and comply with the terms in the [LICENSE](LICENSE) file.\n\nFor commercial support or enterprise inquiries, contact [developers@bkey.me](mailto:developers@bkey.me).\n","readmeFilename":"README.md","_rev":"1-ad1d8a3b15fba381b7684d6f4062720e"}