Skip to main content

Documentation Index

Fetch the complete documentation index at: https://cantonfoundation-networkvars-ci-failure-probe.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Wallet SDK v1 Migration Guide

Wallet SDK v1 is not backwards compatible with v0. Quick links
  • wallet-sdk-config - Detailed configuration guide
  • preparing-and-signing-transactions - Preparing and signing transactions
We have removed the configure() and connect() pattern in favor of passing in a static configuration or a provider with ledger api capabilities. Static configuration initialization where we supply an auth config and a ledgerClientUrl:
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()
}
Provider intialization: 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.
// Notice that `auth` and `ledgerClientUrl` are no longer needed
// when supplying sdk with custom provider
const sdk = await SDK.create(config, provider)

Namespace changes

We have removed the controllers and replaced them with namespaces to appropriately segregate the service layer in terms of business context. When the sdk is initialized, it has access to the users, keys, ledger, and party namespaces. The amulet, token, asset, and events namespace can initialized with a separate config via .extend() method.

Removed functionality

The following methods have been removed: sdk.connect() No longer needed, SDK is connected on creation sdk.connectAdmin() No longer needed, admin operations are available in the ledger namespace and rights are extracted from the token. sdk.connectTopology() No longer needed, the grpc endpoints have been removed and replaced with ledger api endpoints. sdk.setPartyId() Pass partyId explicitly to each operation
sdk.setPartyId(myPartyId)
const holdingTransactionsmyPartyId = await sdk.tokenStandard?.listHoldingTransactions()
sdk.setPartyId(myPartyId2)
const holdingTransactionsmyPartyId2 = await sdk.tokenStandard?.listHoldingTransactions()

const holdingTransactionsmyPartyId = await token.holdings(myPartyId)
const holdingTransactionsmyPartyId2 = await token.holdings(myPartyId2)
In v0, the controllers and sdk were stateful. In v1, party information should be passed explicitly to each function. This enables acting as multiple parties and allows for thread safety in concurrent use.

Migration reference table

v0 controller + methodv1 namespace + method
createKeyPair()sdk.keys.generate()
sdk.userLedger.signAndAllocateExternalParty(privateKey, partyHint)sdk.party.external.create(publicKey, {partyHint}).sign(privateKey).execute()
sdk.userLedger.listWallets()sdk.party.list()
sdk.userLedger.prepareSignExecuteAndWaitForsdk.ledger.prepare({partyId, commands, disclosedContracts}).sign(privateKey).execute(partyId)
sdk.userLedger.activeContractssdk.ledger.acs.read
sdk.adminLedger.uploadDarsdk.ledger.dar.upload
sdk.userLedger.isPackageUploadedsdk.ledger.dar.check
sdk.adminLedger.createUsersdk.user.create
sdk.userLedger.grantRightssdk.user.rights.grant
sdk.tokenStandard.createTransfersdk.token.transfer.create
sdk.tokenStandard.exerciseTransferInstructionChoicesdk.token.transfer.accept / sdk.token.transfer.reject / sdk.token.transfer.withdraw
sdk.tokenStandard.fetchPendingTransferInstructionViewsdk.token.transfer.pending
sdk.tokenStandard.listHoldingTransactions({partyId})sdk.token.holdings
sdk.tokenStandard.listHoldingUtxos()sdk.token.utxos.list({partyId})
sdk.tokenStandard.mergeHoldingUtxossdk.token.utxos.merge
sdk.tokenStandard.fetchPendingAllocationRequestViewsdk.token.allocation.pending(partyId, ALLOCATION_REQUEST_INTERFACE_ID)
sdk.tokenStandard.fetchPendingAllocationInstructionViewsdk.token.allocation.pending(partyId, ALLOCATION_INSTRUCTION_INTERFACE_ID)
sdk.tokenStandard.fetchPendingAllocationViewsdk.token.allocation.pending(partyId)
sdk.tokenStandard.getAllocationExecuteTransferChoiceContext(cId)sdk.token.allocation.context.execute
sdk.tokenStandard.getAllocationWithdrawChoiceContext(cId)sdk.token.allocation.context.withdraw
sdk.tokenStandard.getAllocationCancelChoiceContext(cId)sdk.token.allocation.context.cancel
sdk.tokenStandard.getMemberTrafficStatussdk.amulet.traffic.status
sdk.tokenStandard.buyMemberTrafficsdk.amulet.traffic.buy
sdk.userLedger.createTransferPreapprovalCommandsdk.amulet.preapproval.command.create
sdk.tokenStandard.getTransferPreApprovalByPartysdk.amulet.preapproval.fetchStatus
sdk.tokenStandard.createRenewTransferPreapprovalsdk.amulet.preapproval.renew
sdk.tokenStandard.createCancelTransferPreapprovalsdk.amulet.preapproval.command.cancel
sdk.tokenStandard.createTapsdk.amulet.tap
sdk.tokenStandard.lookupFeaturedAppssdk.amulet.featuredApp.rights
sdk.tokenStandard.selfGrantFeatureAppRightssdk.amulet.featuredApp.grant
sdk.tokenStandard.getInstrumentByIdsdk.asset.find
sdk.tokenStandard.listInstrumentssdk.asset.list
sdk.userLedger.subscribeToUpdatessdk.events.updates
sdk.userLedger.subscribeToCompletionssdk.events.completions
Migration reference table

Party Namespace

The party namespace provides methods to manage wallet parties on the Canton Network. In v1, the party namespace replaces the stateful party management from v0.

Availability

The party namespace is always available as part of the basic SDK interface. It’s initialized automatically when you create an SDK instance and doesn’t require additional configuration via extend().
import { SDK, localNetStaticConfig } from '@canton-network/wallet-sdk'

export default async function () {
    const sdk = await SDK.create({
        auth: global.TOKEN_PROVIDER_CONFIG_DEFAULT,
        ledgerClientUrl: localNetStaticConfig.LOCALNET_APP_USER_LEDGER_URL,
    })

    // party namespace is immediately available
    await sdk.party.list()
}

Key changes from v0 to v1

v0 used a stateful approach where you set a party context once with sdk.setPartyId(). All subsequent operations acted on that party. v1 uses an explicit approach where you pass the party ID to each operation. This enables:
  • Thread-safe concurrent operations
  • Multi-party transactions within the same application
  • Clearer code intent
sdk.setPartyId(myPartyId)
const result = await sdk.userLedger.doSomething()

const result = await sdk.ledger
    .prepare({ partyId: myPartyId, ... })
    .sign(privateKey)
    .execute({ partyId: myPartyId })
Refer to preparing-and-signing-transactions for more information.

Party types

Internal parties
const internalParty =
await sdk.adminLedger!.allocateInternalParty(partyHint)

// v1 - no state, explicit party ID
const internalParty = await sdk.party.internal.allocate({
    partyHint: 'my-service',
    synchronizerId: 'my-synchronizer-id'
})
The below example demonstrates the full usage of the feature: External parties An external party uses an external key pair for signing. You provide the public key, and the SDK generates the topology. You then sign the topology transaction with your private key and execute it on the ledger.
const party = await sdk.userLedger?.signAndAllocateExternalParty(
        privateKey,
        partyHint
    )

const party = await sdk.party.external
    .create(publicKey, { partyHint: 'my-party' })
    .sign(privateKey)
    .execute()
We recommend always providing a partyHint when creating a party. Refer to party-hint for more details.

Listing parties

const wallets = await sdk.userLedger?.listWallets()

const partyIds = await sdk.party.list()
This method returns all parties where the user has CanActAs, CanReadAs, or CanExecuteAs rights. If the user has admin rights, all local parties are returned.

Offline signing workflow

const preparedParty = await sdk.userLedger?.generateExternalParty(
    keyPair.publicKey, partyHint
)
const allocatedParty = await sdk.userLedger?.allocateExternalParty(
    signature,
    preparedParty
)

const party = await sdk.party.external
    .create(publicKey, options)
    .execute(signature, executeOptions)

Migration reference

v0 methodv1 method
sdk.setPartyId(partyId)Pass partyId explicitly to each operation
sdk.userLedger.listWallets()sdk.party.list()
sdk.userLedger.signAndAllocateExternalParty(privateKey, partyHint)sdk.party.external.create(publicKey, {partyHint}).sign(privateKey).execute()
sdk.topology?.prepareExternalPartyTopology()sdk.party.external.create().prepare() (implicit on create)
sdk.topology?.submitExternalPartyTopology()sdk.party.external.create().sign().execute()
Party-related method migration

See also

  • wallet-sdk-config - SDK configuration
  • preparing-and-signing-transactions - Transaction lifecycle

Token Namespace

The token namespace provides methods to manage token operations including transfers, holdings, UTXOs, and allocations on the Canton Network. In v1, the token namespace replaces the tokenStandard controller from v0.

Availability and extensibility

The token namespace is an extended namespace that requires configuration. You can initialize it either during SDK creation or later using the extend() method. Option 1: Initialize during SDK creation
import { SDK, localNetStaticConfig } from '@canton-network/wallet-sdk'

export default async function () {
    const sdk = await SDK.create({
        auth: global.TOKEN_PROVIDER_CONFIG_DEFAULT,
        ledgerClientUrl: localNetStaticConfig.LOCALNET_APP_USER_LEDGER_URL,
        token: global.TOKEN_NAMESPACE_CONFIG,
    })

    const partyId = EXISTING_PARTY_1

    // token namespace is now available
    await sdk.token.utxos.list({ partyId })
}
Option 2: Add token namespace later using extend()
import { SDK, localNetStaticConfig } from '@canton-network/wallet-sdk'

export default async function () {
    // Create basic SDK first
    const basicSDK = await SDK.create({
        auth: global.TOKEN_PROVIDER_CONFIG_DEFAULT,
        ledgerClientUrl: localNetStaticConfig.LOCALNET_APP_USER_LEDGER_URL,
    })

    // Extend with token namespace when needed
    const extendedSDK = await basicSDK.extend({
        token: global.TOKEN_NAMESPACE_CONFIG,
    })

    const partyId = EXISTING_PARTY_1

    // Now token namespace is available
    await extendedSDK.token.utxos.list({ partyId })
}

Key changes from v0 to v1

v0 used the tokenStandard controller with implicit party context set via sdk.setPartyId(). v1 uses the token namespace where you:
  • Pass partyId explicitly to each operation
  • Initialize the namespace with configuration
  • Access operations through logical groupings (transfer, utxos, allocation)
sdk.setPartyId(myPartyId)
const holdings = await sdk.tokenStandard?.listHoldingTransactions()

const holdings = await sdk.token.holdings({ partyId: myPartyId })
This enables thread-safe concurrent operations and clearer code organization.

Transfers

Creating transfers
const transfer = await sdk.tokenStandard.createTransfer(
    senderPartyId,
    recipientPartyId,
    amount.toString(),
    {
      instrumentId: 'Amulet',
      instrumentAdmin: instrumentAdminPartyId
    },
    registryUrl,
    memo
)

const [command, disclosedContracts] = await sdk.token.transfer.create({
    sender: senderPartyId,
    recipient: recipientPartyId,
    amount: amount.toString(),
    instrumentId: 'Amulet',
    registryUrl,
    inputUtxos: ['utxo-1', 'utxo-2'],
    memo: 'Payment for services',
})
Accepting, rejecting, or withdrawing transfers
await sdk.tokenStandard.exerciseTransferInstructionChoice(transferCid, choiceType /* 'Accept' | 'Withdraw' | 'Reject' */)

// Accept transfer
const [acceptCommand, disclosed1] = await sdk.token.transfer.accept({
    transferInstructionCid,
    registryUrl,
})

// Reject transfer
const [rejectCommand, disclosed2] = await sdk.token.transfer.reject({
    transferInstructionCid,
    registryUrl,
})

// Withdraw transfer
const [withdrawCommand, disclosed3] = await sdk.token.transfer.withdraw({
    transferInstructionCid,
    registryUrl,
})
Listing pending transfers
await sdk.setPartyId(partyId)
const pending = await sdk.tokenStandard
    .fetchPendingTransferInstructionView()

const pending = await sdk.token.transfer.pending(myPartyId)

Holdings

Holdings represent the transaction history of token ownership for a party.
await sdk.setPartyId(partyId)
const holdings = await sdk.tokenStandard
    .listHoldingTransactions()

const holdings = await sdk.token.holdings({ partyId })
You can also specify offsets for pagination:
const holdings = await sdk.token.holdings({
    partyId,
    afterOffset: 10,
    beforeOffset: 100,
})
Transactions by updateId:
await sdk.setPartyId(partyId)
const tx = await sdk.tokenStandard.getTransactionById('my-update-id')

const tx = await sdk.token.transactionsById({ updateId, partyId })

UTXOs

UTXOs (Unspent Transaction Outputs) are the actual holding contracts that represent token balances. Listing UTXOs
await sdk.setPartyId(partyId)
// List all UTXOs including locked ones
const allUtxos = await sdk.tokenStandard?.listHoldingUtxos()

// List only unlocked UTXOs
const usableUtxos = await sdk.tokenStandard?.listHoldingUtxos(false)

// List only unlocked UTXOs (default)
const usableUtxos = await sdk.token.utxos.list({
    partyId
})

// List all UTXOs including locked ones
const allUtxos = await sdk.token.utxos.list({
    partyId,
    includeLocked: true,
})
You can specify additional parameters for pagination and limits:
const utxos = await sdk.token.utxos.list({
    partyId,
    includeLocked: false,
    limit: 100,
    offset: 0,
    continueUntilCompletion: false,
})
Merging UTXOs Merging consolidates multiple small UTXOs into larger ones to improve performance.
await sdk.setPartyId(partyId)
const [commands, disclosedContracts] =
    await sdk.tokenStandard.mergeHoldingUtxos(nodeLimit)

const [commands, disclosedContracts] = await sdk.token.utxos.merge({
    partyId,
    nodeLimit: 200,
    memo: 'merge-utxos',
})
The merge operation groups UTXOs by instrument and creates self-transfers to consolidate them. You can optionally provide specific UTXOs to merge:
const [commands, disclosedContracts] = await sdk.token.utxos.merge({
    partyId,
    inputUtxos,
    memo: 'custom merge',
})

Allocation

Allocations handle the issuance and distribution of new tokens. Listing pending allocations
await sdk.setPartyId(partyId)
// Allocation requests
const requests = await sdk.tokenStandard
    .fetchPendingAllocationRequestView()

// Allocation instructions
const instructions = await sdk.tokenStandard
    .fetchPendingAllocationInstructionView()

// Allocations
const allocations = await sdk.tokenStandard
    .fetchPendingAllocationView()

// All pending allocations (default)
const allocations = await sdk.token.allocation.pending(myPartyId)
The pending method accepts an optional interface ID to filter by allocation type:
import {
    ALLOCATION_REQUEST_INTERFACE_ID,
    ALLOCATION_INSTRUCTION_INTERFACE_ID,
    ALLOCATION_INTERFACE_ID,
} from '@canton-network/core-token-standard'

// Filter by specific type
const requests = await sdk.token.allocation.pending(
    myPartyId,
    ALLOCATION_REQUEST_INTERFACE_ID
)
Executing, withdrawing or cancelling allocations
const [command, disclosedContracts] = await sdk.tokenStandard.exerciseAllocationChoice(allocationCid, choice /* 'ExecuteTransfer' | 'Withdraw' | 'Cancel' */)

// Execute allocation
const [executeCommand, disclosedContracts1] = await sdk.token.allocation.execute({
    allocationCid,
    asset
})

 // Withdraw allocation
const [withdrawCommand, disclosedContracts2] = await sdk.token.allocation.withdraw({
    allocationCid,
    asset,
})

// Cancel allocation
const [cancelCommand, disclosedContracts3] = await sdk.token.allocation.cancel({
    allocationCid,
    asset,
})

Migration reference

v0 methodv1 method
sdk.tokenStandard.createTransfersdk.token.transfer.create
sdk.tokenStandard.exerciseTransferInstructionChoicesdk.token.transfer.accept / sdk.token.transfer.reject / sdk.token.transfer.withdraw
sdk.tokenStandard.fetchPendingTransferInstructionViewsdk.token.transfer.pending
sdk.tokenStandard.listHoldingTransactions({partyId})sdk.token.holdings({partyId})
sdk.tokenStandard.listHoldingUtxos()sdk.token.utxos.list({partyId})
sdk.tokenStandard.mergeHoldingUtxossdk.token.utxos.merge
sdk.tokenStandard.fetchPendingAllocationRequestViewsdk.token.allocation.pending(partyId, ALLOCATION_REQUEST_INTERFACE_ID)
sdk.tokenStandard.fetchPendingAllocationInstructionViewsdk.token.allocation.pending(partyId, ALLOCATION_INSTRUCTION_INTERFACE_ID)
sdk.tokenStandard.fetchPendingAllocationViewsdk.token.allocation.pending(partyId)
Token-related method migration

See also

  • wallet-sdk-config - SDK configuration
  • preparing-and-signing-transactions - Transaction lifecycle

User Namespace

The user namespace provides methods for user management on the Canton Network.

Availability

The user namespace is always available as part of the basic SDK interface. It’s initialized automatically when you create an SDK instance and doesn’t require additional configuration via extend().
import { localNetStaticConfig, SDK } from '@canton-network/wallet-sdk'

export default async function () {
    const sdk = await SDK.create({
        auth: TOKEN_PROVIDER_CONFIG_DEFAULT,
        ledgerClientUrl: localNetStaticConfig.LOCALNET_APP_USER_LEDGER_URL,
    })

    const primaryParty = EXISTING_PARTY_1
    const userId = 'user-id'

    // user namespace is immediately available
    await sdk.user.create({ userId, primaryParty })
}

Key changes from v0 to v1

The distinction betwen the user ledger and admin ledger have been removed. Instead, the token is used to determine whether a user has admin rights. Creating users
await sdk.adminLedger.createUser('userId', primaryParty)

const result = await sdk.user
    .create({
      userId: 'userId',
      primaryParty: primaryParty,
      userRights: {...}
    })
Granting user rights
await sdk.userLedger.grantRights([actAsRights], [readAsRights])

const result = await sdk.rights
    .grant({
      userRights: {...},
      userId: {'userId'}, //optional parameter
      idp: {'idp'} //optional parameter otherwise will use default IDP
    })
The below example demonstrates the full usage of the feature:

Migration reference

v0 methodv1 method
sdk.adminLedger.createUsersdk.user.create
sdk.userLedger.grantRightssdk.user.rights.grant
User namespace migration

See also

  • wallet-sdk-config - SDK configuration
  • user management - User management overview

Amulet Namespace

The amulet namespace is used for Canton coin specific operations.

Availability and extensibility

The amulet namespace is an extended namespace that requires configuration. You can initialize it either during SDK creation or later using the extend() method. Option 1: Initialize during SDK creation
const sdk = await SDK.create({
    auth: authConfig,
    ledgerClientUrl: 'http://localhost:2975',
    amulet: {
        validatorUrl: 'http://localhost:2000/api/validator',
        scanApiUrl: 'http://localhost:2000/api/scan',
        auth: amuletAuthConfig,
        registryUrl: 'http://localhost:2000/api/registry'
    }
})

// amulet namespace is now available
await sdk.amulet.traffic.status()
Option 2: Add amulet namespace later using extend()
// Create basic SDK first
const basicSDK = await SDK.create({
    auth: authConfig,
    ledgerClientUrl: 'http://localhost:2975'
})

// Extend with amulet namespace when needed
const extendedSDK = await basicSDK.extend({
    amulet: {
        validatorUrl: 'http://localhost:2000/api/validator',
        scanApiUrl: 'http://localhost:2000/api/scan',
        auth: amuletAuthConfig,
        registryUrl: 'http://localhost:2000/api/registry'
    }
})

// Now amulet namespace is available
await extendedSDK.amulet.traffic.status()

Configuration

The AmuletConfig type defines the configuration for the amulet namespace:
type AmuletConfig = {
    auth: TokenProviderConfig
    validatorUrl: string | URL
    scanApiUrl: string | URL
    registryUrl: URL
}
  • auth: Authentication configuration for accessing the validator and scan services
  • validatorUrl: URL of the validator service
  • scanApiUrl: URL of the scan API
  • registryUrl: URL of the amulet registry

Key changes from v0 to v1

v0 used the tokenStandard controller with implicit party context set via sdk.setPartyId() and the instrumentId and instrumentAdmin were passed in explicitly to each function. v1 uses the amulet namespace where you:
  • Pass partyId explicitly to each operation
  • Initialize the namespace with configuration, which determines the instrumentAdmin and instrumentId
  • Access operations through logical groupings (traffic and preapproval)
Creating preapprovals
const transferPreApprovalProposal =
   await sdk.userLedger?.createTransferPreapprovalCommand(
      validatorOperatorParty!,
      receiver?.partyId!,
      instrumentAdminPartyId
   )

await sdk.userLedger?.prepareSignExecuteAndWaitFor(
   [transferPreApprovalProposal],
   keyPairReceiver.privateKey,
   v4()
)

const createPreapprovalCommand = await sdk.amulet.preapproval.command.create({
   parties: {
      receiver: partyId,
   },
})

await sdk.ledger
      .prepare({
         partyId: partyId,
         commands: createPreapprovalCommand,
      })
      .sign(privateKey)
      .execute({
         partyId: partyId,
      })
The below example demonstrates the full process of renewing and cancelling preapprovals: Buy Member Traffic
const buyMemberTrafficCommand =
   await sdk.tokenStandard.buyMemberTraffic(
      senderPartyId,
      amount,
      participantId,
      inputUtxosOptional
   )

const [buyTrafficCommand, buyTrafficDisclosedContracts] =
   await sdk.amulet.traffic.buy({
      buyer,
      ccAmount,
      inputUtxos: [],
   })
Check Traffic Status
await sdk.tokenStandard.getMemberTrafficStatus(participantId)

await sdk.amulet.traffic.status()
Refer to the following example for more information: Tap The is useful for testing against LocalNet or Devnet.
await sdk.tokenStandard.createTap(partyId,
       amount,
       {
       instrumentId,
       instrumentAdmin
       })

await sdk.amuet.tap(partyId, amount)

Migration reference

v0 methodv1 method
sdk.tokenStandard.getMemberTrafficStatussdk.amulet.traffic.status
sdk.tokenStandard.buyMemberTrafficsdk.amulet.traffic.buy
sdk.userLedger.createTransferPreapprovalCommandsdk.amulet.preapproval.command.create
sdk.tokenStandard.getTransferPreApprovalByPartysdk.amulet.preapproval.fetchStatus
sdk.tokenStandard.createRenewTransferPreapprovalsdk.amulet.preapproval.renew
sdk.tokenStandard.createCancelTransferPreapprovalsdk.amulet.preapproval.command.cancel
sdk.tokenStandard.createTapsdk.amulet.tap
sdk.tokenStandard.lookupFeaturedAppssdk.amulet.featuredApp.rights
sdk.tokenStandard.selfGrantFeatureAppRightssdk.amulet.featuredApp.grant
Amulet namespace migration

See also

  • wallet-sdk-config - SDK configuration
  • user management - User management overview

Ledger Namespace

The ledger namespace is used for preparing, signing, and executing transactions and other Ledger API operations.

Availability

The ledger namespace is always available as part of the basic SDK interface. It’s initialized automatically when you create an SDK instance and doesn’t require additional configuration via extend().
import { localNetStaticConfig, SDK } from '@canton-network/wallet-sdk'

export default async function () {
    const sdk = await SDK.create({
        auth: global.TOKEN_PROVIDER_CONFIG_DEFAULT,
        ledgerClientUrl: localNetStaticConfig.LOCALNET_APP_USER_LEDGER_URL,
        amulet: global.AMULET_NAMESPACE_CONFIG,
    })

    const partyId = EXISTING_PARTY_1
    const privateKey = EXISTING_PARTY_1_KEYS.privateKey

    const [commands, disclosedContracts] = await sdk.amulet.tap(partyId, '200')

    // ledger namespace is immediately available
    await sdk.ledger
        .prepare({ partyId, commands, disclosedContracts })
        .sign(privateKey)
        .execute({ partyId })
}

Key changes from v0 to v1

v0 used the userLedger or adminLedger controller with implicit party context set via sdk.setPartyId(). v1 uses the ledger namespace where you:
  • Pass partyId explicitly to each operation
  • Have an explicit lifecycle with prepare/sign/execute chain instead of a single method
  • Access operations through logical groupings (external, internal, dar, and acs)
Prepare, signing, and executing transactions Previously, a single method would handle everything.
await sdk.userLedger.prepareSignExecuteAndWaitFor(
   commands,
   privateKey,
   workFlowId,
   disclosedContracts
)

await sdk.ledger
      .prepare({
         partyId: partyId,
         commands: [...],
      })
      .sign(privateKey)
      .execute({
         partyId: partyId,
      })
Each step in lifecycle is clearer, workflowIds are generated automatically and there is better typesafety at each step. The below example demonstrates how offline signing works. Active Contract Set (ACS) queries
const contracts = await sdk.userLedger.activeContracts({
   offset,
   templateIds: [...],
   parties: [],
   filterByParty: true
})

const contractId = LedgerController.getActiveContractCid(contracts?.[0]?.contractEntry!)

const contracts = await sdk.ledger.acs.read({
   parties: [...],
   templateIds: [...],
   filterByParty: true
})

const contractId = contracts[0].contractId
No need to manually enter get the ledger end for the offset and there is direct extraction of the activeContracts. However, we still have an acs.readRaw method for unfiltered results. DAR management
const isPackageUploaded = await sdk.userLedger.isPackageUploaded(PACKAGED_ID)

if(!isPackageUploaded){
   await sdk.adminLedger.uploadDar(darBytes)
}

//automatically checks if uploaded and skips if present
await sdk.ledger.dar.upload(darBytes, packageId)

Migration reference

v0 methodv1 method
sdk.userLedger.prepareSignExecuteAndWaitForsdk.ledger.prepare({partyId, commands, disclosedContracts}).sign(privateKey).execute(partyId)
sdk.userLedger.activeContractssdk.ledger.acs.read
sdk.adminLedger.uploadDarsdk.ledger.dar.upload
sdk.userLedger.isPackageUploadedsdk.ledger.dar.check
Ledger namespace migration

See also

  • wallet-sdk-config - SDK configuration
  • preparing-and-signing-transactions - Transaction lifecycle

Asset Namespace

The asset namespace provides methods to discover and find token assets from registries on the Canton Network. In v1, the asset namespace introduces a dedicated API for asset discovery and management.

Key changes from v0 to v1

v0 required accessing asset information through the token standard service or by manually querying registries. v1 introduces a dedicated asset namespace with a clean API for asset discovery. This enables:
  • Simplified asset discovery from multiple registries
  • Type-safe asset information retrieval
  • Centralized asset registry management
  • Built-in error handling for asset not found scenarios

Availability and extensibility

The asset namespace is an extended namespace that requires configuration. You can initialize it either during SDK creation or later using the extend() method. Option 1: Initialize during SDK creation
const sdk = await SDK.create({
    auth: authConfig,
    ledgerClientUrl: 'http://localhost:2975',
    asset: {
        auth: assetAuthConfig,
        registries: [new URL('http://localhost:2000/api/registry')]
    }
})

// asset namespace is now available
const assetList = sdk.asset.list
Option 2: Add asset namespace later using extend()
// Create basic SDK first
const basicSDK = await SDK.create({
    auth: authConfig,
    ledgerClientUrl: 'http://localhost:2975'
})

// Extend with asset namespace when needed
const extendedSDK = await basicSDK.extend({
    asset: {
        auth: assetAuthConfig,
        registries: [new URL('http://localhost:2000/api/registry')]
    }
})

// Now asset namespace is available
const assetList = extendedSDK.asset.list

Configuration

The AssetConfig type defines the configuration for the asset namespace:
type AssetConfig = {
    auth: TokenProviderConfig
    registries: URL[]
}
  • auth: Authentication configuration for accessing registries
  • registries: Array of registry URLs to fetch assets from
You can preview the example config here:

Listing assets

The asset namespace provides a list getter to retrieve all assets from configured registries.
// v0 - manually fetch from token standard service
const tokenStandardService = new TokenStandardService(
    provider, logger, auth, false
)
const assetList = await tokenStandardService.registriesToAssets(
    registries.map((url) => url.href)
)

const assetList = sdk.asset.list

Finding a specific asset

The find method allows you to search for a specific asset by ID, optionally filtering by registry URL.
// v0 - manually filter assets
const tokenStandardService = new TokenStandardService(
    provider, logger, auth, false
)
const assets = await tokenStandardService.registriesToAssets(
    registries.map((url) => url.href)
)
const amuletAsset = assets.find(
    (asset) => asset.id === 'Amulet'
)

const amuletAsset = await sdk.asset.find('Amulet')

Finding an asset with a specific registry

When multiple registries contain assets with the same ID, you can specify the registry URL to disambiguate:
const amuletAsset = await sdk.asset.find(
    'Amulet',
    new URL('https://registry.example.com')
)

Error handling

The asset namespace includes built-in error handling for common scenarios: Asset not found If an asset with the specified ID does not exist in any registry:
try {
    const unknownAsset = await sdk.asset.find('NonExistentAsset')
} catch (error) {
    // SDKError with type 'NotFound'
    // message: 'Asset with id NonExistentAsset not found'
}
Multiple assets found If multiple assets with the same ID exist across different registries and no registry URL is provided:
try {
    const duplicateAsset = await sdk.asset.find('CommonAsset')
} catch (error) {
    // SDKError with type 'BadRequest'
    // message: 'Multiple assets found, please provide a registryUrl'
}

Usage example

In the below example you can find the usage of the find method:

Migration reference

v0 approachv1 method
tokenStandardService.registriesToAssets()sdk.asset.list
Manual array filtering for specific assetasset.find(id, registryUrl?)
Manual error handling for missing assetsBuilt-in error handling in sdk.asset.find()
Asset-related method migration

See also

  • wallet-sdk-config - SDK configuration
  • token-migration-v1 - Token namespace migration