@canton-network/wallet-gateway-remote 1.4.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 (141) hide show
  1. package/dist/auth/jwt-auth-service.d.ts.map +1 -1
  2. package/dist/auth/jwt-auth-service.js +6 -5
  3. package/dist/auth/jwt-auth-service.test.d.ts +2 -0
  4. package/dist/auth/jwt-auth-service.test.d.ts.map +1 -0
  5. package/dist/auth/jwt-auth-service.test.js +259 -0
  6. package/dist/auth/jwt-unsafe-auth-service.test.d.ts +2 -0
  7. package/dist/auth/jwt-unsafe-auth-service.test.d.ts.map +1 -0
  8. package/dist/auth/jwt-unsafe-auth-service.test.js +120 -0
  9. package/dist/config/Config.d.ts +69 -4
  10. package/dist/config/Config.d.ts.map +1 -1
  11. package/dist/config/Config.js +6 -0
  12. package/dist/config/ConfigUtils.d.ts.map +1 -1
  13. package/dist/config/ConfigUtils.js +3 -0
  14. package/dist/dapp-api/controller.d.ts +5 -1
  15. package/dist/dapp-api/controller.d.ts.map +1 -1
  16. package/dist/dapp-api/controller.js +104 -30
  17. package/dist/dapp-api/controller.test.js +2 -2
  18. package/dist/dapp-api/server.d.ts +3 -2
  19. package/dist/dapp-api/server.d.ts.map +1 -1
  20. package/dist/dapp-api/server.js +2 -2
  21. package/dist/dapp-api/server.test.js +1 -6
  22. package/dist/example-config.d.ts +3 -0
  23. package/dist/example-config.d.ts.map +1 -1
  24. package/dist/example-config.js +3 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +10 -0
  27. package/dist/init.d.ts.map +1 -1
  28. package/dist/init.js +19 -7
  29. package/dist/ledger/transaction-service.d.ts +16 -11
  30. package/dist/ledger/transaction-service.d.ts.map +1 -1
  31. package/dist/ledger/transaction-service.js +135 -8
  32. package/dist/ledger/transaction-service.test.d.ts +2 -0
  33. package/dist/ledger/transaction-service.test.d.ts.map +1 -0
  34. package/dist/ledger/transaction-service.test.js +390 -0
  35. package/dist/ledger/wallet-allocation/signing-providers/fireblocks-wallet-allocator.d.ts +5 -2
  36. package/dist/ledger/wallet-allocation/signing-providers/fireblocks-wallet-allocator.d.ts.map +1 -1
  37. package/dist/ledger/wallet-allocation/signing-providers/fireblocks-wallet-allocator.js +7 -6
  38. package/dist/ledger/wallet-allocation/wallet-allocation-service.d.ts +11 -5
  39. package/dist/ledger/wallet-allocation/wallet-allocation-service.d.ts.map +1 -1
  40. package/dist/ledger/wallet-allocation/wallet-allocation-service.js +28 -14
  41. package/dist/ledger/wallet-allocation/wallet-allocation-service.test.js +550 -19
  42. package/dist/ledger/wallet-sync-service.js +3 -3
  43. package/dist/middleware/apiKeyAuth.d.ts +6 -0
  44. package/dist/middleware/apiKeyAuth.d.ts.map +1 -0
  45. package/dist/middleware/apiKeyAuth.js +90 -0
  46. package/dist/middleware/apiKeyAuth.test.d.ts +2 -0
  47. package/dist/middleware/apiKeyAuth.test.d.ts.map +1 -0
  48. package/dist/middleware/apiKeyAuth.test.js +126 -0
  49. package/dist/middleware/jsonRpcHandler.d.ts +11 -1
  50. package/dist/middleware/jsonRpcHandler.d.ts.map +1 -1
  51. package/dist/middleware/jsonRpcHandler.js +14 -15
  52. package/dist/middleware/jsonRpcHandler.test.d.ts +2 -0
  53. package/dist/middleware/jsonRpcHandler.test.d.ts.map +1 -0
  54. package/dist/middleware/jsonRpcHandler.test.js +254 -0
  55. package/dist/middleware/jwtAuth.d.ts.map +1 -1
  56. package/dist/middleware/jwtAuth.js +4 -0
  57. package/dist/middleware/jwtAuth.test.d.ts +2 -0
  58. package/dist/middleware/jwtAuth.test.d.ts.map +1 -0
  59. package/dist/middleware/jwtAuth.test.js +93 -0
  60. package/dist/middleware/rateLimit.test.js +12 -0
  61. package/dist/middleware/sessionHandler.js +1 -1
  62. package/dist/middleware/sessionHandler.test.d.ts +2 -0
  63. package/dist/middleware/sessionHandler.test.d.ts.map +1 -0
  64. package/dist/middleware/sessionHandler.test.js +121 -0
  65. package/dist/notification/NotificationService.test.d.ts +2 -0
  66. package/dist/notification/NotificationService.test.d.ts.map +1 -0
  67. package/dist/notification/NotificationService.test.js +110 -0
  68. package/dist/signing/service-account-session.d.ts +16 -0
  69. package/dist/signing/service-account-session.d.ts.map +1 -0
  70. package/dist/signing/service-account-session.js +25 -0
  71. package/dist/signing/service-account-session.test.d.ts +2 -0
  72. package/dist/signing/service-account-session.test.d.ts.map +1 -0
  73. package/dist/signing/service-account-session.test.js +89 -0
  74. package/dist/signing/signing-drivers.d.ts +7 -0
  75. package/dist/signing/signing-drivers.d.ts.map +1 -0
  76. package/dist/signing/signing-drivers.js +3 -0
  77. package/dist/signing/signing-worker.d.ts +83 -0
  78. package/dist/signing/signing-worker.d.ts.map +1 -0
  79. package/dist/signing/signing-worker.js +206 -0
  80. package/dist/signing/signing-worker.test.d.ts +2 -0
  81. package/dist/signing/signing-worker.test.d.ts.map +1 -0
  82. package/dist/signing/signing-worker.test.js +152 -0
  83. package/dist/user-api/controller.d.ts +6 -4
  84. package/dist/user-api/controller.d.ts.map +1 -1
  85. package/dist/user-api/controller.js +119 -95
  86. package/dist/user-api/controller.test.d.ts +2 -0
  87. package/dist/user-api/controller.test.d.ts.map +1 -0
  88. package/dist/user-api/controller.test.js +862 -0
  89. package/dist/user-api/rpc-gen/index.d.ts +12 -0
  90. package/dist/user-api/rpc-gen/index.d.ts.map +1 -1
  91. package/dist/user-api/rpc-gen/index.js +4 -0
  92. package/dist/user-api/rpc-gen/typings.d.ts +52 -3
  93. package/dist/user-api/rpc-gen/typings.d.ts.map +1 -1
  94. package/dist/utils.d.ts +17 -1
  95. package/dist/utils.d.ts.map +1 -1
  96. package/dist/utils.js +53 -2
  97. package/dist/utils.test.d.ts +2 -0
  98. package/dist/utils.test.d.ts.map +1 -0
  99. package/dist/utils.test.js +24 -0
  100. package/dist/web/frontend/404/index.html +2 -2
  101. package/dist/web/frontend/activities/index.html +3 -3
  102. package/dist/web/frontend/approve/index.html +4 -4
  103. package/dist/web/frontend/assets/{404-C7V2yl2z.js → 404-C4hCL2o8.js} +2 -2
  104. package/dist/web/frontend/assets/{activities-NY8oZAkH.js → activities-Bq7yMMQY.js} +2 -2
  105. package/dist/web/frontend/assets/addIdentityProvider-DRJWLhHF.js +28 -0
  106. package/dist/web/frontend/assets/addNetwork-CJUaKP1q.js +38 -0
  107. package/dist/web/frontend/assets/addParty-2ObTLGIL.js +45 -0
  108. package/dist/web/frontend/assets/approve-BuA5DGC2.js +21 -0
  109. package/dist/web/frontend/assets/callback-C5vJWFLq.js +1 -0
  110. package/dist/web/frontend/assets/{identityProviders-BZ9h_VOJ.js → identityProviders-78FbpzYL.js} +2 -2
  111. package/dist/web/frontend/assets/{index-B-HiKugW.js → index-BTH_md6b.js} +3 -3
  112. package/dist/web/frontend/assets/index-CFnOucPK.js +4737 -0
  113. package/dist/web/frontend/assets/{login-CqwMEG9J.js → login-Dyn1o6fW.js} +2 -2
  114. package/dist/web/frontend/assets/{networks-BJvRi47M.js → networks-_el10AWP.js} +2 -2
  115. package/dist/web/frontend/assets/reviewIdentityProvider-DodUZ2mY.js +43 -0
  116. package/dist/web/frontend/assets/reviewNetwork-BFMtmKEL.js +43 -0
  117. package/dist/web/frontend/assets/settings-BBmkIfYm.js +37 -0
  118. package/dist/web/frontend/assets/{state-qAsNw5qf.js → state-DkxLvywv.js} +1 -1
  119. package/dist/web/frontend/assets/{utils-BDkHxi8V.js → utils-DvJKIYxT.js} +1 -1
  120. package/dist/web/frontend/callback/index.html +2 -2
  121. package/dist/web/frontend/identity-providers/add/index.html +3 -3
  122. package/dist/web/frontend/identity-providers/index.html +3 -3
  123. package/dist/web/frontend/identity-providers/review/index.html +3 -3
  124. package/dist/web/frontend/index.html +1 -1
  125. package/dist/web/frontend/login/index.html +3 -3
  126. package/dist/web/frontend/networks/add/index.html +3 -3
  127. package/dist/web/frontend/networks/index.html +3 -3
  128. package/dist/web/frontend/networks/review/index.html +3 -3
  129. package/dist/web/frontend/parties/add/index.html +5 -5
  130. package/dist/web/frontend/parties/index.html +4 -4
  131. package/dist/web/frontend/settings/index.html +3 -3
  132. package/package.json +36 -32
  133. package/dist/web/frontend/assets/addIdentityProvider-38ghz3RT.js +0 -28
  134. package/dist/web/frontend/assets/addNetwork-BaDhbrZl.js +0 -38
  135. package/dist/web/frontend/assets/addParty-j3JWZX5a.js +0 -41
  136. package/dist/web/frontend/assets/approve-43nVzAAC.js +0 -21
  137. package/dist/web/frontend/assets/callback-BBMiCjHR.js +0 -1
  138. package/dist/web/frontend/assets/index-DoNqknyE.js +0 -4042
  139. package/dist/web/frontend/assets/reviewIdentityProvider-ySlQnw5R.js +0 -43
  140. package/dist/web/frontend/assets/reviewNetwork-DrVxeHd4.js +0 -43
  141. package/dist/web/frontend/assets/settings-BVNkaUyJ.js +0 -37
@@ -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
- import { ledgerPrepareParams } from '../utils.js';
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}`);
@@ -15,11 +17,116 @@ export class TransactionService {
15
17
  this.signingDrivers = signingDrivers;
16
18
  this.notifier = notifier;
17
19
  }
20
+ async sign(authContext, wallet, signParams) {
21
+ const signingProvider = wallet.signingProviderId;
22
+ const driver = this.signingDrivers[signingProvider]?.controller(authContext.userId);
23
+ if (!driver) {
24
+ throw new Error(`No driver found for ${signingProvider}`);
25
+ }
26
+ switch (signingProvider) {
27
+ case SigningProvider.PARTICIPANT: {
28
+ return this.signWithParticipant(wallet);
29
+ }
30
+ case SigningProvider.WALLET_KERNEL: {
31
+ return this.signWithWalletKernel(authContext.userId, wallet, signParams);
32
+ }
33
+ case SigningProvider.BLOCKDAEMON: {
34
+ if (!authContext.email) {
35
+ throw new Error('Email is required for Blockdaemon wallet allocation');
36
+ }
37
+ return this.signWithBlockdaemon(authContext.email, wallet, signParams);
38
+ }
39
+ case SigningProvider.FIREBLOCKS: {
40
+ return this.signWithFireblocks(authContext.userId, wallet, signParams);
41
+ }
42
+ case SigningProvider.DFNS: {
43
+ return this.signWithDfns(authContext.userId, wallet, signParams);
44
+ }
45
+ default:
46
+ throw new Error(`Unsupported signing provider: ${wallet.signingProviderId}`);
47
+ }
48
+ }
49
+ execute(userId, wallet, transaction, executeParams, ledgerClient, network) {
50
+ if (transaction.status === 'executed') {
51
+ throw new Error('Transaction is already executed.');
52
+ }
53
+ switch (wallet.signingProviderId) {
54
+ case SigningProvider.PARTICIPANT: {
55
+ try {
56
+ if (!executeParams) {
57
+ throw new Error('Execute params are required for participant signing');
58
+ }
59
+ if (!ledgerClient) {
60
+ throw new Error('Ledger client is required for participant signing');
61
+ }
62
+ if (!network) {
63
+ throw new Error('Network is required for participant signing');
64
+ }
65
+ return this.executeWithParticipant(userId, executeParams, transaction, ledgerClient, network);
66
+ }
67
+ catch (error) {
68
+ this.logger.error(error, 'Failed to submit transaction');
69
+ throw error;
70
+ }
71
+ }
72
+ case SigningProvider.WALLET_KERNEL:
73
+ case SigningProvider.BLOCKDAEMON:
74
+ case SigningProvider.FIREBLOCKS: {
75
+ if (!executeParams) {
76
+ throw new Error('Execute params are required for external signing');
77
+ }
78
+ if (!ledgerClient) {
79
+ throw new Error('Ledger client is required for external signing');
80
+ }
81
+ return this.executeWithExternal(userId, executeParams, transaction, ledgerClient);
82
+ }
83
+ case SigningProvider.DFNS: {
84
+ return this.executeWithDfns(transaction);
85
+ }
86
+ default:
87
+ throw new Error(`Unsupported signing provider: ${wallet.signingProviderId}`);
88
+ }
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
+ }
18
122
  async loadPreparedTransactionForSigning(transactionId) {
19
123
  const existingTx = await this.store.getTransaction(transactionId);
20
124
  if (!existingTx) {
21
125
  throw new Error(`Transaction not found with id: ${transactionId}`);
22
126
  }
127
+ if (existingTx.status !== 'pending') {
128
+ throw new Error(`Cannot sign an already ${existingTx.status} transaction`);
129
+ }
23
130
  return existingTx;
24
131
  }
25
132
  signWithParticipant(wallet) {
@@ -79,7 +186,7 @@ export class TransactionService {
79
186
  const driver = signingProvider.controller(userId);
80
187
  const tx = await this.loadPreparedTransactionForSigning(signParams.transactionId);
81
188
  let signingResult;
82
- if (tx && tx.externalTxId) {
189
+ if (tx.externalTxId) {
83
190
  signingResult = await driver
84
191
  .getTransaction({
85
192
  userId,
@@ -104,6 +211,10 @@ export class TransactionService {
104
211
  .then(handleSigningError);
105
212
  }
106
213
  const now = new Date();
214
+ logDynamically(this.logger, 'Blockdaemon signing result', {
215
+ info: { transactionId: tx.id, status: signingResult.status },
216
+ debug: { signingResult, tx },
217
+ });
107
218
  if (signingResult.status === 'signed') {
108
219
  if (!signingResult.signature) {
109
220
  throw new Error('No signature returned from signing driver');
@@ -164,7 +275,7 @@ export class TransactionService {
164
275
  const driver = signingProvider.controller(userId);
165
276
  const tx = await this.loadPreparedTransactionForSigning(signParams.transactionId);
166
277
  let signingResult;
167
- if (tx && tx.externalTxId) {
278
+ if (tx.externalTxId) {
168
279
  signingResult = await driver
169
280
  .getTransaction({
170
281
  userId,
@@ -185,6 +296,10 @@ export class TransactionService {
185
296
  .then(handleSigningError);
186
297
  }
187
298
  const now = new Date();
299
+ logDynamically(this.logger, 'Fireblocks signing result', {
300
+ info: { transactionId: tx.id, status: signingResult.status },
301
+ debug: { signingResult, tx },
302
+ });
188
303
  if (signingResult.status === 'signed') {
189
304
  if (!signingResult.signature) {
190
305
  throw new Error('No signature returned from signing driver');
@@ -273,6 +388,10 @@ export class TransactionService {
273
388
  .then(handleSigningError);
274
389
  }
275
390
  const now = new Date();
391
+ logDynamically(this.logger, 'Dfns signing result', {
392
+ info: { transactionId: tx.id, status: signingResult.status },
393
+ debug: { signingResult, tx },
394
+ });
276
395
  if (signingResult.status === 'signed') {
277
396
  if (!signingResult.signature) {
278
397
  throw new Error('No updateId returned from Dfns');
@@ -359,15 +478,19 @@ export class TransactionService {
359
478
  const { partyId } = executeParams;
360
479
  const { commandId } = transaction;
361
480
  const synchronizerId = network.synchronizerId ?? (await ledgerClient.getSynchronizerId());
362
- const prep = ledgerPrepareParams(userId, partyId, synchronizerId, transaction.payload);
363
- const res = await ledgerClient.postWithRetry('/v2/commands/submit-and-wait', prep);
481
+ const prep = ledgerPrepareParams(userId, [partyId], synchronizerId, transaction.payload);
482
+ const result = await ledgerClient.postWithRetry('/v2/commands/submit-and-wait', prep);
483
+ logDynamically(this.logger, 'Participant execution result', {
484
+ info: { transactionId: transaction.id },
485
+ debug: { result, transaction, executeParams, userId },
486
+ });
364
487
  const executedTx = {
365
488
  id: transaction.id,
366
489
  commandId,
367
490
  status: 'executed',
368
491
  preparedTransaction: transaction.preparedTransaction,
369
492
  preparedTransactionHash: transaction.preparedTransactionHash,
370
- payload: res,
493
+ payload: result,
371
494
  origin: transaction.origin ?? null,
372
495
  ...(transaction.createdAt && {
373
496
  createdAt: transaction.createdAt,
@@ -377,10 +500,10 @@ export class TransactionService {
377
500
  }),
378
501
  };
379
502
  await this.store.setTransactionStatus(transaction.id, 'executed', {
380
- payload: res,
503
+ payload: result,
381
504
  });
382
505
  this.notifier.emit('txChanged', executedTx);
383
- return res;
506
+ return result;
384
507
  }
385
508
  async executeWithExternal(userId, executeParams, transaction, ledgerClient) {
386
509
  const { partyId, signature, signedBy } = executeParams;
@@ -409,6 +532,10 @@ export class TransactionService {
409
532
  ],
410
533
  },
411
534
  });
535
+ logDynamically(this.logger, 'Externally signed execution result', {
536
+ info: { transactionId: transaction.id },
537
+ debug: { result, transaction, executeParams, userId },
538
+ });
412
539
  const executedTx = {
413
540
  id: transaction.id,
414
541
  commandId,
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=transaction-service.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transaction-service.test.d.ts","sourceRoot":"","sources":["../../src/ledger/transaction-service.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,390 @@
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 { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
+ import { pino } from 'pino';
5
+ import { sink } from 'pino-test';
6
+ import { SigningProvider, } from '@canton-network/core-signing-lib';
7
+ import { TransactionService } from './transaction-service.js';
8
+ const authContext = {
9
+ userId: 'user-1',
10
+ accessToken: 'access-token-1',
11
+ };
12
+ const authContextWithEmail = {
13
+ ...authContext,
14
+ email: 'user@example.com',
15
+ };
16
+ const wallet = {
17
+ primary: true,
18
+ partyId: 'party::namespace',
19
+ status: 'allocated',
20
+ hint: 'party',
21
+ signingProviderId: SigningProvider.WALLET_KERNEL,
22
+ publicKey: 'wallet-public-key',
23
+ namespace: 'namespace',
24
+ networkId: 'network1',
25
+ rights: [],
26
+ };
27
+ const pendingTransaction = {
28
+ id: 'tx-1',
29
+ commandId: 'cmd-1',
30
+ status: 'pending',
31
+ preparedTransaction: 'prepared-tx',
32
+ preparedTransactionHash: 'tx-hash',
33
+ origin: 'https://dapp.example',
34
+ createdAt: new Date('2026-01-01T00:00:00.000Z'),
35
+ };
36
+ const signParams = {
37
+ transactionId: pendingTransaction.id,
38
+ partyId: wallet.partyId,
39
+ };
40
+ const executeParams = {
41
+ transactionId: pendingTransaction.id,
42
+ partyId: wallet.partyId,
43
+ signature: 'signature',
44
+ signedBy: wallet.namespace,
45
+ };
46
+ const network = {
47
+ id: 'network1',
48
+ name: 'testnet',
49
+ synchronizerId: 'sync::fingerprint',
50
+ description: 'Test',
51
+ identityProviderId: 'idp1',
52
+ ledgerApi: { baseUrl: 'http://ledger.test' },
53
+ auth: {
54
+ method: 'authorization_code',
55
+ clientId: 'cid',
56
+ scope: 'scope',
57
+ audience: 'aud',
58
+ },
59
+ };
60
+ function walletWithProvider(signingProviderId) {
61
+ return { ...wallet, signingProviderId };
62
+ }
63
+ function createDriver(options) {
64
+ return {
65
+ controller: vi.fn().mockReturnValue({
66
+ signTransaction: options.signTransaction ??
67
+ vi.fn().mockResolvedValue({ signature: 'driver-signature' }),
68
+ getTransaction: options.getTransaction ?? vi.fn().mockResolvedValue({}),
69
+ }),
70
+ };
71
+ }
72
+ function createStore(transaction = pendingTransaction) {
73
+ return {
74
+ getTransaction: vi.fn().mockResolvedValue(transaction),
75
+ setTransactionSigned: vi.fn().mockResolvedValue(undefined),
76
+ setTransactionStatus: vi.fn().mockResolvedValue(undefined),
77
+ };
78
+ }
79
+ function createService(store, drivers, notifier, logger) {
80
+ return new TransactionService(store, logger, drivers, notifier);
81
+ }
82
+ describe('TransactionService', () => {
83
+ let logger;
84
+ let notifier;
85
+ let emit;
86
+ beforeEach(() => {
87
+ logger = pino({ level: 'silent' }, sink());
88
+ emit = vi.fn();
89
+ notifier = { emit };
90
+ });
91
+ afterEach(() => {
92
+ vi.clearAllMocks();
93
+ });
94
+ describe('sign', () => {
95
+ describe('participant', () => {
96
+ it('returns a signed result without calling external drivers', async () => {
97
+ const store = createStore();
98
+ const service = createService(store, {
99
+ [SigningProvider.PARTICIPANT]: createDriver({}),
100
+ }, notifier, logger);
101
+ const participantWallet = walletWithProvider(SigningProvider.PARTICIPANT);
102
+ const result = await service.sign(authContext, participantWallet, signParams);
103
+ expect(result).toEqual({
104
+ status: 'signed',
105
+ signature: 'none',
106
+ signedBy: wallet.namespace,
107
+ partyId: wallet.partyId,
108
+ });
109
+ expect(store.getTransaction).not.toHaveBeenCalled();
110
+ });
111
+ });
112
+ describe('wallet-kernel', () => {
113
+ it('signs the transaction and persists the signed state', async () => {
114
+ const signTransaction = vi
115
+ .fn()
116
+ .mockResolvedValue({ signature: 'kernel-signature' });
117
+ const store = createStore();
118
+ const service = createService(store, {
119
+ [SigningProvider.WALLET_KERNEL]: createDriver({
120
+ signTransaction,
121
+ }),
122
+ }, notifier, logger);
123
+ const result = await service.sign(authContext, wallet, signParams);
124
+ expect(signTransaction).toHaveBeenCalledWith({
125
+ tx: pendingTransaction.preparedTransaction,
126
+ txHash: pendingTransaction.preparedTransactionHash,
127
+ keyIdentifier: { publicKey: wallet.publicKey },
128
+ });
129
+ expect(store.setTransactionSigned).toHaveBeenCalledWith(pendingTransaction.id, expect.any(Date));
130
+ expect(emit).toHaveBeenCalledWith('txChanged', expect.objectContaining({
131
+ id: pendingTransaction.id,
132
+ status: 'signed',
133
+ }));
134
+ expect(result).toEqual({
135
+ status: 'signed',
136
+ signature: 'kernel-signature',
137
+ signedBy: wallet.namespace,
138
+ partyId: wallet.partyId,
139
+ });
140
+ });
141
+ it('throws when the wallet-kernel driver is missing', async () => {
142
+ const service = createService(createStore(), {}, notifier, logger);
143
+ await expect(service.sign(authContext, wallet, signParams)).rejects.toThrow('No driver found for wallet-kernel');
144
+ });
145
+ it('throws when the transaction does not exist', async () => {
146
+ const store = createStore();
147
+ store.getTransaction.mockResolvedValue(undefined);
148
+ const service = createService(store, {
149
+ [SigningProvider.WALLET_KERNEL]: createDriver({}),
150
+ }, notifier, logger);
151
+ await expect(service.sign(authContext, wallet, signParams)).rejects.toThrow('Transaction not found with id: tx-1');
152
+ });
153
+ it('throws when the driver returns an RPC error', async () => {
154
+ const signTransaction = vi.fn().mockResolvedValue({
155
+ error: 'access_denied',
156
+ error_description: 'Signing rejected',
157
+ });
158
+ const service = createService(createStore(), {
159
+ [SigningProvider.WALLET_KERNEL]: createDriver({
160
+ signTransaction,
161
+ }),
162
+ }, notifier, logger);
163
+ await expect(service.sign(authContext, wallet, signParams)).rejects.toThrow('Error from signing driver: Signing rejected');
164
+ });
165
+ });
166
+ describe('blockdaemon', () => {
167
+ const blockdaemonWallet = walletWithProvider(SigningProvider.BLOCKDAEMON);
168
+ it('throws when email is missing from auth context', async () => {
169
+ const service = createService(createStore(), {
170
+ [SigningProvider.BLOCKDAEMON]: createDriver({}),
171
+ }, notifier, logger);
172
+ await expect(service.sign(authContext, blockdaemonWallet, signParams)).rejects.toThrow('Email is required for Blockdaemon wallet allocation');
173
+ });
174
+ it('starts signing when there is no external transaction id yet', async () => {
175
+ const signTransaction = vi.fn().mockResolvedValue({
176
+ status: 'pending',
177
+ txId: 'external-tx-1',
178
+ });
179
+ const store = createStore();
180
+ const service = createService(store, {
181
+ [SigningProvider.BLOCKDAEMON]: createDriver({
182
+ signTransaction,
183
+ }),
184
+ }, notifier, logger);
185
+ const result = await service.sign(authContextWithEmail, blockdaemonWallet, signParams);
186
+ expect(signTransaction).toHaveBeenCalledWith(expect.objectContaining({
187
+ tx: pendingTransaction.preparedTransaction,
188
+ internalTxId: expect.any(String),
189
+ }));
190
+ expect(store.setTransactionStatus).toHaveBeenCalledWith(pendingTransaction.id, 'pending', { externalTxId: 'external-tx-1' });
191
+ expect(result).toEqual({
192
+ status: 'pending',
193
+ externalTxId: 'external-tx-1',
194
+ partyId: wallet.partyId,
195
+ });
196
+ });
197
+ it('fetches transaction when an external transaction id already exists', async () => {
198
+ const getTransaction = vi.fn().mockResolvedValue({
199
+ status: 'signed',
200
+ txId: 'external-tx-1',
201
+ signature: 'bd-signature',
202
+ });
203
+ const store = createStore({
204
+ ...pendingTransaction,
205
+ externalTxId: 'external-tx-1',
206
+ });
207
+ const service = createService(store, {
208
+ [SigningProvider.BLOCKDAEMON]: createDriver({
209
+ getTransaction,
210
+ }),
211
+ }, notifier, logger);
212
+ const result = await service.sign(authContextWithEmail, blockdaemonWallet, signParams);
213
+ expect(getTransaction).toHaveBeenCalledWith({
214
+ userId: authContextWithEmail.email,
215
+ txId: 'external-tx-1',
216
+ });
217
+ expect(store.setTransactionSigned).toHaveBeenCalledWith(pendingTransaction.id, expect.any(Date), 'external-tx-1');
218
+ expect(result).toMatchObject({
219
+ status: 'signed',
220
+ signature: 'bd-signature',
221
+ externalTxId: 'external-tx-1',
222
+ });
223
+ });
224
+ });
225
+ describe('fireblocks', () => {
226
+ it('returns a base64 signature when signing completes', async () => {
227
+ const hexSignature = Buffer.from('fireblocks-signature').toString('hex');
228
+ const signTransaction = vi.fn().mockResolvedValue({
229
+ status: 'signed',
230
+ txId: 'fb-tx-1',
231
+ signature: hexSignature,
232
+ });
233
+ const store = createStore();
234
+ const service = createService(store, {
235
+ [SigningProvider.FIREBLOCKS]: createDriver({
236
+ signTransaction,
237
+ }),
238
+ }, notifier, logger);
239
+ const result = await service.sign(authContext, walletWithProvider(SigningProvider.FIREBLOCKS), signParams);
240
+ expect(signTransaction).toHaveBeenCalledWith(expect.objectContaining({
241
+ userId: authContext.userId,
242
+ txHash: Buffer.from(pendingTransaction.preparedTransactionHash, 'base64').toString('hex'),
243
+ }));
244
+ expect(result).toMatchObject({
245
+ status: 'signed',
246
+ signature: Buffer.from(hexSignature, 'hex').toString('base64'),
247
+ externalTxId: 'fb-tx-1',
248
+ });
249
+ });
250
+ });
251
+ describe('dfns', () => {
252
+ it('persists the update id as the signature when signing completes', async () => {
253
+ const signTransaction = vi.fn().mockResolvedValue({
254
+ status: 'signed',
255
+ txId: 'dfns-tx-1',
256
+ signature: 'update-id-123',
257
+ });
258
+ const store = createStore();
259
+ const service = createService(store, {
260
+ [SigningProvider.DFNS]: createDriver({
261
+ signTransaction,
262
+ }),
263
+ }, notifier, logger);
264
+ const result = await service.sign(authContext, walletWithProvider(SigningProvider.DFNS), signParams);
265
+ expect(result).toEqual({
266
+ status: 'signed',
267
+ signature: 'update-id-123',
268
+ signedBy: wallet.namespace,
269
+ partyId: wallet.partyId,
270
+ externalTxId: 'dfns-tx-1',
271
+ });
272
+ });
273
+ });
274
+ });
275
+ describe('execute', () => {
276
+ describe('dfns', () => {
277
+ it('marks the transaction executed using the external tx id', async () => {
278
+ const signedTransaction = {
279
+ ...pendingTransaction,
280
+ status: 'signed',
281
+ externalTxId: 'dfns-update-id',
282
+ };
283
+ const store = createStore(signedTransaction);
284
+ const service = createService(store, {}, notifier, logger);
285
+ const result = await service.execute(authContext.userId, walletWithProvider(SigningProvider.DFNS), signedTransaction);
286
+ expect(store.setTransactionStatus).toHaveBeenCalledWith(signedTransaction.id, 'executed', { externalTxId: 'dfns-update-id' });
287
+ expect(emit).toHaveBeenCalledWith('txChanged', expect.objectContaining({ status: 'executed' }));
288
+ expect(result).toEqual({ updateId: 'dfns-update-id' });
289
+ });
290
+ it('throws when the transaction has no external tx id', async () => {
291
+ const service = createService(createStore(), {}, notifier, logger);
292
+ await expect(service.execute(authContext.userId, walletWithProvider(SigningProvider.DFNS), pendingTransaction)).rejects.toThrow('Cannot execute Dfns transaction without externalTxId from Dfns');
293
+ });
294
+ });
295
+ describe('participant', () => {
296
+ it('submits the prepared transaction to the ledger', async () => {
297
+ const participantWallet = walletWithProvider(SigningProvider.PARTICIPANT);
298
+ const transaction = {
299
+ ...pendingTransaction,
300
+ payload: {
301
+ commandId: pendingTransaction.commandId,
302
+ commands: [],
303
+ },
304
+ };
305
+ const store = createStore(transaction);
306
+ const postWithRetry = vi
307
+ .fn()
308
+ .mockResolvedValue({ updateId: 'ledger-update-1' });
309
+ const ledgerClient = {
310
+ postWithRetry,
311
+ getSynchronizerId: vi.fn(),
312
+ };
313
+ const service = createService(store, {}, notifier, logger);
314
+ const result = await service.execute(authContext.userId, participantWallet, transaction, executeParams, ledgerClient, network);
315
+ expect(postWithRetry).toHaveBeenCalledWith('/v2/commands/submit-and-wait', expect.objectContaining({
316
+ commandId: pendingTransaction.commandId,
317
+ userId: authContext.userId,
318
+ synchronizerId: network.synchronizerId,
319
+ }));
320
+ expect(store.setTransactionStatus).toHaveBeenCalledWith(pendingTransaction.id, 'executed', { payload: { updateId: 'ledger-update-1' } });
321
+ expect(result).toEqual({ updateId: 'ledger-update-1' });
322
+ });
323
+ });
324
+ describe('external signing providers', () => {
325
+ it('executes the prepared transaction with the provided signature', async () => {
326
+ const signedTransaction = {
327
+ ...pendingTransaction,
328
+ status: 'signed',
329
+ };
330
+ const store = createStore(signedTransaction);
331
+ const postWithRetry = vi
332
+ .fn()
333
+ .mockResolvedValue({ updateId: 'external-update-1' });
334
+ const ledgerClient = {
335
+ postWithRetry,
336
+ };
337
+ const service = createService(store, {}, notifier, logger);
338
+ const result = await service.execute(authContext.userId, wallet, signedTransaction, executeParams, ledgerClient, network);
339
+ expect(postWithRetry).toHaveBeenCalledWith('/v2/interactive-submission/executeAndWait', expect.objectContaining({
340
+ userId: authContext.userId,
341
+ preparedTransaction: pendingTransaction.preparedTransaction,
342
+ submissionId: pendingTransaction.commandId,
343
+ partySignatures: expect.objectContaining({
344
+ signatures: [
345
+ expect.objectContaining({
346
+ party: wallet.partyId,
347
+ }),
348
+ ],
349
+ }),
350
+ }));
351
+ expect(store.setTransactionStatus).toHaveBeenCalledWith(pendingTransaction.id, 'executed', { payload: { updateId: 'external-update-1' } });
352
+ expect(result).toEqual({ updateId: 'external-update-1' });
353
+ });
354
+ });
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
+ });
390
+ });
@@ -3,7 +3,7 @@ import { Store, Wallet } from '@canton-network/core-wallet-store';
3
3
  import { SigningDriverInterface } from '@canton-network/core-signing-lib';
4
4
  import { Logger } from 'pino';
5
5
  import { PartyAllocationService } from '../../party-allocation-service.js';
6
- import { PartyHint, Primary } from '../../../user-api/rpc-gen/typings.js';
6
+ import { PartyHint, Primary, VaultName } from '../../../user-api/rpc-gen/typings.js';
7
7
  import type { WalletAllocator } from '../wallet-allocation-service.js';
8
8
  export declare class FireblocksWalletAllocator implements WalletAllocator {
9
9
  private store;
@@ -11,7 +11,10 @@ export declare class FireblocksWalletAllocator implements WalletAllocator {
11
11
  private partyAllocator;
12
12
  private signingDriver;
13
13
  constructor(store: Store, logger: Logger, partyAllocator: PartyAllocationService, signingDriver: SigningDriverInterface);
14
- createWallet(userId: UserId, email: string | undefined, partyHint: PartyHint, primary?: Primary): Promise<Wallet>;
14
+ createWallet(userId: UserId, email: string | undefined, partyHint: PartyHint, primary: Primary | undefined, vaultName: VaultName): Promise<Wallet>;
15
15
  allocateParty(userId: UserId, email: string | undefined, existingWallet: Wallet): Promise<void>;
16
+ getVaults(userId: UserId): Promise<{
17
+ vaults: string[];
18
+ }>;
16
19
  }
17
20
  //# sourceMappingURL=fireblocks-wallet-allocator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fireblocks-wallet-allocator.d.ts","sourceRoot":"","sources":["../../../../src/ledger/wallet-allocation/signing-providers/fireblocks-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,yBAA0B,YAAW,eAAe;IAEzD,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;IA6FZ,aAAa,CACf,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,cAAc,EAAE,MAAM,GACvB,OAAO,CAAC,IAAI,CAAC;CAkEnB"}
1
+ {"version":3,"file":"fireblocks-wallet-allocator.d.ts","sourceRoot":"","sources":["../../../../src/ledger/wallet-allocation/signing-providers/fireblocks-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,EACH,SAAS,EACT,OAAO,EACP,SAAS,EACZ,MAAM,sCAAsC,CAAA;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAA;AAYtE,qBAAa,yBAA0B,YAAW,eAAe;IAEzD,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,EAAE,OAAO,YAAQ,EACxB,SAAS,EAAE,SAAS,GACrB,OAAO,CAAC,MAAM,CAAC;IA6FZ,aAAa,CACf,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,cAAc,EAAE,MAAM,GACvB,OAAO,CAAC,IAAI,CAAC;IAgEV,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CAKjE"}