Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion cmd/account/show/allowances.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (

staking "github.com/oasisprotocol/oasis-core/go/staking/api"

"github.com/oasisprotocol/cli/cmd/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"
Expand Down Expand Up @@ -60,8 +62,12 @@ func prettyPrintAllowanceDescriptions(
// element so we can align all values.
lenLongest := lenLongestString(beneficiaryFieldName, amountFieldName)

// Precompute address formatting context for efficiency (network-aware for paratime names).
addrCtx := common.GenAddressFormatContextForNetwork(network)

for _, desc := range allowDescriptions {
fmt.Fprintf(w, "%s - %-*s %s", prefix, lenLongest, beneficiaryFieldName, desc.beneficiary)
prettyAddr := common.PrettyAddressWith(addrCtx, desc.beneficiary.String())
fmt.Fprintf(w, "%s - %-*s %s", prefix, lenLongest, beneficiaryFieldName, prettyAddr)
if desc.self {
fmt.Fprintf(w, " (self)")
}
Expand Down
12 changes: 11 additions & 1 deletion cmd/account/show/delegations.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ func prettyPrintDelegationDescriptions(

fmt.Fprintf(w, "%sDelegations:\n", prefix)

// Guard against empty slice to prevent panic when accessing delDescriptions[0].
if len(delDescriptions) == 0 {
fmt.Fprintf(w, "%s <none>\n", prefix)
return
}

sort.Sort(byEndTimeAmountAddress(delDescriptions))

// Get the length of name of the longest field to display for each
Expand All @@ -104,8 +110,12 @@ func prettyPrintDelegationDescriptions(
lenLongest = lenLongestString(addressFieldName, amountFieldName, endTimeFieldName)
}

// Precompute address formatting context for efficiency (network-aware for paratime names).
addrCtx := common.GenAddressFormatContextForNetwork(network)

for _, desc := range delDescriptions {
fmt.Fprintf(w, "%s - %-*s %s", prefix, lenLongest, addressFieldName, desc.address)
prettyAddr := common.PrettyAddressWith(addrCtx, desc.address.String())
fmt.Fprintf(w, "%s - %-*s %s", prefix, lenLongest, addressFieldName, prettyAddr)
if desc.self {
fmt.Fprintf(w, " (self)")
}
Expand Down
32 changes: 31 additions & 1 deletion cmd/account/show/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"golang.org/x/term"

consensus "github.com/oasisprotocol/oasis-core/go/consensus/api"
roothash "github.com/oasisprotocol/oasis-core/go/roothash/api"
Expand Down Expand Up @@ -38,13 +39,19 @@ var (
// Determine which address to show. If an explicit argument was given, use that
// otherwise use the default account.
var targetAddress string
var walletNameForEth string
switch {
case len(args) >= 1:
// Explicit argument given.
targetAddress = args[0]
if _, ok := cfg.Wallet.All[targetAddress]; ok {
// Wallet account selected by name.
walletNameForEth = targetAddress
}
case npa.Account != nil:
// Default account is selected.
targetAddress = npa.Account.Address
walletNameForEth = npa.AccountName
default:
// No address given and no wallet configured.
cobra.CheckErr("no address given and no wallet configured")
Expand All @@ -58,10 +65,33 @@ var (
nativeAddr, ethAddr, err := common.ResolveLocalAccountOrAddress(npa.Network, targetAddress)
cobra.CheckErr(err)

if name := common.FindAccountName(nativeAddr.String()); name != "" {
name := common.FindAccountNameForNetwork(npa.Network, nativeAddr.String())
if name != "" {
fmt.Printf("Name: %s\n", name)
}

// If eth address is not available, try to get it from wallet config (no unlock required).
if ethAddr == nil {
for _, walletCfg := range cfg.Wallet.All {
if walletCfg.Address == nativeAddr.String() {
ethAddr = walletCfg.GetEthAddress()
break
}
}
}

// If eth address is still not available and the user selected a wallet account,
// load it (may require passphrase) to derive and persist eth_address metadata.
if ethAddr == nil && walletNameForEth != "" {
if walletCfg, ok := cfg.Wallet.All[walletNameForEth]; ok && walletCfg.SupportsEthAddress() {
// Avoid prompting in non-interactive contexts (e.g. piping output).
if term.IsTerminal(int(os.Stdin.Fd())) && term.IsTerminal(int(os.Stdout.Fd())) {
acc := common.LoadAccount(cfg, walletNameForEth)
ethAddr = acc.EthAddress()
}
}
}

height, err := common.GetActualHeight(
ctx,
c.Consensus().Core(),
Expand Down
184 changes: 180 additions & 4 deletions cmd/common/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@ import (
"fmt"
"os"

ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/spf13/cobra"

staking "github.com/oasisprotocol/oasis-core/go/staking/api"
configSdk "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/testing"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/types"

buildRoflProvider "github.com/oasisprotocol/cli/build/rofl/provider"
"github.com/oasisprotocol/cli/config"
)

Expand Down Expand Up @@ -42,18 +47,51 @@ func CheckForceErr(err interface{}) {
}

// GenAccountNames generates a map of all addresses -> account name for pretty printing.
// Priority order (later entries overwrite earlier): test accounts < addressbook < wallet.
// This ensures wallet identity takes precedence over addressbook labels for the same address.
func GenAccountNames() types.AccountNames {
return GenAccountNamesForNetwork(nil)
}

// GenAccountNamesForNetwork generates a map of all addresses -> account name for pretty printing,
// including network-specific entries (paratimes, ROFL providers) when a network is provided.
// Priority order (later entries overwrite earlier): test accounts < network entries < addressbook < wallet.
func GenAccountNamesForNetwork(net *configSdk.Network) types.AccountNames {
an := types.AccountNames{}
for name, acc := range config.Global().Wallet.All {
an[acc.GetAddress().String()] = name

// Test accounts have lowest priority.
for name, acc := range testing.TestAccounts {
an[acc.Address.String()] = fmt.Sprintf("test:%s", name)
}

// Network-derived entries (paratimes, ROFL providers) have second-lowest priority.
if net != nil {
// Include ParaTime runtime addresses as paratime:<name>.
for ptName, pt := range net.ParaTimes.All {
rtAddr := types.NewAddressFromConsensus(staking.NewRuntimeAddress(pt.Namespace()))
an[rtAddr.String()] = fmt.Sprintf("paratime:%s", ptName)
}

// Include ROFL default provider addresses as rofl:provider:<paratime>.
for ptName, pt := range net.ParaTimes.All {
if svc, ok := buildRoflProvider.DefaultRoflServices[pt.ID]; ok {
if svc.Provider != "" {
if a, _, err := helpers.ResolveEthOrOasisAddress(svc.Provider); err == nil && a != nil {
an[a.String()] = fmt.Sprintf("rofl:provider:%s", ptName)
}
}
}
}
}

// Addressbook entries have medium priority.
for name, acc := range config.Global().AddressBook.All {
an[acc.GetAddress().String()] = name
}

for name, acc := range testing.TestAccounts {
an[acc.Address.String()] = fmt.Sprintf("test:%s", name)
// Wallet entries have highest priority.
for name, acc := range config.Global().Wallet.All {
an[acc.GetAddress().String()] = name
}

return an
Expand All @@ -64,3 +102,141 @@ func FindAccountName(address string) string {
an := GenAccountNames()
return an[address]
}

// FindAccountNameForNetwork finds account's name in the context of a specific network.
func FindAccountNameForNetwork(net *configSdk.Network, address string) string {
an := GenAccountNamesForNetwork(net)
return an[address]
}

// AddressFormatContext contains precomputed maps for address formatting.
type AddressFormatContext struct {
// Names maps native address string to account name.
Names types.AccountNames
// Eth maps native address string to Ethereum hex address string (if known).
Eth map[string]string
}

// GenAccountEthMap generates a map of native address string -> eth hex address (if known).
func GenAccountEthMap() map[string]string {
return GenAccountEthMapForNetwork(nil)
}

// GenAccountEthMapForNetwork generates a map of native address string -> eth hex address (if known).
func GenAccountEthMapForNetwork(_ *configSdk.Network) map[string]string {
eth := make(map[string]string)

// From test accounts.
for _, acc := range testing.TestAccounts {
if acc.EthAddress != nil {
eth[acc.Address.String()] = acc.EthAddress.Hex()
}
}

// From address book entries (higher priority than test accounts).
for _, acc := range config.Global().AddressBook.All {
if ethAddr := acc.GetEthAddress(); ethAddr != nil {
eth[acc.GetAddress().String()] = ethAddr.Hex()
}
}

// From wallet entries (highest priority), when an Ethereum address is available in config.
for _, acc := range config.Global().Wallet.All {
if ethAddr := acc.GetEthAddress(); ethAddr != nil {
eth[acc.GetAddress().String()] = ethAddr.Hex()
}
}

return eth
}

// GenAddressFormatContext builds both name and eth address maps for formatting.
func GenAddressFormatContext() AddressFormatContext {
return GenAddressFormatContextForNetwork(nil)
}

// GenAddressFormatContextForNetwork builds both name and eth address maps for formatting,
// including network-specific entries when a network is provided.
func GenAddressFormatContextForNetwork(net *configSdk.Network) AddressFormatContext {
return AddressFormatContext{
Names: GenAccountNamesForNetwork(net),
Eth: GenAccountEthMapForNetwork(net),
}
}

// PrettyAddressWith formats an address using a precomputed context.
// If the address is known (in wallet, addressbook, or test accounts), it returns "name (address)".
// For secp256k1 accounts with a known Ethereum address, the Ethereum hex format is preferred in parentheses.
// If the address is unknown, it returns the original address string unchanged.
func PrettyAddressWith(ctx AddressFormatContext, addr string) string {
// Try to parse the address to get canonical native form.
nativeAddr, ethFromInput, err := helpers.ResolveEthOrOasisAddress(addr)
if err != nil || nativeAddr == nil {
// Cannot parse, return unchanged.
return addr
}

nativeStr := nativeAddr.String()

// Look up the name.
name := ctx.Names[nativeStr]
if name == "" {
// Unknown address, return the original input.
return addr
}

// Determine which address to show in parentheses.
// Prefer Ethereum address if available (from input or from known eth addresses).
var parenAddr string
if ethFromInput != nil {
parenAddr = ethFromInput.Hex()
} else if ethHex := ctx.Eth[nativeStr]; ethHex != "" {
parenAddr = ethHex
} else {
parenAddr = nativeStr
}

// Guard against redundant "name (name)" output.
if name == parenAddr {
return parenAddr
}

return fmt.Sprintf("%s (%s)", name, parenAddr)
}

func preferredAddressString(nativeAddr *types.Address, ethAddr *ethCommon.Address) string {
if ethAddr != nil {
return ethAddr.Hex()
}
if nativeAddr == nil {
return ""
}
return nativeAddr.String()
}

// PrettyResolvedAddressWith formats a resolved address tuple (native, eth) using a precomputed context.
//
// If ethAddr is non-nil, it is preferred to preserve the "user provided eth" behavior even when
// the native-to-eth mapping is not available in the context.
func PrettyResolvedAddressWith(ctx AddressFormatContext, nativeAddr *types.Address, ethAddr *ethCommon.Address) string {
addrStr := preferredAddressString(nativeAddr, ethAddr)
if addrStr == "" {
return ""
}
return PrettyAddressWith(ctx, addrStr)
}

// PrettyAddress formats an address for display without network context.
// If the address is known (in wallet, addressbook, or test accounts), it returns "name (address)".
// For secp256k1 accounts with a known Ethereum address, the Ethereum hex format is preferred in parentheses.
// If the address is unknown, it returns the original address string unchanged.
func PrettyAddress(addr string) string {
return PrettyAddressWith(GenAddressFormatContext(), addr)
}

// PrettyAddressForNetwork formats an address for display with network context.
// This includes network-derived names like paratime addresses and ROFL providers.
func PrettyAddressForNetwork(net *configSdk.Network, addr types.Address) string {
ctx := GenAddressFormatContextForNetwork(net)
return PrettyAddressWith(ctx, addr.String())
}
Loading
Loading