@canton-network/wallet-gateway-remote 1.2.1 → 1.4.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 (78) hide show
  1. package/README.md +1 -1
  2. package/dist/config/Config.d.ts +4 -4
  3. package/dist/dapp-api/controller.d.ts +1 -0
  4. package/dist/dapp-api/controller.d.ts.map +1 -1
  5. package/dist/dapp-api/controller.js +33 -2
  6. package/dist/dapp-api/controller.test.d.ts +2 -0
  7. package/dist/dapp-api/controller.test.d.ts.map +1 -0
  8. package/dist/dapp-api/controller.test.js +516 -0
  9. package/dist/dapp-api/rpc-gen/index.d.ts +3 -0
  10. package/dist/dapp-api/rpc-gen/index.d.ts.map +1 -1
  11. package/dist/dapp-api/rpc-gen/index.js +1 -0
  12. package/dist/dapp-api/rpc-gen/typings.d.ts +99 -14
  13. package/dist/dapp-api/rpc-gen/typings.d.ts.map +1 -1
  14. package/dist/dapp-api/server.d.ts.map +1 -1
  15. package/dist/dapp-api/server.js +5 -0
  16. package/dist/init.d.ts.map +1 -1
  17. package/dist/init.js +1 -0
  18. package/dist/middleware/rateLimit.js +2 -2
  19. package/dist/middleware/rateLimit.test.js +14 -0
  20. package/dist/user-api/controller.d.ts +6 -0
  21. package/dist/user-api/controller.d.ts.map +1 -1
  22. package/dist/user-api/controller.js +223 -5
  23. package/dist/user-api/rpc-gen/index.d.ts +18 -0
  24. package/dist/user-api/rpc-gen/index.d.ts.map +1 -1
  25. package/dist/user-api/rpc-gen/index.js +6 -0
  26. package/dist/user-api/rpc-gen/typings.d.ts +119 -23
  27. package/dist/user-api/rpc-gen/typings.d.ts.map +1 -1
  28. package/dist/user-api/server.test.js +53 -0
  29. package/dist/utils.d.ts +2 -4
  30. package/dist/utils.d.ts.map +1 -1
  31. package/dist/web/frontend/404/index.html +2 -2
  32. package/dist/web/frontend/activities/index.html +3 -3
  33. package/dist/web/frontend/approve/index.html +4 -5
  34. package/dist/web/frontend/assets/404-C7V2yl2z.js +5 -0
  35. package/dist/web/frontend/assets/activities-NY8oZAkH.js +61 -0
  36. package/dist/web/frontend/assets/addIdentityProvider-38ghz3RT.js +28 -0
  37. package/dist/web/frontend/assets/addNetwork-BaDhbrZl.js +38 -0
  38. package/dist/web/frontend/assets/addParty-j3JWZX5a.js +41 -0
  39. package/dist/web/frontend/assets/approve-43nVzAAC.js +21 -0
  40. package/dist/web/frontend/assets/callback-BBMiCjHR.js +1 -0
  41. package/dist/web/frontend/assets/identityProviders-BZ9h_VOJ.js +71 -0
  42. package/dist/web/frontend/assets/index-B-HiKugW.js +91 -0
  43. package/dist/web/frontend/assets/{index-_pMHlJoE.js → index-DoNqknyE.js} +116 -116
  44. package/dist/web/frontend/assets/login-CqwMEG9J.js +10 -0
  45. package/dist/web/frontend/assets/networks-BJvRi47M.js +73 -0
  46. package/dist/web/frontend/assets/reviewIdentityProvider-ySlQnw5R.js +43 -0
  47. package/dist/web/frontend/assets/reviewNetwork-DrVxeHd4.js +43 -0
  48. package/dist/web/frontend/assets/{settings-B1ga2TK0.js → settings-BVNkaUyJ.js} +1 -1
  49. package/dist/web/frontend/assets/state-qAsNw5qf.js +1 -0
  50. package/dist/web/frontend/assets/{utils-CT9Hzi7v.js → utils-BDkHxi8V.js} +1 -1
  51. package/dist/web/frontend/callback/index.html +2 -2
  52. package/dist/web/frontend/identity-providers/add/index.html +3 -3
  53. package/dist/web/frontend/identity-providers/index.html +3 -3
  54. package/dist/web/frontend/identity-providers/review/index.html +3 -3
  55. package/dist/web/frontend/index.html +1 -1
  56. package/dist/web/frontend/login/index.html +3 -4
  57. package/dist/web/frontend/networks/add/index.html +3 -3
  58. package/dist/web/frontend/networks/index.html +3 -3
  59. package/dist/web/frontend/networks/review/index.html +3 -3
  60. package/dist/web/frontend/parties/add/index.html +5 -5
  61. package/dist/web/frontend/parties/index.html +4 -4
  62. package/dist/web/frontend/settings/index.html +3 -3
  63. package/package.json +27 -24
  64. package/dist/web/frontend/assets/404-B-o9ppJB.js +0 -5
  65. package/dist/web/frontend/assets/activities-CGjCIjUH.js +0 -61
  66. package/dist/web/frontend/assets/addIdentityProvider-CR4Wm9Tl.js +0 -28
  67. package/dist/web/frontend/assets/addNetwork-Dx0-SN4j.js +0 -38
  68. package/dist/web/frontend/assets/addParty-COhk_rFn.js +0 -41
  69. package/dist/web/frontend/assets/approve-B2w66l0J.js +0 -21
  70. package/dist/web/frontend/assets/callback-BTVon_yQ.js +0 -1
  71. package/dist/web/frontend/assets/identityProviders-CK8zSrd3.js +0 -71
  72. package/dist/web/frontend/assets/index-CF4BKzgl.js +0 -91
  73. package/dist/web/frontend/assets/index-DWz_3f3y.js +0 -1
  74. package/dist/web/frontend/assets/login-CVoPNVDw.js +0 -10
  75. package/dist/web/frontend/assets/networks-D1nPvUzM.js +0 -73
  76. package/dist/web/frontend/assets/reviewIdentityProvider-CNSf2qQv.js +0 -43
  77. package/dist/web/frontend/assets/reviewNetwork-DGLK-Ume.js +0 -43
  78. package/dist/web/frontend/assets/state-B2k3ak7d.js +0 -1
package/README.md CHANGED
@@ -59,7 +59,7 @@ Dfns provisions and activates Canton wallets through its validator integration,
59
59
 
60
60
  ## Fireblocks
61
61
 
62
- 1. Complete steps 1–3 from the instructions at https://github.com/canton-network/wallet-gateway/tree/main/core/signing-fireblocks
62
+ 1. Complete steps 1–3 from the instructions at https://github.com/canton-network/wallet/tree/main/core/signing-fireblocks
63
63
 
64
64
  2. set the environment variable `FIREBLOCKS_API_KEY` (get it from `API User (ID)` column in fireblocks api users table).
65
65
 
@@ -61,7 +61,7 @@ export declare const rawConfigSchema: z.ZodObject<{
61
61
  user: z.ZodString;
62
62
  password: z.ZodString;
63
63
  database: z.ZodString;
64
- }, z.core.$strip>], "type">;
64
+ }, z.core.$loose>], "type">;
65
65
  }, z.core.$strip>;
66
66
  signingStore: z.ZodObject<{
67
67
  connection: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -76,7 +76,7 @@ export declare const rawConfigSchema: z.ZodObject<{
76
76
  user: z.ZodString;
77
77
  password: z.ZodString;
78
78
  database: z.ZodString;
79
- }, z.core.$strip>], "type">;
79
+ }, z.core.$loose>], "type">;
80
80
  }, z.core.$strip>;
81
81
  bootstrap: z.ZodObject<{
82
82
  idps: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -220,7 +220,7 @@ export declare const configSchema: z.ZodObject<{
220
220
  user: z.ZodString;
221
221
  password: z.ZodString;
222
222
  database: z.ZodString;
223
- }, z.core.$strip>], "type">;
223
+ }, z.core.$loose>], "type">;
224
224
  }, z.core.$strip>;
225
225
  signingStore: z.ZodObject<{
226
226
  connection: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -235,7 +235,7 @@ export declare const configSchema: z.ZodObject<{
235
235
  user: z.ZodString;
236
236
  password: z.ZodString;
237
237
  database: z.ZodString;
238
- }, z.core.$strip>], "type">;
238
+ }, z.core.$loose>], "type">;
239
239
  }, z.core.$strip>;
240
240
  bootstrap: z.ZodObject<{
241
241
  idps: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -18,5 +18,6 @@ export declare const dappController: (kernelInfo: KernelInfoConfig, dappUrl: str
18
18
  getPrimaryAccount: import("./rpc-gen/typings.js").GetPrimaryAccount;
19
19
  listAccounts: import("./rpc-gen/typings.js").ListAccounts;
20
20
  txChanged: import("./rpc-gen/typings.js").TxChanged;
21
+ messageSignature: import("./rpc-gen/typings.js").MessageSignature;
21
22
  };
22
23
  //# sourceMappingURL=controller.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/dapp-api/controller.ts"],"names":[],"mappings":"AAGA,OAAO,EAEH,WAAW,EAEd,MAAM,kCAAkC,CAAA;AAYzC,OAAO,EAAE,KAAK,EAAe,MAAM,mCAAmC,CAAA;AAQtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AAC5E,OAAO,EAAE,UAAU,IAAI,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACpE,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAI7B,eAAO,MAAM,cAAc,GACvB,YAAY,gBAAgB,EAC5B,SAAS,MAAM,EACf,SAAS,MAAM,EACf,OAAO,KAAK,EACZ,qBAAqB,mBAAmB,EACxC,SAAS,MAAM,EACf,QAAQ,MAAM,GAAG,IAAI,EACrB,UAAU,WAAW;;;;;;;;;;;;;;;CAmTxB,CAAA"}
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/dapp-api/controller.ts"],"names":[],"mappings":"AAGA,OAAO,EAEH,WAAW,EAEd,MAAM,kCAAkC,CAAA;AAczC,OAAO,EAAE,KAAK,EAAe,MAAM,mCAAmC,CAAA;AAQtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AAC5E,OAAO,EAAE,UAAU,IAAI,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACpE,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAI7B,eAAO,MAAM,cAAc,GACvB,YAAY,gBAAgB,EAC5B,SAAS,MAAM,EACf,SAAS,MAAM,EACf,OAAO,KAAK,EACZ,qBAAqB,mBAAmB,EACxC,SAAS,MAAM,EACf,QAAQ,MAAM,GAAG,IAAI,EACrB,UAAU,WAAW;;;;;;;;;;;;;;;;CAyVxB,CAAA"}
@@ -246,8 +246,36 @@ export const dappController = (kernelInfo, dappUrl, userUrl, store, notification
246
246
  : {}),
247
247
  };
248
248
  },
249
- signMessage: function () {
250
- throw new Error('Function not implemented.');
249
+ signMessage: async (params) => {
250
+ if (!params?.message)
251
+ throw new Error('Message is required');
252
+ const wallet = await store.getPrimaryWallet();
253
+ if (context === undefined) {
254
+ throw new Error('Unauthenticated context');
255
+ }
256
+ if (wallet === undefined) {
257
+ throw new Error('No primary wallet found');
258
+ }
259
+ const notifier = notificationService.getNotifier(context.userId);
260
+ const messageId = v4();
261
+ await store.setMessageRaw({
262
+ id: messageId,
263
+ status: 'pending',
264
+ userId: context.userId,
265
+ partyId: wallet.partyId,
266
+ publicKey: wallet.publicKey,
267
+ message: params.message,
268
+ origin: origin || null,
269
+ createdAt: new Date(),
270
+ });
271
+ notifier.emit('messageSignature', {
272
+ status: 'pending',
273
+ messageId,
274
+ });
275
+ return {
276
+ messageId,
277
+ userUrl: `${userUrl}/sign-message/index.html?messageId=${messageId}&closeafteraction`,
278
+ };
251
279
  },
252
280
  getPrimaryAccount: async function () {
253
281
  const wallet = await store.getPrimaryWallet();
@@ -256,6 +284,9 @@ export const dappController = (kernelInfo, dappUrl, userUrl, store, notification
256
284
  }
257
285
  return wallet;
258
286
  },
287
+ messageSignature: function () {
288
+ throw new Error('Only for events.');
289
+ },
259
290
  });
260
291
  };
261
292
  async function prepareSubmission(userId, partyId, synchronizerId, params, ledgerClient) {
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=controller.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller.test.d.ts","sourceRoot":"","sources":["../../src/dapp-api/controller.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,516 @@
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 { PartyLevelRight, } from '@canton-network/core-wallet-store';
7
+ import { StoreInternal } from '@canton-network/core-wallet-store-inmemory';
8
+ import { SigningProvider } from '@canton-network/core-signing-lib';
9
+ import { NotificationService } from '../notification/NotificationService.js';
10
+ import { dappController } from './controller.js';
11
+ const ledgerMocks = vi.hoisted(() => ({
12
+ getWithRetry: vi.fn(),
13
+ postWithRetry: vi.fn(),
14
+ getSynchronizerId: vi.fn(),
15
+ }));
16
+ const mockNetworkStatus = vi.hoisted(() => vi.fn().mockResolvedValue({
17
+ isConnected: true,
18
+ reason: undefined,
19
+ cantonVersion: '3.4',
20
+ }));
21
+ const mockUuidV4 = vi.hoisted(() => vi.fn());
22
+ vi.mock('@canton-network/core-ledger-client', async (importOriginal) => {
23
+ const actual = await importOriginal();
24
+ return {
25
+ ...actual,
26
+ LedgerClient: vi.fn(function LedgerClientMock() {
27
+ return {
28
+ getWithRetry: ledgerMocks.getWithRetry,
29
+ postWithRetry: ledgerMocks.postWithRetry,
30
+ getSynchronizerId: ledgerMocks.getSynchronizerId,
31
+ };
32
+ }),
33
+ };
34
+ });
35
+ vi.mock('../utils.js', async (importOriginal) => {
36
+ const actual = await importOriginal();
37
+ return {
38
+ ...actual,
39
+ networkStatus: mockNetworkStatus,
40
+ };
41
+ });
42
+ vi.mock('uuid', () => ({
43
+ v4: mockUuidV4,
44
+ }));
45
+ const kernelInfo = {
46
+ id: 'kernel-test',
47
+ clientType: 'browser',
48
+ };
49
+ const dappUrl = 'https://dapp.api.example';
50
+ const userUrl = 'https://user.api.example';
51
+ const origin = 'https://dapp.example';
52
+ const idp = {
53
+ id: 'idp1',
54
+ type: 'oauth',
55
+ issuer: 'http://auth',
56
+ configUrl: 'http://auth/.well-known/openid-configuration',
57
+ };
58
+ const storeNetwork = {
59
+ id: 'network1',
60
+ name: 'testnet',
61
+ synchronizerId: 'sync1::fingerprint',
62
+ description: 'Test',
63
+ identityProviderId: 'idp1',
64
+ ledgerApi: { baseUrl: 'http://ledger.test' },
65
+ auth: {
66
+ method: 'authorization_code',
67
+ clientId: 'cid',
68
+ scope: 'scope',
69
+ audience: 'aud',
70
+ },
71
+ adminAuth: {
72
+ method: 'client_credentials',
73
+ clientId: 'admin-cid',
74
+ clientSecret: 'admin-secret',
75
+ audience: 'admin-aud',
76
+ scope: 'admin-scope',
77
+ },
78
+ };
79
+ const auth = {
80
+ userId: 'user-1',
81
+ accessToken: 'access-token-1',
82
+ };
83
+ const session = {
84
+ id: 'session-1',
85
+ network: 'network1',
86
+ accessToken: 'session-token',
87
+ };
88
+ const primaryWallet = {
89
+ primary: true,
90
+ partyId: 'party::namespace',
91
+ status: 'allocated',
92
+ hint: 'party',
93
+ signingProviderId: SigningProvider.WALLET_KERNEL,
94
+ publicKey: 'wallet-public-key',
95
+ namespace: 'namespace',
96
+ networkId: 'network1',
97
+ rights: [PartyLevelRight.CanActAs],
98
+ };
99
+ async function createStore(logger, context, options = {}) {
100
+ const { withSession = true, withWallet = true } = options;
101
+ const store = new StoreInternal({ idps: [idp], networks: [storeNetwork] }, logger, context);
102
+ if (context && withSession) {
103
+ await store.setSession(session);
104
+ }
105
+ if (context && withWallet) {
106
+ await store.addWallet(primaryWallet);
107
+ }
108
+ return store;
109
+ }
110
+ function createController(store, notificationService, logger, context, requestOrigin = origin) {
111
+ return dappController(kernelInfo, dappUrl, userUrl, store, notificationService, logger, requestOrigin, context);
112
+ }
113
+ describe('dappController', () => {
114
+ let logger;
115
+ let notificationService;
116
+ beforeEach(() => {
117
+ logger = pino({ level: 'silent' }, sink());
118
+ notificationService = new NotificationService(logger);
119
+ ledgerMocks.getWithRetry.mockReset();
120
+ ledgerMocks.postWithRetry.mockReset();
121
+ ledgerMocks.getSynchronizerId.mockReset();
122
+ ledgerMocks.getSynchronizerId.mockResolvedValue('sync-from-ledger');
123
+ mockNetworkStatus.mockReset();
124
+ mockNetworkStatus.mockResolvedValue({
125
+ isConnected: true,
126
+ reason: undefined,
127
+ cantonVersion: '3.4',
128
+ });
129
+ mockUuidV4.mockReset();
130
+ });
131
+ afterEach(() => {
132
+ vi.clearAllMocks();
133
+ });
134
+ describe('connect', () => {
135
+ it('returns unauthenticated when there is no auth context', async () => {
136
+ const store = await createStore(logger, auth, {
137
+ withSession: false,
138
+ });
139
+ const controller = createController(store, notificationService, logger, undefined);
140
+ await expect(controller.connect()).resolves.toEqual({
141
+ isConnected: false,
142
+ isNetworkConnected: false,
143
+ networkReason: 'Unauthenticated',
144
+ userUrl: `${userUrl}/login/`,
145
+ });
146
+ });
147
+ it('returns unauthenticated when there is no session', async () => {
148
+ const store = await createStore(logger, auth, {
149
+ withSession: false,
150
+ });
151
+ const controller = createController(store, notificationService, logger, auth);
152
+ await expect(controller.connect()).resolves.toEqual({
153
+ isConnected: false,
154
+ isNetworkConnected: false,
155
+ networkReason: 'Unauthenticated',
156
+ userUrl: `${userUrl}/login/`,
157
+ });
158
+ });
159
+ it('connects and emits statusChanged and connected', async () => {
160
+ const store = await createStore(logger, auth);
161
+ const notifier = notificationService.getNotifier(auth.userId);
162
+ const emitSpy = vi.spyOn(notifier, 'emit');
163
+ const controller = createController(store, notificationService, logger, auth);
164
+ const result = await controller.connect();
165
+ expect(result).toMatchObject({
166
+ isConnected: true,
167
+ reason: 'OK',
168
+ isNetworkConnected: true,
169
+ networkReason: 'OK',
170
+ userUrl: `${userUrl}/login/`,
171
+ });
172
+ expect(mockNetworkStatus).toHaveBeenCalled();
173
+ expect(emitSpy).toHaveBeenCalledWith('statusChanged', expect.objectContaining({
174
+ connection: expect.objectContaining({
175
+ isConnected: true,
176
+ }),
177
+ session: {
178
+ accessToken: auth.accessToken,
179
+ userId: auth.userId,
180
+ },
181
+ }));
182
+ expect(emitSpy).toHaveBeenCalledWith('connected', expect.objectContaining({
183
+ provider: expect.objectContaining({ id: kernelInfo.id }),
184
+ }));
185
+ });
186
+ it('reflects disconnected ledger in network status', async () => {
187
+ mockNetworkStatus.mockResolvedValue({
188
+ isConnected: false,
189
+ reason: 'Ledger unreachable',
190
+ });
191
+ const store = await createStore(logger, auth);
192
+ const controller = createController(store, notificationService, logger, auth);
193
+ const result = await controller.connect();
194
+ expect(result).toMatchObject({
195
+ isConnected: true,
196
+ isNetworkConnected: false,
197
+ networkReason: 'Ledger unreachable',
198
+ });
199
+ });
200
+ });
201
+ describe('disconnect', () => {
202
+ it('returns null when there is no auth context', async () => {
203
+ const store = await createStore(logger, auth);
204
+ const removeSessionSpy = vi.spyOn(store, 'removeSession');
205
+ const controller = createController(store, notificationService, logger, undefined);
206
+ await expect(controller.disconnect()).resolves.toBeNull();
207
+ expect(removeSessionSpy).not.toHaveBeenCalled();
208
+ });
209
+ it('removes the session and emits statusChanged', async () => {
210
+ const store = await createStore(logger, auth);
211
+ const notifier = notificationService.getNotifier(auth.userId);
212
+ const emitSpy = vi.spyOn(notifier, 'emit');
213
+ const controller = createController(store, notificationService, logger, auth);
214
+ await controller.disconnect();
215
+ await expect(store.getSession()).resolves.toBeUndefined();
216
+ expect(emitSpy).toHaveBeenCalledWith('statusChanged', expect.objectContaining({
217
+ connection: expect.objectContaining({
218
+ isConnected: false,
219
+ reason: 'disconnect',
220
+ }),
221
+ }));
222
+ });
223
+ });
224
+ describe('isConnected', () => {
225
+ it('returns unauthenticated when there is no session', async () => {
226
+ const store = await createStore(logger, auth, {
227
+ withSession: false,
228
+ });
229
+ const controller = createController(store, notificationService, logger, auth);
230
+ await expect(controller.isConnected()).resolves.toEqual({
231
+ isConnected: false,
232
+ isNetworkConnected: false,
233
+ networkReason: 'Unauthenticated',
234
+ userUrl: `${userUrl}/login/`,
235
+ });
236
+ });
237
+ it('returns connected status when authenticated', async () => {
238
+ const store = await createStore(logger, auth);
239
+ const controller = createController(store, notificationService, logger, auth);
240
+ await expect(controller.isConnected()).resolves.toEqual({
241
+ isConnected: true,
242
+ reason: 'OK',
243
+ isNetworkConnected: true,
244
+ networkReason: 'OK',
245
+ userUrl: `${userUrl}/login/`,
246
+ });
247
+ });
248
+ });
249
+ describe('status', () => {
250
+ it('returns unauthenticated status when there is no session', async () => {
251
+ const store = await createStore(logger, auth, {
252
+ withSession: false,
253
+ });
254
+ const controller = createController(store, notificationService, logger, auth);
255
+ const result = await controller.status();
256
+ expect(result.connection).toMatchObject({
257
+ isConnected: false,
258
+ reason: 'Unauthenticated',
259
+ });
260
+ expect(result.network).toBeUndefined();
261
+ });
262
+ it('returns full status when authenticated', async () => {
263
+ const store = await createStore(logger, auth);
264
+ const controller = createController(store, notificationService, logger, auth);
265
+ const result = await controller.status();
266
+ expect(result).toMatchObject({
267
+ provider: expect.objectContaining({ id: kernelInfo.id }),
268
+ connection: {
269
+ isConnected: true,
270
+ reason: 'OK',
271
+ isNetworkConnected: true,
272
+ networkReason: 'OK',
273
+ },
274
+ network: {
275
+ networkId: storeNetwork.id,
276
+ ledgerApi: storeNetwork.ledgerApi.baseUrl,
277
+ accessToken: auth.accessToken,
278
+ },
279
+ session: {
280
+ id: session.id,
281
+ accessToken: auth.accessToken,
282
+ userId: auth.userId,
283
+ },
284
+ userUrl: `${userUrl}/login/`,
285
+ });
286
+ });
287
+ });
288
+ describe('ledgerApi', () => {
289
+ it('performs GET via ledger client', async () => {
290
+ ledgerMocks.getWithRetry.mockResolvedValueOnce({ ok: true });
291
+ const store = await createStore(logger, auth);
292
+ const controller = createController(store, notificationService, logger, auth);
293
+ const result = await controller.ledgerApi({
294
+ requestMethod: 'get',
295
+ resource: '/v2/parties',
296
+ path: { partyId: 'party::x' },
297
+ query: { limit: '10' },
298
+ });
299
+ expect(ledgerMocks.getWithRetry).toHaveBeenCalledWith('/v2/parties', undefined, {
300
+ path: { partyId: 'party::x' },
301
+ query: { limit: '10' },
302
+ });
303
+ expect(result).toEqual({ ok: true });
304
+ });
305
+ it('performs POST via ledger client', async () => {
306
+ ledgerMocks.postWithRetry.mockResolvedValueOnce({ created: true });
307
+ const store = await createStore(logger, auth);
308
+ const controller = createController(store, notificationService, logger, auth);
309
+ const result = await controller.ledgerApi({
310
+ requestMethod: 'post',
311
+ resource: '/v2/commands/submit-and-wait',
312
+ body: { commands: [] },
313
+ query: { wait: 'true' },
314
+ });
315
+ expect(ledgerMocks.postWithRetry).toHaveBeenCalledWith('/v2/commands/submit-and-wait', { commands: [] }, undefined, { query: { wait: 'true' }, path: {} });
316
+ expect(result).toEqual({ created: true });
317
+ });
318
+ it('throws for unsupported request methods', async () => {
319
+ const store = await createStore(logger, auth);
320
+ const controller = createController(store, notificationService, logger, auth);
321
+ await expect(controller.ledgerApi({
322
+ requestMethod: 'patch',
323
+ resource: '/v2/parties',
324
+ })).rejects.toThrow('Unsupported request method: patch');
325
+ });
326
+ it('throws when auth context is missing', async () => {
327
+ const store = await createStore(logger, auth);
328
+ const controller = createController(store, notificationService, logger, undefined);
329
+ await expect(controller.ledgerApi({
330
+ requestMethod: 'get',
331
+ resource: '/v2/parties',
332
+ })).rejects.toThrow();
333
+ });
334
+ });
335
+ describe('prepareExecute', () => {
336
+ const prepareParams = {
337
+ commands: [
338
+ {
339
+ CreateCommand: {
340
+ templateId: 'pkg:Mod:T',
341
+ createArguments: {},
342
+ },
343
+ },
344
+ ],
345
+ };
346
+ it('throws when auth context is missing', async () => {
347
+ const store = await createStore(logger, auth);
348
+ const controller = createController(store, notificationService, logger, undefined);
349
+ await expect(controller.prepareExecute(prepareParams)).rejects.toThrow('Unauthenticated context');
350
+ });
351
+ it('throws when there is no primary wallet', async () => {
352
+ const store = await createStore(logger, auth, { withWallet: false });
353
+ const controller = createController(store, notificationService, logger, auth);
354
+ await expect(controller.prepareExecute(prepareParams)).rejects.toThrow('No primary wallet found');
355
+ });
356
+ it('prepares a transaction and returns the approve URL', async () => {
357
+ mockUuidV4.mockReturnValueOnce('generated-command-id');
358
+ mockUuidV4.mockReturnValueOnce('transaction-id');
359
+ ledgerMocks.postWithRetry.mockResolvedValueOnce({
360
+ preparedTransaction: 'prepared-blob',
361
+ preparedTransactionHash: 'hash',
362
+ });
363
+ const store = await createStore(logger, auth);
364
+ const setTransactionSpy = vi.spyOn(store, 'setTransaction');
365
+ const notifier = notificationService.getNotifier(auth.userId);
366
+ const emitSpy = vi.spyOn(notifier, 'emit');
367
+ const controller = createController(store, notificationService, logger, auth);
368
+ const result = await controller.prepareExecute(prepareParams);
369
+ expect(emitSpy).toHaveBeenCalledWith('txChanged', {
370
+ status: 'pending',
371
+ commandId: 'generated-command-id',
372
+ });
373
+ expect(ledgerMocks.postWithRetry).toHaveBeenCalledWith('/v2/interactive-submission/prepare', expect.any(Object));
374
+ expect(setTransactionSpy).toHaveBeenCalledWith(expect.objectContaining({
375
+ id: 'transaction-id',
376
+ commandId: 'generated-command-id',
377
+ status: 'pending',
378
+ preparedTransaction: 'prepared-blob',
379
+ preparedTransactionHash: 'hash',
380
+ origin,
381
+ }));
382
+ expect(result.userUrl).toBe(`${userUrl}/approve/index.html?transactionId=transaction-id&commandId=generated-command-id&closeafteraction`);
383
+ });
384
+ it('uses the provided commandId when present', async () => {
385
+ mockUuidV4.mockReturnValueOnce('transaction-id');
386
+ ledgerMocks.postWithRetry.mockResolvedValueOnce({
387
+ preparedTransaction: 'prepared-blob',
388
+ preparedTransactionHash: 'hash',
389
+ });
390
+ const store = await createStore(logger, auth);
391
+ const controller = createController(store, notificationService, logger, auth);
392
+ await controller.prepareExecute({
393
+ ...prepareParams,
394
+ commandId: 'existing-command-id',
395
+ });
396
+ expect(mockUuidV4).toHaveBeenCalledOnce();
397
+ expect(ledgerMocks.postWithRetry.mock.calls[0][1]).toMatchObject({
398
+ commandId: 'existing-command-id',
399
+ });
400
+ });
401
+ it('fetches synchronizerId from ledger when not on network', async () => {
402
+ const networkWithoutSync = {
403
+ ...storeNetwork,
404
+ synchronizerId: undefined,
405
+ };
406
+ const store = new StoreInternal({ idps: [idp], networks: [networkWithoutSync] }, logger, auth);
407
+ await store.setSession(session);
408
+ await store.addWallet(primaryWallet);
409
+ mockUuidV4.mockReturnValueOnce('cmd-id');
410
+ mockUuidV4.mockReturnValueOnce('tx-id');
411
+ ledgerMocks.postWithRetry.mockResolvedValueOnce({
412
+ preparedTransaction: 'blob',
413
+ preparedTransactionHash: 'hash',
414
+ });
415
+ const controller = createController(store, notificationService, logger, auth);
416
+ await controller.prepareExecute(prepareParams);
417
+ expect(ledgerMocks.getSynchronizerId).toHaveBeenCalled();
418
+ });
419
+ });
420
+ describe('signMessage', () => {
421
+ it('throws when message is missing', async () => {
422
+ const store = await createStore(logger, auth);
423
+ const controller = createController(store, notificationService, logger, auth);
424
+ await expect(controller.signMessage({ message: '' })).rejects.toThrow('Message is required');
425
+ });
426
+ it('throws when auth context is missing', async () => {
427
+ const store = await createStore(logger, auth);
428
+ const controller = createController(store, notificationService, logger, undefined);
429
+ await expect(controller.signMessage({ message: 'hello' })).rejects.toThrow('Unauthenticated context');
430
+ });
431
+ it('throws when there is no primary wallet', async () => {
432
+ const store = await createStore(logger, auth, { withWallet: false });
433
+ const controller = createController(store, notificationService, logger, auth);
434
+ await expect(controller.signMessage({ message: 'hello' })).rejects.toThrow('No primary wallet found');
435
+ });
436
+ it('stores a pending message and returns the sign URL', async () => {
437
+ mockUuidV4.mockReturnValueOnce('message-id');
438
+ const store = await createStore(logger, auth);
439
+ const setMessageSpy = vi.spyOn(store, 'setMessageRaw');
440
+ const notifier = notificationService.getNotifier(auth.userId);
441
+ const emitSpy = vi.spyOn(notifier, 'emit');
442
+ const controller = createController(store, notificationService, logger, auth);
443
+ const result = await controller.signMessage({ message: 'hello' });
444
+ expect(setMessageSpy).toHaveBeenCalledWith(expect.objectContaining({
445
+ id: 'message-id',
446
+ status: 'pending',
447
+ userId: auth.userId,
448
+ partyId: primaryWallet.partyId,
449
+ publicKey: primaryWallet.publicKey,
450
+ message: 'hello',
451
+ origin,
452
+ }));
453
+ expect(emitSpy).toHaveBeenCalledWith('messageSignature', {
454
+ status: 'pending',
455
+ messageId: 'message-id',
456
+ });
457
+ expect(result).toEqual({
458
+ messageId: 'message-id',
459
+ userUrl: `${userUrl}/sign-message/index.html?messageId=message-id&closeafteraction`,
460
+ });
461
+ });
462
+ });
463
+ describe('accounts', () => {
464
+ it('lists accounts from the store', async () => {
465
+ const store = await createStore(logger, auth);
466
+ const controller = createController(store, notificationService, logger, auth);
467
+ const accounts = await controller.listAccounts();
468
+ expect(accounts).toHaveLength(1);
469
+ expect(accounts[0]?.partyId).toBe(primaryWallet.partyId);
470
+ });
471
+ it('returns the primary account', async () => {
472
+ const store = await createStore(logger, auth);
473
+ const controller = createController(store, notificationService, logger, auth);
474
+ await expect(controller.getPrimaryAccount()).resolves.toEqual(primaryWallet);
475
+ });
476
+ it('throws when there is no primary account', async () => {
477
+ const store = await createStore(logger, auth, { withWallet: false });
478
+ const controller = createController(store, notificationService, logger, auth);
479
+ await expect(controller.getPrimaryAccount()).rejects.toThrow('No primary wallet found');
480
+ });
481
+ it('returns the active network with access token when authenticated', async () => {
482
+ const store = await createStore(logger, auth);
483
+ const controller = createController(store, notificationService, logger, auth);
484
+ await expect(controller.getActiveNetwork()).resolves.toEqual({
485
+ networkId: storeNetwork.id,
486
+ ledgerApi: storeNetwork.ledgerApi.baseUrl,
487
+ accessToken: auth.accessToken,
488
+ });
489
+ });
490
+ it('returns the active network without access token when unauthenticated', async () => {
491
+ const store = await createStore(logger, auth);
492
+ const controller = createController(store, notificationService, logger, undefined);
493
+ await expect(controller.getActiveNetwork()).resolves.toEqual({
494
+ networkId: storeNetwork.id,
495
+ ledgerApi: storeNetwork.ledgerApi.baseUrl,
496
+ });
497
+ });
498
+ });
499
+ describe('event-only methods', () => {
500
+ it.each([
501
+ 'connected',
502
+ 'onStatusChanged',
503
+ 'accountsChanged',
504
+ 'txChanged',
505
+ ])('%s throws Only for events', async (method) => {
506
+ const store = await createStore(logger, auth);
507
+ const controller = createController(store, notificationService, logger, auth);
508
+ await expect(controller[method]()).rejects.toThrow('Only for events.');
509
+ });
510
+ it('messageSignature throws Only for events', async () => {
511
+ const store = await createStore(logger, auth);
512
+ const controller = createController(store, notificationService, logger, auth);
513
+ expect(() => controller.messageSignature()).toThrow('Only for events.');
514
+ });
515
+ });
516
+ });
@@ -12,6 +12,7 @@ import { AccountsChanged } from './typings.js';
12
12
  import { GetPrimaryAccount } from './typings.js';
13
13
  import { ListAccounts } from './typings.js';
14
14
  import { TxChanged } from './typings.js';
15
+ import { MessageSignature } from './typings.js';
15
16
  export type Methods = {
16
17
  status: Status;
17
18
  connect: Connect;
@@ -27,6 +28,7 @@ export type Methods = {
27
28
  getPrimaryAccount: GetPrimaryAccount;
28
29
  listAccounts: ListAccounts;
29
30
  txChanged: TxChanged;
31
+ messageSignature: MessageSignature;
30
32
  };
31
33
  declare function buildController(methods: Methods): {
32
34
  status: Status;
@@ -43,6 +45,7 @@ declare function buildController(methods: Methods): {
43
45
  getPrimaryAccount: GetPrimaryAccount;
44
46
  listAccounts: ListAccounts;
45
47
  txChanged: TxChanged;
48
+ messageSignature: MessageSignature;
46
49
  };
47
50
  export default buildController;
48
51
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/dapp-api/rpc-gen/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAExC,MAAM,MAAM,OAAO,GAAG;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,EAAE,UAAU,CAAA;IACtB,WAAW,EAAE,WAAW,CAAA;IACxB,gBAAgB,EAAE,gBAAgB,CAAA;IAClC,cAAc,EAAE,cAAc,CAAA;IAC9B,WAAW,EAAE,WAAW,CAAA;IACxB,SAAS,EAAE,SAAS,CAAA;IACpB,SAAS,EAAE,SAAS,CAAA;IACpB,eAAe,EAAE,eAAe,CAAA;IAChC,eAAe,EAAE,eAAe,CAAA;IAChC,iBAAiB,EAAE,iBAAiB,CAAA;IACpC,YAAY,EAAE,YAAY,CAAA;IAC1B,SAAS,EAAE,SAAS,CAAA;CACvB,CAAA;AAED,iBAAS,eAAe,CAAC,OAAO,EAAE,OAAO;;;;;;;;;;;;;;;EAiBxC;AAED,eAAe,eAAe,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/dapp-api/rpc-gen/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAE/C,MAAM,MAAM,OAAO,GAAG;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,EAAE,UAAU,CAAA;IACtB,WAAW,EAAE,WAAW,CAAA;IACxB,gBAAgB,EAAE,gBAAgB,CAAA;IAClC,cAAc,EAAE,cAAc,CAAA;IAC9B,WAAW,EAAE,WAAW,CAAA;IACxB,SAAS,EAAE,SAAS,CAAA;IACpB,SAAS,EAAE,SAAS,CAAA;IACpB,eAAe,EAAE,eAAe,CAAA;IAChC,eAAe,EAAE,eAAe,CAAA;IAChC,iBAAiB,EAAE,iBAAiB,CAAA;IACpC,YAAY,EAAE,YAAY,CAAA;IAC1B,SAAS,EAAE,SAAS,CAAA;IACpB,gBAAgB,EAAE,gBAAgB,CAAA;CACrC,CAAA;AAED,iBAAS,eAAe,CAAC,OAAO,EAAE,OAAO;;;;;;;;;;;;;;;;EAkBxC;AAED,eAAe,eAAe,CAAA"}
@@ -16,6 +16,7 @@ function buildController(methods) {
16
16
  getPrimaryAccount: methods.getPrimaryAccount,
17
17
  listAccounts: methods.listAccounts,
18
18
  txChanged: methods.txChanged,
19
+ messageSignature: methods.messageSignature,
19
20
  };
20
21
  }
21
22
  export default buildController;