@weblock-wallet/sdk 0.1.69 → 0.1.71

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.cjs CHANGED
@@ -104109,6 +104109,7 @@ var SDKErrorCode = /* @__PURE__ */ ((SDKErrorCode2) => {
104109
104109
  SDKErrorCode2["NOT_LOGGED_IN"] = "NOT_LOGGED_IN";
104110
104110
  SDKErrorCode2["NETWORK_SWITCH_FAILED"] = "NETWORK_SWITCH_FAILED";
104111
104111
  SDKErrorCode2["TRANSACTION_FAILED"] = "TRANSACTION_FAILED";
104112
+ SDKErrorCode2["INVALID_PIN"] = "INVALID_PIN";
104112
104113
  return SDKErrorCode2;
104113
104114
  })(SDKErrorCode || {});
104114
104115
  var SDKError = class extends Error {
@@ -104155,8 +104156,14 @@ var STORAGE_KEYS = {
104155
104156
  walletAddress: (orgHost) => `${orgHost}:walletAddress`,
104156
104157
  share2: (orgHost) => `${orgHost}:share2`,
104157
104158
  encryptedShare2: (orgHost) => `${orgHost}:encryptedShare2`,
104158
- encryptedShare2Device: (orgHost) => `${orgHost}:encryptedShare2_device`,
104159
- deviceSecret: (orgHost) => `${orgHost}:deviceSecret`,
104159
+ // User-scoped keys to prevent cross-account mixing on the same device.
104160
+ deviceId: (orgHost, firebaseId) => `${orgHost}:${firebaseId}:deviceId`,
104161
+ encryptedShare2Device: (orgHost, firebaseId) => `${orgHost}:${firebaseId}:encryptedShare2_device`,
104162
+ deviceSecret: (orgHost, firebaseId) => `${orgHost}:${firebaseId}:deviceSecret`,
104163
+ // Legacy keys (kept for migration / cleanup)
104164
+ encryptedShare2DeviceLegacy: (orgHost) => `${orgHost}:encryptedShare2_device`,
104165
+ deviceSecretLegacy: (orgHost) => `${orgHost}:deviceSecret`,
104166
+ deviceIdLegacy: (orgHost) => `${orgHost}:deviceId`,
104160
104167
  firebaseId: (orgHost) => `${orgHost}:firebaseId`,
104161
104168
  accessToken: (orgHost) => `${orgHost}:accessToken`,
104162
104169
  isNewUser: (orgHost) => `${orgHost}:isNewUser`
@@ -104188,28 +104195,76 @@ var WalletService = class {
104188
104195
  isSixDigitPin(pin) {
104189
104196
  return /^[0-9]{6}$/.test(pin);
104190
104197
  }
104198
+ normalizeAddr(v5) {
104199
+ const s5 = String(v5 ?? "").trim();
104200
+ return s5 ? s5.toLowerCase() : "";
104201
+ }
104202
+ addressesMismatch(a5, b4) {
104203
+ return !!a5 && !!b4 && a5.toLowerCase() !== b4.toLowerCase();
104204
+ }
104205
+ /**
104206
+ * deviceId: stable identifier for the current device/browser profile.
104207
+ * Used to store/fetch server-side device recovery backup.
104208
+ */
104209
+ async getOrCreateDeviceId(firebaseId) {
104210
+ const scopedKey = STORAGE_KEYS.deviceId(this.orgHost, firebaseId);
104211
+ const existing = await LocalForage.get(scopedKey);
104212
+ if (existing) return existing;
104213
+ const legacy = await LocalForage.get(
104214
+ STORAGE_KEYS.deviceIdLegacy(this.orgHost)
104215
+ );
104216
+ if (legacy) {
104217
+ await LocalForage.save(scopedKey, legacy);
104218
+ return legacy;
104219
+ }
104220
+ const id = randomBytes(16).toString("hex");
104221
+ await LocalForage.save(scopedKey, id);
104222
+ return id;
104223
+ }
104191
104224
  /**
104192
- * deviceSecret: local-only secret for device recovery material.
104193
- * This must never be sent to server.
104225
+ * deviceSecret: device recovery secret. In the original SDK this was local-only.
104226
+ * To enable PIN reset after local storage wipe, we back it up to the server
104227
+ * (server should encrypt-at-rest; transport is TLS).
104194
104228
  */
104195
- async getOrCreateDeviceSecret() {
104196
- const key = STORAGE_KEYS.deviceSecret(this.orgHost);
104197
- const existing = await LocalForage.get(key);
104229
+ async getOrCreateDeviceSecret(firebaseId) {
104230
+ const scopedKey = STORAGE_KEYS.deviceSecret(this.orgHost, firebaseId);
104231
+ const existing = await LocalForage.get(scopedKey);
104198
104232
  if (existing) return existing;
104233
+ const legacy = await LocalForage.get(
104234
+ STORAGE_KEYS.deviceSecretLegacy(this.orgHost)
104235
+ );
104236
+ if (legacy) {
104237
+ await LocalForage.save(scopedKey, legacy);
104238
+ return legacy;
104239
+ }
104199
104240
  const secret = randomBytes(32).toString("hex");
104200
- await LocalForage.save(key, secret);
104241
+ await LocalForage.save(scopedKey, secret);
104201
104242
  return secret;
104202
104243
  }
104203
104244
  /**
104204
- * PATCH APPLIED:
104205
104245
  * Always overwrite encryptedShare2_device when we have a fresh share2.
104206
- * Keeping an old device share causes (share1 new + share2 old) => different derived wallet.
104246
+ * Also upsert to server so that PIN reset can work after local storage wipe.
104247
+ *
104248
+ * Note: Server backup is best-effort; we do not fail the main flow if backup fails.
104207
104249
  */
104208
104250
  async ensureDeviceEncryptedShare2(share2Plain, firebaseId) {
104209
- const encryptedKey = STORAGE_KEYS.encryptedShare2Device(this.orgHost);
104210
- const deviceSecret = await this.getOrCreateDeviceSecret();
104251
+ const deviceId = await this.getOrCreateDeviceId(firebaseId);
104252
+ const deviceSecret = await this.getOrCreateDeviceSecret(firebaseId);
104253
+ const encryptedKey = STORAGE_KEYS.encryptedShare2Device(
104254
+ this.orgHost,
104255
+ firebaseId
104256
+ );
104211
104257
  const encrypted = Crypto.encryptShare(share2Plain, deviceSecret, firebaseId);
104212
104258
  await LocalForage.save(encryptedKey, encrypted);
104259
+ try {
104260
+ await this.walletClient.upsertDeviceRecovery({
104261
+ deviceId,
104262
+ encryptedShare2Device: encrypted,
104263
+ deviceSecret
104264
+ });
104265
+ } catch (e7) {
104266
+ console.warn("[WalletService] device recovery backup upsert failed", e7);
104267
+ }
104213
104268
  }
104214
104269
  async getAddress() {
104215
104270
  try {
@@ -104333,6 +104388,7 @@ var WalletService = class {
104333
104388
  }
104334
104389
  };
104335
104390
  const walletInfo = await this.walletClient.getWallet();
104391
+ const serverAddr = this.normalizeAddr(walletInfo?.address);
104336
104392
  let share2 = await LocalForage.get(
104337
104393
  STORAGE_KEYS.share2(this.orgHost)
104338
104394
  );
@@ -104352,6 +104408,13 @@ var WalletService = class {
104352
104408
  share3
104353
104409
  ]);
104354
104410
  const wallet2 = new import_ethers2.Wallet(privateKey2);
104411
+ const derivedAddr2 = this.normalizeAddr(wallet2.address);
104412
+ if (this.addressesMismatch(serverAddr, derivedAddr2)) {
104413
+ throw new SDKError(
104414
+ `Recovered wallet address mismatch. server=${serverAddr} derived=${derivedAddr2}`,
104415
+ "WALLET_RECOVERY_FAILED" /* WALLET_RECOVERY_FAILED */
104416
+ );
104417
+ }
104355
104418
  const newShares = await Secrets.split(wallet2.privateKey, 3, 2);
104356
104419
  const [newShare1, newShare2, newShare3] = newShares;
104357
104420
  await this.walletClient.updateWalletKey({
@@ -104382,6 +104445,13 @@ var WalletService = class {
104382
104445
  share2
104383
104446
  ]);
104384
104447
  const wallet = new import_ethers2.Wallet(privateKey);
104448
+ const derivedAddr = this.normalizeAddr(wallet.address);
104449
+ if (this.addressesMismatch(serverAddr, derivedAddr)) {
104450
+ throw new SDKError(
104451
+ `Recovered wallet address mismatch. server=${serverAddr} derived=${derivedAddr}`,
104452
+ "WALLET_RECOVERY_FAILED" /* WALLET_RECOVERY_FAILED */
104453
+ );
104454
+ }
104385
104455
  await this.ensureDeviceEncryptedShare2(share2, firebaseId);
104386
104456
  this.walletAddress = wallet.address;
104387
104457
  await LocalForage.save(
@@ -104402,7 +104472,8 @@ var WalletService = class {
104402
104472
  }
104403
104473
  }
104404
104474
  /**
104405
- * PIN reset (same private key/address) using encryptedShare2_device.
104475
+ * PIN reset (same private key/address) using device recovery material.
104476
+ * If local recovery material is missing, it attempts to restore it from server backup.
104406
104477
  */
104407
104478
  async resetPin(newPassword) {
104408
104479
  try {
@@ -104424,34 +104495,74 @@ var WalletService = class {
104424
104495
  "INVALID_PARAMS" /* INVALID_PARAMS */
104425
104496
  );
104426
104497
  }
104427
- const encryptedDevice = await LocalForage.get(
104428
- STORAGE_KEYS.encryptedShare2Device(this.orgHost)
104498
+ let encryptedDevice = await LocalForage.get(
104499
+ STORAGE_KEYS.encryptedShare2Device(this.orgHost, firebaseId)
104429
104500
  );
104430
- const deviceSecret = await LocalForage.get(
104431
- STORAGE_KEYS.deviceSecret(this.orgHost)
104501
+ let deviceSecret = await LocalForage.get(
104502
+ STORAGE_KEYS.deviceSecret(this.orgHost, firebaseId)
104432
104503
  );
104433
104504
  if (!encryptedDevice || !deviceSecret) {
104434
- throw new SDKError(
104435
- "PIN reset is not available on this device",
104436
- "RECOVERY_NOT_AVAILABLE" /* RECOVERY_NOT_AVAILABLE */
104505
+ let backup;
104506
+ try {
104507
+ backup = await this.walletClient.getDeviceRecovery(void 0);
104508
+ } catch (e7) {
104509
+ throw new SDKError(
104510
+ "PIN reset is not available on this device",
104511
+ "RECOVERY_NOT_AVAILABLE" /* RECOVERY_NOT_AVAILABLE */,
104512
+ e7
104513
+ );
104514
+ }
104515
+ if (!backup?.found || !backup?.encryptedShare2Device || !backup?.deviceSecret) {
104516
+ throw new SDKError(
104517
+ "PIN reset is not available. No device recovery backup found for this user.",
104518
+ "RECOVERY_NOT_AVAILABLE" /* RECOVERY_NOT_AVAILABLE */
104519
+ );
104520
+ }
104521
+ encryptedDevice = backup.encryptedShare2Device;
104522
+ deviceSecret = backup.deviceSecret;
104523
+ await LocalForage.save(
104524
+ STORAGE_KEYS.encryptedShare2Device(this.orgHost, firebaseId),
104525
+ encryptedDevice
104526
+ );
104527
+ await LocalForage.save(
104528
+ STORAGE_KEYS.deviceSecret(this.orgHost, firebaseId),
104529
+ deviceSecret
104437
104530
  );
104531
+ if (backup?.deviceId) {
104532
+ await LocalForage.save(
104533
+ STORAGE_KEYS.deviceId(this.orgHost, firebaseId),
104534
+ backup.deviceId
104535
+ );
104536
+ }
104438
104537
  }
104439
104538
  let share2;
104440
104539
  try {
104441
- share2 = Crypto.decryptShare(encryptedDevice, deviceSecret, firebaseId);
104540
+ share2 = Crypto.decryptShare(
104541
+ encryptedDevice,
104542
+ deviceSecret,
104543
+ firebaseId
104544
+ );
104442
104545
  } catch (e7) {
104443
104546
  throw new SDKError(
104444
- "PIN reset is not available on this device",
104547
+ "PIN reset is not available. Recovery material cannot be decrypted.",
104445
104548
  "RECOVERY_NOT_AVAILABLE" /* RECOVERY_NOT_AVAILABLE */,
104446
104549
  e7
104447
104550
  );
104448
104551
  }
104449
104552
  const walletInfo = await this.walletClient.getWallet();
104553
+ const serverAddr = this.normalizeAddr(walletInfo?.address);
104450
104554
  const privateKey = await Secrets.combine([
104451
104555
  walletInfo.share1,
104452
104556
  share2
104453
104557
  ]);
104454
104558
  const wallet = new import_ethers2.Wallet(privateKey);
104559
+ const derivedAddr = this.normalizeAddr(wallet.address);
104560
+ if (this.addressesMismatch(serverAddr, derivedAddr)) {
104561
+ throw new SDKError(
104562
+ `Device recovery does not match server wallet. server=${serverAddr} derived=${derivedAddr}`,
104563
+ "RECOVERY_NOT_AVAILABLE" /* RECOVERY_NOT_AVAILABLE */
104564
+ );
104565
+ }
104455
104566
  const newShares = await Secrets.split(wallet.privateKey, 3, 2);
104456
104567
  const [newShare1, newShare2, newShare3] = newShares;
104457
104568
  await this.walletClient.updateWalletKey({
@@ -104488,8 +104599,23 @@ var WalletService = class {
104488
104599
  await LocalForage.delete(STORAGE_KEYS.walletAddress(this.orgHost));
104489
104600
  await LocalForage.delete(STORAGE_KEYS.share2(this.orgHost));
104490
104601
  await LocalForage.delete(STORAGE_KEYS.encryptedShare2(this.orgHost));
104491
- await LocalForage.delete(STORAGE_KEYS.encryptedShare2Device(this.orgHost));
104492
- await LocalForage.delete(STORAGE_KEYS.deviceSecret(this.orgHost));
104602
+ const firebaseId = await LocalForage.get(
104603
+ STORAGE_KEYS.firebaseId(this.orgHost)
104604
+ );
104605
+ if (firebaseId) {
104606
+ await LocalForage.delete(
104607
+ STORAGE_KEYS.encryptedShare2Device(this.orgHost, firebaseId)
104608
+ );
104609
+ await LocalForage.delete(
104610
+ STORAGE_KEYS.deviceSecret(this.orgHost, firebaseId)
104611
+ );
104612
+ await LocalForage.delete(STORAGE_KEYS.deviceId(this.orgHost, firebaseId));
104613
+ }
104614
+ await LocalForage.delete(
104615
+ STORAGE_KEYS.encryptedShare2DeviceLegacy(this.orgHost)
104616
+ );
104617
+ await LocalForage.delete(STORAGE_KEYS.deviceSecretLegacy(this.orgHost));
104618
+ await LocalForage.delete(STORAGE_KEYS.deviceIdLegacy(this.orgHost));
104493
104619
  }
104494
104620
  async getBalance(address, chainId) {
104495
104621
  const response = await this.rpcClient.sendRpc({
@@ -104626,7 +104752,7 @@ var WalletService = class {
104626
104752
  }
104627
104753
  }
104628
104754
  /**
104629
- * PATCH APPLIED (critical):
104755
+ * Critical safety:
104630
104756
  * - Never overwrite cached walletAddress with derived signer address.
104631
104757
  * - If server/cached address != derived address => throw WALLET_RECOVERY_FAILED.
104632
104758
  */
@@ -104654,19 +104780,21 @@ var WalletService = class {
104654
104780
  const firebaseId = await LocalForage.get(
104655
104781
  STORAGE_KEYS.firebaseId(this.orgHost)
104656
104782
  );
104657
- const encryptedDevice = await LocalForage.get(
104658
- STORAGE_KEYS.encryptedShare2Device(this.orgHost)
104659
- );
104660
- const deviceSecret = await LocalForage.get(
104661
- STORAGE_KEYS.deviceSecret(this.orgHost)
104662
- );
104663
- if (firebaseId && encryptedDevice && deviceSecret) {
104664
- share2 = Crypto.decryptShare(
104665
- encryptedDevice,
104666
- deviceSecret,
104667
- firebaseId
104783
+ if (firebaseId) {
104784
+ const encryptedDevice = await LocalForage.get(
104785
+ STORAGE_KEYS.encryptedShare2Device(this.orgHost, firebaseId)
104668
104786
  );
104669
- await LocalForage.save(STORAGE_KEYS.share2(this.orgHost), share2);
104787
+ const deviceSecret = await LocalForage.get(
104788
+ STORAGE_KEYS.deviceSecret(this.orgHost, firebaseId)
104789
+ );
104790
+ if (encryptedDevice && deviceSecret) {
104791
+ share2 = Crypto.decryptShare(
104792
+ encryptedDevice,
104793
+ deviceSecret,
104794
+ firebaseId
104795
+ );
104796
+ await LocalForage.save(STORAGE_KEYS.share2(this.orgHost), share2);
104797
+ }
104670
104798
  }
104671
104799
  } catch {
104672
104800
  }
@@ -105255,6 +105383,38 @@ var WalletClient = class {
105255
105383
  needsAccessToken: true
105256
105384
  });
105257
105385
  }
105386
+ async getDeviceShare2Backup() {
105387
+ try {
105388
+ return await this.client.get(
105389
+ "/api/v1/wallet/device-share2",
105390
+ { needsAccessToken: true }
105391
+ );
105392
+ } catch (e7) {
105393
+ if (e7?.details?.status === 404 || String(e7?.message || "").includes("404"))
105394
+ return null;
105395
+ throw e7;
105396
+ }
105397
+ }
105398
+ async upsertDeviceShare2Backup(body) {
105399
+ await this.client.put("/api/v1/wallet/device-share2", body, {
105400
+ needsAccessToken: true
105401
+ });
105402
+ }
105403
+ async upsertDeviceRecovery(req) {
105404
+ await this.client.put(`${this.baseUrl}/device-recovery`, req, {
105405
+ needsAccessToken: true
105406
+ });
105407
+ }
105408
+ /**
105409
+ * If deviceId is omitted, server returns latest.
105410
+ * Returns { found:false } if no backup exists.
105411
+ */
105412
+ async getDeviceRecovery(deviceId) {
105413
+ const qs = deviceId ? `?deviceId=${encodeURIComponent(deviceId)}` : "";
105414
+ return this.client.get(`${this.baseUrl}/device-recovery${qs}`, {
105415
+ needsAccessToken: true
105416
+ });
105417
+ }
105258
105418
  };
105259
105419
 
105260
105420
  // src/clients/api/rpcs.ts