@chipi-stack/chipi-expo 13.12.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 +16 -3
- package/dist/index.d.ts +16 -3
- package/dist/index.js +17 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +19 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
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;
|
|
@@ -113,4 +126,4 @@ declare function getNativeWalletCredential(): Promise<NativeWalletCredential | n
|
|
|
113
126
|
*/
|
|
114
127
|
declare function removeNativeWalletPasskey(userId: string): Promise<void>;
|
|
115
128
|
|
|
116
|
-
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
|
-
|
|
2
|
-
import {
|
|
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;
|
|
@@ -113,4 +126,4 @@ declare function getNativeWalletCredential(): Promise<NativeWalletCredential | n
|
|
|
113
126
|
*/
|
|
114
127
|
declare function removeNativeWalletPasskey(userId: string): Promise<void>;
|
|
115
128
|
|
|
116
|
-
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,10 +1,11 @@
|
|
|
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');
|
|
7
6
|
var CryptoES = require('crypto-es');
|
|
7
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
8
|
+
var reactQuery = require('@tanstack/react-query');
|
|
8
9
|
var backend = require('@chipi-stack/backend');
|
|
9
10
|
var types = require('@chipi-stack/types');
|
|
10
11
|
|
|
@@ -32,7 +33,7 @@ var LocalAuthentication__namespace = /*#__PURE__*/_interopNamespace(LocalAuthent
|
|
|
32
33
|
var SecureStore__namespace = /*#__PURE__*/_interopNamespace(SecureStore);
|
|
33
34
|
var CryptoES__default = /*#__PURE__*/_interopDefault(CryptoES);
|
|
34
35
|
|
|
35
|
-
// src/
|
|
36
|
+
// src/ChipiProvider.tsx
|
|
36
37
|
var ENCRYPT_KEY_PREFIX = "chipi_wallet_key_";
|
|
37
38
|
var CREDENTIAL_META_KEY = "chipi_wallet_credential";
|
|
38
39
|
function generateRandomHex(byteCount) {
|
|
@@ -107,8 +108,19 @@ async function removeNativeWalletPasskey(userId) {
|
|
|
107
108
|
await SecureStore__namespace.deleteItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`);
|
|
108
109
|
await SecureStore__namespace.deleteItemAsync(CREDENTIAL_META_KEY);
|
|
109
110
|
}
|
|
110
|
-
|
|
111
|
-
|
|
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
|
+
}
|
|
112
124
|
function useCreateWallet() {
|
|
113
125
|
const { chipiSDK } = chipiReact.useChipiContext();
|
|
114
126
|
const mutation = reactQuery.useMutation({
|
|
@@ -204,10 +216,6 @@ function useMigrateWalletToPasskey() {
|
|
|
204
216
|
};
|
|
205
217
|
}
|
|
206
218
|
|
|
207
|
-
Object.defineProperty(exports, "ChipiProvider", {
|
|
208
|
-
enumerable: true,
|
|
209
|
-
get: function () { return chipiReact.ChipiProvider; }
|
|
210
|
-
});
|
|
211
219
|
Object.defineProperty(exports, "useAddSessionKeyToContract", {
|
|
212
220
|
enumerable: true,
|
|
213
221
|
get: function () { return chipiReact.useAddSessionKeyToContract; }
|
|
@@ -292,6 +300,7 @@ Object.defineProperty(exports, "ChainToken", {
|
|
|
292
300
|
enumerable: true,
|
|
293
301
|
get: function () { return types.ChainToken; }
|
|
294
302
|
});
|
|
303
|
+
exports.ChipiProvider = ChipiProvider;
|
|
295
304
|
exports.createNativeWalletPasskey = createNativeWalletPasskey;
|
|
296
305
|
exports.getNativeWalletCredential = getNativeWalletCredential;
|
|
297
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":["CryptoES","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 * - 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 { 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,13 +1,14 @@
|
|
|
1
|
-
import { useChipiContext } from '@chipi-stack/chipi-react';
|
|
2
|
-
export {
|
|
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';
|
|
6
5
|
import CryptoES from 'crypto-es';
|
|
6
|
+
import { jsx } from 'react/jsx-runtime';
|
|
7
|
+
import { useMutation } from '@tanstack/react-query';
|
|
7
8
|
import { decryptPrivateKey, encryptPrivateKey } from '@chipi-stack/backend';
|
|
8
9
|
export { Chain, ChainToken } from '@chipi-stack/types';
|
|
9
10
|
|
|
10
|
-
// src/
|
|
11
|
+
// src/ChipiProvider.tsx
|
|
11
12
|
var ENCRYPT_KEY_PREFIX = "chipi_wallet_key_";
|
|
12
13
|
var CREDENTIAL_META_KEY = "chipi_wallet_credential";
|
|
13
14
|
function generateRandomHex(byteCount) {
|
|
@@ -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
|
-
|
|
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
|
package/dist/index.mjs.map
CHANGED
|
@@ -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 * - 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 { 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.
|
|
3
|
+
"version": "13.13.0",
|
|
4
4
|
"description": "Chipi SDK for React Native and Expo applications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chipi",
|
|
@@ -56,10 +56,10 @@
|
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@tanstack/react-query": "^5.85.0",
|
|
58
58
|
"crypto-es": "^2.1.0",
|
|
59
|
-
"@chipi-stack/backend": "^13.
|
|
60
|
-
"@chipi-stack/chipi-react": "^13.
|
|
61
|
-
"@chipi-stack/types": "^13.
|
|
62
|
-
"@chipi-stack/shared": "^13.
|
|
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"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
65
|
"expo-local-authentication": ">=14.0.0",
|