@bsv/wallet-toolbox-mobile 2.4.1 → 2.4.3

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.
Files changed (117) hide show
  1. package/out/src/Wallet.d.ts +1 -1
  2. package/out/src/Wallet.d.ts.map +1 -1
  3. package/out/src/Wallet.js +8 -7
  4. package/out/src/Wallet.js.map +1 -1
  5. package/out/src/WalletPermissionsManager.d.ts +33 -0
  6. package/out/src/WalletPermissionsManager.d.ts.map +1 -1
  7. package/out/src/WalletPermissionsManager.js +132 -37
  8. package/out/src/WalletPermissionsManager.js.map +1 -1
  9. package/out/src/monitor/tasks/TaskArcSSE.d.ts.map +1 -1
  10. package/out/src/monitor/tasks/TaskArcSSE.js +11 -7
  11. package/out/src/monitor/tasks/TaskArcSSE.js.map +1 -1
  12. package/out/src/monitor/tasks/TaskCheckForProofs.d.ts.map +1 -1
  13. package/out/src/monitor/tasks/TaskCheckForProofs.js +10 -10
  14. package/out/src/monitor/tasks/TaskCheckForProofs.js.map +1 -1
  15. package/out/src/monitor/tasks/TaskReviewStatus.d.ts.map +1 -1
  16. package/out/src/monitor/tasks/TaskReviewStatus.js +7 -2
  17. package/out/src/monitor/tasks/TaskReviewStatus.js.map +1 -1
  18. package/out/src/monitor/tasks/TaskUnFail.d.ts.map +1 -1
  19. package/out/src/monitor/tasks/TaskUnFail.js +2 -57
  20. package/out/src/monitor/tasks/TaskUnFail.js.map +1 -1
  21. package/out/src/sdk/WalletServices.interfaces.d.ts +1 -0
  22. package/out/src/sdk/WalletServices.interfaces.d.ts.map +1 -1
  23. package/out/src/sdk/WalletStorage.interfaces.d.ts +10 -4
  24. package/out/src/sdk/WalletStorage.interfaces.d.ts.map +1 -1
  25. package/out/src/sdk/types.d.ts +1 -1
  26. package/out/src/sdk/types.d.ts.map +1 -1
  27. package/out/src/services/Services.d.ts.map +1 -1
  28. package/out/src/services/Services.js +39 -19
  29. package/out/src/services/Services.js.map +1 -1
  30. package/out/src/services/chaintracker/chaintracks/GoChaintracksServiceClient.d.ts.map +1 -1
  31. package/out/src/services/chaintracker/chaintracks/GoChaintracksServiceClient.js +4 -0
  32. package/out/src/services/chaintracker/chaintracks/GoChaintracksServiceClient.js.map +1 -1
  33. package/out/src/services/chaintracker/chaintracks/Ingest/WhatsOnChainIngestorWs.d.ts.map +1 -1
  34. package/out/src/services/chaintracker/chaintracks/Ingest/WhatsOnChainIngestorWs.js +2 -0
  35. package/out/src/services/chaintracker/chaintracks/Ingest/WhatsOnChainIngestorWs.js.map +1 -1
  36. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageNoDb.d.ts.map +1 -1
  37. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageNoDb.js +2 -1
  38. package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageNoDb.js.map +1 -1
  39. package/out/src/services/chaintracker/chaintracks/util/blockHeaderUtilities.d.ts.map +1 -1
  40. package/out/src/services/chaintracker/chaintracks/util/blockHeaderUtilities.js +1 -0
  41. package/out/src/services/chaintracker/chaintracks/util/blockHeaderUtilities.js.map +1 -1
  42. package/out/src/services/createDefaultWalletServicesOptions.d.ts.map +1 -1
  43. package/out/src/services/createDefaultWalletServicesOptions.js +17 -3
  44. package/out/src/services/createDefaultWalletServicesOptions.js.map +1 -1
  45. package/out/src/services/networkConfig.d.ts +29 -0
  46. package/out/src/services/networkConfig.d.ts.map +1 -0
  47. package/out/src/services/networkConfig.js +54 -0
  48. package/out/src/services/networkConfig.js.map +1 -0
  49. package/out/src/services/providers/Arcade.d.ts.map +1 -1
  50. package/out/src/services/providers/Arcade.js +6 -2
  51. package/out/src/services/providers/Arcade.js.map +1 -1
  52. package/out/src/services/providers/SdkWhatsOnChain.d.ts +1 -1
  53. package/out/src/services/providers/SdkWhatsOnChain.d.ts.map +1 -1
  54. package/out/src/services/providers/SdkWhatsOnChain.js +12 -4
  55. package/out/src/services/providers/SdkWhatsOnChain.js.map +1 -1
  56. package/out/src/signer/methods/buildSignableTransaction.d.ts.map +1 -1
  57. package/out/src/signer/methods/buildSignableTransaction.js +3 -1
  58. package/out/src/signer/methods/buildSignableTransaction.js.map +1 -1
  59. package/out/src/signer/methods/completeSignedTransaction.d.ts.map +1 -1
  60. package/out/src/signer/methods/completeSignedTransaction.js +5 -3
  61. package/out/src/signer/methods/completeSignedTransaction.js.map +1 -1
  62. package/out/src/signer/methods/createAction.d.ts.map +1 -1
  63. package/out/src/signer/methods/createAction.js +12 -5
  64. package/out/src/signer/methods/createAction.js.map +1 -1
  65. package/out/src/signer/methods/resultBeef.d.ts +4 -0
  66. package/out/src/signer/methods/resultBeef.d.ts.map +1 -0
  67. package/out/src/signer/methods/resultBeef.js +12 -0
  68. package/out/src/signer/methods/resultBeef.js.map +1 -0
  69. package/out/src/signer/methods/signAction.d.ts.map +1 -1
  70. package/out/src/signer/methods/signAction.js +6 -1
  71. package/out/src/signer/methods/signAction.js.map +1 -1
  72. package/out/src/storage/StorageProvider.d.ts +17 -0
  73. package/out/src/storage/StorageProvider.d.ts.map +1 -1
  74. package/out/src/storage/StorageProvider.js +206 -13
  75. package/out/src/storage/StorageProvider.js.map +1 -1
  76. package/out/src/storage/methods/createAction.js +17 -4
  77. package/out/src/storage/methods/createAction.js.map +1 -1
  78. package/out/src/storage/methods/getBeefForTransaction.d.ts.map +1 -1
  79. package/out/src/storage/methods/getBeefForTransaction.js +47 -24
  80. package/out/src/storage/methods/getBeefForTransaction.js.map +1 -1
  81. package/out/src/storage/methods/internalizeAction.js +3 -3
  82. package/out/src/storage/methods/internalizeAction.js.map +1 -1
  83. package/out/src/storage/methods/processAction.d.ts.map +1 -1
  84. package/out/src/storage/methods/processAction.js +5 -4
  85. package/out/src/storage/methods/processAction.js.map +1 -1
  86. package/out/src/storage/remoting/BinaryJson.d.ts +10 -0
  87. package/out/src/storage/remoting/BinaryJson.d.ts.map +1 -0
  88. package/out/src/storage/remoting/BinaryJson.js +172 -0
  89. package/out/src/storage/remoting/BinaryJson.js.map +1 -0
  90. package/out/src/storage/remoting/StorageClient.d.ts +2 -2
  91. package/out/src/storage/remoting/StorageClient.d.ts.map +1 -1
  92. package/out/src/storage/remoting/StorageClient.js +14 -5
  93. package/out/src/storage/remoting/StorageClient.js.map +1 -1
  94. package/out/src/storage/remoting/StorageClientBase.d.ts +11 -1
  95. package/out/src/storage/remoting/StorageClientBase.d.ts.map +1 -1
  96. package/out/src/storage/remoting/StorageClientBase.js +4 -1
  97. package/out/src/storage/remoting/StorageClientBase.js.map +1 -1
  98. package/out/src/storage/remoting/StorageMobile.d.ts +2 -2
  99. package/out/src/storage/remoting/StorageMobile.d.ts.map +1 -1
  100. package/out/src/storage/remoting/StorageMobile.js +14 -5
  101. package/out/src/storage/remoting/StorageMobile.js.map +1 -1
  102. package/out/src/storage/schema/tables/TableAuthSession.d.ts +20 -0
  103. package/out/src/storage/schema/tables/TableAuthSession.d.ts.map +1 -0
  104. package/out/src/storage/schema/tables/TableAuthSession.js +15 -0
  105. package/out/src/storage/schema/tables/TableAuthSession.js.map +1 -0
  106. package/out/src/storage/schema/tables/index.d.ts +1 -0
  107. package/out/src/storage/schema/tables/index.d.ts.map +1 -1
  108. package/out/src/storage/schema/tables/index.js +1 -0
  109. package/out/src/storage/schema/tables/index.js.map +1 -1
  110. package/out/src/storage/storageProviderHelpers.d.ts +5 -4
  111. package/out/src/storage/storageProviderHelpers.d.ts.map +1 -1
  112. package/out/src/storage/storageProviderHelpers.js +7 -6
  113. package/out/src/storage/storageProviderHelpers.js.map +1 -1
  114. package/out/src/utility/utilityHelpers.d.ts.map +1 -1
  115. package/out/src/utility/utilityHelpers.js +2 -0
  116. package/out/src/utility/utilityHelpers.js.map +1 -1
  117. package/package.json +2 -2
@@ -91,11 +91,28 @@ class WalletPermissionsManager {
91
91
  /** Cache recently confirmed permissions to avoid repeated lookups. */
92
92
  permissionCache = new Map();
93
93
  recentGrants = new Map();
94
+ /**
95
+ * Token mints currently being written on-chain, keyed by permission cache
96
+ * key. A granted permission is only cached once its token finishes minting
97
+ * (network seconds); ensures arriving in that window await the in-flight
98
+ * mint instead of re-prompting the user for a permission they just granted.
99
+ * Stored promises never reject.
100
+ */
101
+ mintsInFlight = new Map();
94
102
  manifestCache = new Map();
95
103
  manifestFetchInProgress = new Map();
96
104
  static MANIFEST_CACHE_TTL_MS = 5 * 60 * 1000;
97
105
  groupedPermissionFlowTail = new Map();
98
106
  pactEstablishedCache = new Map();
107
+ /**
108
+ * Parsed BEEF bundles keyed by the raw BEEF array of a `listOutputs`
109
+ * result, so token scans parse each bundle once instead of once per output.
110
+ * `null` records a bundle that failed to parse. WeakMap-keyed so entries
111
+ * are released with their results.
112
+ */
113
+ parsedBeefCache = new WeakMap();
114
+ /** Counts token-field decrypts so long scans can periodically yield. */
115
+ tokenFieldDecryptCount = 0;
99
116
  /** How long a cached permission remains valid (5 minutes). */
100
117
  static CACHE_TTL_MS = 5 * 60 * 1000;
101
118
  /** Window during which freshly granted permissions are auto-allowed (except spending). */
@@ -382,26 +399,45 @@ class WalletPermissionsManager {
382
399
  }
383
400
  this.activeRequests.delete(params.requestID);
384
401
  // 3) If `ephemeral !== true`, we create or renew an on-chain token
385
- if (!params.ephemeral) {
386
- const request = matching.request;
387
- if (request.renewal) {
388
- // renewal => spend the old token, produce a new one
389
- await this.renewPermissionOnChain(request.previousToken, request, params.expiry || 0, // default: never expires
390
- params.amount);
391
- }
392
- else {
393
- // brand-new permission token
394
- await this.createPermissionOnChain(request, params.expiry || 0, // default: never expires
395
- params.amount);
396
- }
397
- }
398
402
  // Only cache non-ephemeral permissions
399
403
  // Ephemeral permissions should not be cached as they are one-time authorizations
400
404
  if (!params.ephemeral) {
405
+ const request = matching.request;
401
406
  const expiry = params.expiry || 0; // default: never expires
402
- const key = this.buildRequestKey(matching.request);
407
+ const key = this.buildRequestKey(request);
408
+ // Several first-contact prompts for the same permission can stack up
409
+ // before any token exists (e.g. one per usage type). Granting them all
410
+ // should yield ONE token: if an identical non-renewal grant just minted
411
+ // (or is minting), don't mint a duplicate. Spending authorizations are
412
+ // excluded — their tokens carry amounts, so every grant is honored.
413
+ let skipDuplicateMint = !request.renewal &&
414
+ request.type !== 'spending' &&
415
+ (this.isPermissionCached(key) || this.isRecentlyGranted(key) || this.mintsInFlight.has(key));
416
+ if (skipDuplicateMint) {
417
+ const inFlight = this.mintsInFlight.get(key);
418
+ if (inFlight != null) {
419
+ await inFlight;
420
+ // The awaited mint may have failed; only skip if it landed.
421
+ skipDuplicateMint = this.isPermissionCached(key) || this.isRecentlyGranted(key);
422
+ }
423
+ }
424
+ if (!skipDuplicateMint) {
425
+ const mint = request.renewal
426
+ ? this.renewPermissionOnChain(request.previousToken, request, expiry, params.amount)
427
+ : this.createPermissionOnChain(request, expiry, params.amount);
428
+ // Publish the in-flight mint so concurrent ensures for the same
429
+ // permission wait for it instead of re-prompting (stored promise
430
+ // never rejects; failures still propagate to this caller below).
431
+ this.mintsInFlight.set(key, mint.then(() => { }, () => { }));
432
+ try {
433
+ await mint;
434
+ }
435
+ finally {
436
+ this.mintsInFlight.delete(key);
437
+ }
438
+ }
403
439
  this.cachePermission(key, expiry);
404
- this.markRecentGrant(matching.request);
440
+ this.markRecentGrant(request);
405
441
  }
406
442
  }
407
443
  /**
@@ -417,8 +453,12 @@ class WalletPermissionsManager {
417
453
  throw new Error('Request ID not found.');
418
454
  }
419
455
  // 2) Reject all matching requests, deleting the entry
456
+ // Denials carry the canonical machine-readable code so applications can
457
+ // distinguish user denial from other failures (matches denyActiveRequest).
458
+ const err = new Error('Permission denied.');
459
+ err.code = 'ERR_PERMISSION_DENIED';
420
460
  for (const x of matching.pending) {
421
- x.reject(new Error('Permission denied.'));
461
+ x.reject(err);
422
462
  }
423
463
  this.activeRequests.delete(requestID);
424
464
  }
@@ -664,7 +704,7 @@ class WalletPermissionsManager {
664
704
  if (!privileged && this.isWhitelistedCounterpartyProtocol(counterparty, protocolID))
665
705
  return true;
666
706
  const cacheKey = this.buildRequestKey({ type: 'protocol', originator, privileged, protocolID, counterparty });
667
- if (this.isPermissionCached(cacheKey) || this.isRecentlyGranted(cacheKey))
707
+ if (await this.hasRecentOrPendingGrant(cacheKey))
668
708
  return true;
669
709
  // 4) Attempt to find a valid token in the internal basket
670
710
  const token = await this.findProtocolToken(originator, privileged, protocolID, counterparty, /* includeExpired= */ true, lookupValues);
@@ -704,7 +744,7 @@ class WalletPermissionsManager {
704
744
  if (!this.isBasketUsageRequired(usageType))
705
745
  return true;
706
746
  const cacheKey = this.buildRequestKey({ type: 'basket', originator, basket });
707
- if (this.isPermissionCached(cacheKey) || this.isRecentlyGranted(cacheKey))
747
+ if (await this.hasRecentOrPendingGrant(cacheKey))
708
748
  return true;
709
749
  const token = await this.findBasketToken(originator, basket, true, lookupValues);
710
750
  if (token == null) {
@@ -753,10 +793,7 @@ class WalletPermissionsManager {
753
793
  privileged,
754
794
  certificate: { verifier, certType, fields }
755
795
  });
756
- if (this.isPermissionCached(cacheKey)) {
757
- return true;
758
- }
759
- if (this.isRecentlyGranted(cacheKey)) {
796
+ if (await this.hasRecentOrPendingGrant(cacheKey)) {
760
797
  return true;
761
798
  }
762
799
  const token = await this.findCertificateToken(originator, privileged, verifier, certType, fields,
@@ -807,7 +844,10 @@ class WalletPermissionsManager {
807
844
  return true;
808
845
  }
809
846
  const cacheKey = this.buildRequestKey({ type: 'spending', originator, spending: { satoshis } });
810
- if (this.isPermissionCached(cacheKey)) {
847
+ // Spending keys are amount-scoped. The recent-grant window this adds sits
848
+ // inside the pre-existing permissionCache window grantPermission already
849
+ // wrote for spending, so accounting exposure is unchanged.
850
+ if (await this.hasRecentOrPendingGrant(cacheKey)) {
811
851
  return true;
812
852
  }
813
853
  const token = await this.findSpendingToken(originator, lookupValues);
@@ -1016,11 +1056,13 @@ class WalletPermissionsManager {
1016
1056
  (async () => {
1017
1057
  if (groupPermissions.spendingAuthorization == null)
1018
1058
  return undefined;
1019
- const hasAuth = await this.hasSpendingAuthorization({
1020
- originator,
1021
- satoshis: groupPermissions.spendingAuthorization.amount
1022
- });
1023
- return hasAuth ? undefined : groupPermissions.spendingAuthorization;
1059
+ // A standing authorization is a grant regardless of how much of the
1060
+ // monthly allowance has been used — exhaustion is handled by renewal
1061
+ // at spend time. Checking remaining headroom here re-requests (and,
1062
+ // if approved, re-mints) spending after any monthly usage.
1063
+ const { normalized, lookupValues } = this.prepareOriginator(originator);
1064
+ const token = await this.findSpendingToken(normalized, lookupValues);
1065
+ return token != null ? undefined : groupPermissions.spendingAuthorization;
1024
1066
  })(),
1025
1067
  (async () => {
1026
1068
  const protocolChecks = await Promise.all((groupPermissions.protocolPermissions || []).map(async (p) => {
@@ -1571,7 +1613,45 @@ class WalletPermissionsManager {
1571
1613
  }, this.adminOriginator);
1572
1614
  return ciphertext;
1573
1615
  }
1616
+ /**
1617
+ * Extracts a transaction from a `listOutputs` result's BEEF bundle, parsing
1618
+ * the bundle only once per result. Calling
1619
+ * `Transaction.fromBEEF(result.BEEF, txid)` per output re-parses the entire
1620
+ * bundle every time — O(n²) in the number of outputs — which can block the
1621
+ * caller's thread (in browser wallets, the UI) for seconds on large
1622
+ * permission baskets.
1623
+ */
1624
+ transactionFromResultBeef(result, txid) {
1625
+ const beef = result.BEEF;
1626
+ if (beef != null) {
1627
+ let parsed = this.parsedBeefCache.get(beef);
1628
+ if (parsed === undefined) {
1629
+ try {
1630
+ parsed = sdk_1.Beef.fromBinary(beef);
1631
+ }
1632
+ catch {
1633
+ parsed = null;
1634
+ }
1635
+ this.parsedBeefCache.set(beef, parsed);
1636
+ }
1637
+ // findAtomicTransaction (not findTxid().tx) matches fromBEEF exactly:
1638
+ // it attaches merkle paths and sourceTransaction ancestry, which token
1639
+ // consumers rely on when re-serializing via tx.toBEEF().
1640
+ const tx = parsed?.findAtomicTransaction(txid);
1641
+ if (tx != null)
1642
+ return tx;
1643
+ }
1644
+ // Preserve the original behavior (including throwing) for bundles the
1645
+ // fast path can't serve.
1646
+ return sdk_1.Transaction.fromBEEF(beef ?? [], txid);
1647
+ }
1574
1648
  async decryptPermissionTokenField(ciphertext) {
1649
+ // Field decrypts run in tight loops over every token in a basket; yield a
1650
+ // macrotask periodically so large scans don't starve the caller's event
1651
+ // loop (in browser wallets, that means blocking rendering and input).
1652
+ if (++this.tokenFieldDecryptCount % 8 === 0) {
1653
+ await new Promise(resolve => setTimeout(resolve, 0));
1654
+ }
1575
1655
  try {
1576
1656
  const { plaintext } = await this.underlying.decrypt({
1577
1657
  ciphertext,
@@ -1715,7 +1795,7 @@ class WalletPermissionsManager {
1715
1795
  }, this.adminOriginator);
1716
1796
  for (const out of result.outputs) {
1717
1797
  const [txid, outputIndex] = this.parseOutpoint(out.outpoint);
1718
- const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
1798
+ const tx = this.transactionFromResultBeef(result, txid);
1719
1799
  const dec = sdk_1.PushDrop.decode(tx.outputs[outputIndex].lockingScript);
1720
1800
  if (dec?.fields == null || dec.fields.length < 6)
1721
1801
  continue;
@@ -1774,7 +1854,7 @@ class WalletPermissionsManager {
1774
1854
  if (seen.has(out.outpoint))
1775
1855
  continue;
1776
1856
  const [txid, vout] = this.parseOutpoint(out.outpoint);
1777
- const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
1857
+ const tx = this.transactionFromResultBeef(result, txid);
1778
1858
  const dec = sdk_1.PushDrop.decode(tx.outputs[vout].lockingScript);
1779
1859
  if (dec?.fields == null || dec.fields.length < 6)
1780
1860
  continue;
@@ -1818,7 +1898,7 @@ class WalletPermissionsManager {
1818
1898
  }, this.adminOriginator);
1819
1899
  for (const out of result.outputs) {
1820
1900
  const [txid, outputIndex] = this.parseOutpoint(out.outpoint);
1821
- const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
1901
+ const tx = this.transactionFromResultBeef(result, txid);
1822
1902
  const dec = sdk_1.PushDrop.decode(tx.outputs[outputIndex].lockingScript);
1823
1903
  if (!dec?.fields || dec.fields.length < 3)
1824
1904
  continue;
@@ -1858,7 +1938,7 @@ class WalletPermissionsManager {
1858
1938
  }, this.adminOriginator);
1859
1939
  for (const out of result.outputs) {
1860
1940
  const [txid, outputIndex] = this.parseOutpoint(out.outpoint);
1861
- const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
1941
+ const tx = this.transactionFromResultBeef(result, txid);
1862
1942
  const dec = sdk_1.PushDrop.decode(tx.outputs[outputIndex].lockingScript);
1863
1943
  if (!dec?.fields || dec.fields.length < 6)
1864
1944
  continue;
@@ -1909,7 +1989,7 @@ class WalletPermissionsManager {
1909
1989
  }, this.adminOriginator);
1910
1990
  for (const out of result.outputs) {
1911
1991
  const [txid, outputIndexStr] = out.outpoint.split('.');
1912
- const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
1992
+ const tx = this.transactionFromResultBeef(result, txid);
1913
1993
  const dec = sdk_1.PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript);
1914
1994
  if (!dec?.fields || dec.fields.length < 2)
1915
1995
  continue;
@@ -2366,7 +2446,7 @@ class WalletPermissionsManager {
2366
2446
  if (seen.has(out.outpoint))
2367
2447
  continue;
2368
2448
  const [txid, outputIndex] = this.parseOutpoint(out.outpoint);
2369
- const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
2449
+ const tx = this.transactionFromResultBeef(result, txid);
2370
2450
  const dec = sdk_1.PushDrop.decode(tx.outputs[outputIndex].lockingScript);
2371
2451
  if (!dec?.fields || dec.fields.length < 6)
2372
2452
  continue;
@@ -2434,7 +2514,7 @@ class WalletPermissionsManager {
2434
2514
  if (seen.has(out.outpoint))
2435
2515
  continue;
2436
2516
  const [txid, outputIndex] = this.parseOutpoint(out.outpoint);
2437
- const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
2517
+ const tx = this.transactionFromResultBeef(result, txid);
2438
2518
  const dec = sdk_1.PushDrop.decode(tx.outputs[outputIndex].lockingScript);
2439
2519
  if (!dec?.fields || dec.fields.length < 3)
2440
2520
  continue;
@@ -2497,7 +2577,7 @@ class WalletPermissionsManager {
2497
2577
  const tokens = [];
2498
2578
  for (const out of result.outputs) {
2499
2579
  const [txid, outputIndexStr] = out.outpoint.split('.');
2500
- const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
2580
+ const tx = this.transactionFromResultBeef(result, txid);
2501
2581
  const dec = sdk_1.PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript);
2502
2582
  if (!dec?.fields || dec.fields.length < 2)
2503
2583
  continue;
@@ -2568,7 +2648,7 @@ class WalletPermissionsManager {
2568
2648
  if (seen.has(out.outpoint))
2569
2649
  continue;
2570
2650
  const [txid, outputIndex] = this.parseOutpoint(out.outpoint);
2571
- const tx = sdk_1.Transaction.fromBEEF(result.BEEF, txid);
2651
+ const tx = this.transactionFromResultBeef(result, txid);
2572
2652
  const dec = sdk_1.PushDrop.decode(tx.outputs[outputIndex].lockingScript);
2573
2653
  if (!dec?.fields || dec.fields.length < 6)
2574
2654
  continue;
@@ -3506,6 +3586,21 @@ class WalletPermissionsManager {
3506
3586
  return true;
3507
3587
  return false;
3508
3588
  }
3589
+ /**
3590
+ * Whether the permission is satisfied without consulting on-chain tokens:
3591
+ * cached, recently granted, or — when the user just granted it and its
3592
+ * token is still minting — after the in-flight mint settles. Spares the
3593
+ * user a duplicate prompt for a grant they already made moments ago.
3594
+ */
3595
+ async hasRecentOrPendingGrant(cacheKey) {
3596
+ if (this.isPermissionCached(cacheKey) || this.isRecentlyGranted(cacheKey))
3597
+ return true;
3598
+ const inFlight = this.mintsInFlight.get(cacheKey);
3599
+ if (inFlight == null)
3600
+ return false;
3601
+ await inFlight;
3602
+ return this.isPermissionCached(cacheKey) || this.isRecentlyGranted(cacheKey);
3603
+ }
3509
3604
  /**
3510
3605
  * Returns true if we have a cached record that the permission identified by
3511
3606
  * `key` is valid and unexpired.