@cmdoss/memwal-sdk 0.7.0 → 0.9.0

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 (229) hide show
  1. package/README.md +427 -41
  2. package/dist/client/ClientMemoryManager.js +2 -2
  3. package/dist/client/ClientMemoryManager.js.map +1 -1
  4. package/dist/client/PersonalDataWallet.d.ts.map +1 -1
  5. package/dist/client/SimplePDWClient.d.ts +88 -1
  6. package/dist/client/SimplePDWClient.d.ts.map +1 -1
  7. package/dist/client/SimplePDWClient.js +102 -11
  8. package/dist/client/SimplePDWClient.js.map +1 -1
  9. package/dist/client/namespaces/IndexNamespace.d.ts +1 -1
  10. package/dist/client/namespaces/IndexNamespace.d.ts.map +1 -1
  11. package/dist/client/namespaces/IndexNamespace.js +7 -4
  12. package/dist/client/namespaces/IndexNamespace.js.map +1 -1
  13. package/dist/client/namespaces/MemoryNamespace.d.ts +45 -0
  14. package/dist/client/namespaces/MemoryNamespace.d.ts.map +1 -1
  15. package/dist/client/namespaces/MemoryNamespace.js +292 -46
  16. package/dist/client/namespaces/MemoryNamespace.js.map +1 -1
  17. package/dist/client/namespaces/consolidated/AdvancedNamespace.d.ts +215 -0
  18. package/dist/client/namespaces/consolidated/AdvancedNamespace.d.ts.map +1 -0
  19. package/dist/client/namespaces/consolidated/AdvancedNamespace.js +214 -0
  20. package/dist/client/namespaces/consolidated/AdvancedNamespace.js.map +1 -0
  21. package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts +12 -2
  22. package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts.map +1 -1
  23. package/dist/client/namespaces/consolidated/BlockchainNamespace.js +40 -2
  24. package/dist/client/namespaces/consolidated/BlockchainNamespace.js.map +1 -1
  25. package/dist/client/namespaces/consolidated/StorageNamespace.d.ts +67 -2
  26. package/dist/client/namespaces/consolidated/StorageNamespace.d.ts.map +1 -1
  27. package/dist/client/namespaces/consolidated/StorageNamespace.js +549 -16
  28. package/dist/client/namespaces/consolidated/StorageNamespace.js.map +1 -1
  29. package/dist/client/namespaces/consolidated/index.d.ts +1 -0
  30. package/dist/client/namespaces/consolidated/index.d.ts.map +1 -1
  31. package/dist/client/namespaces/consolidated/index.js +1 -0
  32. package/dist/client/namespaces/consolidated/index.js.map +1 -1
  33. package/dist/config/ConfigurationHelper.js +61 -61
  34. package/dist/config/defaults.d.ts.map +1 -1
  35. package/dist/config/defaults.js +11 -6
  36. package/dist/config/defaults.js.map +1 -1
  37. package/dist/core/types/index.d.ts +4 -0
  38. package/dist/core/types/index.d.ts.map +1 -1
  39. package/dist/core/types/index.js.map +1 -1
  40. package/dist/graph/GraphService.js +20 -20
  41. package/dist/infrastructure/seal/EncryptionService.d.ts +9 -5
  42. package/dist/infrastructure/seal/EncryptionService.d.ts.map +1 -1
  43. package/dist/infrastructure/seal/EncryptionService.js +37 -15
  44. package/dist/infrastructure/seal/EncryptionService.js.map +1 -1
  45. package/dist/infrastructure/seal/SealService.d.ts +13 -5
  46. package/dist/infrastructure/seal/SealService.d.ts.map +1 -1
  47. package/dist/infrastructure/seal/SealService.js +36 -34
  48. package/dist/infrastructure/seal/SealService.js.map +1 -1
  49. package/dist/infrastructure/walrus/WalrusStorageService.d.ts +6 -0
  50. package/dist/infrastructure/walrus/WalrusStorageService.d.ts.map +1 -1
  51. package/dist/infrastructure/walrus/WalrusStorageService.js +23 -4
  52. package/dist/infrastructure/walrus/WalrusStorageService.js.map +1 -1
  53. package/dist/langchain/createPDWRAG.js +30 -30
  54. package/dist/retrieval/MemoryDecryptionPipeline.d.ts.map +1 -1
  55. package/dist/retrieval/MemoryDecryptionPipeline.js +2 -1
  56. package/dist/retrieval/MemoryDecryptionPipeline.js.map +1 -1
  57. package/dist/services/CapabilityService.d.ts.map +1 -1
  58. package/dist/services/CapabilityService.js +30 -14
  59. package/dist/services/CapabilityService.js.map +1 -1
  60. package/dist/services/CrossContextPermissionService.d.ts.map +1 -1
  61. package/dist/services/CrossContextPermissionService.js +9 -7
  62. package/dist/services/CrossContextPermissionService.js.map +1 -1
  63. package/dist/services/EmbeddingService.d.ts +9 -0
  64. package/dist/services/EmbeddingService.d.ts.map +1 -1
  65. package/dist/services/EmbeddingService.js +31 -10
  66. package/dist/services/EmbeddingService.js.map +1 -1
  67. package/dist/services/EncryptionService.d.ts.map +1 -1
  68. package/dist/services/EncryptionService.js +6 -5
  69. package/dist/services/EncryptionService.js.map +1 -1
  70. package/dist/services/GeminiAIService.js +309 -309
  71. package/dist/services/MemoryIndexService.d.ts +2 -0
  72. package/dist/services/MemoryIndexService.d.ts.map +1 -1
  73. package/dist/services/MemoryIndexService.js +11 -4
  74. package/dist/services/MemoryIndexService.js.map +1 -1
  75. package/dist/services/StorageService.d.ts +1 -0
  76. package/dist/services/StorageService.d.ts.map +1 -1
  77. package/dist/services/StorageService.js +60 -10
  78. package/dist/services/StorageService.js.map +1 -1
  79. package/dist/services/TransactionService.d.ts +20 -0
  80. package/dist/services/TransactionService.d.ts.map +1 -1
  81. package/dist/services/TransactionService.js +43 -0
  82. package/dist/services/TransactionService.js.map +1 -1
  83. package/dist/services/VectorService.js +1 -1
  84. package/dist/services/VectorService.js.map +1 -1
  85. package/dist/services/ViewService.js +2 -2
  86. package/dist/services/ViewService.js.map +1 -1
  87. package/dist/vector/BrowserHnswIndexService.js +1 -1
  88. package/dist/vector/BrowserHnswIndexService.js.map +1 -1
  89. package/dist/vector/HnswWasmService.js +1 -1
  90. package/dist/vector/HnswWasmService.js.map +1 -1
  91. package/dist/vector/NodeHnswService.js +1 -1
  92. package/dist/vector/NodeHnswService.js.map +1 -1
  93. package/package.json +1 -1
  94. package/src/access/PermissionService.ts +635 -635
  95. package/src/access/index.ts +8 -8
  96. package/src/aggregation/AggregationService.ts +389 -389
  97. package/src/aggregation/index.ts +8 -8
  98. package/src/ai-sdk/PDWVectorStore.ts +715 -715
  99. package/src/ai-sdk/index.ts +65 -65
  100. package/src/ai-sdk/tools.ts +460 -460
  101. package/src/ai-sdk/types.ts +404 -404
  102. package/src/batch/BatchManager.ts +597 -597
  103. package/src/batch/BatchingService.ts +429 -429
  104. package/src/batch/MemoryProcessingCache.ts +492 -492
  105. package/src/batch/index.ts +30 -30
  106. package/src/browser.ts +200 -200
  107. package/src/client/ClientMemoryManager.ts +987 -987
  108. package/src/client/PersonalDataWallet.ts +345 -345
  109. package/src/client/SimplePDWClient.ts +1369 -1237
  110. package/src/client/factory.ts +154 -154
  111. package/src/client/namespaces/AnalyticsNamespace.ts +377 -377
  112. package/src/client/namespaces/BatchNamespace.ts +356 -356
  113. package/src/client/namespaces/CacheNamespace.ts +123 -123
  114. package/src/client/namespaces/CapabilityNamespace.ts +217 -217
  115. package/src/client/namespaces/ClassifyNamespace.ts +169 -169
  116. package/src/client/namespaces/ContextNamespace.ts +297 -297
  117. package/src/client/namespaces/EmbeddingsNamespace.ts +99 -99
  118. package/src/client/namespaces/EncryptionNamespace.ts +221 -221
  119. package/src/client/namespaces/GraphNamespace.ts +468 -468
  120. package/src/client/namespaces/IndexNamespace.ts +364 -361
  121. package/src/client/namespaces/MemoryNamespace.ts +1569 -1272
  122. package/src/client/namespaces/PermissionsNamespace.ts +254 -254
  123. package/src/client/namespaces/PipelineNamespace.ts +220 -220
  124. package/src/client/namespaces/SearchNamespace.ts +1049 -1049
  125. package/src/client/namespaces/StorageNamespace.ts +458 -458
  126. package/src/client/namespaces/TxNamespace.ts +260 -260
  127. package/src/client/namespaces/WalletNamespace.ts +243 -243
  128. package/src/client/namespaces/consolidated/AINamespace.ts +449 -449
  129. package/src/client/namespaces/consolidated/AdvancedNamespace.ts +264 -0
  130. package/src/client/namespaces/consolidated/BlockchainNamespace.ts +607 -564
  131. package/src/client/namespaces/consolidated/SecurityNamespace.ts +648 -648
  132. package/src/client/namespaces/consolidated/StorageNamespace.ts +1141 -497
  133. package/src/client/namespaces/consolidated/index.ts +41 -39
  134. package/src/client/signers/DappKitSigner.ts +207 -207
  135. package/src/client/signers/KeypairSigner.ts +108 -108
  136. package/src/client/signers/UnifiedSigner.ts +110 -110
  137. package/src/client/signers/WalletAdapterSigner.ts +159 -159
  138. package/src/client/signers/index.ts +26 -26
  139. package/src/config/ConfigurationHelper.ts +412 -412
  140. package/src/config/defaults.ts +56 -51
  141. package/src/config/index.ts +8 -8
  142. package/src/config/validation.ts +70 -70
  143. package/src/core/index.ts +14 -14
  144. package/src/core/interfaces/IService.ts +307 -307
  145. package/src/core/interfaces/index.ts +8 -8
  146. package/src/core/types/capability.ts +297 -297
  147. package/src/core/types/index.ts +874 -870
  148. package/src/core/types/wallet.ts +270 -270
  149. package/src/core/types.ts +9 -9
  150. package/src/core/wallet.ts +222 -222
  151. package/src/embedding/index.ts +19 -19
  152. package/src/embedding/types.ts +357 -357
  153. package/src/errors/index.ts +602 -602
  154. package/src/errors/recovery.ts +461 -461
  155. package/src/errors/validation.ts +567 -567
  156. package/src/generated/pdw/capability.ts +319 -319
  157. package/src/generated/pdw/deps/sui/object.ts +12 -12
  158. package/src/generated/pdw/deps/sui/vec_map.ts +32 -32
  159. package/src/generated/pdw/memory.ts +1087 -1087
  160. package/src/generated/pdw/wallet.ts +123 -123
  161. package/src/generated/utils/index.ts +159 -159
  162. package/src/graph/GraphService.ts +887 -887
  163. package/src/graph/KnowledgeGraphManager.ts +728 -728
  164. package/src/graph/index.ts +25 -25
  165. package/src/index.ts +498 -498
  166. package/src/infrastructure/index.ts +22 -22
  167. package/src/infrastructure/seal/EncryptionService.ts +628 -603
  168. package/src/infrastructure/seal/SealService.ts +613 -615
  169. package/src/infrastructure/seal/index.ts +9 -9
  170. package/src/infrastructure/sui/BlockchainManager.ts +627 -627
  171. package/src/infrastructure/sui/SuiService.ts +888 -888
  172. package/src/infrastructure/sui/index.ts +9 -9
  173. package/src/infrastructure/walrus/StorageManager.ts +604 -604
  174. package/src/infrastructure/walrus/WalrusStorageService.ts +637 -612
  175. package/src/infrastructure/walrus/index.ts +9 -9
  176. package/src/langchain/PDWEmbeddings.ts +145 -145
  177. package/src/langchain/PDWVectorStore.ts +456 -456
  178. package/src/langchain/createPDWRAG.ts +303 -303
  179. package/src/langchain/index.ts +47 -47
  180. package/src/permissions/ConsentRepository.browser.ts +249 -249
  181. package/src/permissions/ConsentRepository.ts +364 -364
  182. package/src/permissions/index.ts +9 -9
  183. package/src/pipeline/MemoryPipeline.ts +862 -862
  184. package/src/pipeline/PipelineManager.ts +683 -683
  185. package/src/pipeline/index.ts +26 -26
  186. package/src/retrieval/AdvancedSearchService.ts +629 -629
  187. package/src/retrieval/MemoryAnalyticsService.ts +711 -711
  188. package/src/retrieval/MemoryDecryptionPipeline.ts +825 -824
  189. package/src/retrieval/MemoryRetrievalService.ts +904 -904
  190. package/src/retrieval/index.ts +42 -42
  191. package/src/services/BatchService.ts +352 -352
  192. package/src/services/CapabilityService.ts +464 -448
  193. package/src/services/ClassifierService.ts +465 -465
  194. package/src/services/CrossContextPermissionService.ts +486 -484
  195. package/src/services/EmbeddingService.ts +796 -771
  196. package/src/services/EncryptionService.ts +712 -711
  197. package/src/services/GeminiAIService.ts +753 -753
  198. package/src/services/IndexManager.ts +977 -977
  199. package/src/services/MemoryIndexService.ts +1009 -1003
  200. package/src/services/MemoryService.ts +369 -369
  201. package/src/services/QueryService.ts +890 -890
  202. package/src/services/StorageService.ts +1182 -1126
  203. package/src/services/TransactionService.ts +838 -790
  204. package/src/services/VectorService.ts +462 -462
  205. package/src/services/ViewService.ts +484 -484
  206. package/src/services/index.ts +25 -25
  207. package/src/services/storage/BlobAttributesManager.ts +333 -333
  208. package/src/services/storage/KnowledgeGraphManager.ts +425 -425
  209. package/src/services/storage/MemorySearchManager.ts +387 -387
  210. package/src/services/storage/QuiltBatchManager.ts +1130 -1130
  211. package/src/services/storage/WalrusMetadataManager.ts +268 -268
  212. package/src/services/storage/WalrusStorageManager.ts +287 -287
  213. package/src/services/storage/index.ts +57 -57
  214. package/src/types/index.ts +13 -13
  215. package/src/utils/LRUCache.ts +378 -378
  216. package/src/utils/index.ts +76 -76
  217. package/src/utils/memoryIndexOnChain.ts +507 -507
  218. package/src/utils/rebuildIndex.ts +290 -290
  219. package/src/utils/rebuildIndexNode.ts +771 -771
  220. package/src/vector/BrowserHnswIndexService.ts +758 -758
  221. package/src/vector/HnswWasmService.ts +731 -731
  222. package/src/vector/IHnswService.ts +233 -233
  223. package/src/vector/NodeHnswService.ts +833 -833
  224. package/src/vector/VectorManager.ts +478 -478
  225. package/src/vector/createHnswService.ts +135 -135
  226. package/src/vector/index.ts +56 -56
  227. package/src/wallet/ContextWalletService.ts +656 -656
  228. package/src/wallet/MainWalletService.ts +317 -317
  229. package/src/wallet/index.ts +17 -17
@@ -1,636 +1,636 @@
1
- /**
2
- * PermissionService - OAuth-style access control management
3
- *
4
- * Manages permissions for cross-app data access, including:
5
- * - OAuth-style consent requests and grants
6
- * - Permission validation and enforcement
7
- * - On-chain access control integration
8
- * - Permission auditing and revocation
9
- */
10
-
11
- // Use Web Crypto API (browser-compatible)
12
- const randomUUID = (): string => crypto.randomUUID();
13
- import { SuiClient } from '@mysten/sui/client';
14
- import { Transaction } from '@mysten/sui/transactions';
15
- import { normalizeSuiAddress } from '@mysten/sui/utils';
16
- import type { Signer } from '@mysten/sui/cryptography';
17
-
18
- import {
19
- ConsentRequest,
20
- ConsentRequestRecord,
21
- ConsentStatus,
22
- AccessGrant,
23
- PermissionScope,
24
- RequestConsentOptions,
25
- GrantPermissionsOptions,
26
- RevokePermissionsOptions
27
- } from '../core/types/wallet.js';
28
- import { ContextWalletService } from '../wallet/ContextWalletService.js';
29
- import { CapabilityService } from '../services/CapabilityService.js';
30
- import { CrossContextPermissionService } from '../services/CrossContextPermissionService';
31
- import type { WalletAllowlistPermission } from '../services/CrossContextPermissionService';
32
- import type { ConsentRepository } from '../permissions/ConsentRepository.js';
33
-
34
- /**
35
- * Configuration for PermissionService
36
- */
37
- export interface PermissionServiceConfig {
38
- /** Sui client instance */
39
- suiClient: SuiClient;
40
- /** Package ID for Move contracts */
41
- packageId: string;
42
- /** Access registry ID for wallet allowlists */
43
- accessRegistryId: string;
44
- /**
45
- * @deprecated Use capabilityService instead for capability-based architecture
46
- * ContextWalletService for validation (legacy)
47
- */
48
- contextWalletService?: ContextWalletService;
49
- /** CapabilityService for capability-based validation (preferred) */
50
- capabilityService?: CapabilityService;
51
- /** Optional injected cross-context permission service */
52
- crossContextPermissionService?: CrossContextPermissionService;
53
- /** Optional repository for consent persistence */
54
- consentRepository?: ConsentRepository;
55
- }
56
-
57
- /**
58
- * PermissionService handles OAuth-style access control
59
- */
60
- export class PermissionService {
61
- private suiClient: SuiClient;
62
- private packageId: string;
63
- /** @deprecated Use capabilityService instead */
64
- private contextWalletService?: ContextWalletService;
65
- private capabilityService?: CapabilityService;
66
- private crossContextPermissions: CrossContextPermissionService;
67
- private pendingConsents: Map<string, ConsentRequestRecord> = new Map();
68
- private consentRepository?: ConsentRepository;
69
-
70
- constructor(config: PermissionServiceConfig) {
71
- this.suiClient = config.suiClient;
72
- this.packageId = config.packageId;
73
- this.contextWalletService = config.contextWalletService;
74
- this.capabilityService = config.capabilityService;
75
- this.consentRepository = config.consentRepository;
76
-
77
- if (!config.accessRegistryId) {
78
- throw new Error('PermissionService requires accessRegistryId for wallet-based permissions');
79
- }
80
-
81
- this.crossContextPermissions =
82
- config.crossContextPermissionService ??
83
- new CrossContextPermissionService(
84
- {
85
- packageId: this.packageId,
86
- accessRegistryId: config.accessRegistryId,
87
- },
88
- this.suiClient,
89
- );
90
- }
91
-
92
- /**
93
- * Request user consent for accessing data
94
- * @param options - Consent request options
95
- * @returns Created consent request
96
- */
97
- async requestConsent(options: RequestConsentOptions): Promise<ConsentRequestRecord> {
98
- const requesterWallet = normalizeSuiAddress(options.requesterWallet);
99
- const targetWallet = normalizeSuiAddress(options.targetWallet);
100
- const now = Date.now();
101
-
102
- const requestId = randomUUID();
103
- const consentRequest: ConsentRequestRecord = {
104
- requesterWallet,
105
- targetWallet,
106
- targetScopes: options.scopes,
107
- purpose: options.purpose,
108
- expiresAt: options.expiresIn ? now + options.expiresIn : undefined,
109
- requestId,
110
- createdAt: now,
111
- updatedAt: now,
112
- status: 'pending',
113
- };
114
-
115
- await this.persistConsentRequest(consentRequest);
116
-
117
- // TODO: Send consent request to backend for UI presentation
118
- // For now, return the persisted record so callers can track status locally
119
- return consentRequest;
120
- }
121
-
122
- /**
123
- * Grant permissions to an app (user approval)
124
- * @param userAddress - User granting permissions
125
- * @param options - Grant options
126
- * @returns Created access grant
127
- */
128
- async grantPermissions(
129
- userAddress: string,
130
- options: GrantPermissionsOptions & { signer?: Signer; appId?: string }
131
- ): Promise<AccessGrant> {
132
- const requestingWallet = normalizeSuiAddress(options.requestingWallet);
133
- const targetWallet = normalizeSuiAddress(options.targetWallet);
134
-
135
- // Validate ownership - prefer capability-based validation
136
- const hasAccess = await this.validateOwnership(userAddress, targetWallet, options.appId);
137
- if (!hasAccess) {
138
- throw new Error('User does not own the specified target wallet or capability');
139
- }
140
-
141
- let lastDigest: string | undefined;
142
- if (options.signer) {
143
- for (const scope of options.scopes) {
144
- const accessLevel = scope.startsWith('write:') ? 'write' : 'read';
145
- lastDigest = await this.crossContextPermissions.grantWalletAllowlistAccess(
146
- {
147
- requestingWallet,
148
- targetWallet,
149
- scope,
150
- accessLevel,
151
- expiresAt: options.expiresAt ?? 0,
152
- },
153
- options.signer,
154
- );
155
- }
156
- }
157
-
158
- const permissions = await this.crossContextPermissions.queryWalletPermissions({
159
- requestingWallet,
160
- targetWallet,
161
- });
162
-
163
- const grantFromChain = this.buildGrantFromPermissions(
164
- requestingWallet,
165
- targetWallet,
166
- permissions,
167
- );
168
-
169
- const now = Date.now();
170
- let grant: AccessGrant =
171
- grantFromChain ?? {
172
- id: `grant_${requestingWallet}_${targetWallet}_${now}`,
173
- requestingWallet,
174
- targetWallet,
175
- scopes: options.scopes,
176
- expiresAt: options.expiresAt,
177
- grantedAt: now,
178
- };
179
-
180
- if (lastDigest) {
181
- grant = {
182
- ...grant,
183
- transactionDigest: lastDigest,
184
- };
185
- }
186
- await this.updateConsentStatus({
187
- requesterWallet: requestingWallet,
188
- targetWallet,
189
- newStatus: 'approved',
190
- updatedAt: now,
191
- });
192
-
193
- return grant;
194
- }
195
-
196
- /**
197
- * Revoke permissions from an app
198
- * @param userAddress - User revoking permissions
199
- * @param options - Revoke options
200
- * @returns Success status
201
- */
202
- async revokePermissions(
203
- userAddress: string,
204
- options: RevokePermissionsOptions & { signer?: Signer; appId?: string }
205
- ): Promise<boolean> {
206
- const requestingWallet = normalizeSuiAddress(options.requestingWallet);
207
- const targetWallet = normalizeSuiAddress(options.targetWallet);
208
-
209
- // Validate ownership - prefer capability-based validation
210
- const hasAccess = await this.validateOwnership(userAddress, targetWallet, options.appId);
211
- if (!hasAccess) {
212
- throw new Error('User does not own the specified target wallet or capability');
213
- }
214
-
215
- if (options.signer) {
216
- await this.crossContextPermissions.revokeWalletAllowlistAccess(
217
- {
218
- requestingWallet,
219
- targetWallet,
220
- scope: options.scope,
221
- },
222
- options.signer,
223
- );
224
- }
225
-
226
- return true;
227
- }
228
-
229
- /**
230
- * Determine if a requesting wallet currently has permission to access a target wallet
231
- */
232
- async hasWalletPermission(
233
- requestingWallet: string,
234
- targetWallet: string,
235
- scope: PermissionScope
236
- ): Promise<boolean> {
237
- return await this.crossContextPermissions.hasWalletPermission({
238
- requestingWallet: normalizeSuiAddress(requestingWallet),
239
- targetWallet: normalizeSuiAddress(targetWallet),
240
- scope,
241
- });
242
- }
243
-
244
- /**
245
- * Legacy compatibility method for app-scoped permission checks
246
- * Interprets appId as requesting wallet address.
247
- */
248
- async checkPermission(
249
- appId: string,
250
- scope: PermissionScope,
251
- userAddressOrTargetWallet: string
252
- ): Promise<boolean> {
253
- return await this.hasWalletPermission(appId, userAddressOrTargetWallet, scope);
254
- }
255
-
256
- /**
257
- * Get all access grants by a user
258
- * @param userAddress - User address
259
- * @returns Array of access grants
260
- */
261
- async getGrantsByUser(userAddress: string): Promise<AccessGrant[]> {
262
- const normalized = normalizeSuiAddress(userAddress);
263
- const permissions = await this.crossContextPermissions.listGrantsByTarget(normalized);
264
- return this.convertPermissionsToGrants(permissions);
265
- }
266
-
267
- /**
268
- * List all consent requests for a user
269
- * @param userAddress - User address
270
- * @returns Array of pending consent requests
271
- */
272
- async getPendingConsents(userAddress: string): Promise<ConsentRequest[]> {
273
- const normalized = normalizeSuiAddress(userAddress);
274
- if (this.consentRepository) {
275
- return await this.consentRepository.listByTarget(normalized, 'pending');
276
- }
277
- return Array.from(this.pendingConsents.values())
278
- .filter((request) => request.targetWallet === normalized && request.status === 'pending')
279
- .map((request) => ({ ...request }));
280
- }
281
-
282
- /**
283
- * Approve a consent request
284
- * @param userAddress - User approving the request
285
- * @param consentRequest - Consent request to approve
286
- * @param contextId - Context ID to grant access to
287
- * @returns Created access grant
288
- */
289
- async approveConsent(
290
- userAddress: string,
291
- consentRequest: ConsentRequest,
292
- _contextId: string
293
- ): Promise<AccessGrant> {
294
- const grant = await this.grantPermissions(userAddress, {
295
- requestingWallet: consentRequest.requesterWallet,
296
- targetWallet: consentRequest.targetWallet,
297
- scopes: consentRequest.targetScopes,
298
- expiresAt: consentRequest.expiresAt
299
- });
300
-
301
- const updatedAt = Date.now();
302
- if (consentRequest.requestId) {
303
- await this.updateConsentStatus({
304
- requesterWallet: consentRequest.requesterWallet,
305
- targetWallet: consentRequest.targetWallet,
306
- newStatus: 'approved',
307
- updatedAt,
308
- requestId: consentRequest.requestId,
309
- });
310
- }
311
-
312
- return grant;
313
- }
314
-
315
- /**
316
- * Deny a consent request
317
- * @param userAddress - User denying the request
318
- * @param consentRequest - Consent request to deny
319
- * @returns Success status
320
- */
321
- async denyConsent(userAddress: string, consentRequest: ConsentRequest): Promise<boolean> {
322
- const now = Date.now();
323
- if (consentRequest.requestId) {
324
- await this.updateConsentStatus({
325
- requesterWallet: consentRequest.requesterWallet,
326
- targetWallet: consentRequest.targetWallet,
327
- newStatus: 'denied',
328
- updatedAt: now,
329
- requestId: consentRequest.requestId,
330
- });
331
- } else {
332
- const inferredRequestId = randomUUID();
333
- await this.persistConsentRequest({
334
- ...consentRequest,
335
- requestId: inferredRequestId,
336
- createdAt: now,
337
- updatedAt: now,
338
- status: 'denied',
339
- });
340
- }
341
-
342
- return true;
343
- }
344
-
345
- /**
346
- * Get permission audit log for a user
347
- * @param userAddress - User address
348
- * @returns Array of permission events
349
- */
350
- async getPermissionAudit(userAddress: string): Promise<Array<{
351
- timestamp: number;
352
- action: 'grant' | 'revoke' | 'request' | 'deny';
353
- requestingWallet: string;
354
- targetWallet: string;
355
- scopes: PermissionScope[];
356
- }>> {
357
- const normalized = normalizeSuiAddress(userAddress);
358
- const auditEntries: Array<{
359
- timestamp: number;
360
- action: 'grant' | 'revoke' | 'request' | 'deny';
361
- requestingWallet: string;
362
- targetWallet: string;
363
- scopes: PermissionScope[];
364
- }> = [];
365
-
366
- const consentRecords = this.consentRepository
367
- ? await this.consentRepository.listByTarget(normalized)
368
- : Array.from(this.pendingConsents.values()).filter((request) => request.targetWallet === normalized);
369
-
370
- for (const request of consentRecords) {
371
- auditEntries.push({
372
- timestamp: request.createdAt,
373
- action: 'request',
374
- requestingWallet: request.requesterWallet,
375
- targetWallet: request.targetWallet,
376
- scopes: request.targetScopes,
377
- });
378
-
379
- if (request.status === 'denied') {
380
- auditEntries.push({
381
- timestamp: request.updatedAt,
382
- action: 'deny',
383
- requestingWallet: request.requesterWallet,
384
- targetWallet: request.targetWallet,
385
- scopes: request.targetScopes,
386
- });
387
- }
388
- }
389
-
390
- const history = await this.crossContextPermissions.getWalletAllowlistHistory({
391
- targetWallet: normalized,
392
- });
393
-
394
- for (const event of history) {
395
- auditEntries.push({
396
- timestamp: event.timestamp,
397
- action: event.action,
398
- requestingWallet: event.requestingWallet,
399
- targetWallet: event.targetWallet,
400
- scopes: [this.toPermissionScope(event.scope)],
401
- });
402
- }
403
-
404
- return auditEntries.sort((a, b) => a.timestamp - b.timestamp);
405
- }
406
-
407
- /**
408
- * Validate OAuth-style permission for SEAL access control
409
- * @param walletOwner - Owner of the wallet
410
- * @param appId - Application requesting access
411
- * @param requestedScope - Required permission scope
412
- * @returns True if permission is valid
413
- */
414
- async validateOAuthPermission(
415
- walletOwner: string,
416
- appId: string,
417
- requestedScope: string
418
- ): Promise<boolean> {
419
- // This integrates with our existing SEAL access control
420
- return await this.hasWalletPermission(appId, walletOwner, requestedScope as PermissionScope);
421
- }
422
-
423
- /**
424
- * Build seal_approve transaction for a requesting wallet
425
- */
426
- createApprovalTransaction(
427
- contentId: Uint8Array,
428
- requestingWallet: string
429
- ): Transaction {
430
- return this.crossContextPermissions.buildSealApproveTransaction(
431
- contentId,
432
- normalizeSuiAddress(requestingWallet),
433
- );
434
- }
435
-
436
- /**
437
- * Get permission statistics for a user
438
- * @param userAddress - User address
439
- * @returns Permission usage statistics
440
- */
441
- async getPermissionStats(userAddress: string): Promise<{
442
- totalGrants: number;
443
- activeGrants: number;
444
- totalApps: number;
445
- totalScopes: number;
446
- recentActivity: number;
447
- }> {
448
- const grants = await this.getGrantsByUser(userAddress);
449
- const now = Date.now();
450
-
451
- const activeGrants = grants.filter((g) => !g.expiresAt || g.expiresAt > now);
452
- const uniqueApps = new Set(grants.map((g) => g.requestingWallet));
453
- const uniqueScopes = new Set(grants.flatMap((g) => g.scopes));
454
-
455
- const recentActivity =
456
- grants.length > 0 ? Math.max(...grants.map((g) => g.expiresAt || 0)) : 0;
457
-
458
- return {
459
- totalGrants: grants.length,
460
- activeGrants: activeGrants.length,
461
- totalApps: uniqueApps.size,
462
- totalScopes: uniqueScopes.size,
463
- recentActivity
464
- };
465
- }
466
-
467
- /**
468
- * Clean up expired permissions
469
- * @param userAddress - User address
470
- * @returns Number of permissions cleaned up
471
- */
472
- async cleanupExpiredPermissions(userAddress: string): Promise<number> {
473
- const now = Date.now();
474
- const grants = await this.getGrantsByUser(userAddress);
475
-
476
- return grants.filter((grant) => {
477
- if (!grant.expiresAt || grant.expiresAt === 0) {
478
- return false;
479
- }
480
- return grant.expiresAt <= now;
481
- }).length;
482
- }
483
-
484
- private buildGrantFromPermissions(
485
- requestingWallet: string,
486
- targetWallet: string,
487
- permissions: WalletAllowlistPermission[],
488
- ): AccessGrant | undefined {
489
- const grants = this.convertPermissionsToGrants(permissions);
490
- return grants.find(
491
- (candidate) =>
492
- candidate.requestingWallet === requestingWallet &&
493
- candidate.targetWallet === targetWallet,
494
- );
495
- }
496
-
497
- private convertPermissionsToGrants(
498
- permissions: WalletAllowlistPermission[],
499
- ): AccessGrant[] {
500
- const grouped = new Map<string, WalletAllowlistPermission[]>();
501
-
502
- for (const permission of permissions) {
503
- const key = `${permission.requestingWallet}-${permission.targetWallet}`;
504
- const existing = grouped.get(key);
505
- if (existing) {
506
- existing.push(permission);
507
- } else {
508
- grouped.set(key, [permission]);
509
- }
510
- }
511
-
512
- const grants: AccessGrant[] = [];
513
- for (const entries of grouped.values()) {
514
- if (entries.length === 0) {
515
- continue;
516
- }
517
-
518
- const [first] = entries;
519
- const scopes = new Set<PermissionScope>();
520
- let earliestGrantedAt = entries[0].grantedAt;
521
- let expiresAtCandidate = 0;
522
-
523
- for (const entry of entries) {
524
- scopes.add(this.toPermissionScope(entry.scope));
525
- if (entry.grantedAt < earliestGrantedAt) {
526
- earliestGrantedAt = entry.grantedAt;
527
- }
528
- if (entry.expiresAt > expiresAtCandidate) {
529
- expiresAtCandidate = entry.expiresAt;
530
- }
531
- }
532
-
533
- const grant: AccessGrant = {
534
- id: `grant_${first.requestingWallet}_${first.targetWallet}_${earliestGrantedAt}`,
535
- requestingWallet: first.requestingWallet,
536
- targetWallet: first.targetWallet,
537
- scopes: Array.from(scopes),
538
- grantedAt: earliestGrantedAt,
539
- };
540
-
541
- if (expiresAtCandidate > 0) {
542
- grant.expiresAt = expiresAtCandidate;
543
- }
544
-
545
- grants.push(grant);
546
- }
547
-
548
- return grants;
549
- }
550
-
551
- private toPermissionScope(scope: string): PermissionScope {
552
- return scope as PermissionScope;
553
- }
554
-
555
- private async persistConsentRequest(record: ConsentRequestRecord): Promise<void> {
556
- if (this.consentRepository) {
557
- await this.consentRepository.save(record);
558
- } else {
559
- this.pendingConsents.set(record.requestId, { ...record });
560
- }
561
- }
562
-
563
- /**
564
- * Swap the consent persistence backend at runtime.
565
- * Useful for applications that want to wire a custom repository after
566
- * the service has been constructed (e.g., demos supplying a filesystem store).
567
- */
568
- setConsentRepository(repository?: ConsentRepository): void {
569
- this.consentRepository = repository;
570
- }
571
-
572
- /**
573
- * Validate user ownership of a target wallet or capability
574
- * Prefers capability-based validation over legacy ContextWalletService
575
- *
576
- * @param userAddress - User's address
577
- * @param targetWallet - Target wallet address
578
- * @param appId - Optional app ID for capability lookup
579
- * @returns True if user has ownership/access
580
- */
581
- private async validateOwnership(
582
- userAddress: string,
583
- targetWallet: string,
584
- appId?: string
585
- ): Promise<boolean> {
586
- // Prefer capability-based validation (new architecture)
587
- if (this.capabilityService && appId) {
588
- return await this.capabilityService.hasCapability(userAddress, appId);
589
- }
590
-
591
- // Fall back to legacy ContextWalletService validation
592
- if (this.contextWalletService) {
593
- return await this.contextWalletService.validateAccess(targetWallet, userAddress);
594
- }
595
-
596
- // If neither service is available, skip validation (trust caller)
597
- return true;
598
- }
599
-
600
- private async updateConsentStatus(params: {
601
- requesterWallet: string;
602
- targetWallet: string;
603
- newStatus: ConsentStatus;
604
- updatedAt: number;
605
- requestId?: string;
606
- }): Promise<void> {
607
- const normalizedRequester = normalizeSuiAddress(params.requesterWallet);
608
- const normalizedTarget = normalizeSuiAddress(params.targetWallet);
609
-
610
- if (this.consentRepository) {
611
- if (params.requestId) {
612
- await this.consentRepository.updateStatus(params.requestId, params.newStatus, params.updatedAt);
613
- } else {
614
- const pendingRecords = await this.consentRepository.listByTarget(normalizedTarget, 'pending');
615
- await Promise.all(
616
- pendingRecords
617
- .filter((record) => record.requesterWallet === normalizedRequester)
618
- .map((record) =>
619
- this.consentRepository!.updateStatus(record.requestId, params.newStatus, params.updatedAt),
620
- ),
621
- );
622
- }
623
- }
624
-
625
- for (const [requestId, record] of this.pendingConsents.entries()) {
626
- if (record.requesterWallet === normalizedRequester && record.targetWallet === normalizedTarget) {
627
- const updatedRecord: ConsentRequestRecord = {
628
- ...record,
629
- status: params.newStatus,
630
- updatedAt: params.updatedAt,
631
- };
632
- this.pendingConsents.set(requestId, updatedRecord);
633
- }
634
- }
635
- }
1
+ /**
2
+ * PermissionService - OAuth-style access control management
3
+ *
4
+ * Manages permissions for cross-app data access, including:
5
+ * - OAuth-style consent requests and grants
6
+ * - Permission validation and enforcement
7
+ * - On-chain access control integration
8
+ * - Permission auditing and revocation
9
+ */
10
+
11
+ // Use Web Crypto API (browser-compatible)
12
+ const randomUUID = (): string => crypto.randomUUID();
13
+ import { SuiClient } from '@mysten/sui/client';
14
+ import { Transaction } from '@mysten/sui/transactions';
15
+ import { normalizeSuiAddress } from '@mysten/sui/utils';
16
+ import type { Signer } from '@mysten/sui/cryptography';
17
+
18
+ import {
19
+ ConsentRequest,
20
+ ConsentRequestRecord,
21
+ ConsentStatus,
22
+ AccessGrant,
23
+ PermissionScope,
24
+ RequestConsentOptions,
25
+ GrantPermissionsOptions,
26
+ RevokePermissionsOptions
27
+ } from '../core/types/wallet.js';
28
+ import { ContextWalletService } from '../wallet/ContextWalletService.js';
29
+ import { CapabilityService } from '../services/CapabilityService.js';
30
+ import { CrossContextPermissionService } from '../services/CrossContextPermissionService';
31
+ import type { WalletAllowlistPermission } from '../services/CrossContextPermissionService';
32
+ import type { ConsentRepository } from '../permissions/ConsentRepository.js';
33
+
34
+ /**
35
+ * Configuration for PermissionService
36
+ */
37
+ export interface PermissionServiceConfig {
38
+ /** Sui client instance */
39
+ suiClient: SuiClient;
40
+ /** Package ID for Move contracts */
41
+ packageId: string;
42
+ /** Access registry ID for wallet allowlists */
43
+ accessRegistryId: string;
44
+ /**
45
+ * @deprecated Use capabilityService instead for capability-based architecture
46
+ * ContextWalletService for validation (legacy)
47
+ */
48
+ contextWalletService?: ContextWalletService;
49
+ /** CapabilityService for capability-based validation (preferred) */
50
+ capabilityService?: CapabilityService;
51
+ /** Optional injected cross-context permission service */
52
+ crossContextPermissionService?: CrossContextPermissionService;
53
+ /** Optional repository for consent persistence */
54
+ consentRepository?: ConsentRepository;
55
+ }
56
+
57
+ /**
58
+ * PermissionService handles OAuth-style access control
59
+ */
60
+ export class PermissionService {
61
+ private suiClient: SuiClient;
62
+ private packageId: string;
63
+ /** @deprecated Use capabilityService instead */
64
+ private contextWalletService?: ContextWalletService;
65
+ private capabilityService?: CapabilityService;
66
+ private crossContextPermissions: CrossContextPermissionService;
67
+ private pendingConsents: Map<string, ConsentRequestRecord> = new Map();
68
+ private consentRepository?: ConsentRepository;
69
+
70
+ constructor(config: PermissionServiceConfig) {
71
+ this.suiClient = config.suiClient;
72
+ this.packageId = config.packageId;
73
+ this.contextWalletService = config.contextWalletService;
74
+ this.capabilityService = config.capabilityService;
75
+ this.consentRepository = config.consentRepository;
76
+
77
+ if (!config.accessRegistryId) {
78
+ throw new Error('PermissionService requires accessRegistryId for wallet-based permissions');
79
+ }
80
+
81
+ this.crossContextPermissions =
82
+ config.crossContextPermissionService ??
83
+ new CrossContextPermissionService(
84
+ {
85
+ packageId: this.packageId,
86
+ accessRegistryId: config.accessRegistryId,
87
+ },
88
+ this.suiClient,
89
+ );
90
+ }
91
+
92
+ /**
93
+ * Request user consent for accessing data
94
+ * @param options - Consent request options
95
+ * @returns Created consent request
96
+ */
97
+ async requestConsent(options: RequestConsentOptions): Promise<ConsentRequestRecord> {
98
+ const requesterWallet = normalizeSuiAddress(options.requesterWallet);
99
+ const targetWallet = normalizeSuiAddress(options.targetWallet);
100
+ const now = Date.now();
101
+
102
+ const requestId = randomUUID();
103
+ const consentRequest: ConsentRequestRecord = {
104
+ requesterWallet,
105
+ targetWallet,
106
+ targetScopes: options.scopes,
107
+ purpose: options.purpose,
108
+ expiresAt: options.expiresIn ? now + options.expiresIn : undefined,
109
+ requestId,
110
+ createdAt: now,
111
+ updatedAt: now,
112
+ status: 'pending',
113
+ };
114
+
115
+ await this.persistConsentRequest(consentRequest);
116
+
117
+ // TODO: Send consent request to backend for UI presentation
118
+ // For now, return the persisted record so callers can track status locally
119
+ return consentRequest;
120
+ }
121
+
122
+ /**
123
+ * Grant permissions to an app (user approval)
124
+ * @param userAddress - User granting permissions
125
+ * @param options - Grant options
126
+ * @returns Created access grant
127
+ */
128
+ async grantPermissions(
129
+ userAddress: string,
130
+ options: GrantPermissionsOptions & { signer?: Signer; appId?: string }
131
+ ): Promise<AccessGrant> {
132
+ const requestingWallet = normalizeSuiAddress(options.requestingWallet);
133
+ const targetWallet = normalizeSuiAddress(options.targetWallet);
134
+
135
+ // Validate ownership - prefer capability-based validation
136
+ const hasAccess = await this.validateOwnership(userAddress, targetWallet, options.appId);
137
+ if (!hasAccess) {
138
+ throw new Error('User does not own the specified target wallet or capability');
139
+ }
140
+
141
+ let lastDigest: string | undefined;
142
+ if (options.signer) {
143
+ for (const scope of options.scopes) {
144
+ const accessLevel = scope.startsWith('write:') ? 'write' : 'read';
145
+ lastDigest = await this.crossContextPermissions.grantWalletAllowlistAccess(
146
+ {
147
+ requestingWallet,
148
+ targetWallet,
149
+ scope,
150
+ accessLevel,
151
+ expiresAt: options.expiresAt ?? 0,
152
+ },
153
+ options.signer,
154
+ );
155
+ }
156
+ }
157
+
158
+ const permissions = await this.crossContextPermissions.queryWalletPermissions({
159
+ requestingWallet,
160
+ targetWallet,
161
+ });
162
+
163
+ const grantFromChain = this.buildGrantFromPermissions(
164
+ requestingWallet,
165
+ targetWallet,
166
+ permissions,
167
+ );
168
+
169
+ const now = Date.now();
170
+ let grant: AccessGrant =
171
+ grantFromChain ?? {
172
+ id: `grant_${requestingWallet}_${targetWallet}_${now}`,
173
+ requestingWallet,
174
+ targetWallet,
175
+ scopes: options.scopes,
176
+ expiresAt: options.expiresAt,
177
+ grantedAt: now,
178
+ };
179
+
180
+ if (lastDigest) {
181
+ grant = {
182
+ ...grant,
183
+ transactionDigest: lastDigest,
184
+ };
185
+ }
186
+ await this.updateConsentStatus({
187
+ requesterWallet: requestingWallet,
188
+ targetWallet,
189
+ newStatus: 'approved',
190
+ updatedAt: now,
191
+ });
192
+
193
+ return grant;
194
+ }
195
+
196
+ /**
197
+ * Revoke permissions from an app
198
+ * @param userAddress - User revoking permissions
199
+ * @param options - Revoke options
200
+ * @returns Success status
201
+ */
202
+ async revokePermissions(
203
+ userAddress: string,
204
+ options: RevokePermissionsOptions & { signer?: Signer; appId?: string }
205
+ ): Promise<boolean> {
206
+ const requestingWallet = normalizeSuiAddress(options.requestingWallet);
207
+ const targetWallet = normalizeSuiAddress(options.targetWallet);
208
+
209
+ // Validate ownership - prefer capability-based validation
210
+ const hasAccess = await this.validateOwnership(userAddress, targetWallet, options.appId);
211
+ if (!hasAccess) {
212
+ throw new Error('User does not own the specified target wallet or capability');
213
+ }
214
+
215
+ if (options.signer) {
216
+ await this.crossContextPermissions.revokeWalletAllowlistAccess(
217
+ {
218
+ requestingWallet,
219
+ targetWallet,
220
+ scope: options.scope,
221
+ },
222
+ options.signer,
223
+ );
224
+ }
225
+
226
+ return true;
227
+ }
228
+
229
+ /**
230
+ * Determine if a requesting wallet currently has permission to access a target wallet
231
+ */
232
+ async hasWalletPermission(
233
+ requestingWallet: string,
234
+ targetWallet: string,
235
+ scope: PermissionScope
236
+ ): Promise<boolean> {
237
+ return await this.crossContextPermissions.hasWalletPermission({
238
+ requestingWallet: normalizeSuiAddress(requestingWallet),
239
+ targetWallet: normalizeSuiAddress(targetWallet),
240
+ scope,
241
+ });
242
+ }
243
+
244
+ /**
245
+ * Legacy compatibility method for app-scoped permission checks
246
+ * Interprets appId as requesting wallet address.
247
+ */
248
+ async checkPermission(
249
+ appId: string,
250
+ scope: PermissionScope,
251
+ userAddressOrTargetWallet: string
252
+ ): Promise<boolean> {
253
+ return await this.hasWalletPermission(appId, userAddressOrTargetWallet, scope);
254
+ }
255
+
256
+ /**
257
+ * Get all access grants by a user
258
+ * @param userAddress - User address
259
+ * @returns Array of access grants
260
+ */
261
+ async getGrantsByUser(userAddress: string): Promise<AccessGrant[]> {
262
+ const normalized = normalizeSuiAddress(userAddress);
263
+ const permissions = await this.crossContextPermissions.listGrantsByTarget(normalized);
264
+ return this.convertPermissionsToGrants(permissions);
265
+ }
266
+
267
+ /**
268
+ * List all consent requests for a user
269
+ * @param userAddress - User address
270
+ * @returns Array of pending consent requests
271
+ */
272
+ async getPendingConsents(userAddress: string): Promise<ConsentRequest[]> {
273
+ const normalized = normalizeSuiAddress(userAddress);
274
+ if (this.consentRepository) {
275
+ return await this.consentRepository.listByTarget(normalized, 'pending');
276
+ }
277
+ return Array.from(this.pendingConsents.values())
278
+ .filter((request) => request.targetWallet === normalized && request.status === 'pending')
279
+ .map((request) => ({ ...request }));
280
+ }
281
+
282
+ /**
283
+ * Approve a consent request
284
+ * @param userAddress - User approving the request
285
+ * @param consentRequest - Consent request to approve
286
+ * @param contextId - Context ID to grant access to
287
+ * @returns Created access grant
288
+ */
289
+ async approveConsent(
290
+ userAddress: string,
291
+ consentRequest: ConsentRequest,
292
+ _contextId: string
293
+ ): Promise<AccessGrant> {
294
+ const grant = await this.grantPermissions(userAddress, {
295
+ requestingWallet: consentRequest.requesterWallet,
296
+ targetWallet: consentRequest.targetWallet,
297
+ scopes: consentRequest.targetScopes,
298
+ expiresAt: consentRequest.expiresAt
299
+ });
300
+
301
+ const updatedAt = Date.now();
302
+ if (consentRequest.requestId) {
303
+ await this.updateConsentStatus({
304
+ requesterWallet: consentRequest.requesterWallet,
305
+ targetWallet: consentRequest.targetWallet,
306
+ newStatus: 'approved',
307
+ updatedAt,
308
+ requestId: consentRequest.requestId,
309
+ });
310
+ }
311
+
312
+ return grant;
313
+ }
314
+
315
+ /**
316
+ * Deny a consent request
317
+ * @param userAddress - User denying the request
318
+ * @param consentRequest - Consent request to deny
319
+ * @returns Success status
320
+ */
321
+ async denyConsent(userAddress: string, consentRequest: ConsentRequest): Promise<boolean> {
322
+ const now = Date.now();
323
+ if (consentRequest.requestId) {
324
+ await this.updateConsentStatus({
325
+ requesterWallet: consentRequest.requesterWallet,
326
+ targetWallet: consentRequest.targetWallet,
327
+ newStatus: 'denied',
328
+ updatedAt: now,
329
+ requestId: consentRequest.requestId,
330
+ });
331
+ } else {
332
+ const inferredRequestId = randomUUID();
333
+ await this.persistConsentRequest({
334
+ ...consentRequest,
335
+ requestId: inferredRequestId,
336
+ createdAt: now,
337
+ updatedAt: now,
338
+ status: 'denied',
339
+ });
340
+ }
341
+
342
+ return true;
343
+ }
344
+
345
+ /**
346
+ * Get permission audit log for a user
347
+ * @param userAddress - User address
348
+ * @returns Array of permission events
349
+ */
350
+ async getPermissionAudit(userAddress: string): Promise<Array<{
351
+ timestamp: number;
352
+ action: 'grant' | 'revoke' | 'request' | 'deny';
353
+ requestingWallet: string;
354
+ targetWallet: string;
355
+ scopes: PermissionScope[];
356
+ }>> {
357
+ const normalized = normalizeSuiAddress(userAddress);
358
+ const auditEntries: Array<{
359
+ timestamp: number;
360
+ action: 'grant' | 'revoke' | 'request' | 'deny';
361
+ requestingWallet: string;
362
+ targetWallet: string;
363
+ scopes: PermissionScope[];
364
+ }> = [];
365
+
366
+ const consentRecords = this.consentRepository
367
+ ? await this.consentRepository.listByTarget(normalized)
368
+ : Array.from(this.pendingConsents.values()).filter((request) => request.targetWallet === normalized);
369
+
370
+ for (const request of consentRecords) {
371
+ auditEntries.push({
372
+ timestamp: request.createdAt,
373
+ action: 'request',
374
+ requestingWallet: request.requesterWallet,
375
+ targetWallet: request.targetWallet,
376
+ scopes: request.targetScopes,
377
+ });
378
+
379
+ if (request.status === 'denied') {
380
+ auditEntries.push({
381
+ timestamp: request.updatedAt,
382
+ action: 'deny',
383
+ requestingWallet: request.requesterWallet,
384
+ targetWallet: request.targetWallet,
385
+ scopes: request.targetScopes,
386
+ });
387
+ }
388
+ }
389
+
390
+ const history = await this.crossContextPermissions.getWalletAllowlistHistory({
391
+ targetWallet: normalized,
392
+ });
393
+
394
+ for (const event of history) {
395
+ auditEntries.push({
396
+ timestamp: event.timestamp,
397
+ action: event.action,
398
+ requestingWallet: event.requestingWallet,
399
+ targetWallet: event.targetWallet,
400
+ scopes: [this.toPermissionScope(event.scope)],
401
+ });
402
+ }
403
+
404
+ return auditEntries.sort((a, b) => a.timestamp - b.timestamp);
405
+ }
406
+
407
+ /**
408
+ * Validate OAuth-style permission for SEAL access control
409
+ * @param walletOwner - Owner of the wallet
410
+ * @param appId - Application requesting access
411
+ * @param requestedScope - Required permission scope
412
+ * @returns True if permission is valid
413
+ */
414
+ async validateOAuthPermission(
415
+ walletOwner: string,
416
+ appId: string,
417
+ requestedScope: string
418
+ ): Promise<boolean> {
419
+ // This integrates with our existing SEAL access control
420
+ return await this.hasWalletPermission(appId, walletOwner, requestedScope as PermissionScope);
421
+ }
422
+
423
+ /**
424
+ * Build seal_approve transaction for a requesting wallet
425
+ */
426
+ createApprovalTransaction(
427
+ contentId: Uint8Array,
428
+ requestingWallet: string
429
+ ): Transaction {
430
+ return this.crossContextPermissions.buildSealApproveTransaction(
431
+ contentId,
432
+ normalizeSuiAddress(requestingWallet),
433
+ );
434
+ }
435
+
436
+ /**
437
+ * Get permission statistics for a user
438
+ * @param userAddress - User address
439
+ * @returns Permission usage statistics
440
+ */
441
+ async getPermissionStats(userAddress: string): Promise<{
442
+ totalGrants: number;
443
+ activeGrants: number;
444
+ totalApps: number;
445
+ totalScopes: number;
446
+ recentActivity: number;
447
+ }> {
448
+ const grants = await this.getGrantsByUser(userAddress);
449
+ const now = Date.now();
450
+
451
+ const activeGrants = grants.filter((g) => !g.expiresAt || g.expiresAt > now);
452
+ const uniqueApps = new Set(grants.map((g) => g.requestingWallet));
453
+ const uniqueScopes = new Set(grants.flatMap((g) => g.scopes));
454
+
455
+ const recentActivity =
456
+ grants.length > 0 ? Math.max(...grants.map((g) => g.expiresAt || 0)) : 0;
457
+
458
+ return {
459
+ totalGrants: grants.length,
460
+ activeGrants: activeGrants.length,
461
+ totalApps: uniqueApps.size,
462
+ totalScopes: uniqueScopes.size,
463
+ recentActivity
464
+ };
465
+ }
466
+
467
+ /**
468
+ * Clean up expired permissions
469
+ * @param userAddress - User address
470
+ * @returns Number of permissions cleaned up
471
+ */
472
+ async cleanupExpiredPermissions(userAddress: string): Promise<number> {
473
+ const now = Date.now();
474
+ const grants = await this.getGrantsByUser(userAddress);
475
+
476
+ return grants.filter((grant) => {
477
+ if (!grant.expiresAt || grant.expiresAt === 0) {
478
+ return false;
479
+ }
480
+ return grant.expiresAt <= now;
481
+ }).length;
482
+ }
483
+
484
+ private buildGrantFromPermissions(
485
+ requestingWallet: string,
486
+ targetWallet: string,
487
+ permissions: WalletAllowlistPermission[],
488
+ ): AccessGrant | undefined {
489
+ const grants = this.convertPermissionsToGrants(permissions);
490
+ return grants.find(
491
+ (candidate) =>
492
+ candidate.requestingWallet === requestingWallet &&
493
+ candidate.targetWallet === targetWallet,
494
+ );
495
+ }
496
+
497
+ private convertPermissionsToGrants(
498
+ permissions: WalletAllowlistPermission[],
499
+ ): AccessGrant[] {
500
+ const grouped = new Map<string, WalletAllowlistPermission[]>();
501
+
502
+ for (const permission of permissions) {
503
+ const key = `${permission.requestingWallet}-${permission.targetWallet}`;
504
+ const existing = grouped.get(key);
505
+ if (existing) {
506
+ existing.push(permission);
507
+ } else {
508
+ grouped.set(key, [permission]);
509
+ }
510
+ }
511
+
512
+ const grants: AccessGrant[] = [];
513
+ for (const entries of grouped.values()) {
514
+ if (entries.length === 0) {
515
+ continue;
516
+ }
517
+
518
+ const [first] = entries;
519
+ const scopes = new Set<PermissionScope>();
520
+ let earliestGrantedAt = entries[0].grantedAt;
521
+ let expiresAtCandidate = 0;
522
+
523
+ for (const entry of entries) {
524
+ scopes.add(this.toPermissionScope(entry.scope));
525
+ if (entry.grantedAt < earliestGrantedAt) {
526
+ earliestGrantedAt = entry.grantedAt;
527
+ }
528
+ if (entry.expiresAt > expiresAtCandidate) {
529
+ expiresAtCandidate = entry.expiresAt;
530
+ }
531
+ }
532
+
533
+ const grant: AccessGrant = {
534
+ id: `grant_${first.requestingWallet}_${first.targetWallet}_${earliestGrantedAt}`,
535
+ requestingWallet: first.requestingWallet,
536
+ targetWallet: first.targetWallet,
537
+ scopes: Array.from(scopes),
538
+ grantedAt: earliestGrantedAt,
539
+ };
540
+
541
+ if (expiresAtCandidate > 0) {
542
+ grant.expiresAt = expiresAtCandidate;
543
+ }
544
+
545
+ grants.push(grant);
546
+ }
547
+
548
+ return grants;
549
+ }
550
+
551
+ private toPermissionScope(scope: string): PermissionScope {
552
+ return scope as PermissionScope;
553
+ }
554
+
555
+ private async persistConsentRequest(record: ConsentRequestRecord): Promise<void> {
556
+ if (this.consentRepository) {
557
+ await this.consentRepository.save(record);
558
+ } else {
559
+ this.pendingConsents.set(record.requestId, { ...record });
560
+ }
561
+ }
562
+
563
+ /**
564
+ * Swap the consent persistence backend at runtime.
565
+ * Useful for applications that want to wire a custom repository after
566
+ * the service has been constructed (e.g., demos supplying a filesystem store).
567
+ */
568
+ setConsentRepository(repository?: ConsentRepository): void {
569
+ this.consentRepository = repository;
570
+ }
571
+
572
+ /**
573
+ * Validate user ownership of a target wallet or capability
574
+ * Prefers capability-based validation over legacy ContextWalletService
575
+ *
576
+ * @param userAddress - User's address
577
+ * @param targetWallet - Target wallet address
578
+ * @param appId - Optional app ID for capability lookup
579
+ * @returns True if user has ownership/access
580
+ */
581
+ private async validateOwnership(
582
+ userAddress: string,
583
+ targetWallet: string,
584
+ appId?: string
585
+ ): Promise<boolean> {
586
+ // Prefer capability-based validation (new architecture)
587
+ if (this.capabilityService && appId) {
588
+ return await this.capabilityService.hasCapability(userAddress, appId);
589
+ }
590
+
591
+ // Fall back to legacy ContextWalletService validation
592
+ if (this.contextWalletService) {
593
+ return await this.contextWalletService.validateAccess(targetWallet, userAddress);
594
+ }
595
+
596
+ // If neither service is available, skip validation (trust caller)
597
+ return true;
598
+ }
599
+
600
+ private async updateConsentStatus(params: {
601
+ requesterWallet: string;
602
+ targetWallet: string;
603
+ newStatus: ConsentStatus;
604
+ updatedAt: number;
605
+ requestId?: string;
606
+ }): Promise<void> {
607
+ const normalizedRequester = normalizeSuiAddress(params.requesterWallet);
608
+ const normalizedTarget = normalizeSuiAddress(params.targetWallet);
609
+
610
+ if (this.consentRepository) {
611
+ if (params.requestId) {
612
+ await this.consentRepository.updateStatus(params.requestId, params.newStatus, params.updatedAt);
613
+ } else {
614
+ const pendingRecords = await this.consentRepository.listByTarget(normalizedTarget, 'pending');
615
+ await Promise.all(
616
+ pendingRecords
617
+ .filter((record) => record.requesterWallet === normalizedRequester)
618
+ .map((record) =>
619
+ this.consentRepository!.updateStatus(record.requestId, params.newStatus, params.updatedAt),
620
+ ),
621
+ );
622
+ }
623
+ }
624
+
625
+ for (const [requestId, record] of this.pendingConsents.entries()) {
626
+ if (record.requesterWallet === normalizedRequester && record.targetWallet === normalizedTarget) {
627
+ const updatedRecord: ConsentRequestRecord = {
628
+ ...record,
629
+ status: params.newStatus,
630
+ updatedAt: params.updatedAt,
631
+ };
632
+ this.pendingConsents.set(requestId, updatedRecord);
633
+ }
634
+ }
635
+ }
636
636
  }