A comprehensive Go SDK for interacting with DAML ledgers, featuring a complete client library, service abstractions, and a powerful code generator for generating type-safe Go code from DAML (.dar) files.
The Go DAML SDK provides a comprehensive toolkit for building Go applications that interact with DAML ledgers. It follows a modular architecture separating concerns between client interaction, service abstraction, and code generation. The SDK includes a full-featured gRPC client for ledger operations, high-level service abstractions for common patterns, and an integrated code generator that creates type-safe Go code from DAML definitions.
The public SDK components are located under pkg/, which includes the client, service, model, auth, codec, errors, and types packages intended for direct use by application developers. The internal/codegen/ directory contains the code generator implementation, which parses DAML-LF (DAML Language Format) binaries extracted from .dar archives and generates corresponding Go structs with proper JSON serialization logic.
- Complete DAML Client Library - Full gRPC client for DAML Ledger API with connection management, authentication, and TLS support
- Dual-Connection Support - Separate connections for ledger and admin endpoints with automatic service routing
- Service Layer Abstractions - High-level services for common ledger and administrative operations
- Ledger Services - Command submission, command completion, event querying, state management, update service, package service, version service, interactive submission
- Typed Contract Queries - Generic
NewContractQuery[T]helper that fetches active contracts by template and decodes them directly into code-generated structs - Admin Services - Package management, user management, party management, participant pruning, command inspection, identity provider configuration
- Topology Services - Topology manager read/write operations for namespace delegations, party-to-key mappings, and party-to-participant mappings
- Authentication Support - Bearer token authentication with automatic token injection via gRPC interceptors
- Error Handling - Comprehensive DAML-specific error processing with categorized error types (authorization, validation, ledger-specific, connection errors)
- JSON Codec - Custom JSON serialization/deserialization for complex DAML types including Records, Variants, Enums, and primitive types
- Type-safe Go code generation from DAML definitions with proper type mapping
- Custom JSON serialization for complex DAML types (Records, Variants, Enums)
- PackageID extraction and embedding in generated code
- Multi-version DAML-LF support - Supports both DAML-LF v2 and v3 with automatic version detection
- Cross-platform support (Linux, macOS, Windows - amd64 and arm64)
- Clean CLI interface with comprehensive error handling and debug logging
- Template-based generation using Go's text/template engine for flexible code generation
# Clone the repository
git clone https://github.com/noders-team/go-daml.git
cd go-daml
# Build the CLI tool
make build
# The binary will be available at ./bin/godamlmake install
# Binary will be installed to $GOPATH/bin/godamlgo get github.com/noders-team/go-damlgo get -tool github.com/noders-team/go-daml/cmd/go-damlUsage:
go tool go-daml ...import (
"context"
"github.com/noders-team/go-daml/pkg/auth"
"github.com/noders-team/go-daml/pkg/client"
"github.com/rs/zerolog/log"
)
bearerToken := "your-auth-token"
grpcAddress := "localhost:6865"
adminAddress := "localhost:6866"
tlsConfig := client.TlsConfig{}
cl, err := client.NewDamlClient(grpcAddress, auth.NewBearerTokenProvider(bearerToken)).
WithAdminAddress(adminAddress).
WithTLSConfig(tlsConfig).
Build(context.Background())
if err != nil {
log.Fatal().Err(err).Msg("failed to build DAML client")
}
cl.CommandService.SubmitAndWait(ctx, commands)
cl.EventQueryService.GetActiveContracts(ctx, filter)
cl.StateService.GetLedgerEnd(ctx)
cl.UserMng.CreateUser(ctx, userRequest)
cl.PartyMng.AllocateParty(ctx, partyRequest)
cl.TopologyManagerWrite.GenerateTransactions(ctx, request)NewDamlClient(address, provider) takes an auth.TokenProvider, which the client uses to
inject a bearer token on every gRPC call. The SDK ships two implementations:
auth.NewBearerTokenProvider(token)— a static, pre-issued JWT.auth.NewKeycloakTokenProvider(cfg)— fetches a token from Keycloak via the client-credentials grant and transparently caches/refreshes it before expiry.
import (
"github.com/noders-team/go-daml/pkg/auth"
"github.com/noders-team/go-daml/pkg/client"
)
kc, err := auth.NewKeycloakTokenProvider(auth.KeycloakConfig{
OIDCURL: "https://keycloak.example.com/realms/canton",
ClientID: "ledger-app",
ClientSecret: "super-secret",
Audience: "https://canton.network.global",
})
if err != nil {
log.Fatal().Err(err).Msg("failed to create keycloak token provider")
}
cl, err := client.NewDamlClient(grpcAddress, kc).
WithTLSConfig(client.TlsConfig{}).
Build(context.Background())TokenURL may be supplied directly instead of OIDCURL; when only OIDCURL is set, the
token endpoint is derived as <OIDCURL>/protocol/openid-connect/token. See
examples/keycloak_app for a complete runnable program.
# Generate Go code from a DAR file
./bin/godaml --dar ./contracts.dar --output ./generated --go_package contracts
# With debug logging
./bin/godaml --dar ./contracts.dar --output ./generated --go_package main --debug| Parameter | Required | Description |
|---|---|---|
--dar |
✅ | Path to the DAR file |
--output |
✅ | Output directory where generated Go files will be saved |
--go_package |
✅ | Go package name for generated code |
--debug |
❌ | Enable debug logging (default: false) |
./bin/godaml --help- Go 1.23 or later
- Make
# Build for current platform
make build
# Build for all platforms
make build-all
# Run tests
make test
# Run tests with coverage
make test-coverage
# Format code
make fmt
# Lint code (requires golangci-lint)
make lint
# Development workflow
make dev
# Release workflow
make release
# Show all available commands
make help# Run all tests
make test
# Run tests with coverage report
make test-coverage
# Run specific test
go test ./internal/codegen -v -run TestGetMainDalfThe Go DAML SDK is organized into the following modules:
-
pkg/client/: DAML ledger gRPC client implementation- Builder pattern for flexible client configuration
- Connection management with TLS support
- Pluggable
auth.TokenProvider(static bearer token or Keycloak) with automatic token injection - Service factory exposing all ledger and admin services
- gRPC interceptors for authentication and error handling
-
pkg/service/ledger/: Ledger operations- Command Service: Submit commands synchronously
- Command Completion: Track command completion status
- Command Submission: Asynchronous command submission
- Event Query Service: Query active contracts, transaction trees, flat transactions
- Interactive Submission: Multi-step command submission workflows
- State Service: Query ledger state and configuration
- Update Service: Subscribe to ledger updates
- Package Service: Query and manage DAML packages
- Version Service: Get ledger API version information
-
pkg/service/admin/: Administrative operations- Package Management: Upload and validate DAR packages
- User Management: Create, update, list, and delete users with rights management
- Party Management: Allocate and manage parties
- Participant Pruning: Prune ledger history
- Command Inspection: Inspect command status
- Identity Provider Configuration: Configure identity providers
-
pkg/service/topology/: Topology management operations- Topology Manager Write: Generate, authorize, sign, and add topology transactions
- Topology Manager Read: Query namespace delegations, party-to-key mappings, party-to-participant mappings
- External Party Support: Onboarding transactions for external party allocation
-
pkg/service/testing/: Testing utilities- Time Service: Control ledger time for testing
-
pkg/model/: Common data models and type definitions for ledger and admin operations -
pkg/auth/: Authentication mechanisms —TokenProviderinterface with static bearer-token and Keycloak (OIDC client-credentials) implementations, injected via gRPC interceptors -
pkg/codec/: JSON codec for DAML types with custom marshaling/unmarshaling -
pkg/errors/: DAML-specific error handling with categorized error types -
pkg/types/: DAML type system definitions
codegen.go: DAR file processing, orchestration, and AST generationastgen/factory.go: AST generator factory with automatic DAML-LF version detectionastgen/v2/: DAML-LF v2 AST generation implementationastgen/v3/: DAML-LF v3 AST generation implementationmodel/manifest.go: DAR manifest parsing and metadata extractionmodel/template.go: Template data structures for code generationtemplate.go: Go template processing, binding, and file generationtemplate_test.go: Template generation tests
cmd/go-daml/main.go: Command-line interface for code generation
examples/admin_app/: Administrative operations examplesexamples/ledger_app/: Ledger interaction examplesexamples/keycloak_app/: Keycloak (OIDC client-credentials) authentication example
package main
import (
"context"
"os"
"github.com/noders-team/go-daml/pkg/auth"
"github.com/noders-team/go-daml/pkg/client"
"github.com/rs/zerolog/log"
)
func main() {
grpcAddress := os.Getenv("GRPC_ADDRESS")
if grpcAddress == "" {
grpcAddress = "localhost:8080"
}
bearerToken := os.Getenv("BEARER_TOKEN")
if bearerToken == "" {
log.Warn().Msg("BEARER_TOKEN environment variable not set")
}
tlsConfig := client.TlsConfig{}
cl, err := client.NewDamlClient(grpcAddress, auth.NewBearerTokenProvider(bearerToken)).
WithTLSConfig(tlsConfig).
Build(context.Background())
if err != nil {
log.Fatal().Err(err).Msg("failed to build DAML client")
}
ctx := context.Background()
version, err := cl.VersionService.GetLedgerApiVersion(ctx)
if err != nil {
log.Error().Err(err).Msg("failed to get ledger version")
}
ledgerEnd, err := cl.StateService.GetLedgerEnd(ctx)
if err != nil {
log.Error().Err(err).Msg("failed to get ledger end")
}
log.Info().Msgf("Connected to ledger API version: %s", version)
log.Info().Msgf("Current ledger end offset: %s", ledgerEnd)
}package main
import (
"context"
"github.com/noders-team/go-daml/pkg/auth"
"github.com/noders-team/go-daml/pkg/client"
"github.com/noders-team/go-daml/pkg/model"
"github.com/rs/zerolog/log"
)
func main() {
cl, err := client.NewDamlClient("localhost:8080", auth.NewBearerTokenProvider("token")).
Build(context.Background())
if err != nil {
log.Fatal().Err(err).Msg("failed to build client")
}
ctx := context.Background()
commands := &model.Commands{
ApplicationId: "my-app",
CommandId: "cmd-001",
Party: "Alice",
Commands: []interface{}{},
}
_, err = cl.CommandService.SubmitAndWait(ctx, commands)
if err != nil {
log.Error().Err(err).Msg("command submission failed")
return
}
filter := &model.TransactionFilter{}
contracts, err := cl.EventQueryService.GetActiveContracts(ctx, filter)
if err != nil {
log.Error().Err(err).Msg("failed to query contracts")
return
}
log.Info().Msgf("Retrieved %d active contracts", len(contracts))
}package main
import (
"context"
"os"
"github.com/noders-team/go-daml/pkg/auth"
"github.com/noders-team/go-daml/pkg/client"
"github.com/noders-team/go-daml/pkg/model"
"github.com/rs/zerolog/log"
)
func main() {
grpcAddress := os.Getenv("GRPC_ADDRESS")
if grpcAddress == "" {
grpcAddress = "localhost:8080"
}
bearerToken := os.Getenv("BEARER_TOKEN")
tlsConfig := client.TlsConfig{}
cl, err := client.NewDamlClient(grpcAddress, auth.NewBearerTokenProvider(bearerToken)).
WithTLSConfig(tlsConfig).
Build(context.Background())
if err != nil {
log.Fatal().Err(err).Msg("failed to build DAML client")
}
ctx := context.Background()
userReq := &model.CreateUserRequest{
UserId: "alice@example.com",
PrimaryParty: "Alice",
}
user, err := cl.UserMng.CreateUser(ctx, userReq)
if err != nil {
log.Error().Err(err).Msg("failed to create user")
}
partyReq := &model.AllocatePartyRequest{
PartyIdHint: "NewParty",
DisplayName: "New Party Display Name",
}
party, err := cl.PartyMng.AllocateParty(ctx, partyReq)
if err != nil {
log.Error().Err(err).Msg("failed to allocate party")
}
darBytes, _ := os.ReadFile("./contracts.dar")
uploadReq := &model.UploadDarFileRequest{
DarFile: darBytes,
}
_, err = cl.PackageMng.UploadDarFile(ctx, uploadReq)
if err != nil {
log.Error().Err(err).Msg("failed to upload DAR")
}
log.Info().Msgf("Created user: %s", user.UserId)
log.Info().Msgf("Allocated party: %s", party.PartyId)
}package main
import (
"context"
"os"
"github.com/noders-team/go-daml/pkg/auth"
"github.com/noders-team/go-daml/pkg/client"
"github.com/noders-team/go-daml/pkg/model"
"github.com/rs/zerolog/log"
)
func main() {
ledgerAddress := "localhost:6865"
adminAddress := "localhost:6866"
bearerToken := os.Getenv("BEARER_TOKEN")
cl, err := client.NewDamlClient(ledgerAddress, auth.NewBearerTokenProvider(bearerToken)).
WithAdminAddress(adminAddress).
Build(context.Background())
if err != nil {
log.Fatal().Err(err).Msg("failed to build DAML client")
}
defer cl.Close()
ctx := context.Background()
proposals := []*model.GenerateTransactionProposal{
{
Operation: model.OperationAddReplace,
Serial: 1,
Mapping: &model.NamespaceDelegation{
Namespace: "namespace-fingerprint",
TargetKey: publicKey,
IsRootDelegation: true,
},
Store: &model.StoreID{Value: "authorized"},
},
}
genResp, err := cl.TopologyManagerWrite.GenerateTransactions(ctx, &model.GenerateTransactionsRequest{
Proposals: proposals,
})
if err != nil {
log.Error().Err(err).Msg("failed to generate topology transactions")
return
}
listResp, err := cl.TopologyManagerRead.ListNamespaceDelegation(ctx, &model.ListNamespaceDelegationRequest{})
if err != nil {
log.Error().Err(err).Msg("failed to list namespace delegations")
return
}
log.Info().Msgf("Generated %d topology transactions", len(genResp.GeneratedTransactions))
log.Info().Msgf("Found %d namespace delegations", len(listResp.Results))
}client.NewContractQuery[T] is a generic helper for fetching active contracts and
decoding them straight into a struct generated by the godaml code generator. It scans
the active contract set filtered by template and returns []client.Contract[T], where
each entry pairs the contract ID with the decoded Data — no manual RecordToStruct
calls or event-loop boilerplate.
Pass the template ID from the generated contract's GetTemplateID() method, which emits
the #package-name:Module:Entity reference resolved by the ledger.
package main
import (
"context"
"github.com/noders-team/go-daml/pkg/auth"
"github.com/noders-team/go-daml/pkg/client"
"github.com/rs/zerolog/log"
)
func main() {
cl, err := client.NewDamlClient("localhost:6865", auth.NewBearerTokenProvider("token")).
Build(context.Background())
if err != nil {
log.Fatal().Err(err).Msg("failed to build client")
}
defer cl.Close()
ctx := context.Background()
// MappyContract is a struct emitted by the godaml code generator.
var tmpl MappyContract
query := client.NewContractQuery[MappyContract](cl)
contracts, err := query.FindContractsByTemplate(ctx, "Alice", tmpl.GetTemplateID())
if err != nil {
log.Fatal().Err(err).Msg("failed to query contracts")
}
for _, c := range contracts {
log.Info().
Str("contractID", c.ContractID).
Str("operator", string(c.Data.Operator)).
Msg("found typed contract")
}
}FindContractsByTemplate(ctx, partyID, templateID) returns every active contract of the
given template visible to partyID, each decoded into T:
type Contract[T any] struct {
ContractID string
Data T
}The Go DAML SDK supports separate connections for Ledger API and Admin API endpoints, which is essential for Canton deployments where these services run on different ports.
Use dual connections when:
- Your Canton participant exposes Ledger API and Admin API on different ports (e.g., 6865 and 6866)
- You need to use topology management services alongside ledger operations
- You're working with external party allocation and onboarding transactions
The client automatically routes services to the appropriate connection:
Main Connection (Ledger Address):
- Command Service, Command Completion, Command Submission
- Event Query Service
- State Service, Update Service
- Package Service, Version Service
- Interactive Submission Service
- User Management, Party Management
- Package Management, Participant Pruning
- Command Inspection, Identity Provider Configuration
Admin Connection (Admin Address):
- Topology Manager Write (GenerateTransactions, Authorize, SignTransactions, AddTransactions)
- Topology Manager Read (ListNamespaceDelegation, ListPartyToKeyMapping, ListPartyToParticipant)
cl, err := client.NewDamlClient("localhost:6865", auth.NewBearerTokenProvider(token)).
WithAdminAddress("localhost:6866"). // Optional: if not provided, all services use main address
Build(ctx)
cl.TopologyManagerWrite.GenerateTransactions(ctx, request)Backward Compatibility: If WithAdminAddress() is not called, all services (including topology) use the main ledger address, maintaining backward compatibility with existing code.
- DAR Extraction: Unzip and parse DAR archive using
UnzipDar() - Manifest Processing: Extract metadata (Main-Dalf, Sdk-Version, package names) from
META-INF/MANIFEST.MF - DAML-LF Version Detection: Determine DAML-LF version from package metadata
- AST Generator Selection: Choose appropriate AST generator (v2 or v3) via factory pattern
- DAML-LF Parsing: Parse protobuf-encoded DAML definitions from .dalf file
- AST Generation: Convert DAML-LF structures to internal AST representation
- Type Classification: Identify Records, Variants, Enums from DAML type definitions
- Package ID Extraction: Extract and embed package IDs in generated code
- Template Application: Apply Go templates to generate type-safe code with custom JSON marshaling
- File Output: Write formatted Go source files to output directory with proper package declarations
The SDK's JSON codec (pkg/codec/json_codec.go) provides comprehensive support for DAML type serialization:
| DAML Type | Go Representation | JSON Format | Notes |
|---|---|---|---|
Party |
PARTY (string) |
String | Party identifiers |
Text |
TEXT (string) |
String | Text values |
Int64 |
INT64 (int64) |
Number | 64-bit integers |
Numeric |
NUMERIC (string) |
String | Arbitrary precision decimals |
Bool |
BOOL (bool) |
Boolean | Boolean values |
Date |
DATE (time.Time) |
String (YYYY-MM-DD) | Date values |
Timestamp |
TIMESTAMP (time.Time) |
String (RFC3339) | Timestamp values |
ContractId |
CONTRACT_ID (string) |
String | Contract identifiers |
Optional T |
OPTIONAL (*interface{}) |
Null or value | Optional values with custom unmarshaling |
[T] |
LIST ([]interface{}) |
Array | Lists with element validation |
GenMap K V |
GEN_MAP ([]MapEntry) |
Array of {key, value} | Generic maps |
| Records | Go struct |
Object | Standard Go structs with field mapping |
| Variants | Go struct with optional fields |
Object with constructor tag | Union types with JSON marshaling |
| Enums | string |
String | Enumeration values |
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes following the existing code style
- Run tests:
make test - Run linting:
make lint - Commit your changes:
git commit -m 'Add amazing feature' - Push to the branch:
git push origin feature/amazing-feature - Open a Pull Request
- Follow Go best practices and conventions
- Write comprehensive tests for new functionality
- Update documentation for API changes
- Use meaningful commit messages
- Ensure all tests pass before submitting PR
This Go SDK is part of the broader DAML ecosystem:
- DAML - Digital Asset Modeling Language for smart contracts
- DAML Ledger API - gRPC API for interacting with DAML ledgers
- Official DAML SDKs - Java, TypeScript, and other language bindings
- Canton - High-performance DAML ledger implementation
For questions, issues, or contributions:
- Open an issue on GitHub
- Check existing documentation and examples
- Review the test files for usage patterns