@canton-network/wallet-gateway-remote 0.25.0 → 0.27.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 (85) hide show
  1. package/dist/auth/jwt-auth-service.d.ts.map +1 -1
  2. package/dist/auth/jwt-auth-service.js +25 -0
  3. package/dist/config/Config.d.ts +5 -2
  4. package/dist/config/Config.d.ts.map +1 -1
  5. package/dist/config/Config.js +6 -0
  6. package/dist/config/Config.test.js +1 -1
  7. package/dist/dapp-api/controller.d.ts +1 -0
  8. package/dist/dapp-api/controller.d.ts.map +1 -1
  9. package/dist/dapp-api/controller.js +29 -2
  10. package/dist/dapp-api/rpc-gen/index.d.ts +3 -0
  11. package/dist/dapp-api/rpc-gen/index.d.ts.map +1 -1
  12. package/dist/dapp-api/rpc-gen/index.js +1 -0
  13. package/dist/dapp-api/rpc-gen/typings.d.ts +3 -2
  14. package/dist/dapp-api/rpc-gen/typings.d.ts.map +1 -1
  15. package/dist/dapp-api/server.test.js +1 -1
  16. package/dist/env.d.ts +1 -0
  17. package/dist/env.d.ts.map +1 -1
  18. package/dist/env.js +1 -0
  19. package/dist/example-config.d.ts +1 -0
  20. package/dist/example-config.d.ts.map +1 -1
  21. package/dist/example-config.js +1 -0
  22. package/dist/index.d.ts +1 -1
  23. package/dist/init.d.ts.map +1 -1
  24. package/dist/init.js +9 -4
  25. package/dist/ledger/party-allocation-service.test.js +19 -17
  26. package/dist/ledger/transaction-service.d.ts +1 -0
  27. package/dist/ledger/transaction-service.d.ts.map +1 -1
  28. package/dist/ledger/transaction-service.js +77 -56
  29. package/dist/ledger/wallet-allocation/signing-providers/blockdaemon-wallet-allocator.d.ts.map +1 -1
  30. package/dist/ledger/wallet-allocation/signing-providers/blockdaemon-wallet-allocator.js +2 -1
  31. package/dist/ledger/wallet-allocation/wallet-allocation-service.test.js +22 -22
  32. package/dist/ledger/wallet-sync-service.js +1 -1
  33. package/dist/ledger/wallet-sync-service.test.js +15 -13
  34. package/dist/middleware/rateLimit.d.ts +5 -0
  35. package/dist/middleware/rateLimit.d.ts.map +1 -1
  36. package/dist/middleware/rateLimit.js +40 -0
  37. package/dist/middleware/rateLimit.test.d.ts +2 -0
  38. package/dist/middleware/rateLimit.test.d.ts.map +1 -0
  39. package/dist/middleware/rateLimit.test.js +30 -0
  40. package/dist/user-api/controller.d.ts.map +1 -1
  41. package/dist/user-api/controller.js +16 -12
  42. package/dist/user-api/rpc-gen/typings.d.ts +26 -20
  43. package/dist/user-api/rpc-gen/typings.d.ts.map +1 -1
  44. package/dist/user-api/server.test.js +1 -1
  45. package/dist/web/frontend/404/index.html +2 -2
  46. package/dist/web/frontend/activities/index.html +3 -3
  47. package/dist/web/frontend/approve/index.html +5 -4
  48. package/dist/web/frontend/assets/404-CEXKCUlr.js +5 -0
  49. package/dist/web/frontend/assets/{activities-Da48Liee.js → activities-CPXS4OC3.js} +5 -3
  50. package/dist/web/frontend/assets/addIdentityProvider-BJjhu--M.js +28 -0
  51. package/dist/web/frontend/assets/{addNetwork-Dr3W42IN.js → addNetwork-B0olMmyv.js} +3 -3
  52. package/dist/web/frontend/assets/addParty-PUjps7Ie.js +38 -0
  53. package/dist/web/frontend/assets/approve-CARzty9R.js +21 -0
  54. package/dist/web/frontend/assets/{callback-OyzKCduG.js → callback-BqE26RlP.js} +1 -1
  55. package/dist/web/frontend/assets/{identityProviders-CNGCwCMd.js → identityProviders-Cnx8VIsD.js} +2 -2
  56. package/dist/web/frontend/assets/{index-CH8oXI5W.js → index-CgsIALyR.js} +1 -1
  57. package/dist/web/frontend/assets/index-UdyYJ_nU.js +4011 -0
  58. package/dist/web/frontend/assets/index-nOmkqkbK.js +91 -0
  59. package/dist/web/frontend/assets/{login-DcYD5VED.js → login-CkpIib74.js} +2 -2
  60. package/dist/web/frontend/assets/{networks--1UXXt0J.js → networks-D7iP_lGh.js} +2 -2
  61. package/dist/web/frontend/assets/reviewIdentityProvider-BRb38sGH.js +43 -0
  62. package/dist/web/frontend/assets/{reviewNetwork-CGV3UYev.js → reviewNetwork-BVdYGugz.js} +3 -3
  63. package/dist/web/frontend/assets/{settings-DnbEcRUK.js → settings-CxEU64oG.js} +2 -2
  64. package/dist/web/frontend/assets/{state-Da4wx0rb.js → state-CypMU0cY.js} +1 -1
  65. package/dist/web/frontend/assets/utils-V1qa66Nv.js +1 -0
  66. package/dist/web/frontend/callback/index.html +2 -2
  67. package/dist/web/frontend/identity-providers/add/index.html +3 -3
  68. package/dist/web/frontend/identity-providers/index.html +3 -3
  69. package/dist/web/frontend/identity-providers/review/index.html +3 -3
  70. package/dist/web/frontend/index.html +1 -1
  71. package/dist/web/frontend/login/index.html +4 -4
  72. package/dist/web/frontend/networks/add/index.html +3 -3
  73. package/dist/web/frontend/networks/index.html +3 -3
  74. package/dist/web/frontend/networks/review/index.html +3 -3
  75. package/dist/web/frontend/parties/add/index.html +5 -3
  76. package/dist/web/frontend/parties/index.html +4 -3
  77. package/dist/web/frontend/settings/index.html +3 -3
  78. package/package.json +25 -28
  79. package/dist/web/frontend/assets/404-F1JWultf.js +0 -5
  80. package/dist/web/frontend/assets/addIdentityProvider-DC3NxrKJ.js +0 -35
  81. package/dist/web/frontend/assets/addParty-C6FKG-e0.js +0 -38
  82. package/dist/web/frontend/assets/approve-BcD3DHcB.js +0 -21
  83. package/dist/web/frontend/assets/index-6feHRhfj.js +0 -3901
  84. package/dist/web/frontend/assets/parties-CjUMJ1Qt.js +0 -91
  85. package/dist/web/frontend/assets/reviewIdentityProvider-BT1CChnu.js +0 -43
@@ -15,6 +15,13 @@ export class TransactionService {
15
15
  this.signingDrivers = signingDrivers;
16
16
  this.notifier = notifier;
17
17
  }
18
+ async loadPreparedTransactionForSigning(transactionId) {
19
+ const existingTx = await this.store.getTransaction(transactionId);
20
+ if (!existingTx) {
21
+ throw new Error(`Transaction not found with id: ${transactionId}`);
22
+ }
23
+ return existingTx;
24
+ }
18
25
  signWithParticipant(wallet) {
19
26
  return {
20
27
  status: 'signed',
@@ -29,11 +36,11 @@ export class TransactionService {
29
36
  throw new Error('Wallet Kernel signing driver not available');
30
37
  }
31
38
  const driver = signingProvider.controller(userId);
32
- const { preparedTransaction, preparedTransactionHash, commandId } = signParams;
39
+ const tx = await this.loadPreparedTransactionForSigning(signParams.transactionId);
33
40
  const { signature } = await driver
34
41
  .signTransaction({
35
- tx: preparedTransaction,
36
- txHash: preparedTransactionHash,
42
+ tx: tx.preparedTransaction,
43
+ txHash: tx.preparedTransactionHash,
37
44
  keyIdentifier: {
38
45
  publicKey: wallet.publicKey,
39
46
  },
@@ -42,20 +49,20 @@ export class TransactionService {
42
49
  if (!signature) {
43
50
  throw new Error('Failed to sign transaction: ' + JSON.stringify(signature));
44
51
  }
45
- const existingTx = await this.store.getTransaction(commandId);
46
52
  const now = new Date();
47
53
  const signedTx = {
48
- commandId,
54
+ id: tx.id,
55
+ commandId: tx.commandId,
49
56
  status: 'signed',
50
- preparedTransaction,
51
- preparedTransactionHash,
52
- origin: existingTx?.origin ?? null,
53
- ...(existingTx?.createdAt && {
54
- createdAt: existingTx.createdAt,
57
+ preparedTransaction: tx.preparedTransaction,
58
+ preparedTransactionHash: tx.preparedTransactionHash,
59
+ origin: tx?.origin ?? null,
60
+ ...(tx?.createdAt && {
61
+ createdAt: tx.createdAt,
55
62
  }),
56
63
  signedAt: now,
57
64
  };
58
- this.store.setTransaction(signedTx);
65
+ await this.store.setTransactionSigned(tx.id, now);
59
66
  this.notifier.emit('txChanged', signedTx);
60
67
  return {
61
68
  status: 'signed',
@@ -70,14 +77,13 @@ export class TransactionService {
70
77
  throw new Error('Blockdaemon signing driver not available');
71
78
  }
72
79
  const driver = signingProvider.controller(userId);
73
- const { preparedTransaction, preparedTransactionHash, commandId } = signParams;
80
+ const tx = await this.loadPreparedTransactionForSigning(signParams.transactionId);
74
81
  let signingResult;
75
- const existingTx = await this.store.getTransaction(commandId);
76
- if (existingTx && existingTx.externalTxId) {
82
+ if (tx && tx.externalTxId) {
77
83
  signingResult = await driver
78
84
  .getTransaction({
79
85
  userId,
80
- txId: existingTx.externalTxId,
86
+ txId: tx.externalTxId,
81
87
  })
82
88
  .then(handleSigningError);
83
89
  }
@@ -88,8 +94,8 @@ export class TransactionService {
88
94
  .substring(0, 16);
89
95
  signingResult = await driver
90
96
  .signTransaction({
91
- tx: preparedTransaction,
92
- txHash: preparedTransactionHash,
97
+ tx: tx.preparedTransaction,
98
+ txHash: tx.preparedTransactionHash,
93
99
  keyIdentifier: {
94
100
  publicKey: wallet.publicKey,
95
101
  },
@@ -103,18 +109,19 @@ export class TransactionService {
103
109
  throw new Error('No signature returned from signing driver');
104
110
  }
105
111
  const signedTx = {
106
- commandId,
112
+ id: tx.id,
113
+ commandId: tx.commandId,
107
114
  status: signingResult.status,
108
- preparedTransaction,
109
- preparedTransactionHash,
110
- origin: existingTx?.origin ?? null,
111
- ...(existingTx?.createdAt && {
112
- createdAt: existingTx.createdAt,
115
+ preparedTransaction: tx.preparedTransaction,
116
+ preparedTransactionHash: tx.preparedTransactionHash,
117
+ origin: tx?.origin ?? null,
118
+ ...(tx?.createdAt && {
119
+ createdAt: tx.createdAt,
113
120
  }),
114
121
  signedAt: now,
115
122
  externalTxId: signingResult.txId,
116
123
  };
117
- this.store.setTransaction(signedTx);
124
+ await this.store.setTransactionSigned(tx.id, now, signingResult.txId);
118
125
  this.notifier.emit('txChanged', signedTx);
119
126
  return {
120
127
  status: signingResult.status,
@@ -127,17 +134,20 @@ export class TransactionService {
127
134
  else {
128
135
  const status = signingResult.status === 'pending' ? 'pending' : 'failed';
129
136
  const pendingTx = {
130
- commandId,
137
+ id: tx.id,
138
+ commandId: tx.commandId,
131
139
  status,
132
- preparedTransaction,
133
- preparedTransactionHash,
140
+ preparedTransaction: tx.preparedTransaction,
141
+ preparedTransactionHash: tx.preparedTransactionHash,
134
142
  externalTxId: signingResult.txId,
135
- origin: existingTx?.origin ?? null,
136
- ...(existingTx?.createdAt && {
137
- createdAt: existingTx.createdAt,
143
+ origin: tx?.origin ?? null,
144
+ ...(tx?.createdAt && {
145
+ createdAt: tx.createdAt,
138
146
  }),
139
147
  };
140
- this.store.setTransaction(pendingTx);
148
+ await this.store.setTransactionStatus(tx.id, status, {
149
+ externalTxId: signingResult.txId,
150
+ });
141
151
  this.notifier.emit('txChanged', pendingTx);
142
152
  return {
143
153
  status: signingResult.status,
@@ -152,14 +162,13 @@ export class TransactionService {
152
162
  throw new Error('Fireblocks signing driver not available');
153
163
  }
154
164
  const driver = signingProvider.controller(userId);
155
- const { preparedTransaction, preparedTransactionHash, commandId } = signParams;
165
+ const tx = await this.loadPreparedTransactionForSigning(signParams.transactionId);
156
166
  let signingResult;
157
- const existingTx = await this.store.getTransaction(commandId);
158
- if (existingTx && existingTx.externalTxId) {
167
+ if (tx && tx.externalTxId) {
159
168
  signingResult = await driver
160
169
  .getTransaction({
161
170
  userId,
162
- txId: existingTx.externalTxId,
171
+ txId: tx.externalTxId,
163
172
  })
164
173
  .then(handleSigningError);
165
174
  }
@@ -167,8 +176,8 @@ export class TransactionService {
167
176
  signingResult = await driver
168
177
  .signTransaction({
169
178
  userId,
170
- tx: preparedTransaction,
171
- txHash: Buffer.from(preparedTransactionHash, 'base64').toString('hex'),
179
+ tx: tx.preparedTransaction,
180
+ txHash: Buffer.from(tx.preparedTransactionHash, 'base64').toString('hex'),
172
181
  keyIdentifier: {
173
182
  publicKey: wallet.publicKey,
174
183
  },
@@ -181,18 +190,19 @@ export class TransactionService {
181
190
  throw new Error('No signature returned from signing driver');
182
191
  }
183
192
  const signedTx = {
184
- commandId,
193
+ id: tx.id,
194
+ commandId: tx.commandId,
185
195
  status: signingResult.status,
186
- preparedTransaction,
187
- preparedTransactionHash,
188
- origin: existingTx?.origin ?? null,
189
- ...(existingTx?.createdAt && {
190
- createdAt: existingTx.createdAt,
196
+ preparedTransaction: tx.preparedTransaction,
197
+ preparedTransactionHash: tx.preparedTransactionHash,
198
+ origin: tx?.origin ?? null,
199
+ ...(tx?.createdAt && {
200
+ createdAt: tx.createdAt,
191
201
  }),
192
202
  signedAt: now,
193
203
  externalTxId: signingResult.txId,
194
204
  };
195
- this.store.setTransaction(signedTx);
205
+ await this.store.setTransactionSigned(tx.id, now, signingResult.txId);
196
206
  this.notifier.emit('txChanged', signedTx);
197
207
  // return signature in format that is already usable in execute
198
208
  const decodedSignature = Buffer.from(signingResult.signature, 'hex').toString('base64');
@@ -207,17 +217,20 @@ export class TransactionService {
207
217
  else {
208
218
  const status = signingResult.status === 'pending' ? 'pending' : 'failed';
209
219
  const pendingTx = {
210
- commandId,
220
+ id: tx.id,
221
+ commandId: tx.commandId,
211
222
  status,
212
- preparedTransaction,
213
- preparedTransactionHash,
223
+ preparedTransaction: tx.preparedTransaction,
224
+ preparedTransactionHash: tx.preparedTransactionHash,
214
225
  externalTxId: signingResult.txId,
215
- origin: existingTx?.origin ?? null,
216
- ...(existingTx?.createdAt && {
217
- createdAt: existingTx.createdAt,
226
+ origin: tx?.origin ?? null,
227
+ ...(tx?.createdAt && {
228
+ createdAt: tx.createdAt,
218
229
  }),
219
230
  };
220
- this.store.setTransaction(pendingTx);
231
+ await this.store.setTransactionStatus(tx.id, status, {
232
+ externalTxId: signingResult.txId,
233
+ });
221
234
  this.notifier.emit('txChanged', pendingTx);
222
235
  return {
223
236
  status: signingResult.status,
@@ -227,11 +240,13 @@ export class TransactionService {
227
240
  }
228
241
  }
229
242
  async executeWithParticipant(userId, executeParams, transaction, ledgerClient, network) {
230
- const { commandId, partyId } = executeParams;
243
+ const { partyId } = executeParams;
244
+ const { commandId } = transaction;
231
245
  const synchronizerId = network.synchronizerId ?? (await ledgerClient.getSynchronizerId());
232
246
  const prep = ledgerPrepareParams(userId, partyId, synchronizerId, transaction.payload);
233
247
  const res = await ledgerClient.postWithRetry('/v2/commands/submit-and-wait', prep);
234
248
  const executedTx = {
249
+ id: transaction.id,
235
250
  commandId,
236
251
  status: 'executed',
237
252
  preparedTransaction: transaction.preparedTransaction,
@@ -245,13 +260,16 @@ export class TransactionService {
245
260
  signedAt: transaction.signedAt,
246
261
  }),
247
262
  };
248
- this.store.setTransaction(executedTx);
263
+ await this.store.setTransactionStatus(transaction.id, 'executed', {
264
+ payload: res,
265
+ });
249
266
  this.notifier.emit('txChanged', executedTx);
250
267
  return res;
251
268
  }
252
269
  async executeWithExternal(userId, executeParams, transaction, ledgerClient) {
253
- const { commandId, partyId, signature, signedBy } = executeParams;
254
- const result = await ledgerClient.postWithRetry('/v2/interactive-submission/execute', {
270
+ const { partyId, signature, signedBy } = executeParams;
271
+ const { commandId } = transaction;
272
+ const result = await ledgerClient.postWithRetry('/v2/interactive-submission/executeAndWait', {
255
273
  userId,
256
274
  preparedTransaction: transaction.preparedTransaction,
257
275
  hashingSchemeVersion: 'HASHING_SCHEME_VERSION_V2',
@@ -276,6 +294,7 @@ export class TransactionService {
276
294
  },
277
295
  });
278
296
  const executedTx = {
297
+ id: transaction.id,
279
298
  commandId,
280
299
  status: 'executed',
281
300
  preparedTransaction: transaction.preparedTransaction,
@@ -289,7 +308,9 @@ export class TransactionService {
289
308
  signedAt: transaction.signedAt,
290
309
  }),
291
310
  };
292
- this.store.setTransaction(executedTx);
311
+ await this.store.setTransactionStatus(transaction.id, 'executed', {
312
+ payload: result,
313
+ });
293
314
  this.notifier.emit('txChanged', executedTx);
294
315
  return result;
295
316
  }
@@ -1 +1 @@
1
- {"version":3,"file":"blockdaemon-wallet-allocator.d.ts","sourceRoot":"","sources":["../../../../src/ledger/wallet-allocation/signing-providers/blockdaemon-wallet-allocator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,kCAAkC,CAAA;AACzD,OAAO,EAAE,KAAK,EAAgB,MAAM,EAAE,MAAM,mCAAmC,CAAA;AAC/E,OAAO,EAEH,sBAAsB,EAEzB,MAAM,kCAAkC,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAA;AAC1E,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,sCAAsC,CAAA;AACzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAA;AAYtE,qBAAa,0BAA2B,YAAW,eAAe;IAE1D,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,aAAa;gBAHb,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,sBAAsB,EACtC,aAAa,EAAE,sBAAsB;IAG3C,YAAY,CACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,SAAS,EAAE,SAAS,EACpB,OAAO,GAAE,OAAe,GACzB,OAAO,CAAC,MAAM,CAAC;IAwGZ,aAAa,CACf,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,cAAc,EAAE,MAAM,GACvB,OAAO,CAAC,IAAI,CAAC;CA6DnB"}
1
+ {"version":3,"file":"blockdaemon-wallet-allocator.d.ts","sourceRoot":"","sources":["../../../../src/ledger/wallet-allocation/signing-providers/blockdaemon-wallet-allocator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,kCAAkC,CAAA;AACzD,OAAO,EAAE,KAAK,EAAgB,MAAM,EAAE,MAAM,mCAAmC,CAAA;AAC/E,OAAO,EAEH,sBAAsB,EAEzB,MAAM,kCAAkC,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAA;AAC1E,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,sCAAsC,CAAA;AACzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAA;AAYtE,qBAAa,0BAA2B,YAAW,eAAe;IAE1D,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,aAAa;gBAHb,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,sBAAsB,EACtC,aAAa,EAAE,sBAAsB;IAG3C,YAAY,CACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,SAAS,EAAE,SAAS,EACpB,OAAO,GAAE,OAAe,GACzB,OAAO,CAAC,MAAM,CAAC;IAwGZ,aAAa,CACf,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,cAAc,EAAE,MAAM,GACvB,OAAO,CAAC,IAAI,CAAC;CAgEnB"}
@@ -102,7 +102,7 @@ export class BlockdaemonWalletAllocator {
102
102
  throw new Error('Existing wallet is missing field externalTxId or topologyTransactions');
103
103
  }
104
104
  const driver = this.signingDriver.controller(email);
105
- const { signature, status } = await driver
105
+ const { signature, status, metadata } = await driver
106
106
  .getTransaction({
107
107
  txId: existingWallet.externalTxId,
108
108
  })
@@ -131,6 +131,7 @@ export class BlockdaemonWalletAllocator {
131
131
  };
132
132
  }
133
133
  else {
134
+ this.logger.warn(`Topology transaction for wallet ${existingWallet.partyId} was ${status} with ${JSON.stringify(metadata)}`);
134
135
  const reason = status === 'rejected'
135
136
  ? WALLET_DISABLED_REASON.TOPOLOGY_TRANSACTION_REJECTED
136
137
  : WALLET_DISABLED_REASON.TOPOLOGY_TRANSACTION_FAILED;
@@ -1,6 +1,6 @@
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 { jest, describe, it, expect, beforeEach, afterEach, } from '@jest/globals';
3
+ import { vi, describe, it, expect, beforeEach, afterEach, } from 'vitest';
4
4
  import { pino } from 'pino';
5
5
  import { sink } from 'pino-test';
6
6
  import { WalletAllocationService } from './wallet-allocation-service.js';
@@ -34,14 +34,14 @@ function createFireblocksDriver(options) {
34
34
  };
35
35
  const getTransactionResult = options.getTransactionResult ?? signTransactionResult;
36
36
  return {
37
- controller: jest.fn().mockReturnValue({
38
- getKeys: jest
37
+ controller: vi.fn().mockReturnValue({
38
+ getKeys: vi
39
39
  .fn()
40
40
  .mockResolvedValue(getKeysResult),
41
- signTransaction: jest
41
+ signTransaction: vi
42
42
  .fn()
43
43
  .mockResolvedValue(signTransactionResult),
44
- getTransaction: jest
44
+ getTransaction: vi
45
45
  .fn()
46
46
  .mockResolvedValue(getTransactionResult),
47
47
  }),
@@ -54,16 +54,16 @@ function createBlockdaemonDriver(options) {
54
54
  };
55
55
  const getTransactionResult = options.getTransactionResult ?? signTransactionResult;
56
56
  return {
57
- controller: jest.fn().mockReturnValue({
58
- createKey: jest
57
+ controller: vi.fn().mockReturnValue({
58
+ createKey: vi
59
59
  .fn()
60
60
  .mockResolvedValue({
61
61
  publicKey: 'bd-pk',
62
62
  }),
63
- signTransaction: jest
63
+ signTransaction: vi
64
64
  .fn()
65
65
  .mockResolvedValue(signTransactionResult),
66
- getTransaction: jest
66
+ getTransaction: vi
67
67
  .fn()
68
68
  .mockResolvedValue(getTransactionResult),
69
69
  }),
@@ -80,19 +80,19 @@ describe('WalletAllocationService', () => {
80
80
  beforeEach(async () => {
81
81
  mockLogger = pino(sink());
82
82
  mockStore = {
83
- getWallets: jest.fn(),
84
- removeWallet: jest.fn(),
85
- addWallet: jest.fn(),
86
- updateWallet: jest.fn(),
87
- getCurrentNetwork: jest
83
+ getWallets: vi.fn(),
84
+ removeWallet: vi.fn(),
85
+ addWallet: vi.fn(),
86
+ updateWallet: vi.fn(),
87
+ getCurrentNetwork: vi
88
88
  .fn()
89
89
  .mockResolvedValue({ id: 'network1' }),
90
90
  };
91
91
  mockPartyAllocator = {
92
- allocateParty: jest.fn(),
93
- allocatePartyWithExistingWallet: jest.fn(),
94
- createFingerprintFromKey: jest.fn().mockReturnValue('fingerprint'),
95
- generateTopologyTransactions: jest
92
+ allocateParty: vi.fn(),
93
+ allocatePartyWithExistingWallet: vi.fn(),
94
+ createFingerprintFromKey: vi.fn().mockReturnValue('fingerprint'),
95
+ generateTopologyTransactions: vi
96
96
  .fn()
97
97
  .mockResolvedValue({
98
98
  topologyTransactions: ['tx1'],
@@ -100,14 +100,14 @@ describe('WalletAllocationService', () => {
100
100
  }),
101
101
  };
102
102
  mockController = {
103
- createKey: jest
103
+ createKey: vi
104
104
  .fn()
105
105
  .mockResolvedValue({
106
106
  id: 'key-id',
107
107
  name: 'test-key',
108
108
  publicKey: 'new-public-key',
109
109
  }),
110
- signTransaction: jest
110
+ signTransaction: vi
111
111
  .fn()
112
112
  .mockResolvedValue({
113
113
  txId: 'tx-id',
@@ -116,14 +116,14 @@ describe('WalletAllocationService', () => {
116
116
  }),
117
117
  };
118
118
  mockWalletKernelDriver = {
119
- controller: jest.fn(() => mockController),
119
+ controller: vi.fn(() => mockController),
120
120
  };
121
121
  service = createService({
122
122
  [SigningProvider.WALLET_KERNEL]: mockWalletKernelDriver,
123
123
  });
124
124
  });
125
125
  afterEach(() => {
126
- jest.restoreAllMocks();
126
+ vi.restoreAllMocks();
127
127
  });
128
128
  describe('Participant', () => {
129
129
  it('createWallet allocates new party and adds wallet', async () => {
@@ -346,7 +346,7 @@ export class WalletSyncService {
346
346
  const needsPrimaryReset = primaryWallet?.status === 'initialized' ||
347
347
  (!primaryWallet && allocatedWallets.length > 0);
348
348
  if (needsPrimaryReset && allocatedWallets.length > 0) {
349
- this.store.setPrimaryWallet(allocatedWallets[0].partyId);
349
+ await this.store.setPrimaryWallet(allocatedWallets[0].partyId);
350
350
  this.logger.info(`Set ${allocatedWallets[0].partyId} as primary wallet in network ${network.id}`);
351
351
  }
352
352
  const disabled = [
@@ -1,6 +1,6 @@
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 { jest, describe, it, expect, beforeEach, afterEach, } from '@jest/globals';
3
+ import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
4
4
  import { pino } from 'pino';
5
5
  import { sink } from 'pino-test';
6
6
  import { SigningProvider, } from '@canton-network/core-signing-lib';
@@ -11,9 +11,11 @@ import { PartyLevelRight, } from '@canton-network/core-wallet-store';
11
11
  import { StoreInternal } from '@canton-network/core-wallet-store-inmemory';
12
12
  import { WalletSyncService } from './wallet-sync-service.js';
13
13
  import { PartyAllocationService } from './party-allocation-service.js';
14
- const mockLedgerGet = jest.fn();
15
- jest.unstable_mockModule('@canton-network/core-ledger-client', () => ({
16
- LedgerClient: jest.fn().mockImplementation(() => {
14
+ const { mockLedgerGet } = vi.hoisted(() => ({
15
+ mockLedgerGet: vi.fn(),
16
+ }));
17
+ vi.mock('@canton-network/core-ledger-client', () => ({
18
+ LedgerClient: vi.fn(function LedgerClientMock() {
17
19
  return {
18
20
  getWithRetry: mockLedgerGet,
19
21
  };
@@ -83,7 +85,7 @@ describe('WalletSyncService - resolveSigningProvider', () => {
83
85
  }, partyAllocator);
84
86
  });
85
87
  afterEach(() => {
86
- jest.restoreAllMocks();
88
+ vi.restoreAllMocks();
87
89
  mockLedgerGet.mockClear();
88
90
  });
89
91
  it('resolves participant when namespace matches participant namespace', async () => {
@@ -128,8 +130,8 @@ describe('WalletSyncService - resolveSigningProvider', () => {
128
130
  const normalizedKey = partyAllocator.normalizePublicKeyToBase64(fireblocksPublicKeyHex);
129
131
  const namespace = partyAllocator.createFingerprintFromKey(normalizedKey);
130
132
  const mockFireblocksDriver = {
131
- controller: jest.fn().mockReturnValue({
132
- getKeys: jest
133
+ controller: vi.fn().mockReturnValue({
134
+ getKeys: vi
133
135
  .fn()
134
136
  .mockResolvedValue({
135
137
  keys: [
@@ -253,7 +255,7 @@ describe('WalletSyncService - multi-network features', () => {
253
255
  service = new WalletSyncService(store, mockLedgerClient, authContext, mockLogger, {}, partyAllocator);
254
256
  });
255
257
  afterEach(() => {
256
- jest.restoreAllMocks();
258
+ vi.restoreAllMocks();
257
259
  mockLedgerGet.mockClear();
258
260
  });
259
261
  it('isWalletSyncNeeded should filter by current network', async () => {
@@ -305,7 +307,7 @@ describe('WalletSyncService - multi-network features', () => {
305
307
  await store.addNetwork(network1);
306
308
  await setSession('network1');
307
309
  await store.addWallet(createWallet('party1::namespace', 'network1'));
308
- const addWalletSpy = jest.spyOn(store, 'addWallet');
310
+ const addWalletSpy = vi.spyOn(store, 'addWallet');
309
311
  mockLedgerGet
310
312
  .mockResolvedValueOnce({
311
313
  rights: [
@@ -542,7 +544,7 @@ describe('WalletSyncService - multi-network features', () => {
542
544
  .mockResolvedValueOnce({
543
545
  participantId: 'participant1::namespace',
544
546
  });
545
- const updateWalletSpy = jest.spyOn(store, 'updateWallet');
547
+ const updateWalletSpy = vi.spyOn(store, 'updateWallet');
546
548
  await service.syncWallets();
547
549
  expect(updateWalletSpy).toHaveBeenCalledWith(expect.objectContaining({
548
550
  partyId: 'party1::namespace',
@@ -575,7 +577,7 @@ describe('WalletSyncService - multi-network features', () => {
575
577
  .mockResolvedValueOnce({
576
578
  participantId: 'participant1::namespace',
577
579
  });
578
- const updateWalletSpy = jest.spyOn(store, 'updateWallet');
580
+ const updateWalletSpy = vi.spyOn(store, 'updateWallet');
579
581
  await service.syncWallets();
580
582
  expect(updateWalletSpy).not.toHaveBeenCalled();
581
583
  const wallets = await store.getWallets();
@@ -603,7 +605,7 @@ describe('WalletSyncService - multi-network features', () => {
603
605
  .mockResolvedValueOnce({
604
606
  participantId: 'participant1::namespace',
605
607
  });
606
- const updateWalletSpy = jest.spyOn(store, 'updateWallet');
608
+ const updateWalletSpy = vi.spyOn(store, 'updateWallet');
607
609
  await service.syncWallets();
608
610
  expect(updateWalletSpy).toHaveBeenCalledTimes(2);
609
611
  expect(updateWalletSpy).toHaveBeenCalledWith(expect.objectContaining({
@@ -639,7 +641,7 @@ describe('WalletSyncService - multi-network features', () => {
639
641
  .mockResolvedValueOnce({
640
642
  participantId: 'participant1::namespace',
641
643
  });
642
- const updateWalletSpy = jest.spyOn(store, 'updateWallet');
644
+ const updateWalletSpy = vi.spyOn(store, 'updateWallet');
643
645
  await service.syncWallets();
644
646
  expect(updateWalletSpy).toHaveBeenCalledWith(expect.objectContaining({
645
647
  partyId: 'party1::namespace',
@@ -1,2 +1,7 @@
1
+ import type { Request } from 'express';
2
+ export declare function ipRateLimitKeyGenerator(req: Request): string;
3
+ export declare function rateLimitKeyGenerator(req: Request): string;
1
4
  export declare function rateLimiter(requestRateLimit: number): import("express-rate-limit").RateLimitRequestHandler;
5
+ export declare function preAuthIpRateLimiter(requestRateLimit: number): import("express-rate-limit").RateLimitRequestHandler;
6
+ export declare function authenticatedRateLimiter(requestRateLimit: number): import("express-rate-limit").RateLimitRequestHandler;
2
7
  //# sourceMappingURL=rateLimit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rateLimit.d.ts","sourceRoot":"","sources":["../../src/middleware/rateLimit.ts"],"names":[],"mappings":"AAKA,wBAAgB,WAAW,CAAC,gBAAgB,EAAE,MAAM,wDAOnD"}
1
+ {"version":3,"file":"rateLimit.d.ts","sourceRoot":"","sources":["../../src/middleware/rateLimit.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAWtC,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAE5D;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAO1D;AAED,wBAAgB,WAAW,CAAC,gBAAgB,EAAE,MAAM,wDAQnD;AAED,wBAAgB,oBAAoB,CAAC,gBAAgB,EAAE,MAAM,wDAU5D;AAED,wBAAgB,wBAAwB,CAAC,gBAAgB,EAAE,MAAM,wDAUhE"}
@@ -1,10 +1,50 @@
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
3
  import rateLimit from 'express-rate-limit';
4
+ function hasBearerToken(req) {
5
+ const authHeader = req.headers.authorization;
6
+ if (typeof authHeader === 'string' && authHeader.startsWith('Bearer ')) {
7
+ return true;
8
+ }
9
+ return typeof req.query.token === 'string' && req.query.token.length > 0;
10
+ }
11
+ export function ipRateLimitKeyGenerator(req) {
12
+ return `ip:${req.ip || req.socket.remoteAddress || 'unknown'}`;
13
+ }
14
+ export function rateLimitKeyGenerator(req) {
15
+ // Prefer authenticated identity to avoid shared proxy IP buckets.
16
+ if (req.authContext?.userId) {
17
+ return `user:${req.authContext.userId}`;
18
+ }
19
+ return ipRateLimitKeyGenerator(req);
20
+ }
4
21
  export function rateLimiter(requestRateLimit) {
5
22
  return rateLimit({
6
23
  windowMs: 1 * 60 * 1000, // 1 minute
7
24
  max: requestRateLimit, // limit each IP to requestRateLimit requests per windowMs
25
+ keyGenerator: rateLimitKeyGenerator,
26
+ standardHeaders: true,
27
+ legacyHeaders: false,
28
+ });
29
+ }
30
+ export function preAuthIpRateLimiter(requestRateLimit) {
31
+ return rateLimit({
32
+ windowMs: 1 * 60 * 1000,
33
+ max: requestRateLimit,
34
+ keyGenerator: ipRateLimitKeyGenerator,
35
+ // Only protect unauthenticated traffic before JWT verification.
36
+ skip: hasBearerToken,
37
+ standardHeaders: true,
38
+ legacyHeaders: false,
39
+ });
40
+ }
41
+ export function authenticatedRateLimiter(requestRateLimit) {
42
+ return rateLimit({
43
+ windowMs: 1 * 60 * 1000,
44
+ max: requestRateLimit,
45
+ keyGenerator: rateLimitKeyGenerator,
46
+ // Apply only after JWT middleware has attached an authenticated context.
47
+ skip: (req) => !req.authContext?.userId,
8
48
  standardHeaders: true,
9
49
  legacyHeaders: false,
10
50
  });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rateLimit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rateLimit.test.d.ts","sourceRoot":"","sources":["../../src/middleware/rateLimit.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,30 @@
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 { describe, expect, test } from 'vitest';
4
+ import { ipRateLimitKeyGenerator, rateLimitKeyGenerator } from './rateLimit.js';
5
+ describe('ipRateLimitKeyGenerator', () => {
6
+ test('uses request ip address', () => {
7
+ const req = {
8
+ ip: '198.51.100.42',
9
+ socket: { remoteAddress: '10.0.0.1' },
10
+ };
11
+ expect(ipRateLimitKeyGenerator(req)).toBe('ip:198.51.100.42');
12
+ });
13
+ });
14
+ describe('rateLimitKeyGenerator', () => {
15
+ test('uses authenticated user id when available', () => {
16
+ const req = {
17
+ authContext: { userId: 'alice' },
18
+ ip: '203.0.113.10',
19
+ socket: { remoteAddress: '10.0.0.1' },
20
+ };
21
+ expect(rateLimitKeyGenerator(req)).toBe('user:alice');
22
+ });
23
+ test('falls back to request ip when user is not authenticated', () => {
24
+ const req = {
25
+ ip: '198.51.100.42',
26
+ socket: { remoteAddress: '10.0.0.1' },
27
+ };
28
+ expect(rateLimitKeyGenerator(req)).toBe('ip:198.51.100.42');
29
+ });
30
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/user-api/controller.ts"],"names":[],"mappings":"AA6BA,OAAO,EAAE,KAAK,EAAW,MAAM,mCAAmC,CAAA;AAClE,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAC7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AAC5E,OAAO,EAEH,WAAW,EAKd,MAAM,kCAAkC,CAAA;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,EACH,sBAAsB,EACtB,eAAe,EAClB,MAAM,kCAAkC,CAAA;AAQzC,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;;;;;;;;;;;;;;;;;;;;;;;CA8vBvB,CAAA"}
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/user-api/controller.ts"],"names":[],"mappings":"AA6BA,OAAO,EAAE,KAAK,EAAW,MAAM,mCAAmC,CAAA;AAClE,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAC7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AAC5E,OAAO,EAEH,WAAW,EAKd,MAAM,kCAAkC,CAAA;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,EACH,sBAAsB,EACtB,eAAe,EAClB,MAAM,kCAAkC,CAAA;AASzC,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;;;;;;;;;;;;;;;;;;;;;;;CAowBvB,CAAA"}