@chipi-stack/chipi-expo 14.1.1 → 14.2.1
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/README.md +2 -2
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Chipi Expo SDK
|
|
2
2
|
|
|
3
|
-
Chipi Expo
|
|
3
|
+
Chipi SDK for Expo and React Native — gasless stablecoin wallets, USDC payments, session keys with spending policies, and native biometric authentication on StarkNet. Add self-custody wallets with Face ID / Touch ID to your mobile app in minutes. All transactions are gasless.
|
|
4
4
|
|
|
5
5
|
## Prerequisites
|
|
6
6
|
|
|
@@ -29,7 +29,7 @@ You can get in touch with us in any of the following ways:
|
|
|
29
29
|
|
|
30
30
|
- Join our [Telegram community](https://t.me/chipi_pay)
|
|
31
31
|
- Visit our [YouTube channel](https://www.youtube.com/@chipipay) for tutorials
|
|
32
|
-
- Email us at
|
|
32
|
+
- Email us at carlos@chipipay.com
|
|
33
33
|
|
|
34
34
|
## Contributing
|
|
35
35
|
|
package/dist/index.js
CHANGED
|
@@ -173,9 +173,10 @@ function useCreateWallet() {
|
|
|
173
173
|
};
|
|
174
174
|
}
|
|
175
175
|
function useMigrateWalletToPasskey() {
|
|
176
|
+
const { chipiSDK } = chipiReact.useChipiContext();
|
|
176
177
|
const mutation = reactQuery.useMutation({
|
|
177
178
|
mutationFn: async (input) => {
|
|
178
|
-
const { wallet, oldEncryptKey, externalUserId } = input;
|
|
179
|
+
const { wallet, oldEncryptKey, externalUserId, bearerToken } = input;
|
|
179
180
|
try {
|
|
180
181
|
let decryptedPrivateKey;
|
|
181
182
|
try {
|
|
@@ -190,6 +191,17 @@ function useMigrateWalletToPasskey() {
|
|
|
190
191
|
decryptedPrivateKey,
|
|
191
192
|
passkeyResult.encryptKey
|
|
192
193
|
);
|
|
194
|
+
const updateResult = await chipiSDK.wallets.updateWalletEncryption(
|
|
195
|
+
{
|
|
196
|
+
externalUserId,
|
|
197
|
+
newEncryptedPrivateKey,
|
|
198
|
+
publicKey: wallet.publicKey
|
|
199
|
+
},
|
|
200
|
+
bearerToken
|
|
201
|
+
);
|
|
202
|
+
if (updateResult?.success === false) {
|
|
203
|
+
throw new Error("Backend rejected wallet encryption update");
|
|
204
|
+
}
|
|
193
205
|
const updatedWallet = {
|
|
194
206
|
...wallet,
|
|
195
207
|
encryptedPrivateKey: newEncryptedPrivateKey
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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(`${ENCRYPT_KEY_PREFIX}${userId}`, 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 try {\n await SecureStore.setItemAsync(CREDENTIAL_META_KEY, JSON.stringify(meta));\n } catch (error) {\n try {\n await SecureStore.deleteItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`);\n } catch {\n // best-effort rollback; preserve original failure\n }\n throw error;\n }\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(userId: string): Promise<string | null> {\n const supported = await isNativeBiometricSupported();\n if (!supported) {\n throw new Error(\"Biometric authentication is not available or not enrolled on this device.\");\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 { CreateWalletParams, CreateWalletResponse } 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: (input: CreateWalletInput) => 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<CreateWalletResponse, Error, CreateWalletInput> = 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 if (!encryptKey) {\n throw new Error(\n \"encryptKey is required when usePasskey is false. Provide a PIN or enable usePasskey.\"\n );\n }\n\n return chipiSDK.createWallet({\n params: {\n ...input.params,\n 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: Validate old encryptKey by decrypting first (before creating passkey)\n let decryptedPrivateKey: string;\n try {\n decryptedPrivateKey = decryptPrivateKey(wallet.encryptedPrivateKey, oldEncryptKey);\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 2: Create new native passkey only after PIN is confirmed valid\n const passkeyResult = await createNativeWalletPasskey(externalUserId, externalUserId);\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmBA,IAAM,kBAAA,GAAqB,mBAAA;AAC3B,IAAM,mBAAA,GAAsB,yBAAA;AAmB5B,SAAS,kBAAkB,SAAA,EAA2B;AACpD,EAAA,MAAM,SAAA,GAAYA,yBAAA,CAAS,GAAA,CAAI,SAAA,CAAU,OAAO,SAAS,CAAA;AACzD,EAAA,OAAO,SAAA,CAAU,QAAA,CAASA,yBAAA,CAAS,GAAA,CAAI,GAAG,CAAA;AAC5C;AAMA,eAAsB,0BAAA,GAA+C;AACnE,EAAA,MAAM,WAAA,GAAc,MAA0BC,8BAAA,CAAA,gBAAA,EAAiB;AAC/D,EAAA,IAAI,CAAC,aAAa,OAAO,KAAA;AACzB,EAAA,MAAM,UAAA,GAAa,MAA0BA,8BAAA,CAAA,eAAA,EAAgB;AAC7D,EAAA,OAAO,UAAA;AACT;AAaA,eAAsB,yBAAA,CACpB,QACA,SAAA,EAC0C;AAC1C,EAAA,MAAM,SAAA,GAAY,MAAM,0BAAA,EAA2B;AACnD,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KAEF;AAAA,EACF;AAGA,EAAA,MAAM,UAAA,GAAa,MAA0BA,8BAAA,CAAA,iBAAA,CAAkB;AAAA,IAC7D,aAAA,EAAe,oCAAA;AAAA,IACf,WAAA,EAAa,QAAA;AAAA,IACb,qBAAA,EAAuB;AAAA,GACxB,CAAA;AAED,EAAA,IAAI,CAAC,WAAW,OAAA,EAAS;AACvB,IAAA,MAAM,MAAA,GAAS,OAAA,IAAW,UAAA,GAAa,UAAA,CAAW,KAAA,GAAQ,SAAA;AAC1D,IAAA,IAAI,MAAA,KAAW,aAAA,IAAiB,MAAA,KAAW,YAAA,EAAc;AACvD,MAAA,MAAM,IAAI,MAAM,wCAAwC,CAAA;AAAA,IAC1D;AACA,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,iCAAA,EAAoC,MAAM,CAAA,CAAE,CAAA;AAAA,EAC9D;AAEA,EAAA,MAAM,UAAA,GAAa,kBAAkB,EAAE,CAAA;AACvC,EAAA,MAAM,eAAe,CAAA,iBAAA,EAAoB,MAAM,CAAA,CAAA,EAAI,IAAA,CAAK,KAAK,CAAA,CAAA;AAG7D,EAAA,MAAkBC,oCAAa,CAAA,EAAG,kBAAkB,CAAA,EAAG,MAAM,IAAI,UAAA,EAAY;AAAA,IAC3E,qBAAA,EAAuB;AAAA,GACxB,CAAA;AAGD,EAAA,MAAM,IAAA,GAA+B;AAAA,IACnC,YAAA;AAAA,IACA,MAAA;AAAA,IACA,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,GACpC;AACA,EAAA,IAAI;AACF,IAAA,MAAkBA,sBAAA,CAAA,YAAA,CAAa,mBAAA,EAAqB,IAAA,CAAK,SAAA,CAAU,IAAI,CAAC,CAAA;AAAA,EAC1E,SAAS,KAAA,EAAO;AACd,IAAA,IAAI;AACF,MAAA,MAAkBA,sBAAA,CAAA,eAAA,CAAgB,CAAA,EAAG,kBAAkB,CAAA,EAAG,MAAM,CAAA,CAAE,CAAA;AAAA,IACpE,CAAA,CAAA,MAAQ;AAAA,IAER;AACA,IAAA,MAAM,KAAA;AAAA,EACR;AAEA,EAAA,OAAO,EAAE,UAAA,EAAY,YAAA,EAAc,YAAA,EAAc,KAAA,EAAM;AACzD;AASA,eAAsB,0BAA0B,MAAA,EAAwC;AACtF,EAAA,MAAM,SAAA,GAAY,MAAM,0BAAA,EAA2B;AACnD,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,MAAM,IAAI,MAAM,2EAA2E,CAAA;AAAA,EAC7F;AAEA,EAAA,OAAmBA,sBAAA,CAAA,YAAA,CAAa,CAAA,EAAG,kBAAkB,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI;AAAA,IAChE,qBAAA,EAAuB;AAAA,GACxB,CAAA;AACH;AAKA,eAAsB,sBAAA,GAA2C;AAC/D,EAAA,MAAM,MAAA,GAAS,MAAkBA,sBAAA,CAAA,YAAA,CAAa,mBAAmB,CAAA;AACjE,EAAA,OAAO,MAAA,KAAW,IAAA;AACpB;AAKA,eAAsB,yBAAA,GAAoE;AACxF,EAAA,MAAM,MAAA,GAAS,MAAkBA,sBAAA,CAAA,YAAA,CAAa,mBAAmB,CAAA;AACjE,EAAA,IAAI,CAAC,QAAQ,OAAO,IAAA;AACpB,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,MAAM,MAAM,CAAA;AAAA,EAC1B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAMA,eAAsB,0BAA0B,MAAA,EAA+B;AAC7E,EAAA,MAAkBA,sBAAA,CAAA,eAAA,CAAgB,CAAA,EAAG,kBAAkB,CAAA,EAAG,MAAM,CAAA,CAAE,CAAA;AAClE,EAAA,MAAkBA,uCAAgB,mBAAmB,CAAA;AACvD;AC7JA,IAAM,kBAAA,GAAqB;AAAA,EACzB,mBAAA,EAAqB,OAAO,KAAA,KAAsC;AAChE,IAAA,IAAI,CAAC,MAAM,cAAA,EAAgB;AACzB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR;AAAA,OAEF;AAAA,IACF;AAEA,IAAA,OAAO,yBAAA,CAA0B,MAAM,cAAc,CAAA;AAAA,EACvD;AACF,CAAA;AAOO,SAAS,aAAA,CAAc,EAAE,QAAA,EAAU,MAAA,EAAO,EAAuB;AACtE,EAAA,uBACEC,cAAA,CAACC,wBAAA,EAAA,EAAmB,MAAA,EAAgB,cAAA,EAAgB,oBACjD,QAAA,EACH,CAAA;AAEJ;ACjBO,SAAS,eAAA,GASd;AACA,EAAA,MAAM,EAAE,QAAA,EAAS,GAAIC,0BAAA,EAAgB;AAErC,EAAA,MAAM,WAA8EC,sBAAA,CAAY;AAAA,IAC9F,UAAA,EAAY,OAAO,KAAA,KAA6B;AAC9C,MAAA,IAAI,UAAA,GAAa,MAAM,MAAA,CAAO,UAAA;AAE9B,MAAA,IAAI,KAAA,CAAM,OAAO,UAAA,EAAY;AAC3B,QAAA,IAAI,CAAC,KAAA,CAAM,MAAA,CAAO,cAAA,EAAgB;AAChC,UAAA,MAAM,IAAI,MAAM,+CAA+C,CAAA;AAAA,QACjE;AAEA,QAAA,IAAI;AACF,UAAA,MAAM,gBAAgB,MAAM,yBAAA;AAAA,YAC1B,MAAM,MAAA,CAAO,cAAA;AAAA,YACb,MAAM,MAAA,CAAO;AAAA,WACf;AACA,UAAA,UAAA,GAAa,aAAA,CAAc,UAAA;AAAA,QAC7B,SAAS,KAAA,EAAO;AACd,UAAA,IAAI,iBAAiB,KAAA,EAAO;AAC1B,YAAA,MAAM,IAAI,KAAA,CAAM,CAAA,yBAAA,EAA4B,KAAA,CAAM,OAAO,CAAA,CAAE,CAAA;AAAA,UAC7D;AACA,UAAA,MAAM,IAAI,MAAM,qCAAqC,CAAA;AAAA,QACvD;AAAA,MACF;AAEA,MAAA,IAAI,CAAC,UAAA,EAAY;AACf,QAAA,MAAM,IAAI,KAAA;AAAA,UACR;AAAA,SACF;AAAA,MACF;AAEA,MAAA,OAAO,SAAS,YAAA,CAAa;AAAA,QAC3B,MAAA,EAAQ;AAAA,UACN,GAAG,KAAA,CAAM,MAAA;AAAA,UACT;AAAA,SACF;AAAA,QACA,aAAa,KAAA,CAAM;AAAA,OACpB,CAAA;AAAA,IACH;AAAA,GACD,CAAA;AAED,EAAA,OAAO;AAAA,IACL,cAAc,QAAA,CAAS,MAAA;AAAA,IACvB,mBAAmB,QAAA,CAAS,WAAA;AAAA,IAC5B,MAAM,QAAA,CAAS,IAAA;AAAA,IACf,WAAW,QAAA,CAAS,SAAA;AAAA,IACpB,SAAS,QAAA,CAAS,OAAA;AAAA,IAClB,OAAO,QAAA,CAAS,KAAA;AAAA,IAChB,WAAW,QAAA,CAAS,SAAA;AAAA,IACpB,OAAO,QAAA,CAAS;AAAA,GAClB;AACF;ACxDO,SAAS,yBAAA,GAWd;AACA,EAAA,MAAM,EAAE,QAAA,EAAS,GAAID,0BAAAA,EAAgB;AAErC,EAAA,MAAM,WAIFC,sBAAAA,CAAY;AAAA,IACd,UAAA,EAAY,OAAO,KAAA,KAAuC;AACxD,MAAA,MAAM,EAAE,MAAA,EAAQ,aAAA,EAAe,cAAA,EAAgB,aAAY,GAAI,KAAA;AAE/D,MAAA,IAAI;AAEF,QAAA,IAAI,mBAAA;AACJ,QAAA,IAAI;AACF,UAAA,mBAAA,GAAsBC,yBAAA,CAAkB,MAAA,CAAO,mBAAA,EAAqB,aAAa,CAAA;AAAA,QACnF,CAAA,CAAA,MAAQ;AACN,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WACF;AAAA,QACF;AAGA,QAAA,MAAM,aAAA,GAAgB,MAAM,yBAAA,CAA0B,cAAA,EAAgB,cAAc,CAAA;AAGpF,QAAA,MAAM,sBAAA,GAAyBC,yBAAA;AAAA,UAC7B,mBAAA;AAAA,UACA,aAAA,CAAc;AAAA,SAChB;AAGA,QAAA,MAAM,YAAA,GAAe,MAAO,QAAA,CAAS,OAAA,CAAyB,sBAAA;AAAA,UAC5D;AAAA,YACE,cAAA;AAAA,YACA,sBAAA;AAAA,YACA,WAAW,MAAA,CAAO;AAAA,WACpB;AAAA,UACA;AAAA,SACF;AAEA,QAAA,IAAI,YAAA,EAAc,YAAY,KAAA,EAAO;AACnC,UAAA,MAAM,IAAI,MAAM,2CAA2C,CAAA;AAAA,QAC7D;AAEA,QAAA,MAAM,aAAA,GAA4B;AAAA,UAChC,GAAG,MAAA;AAAA,UACH,mBAAA,EAAqB;AAAA,SACvB;AAEA,QAAA,OAAO;AAAA,UACL,OAAA,EAAS,IAAA;AAAA,UACT,MAAA,EAAQ,aAAA;AAAA,UACR,cAAc,aAAA,CAAc;AAAA,SAC9B;AAAA,MACF,SAAS,KAAA,EAAO;AACd,QAAA,IAAI,iBAAiB,KAAA,EAAO;AAC1B,UAAA,MAAM,IAAI,KAAA,CAAM,CAAA,kBAAA,EAAqB,KAAA,CAAM,OAAO,CAAA,CAAE,CAAA;AAAA,QACtD;AACA,QAAA,MAAM,IAAI,MAAM,qCAAqC,CAAA;AAAA,MACvD;AAAA,IACF;AAAA,GACD,CAAA;AAED,EAAA,OAAO;AAAA,IACL,wBAAwB,QAAA,CAAS,MAAA;AAAA,IACjC,6BAA6B,QAAA,CAAS,WAAA;AAAA,IACtC,MAAM,QAAA,CAAS,IAAA;AAAA,IACf,WAAW,QAAA,CAAS,SAAA;AAAA,IACpB,SAAS,QAAA,CAAS,OAAA;AAAA,IAClB,OAAO,QAAA,CAAS,KAAA;AAAA,IAChB,WAAW,QAAA,CAAS,SAAA;AAAA,IACpB,OAAO,QAAA,CAAS;AAAA,GAClB;AACF","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(`${ENCRYPT_KEY_PREFIX}${userId}`, 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 try {\n await SecureStore.setItemAsync(CREDENTIAL_META_KEY, JSON.stringify(meta));\n } catch (error) {\n try {\n await SecureStore.deleteItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`);\n } catch {\n // best-effort rollback; preserve original failure\n }\n throw error;\n }\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(userId: string): Promise<string | null> {\n const supported = await isNativeBiometricSupported();\n if (!supported) {\n throw new Error(\"Biometric authentication is not available or not enrolled on this device.\");\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 { CreateWalletParams, CreateWalletResponse } 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: (input: CreateWalletInput) => 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<CreateWalletResponse, Error, CreateWalletInput> = 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 if (!encryptKey) {\n throw new Error(\n \"encryptKey is required when usePasskey is false. Provide a PIN or enable usePasskey.\"\n );\n }\n\n return chipiSDK.createWallet({\n params: {\n ...input.params,\n 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 { useChipiContext } from \"@chipi-stack/chipi-react\";\nimport type { MigrateWalletToPasskeyParams, WalletData } from \"@chipi-stack/types\";\nimport { decryptPrivateKey, encryptPrivateKey, type ChipiWallets } 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(/* Expo-native override */): {\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 { chipiSDK } = useChipiContext();\n\n const mutation: UseMutationResult<\n MigrateWalletToPasskeyResult,\n Error,\n MigrateWalletToPasskeyInput\n > = useMutation({\n mutationFn: async (input: MigrateWalletToPasskeyInput) => {\n const { wallet, oldEncryptKey, externalUserId, bearerToken } = input;\n\n try {\n // Step 1: Validate old encryptKey by decrypting first (before creating passkey)\n let decryptedPrivateKey: string;\n try {\n decryptedPrivateKey = decryptPrivateKey(wallet.encryptedPrivateKey, oldEncryptKey);\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 2: Create new native passkey only after PIN is confirmed valid\n const passkeyResult = await createNativeWalletPasskey(externalUserId, externalUserId);\n\n // Step 3: Re-encrypt with the new biometric-derived encryptKey\n const newEncryptedPrivateKey = encryptPrivateKey(\n decryptedPrivateKey,\n passkeyResult.encryptKey\n );\n\n // Step 4: Persist new encrypted key to backend\n const updateResult = await (chipiSDK.wallets as ChipiWallets).updateWalletEncryption(\n {\n externalUserId,\n newEncryptedPrivateKey,\n publicKey: wallet.publicKey,\n },\n bearerToken\n );\n\n if (updateResult?.success === false) {\n throw new Error(\"Backend rejected wallet encryption update\");\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
|
@@ -148,9 +148,10 @@ function useCreateWallet() {
|
|
|
148
148
|
};
|
|
149
149
|
}
|
|
150
150
|
function useMigrateWalletToPasskey() {
|
|
151
|
+
const { chipiSDK } = useChipiContext();
|
|
151
152
|
const mutation = useMutation({
|
|
152
153
|
mutationFn: async (input) => {
|
|
153
|
-
const { wallet, oldEncryptKey, externalUserId } = input;
|
|
154
|
+
const { wallet, oldEncryptKey, externalUserId, bearerToken } = input;
|
|
154
155
|
try {
|
|
155
156
|
let decryptedPrivateKey;
|
|
156
157
|
try {
|
|
@@ -165,6 +166,17 @@ function useMigrateWalletToPasskey() {
|
|
|
165
166
|
decryptedPrivateKey,
|
|
166
167
|
passkeyResult.encryptKey
|
|
167
168
|
);
|
|
169
|
+
const updateResult = await chipiSDK.wallets.updateWalletEncryption(
|
|
170
|
+
{
|
|
171
|
+
externalUserId,
|
|
172
|
+
newEncryptedPrivateKey,
|
|
173
|
+
publicKey: wallet.publicKey
|
|
174
|
+
},
|
|
175
|
+
bearerToken
|
|
176
|
+
);
|
|
177
|
+
if (updateResult?.success === false) {
|
|
178
|
+
throw new Error("Backend rejected wallet encryption update");
|
|
179
|
+
}
|
|
168
180
|
const updatedWallet = {
|
|
169
181
|
...wallet,
|
|
170
182
|
encryptedPrivateKey: newEncryptedPrivateKey
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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(`${ENCRYPT_KEY_PREFIX}${userId}`, 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 try {\n await SecureStore.setItemAsync(CREDENTIAL_META_KEY, JSON.stringify(meta));\n } catch (error) {\n try {\n await SecureStore.deleteItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`);\n } catch {\n // best-effort rollback; preserve original failure\n }\n throw error;\n }\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(userId: string): Promise<string | null> {\n const supported = await isNativeBiometricSupported();\n if (!supported) {\n throw new Error(\"Biometric authentication is not available or not enrolled on this device.\");\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 { CreateWalletParams, CreateWalletResponse } 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: (input: CreateWalletInput) => 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<CreateWalletResponse, Error, CreateWalletInput> = 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 if (!encryptKey) {\n throw new Error(\n \"encryptKey is required when usePasskey is false. Provide a PIN or enable usePasskey.\"\n );\n }\n\n return chipiSDK.createWallet({\n params: {\n ...input.params,\n 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: Validate old encryptKey by decrypting first (before creating passkey)\n let decryptedPrivateKey: string;\n try {\n decryptedPrivateKey = decryptPrivateKey(wallet.encryptedPrivateKey, oldEncryptKey);\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 2: Create new native passkey only after PIN is confirmed valid\n const passkeyResult = await createNativeWalletPasskey(externalUserId, externalUserId);\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","useChipiContext","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(`${ENCRYPT_KEY_PREFIX}${userId}`, 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 try {\n await SecureStore.setItemAsync(CREDENTIAL_META_KEY, JSON.stringify(meta));\n } catch (error) {\n try {\n await SecureStore.deleteItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`);\n } catch {\n // best-effort rollback; preserve original failure\n }\n throw error;\n }\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(userId: string): Promise<string | null> {\n const supported = await isNativeBiometricSupported();\n if (!supported) {\n throw new Error(\"Biometric authentication is not available or not enrolled on this device.\");\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 { CreateWalletParams, CreateWalletResponse } 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: (input: CreateWalletInput) => 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<CreateWalletResponse, Error, CreateWalletInput> = 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 if (!encryptKey) {\n throw new Error(\n \"encryptKey is required when usePasskey is false. Provide a PIN or enable usePasskey.\"\n );\n }\n\n return chipiSDK.createWallet({\n params: {\n ...input.params,\n 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 { useChipiContext } from \"@chipi-stack/chipi-react\";\nimport type { MigrateWalletToPasskeyParams, WalletData } from \"@chipi-stack/types\";\nimport { decryptPrivateKey, encryptPrivateKey, type ChipiWallets } 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(/* Expo-native override */): {\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 { chipiSDK } = useChipiContext();\n\n const mutation: UseMutationResult<\n MigrateWalletToPasskeyResult,\n Error,\n MigrateWalletToPasskeyInput\n > = useMutation({\n mutationFn: async (input: MigrateWalletToPasskeyInput) => {\n const { wallet, oldEncryptKey, externalUserId, bearerToken } = input;\n\n try {\n // Step 1: Validate old encryptKey by decrypting first (before creating passkey)\n let decryptedPrivateKey: string;\n try {\n decryptedPrivateKey = decryptPrivateKey(wallet.encryptedPrivateKey, oldEncryptKey);\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 2: Create new native passkey only after PIN is confirmed valid\n const passkeyResult = await createNativeWalletPasskey(externalUserId, externalUserId);\n\n // Step 3: Re-encrypt with the new biometric-derived encryptKey\n const newEncryptedPrivateKey = encryptPrivateKey(\n decryptedPrivateKey,\n passkeyResult.encryptKey\n );\n\n // Step 4: Persist new encrypted key to backend\n const updateResult = await (chipiSDK.wallets as ChipiWallets).updateWalletEncryption(\n {\n externalUserId,\n newEncryptedPrivateKey,\n publicKey: wallet.publicKey,\n },\n bearerToken\n );\n\n if (updateResult?.success === false) {\n throw new Error(\"Backend rejected wallet encryption update\");\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": "14.
|
|
3
|
+
"version": "14.2.1",
|
|
4
4
|
"description": "Chipi SDK for React Native and Expo applications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chipi",
|
|
@@ -73,10 +73,10 @@
|
|
|
73
73
|
"dependencies": {
|
|
74
74
|
"@tanstack/react-query": "^5.85.0",
|
|
75
75
|
"crypto-es": "^2.1.0",
|
|
76
|
-
"@chipi-stack/
|
|
77
|
-
"@chipi-stack/
|
|
78
|
-
"@chipi-stack/
|
|
79
|
-
"@chipi-stack/
|
|
76
|
+
"@chipi-stack/backend": "^14.2.1",
|
|
77
|
+
"@chipi-stack/chipi-react": "^14.2.1",
|
|
78
|
+
"@chipi-stack/shared": "^14.2.1",
|
|
79
|
+
"@chipi-stack/types": "^14.2.1"
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
82
|
"expo-local-authentication": ">=55.0.0",
|