@chipi-stack/chipi-expo 13.10.0 → 13.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,3 +1,115 @@
1
- export * from '@chipi-stack/chipi-react';
1
+ export { ChipiProvider, ChipiWalletData, SessionState, SessionWallet, UseChipiSessionConfig, UseChipiSessionReturn, UseChipiWalletConfig, UseChipiWalletReturn, useAddSessionKeyToContract, useApprove, useCallAnyContract, useChipiContext, useChipiSession, useChipiWallet, useCreateSessionKey, useExecuteWithSession, useGetSessionData, useGetSku, useGetSkuList, useGetSkuPurchase, useGetTokenBalance, useGetTransactionList, useGetWallet, usePurchaseSku, useRecordSendTransaction, useRevokeSessionKey, useTransfer } from '@chipi-stack/chipi-react';
2
+ import { CreateWalletParams, CreateWalletResponse, MigrateWalletToPasskeyParams, WalletData } from '@chipi-stack/types';
2
3
  export * from '@chipi-stack/types';
3
4
  export { Chain, ChainToken } from '@chipi-stack/types';
5
+
6
+ type CreateWalletInput = {
7
+ params: CreateWalletParams;
8
+ bearerToken: string;
9
+ };
10
+ /**
11
+ * Expo-native override of useCreateWallet.
12
+ *
13
+ * When usePasskey: true is passed, this uses expo-local-authentication +
14
+ * expo-secure-store instead of the browser-only @simplewebauthn/browser,
15
+ * so it works on real iOS and Android devices.
16
+ */
17
+ declare function useCreateWallet(): {
18
+ createWallet: (input: CreateWalletInput) => void;
19
+ createWalletAsync: (input: CreateWalletInput) => Promise<CreateWalletResponse>;
20
+ data: CreateWalletResponse | undefined;
21
+ isLoading: boolean;
22
+ isError: boolean;
23
+ error: Error | null;
24
+ isSuccess: boolean;
25
+ reset: () => void;
26
+ };
27
+
28
+ type MigrateWalletToPasskeyInput = MigrateWalletToPasskeyParams & {
29
+ bearerToken: string;
30
+ };
31
+ interface MigrateWalletToPasskeyResult {
32
+ success: boolean;
33
+ wallet: WalletData;
34
+ credentialId: string;
35
+ }
36
+ /**
37
+ * Expo-native override of useMigrateWalletToPasskey.
38
+ *
39
+ * Migrates a PIN-encrypted wallet to biometric (Face ID / Touch ID) protection.
40
+ * Uses expo-local-authentication + expo-secure-store instead of browser WebAuthn.
41
+ */
42
+ declare function useMigrateWalletToPasskey(): {
43
+ migrateWalletToPasskey: (input: MigrateWalletToPasskeyInput) => void;
44
+ migrateWalletToPasskeyAsync: (input: MigrateWalletToPasskeyInput) => Promise<MigrateWalletToPasskeyResult>;
45
+ data: MigrateWalletToPasskeyResult | undefined;
46
+ isLoading: boolean;
47
+ isError: boolean;
48
+ error: Error | null;
49
+ isSuccess: boolean;
50
+ reset: () => void;
51
+ };
52
+
53
+ /**
54
+ * Native Passkey for Expo/React Native
55
+ *
56
+ * Replaces the browser-only @simplewebauthn/browser implementation with a
57
+ * native equivalent using:
58
+ * - expo-local-authentication → biometric gate (Face ID / Touch ID / fingerprint)
59
+ * - expo-secure-store → iOS Keychain / Android Keystore protected storage
60
+ *
61
+ * Security model: a random 32-byte key is generated at wallet creation time,
62
+ * stored in the device's secure enclave behind biometric authentication,
63
+ * and retrieved later by prompting the user again. This is functionally
64
+ * equivalent to the WebAuthn PRF approach used on the web.
65
+ */
66
+ interface NativeWalletCredential {
67
+ credentialId: string;
68
+ userId: string;
69
+ createdAt: string;
70
+ }
71
+ interface NativeCreateWalletPasskeyResult {
72
+ encryptKey: string;
73
+ credentialId: string;
74
+ prfSupported: false;
75
+ }
76
+ /**
77
+ * Returns true if the device has biometric hardware AND the user has enrolled.
78
+ * Must be true before calling createNativeWalletPasskey or getNativeWalletEncryptKey.
79
+ */
80
+ declare function isNativeBiometricSupported(): Promise<boolean>;
81
+ /**
82
+ * Create a new native wallet passkey.
83
+ *
84
+ * 1. Verifies biometric support.
85
+ * 2. Prompts the user with Face ID / Touch ID to confirm intent.
86
+ * 3. Generates a random encryption key.
87
+ * 4. Stores the key in the device's Keychain/Keystore, protected by biometrics.
88
+ * 5. Returns the encryption key so the wallet can be created immediately.
89
+ *
90
+ * The key is NEVER stored anywhere else — it lives only in the secure enclave.
91
+ */
92
+ declare function createNativeWalletPasskey(userId: string, _userName: string): Promise<NativeCreateWalletPasskeyResult>;
93
+ /**
94
+ * Retrieve the stored encryption key by authenticating with biometrics.
95
+ * expo-secure-store automatically triggers the Face ID / Touch ID prompt
96
+ * when requireAuthentication: true was used during storage.
97
+ *
98
+ * Returns null if no key is stored for the given userId.
99
+ */
100
+ declare function getNativeWalletEncryptKey(userId: string): Promise<string | null>;
101
+ /**
102
+ * Returns true if a native wallet passkey has been created on this device.
103
+ */
104
+ declare function hasNativeWalletPasskey(): Promise<boolean>;
105
+ /**
106
+ * Returns the stored credential metadata, or null if none exists.
107
+ */
108
+ declare function getNativeWalletCredential(): Promise<NativeWalletCredential | null>;
109
+ /**
110
+ * Removes the stored encryption key and credential metadata from this device.
111
+ * Use with caution — if the wallet has no other recovery mechanism, this is destructive.
112
+ */
113
+ declare function removeNativeWalletPasskey(userId: string): Promise<void>;
114
+
115
+ export { type NativeCreateWalletPasskeyResult, type NativeWalletCredential, createNativeWalletPasskey, getNativeWalletCredential, getNativeWalletEncryptKey, hasNativeWalletPasskey, isNativeBiometricSupported, removeNativeWalletPasskey, useCreateWallet, useMigrateWalletToPasskey };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,115 @@
1
- export * from '@chipi-stack/chipi-react';
1
+ export { ChipiProvider, ChipiWalletData, SessionState, SessionWallet, UseChipiSessionConfig, UseChipiSessionReturn, UseChipiWalletConfig, UseChipiWalletReturn, useAddSessionKeyToContract, useApprove, useCallAnyContract, useChipiContext, useChipiSession, useChipiWallet, useCreateSessionKey, useExecuteWithSession, useGetSessionData, useGetSku, useGetSkuList, useGetSkuPurchase, useGetTokenBalance, useGetTransactionList, useGetWallet, usePurchaseSku, useRecordSendTransaction, useRevokeSessionKey, useTransfer } from '@chipi-stack/chipi-react';
2
+ import { CreateWalletParams, CreateWalletResponse, MigrateWalletToPasskeyParams, WalletData } from '@chipi-stack/types';
2
3
  export * from '@chipi-stack/types';
3
4
  export { Chain, ChainToken } from '@chipi-stack/types';
5
+
6
+ type CreateWalletInput = {
7
+ params: CreateWalletParams;
8
+ bearerToken: string;
9
+ };
10
+ /**
11
+ * Expo-native override of useCreateWallet.
12
+ *
13
+ * When usePasskey: true is passed, this uses expo-local-authentication +
14
+ * expo-secure-store instead of the browser-only @simplewebauthn/browser,
15
+ * so it works on real iOS and Android devices.
16
+ */
17
+ declare function useCreateWallet(): {
18
+ createWallet: (input: CreateWalletInput) => void;
19
+ createWalletAsync: (input: CreateWalletInput) => Promise<CreateWalletResponse>;
20
+ data: CreateWalletResponse | undefined;
21
+ isLoading: boolean;
22
+ isError: boolean;
23
+ error: Error | null;
24
+ isSuccess: boolean;
25
+ reset: () => void;
26
+ };
27
+
28
+ type MigrateWalletToPasskeyInput = MigrateWalletToPasskeyParams & {
29
+ bearerToken: string;
30
+ };
31
+ interface MigrateWalletToPasskeyResult {
32
+ success: boolean;
33
+ wallet: WalletData;
34
+ credentialId: string;
35
+ }
36
+ /**
37
+ * Expo-native override of useMigrateWalletToPasskey.
38
+ *
39
+ * Migrates a PIN-encrypted wallet to biometric (Face ID / Touch ID) protection.
40
+ * Uses expo-local-authentication + expo-secure-store instead of browser WebAuthn.
41
+ */
42
+ declare function useMigrateWalletToPasskey(): {
43
+ migrateWalletToPasskey: (input: MigrateWalletToPasskeyInput) => void;
44
+ migrateWalletToPasskeyAsync: (input: MigrateWalletToPasskeyInput) => Promise<MigrateWalletToPasskeyResult>;
45
+ data: MigrateWalletToPasskeyResult | undefined;
46
+ isLoading: boolean;
47
+ isError: boolean;
48
+ error: Error | null;
49
+ isSuccess: boolean;
50
+ reset: () => void;
51
+ };
52
+
53
+ /**
54
+ * Native Passkey for Expo/React Native
55
+ *
56
+ * Replaces the browser-only @simplewebauthn/browser implementation with a
57
+ * native equivalent using:
58
+ * - expo-local-authentication → biometric gate (Face ID / Touch ID / fingerprint)
59
+ * - expo-secure-store → iOS Keychain / Android Keystore protected storage
60
+ *
61
+ * Security model: a random 32-byte key is generated at wallet creation time,
62
+ * stored in the device's secure enclave behind biometric authentication,
63
+ * and retrieved later by prompting the user again. This is functionally
64
+ * equivalent to the WebAuthn PRF approach used on the web.
65
+ */
66
+ interface NativeWalletCredential {
67
+ credentialId: string;
68
+ userId: string;
69
+ createdAt: string;
70
+ }
71
+ interface NativeCreateWalletPasskeyResult {
72
+ encryptKey: string;
73
+ credentialId: string;
74
+ prfSupported: false;
75
+ }
76
+ /**
77
+ * Returns true if the device has biometric hardware AND the user has enrolled.
78
+ * Must be true before calling createNativeWalletPasskey or getNativeWalletEncryptKey.
79
+ */
80
+ declare function isNativeBiometricSupported(): Promise<boolean>;
81
+ /**
82
+ * Create a new native wallet passkey.
83
+ *
84
+ * 1. Verifies biometric support.
85
+ * 2. Prompts the user with Face ID / Touch ID to confirm intent.
86
+ * 3. Generates a random encryption key.
87
+ * 4. Stores the key in the device's Keychain/Keystore, protected by biometrics.
88
+ * 5. Returns the encryption key so the wallet can be created immediately.
89
+ *
90
+ * The key is NEVER stored anywhere else — it lives only in the secure enclave.
91
+ */
92
+ declare function createNativeWalletPasskey(userId: string, _userName: string): Promise<NativeCreateWalletPasskeyResult>;
93
+ /**
94
+ * Retrieve the stored encryption key by authenticating with biometrics.
95
+ * expo-secure-store automatically triggers the Face ID / Touch ID prompt
96
+ * when requireAuthentication: true was used during storage.
97
+ *
98
+ * Returns null if no key is stored for the given userId.
99
+ */
100
+ declare function getNativeWalletEncryptKey(userId: string): Promise<string | null>;
101
+ /**
102
+ * Returns true if a native wallet passkey has been created on this device.
103
+ */
104
+ declare function hasNativeWalletPasskey(): Promise<boolean>;
105
+ /**
106
+ * Returns the stored credential metadata, or null if none exists.
107
+ */
108
+ declare function getNativeWalletCredential(): Promise<NativeWalletCredential | null>;
109
+ /**
110
+ * Removes the stored encryption key and credential metadata from this device.
111
+ * Use with caution — if the wallet has no other recovery mechanism, this is destructive.
112
+ */
113
+ declare function removeNativeWalletPasskey(userId: string): Promise<void>;
114
+
115
+ export { type NativeCreateWalletPasskeyResult, type NativeWalletCredential, createNativeWalletPasskey, getNativeWalletCredential, getNativeWalletEncryptKey, hasNativeWalletPasskey, isNativeBiometricSupported, removeNativeWalletPasskey, useCreateWallet, useMigrateWalletToPasskey };
package/dist/index.js CHANGED
@@ -1,10 +1,286 @@
1
1
  'use strict';
2
2
 
3
3
  var chipiReact = require('@chipi-stack/chipi-react');
4
+ var reactQuery = require('@tanstack/react-query');
5
+ var LocalAuthentication = require('expo-local-authentication');
6
+ var SecureStore = require('expo-secure-store');
7
+ var backend = require('@chipi-stack/backend');
4
8
  var types = require('@chipi-stack/types');
5
9
 
10
+ function _interopNamespace(e) {
11
+ if (e && e.__esModule) return e;
12
+ var n = Object.create(null);
13
+ if (e) {
14
+ Object.keys(e).forEach(function (k) {
15
+ if (k !== 'default') {
16
+ var d = Object.getOwnPropertyDescriptor(e, k);
17
+ Object.defineProperty(n, k, d.get ? d : {
18
+ enumerable: true,
19
+ get: function () { return e[k]; }
20
+ });
21
+ }
22
+ });
23
+ }
24
+ n.default = e;
25
+ return Object.freeze(n);
26
+ }
6
27
 
28
+ var LocalAuthentication__namespace = /*#__PURE__*/_interopNamespace(LocalAuthentication);
29
+ var SecureStore__namespace = /*#__PURE__*/_interopNamespace(SecureStore);
7
30
 
31
+ // src/index.ts
32
+ var ENCRYPT_KEY_PREFIX = "chipi_wallet_key_";
33
+ var CREDENTIAL_META_KEY = "chipi_wallet_credential";
34
+ function generateRandomHex(byteCount) {
35
+ const array = new Uint8Array(byteCount);
36
+ crypto.getRandomValues(array);
37
+ return Array.from(array).map((b) => b.toString(16).padStart(2, "0")).join("");
38
+ }
39
+ async function isNativeBiometricSupported() {
40
+ const hasHardware = await LocalAuthentication__namespace.hasHardwareAsync();
41
+ if (!hasHardware) return false;
42
+ const isEnrolled = await LocalAuthentication__namespace.isEnrolledAsync();
43
+ return isEnrolled;
44
+ }
45
+ async function createNativeWalletPasskey(userId, _userName) {
46
+ const supported = await isNativeBiometricSupported();
47
+ if (!supported) {
48
+ throw new Error(
49
+ "Biometric authentication is not available or not enrolled on this device. Please enroll Face ID, Touch ID, or a fingerprint in your device settings."
50
+ );
51
+ }
52
+ const authResult = await LocalAuthentication__namespace.authenticateAsync({
53
+ promptMessage: "Authenticate to create your wallet",
54
+ cancelLabel: "Cancel",
55
+ disableDeviceFallback: false
56
+ });
57
+ if (!authResult.success) {
58
+ const reason = "error" in authResult ? authResult.error : "unknown";
59
+ if (reason === "user_cancel" || reason === "app_cancel") {
60
+ throw new Error("Biometric authentication was cancelled");
61
+ }
62
+ throw new Error(`Biometric authentication failed: ${reason}`);
63
+ }
64
+ const encryptKey = generateRandomHex(32);
65
+ const credentialId = `native_biometric_${userId}_${Date.now()}`;
66
+ await SecureStore__namespace.setItemAsync(
67
+ `${ENCRYPT_KEY_PREFIX}${userId}`,
68
+ encryptKey,
69
+ { requireAuthentication: true }
70
+ );
71
+ const meta = {
72
+ credentialId,
73
+ userId,
74
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
75
+ };
76
+ await SecureStore__namespace.setItemAsync(CREDENTIAL_META_KEY, JSON.stringify(meta));
77
+ return { encryptKey, credentialId, prfSupported: false };
78
+ }
79
+ async function getNativeWalletEncryptKey(userId) {
80
+ const supported = await isNativeBiometricSupported();
81
+ if (!supported) {
82
+ throw new Error(
83
+ "Biometric authentication is not available or not enrolled on this device."
84
+ );
85
+ }
86
+ return SecureStore__namespace.getItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`, {
87
+ requireAuthentication: true
88
+ });
89
+ }
90
+ async function hasNativeWalletPasskey() {
91
+ const stored = await SecureStore__namespace.getItemAsync(CREDENTIAL_META_KEY);
92
+ return stored !== null;
93
+ }
94
+ async function getNativeWalletCredential() {
95
+ const stored = await SecureStore__namespace.getItemAsync(CREDENTIAL_META_KEY);
96
+ if (!stored) return null;
97
+ try {
98
+ return JSON.parse(stored);
99
+ } catch {
100
+ return null;
101
+ }
102
+ }
103
+ async function removeNativeWalletPasskey(userId) {
104
+ await SecureStore__namespace.deleteItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`);
105
+ await SecureStore__namespace.deleteItemAsync(CREDENTIAL_META_KEY);
106
+ }
107
+
108
+ // src/hooks/useCreateWallet.ts
109
+ function useCreateWallet() {
110
+ const { chipiSDK } = chipiReact.useChipiContext();
111
+ const mutation = reactQuery.useMutation({
112
+ mutationFn: async (input) => {
113
+ let encryptKey = input.params.encryptKey;
114
+ if (input.params.usePasskey) {
115
+ if (!input.params.externalUserId) {
116
+ throw new Error("externalUserId is required when using passkey");
117
+ }
118
+ try {
119
+ const passkeyResult = await createNativeWalletPasskey(
120
+ input.params.externalUserId,
121
+ input.params.externalUserId
122
+ );
123
+ encryptKey = passkeyResult.encryptKey;
124
+ } catch (error) {
125
+ if (error instanceof Error) {
126
+ throw new Error(`Passkey creation failed: ${error.message}`);
127
+ }
128
+ throw new Error("Failed to create passkey for wallet");
129
+ }
130
+ }
131
+ return chipiSDK.createWallet({
132
+ params: {
133
+ ...input.params,
134
+ encryptKey
135
+ },
136
+ bearerToken: input.bearerToken
137
+ });
138
+ }
139
+ });
140
+ return {
141
+ createWallet: mutation.mutate,
142
+ createWalletAsync: mutation.mutateAsync,
143
+ data: mutation.data,
144
+ isLoading: mutation.isPending,
145
+ isError: mutation.isError,
146
+ error: mutation.error,
147
+ isSuccess: mutation.isSuccess,
148
+ reset: mutation.reset
149
+ };
150
+ }
151
+ function useMigrateWalletToPasskey() {
152
+ const mutation = reactQuery.useMutation({
153
+ mutationFn: async (input) => {
154
+ const { wallet, oldEncryptKey, externalUserId } = input;
155
+ try {
156
+ const passkeyResult = await createNativeWalletPasskey(
157
+ externalUserId,
158
+ externalUserId
159
+ );
160
+ let decryptedPrivateKey;
161
+ try {
162
+ decryptedPrivateKey = backend.decryptPrivateKey(
163
+ wallet.encryptedPrivateKey,
164
+ oldEncryptKey
165
+ );
166
+ } catch {
167
+ throw new Error(
168
+ "Failed to decrypt wallet with provided encryptKey. Please verify your PIN/password is correct."
169
+ );
170
+ }
171
+ const newEncryptedPrivateKey = backend.encryptPrivateKey(
172
+ decryptedPrivateKey,
173
+ passkeyResult.encryptKey
174
+ );
175
+ const updatedWallet = {
176
+ ...wallet,
177
+ encryptedPrivateKey: newEncryptedPrivateKey
178
+ };
179
+ return {
180
+ success: true,
181
+ wallet: updatedWallet,
182
+ credentialId: passkeyResult.credentialId
183
+ };
184
+ } catch (error) {
185
+ if (error instanceof Error) {
186
+ throw new Error(`Migration failed: ${error.message}`);
187
+ }
188
+ throw new Error("Failed to migrate wallet to passkey");
189
+ }
190
+ }
191
+ });
192
+ return {
193
+ migrateWalletToPasskey: mutation.mutate,
194
+ migrateWalletToPasskeyAsync: mutation.mutateAsync,
195
+ data: mutation.data,
196
+ isLoading: mutation.isPending,
197
+ isError: mutation.isError,
198
+ error: mutation.error,
199
+ isSuccess: mutation.isSuccess,
200
+ reset: mutation.reset
201
+ };
202
+ }
203
+
204
+ Object.defineProperty(exports, "ChipiProvider", {
205
+ enumerable: true,
206
+ get: function () { return chipiReact.ChipiProvider; }
207
+ });
208
+ Object.defineProperty(exports, "useAddSessionKeyToContract", {
209
+ enumerable: true,
210
+ get: function () { return chipiReact.useAddSessionKeyToContract; }
211
+ });
212
+ Object.defineProperty(exports, "useApprove", {
213
+ enumerable: true,
214
+ get: function () { return chipiReact.useApprove; }
215
+ });
216
+ Object.defineProperty(exports, "useCallAnyContract", {
217
+ enumerable: true,
218
+ get: function () { return chipiReact.useCallAnyContract; }
219
+ });
220
+ Object.defineProperty(exports, "useChipiContext", {
221
+ enumerable: true,
222
+ get: function () { return chipiReact.useChipiContext; }
223
+ });
224
+ Object.defineProperty(exports, "useChipiSession", {
225
+ enumerable: true,
226
+ get: function () { return chipiReact.useChipiSession; }
227
+ });
228
+ Object.defineProperty(exports, "useChipiWallet", {
229
+ enumerable: true,
230
+ get: function () { return chipiReact.useChipiWallet; }
231
+ });
232
+ Object.defineProperty(exports, "useCreateSessionKey", {
233
+ enumerable: true,
234
+ get: function () { return chipiReact.useCreateSessionKey; }
235
+ });
236
+ Object.defineProperty(exports, "useExecuteWithSession", {
237
+ enumerable: true,
238
+ get: function () { return chipiReact.useExecuteWithSession; }
239
+ });
240
+ Object.defineProperty(exports, "useGetSessionData", {
241
+ enumerable: true,
242
+ get: function () { return chipiReact.useGetSessionData; }
243
+ });
244
+ Object.defineProperty(exports, "useGetSku", {
245
+ enumerable: true,
246
+ get: function () { return chipiReact.useGetSku; }
247
+ });
248
+ Object.defineProperty(exports, "useGetSkuList", {
249
+ enumerable: true,
250
+ get: function () { return chipiReact.useGetSkuList; }
251
+ });
252
+ Object.defineProperty(exports, "useGetSkuPurchase", {
253
+ enumerable: true,
254
+ get: function () { return chipiReact.useGetSkuPurchase; }
255
+ });
256
+ Object.defineProperty(exports, "useGetTokenBalance", {
257
+ enumerable: true,
258
+ get: function () { return chipiReact.useGetTokenBalance; }
259
+ });
260
+ Object.defineProperty(exports, "useGetTransactionList", {
261
+ enumerable: true,
262
+ get: function () { return chipiReact.useGetTransactionList; }
263
+ });
264
+ Object.defineProperty(exports, "useGetWallet", {
265
+ enumerable: true,
266
+ get: function () { return chipiReact.useGetWallet; }
267
+ });
268
+ Object.defineProperty(exports, "usePurchaseSku", {
269
+ enumerable: true,
270
+ get: function () { return chipiReact.usePurchaseSku; }
271
+ });
272
+ Object.defineProperty(exports, "useRecordSendTransaction", {
273
+ enumerable: true,
274
+ get: function () { return chipiReact.useRecordSendTransaction; }
275
+ });
276
+ Object.defineProperty(exports, "useRevokeSessionKey", {
277
+ enumerable: true,
278
+ get: function () { return chipiReact.useRevokeSessionKey; }
279
+ });
280
+ Object.defineProperty(exports, "useTransfer", {
281
+ enumerable: true,
282
+ get: function () { return chipiReact.useTransfer; }
283
+ });
8
284
  Object.defineProperty(exports, "Chain", {
9
285
  enumerable: true,
10
286
  get: function () { return types.Chain; }
@@ -13,17 +289,13 @@ Object.defineProperty(exports, "ChainToken", {
13
289
  enumerable: true,
14
290
  get: function () { return types.ChainToken; }
15
291
  });
16
- Object.keys(chipiReact).forEach(function (k) {
17
- if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
18
- enumerable: true,
19
- get: function () { return chipiReact[k]; }
20
- });
21
- });
22
- Object.keys(types).forEach(function (k) {
23
- if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
24
- enumerable: true,
25
- get: function () { return types[k]; }
26
- });
27
- });
292
+ exports.createNativeWalletPasskey = createNativeWalletPasskey;
293
+ exports.getNativeWalletCredential = getNativeWalletCredential;
294
+ exports.getNativeWalletEncryptKey = getNativeWalletEncryptKey;
295
+ exports.hasNativeWalletPasskey = hasNativeWalletPasskey;
296
+ exports.isNativeBiometricSupported = isNativeBiometricSupported;
297
+ exports.removeNativeWalletPasskey = removeNativeWalletPasskey;
298
+ exports.useCreateWallet = useCreateWallet;
299
+ exports.useMigrateWalletToPasskey = useMigrateWalletToPasskey;
28
300
  //# sourceMappingURL=index.js.map
29
301
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js","sourcesContent":[]}
1
+ {"version":3,"sources":["../src/native-passkey.ts","../src/hooks/useCreateWallet.ts","../src/hooks/useMigrateWalletToPasskey.ts"],"names":["LocalAuthentication","SecureStore","useChipiContext","useMutation","decryptPrivateKey","encryptPrivateKey"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Native Passkey for Expo/React Native\n *\n * Replaces the browser-only @simplewebauthn/browser implementation with a\n * native equivalent using:\n * - expo-local-authentication → biometric gate (Face ID / Touch ID / fingerprint)\n * - expo-secure-store → iOS Keychain / Android Keystore protected storage\n *\n * Security model: a random 32-byte key is generated at wallet creation time,\n * stored in the device's secure enclave behind biometric authentication,\n * and retrieved later by prompting the user again. This is functionally\n * equivalent to the WebAuthn PRF approach used on the web.\n */\n\nimport * as LocalAuthentication from \"expo-local-authentication\";\nimport * as SecureStore from \"expo-secure-store\";\n\nconst ENCRYPT_KEY_PREFIX = \"chipi_wallet_key_\";\nconst CREDENTIAL_META_KEY = \"chipi_wallet_credential\";\n\nexport interface NativeWalletCredential {\n credentialId: string;\n userId: string;\n createdAt: string;\n}\n\nexport interface NativeCreateWalletPasskeyResult {\n encryptKey: string;\n credentialId: string;\n prfSupported: false;\n}\n\n/**\n * Generate a cryptographically random 32-byte hex string.\n * crypto.getRandomValues is available in React Native via Hermes (RN 0.71+).\n */\nfunction generateRandomHex(byteCount: number): string {\n const array = new Uint8Array(byteCount);\n crypto.getRandomValues(array);\n return Array.from(array)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\n/**\n * Returns true if the device has biometric hardware AND the user has enrolled.\n * Must be true before calling createNativeWalletPasskey or getNativeWalletEncryptKey.\n */\nexport async function isNativeBiometricSupported(): Promise<boolean> {\n const hasHardware = await LocalAuthentication.hasHardwareAsync();\n if (!hasHardware) return false;\n const isEnrolled = await LocalAuthentication.isEnrolledAsync();\n return isEnrolled;\n}\n\n/**\n * Create a new native wallet passkey.\n *\n * 1. Verifies biometric support.\n * 2. Prompts the user with Face ID / Touch ID to confirm intent.\n * 3. Generates a random encryption key.\n * 4. Stores the key in the device's Keychain/Keystore, protected by biometrics.\n * 5. Returns the encryption key so the wallet can be created immediately.\n *\n * The key is NEVER stored anywhere else — it lives only in the secure enclave.\n */\nexport async function createNativeWalletPasskey(\n userId: string,\n _userName: string\n): Promise<NativeCreateWalletPasskeyResult> {\n const supported = await isNativeBiometricSupported();\n if (!supported) {\n throw new Error(\n \"Biometric authentication is not available or not enrolled on this device. \" +\n \"Please enroll Face ID, Touch ID, or a fingerprint in your device settings.\"\n );\n }\n\n // Prompt biometrics to confirm user intent before generating/storing the key\n const authResult = await LocalAuthentication.authenticateAsync({\n promptMessage: \"Authenticate to create your wallet\",\n cancelLabel: \"Cancel\",\n disableDeviceFallback: false,\n });\n\n if (!authResult.success) {\n const reason = \"error\" in authResult ? authResult.error : \"unknown\";\n if (reason === \"user_cancel\" || reason === \"app_cancel\") {\n throw new Error(\"Biometric authentication was cancelled\");\n }\n throw new Error(`Biometric authentication failed: ${reason}`);\n }\n\n const encryptKey = generateRandomHex(32);\n const credentialId = `native_biometric_${userId}_${Date.now()}`;\n\n // Store the encryption key — requireAuthentication means future reads need biometrics\n await SecureStore.setItemAsync(\n `${ENCRYPT_KEY_PREFIX}${userId}`,\n encryptKey,\n { requireAuthentication: true }\n );\n\n // Store lightweight credential metadata (not sensitive — no requireAuthentication needed)\n const meta: NativeWalletCredential = {\n credentialId,\n userId,\n createdAt: new Date().toISOString(),\n };\n await SecureStore.setItemAsync(CREDENTIAL_META_KEY, JSON.stringify(meta));\n\n return { encryptKey, credentialId, prfSupported: false };\n}\n\n/**\n * Retrieve the stored encryption key by authenticating with biometrics.\n * expo-secure-store automatically triggers the Face ID / Touch ID prompt\n * when requireAuthentication: true was used during storage.\n *\n * Returns null if no key is stored for the given userId.\n */\nexport async function getNativeWalletEncryptKey(\n userId: string\n): Promise<string | null> {\n const supported = await isNativeBiometricSupported();\n if (!supported) {\n throw new Error(\n \"Biometric authentication is not available or not enrolled on this device.\"\n );\n }\n\n return SecureStore.getItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`, {\n requireAuthentication: true,\n });\n}\n\n/**\n * Returns true if a native wallet passkey has been created on this device.\n */\nexport async function hasNativeWalletPasskey(): Promise<boolean> {\n const stored = await SecureStore.getItemAsync(CREDENTIAL_META_KEY);\n return stored !== null;\n}\n\n/**\n * Returns the stored credential metadata, or null if none exists.\n */\nexport async function getNativeWalletCredential(): Promise<NativeWalletCredential | null> {\n const stored = await SecureStore.getItemAsync(CREDENTIAL_META_KEY);\n if (!stored) return null;\n try {\n return JSON.parse(stored) as NativeWalletCredential;\n } catch {\n return null;\n }\n}\n\n/**\n * Removes the stored encryption key and credential metadata from this device.\n * Use with caution — if the wallet has no other recovery mechanism, this is destructive.\n */\nexport async function removeNativeWalletPasskey(userId: string): Promise<void> {\n await SecureStore.deleteItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`);\n await SecureStore.deleteItemAsync(CREDENTIAL_META_KEY);\n}\n","import { useMutation, type UseMutationResult } from \"@tanstack/react-query\";\nimport { useChipiContext } from \"@chipi-stack/chipi-react\";\nimport type {\n CreateWalletParams,\n CreateWalletResponse,\n} from \"@chipi-stack/types\";\nimport { createNativeWalletPasskey } from \"../native-passkey\";\n\ntype CreateWalletInput = {\n params: CreateWalletParams;\n bearerToken: string;\n};\n\n/**\n * Expo-native override of useCreateWallet.\n *\n * When usePasskey: true is passed, this uses expo-local-authentication +\n * expo-secure-store instead of the browser-only @simplewebauthn/browser,\n * so it works on real iOS and Android devices.\n */\nexport function useCreateWallet(): {\n createWallet: (input: CreateWalletInput) => void;\n createWalletAsync: (\n input: CreateWalletInput\n ) => Promise<CreateWalletResponse>;\n data: CreateWalletResponse | undefined;\n isLoading: boolean;\n isError: boolean;\n error: Error | null;\n isSuccess: boolean;\n reset: () => void;\n} {\n const { chipiSDK } = useChipiContext();\n\n const mutation: UseMutationResult<\n CreateWalletResponse,\n Error,\n CreateWalletInput\n > = useMutation({\n mutationFn: async (input: CreateWalletInput) => {\n let encryptKey = input.params.encryptKey;\n\n if (input.params.usePasskey) {\n if (!input.params.externalUserId) {\n throw new Error(\"externalUserId is required when using passkey\");\n }\n\n try {\n const passkeyResult = await createNativeWalletPasskey(\n input.params.externalUserId,\n input.params.externalUserId\n );\n encryptKey = passkeyResult.encryptKey;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Passkey creation failed: ${error.message}`);\n }\n throw new Error(\"Failed to create passkey for wallet\");\n }\n }\n\n return chipiSDK.createWallet({\n params: {\n ...input.params,\n encryptKey: encryptKey!,\n },\n bearerToken: input.bearerToken,\n });\n },\n });\n\n return {\n createWallet: mutation.mutate,\n createWalletAsync: mutation.mutateAsync,\n data: mutation.data,\n isLoading: mutation.isPending,\n isError: mutation.isError,\n error: mutation.error,\n isSuccess: mutation.isSuccess,\n reset: mutation.reset,\n };\n}\n","import { useMutation, type UseMutationResult } from \"@tanstack/react-query\";\nimport type { MigrateWalletToPasskeyParams, WalletData } from \"@chipi-stack/types\";\nimport { decryptPrivateKey, encryptPrivateKey } from \"@chipi-stack/backend\";\nimport { createNativeWalletPasskey } from \"../native-passkey\";\n\ntype MigrateWalletToPasskeyInput = MigrateWalletToPasskeyParams & {\n bearerToken: string;\n};\n\ninterface MigrateWalletToPasskeyResult {\n success: boolean;\n wallet: WalletData;\n credentialId: string;\n}\n\n/**\n * Expo-native override of useMigrateWalletToPasskey.\n *\n * Migrates a PIN-encrypted wallet to biometric (Face ID / Touch ID) protection.\n * Uses expo-local-authentication + expo-secure-store instead of browser WebAuthn.\n */\nexport function useMigrateWalletToPasskey(): {\n migrateWalletToPasskey: (input: MigrateWalletToPasskeyInput) => void;\n migrateWalletToPasskeyAsync: (\n input: MigrateWalletToPasskeyInput\n ) => Promise<MigrateWalletToPasskeyResult>;\n data: MigrateWalletToPasskeyResult | undefined;\n isLoading: boolean;\n isError: boolean;\n error: Error | null;\n isSuccess: boolean;\n reset: () => void;\n} {\n const mutation: UseMutationResult<\n MigrateWalletToPasskeyResult,\n Error,\n MigrateWalletToPasskeyInput\n > = useMutation({\n mutationFn: async (input: MigrateWalletToPasskeyInput) => {\n const { wallet, oldEncryptKey, externalUserId } = input;\n\n try {\n // Step 1: Create new native passkey and derive encryptKey via biometrics\n const passkeyResult = await createNativeWalletPasskey(\n externalUserId,\n externalUserId\n );\n\n // Step 2: Decrypt the private key with the old PIN/encryptKey\n let decryptedPrivateKey: string;\n try {\n decryptedPrivateKey = decryptPrivateKey(\n wallet.encryptedPrivateKey,\n oldEncryptKey\n );\n } catch {\n throw new Error(\n \"Failed to decrypt wallet with provided encryptKey. Please verify your PIN/password is correct.\"\n );\n }\n\n // Step 3: Re-encrypt with the new biometric-derived encryptKey\n const newEncryptedPrivateKey = encryptPrivateKey(\n decryptedPrivateKey,\n passkeyResult.encryptKey\n );\n\n const updatedWallet: WalletData = {\n ...wallet,\n encryptedPrivateKey: newEncryptedPrivateKey,\n };\n\n return {\n success: true,\n wallet: updatedWallet,\n credentialId: passkeyResult.credentialId,\n };\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Migration failed: ${error.message}`);\n }\n throw new Error(\"Failed to migrate wallet to passkey\");\n }\n },\n });\n\n return {\n migrateWalletToPasskey: mutation.mutate,\n migrateWalletToPasskeyAsync: mutation.mutateAsync,\n data: mutation.data,\n isLoading: mutation.isPending,\n isError: mutation.isError,\n error: mutation.error,\n isSuccess: mutation.isSuccess,\n reset: mutation.reset,\n };\n}\n"]}
package/dist/index.mjs CHANGED
@@ -1,5 +1,184 @@
1
- export * from '@chipi-stack/chipi-react';
2
- export * from '@chipi-stack/types';
1
+ import { useChipiContext } from '@chipi-stack/chipi-react';
2
+ export { ChipiProvider, useAddSessionKeyToContract, useApprove, useCallAnyContract, useChipiContext, useChipiSession, useChipiWallet, useCreateSessionKey, useExecuteWithSession, useGetSessionData, useGetSku, useGetSkuList, useGetSkuPurchase, useGetTokenBalance, useGetTransactionList, useGetWallet, usePurchaseSku, useRecordSendTransaction, useRevokeSessionKey, useTransfer } from '@chipi-stack/chipi-react';
3
+ import { useMutation } from '@tanstack/react-query';
4
+ import * as LocalAuthentication from 'expo-local-authentication';
5
+ import * as SecureStore from 'expo-secure-store';
6
+ import { decryptPrivateKey, encryptPrivateKey } from '@chipi-stack/backend';
3
7
  export { Chain, ChainToken } from '@chipi-stack/types';
8
+
9
+ // src/index.ts
10
+ var ENCRYPT_KEY_PREFIX = "chipi_wallet_key_";
11
+ var CREDENTIAL_META_KEY = "chipi_wallet_credential";
12
+ function generateRandomHex(byteCount) {
13
+ const array = new Uint8Array(byteCount);
14
+ crypto.getRandomValues(array);
15
+ return Array.from(array).map((b) => b.toString(16).padStart(2, "0")).join("");
16
+ }
17
+ async function isNativeBiometricSupported() {
18
+ const hasHardware = await LocalAuthentication.hasHardwareAsync();
19
+ if (!hasHardware) return false;
20
+ const isEnrolled = await LocalAuthentication.isEnrolledAsync();
21
+ return isEnrolled;
22
+ }
23
+ async function createNativeWalletPasskey(userId, _userName) {
24
+ const supported = await isNativeBiometricSupported();
25
+ if (!supported) {
26
+ throw new Error(
27
+ "Biometric authentication is not available or not enrolled on this device. Please enroll Face ID, Touch ID, or a fingerprint in your device settings."
28
+ );
29
+ }
30
+ const authResult = await LocalAuthentication.authenticateAsync({
31
+ promptMessage: "Authenticate to create your wallet",
32
+ cancelLabel: "Cancel",
33
+ disableDeviceFallback: false
34
+ });
35
+ if (!authResult.success) {
36
+ const reason = "error" in authResult ? authResult.error : "unknown";
37
+ if (reason === "user_cancel" || reason === "app_cancel") {
38
+ throw new Error("Biometric authentication was cancelled");
39
+ }
40
+ throw new Error(`Biometric authentication failed: ${reason}`);
41
+ }
42
+ const encryptKey = generateRandomHex(32);
43
+ const credentialId = `native_biometric_${userId}_${Date.now()}`;
44
+ await SecureStore.setItemAsync(
45
+ `${ENCRYPT_KEY_PREFIX}${userId}`,
46
+ encryptKey,
47
+ { requireAuthentication: true }
48
+ );
49
+ const meta = {
50
+ credentialId,
51
+ userId,
52
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
53
+ };
54
+ await SecureStore.setItemAsync(CREDENTIAL_META_KEY, JSON.stringify(meta));
55
+ return { encryptKey, credentialId, prfSupported: false };
56
+ }
57
+ async function getNativeWalletEncryptKey(userId) {
58
+ const supported = await isNativeBiometricSupported();
59
+ if (!supported) {
60
+ throw new Error(
61
+ "Biometric authentication is not available or not enrolled on this device."
62
+ );
63
+ }
64
+ return SecureStore.getItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`, {
65
+ requireAuthentication: true
66
+ });
67
+ }
68
+ async function hasNativeWalletPasskey() {
69
+ const stored = await SecureStore.getItemAsync(CREDENTIAL_META_KEY);
70
+ return stored !== null;
71
+ }
72
+ async function getNativeWalletCredential() {
73
+ const stored = await SecureStore.getItemAsync(CREDENTIAL_META_KEY);
74
+ if (!stored) return null;
75
+ try {
76
+ return JSON.parse(stored);
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+ async function removeNativeWalletPasskey(userId) {
82
+ await SecureStore.deleteItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`);
83
+ await SecureStore.deleteItemAsync(CREDENTIAL_META_KEY);
84
+ }
85
+
86
+ // src/hooks/useCreateWallet.ts
87
+ function useCreateWallet() {
88
+ const { chipiSDK } = useChipiContext();
89
+ const mutation = useMutation({
90
+ mutationFn: async (input) => {
91
+ let encryptKey = input.params.encryptKey;
92
+ if (input.params.usePasskey) {
93
+ if (!input.params.externalUserId) {
94
+ throw new Error("externalUserId is required when using passkey");
95
+ }
96
+ try {
97
+ const passkeyResult = await createNativeWalletPasskey(
98
+ input.params.externalUserId,
99
+ input.params.externalUserId
100
+ );
101
+ encryptKey = passkeyResult.encryptKey;
102
+ } catch (error) {
103
+ if (error instanceof Error) {
104
+ throw new Error(`Passkey creation failed: ${error.message}`);
105
+ }
106
+ throw new Error("Failed to create passkey for wallet");
107
+ }
108
+ }
109
+ return chipiSDK.createWallet({
110
+ params: {
111
+ ...input.params,
112
+ encryptKey
113
+ },
114
+ bearerToken: input.bearerToken
115
+ });
116
+ }
117
+ });
118
+ return {
119
+ createWallet: mutation.mutate,
120
+ createWalletAsync: mutation.mutateAsync,
121
+ data: mutation.data,
122
+ isLoading: mutation.isPending,
123
+ isError: mutation.isError,
124
+ error: mutation.error,
125
+ isSuccess: mutation.isSuccess,
126
+ reset: mutation.reset
127
+ };
128
+ }
129
+ function useMigrateWalletToPasskey() {
130
+ const mutation = useMutation({
131
+ mutationFn: async (input) => {
132
+ const { wallet, oldEncryptKey, externalUserId } = input;
133
+ try {
134
+ const passkeyResult = await createNativeWalletPasskey(
135
+ externalUserId,
136
+ externalUserId
137
+ );
138
+ let decryptedPrivateKey;
139
+ try {
140
+ decryptedPrivateKey = decryptPrivateKey(
141
+ wallet.encryptedPrivateKey,
142
+ oldEncryptKey
143
+ );
144
+ } catch {
145
+ throw new Error(
146
+ "Failed to decrypt wallet with provided encryptKey. Please verify your PIN/password is correct."
147
+ );
148
+ }
149
+ const newEncryptedPrivateKey = encryptPrivateKey(
150
+ decryptedPrivateKey,
151
+ passkeyResult.encryptKey
152
+ );
153
+ const updatedWallet = {
154
+ ...wallet,
155
+ encryptedPrivateKey: newEncryptedPrivateKey
156
+ };
157
+ return {
158
+ success: true,
159
+ wallet: updatedWallet,
160
+ credentialId: passkeyResult.credentialId
161
+ };
162
+ } catch (error) {
163
+ if (error instanceof Error) {
164
+ throw new Error(`Migration failed: ${error.message}`);
165
+ }
166
+ throw new Error("Failed to migrate wallet to passkey");
167
+ }
168
+ }
169
+ });
170
+ return {
171
+ migrateWalletToPasskey: mutation.mutate,
172
+ migrateWalletToPasskeyAsync: mutation.mutateAsync,
173
+ data: mutation.data,
174
+ isLoading: mutation.isPending,
175
+ isError: mutation.isError,
176
+ error: mutation.error,
177
+ isSuccess: mutation.isSuccess,
178
+ reset: mutation.reset
179
+ };
180
+ }
181
+
182
+ export { createNativeWalletPasskey, getNativeWalletCredential, getNativeWalletEncryptKey, hasNativeWalletPasskey, isNativeBiometricSupported, removeNativeWalletPasskey, useCreateWallet, useMigrateWalletToPasskey };
4
183
  //# sourceMappingURL=index.mjs.map
5
184
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"index.mjs","sourcesContent":[]}
1
+ {"version":3,"sources":["../src/native-passkey.ts","../src/hooks/useCreateWallet.ts","../src/hooks/useMigrateWalletToPasskey.ts"],"names":["useMutation"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Native Passkey for Expo/React Native\n *\n * Replaces the browser-only @simplewebauthn/browser implementation with a\n * native equivalent using:\n * - expo-local-authentication → biometric gate (Face ID / Touch ID / fingerprint)\n * - expo-secure-store → iOS Keychain / Android Keystore protected storage\n *\n * Security model: a random 32-byte key is generated at wallet creation time,\n * stored in the device's secure enclave behind biometric authentication,\n * and retrieved later by prompting the user again. This is functionally\n * equivalent to the WebAuthn PRF approach used on the web.\n */\n\nimport * as LocalAuthentication from \"expo-local-authentication\";\nimport * as SecureStore from \"expo-secure-store\";\n\nconst ENCRYPT_KEY_PREFIX = \"chipi_wallet_key_\";\nconst CREDENTIAL_META_KEY = \"chipi_wallet_credential\";\n\nexport interface NativeWalletCredential {\n credentialId: string;\n userId: string;\n createdAt: string;\n}\n\nexport interface NativeCreateWalletPasskeyResult {\n encryptKey: string;\n credentialId: string;\n prfSupported: false;\n}\n\n/**\n * Generate a cryptographically random 32-byte hex string.\n * crypto.getRandomValues is available in React Native via Hermes (RN 0.71+).\n */\nfunction generateRandomHex(byteCount: number): string {\n const array = new Uint8Array(byteCount);\n crypto.getRandomValues(array);\n return Array.from(array)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\n/**\n * Returns true if the device has biometric hardware AND the user has enrolled.\n * Must be true before calling createNativeWalletPasskey or getNativeWalletEncryptKey.\n */\nexport async function isNativeBiometricSupported(): Promise<boolean> {\n const hasHardware = await LocalAuthentication.hasHardwareAsync();\n if (!hasHardware) return false;\n const isEnrolled = await LocalAuthentication.isEnrolledAsync();\n return isEnrolled;\n}\n\n/**\n * Create a new native wallet passkey.\n *\n * 1. Verifies biometric support.\n * 2. Prompts the user with Face ID / Touch ID to confirm intent.\n * 3. Generates a random encryption key.\n * 4. Stores the key in the device's Keychain/Keystore, protected by biometrics.\n * 5. Returns the encryption key so the wallet can be created immediately.\n *\n * The key is NEVER stored anywhere else — it lives only in the secure enclave.\n */\nexport async function createNativeWalletPasskey(\n userId: string,\n _userName: string\n): Promise<NativeCreateWalletPasskeyResult> {\n const supported = await isNativeBiometricSupported();\n if (!supported) {\n throw new Error(\n \"Biometric authentication is not available or not enrolled on this device. \" +\n \"Please enroll Face ID, Touch ID, or a fingerprint in your device settings.\"\n );\n }\n\n // Prompt biometrics to confirm user intent before generating/storing the key\n const authResult = await LocalAuthentication.authenticateAsync({\n promptMessage: \"Authenticate to create your wallet\",\n cancelLabel: \"Cancel\",\n disableDeviceFallback: false,\n });\n\n if (!authResult.success) {\n const reason = \"error\" in authResult ? authResult.error : \"unknown\";\n if (reason === \"user_cancel\" || reason === \"app_cancel\") {\n throw new Error(\"Biometric authentication was cancelled\");\n }\n throw new Error(`Biometric authentication failed: ${reason}`);\n }\n\n const encryptKey = generateRandomHex(32);\n const credentialId = `native_biometric_${userId}_${Date.now()}`;\n\n // Store the encryption key — requireAuthentication means future reads need biometrics\n await SecureStore.setItemAsync(\n `${ENCRYPT_KEY_PREFIX}${userId}`,\n encryptKey,\n { requireAuthentication: true }\n );\n\n // Store lightweight credential metadata (not sensitive — no requireAuthentication needed)\n const meta: NativeWalletCredential = {\n credentialId,\n userId,\n createdAt: new Date().toISOString(),\n };\n await SecureStore.setItemAsync(CREDENTIAL_META_KEY, JSON.stringify(meta));\n\n return { encryptKey, credentialId, prfSupported: false };\n}\n\n/**\n * Retrieve the stored encryption key by authenticating with biometrics.\n * expo-secure-store automatically triggers the Face ID / Touch ID prompt\n * when requireAuthentication: true was used during storage.\n *\n * Returns null if no key is stored for the given userId.\n */\nexport async function getNativeWalletEncryptKey(\n userId: string\n): Promise<string | null> {\n const supported = await isNativeBiometricSupported();\n if (!supported) {\n throw new Error(\n \"Biometric authentication is not available or not enrolled on this device.\"\n );\n }\n\n return SecureStore.getItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`, {\n requireAuthentication: true,\n });\n}\n\n/**\n * Returns true if a native wallet passkey has been created on this device.\n */\nexport async function hasNativeWalletPasskey(): Promise<boolean> {\n const stored = await SecureStore.getItemAsync(CREDENTIAL_META_KEY);\n return stored !== null;\n}\n\n/**\n * Returns the stored credential metadata, or null if none exists.\n */\nexport async function getNativeWalletCredential(): Promise<NativeWalletCredential | null> {\n const stored = await SecureStore.getItemAsync(CREDENTIAL_META_KEY);\n if (!stored) return null;\n try {\n return JSON.parse(stored) as NativeWalletCredential;\n } catch {\n return null;\n }\n}\n\n/**\n * Removes the stored encryption key and credential metadata from this device.\n * Use with caution — if the wallet has no other recovery mechanism, this is destructive.\n */\nexport async function removeNativeWalletPasskey(userId: string): Promise<void> {\n await SecureStore.deleteItemAsync(`${ENCRYPT_KEY_PREFIX}${userId}`);\n await SecureStore.deleteItemAsync(CREDENTIAL_META_KEY);\n}\n","import { useMutation, type UseMutationResult } from \"@tanstack/react-query\";\nimport { useChipiContext } from \"@chipi-stack/chipi-react\";\nimport type {\n CreateWalletParams,\n CreateWalletResponse,\n} from \"@chipi-stack/types\";\nimport { createNativeWalletPasskey } from \"../native-passkey\";\n\ntype CreateWalletInput = {\n params: CreateWalletParams;\n bearerToken: string;\n};\n\n/**\n * Expo-native override of useCreateWallet.\n *\n * When usePasskey: true is passed, this uses expo-local-authentication +\n * expo-secure-store instead of the browser-only @simplewebauthn/browser,\n * so it works on real iOS and Android devices.\n */\nexport function useCreateWallet(): {\n createWallet: (input: CreateWalletInput) => void;\n createWalletAsync: (\n input: CreateWalletInput\n ) => Promise<CreateWalletResponse>;\n data: CreateWalletResponse | undefined;\n isLoading: boolean;\n isError: boolean;\n error: Error | null;\n isSuccess: boolean;\n reset: () => void;\n} {\n const { chipiSDK } = useChipiContext();\n\n const mutation: UseMutationResult<\n CreateWalletResponse,\n Error,\n CreateWalletInput\n > = useMutation({\n mutationFn: async (input: CreateWalletInput) => {\n let encryptKey = input.params.encryptKey;\n\n if (input.params.usePasskey) {\n if (!input.params.externalUserId) {\n throw new Error(\"externalUserId is required when using passkey\");\n }\n\n try {\n const passkeyResult = await createNativeWalletPasskey(\n input.params.externalUserId,\n input.params.externalUserId\n );\n encryptKey = passkeyResult.encryptKey;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Passkey creation failed: ${error.message}`);\n }\n throw new Error(\"Failed to create passkey for wallet\");\n }\n }\n\n return chipiSDK.createWallet({\n params: {\n ...input.params,\n encryptKey: encryptKey!,\n },\n bearerToken: input.bearerToken,\n });\n },\n });\n\n return {\n createWallet: mutation.mutate,\n createWalletAsync: mutation.mutateAsync,\n data: mutation.data,\n isLoading: mutation.isPending,\n isError: mutation.isError,\n error: mutation.error,\n isSuccess: mutation.isSuccess,\n reset: mutation.reset,\n };\n}\n","import { useMutation, type UseMutationResult } from \"@tanstack/react-query\";\nimport type { MigrateWalletToPasskeyParams, WalletData } from \"@chipi-stack/types\";\nimport { decryptPrivateKey, encryptPrivateKey } from \"@chipi-stack/backend\";\nimport { createNativeWalletPasskey } from \"../native-passkey\";\n\ntype MigrateWalletToPasskeyInput = MigrateWalletToPasskeyParams & {\n bearerToken: string;\n};\n\ninterface MigrateWalletToPasskeyResult {\n success: boolean;\n wallet: WalletData;\n credentialId: string;\n}\n\n/**\n * Expo-native override of useMigrateWalletToPasskey.\n *\n * Migrates a PIN-encrypted wallet to biometric (Face ID / Touch ID) protection.\n * Uses expo-local-authentication + expo-secure-store instead of browser WebAuthn.\n */\nexport function useMigrateWalletToPasskey(): {\n migrateWalletToPasskey: (input: MigrateWalletToPasskeyInput) => void;\n migrateWalletToPasskeyAsync: (\n input: MigrateWalletToPasskeyInput\n ) => Promise<MigrateWalletToPasskeyResult>;\n data: MigrateWalletToPasskeyResult | undefined;\n isLoading: boolean;\n isError: boolean;\n error: Error | null;\n isSuccess: boolean;\n reset: () => void;\n} {\n const mutation: UseMutationResult<\n MigrateWalletToPasskeyResult,\n Error,\n MigrateWalletToPasskeyInput\n > = useMutation({\n mutationFn: async (input: MigrateWalletToPasskeyInput) => {\n const { wallet, oldEncryptKey, externalUserId } = input;\n\n try {\n // Step 1: Create new native passkey and derive encryptKey via biometrics\n const passkeyResult = await createNativeWalletPasskey(\n externalUserId,\n externalUserId\n );\n\n // Step 2: Decrypt the private key with the old PIN/encryptKey\n let decryptedPrivateKey: string;\n try {\n decryptedPrivateKey = decryptPrivateKey(\n wallet.encryptedPrivateKey,\n oldEncryptKey\n );\n } catch {\n throw new Error(\n \"Failed to decrypt wallet with provided encryptKey. Please verify your PIN/password is correct.\"\n );\n }\n\n // Step 3: Re-encrypt with the new biometric-derived encryptKey\n const newEncryptedPrivateKey = encryptPrivateKey(\n decryptedPrivateKey,\n passkeyResult.encryptKey\n );\n\n const updatedWallet: WalletData = {\n ...wallet,\n encryptedPrivateKey: newEncryptedPrivateKey,\n };\n\n return {\n success: true,\n wallet: updatedWallet,\n credentialId: passkeyResult.credentialId,\n };\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Migration failed: ${error.message}`);\n }\n throw new Error(\"Failed to migrate wallet to passkey\");\n }\n },\n });\n\n return {\n migrateWalletToPasskey: mutation.mutate,\n migrateWalletToPasskeyAsync: mutation.mutateAsync,\n data: mutation.data,\n isLoading: mutation.isPending,\n isError: mutation.isError,\n error: mutation.error,\n isSuccess: mutation.isSuccess,\n reset: mutation.reset,\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chipi-stack/chipi-expo",
3
- "version": "13.10.0",
3
+ "version": "13.11.0",
4
4
  "description": "Chipi SDK for React Native and Expo applications",
5
5
  "keywords": [
6
6
  "chipi",
@@ -55,15 +55,28 @@
55
55
  },
56
56
  "dependencies": {
57
57
  "@tanstack/react-query": "^5.85.0",
58
- "@chipi-stack/backend": "^13.9.0",
59
- "@chipi-stack/shared": "^13.9.0",
60
- "@chipi-stack/chipi-react": "^13.9.0",
61
- "@chipi-stack/types": "^13.10.0"
58
+ "@chipi-stack/backend": "^13.10.0",
59
+ "@chipi-stack/chipi-react": "^13.10.0",
60
+ "@chipi-stack/types": "^13.11.0",
61
+ "@chipi-stack/shared": "^13.10.0"
62
62
  },
63
63
  "peerDependencies": {
64
+ "expo-local-authentication": ">=14.0.0",
65
+ "expo-secure-store": ">=13.0.0",
64
66
  "react": ">=16.8.0",
65
67
  "react-dom": ">=16.8.0"
66
68
  },
69
+ "peerDependenciesMeta": {
70
+ "expo-local-authentication": {
71
+ "optional": false
72
+ },
73
+ "expo-secure-store": {
74
+ "optional": false
75
+ },
76
+ "react-dom": {
77
+ "optional": true
78
+ }
79
+ },
67
80
  "engines": {
68
81
  "node": ">=18.17.0"
69
82
  },