@canton-network/wallet-gateway-remote 1.3.0 → 1.5.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 (141) hide show
  1. package/README.md +1 -1
  2. package/dist/auth/jwt-auth-service.d.ts.map +1 -1
  3. package/dist/auth/jwt-auth-service.js +6 -5
  4. package/dist/auth/jwt-auth-service.test.d.ts +2 -0
  5. package/dist/auth/jwt-auth-service.test.d.ts.map +1 -0
  6. package/dist/auth/jwt-auth-service.test.js +259 -0
  7. package/dist/auth/jwt-unsafe-auth-service.test.d.ts +2 -0
  8. package/dist/auth/jwt-unsafe-auth-service.test.d.ts.map +1 -0
  9. package/dist/auth/jwt-unsafe-auth-service.test.js +120 -0
  10. package/dist/config/Config.d.ts +64 -8
  11. package/dist/config/Config.d.ts.map +1 -1
  12. package/dist/config/Config.js +1 -0
  13. package/dist/config/ConfigUtils.d.ts.map +1 -1
  14. package/dist/config/ConfigUtils.js +3 -0
  15. package/dist/dapp-api/controller.d.ts +1 -0
  16. package/dist/dapp-api/controller.d.ts.map +1 -1
  17. package/dist/dapp-api/controller.js +87 -26
  18. package/dist/dapp-api/controller.test.d.ts +2 -0
  19. package/dist/dapp-api/controller.test.d.ts.map +1 -0
  20. package/dist/dapp-api/controller.test.js +516 -0
  21. package/dist/dapp-api/rpc-gen/index.d.ts +3 -0
  22. package/dist/dapp-api/rpc-gen/index.d.ts.map +1 -1
  23. package/dist/dapp-api/rpc-gen/index.js +1 -0
  24. package/dist/dapp-api/rpc-gen/typings.d.ts +99 -14
  25. package/dist/dapp-api/rpc-gen/typings.d.ts.map +1 -1
  26. package/dist/dapp-api/server.d.ts.map +1 -1
  27. package/dist/dapp-api/server.js +5 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +10 -0
  30. package/dist/init.d.ts.map +1 -1
  31. package/dist/init.js +4 -6
  32. package/dist/ledger/transaction-service.d.ts +14 -11
  33. package/dist/ledger/transaction-service.d.ts.map +1 -1
  34. package/dist/ledger/transaction-service.js +95 -8
  35. package/dist/ledger/transaction-service.test.d.ts +2 -0
  36. package/dist/ledger/transaction-service.test.d.ts.map +1 -0
  37. package/dist/ledger/transaction-service.test.js +356 -0
  38. package/dist/ledger/wallet-allocation/signing-providers/fireblocks-wallet-allocator.d.ts +5 -2
  39. package/dist/ledger/wallet-allocation/signing-providers/fireblocks-wallet-allocator.d.ts.map +1 -1
  40. package/dist/ledger/wallet-allocation/signing-providers/fireblocks-wallet-allocator.js +7 -6
  41. package/dist/ledger/wallet-allocation/wallet-allocation-service.d.ts +11 -5
  42. package/dist/ledger/wallet-allocation/wallet-allocation-service.d.ts.map +1 -1
  43. package/dist/ledger/wallet-allocation/wallet-allocation-service.js +28 -14
  44. package/dist/ledger/wallet-allocation/wallet-allocation-service.test.js +550 -19
  45. package/dist/ledger/wallet-sync-service.js +3 -3
  46. package/dist/middleware/apiKeyAuth.d.ts +6 -0
  47. package/dist/middleware/apiKeyAuth.d.ts.map +1 -0
  48. package/dist/middleware/apiKeyAuth.js +88 -0
  49. package/dist/middleware/apiKeyAuth.test.d.ts +2 -0
  50. package/dist/middleware/apiKeyAuth.test.d.ts.map +1 -0
  51. package/dist/middleware/apiKeyAuth.test.js +126 -0
  52. package/dist/middleware/jsonRpcHandler.d.ts +11 -1
  53. package/dist/middleware/jsonRpcHandler.d.ts.map +1 -1
  54. package/dist/middleware/jsonRpcHandler.js +14 -15
  55. package/dist/middleware/jsonRpcHandler.test.d.ts +2 -0
  56. package/dist/middleware/jsonRpcHandler.test.d.ts.map +1 -0
  57. package/dist/middleware/jsonRpcHandler.test.js +254 -0
  58. package/dist/middleware/jwtAuth.d.ts.map +1 -1
  59. package/dist/middleware/jwtAuth.js +4 -0
  60. package/dist/middleware/jwtAuth.test.d.ts +2 -0
  61. package/dist/middleware/jwtAuth.test.d.ts.map +1 -0
  62. package/dist/middleware/jwtAuth.test.js +93 -0
  63. package/dist/middleware/rateLimit.js +2 -2
  64. package/dist/middleware/rateLimit.test.js +26 -0
  65. package/dist/middleware/sessionHandler.js +1 -1
  66. package/dist/middleware/sessionHandler.test.d.ts +2 -0
  67. package/dist/middleware/sessionHandler.test.d.ts.map +1 -0
  68. package/dist/middleware/sessionHandler.test.js +121 -0
  69. package/dist/notification/NotificationService.test.d.ts +2 -0
  70. package/dist/notification/NotificationService.test.d.ts.map +1 -0
  71. package/dist/notification/NotificationService.test.js +110 -0
  72. package/dist/signing/signing-drivers.d.ts +7 -0
  73. package/dist/signing/signing-drivers.d.ts.map +1 -0
  74. package/dist/signing/signing-drivers.js +3 -0
  75. package/dist/user-api/controller.d.ts +12 -4
  76. package/dist/user-api/controller.d.ts.map +1 -1
  77. package/dist/user-api/controller.js +330 -95
  78. package/dist/user-api/controller.test.d.ts +2 -0
  79. package/dist/user-api/controller.test.d.ts.map +1 -0
  80. package/dist/user-api/controller.test.js +862 -0
  81. package/dist/user-api/rpc-gen/index.d.ts +30 -0
  82. package/dist/user-api/rpc-gen/index.d.ts.map +1 -1
  83. package/dist/user-api/rpc-gen/index.js +10 -0
  84. package/dist/user-api/rpc-gen/typings.d.ts +170 -25
  85. package/dist/user-api/rpc-gen/typings.d.ts.map +1 -1
  86. package/dist/user-api/server.test.js +53 -0
  87. package/dist/utils.d.ts +19 -5
  88. package/dist/utils.d.ts.map +1 -1
  89. package/dist/utils.js +53 -2
  90. package/dist/utils.test.d.ts +2 -0
  91. package/dist/utils.test.d.ts.map +1 -0
  92. package/dist/utils.test.js +24 -0
  93. package/dist/web/frontend/404/index.html +2 -2
  94. package/dist/web/frontend/activities/index.html +3 -3
  95. package/dist/web/frontend/approve/index.html +4 -5
  96. package/dist/web/frontend/assets/404-MVLWUPwN.js +5 -0
  97. package/dist/web/frontend/assets/activities-DaWkmGqA.js +61 -0
  98. package/dist/web/frontend/assets/addIdentityProvider--rBb0ihb.js +28 -0
  99. package/dist/web/frontend/assets/addNetwork-C6UDsRkU.js +38 -0
  100. package/dist/web/frontend/assets/addParty-B3fFD1eM.js +45 -0
  101. package/dist/web/frontend/assets/approve-WbGL4E82.js +21 -0
  102. package/dist/web/frontend/assets/callback--tihEryB.js +1 -0
  103. package/dist/web/frontend/assets/identityProviders-Dra4qG6t.js +71 -0
  104. package/dist/web/frontend/assets/index-Bm76pXA_.js +91 -0
  105. package/dist/web/frontend/assets/index-D1dC4Cl9.js +4477 -0
  106. package/dist/web/frontend/assets/login-BC8a-kD_.js +10 -0
  107. package/dist/web/frontend/assets/networks-B3ztsogL.js +73 -0
  108. package/dist/web/frontend/assets/reviewIdentityProvider-Co420Dbk.js +43 -0
  109. package/dist/web/frontend/assets/reviewNetwork-B-Sh8pCH.js +43 -0
  110. package/dist/web/frontend/assets/{settings-B1ga2TK0.js → settings-DvyUjqLl.js} +1 -1
  111. package/dist/web/frontend/assets/state-Bq65GwdF.js +1 -0
  112. package/dist/web/frontend/assets/{utils-CT9Hzi7v.js → utils-B2iziW8V.js} +1 -1
  113. package/dist/web/frontend/callback/index.html +2 -2
  114. package/dist/web/frontend/identity-providers/add/index.html +3 -3
  115. package/dist/web/frontend/identity-providers/index.html +3 -3
  116. package/dist/web/frontend/identity-providers/review/index.html +3 -3
  117. package/dist/web/frontend/index.html +1 -1
  118. package/dist/web/frontend/login/index.html +3 -4
  119. package/dist/web/frontend/networks/add/index.html +3 -3
  120. package/dist/web/frontend/networks/index.html +3 -3
  121. package/dist/web/frontend/networks/review/index.html +3 -3
  122. package/dist/web/frontend/parties/add/index.html +5 -5
  123. package/dist/web/frontend/parties/index.html +4 -4
  124. package/dist/web/frontend/settings/index.html +3 -3
  125. package/package.json +40 -33
  126. package/dist/web/frontend/assets/404-B-o9ppJB.js +0 -5
  127. package/dist/web/frontend/assets/activities-CGjCIjUH.js +0 -61
  128. package/dist/web/frontend/assets/addIdentityProvider-CR4Wm9Tl.js +0 -28
  129. package/dist/web/frontend/assets/addNetwork-Dx0-SN4j.js +0 -38
  130. package/dist/web/frontend/assets/addParty-COhk_rFn.js +0 -41
  131. package/dist/web/frontend/assets/approve-B2w66l0J.js +0 -21
  132. package/dist/web/frontend/assets/callback-BTVon_yQ.js +0 -1
  133. package/dist/web/frontend/assets/identityProviders-CK8zSrd3.js +0 -71
  134. package/dist/web/frontend/assets/index-CF4BKzgl.js +0 -91
  135. package/dist/web/frontend/assets/index-DWz_3f3y.js +0 -1
  136. package/dist/web/frontend/assets/index-_pMHlJoE.js +0 -4042
  137. package/dist/web/frontend/assets/login-CVoPNVDw.js +0 -10
  138. package/dist/web/frontend/assets/networks-D1nPvUzM.js +0 -73
  139. package/dist/web/frontend/assets/reviewIdentityProvider-CNSf2qQv.js +0 -43
  140. package/dist/web/frontend/assets/reviewNetwork-DGLK-Ume.js +0 -43
  141. package/dist/web/frontend/assets/state-B2k3ak7d.js +0 -1
@@ -4,14 +4,16 @@
4
4
  /* eslint-disable @typescript-eslint/no-unused-vars */
5
5
  import { LedgerClient } from '@canton-network/core-ledger-client';
6
6
  import buildController from './rpc-gen/index.js';
7
- import { assertConnected, authSchema, AuthTokenProvider, fetchOidcUserInfo, idpSchema, } from '@canton-network/core-wallet-auth';
8
- import { SigningProvider, } from '@canton-network/core-signing-lib';
7
+ import { assertConnected, authSchema, AuthTokenProvider, idpSchema, } from '@canton-network/core-wallet-auth';
8
+ import { isRpcError, SigningProvider } from '@canton-network/core-signing-lib';
9
9
  import { PartyAllocationService } from '../ledger/party-allocation-service.js';
10
10
  import { WalletAllocationService } from '../ledger/wallet-allocation/wallet-allocation-service.js';
11
11
  import { WalletSyncService } from '../ledger/wallet-sync-service.js';
12
- import { networkStatus } from '../utils.js';
12
+ import { logDynamically, networkStatus } from '../utils.js';
13
13
  import { v4 } from 'uuid';
14
14
  import { TransactionService } from '../ledger/transaction-service.js';
15
+ import { rpcErrors } from '@canton-network/core-rpc-errors';
16
+ import crypto from 'crypto';
15
17
  export const userController = (kernelInfo, userUrl, store, notificationService, authContext, drivers, _logger, adminUserId) => {
16
18
  const logger = _logger.child({ component: 'user-controller' });
17
19
  const provider = {
@@ -26,27 +28,6 @@ export const userController = (kernelInfo, userUrl, store, notificationService,
26
28
  throw new Error('Unauthorized: only the admin user can perform this operation');
27
29
  }
28
30
  }
29
- async function resolveUserEmail(connectedContext) {
30
- if (connectedContext.email) {
31
- return connectedContext.email;
32
- }
33
- try {
34
- const network = await store.getCurrentNetwork();
35
- if (!network) {
36
- return undefined;
37
- }
38
- const idp = await store.getIdp(network.identityProviderId);
39
- if (idp.type !== 'oauth') {
40
- return undefined;
41
- }
42
- const userInfo = await fetchOidcUserInfo(idp.configUrl, connectedContext.accessToken);
43
- return userInfo?.email;
44
- }
45
- catch (error) {
46
- logger.warn(error, 'Failed to resolve user email from OIDC userinfo');
47
- return undefined;
48
- }
49
- }
50
31
  return buildController({
51
32
  getUser: async () => {
52
33
  const userId = assertConnected(authContext).userId;
@@ -95,12 +76,42 @@ export const userController = (kernelInfo, userUrl, store, notificationService,
95
76
  listNetworks: async () => {
96
77
  const networks = await store.listNetworks();
97
78
  return {
98
- networks: networks.map((n) => ({
99
- ...n,
100
- ledgerApi: n.ledgerApi.baseUrl,
101
- })),
79
+ networks: networks.map(toPublicNetwork),
102
80
  };
103
81
  },
82
+ getNetwork: async (params) => {
83
+ assertAdmin();
84
+ const network = await store.getNetwork(params.networkId);
85
+ return { network: toNetworkDto(network) };
86
+ },
87
+ selfSignedAccessToken: async (params) => {
88
+ const network = (await store.listNetworks()).find((n) => n.id === params.networkId);
89
+ if (!network) {
90
+ throw new Error(`Network "${params.networkId}" not found`);
91
+ }
92
+ const auth = network.auth;
93
+ if (auth.method !== 'self_signed') {
94
+ throw new Error('Network does not use self_signed authentication');
95
+ }
96
+ const idp = (await store.listIdps()).find((idp) => idp.id === network.identityProviderId);
97
+ if (!idp) {
98
+ throw new Error(`Identity provider "${network.identityProviderId}" not found`);
99
+ }
100
+ if (idp.type !== 'self_signed') {
101
+ throw new Error('Identity provider is not configured for self_signed authentication');
102
+ }
103
+ const accessToken = await new AuthTokenProvider({
104
+ method: 'self_signed',
105
+ issuer: idp.issuer,
106
+ credentials: {
107
+ clientId: params.clientId,
108
+ clientSecret: auth.clientSecret,
109
+ scope: auth.scope,
110
+ audience: auth.audience,
111
+ },
112
+ }, logger).getAccessToken();
113
+ return { accessToken };
114
+ },
104
115
  addIdp: async (params) => {
105
116
  assertAdmin();
106
117
  const validatedIdp = idpSchema.parse(params.idp);
@@ -122,14 +133,10 @@ export const userController = (kernelInfo, userUrl, store, notificationService,
122
133
  await store.removeIdp(params.identityProviderId);
123
134
  return null;
124
135
  },
125
- listIdps: async () => Promise.resolve({ idps: await store.listIdps() }),
136
+ listIdps: async () => ({ idps: await store.listIdps() }),
126
137
  createWallet: async (params) => {
127
- logger.info(`Creating wallet with params: ${JSON.stringify(params)}`);
128
138
  const { signingProviderId, primary, partyHint } = params;
129
139
  const connectedContext = assertConnected(authContext);
130
- const userId = connectedContext.userId;
131
- const email = await resolveUserEmail(connectedContext);
132
- const notifier = notificationService.getNotifier(userId);
133
140
  const network = await store.getCurrentNetwork();
134
141
  if (network === undefined) {
135
142
  throw new Error('No network session found');
@@ -138,6 +145,7 @@ export const userController = (kernelInfo, userUrl, store, notificationService,
138
145
  if (!network.adminAuth) {
139
146
  throw new Error('No admin auth configured');
140
147
  }
148
+ const notifier = notificationService.getNotifier(connectedContext.userId);
141
149
  const adminTokenProvider = AuthTokenProvider.fromGatewayConfig(idp, network.adminAuth, logger);
142
150
  const partyAllocator = new PartyAllocationService({
143
151
  synchronizerId: network.synchronizerId,
@@ -149,7 +157,7 @@ export const userController = (kernelInfo, userUrl, store, notificationService,
149
157
  if (!drivers[signingProviderId]) {
150
158
  throw new Error(`Signing provider ${signingProviderId} not supported`);
151
159
  }
152
- const wallet = await walletAllocationService.createWallet(userId, email, partyHint, primary ?? false, signingProviderId);
160
+ const wallet = await walletAllocationService.createWallet(connectedContext, partyHint, primary ?? false, signingProviderId, params.vaultName);
153
161
  // Sync wallets (TODO: separate rights sync from wallet sync as we only need rights sync here)
154
162
  const ledgerClient = new LedgerClient({
155
163
  baseUrl: new URL(network.ledgerApi.baseUrl),
@@ -164,24 +172,21 @@ export const userController = (kernelInfo, userUrl, store, notificationService,
164
172
  return { wallet };
165
173
  },
166
174
  allocatePartyForWallet: async (params) => {
167
- logger.info(`Allocating party for wallet: ${JSON.stringify(params)}`);
168
175
  const connectedContext = assertConnected(authContext);
169
176
  const userId = connectedContext.userId;
170
- const email = await resolveUserEmail(connectedContext);
171
- const notifier = notificationService.getNotifier(userId);
172
177
  const network = await store.getCurrentNetwork();
173
178
  if (!network) {
174
179
  throw new Error('No network session found');
175
180
  }
181
+ if (!network.adminAuth) {
182
+ throw new Error('No admin auth configured');
183
+ }
176
184
  const allWallets = await store.getWallets();
177
185
  const existingWallet = allWallets.find((w) => w.partyId === params.partyId && w.networkId === network.id);
178
186
  if (!existingWallet) {
179
187
  throw new Error(`Wallet not found for party ${params.partyId}`);
180
188
  }
181
189
  const idp = await store.getIdp(network.identityProviderId);
182
- if (!network.adminAuth) {
183
- throw new Error('No admin auth configured');
184
- }
185
190
  const accessTokenProvider = AuthTokenProvider.fromGatewayConfig(idp, network.adminAuth, logger);
186
191
  const partyAllocator = new PartyAllocationService({
187
192
  synchronizerId: network.synchronizerId,
@@ -194,7 +199,7 @@ export const userController = (kernelInfo, userUrl, store, notificationService,
194
199
  if (!drivers[signingProviderId]) {
195
200
  throw new Error(`Signing provider ${signingProviderId} not supported`);
196
201
  }
197
- await walletAllocationService.allocateParty(userId, email, existingWallet, signingProviderId);
202
+ await walletAllocationService.allocateParty(connectedContext, existingWallet, signingProviderId);
198
203
  // Sync wallets (TODO: separate rights sync from wallet sync as we only need rights sync here)
199
204
  const ledgerClient = new LedgerClient({
200
205
  baseUrl: new URL(network.ledgerApi.baseUrl),
@@ -207,6 +212,7 @@ export const userController = (kernelInfo, userUrl, store, notificationService,
207
212
  const wallets = await store.getWallets();
208
213
  const wallet = wallets.find((w) => w.partyId === existingWallet.partyId &&
209
214
  w.networkId === network.id);
215
+ const notifier = notificationService.getNotifier(userId);
210
216
  notifier?.emit('accountsChanged', wallets);
211
217
  return { wallet };
212
218
  },
@@ -219,7 +225,9 @@ export const userController = (kernelInfo, userUrl, store, notificationService,
219
225
  notifier?.emit('accountsChanged', wallets);
220
226
  return null;
221
227
  },
222
- removeWallet: async (params) => Promise.resolve({}),
228
+ removeWallet: async (params) => {
229
+ throw rpcErrors.methodNotSupported();
230
+ },
223
231
  listWallets: async (params) => {
224
232
  return await store.getWallets(params.filter);
225
233
  },
@@ -235,36 +243,153 @@ export const userController = (kernelInfo, userUrl, store, notificationService,
235
243
  }
236
244
  const connectedContext = assertConnected(authContext);
237
245
  const userId = connectedContext.userId;
238
- const email = await resolveUserEmail(connectedContext);
239
246
  const notifier = notificationService.getNotifier(userId);
240
- const signingProvider = wallet.signingProviderId;
241
- const driver = drivers[signingProvider]?.controller(userId);
242
- if (!driver) {
243
- throw new Error(`No driver found for ${wallet.signingProviderId}`);
244
- }
245
247
  const transactionService = new TransactionService(store, logger, drivers, notifier);
246
- switch (wallet.signingProviderId) {
247
- case SigningProvider.PARTICIPANT: {
248
- return transactionService.signWithParticipant(wallet);
249
- }
250
- case SigningProvider.WALLET_KERNEL: {
251
- return transactionService.signWithWalletKernel(userId, wallet, signParams);
252
- }
253
- case SigningProvider.BLOCKDAEMON: {
254
- if (!email) {
255
- throw new Error('Email is required for Blockdaemon wallet allocation');
256
- }
257
- return transactionService.signWithBlockdaemon(email, wallet, signParams);
258
- }
259
- case SigningProvider.FIREBLOCKS: {
260
- return transactionService.signWithFireblocks(userId, wallet, signParams);
248
+ logDynamically(logger, 'signing transaction with params', {
249
+ info: { transactionId: signParams.transactionId },
250
+ debug: { signParams, wallet, connectedContext },
251
+ });
252
+ const response = await transactionService.sign(connectedContext, wallet, signParams);
253
+ logDynamically(logger, 'transaction signed with response', {
254
+ info: { transactionId: signParams.transactionId },
255
+ debug: { response },
256
+ });
257
+ return response;
258
+ },
259
+ signMessage: async (params) => {
260
+ const pending = await store.getMessageRaw(params.messageId);
261
+ if (!pending) {
262
+ throw new Error(`Message signing request not found with id: ${params.messageId}`);
263
+ }
264
+ if (pending.status !== 'pending') {
265
+ throw new Error(`Cannot sign message with status '${pending.status}'. Only pending messages can be signed.`);
266
+ }
267
+ const userId = assertConnected(authContext).userId;
268
+ if (pending.userId !== userId) {
269
+ throw new Error(`Message signing request ${pending.id} is not owned by user ${userId}`);
270
+ }
271
+ const notifier = notificationService.getNotifier(userId);
272
+ const emitFailedAndPersist = async (details) => {
273
+ // Best-effort: make sure listeners see a terminal state.
274
+ try {
275
+ await store.setMessageRawStatus(pending.id, 'failed');
261
276
  }
262
- case SigningProvider.DFNS: {
263
- return transactionService.signWithDfns(userId, wallet, signParams);
277
+ catch {
278
+ // ignore (e.g. record removed concurrently)
264
279
  }
265
- default:
266
- throw new Error(`Unsupported signing provider: ${wallet.signingProviderId}`);
280
+ notifier.emit('messageSignature', {
281
+ status: 'failed',
282
+ messageId: pending.id,
283
+ });
284
+ // Preserve the original error message for the caller/UI.
285
+ throw new Error(details);
286
+ };
287
+ const wallet = (await store.getWallets()).find((w) => w.partyId === pending.partyId);
288
+ if (!wallet) {
289
+ return await emitFailedAndPersist(`No wallet found for partyId ${pending.partyId} (from message request ${pending.id})`);
290
+ }
291
+ if (wallet.publicKey !== pending.publicKey) {
292
+ return await emitFailedAndPersist(`Wallet public key changed for partyId ${pending.partyId}; refusing to sign message request ${pending.id}`);
293
+ }
294
+ // TODO: support other signing providers
295
+ if (wallet.signingProviderId !== SigningProvider.WALLET_KERNEL) {
296
+ return await emitFailedAndPersist(`signMessage is only supported for ${SigningProvider.WALLET_KERNEL} wallets, got ${wallet.signingProviderId}`);
297
+ }
298
+ const driver = drivers[SigningProvider.WALLET_KERNEL]?.controller(userId);
299
+ if (!driver) {
300
+ return await emitFailedAndPersist('Wallet Kernel signing driver not available');
301
+ }
302
+ const result = await driver.signMessage({
303
+ message: pending.message,
304
+ keyIdentifier: { publicKey: wallet.publicKey },
305
+ });
306
+ if (isRpcError(result)) {
307
+ await store.setMessageRawStatus(pending.id, 'failed');
308
+ notifier.emit('messageSignature', {
309
+ status: 'failed',
310
+ messageId: pending.id,
311
+ });
312
+ throw new Error(result.error_description);
313
+ }
314
+ if (!result?.signature) {
315
+ await store.setMessageRawStatus(pending.id, 'failed');
316
+ notifier.emit('messageSignature', {
317
+ status: 'failed',
318
+ messageId: pending.id,
319
+ });
320
+ throw new Error(`signMessage failed`);
321
+ }
322
+ await store.setMessageRawStatus(pending.id, 'signed', {
323
+ signedAt: new Date(),
324
+ signature: result.signature,
325
+ });
326
+ notifier.emit('messageSignature', {
327
+ status: 'signed',
328
+ messageId: pending.id,
329
+ signature: result.signature,
330
+ });
331
+ return {
332
+ signature: result.signature,
333
+ publicKey: wallet.publicKey,
334
+ };
335
+ },
336
+ getMessageToSign: async (params) => {
337
+ const message = await store.getMessageRaw(params.messageId);
338
+ if (!message) {
339
+ throw new Error(`Message signing request not found with id: ${params.messageId}`);
267
340
  }
341
+ return {
342
+ message: {
343
+ id: message.id,
344
+ status: message.status,
345
+ partyId: message.partyId,
346
+ publicKey: message.publicKey,
347
+ message: message.message,
348
+ ...(message.origin !== null && { origin: message.origin }),
349
+ ...(message.createdAt && {
350
+ createdAt: message.createdAt.toISOString(),
351
+ }),
352
+ ...(message.signedAt && {
353
+ signedAt: message.signedAt.toISOString(),
354
+ }),
355
+ ...(message.signature && { signature: message.signature }),
356
+ },
357
+ };
358
+ },
359
+ listMessagesToSign: async () => {
360
+ const messages = await store.listMessageRaws();
361
+ return {
362
+ messages: messages.map((message) => ({
363
+ id: message.id,
364
+ status: message.status,
365
+ partyId: message.partyId,
366
+ publicKey: message.publicKey,
367
+ message: message.message,
368
+ ...(message.origin !== null && { origin: message.origin }),
369
+ ...(message.createdAt && {
370
+ createdAt: message.createdAt.toISOString(),
371
+ }),
372
+ ...(message.signedAt && {
373
+ signedAt: message.signedAt.toISOString(),
374
+ }),
375
+ ...(message.signature && { signature: message.signature }),
376
+ })),
377
+ };
378
+ },
379
+ deleteMessageToSign: async (params) => {
380
+ const message = await store.getMessageRaw(params.messageId);
381
+ if (!message) {
382
+ throw new Error(`Message signing request not found with id: ${params.messageId}`);
383
+ }
384
+ if (message.status !== 'pending') {
385
+ throw new Error(`Cannot delete message with status '${message.status}'. Only pending messages can be deleted.`);
386
+ }
387
+ const userId = assertConnected(authContext).userId;
388
+ if (message.userId !== userId) {
389
+ throw new Error(`Message signing request ${message.id} is not owned by user ${userId}`);
390
+ }
391
+ await store.removeMessageRaw(message.id);
392
+ return null;
268
393
  },
269
394
  execute: async (executeParams) => {
270
395
  const wallets = await store.getWallets();
@@ -272,45 +397,38 @@ export const userController = (kernelInfo, userUrl, store, notificationService,
272
397
  const transaction = await store.getTransaction(executeParams.transactionId);
273
398
  const wallet = wallets.find((w) => w.partyId === executeParams.partyId);
274
399
  if (wallet === undefined) {
275
- throw new Error('No primary wallet found');
400
+ throw new Error('Requested wallet not found for user');
276
401
  }
277
402
  if (transaction === undefined) {
278
403
  throw new Error('No transaction found');
279
404
  }
280
- const userId = assertConnected(authContext).userId;
405
+ const connectedContext = assertConnected(authContext);
406
+ const accessTokenProvider = AuthTokenProvider.fromToken(connectedContext.accessToken, logger);
281
407
  if (network === undefined) {
282
408
  throw new Error('No network session found');
283
409
  }
284
- const notifier = notificationService.getNotifier(userId);
285
- // Create AccessTokenProvider for user token
286
- const userAccessTokenProvider = AuthTokenProvider.fromToken(authContext.accessToken, logger);
410
+ const notifier = notificationService.getNotifier(connectedContext.userId);
287
411
  const ledgerClient = new LedgerClient({
288
412
  baseUrl: new URL(network.ledgerApi.baseUrl),
289
413
  logger,
290
- accessTokenProvider: userAccessTokenProvider,
414
+ accessTokenProvider,
291
415
  });
292
416
  const transactionService = new TransactionService(store, logger, drivers, notifier);
293
- switch (wallet.signingProviderId) {
294
- case SigningProvider.PARTICIPANT: {
295
- try {
296
- return await transactionService.executeWithParticipant(userId, executeParams, transaction, ledgerClient, network);
297
- }
298
- catch (error) {
299
- logger.error(error, 'Failed to submit transaction');
300
- throw error;
301
- }
302
- }
303
- case SigningProvider.WALLET_KERNEL:
304
- case SigningProvider.BLOCKDAEMON:
305
- case SigningProvider.FIREBLOCKS: {
306
- return transactionService.executeWithExternal(userId, executeParams, transaction, ledgerClient);
307
- }
308
- case SigningProvider.DFNS: {
309
- return transactionService.executeWithDfns(transaction);
310
- }
311
- default:
312
- throw new Error(`Unsupported signing provider: ${wallet.signingProviderId}`);
313
- }
417
+ logDynamically(logger, 'executing transaction with params', {
418
+ info: { transactionId: executeParams.transactionId },
419
+ debug: {
420
+ executeParams,
421
+ transaction,
422
+ wallet,
423
+ userId: connectedContext.userId,
424
+ },
425
+ });
426
+ const response = await transactionService.execute(connectedContext.userId, wallet, transaction, executeParams, ledgerClient, network);
427
+ logDynamically(logger, 'transaction executed with response', {
428
+ info: { transactionId: executeParams.transactionId },
429
+ debug: { response },
430
+ });
431
+ return response;
314
432
  },
315
433
  addSession: async function (params) {
316
434
  try {
@@ -390,7 +508,7 @@ export const userController = (kernelInfo, userUrl, store, notificationService,
390
508
  }
391
509
  },
392
510
  removeSession: async () => {
393
- logger.info(authContext, 'Removing session');
511
+ logger.info({ authContext }, 'Removing session');
394
512
  const userId = assertConnected(authContext).userId;
395
513
  const notifier = notificationService.getNotifier(userId);
396
514
  await store.removeSession();
@@ -561,5 +679,122 @@ export const userController = (kernelInfo, userUrl, store, notificationService,
561
679
  await store.removeTransaction(transaction.id);
562
680
  return null;
563
681
  },
682
+ generateApiKey: async (params) => {
683
+ const userId = assertConnected(authContext).userId;
684
+ const network = await store.getCurrentNetwork();
685
+ const apiKeyId = v4();
686
+ const generatedApiKey = crypto.randomBytes(32).toString('hex');
687
+ const hashedApiKey = crypto
688
+ .createHash('sha256')
689
+ .update(generatedApiKey)
690
+ .digest('hex');
691
+ const storedApiKey = {
692
+ id: apiKeyId,
693
+ name: params.name,
694
+ digest: hashedApiKey,
695
+ userId,
696
+ networkId: network.id,
697
+ email: authContext?.email || null,
698
+ createdAt: new Date(),
699
+ };
700
+ await store.addApiKey(storedApiKey);
701
+ logDynamically(logger, 'Generated new API key', {
702
+ info: { apiKeyId: storedApiKey.id },
703
+ debug: {
704
+ name: storedApiKey.name,
705
+ userId: storedApiKey.userId,
706
+ networkId: storedApiKey.networkId,
707
+ createdAt: storedApiKey.createdAt,
708
+ },
709
+ });
710
+ return {
711
+ id: storedApiKey.id,
712
+ apiKey: generatedApiKey,
713
+ };
714
+ },
715
+ listApiKeys: async () => {
716
+ const apiKeys = await store.listApiKeys().then((keys) => keys.map((key) => ({
717
+ id: key.id,
718
+ name: key.name,
719
+ createdAt: key.createdAt.toISOString(),
720
+ })));
721
+ return { apiKeys };
722
+ },
723
+ removeApiKey: async (params) => {
724
+ await store.removeApiKey(params.id);
725
+ return null;
726
+ },
727
+ listSigningProviderVaults: async (params) => {
728
+ const network = await store.getCurrentNetwork();
729
+ const idp = await store.getIdp(network.identityProviderId);
730
+ if (!network.adminAuth) {
731
+ throw new Error('No admin auth configured');
732
+ }
733
+ const adminAccessTokenProvider = AuthTokenProvider.fromGatewayConfig(idp, network.adminAuth, logger);
734
+ const partyAllocator = new PartyAllocationService({
735
+ synchronizerId: network.synchronizerId,
736
+ accessTokenProvider: adminAccessTokenProvider,
737
+ httpLedgerUrl: network.ledgerApi.baseUrl,
738
+ logger,
739
+ });
740
+ const walletAllocationService = new WalletAllocationService(store, logger, partyAllocator, drivers);
741
+ if (!drivers[params.signingProviderId]) {
742
+ throw new Error(`Signing provider ${params.signingProviderId} not supported`);
743
+ }
744
+ return walletAllocationService.getVaults(assertConnected(authContext), params.signingProviderId);
745
+ },
564
746
  });
565
747
  };
748
+ function toAuthDto(auth) {
749
+ const base = {
750
+ method: auth.method,
751
+ audience: auth.audience,
752
+ scope: auth.scope,
753
+ clientId: auth.clientId,
754
+ };
755
+ if (auth.method === 'self_signed') {
756
+ return {
757
+ ...base,
758
+ issuer: auth.issuer,
759
+ clientSecret: auth.clientSecret,
760
+ };
761
+ }
762
+ if (auth.method === 'client_credentials') {
763
+ return {
764
+ ...base,
765
+ clientSecret: auth.clientSecret,
766
+ };
767
+ }
768
+ return base;
769
+ }
770
+ function toNetworkDto(network) {
771
+ return {
772
+ id: network.id,
773
+ name: network.name,
774
+ description: network.description,
775
+ synchronizerId: network.synchronizerId,
776
+ identityProviderId: network.identityProviderId,
777
+ ledgerApi: network.ledgerApi.baseUrl,
778
+ auth: toAuthDto(network.auth),
779
+ ...(network.adminAuth
780
+ ? { adminAuth: toAuthDto(network.adminAuth) }
781
+ : {}),
782
+ };
783
+ }
784
+ function toPublicNetwork(network) {
785
+ const auth = network.auth;
786
+ return {
787
+ id: network.id,
788
+ name: network.name,
789
+ description: network.description,
790
+ synchronizerId: network.synchronizerId,
791
+ identityProviderId: network.identityProviderId,
792
+ ledgerApi: network.ledgerApi.baseUrl,
793
+ authMethod: auth.method,
794
+ ...(auth.method !== 'client_credentials' && {
795
+ clientId: auth.clientId,
796
+ scope: auth.scope,
797
+ audience: auth.audience,
798
+ }),
799
+ };
800
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=controller.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller.test.d.ts","sourceRoot":"","sources":["../../src/user-api/controller.test.ts"],"names":[],"mappings":""}