Finding and Reading Data

The wallet SDK primarily focus on an on-party basis interaction, therefore it is almost always required to define the party you are using. You can however create a party without defining a party, otherwise you have to set the party as done below:

import {
    WalletSDKImpl,
    localNetAuthDefault,
    localNetLedgerDefault,
} from '@canton-network/wallet-sdk'

// @disable-snapshot-test
export default async function () {
    const sdk = new WalletSDKImpl().configure({
        logger: console,
        authFactory: localNetAuthDefault,
        ledgerFactory: localNetLedgerDefault,
    })
    await sdk.connect()

    await sdk.setPartyId('my-wallet-1')
}

Reading Available Parties

Reading all available parties to you can easily be done using the wallet SDK as shown in the example below, and the result is paginated. It’s worth noting that the call to read all available parties doesn’t use the the party and synchronizer fields therefore changing them has no effect on the result.

import {
    WalletSDKImpl,
    localNetAuthDefault,
    localNetLedgerDefault,
} from '@canton-network/wallet-sdk'

// @disable-snapshot-test
export default async function () {
    const sdk = new WalletSDKImpl().configure({
        logger: console,
        authFactory: localNetAuthDefault,
        ledgerFactory: localNetLedgerDefault,
    })

    await sdk.connect()

    return await sdk.userLedger?.listWallets()
}

Reading Ledger End

A lot of different requests will take a ledger offset to ensure the requested time correlates with ledger time. A Validator does not have a block height since there is no total state replication. There are two values that correlate:

  • ledger time - this is the time the ledger chooses when computing a transaction prior to commit.

  • record time - this is the time assigned by the sequencer when registering the confirmation request.

Ledger time should be used for all operations in your local environment (that does not affect partners). When doing reconciliation for transactions with partners or other members of a synchronizer it is better to use record time.

Ledger end can easily be derived from with the wallet SDK:

import {
    WalletSDKImpl,
    localNetAuthDefault,
    localNetLedgerDefault,
} from '@canton-network/wallet-sdk'

// @disable-snapshot-test
export default async function () {
    const sdk = new WalletSDKImpl().configure({
        logger: console,
        authFactory: localNetAuthDefault,
        ledgerFactory: localNetLedgerDefault,
    })
    await sdk.connect()

    return await sdk.userLedger?.ledgerEnd()
}

Reading Active Contracts

Using the above ledger time we can figure out what the current state of all active contracts are. Contracts can be in two states - active and archived - which correlates to the UTXO mode of unspent and spent. Active contracts are contracts that are unspent and thereby can be used in new transactions or to exercise choices.

import {
    WalletSDKImpl,
    localNetAuthDefault,
    localNetLedgerDefault,
} from '@canton-network/wallet-sdk'

// @disable-snapshot-test
export default async function () {
    const sdk = new WalletSDKImpl().configure({
        logger: console,
        authFactory: localNetAuthDefault,
        ledgerFactory: localNetLedgerDefault,
    })
    await sdk.connect()

    const offset = 100 // you can use the sdk.userLedger.ledgerEnd() to get the latest offset

    const allActiveContracts = await sdk.userLedger?.activeContracts({ offset })

    const myTemplateId = 'your-template-id-here'

    return await sdk.userLedger?.activeContracts({
        offset,
        templateIds: [myTemplateId], //this is optional for if you want to filter by template id
    })
}

Visualizing a Transaction

The Wallet SDK uses a transaction parsing transform a fully fledged transaction tree into human recognizable transaction view. The full code for the transaction parsing can be found at parser typescript class.

The Wallet SDK uses this parser to transform all transaction tree interacted with into PrettyTransactions.

for instance on the getTransactionById or listHoldingTransactions (Detailed here).

The Transactions will have format:

export interface Transaction {
    updateId: string // unique updateId
    offset: number // the ledger offset (local validator)
    recordTime: string // time recorded on the synchronizer (use this if needed to compare with another ledger)
    synchronizerId: string // the synchronizer the transaction happened on
    events: TokenStandardEvent[] // event representing all the changes caused by the transaction
}

A single transaction can contain multiple events (deposits and withdrawals are considered events). In order to figure out the on chain transaction it is required to iterate over all the events. The events have the format:

export interface TokenStandardEvent {
    label: Label // used to identify the type of transaction
    lockedHoldingsChange: HoldingsChange // all the changes to locked holdings
    lockedHoldingsChangeSummary: HoldingsChangeSummary // summary of above changes
    unlockedHoldingsChange: HoldingsChange // all the changes to unlocked holdings
    unlockedHoldingsChangeSummary: HoldingsChangeSummary // summary of above changes
    transferInstruction: TransferInstructionView | null // any pending transfer instructions
}

below you can have a look at different event types and how to potentially visualize the transaction for a client

Here is an example on how a “tap” event looks like (Performing tap):

{
    "updateId": "1220a8d78d06461abd045813491f9997a1bcf2f29d4c2a9afadeb89616998201b40a",
    "offset": 1313,
    "recordTime": "2025-10-14T02:11:45.485840Z",
    "synchronizerId": "global-domain::1220294d264ccf205000d72d9f0106e3a0e8ce8d34982d7f134c42d42d18750ccd36",
    "events": [
        {
            "label": {
                "burnAmount": "0",
                "mintAmount": "2000000",
                "type": "Mint",
                "tokenStandardChoice": null,
                "reason": "tapped faucet",
                "meta": {
                    "values": {}
                }
            },
            "unlockedHoldingsChange": {
                "creates": [
                    {
                        "amount": "2000000.0000000000",
                        "instrumentId": {
                            "admin": "DSO::1220294d264ccf205000d72d9f0106e3a0e8ce8d34982d7f134c42d42d18750ccd36",
                            "id": "Amulet"
                        },
                        "contractId": "00cee8d2659d5966962fbda321aae358092eafbb162d46f2639a8da0688ef3ee8aca11122096aeb27e0fa9c03a3209fda9db88e4e67a2ba1509f094bdd82a38d844ca65305",
                        "owner": "alice::12201acb807c49aceaeb68b1d89bb3bea95fe740b4b0a6cca428e6a351c2450540f4",
                        "meta": {
                            "values": {
                                "amulet.splice.lfdecentralizedtrust.org/created-in-round": "32",
                                "amulet.splice.lfdecentralizedtrust.org/rate-per-round": "0.00380518"
                            }
                        },
                        "lock": null
                    }
                ]
            },
            "unlockedHoldingsChangeSummary": {
                "numOutputs": 1,
                "outputAmount": "2000000",
                "amountChange": "2000000"
            },
            "transferInstruction": null
        }
    ]
}

The tap gives a nice and simple view some key values to look at. Using the label we can quickly gage what is happening:

"label": {
    "burnAmount": "0", // how much was burned
    "mintAmount": "2000000", // how much was minted
    "type": "Mint", // event type
    "tokenStandardChoice": null, // no token standard choice
    "reason": "tapped faucet", // reason
    "meta": {
        "values": {} // any other meta data value
    }
}

For a “tap” event we don’t have any locked holding changes, however we do have an unlocked create event:

"unlockedHoldingsChange": {
    // we have one create event
    // if utxos what spend this would be an archive instead
    "creates": [
        {
            // amount on the utxo
            "amount": "2000000.0000000000",
            // instrument information
            "instrumentId": {
                "admin": "DSO::1220294d264ccf205000d72d9f0106e3a0e8ce8d34982d7f134c42d42d18750ccd36",
                "id": "Amulet"
            },
            // the contract id of the new utxo
            "contractId": "00cee8d2659d5966962fbda321aae358092eafbb162d46f2639a8da0688ef3ee8aca11122096aeb27e0fa9c03a3209fda9db88e4e67a2ba1509f094bdd82a38d844ca65305",
            // owner of the utxo
            "owner": "alice::12201acb807c49aceaeb68b1d89bb3bea95fe740b4b0a6cca428e6a351c2450540f4",
            // any meta data
            "meta": {
                "values": {
                    "amulet.splice.lfdecentralizedtrust.org/created-in-round": "32",
                    "amulet.splice.lfdecentralizedtrust.org/rate-per-round": "0.00380518"
                }
            },
            // lock if applicable
            "lock": null
        }
    ]
}