diff --git a/docs-main/sdks-tools/sdks/wallet-sdk/using-the-sdk/configuration.mdx b/docs-main/sdks-tools/sdks/wallet-sdk/using-the-sdk/configuration.mdx index 4b4138b7e..aab51b51b 100644 --- a/docs-main/sdks-tools/sdks/wallet-sdk/using-the-sdk/configuration.mdx +++ b/docs-main/sdks-tools/sdks/wallet-sdk/using-the-sdk/configuration.mdx @@ -1,273 +1,143 @@ --- -title: 'Configuration' -description: 'Configure the Wallet SDK for the right environment and configuring authentication.' +title: "Configuration" +description: "Configure the Wallet SDK for the right environment and configuring authentication." --- - import CantonDocsIntegrationsWalletConfigurationL70 from "/snippets/canton-docs/integrations_wallet_configuration_L70.mdx"; ## Wallet SDK Configuration -If you have already played around with the wallet SDK you might have come across snippets like: - -``` typescript -import { - localNetStaticConfig, - SDK, - signTransactionHash, -} from '@canton-network/wallet-sdk' -import { pino } from 'pino' -import { v4 } from 'uuid' -import { - TOKEN_NAMESPACE_CONFIG, - TOKEN_PROVIDER_CONFIG_DEFAULT, - AMULET_NAMESPACE_CONFIG, -} from './utils/index.js' - -const logger = pino({ name: 'v1-01-ping-localnet', level: 'info' }) - -const sdk = await SDK.create({ - auth: TOKEN_PROVIDER_CONFIG_DEFAULT, - ledgerClientUrl: localNetStaticConfig.LOCALNET_APP_USER_LEDGER_URL, - token: TOKEN_NAMESPACE_CONFIG, - amulet: AMULET_NAMESPACE_CONFIG, -}) - -const senderKeys = sdk.keys.generate() - -const sender = await sdk.party.external - .create(senderKeys.publicKey, { - partyHint: 'v1-01-alice', - }) - .sign(senderKeys.privateKey) - .execute() - -const senderFingerprint = await sdk.keys.fingerprint(senderKeys.publicKey) - -logger.info({ sender, senderFingerprint }, 'Sender party representation:') - -if (sender.publicKeyFingerprint !== senderFingerprint) - throw Error('Inconsistent fingerprints') - -const receiverKeys = sdk.keys.generate() - -const receiverPartyCreation = sdk.party.external.create( - receiverKeys.publicKey, - { - partyHint: 'v1-01-bob', - } -) - -const unsignedReceiver = await receiverPartyCreation.topology() - -// external signing simulation -const receiverPartySignature = signTransactionHash( - unsignedReceiver.multiHash, - receiverKeys.privateKey -) - -const signedReceiverParty = await receiverPartyCreation.execute( - receiverPartySignature -) - -logger.info({ signedReceiverParty }, 'Receiver party representation:') - -const pingCommand = [ - { - CreateCommand: { - templateId: - '#canton-builtin-admin-workflow-ping:Canton.Internal.Ping:Ping', - createArguments: { - id: v4(), - initiator: sender.partyId, - responder: sender.partyId, - }, - }, +The following code examples show you how to initialize the wallet-sdk. This is the default config that can be used in combination with a non-altered [Localnet](https://docs.canton.network/sdks-tools/development-tools/localnet) running instance. +However as soon as you need to migrate your script, code and deployment to a different environment these default configurations are no longer viable to use. In those cases, the values for the registries, auth, etc must be modified. + +Static configuration initialization where an auth config and ledgerClientUrl are configured: + +```typescript +import { SDK, localNetStaticConfig } 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: "", + }, }, -] - -logger.info({ pingCommand }, 'Ping command to be submitted:') - -await sdk.ledger - .prepare({ - partyId: sender.partyId, - commands: pingCommand, - disclosedContracts: [], - }) - .sign(senderKeys.privateKey) - .execute({ partyId: sender.partyId }) - -logger.info('Ping command submitted with online signing') - -/* -offline signing example -*/ - -const preparedPingCommand = sdk.ledger.prepare({ - partyId: sender.partyId, - commands: pingCommand, - disclosedContracts: [], -}) - -const { response: preparedPingCommandResponse } = - await preparedPingCommand.toJSON() - -logger.info({ preparedPingCommand }, 'Prepared ping command:') - -/* -Note: The following code uses the @canton-network/core-signing-lib as the 'custodian' of the private key to sign the prepared transaction hash, -but in a real scenario, the signing could be done using any compatible signing mechanism, such as a hardware wallet or an external signing service. -*/ -const signature = signTransactionHash( - preparedPingCommandResponse.preparedTransactionHash, - senderKeys.privateKey -) - -const signed = sdk.ledger.fromSignature(preparedPingCommandResponse, signature) - -await sdk.ledger.execute(signed, { partyId: sender.partyId }) - -logger.info('Ping command submitted with offline signing') - -const [amuletTapCommand, amuletTapDisclosedContracts] = await sdk.amulet.tap( - sender.partyId, - '10000' -) - -const result = await sdk.ledger - .prepare({ - partyId: sender.partyId, - commands: amuletTapCommand, - disclosedContracts: amuletTapDisclosedContracts, - }) - .sign(senderKeys.privateKey) - .execute({ partyId: sender.partyId }) - -const senderUtxos = await sdk.token.utxos.list({ partyId: sender.partyId }) - -const tapTransaction = await sdk.token.transactionsById({ - updateId: result.updateId, - partyId: sender.partyId, -}) - -const mintEvent = tapTransaction.events.find( - (tokenStandardEvent) => - tokenStandardEvent.label.type === 'Mint' && - tokenStandardEvent.unlockedHoldingsChange.creates.find( - (h) => h.amount === '10000.0000000000' - ) -) - -if (mintEvent) { - logger.info('Found token standard event with type Mint') -} else { - throw new Error(`Couldn't find tap transaction by updateId`) -} -const senderAmuletUtxos = senderUtxos.filter((utxo) => { - return ( - utxo.interfaceViewValue.amount === '10000.0000000000' && - utxo.interfaceViewValue.instrumentId.id === 'Amulet' - ) -}) - -if (senderAmuletUtxos.length === 0) { - throw new Error('No UTXOs found for Sender') + ledgerClientUrl: new URL("http://localhost:2975"), + token: { + registries: [ + new URL("http://localhost:2000/api/validator/v0/scan-proxy"), + ], + auth: global.TOKEN_PROVIDER_CONFIG_DEFAULT, + }, + amulet: { + scanApiUrl: localNetStaticConfig.LOCALNET_SCAN_API_URL, + auth: TOKEN_PROVIDER_CONFIG_DEFAULT, + registryUrl: localNetStaticConfig.LOCALNET_REGISTRY_API_URL, + }, + asset: { + registries: [localNetStaticConfig.LOCALNET_REGISTRY_API_URL], + auth: TOKEN_PROVIDER_CONFIG_DEFAULT, + }, + }); + + // OR, you can defer loading config by calling .extend() + + const basicSDK = await SDK.create({ + auth: { + method: "self_signed", + issuer: "unsafe-auth", + credentials: { + clientId: "ledger-api-user", + clientSecret: "unsafe", + audience: "https://canton.network.global", + scope: "", + }, + }, + ledgerClientUrl: new URL("http://localhost:2975"), + }); + + // Extend with token namespace + const tokenExtendedSDK = await basicSDK.extend({ + token: { + validatorUrl: new URL("http://localhost:2000/api/validator"), + registries: [ + new URL("http://localhost:2000/api/validator/v0/scan-proxy"), + ], + auth: global.TOKEN_PROVIDER_CONFIG_DEFAULT, + }, + }); + + // Can extend further with more namespaces + const fullyExtendedSDK = await tokenExtendedSDK.extend({ + amulet: { + validatorUrl: localNetStaticConfig.LOCALNET_APP_VALIDATOR_URL, + scanApiUrl: localNetStaticConfig.LOCALNET_SCAN_API_URL, + auth: global.TOKEN_PROVIDER_CONFIG_DEFAULT, + registryUrl: localNetStaticConfig.LOCALNET_REGISTRY_API_URL, + }, + }); } - -logger.info('Tap command for Amulet for Sender submitted and UTXO received') ``` -This is the default config that can be used in combination with a non-altered [Localnet](https://docs.canton.network/sdks-tools/development-tools/localnet) running instance. +Namespace initialization can be deferred until later, so the basicSDK with just ledgerApi capabilities and default namespaces can be initialized. +Here is an example with the basicSDK initialization and the extended namespaces: -However as soon as you need to migrate your script, code and deployment to a different environment these default configurations are no longer viable to use. In those cases creating custom factories for each controller is needed. Here is a template that you can use when setting up your own custom connectivity configuration: - -``` typescript -import { SDK, localNetStaticConfig } from '@canton-network/wallet-sdk' +```typescript +import { SDK, localNetStaticConfig } 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: new URL('http://localhost:2975'), - token: { - validatorUrl: new URL('http://localhost:2000/api/validator'), - registries: [ - new URL('http://localhost:2000/api/validator/v0/scan-proxy'), - ], - auth: global.TOKEN_PROVIDER_CONFIG_DEFAULT, - }, - amulet: { - validatorUrl: localNetStaticConfig.LOCALNET_APP_VALIDATOR_URL, - scanApiUrl: localNetStaticConfig.LOCALNET_SCAN_API_URL, - auth: TOKEN_PROVIDER_CONFIG_DEFAULT, - registryUrl: localNetStaticConfig.LOCALNET_REGISTRY_API_URL, - }, - asset: { - registries: [localNetStaticConfig.LOCALNET_REGISTRY_API_URL], - auth: TOKEN_PROVIDER_CONFIG_DEFAULT, - }, - }) - - const myParty = global.EXISTING_PARTY_1 - - await sdk.token.utxos.list({ partyId: myParty }) - - await sdk.amulet.traffic.status() - - // OR, you can defer loading config by calling .extend() - - const basicSDK = await SDK.create({ - auth: { - method: 'self_signed', - issuer: 'unsafe-auth', - credentials: { - clientId: 'ledger-api-user', - clientSecret: 'unsafe', - audience: 'https://canton.network.global', - scope: '', - }, - }, - ledgerClientUrl: new URL('http://localhost:2975'), - }) - - // Extend with token namespace - const tokenExtendedSDK = await basicSDK.extend({ - token: { - validatorUrl: new URL('http://localhost:2000/api/validator'), - registries: [ - new URL('http://localhost:2000/api/validator/v0/scan-proxy'), - ], - auth: global.TOKEN_PROVIDER_CONFIG_DEFAULT, - }, - }) - - // Now token namespace is available - await tokenExtendedSDK.token.utxos.list({ partyId: myParty }) - - // Can extend further with more namespaces - const fullyExtendedSDK = await tokenExtendedSDK.extend({ - amulet: { - validatorUrl: localNetStaticConfig.LOCALNET_APP_VALIDATOR_URL, - scanApiUrl: localNetStaticConfig.LOCALNET_SCAN_API_URL, - auth: global.TOKEN_PROVIDER_CONFIG_DEFAULT, - registryUrl: localNetStaticConfig.LOCALNET_REGISTRY_API_URL, - }, - }) - - // Now both token and amulet are available - await fullyExtendedSDK.token.utxos.list({ partyId: myParty }) - await fullyExtendedSDK.amulet.traffic.status() + const basicSDK = await SDK.create({ + auth: { + method: "self_signed", + issuer: "unsafe-auth", + credentials: { + clientId: "ledger-api-user", + clientSecret: "unsafe", + audience: "https://canton.network.global", + scope: "", + }, + }, + ledgerClientUrl: new URL("http://localhost:2975"), + }); + + // Extend with token namespace + const tokenExtendedSDK = await basicSDK.extend({ + token: { + validatorUrl: new URL("http://localhost:2000/api/validator"), + registries: [ + new URL("http://localhost:2000/api/validator/v0/scan-proxy"), + ], + auth: global.TOKEN_PROVIDER_CONFIG_DEFAULT, + }, + }); + + // Can extend further with more namespaces + const fullyExtendedSDK = await tokenExtendedSDK.extend({ + amulet: { + validatorUrl: localNetStaticConfig.LOCALNET_APP_VALIDATOR_URL, + scanApiUrl: localNetStaticConfig.LOCALNET_SCAN_API_URL, + auth: global.TOKEN_PROVIDER_CONFIG_DEFAULT, + registryUrl: localNetStaticConfig.LOCALNET_REGISTRY_API_URL, + }, + }); } ``` +An alternative way to inialize the wallet-sdk is through the provider. The provider is an abstraction that ultimately interacts with the Ledger (JSON LAPI). This can be implemented for either a dApp consumer, direct ledger user, or alternative transport channels such as Wallet Connect. + +```typescript +import { SDK, localNetStaticConfig } from "@canton-network/wallet-sdk"; + +// Notice that `auth` and `ledgerClientUrl` are no longer needed +// when supplying sdk with custom provider +const sdk = await SDK.create(config, provider); +``` + ## How do I validate my configurations? Knowing if you are using the correct url and port can be daunting, here is a few curl and gcurl commands you can use to validate against an expected output @@ -276,37 +146,37 @@ Knowing if you are using the correct url and port can be daunting, here is a few ```json { -"version": "3.4.12-SNAPSHOT", -"features": { - "experimental": { - "staticTime": { - "supported": false - }, - "commandInspectionService": { - "supported": true - } - }, - "userManagement": { - "supported": true, - "maxRightsPerUser": 1000, - "maxUsersPageSize": 1000 - }, - "partyManagement": { - "maxPartiesPageSize": 10000 - }, - "offsetCheckpoint": { - "maxOffsetCheckpointEmissionDelay": { - "seconds": 75, - "nanos": 0, - "unknownFields": { - "fields": {} - } - } - }, - "packageFeature": { - "maxVettedPackagesPageSize": 100 - } -} + "version": "3.4.12-SNAPSHOT", + "features": { + "experimental": { + "staticTime": { + "supported": false + }, + "commandInspectionService": { + "supported": true + } + }, + "userManagement": { + "supported": true, + "maxRightsPerUser": 1000, + "maxUsersPageSize": 1000 + }, + "partyManagement": { + "maxPartiesPageSize": 10000 + }, + "offsetCheckpoint": { + "maxOffsetCheckpointEmissionDelay": { + "seconds": 75, + "nanos": 0, + "unknownFields": { + "fields": {} + } + } + }, + "packageFeature": { + "maxVettedPackagesPageSize": 100 + } + } } ``` @@ -315,7 +185,7 @@ the fields may vary based on your configuration. **my-validator-app-api** can be identified with `curl ${api}/version` it should produce an output like ```json -{"version":"0.4.15","commit_ts":"2025-09-05T11:38:13Z"} +{ "version": "0.4.15", "commit_ts": "2025-09-05T11:38:13Z" } ``` **my-scan-proxy-api** is an api inside the validator api and can be defined as `${my-validator-app-api}/v0/scan-proxy`. @@ -326,7 +196,7 @@ the fields may vary based on your configuration. The wallet-sdk can either take in a Provider (which will have auth bundled into it) or a LedgerClientUrl + TokenProviderConfig. In our examples, we have provided a default TokenProviderConfig for connecting to localnet, which uses a self-signed token. -``` javascript +```javascript { method: 'self_signed', issuer: 'unsafe-auth', @@ -347,7 +217,7 @@ When upgrading your setup from a localnet setup to a production or client facing > 2. \`self_signed\`: only for development purposes, used for Canton setups that accept HMAC256 self signed tokens. > 3. \`client_credentials\`: used to programmatically acquire tokens via oauth2, a.k.a "machine-to-machine" tokens -``` javascript +```javascript export type TokenProviderConfig = | { method: 'static' @@ -382,8 +252,6 @@ Each non-LocalNet environment requires different connection endpoints. Configure - **Scan API URL** — The Scan service endpoint (either direct or via the BFT scan proxy) - **Auth token** — A valid JWT token from your OIDC provider -The SDK accepts authentication tokens through factory functions. For remote environments, create custom factories for each controller with the correct URLs and an `AuthTokenProvider`: - See the [config template](https://github.com/canton-network/wallet-gateway/blob/main/docs/wallet-integration-guide/examples/snippets/config-template.ts) in the Wallet SDK repository for a complete example.