@bsv/wallet-toolbox 1.6.39 → 1.6.41

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 (42) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/docs/client.md +67 -29
  3. package/docs/services.md +1 -1
  4. package/docs/storage.md +34 -8
  5. package/docs/wallet.md +67 -29
  6. package/mobile/out/src/WalletPermissionsManager.d.ts +29 -0
  7. package/mobile/out/src/WalletPermissionsManager.d.ts.map +1 -1
  8. package/mobile/out/src/WalletPermissionsManager.js +581 -371
  9. package/mobile/out/src/WalletPermissionsManager.js.map +1 -1
  10. package/mobile/out/src/sdk/WalletError.d.ts.map +1 -1
  11. package/mobile/out/src/sdk/WalletError.js +3 -1
  12. package/mobile/out/src/sdk/WalletError.js.map +1 -1
  13. package/mobile/out/src/storage/WalletStorageManager.d.ts.map +1 -1
  14. package/mobile/out/src/storage/WalletStorageManager.js +3 -1
  15. package/mobile/out/src/storage/WalletStorageManager.js.map +1 -1
  16. package/mobile/out/src/storage/methods/createAction.d.ts.map +1 -1
  17. package/mobile/out/src/storage/methods/createAction.js +1 -1
  18. package/mobile/out/src/storage/methods/createAction.js.map +1 -1
  19. package/mobile/package-lock.json +6 -6
  20. package/mobile/package.json +2 -2
  21. package/out/src/WalletPermissionsManager.d.ts +29 -0
  22. package/out/src/WalletPermissionsManager.d.ts.map +1 -1
  23. package/out/src/WalletPermissionsManager.js +581 -371
  24. package/out/src/WalletPermissionsManager.js.map +1 -1
  25. package/out/src/__tests/WalletPermissionsManager.tokens.test.js +1 -80
  26. package/out/src/__tests/WalletPermissionsManager.tokens.test.js.map +1 -1
  27. package/out/src/sdk/WalletError.d.ts.map +1 -1
  28. package/out/src/sdk/WalletError.js +3 -1
  29. package/out/src/sdk/WalletError.js.map +1 -1
  30. package/out/src/storage/WalletStorageManager.d.ts.map +1 -1
  31. package/out/src/storage/WalletStorageManager.js +3 -1
  32. package/out/src/storage/WalletStorageManager.js.map +1 -1
  33. package/out/src/storage/methods/createAction.d.ts.map +1 -1
  34. package/out/src/storage/methods/createAction.js +1 -1
  35. package/out/src/storage/methods/createAction.js.map +1 -1
  36. package/out/tsconfig.all.tsbuildinfo +1 -1
  37. package/package.json +2 -2
  38. package/src/WalletPermissionsManager.ts +674 -444
  39. package/src/__tests/WalletPermissionsManager.tokens.test.ts +1 -83
  40. package/src/sdk/WalletError.ts +9 -3
  41. package/src/storage/WalletStorageManager.ts +7 -5
  42. package/src/storage/methods/createAction.ts +2 -3
@@ -103,6 +103,7 @@ export type GroupedPermissionEventHandler = (request: GroupedPermissionRequest)
103
103
  export interface PermissionRequest {
104
104
  type: 'protocol' | 'basket' | 'certificate' | 'spending'
105
105
  originator: string // The domain or FQDN of the requesting application
106
+ displayOriginator?: string // Optional raw/original originator string for UI purposes
106
107
  privileged?: boolean // For "protocol" or "certificate" usage, indicating privileged key usage
107
108
  protocolID?: WalletProtocol // For type='protocol': BRC-43 style (securityLevel, protocolName)
108
109
  counterparty?: string // For type='protocol': e.g. target public key or "self"/"anyone"
@@ -166,6 +167,13 @@ export interface PermissionToken {
166
167
  /** The originator domain or FQDN that is allowed to use this permission. */
167
168
  originator: string
168
169
 
170
+ /**
171
+ * The raw, unnormalized originator string captured at the time the permission
172
+ * token was created. This is preserved so we can continue to recognize legacy
173
+ * permissions that were stored with different casing or explicit default ports.
174
+ */
175
+ rawOriginator?: string
176
+
169
177
  /** The expiration time for this token in UNIX epoch seconds. (0 or omitted for spending authorizations, which are indefinite) */
170
178
  expiry: number
171
179
 
@@ -405,9 +413,18 @@ export class WalletPermissionsManager implements WalletInterface {
405
413
 
406
414
  /** Cache recently confirmed permissions to avoid repeated lookups. */
407
415
  private permissionCache: Map<string, { expiry: number; cachedAt: number }> = new Map()
416
+ private recentGrants: Map<string, number> = new Map()
408
417
 
409
418
  /** How long a cached permission remains valid (5 minutes). */
410
419
  private static readonly CACHE_TTL_MS = 5 * 60 * 1000
420
+ /** Window during which freshly granted permissions are auto-allowed (except spending). */
421
+ private static readonly RECENT_GRANT_COVER_MS = 15 * 1000
422
+
423
+ /** Default ports used when normalizing originator values. */
424
+ private static readonly DEFAULT_PORTS: Record<string, string> = {
425
+ 'http:': '80',
426
+ 'https:': '443'
427
+ }
411
428
 
412
429
  /**
413
430
  * Configuration that determines whether to skip or apply various checks and encryption.
@@ -423,7 +440,7 @@ export class WalletPermissionsManager implements WalletInterface {
423
440
  */
424
441
  constructor(underlyingWallet: WalletInterface, adminOriginator: string, config: PermissionsManagerConfig = {}) {
425
442
  this.underlying = underlyingWallet
426
- this.adminOriginator = adminOriginator
443
+ this.adminOriginator = this.normalizeOriginator(adminOriginator) || adminOriginator
427
444
 
428
445
  // Default all config options to true unless specified
429
446
  this.config = {
@@ -577,6 +594,7 @@ export class WalletPermissionsManager implements WalletInterface {
577
594
  const expiry = params.expiry || Math.floor(Date.now() / 1000) + 3600 * 24 * 30
578
595
  const key = this.buildRequestKey(matching.request as PermissionRequest)
579
596
  this.cachePermission(key, expiry)
597
+ this.markRecentGrant(matching.request as PermissionRequest)
580
598
  }
581
599
  }
582
600
 
@@ -616,8 +634,13 @@ export class WalletPermissionsManager implements WalletInterface {
616
634
  throw new Error('Request ID not found.')
617
635
  }
618
636
 
619
- const originalRequest = matching.request as { originator: string; permissions: GroupedPermissions }
620
- const { originator, permissions: requestedPermissions } = originalRequest
637
+ const originalRequest = matching.request as {
638
+ originator: string
639
+ permissions: GroupedPermissions
640
+ displayOriginator?: string
641
+ }
642
+ const { originator, permissions: requestedPermissions, displayOriginator } = originalRequest
643
+ const originLookupValues = this.buildOriginatorLookupValues(displayOriginator, originator)
621
644
 
622
645
  // --- Validation: Ensure granted permissions are a subset of what was requested ---
623
646
  if (
@@ -663,56 +686,52 @@ export class WalletPermissionsManager implements WalletInterface {
663
686
  false, // No privileged protocols allowed in groups for added security.
664
687
  p.protocolID,
665
688
  p.counterparty || 'self',
666
- true
689
+ true,
690
+ originLookupValues
667
691
  )
668
692
  if (token) {
669
- await this.renewPermissionOnChain(
670
- token,
671
- {
672
- type: 'protocol',
673
- originator,
674
- privileged: false, // No privileged protocols allowed in groups for added security.
675
- protocolID: p.protocolID,
676
- counterparty: p.counterparty || 'self',
677
- reason: p.description
678
- },
679
- expiry
680
- )
693
+ const request: PermissionRequest = {
694
+ type: 'protocol',
695
+ originator,
696
+ privileged: false, // No privileged protocols allowed in groups for added security.
697
+ protocolID: p.protocolID,
698
+ counterparty: p.counterparty || 'self',
699
+ reason: p.description
700
+ }
701
+ await this.renewPermissionOnChain(token, request, expiry)
702
+ this.markRecentGrant(request)
681
703
  } else {
682
- await this.createPermissionOnChain(
683
- {
684
- type: 'protocol',
685
- originator,
686
- privileged: false, // No privileged protocols allowed in groups for added security.
687
- protocolID: p.protocolID,
688
- counterparty: p.counterparty || 'self',
689
- reason: p.description
690
- },
691
- expiry
692
- )
704
+ const request: PermissionRequest = {
705
+ type: 'protocol',
706
+ originator,
707
+ privileged: false, // No privileged protocols allowed in groups for added security.
708
+ protocolID: p.protocolID,
709
+ counterparty: p.counterparty || 'self',
710
+ reason: p.description
711
+ }
712
+ await this.createPermissionOnChain(request, expiry)
713
+ this.markRecentGrant(request)
693
714
  }
694
715
  }
695
716
  for (const b of params.granted.basketAccess || []) {
696
- await this.createPermissionOnChain(
697
- { type: 'basket', originator, basket: b.basket, reason: b.description },
698
- expiry
699
- )
717
+ const request: PermissionRequest = { type: 'basket', originator, basket: b.basket, reason: b.description }
718
+ await this.createPermissionOnChain(request, expiry)
719
+ this.markRecentGrant(request)
700
720
  }
701
721
  for (const c of params.granted.certificateAccess || []) {
702
- await this.createPermissionOnChain(
703
- {
704
- type: 'certificate',
705
- originator,
706
- privileged: false, // No certificates on the privileged identity are allowed as part of groups.
707
- certificate: {
708
- verifier: c.verifierPublicKey,
709
- certType: c.type,
710
- fields: c.fields
711
- },
712
- reason: c.description
722
+ const request: PermissionRequest = {
723
+ type: 'certificate',
724
+ originator,
725
+ privileged: false, // No certificates on the privileged identity are allowed as part of groups.
726
+ certificate: {
727
+ verifier: c.verifierPublicKey,
728
+ certType: c.type,
729
+ fields: c.fields
713
730
  },
714
- expiry
715
- )
731
+ reason: c.description
732
+ }
733
+ await this.createPermissionOnChain(request, expiry)
734
+ this.markRecentGrant(request)
716
735
  }
717
736
 
718
737
  // Resolve all pending promises for this request
@@ -764,6 +783,8 @@ export class WalletPermissionsManager implements WalletInterface {
764
783
  seekPermission?: boolean
765
784
  usageType: 'signing' | 'encrypting' | 'hmac' | 'publicKey' | 'identityKey' | 'linkageRevelation' | 'generic'
766
785
  }): Promise<boolean> {
786
+ const { normalized: normalizedOriginator, lookupValues } = this.prepareOriginator(originator)
787
+ originator = normalizedOriginator
767
788
  // 1) adminOriginator can do anything
768
789
  if (this.isAdminOriginator(originator)) return true
769
790
 
@@ -809,6 +830,9 @@ export class WalletPermissionsManager implements WalletInterface {
809
830
  if (this.isPermissionCached(cacheKey)) {
810
831
  return true
811
832
  }
833
+ if (this.isRecentlyGranted(cacheKey)) {
834
+ return true
835
+ }
812
836
 
813
837
  // 4) Attempt to find a valid token in the internal basket
814
838
  const token = await this.findProtocolToken(
@@ -816,7 +840,8 @@ export class WalletPermissionsManager implements WalletInterface {
816
840
  privileged,
817
841
  protocolID,
818
842
  counterparty,
819
- /*includeExpired=*/ true
843
+ /*includeExpired=*/ true,
844
+ lookupValues
820
845
  )
821
846
  if (token) {
822
847
  if (!this.isTokenExpired(token.expiry)) {
@@ -874,6 +899,8 @@ export class WalletPermissionsManager implements WalletInterface {
874
899
  seekPermission?: boolean
875
900
  usageType: 'insertion' | 'removal' | 'listing'
876
901
  }): Promise<boolean> {
902
+ const { normalized: normalizedOriginator, lookupValues } = this.prepareOriginator(originator)
903
+ originator = normalizedOriginator
877
904
  if (this.isAdminOriginator(originator)) return true
878
905
  if (this.isAdminBasket(basket)) {
879
906
  throw new Error(`Basket “${basket}” is admin-only.`)
@@ -885,7 +912,10 @@ export class WalletPermissionsManager implements WalletInterface {
885
912
  if (this.isPermissionCached(cacheKey)) {
886
913
  return true
887
914
  }
888
- const token = await this.findBasketToken(originator, basket, true)
915
+ if (this.isRecentlyGranted(cacheKey)) {
916
+ return true
917
+ }
918
+ const token = await this.findBasketToken(originator, basket, true, lookupValues)
889
919
  if (token) {
890
920
  if (!this.isTokenExpired(token.expiry)) {
891
921
  this.cachePermission(cacheKey, token.expiry)
@@ -942,6 +972,8 @@ export class WalletPermissionsManager implements WalletInterface {
942
972
  seekPermission?: boolean
943
973
  usageType: 'disclosure'
944
974
  }): Promise<boolean> {
975
+ const { normalized: normalizedOriginator, lookupValues } = this.prepareOriginator(originator)
976
+ originator = normalizedOriginator
945
977
  if (this.isAdminOriginator(originator)) return true
946
978
  if (usageType === 'disclosure' && !this.config.seekCertificateDisclosurePermissions) {
947
979
  return true
@@ -958,13 +990,17 @@ export class WalletPermissionsManager implements WalletInterface {
958
990
  if (this.isPermissionCached(cacheKey)) {
959
991
  return true
960
992
  }
993
+ if (this.isRecentlyGranted(cacheKey)) {
994
+ return true
995
+ }
961
996
  const token = await this.findCertificateToken(
962
997
  originator,
963
998
  privileged,
964
999
  verifier,
965
1000
  certType,
966
1001
  fields,
967
- /*includeExpired=*/ true
1002
+ /*includeExpired=*/ true,
1003
+ lookupValues
968
1004
  )
969
1005
  if (token) {
970
1006
  if (!this.isTokenExpired(token.expiry)) {
@@ -1021,6 +1057,8 @@ export class WalletPermissionsManager implements WalletInterface {
1021
1057
  reason?: string
1022
1058
  seekPermission?: boolean
1023
1059
  }): Promise<boolean> {
1060
+ const { normalized: normalizedOriginator, lookupValues } = this.prepareOriginator(originator)
1061
+ originator = normalizedOriginator
1024
1062
  if (this.isAdminOriginator(originator)) return true
1025
1063
  if (!this.config.seekSpendingPermissions) {
1026
1064
  // We skip spending permission entirely
@@ -1030,7 +1068,7 @@ export class WalletPermissionsManager implements WalletInterface {
1030
1068
  if (this.isPermissionCached(cacheKey)) {
1031
1069
  return true
1032
1070
  }
1033
- const token = await this.findSpendingToken(originator)
1071
+ const token = await this.findSpendingToken(originator, lookupValues)
1034
1072
  if (token?.authorizedAmount) {
1035
1073
  // Check how much has been spent so far
1036
1074
  const spentSoFar = await this.querySpentSince(token)
@@ -1085,6 +1123,8 @@ export class WalletPermissionsManager implements WalletInterface {
1085
1123
  seekPermission?: boolean
1086
1124
  usageType: 'apply' | 'list'
1087
1125
  }): Promise<boolean> {
1126
+ const { normalized: normalizedOriginator } = this.prepareOriginator(originator)
1127
+ originator = normalizedOriginator
1088
1128
  // 1) adminOriginator can do anything
1089
1129
  if (this.isAdminOriginator(originator)) return true
1090
1130
 
@@ -1131,7 +1171,13 @@ export class WalletPermissionsManager implements WalletInterface {
1131
1171
  * and return a promise that resolves once permission is granted or rejects if denied.
1132
1172
  */
1133
1173
  private async requestPermissionFlow(r: PermissionRequest): Promise<boolean> {
1134
- const key = this.buildRequestKey(r)
1174
+ const normalizedOriginator = this.normalizeOriginator(r.originator) || r.originator
1175
+ const preparedRequest: PermissionRequest = {
1176
+ ...r,
1177
+ originator: normalizedOriginator,
1178
+ displayOriginator: r.displayOriginator ?? r.originator
1179
+ }
1180
+ const key = this.buildRequestKey(preparedRequest)
1135
1181
 
1136
1182
  // If there's already a queue for the same resource, we piggyback on it
1137
1183
  const existingQueue = this.activeRequests.get(key)
@@ -1145,33 +1191,33 @@ export class WalletPermissionsManager implements WalletInterface {
1145
1191
  // Return a promise that resolves or rejects once the user grants/denies
1146
1192
  return new Promise<boolean>(async (resolve, reject) => {
1147
1193
  this.activeRequests.set(key, {
1148
- request: r,
1194
+ request: preparedRequest,
1149
1195
  pending: [{ resolve, reject }]
1150
1196
  })
1151
1197
 
1152
1198
  // Fire the relevant onXXXRequested event (which one depends on r.type)
1153
- switch (r.type) {
1199
+ switch (preparedRequest.type) {
1154
1200
  case 'protocol':
1155
1201
  await this.callEvent('onProtocolPermissionRequested', {
1156
- ...r,
1202
+ ...preparedRequest,
1157
1203
  requestID: key
1158
1204
  })
1159
1205
  break
1160
1206
  case 'basket':
1161
1207
  await this.callEvent('onBasketAccessRequested', {
1162
- ...r,
1208
+ ...preparedRequest,
1163
1209
  requestID: key
1164
1210
  })
1165
1211
  break
1166
1212
  case 'certificate':
1167
1213
  await this.callEvent('onCertificateAccessRequested', {
1168
- ...r,
1214
+ ...preparedRequest,
1169
1215
  requestID: key
1170
1216
  })
1171
1217
  break
1172
1218
  case 'spending':
1173
1219
  await this.callEvent('onSpendingAuthorizationRequested', {
1174
- ...r,
1220
+ ...preparedRequest,
1175
1221
  requestID: key
1176
1222
  })
1177
1223
  break
@@ -1286,74 +1332,85 @@ export class WalletPermissionsManager implements WalletInterface {
1286
1332
  privileged: boolean,
1287
1333
  protocolID: WalletProtocol,
1288
1334
  counterparty: string,
1289
- includeExpired: boolean
1335
+ includeExpired: boolean,
1336
+ originatorLookupValues?: string[]
1290
1337
  ): Promise<PermissionToken | undefined> {
1291
1338
  const [secLevel, protoName] = protocolID
1292
- const tags = [
1293
- `originator ${originator}`,
1294
- `privileged ${!!privileged}`,
1295
- `protocolName ${protoName}`,
1296
- `protocolSecurityLevel ${secLevel}`
1297
- ]
1298
- if (secLevel === 2) {
1299
- tags.push(`counterparty ${counterparty}`)
1300
- }
1301
- const result = await this.underlying.listOutputs(
1302
- {
1303
- basket: BASKET_MAP.protocol,
1304
- tags,
1305
- tagQueryMode: 'all',
1306
- include: 'entire transactions'
1307
- },
1308
- this.adminOriginator
1309
- )
1339
+ const originsToTry = originatorLookupValues?.length ? originatorLookupValues : [originator]
1340
+
1341
+ for (const originTag of originsToTry) {
1342
+ const tags = [
1343
+ `originator ${originTag}`,
1344
+ `privileged ${!!privileged}`,
1345
+ `protocolName ${protoName}`,
1346
+ `protocolSecurityLevel ${secLevel}`
1347
+ ]
1348
+ if (secLevel === 2) {
1349
+ tags.push(`counterparty ${counterparty}`)
1350
+ }
1310
1351
 
1311
- for (const out of result.outputs) {
1312
- const [txid, outputIndexStr] = out.outpoint.split('.')
1313
- const tx = Transaction.fromBEEF(result.BEEF!, txid)
1314
- const dec = PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript)
1315
- if (!dec || !dec.fields || dec.fields.length < 6) continue
1316
- const domainRaw = dec.fields[0]
1317
- const expiryRaw = dec.fields[1]
1318
- const privRaw = dec.fields[2]
1319
- const secLevelRaw = dec.fields[3]
1320
- const protoNameRaw = dec.fields[4]
1321
- const counterpartyRaw = dec.fields[5]
1352
+ const result = await this.underlying.listOutputs(
1353
+ {
1354
+ basket: BASKET_MAP.protocol,
1355
+ tags,
1356
+ tagQueryMode: 'all',
1357
+ include: 'entire transactions'
1358
+ },
1359
+ this.adminOriginator
1360
+ )
1322
1361
 
1323
- const domainDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw))
1324
- const expiryDecoded = parseInt(Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10)
1325
- const privDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true'
1326
- const secLevelDecoded = parseInt(Utils.toUTF8(await this.decryptPermissionTokenField(secLevelRaw)), 10) as
1327
- | 0
1328
- | 1
1329
- | 2
1330
- const protoNameDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(protoNameRaw))
1331
- const cptyDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(counterpartyRaw))
1332
-
1333
- if (
1334
- domainDecoded !== originator ||
1335
- privDecoded !== !!privileged ||
1336
- secLevelDecoded !== secLevel ||
1337
- protoNameDecoded !== protoName ||
1338
- (secLevelDecoded === 2 && cptyDecoded !== counterparty)
1339
- ) {
1340
- continue
1341
- }
1342
- if (!includeExpired && this.isTokenExpired(expiryDecoded)) {
1343
- continue
1344
- }
1345
- return {
1346
- tx: tx.toBEEF(),
1347
- txid: out.outpoint.split('.')[0],
1348
- outputIndex: parseInt(out.outpoint.split('.')[1], 10),
1349
- outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
1350
- satoshis: out.satoshis,
1351
- originator,
1352
- privileged,
1353
- protocol: protoName,
1354
- securityLevel: secLevel,
1355
- expiry: expiryDecoded,
1356
- counterparty: cptyDecoded
1362
+ for (const out of result.outputs) {
1363
+ const [txid, outputIndexStr] = out.outpoint.split('.')
1364
+ const tx = Transaction.fromBEEF(result.BEEF!, txid)
1365
+ const dec = PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript)
1366
+ if (!dec || !dec.fields || dec.fields.length < 6) continue
1367
+ const domainRaw = dec.fields[0]
1368
+ const expiryRaw = dec.fields[1]
1369
+ const privRaw = dec.fields[2]
1370
+ const secLevelRaw = dec.fields[3]
1371
+ const protoNameRaw = dec.fields[4]
1372
+ const counterpartyRaw = dec.fields[5]
1373
+
1374
+ const domainDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw))
1375
+ const normalizedDomain = this.normalizeOriginator(domainDecoded)
1376
+ if (normalizedDomain !== originator) {
1377
+ continue
1378
+ }
1379
+
1380
+ const expiryDecoded = parseInt(Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10)
1381
+ const privDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true'
1382
+ const secLevelDecoded = parseInt(Utils.toUTF8(await this.decryptPermissionTokenField(secLevelRaw)), 10) as
1383
+ | 0
1384
+ | 1
1385
+ | 2
1386
+ const protoNameDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(protoNameRaw))
1387
+ const cptyDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(counterpartyRaw))
1388
+
1389
+ if (
1390
+ privDecoded !== !!privileged ||
1391
+ secLevelDecoded !== secLevel ||
1392
+ protoNameDecoded !== protoName ||
1393
+ (secLevelDecoded === 2 && cptyDecoded !== counterparty)
1394
+ ) {
1395
+ continue
1396
+ }
1397
+ if (!includeExpired && this.isTokenExpired(expiryDecoded)) {
1398
+ continue
1399
+ }
1400
+ return {
1401
+ tx: tx.toBEEF(),
1402
+ txid: out.outpoint.split('.')[0],
1403
+ outputIndex: parseInt(out.outpoint.split('.')[1], 10),
1404
+ outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
1405
+ satoshis: out.satoshis,
1406
+ originator,
1407
+ rawOriginator: domainDecoded,
1408
+ privileged,
1409
+ protocol: protoName,
1410
+ securityLevel: secLevel,
1411
+ expiry: expiryDecoded,
1412
+ counterparty: cptyDecoded
1413
+ }
1357
1414
  }
1358
1415
  }
1359
1416
  return undefined
@@ -1364,80 +1421,90 @@ export class WalletPermissionsManager implements WalletInterface {
1364
1421
  originator: string,
1365
1422
  privileged: boolean,
1366
1423
  protocolID: WalletProtocol,
1367
- counterparty: string
1424
+ counterparty: string,
1425
+ originatorLookupValues?: string[]
1368
1426
  ): Promise<PermissionToken[]> {
1369
1427
  const [secLevel, protoName] = protocolID
1370
- const tags = [
1371
- `originator ${originator}`,
1372
- `privileged ${!!privileged}`,
1373
- `protocolName ${protoName}`,
1374
- `protocolSecurityLevel ${secLevel}`
1375
- ]
1376
- if (secLevel === 2) {
1377
- tags.push(`counterparty ${counterparty}`)
1378
- }
1428
+ const originsToTry = originatorLookupValues?.length ? originatorLookupValues : [originator]
1429
+ const matches: PermissionToken[] = []
1430
+ const seen = new Set<string>()
1431
+
1432
+ for (const originTag of originsToTry) {
1433
+ const tags = [
1434
+ `originator ${originTag}`,
1435
+ `privileged ${!!privileged}`,
1436
+ `protocolName ${protoName}`,
1437
+ `protocolSecurityLevel ${secLevel}`
1438
+ ]
1439
+ if (secLevel === 2) {
1440
+ tags.push(`counterparty ${counterparty}`)
1441
+ }
1379
1442
 
1380
- const result = await this.underlying.listOutputs(
1381
- {
1382
- basket: BASKET_MAP.protocol,
1383
- tags,
1384
- tagQueryMode: 'all',
1385
- include: 'entire transactions'
1386
- },
1387
- this.adminOriginator
1388
- )
1443
+ const result = await this.underlying.listOutputs(
1444
+ {
1445
+ basket: BASKET_MAP.protocol,
1446
+ tags,
1447
+ tagQueryMode: 'all',
1448
+ include: 'entire transactions'
1449
+ },
1450
+ this.adminOriginator
1451
+ )
1389
1452
 
1390
- const matches: PermissionToken[] = []
1453
+ for (const out of result.outputs) {
1454
+ if (seen.has(out.outpoint)) continue
1455
+ const [txid, outputIndexStr] = out.outpoint.split('.')
1456
+ const tx = Transaction.fromBEEF(result.BEEF!, txid)
1457
+ const vout = Number(outputIndexStr)
1458
+ const dec = PushDrop.decode(tx.outputs[vout].lockingScript)
1459
+ if (!dec || !dec.fields || dec.fields.length < 6) continue
1460
+
1461
+ const domainRaw = dec.fields[0]
1462
+ const expiryRaw = dec.fields[1]
1463
+ const privRaw = dec.fields[2]
1464
+ const secLevelRaw = dec.fields[3]
1465
+ const protoNameRaw = dec.fields[4]
1466
+ const counterpartyRaw = dec.fields[5]
1467
+
1468
+ const domainDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw))
1469
+ const normalizedDomain = this.normalizeOriginator(domainDecoded)
1470
+ if (normalizedDomain !== originator) {
1471
+ continue
1472
+ }
1391
1473
 
1392
- for (const out of result.outputs) {
1393
- const [txid, outputIndexStr] = out.outpoint.split('.')
1394
- const tx = Transaction.fromBEEF(result.BEEF!, txid)
1395
- const vout = Number(outputIndexStr)
1396
- const dec = PushDrop.decode(tx.outputs[vout].lockingScript)
1397
- if (!dec || !dec.fields || dec.fields.length < 6) continue
1398
-
1399
- const domainRaw = dec.fields[0]
1400
- const expiryRaw = dec.fields[1]
1401
- const privRaw = dec.fields[2]
1402
- const secLevelRaw = dec.fields[3]
1403
- const protoNameRaw = dec.fields[4]
1404
- const counterpartyRaw = dec.fields[5]
1405
-
1406
- // Decrypt all fields
1407
- const domainDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw))
1408
- const expiryDecoded = parseInt(Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10)
1409
- const privDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true'
1410
- const secLevelDecoded = parseInt(Utils.toUTF8(await this.decryptPermissionTokenField(secLevelRaw)), 10) as
1411
- | 0
1412
- | 1
1413
- | 2
1414
- const protoNameDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(protoNameRaw))
1415
- const cptyDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(counterpartyRaw))
1416
-
1417
- // Strict attribute match; NO expiry filtering
1418
- if (
1419
- domainDecoded !== originator ||
1420
- privDecoded !== !!privileged ||
1421
- secLevelDecoded !== secLevel ||
1422
- protoNameDecoded !== protoName ||
1423
- (secLevelDecoded === 2 && cptyDecoded !== counterparty)
1424
- ) {
1425
- continue
1426
- }
1474
+ const expiryDecoded = parseInt(Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10)
1475
+ const privDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true'
1476
+ const secLevelDecoded = parseInt(Utils.toUTF8(await this.decryptPermissionTokenField(secLevelRaw)), 10) as
1477
+ | 0
1478
+ | 1
1479
+ | 2
1480
+ const protoNameDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(protoNameRaw))
1481
+ const cptyDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(counterpartyRaw))
1482
+
1483
+ if (
1484
+ privDecoded !== !!privileged ||
1485
+ secLevelDecoded !== secLevel ||
1486
+ protoNameDecoded !== protoName ||
1487
+ (secLevelDecoded === 2 && cptyDecoded !== counterparty)
1488
+ ) {
1489
+ continue
1490
+ }
1427
1491
 
1428
- matches.push({
1429
- tx: tx.toBEEF(),
1430
- txid,
1431
- outputIndex: vout,
1432
- outputScript: tx.outputs[vout].lockingScript.toHex(),
1433
- satoshis: out.satoshis,
1434
- originator,
1435
- privileged,
1436
- protocol: protoName,
1437
- securityLevel: secLevel,
1438
- expiry: expiryDecoded,
1439
- counterparty: cptyDecoded
1440
- })
1492
+ seen.add(out.outpoint)
1493
+ matches.push({
1494
+ tx: tx.toBEEF(),
1495
+ txid,
1496
+ outputIndex: vout,
1497
+ outputScript: tx.outputs[vout].lockingScript.toHex(),
1498
+ satoshis: out.satoshis,
1499
+ originator,
1500
+ rawOriginator: domainDecoded,
1501
+ privileged,
1502
+ protocol: protoName,
1503
+ securityLevel: secLevel,
1504
+ expiry: expiryDecoded,
1505
+ counterparty: cptyDecoded
1506
+ })
1507
+ }
1441
1508
  }
1442
1509
 
1443
1510
  return matches
@@ -1446,42 +1513,53 @@ export class WalletPermissionsManager implements WalletInterface {
1446
1513
  private async findBasketToken(
1447
1514
  originator: string,
1448
1515
  basket: string,
1449
- includeExpired: boolean
1516
+ includeExpired: boolean,
1517
+ originatorLookupValues?: string[]
1450
1518
  ): Promise<PermissionToken | undefined> {
1451
- const result = await this.underlying.listOutputs(
1452
- {
1453
- basket: BASKET_MAP.basket,
1454
- tags: [`originator ${originator}`, `basket ${basket}`],
1455
- tagQueryMode: 'all',
1456
- include: 'entire transactions'
1457
- },
1458
- this.adminOriginator
1459
- )
1519
+ const originsToTry = originatorLookupValues?.length ? originatorLookupValues : [originator]
1460
1520
 
1461
- for (const out of result.outputs) {
1462
- const [txid, outputIndexStr] = out.outpoint.split('.')
1463
- const tx = Transaction.fromBEEF(result.BEEF!, txid)
1464
- const dec = PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript)
1465
- if (!dec?.fields || dec.fields.length < 3) continue
1466
- const domainRaw = dec.fields[0]
1467
- const expiryRaw = dec.fields[1]
1468
- const basketRaw = dec.fields[2]
1521
+ for (const originTag of originsToTry) {
1522
+ const result = await this.underlying.listOutputs(
1523
+ {
1524
+ basket: BASKET_MAP.basket,
1525
+ tags: [`originator ${originTag}`, `basket ${basket}`],
1526
+ tagQueryMode: 'all',
1527
+ include: 'entire transactions'
1528
+ },
1529
+ this.adminOriginator
1530
+ )
1469
1531
 
1470
- const domainDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw))
1471
- const expiryDecoded = parseInt(Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10)
1472
- const basketDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(basketRaw))
1473
- if (domainDecoded !== originator || basketDecoded !== basket) continue
1474
- if (!includeExpired && this.isTokenExpired(expiryDecoded)) continue
1532
+ for (const out of result.outputs) {
1533
+ const [txid, outputIndexStr] = out.outpoint.split('.')
1534
+ const tx = Transaction.fromBEEF(result.BEEF!, txid)
1535
+ const dec = PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript)
1536
+ if (!dec?.fields || dec.fields.length < 3) continue
1537
+ const domainRaw = dec.fields[0]
1538
+ const expiryRaw = dec.fields[1]
1539
+ const basketRaw = dec.fields[2]
1540
+
1541
+ const domainDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw))
1542
+ const normalizedDomain = this.normalizeOriginator(domainDecoded)
1543
+ if (normalizedDomain !== originator) {
1544
+ continue
1545
+ }
1475
1546
 
1476
- return {
1477
- tx: tx.toBEEF(),
1478
- txid: out.outpoint.split('.')[0],
1479
- outputIndex: parseInt(out.outpoint.split('.')[1], 10),
1480
- outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
1481
- satoshis: out.satoshis,
1482
- originator,
1483
- basketName: basketDecoded,
1484
- expiry: expiryDecoded
1547
+ const expiryDecoded = parseInt(Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10)
1548
+ const basketDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(basketRaw))
1549
+ if (basketDecoded !== basket) continue
1550
+ if (!includeExpired && this.isTokenExpired(expiryDecoded)) continue
1551
+
1552
+ return {
1553
+ tx: tx.toBEEF(),
1554
+ txid: out.outpoint.split('.')[0],
1555
+ outputIndex: parseInt(out.outpoint.split('.')[1], 10),
1556
+ outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
1557
+ satoshis: out.satoshis,
1558
+ originator,
1559
+ rawOriginator: domainDecoded,
1560
+ basketName: basketDecoded,
1561
+ expiry: expiryDecoded
1562
+ }
1485
1563
  }
1486
1564
  }
1487
1565
  return undefined
@@ -1494,101 +1572,116 @@ export class WalletPermissionsManager implements WalletInterface {
1494
1572
  verifier: string,
1495
1573
  certType: string,
1496
1574
  fields: string[],
1497
- includeExpired: boolean
1575
+ includeExpired: boolean,
1576
+ originatorLookupValues?: string[]
1498
1577
  ): Promise<PermissionToken | undefined> {
1499
- const result = await this.underlying.listOutputs(
1500
- {
1501
- basket: BASKET_MAP.certificate,
1502
- tags: [`originator ${originator}`, `privileged ${!!privileged}`, `type ${certType}`, `verifier ${verifier}`],
1503
- tagQueryMode: 'all',
1504
- include: 'entire transactions'
1505
- },
1506
- this.adminOriginator
1507
- )
1578
+ const originsToTry = originatorLookupValues?.length ? originatorLookupValues : [originator]
1508
1579
 
1509
- for (const out of result.outputs) {
1510
- const [txid, outputIndexStr] = out.outpoint.split('.')
1511
- const tx = Transaction.fromBEEF(result.BEEF!, txid)
1512
- const dec = PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript)
1513
- if (!dec?.fields || dec.fields.length < 6) continue
1514
- const [domainRaw, expiryRaw, privRaw, typeRaw, fieldsRaw, verifierRaw] = dec.fields
1580
+ for (const originTag of originsToTry) {
1581
+ const result = await this.underlying.listOutputs(
1582
+ {
1583
+ basket: BASKET_MAP.certificate,
1584
+ tags: [`originator ${originTag}`, `privileged ${!!privileged}`, `type ${certType}`, `verifier ${verifier}`],
1585
+ tagQueryMode: 'all',
1586
+ include: 'entire transactions'
1587
+ },
1588
+ this.adminOriginator
1589
+ )
1515
1590
 
1516
- const domainDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw))
1517
- const expiryDecoded = parseInt(Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10)
1518
- const privDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true'
1519
- const typeDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(typeRaw))
1520
- const verifierDec = Utils.toUTF8(await this.decryptPermissionTokenField(verifierRaw))
1521
-
1522
- const fieldsJson = await this.decryptPermissionTokenField(fieldsRaw)
1523
- const allFields = JSON.parse(Utils.toUTF8(fieldsJson)) as string[]
1524
-
1525
- if (
1526
- domainDecoded !== originator ||
1527
- privDecoded !== !!privileged ||
1528
- typeDecoded !== certType ||
1529
- verifierDec !== verifier
1530
- ) {
1531
- continue
1532
- }
1533
- // Check if 'fields' is a subset of 'allFields'
1534
- const setAll = new Set(allFields)
1535
- if (fields.some(f => !setAll.has(f))) {
1536
- continue
1537
- }
1538
- if (!includeExpired && this.isTokenExpired(expiryDecoded)) {
1539
- continue
1540
- }
1541
- return {
1542
- tx: tx.toBEEF(),
1543
- txid: out.outpoint.split('.')[0],
1544
- outputIndex: parseInt(out.outpoint.split('.')[1], 10),
1545
- outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
1546
- satoshis: out.satoshis,
1547
- originator,
1548
- privileged,
1549
- verifier: verifierDec,
1550
- certType: typeDecoded,
1551
- certFields: allFields,
1552
- expiry: expiryDecoded
1591
+ for (const out of result.outputs) {
1592
+ const [txid, outputIndexStr] = out.outpoint.split('.')
1593
+ const tx = Transaction.fromBEEF(result.BEEF!, txid)
1594
+ const dec = PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript)
1595
+ if (!dec?.fields || dec.fields.length < 6) continue
1596
+ const [domainRaw, expiryRaw, privRaw, typeRaw, fieldsRaw, verifierRaw] = dec.fields
1597
+
1598
+ const domainDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw))
1599
+ const normalizedDomain = this.normalizeOriginator(domainDecoded)
1600
+ if (normalizedDomain !== originator) {
1601
+ continue
1602
+ }
1603
+
1604
+ const expiryDecoded = parseInt(Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10)
1605
+ const privDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true'
1606
+ const typeDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(typeRaw))
1607
+ const verifierDec = Utils.toUTF8(await this.decryptPermissionTokenField(verifierRaw))
1608
+
1609
+ const fieldsJson = await this.decryptPermissionTokenField(fieldsRaw)
1610
+ const allFields = JSON.parse(Utils.toUTF8(fieldsJson)) as string[]
1611
+
1612
+ if (privDecoded !== !!privileged || typeDecoded !== certType || verifierDec !== verifier) {
1613
+ continue
1614
+ }
1615
+ // Check if 'fields' is a subset of 'allFields'
1616
+ const setAll = new Set(allFields)
1617
+ if (fields.some(f => !setAll.has(f))) {
1618
+ continue
1619
+ }
1620
+ if (!includeExpired && this.isTokenExpired(expiryDecoded)) {
1621
+ continue
1622
+ }
1623
+ return {
1624
+ tx: tx.toBEEF(),
1625
+ txid: out.outpoint.split('.')[0],
1626
+ outputIndex: parseInt(out.outpoint.split('.')[1], 10),
1627
+ outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
1628
+ satoshis: out.satoshis,
1629
+ originator,
1630
+ rawOriginator: domainDecoded,
1631
+ privileged,
1632
+ verifier: verifierDec,
1633
+ certType: typeDecoded,
1634
+ certFields: allFields,
1635
+ expiry: expiryDecoded
1636
+ }
1553
1637
  }
1554
1638
  }
1555
1639
  return undefined
1556
1640
  }
1557
1641
 
1558
1642
  /** Looks for a DSAP token matching origin, returning the first one found. */
1559
- private async findSpendingToken(originator: string): Promise<PermissionToken | undefined> {
1560
- const result = await this.underlying.listOutputs(
1561
- {
1562
- basket: BASKET_MAP.spending,
1563
- tags: [`originator ${originator}`],
1564
- tagQueryMode: 'all',
1565
- include: 'entire transactions'
1566
- },
1567
- this.adminOriginator
1568
- )
1569
-
1570
- for (const out of result.outputs) {
1571
- const [txid, outputIndexStr] = out.outpoint.split('.')
1572
- const tx = Transaction.fromBEEF(result.BEEF!, txid)
1573
- const dec = PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript)
1574
- if (!dec?.fields || dec.fields.length < 2) continue
1575
- const domainRaw = dec.fields[0]
1576
- const amtRaw = dec.fields[1]
1643
+ private async findSpendingToken(
1644
+ originator: string,
1645
+ originatorLookupValues?: string[]
1646
+ ): Promise<PermissionToken | undefined> {
1647
+ const originsToTry = originatorLookupValues?.length ? originatorLookupValues : [originator]
1577
1648
 
1578
- const domainDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw))
1579
- if (domainDecoded !== originator) continue
1580
- const amtDecodedStr = Utils.toUTF8(await this.decryptPermissionTokenField(amtRaw))
1581
- const authorizedAmount = parseInt(amtDecodedStr, 10)
1649
+ for (const originTag of originsToTry) {
1650
+ const result = await this.underlying.listOutputs(
1651
+ {
1652
+ basket: BASKET_MAP.spending,
1653
+ tags: [`originator ${originTag}`],
1654
+ tagQueryMode: 'all',
1655
+ include: 'entire transactions'
1656
+ },
1657
+ this.adminOriginator
1658
+ )
1582
1659
 
1583
- return {
1584
- tx: tx.toBEEF(),
1585
- txid: out.outpoint.split('.')[0],
1586
- outputIndex: parseInt(out.outpoint.split('.')[1], 10),
1587
- outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
1588
- satoshis: out.satoshis,
1589
- originator,
1590
- authorizedAmount,
1591
- expiry: 0 // Not time-limited, monthly authorization
1660
+ for (const out of result.outputs) {
1661
+ const [txid, outputIndexStr] = out.outpoint.split('.')
1662
+ const tx = Transaction.fromBEEF(result.BEEF!, txid)
1663
+ const dec = PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript)
1664
+ if (!dec?.fields || dec.fields.length < 2) continue
1665
+ const domainRaw = dec.fields[0]
1666
+ const amtRaw = dec.fields[1]
1667
+
1668
+ const domainDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw))
1669
+ const normalizedDomain = this.normalizeOriginator(domainDecoded)
1670
+ if (normalizedDomain !== originator) continue
1671
+ const amtDecodedStr = Utils.toUTF8(await this.decryptPermissionTokenField(amtRaw))
1672
+ const authorizedAmount = parseInt(amtDecodedStr, 10)
1673
+
1674
+ return {
1675
+ tx: tx.toBEEF(),
1676
+ txid: out.outpoint.split('.')[0],
1677
+ outputIndex: parseInt(out.outpoint.split('.')[1], 10),
1678
+ outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
1679
+ satoshis: out.satoshis,
1680
+ originator,
1681
+ rawOriginator: domainDecoded,
1682
+ authorizedAmount,
1683
+ expiry: 0 // Not time-limited, monthly authorization
1684
+ }
1592
1685
  }
1593
1686
  }
1594
1687
  return undefined
@@ -1610,14 +1703,21 @@ export class WalletPermissionsManager implements WalletInterface {
1610
1703
  * Returns spending for an originator in the current calendar month.
1611
1704
  */
1612
1705
  public async querySpentSince(token: PermissionToken): Promise<number> {
1613
- const { actions } = await this.underlying.listActions(
1614
- {
1615
- labels: [`admin originator ${token.originator}`, `admin month ${this.getCurrentMonthYearUTC()}`],
1616
- labelQueryMode: 'all'
1617
- },
1618
- this.adminOriginator
1619
- )
1620
- return actions.reduce((a, e) => a + e.satoshis, 0)
1706
+ const labelOrigins = this.buildOriginatorLookupValues(token.rawOriginator, token.originator)
1707
+ let total = 0
1708
+
1709
+ for (const labelOrigin of labelOrigins) {
1710
+ const { actions } = await this.underlying.listActions(
1711
+ {
1712
+ labels: [`admin originator ${labelOrigin}`, `admin month ${this.getCurrentMonthYearUTC()}`],
1713
+ labelQueryMode: 'all'
1714
+ },
1715
+ this.adminOriginator
1716
+ )
1717
+ total += actions.reduce((a, e) => a + e.satoshis, 0)
1718
+ }
1719
+
1720
+ return total
1621
1721
  }
1622
1722
 
1623
1723
  /* ---------------------------------------------------------------------
@@ -1634,6 +1734,8 @@ export class WalletPermissionsManager implements WalletInterface {
1634
1734
  * @param amount For DSAP, the authorized spending limit
1635
1735
  */
1636
1736
  private async createPermissionOnChain(r: PermissionRequest, expiry: number, amount?: number): Promise<void> {
1737
+ const normalizedOriginator = this.normalizeOriginator(r.originator) || r.originator
1738
+ r.originator = normalizedOriginator
1637
1739
  const basketName = BASKET_MAP[r.type]
1638
1740
  if (!basketName) return
1639
1741
 
@@ -1755,6 +1857,7 @@ export class WalletPermissionsManager implements WalletInterface {
1755
1857
  newExpiry: number,
1756
1858
  newAmount?: number
1757
1859
  ): Promise<void> {
1860
+ r.originator = this.normalizeOriginator(r.originator) || r.originator
1758
1861
  // 1) build new fields
1759
1862
  const newFields = await this.buildPushdropFields(r, newExpiry, newAmount)
1760
1863
 
@@ -1773,7 +1876,8 @@ export class WalletPermissionsManager implements WalletInterface {
1773
1876
  oldToken.originator,
1774
1877
  oldToken.privileged!,
1775
1878
  [oldToken.securityLevel!, oldToken.protocol!],
1776
- oldToken.counterparty!
1879
+ oldToken.counterparty!,
1880
+ this.buildOriginatorLookupValues(oldToken.rawOriginator, oldToken.originator)
1777
1881
  )
1778
1882
 
1779
1883
  // If so, coalesce them into a single token first, to avoid bloat
@@ -1783,7 +1887,6 @@ export class WalletPermissionsManager implements WalletInterface {
1783
1887
  basket: BASKET_MAP[r.type],
1784
1888
  description: `Coalesce ${r.type} permission tokens`
1785
1889
  })
1786
- console.log('Coalesced permission tokens:', txid)
1787
1890
  } else {
1788
1891
  // Otherwise, just proceed with the single-token renewal
1789
1892
  // 3) For BRC-100, we do a "createAction" with a partial input referencing oldToken
@@ -1894,7 +1997,9 @@ export class WalletPermissionsManager implements WalletInterface {
1894
1997
  tags.push(`privileged ${!!r.privileged}`)
1895
1998
  tags.push(`protocolName ${r.protocolID![1]}`)
1896
1999
  tags.push(`protocolSecurityLevel ${r.protocolID![0]}`)
1897
- tags.push(`counterparty ${r.counterparty}`)
2000
+ if (r.protocolID![0] === 2) {
2001
+ tags.push(`counterparty ${r.counterparty}`)
2002
+ }
1898
2003
  break
1899
2004
  }
1900
2005
  case 'basket': {
@@ -1942,66 +2047,81 @@ export class WalletPermissionsManager implements WalletInterface {
1942
2047
  counterparty?: string
1943
2048
  } = {}): Promise<PermissionToken[]> {
1944
2049
  const basketName = BASKET_MAP.protocol
1945
- const tags: string[] = []
1946
-
1947
- if (originator) {
1948
- tags.push(`originator ${originator}`)
1949
- }
2050
+ const baseTags: string[] = []
1950
2051
 
1951
2052
  if (privileged !== undefined) {
1952
- tags.push(`privileged ${!!privileged}`)
2053
+ baseTags.push(`privileged ${!!privileged}`)
1953
2054
  }
1954
2055
 
1955
2056
  if (protocolName) {
1956
- tags.push(`protocolName ${protocolName}`)
2057
+ baseTags.push(`protocolName ${protocolName}`)
1957
2058
  }
1958
2059
 
1959
2060
  if (protocolSecurityLevel !== undefined) {
1960
- tags.push(`protocolSecurityLevel ${protocolSecurityLevel}`)
2061
+ baseTags.push(`protocolSecurityLevel ${protocolSecurityLevel}`)
1961
2062
  }
1962
2063
 
1963
2064
  if (counterparty) {
1964
- tags.push(`counterparty ${counterparty}`)
2065
+ baseTags.push(`counterparty ${counterparty}`)
1965
2066
  }
1966
- const result = await this.underlying.listOutputs(
1967
- {
1968
- basket: basketName,
1969
- tags,
1970
- tagQueryMode: 'all',
1971
- include: 'entire transactions',
1972
- limit: 100
1973
- },
1974
- this.adminOriginator
1975
- )
1976
2067
 
2068
+ const originFilter = originator ? this.prepareOriginator(originator) : undefined
2069
+ const originVariants = originFilter ? originFilter.lookupValues : [undefined]
2070
+ const seen = new Set<string>()
1977
2071
  const tokens: PermissionToken[] = []
1978
- for (const out of result.outputs) {
1979
- const [txid, outputIndexStr] = out.outpoint.split('.')
1980
- const tx = Transaction.fromBEEF(result.BEEF!, txid)
1981
- const dec = PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript)
1982
- if (!dec?.fields || dec.fields.length < 6) continue
1983
- const [domainRaw, expiryRaw, privRaw, secRaw, protoRaw, cptyRaw] = dec.fields
1984
2072
 
1985
- const domainDec = Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw))
1986
- const expiryDec = parseInt(Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10)
1987
- const privDec = Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true'
1988
- const secDec = parseInt(Utils.toUTF8(await this.decryptPermissionTokenField(secRaw)), 10) as 0 | 1 | 2
1989
- const protoDec = Utils.toUTF8(await this.decryptPermissionTokenField(protoRaw))
1990
- const cptyDec = Utils.toUTF8(await this.decryptPermissionTokenField(cptyRaw))
2073
+ for (const originTag of originVariants) {
2074
+ const tags = [...baseTags]
2075
+ if (originTag) {
2076
+ tags.push(`originator ${originTag}`)
2077
+ }
2078
+ const result = await this.underlying.listOutputs(
2079
+ {
2080
+ basket: basketName,
2081
+ tags,
2082
+ tagQueryMode: 'all',
2083
+ include: 'entire transactions',
2084
+ limit: 100
2085
+ },
2086
+ this.adminOriginator
2087
+ )
1991
2088
 
1992
- tokens.push({
1993
- tx: tx.toBEEF(),
1994
- txid: out.outpoint.split('.')[0],
1995
- outputIndex: parseInt(out.outpoint.split('.')[1], 10),
1996
- outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
1997
- satoshis: out.satoshis,
1998
- originator: domainDec,
1999
- expiry: expiryDec,
2000
- privileged: privDec,
2001
- securityLevel: secDec,
2002
- protocol: protoDec,
2003
- counterparty: cptyDec
2004
- })
2089
+ for (const out of result.outputs) {
2090
+ if (seen.has(out.outpoint)) continue
2091
+ const [txid, outputIndexStr] = out.outpoint.split('.')
2092
+ const tx = Transaction.fromBEEF(result.BEEF!, txid)
2093
+ const dec = PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript)
2094
+ if (!dec?.fields || dec.fields.length < 6) continue
2095
+ const [domainRaw, expiryRaw, privRaw, secRaw, protoRaw, cptyRaw] = dec.fields
2096
+
2097
+ const domainDec = Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw))
2098
+ const normalizedDomain = this.normalizeOriginator(domainDec)
2099
+ if (originFilter && normalizedDomain !== originFilter.normalized) {
2100
+ continue
2101
+ }
2102
+
2103
+ const expiryDec = parseInt(Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10)
2104
+ const privDec = Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true'
2105
+ const secDec = parseInt(Utils.toUTF8(await this.decryptPermissionTokenField(secRaw)), 10) as 0 | 1 | 2
2106
+ const protoDec = Utils.toUTF8(await this.decryptPermissionTokenField(protoRaw))
2107
+ const cptyDec = Utils.toUTF8(await this.decryptPermissionTokenField(cptyRaw))
2108
+
2109
+ seen.add(out.outpoint)
2110
+ tokens.push({
2111
+ tx: tx.toBEEF(),
2112
+ txid: out.outpoint.split('.')[0],
2113
+ outputIndex: parseInt(out.outpoint.split('.')[1], 10),
2114
+ outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
2115
+ satoshis: out.satoshis,
2116
+ originator: normalizedDomain,
2117
+ rawOriginator: domainDec,
2118
+ expiry: expiryDec,
2119
+ privileged: privDec,
2120
+ securityLevel: secDec,
2121
+ protocol: protoDec,
2122
+ counterparty: cptyDec
2123
+ })
2124
+ }
2005
2125
  }
2006
2126
  return tokens
2007
2127
  }
@@ -2037,46 +2157,60 @@ export class WalletPermissionsManager implements WalletInterface {
2037
2157
  */
2038
2158
  public async listBasketAccess(params: { originator?: string; basket?: string } = {}): Promise<PermissionToken[]> {
2039
2159
  const basketName = BASKET_MAP.basket
2040
- const tags: string[] = []
2041
-
2042
- if (params.originator) {
2043
- tags.push(`originator ${params.originator}`)
2044
- }
2160
+ const baseTags: string[] = []
2045
2161
 
2046
2162
  if (params.basket) {
2047
- tags.push(`basket ${params.basket}`)
2163
+ baseTags.push(`basket ${params.basket}`)
2048
2164
  }
2049
- const result = await this.underlying.listOutputs(
2050
- {
2051
- basket: basketName,
2052
- tags,
2053
- tagQueryMode: 'all',
2054
- include: 'entire transactions',
2055
- limit: 10000
2056
- },
2057
- this.adminOriginator
2058
- )
2059
2165
 
2166
+ const originFilter = params.originator ? this.prepareOriginator(params.originator) : undefined
2167
+ const originVariants = originFilter ? originFilter.lookupValues : [undefined]
2168
+ const seen = new Set<string>()
2060
2169
  const tokens: PermissionToken[] = []
2061
- for (const out of result.outputs) {
2062
- const [txid, outputIndexStr] = out.outpoint.split('.')
2063
- const tx = Transaction.fromBEEF(result.BEEF!, txid)
2064
- const dec = PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript)
2065
- if (!dec?.fields || dec.fields.length < 3) continue
2066
- const [domainRaw, expiryRaw, basketRaw] = dec.fields
2067
- const domainDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw))
2068
- const expiryDecoded = parseInt(Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10)
2069
- const basketDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(basketRaw))
2070
- tokens.push({
2071
- tx: tx.toBEEF(),
2072
- txid: out.outpoint.split('.')[0],
2073
- outputIndex: parseInt(out.outpoint.split('.')[1], 10),
2074
- satoshis: out.satoshis,
2075
- outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
2076
- originator: domainDecoded,
2077
- basketName: basketDecoded,
2078
- expiry: expiryDecoded
2079
- })
2170
+
2171
+ for (const originTag of originVariants) {
2172
+ const tags = [...baseTags]
2173
+ if (originTag) {
2174
+ tags.push(`originator ${originTag}`)
2175
+ }
2176
+ const result = await this.underlying.listOutputs(
2177
+ {
2178
+ basket: basketName,
2179
+ tags,
2180
+ tagQueryMode: 'all',
2181
+ include: 'entire transactions',
2182
+ limit: 10000
2183
+ },
2184
+ this.adminOriginator
2185
+ )
2186
+
2187
+ for (const out of result.outputs) {
2188
+ if (seen.has(out.outpoint)) continue
2189
+ const [txid, outputIndexStr] = out.outpoint.split('.')
2190
+ const tx = Transaction.fromBEEF(result.BEEF!, txid)
2191
+ const dec = PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript)
2192
+ if (!dec?.fields || dec.fields.length < 3) continue
2193
+ const [domainRaw, expiryRaw, basketRaw] = dec.fields
2194
+ const domainDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw))
2195
+ const normalizedDomain = this.normalizeOriginator(domainDecoded)
2196
+ if (originFilter && normalizedDomain !== originFilter.normalized) {
2197
+ continue
2198
+ }
2199
+ const expiryDecoded = parseInt(Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10)
2200
+ const basketDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(basketRaw))
2201
+ seen.add(out.outpoint)
2202
+ tokens.push({
2203
+ tx: tx.toBEEF(),
2204
+ txid: out.outpoint.split('.')[0],
2205
+ outputIndex: parseInt(out.outpoint.split('.')[1], 10),
2206
+ satoshis: out.satoshis,
2207
+ outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
2208
+ originator: normalizedDomain,
2209
+ rawOriginator: domainDecoded,
2210
+ basketName: basketDecoded,
2211
+ expiry: expiryDecoded
2212
+ })
2213
+ }
2080
2214
  }
2081
2215
  return tokens
2082
2216
  }
@@ -2176,61 +2310,75 @@ export class WalletPermissionsManager implements WalletInterface {
2176
2310
  } = {}
2177
2311
  ): Promise<PermissionToken[]> {
2178
2312
  const basketName = BASKET_MAP.certificate
2179
- const tags: string[] = []
2180
-
2181
- if (params.originator) {
2182
- tags.push(`originator ${params.originator}`)
2183
- }
2313
+ const baseTags: string[] = []
2184
2314
 
2185
2315
  if (params.privileged !== undefined) {
2186
- tags.push(`privileged ${!!params.privileged}`)
2316
+ baseTags.push(`privileged ${!!params.privileged}`)
2187
2317
  }
2188
2318
 
2189
2319
  if (params.certType) {
2190
- tags.push(`type ${params.certType}`)
2320
+ baseTags.push(`type ${params.certType}`)
2191
2321
  }
2192
2322
 
2193
2323
  if (params.verifier) {
2194
- tags.push(`verifier ${params.verifier}`)
2324
+ baseTags.push(`verifier ${params.verifier}`)
2195
2325
  }
2196
- const result = await this.underlying.listOutputs(
2197
- {
2198
- basket: basketName,
2199
- tags,
2200
- tagQueryMode: 'all',
2201
- include: 'entire transactions',
2202
- limit: 10000
2203
- },
2204
- this.adminOriginator
2205
- )
2206
2326
 
2327
+ const originFilter = params.originator ? this.prepareOriginator(params.originator) : undefined
2328
+ const originVariants = originFilter ? originFilter.lookupValues : [undefined]
2329
+ const seen = new Set<string>()
2207
2330
  const tokens: PermissionToken[] = []
2208
- for (const out of result.outputs) {
2209
- const [txid, outputIndexStr] = out.outpoint.split('.')
2210
- const tx = Transaction.fromBEEF(result.BEEF!, txid)
2211
- const dec = PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript)
2212
- if (!dec?.fields || dec.fields.length < 6) continue
2213
- const [domainRaw, expiryRaw, privRaw, typeRaw, fieldsRaw, verifierRaw] = dec.fields
2214
- const domainDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw))
2215
- const expiryDecoded = parseInt(Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10)
2216
- const privDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true'
2217
- const typeDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(typeRaw))
2218
- const verifierDec = Utils.toUTF8(await this.decryptPermissionTokenField(verifierRaw))
2219
- const fieldsJson = await this.decryptPermissionTokenField(fieldsRaw)
2220
- const allFields = JSON.parse(Utils.toUTF8(fieldsJson)) as string[]
2221
- tokens.push({
2222
- tx: tx.toBEEF(),
2223
- txid: out.outpoint.split('.')[0],
2224
- outputIndex: parseInt(out.outpoint.split('.')[1], 10),
2225
- satoshis: out.satoshis,
2226
- outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
2227
- originator: domainDecoded,
2228
- privileged: privDecoded,
2229
- certType: typeDecoded,
2230
- certFields: allFields,
2231
- verifier: verifierDec,
2232
- expiry: expiryDecoded
2233
- })
2331
+
2332
+ for (const originTag of originVariants) {
2333
+ const tags = [...baseTags]
2334
+ if (originTag) {
2335
+ tags.push(`originator ${originTag}`)
2336
+ }
2337
+ const result = await this.underlying.listOutputs(
2338
+ {
2339
+ basket: basketName,
2340
+ tags,
2341
+ tagQueryMode: 'all',
2342
+ include: 'entire transactions',
2343
+ limit: 10000
2344
+ },
2345
+ this.adminOriginator
2346
+ )
2347
+
2348
+ for (const out of result.outputs) {
2349
+ if (seen.has(out.outpoint)) continue
2350
+ const [txid, outputIndexStr] = out.outpoint.split('.')
2351
+ const tx = Transaction.fromBEEF(result.BEEF!, txid)
2352
+ const dec = PushDrop.decode(tx.outputs[Number(outputIndexStr)].lockingScript)
2353
+ if (!dec?.fields || dec.fields.length < 6) continue
2354
+ const [domainRaw, expiryRaw, privRaw, typeRaw, fieldsRaw, verifierRaw] = dec.fields
2355
+ const domainDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(domainRaw))
2356
+ const normalizedDomain = this.normalizeOriginator(domainDecoded)
2357
+ if (originFilter && normalizedDomain !== originFilter.normalized) {
2358
+ continue
2359
+ }
2360
+ const expiryDecoded = parseInt(Utils.toUTF8(await this.decryptPermissionTokenField(expiryRaw)), 10)
2361
+ const privDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(privRaw)) === 'true'
2362
+ const typeDecoded = Utils.toUTF8(await this.decryptPermissionTokenField(typeRaw))
2363
+ const verifierDec = Utils.toUTF8(await this.decryptPermissionTokenField(verifierRaw))
2364
+ const fieldsJson = await this.decryptPermissionTokenField(fieldsRaw)
2365
+ const allFields = JSON.parse(Utils.toUTF8(fieldsJson)) as string[]
2366
+ seen.add(out.outpoint)
2367
+ tokens.push({
2368
+ tx: tx.toBEEF(),
2369
+ txid: out.outpoint.split('.')[0],
2370
+ outputIndex: parseInt(out.outpoint.split('.')[1], 10),
2371
+ satoshis: out.satoshis,
2372
+ outputScript: tx.outputs[Number(outputIndexStr)].lockingScript.toHex(),
2373
+ originator: normalizedDomain,
2374
+ rawOriginator: domainDecoded,
2375
+ privileged: privDecoded,
2376
+ certType: typeDecoded,
2377
+ certFields: allFields,
2378
+ verifier: verifierDec,
2379
+ expiry: expiryDecoded
2380
+ })
2381
+ }
2234
2382
  }
2235
2383
  return tokens
2236
2384
  }
@@ -2881,8 +3029,10 @@ export class WalletPermissionsManager implements WalletInterface {
2881
3029
  public async waitForAuthentication(
2882
3030
  ...args: Parameters<WalletInterface['waitForAuthentication']>
2883
3031
  ): ReturnType<WalletInterface['waitForAuthentication']> {
2884
- const [_, originator] = args
3032
+ let [_, originator] = args
2885
3033
  if (this.config.seekGroupedPermission && originator) {
3034
+ const { normalized: normalizedOriginator } = this.prepareOriginator(originator)
3035
+ originator = normalizedOriginator
2886
3036
  // 1. Fetch manifest.json from the originator
2887
3037
  let groupPermissions: GroupedPermissions | undefined
2888
3038
  try {
@@ -2970,7 +3120,7 @@ export class WalletPermissionsManager implements WalletInterface {
2970
3120
  try {
2971
3121
  await new Promise<boolean>(async (resolve, reject) => {
2972
3122
  this.activeRequests.set(key, {
2973
- request: { originator, permissions: permissionsToRequest },
3123
+ request: { originator: originator as string, permissions: permissionsToRequest },
2974
3124
  pending: [{ resolve, reject }]
2975
3125
  })
2976
3126
 
@@ -3021,7 +3171,7 @@ export class WalletPermissionsManager implements WalletInterface {
3021
3171
 
3022
3172
  /** Returns true if the specified origin is the admin originator. */
3023
3173
  private isAdminOriginator(originator: string): boolean {
3024
- return originator === this.adminOriginator
3174
+ return this.normalizeOriginator(originator) === this.adminOriginator
3025
3175
  }
3026
3176
 
3027
3177
  /**
@@ -3091,20 +3241,100 @@ export class WalletPermissionsManager implements WalletInterface {
3091
3241
  this.permissionCache.set(key, { expiry, cachedAt: Date.now() })
3092
3242
  }
3093
3243
 
3244
+ /** Records that a non-spending permission was just granted so we can skip re-prompting briefly. */
3245
+ private markRecentGrant(request: PermissionRequest): void {
3246
+ if (request.type === 'spending') return
3247
+ const key = this.buildRequestKey(request)
3248
+ if (!key) return
3249
+ this.recentGrants.set(key, Date.now() + WalletPermissionsManager.RECENT_GRANT_COVER_MS)
3250
+ }
3251
+
3252
+ /** Returns true if we are inside the short "cover window" immediately after granting permission. */
3253
+ private isRecentlyGranted(key: string): boolean {
3254
+ const expiry = this.recentGrants.get(key)
3255
+ if (!expiry) return false
3256
+ if (Date.now() > expiry) {
3257
+ this.recentGrants.delete(key)
3258
+ return false
3259
+ }
3260
+ return true
3261
+ }
3262
+
3263
+ /** Normalizes and canonicalizes originator domains (e.g., lowercase + drop default ports). */
3264
+ private normalizeOriginator(originator?: string): string {
3265
+ if (!originator) return ''
3266
+ const trimmed = originator.trim()
3267
+ if (!trimmed) {
3268
+ return ''
3269
+ }
3270
+
3271
+ try {
3272
+ const hasScheme = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(trimmed)
3273
+ const candidate = hasScheme ? trimmed : `https://${trimmed}`
3274
+ const url = new URL(candidate)
3275
+ if (!url.hostname) {
3276
+ return trimmed.toLowerCase()
3277
+ }
3278
+ const hostname = url.hostname.toLowerCase()
3279
+ const needsBrackets = hostname.includes(':')
3280
+ const baseHost = needsBrackets ? `[${hostname}]` : hostname
3281
+ const port = url.port
3282
+ const defaultPort = WalletPermissionsManager.DEFAULT_PORTS[url.protocol]
3283
+ if (port && defaultPort && port === defaultPort) {
3284
+ return baseHost
3285
+ }
3286
+ return port ? `${baseHost}:${port}` : baseHost
3287
+ } catch {
3288
+ // Fall back to a conservative lowercase trim if URL parsing fails.
3289
+ return trimmed.toLowerCase()
3290
+ }
3291
+ }
3292
+
3293
+ /**
3294
+ * Produces a normalized originator value along with the set of legacy
3295
+ * representations that should be considered when searching for existing
3296
+ * permission tokens (for backwards compatibility).
3297
+ */
3298
+ private prepareOriginator(originator?: string): { normalized: string; lookupValues: string[] } {
3299
+ const trimmed = originator?.trim()
3300
+ if (!trimmed) {
3301
+ throw new Error('Originator is required for permission checks.')
3302
+ }
3303
+ const normalized = this.normalizeOriginator(trimmed) || trimmed.toLowerCase()
3304
+ const lookupValues = Array.from(new Set([trimmed, normalized])).filter(Boolean)
3305
+ return { normalized, lookupValues }
3306
+ }
3307
+
3308
+ /**
3309
+ * Builds a unique list of originator variants that should be searched when
3310
+ * looking up on-chain tokens (e.g., legacy raw + normalized forms).
3311
+ */
3312
+ private buildOriginatorLookupValues(...origins: Array<string | undefined>): string[] {
3313
+ const variants = new Set<string>()
3314
+ for (const origin of origins) {
3315
+ const trimmed = origin?.trim()
3316
+ if (trimmed) {
3317
+ variants.add(trimmed)
3318
+ }
3319
+ }
3320
+ return Array.from(variants)
3321
+ }
3322
+
3094
3323
  /**
3095
3324
  * Builds a "map key" string so that identical requests (e.g. "protocol:domain:true:protoName:counterparty")
3096
3325
  * do not produce multiple user prompts.
3097
3326
  */
3098
3327
  private buildRequestKey(r: PermissionRequest): string {
3328
+ const normalizedOriginator = this.normalizeOriginator(r.originator)
3099
3329
  switch (r.type) {
3100
3330
  case 'protocol':
3101
- return `proto:${r.originator}:${!!r.privileged}:${r.protocolID?.join(',')}:${r.counterparty}`
3331
+ return `proto:${normalizedOriginator}:${!!r.privileged}:${r.protocolID?.join(',')}:${r.counterparty}`
3102
3332
  case 'basket':
3103
- return `basket:${r.originator}:${r.basket}`
3333
+ return `basket:${normalizedOriginator}:${r.basket}`
3104
3334
  case 'certificate':
3105
- return `cert:${r.originator}:${!!r.privileged}:${r.certificate?.verifier}:${r.certificate?.certType}:${r.certificate?.fields.join('|')}`
3335
+ return `cert:${normalizedOriginator}:${!!r.privileged}:${r.certificate?.verifier}:${r.certificate?.certType}:${r.certificate?.fields.join('|')}`
3106
3336
  case 'spending':
3107
- return `spend:${r.originator}:${r.spending?.satoshis}`
3337
+ return `spend:${normalizedOriginator}:${r.spending?.satoshis}`
3108
3338
  }
3109
3339
  }
3110
3340
  }