Create an External Party (Wallet)¶
Overview¶
This document describes the steps required to create a new party (wallet/address) on a validator. Parties represent acting entites in the network and all transaction happens between one or more parties. To understand more about parties see Parties section here.
A detailed tutorial of the steps below can be seen in the External Signing Tutorial here using python example scripts.
This document focuses on the steps required to create an external party using the Wallet SDK.
How do I quickly allocate a party?¶
Using the wallet SDK you can quickly allocate a party using the following code snippet:
import {
WalletSDKImpl,
TopologyController,
localNetAuthDefault,
localNetLedgerDefault,
localNetTopologyDefault,
} from '@canton-network/wallet-sdk'
import { LOCALNET_SCAN_API_URL } from '../config.js'
// 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.connectTopology(LOCALNET_SCAN_API_URL)
const { publicKey, privateKey } = TopologyController.createNewKeyPair()
//partyHint is optional but recommended to make it easier to identify the party
const partyHint = 'my-wallet-1'
const allocatedParty = await sdk.topology?.prepareSignAndSubmitExternalParty(
privateKey,
partyHint
)
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,
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 preparedParty = await sdk.topology?.prepareExternalPartyTopology(
keyPair.publicKey
)
console.log('Prepared external topology')
if (preparedParty) {
console.log('Signing the hash')
const base64StringCombinedHash = Buffer.from(
preparedParty?.combinedHash,
'hex'
).toString('base64')
const signedHash = signTransactionHash(
base64StringCombinedHash,
keyPair.privateKey
)
await sdk.topology
?.submitExternalPartyTopology(signedHash, preparedParty)
.then((allocatedParty) => {
console.log('Alocated party ', allocatedParty.partyId)
})
} else {
console.error('Error creating prepared party.')
}
console.log('Create ping command')
const createPingCommand = await sdk.userLedger?.createPingCommand(
preparedParty!.partyId!
)
sdk.adminLedger?.setPartyId(preparedParty!.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)
)
import {
WalletSDKImpl,
localNetAuthDefault,
localNetLedgerDefault,
localNetTopologyDefault,
localNetTokenStandardDefault,
createKeyPair,
signTransactionHash,
} from '@canton-network/wallet-sdk'
import { LOCALNET_SCAN_API_URL } from '../config.js'
import { v4 } from 'uuid'
// 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,
ledgerFactory: localNetLedgerDefault,
topologyFactory: localNetTopologyDefault,
tokenStandardFactory: localNetTokenStandardDefault,
})
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(LOCALNET_SCAN_API_URL)
console.log('Connected to topology')
const keyPair = createKeyPair()
console.log('generated keypair')
const preparedParty = await sdk.topology?.prepareExternalPartyTopology(
keyPair.publicKey
)
console.log('Prepared external topology')
if (preparedParty) {
console.log('Signing the hash')
const base64StringCombinedHash = Buffer.from(
preparedParty?.combinedHash,
'hex'
).toString('base64')
const signedHash = signTransactionHash(
base64StringCombinedHash,
keyPair.privateKey
)
await sdk.topology
?.submitExternalPartyTopology(signedHash, preparedParty)
.then((allocatedParty) => {
sdk.userLedger?.setPartyId(preparedParty!.partyId!)
sdk.adminLedger?.setPartyId(preparedParty!.partyId!)
console.log('Allocated party ', allocatedParty.partyId)
})
} else {
console.error('Error creating prepared party.')
}
console.log('Create ping command for party:', preparedParty!.partyId!)
const createPingCommand = sdk.userLedger?.createPingCommand(
preparedParty!.partyId!
)
console.log('Prepare command submission for ping create command')
const prepareResponse =
await sdk.userLedger?.prepareSubmission(createPingCommand)
console.log('Sign transaction hash')
const signedCommandHash = signTransactionHash(
prepareResponse!.preparedTransactionHash!,
keyPair.privateKey
)
console.log('Submit command')
sdk.userLedger
?.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)
)
sdk.userLedger?.listSynchronizers()
Create the key Pair¶
The process for creating a key using standard encryption practices is similar that in other blockchains. The full details of supported cryptographic algorithms can be found Here. By default an Ed25519 encryption is used. There exists many libraries that can be used to generate such a key pair, you can do it simply with the WalletSDK using:
import { TopologyController } from '@canton-network/wallet-sdk'
// static method call
const { publicKey, privateKey } = TopologyController.createNewKeyPair()
Choosing a party hint¶
A party ID is defined as ${partyHint}::${fingerprint}. The partyHint is a user friendly name for the party and can be anything that is unique for the fingerprint, e.g. “alice”, “bob” or “my-wallet-1”.
If you want to be to derive your party IDs from the public key, you can use a static party hint for all parties with different fingerprints, or also derive party hint from the public key, too.
Generate the fingerprint¶
To generate the fingerprint the wallet SDK has a built in function:
import { TopologyController } from '@canton-network/wallet-sdk'
const publicKey = 'your-public-key-here'
// static method call
const fingerPrint = TopologyController.createFingerprintFromPublicKey(publicKey)
Generating the topology transactions¶
When onboarding using external signing, multiple topology transactions are required to be generated and signed. This is because both the keyHolder (the party) and the node (the validator) need to agree on the hosting relationship. The three transactions that needs to be generated are:
PartyToParticipant: This transaction indicates that the party agrees to be hosted by the participant (validator).
ParticipantToParty: This transaction indicates that the participant (validator) agrees to host the party.
KeyToParty: This transaction indicates that the key (public key) is associated with the party.
Once all the transactions are built they can be combined into a single hash and submitted as part of a single signature. The wallet SDK has helper functions to generate these transactions:
import {
WalletSDKImpl,
TopologyController,
localNetAuthDefault,
localNetLedgerDefault,
localNetTopologyDefault,
} from '@canton-network/wallet-sdk'
import { LOCALNET_SCAN_API_URL } from '../config.js'
// 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.connectTopology(LOCALNET_SCAN_API_URL)
const { publicKey, privateKey } = TopologyController.createNewKeyPair()
//partyHint is optional but recommended to make it easier to identify the party
const partyHint = 'my-wallet-1'
const preparedParty = await sdk.topology?.prepareExternalPartyTopology(
privateKey,
partyHint
)
Sign multi-hash¶
Since the topology transactions need to be submitted together the combined hash needs to be signed. The wallet SDK has a helper function to sign the combined hash:
import { signTransactionHash } from '@canton-network/wallet-sdk'
const preparedParty = { combinedHash: 'combined-hash-here' }
const privateKey = 'your-private-key-here'
const signature = signTransactionHash(preparedParty.combinedHash, privateKey)
Submit the topology transactions¶
Once the signature is generated, the topology transactions can be submitted to the validator. The wallet SDK has a helper function to submit the transactions:
import {
WalletSDKImpl,
localNetAuthDefault,
localNetLedgerDefault,
localNetTopologyDefault,
} from '@canton-network/wallet-sdk'
import { LOCALNET_SCAN_API_URL } from '../config.js'
// 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.connectTopology(LOCALNET_SCAN_API_URL)
const preparedParty = {
partyTransactions: [], // array of topology transactions
combinedHash: 'the-combined-hash',
txHashes: [], // the individual transaction hashes
namespace: 'your-namespace-here',
partyId: 'your-party-id-here',
}
const signature = 'your-signed-hash-here'
sdk.topology?.submitExternalPartyTopology(signature, preparedParty)