@canton-network/wallet-gateway-remote 0.17.3 → 0.19.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 (70) hide show
  1. package/README.md +37 -0
  2. package/dist/config/Config.d.ts +19 -15
  3. package/dist/config/Config.d.ts.map +1 -1
  4. package/dist/config/Config.js +7 -3
  5. package/dist/config/Config.test.js +8 -8
  6. package/dist/config/ConfigUtils.js +6 -6
  7. package/dist/dapp-api/controller.d.ts +7 -6
  8. package/dist/dapp-api/controller.d.ts.map +1 -1
  9. package/dist/dapp-api/controller.js +75 -50
  10. package/dist/dapp-api/rpc-gen/index.d.ts +21 -18
  11. package/dist/dapp-api/rpc-gen/index.d.ts.map +1 -1
  12. package/dist/dapp-api/rpc-gen/index.js +7 -6
  13. package/dist/dapp-api/rpc-gen/typings.d.ts +69 -101
  14. package/dist/dapp-api/rpc-gen/typings.d.ts.map +1 -1
  15. package/dist/dapp-api/server.d.ts.map +1 -1
  16. package/dist/dapp-api/server.js +52 -45
  17. package/dist/dapp-api/server.test.js +1 -5
  18. package/dist/example-config.d.ts +3 -0
  19. package/dist/example-config.d.ts.map +1 -1
  20. package/dist/example-config.js +4 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +11 -1
  23. package/dist/init.d.ts.map +1 -1
  24. package/dist/init.js +52 -5
  25. package/dist/ledger/wallet-sync-service.d.ts.map +1 -1
  26. package/dist/ledger/wallet-sync-service.js +30 -20
  27. package/dist/ledger/wallet-sync-service.test.js +339 -0
  28. package/dist/middleware/jwtAuth.d.ts.map +1 -1
  29. package/dist/middleware/jwtAuth.js +3 -1
  30. package/dist/user-api/controller.d.ts +3 -1
  31. package/dist/user-api/controller.d.ts.map +1 -1
  32. package/dist/user-api/controller.js +128 -47
  33. package/dist/user-api/rpc-gen/index.d.ts +6 -0
  34. package/dist/user-api/rpc-gen/index.d.ts.map +1 -1
  35. package/dist/user-api/rpc-gen/index.js +2 -0
  36. package/dist/user-api/rpc-gen/typings.d.ts +37 -24
  37. package/dist/user-api/rpc-gen/typings.d.ts.map +1 -1
  38. package/dist/user-api/server.d.ts +1 -1
  39. package/dist/user-api/server.d.ts.map +1 -1
  40. package/dist/user-api/server.js +2 -2
  41. package/dist/user-api/server.test.js +1 -1
  42. package/dist/utils.d.ts +0 -1
  43. package/dist/utils.d.ts.map +1 -1
  44. package/dist/web/frontend/404/index.html +2 -3
  45. package/dist/web/frontend/approve/index.html +4 -5
  46. package/dist/web/frontend/assets/404-BQsvObfu.js +8 -0
  47. package/dist/web/frontend/assets/approve-BLORPbEM.js +17 -0
  48. package/dist/web/frontend/assets/{callback-B-wmBVDs.js → callback-C7kRcm3U.js} +1 -1
  49. package/dist/web/frontend/assets/index-DPbaEEZi.js +1722 -0
  50. package/dist/web/frontend/assets/{decode-CLJkuAIr.js → index-NP2zGQqX.js} +1 -1
  51. package/dist/web/frontend/assets/login-p5H_f9In.js +7 -0
  52. package/dist/web/frontend/assets/settings-89wZp5vZ.js +37 -0
  53. package/dist/web/frontend/assets/{state-CziDYJOu.js → state-TjGUBiUh.js} +1 -1
  54. package/dist/web/frontend/assets/transactions-BnROcA2_.js +28 -0
  55. package/dist/web/frontend/assets/wallets-TPeR2lS2.js +63 -0
  56. package/dist/web/frontend/callback/index.html +2 -3
  57. package/dist/web/frontend/index.html +1 -2
  58. package/dist/web/frontend/login/index.html +3 -4
  59. package/dist/web/frontend/settings/index.html +3 -4
  60. package/dist/web/frontend/transactions/index.html +4 -5
  61. package/dist/web/frontend/wallets/index.html +3 -4
  62. package/package.json +26 -21
  63. package/dist/web/frontend/assets/404-C5sXhYIJ.js +0 -16
  64. package/dist/web/frontend/assets/approve-DLsPdBvo.js +0 -227
  65. package/dist/web/frontend/assets/index-CxDOwxiY.css +0 -1
  66. package/dist/web/frontend/assets/index-Dfw8gEND.js +0 -1158
  67. package/dist/web/frontend/assets/login-BiJNU2kh.js +0 -186
  68. package/dist/web/frontend/assets/settings-B8ayu_0I.js +0 -28
  69. package/dist/web/frontend/assets/transactions-D2Yx5LYD.js +0 -140
  70. package/dist/web/frontend/assets/wallets-nCed0fPk.js +0 -266
package/dist/init.js CHANGED
@@ -21,6 +21,7 @@ import path from 'path';
21
21
  import { GATEWAY_VERSION } from './version.js';
22
22
  import { sessionHandler } from './middleware/sessionHandler.js';
23
23
  import { NotificationService } from './notification/NotificationService.js';
24
+ import { sql } from 'kysely';
24
25
  let isReady = false;
25
26
  async function initializeDatabase(config, logger) {
26
27
  logger.info('Checking for database migrations...');
@@ -28,6 +29,25 @@ async function initializeDatabase(config, logger) {
28
29
  if (config.store.connection.type === 'sqlite') {
29
30
  exists = existsSync(config.store.connection.database);
30
31
  }
32
+ if (config.store.connection.type === 'postgres') {
33
+ const db = connection({
34
+ ...config.store,
35
+ connection: { ...config.store.connection, database: 'postgres' },
36
+ });
37
+ const result = await sql
38
+ .raw(`select 1 from pg_database where datname='${config.store.connection.database}';`)
39
+ .execute(db);
40
+ const databaseExist = result.rows.length > 0;
41
+ if (!databaseExist) {
42
+ // Ignore error because postgres does not support `create database if nor exists` clause
43
+ await sql
44
+ .raw(`create database ${config.store.connection.database};`)
45
+ .execute(db)
46
+ .catch(() => { });
47
+ exists = false;
48
+ }
49
+ await db.destroy();
50
+ }
31
51
  const db = connection(config.store);
32
52
  const umzug = migrator(db);
33
53
  const pending = await umzug.pending();
@@ -42,7 +62,7 @@ async function initializeDatabase(config, logger) {
42
62
  // bootstrap database from config file if it did not exist before
43
63
  if (!exists) {
44
64
  logger.info('Bootstrapping database from config...');
45
- await bootstrap(db, config.store, logger);
65
+ await bootstrap(db, config.bootstrap, logger);
46
66
  }
47
67
  return new StoreSql(db, logger);
48
68
  }
@@ -52,6 +72,28 @@ async function initializeSigningDatabase(config, logger) {
52
72
  if (config.signingStore.connection.type === 'sqlite') {
53
73
  exists = existsSync(config.signingStore.connection.database);
54
74
  }
75
+ if (config.signingStore.connection.type === 'postgres') {
76
+ const db = signingConnection({
77
+ ...config.signingStore,
78
+ connection: {
79
+ ...config.signingStore.connection,
80
+ database: 'postgres',
81
+ },
82
+ });
83
+ const result = await sql
84
+ .raw(`select 1 from pg_database where datname='${config.signingStore.connection.database}';`)
85
+ .execute(db);
86
+ const databaseExist = result.rows.length > 0;
87
+ if (!databaseExist) {
88
+ // Ignore error because postgres does not support `create database if nor exists` clause
89
+ await sql
90
+ .raw(`create database ${config.signingStore.connection.database};`)
91
+ .execute(db)
92
+ .catch(() => { });
93
+ exists = false;
94
+ }
95
+ await db.destroy();
96
+ }
55
97
  const db = signingConnection(config.signingStore);
56
98
  const umzug = signingMigrator(db);
57
99
  const pending = await umzug.pending();
@@ -65,8 +107,8 @@ async function initializeSigningDatabase(config, logger) {
65
107
  }
66
108
  // bootstrap database from config file if it did not exist before
67
109
  if (!exists) {
68
- logger.info('Bootstrapping database from config...');
69
- await signingBootstrap(db, config.store, logger);
110
+ logger.info('Bootstrapping signing database from config...');
111
+ await signingBootstrap(db, config.signingStore, logger);
70
112
  }
71
113
  return new SigningStoreSql(db, logger);
72
114
  }
@@ -128,7 +170,12 @@ export async function initialize(opts, logger) {
128
170
  };
129
171
  const allowedPaths = {
130
172
  [config.server.dappPath]: ['*'],
131
- [config.server.userPath]: ['addSession', 'listNetworks', 'listIdps'],
173
+ [config.server.userPath]: [
174
+ 'addSession',
175
+ 'listNetworks',
176
+ 'listIdps',
177
+ 'getUser',
178
+ ],
132
179
  };
133
180
  app.use('/api/*splat', express.json());
134
181
  app.use('/api/*splat', rpcRateLimit);
@@ -138,7 +185,7 @@ export async function initialize(opts, logger) {
138
185
  // register dapp API handlers
139
186
  dapp(config.server.dappPath, app, logger, server, kernelInfo, dappApiUrl, publicUrl, config.server, notificationService, authService, store);
140
187
  // register user API handlers
141
- user(config.server.userPath, app, logger, kernelInfo, publicUrl, notificationService, drivers, store);
188
+ user(config.server.userPath, app, logger, kernelInfo, publicUrl, notificationService, drivers, store, config.server.admin);
142
189
  // register web handler
143
190
  web(app, server, userApiUrl);
144
191
  isReady = true;
@@ -1 +1 @@
1
- {"version":3,"file":"wallet-sync-service.d.ts","sourceRoot":"","sources":["../../src/ledger/wallet-sync-service.ts"],"names":[],"mappings":"AAGA,OAAO,EACH,YAAY,EAEf,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAC9D,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAA;AACjE,OAAO,EACH,sBAAsB,EACtB,eAAe,EAClB,MAAM,kCAAkC,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAA;AAEtE,MAAM,MAAM,gBAAgB,GAAG;IAC3B,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,OAAO,EAAE,MAAM,EAAE,CAAA;CACpB,CAAA;AAED,eAAO,MAAM,sBAAsB;;CAElC,CAAA;AAED,qBAAa,iBAAiB;IAEtB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,iBAAiB;IACzB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,cAAc;IAGtB,OAAO,CAAC,cAAc;gBARd,KAAK,EAAE,KAAK,EACZ,YAAY,EAAE,YAAY,EAC1B,iBAAiB,EAAE,YAAY,EAC/B,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,OAAO,CAC3B,MAAM,CAAC,eAAe,EAAE,sBAAsB,CAAC,CAClD,YAAK,EACE,cAAc,EAAE,sBAAsB;IAG5C,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;cAU3B,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAC5D;QACI,iBAAiB,EAAE,eAAe,CAAC,WAAW,CAAA;QAC9C,OAAO,EAAE,OAAO,CAAA;KACnB,GACD;QACI,iBAAiB,EAAE,OAAO,CACtB,eAAe,EACf,eAAe,CAAC,WAAW,CAC9B,CAAA;QACD,SAAS,EAAE,MAAM,CAAA;QACjB,OAAO,EAAE,OAAO,CAAA;KACnB,CACN;YA6Ha,mBAAmB;IAqC3B,kBAAkB,IAAI,OAAO,CAAC,OAAO,CAAC;IA6BtC,WAAW,IAAI,OAAO,CAAC,gBAAgB,CAAC;CA8HjD"}
1
+ {"version":3,"file":"wallet-sync-service.d.ts","sourceRoot":"","sources":["../../src/ledger/wallet-sync-service.ts"],"names":[],"mappings":"AAGA,OAAO,EACH,YAAY,EAEf,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAC9D,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAA;AACjE,OAAO,EACH,sBAAsB,EACtB,eAAe,EAClB,MAAM,kCAAkC,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAA;AAEtE,MAAM,MAAM,gBAAgB,GAAG;IAC3B,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,OAAO,EAAE,MAAM,EAAE,CAAA;CACpB,CAAA;AAED,eAAO,MAAM,sBAAsB;;CAElC,CAAA;AAED,qBAAa,iBAAiB;IAEtB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,iBAAiB;IACzB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,cAAc;IAGtB,OAAO,CAAC,cAAc;gBARd,KAAK,EAAE,KAAK,EACZ,YAAY,EAAE,YAAY,EAC1B,iBAAiB,EAAE,YAAY,EAC/B,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,OAAO,CAC3B,MAAM,CAAC,eAAe,EAAE,sBAAsB,CAAC,CAClD,YAAK,EACE,cAAc,EAAE,sBAAsB;IAG5C,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;cAU3B,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAC5D;QACI,iBAAiB,EAAE,eAAe,CAAC,WAAW,CAAA;QAC9C,OAAO,EAAE,OAAO,CAAA;KACnB,GACD;QACI,iBAAiB,EAAE,OAAO,CACtB,eAAe,EACf,eAAe,CAAC,WAAW,CAC9B,CAAA;QACD,SAAS,EAAE,MAAM,CAAA;QACjB,OAAO,EAAE,OAAO,CAAA;KACnB,CACN;YA6Ha,mBAAmB;IAqC3B,kBAAkB,IAAI,OAAO,CAAC,OAAO,CAAC;IA+BtC,WAAW,IAAI,OAAO,CAAC,gBAAgB,CAAC;CAsJjD"}
@@ -144,21 +144,19 @@ export class WalletSyncService {
144
144
  }
145
145
  async isWalletSyncNeeded() {
146
146
  try {
147
+ const network = await this.store.getCurrentNetwork();
147
148
  const existingWallets = await this.store.getWallets();
148
- // Check if there are any disabled wallets
149
149
  const hasDisabledWallets = existingWallets.some((w) => w.disabled);
150
150
  if (hasDisabledWallets) {
151
151
  return true;
152
152
  }
153
- // Check if there are parties on ledger that aren't in store
154
153
  const partiesWithRights = await this.getPartiesRightsMap();
155
154
  // Treat disabled wallets as if they don't exist, so they can be re-synced
156
155
  const enabledWallets = existingWallets.filter((w) => !w.disabled);
157
- const existingPartyIds = new Set(enabledWallets.map((w) => w.partyId));
158
- // Check if there are parties on ledger that aren't in store
159
- return partiesWithRights
160
- .keys()
161
- .some((party) => !existingPartyIds.has(party));
156
+ // Track by (partyId, networkId) combination to handle multi-hosted parties
157
+ const existingPartyNetworkPairs = new Set(enabledWallets.map((w) => `${w.partyId}:${w.networkId}`));
158
+ // Check if there are parties on ledger that aren't in store for this network
159
+ return Array.from(partiesWithRights.keys()).some((party) => !existingPartyNetworkPairs.has(`${party}:${network.id}`));
162
160
  }
163
161
  catch (err) {
164
162
  this.logger.error({ err }, 'Error checking if sync is needed');
@@ -171,17 +169,25 @@ export class WalletSyncService {
171
169
  try {
172
170
  const network = await this.store.getCurrentNetwork();
173
171
  this.logger.info(network, 'Current network');
174
- // Get existing parties from participant
175
172
  const partiesWithRights = await this.getPartiesRightsMap();
176
173
  // Add new Wallets given the found parties
174
+ // Only check wallets in the current network
177
175
  const existingWallets = await this.store.getWallets();
178
176
  this.logger.info(existingWallets, 'Existing wallets');
179
177
  // Treat disabled wallets as if they don't exist, so they can be re-synced
180
178
  const enabledWallets = existingWallets.filter((w) => !w.disabled);
181
- const existingPartyIdToSigningProvider = new Map(enabledWallets.map((w) => [w.partyId, w.signingProviderId]));
182
- const disabledPartyIds = new Set(existingWallets.filter((w) => w.disabled).map((w) => w.partyId));
179
+ // Track by (partyId, networkId) combination
180
+ const existingPartyNetworkToSigningProvider = new Map(enabledWallets.map((w) => [
181
+ `${w.partyId}:${w.networkId}`,
182
+ w.signingProviderId,
183
+ ]));
184
+ // Track disabled wallets by (partyId, networkId) combination
185
+ const disabledPartyNetworkPairs = new Set(existingWallets
186
+ .filter((w) => w.disabled)
187
+ .map((w) => `${w.partyId}:${w.networkId}`));
183
188
  // Resolve signing providers for all new parties
184
- const newParties = Array.from(partiesWithRights.keys()).filter((party) => !existingPartyIdToSigningProvider.has(party)
189
+ // Check if (partyId, networkId) combination already exists
190
+ const newParties = Array.from(partiesWithRights.keys()).filter((party) => !existingPartyNetworkToSigningProvider.has(`${party}:${network.id}`)
185
191
  // todo: filter on idp id
186
192
  );
187
193
  this.logger.info({ newParties }, 'Found new parties to sync with Wallet Gateway');
@@ -217,10 +223,14 @@ export class WalletSyncService {
217
223
  return wallet;
218
224
  }));
219
225
  // Remove disabled wallets that are being re-synced before adding them back
226
+ // Filter by (partyId, networkId) combination
220
227
  await Promise.all(newParticipantWallets
221
- .filter((wallet) => disabledPartyIds.has(wallet.partyId))
228
+ .filter((wallet) => disabledPartyNetworkPairs.has(`${wallet.partyId}:${wallet.networkId}`))
222
229
  .map((wallet) => {
223
- this.logger.info({ partyId: wallet.partyId }, 'Removing disabled wallet for re-sync');
230
+ this.logger.info({
231
+ partyId: wallet.partyId,
232
+ networkId: wallet.networkId,
233
+ }, 'Removing disabled wallet for re-sync');
224
234
  return this.store.removeWallet(wallet.partyId);
225
235
  }));
226
236
  await Promise.all(newParticipantWallets.map((wallet) => this.store.addWallet(wallet)));
@@ -231,14 +241,14 @@ export class WalletSyncService {
231
241
  disabled: newParticipantWallets.filter((w) => w.disabled)
232
242
  .length,
233
243
  }, 'Wallet sync summary');
234
- // Set primary wallet if none exists
235
- const wallets = await this.store.getWallets();
236
- const hasPrimary = wallets.some((w) => w.primary);
237
- if (!hasPrimary && wallets.length > 0) {
238
- this.store.setPrimaryWallet(wallets[0].partyId);
239
- this.logger.info(`Set ${wallets[0].partyId} as primary wallet`);
244
+ // Set primary wallet if none exists in current network
245
+ const networkWallets = await this.store.getWallets();
246
+ const hasPrimary = networkWallets.some((w) => w.primary);
247
+ if (!hasPrimary && networkWallets.length > 0) {
248
+ this.store.setPrimaryWallet(networkWallets[0].partyId);
249
+ this.logger.info(`Set ${networkWallets[0].partyId} as primary wallet in network ${network.id}`);
240
250
  }
241
- this.logger.debug(wallets, 'Wallet sync completed.');
251
+ this.logger.debug({ wallets: newParticipantWallets }, 'Wallet sync completed.');
242
252
  return {
243
253
  added: newParticipantWallets,
244
254
  removed: [],
@@ -7,6 +7,7 @@ import { SigningProvider, } from '@canton-network/core-signing-lib';
7
7
  import { InternalSigningDriver } from '@canton-network/core-signing-internal';
8
8
  import { ParticipantSigningDriver } from '@canton-network/core-signing-participant';
9
9
  import { StoreSql, connection, migrator, } from '@canton-network/core-signing-store-sql';
10
+ import { StoreInternal } from '@canton-network/core-wallet-store-inmemory';
10
11
  import { WalletSyncService } from './wallet-sync-service.js';
11
12
  import { PartyAllocationService } from './party-allocation-service.js';
12
13
  const mockLedgerGet = jest.fn();
@@ -103,6 +104,9 @@ describe('WalletSyncService - resolveSigningProvider', () => {
103
104
  const internalDriver = service['signingDrivers'][SigningProvider.WALLET_KERNEL];
104
105
  const controller = internalDriver.controller(authContext.userId);
105
106
  const key = await controller.createKey({ name: 'test-key' });
107
+ if ('error' in key) {
108
+ throw new Error(`Failed to create key in test: ${key.error_description}`);
109
+ }
106
110
  const namespace = partyAllocator.createFingerprintFromKey(key.publicKey);
107
111
  mockLedgerGet.mockResolvedValueOnce({
108
112
  participantId: 'participant1::different-participant-namespace',
@@ -165,3 +169,338 @@ describe('WalletSyncService - resolveSigningProvider', () => {
165
169
  });
166
170
  });
167
171
  });
172
+ describe('WalletSyncService - multi-network features', () => {
173
+ const authContext = {
174
+ userId: 'test-user-id',
175
+ accessToken: 'test-access-token',
176
+ };
177
+ let mockLogger;
178
+ let store;
179
+ let mockLedgerClient;
180
+ let mockAdminLedgerClient;
181
+ let partyAllocator;
182
+ const createNetwork = (id) => ({
183
+ id,
184
+ name: `Network ${id}`,
185
+ synchronizerId: `${id}-sync`,
186
+ identityProviderId: 'idp1',
187
+ description: `Test Network ${id}`,
188
+ ledgerApi: { baseUrl: `http://${id}` },
189
+ auth: {
190
+ method: 'authorization_code',
191
+ clientId: 'cid',
192
+ scope: 'scope',
193
+ audience: 'aud',
194
+ },
195
+ });
196
+ const createWallet = (partyId, networkId, disabled = false) => ({
197
+ primary: false,
198
+ partyId,
199
+ status: 'allocated',
200
+ hint: partyId.split('::')[0],
201
+ signingProviderId: 'internal',
202
+ publicKey: 'publicKey',
203
+ namespace: 'namespace',
204
+ networkId,
205
+ disabled,
206
+ });
207
+ const setSession = async (networkId) => {
208
+ await store.setSession({
209
+ id: `sess-${networkId}`,
210
+ network: networkId,
211
+ accessToken: 'token',
212
+ });
213
+ };
214
+ beforeEach(async () => {
215
+ mockLogger = pino(sink());
216
+ store = new StoreInternal({
217
+ idps: [],
218
+ networks: [],
219
+ }, mockLogger, authContext);
220
+ // Add a default IdP that tests can use (use updateIdp to avoid errors if it already exists)
221
+ try {
222
+ await store.addIdp({
223
+ id: 'idp1',
224
+ type: 'oauth',
225
+ issuer: 'http://auth',
226
+ configUrl: 'http://auth/.well-known/openid-configuration',
227
+ });
228
+ }
229
+ catch {
230
+ // IdP might already exist from previous test, use updateIdp instead
231
+ await store.updateIdp({
232
+ id: 'idp1',
233
+ type: 'oauth',
234
+ issuer: 'http://auth',
235
+ configUrl: 'http://auth/.well-known/openid-configuration',
236
+ });
237
+ }
238
+ partyAllocator = new PartyAllocationService({
239
+ synchronizerId: 'test-sync-id',
240
+ accessTokenProvider: {
241
+ getUserAccessToken: async () => 'user.jwt',
242
+ getAdminAccessToken: async () => 'admin.jwt',
243
+ },
244
+ httpLedgerUrl: 'http://test',
245
+ logger: mockLogger,
246
+ });
247
+ const ledgerModule = await import('@canton-network/core-ledger-client');
248
+ mockLedgerClient = new ledgerModule.LedgerClient({
249
+ baseUrl: new URL('http://test'),
250
+ logger: mockLogger,
251
+ accessTokenProvider: {
252
+ getUserAccessToken: async () => 'token',
253
+ getAdminAccessToken: async () => 'token',
254
+ },
255
+ });
256
+ mockAdminLedgerClient = new ledgerModule.LedgerClient({
257
+ baseUrl: new URL('http://test'),
258
+ logger: mockLogger,
259
+ isAdmin: true,
260
+ accessTokenProvider: {
261
+ getUserAccessToken: async () => 'token',
262
+ getAdminAccessToken: async () => 'token',
263
+ },
264
+ });
265
+ });
266
+ afterEach(() => {
267
+ jest.restoreAllMocks();
268
+ mockLedgerGet.mockClear();
269
+ });
270
+ it('isWalletSyncNeeded should filter by current network', async () => {
271
+ const network1 = createNetwork('network1');
272
+ await store.addNetwork(network1);
273
+ await setSession('network1');
274
+ await store.addWallet(createWallet('party1::namespace', 'network1'));
275
+ await store.addWallet(createWallet('party2::namespace', 'network2'));
276
+ mockLedgerGet.mockResolvedValueOnce({
277
+ rights: [
278
+ {
279
+ kind: {
280
+ CanActAs: {
281
+ value: {
282
+ party: 'party1::namespace',
283
+ },
284
+ },
285
+ },
286
+ },
287
+ ],
288
+ });
289
+ const service = new WalletSyncService(store, mockLedgerClient, mockAdminLedgerClient, authContext, mockLogger, {}, partyAllocator);
290
+ const syncNeeded = await service.isWalletSyncNeeded();
291
+ // Should return false because party1 already exists in network1
292
+ expect(syncNeeded).toBe(false);
293
+ });
294
+ it('isWalletSyncNeeded should detect new parties for current network only', async () => {
295
+ const network1 = createNetwork('network1');
296
+ await store.addNetwork(network1);
297
+ await setSession('network1');
298
+ mockLedgerGet.mockResolvedValueOnce({
299
+ rights: [
300
+ {
301
+ kind: {
302
+ CanActAs: {
303
+ value: {
304
+ party: 'party1::namespace',
305
+ },
306
+ },
307
+ },
308
+ },
309
+ ],
310
+ });
311
+ const service = new WalletSyncService(store, mockLedgerClient, mockAdminLedgerClient, authContext, mockLogger, {}, partyAllocator);
312
+ const syncNeeded = await service.isWalletSyncNeeded();
313
+ // Should return true because party1 exists on ledger but not in store for network1
314
+ expect(syncNeeded).toBe(true);
315
+ });
316
+ it('syncWallets should only sync wallets for current network', async () => {
317
+ const network1 = createNetwork('network1');
318
+ await store.addNetwork(network1);
319
+ await setSession('network1');
320
+ await store.addWallet(createWallet('party1::namespace', 'network1'));
321
+ const addWalletSpy = jest.spyOn(store, 'addWallet');
322
+ mockLedgerGet
323
+ .mockResolvedValueOnce({
324
+ rights: [
325
+ {
326
+ kind: {
327
+ CanActAs: {
328
+ value: {
329
+ party: 'party1::namespace',
330
+ },
331
+ },
332
+ },
333
+ },
334
+ {
335
+ kind: {
336
+ CanActAs: {
337
+ value: {
338
+ party: 'party3::namespace',
339
+ },
340
+ },
341
+ },
342
+ },
343
+ ],
344
+ })
345
+ .mockResolvedValueOnce({
346
+ participantId: 'participant1::namespace',
347
+ });
348
+ const service = new WalletSyncService(store, mockLedgerClient, mockAdminLedgerClient, authContext, mockLogger, {}, partyAllocator);
349
+ await service.syncWallets();
350
+ // Should only add wallet for party3 (party1 already exists)
351
+ const wallets = await store.getAllWallets({ networkIds: ['network1'] });
352
+ expect(wallets.some((w) => w.partyId === 'party3::namespace')).toBe(true);
353
+ expect(addWalletSpy).toHaveBeenCalled();
354
+ });
355
+ it('syncWallets should handle same party ID across different networks', async () => {
356
+ const network1 = createNetwork('network1');
357
+ await store.addNetwork(network1);
358
+ await setSession('network1');
359
+ // Mock ledger client to return rights for party1
360
+ mockLedgerGet
361
+ .mockResolvedValueOnce({
362
+ rights: [
363
+ {
364
+ kind: {
365
+ CanActAs: {
366
+ value: {
367
+ party: 'party1::namespace',
368
+ },
369
+ },
370
+ },
371
+ },
372
+ ],
373
+ })
374
+ .mockResolvedValueOnce({
375
+ participantId: 'participant1::namespace',
376
+ });
377
+ const service = new WalletSyncService(store, mockLedgerClient, mockAdminLedgerClient, authContext, mockLogger, {}, partyAllocator);
378
+ await service.syncWallets();
379
+ // Should add party1 for network1
380
+ const wallets = await store.getAllWallets({ networkIds: ['network1'] });
381
+ expect(wallets.some((w) => w.partyId === 'party1::namespace')).toBe(true);
382
+ });
383
+ it('isWalletSyncNeeded should detect multi-hosted party on different network', async () => {
384
+ const network1 = createNetwork('network1');
385
+ const network2 = createNetwork('network2');
386
+ await store.addNetwork(network1);
387
+ await store.addNetwork(network2);
388
+ await store.addWallet(createWallet('party1::namespace', 'network1'));
389
+ const service = new WalletSyncService(store, mockLedgerClient, mockAdminLedgerClient, authContext, mockLogger, {}, partyAllocator);
390
+ // Mock ledger client to return rights for party1 (multi-hosted party) for network1 check
391
+ mockLedgerGet.mockResolvedValueOnce({
392
+ rights: [
393
+ {
394
+ kind: {
395
+ CanActAs: {
396
+ value: {
397
+ party: 'party1::namespace',
398
+ },
399
+ },
400
+ },
401
+ },
402
+ ],
403
+ });
404
+ await setSession('network1');
405
+ // Check sync needed for network1 (party already exists)
406
+ const syncNeeded1 = await service.isWalletSyncNeeded();
407
+ expect(syncNeeded1).toBe(false);
408
+ // Mock ledger client to return rights for party1 for network2 check
409
+ mockLedgerGet.mockResolvedValueOnce({
410
+ rights: [
411
+ {
412
+ kind: {
413
+ CanActAs: {
414
+ value: {
415
+ party: 'party1::namespace',
416
+ },
417
+ },
418
+ },
419
+ },
420
+ ],
421
+ });
422
+ await setSession('network2');
423
+ // Check sync needed for network2 (party doesn't exist yet)
424
+ const syncNeeded2 = await service.isWalletSyncNeeded();
425
+ expect(syncNeeded2).toBe(true);
426
+ });
427
+ it('syncWallets should handle multi-hosted party across networks', async () => {
428
+ const network1 = createNetwork('network1');
429
+ const network2 = createNetwork('network2');
430
+ await store.addNetwork(network1);
431
+ await store.addNetwork(network2);
432
+ // Add wallet to network1 (simulating it was synced there previously)
433
+ await setSession('network1');
434
+ await store.addWallet(createWallet('party1::namespace', 'network1'));
435
+ const service = new WalletSyncService(store, mockLedgerClient, mockAdminLedgerClient, authContext, mockLogger, {}, partyAllocator);
436
+ // Sync on network1 (party already exists, should not add)
437
+ await setSession('network1');
438
+ // Only need one mock since resolveSigningProvider won't be called if party already exists
439
+ mockLedgerGet.mockResolvedValueOnce({
440
+ rights: [
441
+ {
442
+ kind: {
443
+ CanActAs: {
444
+ value: {
445
+ party: 'party1::namespace',
446
+ },
447
+ },
448
+ },
449
+ },
450
+ ],
451
+ });
452
+ const syncResult1 = await service.syncWallets();
453
+ expect(syncResult1.added.length).toBe(0); // Should not add, already exists
454
+ // Sync on network2 (party doesn't exist, should add)
455
+ await setSession('network2');
456
+ // Verify no wallets exist for network2 before sync
457
+ const walletsBeforeSync = await store.getAllWallets({
458
+ networkIds: ['network2'],
459
+ });
460
+ expect(walletsBeforeSync.length).toBe(0);
461
+ mockLedgerGet.mockClear();
462
+ // First mock: getPartiesRightsMap calls ledgerClient.getWithRetry('/v2/users/{user-id}/rights')
463
+ mockLedgerGet.mockResolvedValueOnce({
464
+ rights: [
465
+ {
466
+ kind: {
467
+ CanActAs: {
468
+ value: {
469
+ party: 'party1::namespace',
470
+ },
471
+ },
472
+ },
473
+ },
474
+ ],
475
+ });
476
+ // Second mock: resolveSigningProvider calls adminLedgerClient.getWithRetry('/v2/parties/participant-id')
477
+ mockLedgerGet.mockResolvedValueOnce({
478
+ participantId: 'participant1::namespace',
479
+ });
480
+ expect(mockLedgerGet).toHaveBeenCalledTimes(0);
481
+ const syncResult = await service.syncWallets();
482
+ expect(mockLedgerGet).toHaveBeenCalledTimes(2); // Once for rights, once for participantId
483
+ expect(syncResult.added.length).toBe(1);
484
+ expect(syncResult.added[0].partyId).toBe('party1::namespace');
485
+ expect(syncResult.added[0].networkId).toBe('network2');
486
+ expect(syncResult.added[0].disabled).toBe(false);
487
+ const network2Wallets = await store.getAllWallets({
488
+ networkIds: ['network2'],
489
+ });
490
+ const party1Wallet = network2Wallets.find((w) => w.partyId === 'party1::namespace');
491
+ expect(party1Wallet).toBeDefined();
492
+ expect(party1Wallet?.networkId).toBe('network2');
493
+ expect(party1Wallet?.disabled).toBe(false);
494
+ });
495
+ // TODO maybe now that we never block sync button don't consider disabled wallet as sync is needed?
496
+ it('isWalletSyncNeeded should return true when disabled wallets exist', async () => {
497
+ const network1 = createNetwork('network1');
498
+ await store.addNetwork(network1);
499
+ await setSession('network1');
500
+ await store.addWallet(createWallet('party1::namespace', 'network1', true));
501
+ const service = new WalletSyncService(store, mockLedgerClient, mockAdminLedgerClient, authContext, mockLogger, {}, partyAllocator);
502
+ const syncNeeded = await service.isWalletSyncNeeded();
503
+ // Should return true because there's a disabled wallet
504
+ expect(syncNeeded).toBe(true);
505
+ });
506
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"jwtAuth.d.ts","sourceRoot":"","sources":["../../src/middleware/jwtAuth.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAE7B,wBAAgB,OAAO,CAAC,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,IAC9C,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,mBAehE"}
1
+ {"version":3,"file":"jwtAuth.d.ts","sourceRoot":"","sources":["../../src/middleware/jwtAuth.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAE7B,wBAAgB,OAAO,CAAC,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,IAC9C,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,mBAkBhE"}
@@ -2,7 +2,9 @@
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
  export function jwtAuth(authService, logger) {
4
4
  return async (req, res, next) => {
5
- const authHeader = req.headers.authorization;
5
+ // Support both Authorization header and token query parameter (for EventSource)
6
+ const authHeader = req.headers.authorization ||
7
+ (req.query.token ? `Bearer ${req.query.token}` : undefined);
6
8
  try {
7
9
  const context = await authService.verifyToken(authHeader);
8
10
  req.authContext = context;
@@ -5,7 +5,7 @@ import { AuthContext } from '@canton-network/core-wallet-auth';
5
5
  import { KernelInfo } from '../config/Config.js';
6
6
  import { SigningDriverInterface, SigningProvider } from '@canton-network/core-signing-lib';
7
7
  type AvailableSigningDrivers = Partial<Record<SigningProvider, SigningDriverInterface>>;
8
- export declare const userController: (kernelInfo: KernelInfo, userUrl: string, store: Store, notificationService: NotificationService, authContext: AuthContext | undefined, drivers: AvailableSigningDrivers, _logger: Logger) => {
8
+ export declare const userController: (kernelInfo: KernelInfo, userUrl: string, store: Store, notificationService: NotificationService, authContext: AuthContext | undefined, drivers: AvailableSigningDrivers, _logger: Logger, adminUserId?: string) => {
9
9
  addNetwork: import("./rpc-gen/typings.js").AddNetwork;
10
10
  removeNetwork: import("./rpc-gen/typings.js").RemoveNetwork;
11
11
  listNetworks: import("./rpc-gen/typings.js").ListNetworks;
@@ -25,6 +25,8 @@ export declare const userController: (kernelInfo: KernelInfo, userUrl: string, s
25
25
  listSessions: import("./rpc-gen/typings.js").ListSessions;
26
26
  getTransaction: import("./rpc-gen/typings.js").GetTransaction;
27
27
  listTransactions: import("./rpc-gen/typings.js").ListTransactions;
28
+ deleteTransaction: import("./rpc-gen/typings.js").DeleteTransaction;
29
+ getUser: import("./rpc-gen/typings.js").GetUser;
28
30
  };
29
31
  export {};
30
32
  //# sourceMappingURL=controller.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/user-api/controller.ts"],"names":[],"mappings":"AA0BA,OAAO,EACH,KAAK,EAIR,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAC7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AAC5E,OAAO,EAGH,WAAW,EAId,MAAM,kCAAkC,CAAA;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,EACH,sBAAsB,EACtB,eAAe,EAClB,MAAM,kCAAkC,CAAA;AAczC,KAAK,uBAAuB,GAAG,OAAO,CAClC,MAAM,CAAC,eAAe,EAAE,sBAAsB,CAAC,CAClD,CAAA;AAED,eAAO,MAAM,cAAc,GACvB,YAAY,UAAU,EACtB,SAAS,MAAM,EACf,OAAO,KAAK,EACZ,qBAAqB,mBAAmB,EACxC,aAAa,WAAW,GAAG,SAAS,EACpC,SAAS,uBAAuB,EAChC,SAAS,MAAM;;;;;;;;;;;;;;;;;;;;CAu8BlB,CAAA"}
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/user-api/controller.ts"],"names":[],"mappings":"AA4BA,OAAO,EACH,KAAK,EAIR,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAC7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AAC5E,OAAO,EAGH,WAAW,EAId,MAAM,kCAAkC,CAAA;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,EACH,sBAAsB,EACtB,eAAe,EAElB,MAAM,kCAAkC,CAAA;AAazC,KAAK,uBAAuB,GAAG,OAAO,CAClC,MAAM,CAAC,eAAe,EAAE,sBAAsB,CAAC,CAClD,CAAA;AAED,eAAO,MAAM,cAAc,GACvB,YAAY,UAAU,EACtB,SAAS,MAAM,EACf,OAAO,KAAK,EACZ,qBAAqB,mBAAmB,EACxC,aAAa,WAAW,GAAG,SAAS,EACpC,SAAS,uBAAuB,EAChC,SAAS,MAAM,EACf,cAAc,MAAM;;;;;;;;;;;;;;;;;;;;;;CA4iCvB,CAAA"}