@canton-network/wallet-gateway-remote 0.20.0 → 0.22.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.
- package/README.md +1 -3
- package/dist/config/Config.d.ts +145 -0
- package/dist/config/Config.d.ts.map +1 -1
- package/dist/config/Config.js +18 -2
- package/dist/config/Config.test.js +3 -0
- package/dist/config/ConfigUtils.d.ts.map +1 -1
- package/dist/config/ConfigUtils.js +41 -2
- package/dist/dapp-api/controller.d.ts.map +1 -1
- package/dist/dapp-api/controller.js +7 -14
- package/dist/dapp-api/rpc-gen/typings.d.ts +1 -1
- package/dist/dapp-api/rpc-gen/typings.d.ts.map +1 -1
- package/dist/env.d.ts +19 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +16 -0
- package/dist/example-config.d.ts +3 -1
- package/dist/example-config.d.ts.map +1 -1
- package/dist/example-config.js +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +8 -18
- package/dist/ledger/party-allocation-service.d.ts +1 -2
- package/dist/ledger/party-allocation-service.d.ts.map +1 -1
- package/dist/ledger/party-allocation-service.js +5 -4
- package/dist/ledger/party-allocation-service.test.js +7 -4
- package/dist/ledger/transaction-service.d.ts +22 -0
- package/dist/ledger/transaction-service.d.ts.map +1 -0
- package/dist/ledger/transaction-service.js +296 -0
- package/dist/ledger/wallet-allocation/signing-providers/blockdaemon-wallet-allocator.d.ts +17 -0
- package/dist/ledger/wallet-allocation/signing-providers/blockdaemon-wallet-allocator.d.ts.map +1 -0
- package/dist/ledger/wallet-allocation/signing-providers/blockdaemon-wallet-allocator.js +146 -0
- package/dist/ledger/wallet-allocation/signing-providers/fireblocks-wallet-allocator.d.ts +17 -0
- package/dist/ledger/wallet-allocation/signing-providers/fireblocks-wallet-allocator.d.ts.map +1 -0
- package/dist/ledger/wallet-allocation/signing-providers/fireblocks-wallet-allocator.js +140 -0
- package/dist/ledger/wallet-allocation/signing-providers/kernel-wallet-allocator.d.ts +17 -0
- package/dist/ledger/wallet-allocation/signing-providers/kernel-wallet-allocator.d.ts.map +1 -0
- package/dist/ledger/wallet-allocation/signing-providers/kernel-wallet-allocator.js +79 -0
- package/dist/ledger/wallet-allocation/signing-providers/participant-wallet-allocator.d.ts +15 -0
- package/dist/ledger/wallet-allocation/signing-providers/participant-wallet-allocator.d.ts.map +1 -0
- package/dist/ledger/wallet-allocation/signing-providers/participant-wallet-allocator.js +37 -0
- package/dist/ledger/wallet-allocation/wallet-allocation-service.d.ts +20 -0
- package/dist/ledger/wallet-allocation/wallet-allocation-service.d.ts.map +1 -0
- package/dist/ledger/wallet-allocation/wallet-allocation-service.js +70 -0
- package/dist/ledger/wallet-allocation/wallet-allocation-service.test.d.ts +2 -0
- package/dist/ledger/wallet-allocation/wallet-allocation-service.test.d.ts.map +1 -0
- package/dist/ledger/wallet-allocation/wallet-allocation-service.test.js +301 -0
- package/dist/ledger/wallet-sync-service.d.ts +7 -12
- package/dist/ledger/wallet-sync-service.d.ts.map +1 -1
- package/dist/ledger/wallet-sync-service.js +128 -100
- package/dist/ledger/wallet-sync-service.test.js +194 -46
- package/dist/user-api/controller.d.ts +1 -0
- package/dist/user-api/controller.d.ts.map +1 -1
- package/dist/user-api/controller.js +77 -406
- package/dist/user-api/rpc-gen/index.d.ts +3 -0
- package/dist/user-api/rpc-gen/index.d.ts.map +1 -1
- package/dist/user-api/rpc-gen/index.js +1 -0
- package/dist/user-api/rpc-gen/typings.d.ts +61 -40
- package/dist/user-api/rpc-gen/typings.d.ts.map +1 -1
- package/dist/web/frontend/404/index.html +2 -2
- package/dist/web/frontend/approve/index.html +4 -3
- package/dist/web/frontend/assets/404-DzH9sSlT.js +8 -0
- package/dist/web/frontend/assets/approve-Duv1K5LE.js +17 -0
- package/dist/web/frontend/assets/callback-D_VLeaX-.js +1 -0
- package/dist/web/frontend/assets/index-Bj5VTWmh.js +1686 -0
- package/dist/web/frontend/assets/login-B-jF6DLr.js +7 -0
- package/dist/web/frontend/assets/{settings-Br8FgNOa.js → settings-DZpeOwSh.js} +2 -2
- package/dist/web/frontend/assets/{state-1o1CuDWy.js → state-PjJJ3Anb.js} +1 -1
- package/dist/web/frontend/assets/transactions-DGdh8VAO.js +28 -0
- package/dist/web/frontend/assets/utils-D5kQDwtZ.js +1 -0
- package/dist/web/frontend/assets/wallets-CjjRt-cQ.js +62 -0
- package/dist/web/frontend/callback/index.html +2 -2
- package/dist/web/frontend/index.html +1 -1
- package/dist/web/frontend/login/index.html +3 -3
- package/dist/web/frontend/settings/index.html +3 -3
- package/dist/web/frontend/transactions/index.html +4 -3
- package/dist/web/frontend/wallets/index.html +4 -3
- package/package.json +20 -19
- package/dist/web/frontend/assets/404-CKmKuu6H.js +0 -8
- package/dist/web/frontend/assets/approve-MzXNo4XO.js +0 -17
- package/dist/web/frontend/assets/callback-CcXLRr7Q.js +0 -1
- package/dist/web/frontend/assets/index-BnEARqU4.js +0 -1679
- package/dist/web/frontend/assets/login-Btlo0Zla.js +0 -7
- package/dist/web/frontend/assets/transactions-Cnu9eUFe.js +0 -28
- package/dist/web/frontend/assets/wallets-Dj5yeBJd.js +0 -63
|
@@ -2,14 +2,11 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import { defaultRetryableOptions, } from '@canton-network/core-ledger-client';
|
|
4
4
|
import { SigningProvider, } from '@canton-network/core-signing-lib';
|
|
5
|
-
|
|
6
|
-
NO_SIGNING_PROVIDER_MATCHED: 'no signing provider matched',
|
|
7
|
-
};
|
|
5
|
+
import { WALLET_DISABLED_REASON } from '@canton-network/core-types';
|
|
8
6
|
export class WalletSyncService {
|
|
9
|
-
constructor(store, ledgerClient,
|
|
7
|
+
constructor(store, ledgerClient, authContext, logger, signingDrivers = {}, partyAllocator) {
|
|
10
8
|
this.store = store;
|
|
11
9
|
this.ledgerClient = ledgerClient;
|
|
12
|
-
this.adminLedgerClient = adminLedgerClient;
|
|
13
10
|
this.authContext = authContext;
|
|
14
11
|
this.logger = logger;
|
|
15
12
|
this.signingDrivers = signingDrivers;
|
|
@@ -28,7 +25,7 @@ export class WalletSyncService {
|
|
|
28
25
|
// (participant parties have namespace === participantId's namespace)
|
|
29
26
|
let participantNamespace;
|
|
30
27
|
try {
|
|
31
|
-
const { participantId } = await this.
|
|
28
|
+
const { participantId } = await this.ledgerClient.getWithRetry('/v2/parties/participant-id', defaultRetryableOptions);
|
|
32
29
|
// Extract the namespace part from participantId
|
|
33
30
|
// Format is hint::namespace
|
|
34
31
|
const [, extractedNamespace] = participantId.split('::');
|
|
@@ -62,7 +59,7 @@ export class WalletSyncService {
|
|
|
62
59
|
const result = await controller.getKeys();
|
|
63
60
|
// In case of error getKeys resolve Promise but with error object
|
|
64
61
|
if ('error' in result) {
|
|
65
|
-
this.logger.
|
|
62
|
+
this.logger.warn({
|
|
66
63
|
providerId,
|
|
67
64
|
error: result.error,
|
|
68
65
|
error_description: result.error_description,
|
|
@@ -77,7 +74,7 @@ export class WalletSyncService {
|
|
|
77
74
|
continue;
|
|
78
75
|
const keyNamespace = this.partyAllocator.createFingerprintFromKey(normalizedKey);
|
|
79
76
|
if (keyNamespace === namespace) {
|
|
80
|
-
this.logger.
|
|
77
|
+
this.logger.info({
|
|
81
78
|
namespace,
|
|
82
79
|
providerId,
|
|
83
80
|
keyId: key.id,
|
|
@@ -93,7 +90,7 @@ export class WalletSyncService {
|
|
|
93
90
|
}
|
|
94
91
|
}
|
|
95
92
|
catch (err) {
|
|
96
|
-
this.logger.
|
|
93
|
+
this.logger.error({ err, providerId }, 'Error getting keys from signing provider');
|
|
97
94
|
// Continue to next signing provider
|
|
98
95
|
}
|
|
99
96
|
}
|
|
@@ -113,50 +110,47 @@ export class WalletSyncService {
|
|
|
113
110
|
};
|
|
114
111
|
}
|
|
115
112
|
}
|
|
116
|
-
async
|
|
113
|
+
async getPartiesWithRights() {
|
|
117
114
|
const rights = await this.ledgerClient.getWithRetry('/v2/users/{user-id}/rights', defaultRetryableOptions, {
|
|
118
115
|
path: {
|
|
119
116
|
'user-id': this.authContext.userId,
|
|
120
117
|
},
|
|
121
118
|
});
|
|
122
|
-
const
|
|
119
|
+
const parties = new Set();
|
|
123
120
|
rights.rights?.forEach((right) => {
|
|
124
121
|
let party;
|
|
125
|
-
let rightType;
|
|
126
122
|
if ('CanActAs' in right.kind) {
|
|
127
123
|
party = right.kind.CanActAs.value.party;
|
|
128
|
-
rightType = 'CanActAs';
|
|
129
124
|
}
|
|
130
125
|
else if ('CanExecuteAs' in right.kind) {
|
|
131
126
|
party = right.kind.CanExecuteAs.value.party;
|
|
132
|
-
rightType = 'CanExecuteAs';
|
|
133
127
|
}
|
|
134
128
|
else if ('CanReadAs' in right.kind) {
|
|
135
129
|
party = right.kind.CanReadAs.value.party;
|
|
136
|
-
rightType = 'CanReadAs';
|
|
137
130
|
}
|
|
138
|
-
if (party !== undefined
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
partiesWithRights.set(party, rightType);
|
|
131
|
+
if (party !== undefined) {
|
|
132
|
+
parties.add(party);
|
|
133
|
+
}
|
|
142
134
|
});
|
|
143
|
-
return
|
|
135
|
+
return Array.from(parties);
|
|
144
136
|
}
|
|
145
137
|
async isWalletSyncNeeded() {
|
|
146
138
|
try {
|
|
147
139
|
const network = await this.store.getCurrentNetwork();
|
|
148
140
|
const existingWallets = await this.store.getWallets();
|
|
149
|
-
const
|
|
150
|
-
if (hasDisabledWallets) {
|
|
151
|
-
return true;
|
|
152
|
-
}
|
|
153
|
-
const partiesWithRights = await this.getPartiesRightsMap();
|
|
141
|
+
const partiesWithRights = await this.getPartiesWithRights();
|
|
154
142
|
// Treat disabled wallets as if they don't exist, so they can be re-synced
|
|
155
143
|
const enabledWallets = existingWallets.filter((w) => !w.disabled);
|
|
156
144
|
// Track by (partyId, networkId) combination to handle multi-hosted parties
|
|
157
145
|
const existingPartyNetworkPairs = new Set(enabledWallets.map((w) => `${w.partyId}:${w.networkId}`));
|
|
158
146
|
// Check if there are parties on ledger that aren't in store for this network
|
|
159
|
-
|
|
147
|
+
const hasNewPartiesOnLedger = partiesWithRights.some((party) => !existingPartyNetworkPairs.has(`${party}:${network.id}`));
|
|
148
|
+
if (hasNewPartiesOnLedger)
|
|
149
|
+
return true;
|
|
150
|
+
// Check if there are allocated wallets in store whose party is not on ledger
|
|
151
|
+
const hasWalletsWithoutParty = enabledWallets.some((wallet) => wallet.status === 'allocated' &&
|
|
152
|
+
!partiesWithRights.includes(wallet.partyId));
|
|
153
|
+
return hasWalletsWithoutParty;
|
|
160
154
|
}
|
|
161
155
|
catch (err) {
|
|
162
156
|
this.logger.error({ err }, 'Error checking if sync is needed');
|
|
@@ -164,94 +158,128 @@ export class WalletSyncService {
|
|
|
164
158
|
throw err;
|
|
165
159
|
}
|
|
166
160
|
}
|
|
161
|
+
// Participant wallets: disable when party not on ledger (participant node reset, namespace changed).
|
|
162
|
+
// Other wallets: mark as initialized so user can re-allocate (e.g. after external signing).
|
|
163
|
+
async handleWalletsWithoutParty(enabledWallets, partiesWithRights) {
|
|
164
|
+
const walletsWithoutParty = enabledWallets.filter((wallet) => !partiesWithRights.includes(wallet.partyId));
|
|
165
|
+
const markedForAllocateWallets = [];
|
|
166
|
+
const disabledExistingWallets = [];
|
|
167
|
+
for (const wallet of walletsWithoutParty) {
|
|
168
|
+
if (wallet.status !== 'allocated')
|
|
169
|
+
continue;
|
|
170
|
+
try {
|
|
171
|
+
if (wallet.signingProviderId === SigningProvider.PARTICIPANT) {
|
|
172
|
+
this.logger.warn({
|
|
173
|
+
partyId: wallet.partyId,
|
|
174
|
+
signingProviderId: wallet.signingProviderId,
|
|
175
|
+
}, 'Participant wallet party not on ledger, disabling (participant namespace changed)');
|
|
176
|
+
await this.store.updateWallet({
|
|
177
|
+
partyId: wallet.partyId,
|
|
178
|
+
networkId: wallet.networkId,
|
|
179
|
+
disabled: true,
|
|
180
|
+
reason: WALLET_DISABLED_REASON.PARTICIPANT_NAMESPACE_CHANGED,
|
|
181
|
+
...(wallet.primary && { primary: false }),
|
|
182
|
+
});
|
|
183
|
+
disabledExistingWallets.push(wallet);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
this.logger.info({
|
|
187
|
+
partyId: wallet.partyId,
|
|
188
|
+
signingProviderId: wallet.signingProviderId,
|
|
189
|
+
}, 'Party not found on participant, marking wallet as initialized');
|
|
190
|
+
await this.store.updateWallet({
|
|
191
|
+
partyId: wallet.partyId,
|
|
192
|
+
networkId: wallet.networkId,
|
|
193
|
+
status: 'initialized',
|
|
194
|
+
...(wallet.primary && { primary: false }),
|
|
195
|
+
});
|
|
196
|
+
markedForAllocateWallets.push(wallet);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
this.logger.warn({ err, partyId: wallet.partyId }, 'Failed to update wallet');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
markedForAllocateWallets,
|
|
205
|
+
walletsWithoutParty,
|
|
206
|
+
disabledExistingWallets,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
// Creates wallets for parties user has rights to
|
|
210
|
+
async handlePartiesWithoutWallet(newParties, networkId) {
|
|
211
|
+
return await Promise.all(newParties.map(async (partyId) => {
|
|
212
|
+
const [hint, namespace] = partyId.split('::');
|
|
213
|
+
const resolvedSigningProvider = await this.resolveSigningProvider(namespace);
|
|
214
|
+
const isMatched = resolvedSigningProvider.matched;
|
|
215
|
+
const walletPublicKey = resolvedSigningProvider.signingProviderId ===
|
|
216
|
+
SigningProvider.PARTICIPANT
|
|
217
|
+
? namespace
|
|
218
|
+
: 'publicKey' in resolvedSigningProvider
|
|
219
|
+
? resolvedSigningProvider.publicKey
|
|
220
|
+
: namespace;
|
|
221
|
+
const wallet = {
|
|
222
|
+
primary: false,
|
|
223
|
+
status: 'allocated',
|
|
224
|
+
partyId,
|
|
225
|
+
hint,
|
|
226
|
+
publicKey: walletPublicKey,
|
|
227
|
+
namespace,
|
|
228
|
+
networkId,
|
|
229
|
+
signingProviderId: resolvedSigningProvider.signingProviderId,
|
|
230
|
+
disabled: !isMatched,
|
|
231
|
+
...(!isMatched && {
|
|
232
|
+
reason: WALLET_DISABLED_REASON.NO_SIGNING_PROVIDER_MATCHED,
|
|
233
|
+
}),
|
|
234
|
+
};
|
|
235
|
+
this.logger.info({ ...wallet }, 'Wallet sync result');
|
|
236
|
+
await this.store.addWallet(wallet);
|
|
237
|
+
return wallet;
|
|
238
|
+
}));
|
|
239
|
+
}
|
|
167
240
|
async syncWallets() {
|
|
168
241
|
this.logger.info('Starting wallet sync...');
|
|
169
242
|
try {
|
|
170
243
|
const network = await this.store.getCurrentNetwork();
|
|
171
244
|
this.logger.info(network, 'Current network');
|
|
172
|
-
const partiesWithRights = await this.
|
|
173
|
-
// Add new Wallets given the found parties
|
|
174
|
-
// Only check wallets in the current network
|
|
245
|
+
const partiesWithRights = await this.getPartiesWithRights();
|
|
175
246
|
const existingWallets = await this.store.getWallets();
|
|
176
247
|
this.logger.info(existingWallets, 'Existing wallets');
|
|
177
|
-
//
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
`${w.partyId}:${w.networkId}`,
|
|
182
|
-
w.signingProviderId,
|
|
183
|
-
]));
|
|
184
|
-
// Track disabled wallets by (partyId, networkId) combination
|
|
185
|
-
const disabledPartyNetworkPairs = new Set(existingWallets
|
|
186
|
-
.filter((w) => w.disabled)
|
|
187
|
-
.map((w) => `${w.partyId}:${w.networkId}`));
|
|
188
|
-
// Resolve signing providers for all new parties
|
|
189
|
-
// Check if (partyId, networkId) combination already exists
|
|
190
|
-
const newParties = Array.from(partiesWithRights.keys()).filter((party) => !existingPartyNetworkToSigningProvider.has(`${party}:${network.id}`)
|
|
248
|
+
// Skips wallets for which we didn't allocate a party
|
|
249
|
+
const existingAllocatedWallets = existingWallets.filter((w) => w.status === 'allocated');
|
|
250
|
+
const existingPartiesOnNetwork = new Set(existingAllocatedWallets.map((w) => `${w.partyId}:${w.networkId}`));
|
|
251
|
+
const newParties = partiesWithRights.filter((party) => !existingPartiesOnNetwork.has(`${party}:${network.id}`)
|
|
191
252
|
// todo: filter on idp id
|
|
192
253
|
);
|
|
193
|
-
|
|
194
|
-
const newParticipantWallets = await Promise.all(newParties.map(async (party) => {
|
|
195
|
-
const [hint, namespace] = party.split('::');
|
|
196
|
-
const resolvedSigningProvider = await this.resolveSigningProvider(namespace);
|
|
197
|
-
// resolvedSigningProvider is never null (participant is default)
|
|
198
|
-
const isMatched = resolvedSigningProvider.matched;
|
|
199
|
-
// Namespace is saved as public key in case of participant
|
|
200
|
-
const walletPublicKey = resolvedSigningProvider.signingProviderId ===
|
|
201
|
-
SigningProvider.PARTICIPANT
|
|
202
|
-
? namespace
|
|
203
|
-
: 'publicKey' in resolvedSigningProvider
|
|
204
|
-
? resolvedSigningProvider.publicKey
|
|
205
|
-
: namespace;
|
|
206
|
-
const wallet = {
|
|
207
|
-
primary: false,
|
|
208
|
-
status: 'allocated',
|
|
209
|
-
partyId: party,
|
|
210
|
-
hint: hint,
|
|
211
|
-
publicKey: walletPublicKey,
|
|
212
|
-
namespace: namespace,
|
|
213
|
-
networkId: network.id,
|
|
214
|
-
signingProviderId: resolvedSigningProvider.signingProviderId,
|
|
215
|
-
disabled: !isMatched,
|
|
216
|
-
...(!isMatched && {
|
|
217
|
-
reason: WALLET_DISABLED_REASON.NO_SIGNING_PROVIDER_MATCHED,
|
|
218
|
-
}),
|
|
219
|
-
};
|
|
220
|
-
this.logger.info({
|
|
221
|
-
...wallet,
|
|
222
|
-
}, 'Wallet sync result');
|
|
223
|
-
return wallet;
|
|
224
|
-
}));
|
|
225
|
-
// Remove disabled wallets that are being re-synced before adding them back
|
|
226
|
-
// Filter by (partyId, networkId) combination
|
|
227
|
-
await Promise.all(newParticipantWallets
|
|
228
|
-
.filter((wallet) => disabledPartyNetworkPairs.has(`${wallet.partyId}:${wallet.networkId}`))
|
|
229
|
-
.map((wallet) => {
|
|
230
|
-
this.logger.info({
|
|
231
|
-
partyId: wallet.partyId,
|
|
232
|
-
networkId: wallet.networkId,
|
|
233
|
-
}, 'Removing disabled wallet for re-sync');
|
|
234
|
-
return this.store.removeWallet(wallet.partyId);
|
|
235
|
-
}));
|
|
236
|
-
await Promise.all(newParticipantWallets.map((wallet) => this.store.addWallet(wallet)));
|
|
237
|
-
this.logger.info({ newParticipantWallets }, 'Created new wallets');
|
|
254
|
+
const { markedForAllocateWallets, walletsWithoutParty, disabledExistingWallets, } = await this.handleWalletsWithoutParty(existingAllocatedWallets, partiesWithRights);
|
|
238
255
|
this.logger.info({
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
// Set primary wallet if none exists in current network
|
|
256
|
+
newParties,
|
|
257
|
+
markedForAllocate: markedForAllocateWallets.map((w) => w.partyId),
|
|
258
|
+
}, 'Wallets without parties');
|
|
259
|
+
const newParticipantWallets = await this.handlePartiesWithoutWallet(newParties, network.id);
|
|
260
|
+
// Set primary wallet if none exists, or if primary is on an initialized wallet
|
|
245
261
|
const networkWallets = await this.store.getWallets();
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
262
|
+
const primaryWallet = networkWallets.find((w) => w.primary);
|
|
263
|
+
const allocatedWallets = networkWallets.filter((w) => w.status === 'allocated' && !w.disabled);
|
|
264
|
+
const needsPrimaryReset = primaryWallet?.status === 'initialized' ||
|
|
265
|
+
(!primaryWallet && allocatedWallets.length > 0);
|
|
266
|
+
if (needsPrimaryReset && allocatedWallets.length > 0) {
|
|
267
|
+
this.store.setPrimaryWallet(allocatedWallets[0].partyId);
|
|
268
|
+
this.logger.info(`Set ${allocatedWallets[0].partyId} as primary wallet in network ${network.id}`);
|
|
250
269
|
}
|
|
251
|
-
|
|
270
|
+
const disabled = [
|
|
271
|
+
...newParticipantWallets.filter((wallet) => wallet.disabled),
|
|
272
|
+
...disabledExistingWallets,
|
|
273
|
+
];
|
|
274
|
+
this.logger.info({
|
|
275
|
+
added: newParticipantWallets,
|
|
276
|
+
updated: walletsWithoutParty,
|
|
277
|
+
disabled: disabled,
|
|
278
|
+
}, 'Wallet sync completed.');
|
|
252
279
|
return {
|
|
253
280
|
added: newParticipantWallets,
|
|
254
|
-
|
|
281
|
+
updated: walletsWithoutParty,
|
|
282
|
+
disabled: disabled,
|
|
255
283
|
};
|
|
256
284
|
}
|
|
257
285
|
catch (err) {
|
|
@@ -25,6 +25,13 @@ class TestableWalletSyncService extends WalletSyncService {
|
|
|
25
25
|
return super.resolveSigningProvider(namespace);
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
+
const testATP = (user, token) => ({
|
|
29
|
+
getAccessToken: async () => token,
|
|
30
|
+
getAuthContext: async () => ({
|
|
31
|
+
userId: user,
|
|
32
|
+
accessToken: token,
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
28
35
|
describe('WalletSyncService - resolveSigningProvider', () => {
|
|
29
36
|
const authContext = {
|
|
30
37
|
userId: 'test-user-id',
|
|
@@ -57,10 +64,7 @@ describe('WalletSyncService - resolveSigningProvider', () => {
|
|
|
57
64
|
// Create real PartyAllocationService
|
|
58
65
|
partyAllocator = new PartyAllocationService({
|
|
59
66
|
synchronizerId: 'test-sync-id',
|
|
60
|
-
accessTokenProvider:
|
|
61
|
-
getUserAccessToken: async () => 'user.jwt',
|
|
62
|
-
getAdminAccessToken: async () => 'admin.jwt',
|
|
63
|
-
},
|
|
67
|
+
accessTokenProvider: testATP('admin', 'admin.jwt'),
|
|
64
68
|
httpLedgerUrl: 'http://test',
|
|
65
69
|
logger: mockLogger,
|
|
66
70
|
});
|
|
@@ -69,13 +73,10 @@ describe('WalletSyncService - resolveSigningProvider', () => {
|
|
|
69
73
|
ledgerClient = new ledgerModule.LedgerClient({
|
|
70
74
|
baseUrl: new URL('http://test'),
|
|
71
75
|
logger: mockLogger,
|
|
72
|
-
accessTokenProvider:
|
|
73
|
-
getUserAccessToken: async () => 'token',
|
|
74
|
-
getAdminAccessToken: async () => 'token',
|
|
75
|
-
},
|
|
76
|
+
accessTokenProvider: testATP('token', 'token'),
|
|
76
77
|
});
|
|
77
78
|
// Create service with real drivers
|
|
78
|
-
service = new TestableWalletSyncService(store, ledgerClient,
|
|
79
|
+
service = new TestableWalletSyncService(store, ledgerClient, authContext, mockLogger, {
|
|
79
80
|
[SigningProvider.WALLET_KERNEL]: internalDriver,
|
|
80
81
|
[SigningProvider.PARTICIPANT]: new ParticipantSigningDriver(),
|
|
81
82
|
}, partyAllocator);
|
|
@@ -142,7 +143,7 @@ describe('WalletSyncService - resolveSigningProvider', () => {
|
|
|
142
143
|
partyMode: 'EXTERNAL',
|
|
143
144
|
signingProvider: SigningProvider.FIREBLOCKS,
|
|
144
145
|
};
|
|
145
|
-
const serviceWithFireblocks = new TestableWalletSyncService(store, ledgerClient,
|
|
146
|
+
const serviceWithFireblocks = new TestableWalletSyncService(store, ledgerClient, authContext, mockLogger, {
|
|
146
147
|
[SigningProvider.FIREBLOCKS]: mockFireblocksDriver,
|
|
147
148
|
}, partyAllocator);
|
|
148
149
|
mockLedgerGet.mockResolvedValueOnce({
|
|
@@ -177,8 +178,8 @@ describe('WalletSyncService - multi-network features', () => {
|
|
|
177
178
|
let mockLogger;
|
|
178
179
|
let store;
|
|
179
180
|
let mockLedgerClient;
|
|
180
|
-
let mockAdminLedgerClient;
|
|
181
181
|
let partyAllocator;
|
|
182
|
+
let service;
|
|
182
183
|
const createNetwork = (id) => ({
|
|
183
184
|
id,
|
|
184
185
|
name: `Network ${id}`,
|
|
@@ -193,10 +194,10 @@ describe('WalletSyncService - multi-network features', () => {
|
|
|
193
194
|
audience: 'aud',
|
|
194
195
|
},
|
|
195
196
|
});
|
|
196
|
-
const createWallet = (partyId, networkId, disabled = false) => ({
|
|
197
|
+
const createWallet = (partyId, networkId, disabled = false, status = 'allocated') => ({
|
|
197
198
|
primary: false,
|
|
198
199
|
partyId,
|
|
199
|
-
status
|
|
200
|
+
status,
|
|
200
201
|
hint: partyId.split('::')[0],
|
|
201
202
|
signingProviderId: 'internal',
|
|
202
203
|
publicKey: 'publicKey',
|
|
@@ -237,10 +238,7 @@ describe('WalletSyncService - multi-network features', () => {
|
|
|
237
238
|
}
|
|
238
239
|
partyAllocator = new PartyAllocationService({
|
|
239
240
|
synchronizerId: 'test-sync-id',
|
|
240
|
-
accessTokenProvider:
|
|
241
|
-
getUserAccessToken: async () => 'user.jwt',
|
|
242
|
-
getAdminAccessToken: async () => 'admin.jwt',
|
|
243
|
-
},
|
|
241
|
+
accessTokenProvider: testATP('admin', 'admin.jwt'),
|
|
244
242
|
httpLedgerUrl: 'http://test',
|
|
245
243
|
logger: mockLogger,
|
|
246
244
|
});
|
|
@@ -248,20 +246,9 @@ describe('WalletSyncService - multi-network features', () => {
|
|
|
248
246
|
mockLedgerClient = new ledgerModule.LedgerClient({
|
|
249
247
|
baseUrl: new URL('http://test'),
|
|
250
248
|
logger: mockLogger,
|
|
251
|
-
accessTokenProvider:
|
|
252
|
-
getUserAccessToken: async () => 'token',
|
|
253
|
-
getAdminAccessToken: async () => 'token',
|
|
254
|
-
},
|
|
255
|
-
});
|
|
256
|
-
mockAdminLedgerClient = new ledgerModule.LedgerClient({
|
|
257
|
-
baseUrl: new URL('http://test'),
|
|
258
|
-
logger: mockLogger,
|
|
259
|
-
isAdmin: true,
|
|
260
|
-
accessTokenProvider: {
|
|
261
|
-
getUserAccessToken: async () => 'token',
|
|
262
|
-
getAdminAccessToken: async () => 'token',
|
|
263
|
-
},
|
|
249
|
+
accessTokenProvider: testATP('token', 'token'),
|
|
264
250
|
});
|
|
251
|
+
service = new WalletSyncService(store, mockLedgerClient, authContext, mockLogger, {}, partyAllocator);
|
|
265
252
|
});
|
|
266
253
|
afterEach(() => {
|
|
267
254
|
jest.restoreAllMocks();
|
|
@@ -286,7 +273,6 @@ describe('WalletSyncService - multi-network features', () => {
|
|
|
286
273
|
},
|
|
287
274
|
],
|
|
288
275
|
});
|
|
289
|
-
const service = new WalletSyncService(store, mockLedgerClient, mockAdminLedgerClient, authContext, mockLogger, {}, partyAllocator);
|
|
290
276
|
const syncNeeded = await service.isWalletSyncNeeded();
|
|
291
277
|
// Should return false because party1 already exists in network1
|
|
292
278
|
expect(syncNeeded).toBe(false);
|
|
@@ -308,7 +294,6 @@ describe('WalletSyncService - multi-network features', () => {
|
|
|
308
294
|
},
|
|
309
295
|
],
|
|
310
296
|
});
|
|
311
|
-
const service = new WalletSyncService(store, mockLedgerClient, mockAdminLedgerClient, authContext, mockLogger, {}, partyAllocator);
|
|
312
297
|
const syncNeeded = await service.isWalletSyncNeeded();
|
|
313
298
|
// Should return true because party1 exists on ledger but not in store for network1
|
|
314
299
|
expect(syncNeeded).toBe(true);
|
|
@@ -345,7 +330,6 @@ describe('WalletSyncService - multi-network features', () => {
|
|
|
345
330
|
.mockResolvedValueOnce({
|
|
346
331
|
participantId: 'participant1::namespace',
|
|
347
332
|
});
|
|
348
|
-
const service = new WalletSyncService(store, mockLedgerClient, mockAdminLedgerClient, authContext, mockLogger, {}, partyAllocator);
|
|
349
333
|
await service.syncWallets();
|
|
350
334
|
// Should only add wallet for party3 (party1 already exists)
|
|
351
335
|
const wallets = await store.getAllWallets({ networkIds: ['network1'] });
|
|
@@ -374,7 +358,6 @@ describe('WalletSyncService - multi-network features', () => {
|
|
|
374
358
|
.mockResolvedValueOnce({
|
|
375
359
|
participantId: 'participant1::namespace',
|
|
376
360
|
});
|
|
377
|
-
const service = new WalletSyncService(store, mockLedgerClient, mockAdminLedgerClient, authContext, mockLogger, {}, partyAllocator);
|
|
378
361
|
await service.syncWallets();
|
|
379
362
|
// Should add party1 for network1
|
|
380
363
|
const wallets = await store.getAllWallets({ networkIds: ['network1'] });
|
|
@@ -386,7 +369,6 @@ describe('WalletSyncService - multi-network features', () => {
|
|
|
386
369
|
await store.addNetwork(network1);
|
|
387
370
|
await store.addNetwork(network2);
|
|
388
371
|
await store.addWallet(createWallet('party1::namespace', 'network1'));
|
|
389
|
-
const service = new WalletSyncService(store, mockLedgerClient, mockAdminLedgerClient, authContext, mockLogger, {}, partyAllocator);
|
|
390
372
|
// Mock ledger client to return rights for party1 (multi-hosted party) for network1 check
|
|
391
373
|
mockLedgerGet.mockResolvedValueOnce({
|
|
392
374
|
rights: [
|
|
@@ -432,7 +414,6 @@ describe('WalletSyncService - multi-network features', () => {
|
|
|
432
414
|
// Add wallet to network1 (simulating it was synced there previously)
|
|
433
415
|
await setSession('network1');
|
|
434
416
|
await store.addWallet(createWallet('party1::namespace', 'network1'));
|
|
435
|
-
const service = new WalletSyncService(store, mockLedgerClient, mockAdminLedgerClient, authContext, mockLogger, {}, partyAllocator);
|
|
436
417
|
// Sync on network1 (party already exists, should not add)
|
|
437
418
|
await setSession('network1');
|
|
438
419
|
// Only need one mock since resolveSigningProvider won't be called if party already exists
|
|
@@ -492,15 +473,182 @@ describe('WalletSyncService - multi-network features', () => {
|
|
|
492
473
|
expect(party1Wallet?.networkId).toBe('network2');
|
|
493
474
|
expect(party1Wallet?.disabled).toBe(false);
|
|
494
475
|
});
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
476
|
+
describe('Wallet sync - handling wallet not having a party', () => {
|
|
477
|
+
// When party is not on ledger (e.g. after participant reset), sync marks wallet
|
|
478
|
+
// status as 'initialized' so the user can manually re-allocate each one with "Allocate" button
|
|
479
|
+
it('isWalletSyncNeeded should return true when allocated wallet exists but has no party', async () => {
|
|
480
|
+
const network1 = createNetwork('network1');
|
|
481
|
+
await store.addNetwork(network1);
|
|
482
|
+
await setSession('network1');
|
|
483
|
+
await store.addWallet(createWallet('party1::namespace', 'network1', undefined, 'allocated'));
|
|
484
|
+
await store.addWallet(createWallet('party2::namespace', 'network1', undefined, 'allocated'));
|
|
485
|
+
mockLedgerGet.mockResolvedValueOnce({
|
|
486
|
+
rights: [
|
|
487
|
+
{
|
|
488
|
+
kind: {
|
|
489
|
+
CanActAs: {
|
|
490
|
+
value: {
|
|
491
|
+
party: 'party2::namespace',
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
],
|
|
497
|
+
});
|
|
498
|
+
const syncNeeded = await service.isWalletSyncNeeded();
|
|
499
|
+
expect(syncNeeded).toBe(true);
|
|
500
|
+
});
|
|
501
|
+
it('isWalletSyncNeeded should return false when initialized wallet exists and has no party', async () => {
|
|
502
|
+
const network1 = createNetwork('network1');
|
|
503
|
+
await store.addNetwork(network1);
|
|
504
|
+
await setSession('network1');
|
|
505
|
+
await store.addWallet(createWallet('party1::namespace', 'network1', undefined, 'initialized'));
|
|
506
|
+
await store.addWallet(createWallet('party2::namespace', 'network1', undefined, 'allocated'));
|
|
507
|
+
mockLedgerGet.mockResolvedValueOnce({
|
|
508
|
+
rights: [
|
|
509
|
+
{
|
|
510
|
+
kind: {
|
|
511
|
+
CanActAs: {
|
|
512
|
+
value: {
|
|
513
|
+
party: 'party2::namespace',
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
],
|
|
519
|
+
});
|
|
520
|
+
const syncNeeded = await service.isWalletSyncNeeded();
|
|
521
|
+
expect(syncNeeded).toBe(false);
|
|
522
|
+
});
|
|
523
|
+
it('syncWallets marks allocated wallet as initialized when party not on ledger', async () => {
|
|
524
|
+
const network1 = createNetwork('network1');
|
|
525
|
+
await store.addNetwork(network1);
|
|
526
|
+
await setSession('network1');
|
|
527
|
+
await store.addWallet(createWallet('party1::namespace', 'network1'));
|
|
528
|
+
mockLedgerGet
|
|
529
|
+
.mockResolvedValueOnce({
|
|
530
|
+
rights: [
|
|
531
|
+
{
|
|
532
|
+
kind: {
|
|
533
|
+
CanActAs: {
|
|
534
|
+
value: { party: 'party2::namespace' },
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
],
|
|
539
|
+
})
|
|
540
|
+
.mockResolvedValueOnce({
|
|
541
|
+
participantId: 'participant1::namespace',
|
|
542
|
+
});
|
|
543
|
+
const updateWalletSpy = jest.spyOn(store, 'updateWallet');
|
|
544
|
+
await service.syncWallets();
|
|
545
|
+
expect(updateWalletSpy).toHaveBeenCalledWith(expect.objectContaining({
|
|
546
|
+
partyId: 'party1::namespace',
|
|
547
|
+
networkId: 'network1',
|
|
548
|
+
status: 'initialized',
|
|
549
|
+
}));
|
|
550
|
+
const wallets = await store.getWallets();
|
|
551
|
+
const party1Wallet = wallets.find((w) => w.partyId === 'party1::namespace');
|
|
552
|
+
expect(party1Wallet?.status).toBe('initialized');
|
|
553
|
+
});
|
|
554
|
+
it('syncWallets skips wallet when status is already initialized', async () => {
|
|
555
|
+
const network1 = createNetwork('network1');
|
|
556
|
+
await store.addNetwork(network1);
|
|
557
|
+
await setSession('network1');
|
|
558
|
+
const initializedWallet = createWallet('party1::namespace', 'network1');
|
|
559
|
+
initializedWallet.status = 'initialized';
|
|
560
|
+
await store.addWallet(initializedWallet);
|
|
561
|
+
mockLedgerGet
|
|
562
|
+
.mockResolvedValueOnce({
|
|
563
|
+
rights: [
|
|
564
|
+
{
|
|
565
|
+
kind: {
|
|
566
|
+
CanActAs: {
|
|
567
|
+
value: { party: 'party2::namespace' },
|
|
568
|
+
},
|
|
569
|
+
},
|
|
570
|
+
},
|
|
571
|
+
],
|
|
572
|
+
})
|
|
573
|
+
.mockResolvedValueOnce({
|
|
574
|
+
participantId: 'participant1::namespace',
|
|
575
|
+
});
|
|
576
|
+
const updateWalletSpy = jest.spyOn(store, 'updateWallet');
|
|
577
|
+
await service.syncWallets();
|
|
578
|
+
expect(updateWalletSpy).not.toHaveBeenCalled();
|
|
579
|
+
const wallets = await store.getWallets();
|
|
580
|
+
const party1Wallet = wallets.find((w) => w.partyId === 'party1::namespace');
|
|
581
|
+
expect(party1Wallet?.status).toBe('initialized');
|
|
582
|
+
});
|
|
583
|
+
it('syncWallets marks multiple wallets as initialized when parties not on ledger', async () => {
|
|
584
|
+
const network1 = createNetwork('network1');
|
|
585
|
+
await store.addNetwork(network1);
|
|
586
|
+
await setSession('network1');
|
|
587
|
+
await store.addWallet(createWallet('party1::namespace', 'network1'));
|
|
588
|
+
await store.addWallet(createWallet('party2::namespace', 'network1'));
|
|
589
|
+
mockLedgerGet
|
|
590
|
+
.mockResolvedValueOnce({
|
|
591
|
+
rights: [
|
|
592
|
+
{
|
|
593
|
+
kind: {
|
|
594
|
+
CanActAs: {
|
|
595
|
+
value: { party: 'party3::namespace' },
|
|
596
|
+
},
|
|
597
|
+
},
|
|
598
|
+
},
|
|
599
|
+
],
|
|
600
|
+
})
|
|
601
|
+
.mockResolvedValueOnce({
|
|
602
|
+
participantId: 'participant1::namespace',
|
|
603
|
+
});
|
|
604
|
+
const updateWalletSpy = jest.spyOn(store, 'updateWallet');
|
|
605
|
+
await service.syncWallets();
|
|
606
|
+
expect(updateWalletSpy).toHaveBeenCalledTimes(2);
|
|
607
|
+
expect(updateWalletSpy).toHaveBeenCalledWith(expect.objectContaining({
|
|
608
|
+
partyId: 'party1::namespace',
|
|
609
|
+
networkId: 'network1',
|
|
610
|
+
status: 'initialized',
|
|
611
|
+
}));
|
|
612
|
+
expect(updateWalletSpy).toHaveBeenCalledWith(expect.objectContaining({
|
|
613
|
+
partyId: 'party2::namespace',
|
|
614
|
+
networkId: 'network1',
|
|
615
|
+
status: 'initialized',
|
|
616
|
+
}));
|
|
617
|
+
});
|
|
618
|
+
it('syncWallets disables participant wallet when party not on ledger', async () => {
|
|
619
|
+
const network1 = createNetwork('network1');
|
|
620
|
+
await store.addNetwork(network1);
|
|
621
|
+
await setSession('network1');
|
|
622
|
+
const participantWallet = createWallet('party1::namespace', 'network1');
|
|
623
|
+
participantWallet.signingProviderId = SigningProvider.PARTICIPANT;
|
|
624
|
+
await store.addWallet(participantWallet);
|
|
625
|
+
mockLedgerGet
|
|
626
|
+
.mockResolvedValueOnce({
|
|
627
|
+
rights: [
|
|
628
|
+
{
|
|
629
|
+
kind: {
|
|
630
|
+
CanActAs: {
|
|
631
|
+
value: { party: 'party2::namespace' },
|
|
632
|
+
},
|
|
633
|
+
},
|
|
634
|
+
},
|
|
635
|
+
],
|
|
636
|
+
})
|
|
637
|
+
.mockResolvedValueOnce({
|
|
638
|
+
participantId: 'participant1::namespace',
|
|
639
|
+
});
|
|
640
|
+
const updateWalletSpy = jest.spyOn(store, 'updateWallet');
|
|
641
|
+
await service.syncWallets();
|
|
642
|
+
expect(updateWalletSpy).toHaveBeenCalledWith(expect.objectContaining({
|
|
643
|
+
partyId: 'party1::namespace',
|
|
644
|
+
networkId: 'network1',
|
|
645
|
+
disabled: true,
|
|
646
|
+
reason: 'participant namespace changed',
|
|
647
|
+
}));
|
|
648
|
+
const wallets = await store.getWallets();
|
|
649
|
+
const party1Wallet = wallets.find((w) => w.partyId === 'party1::namespace');
|
|
650
|
+
expect(party1Wallet?.disabled).toBe(true);
|
|
651
|
+
expect(party1Wallet?.reason).toBe('participant namespace changed');
|
|
652
|
+
});
|
|
505
653
|
});
|
|
506
654
|
});
|