From caa5bd354a0ba172e30faf6a604379cbae2af311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matev=C5=BE=20Jekovec?= Date: Thu, 15 Jan 2026 13:05:11 +0100 Subject: [PATCH 1/2] cmd/account/show: Add JSON output --- cmd/account/show/delegations.go | 12 +- cmd/account/show/show.go | 302 +++++++++++++++++++------------- 2 files changed, 187 insertions(+), 127 deletions(-) diff --git a/cmd/account/show/delegations.go b/cmd/account/show/delegations.go index 14d84ec2..7186ace3 100644 --- a/cmd/account/show/delegations.go +++ b/cmd/account/show/delegations.go @@ -11,8 +11,6 @@ import ( staking "github.com/oasisprotocol/oasis-core/go/staking/api" "github.com/spf13/cobra" - "github.com/oasisprotocol/cli/cmd/common" - "github.com/oasisprotocol/oasis-sdk/client-sdk/go/config" "github.com/oasisprotocol/oasis-sdk/client-sdk/go/connection" "github.com/oasisprotocol/oasis-sdk/client-sdk/go/helpers" @@ -259,7 +257,7 @@ func prettyPrintParaTimeDelegations( ctx context.Context, c connection.Connection, height int64, - npa *common.NPASelection, + network *config.Network, addr *types.Address, rtDelegations []*consensusaccounts.ExtendedDelegationInfo, rtUndelegations []*consensusaccounts.UndelegationInfo, @@ -316,21 +314,21 @@ func prettyPrintParaTimeDelegations( if len(delegations) > 0 { fmt.Fprintf(w, "%sActive Delegations from this Account:\n", prefix) - fmt.Fprintf(w, "%sTotal: %s\n", innerPrefix, helpers.FormatConsensusDenomination(npa.Network, totalDeg)) + fmt.Fprintf(w, "%sTotal: %s\n", innerPrefix, helpers.FormatConsensusDenomination(network, totalDeg)) fmt.Fprintln(w) sort.Sort(byEndTimeAmountAddress(delegations)) - prettyPrintDelegationDescriptions(npa.Network, delegations, "To:", innerPrefix, w) + prettyPrintDelegationDescriptions(network, delegations, "To:", innerPrefix, w) fmt.Fprintln(w) } if len(undelegations) > 0 { fmt.Fprintf(w, "%sDebonding Delegations from this Account:\n", prefix) - fmt.Fprintf(w, "%sTotal: %s\n", innerPrefix, helpers.FormatConsensusDenomination(npa.Network, totalUndeg)) + fmt.Fprintf(w, "%sTotal: %s\n", innerPrefix, helpers.FormatConsensusDenomination(network, totalUndeg)) fmt.Fprintln(w) sort.Sort(byEndTimeAmountAddress(undelegations)) - prettyPrintDelegationDescriptions(npa.Network, undelegations, "To:", innerPrefix, w) + prettyPrintDelegationDescriptions(network, undelegations, "To:", innerPrefix, w) fmt.Fprintln(w) } } diff --git a/cmd/account/show/show.go b/cmd/account/show/show.go index 79caa4cf..adf9804c 100644 --- a/cmd/account/show/show.go +++ b/cmd/account/show/show.go @@ -2,10 +2,15 @@ package show import ( "context" + "encoding/json" "fmt" "os" "strings" + ethCommon "github.com/ethereum/go-ethereum/common" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/config" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/helpers" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/types" "github.com/spf13/cobra" flag "github.com/spf13/pflag" @@ -14,14 +19,32 @@ import ( staking "github.com/oasisprotocol/oasis-core/go/staking/api" "github.com/oasisprotocol/oasis-sdk/client-sdk/go/client" "github.com/oasisprotocol/oasis-sdk/client-sdk/go/connection" - "github.com/oasisprotocol/oasis-sdk/client-sdk/go/helpers" "github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/consensusaccounts" - "github.com/oasisprotocol/oasis-sdk/client-sdk/go/types" "github.com/oasisprotocol/cli/cmd/common" cliConfig "github.com/oasisprotocol/cli/config" ) +type accountShowOutput struct { + Name string `json:"name,omitempty"` + EthereumAddress *ethCommon.Address `json:"ethereum_address,omitempty"` + NativeAddress *types.Address `json:"native_address"` + Nonce uint64 `json:"nonce"` + NetworkName string `json:"network_name"` + Height int64 `json:"height"` + GeneralAccount *staking.GeneralAccount `json:"general_account,omitempty"` + OutgoingDelegations map[staking.Address]*staking.DelegationInfo `json:"outgoing_delegations,omitempty"` + OutgoingDebondingDelegations map[staking.Address][]*staking.DebondingDelegationInfo `json:"outgoing_debonding_delegations,omitempty"` + IncomingDelegations map[staking.Address]*staking.Delegation `json:"incoming_delegations,omitempty"` + IncomingDebondingDelegations map[staking.Address][]*staking.DebondingDelegation `json:"incoming_debonding_delegations,omitempty"` + EscrowAccount *staking.EscrowAccount `json:"escrow_account,omitempty"` + ParaTimeName string `json:"paratime_name,omitempty"` + ParaTimeBalances map[types.Denomination]types.Quantity `json:"paratime_balances,omitempty"` + ParaTimeNonce uint64 `json:"paratime_nonce,omitempty"` + ParaTimeDelegations []*consensusaccounts.ExtendedDelegationInfo `json:"paratime_delegations,omitempty"` + ParaTimeUndelegations []*consensusaccounts.UndelegationInfo `json:"paratime_undelegations,omitempty"` +} + var ( showDelegations bool @@ -35,6 +58,9 @@ var ( cfg := cliConfig.Global() npa := common.GetNPASelection(cfg) + out := accountShowOutput{} + out.NetworkName = npa.NetworkName + // Determine which address to show. If an explicit argument was given, use that // otherwise use the default account. var targetAddress string @@ -57,9 +83,11 @@ var ( nativeAddr, ethAddr, err := common.ResolveLocalAccountOrAddress(npa.Network, targetAddress) cobra.CheckErr(err) + out.EthereumAddress = ethAddr + out.NativeAddress = nativeAddr if name := common.FindAccountName(nativeAddr.String()); name != "" { - fmt.Printf("Name: %s\n", name) + out.Name = name } height, err := common.GetActualHeight( @@ -67,6 +95,7 @@ var ( c.Consensus().Core(), ) cobra.CheckErr(err) + out.Height = height ownerQuery := &staking.OwnerQuery{ Owner: nativeAddr.ConsensusAddress(), @@ -74,18 +103,11 @@ var ( } // Query consensus layer account. - // TODO: Nicer overall formatting. - consensusAccount, err := c.Consensus().Staking().Account(ctx, ownerQuery) cobra.CheckErr(err) - if ethAddr != nil { - fmt.Printf("Ethereum address: %s\n", ethAddr) - } - fmt.Printf("Native address: %s\n", nativeAddr) - fmt.Println() - fmt.Printf("=== CONSENSUS LAYER (%s) ===\n", npa.NetworkName) - fmt.Printf(" Nonce: %d\n", consensusAccount.General.Nonce) - fmt.Println() + out.EscrowAccount = &consensusAccount.Escrow + out.GeneralAccount = &consensusAccount.General + out.Nonce = consensusAccount.General.Nonce var ( outgoingDelegations map[staking.Address]*staking.DelegationInfo @@ -94,79 +116,24 @@ var ( if showDelegations { outgoingDelegations, err = c.Consensus().Staking().DelegationInfosFor(ctx, ownerQuery) cobra.CheckErr(err) + out.OutgoingDelegations = outgoingDelegations + outgoingDebondingDelegations, err = c.Consensus().Staking().DebondingDelegationInfosFor(ctx, ownerQuery) cobra.CheckErr(err) - } - - prettyPrintAccountBalanceAndDelegationsFrom( - npa.Network, - nativeAddr, - consensusAccount.General, - outgoingDelegations, - outgoingDebondingDelegations, - " ", - os.Stdout, - ) - - if len(consensusAccount.General.Allowances) > 0 { - fmt.Println(" Allowances for this Account:") - prettyPrintAllowances( - npa.Network, - nativeAddr, - consensusAccount.General.Allowances, - " ", - os.Stdout, - ) - fmt.Println() - } + out.OutgoingDebondingDelegations = outgoingDebondingDelegations - if showDelegations { incomingDelegations, err := c.Consensus().Staking().DelegationsTo(ctx, ownerQuery) cobra.CheckErr(err) + out.IncomingDelegations = incomingDelegations + incomingDebondingDelegations, err := c.Consensus().Staking().DebondingDelegationsTo(ctx, ownerQuery) cobra.CheckErr(err) - - if len(incomingDelegations) > 0 { - fmt.Println(" Active Delegations to this Account:") - prettyPrintDelegationsTo( - npa.Network, - nativeAddr, - consensusAccount.Escrow.Active, - incomingDelegations, - " ", - os.Stdout, - ) - fmt.Println() - } - if len(incomingDebondingDelegations) > 0 { - fmt.Println(" Debonding Delegations to this Account:") - prettyPrintDelegationsTo( - npa.Network, - nativeAddr, - consensusAccount.Escrow.Debonding, - incomingDebondingDelegations, - " ", - os.Stdout, - ) - fmt.Println() - } - } - - cs := consensusAccount.Escrow.CommissionSchedule - if len(cs.Rates) > 0 || len(cs.Bounds) > 0 { - fmt.Println(" Commission Schedule:") - cs.PrettyPrint(ctx, " ", os.Stdout) - fmt.Println() - } - - sa := consensusAccount.Escrow.StakeAccumulator - if len(sa.Claims) > 0 { - fmt.Println(" Stake Accumulator:") - sa.PrettyPrint(ctx, " ", os.Stdout) - fmt.Println() + out.IncomingDebondingDelegations = incomingDebondingDelegations } if npa.ParaTime != nil { + out.ParaTimeName = npa.ParaTimeName + // Make an effort to support the height query. // // Note: Public gRPC endpoints do not allow this method. @@ -186,63 +153,158 @@ var ( // Query runtime account when a ParaTime has been configured. rtBalances, err := c.Runtime(npa.ParaTime).Accounts.Balances(ctx, round, *nativeAddr) cobra.CheckErr(err) - - var hasNonZeroBalance bool - for _, balance := range rtBalances.Balances { - if hasNonZeroBalance = !balance.IsZero(); hasNonZeroBalance { - break - } - } + out.ParaTimeBalances = rtBalances.Balances nonce, err := c.Runtime(npa.ParaTime).Accounts.Nonce(ctx, round, *nativeAddr) cobra.CheckErr(err) - hasNonZeroNonce := nonce > 0 - - if hasNonZeroBalance || hasNonZeroNonce { - fmt.Printf("=== %s PARATIME ===\n", npa.ParaTimeName) - fmt.Printf(" Nonce: %d\n", nonce) - fmt.Println() - - if hasNonZeroBalance { - fmt.Printf(" Balances for all denominations:\n") - for denom, balance := range rtBalances.Balances { - fmtAmnt := helpers.FormatParaTimeDenomination(npa.ParaTime, types.NewBaseUnits(balance, denom)) - amnt, symbol, _ := strings.Cut(fmtAmnt, " ") - - fmt.Printf(" - Amount: %s\n", amnt) - fmt.Printf(" Symbol: %s\n", symbol) - } - - fmt.Println() - } - - if showDelegations { - rtDelegations, _ := c.Runtime(npa.ParaTime).ConsensusAccounts.Delegations( - ctx, - round, - &consensusaccounts.DelegationsQuery{ - From: *nativeAddr, - }, - ) - rtUndelegations, _ := c.Runtime(npa.ParaTime).ConsensusAccounts.Undelegations( - ctx, - round, - &consensusaccounts.UndelegationsQuery{ - To: *nativeAddr, - }, - ) - prettyPrintParaTimeDelegations(ctx, c, height, npa, nativeAddr, rtDelegations, rtUndelegations, " ", os.Stdout) - } + out.ParaTimeNonce = nonce + + if showDelegations { + rtDelegations, _ := c.Runtime(npa.ParaTime).ConsensusAccounts.Delegations( + ctx, + round, + &consensusaccounts.DelegationsQuery{ + From: *nativeAddr, + }, + ) + out.ParaTimeDelegations = rtDelegations + + rtUndelegations, _ := c.Runtime(npa.ParaTime).ConsensusAccounts.Undelegations( + ctx, + round, + &consensusaccounts.UndelegationsQuery{ + To: *nativeAddr, + }, + ) + out.ParaTimeUndelegations = rtUndelegations } } + + if common.OutputFormat() == common.FormatJSON { + data, err := json.MarshalIndent(out, "", " ") + cobra.CheckErr(err) + fmt.Printf("%s\n", data) + } else { + prettyPrintAccount(ctx, c, npa.Network, npa.ParaTime, &out) + } }, } ) +// prettyPrintAccount prints a compact human-readable summary of the account. +func prettyPrintAccount(ctx context.Context, c connection.Connection, network *config.Network, pt *config.ParaTime, out *accountShowOutput) { + if out.Name != "" { + fmt.Printf("Name: %s\n", out.Name) + } + if out.EthereumAddress != nil { + fmt.Printf("Ethereum address: %s\n", out.EthereumAddress) + } + fmt.Printf("Native address: %s\n", out.NativeAddress) + fmt.Println() + fmt.Printf("=== CONSENSUS LAYER (%s) ===\n", out.NetworkName) + fmt.Printf(" Nonce: %d\n", out.Nonce) + fmt.Println() + + prettyPrintAccountBalanceAndDelegationsFrom( + network, + out.NativeAddress, + *out.GeneralAccount, + out.OutgoingDelegations, + out.OutgoingDebondingDelegations, + " ", + os.Stdout, + ) + + if len(out.GeneralAccount.Allowances) > 0 { + fmt.Println(" Allowances for this Account:") + prettyPrintAllowances( + network, + out.NativeAddress, + out.GeneralAccount.Allowances, + " ", + os.Stdout, + ) + fmt.Println() + } + + if len(out.IncomingDelegations) > 0 { + fmt.Println(" Active Delegations to this Account:") + prettyPrintDelegationsTo( + network, + out.NativeAddress, + out.EscrowAccount.Active, + out.IncomingDelegations, + " ", + os.Stdout, + ) + fmt.Println() + } + + if len(out.IncomingDebondingDelegations) > 0 { + fmt.Println(" Debonding Delegations to this Account:") + prettyPrintDelegationsTo( + network, + out.NativeAddress, + out.EscrowAccount.Debonding, + out.IncomingDebondingDelegations, + " ", + os.Stdout, + ) + fmt.Println() + } + + if ea := out.EscrowAccount; ea != nil { + if len(ea.CommissionSchedule.Rates) > 0 || len(ea.CommissionSchedule.Bounds) > 0 { + fmt.Println(" Commission Schedule:") + ea.CommissionSchedule.PrettyPrint(ctx, " ", os.Stdout) + fmt.Println() + } + if len(ea.StakeAccumulator.Claims) > 0 { + fmt.Println(" Stake Accumulator:") + ea.StakeAccumulator.PrettyPrint(ctx, " ", os.Stdout) + fmt.Println() + } + } + + if out.ParaTimeNonce > 0 || len(out.ParaTimeBalances) > 0 || len(out.ParaTimeDelegations) > 0 || len(out.ParaTimeUndelegations) > 0 { + fmt.Printf("=== %s PARATIME ===\n", out.ParaTimeName) + fmt.Printf(" Nonce: %d\n", out.ParaTimeNonce) + fmt.Println() + + if balances := out.ParaTimeBalances; balances != nil { + fmt.Printf(" Balances for all denominations:\n") + for denom, balance := range balances { + fmtAmnt := helpers.FormatParaTimeDenomination(pt, types.NewBaseUnits(balance, denom)) + amnt, symbol, _ := strings.Cut(fmtAmnt, " ") + + fmt.Printf(" - Amount: %s\n", amnt) + fmt.Printf(" Symbol: %s\n", symbol) + } + + fmt.Println() + + if showDelegations { + prettyPrintParaTimeDelegations( + ctx, + c, + out.Height, + network, + out.NativeAddress, + out.ParaTimeDelegations, + out.ParaTimeUndelegations, + " ", + os.Stdout, + ) + } + } + } +} + func init() { f := flag.NewFlagSet("", flag.ContinueOnError) f.BoolVar(&showDelegations, "show-delegations", false, "show incoming and outgoing delegations") common.AddSelectorFlags(Cmd) Cmd.Flags().AddFlagSet(common.HeightFlag) + Cmd.Flags().AddFlagSet(common.FormatFlag) Cmd.Flags().AddFlagSet(f) } From 6a6f8a5b5c98b33d4097240f949cb6c6275d70c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matev=C5=BE=20Jekovec?= Date: Tue, 20 Jan 2026 16:08:07 +0100 Subject: [PATCH 2/2] cmd/rofl/machine/show: Add JSON output --- cmd/rofl/build/artifacts.go | 13 +++++++++---- cmd/rofl/machine/show.go | 16 +++++++++++++++- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/cmd/rofl/build/artifacts.go b/cmd/rofl/build/artifacts.go index a937afe2..b50e89bf 100644 --- a/cmd/rofl/build/artifacts.go +++ b/cmd/rofl/build/artifacts.go @@ -22,6 +22,7 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/crypto/hash" "github.com/oasisprotocol/cli/build/env" + "github.com/oasisprotocol/cli/cmd/common" ) const artifactCacheDir = "build_cache" @@ -29,8 +30,10 @@ const artifactCacheDir = "build_cache" // maybeDownloadArtifact downloads the given artifact and optionally verifies its integrity against // the hash provided in the URI fragment. func maybeDownloadArtifact(kind, uri string) string { - fmt.Printf("Downloading %s artifact...\n", kind) - fmt.Printf(" URI: %s\n", uri) + if common.OutputFormat() != common.FormatJSON { + fmt.Printf("Downloading %s artifact...\n", kind) + fmt.Printf(" URI: %s\n", uri) + } url, err := url.Parse(uri) if err != nil { @@ -49,7 +52,7 @@ func maybeDownloadArtifact(kind, uri string) string { if url.Fragment != "" { knownHash = url.Fragment } - if knownHash != "" { + if knownHash != "" && common.OutputFormat() != common.FormatJSON { fmt.Printf(" Hash: %s\n", knownHash) } @@ -77,7 +80,9 @@ func maybeDownloadArtifact(kind, uri string) string { } f.Close() - fmt.Printf(" (using cached artifact)\n") + if common.OutputFormat() != common.FormatJSON { + fmt.Printf(" (using cached artifact)\n") + } case errors.Is(err, os.ErrNotExist): // Does not exist in cache, download. f, err = os.Create(cacheFn) diff --git a/cmd/rofl/machine/show.go b/cmd/rofl/machine/show.go index 635fdb94..f66c3a01 100644 --- a/cmd/rofl/machine/show.go +++ b/cmd/rofl/machine/show.go @@ -3,6 +3,7 @@ package machine import ( "context" "encoding/binary" + "encoding/json" "fmt" "net" "strings" @@ -63,7 +64,12 @@ var showCmd = &cobra.Command{ if err != nil { // The "instance not found" error originates from Rust code, so we can't compare it nicely here. if strings.Contains(err.Error(), "instance not found") { - cobra.CheckErr("Machine instance not found.\nTip: This often happens when instances expire. Run `oasis rofl deploy --replace-machine` to rent a new one.") + switch common.OutputFormat() { + case common.FormatJSON: + fmt.Printf("{ \"error\": \"%s\" }\n", err) + case common.FormatText: + cobra.CheckErr("Machine instance not found.\nTip: This often happens when instances expire. Run `oasis rofl deploy --replace-machine` to rent a new one.") + } } cobra.CheckErr(err) } @@ -90,6 +96,13 @@ var showCmd = &cobra.Command{ paidUntil := time.Unix(int64(insDsc.PaidUntil), 0) expired := !time.Now().Before(paidUntil) + if common.OutputFormat() == common.FormatJSON { + data, err := json.MarshalIndent(insDsc, "", " ") + cobra.CheckErr(err) + fmt.Printf("%s\n", data) + return + } + fmt.Printf("Name: %s\n", machineName) fmt.Printf("Provider: %s\n", insDsc.Provider) fmt.Printf("ID: %s\n", insDsc.ID) @@ -236,5 +249,6 @@ func showMachinePorts(extraCfg *roflCmdBuild.AppExtraConfig, appID rofl.AppID, i func init() { common.AddSelectorFlags(showCmd) + showCmd.Flags().AddFlagSet(common.FormatFlag) showCmd.Flags().AddFlagSet(roflCommon.DeploymentFlags) }