Documentation
¶
Overview ¶
Package sdk provides the official Go client for the Nitrolite Nitronode API.
The SDK offers a unified interface for interacting with Nitronode payment channels, supporting both high-level state operations and low-level RPC access. It simplifies the process of managing channel states, performing off-chain transactions, and settling on-chain when necessary.
Key Features ¶
- Unified Client: A single `Client` struct for all operations.
- State Operations: High-level methods (`Deposit`, `Withdraw`, `Transfer`, `CloseHomeChannel`, `Acknowledge`) to build and co-sign channel states off-chain.
- Blockchain Settlement: A single `Checkpoint` method to settle the latest state on-chain (creating channels, depositing, withdrawing, or finalizing).
- Low-Level Access: Direct access to Nitronode RPC methods for advanced use cases (e.g., querying node config, balances, channel info).
- App Sessions: Comprehensive support for creating and managing application sessions.
- Session Keys: Support for registering and using session keys for delegated signing.
Usage ¶
To use the SDK, create a `Client` instance with your Nitronode WebSocket URL and signers. You can configure blockchain RPCs for on-chain operations.
package main
import (
"context"
"log"
"time"
"github.com/layer-3/nitrolite/pkg/sign"
sdk "github.com/layer-3/nitrolite/sdk/go"
"github.com/shopspring/decimal"
)
func main() {
// Initialize signers
privateKeyHex := "YOUR_PRIVATE_KEY_HEX"
stateSigner, err := sign.NewEthereumMsgSigner(privateKeyHex)
if err != nil {
log.Fatal(err)
}
txSigner, err := sign.NewEthereumRawSigner(privateKeyHex)
if err != nil {
log.Fatal(err)
}
// Create Client
client, err := sdk.NewClient(
"wss://nitronode-sandbox.yellow.org/v1/ws",
stateSigner,
txSigner,
sdk.WithBlockchainRPC(80002, "https://rpc-endpoint.example.com"),
sdk.WithHandshakeTimeout(10*time.Second),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
// 1. Deposit (Off-chain state preparation)
// This creates a new state reflecting the deposit intent.
state, err := client.Deposit(ctx, 80002, "usdc", decimal.NewFromInt(100))
if err != nil {
log.Fatal(err)
}
log.Printf("Deposit prepared. New state version: %d", state.Version)
// 2. Settlement (On-chain execution)
// This submits the transaction to the blockchain to create the channel or deposit funds.
txHash, err := client.Checkpoint(ctx, "usdc")
if err != nil {
log.Fatal(err)
}
log.Printf("On-chain transaction hash: %s", txHash)
// 3. Transfer (Off-chain transaction)
// Transfers are instant and don't require immediate on-chain settlement.
_, err = client.Transfer(ctx, "0xRecipientAddress...", "usdc", decimal.NewFromInt(50))
if err != nil {
log.Fatal(err)
}
log.Println("Transfer completed off-chain")
}
Client Configuration ¶
The `NewClient` function accepts variadic `ClientOption` functions to customize behavior:
- `WithBlockchainRPC(chainID, url)`: Registers an RPC endpoint for on-chain settlement.
- `WithHandshakeTimeout(duration)`: Sets the timeout for the initial WebSocket handshake.
- `WithPingInterval(duration)`: Sets the interval for WebSocket ping/pong keepalives.
- `WithErrorHandler(func(error))`: Sets a callback for handling background connection errors.
Error Handling ¶
The SDK methods return standard Go errors. Common errors to check for include connection issues, insufficient balances, or invalid state transitions. Errors from RPC calls often contain detailed messages from the Nitronode server.
Index ¶
- Constants
- Variables
- func SignAppSessionKeyOwnership(state app.AppSessionKeyStateV1, sessionKeySigner *sign.EthereumMsgSigner) (string, error)
- func SignChannelSessionKeyOwnership(state core.ChannelSessionKeyStateV1, sessionKeySigner *sign.EthereumMsgSigner) (string, error)
- type Client
- func (c *Client) Acknowledge(ctx context.Context, asset string) (*core.State, error)
- func (c *Client) ApproveSecurityToken(ctx context.Context, chainID uint64, amount decimal.Decimal) (string, error)
- func (c *Client) ApproveToken(ctx context.Context, chainID uint64, asset string, amount decimal.Decimal) (string, error)
- func (c *Client) CancelSecurityTokensWithdrawal(ctx context.Context, blockchainID uint64) (string, error)
- func (c *Client) Challenge(ctx context.Context, state core.State) (string, error)
- func (c *Client) Checkpoint(ctx context.Context, asset string) (string, error)
- func (c *Client) Close() error
- func (c *Client) CloseHomeChannel(ctx context.Context, asset string) (*core.State, error)
- func (c *Client) CreateAppSession(ctx context.Context, definition app.AppDefinitionV1, sessionData string, ...) (string, string, string, error)
- func (c *Client) Deposit(ctx context.Context, blockchainID uint64, asset string, amount decimal.Decimal) (*core.State, error)
- func (c *Client) EscrowSecurityTokens(ctx context.Context, targetWalletAddress string, blockchainID uint64, ...) (string, error)
- func (c *Client) GetActionAllowances(ctx context.Context, wallet string) ([]core.ActionAllowance, error)
- func (c *Client) GetAppDefinition(ctx context.Context, appSessionID string) (*app.AppDefinitionV1, error)
- func (c *Client) GetAppSessions(ctx context.Context, opts *GetAppSessionsOptions) ([]app.AppSessionInfoV1, core.PaginationMetadata, error)
- func (c *Client) GetApps(ctx context.Context, opts *GetAppsOptions) ([]app.AppInfoV1, core.PaginationMetadata, error)
- func (c *Client) GetAssets(ctx context.Context, blockchainID *uint64) ([]core.Asset, error)
- func (c *Client) GetBalances(ctx context.Context, wallet string) ([]core.BalanceEntry, error)
- func (c *Client) GetBlockchains(ctx context.Context) ([]core.Blockchain, error)
- func (c *Client) GetConfig(ctx context.Context) (*core.NodeConfig, error)
- func (c *Client) GetEscrowChannel(ctx context.Context, escrowChannelID string) (*core.Channel, error)
- func (c *Client) GetHomeChannel(ctx context.Context, wallet, asset string) (*core.Channel, error)
- func (c *Client) GetLastAppKeyStates(ctx context.Context, userAddress string, opts *GetLastKeyStatesOptions) ([]app.AppSessionKeyStateV1, error)
- func (c *Client) GetLastChannelKeyStates(ctx context.Context, userAddress string, opts *GetLastChannelKeyStatesOptions) ([]core.ChannelSessionKeyStateV1, error)
- func (c *Client) GetLatestState(ctx context.Context, wallet, asset string, onlySigned bool) (*core.State, error)
- func (c *Client) GetLockedBalance(ctx context.Context, chainID uint64, wallet string) (decimal.Decimal, error)
- func (c *Client) GetOnChainBalance(ctx context.Context, chainID uint64, asset string, wallet string) (decimal.Decimal, error)
- func (c *Client) GetTransactions(ctx context.Context, wallet string, opts *GetTransactionsOptions) ([]core.Transaction, core.PaginationMetadata, error)
- func (c *Client) GetUserAddress() string
- func (c *Client) InitiateSecurityTokensWithdrawal(ctx context.Context, blockchainID uint64) (string, error)
- func (c *Client) Ping(ctx context.Context) error
- func (c *Client) RebalanceAppSessions(ctx context.Context, signedUpdates []app.SignedAppStateUpdateV1) (string, error)
- func (c *Client) RegisterApp(ctx context.Context, appID string, metadata string, ...) error
- func (c *Client) SetHomeBlockchain(asset string, blockchainId uint64) error
- func (c *Client) SignChannelSessionKeyState(state core.ChannelSessionKeyStateV1) (string, error)
- func (c *Client) SignSessionKeyState(state app.AppSessionKeyStateV1) (string, error)
- func (c *Client) SubmitAppSessionDeposit(ctx context.Context, appStateUpdate app.AppStateUpdateV1, quorumSigs []string, ...) (string, error)
- func (c *Client) SubmitAppSessionKeyState(ctx context.Context, state app.AppSessionKeyStateV1) error
- func (c *Client) SubmitAppState(ctx context.Context, appStateUpdate app.AppStateUpdateV1, quorumSigs []string) error
- func (c *Client) SubmitChannelSessionKeyState(ctx context.Context, state core.ChannelSessionKeyStateV1) error
- func (c *Client) Transfer(ctx context.Context, recipientWallet string, asset string, ...) (*core.State, error)
- func (c *Client) ValidateAndSignState(currentState, proposedState *core.State) (string, error)
- func (c *Client) WaitCh() <-chan struct{}
- func (c *Client) WatchValidatorRegistered(ctx context.Context, chainID uint64, fromBlock uint64) (<-chan *core.ValidatorRegisteredEvent, error)
- func (c *Client) Withdraw(ctx context.Context, blockchainID uint64, asset string, amount decimal.Decimal) (*core.State, error)
- func (c *Client) WithdrawSecurityTokens(ctx context.Context, blockchainID uint64, destinationWalletAddress string) (string, error)
- type Config
- type CreateAppSessionOptions
- type GetAppSessionsOptions
- type GetAppsOptions
- type GetLastChannelKeyStatesOptions
- type GetLastKeyStatesOptions
- type GetTransactionsOptions
- type Option
Constants ¶
const (
// DefaultChallengePeriod is the default challenge period for channels (1 day in seconds)
DefaultChallengePeriod = 86400
)
Variables ¶
var DefaultConfig = Config{ HandshakeTimeout: 5 * time.Second, PingTimeout: 15 * time.Second, ErrorHandler: defaultErrorHandler, }
DefaultConfig returns the default configuration with sensible defaults.
Functions ¶
func SignAppSessionKeyOwnership ¶ added in v1.3.0
func SignAppSessionKeyOwnership(state app.AppSessionKeyStateV1, sessionKeySigner *sign.EthereumMsgSigner) (string, error)
SignAppSessionKeyOwnership produces the session-key holder's ownership signature over the packed app-session key state. The signer must be the holder of the session key being registered; the resulting hex-encoded signature is intended to populate state.SessionKeySig before submitting via SubmitAppSessionKeyState. The packed state already binds user_address, so replay across wallets is not possible.
The parameter is narrowed to *sign.EthereumMsgSigner because the server recovers SessionKeySig under sign.TypeEthereumMsg — a broader signer interface could produce a signature without the EIP-191 prefix (or with extra wrapper bytes) that the server rejects.
func SignChannelSessionKeyOwnership ¶ added in v1.3.0
func SignChannelSessionKeyOwnership(state core.ChannelSessionKeyStateV1, sessionKeySigner *sign.EthereumMsgSigner) (string, error)
SignChannelSessionKeyOwnership produces the session-key holder's ownership signature for a channel session key registration. The signer must hold the session key; the returned hex string populates state.SessionKeySig before submit. The signed payload binds session_key into the metadata hash so a signature minted for one key cannot be replayed for another.
The parameter is narrowed to *sign.EthereumMsgSigner because the server recovers SessionKeySig under sign.TypeEthereumMsg — a broader signer interface could produce a signature without the EIP-191 prefix (or with extra wrapper bytes) that the server rejects.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client provides a unified interface for interacting with Nitronode. It combines state-building operations (Deposit, Withdraw, Transfer) with a single Checkpoint method for blockchain settlement, plus low-level RPC access for advanced use cases.
The two-step pattern for blockchain operations:
- Build and co-sign the state off-chain (Deposit, Withdraw, CloseHomeChannel, etc.)
- Settle on-chain via Checkpoint
High-level example:
stateSigner, _ := sign.NewEthereumMsgSigner(privateKeyHex)
txSigner, _ := sign.NewEthereumRawSigner(privateKeyHex)
client, _ := sdk.NewClient(
"wss://nitronode-sandbox.yellow.org/v1/ws",
stateSigner,
txSigner,
sdk.WithBlockchainRPC(80002, "https://polygon-amoy.alchemy.com/v2/KEY"),
)
defer client.Close()
// Deposit: build state, then settle on-chain
state, _ := client.Deposit(ctx, 80002, "usdc", decimal.NewFromInt(100))
txHash, _ := client.Checkpoint(ctx, "usdc")
// Transfer: off-chain only, no Checkpoint needed for existing channels
state, _ = client.Transfer(ctx, "0xRecipient...", "usdc", decimal.NewFromInt(50))
// Low-level operations
config, _ := client.GetConfig(ctx)
balances, _ := client.GetBalances(ctx, walletAddress)
func NewClient ¶
func NewClient(wsURL string, stateSigner core.ChannelSigner, rawSigner sign.Signer, opts ...Option) (*Client, error)
NewClient creates a new Nitronode client with both high-level and low-level methods. This is the recommended constructor for most use cases.
Parameters:
- wsURL: WebSocket URL of the Nitronode server (e.g., "wss://nitronode-sandbox.yellow.org/v1/ws")
- stateSigner: core.ChannelSigner for signing channel states (use sign.NewEthereumMsgSigner)
- txSigner: sign.Signer for signing blockchain transactions (use sign.NewEthereumRawSigner)
- opts: Optional configuration (WithBlockchainRPC, WithHandshakeTimeout, etc.)
Returns:
- Configured Client ready for operations
- Error if connection or initialization fails
Example:
stateSigner, _ := sign.NewEthereumMsgSigner(privateKeyHex)
txSigner, _ := sign.NewEthereumRawSigner(privateKeyHex)
client, err := sdk.NewClient(
"wss://nitronode-sandbox.yellow.org/v1/ws",
stateSigner,
txSigner,
sdk.WithBlockchainRPC(80002, "https://polygon-amoy.alchemy.com/v2/KEY"),
)
func (*Client) Acknowledge ¶
Acknowledge prepares an acknowledgement state for the given asset. This is used when a user receives a transfer but hasn't yet acknowledged the state, or to acknowledge channel creation without a deposit.
This method handles two scenarios automatically:
- If no channel exists: Creates a new channel with the acknowledgement transition
- If channel exists: Advances the state with an acknowledgement transition
The returned state is signed by both the user and the node.
Parameters:
- ctx: Context for the operation
- asset: The asset symbol to acknowledge (e.g., "usdc")
Returns:
- The co-signed state with the acknowledgement transition applied
- Error if the operation fails
Requirements:
- Home blockchain must be set for the asset (use SetHomeBlockchain) when no channel exists
Example:
state, err := client.Acknowledge(ctx, "usdc")
func (*Client) ApproveSecurityToken ¶
func (c *Client) ApproveSecurityToken(ctx context.Context, chainID uint64, amount decimal.Decimal) (string, error)
ApproveSecurityToken approves the Locking contract to spend tokens on behalf of the caller. This must be called before Lock Security Tokens.
Parameters:
- ctx: Context for the operation
- chainID: The blockchain network ID
- amount: The amount of tokens to approve
Returns:
- Transaction hash
- Error if the operation fails
func (*Client) ApproveToken ¶
func (c *Client) ApproveToken(ctx context.Context, chainID uint64, asset string, amount decimal.Decimal) (string, error)
ApproveToken approves the ChannelHub contract to spend tokens on behalf of the user. This is required before depositing ERC-20 tokens. Native tokens (e.g., ETH) do not require approval and will return an error if attempted.
Parameters:
- ctx: Context for the operation
- chainID: The blockchain network ID (e.g., 11155111 for Sepolia)
- asset: The asset symbol to approve (e.g., "usdc")
- amount: The amount to approve for spending
Returns:
- Transaction hash of the approval transaction
- Error if the operation fails or the asset is a native token
func (*Client) CancelSecurityTokensWithdrawal ¶
func (c *Client) CancelSecurityTokensWithdrawal(ctx context.Context, blockchainID uint64) (string, error)
CancelSecurityTokensWithdrawal re-locks tokens that are currently in the unlocking state, cancelling the pending unlock and returning them to the locked state.
Parameters:
- ctx: Context for the operation
- blockchainID: The blockchain network ID
Returns:
- Transaction hash
- Error if the operation fails
func (*Client) Challenge ¶
Challenge submits an on-chain challenge for a channel using a co-signed state. The state must have both user and node signatures, which are validated before the challenge transaction is submitted.
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.
Parameters:
- ctx: Context for the operation
- state: A co-signed state (both UserSig and NodeSig must be present)
Returns:
- Transaction hash of the on-chain challenge transaction
- Error if validation or submission fails
Requirements:
- Blockchain RPC must be configured for the chain (use WithBlockchainRPC)
- State must have both user and node signatures
- State must have a HomeChannelID
Example:
state, err := client.GetLatestState(ctx, wallet, "usdc", true)
txHash, err := client.Challenge(ctx, *state)
fmt.Printf("Challenge transaction: %s\n", txHash)
func (*Client) Checkpoint ¶
Checkpoint executes the blockchain transaction for the latest signed state. It fetches the latest co-signed state and, based on the transition type and on-chain channel status, calls the appropriate blockchain method.
This is the only method that interacts with the blockchain. It should be called after any state-building method (Deposit, Withdraw, CloseHomeChannel, etc.) to settle the state on-chain. It can also be used as a recovery mechanism if a previous blockchain transaction failed (e.g., due to gas issues or network problems).
Blockchain method mapping:
- Channel not yet on-chain (status Void): Creates the channel via blockchainClient.Create
- HomeDeposit/HomeWithdrawal on existing channel: Checkpoints via blockchainClient.Checkpoint
- Finalize: Closes the channel via blockchainClient.Close
Parameters:
- ctx: Context for the operation
- asset: The asset symbol (e.g., "usdc")
Returns:
- Transaction hash of the blockchain transaction
- Error if the operation fails or no blockchain operation is needed
Requirements:
- Blockchain RPC must be configured for the chain (use WithBlockchainRPC)
- A co-signed state must exist (call Deposit, Withdraw, etc. first)
Example:
state, err := client.Deposit(ctx, 80002, "usdc", decimal.NewFromInt(100))
txHash, err := client.Checkpoint(ctx, "usdc")
fmt.Printf("On-chain transaction: %s\n", txHash)
func (*Client) Close ¶
Close cleanly shuts down the client connection. It's recommended to defer this call after creating the client.
Example:
client, err := NewClient(...)
if err != nil {
log.Fatal(err)
}
defer client.Close()
func (*Client) CloseHomeChannel ¶
CloseHomeChannel prepares a finalize state to close the user's channel for a specific asset. This creates a final state with zero user balance and submits it to the node.
The returned state is signed by both the user and the node, but has not yet been submitted to the blockchain. Use Checkpoint to execute the on-chain close.
Parameters:
- ctx: Context for the operation
- asset: The asset symbol to close (e.g., "usdc")
Returns:
- The co-signed finalize state ready for on-chain close
- Error if the operation fails
Errors:
- Returns error if no channel exists for the asset
- Returns error if state signing or submission fails
Example:
state, err := client.CloseHomeChannel(ctx, "usdc")
txHash, err := client.Checkpoint(ctx, "usdc")
fmt.Printf("Close transaction: %s\n", txHash)
func (*Client) CreateAppSession ¶
func (c *Client) CreateAppSession(ctx context.Context, definition app.AppDefinitionV1, sessionData string, quorumSigs []string, opts ...CreateAppSessionOptions) (string, string, string, error)
CreateAppSession creates a new application session between participants.
Parameters:
- definition: The app definition with participants, quorum, application ID
- sessionData: Optional JSON stringified session data
- quorumSigs: Participant signatures for the app session creation
- opts: Optional parameters (owner signature for apps requiring approval)
Returns:
- AppSessionID of the created session
- Initial version of the session
- Status of the session
- Error if the request fails
Example:
def := app.AppDefinitionV1{
Application: "chess-v1",
Participants: []app.AppParticipantV1{...},
Quorum: 2,
Nonce: 1,
}
sessionID, version, status, err := client.CreateAppSession(ctx, def, "{}", []string{"sig1", "sig2"})
func (*Client) Deposit ¶
func (c *Client) Deposit(ctx context.Context, blockchainID uint64, asset string, amount decimal.Decimal) (*core.State, error)
Deposit prepares a deposit state for the user's channel. This method handles two scenarios automatically:
- If no channel exists: Creates a new channel with the initial deposit
- If channel exists: Advances the state with a deposit transition
The returned state is signed by both the user and the node, but has not yet been submitted to the blockchain. Use Checkpoint to execute the on-chain transaction.
Parameters:
- ctx: Context for the operation
- blockchainID: The blockchain network ID (e.g., 80002 for Polygon Amoy)
- asset: The asset symbol to deposit (e.g., "usdc")
- amount: The amount to deposit
Returns:
- The co-signed state ready for on-chain checkpoint
- Error if the operation fails
Example:
state, err := client.Deposit(ctx, 80002, "usdc", decimal.NewFromInt(100))
txHash, err := client.Checkpoint(ctx, "usdc")
fmt.Printf("Deposit transaction: %s\n", txHash)
func (*Client) EscrowSecurityTokens ¶
func (c *Client) EscrowSecurityTokens(ctx context.Context, targetWalletAddress string, blockchainID uint64, amount decimal.Decimal) (string, error)
EscrowSecurityTokens locks tokens into the Locking contract on the specified blockchain. The tokens are locked for the caller's own address. Before calling this method, you must approve the Locking to spend your tokens using ApproveSecurityToken.
Parameters:
- ctx: Context for the operation
- destinationWalletAddress: The Ethereum address to lock tokens for
- blockchainID: The blockchain network ID
- amount: The amount of tokens to lock (in human-readable decimals, e.g., 100.5 USDC)
Returns:
- Transaction hash
- Error if the operation fails
func (*Client) GetActionAllowances ¶
func (c *Client) GetActionAllowances(ctx context.Context, wallet string) ([]core.ActionAllowance, error)
GetActionAllowances retrieves the action allowances for a user based on their staking level.
Parameters:
- wallet: The user's wallet address
Returns:
- Slice of ActionAllowance containing allowance information per gated action
- Error if the request fails
func (*Client) GetAppDefinition ¶
func (c *Client) GetAppDefinition(ctx context.Context, appSessionID string) (*app.AppDefinitionV1, error)
GetAppDefinition retrieves the definition for a specific app session.
Returns (nil, nil) when no app session exists for the given ID — absence is a successful response, not an error.
Parameters:
- appSessionID: The application session ID
Returns:
- app.AppDefinitionV1 with participants, quorum, and application info, or nil if absent
- Error if the request fails
Example:
def, err := client.GetAppDefinition(ctx, "session123")
if err != nil { return err }
if def == nil { /* not found */ }
func (*Client) GetAppSessions ¶
func (c *Client) GetAppSessions(ctx context.Context, opts *GetAppSessionsOptions) ([]app.AppSessionInfoV1, core.PaginationMetadata, error)
GetAppSessions retrieves application sessions with optional filtering.
Parameters:
- opts: Optional filters (pass nil for no filters)
Returns:
- Slice of AppSession
- core.PaginationMetadata with pagination information
- Error if the request fails
Example:
sessions, meta, err := client.GetAppSessions(ctx, nil)
for _, session := range sessions {
fmt.Printf("Session %s: %d participants\n", session.AppSessionID, len(session.Participants))
}
func (*Client) GetApps ¶
func (c *Client) GetApps(ctx context.Context, opts *GetAppsOptions) ([]app.AppInfoV1, core.PaginationMetadata, error)
GetApps retrieves registered applications with optional filtering.
Parameters:
- opts: Optional filters (pass nil for no filters)
Returns:
- Slice of AppInfoV1 with application information
- core.PaginationMetadata with pagination information
- Error if the request fails
Example:
apps, meta, err := client.GetApps(ctx, nil)
for _, a := range apps {
fmt.Printf("App %s owned by %s\n", a.App.ID, a.App.OwnerWallet)
}
func (*Client) GetAssets ¶
GetAssets retrieves all supported assets with optional blockchain filter.
Parameters:
- blockchainID: Optional blockchain ID to filter assets (pass nil for all assets)
Returns:
- Slice of Asset containing asset information and token implementations
- Error if the request fails
Example:
assets, err := client.GetAssets(ctx, nil)
for _, asset := range assets {
fmt.Printf("%s (%s): %d tokens\n", asset.Name, asset.Symbol, len(asset.Tokens))
}
func (*Client) GetBalances ¶
GetBalances retrieves the balance information for a user.
Parameters:
- wallet: The user's wallet address
Returns:
- Slice of Balance containing asset balances
- Error if the request fails
Example:
balances, err := client.GetBalances(ctx, "0x1234567890abcdef...")
for _, balance := range balances {
fmt.Printf("%s: %s\n", balance.Asset, balance.Balance)
}
func (*Client) GetBlockchains ¶
GetBlockchains retrieves the list of supported blockchain networks. This is a convenience method that calls GetConfig and extracts the blockchains list.
Returns:
- Slice of Blockchain containing name, chain ID, and contract address for each network
- Error if the request fails
Example:
blockchains, err := client.GetBlockchains(ctx)
for _, bc := range blockchains {
fmt.Printf("%s: %s\n", bc.Name, bc.ChannelHubAddress)
}
func (*Client) GetConfig ¶
GetConfig retrieves the nitronode configuration including node identity and supported blockchains.
Returns:
- NodeConfig containing the node address, version, and list of supported blockchain networks
- Error if the request fails
Example:
config, err := client.GetConfig(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Node: %s (v%s)\n", config.NodeAddress, config.NodeVersion)
func (*Client) GetEscrowChannel ¶
func (c *Client) GetEscrowChannel(ctx context.Context, escrowChannelID string) (*core.Channel, error)
GetEscrowChannel retrieves escrow channel information for a specific channel ID.
Returns (nil, nil) when no escrow channel exists for the given ID — absence is a successful response, not an error.
Parameters:
- escrowChannelID: The escrow channel ID to query
Returns:
- Channel information for the escrow channel, or nil if absent
- Error if the request fails
Note: when the escrow channel has been closed by the on-chain purge queue (no signed FINALIZE_ESCROW_DEPOSIT was received before expiry), StateVersion on the returned channel reflects the initiate version (N) and does not advance to the finalize version (N+1).
Example:
channel, err := client.GetEscrowChannel(ctx, "0x1234...")
if err != nil { return err }
if channel == nil { /* not found */ }
func (*Client) GetHomeChannel ¶
GetHomeChannel retrieves home channel information for a user's asset.
Returns (nil, nil) when no home channel exists for the wallet/asset pair — absence is a successful response, not an error.
Parameters:
- wallet: The user's wallet address
- asset: The asset symbol
Returns:
- Channel information for the home channel, or nil if absent
- Error if the request fails
Example:
channel, err := client.GetHomeChannel(ctx, "0x1234...", "usdc")
if err != nil { return err }
if channel == nil { /* no channel yet */ }
func (*Client) GetLastAppKeyStates ¶
func (c *Client) GetLastAppKeyStates(ctx context.Context, userAddress string, opts *GetLastKeyStatesOptions) ([]app.AppSessionKeyStateV1, error)
GetLastAppKeyStates retrieves the latest app session key states for a user. By default only currently active (non-expired) states are returned; set opts.IncludeInactive to true to include expired or revoked latest states.
Parameters:
- userAddress: The user's wallet address
- opts: Optional filters (pass nil for active-only with no session-key filter)
Returns:
- Slice of AppSessionKeyStateV1 with the latest session key states matching the filter
- Error if the request fails
Example:
states, err := client.GetLastAppKeyStates(ctx, "0x1234...", nil)
for _, state := range states {
fmt.Printf("Session key %s expires at %s\n", state.SessionKey, state.ExpiresAt)
}
func (*Client) GetLastChannelKeyStates ¶
func (c *Client) GetLastChannelKeyStates(ctx context.Context, userAddress string, opts *GetLastChannelKeyStatesOptions) ([]core.ChannelSessionKeyStateV1, error)
GetLastChannelKeyStates retrieves the latest channel session key states for a user. By default only currently active (non-expired) states are returned; set opts.IncludeInactive to true to include expired or revoked latest states.
Parameters:
- userAddress: The user's wallet address
- opts: Optional filters (pass nil for active-only with no session-key filter)
Returns:
- Slice of ChannelSessionKeyStateV1 with the latest session key states matching the filter
- Error if the request fails
Example:
states, err := client.GetLastChannelKeyStates(ctx, "0x1234...", nil)
for _, state := range states {
fmt.Printf("Session key %s expires at %s\n", state.SessionKey, state.ExpiresAt)
}
func (*Client) GetLatestState ¶
func (c *Client) GetLatestState(ctx context.Context, wallet, asset string, onlySigned bool) (*core.State, error)
GetLatestState retrieves the latest state for a user's asset.
Returns (nil, nil) when the node has no stored state for the wallet/asset pair — absence is a successful response, not an error.
Parameters:
- wallet: The user's wallet address
- asset: The asset symbol (e.g., "usdc")
- onlySigned: If true, returns only the latest signed state
Returns:
- core.State containing all state information, or nil if absent
- Error if the request fails
Example:
state, err := client.GetLatestState(ctx, "0x1234...", "usdc", false)
if err != nil { return err }
if state == nil { /* no state yet */ }
func (*Client) GetLockedBalance ¶
func (c *Client) GetLockedBalance(ctx context.Context, chainID uint64, wallet string) (decimal.Decimal, error)
GetLockedBalance returns the locked balance of a user in the Locking contract.
Parameters:
- ctx: Context for the operation
- chainID: The blockchain network ID
- wallet: The Ethereum address to check
Returns:
- The locked balance as a decimal (adjusted for token decimals)
- Error if the query fails
func (*Client) GetOnChainBalance ¶
func (c *Client) GetOnChainBalance(ctx context.Context, chainID uint64, asset string, wallet string) (decimal.Decimal, error)
GetOnChainBalance queries the on-chain token balance (ERC-20 or native ETH) for a wallet on a specific blockchain.
Parameters:
- ctx: Context for the operation
- chainID: The blockchain ID to query (e.g., 80002 for Polygon Amoy)
- asset: The asset symbol (e.g., "usdc", "weth")
- wallet: The Ethereum address to check the balance for
Returns:
- The token balance as a decimal (already adjusted for token decimals)
- Error if the query fails
Requirements:
- Blockchain RPC must be configured for the chain (use WithBlockchainRPC)
func (*Client) GetTransactions ¶
func (c *Client) GetTransactions(ctx context.Context, wallet string, opts *GetTransactionsOptions) ([]core.Transaction, core.PaginationMetadata, error)
GetTransactions retrieves transaction history for a user with optional filtering.
Parameters:
- wallet: The user's wallet address
- opts: Optional filters (pass nil for no filters)
Returns:
- Slice of Transaction
- core.PaginationMetadata with pagination information
- Error if the request fails
Example:
txs, meta, err := client.GetTransactions(ctx, "0x1234...", nil)
for _, tx := range txs {
fmt.Printf("%s: %s → %s (%s %s)\n", tx.TxType, tx.FromAccount, tx.ToAccount, tx.Amount, tx.Asset)
}
func (*Client) GetUserAddress ¶
GetUserAddress returns the Ethereum address associated with the signer. This is useful for identifying the current user's wallet address.
func (*Client) InitiateSecurityTokensWithdrawal ¶
func (c *Client) InitiateSecurityTokensWithdrawal(ctx context.Context, blockchainID uint64) (string, error)
InitiateSecurityTokensWithdrawal initiates the unlock process for locked tokens in the Locking contract. After the unlock period elapses, Withdraw Security Tokens can be called to retrieve the tokens.
Parameters:
- ctx: Context for the operation
- blockchainID: The blockchain network ID
Returns:
- Transaction hash
- Error if the operation fails
func (*Client) Ping ¶
Ping checks connectivity to the nitronode server. This is useful for health checks and verifying the connection is active.
Example:
if err := client.Ping(ctx); err != nil {
log.Printf("Server is unreachable: %v", err)
}
func (*Client) RebalanceAppSessions ¶
func (c *Client) RebalanceAppSessions(ctx context.Context, signedUpdates []app.SignedAppStateUpdateV1) (string, error)
RebalanceAppSessions rebalances multiple application sessions atomically.
This method performs atomic rebalancing across multiple app sessions, ensuring that funds are redistributed consistently without the risk of partial updates.
Parameters:
- signedUpdates: Slice of signed app state updates to apply atomically
Returns:
- BatchID for tracking the rebalancing operation
- Error if the request fails
Example:
updates := []app.SignedAppStateUpdateV1{...}
batchID, err := client.RebalanceAppSessions(ctx, updates)
fmt.Printf("Rebalance batch ID: %s\n", batchID)
func (*Client) RegisterApp ¶
func (c *Client) RegisterApp(ctx context.Context, appID string, metadata string, creationApprovalNotRequired bool) error
RegisterApp registers a new application in the app registry. Currently only version 1 (creation) is supported.
The method builds the app definition from the provided parameters, using the client's signer address as the owner wallet and version 1. It then packs and signs the definition automatically.
Session key signers are not allowed to perform this action; the main wallet signer must be used.
Parameters:
- appID: The application identifier
- metadata: The application metadata
- creationApprovalNotRequired: Whether sessions can be created without owner approval
Returns:
- Error if the request fails
Example:
err := client.RegisterApp(ctx, "my-app", `{"name": "My App"}`, false)
func (*Client) SetHomeBlockchain ¶
SetHomeBlockchain configures the primary blockchain network for a specific asset. This is required for operations like Transfer which may trigger channel creation but do not accept a blockchain ID as a parameter.
Validation:
- Checks if the asset is actually supported on the specified blockchain.
- Verifies that a home blockchain hasn't already been set for this asset.
Constraints:
- This mapping is immutable once set for the client instance.
- To move an asset to a different blockchain, use the Migrate() method instead.
Parameters:
- asset: The asset symbol (e.g., "usdc")
- blockchainId: The chain ID to associate with the asset (e.g., 80002)
Example:
// Set USDC to settle on Polygon Amoy
if err := client.SetHomeBlockchain("usdc", 80002); err != nil {
log.Fatal(err)
}
func (*Client) SignChannelSessionKeyState ¶
func (c *Client) SignChannelSessionKeyState(state core.ChannelSessionKeyStateV1) (string, error)
SignChannelSessionKeyState produces the wallet UserSig over the channel session key state using the client's state signer. Set the returned hex on state.UserSig before submit. The matching session-key-holder SessionKeySig must also be populated (see SignChannelSessionKeyOwnership) — submits with only one of the two are rejected.
Parameters:
- state: The channel session key state to sign (UserSig and SessionKeySig fields are excluded from signing)
Returns:
- The hex-encoded signature string
- Error if signing fails
Example:
state := core.ChannelSessionKeyStateV1{
UserAddress: client.GetUserAddress(),
SessionKey: "0xabcd...",
Version: 1,
Assets: []string{"usdc"},
ExpiresAt: time.Now().Add(24 * time.Hour),
}
state.UserSig, _ = client.SignChannelSessionKeyState(state)
state.SessionKeySig, _ = sdk.SignChannelSessionKeyOwnership(state, sessionKeySigner)
err = client.SubmitChannelSessionKeyState(ctx, state)
func (*Client) SignSessionKeyState ¶
func (c *Client) SignSessionKeyState(state app.AppSessionKeyStateV1) (string, error)
SignSessionKeyState produces the wallet UserSig over the session key state using the client's state signer. Set the returned hex on state.UserSig before submit. The matching session-key-holder SessionKeySig must also be populated (see SignAppSessionKeyOwnership) — submits with only one of the two are rejected.
Parameters:
- state: The session key state to sign (UserSig and SessionKeySig fields are excluded from signing)
Returns:
- The hex-encoded signature string
- Error if signing fails
Example:
state := app.AppSessionKeyStateV1{
UserAddress: client.GetUserAddress(),
SessionKey: "0xabcd...",
Version: 1,
ApplicationIDs: []string{},
AppSessionIDs: []string{},
ExpiresAt: time.Now().Add(24 * time.Hour),
}
state.UserSig, _ = client.SignSessionKeyState(state)
state.SessionKeySig, _ = sdk.SignAppSessionKeyOwnership(state, sessionKeySigner)
err = client.SubmitAppSessionKeyState(ctx, state)
func (*Client) SubmitAppSessionDeposit ¶
func (c *Client) SubmitAppSessionDeposit(ctx context.Context, appStateUpdate app.AppStateUpdateV1, quorumSigs []string, asset string, depositAmount decimal.Decimal) (string, error)
SubmitAppSessionDeposit submits a deposit to an app session. This updates both the app session state and the user's channel state.
Parameters:
- appStateUpdate: The app state update with deposit intent
- quorumSigs: Participant signatures for the app state update
- userState: The user's updated channel state
Returns:
- Node's signature for the state
- Error if the request fails
Example:
appUpdate := app.AppStateUpdateV1{
AppSessionID: "session123",
Intent: app.AppStateUpdateIntentDeposit,
Version: 2,
Allocations: []app.AppAllocationV1{...},
}
nodeSig, err := client.SubmitAppSessionDeposit(ctx, appUpdate, []string{"sig1"}, userState)
func (*Client) SubmitAppSessionKeyState ¶
func (c *Client) SubmitAppSessionKeyState(ctx context.Context, state app.AppSessionKeyStateV1) error
SubmitAppSessionKeyState submits a session key state for registration, update, revocation, or re-activation. The state must carry both the wallet's UserSig (authorizing the delegation) and the session-key holder's SessionKeySig (proving possession of the key); submits without a valid SessionKeySig are rejected on every path, including revocation — the session key must co-sign its own deactivation. Wallet-only revocation (for a lost or compromised key) is not supported by this method.
Set state.ExpiresAt to a future time to register or update the key. Set it to a value at or before time.Now() to revoke the key — the auth path filters by expires_at, so the key is deactivated immediately while keeping the same monotonic version sequence. A later submit with the next version and a future ExpiresAt re-activates the same session key address. Negative unix timestamps are rejected by the server.
Parameters:
- state: The session key state containing delegation information
Returns:
- Error if the request fails
Example:
state := app.AppSessionKeyStateV1{
UserAddress: "0x1234...",
SessionKey: "0xabcd...",
Version: 1,
ApplicationIDs: []string{"app1"},
AppSessionIDs: []string{},
ExpiresAt: time.Now().Add(24 * time.Hour),
}
state.UserSig, _ = client.SignSessionKeyState(state)
state.SessionKeySig, _ = sdk.SignAppSessionKeyOwnership(state, sessionKeySigner)
err := client.SubmitAppSessionKeyState(ctx, state)
func (*Client) SubmitAppState ¶
func (c *Client) SubmitAppState(ctx context.Context, appStateUpdate app.AppStateUpdateV1, quorumSigs []string) error
SubmitAppState submits an app session state update. This method handles operate, withdraw, and close intents. For deposits, use SubmitAppSessionDeposit instead.
Parameters:
- appStateUpdate: The app state update (intent: operate, withdraw, or close)
- quorumSigs: Participant signatures for the app state update
Returns:
- Error if the request fails
Example:
appUpdate := app.AppStateUpdateV1{
AppSessionID: "session123",
Intent: app.AppStateUpdateIntentOperate,
Version: 3,
Allocations: []app.AppAllocationV1{...},
}
err := client.SubmitAppState(ctx, appUpdate, []string{"sig1", "sig2"})
func (*Client) SubmitChannelSessionKeyState ¶
func (c *Client) SubmitChannelSessionKeyState(ctx context.Context, state core.ChannelSessionKeyStateV1) error
SubmitChannelSessionKeyState submits a channel session key state for registration, update, revocation, or re-activation. The state must carry both the wallet's UserSig (authorizing the delegation) and the session-key holder's SessionKeySig (proving possession of the key); submits without a valid SessionKeySig are rejected on every path, including revocation — the session key must co-sign its own deactivation. Wallet-only revocation (for a lost or compromised key) is not supported by this method.
Set state.ExpiresAt to a future time to register or update the key. Set it to a value at or before time.Now() to revoke the key — the auth path filters by expires_at, so the key is deactivated immediately while keeping the same monotonic version sequence. A later submit with the next version and a future ExpiresAt re-activates the same session key address. Negative unix timestamps are rejected by the server.
Parameters:
- state: The channel session key state containing delegation information
Returns:
- Error if the request fails
Example:
state := core.ChannelSessionKeyStateV1{
UserAddress: "0x1234...",
SessionKey: "0xabcd...",
Version: 1,
Assets: []string{"usdc", "weth"},
ExpiresAt: time.Now().Add(24 * time.Hour),
}
state.UserSig, _ = client.SignChannelSessionKeyState(state)
state.SessionKeySig, _ = sdk.SignChannelSessionKeyOwnership(state, sessionKeySigner)
err := client.SubmitChannelSessionKeyState(ctx, state)
func (*Client) Transfer ¶
func (c *Client) Transfer(ctx context.Context, recipientWallet string, asset string, amount decimal.Decimal) (*core.State, error)
Transfer prepares a transfer state to send funds to another wallet address. This method handles two scenarios automatically:
- If no channel exists: Creates a new channel with the transfer transition
- If channel exists: Advances the state with a transfer send transition
The returned state is signed by both the user and the node. For existing channels, no blockchain interaction is needed. For new channels, use Checkpoint to create the channel on-chain.
Parameters:
- ctx: Context for the operation
- recipientWallet: The recipient's wallet address (e.g., "0x1234...")
- asset: The asset symbol to transfer (e.g., "usdc")
- amount: The amount to transfer
Returns:
- The co-signed state with the transfer transition applied
- Error if the operation fails
Example:
state, err := client.Transfer(ctx, "0xRecipient...", "usdc", decimal.NewFromInt(50))
fmt.Printf("Transfer tx ID: %s\n", state.Transition.TxID)
func (*Client) ValidateAndSignState ¶ added in v1.3.0
ValidateAndSignState firstly validates and then signs a channel state by packing it, hashing it, and signing the hash. Returns the signature as a hex-encoded string (with 0x prefix).
This is a low-level method exposed for advanced users who want to manually construct and sign states. Most users should use the high-level methods like Transfer, Deposit, and Withdraw instead.
func (*Client) WaitCh ¶
func (c *Client) WaitCh() <-chan struct{}
WaitCh returns a channel that closes when the connection is lost or closed. This is useful for monitoring connection health in long-running applications.
Example:
go func() {
<-client.WaitCh()
log.Println("Connection closed")
}()
func (*Client) WatchValidatorRegistered ¶ added in v1.3.0
func (c *Client) WatchValidatorRegistered(ctx context.Context, chainID uint64, fromBlock uint64) (<-chan *core.ValidatorRegisteredEvent, error)
WatchValidatorRegistered subscribes to ValidatorRegistered events on the ChannelHub contract for the given chain and delivers them on the returned channel.
Each event carries the newly registered validator ID, its contract address, and the block number at which it was emitted. App builders should alert users and prompt them to revoke ERC20 approvals granted to ChannelHub whenever an unexpected validator appears — see contracts/SECURITY.md.
Gap-free monitoring: pass fromBlock = 0 on the first call. On reconnect, pass lastEvent.BlockNumber + 1 so any events emitted during the outage are replayed before live events resume. This ensures the 1-day VALIDATOR_ACTIVATION_DELAY window is not silently shortened by network interruptions.
The channel is closed when ctx is cancelled or the subscription is lost. On a lost subscription (network drop), call WatchValidatorRegistered again with the last received BlockNumber + 1 to resubscribe without gaps.
The RPC URL configured via WithBlockchainRPC for chainID must be a WebSocket endpoint (wss:// or ws://). HTTP endpoints do not support event subscriptions and will return an error.
func (*Client) Withdraw ¶
func (c *Client) Withdraw(ctx context.Context, blockchainID uint64, asset string, amount decimal.Decimal) (*core.State, error)
Withdraw prepares a withdrawal state to remove funds from the user's channel. This operation handles two scenarios automatically:
- If no channel exists: Creates a new channel with the withdrawal transition
- If channel exists: Advances the state with a withdrawal transition
The returned state is signed by both the user and the node, but has not yet been submitted to the blockchain. Use Checkpoint to execute the on-chain transaction.
Parameters:
- ctx: Context for the operation
- blockchainID: The blockchain network ID (e.g., 80002 for Polygon Amoy)
- asset: The asset symbol to withdraw (e.g., "usdc")
- amount: The amount to withdraw
Returns:
- The co-signed state ready for on-chain checkpoint
- Error if the operation fails
Example:
state, err := client.Withdraw(ctx, 80002, "usdc", decimal.NewFromInt(25))
txHash, err := client.Checkpoint(ctx, "usdc")
fmt.Printf("Withdrawal transaction: %s\n", txHash)
func (*Client) WithdrawSecurityTokens ¶
func (c *Client) WithdrawSecurityTokens(ctx context.Context, blockchainID uint64, destinationWalletAddress string) (string, error)
WithdrawSecurityTokens withdraws unlocked tokens from the Locking contract to the specified destination. Can only be called after the unlock period has fully elapsed.
Parameters:
- ctx: Context for the operation
- blockchainID: The blockchain network ID
- destinationWalletAddress: The Ethereum address to receive the withdrawn tokens
Returns:
- Transaction hash
- Error if the operation fails
type Config ¶
type Config struct {
// URL is the WebSocket URL of the nitronode server
URL string
// HandshakeTimeout is the maximum time to wait for initial connection
HandshakeTimeout time.Duration
// PingTimeout is how long to wait for a ping from the server before considering the connection dead.
// The server sends periodic pings to detect dead clients.
PingTimeout time.Duration
// ErrorHandler is called when connection errors occur
ErrorHandler func(error)
// BlockchainRPCs maps blockchain IDs to their RPC endpoints
// Used by SDKClient for on-chain operations
BlockchainRPCs map[uint64]string
// ApplicationID is an advisory origin tag the client sends to the nitronode as
// the "app_id" WebSocket query parameter. The nitronode stamps this value on
// records produced by requests from this client. Empty means no tag.
ApplicationID string
}
Config holds the configuration options for the Nitronode client.
type CreateAppSessionOptions ¶
type CreateAppSessionOptions struct {
// OwnerSig is the app owner's signature approving session creation.
// Required when the app's CreationApprovalNotRequired is false.
OwnerSig string
}
CreateAppSessionOptions contains optional parameters for CreateAppSession.
type GetAppSessionsOptions ¶
type GetAppSessionsOptions struct {
// AppSessionID filters by application session ID
AppSessionID *string
// Participant filters by participant wallet address
Participant *string
// Status filters by status ("open" or "closed")
Status *string
// Pagination parameters
Pagination *core.PaginationParams
}
GetAppSessionsOptions contains optional filters for GetAppSessions.
type GetAppsOptions ¶
type GetAppsOptions struct {
// AppID filters by application ID
AppID *string
// OwnerWallet filters by owner wallet address
OwnerWallet *string
// Pagination parameters
Pagination *core.PaginationParams
}
GetAppsOptions contains optional filters for GetApps.
type GetLastChannelKeyStatesOptions ¶
type GetLastChannelKeyStatesOptions struct {
// SessionKey filters by a specific session key address
SessionKey *string
// IncludeInactive, when set to true, includes latest states whose expires_at is in
// the past (expired or revoked). Defaults to false (active-only) when nil or false.
IncludeInactive *bool
}
GetLastChannelKeyStatesOptions contains optional filters for GetLastChannelKeyStates.
type GetLastKeyStatesOptions ¶
type GetLastKeyStatesOptions struct {
// SessionKey filters by a specific session key address
SessionKey *string
// IncludeInactive, when set to true, includes latest states whose expires_at is in
// the past (expired or revoked). Defaults to false (active-only) when nil or false.
IncludeInactive *bool
}
GetLastKeyStatesOptions contains optional filters for GetLastKeyStates.
type GetTransactionsOptions ¶
type GetTransactionsOptions struct {
// Asset filters by asset symbol
Asset *string
// Pagination parameters
Pagination *core.PaginationParams
}
GetTransactionsOptions contains optional filters for GetTransactions.
type Option ¶
type Option func(*Config)
Option is a functional option for configuring the Client.
func WithApplicationID ¶ added in v1.3.0
WithApplicationID sets the application ID sent to the nitronode as the "app_id" WebSocket query parameter. The nitronode treats this as an advisory origin tag on records produced by requests from this connection.
func WithBlockchainRPC ¶
WithBlockchainRPC returns an Option that configures a blockchain RPC client for a specific chain. This is required for the Checkpoint method which settles states on-chain.
Parameters:
- chainID: The blockchain network ID (e.g., 80002 for Polygon Amoy testnet)
- rpcURL: The RPC endpoint URL (e.g., "https://polygon-amoy.alchemy.com/v2/KEY")
Example:
client, err := sdk.NewClient(
wsURL,
stateSigner,
txSigner,
sdk.WithBlockchainRPC(80002, "https://polygon-amoy.alchemy.com/v2/KEY"),
sdk.WithBlockchainRPC(84532, "https://base-sepolia.alchemy.com/v2/KEY"),
)
func WithErrorHandler ¶
WithErrorHandler sets a custom error handler for connection errors. The handler is called when the connection encounters an error or is closed.
func WithHandshakeTimeout ¶
WithHandshakeTimeout sets the maximum time to wait for initial connection.
func WithPingTimeout ¶
WithPingTimeout sets how long to wait for a ping from the server before considering the connection dead.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
app_sessions
command
|
|
|
challenge
command
|
|
|
channel_session_key
command
|
|
|
round_robin
command
|
|
|
validator_watcher
command
|