@canton-network/wallet-gateway-remote 1.5.0 → 1.6.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 (75) hide show
  1. package/dist/config/Config.d.ts +9 -0
  2. package/dist/config/Config.d.ts.map +1 -1
  3. package/dist/config/Config.js +5 -0
  4. package/dist/dapp-api/controller.d.ts +5 -1
  5. package/dist/dapp-api/controller.d.ts.map +1 -1
  6. package/dist/dapp-api/controller.js +70 -26
  7. package/dist/dapp-api/controller.test.js +2 -2
  8. package/dist/dapp-api/server.d.ts +3 -2
  9. package/dist/dapp-api/server.d.ts.map +1 -1
  10. package/dist/dapp-api/server.js +2 -2
  11. package/dist/dapp-api/server.test.js +1 -6
  12. package/dist/example-config.d.ts +3 -0
  13. package/dist/example-config.d.ts.map +1 -1
  14. package/dist/example-config.js +3 -0
  15. package/dist/init.d.ts.map +1 -1
  16. package/dist/init.js +16 -1
  17. package/dist/ledger/transaction-service.d.ts +2 -0
  18. package/dist/ledger/transaction-service.d.ts.map +1 -1
  19. package/dist/ledger/transaction-service.js +40 -0
  20. package/dist/ledger/transaction-service.test.js +34 -0
  21. package/dist/middleware/apiKeyAuth.d.ts.map +1 -1
  22. package/dist/middleware/apiKeyAuth.js +2 -0
  23. package/dist/signing/service-account-session.d.ts +16 -0
  24. package/dist/signing/service-account-session.d.ts.map +1 -0
  25. package/dist/signing/service-account-session.js +25 -0
  26. package/dist/signing/service-account-session.test.d.ts +2 -0
  27. package/dist/signing/service-account-session.test.d.ts.map +1 -0
  28. package/dist/signing/service-account-session.test.js +89 -0
  29. package/dist/signing/signing-worker.d.ts +83 -0
  30. package/dist/signing/signing-worker.d.ts.map +1 -0
  31. package/dist/signing/signing-worker.js +206 -0
  32. package/dist/signing/signing-worker.test.d.ts +2 -0
  33. package/dist/signing/signing-worker.test.d.ts.map +1 -0
  34. package/dist/signing/signing-worker.test.js +152 -0
  35. package/dist/user-api/controller.d.ts.map +1 -1
  36. package/dist/user-api/controller.js +8 -1
  37. package/dist/web/frontend/404/index.html +2 -2
  38. package/dist/web/frontend/activities/index.html +3 -3
  39. package/dist/web/frontend/approve/index.html +4 -4
  40. package/dist/web/frontend/assets/404-C4hCL2o8.js +5 -0
  41. package/dist/web/frontend/assets/{activities-DaWkmGqA.js → activities-Bq7yMMQY.js} +2 -2
  42. package/dist/web/frontend/assets/addIdentityProvider-DRJWLhHF.js +28 -0
  43. package/dist/web/frontend/assets/addNetwork-CJUaKP1q.js +38 -0
  44. package/dist/web/frontend/assets/{addParty-B3fFD1eM.js → addParty-2ObTLGIL.js} +1 -1
  45. package/dist/web/frontend/assets/{approve-WbGL4E82.js → approve-BuA5DGC2.js} +2 -2
  46. package/dist/web/frontend/assets/{callback--tihEryB.js → callback-C5vJWFLq.js} +1 -1
  47. package/dist/web/frontend/assets/{identityProviders-Dra4qG6t.js → identityProviders-78FbpzYL.js} +2 -2
  48. package/dist/web/frontend/assets/{index-Bm76pXA_.js → index-BTH_md6b.js} +3 -3
  49. package/dist/web/frontend/assets/index-CFnOucPK.js +4737 -0
  50. package/dist/web/frontend/assets/{login-BC8a-kD_.js → login-Dyn1o6fW.js} +2 -2
  51. package/dist/web/frontend/assets/{networks-B3ztsogL.js → networks-_el10AWP.js} +2 -2
  52. package/dist/web/frontend/assets/{reviewIdentityProvider-Co420Dbk.js → reviewIdentityProvider-DodUZ2mY.js} +3 -3
  53. package/dist/web/frontend/assets/reviewNetwork-BFMtmKEL.js +43 -0
  54. package/dist/web/frontend/assets/settings-BBmkIfYm.js +37 -0
  55. package/dist/web/frontend/assets/{state-Bq65GwdF.js → state-DkxLvywv.js} +1 -1
  56. package/dist/web/frontend/assets/{utils-B2iziW8V.js → utils-DvJKIYxT.js} +1 -1
  57. package/dist/web/frontend/callback/index.html +2 -2
  58. package/dist/web/frontend/identity-providers/add/index.html +3 -3
  59. package/dist/web/frontend/identity-providers/index.html +3 -3
  60. package/dist/web/frontend/identity-providers/review/index.html +3 -3
  61. package/dist/web/frontend/index.html +1 -1
  62. package/dist/web/frontend/login/index.html +3 -3
  63. package/dist/web/frontend/networks/add/index.html +3 -3
  64. package/dist/web/frontend/networks/index.html +3 -3
  65. package/dist/web/frontend/networks/review/index.html +3 -3
  66. package/dist/web/frontend/parties/add/index.html +5 -5
  67. package/dist/web/frontend/parties/index.html +4 -4
  68. package/dist/web/frontend/settings/index.html +3 -3
  69. package/package.json +20 -20
  70. package/dist/web/frontend/assets/404-MVLWUPwN.js +0 -5
  71. package/dist/web/frontend/assets/addIdentityProvider--rBb0ihb.js +0 -28
  72. package/dist/web/frontend/assets/addNetwork-C6UDsRkU.js +0 -38
  73. package/dist/web/frontend/assets/index-D1dC4Cl9.js +0 -4477
  74. package/dist/web/frontend/assets/reviewNetwork-B-Sh8pCH.js +0 -43
  75. package/dist/web/frontend/assets/settings-DvyUjqLl.js +0 -37
@@ -15,6 +15,9 @@ export declare const serverConfigSchema: z.ZodObject<{
15
15
  requestRateLimit: z.ZodDefault<z.ZodNumber>;
16
16
  trustProxy: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodNumber, z.ZodString]>>;
17
17
  admin: z.ZodOptional<z.ZodString>;
18
+ signingWorker: z.ZodPreprocess<z.ZodObject<{
19
+ pollInterval: z.ZodDefault<z.ZodNumber>;
20
+ }, z.core.$strip>>;
18
21
  }, z.core.$strip>;
19
22
  export declare const rawConfigSchema: z.ZodObject<{
20
23
  kernel: z.ZodObject<{
@@ -33,6 +36,9 @@ export declare const rawConfigSchema: z.ZodObject<{
33
36
  requestRateLimit: z.ZodDefault<z.ZodNumber>;
34
37
  trustProxy: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodNumber, z.ZodString]>>;
35
38
  admin: z.ZodOptional<z.ZodString>;
39
+ signingWorker: z.ZodPreprocess<z.ZodObject<{
40
+ pollInterval: z.ZodDefault<z.ZodNumber>;
41
+ }, z.core.$strip>>;
36
42
  }, z.core.$strip>>;
37
43
  logging: z.ZodOptional<z.ZodPreprocess<z.ZodObject<{
38
44
  level: z.ZodOptional<z.ZodEnum<{
@@ -229,6 +235,9 @@ export declare const configSchema: z.ZodObject<{
229
235
  requestRateLimit: z.ZodDefault<z.ZodNumber>;
230
236
  trustProxy: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodNumber, z.ZodString]>>;
231
237
  admin: z.ZodOptional<z.ZodString>;
238
+ signingWorker: z.ZodPreprocess<z.ZodObject<{
239
+ pollInterval: z.ZodDefault<z.ZodNumber>;
240
+ }, z.core.$strip>>;
232
241
  }, z.core.$strip>>;
233
242
  logging: z.ZodOptional<z.ZodPreprocess<z.ZodObject<{
234
243
  level: z.ZodOptional<z.ZodEnum<{
@@ -1 +1 @@
1
- {"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../../src/config/Config.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AASvB,eAAO,MAAM,gBAAgB;;;;iBAY3B,CAAA;AAEF,eAAO,MAAM,kBAAkB;;;;;;;;;;;iBAiD7B,CAAA;AAkCF,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAO1B,CAAA;AAEF,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAOvB,CAAA;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAA;AACzD,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAC7D,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AACvD,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAA"}
1
+ {"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../../src/config/Config.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AASvB,eAAO,MAAM,gBAAgB;;;;iBAY3B,CAAA;AAEF,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;iBA0D7B,CAAA;AAkCF,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAO1B,CAAA;AAEF,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAOvB,CAAA;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAA;AACzD,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAC7D,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AACvD,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAA"}
@@ -57,6 +57,11 @@ export const serverConfigSchema = z.object({
57
57
  admin: z.string().optional().meta({
58
58
  description: 'The JWT claim (e.g. "sub") identifying the admin user. If set, requests with a matching claim will be granted admin privileges.',
59
59
  }),
60
+ signingWorker: z.preprocess((val) => val ?? {}, z.object({
61
+ pollInterval: z.number().int().positive().default(5000).meta({
62
+ description: 'Interval in milliseconds for the signing worker to poll external signing providers on pending transactions. Defaults to 5000.',
63
+ }),
64
+ })),
60
65
  });
61
66
  const loggingConfigSchema = z
62
67
  .object({
@@ -3,7 +3,11 @@ import { Store } from '@canton-network/core-wallet-store';
3
3
  import { NotificationService } from '../notification/NotificationService.js';
4
4
  import { KernelInfo as KernelInfoConfig } from '../config/Config.js';
5
5
  import { Logger } from 'pino';
6
- export declare const dappController: (kernelInfo: KernelInfoConfig, dappUrl: string, userUrl: string, store: Store, notificationService: NotificationService, _logger: Logger, origin: string | null, context?: AuthContext) => {
6
+ import { SigningDrivers } from '../signing/signing-drivers.js';
7
+ export interface DappControllerDeps {
8
+ signingDrivers: SigningDrivers;
9
+ }
10
+ export declare const dappController: (kernelInfo: KernelInfoConfig, dappUrl: string, userUrl: string, store: Store, notificationService: NotificationService, _logger: Logger, origin: string | null, deps: DappControllerDeps, context?: AuthContext) => {
7
11
  status: import("./rpc-gen/typings.js").Status;
8
12
  connect: import("./rpc-gen/typings.js").Connect;
9
13
  disconnect: import("./rpc-gen/typings.js").Disconnect;
@@ -1 +1 @@
1
- {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/dapp-api/controller.ts"],"names":[],"mappings":"AAGA,OAAO,EAEH,WAAW,EAEd,MAAM,kCAAkC,CAAA;AAczC,OAAO,EAAE,KAAK,EAAe,MAAM,mCAAmC,CAAA;AAQtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AAC5E,OAAO,EAAE,UAAU,IAAI,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACpE,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAK7B,eAAO,MAAM,cAAc,GACvB,YAAY,gBAAgB,EAC5B,SAAS,MAAM,EACf,SAAS,MAAM,EACf,OAAO,KAAK,EACZ,qBAAqB,mBAAmB,EACxC,SAAS,MAAM,EACf,QAAQ,MAAM,GAAG,IAAI,EACrB,UAAU,WAAW;;;;;;;;;;;;;;;;CA0YxB,CAAA"}
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/dapp-api/controller.ts"],"names":[],"mappings":"AAGA,OAAO,EAEH,WAAW,EAEd,MAAM,kCAAkC,CAAA;AAczC,OAAO,EAAE,KAAK,EAAe,MAAM,mCAAmC,CAAA;AAQtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AAC5E,OAAO,EAAE,UAAU,IAAI,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACpE,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAK7B,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAG9D,MAAM,WAAW,kBAAkB;IAC/B,cAAc,EAAE,cAAc,CAAA;CACjC;AAED,eAAO,MAAM,cAAc,GACvB,YAAY,gBAAgB,EAC5B,SAAS,MAAM,EACf,SAAS,MAAM,EACf,OAAO,KAAK,EACZ,qBAAqB,mBAAmB,EACxC,SAAS,MAAM,EACf,QAAQ,MAAM,GAAG,IAAI,EACrB,MAAM,kBAAkB,EACxB,UAAU,WAAW;;;;;;;;;;;;;;;;CA2cxB,CAAA"}
@@ -5,9 +5,17 @@ import buildController from './rpc-gen/index.js';
5
5
  import { LedgerClient, } from '@canton-network/core-ledger-client';
6
6
  import { v4 } from 'uuid';
7
7
  import { networkStatus, ledgerPrepareParams, logDynamically } from '../utils.js';
8
+ import { TransactionService } from '../ledger/transaction-service.js';
8
9
  import { rpcErrors } from '@canton-network/core-rpc-errors';
9
- export const dappController = (kernelInfo, dappUrl, userUrl, store, notificationService, _logger, origin, context) => {
10
+ export const dappController = (kernelInfo, dappUrl, userUrl, store, notificationService, _logger, origin, deps, context) => {
10
11
  const logger = _logger.child({ component: 'dapp-controller' });
12
+ function assertActAsPartiesBelongToUser(actAs, wallets) {
13
+ for (const party of actAs) {
14
+ if (wallets.find((w) => w.partyId === party) === undefined) {
15
+ throw rpcErrors.invalidRequest(`Acting party ${party} does not belong to user`);
16
+ }
17
+ }
18
+ }
11
19
  return buildController({
12
20
  connect: async () => {
13
21
  if (!context || !(await store.getSession())) {
@@ -127,54 +135,63 @@ export const dappController = (kernelInfo, dappUrl, userUrl, store, notification
127
135
  return result;
128
136
  },
129
137
  prepareExecute: async (params) => {
130
- const wallet = await store.getPrimaryWallet();
138
+ const primaryWallet = await store.getPrimaryWallet();
131
139
  const wallets = await store.getWallets();
132
140
  const network = await store.getCurrentNetwork();
133
141
  if (context === undefined) {
134
142
  throw new Error('Unauthenticated context');
135
143
  }
136
- if (wallet === undefined) {
137
- throw new Error('No primary wallet found');
138
- }
139
- let userId = context.userId;
144
+ // determine user ID
145
+ const gatewayUserId = context.userId;
146
+ let ledgerUserId = context.userId;
140
147
  const accessTokenProvider = AuthTokenProvider.fromToken(context.accessToken, logger);
141
148
  if (context?.isApiKey) {
142
149
  logger.info('Authenticated with API Key, fetching m2m token for ledger access');
143
- userId = context.ledgerUserId;
150
+ ledgerUserId = context.ledgerUserId;
151
+ }
152
+ // determine party ID
153
+ let actAs = params.actAs || [];
154
+ if (actAs.length === 0) {
155
+ if (!primaryWallet) {
156
+ throw new Error('No primary wallet found. Create or sync a wallet and set it as primary before prepareExecute.');
157
+ }
158
+ actAs = [primaryWallet.partyId];
159
+ }
160
+ assertActAsPartiesBelongToUser(actAs, wallets);
161
+ // determine wallet
162
+ const wallet = wallets.find((w) => w.partyId === actAs[0]);
163
+ if (wallet === undefined) {
164
+ throw new Error('No wallet found for the first acting party. Create or sync a wallet and set it as primary before prepareExecute.');
144
165
  }
145
166
  const ledgerClient = new LedgerClient({
146
167
  baseUrl: new URL(network.ledgerApi.baseUrl),
147
168
  logger,
148
169
  accessTokenProvider,
149
170
  });
150
- const notifier = notificationService.getNotifier(userId);
171
+ const notifier = notificationService.getNotifier(gatewayUserId);
151
172
  const commandId = params.commandId || v4();
152
173
  const transactionId = v4();
153
174
  notifier.emit('txChanged', { status: 'pending', commandId });
154
175
  const synchronizerId = network.synchronizerId ??
155
176
  (await ledgerClient.getSynchronizerId());
156
- let actAs = [wallet.partyId];
157
- if (params.actAs && params.actAs.length > 0) {
158
- actAs = params.actAs;
159
- for (const actingParty of actAs) {
160
- if (wallets.find((w) => w.partyId === actingParty) ===
161
- undefined) {
162
- throw rpcErrors.invalidRequest(`Acting party ${actingParty} does not belong to user`);
163
- }
164
- }
165
- }
166
177
  logDynamically(logger, 'prepareExecute: Submitting request to ledger', {
167
178
  info: { transactionId },
168
- debug: { commandId, userId, actAs },
179
+ debug: {
180
+ commandId,
181
+ gatewayUserId,
182
+ ledgerUserId,
183
+ actAs,
184
+ params,
185
+ },
169
186
  });
170
- const actAsParty = params.actAs || [wallet.partyId];
171
- const prepared = await prepareSubmission(context.userId, actAsParty, synchronizerId, params, ledgerClient);
187
+ const prepared = await prepareSubmission(ledgerUserId, actAs, synchronizerId, params, ledgerClient);
172
188
  logDynamically(logger, 'prepareExecute: Received response from ledger', {
173
189
  info: { transactionId },
174
190
  debug: {
175
191
  commandId,
176
- userId,
177
- partyId: actAsParty,
192
+ gatewayUserId,
193
+ ledgerUserId,
194
+ actAs,
178
195
  prepared,
179
196
  },
180
197
  });
@@ -189,18 +206,45 @@ export const dappController = (kernelInfo, dappUrl, userUrl, store, notification
189
206
  createdAt: new Date(),
190
207
  };
191
208
  logger.info({
192
- actAs: params.actAs || [wallet.partyId],
209
+ actAs,
193
210
  readAs: params.readAs || [],
194
- userId,
211
+ gatewayUserId,
212
+ ledgerUserId,
195
213
  commandId,
196
214
  commands: params.commands?.[0],
197
215
  confirmationRequestTrafficCostEstimation: prepared.costEstimation
198
216
  ?.confirmationRequestTrafficCostEstimation,
199
217
  }, 'prepared transaction traffic estimation');
200
218
  await store.setTransaction(transaction);
219
+ const approveUrl = `${userUrl}/approve/index.html?transactionId=${transactionId}&commandId=${commandId}&closeafteraction`;
220
+ if (context.isApiKey) {
221
+ logger.info({
222
+ gatewayUserId,
223
+ ledgerUserId,
224
+ commandId,
225
+ transactionId,
226
+ signingProviderId: wallet.signingProviderId,
227
+ }, 'Service account straight-through prepare/sign/execute');
228
+ const transactionService = new TransactionService(store, logger, deps.signingDrivers, notifier);
229
+ try {
230
+ await transactionService.signAndExecute(context, network, wallet, transaction);
231
+ }
232
+ catch (error) {
233
+ logger.error({
234
+ err: error,
235
+ gatewayUserId,
236
+ ledgerUserId,
237
+ commandId,
238
+ transactionId,
239
+ actAs,
240
+ signingProviderId: wallet.signingProviderId,
241
+ }, 'Service account sign/execute failed after prepare');
242
+ throw error;
243
+ }
244
+ }
201
245
  return {
202
246
  // closeafteraction query param flag makes approving or deleting tx close the popup
203
- userUrl: `${userUrl}/approve/index.html?transactionId=${transactionId}&commandId=${commandId}&closeafteraction`,
247
+ userUrl: approveUrl,
204
248
  };
205
249
  },
206
250
  status: async () => {
@@ -107,8 +107,8 @@ async function createStore(logger, context, options = {}) {
107
107
  }
108
108
  return store;
109
109
  }
110
- function createController(store, notificationService, logger, context, requestOrigin = origin) {
111
- return dappController(kernelInfo, dappUrl, userUrl, store, notificationService, logger, requestOrigin, context);
110
+ function createController(store, notificationService, logger, context, requestOrigin = origin, deps) {
111
+ return dappController(kernelInfo, dappUrl, userUrl, store, notificationService, logger, requestOrigin, deps || { signingDrivers: {} }, context);
112
112
  }
113
113
  describe('dappController', () => {
114
114
  let logger;
@@ -1,9 +1,10 @@
1
1
  import express from 'express';
2
2
  import { Logger } from 'pino';
3
3
  import { Store } from '@canton-network/core-wallet-store';
4
- import { AuthService, AuthAware } from '@canton-network/core-wallet-auth';
4
+ import { AuthAware } from '@canton-network/core-wallet-auth';
5
5
  import { Server } from 'http';
6
6
  import { NotificationService } from '../notification/NotificationService.js';
7
7
  import { KernelInfo, ServerConfig } from '../config/Config.js';
8
- export declare const dapp: (route: string, app: express.Express, logger: Logger, server: Server, kernelInfo: KernelInfo, dappUrl: string, userUrl: string, serverConfig: ServerConfig, notificationService: NotificationService, authService: AuthService, store: Store & AuthAware<Store>) => Server<typeof import("node:http").IncomingMessage, typeof import("node:http").ServerResponse>;
8
+ import { DappControllerDeps } from './controller.js';
9
+ export declare const dapp: (route: string, app: express.Express, logger: Logger, server: Server, kernelInfo: KernelInfo, dappUrl: string, userUrl: string, serverConfig: ServerConfig, notificationService: NotificationService, store: Store & AuthAware<Store>, controllerDeps: DappControllerDeps) => Server<typeof import("node:http").IncomingMessage, typeof import("node:http").ServerResponse>;
9
10
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dapp-api/server.ts"],"names":[],"mappings":"AAGA,OAAO,OAAO,MAAM,SAAS,CAAA;AAG7B,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAG7B,OAAO,EAAE,KAAK,EAAE,MAAM,mCAAmC,CAAA;AACzD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAA;AACzE,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAC7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AAC5E,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAO9D,eAAO,MAAM,IAAI,GACb,OAAO,MAAM,EACb,KAAK,OAAO,CAAC,OAAO,EACpB,QAAQ,MAAM,EACd,QAAQ,MAAM,EACd,YAAY,UAAU,EACtB,SAAS,MAAM,EACf,SAAS,MAAM,EACf,cAAc,YAAY,EAC1B,qBAAqB,mBAAmB,EACxC,aAAa,WAAW,EACxB,OAAO,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,kGA4FlC,CAAA"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dapp-api/server.ts"],"names":[],"mappings":"AAGA,OAAO,OAAO,MAAM,SAAS,CAAA;AAG7B,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAG7B,OAAO,EAAE,KAAK,EAAE,MAAM,mCAAmC,CAAA;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAA;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAC7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AAC5E,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAOpD,eAAO,MAAM,IAAI,GACb,OAAO,MAAM,EACb,KAAK,OAAO,CAAC,OAAO,EACpB,QAAQ,MAAM,EACd,QAAQ,MAAM,EACd,YAAY,UAAU,EACtB,SAAS,MAAM,EACf,SAAS,MAAM,EACf,cAAc,YAAY,EAC1B,qBAAqB,mBAAmB,EACxC,OAAO,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,EAC/B,gBAAgB,kBAAkB,kGA6FrC,CAAA"}
@@ -7,7 +7,7 @@ function writeSSE(res, event, data) {
7
7
  res.write(`event: ${event}\n`);
8
8
  res.write(`data: ${JSON.stringify(data)}\n\n`);
9
9
  }
10
- export const dapp = (route, app, logger, server, kernelInfo, dappUrl, userUrl, serverConfig, notificationService, authService, store) => {
10
+ export const dapp = (route, app, logger, server, kernelInfo, dappUrl, userUrl, serverConfig, notificationService, store, controllerDeps) => {
11
11
  app.use(cors({
12
12
  origin: serverConfig.allowedOrigins,
13
13
  }));
@@ -67,7 +67,7 @@ export const dapp = (route, app, logger, server, kernelInfo, dappUrl, userUrl, s
67
67
  app.use(route, (req, res, next) => {
68
68
  const origin = req.headers.origin ?? null;
69
69
  jsonRpcHandler({
70
- controller: dappController(kernelInfo, dappUrl, userUrl, store.withAuthContext(req.authContext), notificationService, logger, origin, req.authContext),
70
+ controller: dappController(kernelInfo, dappUrl, userUrl, store.withAuthContext(req.authContext), notificationService, logger, origin, controllerDeps, req.authContext),
71
71
  logger,
72
72
  })(req, res, next);
73
73
  });
@@ -11,11 +11,6 @@ import { NotificationService } from '../notification/NotificationService.js';
11
11
  import { pino } from 'pino';
12
12
  import { sink } from 'pino-test';
13
13
  import { createServer } from 'http';
14
- const authService = {
15
- verifyToken: async () => {
16
- return new Promise((resolve) => resolve({ userId: 'user123', accessToken: 'token123' }));
17
- },
18
- };
19
14
  const configPath = '../test/config.json';
20
15
  const config = ConfigUtils.loadConfigFile(configPath);
21
16
  const store = new StoreInternal(config.bootstrap, pino(sink()));
@@ -26,7 +21,7 @@ test('call connect rpc', async () => {
26
21
  app.use(express.json());
27
22
  const server = createServer(app);
28
23
  const { dappApiUrl, publicUrl } = deriveUrls(config);
29
- const response = await request(dapp('/api/v0/dapp', app, pino(sink()), server, config.kernel, dappApiUrl, publicUrl, config.server, notificationService, authService, store))
24
+ const response = await request(dapp('/api/v0/dapp', app, pino(sink()), server, config.kernel, dappApiUrl, publicUrl, config.server, notificationService, store, { signingDrivers: {} }))
30
25
  .post('/api/v0/dapp')
31
26
  .send({ jsonrpc: '2.0', id: 0, method: 'connect', params: [] })
32
27
  .set('Accept', 'application/json');
@@ -16,6 +16,9 @@ declare const _default: {
16
16
  requestRateLimit: number;
17
17
  trustProxy: false;
18
18
  admin: string;
19
+ signingWorker: {
20
+ pollInterval: number;
21
+ };
19
22
  };
20
23
  signingStore: {
21
24
  connection: {
@@ -1 +1 @@
1
- {"version":3,"file":"example-config.d.ts","sourceRoot":"","sources":["../src/example-config.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAKA,wBAkGqB"}
1
+ {"version":3,"file":"example-config.d.ts","sourceRoot":"","sources":["../src/example-config.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAKA,wBAqGqB"}
@@ -18,6 +18,9 @@ export default {
18
18
  requestRateLimit: 10000,
19
19
  trustProxy: false,
20
20
  admin: 'sub',
21
+ signingWorker: {
22
+ pollInterval: 5000,
23
+ },
21
24
  },
22
25
  signingStore: {
23
26
  connection: {
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAyB7B,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAuMvC,wBAAsB,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,iBAoKhE"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAyB7B,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAyMvC,wBAAsB,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,iBAmLhE"}
package/dist/init.js CHANGED
@@ -23,8 +23,10 @@ import { sessionHandler } from './middleware/sessionHandler.js';
23
23
  import { NotificationService } from './notification/NotificationService.js';
24
24
  import { sql } from 'kysely';
25
25
  import { Env } from './env.js';
26
+ import { SigningWorker } from './signing/signing-worker.js';
26
27
  import { apiKeyAuth } from './middleware/apiKeyAuth.js';
27
28
  let isReady = false;
29
+ let signingWorker;
28
30
  async function initializeDatabase(config, logger) {
29
31
  logger.info('Checking for database migrations...');
30
32
  let exists = true;
@@ -231,8 +233,21 @@ export async function initialize(opts, logger) {
231
233
  app.use('/api/*splat', express.json(), preAuthRateLimit, apiKeyAuth(store, config.server.dappPath, logger.child({ component: 'ApiKeyHandler' })), jwtAuth(authService, logger.child({ component: 'JwtHandler' })), postAuthRateLimit, sessionHandler(store, allowedPaths, logger.child({ component: 'SessionHandler' })));
232
234
  logger.info({ ...config.server, port }, 'Server configuration');
233
235
  const kernelInfo = config.kernel;
236
+ const signingWorkerLogger = logger.child({
237
+ component: 'SigningWorker',
238
+ });
239
+ signingWorker = new SigningWorker({
240
+ intervalMs: config.server.signingWorker.pollInterval,
241
+ signingDrivers: drivers,
242
+ store,
243
+ notificationService,
244
+ logger: signingWorkerLogger,
245
+ });
246
+ signingWorker.start();
234
247
  // register dapp API handlers
235
- dapp(config.server.dappPath, app, logger, server, kernelInfo, dappApiUrl, publicUrl, config.server, notificationService, authService, store);
248
+ dapp(config.server.dappPath, app, logger, server, kernelInfo, dappApiUrl, publicUrl, config.server, notificationService, store, {
249
+ signingDrivers: drivers,
250
+ });
236
251
  // register user API handlers
237
252
  user(config.server.userPath, app, logger, kernelInfo, publicUrl, notificationService, drivers, store, config.server.admin);
238
253
  // register web handler
@@ -7,6 +7,7 @@ import { ExecuteParams, ExecuteResult, SignParams } from '../user-api/rpc-gen/ty
7
7
  import { UserId } from '../dapp-api/rpc-gen/typings.js';
8
8
  import { Notifier } from '../notification/NotificationService.js';
9
9
  import { AuthContext } from '@canton-network/core-wallet-auth';
10
+ export type SignAndExecuteResult = SignResult | ExecuteResult;
10
11
  export declare class TransactionService {
11
12
  private store;
12
13
  private logger;
@@ -15,6 +16,7 @@ export declare class TransactionService {
15
16
  constructor(store: Store, logger: Logger, signingDrivers: SigningDrivers | undefined, notifier: Notifier);
16
17
  sign(authContext: AuthContext, wallet: Wallet, signParams: SignParams): Promise<SignResult>;
17
18
  execute(userId: UserId, wallet: Wallet, transaction: Transaction, executeParams?: ExecuteParams, ledgerClient?: LedgerClient, network?: Network): Promise<ExecuteResult>;
19
+ signAndExecute(authContext: AuthContext, network: Network, wallet: Wallet, transaction: Transaction): Promise<SignAndExecuteResult>;
18
20
  private loadPreparedTransactionForSigning;
19
21
  private signWithParticipant;
20
22
  private signWithWalletKernel;
@@ -1 +1 @@
1
- {"version":3,"file":"transaction-service.d.ts","sourceRoot":"","sources":["../../src/ledger/transaction-service.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AACjE,OAAO,EACH,KAAK,EACL,WAAW,EACX,MAAM,EACN,OAAO,EACV,MAAM,mCAAmC,CAAA;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAA;AAOhE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AACnE,OAAO,EACH,aAAa,EACb,aAAa,EACb,UAAU,EAEb,MAAM,gCAAgC,CAAA;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,gCAAgC,CAAA;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,wCAAwC,CAAA;AAMjE,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAW9D,qBAAa,kBAAkB;IAEvB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,QAAQ;gBAHR,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,cAAc,YAAK,EACnC,QAAQ,EAAE,QAAQ;IAGjB,IAAI,CACb,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,GACvB,OAAO,CAAC,UAAU,CAAC;IAiDf,OAAO,CACV,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,WAAW,EACxB,aAAa,CAAC,EAAE,aAAa,EAC7B,YAAY,CAAC,EAAE,YAAY,EAC3B,OAAO,CAAC,EAAE,OAAO,GAClB,OAAO,CAAC,aAAa,CAAC;YA6DX,iCAAiC;IAW/C,OAAO,CAAC,mBAAmB;YASb,oBAAoB;YAyDpB,mBAAmB;YAiHnB,kBAAkB;IAqHhC;;;;;OAKG;YACW,YAAY;IA2G1B;;;;OAIG;YACW,eAAe;YAgCf,sBAAsB;YAoDtB,mBAAmB;CAiEpC"}
1
+ {"version":3,"file":"transaction-service.d.ts","sourceRoot":"","sources":["../../src/ledger/transaction-service.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AACjE,OAAO,EACH,KAAK,EACL,WAAW,EACX,MAAM,EACN,OAAO,EACV,MAAM,mCAAmC,CAAA;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAA;AAOhE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AACnE,OAAO,EACH,aAAa,EACb,aAAa,EACb,UAAU,EAEb,MAAM,gCAAgC,CAAA;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,gCAAgC,CAAA;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,wCAAwC,CAAA;AAMjE,OAAO,EACH,WAAW,EAEd,MAAM,kCAAkC,CAAA;AAEzC,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG,aAAa,CAAA;AAW7D,qBAAa,kBAAkB;IAEvB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,QAAQ;gBAHR,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,cAAc,YAAK,EACnC,QAAQ,EAAE,QAAQ;IAGjB,IAAI,CACb,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,GACvB,OAAO,CAAC,UAAU,CAAC;IAiDf,OAAO,CACV,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,WAAW,EACxB,aAAa,CAAC,EAAE,aAAa,EAC7B,YAAY,CAAC,EAAE,YAAY,EAC3B,OAAO,CAAC,EAAE,OAAO,GAClB,OAAO,CAAC,aAAa,CAAC;IAiEZ,cAAc,CACvB,WAAW,EAAE,WAAW,EACxB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,WAAW,GACzB,OAAO,CAAC,oBAAoB,CAAC;YAyDlB,iCAAiC;IAkB/C,OAAO,CAAC,mBAAmB;YASb,oBAAoB;YAyDpB,mBAAmB;YAiHnB,kBAAkB;IAqHhC;;;;;OAKG;YACW,YAAY;IA2G1B;;;;OAIG;YACW,eAAe;YAgCf,sBAAsB;YAoDtB,mBAAmB;CAiEpC"}
@@ -1,7 +1,9 @@
1
1
  // Copyright (c) 2025-2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
+ import { LedgerClient } from '@canton-network/core-ledger-client';
3
4
  import { SigningProvider, } from '@canton-network/core-signing-lib';
4
5
  import { ledgerPrepareParams, logDynamically, } from '../utils.js';
6
+ import { AuthTokenProvider, } from '@canton-network/core-wallet-auth';
5
7
  function handleSigningError(result) {
6
8
  if ('error' in result) {
7
9
  throw new Error(`Error from signing driver: ${result.error_description}`);
@@ -45,6 +47,9 @@ export class TransactionService {
45
47
  }
46
48
  }
47
49
  execute(userId, wallet, transaction, executeParams, ledgerClient, network) {
50
+ if (transaction.status === 'executed') {
51
+ throw new Error('Transaction is already executed.');
52
+ }
48
53
  switch (wallet.signingProviderId) {
49
54
  case SigningProvider.PARTICIPANT: {
50
55
  try {
@@ -82,11 +87,46 @@ export class TransactionService {
82
87
  throw new Error(`Unsupported signing provider: ${wallet.signingProviderId}`);
83
88
  }
84
89
  }
90
+ async signAndExecute(authContext, network, wallet, transaction) {
91
+ const signParams = {
92
+ transactionId: transaction.id,
93
+ partyId: wallet.partyId,
94
+ };
95
+ const signResult = await this.sign(authContext, wallet, signParams);
96
+ if (signResult.status === 'pending') {
97
+ return signResult;
98
+ }
99
+ if (signResult.status !== 'signed') {
100
+ throw new Error(`Service account signing failed with status: ${signResult.status}`);
101
+ }
102
+ if (!('signature' in signResult) ||
103
+ signResult.signature === undefined) {
104
+ throw new Error('Service account signing did not return a signature');
105
+ }
106
+ const ledgerClient = new LedgerClient({
107
+ baseUrl: new URL(network.ledgerApi.baseUrl),
108
+ logger: this.logger,
109
+ accessTokenProvider: AuthTokenProvider.fromToken(authContext.accessToken, this.logger),
110
+ });
111
+ const executeParams = {
112
+ transactionId: transaction.id,
113
+ partyId: wallet.partyId,
114
+ signature: signResult.signature,
115
+ signedBy: signResult.signedBy,
116
+ };
117
+ const userId = authContext.isApiKey
118
+ ? authContext.ledgerUserId
119
+ : authContext.userId;
120
+ return this.execute(userId, wallet, transaction, executeParams, ledgerClient, network);
121
+ }
85
122
  async loadPreparedTransactionForSigning(transactionId) {
86
123
  const existingTx = await this.store.getTransaction(transactionId);
87
124
  if (!existingTx) {
88
125
  throw new Error(`Transaction not found with id: ${transactionId}`);
89
126
  }
127
+ if (existingTx.status !== 'pending') {
128
+ throw new Error(`Cannot sign an already ${existingTx.status} transaction`);
129
+ }
90
130
  return existingTx;
91
131
  }
92
132
  signWithParticipant(wallet) {
@@ -353,4 +353,38 @@ describe('TransactionService', () => {
353
353
  });
354
354
  });
355
355
  });
356
+ describe('signAndExecute', () => {
357
+ const participantWallet = walletWithProvider(SigningProvider.PARTICIPANT);
358
+ it('signs and executes when signing completes synchronously', async () => {
359
+ const service = createService(createStore(), {}, notifier, logger);
360
+ const executeSpy = vi
361
+ .spyOn(service, 'execute')
362
+ .mockResolvedValue({ commandId: 'cmd-1' });
363
+ vi.spyOn(service, 'sign').mockResolvedValue({
364
+ status: 'signed',
365
+ signature: 'sig',
366
+ signedBy: 'namespace',
367
+ partyId: participantWallet.partyId,
368
+ });
369
+ const result = await service.signAndExecute(authContext, network, participantWallet, pendingTransaction);
370
+ expect(result).toEqual({ commandId: 'cmd-1' });
371
+ expect(executeSpy).toHaveBeenCalled();
372
+ });
373
+ it('returns pending sign result without executing', async () => {
374
+ const service = createService(createStore(), {}, notifier, logger);
375
+ const executeSpy = vi.spyOn(service, 'execute');
376
+ vi.spyOn(service, 'sign').mockResolvedValue({
377
+ status: 'pending',
378
+ externalTxId: 'ext-1',
379
+ partyId: participantWallet.partyId,
380
+ });
381
+ const result = await service.signAndExecute(authContext, network, participantWallet, pendingTransaction);
382
+ expect(result).toEqual({
383
+ status: 'pending',
384
+ externalTxId: 'ext-1',
385
+ partyId: participantWallet.partyId,
386
+ });
387
+ expect(executeSpy).not.toHaveBeenCalled();
388
+ });
389
+ });
356
390
  });
@@ -1 +1 @@
1
- {"version":3,"file":"apiKeyAuth.d.ts","sourceRoot":"","sources":["../../src/middleware/apiKeyAuth.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC9D,OAAO,EACH,SAAS,EAGZ,MAAM,kCAAkC,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAC7B,OAAO,EAAE,KAAK,EAAE,MAAM,mCAAmC,CAAA;AAMzD,wBAAgB,UAAU,CACtB,KAAK,EAAE,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,EAC/B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,IAEA,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,wDAuHhE"}
1
+ {"version":3,"file":"apiKeyAuth.d.ts","sourceRoot":"","sources":["../../src/middleware/apiKeyAuth.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC9D,OAAO,EACH,SAAS,EAGZ,MAAM,kCAAkC,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAC7B,OAAO,EAAE,KAAK,EAAE,MAAM,mCAAmC,CAAA;AAMzD,wBAAgB,UAAU,CACtB,KAAK,EAAE,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,EAC/B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,IAEA,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,wDAiIhE"}
@@ -48,6 +48,7 @@ export function apiKeyAuth(store, dappApiPath, logger) {
48
48
  const network = await authStore.getNetwork(matchingKey.networkId);
49
49
  const idp = await authStore.getIdp(network.identityProviderId);
50
50
  if (!network.serviceAccountAuth) {
51
+ logger.debug({ network }, 'Network does not have a service account configured');
51
52
  return res.status(401).json(jsonRpcResponse(reqId, {
52
53
  error: {
53
54
  code: rpcErrors.invalidRequest().code,
@@ -56,6 +57,7 @@ export function apiKeyAuth(store, dappApiPath, logger) {
56
57
  }));
57
58
  }
58
59
  if (network.serviceAccountAuth.method !== 'client_credentials') {
60
+ logger.debug({ network }, 'Network does not have a service account configured with client_credentials method');
59
61
  return res.status(401).json(jsonRpcResponse(reqId, {
60
62
  error: {
61
63
  code: rpcErrors.invalidRequest().code,
@@ -0,0 +1,16 @@
1
+ import { Logger } from 'pino';
2
+ import { AuthAware, AuthContext, AuthTokenProvider } from '@canton-network/core-wallet-auth';
3
+ import { Network, Store } from '@canton-network/core-wallet-store';
4
+ export type AccessTokenProviderFactory = (network: Network) => Promise<AuthTokenProvider>;
5
+ export interface AutomationRunContext {
6
+ authContext: AuthContext;
7
+ scopedStore: Store;
8
+ network: Network;
9
+ }
10
+ /**
11
+ * Resolves auth for background completion of pending external transactions.
12
+ * Service-account networks can run without a pre-existing session by minting
13
+ * an access token. Interactive networks still require a valid stored session.
14
+ */
15
+ export declare function resolveAutomationRunContext(bootstrapStore: Store & AuthAware<Store>, userId: string, networkId: string, logger: Logger): Promise<AutomationRunContext | undefined>;
16
+ //# sourceMappingURL=service-account-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-account-session.d.ts","sourceRoot":"","sources":["../../src/signing/service-account-session.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAC7B,OAAO,EACH,SAAS,EACT,WAAW,EACX,iBAAiB,EACpB,MAAM,kCAAkC,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,mCAAmC,CAAA;AAElE,MAAM,MAAM,0BAA0B,GAAG,CACrC,OAAO,EAAE,OAAO,KACf,OAAO,CAAC,iBAAiB,CAAC,CAAA;AAE/B,MAAM,WAAW,oBAAoB;IACjC,WAAW,EAAE,WAAW,CAAA;IACxB,WAAW,EAAE,KAAK,CAAA;IAClB,OAAO,EAAE,OAAO,CAAA;CACnB;AAED;;;;GAIG;AACH,wBAAsB,2BAA2B,CAC7C,cAAc,EAAE,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,EACxC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACf,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAkC3C"}
@@ -0,0 +1,25 @@
1
+ // Copyright (c) 2025-2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { AuthTokenProvider, } from '@canton-network/core-wallet-auth';
4
+ /**
5
+ * Resolves auth for background completion of pending external transactions.
6
+ * Service-account networks can run without a pre-existing session by minting
7
+ * an access token. Interactive networks still require a valid stored session.
8
+ */
9
+ export async function resolveAutomationRunContext(bootstrapStore, userId, networkId, logger) {
10
+ const network = await bootstrapStore.getNetwork(networkId);
11
+ if (!network) {
12
+ logger.warn({ userId, networkId }, 'Skipping signing worker tick: network not found');
13
+ return undefined;
14
+ }
15
+ if (!network.serviceAccountAuth) {
16
+ logger.debug({ userId, networkId }, 'Skipping signing worker tick: network is not service-account');
17
+ return undefined;
18
+ }
19
+ const idp = await bootstrapStore.getIdp(network.identityProviderId);
20
+ const provider = AuthTokenProvider.fromGatewayConfig(idp, network.serviceAccountAuth, logger);
21
+ const authContext = await provider.getAuthContext();
22
+ const scopedStore = bootstrapStore.withAuthContext(authContext);
23
+ logger.debug({ userId, networkId }, 'Signing worker prepared service account session');
24
+ return { authContext, scopedStore, network };
25
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=service-account-session.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-account-session.test.d.ts","sourceRoot":"","sources":["../../src/signing/service-account-session.test.ts"],"names":[],"mappings":""}