Skip to content
Draft
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
8 changes: 8 additions & 0 deletions api-specs/openrpc-dapp-api.json
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@
"title": "KernelInfo",
"type": "object",
"description": "Represents a Wallet Gateway.",
"additionalProperties": false,
"properties": {
"id": {
"title": "id",
Expand Down Expand Up @@ -202,6 +203,7 @@
"title": "LedgerApiResult",
"type": "object",
"description": "Ledger Api configuration options",
"additionalProperties": false,
"properties": {
"response": {
"title": "response",
Expand All @@ -220,6 +222,7 @@
"title": "JsPrepareSubmissionRequest",
"type": "object",
"description": "Structure representing the request for prepare and execute calls",
"additionalProperties": false,
"properties": {
"commandId": {
"$ref": "api-specs/openrpc-dapp-api.json#/components/schemas/CommandId"
Expand Down Expand Up @@ -274,6 +277,7 @@
"title": "DisclosedContract",
"type": "object",
"description": "Structure representing a disclosed contract for transaction execution",
"additionalProperties": false,
"properties": {
"templateId": {
"title": "templateId",
Expand Down Expand Up @@ -503,6 +507,7 @@
"StatusEvent": {
"title": "StatusEvent",
"type": "object",
"additionalProperties": false,
"properties": {
"kernel": {
"$ref": "api-specs/openrpc-dapp-api.json#/components/schemas/KernelInfo"
Expand All @@ -525,6 +530,7 @@
"network": {
"title": "network",
"type": "object",
"additionalProperties": false,
"description": "Network information, if connected to a network.",
"properties": {
"networkId": {
Expand All @@ -536,6 +542,7 @@
"title": "LedgerApiConfig",
"type": "object",
"description": "Ledger API configuration.",
"additionalProperties": false,
"properties": {
"baseUrl": {
"title": "baseUrl",
Expand All @@ -553,6 +560,7 @@
"title": "session",
"type": "object",
"description": "Session information, if authenticated.",
"additionalProperties": false,
"properties": {
"accessToken": {
"title": "accessToken",
Expand Down
29 changes: 25 additions & 4 deletions core/rpc-generator/src/components/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,21 @@ import { RpcTransport } from '@canton-network/core-rpc-transport'

<%= methodTypings.toString("typescript").replace(/export type AnyOf[A-Za-z0-9]+ =(?:[\\r\\n]|.)*?;/gm, "") %>

type AsyncReturnType<T> = T extends (...args: any[]) => Promise<infer R>
? R
: never

export type RpcMethods = {
<% openrpcDocument.methods.forEach((method) => { %>
<%= method.name %>: {
params: Parameters<<%= _.upperFirst(method.name) %>>
result: AsyncReturnType<<%= _.upperFirst(method.name) %>>
}
<% }); %>
}

export type RpcClientRequest<M extends keyof RpcMethods> = (method: M, params?: RpcMethods[M]['params']) => Promise<RpcMethods[M]['result']>

export class <%= className %> {
public transport: RpcTransport;

Expand All @@ -33,15 +48,21 @@ export class <%= className %> {
* <%= method.summary %>
*/
// tslint:disable-next-line:max-line-length
public async request(method: "<%= method.name %>", ...params: Parameters<<%= _.upperFirst(method.name) %>>): ReturnType<<%= _.upperFirst(method.name) %>>
// public async request(method: "<%= method.name %>", ...params: Parameters<<%= _.upperFirst(method.name) %>>): ReturnType<<%= _.upperFirst(method.name) %>>
<% }); %>
public async request(method: string, params?: RequestPayload['params']): Promise<unknown> {
const response = await this.transport.submit({ method, params });

public async request<M extends keyof RpcMethods>(method: M, params?: RpcMethods[M]['params'][0]): Promise<RpcMethods[M]['result']> {
const submitParams = params ? { method, params } : { method }

const response = await this.transport.submit({
method,
params: submitParams,
})

if ('error' in response) {
throw new Error('RPC error: ' + response.error.code + ' - ' + response.error.message);
} else {
return response.result;
return response.result as RpcMethods[M]['result'];
}
}
}
Expand Down
20 changes: 16 additions & 4 deletions core/splice-provider/src/SpliceProvider.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
// Copyright (c) 2025-2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { RequestPayload } from '@canton-network/core-types'
import SpliceWalletJSONRPCDAppAPI, {
RpcMethods,
} from '@canton-network/core-wallet-dapp-rpc-client'

export type EventListener<T> = (...args: T[]) => void

export interface SpliceProvider {
request<T>(args: RequestPayload): Promise<T>
request<M extends keyof RpcMethods>(
method: M,
params?: RpcMethods[M]['params'][0]
): Promise<RpcMethods[M]['result']>
on<T>(event: string, listener: EventListener<T>): SpliceProvider
emit<T>(event: string, ...args: T[]): boolean
removeListener<T>(
Expand All @@ -17,12 +22,19 @@ export interface SpliceProvider {

export abstract class SpliceProviderBase implements SpliceProvider {
listeners: { [event: string]: EventListener<unknown>[] }
_client: SpliceWalletJSONRPCDAppAPI

constructor() {
constructor(client: SpliceWalletJSONRPCDAppAPI) {
this.listeners = {} // Event listeners
this._client = client
}

abstract request<T>(args: RequestPayload): Promise<T>
public async request(
method: keyof RpcMethods,
params?: RpcMethods[typeof method]['params'][0]
): Promise<RpcMethods[typeof method]['result']> {
return this._client.request(method, params)
}

// Event handling
public on<T>(event: string, listener: EventListener<T>): SpliceProvider {
Expand Down
25 changes: 5 additions & 20 deletions core/splice-provider/src/SpliceProviderHttp.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
// Copyright (c) 2025-2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import {
isSpliceMessageEvent,
RequestPayload,
WalletEvent,
} from '@canton-network/core-types'
import { isSpliceMessageEvent, WalletEvent } from '@canton-network/core-types'
import { HttpTransport } from '@canton-network/core-rpc-transport'
import SpliceWalletJSONRPCDAppAPI from '@canton-network/core-wallet-dapp-rpc-client'
import { SpliceProviderBase } from './SpliceProvider'
Expand All @@ -23,7 +19,6 @@ let connection: GatewaySocket = null

export class SpliceProviderHttp extends SpliceProviderBase {
private sessionToken?: string
private client: SpliceWalletJSONRPCDAppAPI

private createClient(sessionToken?: string): SpliceWalletJSONRPCDAppAPI {
const transport = new HttpTransport(this.url, sessionToken)
Expand Down Expand Up @@ -61,18 +56,17 @@ export class SpliceProviderHttp extends SpliceProviderBase {
}

constructor(
client: SpliceWalletJSONRPCDAppAPI,
private url: URL,
sessionToken?: string
) {
super()
super(client)

if (sessionToken) {
this.sessionToken = sessionToken
this.openSocket(url, sessionToken)
}

this.client = this.createClient(sessionToken)

// Listen for the auth success event sent from the WK UI popup to the SDK running in the parent window.
window.addEventListener('message', async (event) => {
if (!isSpliceMessageEvent(event)) return
Expand All @@ -81,13 +75,13 @@ export class SpliceProviderHttp extends SpliceProviderBase {
event.data.type === WalletEvent.SPLICE_WALLET_IDP_AUTH_SUCCESS
) {
this.sessionToken = event.data.token
this.client = this.createClient(this.sessionToken)
this._client = this.createClient(this.sessionToken)
this.openSocket(this.url, event.data.token)

// We requery the status explicitly here, as it's not guaranteed that the socket will be open & authenticated
// before the `onConnected` event is fired from the `addSession` RPC call. The dappApi.StatusResult and
// dappApi.OnConnectedEvent are mapped manually to avoid dependency.
this.request({ method: 'status' })
this.request('status')
.then((status) => {
this.emit('onConnected', status)
})
Expand All @@ -100,13 +94,4 @@ export class SpliceProviderHttp extends SpliceProviderBase {
}
})
}

public async request<T>({ method, params }: RequestPayload): Promise<T> {
return (await (
this.client.request as (
method: string,
params?: RequestPayload['params']
) => Promise<unknown>
)(method, params)) as T
}
}
21 changes: 5 additions & 16 deletions core/splice-provider/src/SpliceProviderWindow.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,15 @@
// Copyright (c) 2025-2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { RequestPayload } from '@canton-network/core-types'
import { WindowTransport } from '@canton-network/core-rpc-transport'
// import { RequestPayload } from '@canton-network/core-types'
// import { WindowTransport } from '@canton-network/core-rpc-transport'
import SpliceWalletJSONRPCDAppAPI from '@canton-network/core-wallet-dapp-rpc-client'
import { SpliceProviderBase } from './SpliceProvider.js'

export class SpliceProviderWindow extends SpliceProviderBase {
private client: SpliceWalletJSONRPCDAppAPI
// private client: SpliceWalletJSONRPCDAppAPI

constructor() {
super()
const transport = new WindowTransport(window)
this.client = new SpliceWalletJSONRPCDAppAPI(transport)
}

public async request<T>({ method, params }: RequestPayload): Promise<T> {
return (await (
this.client.request as (
method: string,
params?: RequestPayload['params']
) => Promise<unknown>
)(method, params)) as T
constructor(client: SpliceWalletJSONRPCDAppAPI) {
super(client)
}
}
4 changes: 1 addition & 3 deletions core/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ export type PartyId = z.infer<typeof PartyId>
*/
export const RequestPayload = z.object({
method: z.string(),
params: z.optional(
z.union([z.array(z.unknown()), z.record(z.string(), z.unknown())])
),
params: z.optional(z.union([z.array(z.unknown()), z.looseObject({})])),
})
export type RequestPayload = z.infer<typeof RequestPayload>

Expand Down
Loading
Loading