A Rust library for interacting with the Canton blockchain.
- Overview
- Installation
- Configuration
- Usage Examples
- API Reference
- Direct Canton API Usage
- Contributing
This library provides a Rust interface for interacting with Canton blockchain participant nodes. It handles authentication, ledger queries, and contract management.
| Crate | Description |
|---|---|
ledger |
Low-level Canton Ledger API client (active contracts, submissions, WebSocket streaming) |
keycloak |
Keycloak/OIDC authentication (password grant, client credentials, token refresh) |
registry |
Canton registry service integration |
common |
Common types for transfers and contract operations |
wallet |
Wallet operations (amulet rules, mining rounds) |
cryptography |
Cryptographic utilities (AES-256-GCM) |
Before using this library, you need:
- A Canton Participant Node - Access to a Canton participant node (devnet, testnet, or mainnet)
- Keycloak Credentials - Authentication credentials for your participant node
- A Party ID - Your unique party identifier on the Canton network
Add the crates you need to your Cargo.toml:
[dependencies]
ledger = { git = "ssh://git@github.com/DLC-link/canton-lib", branch = "main" }
keycloak = { git = "ssh://git@github.com/DLC-link/canton-lib", branch = "main" }
registry = { git = "ssh://git@github.com/DLC-link/canton-lib", branch = "main" }
common = { git = "ssh://git@github.com/DLC-link/canton-lib", branch = "main" }
wallet = { git = "ssh://git@github.com/DLC-link/canton-lib", branch = "main" }- Create environment configuration
Copy .env.example to .env and fill in your values:
cp .env.example .env- Configure your environment variables
Edit .env with your Canton participant node details:
# Authentication
KEYCLOAK_HOST=https://keycloak.example.com
KEYCLOAK_REALM=your-realm
KEYCLOAK_CLIENT_ID=your-client-id
KEYCLOAK_USERNAME=your-username
KEYCLOAK_PASSWORD=your-password
# Canton Network
LEDGER_HOST=https://participant.example.com
PARTY_ID=your-party::1220...use keycloak::login;
// Password grant authentication
let auth = login::password(login::PasswordParams {
client_id: "your-client-id".to_string(),
username: "your-username".to_string(),
password: "your-password".to_string(),
url: login::password_url("https://your-keycloak-host", "your-realm"),
}).await?;
// Use auth.access_token for subsequent API callsuse ledger::{ledger_end, websocket::active_contracts, common};
// Get ledger end offset
let ledger_end = ledger_end::get(ledger_end::Params {
access_token: auth.access_token.clone(),
ledger_host: "https://participant.example.com".to_string(),
}).await?;
// Query contracts by template ID
let contracts = active_contracts::get(active_contracts::Params {
ledger_host: "https://participant.example.com".to_string(),
party: "your-party::1220...".to_string(),
filter: common::IdentifierFilter::TemplateIdentifierFilter(
common::TemplateIdentifierFilter {
template_filter: common::TemplateFilter {
value: common::TemplateFilterValue {
template_id: Some("package:Module:Template".to_string()),
include_created_event_blob: true,
},
},
},
),
access_token: auth.access_token,
ledger_end: ledger_end.offset,
}).await?;The examples crate includes ready-to-run programs:
# List contracts by template ID
cargo run -p examples --bin list_contracts -- "splice-amulet-0.1.10:Splice.Amulet:Amulet"
# Delete executed transfer contracts
cargo run -p examples --bin delete_executed_transferspassword(PasswordParams)- Authenticate with username/passwordclient_credentials(ClientCredentialsParams)- Service account authenticationpassword_url(host, realm)- Build password grant URL
ledger_end::get(Params)- Get current ledger offsetactive_contracts::get(Params)- Query active contracts (REST)websocket::active_contracts::get(Params)- Query active contracts (WebSocket)websocket::update::stream(Params)- Stream ledger updatessubmit::submit(Params)- Submit commands to the ledger
transfer_factory::get(Params)- Get transfer factory disclosuresaccept_context::get(Params)- Get accept choice context
- Types for transfer operations, submissions, and contract filters
amulet_rules- Amulet rules queriesmining_rounds- Mining round operations
For teams who want to understand the underlying protocol or implement custom workflows, here's how to interact with Canton APIs directly.
curl -X GET "$LEDGER_HOST/v2/state/ledger-end" \
-H "Authorization: Bearer $ACCESS_TOKEN"LEDGER_OFFSET=$(curl -X GET "$LEDGER_HOST/v2/state/ledger-end" \
-H "Authorization: Bearer $ACCESS_TOKEN" | jq -r '.offset')
curl -X POST $LEDGER_HOST/v2/state/active-contracts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"filter": {
"filtersByParty": {
"'$PARTY_ID'": {
"cumulative": [{
"identifierFilter": {
"TemplateFilter": {
"value": {
"templateId": "package:Module:Template",
"includeCreatedEventBlob": true
}
}
}
}]
}
}
},
"verbose": false,
"activeAtOffset": '$LEDGER_OFFSET'
}'curl -X POST $LEDGER_HOST/v2/commands/submit-and-wait-for-transaction-tree \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"commands": [{
"ExerciseCommand": {
"templateId": "package:Module:Template",
"contractId": "'$CONTRACT_ID'",
"choice": "ChoiceName",
"choiceArgument": {}
}
}],
"commandId": "'$(uuidgen)'",
"actAs": ["'$PARTY_ID'"]
}'The common crate's common::submission::Submission has a few extra fields, you can simple add ..Deafult::default(), to fulfill that need.
let submission_request = common::submission::Submission {
// ....
..Default::default()
};The common crate's common::transfer::DisclosedContract has a field called template_id changed from String to Option<String>. You can set it to Some(template_id) or None if not applicable. It is because in the latest Canton versions, it became optional.
let disclosed_contract = common::transfer::DisclosedContract {
template_id: Some("package:Module:Template".to_string()),
// ....
};Contributions are welcome! This library is designed to help developers build on Canton.
-
Fork the repository and create a feature branch
git checkout -b feature/your-feature-name
-
Make your changes
- Follow Rust best practices and naming conventions
- Keep library code free of environment variable dependencies
- Add tests for new functionality (when applicable)
-
Test your changes
# Build the library cargo build --release # Run clippy for linting cargo clippy --all-targets --all-features # Format code cargo fmt --all
-
Submit a pull request
- Library code should accept all configuration as function parameters (no
env::var()calls) - Example code can read from environment variables to demonstrate usage
MIT License - see LICENSE file for details