From 8d05ea00fb57cf759e6505233300bcc8f1dfb6e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Pi=C4=85tkowski?= Date: Mon, 18 May 2026 12:11:14 +0200 Subject: [PATCH 1/6] refactor(wallet-sdk): export transaction types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mateusz Piątkowski --- sdk/wallet-sdk/src/wallet/sdk.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/wallet-sdk/src/wallet/sdk.ts b/sdk/wallet-sdk/src/wallet/sdk.ts index 8147ffd77..d0ee4d683 100644 --- a/sdk/wallet-sdk/src/wallet/sdk.ts +++ b/sdk/wallet-sdk/src/wallet/sdk.ts @@ -35,6 +35,7 @@ export type * from './namespace/amulet/index.js' export { type TokenProviderConfig } from '@canton-network/core-wallet-auth' export { LedgerProvider } from '@canton-network/core-provider-ledger' export { type Event } from './namespace/events/index.js' +export type * from './namespace/transactions/types.js' export { signTransactionHash, getPublicKeyFromPrivate, From 8b609a1ddb8ad8a15285d403716584219c42d691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Pi=C4=85tkowski?= Date: Tue, 19 May 2026 14:59:35 +0200 Subject: [PATCH 2/6] feat(wallet-sdk): add plugin system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mateusz Piątkowski --- sdk/wallet-sdk/src/wallet/init/index.ts | 1 + .../src/wallet/init/initializedSDK.ts | 22 ++++++++++++++- sdk/wallet-sdk/src/wallet/init/plugin.ts | 27 +++++++++++++++++++ sdk/wallet-sdk/src/wallet/init/types/sdk.ts | 19 ++++++++++++- sdk/wallet-sdk/src/wallet/sdk.ts | 2 +- 5 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 sdk/wallet-sdk/src/wallet/init/plugin.ts diff --git a/sdk/wallet-sdk/src/wallet/init/index.ts b/sdk/wallet-sdk/src/wallet/init/index.ts index e43196569..1d8ca6c87 100644 --- a/sdk/wallet-sdk/src/wallet/init/index.ts +++ b/sdk/wallet-sdk/src/wallet/init/index.ts @@ -3,3 +3,4 @@ export * from './initializedSDK.js' export * from './types/index.js' +export { SDKPlugin } from './plugin.js' diff --git a/sdk/wallet-sdk/src/wallet/init/initializedSDK.ts b/sdk/wallet-sdk/src/wallet/init/initializedSDK.ts index 57d7dc701..b2e881813 100644 --- a/sdk/wallet-sdk/src/wallet/init/initializedSDK.ts +++ b/sdk/wallet-sdk/src/wallet/init/initializedSDK.ts @@ -19,6 +19,7 @@ import { ExtendedFullSDKInterface, ExtendedSDKOptions, OfflineSDKInterface, + RegisteredPlugins, SDKInterface, TokenConfig, } from './types/index.js' @@ -32,6 +33,7 @@ import { TokenStandardService } from '@canton-network/core-token-standard-servic import { SDKLogger } from '../logger/logger.js' import { AmuletNamespace } from '../namespace/amulet/namespace.js' import { EventsNamespace } from '../namespace/events/index.js' +import { SDKPlugin } from './plugin.js' const createNamespace: { [K in keyof ExtendedSDKOptions]: ( @@ -155,6 +157,24 @@ export class InitializedSDK implements BasicSDKInterface { ) { return await ExtendedInitializedSDK.create(this.ctx, config) } + + public registerPlugins< + P extends Record SDKPlugin>, + >(plugins: P): InitializedSDK & RegisteredPlugins

{ + const newSDK = new InitializedSDK(this.ctx) + + for (const name in plugins) { + const plugin = new plugins[name](this.ctx) + Object.defineProperty(newSDK, plugin.name, { + value: plugin, + writable: false, + enumerable: true, + configurable: false, + }) + } + + return newSDK as InitializedSDK & RegisteredPlugins

+ } } export class OfflineInitializedSDK implements OfflineSDKInterface { @@ -229,7 +249,7 @@ export class ExtendedInitializedSDK< ...config, } as Pick - return await ExtendedInitializedSDK.create(this.ctx, mergedConfig) + return await super.extend(mergedConfig) } } diff --git a/sdk/wallet-sdk/src/wallet/init/plugin.ts b/sdk/wallet-sdk/src/wallet/init/plugin.ts new file mode 100644 index 000000000..c3d508438 --- /dev/null +++ b/sdk/wallet-sdk/src/wallet/init/plugin.ts @@ -0,0 +1,27 @@ +// Copyright (c) 2025-2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { SDKLogger } from '../logger/index.js' +import { + EXTENDED_SDK_OPTION_KEYS, + ExtendedSDKOptions, + SDKContext, +} from '../sdk.js' + +export abstract class SDKPlugin { + protected readonly logger: ReturnType + + constructor( + public readonly name: string, + protected readonly ctx: SDKContext + ) { + if (EXTENDED_SDK_OPTION_KEYS.includes(name as keyof ExtendedSDKOptions)) + throw Error( + `Name ${name} is reserved and cannot be used to register the plugin. Reserved names: ${EXTENDED_SDK_OPTION_KEYS.join(', ')}.` + ) + + this.logger = ctx.logger.child({ + plugin: name, + }) + } +} diff --git a/sdk/wallet-sdk/src/wallet/init/types/sdk.ts b/sdk/wallet-sdk/src/wallet/init/types/sdk.ts index f3b24be7b..51a8d3940 100644 --- a/sdk/wallet-sdk/src/wallet/init/types/sdk.ts +++ b/sdk/wallet-sdk/src/wallet/init/types/sdk.ts @@ -9,7 +9,7 @@ import { PartyNamespace } from '../../namespace/party/index.js' import { UserNamespace } from '../../namespace/user/index.js' import { SDKUtilsNamespace } from '../../namespace/utils/index.js' import { AmuletNamespace } from '../../namespace/amulet/namespace.js' -import { AssetNamespace, TokenNamespace } from '../../sdk.js' +import { AssetNamespace, SDKContext, TokenNamespace } from '../../sdk.js' import { EventsNamespace } from '../../namespace/events/namespace.js' import { AmuletConfig, @@ -19,6 +19,7 @@ import { } from './config.js' import { Provider } from '@canton-network/core-splice-provider' import { LedgerTypes } from '@canton-network/core-ledger-client-types' +import { SDKPlugin } from '../plugin.js' // SDK OPTIONS @@ -87,6 +88,11 @@ export type BasicSDKInterface< extend: ( config: Pick ) => Promise> + registerPlugins: < + P extends Record SDKPlugin>, + >( + plugins: P + ) => BasicSDKInterface & RegisteredPlugins

}> export type ExtendedFullSDKInterface = Readonly<{ @@ -121,3 +127,14 @@ export type OfflineSDKInterface = Readonly<{ keys: KeysNamespace utils: SDKUtilsNamespace }> + +// PLUGINS + +export type RegisteredPlugins< + P extends Record SDKPlugin> = Record< + string, + new (ctx: SDKContext) => SDKPlugin + >, +> = { + [K in keyof P]: InstanceType +} diff --git a/sdk/wallet-sdk/src/wallet/sdk.ts b/sdk/wallet-sdk/src/wallet/sdk.ts index d0ee4d683..54b6f6232 100644 --- a/sdk/wallet-sdk/src/wallet/sdk.ts +++ b/sdk/wallet-sdk/src/wallet/sdk.ts @@ -56,7 +56,7 @@ export type OfflineSDKContext = { error: SDKErrorHandler } -export type * from './init/index.js' +export * from './init/index.js' export { PrepareOptions, ExecuteOptions } from './namespace/ledger/index.js' export * from './namespace/transactions/prepared.js' export * from './namespace/transactions/signed.js' From 1f8c69e0b220987651db4362f88adbf724dfaef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Pi=C4=85tkowski?= Date: Tue, 19 May 2026 15:18:42 +0200 Subject: [PATCH 3/6] docs(wallet-sdk): add plugin docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mateusz Piątkowski --- .../examples/snippets/plugin.ts | 32 +++++++++++++++++ .../src/wallet-sdk-configuration/index.rst | 35 +++++++++++++++++-- 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 docs/wallet-integration-guide/examples/snippets/plugin.ts diff --git a/docs/wallet-integration-guide/examples/snippets/plugin.ts b/docs/wallet-integration-guide/examples/snippets/plugin.ts new file mode 100644 index 000000000..c9d869dbd --- /dev/null +++ b/docs/wallet-integration-guide/examples/snippets/plugin.ts @@ -0,0 +1,32 @@ +import { SDK, SDKContext, SDKPlugin } from '@canton-network/wallet-sdk' + +export default async function () { + const sdk = ( + await SDK.create({ + auth: { + method: 'self_signed', + issuer: 'unsafe-auth', + credentials: { + clientId: 'ledger-api-user', + clientSecret: 'unsafe', + audience: 'https://canton.network.global', + scope: '', + }, + }, + ledgerClientUrl: 'http://localhost:2975', + }) + ).registerPlugins({ + myPlugin: class extends SDKPlugin { + // wallet-sdk plugin should always accept SDKContext + constructor(protected readonly ctx: SDKContext) { + super('myPlugin', ctx) + } + + myMethod() { + console.log("It's working!", this.ctx) + } + }, + }) + + sdk.myPlugin.myMethod() +} diff --git a/docs/wallet-integration-guide/src/wallet-sdk-configuration/index.rst b/docs/wallet-integration-guide/src/wallet-sdk-configuration/index.rst index 14a421c61..903f11f35 100644 --- a/docs/wallet-integration-guide/src/wallet-sdk-configuration/index.rst +++ b/docs/wallet-integration-guide/src/wallet-sdk-configuration/index.rst @@ -88,7 +88,7 @@ The wallet-sdk can either take in a Provider (which will have auth bundled into In our examples, we have provided a default TokenProviderConfig for connecting to localnet, which uses a self-signed token. .. code-block:: javascript - + { method: 'self_signed', issuer: 'unsafe-auth', @@ -129,7 +129,7 @@ SDK using a different TokenProviderConfig. The following programmatic methods of configUrl: string credentials: ClientCredentials } - + export interface ClientCredentials { clientId: string clientSecret: string @@ -138,3 +138,34 @@ SDK using a different TokenProviderConfig. The following programmatic methods of } +Registering Plugins +------------------- + +The Wallet SDK supports extending its functionality through a plugin system. Plugins allow you to add custom methods and functionality +to the SDK instance while maintaining access to the SDK context and logger. + +Creating and Registering a Plugin +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To create a plugin, extend the ``SDKPlugin`` class and implement your custom functionality. Plugins are registered using the +``registerPlugins`` method, which accepts a record of plugin constructors keyed by their desired property names. + +.. literalinclude:: ../examples/snippets/plugin.ts + :language: typescript + :dedent: + +Key Points +^^^^^^^^^^ + +- **Plugin Constructor**: Plugin classes must accept ``SDKContext`` as a constructor parameter and pass it to the ``super()`` call along with the plugin name. +- **Type Safety**: The ``registerPlugins`` method provides full type safety, ensuring that registered plugins are accessible with proper autocompletion and type checking. +- **Access to SDK Context**: Plugins have access to the SDK's context, logger, and other internal utilities through the ``ctx`` property. +- **Multiple Plugins**: You can register multiple plugins at once by passing them in a single object to ``registerPlugins``. + +Example with Multiple Plugins: + +.. literalinclude:: ../../examples/snippets/plugin.ts + :language: typescript + :dedent: + + From 5d2c5d7c5551bb044289b1cfe38eae772a87cd75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Pi=C4=85tkowski?= Date: Wed, 20 May 2026 08:20:34 +0200 Subject: [PATCH 4/6] docs: fix docs build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mateusz Piątkowski --- .../src/wallet-sdk-configuration/index.rst | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/wallet-integration-guide/src/wallet-sdk-configuration/index.rst b/docs/wallet-integration-guide/src/wallet-sdk-configuration/index.rst index 903f11f35..e24543f6e 100644 --- a/docs/wallet-integration-guide/src/wallet-sdk-configuration/index.rst +++ b/docs/wallet-integration-guide/src/wallet-sdk-configuration/index.rst @@ -150,7 +150,7 @@ Creating and Registering a Plugin To create a plugin, extend the ``SDKPlugin`` class and implement your custom functionality. Plugins are registered using the ``registerPlugins`` method, which accepts a record of plugin constructors keyed by their desired property names. -.. literalinclude:: ../examples/snippets/plugin.ts +.. literalinclude:: ../../examples/snippets/plugin.ts :language: typescript :dedent: @@ -162,10 +162,3 @@ Key Points - **Access to SDK Context**: Plugins have access to the SDK's context, logger, and other internal utilities through the ``ctx`` property. - **Multiple Plugins**: You can register multiple plugins at once by passing them in a single object to ``registerPlugins``. -Example with Multiple Plugins: - -.. literalinclude:: ../../examples/snippets/plugin.ts - :language: typescript - :dedent: - - From 4039c8d82bdf266b401ada6155c9f815b776c96c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Pi=C4=85tkowski?= Date: Wed, 20 May 2026 09:05:45 +0200 Subject: [PATCH 5/6] fix: build fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mateusz Piątkowski --- .../examples/__snapshots__/snippets.test.ts.snap | 2 ++ docs/wallet-integration-guide/examples/snippets/plugin.ts | 2 +- sdk/wallet-sdk/src/wallet/sdk.ts | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/wallet-integration-guide/examples/__snapshots__/snippets.test.ts.snap b/docs/wallet-integration-guide/examples/__snapshots__/snippets.test.ts.snap index 15cbd48b0..787cf18f6 100644 --- a/docs/wallet-integration-guide/examples/__snapshots__/snippets.test.ts.snap +++ b/docs/wallet-integration-guide/examples/__snapshots__/snippets.test.ts.snap @@ -46,6 +46,8 @@ exports[`testing doc snippets > monitor-transaction-holdings.ts 1`] = `undefined exports[`testing doc snippets > party.ts 1`] = `undefined`; +exports[`testing doc snippets > plugin.ts 1`] = `undefined`; + exports[`testing doc snippets > prepare-incoming-command.ts 1`] = `undefined`; exports[`testing doc snippets > prepare-transfer-transaction.ts 1`] = `undefined`; diff --git a/docs/wallet-integration-guide/examples/snippets/plugin.ts b/docs/wallet-integration-guide/examples/snippets/plugin.ts index c9d869dbd..6ac6eb8a3 100644 --- a/docs/wallet-integration-guide/examples/snippets/plugin.ts +++ b/docs/wallet-integration-guide/examples/snippets/plugin.ts @@ -23,7 +23,7 @@ export default async function () { } myMethod() { - console.log("It's working!", this.ctx) + console.log("It's working!") } }, }) diff --git a/sdk/wallet-sdk/src/wallet/sdk.ts b/sdk/wallet-sdk/src/wallet/sdk.ts index 54b6f6232..e1dd8001c 100644 --- a/sdk/wallet-sdk/src/wallet/sdk.ts +++ b/sdk/wallet-sdk/src/wallet/sdk.ts @@ -100,7 +100,8 @@ export class SDK { if ( //this is only the cause if authentication is completely disabled on the ledger. err?.cause && - (err.cause as string).includes( + typeof err.cause === 'string' && + err.cause.includes( 'The submitted request is missing a user-id' ) ) { From 8520c505c0e5d13d5ebfe6d3861483f28c5902dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Pi=C4=85tkowski?= Date: Wed, 20 May 2026 09:24:11 +0200 Subject: [PATCH 6/6] docs: update snippet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mateusz Piątkowski --- docs/wallet-integration-guide/examples/snippets/plugin.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/wallet-integration-guide/examples/snippets/plugin.ts b/docs/wallet-integration-guide/examples/snippets/plugin.ts index 6ac6eb8a3..500a0cd0a 100644 --- a/docs/wallet-integration-guide/examples/snippets/plugin.ts +++ b/docs/wallet-integration-guide/examples/snippets/plugin.ts @@ -23,7 +23,8 @@ export default async function () { } myMethod() { - console.log("It's working!") + // do some logic + return } }, })