This guide is for engineers building on the Lumera blockchain and Cascade storage. It shows how to configure the SDK, call the unified client, and run the included examples.
- Go 1.25+ with module support.
- Access to Lumera endpoints:
grpc(chain queries/tx),rpc(websocket for tx inclusion), and at least one SuperNode for Cascade uploads/downloads. - A Cosmos keyring entry that can sign Lumera transactions (
github.com/cosmos/cosmos-sdk/crypto/keyringis used throughout the SDK).
go get github.com/LumeraProtocol/sdk-goclient.Config (in client/config) drives both blockchain and Cascade clients:
ChainID,GRPCEndpoint,RPCEndpoint– chain connection details. gRPC uses TLS automatically for non-local hosts/port 443.Address,KeyName– Cosmos account info in your keyring.BlockchainTimeout,StorageTimeout– default deadlines for chain and Cascade operations.MaxRecvMsgSize,MaxSendMsgSize,MaxRetries– transport tuning.WaitTx– controls websocket vs polling behaviour when waiting for tx inclusion (see defaults inclient/config).Logger– optional; when set, SDK operations emit diagnostics.LogLevel– default logging threshold when no custom logger is supplied (default: error).
You can override fields with client.With... option helpers when calling client.New.
ctx := context.Background()
kr, _ := keyring.New("lumera", "test", "/tmp", nil)
cfg := client.Config{
ChainID: "lumera-testnet-2",
GRPCEndpoint: "localhost:9090",
RPCEndpoint: "http://localhost:26657",
Address: "lumera1abc...",
KeyName: "my-key",
}
logger := zap.NewExample()
lumera, err := client.New(ctx, cfg, kr, client.WithLogger(logger))
if err != nil {
logger.Error("client init failed", zap.Error(err))
}
defer lumera.Close()client.Client exposes Blockchain (gRPC chain modules) and Cascade (SuperNode SDK + SnApi).
client.NewFactory keeps a shared config/keyring and returns signer-specific clients:
factory, _ := client.NewFactory(cfg, kr)
alice, _ := factory.WithSigner(ctx, "lumera1alice...", "alice")
bob, _ := factory.WithSigner(ctx, "lumera1bob...", "bob")
defer alice.Close()
defer bob.Close()The pkg/crypto package provides keyring creation, key import, and address derivation. A single keyring supports both Cosmos (secp256k1) and EVM (eth_secp256k1) key types.
KeyType selects the cryptographic algorithm and BIP44 derivation path:
| KeyType | Algorithm | BIP44 Coin Type | HD Path |
|---|---|---|---|
KeyTypeCosmos |
secp256k1 |
118 | m/44'/118'/0'/0/0 |
KeyTypeEVM |
eth_secp256k1 |
60 | m/44'/60'/0'/0/0 |
NewKeyring creates a keyring that accepts both key types. The algorithm is selected when importing or creating keys, not at keyring creation time.
import sdkcrypto "github.com/LumeraProtocol/sdk-go/pkg/crypto"
kr, err := sdkcrypto.NewKeyring(sdkcrypto.DefaultKeyringParams())Use LoadKeyring to create a test keyring and import a key in one step:
kr, pubBytes, addr, err := sdkcrypto.LoadKeyring("alice", "mnemonic.txt", sdkcrypto.KeyTypeCosmos)Use ImportKey to add a key to an existing keyring:
pubBytes, addr, err := sdkcrypto.ImportKey(kr, "bob", "mnemonic.txt", "lumera", sdkcrypto.KeyTypeCosmos)When controller and host chains use different cryptographic key types, import keys under separate names:
kr, _ := sdkcrypto.NewKeyring(sdkcrypto.DefaultKeyringParams())
// Controller chain: standard Cosmos key (secp256k1, coin type 118)
sdkcrypto.ImportKey(kr, "controller-key", "mnemonic.txt", "lumera", sdkcrypto.KeyTypeCosmos)
// Host chain: EVM-compatible key (eth_secp256k1, coin type 60)
sdkcrypto.ImportKey(kr, "host-key", "mnemonic.txt", "inj", sdkcrypto.KeyTypeEVM)The ICA controller supports this via the HostKeyName config field (see Tutorial 6 below).
AddressFromKey derives a bech32 address for any HRP without mutating global SDK config:
addr, err := sdkcrypto.AddressFromKey(kr, "alice", "lumera")action, err := lumera.Blockchain.Action.GetAction(ctx, "action-id")
if err != nil {
log.Fatal(err)
}
fmt.Println(action)Steps: build Cascade metadata, register an action on-chain, upload bytes to SuperNodes, wait for completion.
result, err := lumera.Cascade.Upload(ctx, cfg.Address, lumera.Blockchain, "/path/to/file",
cascade.WithPublic(true), // optional: make file public
)
if err != nil {
log.Fatal(err)
}
log.Printf("action=%s task=%s", result.ActionID, result.TaskID)Upload wraps Client.CreateRequestActionMessage, Client.SendRequestActionMessage, and Client.UploadToSupernode. For manual control, call those methods separately and reuse the returned MsgRequestAction or types.ActionResult.
dl, err := lumera.Cascade.Download(ctx, "action-id", "/tmp/downloads")
if err != nil {
log.Fatal(err)
}
log.Printf("downloaded to %s", dl.OutputPath)The Cascade client bridges SuperNode SDK events and adds SDK-specific ones (prefixed sdk-go:).
lumera.Cascade.SubscribeToAllEvents(ctx, func(_ context.Context, e event.Event) {
log.Printf("%s task=%s msg=%v", e.Type, e.TaskID, e.Data[event.KeyMessage])
})msg, meta, err := lumera.Cascade.CreateRequestActionMessage(ctx, cfg.Address, "/path/file", nil)
_ = meta // metadata bytes used in the action
if err != nil { log.Fatal(err) }
ar, err := lumera.Cascade.SendRequestActionMessage(ctx, lumera.Blockchain, msg, "memo", nil)
if err != nil { log.Fatal(err) }
log.Printf("action registered: %s", ar.ActionID)
// Approve the action (if your flow requires it)
approve := blockchain.NewMsgApproveAction(cfg.Address, ar.ActionID)
_, err = lumera.Cascade.SendApproveActionMessage(ctx, lumera.Blockchain, approve, "")For offline/ICA-style flows, the package-level cascade.CreateApproveActionMessage helper builds approvals without SuperNode dependencies.
Use ICA when a controller chain account submits Lumera MsgRequestAction messages on behalf of an ICA address. The SDK helps build the request message and ICA packet, but you still broadcast the controller-chain MsgSendTx with your controller chain tooling.
Key points:
- You must provide Lumera chain
grpc+chain-idso metadata (price/expiration) can be computed. - For ICA, set the ICA creator address and app pubkey on the request message.
- The Cascade client uses
ICAOwnerKeyName+ICAOwnerHRPto derive the controller owner address.appPubkeyshould be the controller key's pubkey bytes from the keyring. - When controller and host chains use different key types, import keys under separate names into the same keyring and set
HostKeyNameon the ICAConfig(see the Crypto Helpers section above).
ctx := context.Background()
// Reuse kr from the client setup above.
cascadeClient, err := cascade.New(ctx, cascade.Config{
ChainID: "lumera-testnet-2",
GRPCAddr: "localhost:9090",
Address: "lumera1abc...",
KeyName: "my-key",
ICAOwnerKeyName: "my-key",
ICAOwnerHRP: "inj",
}, kr)
if err != nil { log.Fatal(err) }
defer cascadeClient.Close()
uploadOpts := &cascade.UploadOptions{}
cascade.WithICACreatorAddress("lumera1ica...")(uploadOpts)
cascade.WithAppPubkey(appPubkey)(uploadOpts)
msg, _, err := cascadeClient.CreateRequestActionMessage(ctx, "lumera1abc...", "/path/file", uploadOpts)
if err != nil { log.Fatal(err) }
any, err := ica.PackRequestAny(msg)
if err != nil { log.Fatal(err) }
packet, err := ica.BuildICAPacketData([]*codectypes.Any{any})
if err != nil { log.Fatal(err) }
msgSendTx, err := ica.BuildMsgSendTx(ownerAddr, "connection-0", 600_000_000_000, packet)
if err != nil { log.Fatal(err) }
// Broadcast msgSendTx using your controller-chain SDK or CLI.See examples/ica-request-tx for a full CLI that builds the ICA packet and prints the JSON.
Registration/updates use lumera.Blockchain.SuperNode transaction helpers:
_, err := lumera.Blockchain.RegisterSupernodeTx(ctx, cfg.Address, "lumeravaloper...", "1.2.3.4", "lumera1sn...", "26656", "")
if err != nil { log.Fatal(err) }Query helpers include GetSuperNode, ListSuperNodes, and GetTopSuperNodesForBlock.
The ica package provides a production-ready ICA (Interchain Accounts / ICS-27) controller that manages the full lifecycle of cross-chain message execution against Lumera.
ica.Controller connects to both a controller chain and the Lumera host chain over gRPC. It handles ICA registration, IBC packet construction, transaction broadcasting, acknowledgement polling, and action ID extraction — all behind a small set of methods:
ctrl, _ := ica.NewController(ctx, ica.Config{
Controller: controllerBaseConfig,
Host: hostBaseConfig,
Keyring: kr,
KeyName: "controller-key",
HostKeyName: "host-key", // optional: separate key for host chain
ConnectionID: "connection-0",
})
defer ctrl.Close()
addr, _ := ctrl.EnsureICAAddress(ctx) // register + poll until ready
result, _ := ctrl.SendRequestAction(ctx, msg) // send, wait for ack, return action ID
txHash, _ := ctrl.SendApproveAction(ctx, approveMsg)For lower-level or offline workflows, packet-building helpers are available separately: PackRequestAny, BuildICAPacketData, BuildMsgSendTx.
- Minimal setup — only gRPC endpoints, a keyring, and an IBC connection ID are required. No Docker, no relayer binary, no chain binaries.
- End-to-end in one call —
SendRequestActionbuilds the ICA packet, broadcasts on the controller chain, waits for tx inclusion, resolves the counterparty channel, polls for the host-chain acknowledgement, and extracts the action ID. - Mixed key type support — controller and host chains can use different cryptographic key types (
KeyTypeCosmos/KeyTypeEVM) by settingHostKeyNameto a separate key in the same keyring. - Resilient polling — configurable retry counts and delays for both ICA registration (
PollRetries/PollDelay) and acknowledgement waiting (AckRetries). - Tight Lumera integration — purpose-built for
MsgRequestActionandMsgApproveAction, with typed results (ActionResult) and Cascade metadata compatibility.
- Requires running chains — the controller connects to live gRPC endpoints. It does not spin up chains or relayers; infrastructure must already be deployed.
- Lumera-specific high-level methods —
SendRequestActionandSendApproveActionare tailored to Lumera action messages. Generic ICA message execution requires using the lower-level packet helpers directly. - No chain lifecycle management — unlike e2e testing frameworks (e.g., interchaintest), there is no built-in chain provisioning, genesis configuration, or relayer orchestration.
- Relayer dependency — IBC packet relay between controller and host chains depends on an external relayer (e.g., Hermes). The controller does not relay packets itself.
| Aspect | ica.Controller |
interchaintest |
|---|---|---|
| Purpose | Production client / scripting | E2E integration testing |
| Infrastructure | Connects to running chains | Spins up chains + relayers in Docker |
| Setup effort | Config struct + keyring | Docker, chain binaries, genesis config |
| Iteration speed | Fast (gRPC calls) | Slower (container lifecycle + block production) |
| Scope | Lumera ICA operations | Any IBC flow, any chain |
Use ica.Controller when you have running chains and need to execute ICA operations in production or automation scripts. Use interchaintest when you need to validate the full ICA flow in CI from scratch without external infrastructure.
- Run tests:
make test - Build samples:
make examples - Execute tutorials end-to-end:
go run ./examples/cascade-upload,go run ./examples/cascade-download,go run ./examples/query-actions,go run ./examples/multi-account,go run ./examples/ica-request-tx --help
- Tx inclusion timing out: adjust
WaitTxpolling/backoff (seeclient/config). EnsureRPCEndpointallows websocket subscriptions. - gRPC TLS errors: remote hosts/port 443 default to TLS; for local nodes use
localhost:9090or127.0.0.1:9090. - Key not found: confirm the key name exists in the keyring path you passed to
keyring.New. - SuperNode availability: Cascade operations require reachable SuperNodes; watch
sdk:supernodes_unavailableevents for diagnostics.