### Integrate with Canton Wallet SDK (OAuth) Source: https://docs.digitalasset.com/integrate/devnet/party-management/index This example demonstrates integrating with the Canton Wallet SDK, potentially for OAuth flows, though the provided code focuses on localnet setup and party generation. It requires '@canton-network/wallet-sdk' and 'uuid'. It configures the SDK with console logging, local authentication, ledger, topology, and token standard factories. It connects to the ledger, admin ledger, and topology, then generates an external party using a key pair. ```typescript import { v4 } from 'uuid' import { localAuthDefault, localLedgerDefault, localTopologyDefault, WalletSDKImpl, createKeyPair, signTransactionHash, localTokenStandardDefault, } from '@canton-network/wallet-sdk' // it is important to configure the SDK correctly else you might run into connectivity or authentication issues const sdk = new WalletSDKImpl().configure({ logger: console, authFactory: () => localAuthDefault(console), ledgerFactory: localLedgerDefault, topologyFactory: localTopologyDefault, tokenStandardFactory: localTokenStandardDefault, }) const fixedLocalNetSynchronizer = 'wallet::1220e7b23ea52eb5c672fb0b1cdbc916922ffed3dd7676c223a605664315e2d43edd' console.log('SDK initialized') await sdk.connect() console.log('Connected to ledger') await sdk.userLedger ?.listWallets() .then((wallets) => { console.log('Wallets:', wallets) }) .catch((error) => { console.error('Error listing wallets:', error) }) await sdk.connectAdmin() console.log('Connected to admin ledger') await sdk.adminLedger ?.listWallets() .then((wallets) => { console.log('Wallets:', wallets) }) .catch((error) => { console.error('Error listing wallets:', error) }) await sdk.connectTopology(fixedLocalNetSynchronizer) console.log('Connected to topology') const keyPair = createKeyPair() console.log('generated keypair') const generatedParty = await sdk.userLedger?.generateExternalParty( keyPair.publicKey ) if (!generatedParty) { throw new Error('Error generating external party topology') } ``` -------------------------------- ### Example 'Tap' Transaction Data (JSON) Source: https://docs.digitalasset.com/integrate/devnet/finding-and-reading-data/index Illustrates the structure of a 'tap' event within a transaction. This example shows the detailed breakdown of a mint operation, including amounts, instrument details, and meta-data. ```json { "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 } ] } ``` -------------------------------- ### Allocate Party with Splice LocalNet (Quick) Source: https://docs.digitalasset.com/integrate/devnet/party-management/index This quick example demonstrates allocating a party using Splice LocalNet with minimal configuration. It requires the '@canton-network/wallet-sdk' package. It initializes the SDK, connects to the ledger and topology, generates a key pair, and then directly signs and allocates an external party with an optional party hint. ```typescript import { WalletSDKImpl, createKeyPair, localNetAuthDefault, localNetLedgerDefault, localNetTopologyDefault, localNetStaticConfig, } from '@canton-network/wallet-sdk' export default async function () { // it is important to configure the SDK correctly else you might run into connectivity or authentication issues const sdk = new WalletSDKImpl().configure({ logger: console, authFactory: localNetAuthDefault, // or use your specific configuration ledgerFactory: localNetLedgerDefault, // or use your specific configuration }) await sdk.connect() await sdk.connectTopology(localNetStaticConfig.LOCALNET_SCAN_PROXY_API_URL) const key = createKeyPair() // partyHint is optional but recommended to make it easier to identify the party const partyHint = 'my-wallet-1' const party = await sdk.userLedger?.signAndAllocateExternalParty( key.privateKey, partyHint ) } ``` -------------------------------- ### Install Canton dApp SDK Source: https://docs.digitalasset.com/integrate/devnet/integrating-with-canton-network/index Installs the Canton dApp SDK package from the NPM registry using npm, yarn, or pnpm. This SDK is optimized for browser usage and is suitable for dApp development. ```bash npm install @canton-network/dapp-sdk ``` ```bash yarn add @canton-network/dapp-sdk ``` ```bash pnpm add @canton-network/dapp-sdk ``` -------------------------------- ### Update Party Onboarding Method in SDK Source: https://docs.digitalasset.com/integrate/devnet/release-notes/index Party onboarding can now be performed on the ledgerController instead of the TopologyController, removing the need for gRPC admin access. This section details the method changes and provides examples for single and multi-hosted configurations. ```javascript //previous example of multi hosting const multiHostedParticipantEndpointConfig = [ { adminApiUrl: '127.0.0.1:2902', //this is the ledger we actual call to allocate baseUrl: new URL('http://127.0.0.1:2975'), accessToken: adminToken.accessToken, }, { adminApiUrl: '127.0.0.1:3902', baseUrl: new URL('http://127.0.0.1:3975'), accessToken: adminToken.accessToken, }, ] //new example of multi hosting const multiHostedParticipantEndpointConfig = [ { //admin url is not needed anymore url: new URL('http://127.0.0.1:3975'), accessToken: adminToken.accessToken, }, ] ``` -------------------------------- ### Setup Multi-Hosting for a Party in JavaScript Source: https://docs.digitalasset.com/integrate/devnet/release-notes/index Configures and prepares a party for multi-hosting against multiple validators using the SDK. It involves setting up endpoint configurations, acquiring participant IDs, mapping permissions, and submitting the multi-host external party request. Dependencies include the SDK and admin token. ```javascript const multiHostedParticipantEndpointConfig = [ { adminApiUrl: '127.0.0.1:2902', baseUrl: new URL('http://127.0.0.1:2975'), accessToken: adminToken.accessToken, }, { adminApiUrl: '127.0.0.1:3902', baseUrl: new URL('http://127.0.0.1:3975'), accessToken: adminToken.accessToken, }, ] const participantIdPromises = multiHostedParticipantEndpointConfig.map( async (endpoint) => { return await sdk.topology?.getParticipantId(endpoint) } ) const participantIds = await Promise.all(participantIdPromises) const participantPermissionMap = new Map() // decide on Permission for each participant participantIds.map((pId) => participantPermissionMap.set(pId!, Enums_ParticipantPermission.CONFIRMATION) ) // setup multi-hosting for a party against await sdk.topology?.prepareSignAndSubmitMultiHostExternalParty( multiHostedParticipantEndpointConfig, multiHostedParty.privateKey, synchronizerId, participantPermissionMap, 'bob' ) ``` -------------------------------- ### Install Canton Wallet SDK Source: https://docs.digitalasset.com/integrate/devnet/integrating-with-canton-network/index Installs the Canton Wallet SDK package from the NPM registry using npm, yarn, or pnpm. This SDK is essential for wallet providers to integrate with the Canton Network. ```bash npm install @canton-network/wallet-sdk ``` ```bash yarn add @canton-network/wallet-sdk ``` ```bash pnpm add @canton-network/wallet-sdk ``` -------------------------------- ### Allocate Party with Splice LocalNet (Comprehensive) Source: https://docs.digitalasset.com/integrate/devnet/party-management/index This comprehensive example demonstrates allocating a party using Splice LocalNet with detailed logging and command submission. It requires the '@canton-network/wallet-sdk' and 'uuid' packages. It initializes the SDK, connects to the ledger and topology, generates a key pair, prepares and allocates an external party, signs the transaction hash, and executes a ping command submission. ```typescript import { WalletSDKImpl, localNetAuthDefault, localNetLedgerDefault, localNetTokenStandardDefault, createKeyPair, signTransactionHash, localNetStaticConfig, } from '@canton-network/wallet-sdk' import { v4 } from 'uuid' import { pino } from 'pino' const logger = pino({ name: '02-auth-localnet', level: 'info' }) // it is important to configure the SDK correctly else you might run into connectivity or authentication issues const sdk = new WalletSDKImpl().configure({ logger: logger, authFactory: localNetAuthDefault, ledgerFactory: localNetLedgerDefault, tokenStandardFactory: localNetTokenStandardDefault, }) logger.info('SDK initialized') await sdk.connect() logger.info('Connected to ledger') await sdk.connectTopology(localNetStaticConfig.LOCALNET_SCAN_PROXY_API_URL) logger.info('Connected to topology') const keyPair = createKeyPair() logger.info('generated keypair') const generatedParty = await sdk.userLedger?.generateExternalParty( keyPair.publicKey ) if (!generatedParty) { throw new Error('Error creating prepared party') } logger.info('Signing the hash') const signedHash = signTransactionHash( generatedParty.multiHash, keyPair.privateKey ) const allocatedParty = await sdk.userLedger?.allocateExternalParty( signedHash, generatedParty ) logger.info({ partyId: allocatedParty!.partyId }, 'Allocated party') await sdk.setPartyId(allocatedParty!.partyId!) logger.info(allocatedParty, 'Create ping command for party') const createPingCommand = sdk.userLedger?.createPingCommand( allocatedParty!.partyId! ) logger.info('Prepare command submission for ping create command') const prepareResponse = await sdk.userLedger?.prepareSubmission(createPingCommand) logger.info('Sign transaction hash') const signedCommandHash = signTransactionHash( prepareResponse!.preparedTransactionHash!, keyPair.privateKey ) logger.info('Submit command') const response = await sdk.userLedger?.executeSubmissionAndWaitFor( prepareResponse!, signedCommandHash, keyPair.publicKey, v4() ) logger.info(response, 'Executed command submission succeeded') ``` -------------------------------- ### Simplify Party and Synchronizer Setup (TypeScript) Source: https://docs.digitalasset.com/integrate/devnet/release-notes/index This snippet shows the simplified method for setting party and synchronizer IDs. The new `sdk.setPartyId()` function consolidates multiple previous calls, making initialization more straightforward. The synchronizer ID is now optional. ```typescript //the connects are still needed and should be run before sdk.setPartyId await sdk.connect() await sdk.connectAdmin() await sdk.connectTopology(LOCALNET_SCAN_API_URL) //Previously all these was required to get everything working sdk.userLedger!.setPartyId(partyId) sdk.userLedger!.setSynchronizerId(synchronizerId) sdk.tokenStandard?.setPartyId(partyId) sdk.tokenStandard?.setSynchronizerId(synchronizerId) sdk.validator?.setPartyId(partyId) sdk.validator?.setSynchronizerId(synchronizerId) //New version await sdk.setPartyId(partyId,synchronizerId) //synchronizerId is optional, it will automatically select the first synchronizerId, //that the party is connected to if, none is defined ``` -------------------------------- ### Configure LocalNet SDK with Static Configuration Source: https://docs.digitalasset.com/integrate/devnet/release-notes/index This snippet demonstrates how to initialize and configure the WalletSDKImpl using localNet static configurations for an easier local development setup. It shows connecting to the topology proxy and setting the transfer factory registry URL. Dependencies include WalletSDKImpl and various localNet configuration objects from '@canton-network/wallet-sdk'. ```typescript import { WalletSDKImpl, localNetAuthDefault, localNetLedgerDefault, localNetTopologyDefault, localNetTokenStandardDefault, localNetStaticConfig, } from '@canton-network/wallet-sdk' const sdk = new WalletSDKImpl().configure({ logger, authFactory: localNetAuthDefault, ledgerFactory: localNetLedgerDefault, topologyFactory: localNetTopologyDefault, tokenStandardFactory: localNetTokenStandardDefault, }) await sdk.connectTopology(localNetStaticConfig.LOCALNET_SCAN_PROXY_API_URL) sdk.tokenStandard?.setTransferFactoryRegistryUrl( localNetStaticConfig.LOCALNET_REGISTRY_API_URL ) ``` -------------------------------- ### Setup Transfer Preapproval and Execute Transfer with Canton Wallet SDK Source: https://docs.digitalasset.com/integrate/devnet/token-standard/index This TypeScript code snippet initializes the Canton Wallet SDK, creates sender and receiver parties, sets up transfer preapproval with the validator, and executes a 1-step transfer from Alice to Bob. It logs various steps and transaction details. Dependencies include '@canton-network/wallet-sdk' and 'pino'. ```typescript import { WalletSDKImpl, localNetAuthDefault, localNetLedgerDefault, localNetTopologyDefault, localNetTokenStandardDefault, createKeyPair, localValidatorDefault, localNetStaticConfig, LedgerController, } from '@canton-network/wallet-sdk' import { pino } from 'pino' import { v4 } from 'uuid' const logger = pino({ name: '05-external-party-setup', level: 'info' }) // it is important to configure the SDK correctly else you might run into connectivity or authentication issues const sdk = new WalletSDKImpl().configure({ logger, authFactory: localNetAuthDefault, ledgerFactory: localNetLedgerDefault, topologyFactory: localNetTopologyDefault, tokenStandardFactory: localNetTokenStandardDefault, validatorFactory: localValidatorDefault, }) logger.info('SDK initialized') await sdk.connect() logger.info('Connected to ledger') const keyPairSender = createKeyPair() const keyPairReceiver = createKeyPair() await sdk.connectAdmin() await sdk.connectTopology(localNetStaticConfig.LOCALNET_SCAN_PROXY_API_URL) const sender = await sdk.userLedger?.signAndAllocateExternalParty( keyPairSender.privateKey, 'alice' ) logger.info(`Created party: ${sender!.partyId}`) await sdk.setPartyId(sender!.partyId) sender?.topologyTransactions!.map((topologyTx) => { const decodedTx = LedgerController.toDecodedTopologyTransaction(topologyTx) logger.info(decodedTx) }) const receiver = await sdk.userLedger?.signAndAllocateExternalParty( keyPairReceiver.privateKey, 'bob' ) logger.info(`Created party: ${receiver!.partyId}`) await sdk.userLedger ?.listWallets() .then((wallets) => { logger.info(wallets, 'Wallets:') }) .catch((error) => { logger.error({ error }, 'Error listing wallets') }) sdk.tokenStandard?.setTransferFactoryRegistryUrl( localNetStaticConfig.LOCALNET_REGISTRY_API_URL ) await sdk.setPartyId(receiver?.partyId!) const validatorOperatorParty = await sdk.validator?.getValidatorUser() const instrumentAdminPartyId = (await sdk.tokenStandard?.getInstrumentAdmin()) || '' await new Promise((res) => setTimeout(res, 5000)) logger.info('creating transfer preapproval proposal') await sdk.setPartyId(validatorOperatorParty!) await sdk.tokenStandard?.createAndSubmitTapInternal( validatorOperatorParty!, '20000000', { instrumentId: 'Amulet', instrumentAdmin: instrumentAdminPartyId, } ) await sdk.setPartyId(receiver?.partyId!) const transferPreApprovalProposal = await sdk.userLedger?.createTransferPreapprovalCommand( validatorOperatorParty!, receiver?.partyId!, instrumentAdminPartyId ) await sdk.userLedger?.prepareSignExecuteAndWaitFor( [transferPreApprovalProposal], keyPairReceiver.privateKey, v4() ) logger.info('transfer pre approval proposal is created') await sdk.setPartyId(sender?.partyId!) const [tapCommand, disclosedContracts] = await sdk.tokenStandard!.createTap( sender!.partyId, '20000000', { instrumentId: 'Amulet', instrumentAdmin: instrumentAdminPartyId, } ) await sdk.userLedger?.prepareSignExecuteAndWaitFor( tapCommand, keyPairSender.privateKey, v4(), disclosedContracts ) const utxos = await sdk.tokenStandard?.listHoldingUtxos() logger.info(utxos, 'List Token Standard Holding UTXOs') await sdk.tokenStandard ?.listHoldingTransactions() .then((transactions) => { logger.info(transactions, 'Token Standard Holding Transactions:') }) .catch((error) => { logger.error( { error }, 'Error listing token standard holding transactions:' ) }) logger.info('Creating transfer transaction') const [transferCommand, disclosedContracts2] = await sdk.tokenStandard!.createTransfer( sender!.partyId, receiver!.partyId, '100', { instrumentId: 'Amulet', instrumentAdmin: instrumentAdminPartyId, }, [], 'memo-ref' ) await sdk.userLedger?.prepareSignExecuteAndWaitFor( transferCommand, keyPairSender.privateKey, v4(), disclosedContracts2 ) logger.info('Submitted transfer transaction') await sdk.setPartyId(validatorOperatorParty!) const validatorFeatureAppRights = await sdk.tokenStandard!.grantFeatureAppRightsForInternalParty() logger.info( validatorFeatureAppRights, `Featured App Rights for validator ${validatorOperatorParty}` ) { await sdk.setPartyId(sender!.partyId) const aliceHoldings = await sdk.tokenStandard?.listHoldingTransactions() logger.info(aliceHoldings, '[ALICE] holding transactions') } ``` -------------------------------- ### Prepare and Execute Transaction Submission with Wallet SDK Source: https://docs.digitalasset.com/integrate/devnet/signing-transactions-from-dapps/index This snippet demonstrates how to prepare and execute a transaction submission using the Canton Wallet SDK. It involves connecting to the SDK, setting the party ID, preparing the transaction, signing its hash, and executing the submission with the signature. Ensure the Wallet SDK is installed and configured. ```typescript import { WalletSDKImpl, localNetAuthDefault, localNetLedgerDefault, signTransactionHash, } from '@canton-network/wallet-sdk' import { v4 } from 'uuid' export default async function () { const sdk = new WalletSDKImpl().configure({ logger: console, authFactory: localNetAuthDefault, ledgerFactory: localNetLedgerDefault, }) const preparedCommand = global.PREPARED_COMMAND const keys = global.EXISTING_PARTY_1_KEYS const myParty = global.EXISTING_PARTY_1 await sdk.connect() await sdk.setPartyId(myParty) const preparedTransaction = await sdk.userLedger!.prepareSubmission( preparedCommand, //the incoming command v4() //a unique deduplication id for this transaction ) const signature = signTransactionHash( preparedTransaction!.preparedTransactionHash ?? '', keys.privateKey ) //if client calls ``prepareExecute`` then this is how they would call ``execute`` await sdk.userLedger!.executeSubmission( preparedTransaction!, signature, keys.publicKey, v4() ) } ``` -------------------------------- ### Setup Users, Parties, and Permissions with Wallet SDK (TypeScript) Source: https://docs.digitalasset.com/integrate/devnet/user-management/index This script initializes three users (alice, bob, master) and their corresponding internal and external parties. It grants master user 'canReadAsAnyParty' and 'canExecuteAsAnyParty' rights. The script then sets up separate SDK instances for each user with their authentication details and connects them to the network. Finally, it verifies that the master user can see the parties created by alice and bob. ```typescript import { WalletSDKImpl, localNetAuthDefault, localNetLedgerDefault, localNetTopologyDefault, localNetTokenStandardDefault, createKeyPair, localNetStaticConfig, AuthController, UnsafeAuthController, } from '@canton-network/wallet-sdk' import { Logger, pino } from 'pino' import { v4 } from 'uuid' const logger = pino({ name: '04-token-standard-localnet', level: 'info' }) // it is important to configure the SDK correctly else you might run into connectivity or authentication issues const operatorSDK = new WalletSDKImpl().configure({ logger, }) logger.info('Operator sets up users and primary parties') await operatorSDK.connect() await operatorSDK.connectAdmin() await operatorSDK.connectTopology( localNetStaticConfig.LOCALNET_SCAN_PROXY_API_URL ) const aliceInternal = await operatorSDK.adminLedger!.allocateInternalParty('alice') const bobInternal = await operatorSDK.adminLedger!.allocateInternalParty('bob') const masterUserInternal = await operatorSDK.adminLedger!.allocateInternalParty('master-user') const aliceUser = await operatorSDK.adminLedger!.createUser( 'alice-user', aliceInternal ) const bobUser = await operatorSDK.adminLedger!.createUser( 'bob-user', bobInternal ) const masterUser = await operatorSDK.adminLedger!.createUser( 'master-user', masterUserInternal ) await operatorSDK.adminLedger!.grantMasterUserRights(masterUser.id, true, true) logger.info( `Created alice user: ${aliceUser.id} with primary party (internal) ${aliceUser.primaryParty}` ) logger.info( `Created bob user: ${bobUser.id} with primary party (internal) ${bobUser.primaryParty}` ) logger.info( `Created master user: ${masterUser.id} with primary party (internal) ${masterUser.primaryParty}, with read as and execute as rights` ) //create a SDK for each user with their own auth factory const aliceSDK = new WalletSDKImpl().configure({ logger, authFactory: (logger?: Logger): AuthController => { const controller = new UnsafeAuthController(logger) controller.userId = aliceUser.id controller.adminId = aliceUser.id controller.audience = 'https://canton.network.global' controller.unsafeSecret = 'unsafe' return controller }, }) await aliceSDK.connect() await aliceSDK.connectTopology(localNetStaticConfig.LOCALNET_SCAN_PROXY_API_URL) aliceSDK.tokenStandard?.setTransferFactoryRegistryUrl( localNetStaticConfig.LOCALNET_REGISTRY_API_URL ) const bobSDK = new WalletSDKImpl().configure({ logger, authFactory: (logger?: Logger): AuthController => { const controller = new UnsafeAuthController(logger) controller.userId = bobUser.id controller.adminId = bobUser.id controller.audience = 'https://canton.network.global' controller.unsafeSecret = 'unsafe' return controller }, }) await bobSDK.connect() await bobSDK.connectTopology(localNetStaticConfig.LOCALNET_SCAN_PROXY_API_URL) bobSDK.tokenStandard?.setTransferFactoryRegistryUrl( localNetStaticConfig.LOCALNET_REGISTRY_API_URL ) const masterUserSDK = new WalletSDKImpl().configure({ logger, authFactory: (logger?: Logger): AuthController => { const controller = new UnsafeAuthController(logger) controller.userId = masterUser.id controller.adminId = masterUser.id controller.audience = 'https://canton.network.global' controller.unsafeSecret = 'unsafe' return controller }, }) await masterUserSDK.connect() await masterUserSDK.connectTopology( localNetStaticConfig.LOCALNET_SCAN_PROXY_API_URL ) masterUserSDK.tokenStandard?.setTransferFactoryRegistryUrl( localNetStaticConfig.LOCALNET_REGISTRY_API_URL ) logger.info('connected ledger only SDK for each user') const aliceKeyPair = createKeyPair() const bobKeyPair = createKeyPair() const alice = await aliceSDK.userLedger?.signAndAllocateExternalParty( aliceKeyPair.privateKey, 'alice' ) logger.info(`Created party: ${alice!.partyId}`) await aliceSDK.setPartyId(alice!.partyId) const bob = await bobSDK.userLedger?.signAndAllocateExternalParty( bobKeyPair.privateKey, 'bob' ) logger.info(`Created party: ${bob!.partyId}`) await bobSDK.setPartyId(bob!.partyId) logger.info('alice and bob each create an external party') const masterWalletView = await masterUserSDK.userLedger?.listWallets() if (!masterWalletView?.find((p) => p === alice!.partyId)) { throw new Error('master user cannot see alice party') } if (!masterWalletView?.find((p) => p === bob!.partyId)) { throw new Error('master user cannot see bob party') } ``` -------------------------------- ### Query Recovery Offset using grpcurl Source: https://docs.digitalasset.com/integrate/devnet/exchange-integration/disaster-recovery This example shows how to use `grpcurl` to query the recovery offset from the Canton Admin gRPC API. It requires `grpcurl` to be installed and accessible, and the correct gRPC service endpoint. Authentication flags may be needed if the API is secured. ```bash grpcurl -plaintext -d \ '{"synchronizerId" : "example::1220be58c29e65de40bf273be1dc2b266d43a9a002ea5b18955aeef7aac881bb471a", "timestamp": "2025-11-27T06:50:00.000Z"}' \ localhost:5002 \ com.digitalasset.canton.admin.participant.v30.PartyManagementService.GetHighestOffsetByTimestamp ``` -------------------------------- ### Prepare and Submit Topology Transactions Source: https://docs.digitalasset.com/integrate/devnet/party-management/index This snippet demonstrates preparing and submitting topology transactions for external signing. It involves generating a key pair, allocating an external party, creating a ping command, preparing its submission, signing the transaction hash, and finally executing the submission. Dependencies include the Wallet SDK and cryptographic key pair generation. ```javascript console.log('Prepared external topology') console.log('Signing the hash') const { partyId, multiHash } = generatedParty const signedHash = signTransactionHash(multiHash, keyPair.privateKey) await sdk.userLedger ?.allocateExternalParty(signedHash, generatedParty) .then((allocatedParty) => { console.log('Alocated party ', allocatedParty.partyId) }) console.log('Create ping command') const createPingCommand = await sdk.userLedger?.createPingCommand(partyId) sdk.setPartyId(partyId) console.log('Prepare command submission for ping create command') const prepareResponse = await sdk.adminLedger?.prepareSubmission(createPingCommand) console.log('Sign transaction hash') const signedCommandHash = signTransactionHash( prepareResponse!.preparedTransactionHash!, keyPair.privateKey ) console.log('Submit command') sdk.adminLedger ?.executeSubmission( prepareResponse!, signedCommandHash, keyPair.publicKey, v4() ) .then((executeSubmissionResponse) => { console.log( 'Executed command submission succeeded', executeSubmissionResponse ) }) .catch((error) console.error('Failed to submit command with error %d', error) ) ``` -------------------------------- ### GET /v2/updates/update-by-id Source: https://docs.digitalasset.com/integrate/devnet/exchange-integration/txingestion Retrieves a single transaction update from the JSON Ledger API. This endpoint is useful for understanding the structure of 1-step transfers and can be used with the same filters as streaming transactions via /v2/updates/flats. ```APIDOC ## GET /v2/updates/update-by-id ### Description Retrieves a single transaction update from the JSON Ledger API. This endpoint is useful for understanding the structure of 1-step transfers and can be used with the same filters as streaming transactions via `/v2/updates/flats`. ### Method POST ### Endpoint `http://json-api-url/v2/updates/update-by-id` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **updateId** (string) - Required - The ID of the update to retrieve. - **updateFormat** (object) - Required - Specifies the format of the update data to be returned. - **includeTransactions** (object) - Required - Includes transaction details in the response. - **transactionShape** (string) - Required - The shape of the transaction data. Use `TRANSACTION_SHAPE_LEDGER_EFFECTS` for detailed ledger effects. - **eventFormat** (object) - Required - Specifies the format for events within the transaction. - **filtersByParty** (object) - Optional - Filters events by party. - **``** (object) - Required - The treasury party identifier. - **cumulative** (array) - Required - A list of filters to apply cumulatively. - **identifierFilter** (object) - Required - Filter by identifier. - **WildcardFilter** (object) - Optional - A wildcard filter. - **value** (object) - Required - The wildcard value. - **includeCreatedEventBlob** (boolean) - Optional - Whether to include the created event blob. - **InterfaceFilter** (object) - Optional - Filter by interface. - **value** (object) - Required - The interface filter value. - **interfaceId** (string) - Required - The ID of the interface. - **includeInterfaceView** (boolean) - Optional - Whether to include the interface view. - **includeCreatedEventBlob** (boolean) - Optional - Whether to include the created event blob. - **verbose** (boolean) - Optional - If true, returns verbose event data. ### Request Example ```json { "updateId": "", "updateFormat": { "includeTransactions": { "transactionShape": "TRANSACTION_SHAPE_LEDGER_EFFECTS", "eventFormat": { "filtersByParty": { "": { "cumulative": [ {"identifierFilter": {"WildcardFilter": {"value": {"includeCreatedEventBlob": false}}}}, {"identifierFilter": {"InterfaceFilter": {"value": {"interfaceId": "#splice-api-token-transfer-instruction-v1:Splice.Api.Token.TransferInstructionV1:TransferFactory", "includeInterfaceView": true, "includeCreatedEventBlob": false}}}}, {"identifierFilter": {"InterfaceFilter": {"value": {"interfaceId": "#splice-api-token-holding-v1:Splice.Api.Token.HoldingV1:Holding", "includeInterfaceView": true, "includeCreatedEventBlob": false}}}}, {"identifierFilter": {"InterfaceFilter": {"value": {"interfaceId": "#splice-api-token-transfer-instruction-v1:Splice.Api.Token.TransferInstructionV1:TransferInstruction", "includeInterfaceView": true, "includeCreatedEventBlob": false}}}} ] } }, "verbose": true } } } } ``` ### Response #### Success Response (200) - **update** (object) - Contains the transaction update details. - **Transaction** (object) - Represents the transaction. - **value** (object) - The value of the transaction. - **updateId** (string) - The ID of the update. - **commandId** (string) - The ID of the command. - **workflowId** (string) - The ID of the workflow. - **effectiveAt** (string) - The effective timestamp of the transaction. - **events** (array) - A list of events within the transaction. - **ExercisedEvent** (object) - Details of an exercised event. - **offset** (integer) - The offset of the event. - **nodeId** (integer) - The node ID. - **contractId** (string) - The contract ID. - **templateId** (string) - The template ID. - **interfaceId** (string or null) - The interface ID. - **choice** (string) - The choice made for the event. - **choiceArgument** (object) - Arguments for the choice. - **actingParties** (array) - Parties acting in the event. - **consuming** (boolean) - Whether the event is consuming. - **witnessParties** (array) - Witness parties for the event. - **lastDescendantNodeId** (integer) - The last descendant node ID. - **exerciseResult** (object) - The result of the exercise. #### Response Example ```json { "update": { "Transaction": { "value": { "updateId": "122008a4699e61ce682917c9515ecb3b4426adf276441e41edede8b3862efa2de80e", "commandId": "582f81e4-86e4-48fa-ad95-607e9ebe8c9b", "workflowId": "", "effectiveAt": "1970-01-01T00:01:00Z", "events": [ { "ExercisedEvent": { "offset": 107, "nodeId": 4, "contractId": "0003113864953b90e689737a131569e8758df3cf82e3db12c89f010ac330276f3cca1112204bc3e9f7d695217e06aec436ac87a441c8e1a0f0d3ae2668c38e408fa2d5ebda", "templateId": "6e9fc50fb94e56751b49f09ba2dc84da53a9d7cff08115ebb4f6b7a12d0c990c:Splice.AmuletRules:TransferPreapproval", "interfaceId": null, "choice": "TransferPreapproval_Send", "choiceArgument": { "context": { "amuletRules": "002402fc37d1f6fcb5c9247342c66659f95eb03efebba3da8c244ae7c10925aae2ca1112201ac4dd28e75b1ba2be4df65e674b0c66fa2ec934abc15824584d8566af4916e9", "context": { "openMiningRound": "009d18bf51238bb679b45ac760d418d31d95fead0538971a26eff6d2b2d582005dca1112204d969f7a6e0d271b3a85b27297879812e8c0fdaaaf8d72d64441a06556bb5955", "issuingMiningRounds": [], "validatorRights": [], "featuredAppRight": null } }, "inputs": [ { "tag": "InputAmulet", "value": "009b939ae451ef1a0cb81d1606391406690e055b5be301fd2f51efb6be5675577eca1112200f58604ac538224f73bdc57117d73830ed1e3167f956d66f9e3ecdacbf2359a7" } ], "amount": "200.0000000000", "sender": "sender::1220278a4a0eb2c244b425dff62853ef1cd04ca1095bffcea465c0de766faf9ab8cd", "description": "token-standard-transfer-description" }, "actingParties": [ "sender::1220278a4a0eb2c244b425dff62853ef1cd04ca1095bffcea465c0de766faf9ab8cd" ], "consuming": false, "witnessParties": [ "treasury-party::1220278a4a0eb2c244b425dff62853ef1cd04ca1095bffcea465c0de766faf9ab8cd" ], "lastDescendantNodeId": 12, "exerciseResult": { "result": { ``` -------------------------------- ### Handle Topology Preparation and Signing (TypeScript) Source: https://docs.digitalasset.com/integrate/devnet/release-notes/index This code illustrates the process of preparing an external party topology and signing the resulting hash. It highlights a previous bug fix where the combined hash is now correctly handled in base64 encoding instead of hex. ```typescript const preparedParty = await sdk.topology?.prepareExternalPartyTopology( keyPair.publicKey ) logger.info('Prepared external topology') if (preparedParty) { logger.info('Signing the hash') const signedHash = signTransactionHash( //previously this would have to be converted from hex to base64 preparedParty?.combinedHash, keyPair.privateKey ) const allocatedParty = await sdk.topology?.submitExternalPartyTopology( signedHash, preparedParty ) ``` -------------------------------- ### Configure Wallet SDK with Custom OAuth Controller Source: https://docs.digitalasset.com/integrate/devnet/wallet-sdk-configuration/index Demonstrates configuring the Wallet SDK using a custom `ClientCredentialOAuthController` for OIDC authentication. This setup requires providing OAuth server details, client credentials, and audience/scope configurations. ```typescript import { WalletSDKImpl, localNetLedgerDefault, localNetTokenStandardDefault, AuthController, ClientCredentialOAuthController, } from '@canton-network/wallet-sdk' import { Logger } from 'pino' export default async function () { const participantId = 'my-participant-id' const myOAuthUrl = new URL('https://my-oauth-url') const myOAuthController = (logger?: Logger): AuthController => { const controller = new ClientCredentialOAuthController( //your oauth server `http://${myOAuthUrl.href}/.well-known/openid-configuration`, logger ) //oAuth M2M token for client id and client secret controller.userId = 'your-client-id' controller.userSecret = 'your-client-secret' // these are only needed if you intend to use admin only functions // these can be different from your userId and userSecret // if they are the same you can supply it twice //controller.adminId = 'your-client-id' //controller.adminSecret = 'your-client-secret' controller.audience = `https://daml.com/jwt/aud/participant/${participantId}` controller.scope = 'openid daml_ledger_api offline_access' return controller } const sdk = new WalletSDKImpl().configure({ logger: console, authFactory: myOAuthController, ledgerFactory: localNetLedgerDefault, tokenStandardFactory: localNetTokenStandardDefault, }) } ``` -------------------------------- ### Create New User with Canton Wallet SDK Source: https://docs.digitalasset.com/integrate/devnet/user-management/index Demonstrates how to create a new user and a default party using the adminLedger. The newly created user can then be granted rights or create new parties. Ensure SDK is configured correctly for connectivity and authentication. ```javascript import { WalletSDKImpl, localNetAuthDefault, localNetLedgerDefault, localNetTopologyDefault, localNetStaticConfig, } from '@canton-network/wallet-sdk' export default async function () { // it is important to configure the SDK correctly else you might run into connectivity or authentication issues const sdk = new WalletSDKImpl().configure({ logger: console, authFactory: localNetAuthDefault, // or use your specific configuration ledgerFactory: localNetLedgerDefault, // or use your specific configuration topologyFactory: localNetTopologyDefault, // or use your specific configuration }) await sdk.connect() await sdk.connectAdmin() await sdk.connectTopology(localNetStaticConfig.LOCALNET_SCAN_PROXY_API_URL) const defaultParty = 'my-default-party' const newUser = await sdk.adminLedger!.createUser( 'my-new-user', defaultParty ) } ``` -------------------------------- ### Multi-host a Party using Canton Wallet SDK Source: https://docs.digitalasset.com/integrate/devnet/party-management/index This script demonstrates how to create and manage a multi-hosted party using the Canton Wallet SDK. It initializes the SDK, connects to the ledger, creates a single-hosted party for synchronization, and then creates a multi-hosted party across different validators. It also shows how to create a party with an observing participant and execute a ping command. ```typescript import { WalletSDKImpl, localNetAuthDefault, localNetLedgerDefault, localNetTopologyDefault, localNetTokenStandardDefault, localNetLedgerAppProvider, createKeyPair, localValidatorDefault, localNetStaticConfig, } from '@canton-network/wallet-sdk' import { pino } from 'pino' import { v4 } from 'uuid' const logger = pino({ name: '06-external-party-setup', level: 'info' }) // it is important to configure the SDK correctly else you might run into connectivity or authentication issues const sdk = new WalletSDKImpl().configure({ logger, authFactory: localNetAuthDefault, ledgerFactory: localNetLedgerDefault, topologyFactory: localNetTopologyDefault, tokenStandardFactory: localNetTokenStandardDefault, validatorFactory: localValidatorDefault, }) logger.info('SDK initialized') await sdk.connect() logger.info('Connected to ledger') const multiHostedPartyKeyPair = createKeyPair() const singleHostedPartyKeyPair = createKeyPair() const multiHostedKeyPairWithObserverParticipant = createKeyPair() await sdk.connectAdmin() await sdk.connectTopology(localNetStaticConfig.LOCALNET_SCAN_PROXY_API_URL) const authTokenProvider = sdk.authTokenProvider const alice = await sdk.userLedger?.signAndAllocateExternalParty( singleHostedPartyKeyPair.privateKey, 'alice' ) logger.info( { partyId: alice?.partyId! }, 'created single hosted party to get synchronzerId' ) await sdk.setPartyId(alice?.partyId!) const multiHostedParticipantEndpointConfig = [ { url: new URL('http://127.0.0.1:3975'), accessTokenProvider: authTokenProvider, }, ] logger.info('multi host party starting...') const multiHostedParty = await sdk.userLedger?.signAndAllocateExternalParty( multiHostedPartyKeyPair.privateKey, 'bob', 1, multiHostedParticipantEndpointConfig ) logger.info(multiHostedParty, 'multi hosted party succeeded!') await sdk.setPartyId(multiHostedParty?.partyId!) logger.info('Create ping command') const createPingCommand = sdk.userLedger?.createPingCommand( multiHostedParty!.partyId! ) logger.info('Prepare command submission for ping create command') const pingCommandResponse = await sdk.userLedger?.prepareSignExecuteAndWaitFor( createPingCommand, multiHostedPartyKeyPair.privateKey, v4() ) logger.info(pingCommandResponse, 'ping command response') const multiHostedPartyWithObservingParticipant = await sdk.userLedger?.signAndAllocateExternalParty( multiHostedKeyPairWithObserverParticipant.privateKey, 'jon', 1, [], [ { url: new URL('http://127.0.0.1:3975'), accessTokenProvider: authTokenProvider, }, ] ) logger.info( multiHostedPartyWithObservingParticipant, 'created party with an observing participant' ) await sdk.setPartyId(multiHostedPartyWithObservingParticipant?.partyId!) const createPingCommand2 = sdk.userLedger?.createPingCommand( multiHostedPartyWithObservingParticipant!.partyId! ) const pingCommandResponse2 = await sdk.userLedger?.prepareSignExecuteAndWaitFor( createPingCommand2, multiHostedKeyPairWithObserverParticipant.privateKey, v4() ) logger.info(pingCommandResponse2, 'ping command response') ```