@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.d.cts CHANGED
@@ -28,7 +28,8 @@ declare enum SDKErrorCode {
28
28
  WALLET_RETRIEVAL_FAILED = "WALLET_RETRIEVAL_FAILED",
29
29
  NOT_LOGGED_IN = "NOT_LOGGED_IN",
30
30
  NETWORK_SWITCH_FAILED = "NETWORK_SWITCH_FAILED",
31
- TRANSACTION_FAILED = "TRANSACTION_FAILED"
31
+ TRANSACTION_FAILED = "TRANSACTION_FAILED",
32
+ INVALID_PIN = "INVALID_PIN"
32
33
  }
33
34
  declare class SDKError extends Error {
34
35
  readonly code: SDKErrorCode;
package/dist/index.d.ts CHANGED
@@ -28,7 +28,8 @@ declare enum SDKErrorCode {
28
28
  WALLET_RETRIEVAL_FAILED = "WALLET_RETRIEVAL_FAILED",
29
29
  NOT_LOGGED_IN = "NOT_LOGGED_IN",
30
30
  NETWORK_SWITCH_FAILED = "NETWORK_SWITCH_FAILED",
31
- TRANSACTION_FAILED = "TRANSACTION_FAILED"
31
+ TRANSACTION_FAILED = "TRANSACTION_FAILED",
32
+ INVALID_PIN = "INVALID_PIN"
32
33
  }
33
34
  declare class SDKError extends Error {
34
35
  readonly code: SDKErrorCode;
package/dist/index.js CHANGED
@@ -104064,6 +104064,7 @@ var SDKErrorCode = /* @__PURE__ */ ((SDKErrorCode2) => {
104064
104064
  SDKErrorCode2["NOT_LOGGED_IN"] = "NOT_LOGGED_IN";
104065
104065
  SDKErrorCode2["NETWORK_SWITCH_FAILED"] = "NETWORK_SWITCH_FAILED";
104066
104066
  SDKErrorCode2["TRANSACTION_FAILED"] = "TRANSACTION_FAILED";
104067
+ SDKErrorCode2["INVALID_PIN"] = "INVALID_PIN";
104067
104068
  return SDKErrorCode2;
104068
104069
  })(SDKErrorCode || {});
104069
104070
  var SDKError = class extends Error {
@@ -104110,8 +104111,14 @@ var STORAGE_KEYS = {
104110
104111
  walletAddress: (orgHost) => `${orgHost}:walletAddress`,
104111
104112
  share2: (orgHost) => `${orgHost}:share2`,
104112
104113
  encryptedShare2: (orgHost) => `${orgHost}:encryptedShare2`,
104113
- encryptedShare2Device: (orgHost) => `${orgHost}:encryptedShare2_device`,
104114
- deviceSecret: (orgHost) => `${orgHost}:deviceSecret`,
104114
+ // User-scoped keys to prevent cross-account mixing on the same device.
104115
+ deviceId: (orgHost, firebaseId) => `${orgHost}:${firebaseId}:deviceId`,
104116
+ encryptedShare2Device: (orgHost, firebaseId) => `${orgHost}:${firebaseId}:encryptedShare2_device`,
104117
+ deviceSecret: (orgHost, firebaseId) => `${orgHost}:${firebaseId}:deviceSecret`,
104118
+ // Legacy keys (kept for migration / cleanup)
104119
+ encryptedShare2DeviceLegacy: (orgHost) => `${orgHost}:encryptedShare2_device`,
104120
+ deviceSecretLegacy: (orgHost) => `${orgHost}:deviceSecret`,
104121
+ deviceIdLegacy: (orgHost) => `${orgHost}:deviceId`,
104115
104122
  firebaseId: (orgHost) => `${orgHost}:firebaseId`,
104116
104123
  accessToken: (orgHost) => `${orgHost}:accessToken`,
104117
104124
  isNewUser: (orgHost) => `${orgHost}:isNewUser`
@@ -104143,28 +104150,76 @@ var WalletService = class {
104143
104150
  isSixDigitPin(pin) {
104144
104151
  return /^[0-9]{6}$/.test(pin);
104145
104152
  }
104153
+ normalizeAddr(v5) {
104154
+ const s5 = String(v5 ?? "").trim();
104155
+ return s5 ? s5.toLowerCase() : "";
104156
+ }
104157
+ addressesMismatch(a5, b4) {
104158
+ return !!a5 && !!b4 && a5.toLowerCase() !== b4.toLowerCase();
104159
+ }
104160
+ /**
104161
+ * deviceId: stable identifier for the current device/browser profile.
104162
+ * Used to store/fetch server-side device recovery backup.
104163
+ */
104164
+ async getOrCreateDeviceId(firebaseId) {
104165
+ const scopedKey = STORAGE_KEYS.deviceId(this.orgHost, firebaseId);
104166
+ const existing = await LocalForage.get(scopedKey);
104167
+ if (existing) return existing;
104168
+ const legacy = await LocalForage.get(
104169
+ STORAGE_KEYS.deviceIdLegacy(this.orgHost)
104170
+ );
104171
+ if (legacy) {
104172
+ await LocalForage.save(scopedKey, legacy);
104173
+ return legacy;
104174
+ }
104175
+ const id = randomBytes(16).toString("hex");
104176
+ await LocalForage.save(scopedKey, id);
104177
+ return id;
104178
+ }
104146
104179
  /**
104147
- * deviceSecret: local-only secret for device recovery material.
104148
- * This must never be sent to server.
104180
+ * deviceSecret: device recovery secret. In the original SDK this was local-only.
104181
+ * To enable PIN reset after local storage wipe, we back it up to the server
104182
+ * (server should encrypt-at-rest; transport is TLS).
104149
104183
  */
104150
- async getOrCreateDeviceSecret() {
104151
- const key = STORAGE_KEYS.deviceSecret(this.orgHost);
104152
- const existing = await LocalForage.get(key);
104184
+ async getOrCreateDeviceSecret(firebaseId) {
104185
+ const scopedKey = STORAGE_KEYS.deviceSecret(this.orgHost, firebaseId);
104186
+ const existing = await LocalForage.get(scopedKey);
104153
104187
  if (existing) return existing;
104188
+ const legacy = await LocalForage.get(
104189
+ STORAGE_KEYS.deviceSecretLegacy(this.orgHost)
104190
+ );
104191
+ if (legacy) {
104192
+ await LocalForage.save(scopedKey, legacy);
104193
+ return legacy;
104194
+ }
104154
104195
  const secret = randomBytes(32).toString("hex");
104155
- await LocalForage.save(key, secret);
104196
+ await LocalForage.save(scopedKey, secret);
104156
104197
  return secret;
104157
104198
  }
104158
104199
  /**
104159
- * PATCH APPLIED:
104160
104200
  * Always overwrite encryptedShare2_device when we have a fresh share2.
104161
- * Keeping an old device share causes (share1 new + share2 old) => different derived wallet.
104201
+ * Also upsert to server so that PIN reset can work after local storage wipe.
104202
+ *
104203
+ * Note: Server backup is best-effort; we do not fail the main flow if backup fails.
104162
104204
  */
104163
104205
  async ensureDeviceEncryptedShare2(share2Plain, firebaseId) {
104164
- const encryptedKey = STORAGE_KEYS.encryptedShare2Device(this.orgHost);
104165
- const deviceSecret = await this.getOrCreateDeviceSecret();
104206
+ const deviceId = await this.getOrCreateDeviceId(firebaseId);
104207
+ const deviceSecret = await this.getOrCreateDeviceSecret(firebaseId);
104208
+ const encryptedKey = STORAGE_KEYS.encryptedShare2Device(
104209
+ this.orgHost,
104210
+ firebaseId
104211
+ );
104166
104212
  const encrypted = Crypto.encryptShare(share2Plain, deviceSecret, firebaseId);
104167
104213
  await LocalForage.save(encryptedKey, encrypted);
104214
+ try {
104215
+ await this.walletClient.upsertDeviceRecovery({
104216
+ deviceId,
104217
+ encryptedShare2Device: encrypted,
104218
+ deviceSecret
104219
+ });
104220
+ } catch (e7) {
104221
+ console.warn("[WalletService] device recovery backup upsert failed", e7);
104222
+ }
104168
104223
  }
104169
104224
  async getAddress() {
104170
104225
  try {
@@ -104288,6 +104343,7 @@ var WalletService = class {
104288
104343
  }
104289
104344
  };
104290
104345
  const walletInfo = await this.walletClient.getWallet();
104346
+ const serverAddr = this.normalizeAddr(walletInfo?.address);
104291
104347
  let share2 = await LocalForage.get(
104292
104348
  STORAGE_KEYS.share2(this.orgHost)
104293
104349
  );
@@ -104307,6 +104363,13 @@ var WalletService = class {
104307
104363
  share3
104308
104364
  ]);
104309
104365
  const wallet2 = new Wallet(privateKey2);
104366
+ const derivedAddr2 = this.normalizeAddr(wallet2.address);
104367
+ if (this.addressesMismatch(serverAddr, derivedAddr2)) {
104368
+ throw new SDKError(
104369
+ `Recovered wallet address mismatch. server=${serverAddr} derived=${derivedAddr2}`,
104370
+ "WALLET_RECOVERY_FAILED" /* WALLET_RECOVERY_FAILED */
104371
+ );
104372
+ }
104310
104373
  const newShares = await Secrets.split(wallet2.privateKey, 3, 2);
104311
104374
  const [newShare1, newShare2, newShare3] = newShares;
104312
104375
  await this.walletClient.updateWalletKey({
@@ -104337,6 +104400,13 @@ var WalletService = class {
104337
104400
  share2
104338
104401
  ]);
104339
104402
  const wallet = new Wallet(privateKey);
104403
+ const derivedAddr = this.normalizeAddr(wallet.address);
104404
+ if (this.addressesMismatch(serverAddr, derivedAddr)) {
104405
+ throw new SDKError(
104406
+ `Recovered wallet address mismatch. server=${serverAddr} derived=${derivedAddr}`,
104407
+ "WALLET_RECOVERY_FAILED" /* WALLET_RECOVERY_FAILED */
104408
+ );
104409
+ }
104340
104410
  await this.ensureDeviceEncryptedShare2(share2, firebaseId);
104341
104411
  this.walletAddress = wallet.address;
104342
104412
  await LocalForage.save(
@@ -104357,7 +104427,8 @@ var WalletService = class {
104357
104427
  }
104358
104428
  }
104359
104429
  /**
104360
- * PIN reset (same private key/address) using encryptedShare2_device.
104430
+ * PIN reset (same private key/address) using device recovery material.
104431
+ * If local recovery material is missing, it attempts to restore it from server backup.
104361
104432
  */
104362
104433
  async resetPin(newPassword) {
104363
104434
  try {
@@ -104379,34 +104450,74 @@ var WalletService = class {
104379
104450
  "INVALID_PARAMS" /* INVALID_PARAMS */
104380
104451
  );
104381
104452
  }
104382
- const encryptedDevice = await LocalForage.get(
104383
- STORAGE_KEYS.encryptedShare2Device(this.orgHost)
104453
+ let encryptedDevice = await LocalForage.get(
104454
+ STORAGE_KEYS.encryptedShare2Device(this.orgHost, firebaseId)
104384
104455
  );
104385
- const deviceSecret = await LocalForage.get(
104386
- STORAGE_KEYS.deviceSecret(this.orgHost)
104456
+ let deviceSecret = await LocalForage.get(
104457
+ STORAGE_KEYS.deviceSecret(this.orgHost, firebaseId)
104387
104458
  );
104388
104459
  if (!encryptedDevice || !deviceSecret) {
104389
- throw new SDKError(
104390
- "PIN reset is not available on this device",
104391
- "RECOVERY_NOT_AVAILABLE" /* RECOVERY_NOT_AVAILABLE */
104460
+ let backup;
104461
+ try {
104462
+ backup = await this.walletClient.getDeviceRecovery(void 0);
104463
+ } catch (e7) {
104464
+ throw new SDKError(
104465
+ "PIN reset is not available on this device",
104466
+ "RECOVERY_NOT_AVAILABLE" /* RECOVERY_NOT_AVAILABLE */,
104467
+ e7
104468
+ );
104469
+ }
104470
+ if (!backup?.found || !backup?.encryptedShare2Device || !backup?.deviceSecret) {
104471
+ throw new SDKError(
104472
+ "PIN reset is not available. No device recovery backup found for this user.",
104473
+ "RECOVERY_NOT_AVAILABLE" /* RECOVERY_NOT_AVAILABLE */
104474
+ );
104475
+ }
104476
+ encryptedDevice = backup.encryptedShare2Device;
104477
+ deviceSecret = backup.deviceSecret;
104478
+ await LocalForage.save(
104479
+ STORAGE_KEYS.encryptedShare2Device(this.orgHost, firebaseId),
104480
+ encryptedDevice
104481
+ );
104482
+ await LocalForage.save(
104483
+ STORAGE_KEYS.deviceSecret(this.orgHost, firebaseId),
104484
+ deviceSecret
104392
104485
  );
104486
+ if (backup?.deviceId) {
104487
+ await LocalForage.save(
104488
+ STORAGE_KEYS.deviceId(this.orgHost, firebaseId),
104489
+ backup.deviceId
104490
+ );
104491
+ }
104393
104492
  }
104394
104493
  let share2;
104395
104494
  try {
104396
- share2 = Crypto.decryptShare(encryptedDevice, deviceSecret, firebaseId);
104495
+ share2 = Crypto.decryptShare(
104496
+ encryptedDevice,
104497
+ deviceSecret,
104498
+ firebaseId
104499
+ );
104397
104500
  } catch (e7) {
104398
104501
  throw new SDKError(
104399
- "PIN reset is not available on this device",
104502
+ "PIN reset is not available. Recovery material cannot be decrypted.",
104400
104503
  "RECOVERY_NOT_AVAILABLE" /* RECOVERY_NOT_AVAILABLE */,
104401
104504
  e7
104402
104505
  );
104403
104506
  }
104404
104507
  const walletInfo = await this.walletClient.getWallet();
104508
+ const serverAddr = this.normalizeAddr(walletInfo?.address);
104405
104509
  const privateKey = await Secrets.combine([
104406
104510
  walletInfo.share1,
104407
104511
  share2
104408
104512
  ]);
104409
104513
  const wallet = new Wallet(privateKey);
104514
+ const derivedAddr = this.normalizeAddr(wallet.address);
104515
+ if (this.addressesMismatch(serverAddr, derivedAddr)) {
104516
+ throw new SDKError(
104517
+ `Device recovery does not match server wallet. server=${serverAddr} derived=${derivedAddr}`,
104518
+ "RECOVERY_NOT_AVAILABLE" /* RECOVERY_NOT_AVAILABLE */
104519
+ );
104520
+ }
104410
104521
  const newShares = await Secrets.split(wallet.privateKey, 3, 2);
104411
104522
  const [newShare1, newShare2, newShare3] = newShares;
104412
104523
  await this.walletClient.updateWalletKey({
@@ -104443,8 +104554,23 @@ var WalletService = class {
104443
104554
  await LocalForage.delete(STORAGE_KEYS.walletAddress(this.orgHost));
104444
104555
  await LocalForage.delete(STORAGE_KEYS.share2(this.orgHost));
104445
104556
  await LocalForage.delete(STORAGE_KEYS.encryptedShare2(this.orgHost));
104446
- await LocalForage.delete(STORAGE_KEYS.encryptedShare2Device(this.orgHost));
104447
- await LocalForage.delete(STORAGE_KEYS.deviceSecret(this.orgHost));
104557
+ const firebaseId = await LocalForage.get(
104558
+ STORAGE_KEYS.firebaseId(this.orgHost)
104559
+ );
104560
+ if (firebaseId) {
104561
+ await LocalForage.delete(
104562
+ STORAGE_KEYS.encryptedShare2Device(this.orgHost, firebaseId)
104563
+ );
104564
+ await LocalForage.delete(
104565
+ STORAGE_KEYS.deviceSecret(this.orgHost, firebaseId)
104566
+ );
104567
+ await LocalForage.delete(STORAGE_KEYS.deviceId(this.orgHost, firebaseId));
104568
+ }
104569
+ await LocalForage.delete(
104570
+ STORAGE_KEYS.encryptedShare2DeviceLegacy(this.orgHost)
104571
+ );
104572
+ await LocalForage.delete(STORAGE_KEYS.deviceSecretLegacy(this.orgHost));
104573
+ await LocalForage.delete(STORAGE_KEYS.deviceIdLegacy(this.orgHost));
104448
104574
  }
104449
104575
  async getBalance(address, chainId) {
104450
104576
  const response = await this.rpcClient.sendRpc({
@@ -104581,7 +104707,7 @@ var WalletService = class {
104581
104707
  }
104582
104708
  }
104583
104709
  /**
104584
- * PATCH APPLIED (critical):
104710
+ * Critical safety:
104585
104711
  * - Never overwrite cached walletAddress with derived signer address.
104586
104712
  * - If server/cached address != derived address => throw WALLET_RECOVERY_FAILED.
104587
104713
  */
@@ -104609,19 +104735,21 @@ var WalletService = class {
104609
104735
  const firebaseId = await LocalForage.get(
104610
104736
  STORAGE_KEYS.firebaseId(this.orgHost)
104611
104737
  );
104612
- const encryptedDevice = await LocalForage.get(
104613
- STORAGE_KEYS.encryptedShare2Device(this.orgHost)
104614
- );
104615
- const deviceSecret = await LocalForage.get(
104616
- STORAGE_KEYS.deviceSecret(this.orgHost)
104617
- );
104618
- if (firebaseId && encryptedDevice && deviceSecret) {
104619
- share2 = Crypto.decryptShare(
104620
- encryptedDevice,
104621
- deviceSecret,
104622
- firebaseId
104738
+ if (firebaseId) {
104739
+ const encryptedDevice = await LocalForage.get(
104740
+ STORAGE_KEYS.encryptedShare2Device(this.orgHost, firebaseId)
104623
104741
  );
104624
- await LocalForage.save(STORAGE_KEYS.share2(this.orgHost), share2);
104742
+ const deviceSecret = await LocalForage.get(
104743
+ STORAGE_KEYS.deviceSecret(this.orgHost, firebaseId)
104744
+ );
104745
+ if (encryptedDevice && deviceSecret) {
104746
+ share2 = Crypto.decryptShare(
104747
+ encryptedDevice,
104748
+ deviceSecret,
104749
+ firebaseId
104750
+ );
104751
+ await LocalForage.save(STORAGE_KEYS.share2(this.orgHost), share2);
104752
+ }
104625
104753
  }
104626
104754
  } catch {
104627
104755
  }
@@ -105214,6 +105342,38 @@ var WalletClient = class {
105214
105342
  needsAccessToken: true
105215
105343
  });
105216
105344
  }
105345
+ async getDeviceShare2Backup() {
105346
+ try {
105347
+ return await this.client.get(
105348
+ "/api/v1/wallet/device-share2",
105349
+ { needsAccessToken: true }
105350
+ );
105351
+ } catch (e7) {
105352
+ if (e7?.details?.status === 404 || String(e7?.message || "").includes("404"))
105353
+ return null;
105354
+ throw e7;
105355
+ }
105356
+ }
105357
+ async upsertDeviceShare2Backup(body) {
105358
+ await this.client.put("/api/v1/wallet/device-share2", body, {
105359
+ needsAccessToken: true
105360
+ });
105361
+ }
105362
+ async upsertDeviceRecovery(req) {
105363
+ await this.client.put(`${this.baseUrl}/device-recovery`, req, {
105364
+ needsAccessToken: true
105365
+ });
105366
+ }
105367
+ /**
105368
+ * If deviceId is omitted, server returns latest.
105369
+ * Returns { found:false } if no backup exists.
105370
+ */
105371
+ async getDeviceRecovery(deviceId) {
105372
+ const qs = deviceId ? `?deviceId=${encodeURIComponent(deviceId)}` : "";
105373
+ return this.client.get(`${this.baseUrl}/device-recovery${qs}`, {
105374
+ needsAccessToken: true
105375
+ });
105376
+ }
105217
105377
  };
105218
105378
 
105219
105379
  // src/clients/api/rpcs.ts