@bsv/wallet-toolbox 1.3.31 → 1.3.32

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/wallet-toolbox",
3
- "version": "1.3.31",
3
+ "version": "1.3.32",
4
4
  "description": "BRC100 conforming wallet, wallet storage and wallet signer components",
5
5
  "main": "./out/src/index.js",
6
6
  "types": "./out/src/index.d.ts",
@@ -324,6 +324,12 @@ export class WalletPermissionsManager implements WalletInterface {
324
324
  }
325
325
  > = new Map()
326
326
 
327
+ /** Cache recently confirmed permissions to avoid repeated lookups. */
328
+ private permissionCache: Map<string, { expiry: number; cachedAt: number }> = new Map()
329
+
330
+ /** How long a cached permission remains valid (5 minutes). */
331
+ private static readonly CACHE_TTL_MS = 5 * 60 * 1000
332
+
327
333
  /**
328
334
  * Configuration that determines whether to skip or apply various checks and encryption.
329
335
  */
@@ -480,6 +486,11 @@ export class WalletPermissionsManager implements WalletInterface {
480
486
  )
481
487
  }
482
488
  }
489
+
490
+ // Update the cache regardless of ephemeral status
491
+ const expiry = params.expiry || Math.floor(Date.now() / 1000) + 3600 * 24 * 30
492
+ const key = this.buildRequestKey(matching.request)
493
+ this.cachePermission(key, expiry)
483
494
  }
484
495
 
485
496
  /**
@@ -562,6 +573,17 @@ export class WalletPermissionsManager implements WalletInterface {
562
573
  privileged = false
563
574
  }
564
575
 
576
+ const cacheKey = this.buildRequestKey({
577
+ type: 'protocol',
578
+ originator,
579
+ privileged,
580
+ protocolID,
581
+ counterparty
582
+ })
583
+ if (this.isPermissionCached(cacheKey)) {
584
+ return true
585
+ }
586
+
565
587
  // 4) Attempt to find a valid token in the internal basket
566
588
  const token = await this.findProtocolToken(
567
589
  originator,
@@ -573,6 +595,7 @@ export class WalletPermissionsManager implements WalletInterface {
573
595
  if (token) {
574
596
  if (!this.isTokenExpired(token.expiry)) {
575
597
  // valid and unexpired
598
+ this.cachePermission(cacheKey, token.expiry)
576
599
  return true
577
600
  } else {
578
601
  // has a token but expired => request renewal if allowed
@@ -595,7 +618,7 @@ export class WalletPermissionsManager implements WalletInterface {
595
618
  if (!seekPermission) {
596
619
  throw new Error(`No protocol permission token found (seekPermission=false).`)
597
620
  }
598
- return await this.requestPermissionFlow({
621
+ const granted = await this.requestPermissionFlow({
599
622
  type: 'protocol',
600
623
  originator,
601
624
  privileged,
@@ -604,6 +627,11 @@ export class WalletPermissionsManager implements WalletInterface {
604
627
  reason,
605
628
  renewal: false
606
629
  })
630
+ if (granted) {
631
+ // Unknown expiry until grantPermission() is called; cache for now with TTL only
632
+ this.cachePermission(cacheKey, 0)
633
+ }
634
+ return granted
607
635
  }
608
636
  }
609
637
 
@@ -631,9 +659,14 @@ export class WalletPermissionsManager implements WalletInterface {
631
659
  if (usageType === 'insertion' && !this.config.seekBasketInsertionPermissions) return true
632
660
  if (usageType === 'removal' && !this.config.seekBasketRemovalPermissions) return true
633
661
  if (usageType === 'listing' && !this.config.seekBasketListingPermissions) return true
662
+ const cacheKey = this.buildRequestKey({ type: 'basket', originator, basket })
663
+ if (this.isPermissionCached(cacheKey)) {
664
+ return true
665
+ }
634
666
  const token = await this.findBasketToken(originator, basket, true)
635
667
  if (token) {
636
668
  if (!this.isTokenExpired(token.expiry)) {
669
+ this.cachePermission(cacheKey, token.expiry)
637
670
  return true
638
671
  } else {
639
672
  if (!seekPermission) {
@@ -653,13 +686,17 @@ export class WalletPermissionsManager implements WalletInterface {
653
686
  if (!seekPermission) {
654
687
  throw new Error(`No basket permission found, and no user consent allowed (seekPermission=false).`)
655
688
  }
656
- return await this.requestPermissionFlow({
689
+ const granted = await this.requestPermissionFlow({
657
690
  type: 'basket',
658
691
  originator,
659
692
  basket,
660
693
  reason,
661
694
  renewal: false
662
695
  })
696
+ if (granted) {
697
+ this.cachePermission(cacheKey, 0)
698
+ }
699
+ return granted
663
700
  }
664
701
  }
665
702
 
@@ -693,6 +730,15 @@ export class WalletPermissionsManager implements WalletInterface {
693
730
  if (!this.config.differentiatePrivilegedOperations) {
694
731
  privileged = false
695
732
  }
733
+ const cacheKey = this.buildRequestKey({
734
+ type: 'certificate',
735
+ originator,
736
+ privileged,
737
+ certificate: { verifier, certType, fields }
738
+ })
739
+ if (this.isPermissionCached(cacheKey)) {
740
+ return true
741
+ }
696
742
  const token = await this.findCertificateToken(
697
743
  originator,
698
744
  privileged,
@@ -703,6 +749,7 @@ export class WalletPermissionsManager implements WalletInterface {
703
749
  )
704
750
  if (token) {
705
751
  if (!this.isTokenExpired(token.expiry)) {
752
+ this.cachePermission(cacheKey, token.expiry)
706
753
  return true
707
754
  } else {
708
755
  if (!seekPermission) {
@@ -722,7 +769,7 @@ export class WalletPermissionsManager implements WalletInterface {
722
769
  if (!seekPermission) {
723
770
  throw new Error(`No certificate permission found (seekPermission=false).`)
724
771
  }
725
- return await this.requestPermissionFlow({
772
+ const granted = await this.requestPermissionFlow({
726
773
  type: 'certificate',
727
774
  originator,
728
775
  privileged,
@@ -730,6 +777,10 @@ export class WalletPermissionsManager implements WalletInterface {
730
777
  reason,
731
778
  renewal: false
732
779
  })
780
+ if (granted) {
781
+ this.cachePermission(cacheKey, 0)
782
+ }
783
+ return granted
733
784
  }
734
785
  }
735
786
 
@@ -759,11 +810,16 @@ export class WalletPermissionsManager implements WalletInterface {
759
810
  // We skip spending permission entirely
760
811
  return true
761
812
  }
813
+ const cacheKey = this.buildRequestKey({ type: 'spending', originator })
814
+ if (this.isPermissionCached(cacheKey)) {
815
+ return true
816
+ }
762
817
  const token = await this.findSpendingToken(originator)
763
818
  if (token?.authorizedAmount) {
764
819
  // Check how much has been spent so far
765
820
  const spentSoFar = await this.querySpentSince(token)
766
821
  if (spentSoFar + satoshis <= token.authorizedAmount) {
822
+ this.cachePermission(cacheKey, token.expiry)
767
823
  return true
768
824
  } else {
769
825
  // Renew if possible
@@ -772,7 +828,7 @@ export class WalletPermissionsManager implements WalletInterface {
772
828
  `Spending authorization insufficient for ${satoshis}, no user consent (seekPermission=false).`
773
829
  )
774
830
  }
775
- return await this.requestPermissionFlow({
831
+ const granted = await this.requestPermissionFlow({
776
832
  type: 'spending',
777
833
  originator,
778
834
  spending: { satoshis, lineItems },
@@ -780,19 +836,27 @@ export class WalletPermissionsManager implements WalletInterface {
780
836
  renewal: true,
781
837
  previousToken: token
782
838
  })
839
+ if (granted) {
840
+ this.cachePermission(cacheKey, 0)
841
+ }
842
+ return granted
783
843
  }
784
844
  } else {
785
845
  // no token
786
846
  if (!seekPermission) {
787
847
  throw new Error(`No spending authorization found, (seekPermission=false).`)
788
848
  }
789
- return await this.requestPermissionFlow({
849
+ const granted = await this.requestPermissionFlow({
790
850
  type: 'spending',
791
851
  originator,
792
852
  spending: { satoshis, lineItems },
793
853
  reason,
794
854
  renewal: false
795
855
  })
856
+ if (granted) {
857
+ this.cachePermission(cacheKey, 0)
858
+ }
859
+ return granted
796
860
  }
797
861
  }
798
862
 
@@ -828,8 +892,19 @@ export class WalletPermissionsManager implements WalletInterface {
828
892
  return true
829
893
  }
830
894
 
895
+ const cacheKey = this.buildRequestKey({
896
+ type: 'protocol',
897
+ originator,
898
+ privileged: false,
899
+ protocolID: [1, `action label ${label}`],
900
+ counterparty: 'self'
901
+ })
902
+ if (this.isPermissionCached(cacheKey)) {
903
+ return true
904
+ }
905
+
831
906
  // 3) Let ensureProtocolPermission handle the rest.
832
- return await this.ensureProtocolPermission({
907
+ const granted = await this.ensureProtocolPermission({
833
908
  originator,
834
909
  privileged: false,
835
910
  protocolID: [1, `action label ${label}`],
@@ -838,6 +913,10 @@ export class WalletPermissionsManager implements WalletInterface {
838
913
  seekPermission,
839
914
  usageType: 'generic'
840
915
  })
916
+ if (granted) {
917
+ this.cachePermission(cacheKey, 0)
918
+ }
919
+ return granted
841
920
  }
842
921
 
843
922
  /**
@@ -2525,6 +2604,29 @@ export class WalletPermissionsManager implements WalletInterface {
2525
2604
  return false
2526
2605
  }
2527
2606
 
2607
+ /**
2608
+ * Returns true if we have a cached record that the permission identified by
2609
+ * `key` is valid and unexpired.
2610
+ */
2611
+ private isPermissionCached(key: string): boolean {
2612
+ const entry = this.permissionCache.get(key)
2613
+ if (!entry) return false
2614
+ if (Date.now() - entry.cachedAt > WalletPermissionsManager.CACHE_TTL_MS) {
2615
+ this.permissionCache.delete(key)
2616
+ return false
2617
+ }
2618
+ if (this.isTokenExpired(entry.expiry)) {
2619
+ this.permissionCache.delete(key)
2620
+ return false
2621
+ }
2622
+ return true
2623
+ }
2624
+
2625
+ /** Caches the fact that the permission for `key` is valid until `expiry`. */
2626
+ private cachePermission(key: string, expiry: number): void {
2627
+ this.permissionCache.set(key, { expiry, cachedAt: Date.now() })
2628
+ }
2629
+
2528
2630
  /**
2529
2631
  * Builds a "map key" string so that identical requests (e.g. "protocol:domain:true:protoName:counterparty")
2530
2632
  * do not produce multiple user prompts.
@@ -295,6 +295,9 @@ describe('WalletPermissionsManager - Permission Request Flow & Active Requests',
295
295
  // Because ephemeral=true, we do NOT create an on-chain token
296
296
  expect(createTokenSpy).not.toHaveBeenCalled()
297
297
 
298
+ // Clear cache to actually run again
299
+ ;(manager as any).permissionCache = new Map()
300
+
298
301
  // 2) Immediately call ensureProtocolPermission again for the same resource
299
302
  // Because ephemeral usage didn't store a token, it should re-prompt.
300
303
  const pCall2 = manager.ensureProtocolPermission({
@@ -52,6 +52,7 @@ describe('operations.man tests', () => {
52
52
  let offset = 0
53
53
  const limit = 100
54
54
  let allUnfails: number[] = []
55
+ let reviewed = 0
55
56
  for (;;) {
56
57
  let log = ''
57
58
  const unfails: number[] = []
@@ -66,8 +67,9 @@ describe('operations.man tests', () => {
66
67
  log += `unfail ${req.provenTxReqId} ${req.txid}\n`
67
68
  unfails.push(req.provenTxReqId)
68
69
  }
70
+ reviewed++
69
71
  }
70
- console.log(`DoubleSpends OFFSET: ${offset} ${unfails.length} unfails\n${log}`)
72
+ console.log(`DoubleSpends OFFSET: ${offset} ${reviewed} ${unfails.length} unfails\n${log}`)
71
73
  allUnfails = allUnfails.concat(unfails)
72
74
  if (reqs.length < limit) break
73
75
  offset += reqs.length
@@ -83,6 +85,7 @@ describe('operations.man tests', () => {
83
85
  let offset = 0
84
86
  const limit = 100
85
87
  let allUnfails: number[] = []
88
+ let reviewed = 0
86
89
  for (;;) {
87
90
  let log = ''
88
91
  const unfails: number[] = []
@@ -98,8 +101,9 @@ describe('operations.man tests', () => {
98
101
  log += `unfail ${req.provenTxReqId} ${req.txid}\n`
99
102
  unfails.push(req.provenTxReqId)
100
103
  }
104
+ reviewed++
101
105
  }
102
- console.log(`Failed OFFSET: ${offset} ${unfails.length} unfails\n${log}`)
106
+ console.log(`Failed OFFSET: ${offset} ${reviewed} ${unfails.length} unfails\n${log}`)
103
107
  allUnfails = allUnfails.concat(unfails)
104
108
  if (reqs.length < limit) break
105
109
  offset += reqs.length