Wallet SDK Release Notes¶
Below are the release notes for the Wallet SDK versions, detailing new features, improvements, and bug fixes in each version.
0.10.0¶
Released on October 2nd, 2025
Self-issue feature app rights
you can now grant yourself feature app rights (similar to the wallet UI) for both internal and external parties
// For external parties
const [command,disclosedContracts] = sdk.tokenStandard!.selfGrantFeatureAppRights()
await sdk.userLedger?.prepareSignExecuteAndWaitFor(
command,
keyPair.privateKey,
v4(),
disclosedContracts
)
// For internal parties
await sdk.tokenStandard!.grantFeatureAppRightsForInternalParty()
localNet variation for AppProvider & AppUser
you can now use both the appProvider and AppUser easily for show operations between two validators
const providerSDK = new WalletSDKImpl().configure({
logger,
authFactory: localNetAuthDefault,
ledgerFactory: localNetLedgerAppProvider, //new variations here
topologyFactory: localNetTopologyAppProvider, //new variations here
tokenStandardFactory: localNetTokenStandardAppProvider, //new variations here
validatorFactory: localValidatorDefault,
})
const userSDK = new WalletSDKImpl().configure({
logger,
authFactory: localNetAuthDefault,
ledgerFactory: localNetLedgerAppUser, //new variations here
topologyFactory: localNetTopologyAppUser, //new variations here
tokenStandardFactory: localNetTokenStandardAppUser, //new variations here
validatorFactory: localValidatorDefault,
})
LocalNet..Default still exists, they as previously defaults to the appUser validator
topology transaction recalculate hash
you can now offline validate a topology transaction by recomputing the hash
const recomputeHash = await TopologyController.computeTopologyTxHash(
prepared!.partyTransactions
)
if (recomputeHash !== prepared!.combinedHash) {
throw new Error(
'Recomputed hash does not match prepared combined hash'
)
}
new awaiting variation with prepareSignExecuteAndWaitFor & executeSubmissionAndWaitFor
release 0.7.0 introduced the `waitForCompletion`, we have now backed that into the executions
// PREVIOUS CODE EXAMPLE
//it is recommended to fetch ledger offset before preparing your command
const offsetLatest = (await sdk.userLedger?.ledgerEnd())?.offset ?? 0
const transferCommandId =
// prepareSignAndExecuteTransaction & prepareSign now returns the commandId
await sdk.userLedger?.prepareSignAndExecuteTransaction(
[{ ExerciseCommand: transferCommand }],
keyPairSender.privateKey,
v4(),
disclosedContracts2
)
//new command that scans the ledger to ensure the command have completed
const completion = await sdk.userLedger?.waitForCompletion(
offsetLatest, //where to start from
5000, //optional timeout in ms
transferCommandId! //the command to look for
)
// NEW VARIATION
const completion =
await sdk.userLedger?.prepareSignExecuteAndWaitFor(
transferCommand,
keyPairSender.privateKey,
v4(),
disclosedContracts,
10000 // 10 second timeout, if no value is provided here a default of 15 seconds is used
)
// VARIATION FOR `ExecuteSubmission`
const completion =
await onlineSDK.userLedger?.executeSubmissionAndWaitFor(
transferCommand,
signedHash,
keyPairSender.publicKey,
v4()
)
executeSubmission now returns the submissionId similarly to prepareSignAndExecuteTransaction
fixed thrown exception for missing seed when using TopologyController.createTransactionHash
prepareSubmission now has same command input signature as prepareSignAndExecuteTransaction
0.9.0¶
Released on September 26th, 2025
Supporting both canton 3.3 and 3.4 at the same timeout
since canton 3.4 will soon come to splice being able to support both versions is imperative before
localNetStaticConfig added
since the wallet api and registry are static for localnet, a new config has been added to make early development easier
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
)
0.8.0¶
Release on September 24th, 2025
Important!: The flow has been simplified for prepare and execute of commands, however this means code needs to be converted
// previous prepare and submit flow
const [tapCommand, disclosedContracts] = await sdk.tokenStandard!.createTap(
sender!.partyId,
'2000000',
{
instrumentId: 'Amulet',
instrumentAdmin: instrumentAdminPartyId,
}
)
await sdk.userLedger?.prepareSignAndExecuteTransaction(
[{ ExerciseCommand: tapCommand }],
keyPairSender.privateKey,
v4(),
disclosedContracts
)
in the new flow it is no longer needed to perform the array wrapping [{ ExerciseCommand: tapCommand }] and you can instead pass the tapCommand directly
// new prepare and submit flow
const [tapCommand, disclosedContracts] = await sdk.tokenStandard!.createTap(
sender!.partyId,
'2000000',
{
instrumentId: 'Amulet',
instrumentAdmin: instrumentAdminPartyId,
}
)
await sdk.userLedger?.prepareSignAndExecuteTransaction(
tapCommand,
keyPairSender.privateKey,
v4(),
disclosedContracts
)
this goes for all transaction!
Support Withdrawal flow for 2-step transfer
it is now possible for sender to withdraw a 2-step transfer that have previously been send
// Alice withdraws the transfer
const [withdrawTransferCommand, disclosedContracts] =
await sdk.tokenStandard!.exerciseTransferInstructionChoice(
transferCid!,
'Withdraw'
)
note: this does not work if the receiver have already perform Accept or Reject
Allow validating if receiver have set up transfer pre-approval before performing a transaction
//check if bob have set up transfer pre approval before sending
const transferPreApprovalStatus =
await sdk.tokenStandard?.getTransferPreApprovalByParty(
receiver!.partyId,
'Amulet'
)
logger.info(transferPreApprovalStatus, '[BOB] transfer preapproval status')
Tested and verified against Splice 0.4.17
Fix endless loop bug when onboarding a party
0.7.0¶
Release on September 18th, 2025
Important!: scan api is not longer used for methods like `connectTopology` use scan proxy instead
Added support for multi-hosting a party upon creation against multiple validators
// setup config against multiple nodes to acquire signature
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<string, Enums_ParticipantPermission>()
// 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'
)
Verify signed transaction hash
we have also extended the executeSubmission and prepareSignAndExecuteTransaction to validate the hash before transmitting to the ledger
const hash = 'my-transaction-hash'
const publicKey = 'my-public-key'
const signature = 'my-signed-hash-with-private-key'
const isValid = sdk.userLedger?.verifyTxHash(hash, publicKey, signature)
wait for command completion
//it is recommended to fetch ledger offset before preparing your command
const offsetLatest = (await sdk.userLedger?.ledgerEnd())?.offset ?? 0
const transferCommandId =
// prepareSignAndExecuteTransaction & prepareSign now returns the commandId
await sdk.userLedger?.prepareSignAndExecuteTransaction(
[{ ExerciseCommand: transferCommand }],
keyPairSender.privateKey,
v4(),
disclosedContracts2
)
//new command that scans the ledger to ensure the command have completed
const completion = await sdk.userLedger?.waitForCompletion(
offsetLatest, //where to start from
5000, //optional timeout in ms
transferCommandId! //the command to look for
)
Added new endpoint to quickly fetch all pending 2-step incoming transfer to easily accept or reject
const pendingInstructions = await sdk.tokenStandard?.fetchPendingTransferInstructionView()
const [acceptTransferCommand, disclosedContracts3] =
await sdk.tokenStandard!.exerciseTransferInstructionChoice(
transferCid,
'Accept'
)
optional expiry date for create transfer
const [transferCommand, disclosedContracts2] =
await sdk.tokenStandard!.createTransfer(
sender!.partyId,
receiver!.partyId,
'100',
{
instrumentId: 'Amulet',
instrumentAdmin: instrumentAdminPartyId,
},
utxos?.map((t) => t.contractId),
'memo-ref',
new Date(Date.now()+60*1000) // custom expiry of 1 hour
// default is 24 hours
)
fetch transaction by update id
// convenient new endpoint to get transaction based on update id
// this will come out in same format as listHoldingTransactions
sdk.tokenStandard?.getTransactionById('my-update-id')
The access token generated by the authController is now correctly passed to the scan proxy and registry
0.6.1¶
Released on September 16th, 2025
Fixed a minor edge case where a future mining round would be chosen if there was a client clock skew.
0.6.0¶
Released on September 16th, 2025
ledgerFactory, TopologyFactory & ValidatorFactory changed to use URL instead of strings (where applicable)
const myLedgerFactory = (userId: string, token: string) => {
return new LedgerController(
userId,
new URL('http://my-json-ledger-api'), //HERE
token
)
}
const myTopologyFactory = (
userId: string,
userAdminToken: string,
synchronizerId: string
) => {
return new TopologyController(
'my-grpc-admin-api',
new URL('http://my-json-ledger-api'), //HERE
userId,
userAdminToken,
synchronizerId
)
}
const myValidatorFactory = (userId: string, token: string) => {
return new ValidatorController(
userId,
new URL('http://my-validator-app-api'), //HERE
token
)
}
connectTopology now uses scanProxy instead of scan for proper decentralized setup
stronger typing now required strings of specific formats for parties across all controllers
fixed a bug where the combinedHash returned from topologyController.prepareExternalPartyTopology was in hex encoding instead of base64
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
)
fixed a bug that caused the expectedDso field to be required when performing TransferPreApprovalProposal (this is only required after Splice 0.1.11)
simplified setParty & setSynchronizer, now it can all be done with one call on sdk.setPartyId()
//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
0.5.0¶
Released on September 11th, 2025
Memo field added to create transfer
const [transferCommand, disclosedContracts2] =
await sdk.tokenStandard!.createTransfer(
sender!.partyId,
receiver!.partyId,
'100',
{
instrumentId: 'Amulet',
instrumentAdmin: instrumentAdminPartyId,
},
'my-new-favorite-memo-field'
)
pre-approval creation now supported through ledgerController instead of validatorController
previously
await sdk.validator?.externalPartyPreApprovalSetup(privateKey)
now instead using ledger api:
const transferPreApprovalProposal =
sdk.userLedger?.createTransferPreapprovalCommand(
validatorOperatorParty, //this needs to be sourced from the validator
receiver?.partyId,
instrumentAdminPartyId
)
await sdk.userLedger?.prepareSignAndExecuteTransaction(
[transferPreApprovalProposal],
keyPairReceiver.privateKey,
v4()
)
0.4.0¶
Released on September 10th, 2025
Range filter for listHoldingTransactions(afterOffset?: string,beforeOffset?: string)
Transfer pre-approval support:
const sdk = new WalletSDKImpl().configure({
logger,
authFactory: localNetAuthDefault,
ledgerFactory: localNetLedgerDefault,
topologyFactory: localNetTopologyDefault,
tokenStandardFactory: localNetTokenStandardDefault,
validatorFactory: localValidatorDefault, //Extend SDK with new validator factory
})
//set the party
sdk.validator?.setPartyId(receiver?.partyId!)
//provide private key to sign the pre-approval
await sdk.validator?.externalPartyPreApprovalSetup(keyPairReceiver.privateKey)
Support added for 2-step transfers (propose / accept)
const [acceptTransferCommand, disclosedContracts3] =
await sdk.tokenStandard!.exerciseTransferInstructionChoice(
transferCid, //cid of the transfer instruction
'Accept' // or 'Reject'
)
listHoldingsUtxo
has been extended to onlynonLocked
UTXOs
//new optional parameter, default is true (to be backwards compatible
const usableUtxos = await sdk.tokenStandard?.listHoldingUtxos(false)
//this include locked UTXOs
const allUtxos = await sdk.tokenStandard?.listHoldingUtxos()
- Include some small bug fixes. The most noteable are:
Contract not found
error when listing holdings (https://github.com/hyperledger-labs/splice-wallet-kernel/issues/357)Requirements to have extra import (like @protobuf-ts/runtime-rpc) resolved