@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.
- package/CHANGELOG.md +4 -0
- package/docs/client.md +67 -29
- package/docs/services.md +1 -1
- package/docs/storage.md +34 -8
- package/docs/wallet.md +67 -29
- package/mobile/out/src/WalletPermissionsManager.d.ts +29 -0
- package/mobile/out/src/WalletPermissionsManager.d.ts.map +1 -1
- package/mobile/out/src/WalletPermissionsManager.js +581 -371
- package/mobile/out/src/WalletPermissionsManager.js.map +1 -1
- package/mobile/out/src/sdk/WalletError.d.ts.map +1 -1
- package/mobile/out/src/sdk/WalletError.js +3 -1
- package/mobile/out/src/sdk/WalletError.js.map +1 -1
- package/mobile/out/src/storage/WalletStorageManager.d.ts.map +1 -1
- package/mobile/out/src/storage/WalletStorageManager.js +3 -1
- package/mobile/out/src/storage/WalletStorageManager.js.map +1 -1
- package/mobile/out/src/storage/methods/createAction.d.ts.map +1 -1
- package/mobile/out/src/storage/methods/createAction.js +1 -1
- package/mobile/out/src/storage/methods/createAction.js.map +1 -1
- package/mobile/package-lock.json +6 -6
- package/mobile/package.json +2 -2
- package/out/src/WalletPermissionsManager.d.ts +29 -0
- package/out/src/WalletPermissionsManager.d.ts.map +1 -1
- package/out/src/WalletPermissionsManager.js +581 -371
- package/out/src/WalletPermissionsManager.js.map +1 -1
- package/out/src/__tests/WalletPermissionsManager.tokens.test.js +1 -80
- package/out/src/__tests/WalletPermissionsManager.tokens.test.js.map +1 -1
- package/out/src/sdk/WalletError.d.ts.map +1 -1
- package/out/src/sdk/WalletError.js +3 -1
- package/out/src/sdk/WalletError.js.map +1 -1
- package/out/src/storage/WalletStorageManager.d.ts.map +1 -1
- package/out/src/storage/WalletStorageManager.js +3 -1
- package/out/src/storage/WalletStorageManager.js.map +1 -1
- package/out/src/storage/methods/createAction.d.ts.map +1 -1
- package/out/src/storage/methods/createAction.js +1 -1
- package/out/src/storage/methods/createAction.js.map +1 -1
- package/out/tsconfig.all.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/WalletPermissionsManager.ts +674 -444
- package/src/__tests/WalletPermissionsManager.tokens.test.ts +1 -83
- package/src/sdk/WalletError.ts +9 -3
- package/src/storage/WalletStorageManager.ts +7 -5
- 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 {
|
|
620
|
-
|
|
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
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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
|
-
|
|
697
|
-
|
|
698
|
-
|
|
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
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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 (
|
|
1199
|
+
switch (preparedRequest.type) {
|
|
1154
1200
|
case 'protocol':
|
|
1155
1201
|
await this.callEvent('onProtocolPermissionRequested', {
|
|
1156
|
-
...
|
|
1202
|
+
...preparedRequest,
|
|
1157
1203
|
requestID: key
|
|
1158
1204
|
})
|
|
1159
1205
|
break
|
|
1160
1206
|
case 'basket':
|
|
1161
1207
|
await this.callEvent('onBasketAccessRequested', {
|
|
1162
|
-
...
|
|
1208
|
+
...preparedRequest,
|
|
1163
1209
|
requestID: key
|
|
1164
1210
|
})
|
|
1165
1211
|
break
|
|
1166
1212
|
case 'certificate':
|
|
1167
1213
|
await this.callEvent('onCertificateAccessRequested', {
|
|
1168
|
-
...
|
|
1214
|
+
...preparedRequest,
|
|
1169
1215
|
requestID: key
|
|
1170
1216
|
})
|
|
1171
1217
|
break
|
|
1172
1218
|
case 'spending':
|
|
1173
1219
|
await this.callEvent('onSpendingAuthorizationRequested', {
|
|
1174
|
-
...
|
|
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
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
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
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
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
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
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
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
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
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
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
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
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
|
|
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
|
|
1462
|
-
const
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
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
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
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
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
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
|
|
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
|
|
1510
|
-
const
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
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
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
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(
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
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
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
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
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
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
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
}
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
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
|
-
|
|
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
|
|
1946
|
-
|
|
1947
|
-
if (originator) {
|
|
1948
|
-
tags.push(`originator ${originator}`)
|
|
1949
|
-
}
|
|
2050
|
+
const baseTags: string[] = []
|
|
1950
2051
|
|
|
1951
2052
|
if (privileged !== undefined) {
|
|
1952
|
-
|
|
2053
|
+
baseTags.push(`privileged ${!!privileged}`)
|
|
1953
2054
|
}
|
|
1954
2055
|
|
|
1955
2056
|
if (protocolName) {
|
|
1956
|
-
|
|
2057
|
+
baseTags.push(`protocolName ${protocolName}`)
|
|
1957
2058
|
}
|
|
1958
2059
|
|
|
1959
2060
|
if (protocolSecurityLevel !== undefined) {
|
|
1960
|
-
|
|
2061
|
+
baseTags.push(`protocolSecurityLevel ${protocolSecurityLevel}`)
|
|
1961
2062
|
}
|
|
1962
2063
|
|
|
1963
2064
|
if (counterparty) {
|
|
1964
|
-
|
|
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
|
-
|
|
1986
|
-
const
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
const
|
|
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
|
-
|
|
1993
|
-
|
|
1994
|
-
txid
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
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
|
|
2041
|
-
|
|
2042
|
-
if (params.originator) {
|
|
2043
|
-
tags.push(`originator ${params.originator}`)
|
|
2044
|
-
}
|
|
2160
|
+
const baseTags: string[] = []
|
|
2045
2161
|
|
|
2046
2162
|
if (params.basket) {
|
|
2047
|
-
|
|
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
|
-
|
|
2062
|
-
|
|
2063
|
-
const
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
const
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
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
|
|
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
|
-
|
|
2316
|
+
baseTags.push(`privileged ${!!params.privileged}`)
|
|
2187
2317
|
}
|
|
2188
2318
|
|
|
2189
2319
|
if (params.certType) {
|
|
2190
|
-
|
|
2320
|
+
baseTags.push(`type ${params.certType}`)
|
|
2191
2321
|
}
|
|
2192
2322
|
|
|
2193
2323
|
if (params.verifier) {
|
|
2194
|
-
|
|
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
|
-
|
|
2209
|
-
|
|
2210
|
-
const
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
const
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
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
|
-
|
|
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:${
|
|
3331
|
+
return `proto:${normalizedOriginator}:${!!r.privileged}:${r.protocolID?.join(',')}:${r.counterparty}`
|
|
3102
3332
|
case 'basket':
|
|
3103
|
-
return `basket:${
|
|
3333
|
+
return `basket:${normalizedOriginator}:${r.basket}`
|
|
3104
3334
|
case 'certificate':
|
|
3105
|
-
return `cert:${
|
|
3335
|
+
return `cert:${normalizedOriginator}:${!!r.privileged}:${r.certificate?.verifier}:${r.certificate?.certType}:${r.certificate?.fields.join('|')}`
|
|
3106
3336
|
case 'spending':
|
|
3107
|
-
return `spend:${
|
|
3337
|
+
return `spend:${normalizedOriginator}:${r.spending?.satoshis}`
|
|
3108
3338
|
}
|
|
3109
3339
|
}
|
|
3110
3340
|
}
|