> ## Documentation Index
> Fetch the complete documentation index at: https://docs.canton.network/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration

> Configure the Wallet SDK for the right environment and configuring authentication.

## Wallet SDK Configuration

If you have already played around with the wallet SDK you might have come across snippets like:

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
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,
            },
        },
    },
]

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')
}

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.

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 theme={"theme":{"light":"github-light","dark":"github-dark"}}
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()
}
```

## 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

**my-json-ledger-api** can be identified with `curl http://${my-json-ledger-api}/v2/version` it should produce a json that looks like

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
"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
   }
}
}
```

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 theme={"theme":{"light":"github-light","dark":"github-dark"}}
{"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`.

**my-registry-api** is the registry for the token you want to use, for Canton Coin you can use **my-scan-proxy-api**, however for any other token standard token it is required to source the api from a reputable source.

## Configuring auth

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 theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
method: 'self_signed',
issuer: 'unsafe-auth',
credentials: {
   clientId: 'ledger-api-user',
   clientSecret: 'unsafe',
   audience: 'https://canton.network.global',
   scope: '',
},
}
```

The value for some of the audiences in localnet would have to be adjusted to match "[https://canton.network.global](https://canton.network.global)". This is specifically the `LEDGER_API_AUTH_AUDIENCE` & `VALIDATOR_AUTH_AUDIENCE`.

When upgrading your setup from a localnet setup to a production or client facing environment then it might make more sense to add proper authentication to the ledger api and other services. The community contributions include okta and keycloak [OIDC](/global-synchronizer/deployment/oidc-providers). These can easily be configured for the SDK using a different TokenProviderConfig. The following programmatic methods of token fetching are supported:

> 1. \`static\`: a fixed, in-memory token. Only used for compatibility, it will totally break for expired tokens.
> 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 theme={"theme":{"light":"github-light","dark":"github-dark"}}
export type TokenProviderConfig =
   | {
         method: 'static'
         token: string
      }
   | {
         method: 'self_signed'
         issuer: string
         credentials: ClientCredentials
      }
   | {
         method: 'client_credentials'
         configUrl: string
         credentials: ClientCredentials
      }

export interface ClientCredentials {
 clientId: string
 clientSecret: string
 scope: string | undefined
 audience: string | undefined
}
```

## Environment-specific endpoints

Each non-LocalNet environment requires different connection endpoints. Configure the following connection parameters:

* **JSON Ledger API URL** — The HTTP/JSON API endpoint for your validator's participant
* **gRPC Admin API URL** — The gRPC endpoint for participant administration
* **Validator API URL** — The validator app's REST API endpoint
* **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`:

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
import {
  WalletSDKImpl,
  LedgerController,
  ValidatorController,
  TokenStandardController,
  AuthTokenProvider,
} from "@canton-network/wallet-sdk";

const myLedgerFactory = (
  userId: string,
  authTokenProvider: AuthTokenProvider
) => {
  return new LedgerController(
    userId,
    new URL("https://json-api.validator.YOUR_HOSTNAME"),
    undefined,
    false,
    authTokenProvider
  );
};

const myValidatorFactory = (
  userId: string,
  authTokenProvider: AuthTokenProvider
) => {
  return new ValidatorController(
    userId,
    new URL("https://wallet.validator.YOUR_HOSTNAME"),
    authTokenProvider
  );
};

const myTokenStandardFactory = (
  userId: string,
  authTokenProvider: AuthTokenProvider
) => {
  return new TokenStandardController(
    userId,
    new URL("https://json-api.validator.YOUR_HOSTNAME"),
    new URL("https://wallet.validator.YOUR_HOSTNAME"),
    undefined,
    authTokenProvider
  );
};

const sdk = new WalletSDKImpl().configure({
  logger: console,
  authFactory: myAuthFactory, // your OIDC auth implementation
  ledgerFactory: myLedgerFactory,
  validatorFactory: myValidatorFactory,
  tokenStandardFactory: myTokenStandardFactory,
});

await sdk.connect();
await sdk.connectAdmin();
await sdk.connectTopology(
  new URL("https://scan.sv.YOUR_HOSTNAME")
);
```

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.
