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
32 changes: 32 additions & 0 deletions docs/wallet-integration-guide/examples/snippets/plugin.ts
Original file line number Diff line number Diff line change
@@ -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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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
Expand All @@ -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:


1 change: 1 addition & 0 deletions sdk/wallet-sdk/src/wallet/init/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@

export * from './initializedSDK.js'
export * from './types/index.js'
export { SDKPlugin } from './plugin.js'
22 changes: 21 additions & 1 deletion sdk/wallet-sdk/src/wallet/init/initializedSDK.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
ExtendedFullSDKInterface,
ExtendedSDKOptions,
OfflineSDKInterface,
RegisteredPlugins,
SDKInterface,
TokenConfig,
} from './types/index.js'
Expand All @@ -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]: (
Expand Down Expand Up @@ -155,6 +157,24 @@ export class InitializedSDK implements BasicSDKInterface {
) {
return await ExtendedInitializedSDK.create(this.ctx, config)
}

public registerPlugins<
P extends Record<string, new (ctx: SDKContext) => SDKPlugin>,
>(plugins: P): InitializedSDK & RegisteredPlugins<P> {
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<P>
}
}

export class OfflineInitializedSDK implements OfflineSDKInterface {
Expand Down Expand Up @@ -229,7 +249,7 @@ export class ExtendedInitializedSDK<
...config,
} as Pick<ExtendedSDKOptions, ExtendedItems | NewItems>

return await ExtendedInitializedSDK.create(this.ctx, mergedConfig)
return await super.extend(mergedConfig)
}
}

Expand Down
27 changes: 27 additions & 0 deletions sdk/wallet-sdk/src/wallet/init/plugin.ts
Original file line number Diff line number Diff line change
@@ -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<SDKLogger['child']>

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,
})
}
}
19 changes: 18 additions & 1 deletion sdk/wallet-sdk/src/wallet/init/types/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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

Expand Down Expand Up @@ -87,6 +88,11 @@ export type BasicSDKInterface<
extend: <ExtendedItems extends keyof ExtendedSDKOptions>(
config: Pick<ExtendedSDKOptions, ExtendedItems>
) => Promise<SDKInterface<ExtendedItems | CurrentlyExtended>>
registerPlugins: <
P extends Record<string, new (ctx: SDKContext) => SDKPlugin>,
>(
plugins: P
) => BasicSDKInterface<CurrentlyExtended> & RegisteredPlugins<P>
}>

export type ExtendedFullSDKInterface = Readonly<{
Expand Down Expand Up @@ -121,3 +127,14 @@ export type OfflineSDKInterface = Readonly<{
keys: KeysNamespace
utils: SDKUtilsNamespace
}>

// PLUGINS

export type RegisteredPlugins<
P extends Record<string, new (ctx: SDKContext) => SDKPlugin> = Record<
string,
new (ctx: SDKContext) => SDKPlugin
>,
> = {
[K in keyof P]: InstanceType<P[K]>
}
3 changes: 2 additions & 1 deletion sdk/wallet-sdk/src/wallet/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -55,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'
Expand Down