From 4f68f4ab8dec9404fe91e798e88ac7c76afa732c Mon Sep 17 00:00:00 2001 From: Pawel Stepien Date: Thu, 27 Nov 2025 11:59:48 +0100 Subject: [PATCH 1/8] userUrl from kernelInfo Signed-off-by: Pawel Stepien --- wallet-gateway/remote/src/dapp-api/controller.ts | 9 ++++----- wallet-gateway/remote/src/user-api/controller.ts | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/wallet-gateway/remote/src/dapp-api/controller.ts b/wallet-gateway/remote/src/dapp-api/controller.ts index d1cb99aad..ba314cda4 100644 --- a/wallet-gateway/remote/src/dapp-api/controller.ts +++ b/wallet-gateway/remote/src/dapp-api/controller.ts @@ -41,7 +41,7 @@ export const dappController = ( isConnected: false, isNetworkConnected: false, networkReason: 'Unauthenticated', - userUrl: 'http://localhost:3030/login/', // TODO: pull user URL from config + userUrl: `${kernelInfo.userUrl}/login/`, }, } } @@ -61,7 +61,7 @@ export const dappController = ( isConnected: true, isNetworkConnected: status.isConnected, networkReason: status.reason ? status.reason : 'OK', - userUrl: 'http://localhost:3030/login/', // TODO: pull user URL from config + userUrl: `${kernelInfo.userUrl}/login/`, }, } }, @@ -76,7 +76,7 @@ export const dappController = ( isConnected: false, isNetworkConnected: false, networkReason: 'Unauthenticated', - userUrl: 'http://localhost:3030/login/', // TODO: pull user URL from config + userUrl: `${kernelInfo.userUrl}/login/`, } as StatusEvent) } @@ -164,8 +164,7 @@ export const dappController = ( }) return { - // TODO: pull user base URL / port from config - userUrl: `http://localhost:3030/approve/index.html?commandId=${commandId}`, + userUrl: `${kernelInfo.userUrl}/approve/index.html?commandId=${commandId}`, } }, prepareReturn: async (params: PrepareReturnParams) => { diff --git a/wallet-gateway/remote/src/user-api/controller.ts b/wallet-gateway/remote/src/user-api/controller.ts index 96823a371..3f988444a 100644 --- a/wallet-gateway/remote/src/user-api/controller.ts +++ b/wallet-gateway/remote/src/user-api/controller.ts @@ -602,7 +602,7 @@ export const userController = ( isConnected: false, isNetworkConnected: false, networkReason: 'Unauthenticated', - userUrl: 'http://localhost:3030/login/', // TODO: pull user URL from config + userUrl: `${kernelInfo.userUrl}/login/`, } as StatusEvent) return null }, From 59e2c07d30059f7938a369800102a3e3260ec721 Mon Sep 17 00:00:00 2001 From: Pawel Stepien Date: Thu, 27 Nov 2025 13:49:14 +0100 Subject: [PATCH 2/8] add server config and set cors allowed urls Signed-off-by: Pawel Stepien --- wallet-gateway/remote/src/config/Config.ts | 6 ++++++ wallet-gateway/remote/src/dapp-api/server.test.ts | 1 + wallet-gateway/remote/src/dapp-api/server.ts | 11 ++++++++--- wallet-gateway/remote/src/example-config.ts | 3 +++ wallet-gateway/remote/src/init.ts | 1 + wallet-gateway/test/config.json | 3 +++ 6 files changed, 22 insertions(+), 3 deletions(-) diff --git a/wallet-gateway/remote/src/config/Config.ts b/wallet-gateway/remote/src/config/Config.ts index 8ea383689..550d1ca7a 100644 --- a/wallet-gateway/remote/src/config/Config.ts +++ b/wallet-gateway/remote/src/config/Config.ts @@ -17,11 +17,17 @@ export const kernelInfoSchema = z.object({ userUrl: z.string().url(), }) +export const serverConfigSchema = z.object({ + allowedOrigins: z.array(z.string()).default(['*']), +}) + export const configSchema = z.object({ kernel: kernelInfoSchema, + server: serverConfigSchema, store: storeConfigSchema, signingStore: signingStoreConfigSchema, }) export type KernelInfo = z.infer +export type ServerConfig = z.infer export type Config = z.infer diff --git a/wallet-gateway/remote/src/dapp-api/server.test.ts b/wallet-gateway/remote/src/dapp-api/server.test.ts index dde196bf2..4e9bb4754 100644 --- a/wallet-gateway/remote/src/dapp-api/server.test.ts +++ b/wallet-gateway/remote/src/dapp-api/server.test.ts @@ -48,6 +48,7 @@ test('call connect rpc', async () => { pino(sink()), server, config.kernel, + config.server, notificationService, authService, store diff --git a/wallet-gateway/remote/src/dapp-api/server.ts b/wallet-gateway/remote/src/dapp-api/server.ts index f4fe8a104..1a50de5b1 100644 --- a/wallet-gateway/remote/src/dapp-api/server.ts +++ b/wallet-gateway/remote/src/dapp-api/server.ts @@ -15,7 +15,7 @@ import { NotificationService, Notifier, } from '../notification/NotificationService.js' -import { KernelInfo } from '../config/Config.js' +import { KernelInfo, ServerConfig } from '../config/Config.js' export const dapp = ( route: string, @@ -23,11 +23,16 @@ export const dapp = ( logger: Logger, server: Server, kernelInfo: KernelInfo, + serverConfig: ServerConfig, notificationService: NotificationService, authService: AuthService, store: Store & AuthAware ) => { - app.use(cors()) // TODO: read allowedOrigins from config + app.use( + cors({ + origin: serverConfig.allowedOrigins, + }) + ) app.use(route, (req, res, next) => jsonRpcHandler({ controller: dappController( @@ -43,7 +48,7 @@ export const dapp = ( const io = new SocketIoServer(server, { cors: { - origin: '*', // TODO: read allowedOrigins from config + origin: serverConfig.allowedOrigins, methods: ['GET', 'POST'], }, }) diff --git a/wallet-gateway/remote/src/example-config.ts b/wallet-gateway/remote/src/example-config.ts index 9d0c3168f..5a8c692ca 100644 --- a/wallet-gateway/remote/src/example-config.ts +++ b/wallet-gateway/remote/src/example-config.ts @@ -10,6 +10,9 @@ export default { url: 'http://localhost:3030/api/v0/dapp', userUrl: 'http://localhost:3030', }, + server: { + allowedOrigins: ['http://localhost:8080'], + }, signingStore: { connection: { type: 'sqlite', diff --git a/wallet-gateway/remote/src/init.ts b/wallet-gateway/remote/src/init.ts index b1b8403aa..2e9d999ff 100644 --- a/wallet-gateway/remote/src/init.ts +++ b/wallet-gateway/remote/src/init.ts @@ -199,6 +199,7 @@ export async function initialize(opts: CliOptions, logger: Logger) { logger, server, config.kernel, + config.server, notificationService, authService, store diff --git a/wallet-gateway/test/config.json b/wallet-gateway/test/config.json index 4dcfab25c..ab9515a1c 100644 --- a/wallet-gateway/test/config.json +++ b/wallet-gateway/test/config.json @@ -5,6 +5,9 @@ "url": "http://localhost:3030/api/v0/dapp", "userUrl": "http://localhost:3030" }, + "server": { + "allowedOrigins": ["http://localhost:8080"] + }, "store": { "connection": { "type": "sqlite", From 25c28e10e6ffde49f85d93bba0438e479617ba80 Mon Sep 17 00:00:00 2001 From: Pawel Stepien Date: Thu, 27 Nov 2025 13:54:09 +0100 Subject: [PATCH 3/8] fix default wildcard cors Signed-off-by: Pawel Stepien --- wallet-gateway/remote/src/config/Config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet-gateway/remote/src/config/Config.ts b/wallet-gateway/remote/src/config/Config.ts index 550d1ca7a..cebb7dd5d 100644 --- a/wallet-gateway/remote/src/config/Config.ts +++ b/wallet-gateway/remote/src/config/Config.ts @@ -18,7 +18,7 @@ export const kernelInfoSchema = z.object({ }) export const serverConfigSchema = z.object({ - allowedOrigins: z.array(z.string()).default(['*']), + allowedOrigins: z.union([z.literal('*'), z.array(z.string())]).default('*'), }) export const configSchema = z.object({ From 718fdad9ef0b82a56d21ae9f9a7bd7ea65656a20 Mon Sep 17 00:00:00 2001 From: Pawel Stepien Date: Fri, 28 Nov 2025 14:14:39 +0100 Subject: [PATCH 4/8] all URL related stuff from config instead of hardcode Signed-off-by: Pawel Stepien --- wallet-gateway/remote/src/config/Config.ts | 7 +++- .../remote/src/config/ConfigUtils.ts | 21 +++++++++- .../remote/src/dapp-api/controller.ts | 10 +++-- .../remote/src/dapp-api/server.test.ts | 7 ++-- wallet-gateway/remote/src/dapp-api/server.ts | 4 ++ wallet-gateway/remote/src/example-config.ts | 8 +++- wallet-gateway/remote/src/init.ts | 36 +++++++++++++----- .../remote/src/user-api/controller.ts | 3 +- .../remote/src/user-api/server.test.ts | 4 +- wallet-gateway/remote/src/user-api/server.ts | 2 + .../remote/src/web/frontend/approve/index.ts | 8 +++- .../remote/src/web/frontend/index.ts | 6 ++- .../remote/src/web/frontend/login/login.ts | 10 +++-- .../remote/src/web/frontend/rpc-client.ts | 31 +++++++++++---- .../remote/src/web/frontend/settings/index.ts | 38 ++++++++++++++----- .../remote/src/web/frontend/wallets/index.ts | 20 +++++++--- wallet-gateway/remote/src/web/server.ts | 6 ++- wallet-gateway/test/config.json | 9 +++-- 18 files changed, 174 insertions(+), 56 deletions(-) diff --git a/wallet-gateway/remote/src/config/Config.ts b/wallet-gateway/remote/src/config/Config.ts index cebb7dd5d..c22d6a7bb 100644 --- a/wallet-gateway/remote/src/config/Config.ts +++ b/wallet-gateway/remote/src/config/Config.ts @@ -13,11 +13,14 @@ export const kernelInfoSchema = z.object({ z.literal('mobile'), z.literal('remote'), ]), - url: z.string().url(), - userUrl: z.string().url(), }) export const serverConfigSchema = z.object({ + host: z.string().optional(), + port: z.number().optional(), + tls: z.boolean().optional(), + dappPath: z.string().default('/api/v0/dapp'), + userPath: z.string().default('/api/v0/user'), allowedOrigins: z.union([z.literal('*'), z.array(z.string())]).default('*'), }) diff --git a/wallet-gateway/remote/src/config/ConfigUtils.ts b/wallet-gateway/remote/src/config/ConfigUtils.ts index 857a02443..b9517233e 100644 --- a/wallet-gateway/remote/src/config/ConfigUtils.ts +++ b/wallet-gateway/remote/src/config/ConfigUtils.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { readFileSync, existsSync } from 'fs' -import { Config, configSchema } from './Config.js' +import { Config, configSchema, ServerConfig } from './Config.js' export class ConfigUtils { static loadConfigFile(filePath: string): Config { @@ -104,3 +104,22 @@ function validateNetworkAuthMethods( } } } + +export const deriveKernelUrls = (serverConfig: ServerConfig) => { + // Only derive if all required properties are present + if ( + !serverConfig.host || + serverConfig.port === undefined || + serverConfig.tls === undefined + ) { + return { dappUrl: undefined, userUrl: undefined } + } + + const protocol = serverConfig.tls ? 'https' : 'http' + const dappPath = serverConfig.dappPath ?? '/api/v0/dapp' + const dappUrl = `${protocol}://${serverConfig.host}:${serverConfig.port}${dappPath}` + // userUrl is the base URL for the web frontend (no path) + const userUrl = `${protocol}://${serverConfig.host}:${serverConfig.port}` + + return { dappUrl, userUrl } +} diff --git a/wallet-gateway/remote/src/dapp-api/controller.ts b/wallet-gateway/remote/src/dapp-api/controller.ts index ba314cda4..488d081d6 100644 --- a/wallet-gateway/remote/src/dapp-api/controller.ts +++ b/wallet-gateway/remote/src/dapp-api/controller.ts @@ -25,6 +25,8 @@ import { networkStatus } from '../utils.js' export const dappController = ( kernelInfo: KernelInfoConfig, + dappUrl: string, + userUrl: string, store: Store, notificationService: NotificationService, _logger: Logger, @@ -41,7 +43,7 @@ export const dappController = ( isConnected: false, isNetworkConnected: false, networkReason: 'Unauthenticated', - userUrl: `${kernelInfo.userUrl}/login/`, + userUrl: `${userUrl}/login/`, }, } } @@ -61,7 +63,7 @@ export const dappController = ( isConnected: true, isNetworkConnected: status.isConnected, networkReason: status.reason ? status.reason : 'OK', - userUrl: `${kernelInfo.userUrl}/login/`, + userUrl: `${userUrl}/login/`, }, } }, @@ -76,7 +78,7 @@ export const dappController = ( isConnected: false, isNetworkConnected: false, networkReason: 'Unauthenticated', - userUrl: `${kernelInfo.userUrl}/login/`, + userUrl: `${userUrl}/login/`, } as StatusEvent) } @@ -164,7 +166,7 @@ export const dappController = ( }) return { - userUrl: `${kernelInfo.userUrl}/approve/index.html?commandId=${commandId}`, + userUrl: `${userUrl}/approve/index.html?commandId=${commandId}`, } }, prepareReturn: async (params: PrepareReturnParams) => { diff --git a/wallet-gateway/remote/src/dapp-api/server.test.ts b/wallet-gateway/remote/src/dapp-api/server.test.ts index 4e9bb4754..24a679b19 100644 --- a/wallet-gateway/remote/src/dapp-api/server.test.ts +++ b/wallet-gateway/remote/src/dapp-api/server.test.ts @@ -9,7 +9,7 @@ import express from 'express' import { dapp } from './server.js' import { StoreInternal } from '@canton-network/core-wallet-store-inmemory' import { AuthService } from '@canton-network/core-wallet-auth' -import { ConfigUtils } from '../config/ConfigUtils.js' +import { ConfigUtils, deriveKernelUrls } from '../config/ConfigUtils.js' import { Notifier } from '../notification/NotificationService.js' import { pino } from 'pino' import { sink } from 'pino-test' @@ -41,6 +41,7 @@ test('call connect rpc', async () => { app.use(cors()) app.use(express.json()) const server = createServer(app) + const { dappUrl, userUrl } = deriveKernelUrls(config.server)! const response = await request( dapp( '/api/v0/dapp', @@ -48,6 +49,8 @@ test('call connect rpc', async () => { pino(sink()), server, config.kernel, + dappUrl!, + userUrl!, config.server, notificationService, authService, @@ -68,8 +71,6 @@ test('call connect rpc', async () => { kernel: { id: 'remote-da', clientType: 'remote', - url: 'http://localhost:3030/api/v0/dapp', - userUrl: 'http://localhost:3030', }, isConnected: false, isNetworkConnected: false, diff --git a/wallet-gateway/remote/src/dapp-api/server.ts b/wallet-gateway/remote/src/dapp-api/server.ts index 1a50de5b1..740d8b4f0 100644 --- a/wallet-gateway/remote/src/dapp-api/server.ts +++ b/wallet-gateway/remote/src/dapp-api/server.ts @@ -23,6 +23,8 @@ export const dapp = ( logger: Logger, server: Server, kernelInfo: KernelInfo, + dappUrl: string, + userUrl: string, serverConfig: ServerConfig, notificationService: NotificationService, authService: AuthService, @@ -37,6 +39,8 @@ export const dapp = ( jsonRpcHandler({ controller: dappController( kernelInfo, + dappUrl, + userUrl, store.withAuthContext(req.authContext), notificationService, logger, diff --git a/wallet-gateway/remote/src/example-config.ts b/wallet-gateway/remote/src/example-config.ts index 5a8c692ca..e6c884ad1 100644 --- a/wallet-gateway/remote/src/example-config.ts +++ b/wallet-gateway/remote/src/example-config.ts @@ -7,10 +7,14 @@ export default { kernel: { id: 'remote-da', clientType: 'remote', - url: 'http://localhost:3030/api/v0/dapp', - userUrl: 'http://localhost:3030', + // URLs will be derived from server config }, server: { + host: 'localhost', + port: 3030, + tls: false, + dappPath: '/api/v0/dapp', + userPath: '/api/v0/user', allowedOrigins: ['http://localhost:8080'], }, signingStore: { diff --git a/wallet-gateway/remote/src/init.ts b/wallet-gateway/remote/src/init.ts index 2e9d999ff..dfc86fc32 100644 --- a/wallet-gateway/remote/src/init.ts +++ b/wallet-gateway/remote/src/init.ts @@ -30,6 +30,7 @@ import { CliOptions } from './index.js' import { jwtAuth } from './middleware/jwtAuth.js' import { rpcRateLimit } from './middleware/rateLimit.js' import { Config } from './config/Config.js' +import { deriveKernelUrls } from './config/ConfigUtils.js' import { existsSync, readFileSync } from 'fs' import path from 'path' @@ -133,12 +134,17 @@ async function initializeSigningDatabase( } export async function initialize(opts: CliOptions, logger: Logger) { - const port = opts.port ? Number(opts.port) : 3030 + const config = ConfigUtils.loadConfigFile(opts.config) + + // Use CLI port override, config port, or default + const port = opts.port ? Number(opts.port) : (config.server.port ?? 3030) + const host = config.server.host ?? 'localhost' + const protocol = (config.server.tls ?? false) ? 'https' : 'http' const app = express() - const server = app.listen(port, () => { + const server = app.listen(port, host, () => { logger.info( - `Remote Wallet Gateway starting on http://localhost:${port}` + `Remote Wallet Gateway starting on ${protocol}://${host}:${port}` ) }) @@ -153,8 +159,6 @@ export async function initialize(opts: CliOptions, logger: Logger) { const notificationService = new NotificationService(logger) - const config = ConfigUtils.loadConfigFile(opts.config) - const store = await initializeDatabase(config, logger) const signingStore = await initializeSigningDatabase(config, logger) const authService = jwtAuthService(store, logger) @@ -192,13 +196,24 @@ export async function initialize(opts: CliOptions, logger: Logger) { app.use('/api/*splat', rpcRateLimit) app.use('/api/*splat', jwtAuth(authService, logger)) + const { dappUrl, userUrl } = deriveKernelUrls(config.server) + if (!dappUrl || !userUrl) { + throw new Error( + 'Server config must provide host, port, and tls to derive URLs' + ) + } + + const kernelInfo = config.kernel + // register dapp API handlers dapp( - '/api/v0/dapp', + config.server.dappPath, app, logger, server, - config.kernel, + kernelInfo, + dappUrl, + userUrl, config.server, notificationService, authService, @@ -207,17 +222,18 @@ export async function initialize(opts: CliOptions, logger: Logger) { // register user API handlers user( - '/api/v0/user', + config.server.userPath, app, logger, - config.kernel, + kernelInfo, + userUrl, notificationService, drivers, store ) // register web handler - web(app, server) + web(app, server, config.server.userPath) isReady = true logger.info('Wallet Gateway initialization complete') diff --git a/wallet-gateway/remote/src/user-api/controller.ts b/wallet-gateway/remote/src/user-api/controller.ts index 3f988444a..ca81fd2a1 100644 --- a/wallet-gateway/remote/src/user-api/controller.ts +++ b/wallet-gateway/remote/src/user-api/controller.ts @@ -57,6 +57,7 @@ type AvailableSigningDrivers = Partial< export const userController = ( kernelInfo: KernelInfo, + userUrl: string, store: Store, notificationService: NotificationService, authContext: AuthContext | undefined, @@ -602,7 +603,7 @@ export const userController = ( isConnected: false, isNetworkConnected: false, networkReason: 'Unauthenticated', - userUrl: `${kernelInfo.userUrl}/login/`, + userUrl: `${userUrl}/login/`, } as StatusEvent) return null }, diff --git a/wallet-gateway/remote/src/user-api/server.test.ts b/wallet-gateway/remote/src/user-api/server.test.ts index 22c5ec230..6e717f187 100644 --- a/wallet-gateway/remote/src/user-api/server.test.ts +++ b/wallet-gateway/remote/src/user-api/server.test.ts @@ -9,7 +9,7 @@ import request from 'supertest' import { user } from './server.js' import { StoreInternal } from '@canton-network/core-wallet-store-inmemory' import { Network } from '@canton-network/core-wallet-store' -import { ConfigUtils } from '../config/ConfigUtils.js' +import { ConfigUtils, deriveKernelUrls } from '../config/ConfigUtils.js' import { Notifier } from '../notification/NotificationService.js' import { pino } from 'pino' import { sink } from 'pino-test' @@ -33,12 +33,14 @@ test('call listNetworks rpc', async () => { app.use(cors()) app.use(express.json()) + const { userUrl } = deriveKernelUrls(config.server)! const response = await request( user( '/api/v0/user', app, pino(sink()), config.kernel, + userUrl!, notificationService, drivers, store diff --git a/wallet-gateway/remote/src/user-api/server.ts b/wallet-gateway/remote/src/user-api/server.ts index 1abf17cf7..0bb1b9b6d 100644 --- a/wallet-gateway/remote/src/user-api/server.ts +++ b/wallet-gateway/remote/src/user-api/server.ts @@ -20,6 +20,7 @@ export const user = ( app: express.Express, logger: Logger, kernelInfo: KernelInfo, + userUrl: string, notificationService: NotificationService, drivers: Partial>, store: Store & AuthAware @@ -28,6 +29,7 @@ export const user = ( jsonRpcHandler({ controller: userController( kernelInfo, + userUrl, store.withAuthContext(req.authContext), notificationService, req.authContext, diff --git a/wallet-gateway/remote/src/web/frontend/approve/index.ts b/wallet-gateway/remote/src/web/frontend/approve/index.ts index e66cc1fb7..5af9b44d7 100644 --- a/wallet-gateway/remote/src/web/frontend/approve/index.ts +++ b/wallet-gateway/remote/src/web/frontend/approve/index.ts @@ -164,7 +164,9 @@ export class ApproveUi extends LitElement { } private async updateState() { - const userClient = createUserClient(stateManager.accessToken.get()) + const userClient = await createUserClient( + stateManager.accessToken.get() + ) userClient .request('getTransaction', { commandId: this.commandId }) .then((result) => { @@ -201,7 +203,9 @@ export class ApproveUi extends LitElement { preparedTransaction: this.tx, } - const userClient = createUserClient(stateManager.accessToken.get()) + const userClient = await createUserClient( + stateManager.accessToken.get() + ) const { signature, signedBy } = await userClient.request( 'sign', signRequest diff --git a/wallet-gateway/remote/src/web/frontend/index.ts b/wallet-gateway/remote/src/web/frontend/index.ts index 51b0dd7b9..8608ac463 100644 --- a/wallet-gateway/remote/src/web/frontend/index.ts +++ b/wallet-gateway/remote/src/web/frontend/index.ts @@ -21,7 +21,9 @@ export class UserApp extends LitElement { private async handleLogout() { localStorage.clear() - const userClient = createUserClient(stateManager.accessToken.get()) + const userClient = await createUserClient( + stateManager.accessToken.get() + ) await userClient.request('removeSession') if ( @@ -106,7 +108,7 @@ export const authenticate = async ( accessToken: string, networkId: string ): Promise => { - const authenticatedUserClient = createUserClient(accessToken) + const authenticatedUserClient = await createUserClient(accessToken) await authenticatedUserClient.request('addSession', { networkId, }) diff --git a/wallet-gateway/remote/src/web/frontend/login/login.ts b/wallet-gateway/remote/src/web/frontend/login/login.ts index 0da956178..e5b4025d1 100644 --- a/wallet-gateway/remote/src/web/frontend/login/login.ts +++ b/wallet-gateway/remote/src/web/frontend/login/login.ts @@ -177,13 +177,17 @@ export class LoginUI extends LitElement { } private async loadNetworks() { - const userClient = createUserClient(stateManager.accessToken.get()) + const userClient = await createUserClient( + stateManager.accessToken.get() + ) const response = await userClient.request('listNetworks') return response.networks } private async loadIdps() { - const userClient = createUserClient(stateManager.accessToken.get()) + const userClient = await createUserClient( + stateManager.accessToken.get() + ) const response = await userClient.request('listIdps') return response.idps } @@ -289,7 +293,7 @@ export class LoginUI extends LitElement { stateManager.accessToken.set(access_token) - const authenticatedUserClient = createUserClient(access_token) + const authenticatedUserClient = await createUserClient(access_token) await authenticatedUserClient.request('addSession', { networkId: stateManager.networkId.get() || '', diff --git a/wallet-gateway/remote/src/web/frontend/rpc-client.ts b/wallet-gateway/remote/src/web/frontend/rpc-client.ts index f74e85da1..a0072bc53 100644 --- a/wallet-gateway/remote/src/web/frontend/rpc-client.ts +++ b/wallet-gateway/remote/src/web/frontend/rpc-client.ts @@ -4,11 +4,28 @@ import { HttpTransport } from '@canton-network/core-types' import UserApiClient from '@canton-network/core-wallet-user-rpc-client' -export const createUserClient = (token?: string) => { - return new UserApiClient( - new HttpTransport( - new URL(`${window.location.origin}/api/v0/user`), - token - ) - ) +let userPathPromise: Promise | null = null + +const getUserPath = async (): Promise => { + if (!userPathPromise) { + userPathPromise = fetch('/.well-known/wallet-gateway-config') + .then((response) => response.json()) + .then((config) => config.userPath || '/api/v0/user') + .catch((error) => { + console.warn( + 'Failed to fetch userPath from config, using default', + error + ) + return '/api/v0/user' // Fallback to default + }) + } + return userPathPromise +} + +export const createUserClient = async ( + token?: string +): Promise => { + const userPath = await getUserPath() + const url = new URL(`${window.location.origin}${userPath}`) + return new UserApiClient(new HttpTransport(url, token)) } diff --git a/wallet-gateway/remote/src/web/frontend/settings/index.ts b/wallet-gateway/remote/src/web/frontend/settings/index.ts index 2059a8bdd..b87c88428 100644 --- a/wallet-gateway/remote/src/web/frontend/settings/index.ts +++ b/wallet-gateway/remote/src/web/frontend/settings/index.ts @@ -19,6 +19,7 @@ import { Idp, Auth as ApiAuth, } from '@canton-network/core-wallet-user-rpc-client' +import UserApiClient from '@canton-network/core-wallet-user-rpc-client' import '../index' import '/index.css' @@ -43,28 +44,36 @@ export class UserUiSettings extends LitElement { @state() accessor networks: Network[] = [] @state() accessor sessions: Session[] = [] @state() accessor idps: Idp[] = [] + @state() accessor client: UserApiClient | null = null - connectedCallback(): void { + async connectedCallback(): Promise { super.connectedCallback() + this.client = await createUserClient(stateManager.accessToken.get()) this.listNetworks() this.listSessions() this.listIdps() } private async listNetworks() { - const userClient = createUserClient(stateManager.accessToken.get()) + const userClient = await createUserClient( + stateManager.accessToken.get() + ) const response = await userClient.request('listNetworks') this.networks = response.networks } private async listSessions() { - const userClient = createUserClient(stateManager.accessToken.get()) + const userClient = await createUserClient( + stateManager.accessToken.get() + ) const response = await userClient.request('listSessions') this.sessions = response.sessions } private async listIdps() { - const userClient = createUserClient(stateManager.accessToken.get()) + const userClient = await createUserClient( + stateManager.accessToken.get() + ) const response = await userClient.request('listIdps') this.idps = response.idps } @@ -108,7 +117,9 @@ export class UserUiSettings extends LitElement { } try { - const userClient = createUserClient(stateManager.accessToken.get()) + const userClient = await createUserClient( + stateManager.accessToken.get() + ) await userClient.request('addNetwork', { network }) await this.listNetworks() } catch (e) { @@ -122,7 +133,9 @@ export class UserUiSettings extends LitElement { const params: RemoveNetworkParams = { networkName: e.network.id, } - const userClient = createUserClient(stateManager.accessToken.get()) + const userClient = await createUserClient( + stateManager.accessToken.get() + ) await userClient.request('removeNetwork', params) await this.listNetworks() } catch (e) { @@ -133,7 +146,9 @@ export class UserUiSettings extends LitElement { private handleIdpSubmit = async (ev: IdpAddEvent) => { console.log(ev) try { - const userClient = createUserClient(stateManager.accessToken.get()) + const userClient = await createUserClient( + stateManager.accessToken.get() + ) await userClient.request('addIdp', { idp: ev.idp }) await this.listIdps() } catch (e) { @@ -144,7 +159,9 @@ export class UserUiSettings extends LitElement { private handleIdpDelete = async (ev: IdpCardDeleteEvent) => { console.log(ev) try { - const userClient = createUserClient(stateManager.accessToken.get()) + const userClient = await createUserClient( + stateManager.accessToken.get() + ) await userClient.request('removeIdp', { identityProviderId: ev.idp.id, }) @@ -155,7 +172,10 @@ export class UserUiSettings extends LitElement { } protected render() { - const client = createUserClient(stateManager.accessToken.get()) + if (!this.client) { + return html`` + } + const client = this.client return html` diff --git a/wallet-gateway/remote/src/web/frontend/wallets/index.ts b/wallet-gateway/remote/src/web/frontend/wallets/index.ts index fa5bb2aad..3dc4f340b 100644 --- a/wallet-gateway/remote/src/web/frontend/wallets/index.ts +++ b/wallet-gateway/remote/src/web/frontend/wallets/index.ts @@ -336,21 +336,27 @@ export class UserUiWallets extends LitElement { } private async updateNetworks() { - const userClient = createUserClient(stateManager.accessToken.get()) + const userClient = await createUserClient( + stateManager.accessToken.get() + ) userClient.request('listNetworks').then(({ networks }) => { this.networks = networks.map((network) => network.id) }) } private async updateWallets() { - const userClient = createUserClient(stateManager.accessToken.get()) + const userClient = await createUserClient( + stateManager.accessToken.get() + ) userClient.request('listWallets', []).then((wallets) => { this.wallets = wallets || [] }) } private async _setPrimary(wallet: Wallet) { - const userClient = createUserClient(stateManager.accessToken.get()) + const userClient = await createUserClient( + stateManager.accessToken.get() + ) await userClient.request('setPrimaryWallet', { partyId: wallet.partyId, }) @@ -378,7 +384,9 @@ export class UserUiWallets extends LitElement { signingProviderId, } - const userClient = createUserClient(stateManager.accessToken.get()) + const userClient = await createUserClient( + stateManager.accessToken.get() + ) await userClient.request('createWallet', body) } catch (e) { handleErrorToast(e) @@ -395,7 +403,9 @@ export class UserUiWallets extends LitElement { private async _allocateParty(wallet: Wallet) { this.loading = true try { - const userClient = createUserClient(stateManager.accessToken.get()) + const userClient = await createUserClient( + stateManager.accessToken.get() + ) await userClient.request('createWallet', { primary: wallet.primary, partyHint: wallet.hint, diff --git a/wallet-gateway/remote/src/web/server.ts b/wallet-gateway/remote/src/web/server.ts index 7fb7f6bbb..86adaa3b6 100644 --- a/wallet-gateway/remote/src/web/server.ts +++ b/wallet-gateway/remote/src/web/server.ts @@ -7,7 +7,11 @@ import path, { dirname } from 'path' import { fileURLToPath } from 'url' import ViteExpress from 'vite-express' -export const web = (app: express.Express, server: Server) => { +export const web = (app: express.Express, server: Server, userPath: string) => { + // Expose userPath via well-known configuration endpoint + app.get('/.well-known/wallet-gateway-config', (_req, res) => { + res.json({ userPath }) + }) if (process.env.NODE_ENV === 'development') { // Enable live reloading and Vite dev server for frontend in development ViteExpress.bind(app, server) diff --git a/wallet-gateway/test/config.json b/wallet-gateway/test/config.json index ab9515a1c..711d873ec 100644 --- a/wallet-gateway/test/config.json +++ b/wallet-gateway/test/config.json @@ -1,11 +1,14 @@ { "kernel": { "id": "remote-da", - "clientType": "remote", - "url": "http://localhost:3030/api/v0/dapp", - "userUrl": "http://localhost:3030" + "clientType": "remote" }, "server": { + "host": "localhost", + "port": 3030, + "tls": false, + "dappPath": "/api/v0/dapp", + "userPath": "/api/v0/user", "allowedOrigins": ["http://localhost:8080"] }, "store": { From b31c408ccd98e97c1922706b9d916be4ab495faa Mon Sep 17 00:00:00 2001 From: Pawel Stepien Date: Fri, 28 Nov 2025 14:33:41 +0100 Subject: [PATCH 5/8] make server config required by zod Signed-off-by: Pawel Stepien --- wallet-gateway/remote/src/config/Config.ts | 6 +++--- wallet-gateway/remote/src/config/ConfigUtils.ts | 16 ++++------------ .../remote/src/dapp-api/server.test.ts | 2 +- wallet-gateway/remote/src/init.ts | 5 ----- .../remote/src/user-api/server.test.ts | 2 +- 5 files changed, 9 insertions(+), 22 deletions(-) diff --git a/wallet-gateway/remote/src/config/Config.ts b/wallet-gateway/remote/src/config/Config.ts index c22d6a7bb..fb121a38f 100644 --- a/wallet-gateway/remote/src/config/Config.ts +++ b/wallet-gateway/remote/src/config/Config.ts @@ -16,9 +16,9 @@ export const kernelInfoSchema = z.object({ }) export const serverConfigSchema = z.object({ - host: z.string().optional(), - port: z.number().optional(), - tls: z.boolean().optional(), + host: z.string(), + port: z.number(), + tls: z.boolean(), dappPath: z.string().default('/api/v0/dapp'), userPath: z.string().default('/api/v0/user'), allowedOrigins: z.union([z.literal('*'), z.array(z.string())]).default('*'), diff --git a/wallet-gateway/remote/src/config/ConfigUtils.ts b/wallet-gateway/remote/src/config/ConfigUtils.ts index b9517233e..9e97b6ba2 100644 --- a/wallet-gateway/remote/src/config/ConfigUtils.ts +++ b/wallet-gateway/remote/src/config/ConfigUtils.ts @@ -105,19 +105,11 @@ function validateNetworkAuthMethods( } } -export const deriveKernelUrls = (serverConfig: ServerConfig) => { - // Only derive if all required properties are present - if ( - !serverConfig.host || - serverConfig.port === undefined || - serverConfig.tls === undefined - ) { - return { dappUrl: undefined, userUrl: undefined } - } - +export const deriveKernelUrls = ( + serverConfig: ServerConfig +): { dappUrl: string; userUrl: string } => { const protocol = serverConfig.tls ? 'https' : 'http' - const dappPath = serverConfig.dappPath ?? '/api/v0/dapp' - const dappUrl = `${protocol}://${serverConfig.host}:${serverConfig.port}${dappPath}` + const dappUrl = `${protocol}://${serverConfig.host}:${serverConfig.port}${serverConfig.dappPath}` // userUrl is the base URL for the web frontend (no path) const userUrl = `${protocol}://${serverConfig.host}:${serverConfig.port}` diff --git a/wallet-gateway/remote/src/dapp-api/server.test.ts b/wallet-gateway/remote/src/dapp-api/server.test.ts index 24a679b19..988315967 100644 --- a/wallet-gateway/remote/src/dapp-api/server.test.ts +++ b/wallet-gateway/remote/src/dapp-api/server.test.ts @@ -41,7 +41,7 @@ test('call connect rpc', async () => { app.use(cors()) app.use(express.json()) const server = createServer(app) - const { dappUrl, userUrl } = deriveKernelUrls(config.server)! + const { dappUrl, userUrl } = deriveKernelUrls(config.server) const response = await request( dapp( '/api/v0/dapp', diff --git a/wallet-gateway/remote/src/init.ts b/wallet-gateway/remote/src/init.ts index dfc86fc32..7a7d0dd62 100644 --- a/wallet-gateway/remote/src/init.ts +++ b/wallet-gateway/remote/src/init.ts @@ -197,11 +197,6 @@ export async function initialize(opts: CliOptions, logger: Logger) { app.use('/api/*splat', jwtAuth(authService, logger)) const { dappUrl, userUrl } = deriveKernelUrls(config.server) - if (!dappUrl || !userUrl) { - throw new Error( - 'Server config must provide host, port, and tls to derive URLs' - ) - } const kernelInfo = config.kernel diff --git a/wallet-gateway/remote/src/user-api/server.test.ts b/wallet-gateway/remote/src/user-api/server.test.ts index 6e717f187..72ebd6459 100644 --- a/wallet-gateway/remote/src/user-api/server.test.ts +++ b/wallet-gateway/remote/src/user-api/server.test.ts @@ -33,7 +33,7 @@ test('call listNetworks rpc', async () => { app.use(cors()) app.use(express.json()) - const { userUrl } = deriveKernelUrls(config.server)! + const { userUrl } = deriveKernelUrls(config.server) const response = await request( user( '/api/v0/user', From 5d50f3a92e5955fdc0ad51cf40ff69515d93fcb0 Mon Sep 17 00:00:00 2001 From: Pawel Stepien Date: Fri, 28 Nov 2025 14:37:40 +0100 Subject: [PATCH 6/8] merge fix Signed-off-by: Pawel Stepien --- wallet-gateway/remote/src/web/frontend/transactions/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wallet-gateway/remote/src/web/frontend/transactions/index.ts b/wallet-gateway/remote/src/web/frontend/transactions/index.ts index c02683f69..026a0372d 100644 --- a/wallet-gateway/remote/src/web/frontend/transactions/index.ts +++ b/wallet-gateway/remote/src/web/frontend/transactions/index.ts @@ -185,7 +185,9 @@ export class UserUiTransactions extends LitElement { } private async updateTransactions() { - const userClient = createUserClient(stateManager.accessToken.get()) + const userClient = await createUserClient( + stateManager.accessToken.get() + ) userClient.request('listTransactions').then((result) => { this.transactions = result.transactions || [] for (const tx of this.transactions) { From 23236b41e23d70705c14d955ce62d8025ab919f7 Mon Sep 17 00:00:00 2001 From: Pawel Stepien Date: Fri, 28 Nov 2025 15:05:35 +0100 Subject: [PATCH 7/8] cleanup Signed-off-by: Pawel Stepien --- wallet-gateway/remote/src/dapp-api/server.test.ts | 4 ++-- wallet-gateway/remote/src/example-config.ts | 1 - wallet-gateway/remote/src/user-api/server.test.ts | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/wallet-gateway/remote/src/dapp-api/server.test.ts b/wallet-gateway/remote/src/dapp-api/server.test.ts index 988315967..bd8ecf24a 100644 --- a/wallet-gateway/remote/src/dapp-api/server.test.ts +++ b/wallet-gateway/remote/src/dapp-api/server.test.ts @@ -49,8 +49,8 @@ test('call connect rpc', async () => { pino(sink()), server, config.kernel, - dappUrl!, - userUrl!, + dappUrl, + userUrl, config.server, notificationService, authService, diff --git a/wallet-gateway/remote/src/example-config.ts b/wallet-gateway/remote/src/example-config.ts index e6c884ad1..0815576e9 100644 --- a/wallet-gateway/remote/src/example-config.ts +++ b/wallet-gateway/remote/src/example-config.ts @@ -7,7 +7,6 @@ export default { kernel: { id: 'remote-da', clientType: 'remote', - // URLs will be derived from server config }, server: { host: 'localhost', diff --git a/wallet-gateway/remote/src/user-api/server.test.ts b/wallet-gateway/remote/src/user-api/server.test.ts index 72ebd6459..f78dcd36f 100644 --- a/wallet-gateway/remote/src/user-api/server.test.ts +++ b/wallet-gateway/remote/src/user-api/server.test.ts @@ -40,7 +40,7 @@ test('call listNetworks rpc', async () => { app, pino(sink()), config.kernel, - userUrl!, + userUrl, notificationService, drivers, store From 436baf8a3be6082b23e8e9f78978a2d15cde7bc0 Mon Sep 17 00:00:00 2001 From: Pawel Stepien Date: Fri, 28 Nov 2025 15:19:02 +0100 Subject: [PATCH 8/8] fix port override in CLI Signed-off-by: Pawel Stepien --- wallet-gateway/remote/src/init.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/wallet-gateway/remote/src/init.ts b/wallet-gateway/remote/src/init.ts index 7a7d0dd62..c91bfb2d4 100644 --- a/wallet-gateway/remote/src/init.ts +++ b/wallet-gateway/remote/src/init.ts @@ -136,10 +136,10 @@ async function initializeSigningDatabase( export async function initialize(opts: CliOptions, logger: Logger) { const config = ConfigUtils.loadConfigFile(opts.config) - // Use CLI port override, config port, or default - const port = opts.port ? Number(opts.port) : (config.server.port ?? 3030) - const host = config.server.host ?? 'localhost' - const protocol = (config.server.tls ?? false) ? 'https' : 'http' + // Use CLI port override or config port + const port = opts.port ? Number(opts.port) : config.server.port + const host = config.server.host + const protocol = config.server.tls ? 'https' : 'http' const app = express() const server = app.listen(port, host, () => { @@ -196,7 +196,12 @@ export async function initialize(opts: CliOptions, logger: Logger) { app.use('/api/*splat', rpcRateLimit) app.use('/api/*splat', jwtAuth(authService, logger)) - const { dappUrl, userUrl } = deriveKernelUrls(config.server) + // Override config port with CLI parameter port if provided, then derive URLs + const serverConfigWithOverride = { + ...config.server, + port, // Use the actual port we're listening on + } + const { dappUrl, userUrl } = deriveKernelUrls(serverConfigWithOverride) const kernelInfo = config.kernel