@canton-network/wallet-gateway-remote 0.5.0 → 0.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.
- package/README.md +8 -0
- package/dist/auth/jwt-auth-service.d.ts.map +1 -1
- package/dist/auth/jwt-auth-service.js +2 -3
- package/dist/auth/jwt-unsafe-auth-service.d.ts.map +1 -1
- package/dist/auth/jwt-unsafe-auth-service.js +2 -3
- package/dist/config/Config.d.ts +371 -351
- package/dist/config/Config.d.ts.map +1 -1
- package/dist/config/Config.js +2 -0
- package/dist/config/Config.test.js +7 -10
- package/dist/config/ConfigUtils.d.ts.map +1 -1
- package/dist/config/ConfigUtils.js +59 -1
- package/dist/dapp-api/controller.js +1 -1
- package/dist/dapp-api/rpc-gen/typings.d.ts +18 -4
- package/dist/dapp-api/rpc-gen/typings.d.ts.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +50 -2
- package/dist/ledger/party-allocation-service.d.ts +20 -0
- package/dist/ledger/party-allocation-service.d.ts.map +1 -1
- package/dist/ledger/party-allocation-service.js +22 -2
- package/dist/ledger/party-allocation-service.test.js +10 -6
- package/dist/ledger/wallet-sync-service.js +1 -1
- package/dist/user-api/controller.d.ts +2 -1
- package/dist/user-api/controller.d.ts.map +1 -1
- package/dist/user-api/controller.js +107 -104
- package/dist/user-api/rpc-gen/index.d.ts +6 -3
- package/dist/user-api/rpc-gen/index.d.ts.map +1 -1
- package/dist/user-api/rpc-gen/index.js +2 -1
- package/dist/user-api/rpc-gen/typings.d.ts +96 -41
- package/dist/user-api/rpc-gen/typings.d.ts.map +1 -1
- package/dist/user-api/server.test.js +5 -6
- package/dist/web/frontend/404/index.html +2 -2
- package/dist/web/frontend/approve/index.html +4 -4
- package/dist/web/frontend/assets/{404-BJiXCpwg.js → 404-CXLFjs4t.js} +1 -1
- package/dist/web/frontend/assets/{approve-hB6irrWF.js → approve-gdszLXiW.js} +1 -1
- package/dist/web/frontend/assets/{callback-DHz7ggNR.js → callback-DAqmeyV_.js} +1 -1
- package/dist/web/frontend/assets/{handle-errors-DT2Go5Ch.js → handle-errors-D7N_0lCR.js} +1 -1
- package/dist/web/frontend/assets/{index-YpAHEASq.js → index-DZurNsA2.js} +123 -116
- package/dist/web/frontend/assets/login-BT0j3jH9.js +159 -0
- package/dist/web/frontend/assets/{networks-CaAXx7MG.js → networks-C7TB_OYA.js} +9 -9
- package/dist/web/frontend/assets/{state-BTqOtYLt.js → state-ZaohYYFB.js} +2 -2
- package/dist/web/frontend/assets/wallets-B3XNkikq.js +244 -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/networks/index.html +4 -4
- package/dist/web/frontend/wallets/index.html +4 -4
- package/package.json +17 -15
- package/dist/web/frontend/assets/login-Dw5jymys.js +0 -159
- package/dist/web/frontend/assets/wallets-fk9mMhcI.js +0 -214
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../../src/config/Config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../../src/config/Config.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;EAU3B,CAAA;AAEF,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAQ22W,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAAwwC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAA2wD,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAA4uD,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAA9uD,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAA4uD,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAJrogB,CAAA;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAA;AACzD,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAA"}
|
package/dist/config/Config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Copyright (c) 2025 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import { storeConfigSchema } from '@canton-network/core-wallet-store';
|
|
4
|
+
import { storeConfigSchema as signingStoreConfigSchema } from '@canton-network/core-signing-store-sql';
|
|
4
5
|
import { z } from 'zod';
|
|
5
6
|
export const kernelInfoSchema = z.object({
|
|
6
7
|
id: z.string(),
|
|
@@ -16,4 +17,5 @@ export const kernelInfoSchema = z.object({
|
|
|
16
17
|
export const configSchema = z.object({
|
|
17
18
|
kernel: kernelInfoSchema,
|
|
18
19
|
store: storeConfigSchema,
|
|
20
|
+
signingStore: signingStoreConfigSchema,
|
|
19
21
|
});
|
|
@@ -4,16 +4,13 @@ import { expect, test } from '@jest/globals';
|
|
|
4
4
|
import { ConfigUtils } from './ConfigUtils.js';
|
|
5
5
|
test('config from json file', async () => {
|
|
6
6
|
const resp = ConfigUtils.loadConfigFile('../test/config.json');
|
|
7
|
-
expect(resp.store.networks[0].name).toBe('Local (
|
|
8
|
-
expect(resp.store.networks[0].ledgerApi.baseUrl).toBe('
|
|
9
|
-
expect(resp.store.networks[0].auth.clientId).toBe('
|
|
10
|
-
expect(resp.store.networks[0].auth.scope).toBe('openid');
|
|
11
|
-
expect(resp.store.networks[0].auth.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
expect(resp.store.networks[1].auth.type).toBe('implicit');
|
|
16
|
-
if (resp.store.networks[1].auth.type === 'implicit') {
|
|
7
|
+
expect(resp.store.networks[0].name).toBe('Local (OAuth IDP)');
|
|
8
|
+
expect(resp.store.networks[0].ledgerApi.baseUrl).toBe('http://127.0.0.1:5003');
|
|
9
|
+
expect(resp.store.networks[0].auth.clientId).toBe('operator');
|
|
10
|
+
expect(resp.store.networks[0].auth.scope).toBe('openid daml_ledger_api offline_access');
|
|
11
|
+
expect(resp.store.networks[0].auth.method).toBe('authorization_code');
|
|
12
|
+
expect(resp.store.networks[1].auth.method).toBe('client_credentials');
|
|
13
|
+
if (resp.store.networks[1].auth.method === 'client_credentials') {
|
|
17
14
|
expect(resp.store.networks[1].auth.audience).toBe('https://daml.com/jwt/aud/participant/participant1::1220d44fc1c3ba0b5bdf7b956ee71bc94ebe2d23258dc268fdf0824fbaeff2c61424');
|
|
18
15
|
}
|
|
19
16
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConfigUtils.d.ts","sourceRoot":"","sources":["../../src/config/ConfigUtils.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAgB,MAAM,aAAa,CAAA;AAElD,qBAAa,WAAW;IACpB,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;
|
|
1
|
+
{"version":3,"file":"ConfigUtils.d.ts","sourceRoot":"","sources":["../../src/config/ConfigUtils.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAgB,MAAM,aAAa,CAAA;AAElD,qBAAa,WAAW;IACpB,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;CAoDlD"}
|
|
@@ -5,10 +5,68 @@ import { configSchema } from './Config.js';
|
|
|
5
5
|
export class ConfigUtils {
|
|
6
6
|
static loadConfigFile(filePath) {
|
|
7
7
|
if (existsSync(filePath)) {
|
|
8
|
-
|
|
8
|
+
const config = configSchema.parse(JSON.parse(readFileSync(filePath, 'utf-8')));
|
|
9
|
+
/**
|
|
10
|
+
* Perform extra config validation beyond schema validation.
|
|
11
|
+
* We want to enforce the following constraints:
|
|
12
|
+
*
|
|
13
|
+
* 1. IDP IDs are unique
|
|
14
|
+
* 2. Network IDs are unique
|
|
15
|
+
* 3. Each Network's identityProviderId maps to an existing IDP (in config)
|
|
16
|
+
* 4. Each Network's auth method is compatible with its IDP type
|
|
17
|
+
*/
|
|
18
|
+
const duplicateIdpId = hasDuplicateElement(config.store.idps.map((idp) => idp.id));
|
|
19
|
+
if (duplicateIdpId) {
|
|
20
|
+
throw new Error(`Non-unique IDP IDs found in config file: ${duplicateIdpId}`);
|
|
21
|
+
}
|
|
22
|
+
const duplicateNetworkId = hasDuplicateElement(config.store.networks.map((network) => network.id));
|
|
23
|
+
if (duplicateNetworkId) {
|
|
24
|
+
throw new Error(`Non-unique Network IDs found in config file: ${duplicateNetworkId}`);
|
|
25
|
+
}
|
|
26
|
+
const invalidMapping = validateNetworkToIdpMapping(config);
|
|
27
|
+
if (invalidMapping) {
|
|
28
|
+
throw new Error(`Network ${invalidMapping.networkId} references unknown Identity Provider ID ${invalidMapping.idpId}`);
|
|
29
|
+
}
|
|
30
|
+
const invalidAuthMethod = validateNetworkAuthMethods(config);
|
|
31
|
+
if (invalidAuthMethod) {
|
|
32
|
+
throw new Error(`Network ${invalidAuthMethod.networkId} has invalid auth method ${invalidAuthMethod.invalidAuthMethod} for its Identity Provider`);
|
|
33
|
+
}
|
|
34
|
+
return config;
|
|
9
35
|
}
|
|
10
36
|
else {
|
|
11
37
|
throw new Error("Supplied file path doesn't exist " + filePath);
|
|
12
38
|
}
|
|
13
39
|
}
|
|
14
40
|
}
|
|
41
|
+
function hasDuplicateElement(list) {
|
|
42
|
+
let duplicate;
|
|
43
|
+
list.forEach((item, i) => {
|
|
44
|
+
if (list.indexOf(item) !== i && duplicate === undefined) {
|
|
45
|
+
duplicate = item;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
return duplicate;
|
|
49
|
+
}
|
|
50
|
+
function validateNetworkToIdpMapping(config) {
|
|
51
|
+
for (const network of config.store.networks) {
|
|
52
|
+
const idp = config.store.idps.find((idp) => idp.id === network.identityProviderId);
|
|
53
|
+
if (typeof idp === 'undefined') {
|
|
54
|
+
return { networkId: network.id, idpId: network.identityProviderId };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const SUPPORTED_IDP_METHODS = {
|
|
59
|
+
self_signed: ['self_signed'],
|
|
60
|
+
oauth: ['authorization_code', 'client_credentials'],
|
|
61
|
+
};
|
|
62
|
+
function validateNetworkAuthMethods(config) {
|
|
63
|
+
for (const network of config.store.networks) {
|
|
64
|
+
const idp = config.store.idps.find((idp) => idp.id === network.identityProviderId);
|
|
65
|
+
if (!SUPPORTED_IDP_METHODS[idp.type].includes(network.auth.method)) {
|
|
66
|
+
return {
|
|
67
|
+
networkId: network.id,
|
|
68
|
+
invalidAuthMethod: network.auth.method,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -117,7 +117,7 @@ export const dappController = (kernelInfo, store, notificationService, _logger,
|
|
|
117
117
|
return {
|
|
118
118
|
kernel: kernelInfo,
|
|
119
119
|
isConnected: true,
|
|
120
|
-
|
|
120
|
+
networkId: (await store.getCurrentNetwork()).id,
|
|
121
121
|
};
|
|
122
122
|
}
|
|
123
123
|
},
|
|
@@ -49,11 +49,11 @@ export type IsConnected = boolean;
|
|
|
49
49
|
* The network ID the wallet corresponds to.
|
|
50
50
|
*
|
|
51
51
|
*/
|
|
52
|
-
export type
|
|
52
|
+
export type NetworkId = string;
|
|
53
53
|
export interface StatusEvent {
|
|
54
54
|
kernel: KernelInfo;
|
|
55
55
|
isConnected: IsConnected;
|
|
56
|
-
|
|
56
|
+
networkId?: NetworkId;
|
|
57
57
|
[k: string]: any;
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
@@ -129,6 +129,18 @@ export type Namespace = string;
|
|
|
129
129
|
*
|
|
130
130
|
*/
|
|
131
131
|
export type SigningProviderId = string;
|
|
132
|
+
/**
|
|
133
|
+
*
|
|
134
|
+
* Unique identifier of the signed transaction given by the Signing Provider. This may not be the same as the internal txId given by the Wallet Gateway.
|
|
135
|
+
*
|
|
136
|
+
*/
|
|
137
|
+
export type ExternalTxId = string;
|
|
138
|
+
/**
|
|
139
|
+
*
|
|
140
|
+
* The topology transactions
|
|
141
|
+
*
|
|
142
|
+
*/
|
|
143
|
+
export type TopologyTransactions = string;
|
|
132
144
|
/**
|
|
133
145
|
*
|
|
134
146
|
* Structure representing a wallet
|
|
@@ -140,8 +152,10 @@ export interface Wallet {
|
|
|
140
152
|
hint: Hint;
|
|
141
153
|
publicKey: PublicKey;
|
|
142
154
|
namespace: Namespace;
|
|
143
|
-
|
|
155
|
+
networkId: NetworkId;
|
|
144
156
|
signingProviderId: SigningProviderId;
|
|
157
|
+
externalTxId?: ExternalTxId;
|
|
158
|
+
topologyTransactions?: TopologyTransactions;
|
|
145
159
|
[k: string]: any;
|
|
146
160
|
}
|
|
147
161
|
/**
|
|
@@ -301,7 +315,7 @@ export interface LedgerApiResult {
|
|
|
301
315
|
}
|
|
302
316
|
export interface OnConnectedEvent {
|
|
303
317
|
kernel: KernelInfo;
|
|
304
|
-
|
|
318
|
+
networkId: NetworkId;
|
|
305
319
|
sessionToken?: SessionToken;
|
|
306
320
|
[k: string]: any;
|
|
307
321
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typings.d.ts","sourceRoot":"","sources":["../../../src/dapp-api/rpc-gen/typings.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACvB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACrB;AACD,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAA;AAC7D,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAA;AAC7B,MAAM,MAAM,IAAI,GAAG,MAAM,CAAA;AACzB;;;;GAIG;AACH,MAAM,MAAM,EAAE,GAAG,MAAM,CAAA;AACvB;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAA;AACpE;;;;GAIG;AACH,MAAM,MAAM,GAAG,GAAG,MAAM,CAAA;AACxB;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACvB,EAAE,EAAE,EAAE,CAAA;IACN,UAAU,EAAE,UAAU,CAAA;IACtB,GAAG,CAAC,EAAE,GAAG,CAAA;IACT,CAAC,CAAC,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB;AACD;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,CAAA;AACjC;;;;GAIG;AACH,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"typings.d.ts","sourceRoot":"","sources":["../../../src/dapp-api/rpc-gen/typings.ts"],"names":[],"mappings":"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"}
|
package/dist/init.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAsB7B,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AA0GvC,wBAAsB,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,iBAwFhE"}
|
package/dist/init.js
CHANGED
|
@@ -4,16 +4,19 @@ import { dapp } from './dapp-api/server.js';
|
|
|
4
4
|
import { user } from './user-api/server.js';
|
|
5
5
|
import { web } from './web/server.js';
|
|
6
6
|
import { StoreSql, bootstrap, connection, migrator, } from '@canton-network/core-wallet-store-sql';
|
|
7
|
+
import { StoreSql as SigningStoreSql, bootstrap as signingBootstrap, connection as signingConnection, migrator as signingMigrator, } from '@canton-network/core-signing-store-sql';
|
|
7
8
|
import { ConfigUtils } from './config/ConfigUtils.js';
|
|
8
9
|
import EventEmitter from 'events';
|
|
9
10
|
import { SigningProvider } from '@canton-network/core-signing-lib';
|
|
10
11
|
import { ParticipantSigningDriver } from '@canton-network/core-signing-participant';
|
|
11
12
|
import { InternalSigningDriver } from '@canton-network/core-signing-internal';
|
|
13
|
+
import FireblocksSigningProvider from '@canton-network/core-signing-fireblocks';
|
|
12
14
|
import { jwtAuthService } from './auth/jwt-auth-service.js';
|
|
13
15
|
import express from 'express';
|
|
14
16
|
import { jwtAuth } from './middleware/jwtAuth.js';
|
|
15
17
|
import { rpcRateLimit } from './middleware/rateLimit.js';
|
|
16
|
-
import { existsSync } from 'fs';
|
|
18
|
+
import { existsSync, readFileSync } from 'fs';
|
|
19
|
+
import path from 'path';
|
|
17
20
|
let isReady = false;
|
|
18
21
|
class NotificationService {
|
|
19
22
|
constructor(logger) {
|
|
@@ -60,6 +63,30 @@ async function initializeDatabase(config, logger) {
|
|
|
60
63
|
}
|
|
61
64
|
return new StoreSql(db, logger);
|
|
62
65
|
}
|
|
66
|
+
async function initializeSigningDatabase(config, logger) {
|
|
67
|
+
logger.info('Checking for signing database migrations...');
|
|
68
|
+
let exists = true;
|
|
69
|
+
if (config.signingStore.connection.type === 'sqlite') {
|
|
70
|
+
exists = existsSync(config.signingStore.connection.database);
|
|
71
|
+
}
|
|
72
|
+
const db = signingConnection(config.signingStore);
|
|
73
|
+
const umzug = signingMigrator(db);
|
|
74
|
+
const pending = await umzug.pending();
|
|
75
|
+
if (pending.length > 0) {
|
|
76
|
+
logger.info({ pendingMigrations: pending.map((m) => m.name) }, 'Applying database migrations...');
|
|
77
|
+
await umzug.up();
|
|
78
|
+
logger.info('Database migrations applied successfully.');
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
logger.info('No pending database migrations found.');
|
|
82
|
+
}
|
|
83
|
+
// bootstrap database from config file if it did not exist before
|
|
84
|
+
if (!exists) {
|
|
85
|
+
logger.info('Bootstrapping database from config...');
|
|
86
|
+
await signingBootstrap(db, config.store, logger);
|
|
87
|
+
}
|
|
88
|
+
return new SigningStoreSql(db, logger);
|
|
89
|
+
}
|
|
63
90
|
export async function initialize(opts, logger) {
|
|
64
91
|
const port = opts.port ? Number(opts.port) : 3030;
|
|
65
92
|
const app = express();
|
|
@@ -78,10 +105,31 @@ export async function initialize(opts, logger) {
|
|
|
78
105
|
const notificationService = new NotificationService(logger);
|
|
79
106
|
const config = ConfigUtils.loadConfigFile(opts.config);
|
|
80
107
|
const store = await initializeDatabase(config, logger);
|
|
108
|
+
const signingStore = await initializeSigningDatabase(config, logger);
|
|
81
109
|
const authService = jwtAuthService(store, logger);
|
|
110
|
+
// Provide apiKey from User API in Fireblocks
|
|
111
|
+
const apiPath = path.resolve(process.cwd(), 'fireblocks_api.key');
|
|
112
|
+
const secretPath = path.resolve(process.cwd(), 'fireblocks_secret.key');
|
|
113
|
+
let apiKey = '';
|
|
114
|
+
let apiSecret = '';
|
|
115
|
+
if (existsSync(apiPath) && existsSync(secretPath)) {
|
|
116
|
+
apiKey = readFileSync(apiPath, 'utf8');
|
|
117
|
+
apiSecret = readFileSync(secretPath, 'utf8');
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
apiKey = 'missing';
|
|
121
|
+
apiSecret = 'missing';
|
|
122
|
+
logger.warn('Fireblocks keys files are missing');
|
|
123
|
+
}
|
|
124
|
+
const keyInfo = { apiKey, apiSecret };
|
|
125
|
+
const userApiKeys = new Map([['user', keyInfo]]);
|
|
82
126
|
const drivers = {
|
|
83
127
|
[SigningProvider.PARTICIPANT]: new ParticipantSigningDriver(),
|
|
84
|
-
[SigningProvider.WALLET_KERNEL]: new InternalSigningDriver(),
|
|
128
|
+
[SigningProvider.WALLET_KERNEL]: new InternalSigningDriver(signingStore),
|
|
129
|
+
[SigningProvider.FIREBLOCKS]: new FireblocksSigningProvider({
|
|
130
|
+
defaultKeyInfo: keyInfo,
|
|
131
|
+
userApiKeys,
|
|
132
|
+
}),
|
|
85
133
|
};
|
|
86
134
|
app.use('/api/*splat', express.json());
|
|
87
135
|
app.use('/api/*splat', rpcRateLimit);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { GenerateTransactionResponse } from '@canton-network/core-ledger-client';
|
|
1
2
|
import { AccessTokenProvider } from '@canton-network/core-wallet-auth';
|
|
2
3
|
import { Logger } from 'pino';
|
|
3
4
|
export type AllocatedParty = {
|
|
@@ -28,6 +29,25 @@ export declare class PartyAllocationService {
|
|
|
28
29
|
* @param signingCallback A callback function that asynchronously signs the onboarding request hash.
|
|
29
30
|
*/
|
|
30
31
|
allocateParty(userId: string, hint: string, publicKey: string, signingCallback: SigningCbFn): Promise<AllocatedParty>;
|
|
32
|
+
/**
|
|
33
|
+
* Create fingerprint
|
|
34
|
+
* @param publicKey The public key of the user.
|
|
35
|
+
*/
|
|
36
|
+
createFingerprintFromKey(publicKey: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* Generate topology transactions
|
|
39
|
+
* @param hint A hint for the party ID.
|
|
40
|
+
* @param publicKey The public key of the user.
|
|
41
|
+
*/
|
|
42
|
+
generateTopologyTransactions(hint: string, publicKey: string): Promise<GenerateTransactionResponse>;
|
|
43
|
+
/**
|
|
44
|
+
* Allocate party with wallet
|
|
45
|
+
* @param namespace The namespace of wallet.
|
|
46
|
+
* @param transactions There are topology transactions.
|
|
47
|
+
* @param signature A transaction signature from signingProviderId
|
|
48
|
+
* @param userId The ID of the user.
|
|
49
|
+
*/
|
|
50
|
+
allocatePartyWithExistingWallet(namespace: string, transactions: string[], signature: string, userId: string): Promise<string>;
|
|
31
51
|
private allocateInternalParty;
|
|
32
52
|
private allocateExternalParty;
|
|
33
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"party-allocation-service.d.ts","sourceRoot":"","sources":["../../src/ledger/party-allocation-service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"party-allocation-service.d.ts","sourceRoot":"","sources":["../../src/ledger/party-allocation-service.ts"],"names":[],"mappings":"AAGA,OAAO,EACH,2BAA2B,EAG9B,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AACtE,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAE7B,MAAM,MAAM,cAAc,GAAG;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,KAAK,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;AAEpD;;GAEG;AACH,qBAAa,sBAAsB;IAI3B,OAAO,CAAC,cAAc;IAGtB,OAAO,CAAC,MAAM;IANlB,OAAO,CAAC,YAAY,CAAc;gBAGtB,cAAc,EAAE,MAAM,EAC9B,mBAAmB,EAAE,mBAAmB,EACxC,aAAa,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACtB,WAAW,GAAE,MAAW;IAW5B;;;;OAIG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAE1E;;;;;;OAMG;IACG,aAAa,CACf,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,WAAW,GAC7B,OAAO,CAAC,cAAc,CAAC;IAoB1B;;;OAGG;IACH,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAMnD;;;;OAIG;IACG,4BAA4B,CAC9B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAClB,OAAO,CAAC,2BAA2B,CAAC;IAavC;;;;;;OAMG;IACG,+BAA+B,CACjC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EAAE,EACtB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC;YA8BJ,qBAAqB;YAuBrB,qBAAqB;CAoCtC"}
|
|
@@ -18,6 +18,26 @@ export class PartyAllocationService {
|
|
|
18
18
|
return this.allocateInternalParty(userId, hint);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
createFingerprintFromKey(publicKey) {
|
|
22
|
+
return TopologyWriteService.createFingerprintFromKey(publicKey);
|
|
23
|
+
}
|
|
24
|
+
async generateTopologyTransactions(hint, publicKey) {
|
|
25
|
+
return this.ledgerClient.generateTopology(this.synchronizerId, publicKey, hint);
|
|
26
|
+
}
|
|
27
|
+
async allocatePartyWithExistingWallet(namespace, transactions, signature, userId) {
|
|
28
|
+
const res = await this.ledgerClient.allocateExternalParty(this.synchronizerId, transactions.map((transaction) => ({
|
|
29
|
+
transaction,
|
|
30
|
+
})), [
|
|
31
|
+
{
|
|
32
|
+
format: 'SIGNATURE_FORMAT_CONCAT',
|
|
33
|
+
signature: signature,
|
|
34
|
+
signedBy: namespace,
|
|
35
|
+
signingAlgorithmSpec: 'SIGNING_ALGORITHM_SPEC_ED25519',
|
|
36
|
+
},
|
|
37
|
+
]);
|
|
38
|
+
await this.ledgerClient.waitForPartyAndGrantUserRights(userId, res.partyId);
|
|
39
|
+
return res.partyId;
|
|
40
|
+
}
|
|
21
41
|
async allocateInternalParty(userId, hint) {
|
|
22
42
|
const { participantId: namespace } = await this.ledgerClient.getWithRetry('/v2/parties/participant-id');
|
|
23
43
|
const res = await this.ledgerClient.postWithRetry('/v2/parties', {
|
|
@@ -31,8 +51,8 @@ export class PartyAllocationService {
|
|
|
31
51
|
return { hint, namespace, partyId: res.partyDetails.party };
|
|
32
52
|
}
|
|
33
53
|
async allocateExternalParty(userId, hint, publicKey, signingCallback) {
|
|
34
|
-
const namespace =
|
|
35
|
-
const transactions = await this.
|
|
54
|
+
const namespace = this.createFingerprintFromKey(publicKey);
|
|
55
|
+
const transactions = await this.generateTopologyTransactions(hint, publicKey);
|
|
36
56
|
const signature = await signingCallback(transactions.multiHash);
|
|
37
57
|
const res = await this.ledgerClient.allocateExternalParty(this.synchronizerId, transactions.topologyTransactions.map((transaction) => ({
|
|
38
58
|
transaction,
|
|
@@ -39,21 +39,25 @@ jest.unstable_mockModule('@canton-network/core-ledger-client', () => ({
|
|
|
39
39
|
describe('PartyAllocationService', () => {
|
|
40
40
|
const network = {
|
|
41
41
|
name: 'test',
|
|
42
|
-
|
|
42
|
+
id: 'network-id',
|
|
43
43
|
synchronizerId: 'sync-id',
|
|
44
44
|
description: 'desc',
|
|
45
|
+
identityProviderId: 'idp',
|
|
45
46
|
ledgerApi: {
|
|
46
47
|
baseUrl: 'http://ledger',
|
|
47
48
|
},
|
|
48
49
|
auth: {
|
|
49
|
-
|
|
50
|
-
type: 'implicit',
|
|
51
|
-
issuer: 'http://idp',
|
|
52
|
-
configUrl: 'http://idp/.well-known/openid-configuration',
|
|
50
|
+
method: 'authorization_code',
|
|
53
51
|
audience: 'aud',
|
|
54
52
|
scope: 'scope',
|
|
55
53
|
clientId: 'cid',
|
|
56
|
-
|
|
54
|
+
},
|
|
55
|
+
adminAuth: {
|
|
56
|
+
method: 'client_credentials',
|
|
57
|
+
audience: 'aud',
|
|
58
|
+
scope: 'scope',
|
|
59
|
+
clientId: 'cid',
|
|
60
|
+
clientSecret: 'secret',
|
|
57
61
|
},
|
|
58
62
|
};
|
|
59
63
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -8,6 +8,8 @@ type AvailableSigningDrivers = Partial<Record<SigningProvider, SigningDriverInte
|
|
|
8
8
|
export declare const userController: (kernelInfo: KernelInfo, store: Store, notificationService: NotificationService, authContext: AuthContext | undefined, drivers: AvailableSigningDrivers, _logger: Logger) => {
|
|
9
9
|
addNetwork: import("./rpc-gen/typings.js").AddNetwork;
|
|
10
10
|
removeNetwork: import("./rpc-gen/typings.js").RemoveNetwork;
|
|
11
|
+
listNetworks: import("./rpc-gen/typings.js").ListNetworks;
|
|
12
|
+
listIdps: import("./rpc-gen/typings.js").ListIdps;
|
|
11
13
|
createWallet: import("./rpc-gen/typings.js").CreateWallet;
|
|
12
14
|
setPrimaryWallet: import("./rpc-gen/typings.js").SetPrimaryWallet;
|
|
13
15
|
removeWallet: import("./rpc-gen/typings.js").RemoveWallet;
|
|
@@ -15,7 +17,6 @@ export declare const userController: (kernelInfo: KernelInfo, store: Store, noti
|
|
|
15
17
|
syncWallets: import("./rpc-gen/typings.js").SyncWallets;
|
|
16
18
|
sign: import("./rpc-gen/typings.js").Sign;
|
|
17
19
|
execute: import("./rpc-gen/typings.js").Execute;
|
|
18
|
-
listNetworks: import("./rpc-gen/typings.js").ListNetworks;
|
|
19
20
|
addSession: import("./rpc-gen/typings.js").AddSession;
|
|
20
21
|
listSessions: import("./rpc-gen/typings.js").ListSessions;
|
|
21
22
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/user-api/controller.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/user-api/controller.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,KAAK,EAAwB,MAAM,mCAAmC,CAAA;AAC/E,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAC7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AAC5E,OAAO,EAGH,WAAW,EAGd,MAAM,kCAAkC,CAAA;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,EACH,sBAAsB,EACtB,eAAe,EAClB,MAAM,kCAAkC,CAAA;AAOzC,KAAK,uBAAuB,GAAG,OAAO,CAClC,MAAM,CAAC,eAAe,EAAE,sBAAsB,CAAC,CAClD,CAAA;AAED,eAAO,MAAM,cAAc,GACvB,YAAY,UAAU,EACtB,OAAO,KAAK,EACZ,qBAAqB,mBAAmB,EACxC,aAAa,WAAW,GAAG,SAAS,EACpC,SAAS,uBAAuB,EAChC,SAAS,MAAM;;;;;;;;;;;;;;CA6hBlB,CAAA"}
|