User Management¶
The Wallet SDK has functionality for creating and managing user rights, by default when you are connecting it uses whichever user is defined in your auth-controller. If the user is an admin user on the ledger api they can be used to create other users and grant them rights.
How do I quickly setup canReadAsAnyParty and canExecuteAsAnyParty?¶
This script sets up three users alice, bob and master. master is given canReadAsAnyParty and canExecuteAsAnyParty and it shows proper access control by creating parties and ensuring that alice and bob can not see each others parties.
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')
}
logger.info('master user can see both parties')
const aliceWalletView = await aliceSDK.userLedger?.listWallets()
if (aliceWalletView?.find((p) => p === bob!.partyId)) {
throw new Error('alice user can see bob party')
}
const bobWalletView = await bobSDK.userLedger?.listWallets()
if (bobWalletView?.find((p) => p === alice!.partyId)) {
throw new Error('bob user can see alice party')
}
logger.info(
'alice and bob have proper isolation and cannot see each others external parties'
)
Creating a new user¶
Creating a new user can be done using the adminLedger, this new user can then be granted rights or can create new parties as needed.
import {
WalletSDKImpl,
localNetAuthDefault,
localNetLedgerDefault,
localNetTopologyDefault,
localNetStaticConfig,
} from '@canton-network/wallet-sdk'
// @disable-snapshot-test
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
)
}
ReadAs and ActAs limitations¶
Currently when allocating a new party we also grant ReadAs and ActAs rights for that party for the submitting user. This allows the user to do the normal flows involved like preparing transactions and executing those. There are performance issues if too many of these rights are assigned to the same user, in the case of a master user that is interacting on behalf of a client, then it might be more convenient to use CanReadAsAnyParty and CanExecuteAsAnyParty as described below.
Here is how the method changes if you need to allocate a party without granting rights:
import {
WalletSDKImpl,
createKeyPair,
localNetAuthDefault,
localNetLedgerDefault,
localNetTopologyDefault,
localNetStaticConfig,
} from '@canton-network/wallet-sdk'
// @disable-snapshot-test
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.connectTopology(localNetStaticConfig.LOCALNET_SCAN_PROXY_API_URL)
const signedHash = 'my-signed-hash'
const preparedParty = {
partyId: 'party-id',
publicKeyFingerprint: 'finger-print',
topologyTransactions: [],
multiHash: 'multi-hash',
}
const party = await sdk.userLedger?.allocateExternalParty(
signedHash,
preparedParty,
false //do not grant user actAs and readAs for the party
)
return party?.partyId
}
CanReadAsAnyParty¶
CanReadAsAnyParty gives a user full information about any party on the ledger, if a user is set up with this they will see: 1. All parties hosted on the ledger (multi-hosted and single hosted) 2. All transaction happening involving a party on the ledger 3. Prepare transactions on behalf of any party
This will not grant information about parties hosted on other ledgers or their transactions.
import {
WalletSDKImpl,
localNetAuthDefault,
localNetLedgerDefault,
localNetTopologyDefault,
localNetStaticConfig,
} from '@canton-network/wallet-sdk'
// @disable-snapshot-test
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 userId = 'my-user-id'
//public async grantMasterUserRights(userId: string, canReadAsAnyParty: boolean, canExecuteAsAnyParty: boolean)
await sdk.adminLedger!.grantMasterUserRights(userId, true, false)
}
The SDK automatically leverages this elevated permission for certain endpoints like listWallets.
CanExecuteAsAnyParty¶
CanExecuteAsAnyParty gives full execution rights for a party, this means that a user with these rights can submit transaction on behalf of a party hosted on the ledger.
This does not give the user rights to move funds without a valid signature!
The setup is similar to the CanReadAsAnyParty:
import {
WalletSDKImpl,
localNetAuthDefault,
localNetLedgerDefault,
localNetTopologyDefault,
localNetStaticConfig,
} from '@canton-network/wallet-sdk'
// @disable-snapshot-test
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 userId = 'my-user-id'
//public async grantMasterUserRights(userId: string, canReadAsAnyParty: boolean, canExecuteAsAnyParty: boolean)
await sdk.adminLedger!.grantMasterUserRights(userId, true, true)
}