@chipi-stack/chipi-expo 13.11.0 → 13.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,7 +1,20 @@
1
- export { ChipiProvider, ChipiWalletData, SessionState, SessionWallet, UseChipiSessionConfig, UseChipiSessionReturn, UseChipiWalletConfig, UseChipiWalletReturn, useAddSessionKeyToContract, useApprove, useCallAnyContract, useChipiContext, useChipiSession, useChipiWallet, useCreateSessionKey, useExecuteWithSession, useGetSessionData, useGetSku, useGetSkuList, useGetSkuPurchase, useGetTokenBalance, useGetTransactionList, useGetWallet, usePurchaseSku, useRecordSendTransaction, useRevokeSessionKey, useTransfer } from '@chipi-stack/chipi-react';
2
- import { CreateWalletParams, CreateWalletResponse, MigrateWalletToPasskeyParams, WalletData } from '@chipi-stack/types';
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { ChipiSDKConfig, CreateWalletParams, CreateWalletResponse, MigrateWalletToPasskeyParams, WalletData } from '@chipi-stack/types';
3
4
  export * from '@chipi-stack/types';
4
5
  export { Chain, ChainToken } from '@chipi-stack/types';
6
+ export { ChipiWalletData, SessionState, SessionWallet, UseChipiSessionConfig, UseChipiSessionReturn, UseChipiWalletConfig, UseChipiWalletReturn, useAddSessionKeyToContract, useApprove, useCallAnyContract, useChipiContext, useChipiSession, useChipiWallet, useCreateSessionKey, useExecuteWithSession, useGetSessionData, useGetSku, useGetSkuList, useGetSkuPurchase, useGetTokenBalance, useGetTransactionList, useGetWallet, usePurchaseSku, useRecordSendTransaction, useRevokeSessionKey, useTransfer } from '@chipi-stack/chipi-react';
7
+
8
+ interface ChipiProviderProps {
9
+ children: ReactNode;
10
+ config: ChipiSDKConfig;
11
+ }
12
+ /**
13
+ * Expo-aware provider that injects a native passkey adapter into chipi-react hooks.
14
+ * This ensures useTransfer/useApprove/useCallAnyContract use native biometrics
15
+ * instead of browser WebAuthn in React Native.
16
+ */
17
+ declare function ChipiProvider({ children, config }: ChipiProviderProps): react_jsx_runtime.JSX.Element;
5
18
 
6
19
  type CreateWalletInput = {
7
20
  params: CreateWalletParams;
@@ -57,6 +70,7 @@ declare function useMigrateWalletToPasskey(): {
57
70
  * native equivalent using:
58
71
  * - expo-local-authentication → biometric gate (Face ID / Touch ID / fingerprint)
59
72
  * - expo-secure-store → iOS Keychain / Android Keystore protected storage
73
+ * - crypto-es → cryptographically secure random bytes (no Web Crypto in RN)
60
74
  *
61
75
  * Security model: a random 32-byte key is generated at wallet creation time,
62
76
  * stored in the device's secure enclave behind biometric authentication,
@@ -112,4 +126,4 @@ declare function getNativeWalletCredential(): Promise<NativeWalletCredential | n
112
126
  */
113
127
  declare function removeNativeWalletPasskey(userId: string): Promise<void>;
114
128
 
115
- export { type NativeCreateWalletPasskeyResult, type NativeWalletCredential, createNativeWalletPasskey, getNativeWalletCredential, getNativeWalletEncryptKey, hasNativeWalletPasskey, isNativeBiometricSupported, removeNativeWalletPasskey, useCreateWallet, useMigrateWalletToPasskey };
129
+ export { ChipiProvider, type NativeCreateWalletPasskeyResult, type NativeWalletCredential, createNativeWalletPasskey, getNativeWalletCredential, getNativeWalletEncryptKey, hasNativeWalletPasskey, isNativeBiometricSupported, removeNativeWalletPasskey, useCreateWallet, useMigrateWalletToPasskey };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,20 @@
1
- export { ChipiProvider, ChipiWalletData, SessionState, SessionWallet, UseChipiSessionConfig, UseChipiSessionReturn, UseChipiWalletConfig, UseChipiWalletReturn, useAddSessionKeyToContract, useApprove, useCallAnyContract, useChipiContext, useChipiSession, useChipiWallet, useCreateSessionKey, useExecuteWithSession, useGetSessionData, useGetSku, useGetSkuList, useGetSkuPurchase, useGetTokenBalance, useGetTransactionList, useGetWallet, usePurchaseSku, useRecordSendTransaction, useRevokeSessionKey, useTransfer } from '@chipi-stack/chipi-react';
2
- import { CreateWalletParams, CreateWalletResponse, MigrateWalletToPasskeyParams, WalletData } from '@chipi-stack/types';
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { ChipiSDKConfig, CreateWalletParams, CreateWalletResponse, MigrateWalletToPasskeyParams, WalletData } from '@chipi-stack/types';
3
4
  export * from '@chipi-stack/types';
4
5
  export { Chain, ChainToken } from '@chipi-stack/types';
6
+ export { ChipiWalletData, SessionState, SessionWallet, UseChipiSessionConfig, UseChipiSessionReturn, UseChipiWalletConfig, UseChipiWalletReturn, useAddSessionKeyToContract, useApprove, useCallAnyContract, useChipiContext, useChipiSession, useChipiWallet, useCreateSessionKey, useExecuteWithSession, useGetSessionData, useGetSku, useGetSkuList, useGetSkuPurchase, useGetTokenBalance, useGetTransactionList, useGetWallet, usePurchaseSku, useRecordSendTransaction, useRevokeSessionKey, useTransfer } from '@chipi-stack/chipi-react';
7
+
8
+ interface ChipiProviderProps {
9
+ children: ReactNode;
10
+ config: ChipiSDKConfig;
11
+ }
12
+ /**
13
+ * Expo-aware provider that injects a native passkey adapter into chipi-react hooks.
14
+ * This ensures useTransfer/useApprove/useCallAnyContract use native biometrics
15
+ * instead of browser WebAuthn in React Native.
16
+ */
17
+ declare function ChipiProvider({ children, config }: ChipiProviderProps): react_jsx_runtime.JSX.Element;
5
18
 
6
19
  type CreateWalletInput = {
7
20
  params: CreateWalletParams;
@@ -57,6 +70,7 @@ declare function useMigrateWalletToPasskey(): {
57
70
  * native equivalent using:
58
71
  * - expo-local-authentication → biometric gate (Face ID / Touch ID / fingerprint)
59
72
  * - expo-secure-store → iOS Keychain / Android Keystore protected storage
73
+ * - crypto-es → cryptographically secure random bytes (no Web Crypto in RN)
60
74
  *
61
75
  * Security model: a random 32-byte key is generated at wallet creation time,
62
76
  * stored in the device's secure enclave behind biometric authentication,
@@ -112,4 +126,4 @@ declare function getNativeWalletCredential(): Promise<NativeWalletCredential | n
112
126
  */
113
127
  declare function removeNativeWalletPasskey(userId: string): Promise<void>;
114
128
 
115
- export { type NativeCreateWalletPasskeyResult, type NativeWalletCredential, createNativeWalletPasskey, getNativeWalletCredential, getNativeWalletEncryptKey, hasNativeWalletPasskey, isNativeBiometricSupported, removeNativeWalletPasskey, useCreateWallet, useMigrateWalletToPasskey };
129
+ export { ChipiProvider, type NativeCreateWalletPasskeyResult, type NativeWalletCredential, createNativeWalletPasskey, getNativeWalletCredential, getNativeWalletEncryptKey, hasNativeWalletPasskey, isNativeBiometricSupported, removeNativeWalletPasskey, useCreateWallet, useMigrateWalletToPasskey };
package/dist/index.js CHANGED
@@ -1,12 +1,16 @@
1
1
  'use strict';
2
2
 
3
3
  var chipiReact = require('@chipi-stack/chipi-react');
4
- var reactQuery = require('@tanstack/react-query');
5
4
  var LocalAuthentication = require('expo-local-authentication');
6
5
  var SecureStore = require('expo-secure-store');
6
+ var CryptoES = require('crypto-es');
7
+ var jsxRuntime = require('react/jsx-runtime');
8
+ var reactQuery = require('@tanstack/react-query');
7
9
  var backend = require('@chipi-stack/backend');
8
10
  var types = require('@chipi-stack/types');
9
11
 
12
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
+
10
14
  function _interopNamespace(e) {
11
15
  if (e && e.__esModule) return e;
12
16
  var n = Object.create(null);
@@ -27,14 +31,14 @@ function _interopNamespace(e) {
27
31
 
28
32
  var LocalAuthentication__namespace = /*#__PURE__*/_interopNamespace(LocalAuthentication);
29
33
  var SecureStore__namespace = /*#__PURE__*/_interopNamespace(SecureStore);
34
+ var CryptoES__default = /*#__PURE__*/_interopDefault(CryptoES);
30
35
 
31
- // src/index.ts
36
+ // src/ChipiProvider.tsx
32
37
  var ENCRYPT_KEY_PREFIX = "chipi_wallet_key_";
33
38
  var CREDENTIAL_META_KEY = "chipi_wallet_credential";
34
39
  function generateRandomHex(byteCount) {
35
- const array = new Uint8Array(byteCount);
36
- crypto.getRandomValues(array);
37
- return Array.from(array).map((b) => b.toString(16).padStart(2, "0")).join("");
40
+ const wordArray = CryptoES__default.default.lib.WordArray.random(byteCount);
41
+ return wordArray.toString(CryptoES__default.default.enc.Hex);
38
42
  }
39
43
  async function isNativeBiometricSupported() {
40
44
  const hasHardware = await LocalAuthentication__namespace.hasHardwareAsync();
@@ -104,8 +108,19 @@ async function removeNativeWalletPasskey(userId) {
104
108
  await SecureStore__namespace.deleteItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`);
105
109
  await SecureStore__namespace.deleteItemAsync(CREDENTIAL_META_KEY);
106
110
  }
107
-
108
- // src/hooks/useCreateWallet.ts
111
+ var expoPasskeyAdapter = {
112
+ getWalletEncryptKey: async (input) => {
113
+ if (!input.externalUserId) {
114
+ throw new Error(
115
+ "externalUserId is required when usePasskey is true in Expo. Pass externalUserId in the hook params so the native key can be retrieved."
116
+ );
117
+ }
118
+ return getNativeWalletEncryptKey(input.externalUserId);
119
+ }
120
+ };
121
+ function ChipiProvider({ children, config }) {
122
+ return /* @__PURE__ */ jsxRuntime.jsx(chipiReact.ChipiProvider, { config, passkeyAdapter: expoPasskeyAdapter, children });
123
+ }
109
124
  function useCreateWallet() {
110
125
  const { chipiSDK } = chipiReact.useChipiContext();
111
126
  const mutation = reactQuery.useMutation({
@@ -201,10 +216,6 @@ function useMigrateWalletToPasskey() {
201
216
  };
202
217
  }
203
218
 
204
- Object.defineProperty(exports, "ChipiProvider", {
205
- enumerable: true,
206
- get: function () { return chipiReact.ChipiProvider; }
207
- });
208
219
  Object.defineProperty(exports, "useAddSessionKeyToContract", {
209
220
  enumerable: true,
210
221
  get: function () { return chipiReact.useAddSessionKeyToContract; }
@@ -289,6 +300,7 @@ Object.defineProperty(exports, "ChainToken", {
289
300
  enumerable: true,
290
301
  get: function () { return types.ChainToken; }
291
302
  });
303
+ exports.ChipiProvider = ChipiProvider;
292
304
  exports.createNativeWalletPasskey = createNativeWalletPasskey;
293
305
  exports.getNativeWalletCredential = getNativeWalletCredential;
294
306
  exports.getNativeWalletEncryptKey = getNativeWalletEncryptKey;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/native-passkey.ts","../src/hooks/useCreateWallet.ts","../src/hooks/useMigrateWalletToPasskey.ts"],"names":["LocalAuthentication","SecureStore","useChipiContext","useMutation","decryptPrivateKey","encryptPrivateKey"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Native Passkey for Expo/React Native\n *\n * Replaces the browser-only @simplewebauthn/browser implementation with a\n * native equivalent using:\n * - expo-local-authentication → biometric gate (Face ID / Touch ID / fingerprint)\n * - expo-secure-store → iOS Keychain / Android Keystore protected storage\n *\n * Security model: a random 32-byte key is generated at wallet creation time,\n * stored in the device's secure enclave behind biometric authentication,\n * and retrieved later by prompting the user again. This is functionally\n * equivalent to the WebAuthn PRF approach used on the web.\n */\n\nimport * as LocalAuthentication from \"expo-local-authentication\";\nimport * as SecureStore from \"expo-secure-store\";\n\nconst ENCRYPT_KEY_PREFIX = \"chipi_wallet_key_\";\nconst CREDENTIAL_META_KEY = \"chipi_wallet_credential\";\n\nexport interface NativeWalletCredential {\n credentialId: string;\n userId: string;\n createdAt: string;\n}\n\nexport interface NativeCreateWalletPasskeyResult {\n encryptKey: string;\n credentialId: string;\n prfSupported: false;\n}\n\n/**\n * Generate a cryptographically random 32-byte hex string.\n * crypto.getRandomValues is available in React Native via Hermes (RN 0.71+).\n */\nfunction generateRandomHex(byteCount: number): string {\n const array = new Uint8Array(byteCount);\n crypto.getRandomValues(array);\n return Array.from(array)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\n/**\n * Returns true if the device has biometric hardware AND the user has enrolled.\n * Must be true before calling createNativeWalletPasskey or getNativeWalletEncryptKey.\n */\nexport async function isNativeBiometricSupported(): Promise<boolean> {\n const hasHardware = await LocalAuthentication.hasHardwareAsync();\n if (!hasHardware) return false;\n const isEnrolled = await LocalAuthentication.isEnrolledAsync();\n return isEnrolled;\n}\n\n/**\n * Create a new native wallet passkey.\n *\n * 1. Verifies biometric support.\n * 2. Prompts the user with Face ID / Touch ID to confirm intent.\n * 3. Generates a random encryption key.\n * 4. Stores the key in the device's Keychain/Keystore, protected by biometrics.\n * 5. Returns the encryption key so the wallet can be created immediately.\n *\n * The key is NEVER stored anywhere else — it lives only in the secure enclave.\n */\nexport async function createNativeWalletPasskey(\n userId: string,\n _userName: string\n): Promise<NativeCreateWalletPasskeyResult> {\n const supported = await isNativeBiometricSupported();\n if (!supported) {\n throw new Error(\n \"Biometric authentication is not available or not enrolled on this device. \" +\n \"Please enroll Face ID, Touch ID, or a fingerprint in your device settings.\"\n );\n }\n\n // Prompt biometrics to confirm user intent before generating/storing the key\n const authResult = await LocalAuthentication.authenticateAsync({\n promptMessage: \"Authenticate to create your wallet\",\n cancelLabel: \"Cancel\",\n disableDeviceFallback: false,\n });\n\n if (!authResult.success) {\n const reason = \"error\" in authResult ? authResult.error : \"unknown\";\n if (reason === \"user_cancel\" || reason === \"app_cancel\") {\n throw new Error(\"Biometric authentication was cancelled\");\n }\n throw new Error(`Biometric authentication failed: ${reason}`);\n }\n\n const encryptKey = generateRandomHex(32);\n const credentialId = `native_biometric_${userId}_${Date.now()}`;\n\n // Store the encryption key — requireAuthentication means future reads need biometrics\n await SecureStore.setItemAsync(\n `${ENCRYPT_KEY_PREFIX}${userId}`,\n encryptKey,\n { requireAuthentication: true }\n );\n\n // Store lightweight credential metadata (not sensitive — no requireAuthentication needed)\n const meta: NativeWalletCredential = {\n credentialId,\n userId,\n createdAt: new Date().toISOString(),\n };\n await SecureStore.setItemAsync(CREDENTIAL_META_KEY, JSON.stringify(meta));\n\n return { encryptKey, credentialId, prfSupported: false };\n}\n\n/**\n * Retrieve the stored encryption key by authenticating with biometrics.\n * expo-secure-store automatically triggers the Face ID / Touch ID prompt\n * when requireAuthentication: true was used during storage.\n *\n * Returns null if no key is stored for the given userId.\n */\nexport async function getNativeWalletEncryptKey(\n userId: string\n): Promise<string | null> {\n const supported = await isNativeBiometricSupported();\n if (!supported) {\n throw new Error(\n \"Biometric authentication is not available or not enrolled on this device.\"\n );\n }\n\n return SecureStore.getItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`, {\n requireAuthentication: true,\n });\n}\n\n/**\n * Returns true if a native wallet passkey has been created on this device.\n */\nexport async function hasNativeWalletPasskey(): Promise<boolean> {\n const stored = await SecureStore.getItemAsync(CREDENTIAL_META_KEY);\n return stored !== null;\n}\n\n/**\n * Returns the stored credential metadata, or null if none exists.\n */\nexport async function getNativeWalletCredential(): Promise<NativeWalletCredential | null> {\n const stored = await SecureStore.getItemAsync(CREDENTIAL_META_KEY);\n if (!stored) return null;\n try {\n return JSON.parse(stored) as NativeWalletCredential;\n } catch {\n return null;\n }\n}\n\n/**\n * Removes the stored encryption key and credential metadata from this device.\n * Use with caution — if the wallet has no other recovery mechanism, this is destructive.\n */\nexport async function removeNativeWalletPasskey(userId: string): Promise<void> {\n await SecureStore.deleteItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`);\n await SecureStore.deleteItemAsync(CREDENTIAL_META_KEY);\n}\n","import { useMutation, type UseMutationResult } from \"@tanstack/react-query\";\nimport { useChipiContext } from \"@chipi-stack/chipi-react\";\nimport type {\n CreateWalletParams,\n CreateWalletResponse,\n} from \"@chipi-stack/types\";\nimport { createNativeWalletPasskey } from \"../native-passkey\";\n\ntype CreateWalletInput = {\n params: CreateWalletParams;\n bearerToken: string;\n};\n\n/**\n * Expo-native override of useCreateWallet.\n *\n * When usePasskey: true is passed, this uses expo-local-authentication +\n * expo-secure-store instead of the browser-only @simplewebauthn/browser,\n * so it works on real iOS and Android devices.\n */\nexport function useCreateWallet(): {\n createWallet: (input: CreateWalletInput) => void;\n createWalletAsync: (\n input: CreateWalletInput\n ) => Promise<CreateWalletResponse>;\n data: CreateWalletResponse | undefined;\n isLoading: boolean;\n isError: boolean;\n error: Error | null;\n isSuccess: boolean;\n reset: () => void;\n} {\n const { chipiSDK } = useChipiContext();\n\n const mutation: UseMutationResult<\n CreateWalletResponse,\n Error,\n CreateWalletInput\n > = useMutation({\n mutationFn: async (input: CreateWalletInput) => {\n let encryptKey = input.params.encryptKey;\n\n if (input.params.usePasskey) {\n if (!input.params.externalUserId) {\n throw new Error(\"externalUserId is required when using passkey\");\n }\n\n try {\n const passkeyResult = await createNativeWalletPasskey(\n input.params.externalUserId,\n input.params.externalUserId\n );\n encryptKey = passkeyResult.encryptKey;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Passkey creation failed: ${error.message}`);\n }\n throw new Error(\"Failed to create passkey for wallet\");\n }\n }\n\n return chipiSDK.createWallet({\n params: {\n ...input.params,\n encryptKey: encryptKey!,\n },\n bearerToken: input.bearerToken,\n });\n },\n });\n\n return {\n createWallet: mutation.mutate,\n createWalletAsync: mutation.mutateAsync,\n data: mutation.data,\n isLoading: mutation.isPending,\n isError: mutation.isError,\n error: mutation.error,\n isSuccess: mutation.isSuccess,\n reset: mutation.reset,\n };\n}\n","import { useMutation, type UseMutationResult } from \"@tanstack/react-query\";\nimport type { MigrateWalletToPasskeyParams, WalletData } from \"@chipi-stack/types\";\nimport { decryptPrivateKey, encryptPrivateKey } from \"@chipi-stack/backend\";\nimport { createNativeWalletPasskey } from \"../native-passkey\";\n\ntype MigrateWalletToPasskeyInput = MigrateWalletToPasskeyParams & {\n bearerToken: string;\n};\n\ninterface MigrateWalletToPasskeyResult {\n success: boolean;\n wallet: WalletData;\n credentialId: string;\n}\n\n/**\n * Expo-native override of useMigrateWalletToPasskey.\n *\n * Migrates a PIN-encrypted wallet to biometric (Face ID / Touch ID) protection.\n * Uses expo-local-authentication + expo-secure-store instead of browser WebAuthn.\n */\nexport function useMigrateWalletToPasskey(): {\n migrateWalletToPasskey: (input: MigrateWalletToPasskeyInput) => void;\n migrateWalletToPasskeyAsync: (\n input: MigrateWalletToPasskeyInput\n ) => Promise<MigrateWalletToPasskeyResult>;\n data: MigrateWalletToPasskeyResult | undefined;\n isLoading: boolean;\n isError: boolean;\n error: Error | null;\n isSuccess: boolean;\n reset: () => void;\n} {\n const mutation: UseMutationResult<\n MigrateWalletToPasskeyResult,\n Error,\n MigrateWalletToPasskeyInput\n > = useMutation({\n mutationFn: async (input: MigrateWalletToPasskeyInput) => {\n const { wallet, oldEncryptKey, externalUserId } = input;\n\n try {\n // Step 1: Create new native passkey and derive encryptKey via biometrics\n const passkeyResult = await createNativeWalletPasskey(\n externalUserId,\n externalUserId\n );\n\n // Step 2: Decrypt the private key with the old PIN/encryptKey\n let decryptedPrivateKey: string;\n try {\n decryptedPrivateKey = decryptPrivateKey(\n wallet.encryptedPrivateKey,\n oldEncryptKey\n );\n } catch {\n throw new Error(\n \"Failed to decrypt wallet with provided encryptKey. Please verify your PIN/password is correct.\"\n );\n }\n\n // Step 3: Re-encrypt with the new biometric-derived encryptKey\n const newEncryptedPrivateKey = encryptPrivateKey(\n decryptedPrivateKey,\n passkeyResult.encryptKey\n );\n\n const updatedWallet: WalletData = {\n ...wallet,\n encryptedPrivateKey: newEncryptedPrivateKey,\n };\n\n return {\n success: true,\n wallet: updatedWallet,\n credentialId: passkeyResult.credentialId,\n };\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Migration failed: ${error.message}`);\n }\n throw new Error(\"Failed to migrate wallet to passkey\");\n }\n },\n });\n\n return {\n migrateWalletToPasskey: mutation.mutate,\n migrateWalletToPasskeyAsync: mutation.mutateAsync,\n data: mutation.data,\n isLoading: mutation.isPending,\n isError: mutation.isError,\n error: mutation.error,\n isSuccess: mutation.isSuccess,\n reset: mutation.reset,\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/native-passkey.ts","../src/ChipiProvider.tsx","../src/hooks/useCreateWallet.ts","../src/hooks/useMigrateWalletToPasskey.ts"],"names":["CryptoES","LocalAuthentication","SecureStore","jsx","ReactChipiProvider","useChipiContext","useMutation","decryptPrivateKey","encryptPrivateKey"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Native Passkey for Expo/React Native\n *\n * Replaces the browser-only @simplewebauthn/browser implementation with a\n * native equivalent using:\n * - expo-local-authentication → biometric gate (Face ID / Touch ID / fingerprint)\n * - expo-secure-store → iOS Keychain / Android Keystore protected storage\n * - crypto-es → cryptographically secure random bytes (no Web Crypto in RN)\n *\n * Security model: a random 32-byte key is generated at wallet creation time,\n * stored in the device's secure enclave behind biometric authentication,\n * and retrieved later by prompting the user again. This is functionally\n * equivalent to the WebAuthn PRF approach used on the web.\n */\n\nimport * as LocalAuthentication from \"expo-local-authentication\";\nimport * as SecureStore from \"expo-secure-store\";\nimport CryptoES from \"crypto-es\";\n\nconst ENCRYPT_KEY_PREFIX = \"chipi_wallet_key_\";\nconst CREDENTIAL_META_KEY = \"chipi_wallet_credential\";\n\nexport interface NativeWalletCredential {\n credentialId: string;\n userId: string;\n createdAt: string;\n}\n\nexport interface NativeCreateWalletPasskeyResult {\n encryptKey: string;\n credentialId: string;\n prfSupported: false;\n}\n\n/**\n * Generate a cryptographically random hex string (32 bytes = 64 hex chars).\n * Uses crypto-es because React Native has no Web Crypto API (crypto is undefined).\n * Same approach as @chipi-stack/backend for consistency.\n */\nfunction generateRandomHex(byteCount: number): string {\n const wordArray = CryptoES.lib.WordArray.random(byteCount);\n return wordArray.toString(CryptoES.enc.Hex);\n}\n\n/**\n * Returns true if the device has biometric hardware AND the user has enrolled.\n * Must be true before calling createNativeWalletPasskey or getNativeWalletEncryptKey.\n */\nexport async function isNativeBiometricSupported(): Promise<boolean> {\n const hasHardware = await LocalAuthentication.hasHardwareAsync();\n if (!hasHardware) return false;\n const isEnrolled = await LocalAuthentication.isEnrolledAsync();\n return isEnrolled;\n}\n\n/**\n * Create a new native wallet passkey.\n *\n * 1. Verifies biometric support.\n * 2. Prompts the user with Face ID / Touch ID to confirm intent.\n * 3. Generates a random encryption key.\n * 4. Stores the key in the device's Keychain/Keystore, protected by biometrics.\n * 5. Returns the encryption key so the wallet can be created immediately.\n *\n * The key is NEVER stored anywhere else — it lives only in the secure enclave.\n */\nexport async function createNativeWalletPasskey(\n userId: string,\n _userName: string\n): Promise<NativeCreateWalletPasskeyResult> {\n const supported = await isNativeBiometricSupported();\n if (!supported) {\n throw new Error(\n \"Biometric authentication is not available or not enrolled on this device. \" +\n \"Please enroll Face ID, Touch ID, or a fingerprint in your device settings.\"\n );\n }\n\n // Prompt biometrics to confirm user intent before generating/storing the key\n const authResult = await LocalAuthentication.authenticateAsync({\n promptMessage: \"Authenticate to create your wallet\",\n cancelLabel: \"Cancel\",\n disableDeviceFallback: false,\n });\n\n if (!authResult.success) {\n const reason = \"error\" in authResult ? authResult.error : \"unknown\";\n if (reason === \"user_cancel\" || reason === \"app_cancel\") {\n throw new Error(\"Biometric authentication was cancelled\");\n }\n throw new Error(`Biometric authentication failed: ${reason}`);\n }\n\n const encryptKey = generateRandomHex(32);\n const credentialId = `native_biometric_${userId}_${Date.now()}`;\n\n // Store the encryption key — requireAuthentication means future reads need biometrics\n await SecureStore.setItemAsync(\n `${ENCRYPT_KEY_PREFIX}${userId}`,\n encryptKey,\n { requireAuthentication: true }\n );\n\n // Store lightweight credential metadata (not sensitive — no requireAuthentication needed)\n const meta: NativeWalletCredential = {\n credentialId,\n userId,\n createdAt: new Date().toISOString(),\n };\n await SecureStore.setItemAsync(CREDENTIAL_META_KEY, JSON.stringify(meta));\n\n return { encryptKey, credentialId, prfSupported: false };\n}\n\n/**\n * Retrieve the stored encryption key by authenticating with biometrics.\n * expo-secure-store automatically triggers the Face ID / Touch ID prompt\n * when requireAuthentication: true was used during storage.\n *\n * Returns null if no key is stored for the given userId.\n */\nexport async function getNativeWalletEncryptKey(\n userId: string\n): Promise<string | null> {\n const supported = await isNativeBiometricSupported();\n if (!supported) {\n throw new Error(\n \"Biometric authentication is not available or not enrolled on this device.\"\n );\n }\n\n return SecureStore.getItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`, {\n requireAuthentication: true,\n });\n}\n\n/**\n * Returns true if a native wallet passkey has been created on this device.\n */\nexport async function hasNativeWalletPasskey(): Promise<boolean> {\n const stored = await SecureStore.getItemAsync(CREDENTIAL_META_KEY);\n return stored !== null;\n}\n\n/**\n * Returns the stored credential metadata, or null if none exists.\n */\nexport async function getNativeWalletCredential(): Promise<NativeWalletCredential | null> {\n const stored = await SecureStore.getItemAsync(CREDENTIAL_META_KEY);\n if (!stored) return null;\n try {\n return JSON.parse(stored) as NativeWalletCredential;\n } catch {\n return null;\n }\n}\n\n/**\n * Removes the stored encryption key and credential metadata from this device.\n * Use with caution — if the wallet has no other recovery mechanism, this is destructive.\n */\nexport async function removeNativeWalletPasskey(userId: string): Promise<void> {\n await SecureStore.deleteItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`);\n await SecureStore.deleteItemAsync(CREDENTIAL_META_KEY);\n}\n","import type { ReactNode } from \"react\";\nimport { ChipiProvider as ReactChipiProvider } from \"@chipi-stack/chipi-react\";\nimport type { ChipiSDKConfig } from \"@chipi-stack/types\";\nimport { getNativeWalletEncryptKey } from \"./native-passkey\";\n\ninterface ChipiProviderProps {\n children: ReactNode;\n config: ChipiSDKConfig;\n}\n\nconst expoPasskeyAdapter = {\n getWalletEncryptKey: async (input: { externalUserId: string }) => {\n if (!input.externalUserId) {\n throw new Error(\n \"externalUserId is required when usePasskey is true in Expo. \" +\n \"Pass externalUserId in the hook params so the native key can be retrieved.\",\n );\n }\n\n return getNativeWalletEncryptKey(input.externalUserId);\n },\n};\n\n/**\n * Expo-aware provider that injects a native passkey adapter into chipi-react hooks.\n * This ensures useTransfer/useApprove/useCallAnyContract use native biometrics\n * instead of browser WebAuthn in React Native.\n */\nexport function ChipiProvider({ children, config }: ChipiProviderProps) {\n return (\n <ReactChipiProvider config={config} passkeyAdapter={expoPasskeyAdapter}>\n {children}\n </ReactChipiProvider>\n );\n}\n","import { useMutation, type UseMutationResult } from \"@tanstack/react-query\";\nimport { useChipiContext } from \"@chipi-stack/chipi-react\";\nimport type {\n CreateWalletParams,\n CreateWalletResponse,\n} from \"@chipi-stack/types\";\nimport { createNativeWalletPasskey } from \"../native-passkey\";\n\ntype CreateWalletInput = {\n params: CreateWalletParams;\n bearerToken: string;\n};\n\n/**\n * Expo-native override of useCreateWallet.\n *\n * When usePasskey: true is passed, this uses expo-local-authentication +\n * expo-secure-store instead of the browser-only @simplewebauthn/browser,\n * so it works on real iOS and Android devices.\n */\nexport function useCreateWallet(): {\n createWallet: (input: CreateWalletInput) => void;\n createWalletAsync: (\n input: CreateWalletInput,\n ) => Promise<CreateWalletResponse>;\n data: CreateWalletResponse | undefined;\n isLoading: boolean;\n isError: boolean;\n error: Error | null;\n isSuccess: boolean;\n reset: () => void;\n} {\n const { chipiSDK } = useChipiContext();\n\n const mutation: UseMutationResult<\n CreateWalletResponse,\n Error,\n CreateWalletInput\n > = useMutation({\n mutationFn: async (input: CreateWalletInput) => {\n let encryptKey = input.params.encryptKey;\n\n if (input.params.usePasskey) {\n if (!input.params.externalUserId) {\n throw new Error(\"externalUserId is required when using passkey\");\n }\n\n try {\n const passkeyResult = await createNativeWalletPasskey(\n input.params.externalUserId,\n input.params.externalUserId,\n );\n encryptKey = passkeyResult.encryptKey;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Passkey creation failed: ${error.message}`);\n }\n throw new Error(\"Failed to create passkey for wallet\");\n }\n }\n\n return chipiSDK.createWallet({\n params: {\n ...input.params,\n encryptKey: encryptKey!,\n },\n bearerToken: input.bearerToken,\n });\n },\n });\n\n return {\n createWallet: mutation.mutate,\n createWalletAsync: mutation.mutateAsync,\n data: mutation.data,\n isLoading: mutation.isPending,\n isError: mutation.isError,\n error: mutation.error,\n isSuccess: mutation.isSuccess,\n reset: mutation.reset,\n };\n}\n","import { useMutation, type UseMutationResult } from \"@tanstack/react-query\";\nimport type {\n MigrateWalletToPasskeyParams,\n WalletData,\n} from \"@chipi-stack/types\";\nimport { decryptPrivateKey, encryptPrivateKey } from \"@chipi-stack/backend\";\nimport { createNativeWalletPasskey } from \"../native-passkey\";\n\ntype MigrateWalletToPasskeyInput = MigrateWalletToPasskeyParams & {\n bearerToken: string;\n};\n\ninterface MigrateWalletToPasskeyResult {\n success: boolean;\n wallet: WalletData;\n credentialId: string;\n}\n\n/**\n * Expo-native override of useMigrateWalletToPasskey.\n *\n * Migrates a PIN-encrypted wallet to biometric (Face ID / Touch ID) protection.\n * Uses expo-local-authentication + expo-secure-store instead of browser WebAuthn.\n */\nexport function useMigrateWalletToPasskey(): {\n migrateWalletToPasskey: (input: MigrateWalletToPasskeyInput) => void;\n migrateWalletToPasskeyAsync: (\n input: MigrateWalletToPasskeyInput,\n ) => Promise<MigrateWalletToPasskeyResult>;\n data: MigrateWalletToPasskeyResult | undefined;\n isLoading: boolean;\n isError: boolean;\n error: Error | null;\n isSuccess: boolean;\n reset: () => void;\n} {\n const mutation: UseMutationResult<\n MigrateWalletToPasskeyResult,\n Error,\n MigrateWalletToPasskeyInput\n > = useMutation({\n mutationFn: async (input: MigrateWalletToPasskeyInput) => {\n const { wallet, oldEncryptKey, externalUserId } = input;\n\n try {\n // Step 1: Create new native passkey and derive encryptKey via biometrics\n const passkeyResult = await createNativeWalletPasskey(\n externalUserId,\n externalUserId,\n );\n\n // Step 2: Decrypt the private key with the old PIN/encryptKey\n let decryptedPrivateKey: string;\n try {\n decryptedPrivateKey = decryptPrivateKey(\n wallet.encryptedPrivateKey,\n oldEncryptKey,\n );\n } catch {\n throw new Error(\n \"Failed to decrypt wallet with provided encryptKey. Please verify your PIN/password is correct.\",\n );\n }\n\n // Step 3: Re-encrypt with the new biometric-derived encryptKey\n const newEncryptedPrivateKey = encryptPrivateKey(\n decryptedPrivateKey,\n passkeyResult.encryptKey,\n );\n\n const updatedWallet: WalletData = {\n ...wallet,\n encryptedPrivateKey: newEncryptedPrivateKey,\n };\n\n return {\n success: true,\n wallet: updatedWallet,\n credentialId: passkeyResult.credentialId,\n };\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Migration failed: ${error.message}`);\n }\n throw new Error(\"Failed to migrate wallet to passkey\");\n }\n },\n });\n\n return {\n migrateWalletToPasskey: mutation.mutate,\n migrateWalletToPasskeyAsync: mutation.mutateAsync,\n data: mutation.data,\n isLoading: mutation.isPending,\n isError: mutation.isError,\n error: mutation.error,\n isSuccess: mutation.isSuccess,\n reset: mutation.reset,\n };\n}\n"]}
package/dist/index.mjs CHANGED
@@ -1,18 +1,19 @@
1
- import { useChipiContext } from '@chipi-stack/chipi-react';
2
- export { ChipiProvider, useAddSessionKeyToContract, useApprove, useCallAnyContract, useChipiContext, useChipiSession, useChipiWallet, useCreateSessionKey, useExecuteWithSession, useGetSessionData, useGetSku, useGetSkuList, useGetSkuPurchase, useGetTokenBalance, useGetTransactionList, useGetWallet, usePurchaseSku, useRecordSendTransaction, useRevokeSessionKey, useTransfer } from '@chipi-stack/chipi-react';
3
- import { useMutation } from '@tanstack/react-query';
1
+ import { ChipiProvider as ChipiProvider$1, useChipiContext } from '@chipi-stack/chipi-react';
2
+ export { useAddSessionKeyToContract, useApprove, useCallAnyContract, useChipiContext, useChipiSession, useChipiWallet, useCreateSessionKey, useExecuteWithSession, useGetSessionData, useGetSku, useGetSkuList, useGetSkuPurchase, useGetTokenBalance, useGetTransactionList, useGetWallet, usePurchaseSku, useRecordSendTransaction, useRevokeSessionKey, useTransfer } from '@chipi-stack/chipi-react';
4
3
  import * as LocalAuthentication from 'expo-local-authentication';
5
4
  import * as SecureStore from 'expo-secure-store';
5
+ import CryptoES from 'crypto-es';
6
+ import { jsx } from 'react/jsx-runtime';
7
+ import { useMutation } from '@tanstack/react-query';
6
8
  import { decryptPrivateKey, encryptPrivateKey } from '@chipi-stack/backend';
7
9
  export { Chain, ChainToken } from '@chipi-stack/types';
8
10
 
9
- // src/index.ts
11
+ // src/ChipiProvider.tsx
10
12
  var ENCRYPT_KEY_PREFIX = "chipi_wallet_key_";
11
13
  var CREDENTIAL_META_KEY = "chipi_wallet_credential";
12
14
  function generateRandomHex(byteCount) {
13
- const array = new Uint8Array(byteCount);
14
- crypto.getRandomValues(array);
15
- return Array.from(array).map((b) => b.toString(16).padStart(2, "0")).join("");
15
+ const wordArray = CryptoES.lib.WordArray.random(byteCount);
16
+ return wordArray.toString(CryptoES.enc.Hex);
16
17
  }
17
18
  async function isNativeBiometricSupported() {
18
19
  const hasHardware = await LocalAuthentication.hasHardwareAsync();
@@ -82,8 +83,19 @@ async function removeNativeWalletPasskey(userId) {
82
83
  await SecureStore.deleteItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`);
83
84
  await SecureStore.deleteItemAsync(CREDENTIAL_META_KEY);
84
85
  }
85
-
86
- // src/hooks/useCreateWallet.ts
86
+ var expoPasskeyAdapter = {
87
+ getWalletEncryptKey: async (input) => {
88
+ if (!input.externalUserId) {
89
+ throw new Error(
90
+ "externalUserId is required when usePasskey is true in Expo. Pass externalUserId in the hook params so the native key can be retrieved."
91
+ );
92
+ }
93
+ return getNativeWalletEncryptKey(input.externalUserId);
94
+ }
95
+ };
96
+ function ChipiProvider({ children, config }) {
97
+ return /* @__PURE__ */ jsx(ChipiProvider$1, { config, passkeyAdapter: expoPasskeyAdapter, children });
98
+ }
87
99
  function useCreateWallet() {
88
100
  const { chipiSDK } = useChipiContext();
89
101
  const mutation = useMutation({
@@ -179,6 +191,6 @@ function useMigrateWalletToPasskey() {
179
191
  };
180
192
  }
181
193
 
182
- export { createNativeWalletPasskey, getNativeWalletCredential, getNativeWalletEncryptKey, hasNativeWalletPasskey, isNativeBiometricSupported, removeNativeWalletPasskey, useCreateWallet, useMigrateWalletToPasskey };
194
+ export { ChipiProvider, createNativeWalletPasskey, getNativeWalletCredential, getNativeWalletEncryptKey, hasNativeWalletPasskey, isNativeBiometricSupported, removeNativeWalletPasskey, useCreateWallet, useMigrateWalletToPasskey };
183
195
  //# sourceMappingURL=index.mjs.map
184
196
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/native-passkey.ts","../src/hooks/useCreateWallet.ts","../src/hooks/useMigrateWalletToPasskey.ts"],"names":["useMutation"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Native Passkey for Expo/React Native\n *\n * Replaces the browser-only @simplewebauthn/browser implementation with a\n * native equivalent using:\n * - expo-local-authentication → biometric gate (Face ID / Touch ID / fingerprint)\n * - expo-secure-store → iOS Keychain / Android Keystore protected storage\n *\n * Security model: a random 32-byte key is generated at wallet creation time,\n * stored in the device's secure enclave behind biometric authentication,\n * and retrieved later by prompting the user again. This is functionally\n * equivalent to the WebAuthn PRF approach used on the web.\n */\n\nimport * as LocalAuthentication from \"expo-local-authentication\";\nimport * as SecureStore from \"expo-secure-store\";\n\nconst ENCRYPT_KEY_PREFIX = \"chipi_wallet_key_\";\nconst CREDENTIAL_META_KEY = \"chipi_wallet_credential\";\n\nexport interface NativeWalletCredential {\n credentialId: string;\n userId: string;\n createdAt: string;\n}\n\nexport interface NativeCreateWalletPasskeyResult {\n encryptKey: string;\n credentialId: string;\n prfSupported: false;\n}\n\n/**\n * Generate a cryptographically random 32-byte hex string.\n * crypto.getRandomValues is available in React Native via Hermes (RN 0.71+).\n */\nfunction generateRandomHex(byteCount: number): string {\n const array = new Uint8Array(byteCount);\n crypto.getRandomValues(array);\n return Array.from(array)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\n/**\n * Returns true if the device has biometric hardware AND the user has enrolled.\n * Must be true before calling createNativeWalletPasskey or getNativeWalletEncryptKey.\n */\nexport async function isNativeBiometricSupported(): Promise<boolean> {\n const hasHardware = await LocalAuthentication.hasHardwareAsync();\n if (!hasHardware) return false;\n const isEnrolled = await LocalAuthentication.isEnrolledAsync();\n return isEnrolled;\n}\n\n/**\n * Create a new native wallet passkey.\n *\n * 1. Verifies biometric support.\n * 2. Prompts the user with Face ID / Touch ID to confirm intent.\n * 3. Generates a random encryption key.\n * 4. Stores the key in the device's Keychain/Keystore, protected by biometrics.\n * 5. Returns the encryption key so the wallet can be created immediately.\n *\n * The key is NEVER stored anywhere else — it lives only in the secure enclave.\n */\nexport async function createNativeWalletPasskey(\n userId: string,\n _userName: string\n): Promise<NativeCreateWalletPasskeyResult> {\n const supported = await isNativeBiometricSupported();\n if (!supported) {\n throw new Error(\n \"Biometric authentication is not available or not enrolled on this device. \" +\n \"Please enroll Face ID, Touch ID, or a fingerprint in your device settings.\"\n );\n }\n\n // Prompt biometrics to confirm user intent before generating/storing the key\n const authResult = await LocalAuthentication.authenticateAsync({\n promptMessage: \"Authenticate to create your wallet\",\n cancelLabel: \"Cancel\",\n disableDeviceFallback: false,\n });\n\n if (!authResult.success) {\n const reason = \"error\" in authResult ? authResult.error : \"unknown\";\n if (reason === \"user_cancel\" || reason === \"app_cancel\") {\n throw new Error(\"Biometric authentication was cancelled\");\n }\n throw new Error(`Biometric authentication failed: ${reason}`);\n }\n\n const encryptKey = generateRandomHex(32);\n const credentialId = `native_biometric_${userId}_${Date.now()}`;\n\n // Store the encryption key — requireAuthentication means future reads need biometrics\n await SecureStore.setItemAsync(\n `${ENCRYPT_KEY_PREFIX}${userId}`,\n encryptKey,\n { requireAuthentication: true }\n );\n\n // Store lightweight credential metadata (not sensitive — no requireAuthentication needed)\n const meta: NativeWalletCredential = {\n credentialId,\n userId,\n createdAt: new Date().toISOString(),\n };\n await SecureStore.setItemAsync(CREDENTIAL_META_KEY, JSON.stringify(meta));\n\n return { encryptKey, credentialId, prfSupported: false };\n}\n\n/**\n * Retrieve the stored encryption key by authenticating with biometrics.\n * expo-secure-store automatically triggers the Face ID / Touch ID prompt\n * when requireAuthentication: true was used during storage.\n *\n * Returns null if no key is stored for the given userId.\n */\nexport async function getNativeWalletEncryptKey(\n userId: string\n): Promise<string | null> {\n const supported = await isNativeBiometricSupported();\n if (!supported) {\n throw new Error(\n \"Biometric authentication is not available or not enrolled on this device.\"\n );\n }\n\n return SecureStore.getItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`, {\n requireAuthentication: true,\n });\n}\n\n/**\n * Returns true if a native wallet passkey has been created on this device.\n */\nexport async function hasNativeWalletPasskey(): Promise<boolean> {\n const stored = await SecureStore.getItemAsync(CREDENTIAL_META_KEY);\n return stored !== null;\n}\n\n/**\n * Returns the stored credential metadata, or null if none exists.\n */\nexport async function getNativeWalletCredential(): Promise<NativeWalletCredential | null> {\n const stored = await SecureStore.getItemAsync(CREDENTIAL_META_KEY);\n if (!stored) return null;\n try {\n return JSON.parse(stored) as NativeWalletCredential;\n } catch {\n return null;\n }\n}\n\n/**\n * Removes the stored encryption key and credential metadata from this device.\n * Use with caution — if the wallet has no other recovery mechanism, this is destructive.\n */\nexport async function removeNativeWalletPasskey(userId: string): Promise<void> {\n await SecureStore.deleteItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`);\n await SecureStore.deleteItemAsync(CREDENTIAL_META_KEY);\n}\n","import { useMutation, type UseMutationResult } from \"@tanstack/react-query\";\nimport { useChipiContext } from \"@chipi-stack/chipi-react\";\nimport type {\n CreateWalletParams,\n CreateWalletResponse,\n} from \"@chipi-stack/types\";\nimport { createNativeWalletPasskey } from \"../native-passkey\";\n\ntype CreateWalletInput = {\n params: CreateWalletParams;\n bearerToken: string;\n};\n\n/**\n * Expo-native override of useCreateWallet.\n *\n * When usePasskey: true is passed, this uses expo-local-authentication +\n * expo-secure-store instead of the browser-only @simplewebauthn/browser,\n * so it works on real iOS and Android devices.\n */\nexport function useCreateWallet(): {\n createWallet: (input: CreateWalletInput) => void;\n createWalletAsync: (\n input: CreateWalletInput\n ) => Promise<CreateWalletResponse>;\n data: CreateWalletResponse | undefined;\n isLoading: boolean;\n isError: boolean;\n error: Error | null;\n isSuccess: boolean;\n reset: () => void;\n} {\n const { chipiSDK } = useChipiContext();\n\n const mutation: UseMutationResult<\n CreateWalletResponse,\n Error,\n CreateWalletInput\n > = useMutation({\n mutationFn: async (input: CreateWalletInput) => {\n let encryptKey = input.params.encryptKey;\n\n if (input.params.usePasskey) {\n if (!input.params.externalUserId) {\n throw new Error(\"externalUserId is required when using passkey\");\n }\n\n try {\n const passkeyResult = await createNativeWalletPasskey(\n input.params.externalUserId,\n input.params.externalUserId\n );\n encryptKey = passkeyResult.encryptKey;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Passkey creation failed: ${error.message}`);\n }\n throw new Error(\"Failed to create passkey for wallet\");\n }\n }\n\n return chipiSDK.createWallet({\n params: {\n ...input.params,\n encryptKey: encryptKey!,\n },\n bearerToken: input.bearerToken,\n });\n },\n });\n\n return {\n createWallet: mutation.mutate,\n createWalletAsync: mutation.mutateAsync,\n data: mutation.data,\n isLoading: mutation.isPending,\n isError: mutation.isError,\n error: mutation.error,\n isSuccess: mutation.isSuccess,\n reset: mutation.reset,\n };\n}\n","import { useMutation, type UseMutationResult } from \"@tanstack/react-query\";\nimport type { MigrateWalletToPasskeyParams, WalletData } from \"@chipi-stack/types\";\nimport { decryptPrivateKey, encryptPrivateKey } from \"@chipi-stack/backend\";\nimport { createNativeWalletPasskey } from \"../native-passkey\";\n\ntype MigrateWalletToPasskeyInput = MigrateWalletToPasskeyParams & {\n bearerToken: string;\n};\n\ninterface MigrateWalletToPasskeyResult {\n success: boolean;\n wallet: WalletData;\n credentialId: string;\n}\n\n/**\n * Expo-native override of useMigrateWalletToPasskey.\n *\n * Migrates a PIN-encrypted wallet to biometric (Face ID / Touch ID) protection.\n * Uses expo-local-authentication + expo-secure-store instead of browser WebAuthn.\n */\nexport function useMigrateWalletToPasskey(): {\n migrateWalletToPasskey: (input: MigrateWalletToPasskeyInput) => void;\n migrateWalletToPasskeyAsync: (\n input: MigrateWalletToPasskeyInput\n ) => Promise<MigrateWalletToPasskeyResult>;\n data: MigrateWalletToPasskeyResult | undefined;\n isLoading: boolean;\n isError: boolean;\n error: Error | null;\n isSuccess: boolean;\n reset: () => void;\n} {\n const mutation: UseMutationResult<\n MigrateWalletToPasskeyResult,\n Error,\n MigrateWalletToPasskeyInput\n > = useMutation({\n mutationFn: async (input: MigrateWalletToPasskeyInput) => {\n const { wallet, oldEncryptKey, externalUserId } = input;\n\n try {\n // Step 1: Create new native passkey and derive encryptKey via biometrics\n const passkeyResult = await createNativeWalletPasskey(\n externalUserId,\n externalUserId\n );\n\n // Step 2: Decrypt the private key with the old PIN/encryptKey\n let decryptedPrivateKey: string;\n try {\n decryptedPrivateKey = decryptPrivateKey(\n wallet.encryptedPrivateKey,\n oldEncryptKey\n );\n } catch {\n throw new Error(\n \"Failed to decrypt wallet with provided encryptKey. Please verify your PIN/password is correct.\"\n );\n }\n\n // Step 3: Re-encrypt with the new biometric-derived encryptKey\n const newEncryptedPrivateKey = encryptPrivateKey(\n decryptedPrivateKey,\n passkeyResult.encryptKey\n );\n\n const updatedWallet: WalletData = {\n ...wallet,\n encryptedPrivateKey: newEncryptedPrivateKey,\n };\n\n return {\n success: true,\n wallet: updatedWallet,\n credentialId: passkeyResult.credentialId,\n };\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Migration failed: ${error.message}`);\n }\n throw new Error(\"Failed to migrate wallet to passkey\");\n }\n },\n });\n\n return {\n migrateWalletToPasskey: mutation.mutate,\n migrateWalletToPasskeyAsync: mutation.mutateAsync,\n data: mutation.data,\n isLoading: mutation.isPending,\n isError: mutation.isError,\n error: mutation.error,\n isSuccess: mutation.isSuccess,\n reset: mutation.reset,\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/native-passkey.ts","../src/ChipiProvider.tsx","../src/hooks/useCreateWallet.ts","../src/hooks/useMigrateWalletToPasskey.ts"],"names":["ReactChipiProvider","useMutation"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Native Passkey for Expo/React Native\n *\n * Replaces the browser-only @simplewebauthn/browser implementation with a\n * native equivalent using:\n * - expo-local-authentication → biometric gate (Face ID / Touch ID / fingerprint)\n * - expo-secure-store → iOS Keychain / Android Keystore protected storage\n * - crypto-es → cryptographically secure random bytes (no Web Crypto in RN)\n *\n * Security model: a random 32-byte key is generated at wallet creation time,\n * stored in the device's secure enclave behind biometric authentication,\n * and retrieved later by prompting the user again. This is functionally\n * equivalent to the WebAuthn PRF approach used on the web.\n */\n\nimport * as LocalAuthentication from \"expo-local-authentication\";\nimport * as SecureStore from \"expo-secure-store\";\nimport CryptoES from \"crypto-es\";\n\nconst ENCRYPT_KEY_PREFIX = \"chipi_wallet_key_\";\nconst CREDENTIAL_META_KEY = \"chipi_wallet_credential\";\n\nexport interface NativeWalletCredential {\n credentialId: string;\n userId: string;\n createdAt: string;\n}\n\nexport interface NativeCreateWalletPasskeyResult {\n encryptKey: string;\n credentialId: string;\n prfSupported: false;\n}\n\n/**\n * Generate a cryptographically random hex string (32 bytes = 64 hex chars).\n * Uses crypto-es because React Native has no Web Crypto API (crypto is undefined).\n * Same approach as @chipi-stack/backend for consistency.\n */\nfunction generateRandomHex(byteCount: number): string {\n const wordArray = CryptoES.lib.WordArray.random(byteCount);\n return wordArray.toString(CryptoES.enc.Hex);\n}\n\n/**\n * Returns true if the device has biometric hardware AND the user has enrolled.\n * Must be true before calling createNativeWalletPasskey or getNativeWalletEncryptKey.\n */\nexport async function isNativeBiometricSupported(): Promise<boolean> {\n const hasHardware = await LocalAuthentication.hasHardwareAsync();\n if (!hasHardware) return false;\n const isEnrolled = await LocalAuthentication.isEnrolledAsync();\n return isEnrolled;\n}\n\n/**\n * Create a new native wallet passkey.\n *\n * 1. Verifies biometric support.\n * 2. Prompts the user with Face ID / Touch ID to confirm intent.\n * 3. Generates a random encryption key.\n * 4. Stores the key in the device's Keychain/Keystore, protected by biometrics.\n * 5. Returns the encryption key so the wallet can be created immediately.\n *\n * The key is NEVER stored anywhere else — it lives only in the secure enclave.\n */\nexport async function createNativeWalletPasskey(\n userId: string,\n _userName: string\n): Promise<NativeCreateWalletPasskeyResult> {\n const supported = await isNativeBiometricSupported();\n if (!supported) {\n throw new Error(\n \"Biometric authentication is not available or not enrolled on this device. \" +\n \"Please enroll Face ID, Touch ID, or a fingerprint in your device settings.\"\n );\n }\n\n // Prompt biometrics to confirm user intent before generating/storing the key\n const authResult = await LocalAuthentication.authenticateAsync({\n promptMessage: \"Authenticate to create your wallet\",\n cancelLabel: \"Cancel\",\n disableDeviceFallback: false,\n });\n\n if (!authResult.success) {\n const reason = \"error\" in authResult ? authResult.error : \"unknown\";\n if (reason === \"user_cancel\" || reason === \"app_cancel\") {\n throw new Error(\"Biometric authentication was cancelled\");\n }\n throw new Error(`Biometric authentication failed: ${reason}`);\n }\n\n const encryptKey = generateRandomHex(32);\n const credentialId = `native_biometric_${userId}_${Date.now()}`;\n\n // Store the encryption key — requireAuthentication means future reads need biometrics\n await SecureStore.setItemAsync(\n `${ENCRYPT_KEY_PREFIX}${userId}`,\n encryptKey,\n { requireAuthentication: true }\n );\n\n // Store lightweight credential metadata (not sensitive — no requireAuthentication needed)\n const meta: NativeWalletCredential = {\n credentialId,\n userId,\n createdAt: new Date().toISOString(),\n };\n await SecureStore.setItemAsync(CREDENTIAL_META_KEY, JSON.stringify(meta));\n\n return { encryptKey, credentialId, prfSupported: false };\n}\n\n/**\n * Retrieve the stored encryption key by authenticating with biometrics.\n * expo-secure-store automatically triggers the Face ID / Touch ID prompt\n * when requireAuthentication: true was used during storage.\n *\n * Returns null if no key is stored for the given userId.\n */\nexport async function getNativeWalletEncryptKey(\n userId: string\n): Promise<string | null> {\n const supported = await isNativeBiometricSupported();\n if (!supported) {\n throw new Error(\n \"Biometric authentication is not available or not enrolled on this device.\"\n );\n }\n\n return SecureStore.getItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`, {\n requireAuthentication: true,\n });\n}\n\n/**\n * Returns true if a native wallet passkey has been created on this device.\n */\nexport async function hasNativeWalletPasskey(): Promise<boolean> {\n const stored = await SecureStore.getItemAsync(CREDENTIAL_META_KEY);\n return stored !== null;\n}\n\n/**\n * Returns the stored credential metadata, or null if none exists.\n */\nexport async function getNativeWalletCredential(): Promise<NativeWalletCredential | null> {\n const stored = await SecureStore.getItemAsync(CREDENTIAL_META_KEY);\n if (!stored) return null;\n try {\n return JSON.parse(stored) as NativeWalletCredential;\n } catch {\n return null;\n }\n}\n\n/**\n * Removes the stored encryption key and credential metadata from this device.\n * Use with caution — if the wallet has no other recovery mechanism, this is destructive.\n */\nexport async function removeNativeWalletPasskey(userId: string): Promise<void> {\n await SecureStore.deleteItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`);\n await SecureStore.deleteItemAsync(CREDENTIAL_META_KEY);\n}\n","import type { ReactNode } from \"react\";\nimport { ChipiProvider as ReactChipiProvider } from \"@chipi-stack/chipi-react\";\nimport type { ChipiSDKConfig } from \"@chipi-stack/types\";\nimport { getNativeWalletEncryptKey } from \"./native-passkey\";\n\ninterface ChipiProviderProps {\n children: ReactNode;\n config: ChipiSDKConfig;\n}\n\nconst expoPasskeyAdapter = {\n getWalletEncryptKey: async (input: { externalUserId: string }) => {\n if (!input.externalUserId) {\n throw new Error(\n \"externalUserId is required when usePasskey is true in Expo. \" +\n \"Pass externalUserId in the hook params so the native key can be retrieved.\",\n );\n }\n\n return getNativeWalletEncryptKey(input.externalUserId);\n },\n};\n\n/**\n * Expo-aware provider that injects a native passkey adapter into chipi-react hooks.\n * This ensures useTransfer/useApprove/useCallAnyContract use native biometrics\n * instead of browser WebAuthn in React Native.\n */\nexport function ChipiProvider({ children, config }: ChipiProviderProps) {\n return (\n <ReactChipiProvider config={config} passkeyAdapter={expoPasskeyAdapter}>\n {children}\n </ReactChipiProvider>\n );\n}\n","import { useMutation, type UseMutationResult } from \"@tanstack/react-query\";\nimport { useChipiContext } from \"@chipi-stack/chipi-react\";\nimport type {\n CreateWalletParams,\n CreateWalletResponse,\n} from \"@chipi-stack/types\";\nimport { createNativeWalletPasskey } from \"../native-passkey\";\n\ntype CreateWalletInput = {\n params: CreateWalletParams;\n bearerToken: string;\n};\n\n/**\n * Expo-native override of useCreateWallet.\n *\n * When usePasskey: true is passed, this uses expo-local-authentication +\n * expo-secure-store instead of the browser-only @simplewebauthn/browser,\n * so it works on real iOS and Android devices.\n */\nexport function useCreateWallet(): {\n createWallet: (input: CreateWalletInput) => void;\n createWalletAsync: (\n input: CreateWalletInput,\n ) => Promise<CreateWalletResponse>;\n data: CreateWalletResponse | undefined;\n isLoading: boolean;\n isError: boolean;\n error: Error | null;\n isSuccess: boolean;\n reset: () => void;\n} {\n const { chipiSDK } = useChipiContext();\n\n const mutation: UseMutationResult<\n CreateWalletResponse,\n Error,\n CreateWalletInput\n > = useMutation({\n mutationFn: async (input: CreateWalletInput) => {\n let encryptKey = input.params.encryptKey;\n\n if (input.params.usePasskey) {\n if (!input.params.externalUserId) {\n throw new Error(\"externalUserId is required when using passkey\");\n }\n\n try {\n const passkeyResult = await createNativeWalletPasskey(\n input.params.externalUserId,\n input.params.externalUserId,\n );\n encryptKey = passkeyResult.encryptKey;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Passkey creation failed: ${error.message}`);\n }\n throw new Error(\"Failed to create passkey for wallet\");\n }\n }\n\n return chipiSDK.createWallet({\n params: {\n ...input.params,\n encryptKey: encryptKey!,\n },\n bearerToken: input.bearerToken,\n });\n },\n });\n\n return {\n createWallet: mutation.mutate,\n createWalletAsync: mutation.mutateAsync,\n data: mutation.data,\n isLoading: mutation.isPending,\n isError: mutation.isError,\n error: mutation.error,\n isSuccess: mutation.isSuccess,\n reset: mutation.reset,\n };\n}\n","import { useMutation, type UseMutationResult } from \"@tanstack/react-query\";\nimport type {\n MigrateWalletToPasskeyParams,\n WalletData,\n} from \"@chipi-stack/types\";\nimport { decryptPrivateKey, encryptPrivateKey } from \"@chipi-stack/backend\";\nimport { createNativeWalletPasskey } from \"../native-passkey\";\n\ntype MigrateWalletToPasskeyInput = MigrateWalletToPasskeyParams & {\n bearerToken: string;\n};\n\ninterface MigrateWalletToPasskeyResult {\n success: boolean;\n wallet: WalletData;\n credentialId: string;\n}\n\n/**\n * Expo-native override of useMigrateWalletToPasskey.\n *\n * Migrates a PIN-encrypted wallet to biometric (Face ID / Touch ID) protection.\n * Uses expo-local-authentication + expo-secure-store instead of browser WebAuthn.\n */\nexport function useMigrateWalletToPasskey(): {\n migrateWalletToPasskey: (input: MigrateWalletToPasskeyInput) => void;\n migrateWalletToPasskeyAsync: (\n input: MigrateWalletToPasskeyInput,\n ) => Promise<MigrateWalletToPasskeyResult>;\n data: MigrateWalletToPasskeyResult | undefined;\n isLoading: boolean;\n isError: boolean;\n error: Error | null;\n isSuccess: boolean;\n reset: () => void;\n} {\n const mutation: UseMutationResult<\n MigrateWalletToPasskeyResult,\n Error,\n MigrateWalletToPasskeyInput\n > = useMutation({\n mutationFn: async (input: MigrateWalletToPasskeyInput) => {\n const { wallet, oldEncryptKey, externalUserId } = input;\n\n try {\n // Step 1: Create new native passkey and derive encryptKey via biometrics\n const passkeyResult = await createNativeWalletPasskey(\n externalUserId,\n externalUserId,\n );\n\n // Step 2: Decrypt the private key with the old PIN/encryptKey\n let decryptedPrivateKey: string;\n try {\n decryptedPrivateKey = decryptPrivateKey(\n wallet.encryptedPrivateKey,\n oldEncryptKey,\n );\n } catch {\n throw new Error(\n \"Failed to decrypt wallet with provided encryptKey. Please verify your PIN/password is correct.\",\n );\n }\n\n // Step 3: Re-encrypt with the new biometric-derived encryptKey\n const newEncryptedPrivateKey = encryptPrivateKey(\n decryptedPrivateKey,\n passkeyResult.encryptKey,\n );\n\n const updatedWallet: WalletData = {\n ...wallet,\n encryptedPrivateKey: newEncryptedPrivateKey,\n };\n\n return {\n success: true,\n wallet: updatedWallet,\n credentialId: passkeyResult.credentialId,\n };\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Migration failed: ${error.message}`);\n }\n throw new Error(\"Failed to migrate wallet to passkey\");\n }\n },\n });\n\n return {\n migrateWalletToPasskey: mutation.mutate,\n migrateWalletToPasskeyAsync: mutation.mutateAsync,\n data: mutation.data,\n isLoading: mutation.isPending,\n isError: mutation.isError,\n error: mutation.error,\n isSuccess: mutation.isSuccess,\n reset: mutation.reset,\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chipi-stack/chipi-expo",
3
- "version": "13.11.0",
3
+ "version": "13.13.0",
4
4
  "description": "Chipi SDK for React Native and Expo applications",
5
5
  "keywords": [
6
6
  "chipi",
@@ -55,10 +55,11 @@
55
55
  },
56
56
  "dependencies": {
57
57
  "@tanstack/react-query": "^5.85.0",
58
- "@chipi-stack/backend": "^13.10.0",
59
- "@chipi-stack/chipi-react": "^13.10.0",
60
- "@chipi-stack/types": "^13.11.0",
61
- "@chipi-stack/shared": "^13.10.0"
58
+ "crypto-es": "^2.1.0",
59
+ "@chipi-stack/backend": "^13.12.0",
60
+ "@chipi-stack/chipi-react": "^13.12.0",
61
+ "@chipi-stack/types": "^13.13.0",
62
+ "@chipi-stack/shared": "^13.12.0"
62
63
  },
63
64
  "peerDependencies": {
64
65
  "expo-local-authentication": ">=14.0.0",