@getpara/react-native-wallet 2.17.0 → 2.18.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.
@@ -34,6 +34,18 @@ export declare class ParaMobile extends ParaCore {
34
34
  * @returns {Promise<void>}
35
35
  */
36
36
  loginWithPasskey(): Promise<void>;
37
+ /**
38
+ * Sets a global handler for transaction review URLs. When a signing operation requires
39
+ * user approval (e.g., due to permissions policies), the SDK needs to open a review URL.
40
+ * On React Native, provide a handler that opens the URL in an in-app browser.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * import { openBrowserAsync } from 'expo-web-browser';
45
+ * para.setTransactionReviewHandler((url) => openBrowserAsync(url));
46
+ * ```
47
+ */
48
+ setTransactionReviewHandler(handler: (url: string) => void): void;
37
49
  /**
38
50
  * Handles a message from an in-app browser (WebView).
39
51
  * Call this from your WebView's `onMessage` callback to bridge portal events
@@ -13,7 +13,6 @@ import { Passkey, } from 'react-native-passkey';
13
13
  import { AuthMethodStatus } from '@getpara/user-management-client';
14
14
  import { setEnv } from '../config.js';
15
15
  import base64url from 'base64url';
16
- import { webcrypto } from 'crypto';
17
16
  const ES256_ALGORITHM = -7;
18
17
  const RS256_ALGORITHM = -257;
19
18
  /**
@@ -120,11 +119,11 @@ export class ParaMobile extends ParaCore {
120
119
  }
121
120
  const userId = this.assertUserId();
122
121
  const authInfo = this.assertIsAuthSet();
123
- if (!webcrypto || !webcrypto.getRandomValues) {
122
+ if (!globalThis.crypto || !globalThis.crypto.getRandomValues) {
124
123
  throw new Error('Web crypto is not available. Ensure you have imported the shim from @getpara/react-native-wallet.');
125
124
  }
126
125
  const userHandle = new Uint8Array(32);
127
- webcrypto.getRandomValues(userHandle);
126
+ globalThis.crypto.getRandomValues(userHandle);
128
127
  const userHandleEncoded = base64url.encode(userHandle);
129
128
  const requestJson = {
130
129
  authenticatorSelection: {
@@ -256,6 +255,20 @@ export class ParaMobile extends ParaCore {
256
255
  });
257
256
  });
258
257
  }
258
+ /**
259
+ * Sets a global handler for transaction review URLs. When a signing operation requires
260
+ * user approval (e.g., due to permissions policies), the SDK needs to open a review URL.
261
+ * On React Native, provide a handler that opens the URL in an in-app browser.
262
+ *
263
+ * @example
264
+ * ```ts
265
+ * import { openBrowserAsync } from 'expo-web-browser';
266
+ * para.setTransactionReviewHandler((url) => openBrowserAsync(url));
267
+ * ```
268
+ */
269
+ setTransactionReviewHandler(handler) {
270
+ this.platformUtils.transactionReviewHandler = handler;
271
+ }
259
272
  /**
260
273
  * Handles a message from an in-app browser (WebView).
261
274
  * Call this from your WebView's `onMessage` callback to bridge portal events
@@ -11,6 +11,7 @@ export declare class ReactNativeUtils implements PlatformUtils {
11
11
  sessionStorage: AsyncStorage;
12
12
  secureStorage: KeychainStorage;
13
13
  isSyncStorage: boolean;
14
+ transactionReviewHandler?: (url: string) => void;
14
15
  private eventHandlers;
15
16
  addEventListener<T = EventData>({ event, handler }: {
16
17
  ctx: Ctx;
@@ -45,7 +46,7 @@ export declare class ReactNativeUtils implements PlatformUtils {
45
46
  walletId: string;
46
47
  }>;
47
48
  getPrivateKey(_ctx: Ctx, _userId: string, _walletId: string, _share: string, _sessionCookie: string): Promise<string>;
48
- openPopup(_popupUrl: string): any;
49
+ openPopup(popupUrl: string): any;
49
50
  private baseSignTransaction;
50
51
  sendTransaction(ctx: Ctx, userId: string, walletId: string, share: string, rlpEncodedTxBase64: string, chainId: string, _sessionCookie: string, isDKLS?: boolean): Promise<SignatureRes>;
51
52
  signHash(_address: string, _hash: string): Promise<{
@@ -118,8 +118,12 @@ export class ReactNativeUtils {
118
118
  getPrivateKey(_ctx, _userId, _walletId, _share, _sessionCookie) {
119
119
  throw new Error('Method not implemented.');
120
120
  }
121
- openPopup(_popupUrl) {
122
- throw new Error('Method not implemented.');
121
+ openPopup(popupUrl) {
122
+ if (this.transactionReviewHandler) {
123
+ this.transactionReviewHandler(popupUrl);
124
+ return;
125
+ }
126
+ throw new Error('openPopup is not supported on React Native. Use setTransactionReviewHandler() to handle transaction review URLs.');
123
127
  }
124
128
  baseSignTransaction(ctx, userId, walletId, protocolId, share, rlpEncodedTxBase64, chainId, isDKLS) {
125
129
  return __awaiter(this, void 0, void 0, function* () {
@@ -135,11 +139,14 @@ export class ReactNativeUtils {
135
139
  }
136
140
  sendTransaction(ctx, userId, walletId, share, rlpEncodedTxBase64, chainId, _sessionCookie, isDKLS) {
137
141
  return __awaiter(this, void 0, void 0, function* () {
138
- const { protocolId } = (yield ctx.client.sendTransaction(userId, walletId, {
142
+ const { data } = yield ctx.client.sendTransaction(userId, walletId, {
139
143
  transaction: rlpEncodedTxBase64,
140
144
  chainId,
141
- })).data;
142
- return this.baseSignTransaction(ctx, userId, walletId, protocolId, share, rlpEncodedTxBase64, chainId, isDKLS);
145
+ });
146
+ if (data.pendingTransactionId) {
147
+ return { pendingTransactionId: data.pendingTransactionId };
148
+ }
149
+ return this.baseSignTransaction(ctx, userId, walletId, data.protocolId, share, rlpEncodedTxBase64, chainId, isDKLS);
143
150
  });
144
151
  }
145
152
  signHash(_address, _hash) {
@@ -151,6 +158,9 @@ export class ReactNativeUtils {
151
158
  _sessionCookie, isDKLS) {
152
159
  return __awaiter(this, void 0, void 0, function* () {
153
160
  const res = yield ctx.client.preSignMessage(userId, walletId, messageBase64);
161
+ if (res.pendingTransactionId) {
162
+ return { pendingTransactionId: res.pendingTransactionId };
163
+ }
154
164
  if (ctx.mpcComputationClient && !isDKLS) {
155
165
  const signature = (yield signMessageRequest(ctx, userId, walletId, res.protocolId, messageBase64, share)).signature;
156
166
  return { signature };
@@ -166,11 +176,14 @@ export class ReactNativeUtils {
166
176
  signTransaction(ctx, userId, walletId, share, rlpEncodedTxBase64, // base64 encoding of rlp encoded tx
167
177
  chainId, _sessionCookie, isDKLS) {
168
178
  return __awaiter(this, void 0, void 0, function* () {
169
- const { protocolId } = (yield ctx.client.signTransaction(userId, walletId, {
179
+ const { data } = yield ctx.client.signTransaction(userId, walletId, {
170
180
  transaction: rlpEncodedTxBase64,
171
181
  chainId,
172
- })).data;
173
- return this.baseSignTransaction(ctx, userId, walletId, protocolId, share, rlpEncodedTxBase64, chainId, isDKLS);
182
+ });
183
+ if (data.pendingTransactionId) {
184
+ return { pendingTransactionId: data.pendingTransactionId };
185
+ }
186
+ return this.baseSignTransaction(ctx, userId, walletId, data.protocolId, share, rlpEncodedTxBase64, chainId, isDKLS);
174
187
  });
175
188
  }
176
189
  ed25519Keygen(ctx, userId, _sessionCookie, _emailProps) {
@@ -197,8 +210,11 @@ export class ReactNativeUtils {
197
210
  }
198
211
  ed25519Sign(ctx, userId, walletId, share, base64Bytes, _sessionCookie) {
199
212
  return __awaiter(this, void 0, void 0, function* () {
200
- const { protocolId } = yield ctx.client.preSignMessage(userId, walletId, base64Bytes, 'ED25519');
201
- const base64Sig = yield ParaSignerModule.ed25519Sign(protocolId, share, base64Bytes);
213
+ const res = yield ctx.client.preSignMessage(userId, walletId, base64Bytes, 'ED25519');
214
+ if (res.pendingTransactionId) {
215
+ return { pendingTransactionId: res.pendingTransactionId };
216
+ }
217
+ const base64Sig = yield ParaSignerModule.ed25519Sign(res.protocolId, share, base64Bytes);
202
218
  return { signature: base64Sig };
203
219
  });
204
220
  }
package/dist/shim.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export function ensureParaCrypto(): {
2
- baseCrypto: webcrypto.Crypto | {
2
+ baseCrypto: import("crypto").webcrypto.Crypto | {
3
3
  getCiphers: () => string[];
4
4
  getHashes: () => string[];
5
5
  webcrypto: {
@@ -125,6 +125,5 @@ export function ensureParaCrypto(): {
125
125
  peculiarCrypto: PeculiarCrypto;
126
126
  };
127
127
  export function ensureCreateECDH(): any;
128
- import { webcrypto } from 'crypto';
129
128
  import { Buffer } from '@craftzdog/react-native-buffer';
130
129
  import { Crypto as PeculiarCrypto } from '@peculiar/webcrypto';
package/dist/shim.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import quickCrypto from 'react-native-quick-crypto';
2
- import { webcrypto } from 'crypto';
3
2
  import { Crypto as PeculiarCrypto } from '@peculiar/webcrypto';
4
3
  import { ec as EllipticEC } from 'elliptic';
5
4
  import Forge from 'node-forge';
@@ -246,8 +245,26 @@ const ensureParaCrypto = () => {
246
245
  };
247
246
  const setupCryptoPolyfills = () => {
248
247
  const { peculiarCrypto } = ensureParaCrypto();
249
- if (webcrypto && typeof webcrypto === 'object') {
250
- webcrypto.getRandomValues = peculiarCrypto.getRandomValues.bind(peculiarCrypto);
248
+ // Ensure `import { webcrypto } from 'crypto'` works regardless of quick-crypto version.
249
+ // quick-crypto 1.x (Nitro) removed the webcrypto export that 0.7.x had.
250
+ try {
251
+ const cryptoModule = require('crypto');
252
+ if (!cryptoModule.webcrypto || typeof cryptoModule.webcrypto !== 'object') {
253
+ Object.defineProperty(cryptoModule, 'webcrypto', {
254
+ get() {
255
+ return globalThis.crypto;
256
+ },
257
+ configurable: true,
258
+ enumerable: true,
259
+ });
260
+ }
261
+ if (cryptoModule.webcrypto && typeof cryptoModule.webcrypto === 'object') {
262
+ cryptoModule.webcrypto.getRandomValues = peculiarCrypto.getRandomValues.bind(peculiarCrypto);
263
+ }
264
+ // eslint-disable-next-line no-unused-vars
265
+ }
266
+ catch (_err) {
267
+ // crypto module not available
251
268
  }
252
269
  if (typeof Forge === 'undefined') {
253
270
  throw new Error('node-forge not loaded');
package/package.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "name": "@getpara/react-native-wallet",
3
3
  "description": "Para Wallet for React Native",
4
- "version": "2.17.0",
4
+ "version": "2.18.1",
5
5
  "author": "Para Team <hello@getpara.com> (https://getpara.com)",
6
6
  "dependencies": {
7
- "@getpara/core-sdk": "2.17.0",
8
- "@getpara/user-management-client": "2.17.0",
9
- "@getpara/viem-v2-integration": "2.17.0",
10
- "@getpara/web-sdk": "2.17.0",
7
+ "@getpara/core-sdk": "2.18.0",
8
+ "@getpara/user-management-client": "2.18.0",
9
+ "@getpara/viem-v2-integration": "2.18.0",
10
+ "@getpara/web-sdk": "2.18.0",
11
11
  "@peculiar/webcrypto": "^1.5.0",
12
12
  "@ungap/structured-clone": "1.3.0",
13
+ "readable-stream": "^4.5.2",
13
14
  "react-native-url-polyfill": "2.0.0",
14
15
  "text-encoding": "0.7.0"
15
16
  },
@@ -24,7 +25,8 @@
24
25
  "react-native-modpow": "^1.1.0",
25
26
  "react-native-passkey": "^3.1.0",
26
27
  "react-native-quick-base64": "^2.2.0",
27
- "react-native-quick-crypto": "^0.7.14",
28
+ "react-native-nitro-modules": "^0.29.1",
29
+ "react-native-quick-crypto": "^1.0.0",
28
30
  "typescript": "^5.8.3"
29
31
  },
30
32
  "exports": {
@@ -58,10 +60,10 @@
58
60
  "react-native": ">=0.70.0",
59
61
  "react-native-keychain": ">=8.0.0",
60
62
  "react-native-modpow": ">=1.0.0",
63
+ "react-native-nitro-modules": ">=0.29.1",
61
64
  "react-native-passkey": ">=3.0.0",
62
65
  "react-native-quick-base64": ">=2.0.0",
63
- "react-native-quick-crypto": ">=0.7.0",
64
- "readable-stream": ">=4.0.0"
66
+ "react-native-quick-crypto": ">=1.0.0"
65
67
  },
66
68
  "publishConfig": {
67
69
  "access": "public"
@@ -92,6 +94,5 @@
92
94
  "./dist/index.d.ts"
93
95
  ]
94
96
  }
95
- },
96
- "gitHead": "3ed1f835b97ae720f1ac747611296e3b86f61138"
97
+ }
97
98
  }
@@ -26,7 +26,6 @@ import {
26
26
  import { CurrentWalletIds, AuthMethodStatus, TWalletScheme } from '@getpara/user-management-client';
27
27
  import { setEnv } from '../config.js';
28
28
  import base64url from 'base64url';
29
- import { webcrypto } from 'crypto';
30
29
 
31
30
  const ES256_ALGORITHM = -7;
32
31
  const RS256_ALGORITHM = -257;
@@ -159,11 +158,11 @@ export class ParaMobile extends ParaCore {
159
158
  const userId = this.assertUserId();
160
159
  const authInfo = this.assertIsAuthSet();
161
160
 
162
- if (!webcrypto || !webcrypto.getRandomValues) {
161
+ if (!globalThis.crypto || !globalThis.crypto.getRandomValues) {
163
162
  throw new Error('Web crypto is not available. Ensure you have imported the shim from @getpara/react-native-wallet.');
164
163
  }
165
164
  const userHandle = new Uint8Array(32);
166
- webcrypto.getRandomValues(userHandle);
165
+ globalThis.crypto.getRandomValues(userHandle);
167
166
  const userHandleEncoded = base64url.encode(userHandle as any);
168
167
 
169
168
  const requestJson: PasskeyCreateRequest = {
@@ -326,6 +325,21 @@ export class ParaMobile extends ParaCore {
326
325
  });
327
326
  }
328
327
 
328
+ /**
329
+ * Sets a global handler for transaction review URLs. When a signing operation requires
330
+ * user approval (e.g., due to permissions policies), the SDK needs to open a review URL.
331
+ * On React Native, provide a handler that opens the URL in an in-app browser.
332
+ *
333
+ * @example
334
+ * ```ts
335
+ * import { openBrowserAsync } from 'expo-web-browser';
336
+ * para.setTransactionReviewHandler((url) => openBrowserAsync(url));
337
+ * ```
338
+ */
339
+ setTransactionReviewHandler(handler: (url: string) => void): void {
340
+ (this.platformUtils as ReactNativeUtils).transactionReviewHandler = handler;
341
+ }
342
+
329
343
  /**
330
344
  * Handles a message from an in-app browser (WebView).
331
345
  * Call this from your WebView's `onMessage` callback to bridge portal events
@@ -62,6 +62,8 @@ export class ReactNativeUtils implements PlatformUtils {
62
62
  secureStorage = new KeychainStorage();
63
63
  isSyncStorage = false;
64
64
 
65
+ transactionReviewHandler?: (url: string) => void;
66
+
65
67
  private eventHandlers = new Map<string, Set<EventHandler>>();
66
68
 
67
69
  addEventListener<T = EventData>({ event, handler }: { ctx: Ctx; event: string; handler: EventHandler<T> }): void {
@@ -158,8 +160,14 @@ export class ReactNativeUtils implements PlatformUtils {
158
160
  throw new Error('Method not implemented.');
159
161
  }
160
162
 
161
- openPopup(_popupUrl: string): any {
162
- throw new Error('Method not implemented.');
163
+ openPopup(popupUrl: string): any {
164
+ if (this.transactionReviewHandler) {
165
+ this.transactionReviewHandler(popupUrl);
166
+ return;
167
+ }
168
+ throw new Error(
169
+ 'openPopup is not supported on React Native. Use setTransactionReviewHandler() to handle transaction review URLs.',
170
+ );
163
171
  }
164
172
 
165
173
  private async baseSignTransaction(
@@ -193,13 +201,16 @@ export class ReactNativeUtils implements PlatformUtils {
193
201
  _sessionCookie: string,
194
202
  isDKLS?: boolean,
195
203
  ): Promise<SignatureRes> {
196
- const { protocolId } = (
197
- await ctx.client.sendTransaction(userId, walletId, {
198
- transaction: rlpEncodedTxBase64,
199
- chainId,
200
- })
201
- ).data;
202
- return this.baseSignTransaction(ctx, userId, walletId, protocolId, share, rlpEncodedTxBase64, chainId, isDKLS);
204
+ const { data } = await ctx.client.sendTransaction(userId, walletId, {
205
+ transaction: rlpEncodedTxBase64,
206
+ chainId,
207
+ });
208
+
209
+ if (data.pendingTransactionId) {
210
+ return { pendingTransactionId: data.pendingTransactionId };
211
+ }
212
+
213
+ return this.baseSignTransaction(ctx, userId, walletId, data.protocolId, share, rlpEncodedTxBase64, chainId, isDKLS);
203
214
  }
204
215
 
205
216
  async signHash(_address: string, _hash: string): Promise<{ v: number; r: Buffer; s: Buffer }> {
@@ -217,6 +228,10 @@ export class ReactNativeUtils implements PlatformUtils {
217
228
  ): Promise<SignatureRes> {
218
229
  const res = await ctx.client.preSignMessage(userId, walletId, messageBase64);
219
230
 
231
+ if (res.pendingTransactionId) {
232
+ return { pendingTransactionId: res.pendingTransactionId };
233
+ }
234
+
220
235
  if (ctx.mpcComputationClient && !isDKLS) {
221
236
  const signature = (await signMessageRequest(ctx, userId, walletId, res.protocolId, messageBase64, share)).signature;
222
237
  return { signature };
@@ -241,13 +256,16 @@ export class ReactNativeUtils implements PlatformUtils {
241
256
  _sessionCookie: string,
242
257
  isDKLS?: boolean,
243
258
  ): Promise<SignatureRes> {
244
- const { protocolId } = (
245
- await ctx.client.signTransaction(userId, walletId, {
246
- transaction: rlpEncodedTxBase64,
247
- chainId,
248
- })
249
- ).data;
250
- return this.baseSignTransaction(ctx, userId, walletId, protocolId, share, rlpEncodedTxBase64, chainId, isDKLS);
259
+ const { data } = await ctx.client.signTransaction(userId, walletId, {
260
+ transaction: rlpEncodedTxBase64,
261
+ chainId,
262
+ });
263
+
264
+ if (data.pendingTransactionId) {
265
+ return { pendingTransactionId: data.pendingTransactionId };
266
+ }
267
+
268
+ return this.baseSignTransaction(ctx, userId, walletId, data.protocolId, share, rlpEncodedTxBase64, chainId, isDKLS);
251
269
  }
252
270
 
253
271
  async ed25519Keygen(
@@ -296,9 +314,13 @@ export class ReactNativeUtils implements PlatformUtils {
296
314
  base64Bytes: string,
297
315
  _sessionCookie: string,
298
316
  ): Promise<SignatureRes> {
299
- const { protocolId } = await ctx.client.preSignMessage(userId, walletId, base64Bytes, 'ED25519');
317
+ const res = await ctx.client.preSignMessage(userId, walletId, base64Bytes, 'ED25519');
318
+
319
+ if (res.pendingTransactionId) {
320
+ return { pendingTransactionId: res.pendingTransactionId };
321
+ }
300
322
 
301
- const base64Sig = await ParaSignerModule.ed25519Sign(protocolId, share, base64Bytes);
323
+ const base64Sig = await ParaSignerModule.ed25519Sign(res.protocolId, share, base64Bytes);
302
324
  return { signature: base64Sig };
303
325
  }
304
326
 
package/src/shim.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import quickCrypto from 'react-native-quick-crypto';
2
- import { webcrypto } from 'crypto';
3
2
  import { Crypto as PeculiarCrypto } from '@peculiar/webcrypto';
4
3
  import { ec as EllipticEC } from 'elliptic';
5
4
  import Forge from 'node-forge';
@@ -281,8 +280,25 @@ const ensureParaCrypto = () => {
281
280
  const setupCryptoPolyfills = () => {
282
281
  const { peculiarCrypto } = ensureParaCrypto();
283
282
 
284
- if (webcrypto && typeof webcrypto === 'object') {
285
- webcrypto.getRandomValues = peculiarCrypto.getRandomValues.bind(peculiarCrypto);
283
+ // Ensure `import { webcrypto } from 'crypto'` works regardless of quick-crypto version.
284
+ // quick-crypto 1.x (Nitro) removed the webcrypto export that 0.7.x had.
285
+ try {
286
+ const cryptoModule = require('crypto');
287
+ if (!cryptoModule.webcrypto || typeof cryptoModule.webcrypto !== 'object') {
288
+ Object.defineProperty(cryptoModule, 'webcrypto', {
289
+ get() {
290
+ return globalThis.crypto;
291
+ },
292
+ configurable: true,
293
+ enumerable: true,
294
+ });
295
+ }
296
+ if (cryptoModule.webcrypto && typeof cryptoModule.webcrypto === 'object') {
297
+ cryptoModule.webcrypto.getRandomValues = peculiarCrypto.getRandomValues.bind(peculiarCrypto);
298
+ }
299
+ // eslint-disable-next-line no-unused-vars
300
+ } catch (_err) {
301
+ // crypto module not available
286
302
  }
287
303
 
288
304
  if (typeof Forge === 'undefined') {