Go SDK for Clearnode payment channels providing both high-level and low-level operations in a unified client:
- State Operations:
Deposit,Withdraw,Transfer,CloseHomeChannel,Acknowledge- build and co-sign states off-chain - Blockchain Settlement:
Checkpoint- the single entry point for all on-chain transactions - Low-Level Operations: Direct RPC access for custom flows and advanced use cases
client.Deposit(ctx, blockchainID, asset, amount) // Prepare deposit state
client.Withdraw(ctx, blockchainID, asset, amount) // Prepare withdrawal state
client.Transfer(ctx, recipientWallet, asset, amount) // Prepare transfer state
client.CloseHomeChannel(ctx, asset) // Prepare finalize state
client.Acknowledge(ctx, asset) // Acknowledge received stateclient.Checkpoint(ctx, asset) // Settle latest state on-chain
client.Challenge(ctx, state) // Submit on-chain challenge
client.ApproveToken(ctx, chainID, asset, amount) // Approve ChannelHub to spend tokens
client.GetOnChainBalance(ctx, chainID, asset, wallet) // Query on-chain token balanceclient.Ping(ctx) // Health check
client.GetConfig(ctx) // Node configuration
client.GetBlockchains(ctx) // Supported blockchains
client.GetAssets(ctx, blockchainID) // Supported assetsclient.GetBalances(ctx, wallet) // User balances
client.GetTransactions(ctx, wallet, opts) // Transaction historyclient.GetHomeChannel(ctx, wallet, asset) // Home channel info
client.GetEscrowChannel(ctx, escrowChannelID) // Escrow channel info
client.GetLatestState(ctx, wallet, asset, onlySigned) // Latest stateclient.GetApps(ctx, opts) // List registered apps
client.RegisterApp(ctx, appID, metadata, approvalNotRequired) // Register new appclient.GetAppSessions(ctx, opts) // List sessions
client.GetAppDefinition(ctx, appSessionID) // Session definition
client.CreateAppSession(ctx, definition, sessionData, sigs) // Create session
client.CreateAppSession(ctx, def, data, sigs, opts) // Create with owner approval
client.SubmitAppSessionDeposit(ctx, update, sigs, asset, amount) // Deposit to session
client.SubmitAppState(ctx, update, sigs) // Update session
client.RebalanceAppSessions(ctx, signedUpdates) // Atomic rebalanceclient.SignSessionKeyState(state) // Sign an app session key state
client.SubmitAppSessionKeyState(ctx, state) // Register/update app session key
client.GetLastAppKeyStates(ctx, userAddress, opts) // Get active app session key statesclient.SignChannelSessionKeyState(state) // Sign a channel session key state
client.SubmitChannelSessionKeyState(ctx, state) // Register/update channel session key
client.GetLastChannelKeyStates(ctx, userAddress, opts) // Get active channel session key statesclient.Close() // Close connection
client.WaitCh() // Connection monitor channel
client.SignState(state) // Sign a state (advanced)
client.GetUserAddress() // Get signer's address
client.SetHomeBlockchain(asset, chainID) // Set default blockchain for assetpackage main
import (
"context"
"github.com/layer-3/nitrolite/pkg/core"
"github.com/layer-3/nitrolite/pkg/sign"
sdk "github.com/layer-3/nitrolite/sdk/go"
"github.com/shopspring/decimal"
)
func main() {
// Create signers from private key
msgSigner, _ := sign.NewEthereumMsgSigner(privateKeyHex)
stateSigner, _ := core.NewChannelDefaultSigner(msgSigner)
txSigner, _ := sign.NewEthereumRawSigner(privateKeyHex)
// Create unified client
client, _ := sdk.NewClient(
"wss://clearnode-sandbox.yellow.org/v1/ws",
stateSigner,
txSigner,
sdk.WithBlockchainRPC(80002, "https://polygon-amoy.alchemy.com/v2/KEY"),
)
defer client.Close()
ctx := context.Background()
// Step 1: Build and co-sign states off-chain
state, _ := client.Deposit(ctx, 80002, "usdc", decimal.NewFromInt(100))
fmt.Printf("Deposit state version: %d\n", state.Version)
// Step 2: Settle on-chain via Checkpoint
txHash, _ := client.Checkpoint(ctx, "usdc")
fmt.Printf("On-chain tx: %s\n", txHash)
// Transfer (off-chain only, no Checkpoint needed for existing channels)
state, _ = client.Transfer(ctx, "0xRecipient...", "usdc", decimal.NewFromInt(50))
// Low-level operations - same client
config, _ := client.GetConfig(ctx)
balances, _ := client.GetBalances(ctx, walletAddress)
}sdk/go/
├── client.go # Core client, constructors, high-level operations
├── node.go # Node information methods
├── user.go # User query methods
├── channel.go # Channel and state management
├── app_registry.go # App registry methods
├── app_session.go # App session methods
├── asset_cache.go # Asset lookup and caching
├── config.go # Configuration options
├── doc.go # Package documentation
└── utils.go # Type conversions
// Step 1: Create signers from private key
msgSigner, err := sign.NewEthereumMsgSigner("0x1234...")
if err != nil {
log.Fatal(err)
}
// Wrap with ChannelDefaultSigner (prepends 0x00 type byte)
stateSigner, err := core.NewChannelDefaultSigner(msgSigner)
if err != nil {
log.Fatal(err)
}
txSigner, err := sign.NewEthereumRawSigner("0x1234...")
if err != nil {
log.Fatal(err)
}
// Step 2: Create unified client
client, err := sdk.NewClient(
wsURL,
stateSigner, // core.ChannelSigner for signing channel states
txSigner, // sign.Signer for signing blockchain transactions
sdk.WithBlockchainRPC(chainID, rpcURL), // Required for Checkpoint
sdk.WithHandshakeTimeout(10*time.Second),
sdk.WithPingInterval(5*time.Second),
)
// Step 3: (Optional) Set home blockchain for assets
// Required for Transfer operations that may trigger channel creation
err = client.SetHomeBlockchain("usdc", 80002)
if err != nil {
log.Fatal(err)
}Sets the default blockchain network for a specific asset. This is required for Transfer() operations that may trigger channel creation, as Transfer doesn't accept a blockchain ID parameter.
err := client.SetHomeBlockchain("usdc", 80002)
if err != nil {
log.Fatal(err)
}Important Notes:
- This mapping is immutable once set for the client instance
- The asset must be supported on the specified blockchain
- Required before calling
Transfer()on a new channel
All state operations build and co-sign a state off-chain. They return (*core.State, error). Use Checkpoint to settle the state on-chain.
Prepares a deposit state. Creates a new channel if none exists, otherwise advances the existing state.
state, err := client.Deposit(ctx, 80002, "usdc", decimal.NewFromInt(100))
txHash, err := client.Checkpoint(ctx, "usdc") // settle on-chainRequirements:
- Sufficient token balance (checked on-chain during Checkpoint)
Prepares a withdrawal state to remove funds from the channel.
state, err := client.Withdraw(ctx, 80002, "usdc", decimal.NewFromInt(25))
txHash, err := client.Checkpoint(ctx, "usdc") // settle on-chainRequirements:
- Existing channel with sufficient balance
Prepares an off-chain transfer to another wallet. For existing channels, no Checkpoint is needed.
state, err := client.Transfer(ctx, "0xRecipient...", "usdc", decimal.NewFromInt(50))Requirements:
- Existing channel with sufficient balance OR
- Home blockchain configured via
SetHomeBlockchain()(for new channels)
Prepares a finalize state to close the user's channel.
state, err := client.CloseHomeChannel(ctx, "usdc")
txHash, err := client.Checkpoint(ctx, "usdc") // close on-chainRequirements:
- Existing channel (user must have deposited first)
Acknowledges a received state (e.g., after receiving a transfer).
state, err := client.Acknowledge(ctx, "usdc")Requirements:
- Home blockchain configured via
SetHomeBlockchain()when no channel exists
Settles the latest co-signed state on-chain. This is the single entry point for all blockchain transactions. Based on the transition type and on-chain channel status, it calls the appropriate blockchain method:
- Channel not on-chain (status Void): Creates the channel
- Deposit/Withdrawal on existing channel: Checkpoints the state
- Finalize: Closes the channel
txHash, err := client.Checkpoint(ctx, "usdc")Requirements:
- Blockchain RPC configured via
WithBlockchainRPC - A co-signed state must exist (call Deposit, Withdraw, etc. first)
- Sufficient gas for the blockchain transaction
Submits an on-chain challenge for a channel using a co-signed state. A challenge initiates a dispute period on-chain. If the counterparty does not respond with a higher-versioned state before the challenge period expires, the channel can be closed with the challenged state.
state, err := client.GetLatestState(ctx, wallet, "usdc", true)
txHash, err := client.Challenge(ctx, *state)Requirements:
- Blockchain RPC configured via
WithBlockchainRPC - State must have both user and node signatures
- State must have a HomeChannelID
Approves the ChannelHub contract to spend ERC-20 tokens on behalf of the user. This is required before depositing ERC-20 tokens. Native tokens (e.g., ETH) do not require approval.
txHash, err := client.ApproveToken(ctx, 80002, "usdc", decimal.NewFromInt(1000))Requirements:
- Blockchain RPC configured via
WithBlockchainRPC
Queries the on-chain token balance (ERC-20 or native) for a wallet on a specific blockchain. The returned value is already adjusted for token decimals.
balance, err := client.GetOnChainBalance(ctx, 80002, "usdc", "0x1234...")
fmt.Printf("On-chain balance: %s\n", balance)Requirements:
- Blockchain RPC configured via
WithBlockchainRPC
All low-level RPC methods are available on the same Client instance.
err := client.Ping(ctx)
config, err := client.GetConfig(ctx)
blockchains, err := client.GetBlockchains(ctx)
assets, err := client.GetAssets(ctx, &blockchainID) // or nil for allbalances, err := client.GetBalances(ctx, wallet)
txs, meta, err := client.GetTransactions(ctx, wallet, opts)channel, err := client.GetHomeChannel(ctx, wallet, asset)
channel, err := client.GetEscrowChannel(ctx, escrowChannelID)
state, err := client.GetLatestState(ctx, wallet, asset, onlySigned)Note: State submission and channel creation are handled internally by state operations (Deposit, Withdraw, Transfer). On-chain settlement is handled by Checkpoint.
// List registered applications with optional filtering
apps, meta, err := client.GetApps(ctx, &sdk.GetAppsOptions{
AppID: &appID,
OwnerWallet: &wallet,
})
// Register a new application
err := client.RegisterApp(ctx, "my-app", `{"name": "My App"}`, false)sessions, meta, err := client.GetAppSessions(ctx, opts)
def, err := client.GetAppDefinition(ctx, appSessionID)
sessionID, version, status, err := client.CreateAppSession(ctx, def, data, sigs)
nodeSig, err := client.SubmitAppSessionDeposit(ctx, update, sigs, asset, amount)
err := client.SubmitAppState(ctx, update, sigs)
batchID, err := client.RebalanceAppSessions(ctx, signedUpdates)When an app is registered with creationApprovalNotRequired: false, the app owner must sign the session creation request. Pass the owner's signature via CreateAppSessionOptions:
// Owner signs the create request using their app session signer
ownerSig, _ := ownerAppSessionSigner.Sign(createRequest)
sessionID, _, _, err := client.CreateAppSession(ctx, def, data, sigs,
sdk.CreateAppSessionOptions{OwnerSig: ownerSig.String()},
)App session operations require signatures with a type byte prefix, similar to channel signers:
| Type | Byte | Constructor | Usage |
|---|---|---|---|
| Wallet | 0xA1 |
app.NewAppSessionWalletSignerV1(msgSigner) |
Main wallet signs app session operations |
| Session Key | 0xA2 |
app.NewAppSessionKeySignerV1(msgSigner) |
Delegated session key signs on behalf of wallet |
// Create app session wallet signer
msgSigner, _ := sign.NewEthereumMsgSigner(privateKeyHex)
appSessionSigner, _ := app.NewAppSessionWalletSignerV1(msgSigner)
// Sign app session operations (create, deposit, state updates, etc.)
sig, _ := appSessionSigner.Sign(packedRequest)// Sign and submit an app session key state
state := app.AppSessionKeyStateV1{
UserAddress: client.GetUserAddress(),
SessionKey: "0xSessionKey...",
Version: 1,
ApplicationIDs: []string{"app1"},
AppSessionIDs: []string{},
ExpiresAt: time.Now().Add(24 * time.Hour),
}
sig, err := client.SignSessionKeyState(state)
state.UserSig = sig
err = client.SubmitAppSessionKeyState(ctx, state)
// Query active app session key states
states, err := client.GetLastAppKeyStates(ctx, userAddress, nil)
states, err := client.GetLastAppKeyStates(ctx, userAddress, &sdk.GetLastKeyStatesOptions{
SessionKey: &sessionKeyAddr,
})// Sign and submit a channel session key state
state := core.ChannelSessionKeyStateV1{
UserAddress: client.GetUserAddress(),
SessionKey: "0xSessionKey...",
Version: 1,
Assets: []string{"usdc", "weth"},
ExpiresAt: time.Now().Add(24 * time.Hour),
}
sig, err := client.SignChannelSessionKeyState(state)
state.UserSig = sig
err = client.SubmitChannelSessionKeyState(ctx, state)
// Query active channel session key states
states, err := client.GetLastChannelKeyStates(ctx, userAddress, nil)
states, err := client.GetLastChannelKeyStates(ctx, userAddress, &sdk.GetLastChannelKeyStatesOptions{
SessionKey: &sessionKeyAddr,
})Payment channels use versioned states signed by both user and node. The SDK uses a two-step pattern:
// Step 1: Build and co-sign state off-chain
state, _ := client.Deposit(...) // Returns *core.State
state, _ = client.Withdraw(...) // Returns *core.State
state, _ = client.Transfer(...) // Returns *core.State
// Step 2: Settle on-chain (when needed)
txHash, _ := client.Checkpoint(ctx, "usdc")State Flow (Internal):
- Get latest state with
GetLatestState() - Create next state with
state.NextState() - Apply transition (deposit, withdraw, transfer, etc.)
- Sign state with
SignState() - Submit to node for co-signing
- Return co-signed state
On-chain settlement is handled separately by Checkpoint.
States are signed using ECDSA with EIP-155 via pkg/sign:
// Create signers from private key
stateSigner, err := sign.NewEthereumMsgSigner(privateKeyHex) // For channel states
txSigner, err := sign.NewEthereumRawSigner(privateKeyHex) // For blockchain transactions
// Get address
address := txSigner.PublicKey().Address().String()Signing Process:
- State -> ABI Encode (via
core.PackState) - Packed State -> Keccak256 Hash
- Hash -> ECDSA Sign (via
signer.Sign) - Result: 65-byte signature (R || S || V)
Two Signer Types:
EthereumMsgSigner: Signs channel state updates (off-chain signatures)EthereumRawSigner: Signs blockchain transactions (on-chain operations)
The SDK wraps raw signers with a ChannelSigner interface that prepends a type byte to every signature. This allows the on-chain contract to dispatch signature verification to the correct validator.
// ChannelSigner interface (in pkg/core)
type ChannelSigner interface {
sign.Signer
Type() ChannelSignerType
}Two channel signer types:
| Type | Byte | Struct | Usage |
|---|---|---|---|
| Default | 0x00 |
core.ChannelDefaultSigner |
Main wallet signs directly. Signature = 0x00 || EIP-191 sig. |
| Session Key | 0x01 |
core.ChannelSessionKeySignerV1 |
Delegated session key signs on behalf of main wallet. Signature = 0x01 || ABI-encoded auth + session key sig. |
Creating a channel signer:
// Default signer (wraps EthereumMsgSigner with 0x00 prefix)
msgSigner, _ := sign.NewEthereumMsgSigner(privateKeyHex)
channelSigner, _ := core.NewChannelDefaultSigner(msgSigner)
// Pass to NewClient as the stateSigner parameter
client, _ := sdk.NewClient(wsURL, channelSigner, txSigner, opts...)The NewClient constructor expects a core.ChannelSigner for the stateSigner parameter. When using sign.NewEthereumMsgSigner directly, it must first be wrapped with core.NewChannelDefaultSigner (or core.ChannelSessionKeySignerV1 for session key operation).
- Void: No channel exists
- Create: Deposit creates channel on-chain
- Open: Channel active, can deposit/withdraw/transfer
- Challenged: Dispute initiated (advanced)
- Closed: Channel finalized (advanced)
- Building user-facing applications
- Need simple deposit/withdraw/transfer
- Want automatic state management with two-step pattern
- Don't need custom flows
- Building infrastructure/tooling
- Implementing custom state transitions
- Need fine-grained control
- Working with app sessions directly
All errors include context:
state, err := client.Deposit(ctx, 80002, "usdc", amount)
if err != nil {
log.Printf("State error: %v", err)
}
txHash, err := client.Checkpoint(ctx, "usdc")
if err != nil {
// Error: "failed to create channel on blockchain: insufficient balance"
log.Printf("Checkpoint error: %v", err)
}Common errors:
"home blockchain not set for asset"- MissingSetHomeBlockchainfor new channel creation"blockchain RPC not configured for chain"- MissingWithBlockchainRPC(for Checkpoint)"no channel exists for asset"- Checkpoint called without a co-signed state"insufficient balance"- Not enough funds in channel/wallet"failed to sign state"- Invalid private key or state"transition type ... does not require a blockchain operation"- Checkpoint called on unsupported transition
sdk.WithBlockchainRPC(chainID, rpcURL) // Configure blockchain RPC (required for Checkpoint)
sdk.WithHandshakeTimeout(duration) // Connection timeout (default: 5s)
sdk.WithPingInterval(duration) // Keepalive interval (default: 5s)
sdk.WithErrorHandler(func(error)) // Connection error handlerComprehensive example demonstrating app session lifecycle and operations.
See examples/app_sessions/lifecycle.go
cd examples/app_sessions
go run lifecycle.goThis example demonstrates:
- Registering apps in the app registry (with and without owner approval)
- Creating app sessions with single and multiple participants
- Owner approval for app session creation
- Session key delegation for app session participants
- Depositing assets into app sessions
- Operating on app session state (redistributing allocations)
- Withdrawing from app sessions
- Closing app sessions
- Fail case: attempting to create a session for an unregistered app
The example walks through a complete multi-party app session scenario with three wallets.
All types are imported from pkg/core and pkg/app:
// Core types
core.State // Channel state
core.Channel // Channel info
core.Transition // State transition
core.Transaction // Transaction record
core.Asset // Asset info
core.Token // Token implementation
core.Blockchain // Blockchain info
// Core channel session key types
core.ChannelSessionKeyStateV1 // Channel session key state
// Fields: UserAddress, SessionKey, Version (uint64), Assets []string,
// ExpiresAt (time.Time), UserSig string
// App registry types
app.AppV1 // Application definition
app.AppInfoV1 // Application info with timestamps
// App session types
app.AppSessionInfoV1 // Session info
app.AppDefinitionV1 // Session definition
app.AppStateUpdateV1 // Session update
app.AppSessionKeyStateV1 // App session key state
// Fields: UserAddress, SessionKey, Version (uint64), ApplicationIDs []string,
// AppSessionIDs []string, ExpiresAt (time.Time), UserSig stringFor understanding how operations work under the hood:
- Create channel definition
- Create void state
- Set home ledger (token, chain)
- Apply deposit transition
- Sign state
- Request channel creation from node (co-sign)
- Return co-signed state
- Get latest state
- Create next state
- Apply deposit transition
- Sign state
- Submit to node (co-sign)
- Return co-signed state
- Get latest state
- Create next state
- Apply withdrawal transition
- Sign state
- Submit to node (co-sign)
- Return co-signed state
- Get latest state
- Create next state
- Apply transfer transition
- Sign state
- Submit to node (co-sign)
- Return co-signed state
- Get latest state
- Verify channel exists
- Create next state
- Apply finalize transition
- Sign state
- Submit to node (co-sign)
- Return co-signed state
- Get latest signed state (both signatures)
- Determine blockchain ID from state's home ledger
- Get on-chain channel status
- Route based on transition type + status:
- Void channel ->
blockchainClient.Create() - Existing channel ->
blockchainClient.Checkpoint() - Finalize ->
blockchainClient.Close()
- Void channel ->
- Return transaction hash
- Go 1.21+
- Running Clearnode instance
- Blockchain RPC endpoint (for Checkpoint settlement)
Part of the Nitrolite project.