Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6,833 changes: 0 additions & 6,833 deletions api-specs/ledger-api/3.4.0/openapi.yaml

This file was deleted.

7,318 changes: 0 additions & 7,318 deletions api-specs/ledger-api/3.4.7/openapi.yaml

This file was deleted.

36 changes: 33 additions & 3 deletions api-specs/openrpc-user-api.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
"title": "networks",
"type": "array",
"items": {
"$ref": "#/components/schemas/Network"
"$ref": "#/components/schemas/VerifiedNetwork"
}
}
},
Expand Down Expand Up @@ -553,6 +553,28 @@
"type": "string",
"description": "The unique identifier of the command associated with the transaction."
},
"VerifiedNetwork": {
"title": "VerifiedNetwork",
"description": "Structure representing the Networks with connectivity verification",
"allOf": [
{
"$ref": "#/components/schemas/Network"
},
{
"title": "VerifiedNetworkProperties",
"description": "holder for boolean verified property",
"type": "object",
"properties": {
"verified": {
"title": "verified",
"description": "true if we could succesfully call the ledger version endpoint",
"type": "boolean"
}
},
"required": ["verified"]
}
]
},
Comment on lines +556 to +577
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does this need a separate schema, rather than adding an optional "verified" to Network itself?

"Network": {
"title": "Network",
"type": "object",
Expand Down Expand Up @@ -591,8 +613,16 @@
},
"ledgerApi": {
"title": "ledgerApi",
"type": "string",
"description": "Ledger api url"
"type": "object",
"description": "Ledger api url",
"properties": {
"baseUrl": {
"title": "baseUrl",
"type": "string",
"description": "Ledger api url"
}
},
"required": ["baseUrl"]
}
},
"required": [
Expand All @@ -609,7 +639,7 @@
"title": "Idp",
"description": "Structure representing the Identity Providers",
"type": "object",
"properties": {

Check failure on line 642 in api-specs/openrpc-user-api.json

View workflow job for this annotation

GitHub Actions / test-static

Property 'components.schemas.Idp.properties' is missing a title or $ref.

Check failure on line 642 in api-specs/openrpc-user-api.json

View workflow job for this annotation

GitHub Actions / test-static

Property 'components.schemas.Idp.properties' is missing a title or $ref.
"id": {
"title": "id",
"type": "string",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,8 @@
"typedoc": "^0.28.14",
"typescript": "^5.9.3"
},
"repository": "github:hyperledger-labs/splice-wallet-kernel"
"repository": {
"type": "git",
"url": "git+https://github.com/hyperledger-labs/splice-wallet-kernel.git"
}
}
2 changes: 1 addition & 1 deletion core/wallet-store-sql/src/store-sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ export class StoreSql implements BaseStore, AuthAware<StoreSql> {
await this.db.transaction().execute(async (trx) => {
// we do not set a userId for now and leave all networks global when updating
const networkEntry = fromNetwork(network, undefined)
this.logger.info(networkEntry, 'Updating network table')
this.logger.debug(networkEntry, 'Updating network table')
await trx
.updateTable('networks')
.set(networkEntry)
Expand Down
5 changes: 5 additions & 0 deletions core/wallet-store/src/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export const storeConfigSchema = z.object({
networks: z.array(networkSchema),
})

export const verifiedNetworkSchema = networkSchema.extend({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I don't think this has anything to do with the wallet-store. Can we define it directly in the wallet-gateway?

verified: z.boolean(),
})

export type StoreConfig = z.infer<typeof storeConfigSchema>
export type Network = z.infer<typeof networkSchema>
export type VerifiedNetwork = z.infer<typeof verifiedNetworkSchema>
export type LedgerApi = z.infer<typeof ledgerApiSchema>
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const mockSessions: Sessions = [
'wallet::1220e7b23ea52eb5c672fb0b1cdbc916922ffed3dd7676c223a605664315e2d43edd',
identityProviderId: 'idp-mock-oauth',
description: 'Mock OAuth IDP',
ledgerApi: 'http://127.0.0.1:5003',
ledgerApi: { baseUrl: 'http://127.0.0.1:5003' },
auth: {
method: 'authorization_code',
audience: 'https://audience',
Expand Down
34 changes: 32 additions & 2 deletions core/wallet-user-rpc-client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,16 @@ export interface Auth {
* Ledger api url
*
*/
export type LedgerApi = string
export type BaseUrl = string
/**
*
* Ledger api url
*
*/
export interface LedgerApi {
baseUrl: BaseUrl
[k: string]: any
}
/**
*
* Structure representing the Networks
Expand Down Expand Up @@ -204,7 +213,28 @@ export type PreparedTransactionHash = string
export type CommandId = string
export type Signature = string
export type SignedBy = string
export type Networks = Network[]
/**
*
* true if we could succesfully call the ledger version endpoint
*
*/
export type Verified = boolean
/**
*
* holder for boolean verified property
*
*/
export interface VerifiedNetworkProperties {
verified: Verified
[k: string]: any
}
/**
*
* Structure representing the Networks with connectivity verification
*
*/
export type VerifiedNetwork = Network & VerifiedNetworkProperties
export type Networks = VerifiedNetwork[]
export type Idps = Idp[]
/**
*
Expand Down
36 changes: 33 additions & 3 deletions core/wallet-user-rpc-client/src/openrpc.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
"title": "networks",
"type": "array",
"items": {
"$ref": "#/components/schemas/Network"
"$ref": "#/components/schemas/VerifiedNetwork"
}
}
},
Expand Down Expand Up @@ -553,6 +553,28 @@
"type": "string",
"description": "The unique identifier of the command associated with the transaction."
},
"VerifiedNetwork": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The term Verified is a bit ambiguous since we use it to indicate a "verified" wallet in the SDK.
What this really indicates is the "availability", no?

Copy link
Contributor

@alexmatson-da alexmatson-da Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hahaha, I just had the same comment. Didn't think about availability though, I like that the most!

EDIT: or plain available

"title": "VerifiedNetwork",
"description": "Structure representing the Networks with connectivity verification",
"allOf": [
{
"$ref": "#/components/schemas/Network"
},
{
"title": "VerifiedNetworkProperties",
"description": "holder for boolean verified property",
"type": "object",
"properties": {
"verified": {
"title": "verified",
"description": "true if we could succesfully call the ledger version endpoint",
"type": "boolean"
}
},
"required": ["verified"]
}
]
},
"Network": {
"title": "Network",
"type": "object",
Expand Down Expand Up @@ -591,8 +613,16 @@
},
"ledgerApi": {
"title": "ledgerApi",
"type": "string",
"description": "Ledger api url"
"type": "object",
"description": "Ledger api url",
"properties": {
"baseUrl": {
"title": "baseUrl",
"type": "string",
"description": "Ledger api url"
}
},
"required": ["baseUrl"]
}
},
"required": [
Expand Down
4 changes: 3 additions & 1 deletion wallet-gateway/remote/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"express-rate-limit": "^8.2.1",
"jose": "^6.1.2",
"lit": "^3.3.1",
"node-cron": "^4.2.1",
"pino": "^10.1.0",
"pino-pretty": "^13.1.2",
"socket.io": "^4.8.1",
Expand All @@ -77,7 +78,8 @@
"supertest": "^7.1.4",
"ts-jest-resolver": "^2.0.1",
"tsx": "^4.20.6",
"typescript": "^5.9.3"
"typescript": "^5.9.3",
"typescript-lru-cache": "^2.0.0"
},
"nx": {
"targets": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (c) 2025 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { LedgerClient } from '@canton-network/core-ledger-client'
import { Logger } from 'pino'
import { NetworkCacheStore } from '../cache/network-cache.js'

export async function startLedgerConnectivityTester(
networkCacheStore: NetworkCacheStore,
logger: Logger
) {
const networks = await networkCacheStore.getStore().listNetworks()

networks.map(async (network) => {
try {
logger.debug(
`Testing connectivity for network ${network.name} (${network.id})...`
)

//connectivity test
const ledgerClient = new LedgerClient({
baseUrl: new URL(network.ledgerApi.baseUrl),
logger: logger,
})
let connectivityCheck = false

try {
const version = await ledgerClient.get('/v2/version')
if (version.version) {
connectivityCheck = true
}
} catch (error) {
logger.debug(
`Failed to connect to ledger at ${network.name}(${network.ledgerApi.baseUrl}): ${JSON.stringify(error)}`
)
}

networkCacheStore.getCache().set(network.id, {
...network,
verified: connectivityCheck,
})
logger.info(
connectivityCheck
? `Network ${network.name} (${network.id}) verified successfully.`
: `Network ${network.name} (${network.id}) verification failed.`
)
} catch (error) {
logger.error(
`Failed to verify network ${network.name} (${network.id}): ${error}`
)
}
})
}
75 changes: 75 additions & 0 deletions wallet-gateway/remote/src/cache/network-cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) 2025 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { LRUCache } from 'typescript-lru-cache'
import {
Store,
Network,
VerifiedNetwork,
} from '@canton-network/core-wallet-store'
import { Logger } from 'pino'

export const DEFAULT_MAX_CACHE_SIZE = 100
export const DEFAULT_ENTRY_EXPIRATION_TIME = 10 * 60 * 1000

const NetworkCache = new LRUCache<string, VerifiedNetwork>({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not simply cache the status for each network NetworkStatusCache = LRU<NetworkId, NetworkStatus>?

With

NetworkStatus = { connected: boolean, reason?: string, lastChecked: Date }

This way, when listing existing networks, nothing needs to change, except for an additional cache lookup to get the status.

maxSize: DEFAULT_MAX_CACHE_SIZE,
entryExpirationTimeInMS: DEFAULT_ENTRY_EXPIRATION_TIME,
})

export class NetworkCacheStore {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the benefit of this. I'd separate concerns and only cache the NetworkStatus (or an alternative name: NetworkAvailability).

constructor(
private readonly store: Store,
private readonly logger: Logger
) {}
Comment on lines +20 to +24
Copy link
Contributor

@alexmatson-da alexmatson-da Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need all this? I'm wondering why not decouple the cache from the underlying store?

The network cache can just store { networkId: string, verified: boolean }, and only needs a getter and setter.

In the controller you retrieve the network from the store directly, and merge based on ID to include the verified status

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we wanted to go all out, we might even feel inclined to design a Cache interface analogous to Store. Then down the road we could have an in-memory cache implementation, and then something like Redis implementation for a HA/scalable production setup


getCache() {
return NetworkCache
}

getStore() {
return this.store
}

async getNetwork(networkId: string): Promise<VerifiedNetwork> {
const cachedNetwork = NetworkCache.get(networkId)
if (cachedNetwork) {
return cachedNetwork
}
return await this.store.getNetwork(networkId)
}

getCurrentNetwork(): Promise<VerifiedNetwork> {
return this.store.getCurrentNetwork()
}

listNetworks(): Promise<Array<VerifiedNetwork>> {
if (NetworkCache.size !== 0) {
return Promise.resolve(NetworkCache.values().toArray())
}
return this.store.listNetworks()
}

updateNetwork(network: Network): Promise<void> {
NetworkCache.set(network.id, {
...network,
verified: false,
})

return this.store.updateNetwork(network)
}

addNetwork(network: Network): Promise<void> {
NetworkCache.set(network.id, {
...network,
verified: false,
})

return this.store.addNetwork(network)
}
removeNetwork(networkId: string): Promise<void> {
NetworkCache.delete(networkId)

return this.store.removeNetwork(networkId)
}
}
Loading
Loading