@bsv/wallet-toolbox 1.7.22 → 1.8.2
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 +8 -0
- package/docs/README.md +1 -0
- package/docs/client.md +135 -0
- package/docs/wab-shamir.md +311 -0
- package/docs/wallet.md +135 -0
- package/out/src/ShamirWalletManager.d.ts +213 -0
- package/out/src/ShamirWalletManager.d.ts.map +1 -0
- package/out/src/ShamirWalletManager.js +363 -0
- package/out/src/ShamirWalletManager.js.map +1 -0
- package/out/src/WalletPermissionsManager.d.ts +27 -0
- package/out/src/WalletPermissionsManager.d.ts.map +1 -1
- package/out/src/WalletPermissionsManager.js +308 -147
- package/out/src/WalletPermissionsManager.js.map +1 -1
- package/out/src/__tests/ShamirWalletManager.test.d.ts +2 -0
- package/out/src/__tests/ShamirWalletManager.test.d.ts.map +1 -0
- package/out/src/__tests/ShamirWalletManager.test.js +298 -0
- package/out/src/__tests/ShamirWalletManager.test.js.map +1 -0
- package/out/src/__tests/WalletPermissionsManager.callbacks.test.js +116 -0
- package/out/src/__tests/WalletPermissionsManager.callbacks.test.js.map +1 -1
- package/out/src/__tests/WalletPermissionsManager.pmodules.test.js +111 -0
- package/out/src/__tests/WalletPermissionsManager.pmodules.test.js.map +1 -1
- package/out/src/entropy/EntropyCollector.d.ts +89 -0
- package/out/src/entropy/EntropyCollector.d.ts.map +1 -0
- package/out/src/entropy/EntropyCollector.js +176 -0
- package/out/src/entropy/EntropyCollector.js.map +1 -0
- package/out/src/entropy/__tests/EntropyCollector.test.d.ts +2 -0
- package/out/src/entropy/__tests/EntropyCollector.test.d.ts.map +1 -0
- package/out/src/entropy/__tests/EntropyCollector.test.js +137 -0
- package/out/src/entropy/__tests/EntropyCollector.test.js.map +1 -0
- package/out/src/index.all.d.ts +2 -0
- package/out/src/index.all.d.ts.map +1 -1
- package/out/src/index.all.js +2 -0
- package/out/src/index.all.js.map +1 -1
- package/out/src/sdk/WalletServices.interfaces.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.js +4 -1
- package/out/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.js.map +1 -1
- package/out/src/wab-client/WABClient.d.ts +65 -0
- package/out/src/wab-client/WABClient.d.ts.map +1 -1
- package/out/src/wab-client/WABClient.js +107 -0
- package/out/src/wab-client/WABClient.js.map +1 -1
- package/out/tsconfig.all.tsbuildinfo +1 -1
- package/package.json +5 -1
- package/src/ShamirWalletManager.ts +499 -0
- package/src/WalletPermissionsManager.ts +368 -181
- package/src/__tests/ShamirWalletManager.test.ts +369 -0
- package/src/__tests/WalletPermissionsManager.callbacks.test.ts +140 -1
- package/src/__tests/WalletPermissionsManager.pmodules.test.ts +152 -0
- package/src/entropy/EntropyCollector.ts +228 -0
- package/src/entropy/__tests/EntropyCollector.test.ts +182 -0
- package/src/index.all.ts +2 -0
- package/src/sdk/WalletServices.interfaces.ts +0 -1
- package/src/services/chaintracker/chaintracks/Storage/ChaintracksStorageBase.ts +4 -1
- package/src/wab-client/WABClient.ts +135 -0
|
@@ -20,7 +20,9 @@ import {
|
|
|
20
20
|
RelinquishOutputArgs,
|
|
21
21
|
GetPublicKeyArgs,
|
|
22
22
|
CreateActionArgs,
|
|
23
|
-
ListOutputsResult
|
|
23
|
+
ListOutputsResult,
|
|
24
|
+
ListActionsArgs,
|
|
25
|
+
ListActionsResult
|
|
24
26
|
} from '@bsv/sdk'
|
|
25
27
|
|
|
26
28
|
////// TODO: ADD SUPPORT FOR ADMIN COUNTERPARTIES BASED ON WALLET STORAGE
|
|
@@ -618,6 +620,80 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
618
620
|
})
|
|
619
621
|
}
|
|
620
622
|
|
|
623
|
+
/**
|
|
624
|
+
* Adds a permission module for the given schemeID if needed, throwing if unsupported.
|
|
625
|
+
*/
|
|
626
|
+
private addPModuleByScheme(
|
|
627
|
+
schemeID: string,
|
|
628
|
+
kind: 'label' | 'basket',
|
|
629
|
+
pModulesByScheme: Map<string, PermissionsModule>
|
|
630
|
+
): void {
|
|
631
|
+
if (pModulesByScheme.has(schemeID)) return
|
|
632
|
+
const module = this.config.permissionModules?.[schemeID]
|
|
633
|
+
if (!module) {
|
|
634
|
+
throw new Error(`Unsupported P-${kind} scheme: p ${schemeID}`)
|
|
635
|
+
}
|
|
636
|
+
pModulesByScheme.set(schemeID, module)
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Splits labels into P and non-P lists, registering any P-modules encountered.
|
|
641
|
+
*
|
|
642
|
+
* P-labels follow BRC-111 format: `p <moduleId> <payload>`
|
|
643
|
+
* - Must start with "p " (lowercase p + space)
|
|
644
|
+
* - Module ID must be at least 1 character with no spaces
|
|
645
|
+
* - Single space separates module ID from payload
|
|
646
|
+
* - Payload must be at least 1 character
|
|
647
|
+
*
|
|
648
|
+
* @example Valid: "p btms token123", "p invoicing invoice 2026-02-02"
|
|
649
|
+
* @example Invalid: "p btms" (no payload), "p btms " (empty payload), "p data" (empty moduleId)
|
|
650
|
+
*
|
|
651
|
+
* @param labels - Array of label strings to process
|
|
652
|
+
* @param pModulesByScheme - Map to populate with discovered p-modules
|
|
653
|
+
* @returns Array of non-P labels for normal permission checks
|
|
654
|
+
* @throws Error if p-label format is invalid or module is unsupported
|
|
655
|
+
*/
|
|
656
|
+
private splitLabelsByPermissionModule(
|
|
657
|
+
labels: string[] | undefined,
|
|
658
|
+
pModulesByScheme: Map<string, PermissionsModule>
|
|
659
|
+
): string[] {
|
|
660
|
+
const nonPLabels: string[] = []
|
|
661
|
+
if (!labels) return nonPLabels
|
|
662
|
+
|
|
663
|
+
for (const label of labels) {
|
|
664
|
+
if (label.startsWith('p ')) {
|
|
665
|
+
// Remove "p " prefix to get "moduleId payload"
|
|
666
|
+
const remainder = label.slice(2)
|
|
667
|
+
|
|
668
|
+
// Find the space that separates moduleId from payload
|
|
669
|
+
const separatorIndex = remainder.indexOf(' ')
|
|
670
|
+
|
|
671
|
+
// Validate: must have a space (separatorIndex > 0) and payload after it
|
|
672
|
+
// separatorIndex <= 0 means no space found or moduleId is empty
|
|
673
|
+
// separatorIndex === remainder.length - 1 means space is last char (no payload)
|
|
674
|
+
if (separatorIndex <= 0 || separatorIndex === remainder.length - 1) {
|
|
675
|
+
throw new Error(`Invalid P-label format: ${label}`)
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Reject double spaces after moduleId (payload can't start with space)
|
|
679
|
+
if (remainder[separatorIndex + 1] === ' ') {
|
|
680
|
+
throw new Error(`Invalid P-label format: ${label}`)
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Extract moduleId (substring before first space)
|
|
684
|
+
const schemeID = remainder.slice(0, separatorIndex)
|
|
685
|
+
|
|
686
|
+
// Register the module (throws if unsupported)
|
|
687
|
+
this.addPModuleByScheme(schemeID, 'label', pModulesByScheme)
|
|
688
|
+
} else {
|
|
689
|
+
// Regular label - add to list for normal permission checks
|
|
690
|
+
nonPLabels.push(label)
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
return nonPLabels
|
|
695
|
+
}
|
|
696
|
+
|
|
621
697
|
/**
|
|
622
698
|
* Decrypts custom instructions in listOutputs results if encryption is configured.
|
|
623
699
|
*/
|
|
@@ -634,6 +710,43 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
634
710
|
return results
|
|
635
711
|
}
|
|
636
712
|
|
|
713
|
+
/**
|
|
714
|
+
* Decrypts metadata in listActions results if encryption is configured.
|
|
715
|
+
*/
|
|
716
|
+
private async decryptListActionsMetadata(results: ListActionsResult): Promise<ListActionsResult> {
|
|
717
|
+
if (results.actions) {
|
|
718
|
+
for (let i = 0; i < results.actions.length; i++) {
|
|
719
|
+
if (results.actions[i].description) {
|
|
720
|
+
results.actions[i].description = await this.maybeDecryptMetadata(results.actions[i].description)
|
|
721
|
+
}
|
|
722
|
+
if (results.actions[i].inputs) {
|
|
723
|
+
for (let j = 0; j < results.actions[i].inputs!.length; j++) {
|
|
724
|
+
if (results.actions[i].inputs![j].inputDescription) {
|
|
725
|
+
results.actions[i].inputs![j].inputDescription = await this.maybeDecryptMetadata(
|
|
726
|
+
results.actions[i].inputs![j].inputDescription
|
|
727
|
+
)
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (results.actions[i].outputs) {
|
|
732
|
+
for (let j = 0; j < results.actions[i].outputs!.length; j++) {
|
|
733
|
+
if (results.actions[i].outputs![j].outputDescription) {
|
|
734
|
+
results.actions[i].outputs![j].outputDescription = await this.maybeDecryptMetadata(
|
|
735
|
+
results.actions[i].outputs![j].outputDescription
|
|
736
|
+
)
|
|
737
|
+
}
|
|
738
|
+
if (results.actions[i].outputs![j].customInstructions) {
|
|
739
|
+
results.actions[i].outputs![j].customInstructions = await this.maybeDecryptMetadata(
|
|
740
|
+
results.actions[i].outputs![j].customInstructions!
|
|
741
|
+
)
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return results
|
|
748
|
+
}
|
|
749
|
+
|
|
637
750
|
/* ---------------------------------------------------------------------
|
|
638
751
|
* 1) PUBLIC API FOR REGISTERING CALLBACKS (UI PROMPTS, LOGGING, ETC.)
|
|
639
752
|
* --------------------------------------------------------------------- */
|
|
@@ -800,118 +913,128 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
800
913
|
throw new Error('Request ID not found.')
|
|
801
914
|
}
|
|
802
915
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
// --- Validation: Ensure granted permissions are a subset of what was requested ---
|
|
812
|
-
if (params.granted.spendingAuthorization && !requestedPermissions.spendingAuthorization) {
|
|
813
|
-
throw new Error('Granted spending authorization was not part of the original request.')
|
|
814
|
-
}
|
|
815
|
-
if (
|
|
816
|
-
params.granted.protocolPermissions?.some(
|
|
817
|
-
g => !requestedPermissions.protocolPermissions?.find(r => deepEqual(r, g))
|
|
818
|
-
)
|
|
819
|
-
) {
|
|
820
|
-
throw new Error('Granted protocol permissions are not a subset of the original request.')
|
|
821
|
-
}
|
|
822
|
-
if (params.granted.basketAccess?.some(g => !requestedPermissions.basketAccess?.find(r => deepEqual(r, g)))) {
|
|
823
|
-
throw new Error('Granted basket access permissions are not a subset of the original request.')
|
|
824
|
-
}
|
|
825
|
-
if (
|
|
826
|
-
params.granted.certificateAccess?.some(g => !requestedPermissions.certificateAccess?.find(r => deepEqual(r, g)))
|
|
827
|
-
) {
|
|
828
|
-
throw new Error('Granted certificate access permissions are not a subset of the original request.')
|
|
829
|
-
}
|
|
830
|
-
// --- End Validation ---
|
|
831
|
-
|
|
832
|
-
const expiry = params.expiry || 0 // default: never expires
|
|
916
|
+
try {
|
|
917
|
+
const originalRequest = matching.request as {
|
|
918
|
+
originator: string
|
|
919
|
+
permissions: GroupedPermissions
|
|
920
|
+
displayOriginator?: string
|
|
921
|
+
}
|
|
922
|
+
const { originator, permissions: requestedPermissions, displayOriginator } = originalRequest
|
|
923
|
+
const originLookupValues = this.buildOriginatorLookupValues(displayOriginator, originator)
|
|
833
924
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
925
|
+
// --- Validation: Ensure granted permissions are a subset of what was requested ---
|
|
926
|
+
if (params.granted.spendingAuthorization && !requestedPermissions.spendingAuthorization) {
|
|
927
|
+
throw new Error('Granted spending authorization was not part of the original request.')
|
|
928
|
+
}
|
|
929
|
+
if (
|
|
930
|
+
params.granted.protocolPermissions?.some(
|
|
931
|
+
g => !requestedPermissions.protocolPermissions?.find(r => deepEqual(r, g))
|
|
932
|
+
)
|
|
933
|
+
) {
|
|
934
|
+
throw new Error('Granted protocol permissions are not a subset of the original request.')
|
|
935
|
+
}
|
|
936
|
+
if (params.granted.basketAccess?.some(g => !requestedPermissions.basketAccess?.find(r => deepEqual(r, g)))) {
|
|
937
|
+
throw new Error('Granted basket access permissions are not a subset of the original request.')
|
|
938
|
+
}
|
|
939
|
+
if (
|
|
940
|
+
params.granted.certificateAccess?.some(g => !requestedPermissions.certificateAccess?.find(r => deepEqual(r, g)))
|
|
941
|
+
) {
|
|
942
|
+
throw new Error('Granted certificate access permissions are not a subset of the original request.')
|
|
943
|
+
}
|
|
944
|
+
// --- End Validation ---
|
|
837
945
|
|
|
838
|
-
|
|
839
|
-
toCreate.push({
|
|
840
|
-
request: {
|
|
841
|
-
type: 'spending',
|
|
842
|
-
originator,
|
|
843
|
-
spending: { satoshis: params.granted.spendingAuthorization.amount },
|
|
844
|
-
reason: params.granted.spendingAuthorization.description
|
|
845
|
-
},
|
|
846
|
-
expiry: 0,
|
|
847
|
-
amount: params.granted.spendingAuthorization.amount
|
|
848
|
-
})
|
|
849
|
-
}
|
|
946
|
+
const expiry = params.expiry || 0 // default: never expires
|
|
850
947
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
originator,
|
|
855
|
-
false,
|
|
856
|
-
p.protocolID,
|
|
857
|
-
p.counterparty || 'self',
|
|
858
|
-
true,
|
|
859
|
-
originLookupValues
|
|
860
|
-
)
|
|
861
|
-
return { p, token }
|
|
862
|
-
})
|
|
948
|
+
const toCreate: Array<{ request: PermissionRequest; expiry: number; amount?: number }> = []
|
|
949
|
+
const toRenew: Array<{ oldToken: PermissionToken; request: PermissionRequest; expiry: number; amount?: number }> =
|
|
950
|
+
[]
|
|
863
951
|
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
} else {
|
|
876
|
-
toCreate.push({ request, expiry })
|
|
952
|
+
if (params.granted.spendingAuthorization) {
|
|
953
|
+
toCreate.push({
|
|
954
|
+
request: {
|
|
955
|
+
type: 'spending',
|
|
956
|
+
originator,
|
|
957
|
+
spending: { satoshis: params.granted.spendingAuthorization.amount },
|
|
958
|
+
reason: params.granted.spendingAuthorization.description
|
|
959
|
+
},
|
|
960
|
+
expiry: 0,
|
|
961
|
+
amount: params.granted.spendingAuthorization.amount
|
|
962
|
+
})
|
|
877
963
|
}
|
|
878
|
-
}
|
|
879
964
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
965
|
+
const grantedProtocols = params.granted.protocolPermissions || []
|
|
966
|
+
const protocolTokens = await this.mapWithConcurrency(grantedProtocols, 8, async p => {
|
|
967
|
+
const token = await this.findProtocolToken(
|
|
968
|
+
originator,
|
|
969
|
+
false,
|
|
970
|
+
p.protocolID,
|
|
971
|
+
p.counterparty || 'self',
|
|
972
|
+
true,
|
|
973
|
+
originLookupValues
|
|
974
|
+
)
|
|
975
|
+
return { p, token }
|
|
884
976
|
})
|
|
885
|
-
}
|
|
886
977
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
type: 'certificate',
|
|
978
|
+
for (const { p, token } of protocolTokens) {
|
|
979
|
+
const request: PermissionRequest = {
|
|
980
|
+
type: 'protocol',
|
|
891
981
|
originator,
|
|
892
982
|
privileged: false,
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
983
|
+
protocolID: p.protocolID,
|
|
984
|
+
counterparty: p.counterparty || 'self',
|
|
985
|
+
reason: p.description
|
|
986
|
+
}
|
|
987
|
+
if (token) {
|
|
988
|
+
toRenew.push({ oldToken: token, request, expiry })
|
|
989
|
+
} else {
|
|
990
|
+
toCreate.push({ request, expiry })
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
for (const b of params.granted.basketAccess || []) {
|
|
995
|
+
toCreate.push({
|
|
996
|
+
request: { type: 'basket', originator, basket: b.basket, reason: b.description },
|
|
997
|
+
expiry
|
|
998
|
+
})
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
for (const c of params.granted.certificateAccess || []) {
|
|
1002
|
+
toCreate.push({
|
|
1003
|
+
request: {
|
|
1004
|
+
type: 'certificate',
|
|
1005
|
+
originator,
|
|
1006
|
+
privileged: false,
|
|
1007
|
+
certificate: {
|
|
1008
|
+
verifier: c.verifierPublicKey,
|
|
1009
|
+
certType: c.type,
|
|
1010
|
+
fields: c.fields
|
|
1011
|
+
},
|
|
1012
|
+
reason: c.description
|
|
897
1013
|
},
|
|
898
|
-
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
})
|
|
902
|
-
}
|
|
1014
|
+
expiry
|
|
1015
|
+
})
|
|
1016
|
+
}
|
|
903
1017
|
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1018
|
+
const created = await this.createPermissionTokensBestEffort(toCreate)
|
|
1019
|
+
const renewed = await this.renewPermissionTokensBestEffort(toRenew)
|
|
1020
|
+
for (const req of [...created, ...renewed]) {
|
|
1021
|
+
this.markRecentGrant(req)
|
|
1022
|
+
}
|
|
909
1023
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1024
|
+
// Success - resolve all pending promises for this request
|
|
1025
|
+
for (const p of matching.pending) {
|
|
1026
|
+
p.resolve(true)
|
|
1027
|
+
}
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
// Failure - reject all pending promises so callers don't hang forever
|
|
1030
|
+
for (const p of matching.pending) {
|
|
1031
|
+
p.reject(error)
|
|
1032
|
+
}
|
|
1033
|
+
throw error
|
|
1034
|
+
} finally {
|
|
1035
|
+
// Always clean up the request entry
|
|
1036
|
+
this.activeRequests.delete(params.requestID)
|
|
913
1037
|
}
|
|
914
|
-
this.activeRequests.delete(params.requestID)
|
|
915
1038
|
}
|
|
916
1039
|
|
|
917
1040
|
/**
|
|
@@ -3510,22 +3633,17 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
3510
3633
|
args: Parameters<WalletInterface['createAction']>[0],
|
|
3511
3634
|
originator?: string
|
|
3512
3635
|
): ReturnType<WalletInterface['createAction']> {
|
|
3513
|
-
// 1) Identify unique P-modules involved (one per schemeID)
|
|
3636
|
+
// 1) Identify unique P-modules involved (one per schemeID) from both baskets and labels
|
|
3514
3637
|
const pModulesByScheme = new Map<string, PermissionsModule>()
|
|
3515
3638
|
const nonPBaskets: string[] = []
|
|
3516
3639
|
|
|
3640
|
+
// Check baskets for p modules
|
|
3517
3641
|
if (args.outputs) {
|
|
3518
3642
|
for (const out of args.outputs) {
|
|
3519
3643
|
if (out.basket) {
|
|
3520
3644
|
if (out.basket.startsWith('p ')) {
|
|
3521
3645
|
const schemeID = out.basket.split(' ')[1]
|
|
3522
|
-
|
|
3523
|
-
const module = this.config.permissionModules?.[schemeID]
|
|
3524
|
-
if (!module) {
|
|
3525
|
-
throw new Error(`Unsupported P-basket scheme: p ${schemeID}`)
|
|
3526
|
-
}
|
|
3527
|
-
pModulesByScheme.set(schemeID, module)
|
|
3528
|
-
}
|
|
3646
|
+
this.addPModuleByScheme(schemeID, 'basket', pModulesByScheme)
|
|
3529
3647
|
} else {
|
|
3530
3648
|
// Track non-P baskets for normal permission checks
|
|
3531
3649
|
nonPBaskets.push(out.basket)
|
|
@@ -3534,6 +3652,9 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
3534
3652
|
}
|
|
3535
3653
|
}
|
|
3536
3654
|
|
|
3655
|
+
// Check labels for p modules
|
|
3656
|
+
const nonPLabels = this.splitLabelsByPermissionModule(args.labels, pModulesByScheme)
|
|
3657
|
+
|
|
3537
3658
|
// 2) Check permissions for non-P baskets
|
|
3538
3659
|
for (const basket of nonPBaskets) {
|
|
3539
3660
|
await this.ensureBasketAccess({
|
|
@@ -3544,15 +3665,14 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
3544
3665
|
})
|
|
3545
3666
|
}
|
|
3546
3667
|
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
}
|
|
3668
|
+
// 3) Check permissions for non-P labels
|
|
3669
|
+
for (const lbl of nonPLabels) {
|
|
3670
|
+
await this.ensureLabelAccess({
|
|
3671
|
+
originator: originator!,
|
|
3672
|
+
label: lbl,
|
|
3673
|
+
reason: args.description,
|
|
3674
|
+
usageType: 'apply'
|
|
3675
|
+
})
|
|
3556
3676
|
}
|
|
3557
3677
|
|
|
3558
3678
|
/**
|
|
@@ -3758,91 +3878,154 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
3758
3878
|
...args: Parameters<WalletInterface['listActions']>
|
|
3759
3879
|
): ReturnType<WalletInterface['listActions']> {
|
|
3760
3880
|
const [requestArgs, originator] = args
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3881
|
+
|
|
3882
|
+
// 1) Identify unique P-modules involved (one per schemeID, preserving label order)
|
|
3883
|
+
const pModulesByScheme = new Map<string, PermissionsModule>()
|
|
3884
|
+
const nonPLabels = this.splitLabelsByPermissionModule(requestArgs.labels, pModulesByScheme)
|
|
3885
|
+
|
|
3886
|
+
// 2) Check permissions for non-P labels
|
|
3887
|
+
for (const lbl of nonPLabels) {
|
|
3888
|
+
await this.ensureLabelAccess({
|
|
3889
|
+
originator: originator!,
|
|
3890
|
+
label: lbl,
|
|
3891
|
+
reason: 'listActions',
|
|
3892
|
+
usageType: 'list'
|
|
3893
|
+
})
|
|
3894
|
+
}
|
|
3895
|
+
|
|
3896
|
+
// 3) Call underlying wallet, with P-module transformations if needed
|
|
3897
|
+
let results: ListActionsResult
|
|
3898
|
+
|
|
3899
|
+
if (pModulesByScheme.size > 0) {
|
|
3900
|
+
// P-modules are involved - chain transformations
|
|
3901
|
+
const pModules = Array.from(pModulesByScheme.values())
|
|
3902
|
+
|
|
3903
|
+
// Chain onRequest calls through all modules in order
|
|
3904
|
+
let transformedArgs: object = requestArgs
|
|
3905
|
+
for (const module of pModules) {
|
|
3906
|
+
const transformed = await module.onRequest({
|
|
3907
|
+
method: 'listActions',
|
|
3908
|
+
args: transformedArgs,
|
|
3909
|
+
originator: originator!
|
|
3769
3910
|
})
|
|
3911
|
+
transformedArgs = transformed.args
|
|
3770
3912
|
}
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
if (results.actions[i].inputs![j].inputDescription) {
|
|
3782
|
-
results.actions[i].inputs![j].inputDescription = await this.maybeDecryptMetadata(
|
|
3783
|
-
results.actions[i].inputs![j].inputDescription
|
|
3784
|
-
)
|
|
3785
|
-
}
|
|
3786
|
-
}
|
|
3787
|
-
}
|
|
3788
|
-
if (results.actions[i].outputs) {
|
|
3789
|
-
for (let j = 0; j < results.actions[i].outputs!.length; j++) {
|
|
3790
|
-
if (results.actions[i].outputs![j].outputDescription) {
|
|
3791
|
-
results.actions[i].outputs![j].outputDescription = await this.maybeDecryptMetadata(
|
|
3792
|
-
results.actions[i].outputs![j].outputDescription
|
|
3793
|
-
)
|
|
3794
|
-
}
|
|
3795
|
-
if (results.actions[i].outputs![j].customInstructions) {
|
|
3796
|
-
results.actions[i].outputs![j].customInstructions = await this.maybeDecryptMetadata(
|
|
3797
|
-
results.actions[i].outputs![j].customInstructions!
|
|
3798
|
-
)
|
|
3799
|
-
}
|
|
3800
|
-
}
|
|
3801
|
-
}
|
|
3913
|
+
|
|
3914
|
+
// Call underlying wallet with transformed args
|
|
3915
|
+
results = await this.underlying.listActions(transformedArgs as ListActionsArgs, originator!)
|
|
3916
|
+
|
|
3917
|
+
// Chain onResponse calls in reverse order
|
|
3918
|
+
for (let i = pModules.length - 1; i >= 0; i--) {
|
|
3919
|
+
results = await pModules[i].onResponse(results, {
|
|
3920
|
+
method: 'listActions',
|
|
3921
|
+
originator: originator!
|
|
3922
|
+
})
|
|
3802
3923
|
}
|
|
3924
|
+
} else {
|
|
3925
|
+
// No P-modules - call underlying wallet directly
|
|
3926
|
+
results = await this.underlying.listActions(...args)
|
|
3803
3927
|
}
|
|
3804
|
-
|
|
3928
|
+
|
|
3929
|
+
// 4) Transparently decrypt transaction metadata, if configured to do so.
|
|
3930
|
+
return await this.decryptListActionsMetadata(results)
|
|
3805
3931
|
}
|
|
3806
3932
|
|
|
3807
3933
|
public async internalizeAction(
|
|
3808
3934
|
...args: Parameters<WalletInterface['internalizeAction']>
|
|
3809
3935
|
): ReturnType<WalletInterface['internalizeAction']> {
|
|
3810
3936
|
const [requestArgs, originator] = args
|
|
3811
|
-
|
|
3937
|
+
|
|
3938
|
+
// 1) Identify unique P-modules involved (one per schemeID) from both baskets and labels
|
|
3939
|
+
const pModulesByScheme = new Map<string, PermissionsModule>()
|
|
3940
|
+
const nonPBaskets: Array<{ outIndex: string; basket: string; customInstructions?: string }> = []
|
|
3941
|
+
|
|
3942
|
+
// Check baskets for p modules
|
|
3812
3943
|
for (const outIndex in requestArgs.outputs) {
|
|
3813
3944
|
const out = requestArgs.outputs[outIndex]
|
|
3814
3945
|
if (out.protocol === 'basket insertion') {
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
'
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
return await this.underlying.internalizeAction(transformedArgs as InternalizeActionArgs, originator!)
|
|
3827
|
-
}
|
|
3828
|
-
)
|
|
3829
|
-
if (pModuleResult !== null) {
|
|
3830
|
-
return pModuleResult
|
|
3946
|
+
const basket = out.insertionRemittance!.basket
|
|
3947
|
+
if (basket.startsWith('p ')) {
|
|
3948
|
+
const schemeID = basket.split(' ')[1]
|
|
3949
|
+
this.addPModuleByScheme(schemeID, 'basket', pModulesByScheme)
|
|
3950
|
+
} else {
|
|
3951
|
+
// Track non-P baskets for normal permission checks
|
|
3952
|
+
nonPBaskets.push({
|
|
3953
|
+
outIndex,
|
|
3954
|
+
basket,
|
|
3955
|
+
customInstructions: out.insertionRemittance!.customInstructions
|
|
3956
|
+
})
|
|
3831
3957
|
}
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3832
3960
|
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3961
|
+
// Check labels for p modules
|
|
3962
|
+
const nonPLabels = this.splitLabelsByPermissionModule(requestArgs.labels, pModulesByScheme)
|
|
3963
|
+
|
|
3964
|
+
// 2) Check permissions for non-P baskets
|
|
3965
|
+
for (const { outIndex, basket, customInstructions } of nonPBaskets) {
|
|
3966
|
+
await this.ensureBasketAccess({
|
|
3967
|
+
originator: originator!,
|
|
3968
|
+
basket,
|
|
3969
|
+
reason: requestArgs.description,
|
|
3970
|
+
usageType: 'insertion'
|
|
3971
|
+
})
|
|
3972
|
+
if (customInstructions) {
|
|
3973
|
+
requestArgs.outputs[outIndex].insertionRemittance!.customInstructions =
|
|
3974
|
+
await this.maybeEncryptMetadata(customInstructions)
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3977
|
+
|
|
3978
|
+
// 3) Check permissions for non-P labels
|
|
3979
|
+
for (const lbl of nonPLabels) {
|
|
3980
|
+
await this.ensureLabelAccess({
|
|
3981
|
+
originator: originator!,
|
|
3982
|
+
label: lbl,
|
|
3983
|
+
reason: requestArgs.description,
|
|
3984
|
+
usageType: 'apply'
|
|
3985
|
+
})
|
|
3986
|
+
}
|
|
3987
|
+
|
|
3988
|
+
// 4) Call underlying wallet, with P-module transformations if needed
|
|
3989
|
+
if (pModulesByScheme.size > 0) {
|
|
3990
|
+
// P-modules are involved - chain transformations
|
|
3991
|
+
const pModules = Array.from(pModulesByScheme.values())
|
|
3992
|
+
|
|
3993
|
+
// Chain onRequest calls through all modules in order
|
|
3994
|
+
let transformedArgs: object = requestArgs
|
|
3995
|
+
for (const module of pModules) {
|
|
3996
|
+
const transformed = await module.onRequest({
|
|
3997
|
+
method: 'internalizeAction',
|
|
3998
|
+
args: transformedArgs,
|
|
3999
|
+
originator: originator!
|
|
3838
4000
|
})
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
4001
|
+
transformedArgs = transformed.args
|
|
4002
|
+
}
|
|
4003
|
+
|
|
4004
|
+
// Encrypt custom instructions for p basket outputs
|
|
4005
|
+
for (const outIndex in (transformedArgs as InternalizeActionArgs).outputs) {
|
|
4006
|
+
const out = (transformedArgs as InternalizeActionArgs).outputs[outIndex]
|
|
4007
|
+
if (out.protocol === 'basket insertion' && out.insertionRemittance?.customInstructions) {
|
|
4008
|
+
out.insertionRemittance.customInstructions = await this.maybeEncryptMetadata(
|
|
4009
|
+
out.insertionRemittance.customInstructions
|
|
3842
4010
|
)
|
|
3843
4011
|
}
|
|
3844
4012
|
}
|
|
4013
|
+
|
|
4014
|
+
// Call underlying wallet with transformed args
|
|
4015
|
+
let results = await this.underlying.internalizeAction(transformedArgs as InternalizeActionArgs, originator!)
|
|
4016
|
+
|
|
4017
|
+
// Chain onResponse calls in reverse order
|
|
4018
|
+
for (let i = pModules.length - 1; i >= 0; i--) {
|
|
4019
|
+
results = await pModules[i].onResponse(results, {
|
|
4020
|
+
method: 'internalizeAction',
|
|
4021
|
+
originator: originator!
|
|
4022
|
+
})
|
|
4023
|
+
}
|
|
4024
|
+
|
|
4025
|
+
return results
|
|
3845
4026
|
}
|
|
4027
|
+
|
|
4028
|
+
// No P-modules - call underlying wallet directly
|
|
3846
4029
|
return this.underlying.internalizeAction(...args)
|
|
3847
4030
|
}
|
|
3848
4031
|
|
|
@@ -4359,6 +4542,7 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
4359
4542
|
* Checks if the given label is admin-reserved per BRC-100 rules:
|
|
4360
4543
|
*
|
|
4361
4544
|
* - Must not start with `admin` (admin-reserved)
|
|
4545
|
+
* - Must not start with `p ` (permissioned labels requiring a permission module)
|
|
4362
4546
|
*
|
|
4363
4547
|
* If it violates these rules and the caller is not admin, we consider it "admin-only."
|
|
4364
4548
|
*/
|
|
@@ -4366,6 +4550,9 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
4366
4550
|
if (label.startsWith('admin')) {
|
|
4367
4551
|
return true
|
|
4368
4552
|
}
|
|
4553
|
+
if (label.startsWith('p ')) {
|
|
4554
|
+
return true
|
|
4555
|
+
}
|
|
4369
4556
|
return false
|
|
4370
4557
|
}
|
|
4371
4558
|
|