Skip to content

Conversation

@sirily11
Copy link
Contributor

@sirily11 sirily11 commented Nov 3, 2025

No description provided.

Copilot AI review requested due to automatic review settings November 3, 2025 06:18
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements wallet management functionality and refactors configuration handling for an EVM smart contract CLI application. The key change is transitioning from shared memory to database-backed configuration storage.

Key changes:

  • Refactored config API from multi-config CRUD to singleton pattern with GetCurrentConfig(), CreateConfig(), UpdateConfig(), and DeleteConfig()
  • Added complete wallet management pages including add, update, delete, select, details, and actions
  • Centralized storage access through utility functions (GetStorageClientFromSharedMemory())
  • Updated EVMConfig model to include SelectedWalletID field for tracking active wallet

Reviewed Changes

Copilot reviewed 20 out of 22 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
internal/contract/evm/storage/sql/queries/config_queries.go Refactored from multi-config CRUD to singleton config pattern
internal/contract/evm/storage/sql/storage.go Updated Storage interface to match singleton config pattern
internal/contract/evm/storage/sql/sqlite.go Implemented singleton config methods
internal/contract/evm/storage/models/evm/evm_config.go Added SelectedWalletID and SelectedWallet fields
internal/utils/storage.go Added utility function for storage client access
internal/view/router.go Enhanced router to handle pending commands after navigation
app/evm/wallet/*.go Implemented wallet management pages (add, update, delete, select, details, actions)
CLAUDE.md Added EVM wallet page pattern documentation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +83 to +90
// Update current config with provided values
updates := map[string]any{
"endpoint_id": config.EndpointId,
"selected_evm_contract_id": config.SelectedEVMContractId,
"selected_evm_abi_id": config.SelectedEVMAbiId,
"selected_wallet_id": config.SelectedWalletID,
}

Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Update method allows setting pointer fields to nil without validation. If all fields in the provided config are nil pointers, this will clear all configuration values. Consider validating that at least one field is being updated or explicitly handle nil pointer values to preserve existing config values.

Suggested change
// Update current config with provided values
updates := map[string]any{
"endpoint_id": config.EndpointId,
"selected_evm_contract_id": config.SelectedEVMContractId,
"selected_evm_abi_id": config.SelectedEVMAbiId,
"selected_wallet_id": config.SelectedWalletID,
}
// Build updates map only with non-nil fields
updates := make(map[string]any)
if config.EndpointId != nil {
updates["endpoint_id"] = config.EndpointId
}
if config.SelectedEVMContractId != nil {
updates["selected_evm_contract_id"] = config.SelectedEVMContractId
}
if config.SelectedEVMAbiId != nil {
updates["selected_evm_abi_id"] = config.SelectedEVMAbiId
}
if config.SelectedWalletID != nil {
updates["selected_wallet_id"] = config.SelectedWalletID
}
if len(updates) == 0 {
return customerrors.NewDatabaseError(customerrors.ErrCodeInvalidInput, "no fields to update: all provided fields are nil")
}

Copilot uses AI. Check for mistakes.

func (m Model) selectWallet() tea.Msg {
// Update selected wallet in shared memory
err := m.sharedMemory.Set(config.SelectedWalletIDKey, m.newWalletID)
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wallet selection updates shared memory but doesn't persist to database. According to the PR's pattern (documented in CLAUDE.md lines 1467-1475), selected wallet ID should be stored in EVMConfig via sqlStorage.UpdateConfig(), not in shared memory. This creates inconsistency between the documented pattern and implementation.

Copilot uses AI. Check for mistakes.
Comment on lines +122 to +128
selectedWalletIDVal, _ := m.sharedMemory.Get(config.SelectedWalletIDKey)
var selectedWalletID uint
if selectedWalletIDVal != nil {
if id, ok := selectedWalletIDVal.(uint); ok {
selectedWalletID = id
}
}
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code retrieves selected wallet ID from shared memory, which violates the pattern documented in CLAUDE.md (lines 1467-1475). Should use sqlStorage.GetCurrentConfig() to retrieve SelectedWalletID from the database instead.

Suggested change
selectedWalletIDVal, _ := m.sharedMemory.Get(config.SelectedWalletIDKey)
var selectedWalletID uint
if selectedWalletIDVal != nil {
if id, ok := selectedWalletIDVal.(uint); ok {
selectedWalletID = id
}
}
var selectedWalletID uint
configObj, err := sql.GetCurrentConfig()
if err != nil {
return walletLoadedMsg{err: fmt.Errorf("failed to retrieve current config: %w", err)}
}
selectedWalletID = configObj.SelectedWalletID

Copilot uses AI. Check for mistakes.
Comment on lines +125 to +132
// Get selected wallet ID from shared memory
selectedWalletIDVal, _ := m.sharedMemory.Get(config.SelectedWalletIDKey)
var selectedWalletID uint
if selectedWalletIDVal != nil {
if id, ok := selectedWalletIDVal.(uint); ok {
selectedWalletID = id
}
}
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading selected wallet ID from shared memory instead of database config. This is inconsistent with the documented best practice in CLAUDE.md (lines 1467-1475) which specifies retrieving this from sqlStorage.GetCurrentConfig().

Suggested change
// Get selected wallet ID from shared memory
selectedWalletIDVal, _ := m.sharedMemory.Get(config.SelectedWalletIDKey)
var selectedWalletID uint
if selectedWalletIDVal != nil {
if id, ok := selectedWalletIDVal.(uint); ok {
selectedWalletID = id
}
}
// Get selected wallet ID from database config (best practice)
var selectedWalletID uint
currentConfig, err := sql.GetCurrentConfig()
if err != nil {
return walletLoadedMsg{err: fmt.Errorf("failed to get current config: %w", err)}
}
selectedWalletID = currentConfig.SelectedWalletID

Copilot uses AI. Check for mistakes.
walletService = svc
}

rpcEndpoint := "http://localhost:8545"
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded RPC endpoint violates the documented pattern in CLAUDE.md (lines 1448-1463). Should retrieve the endpoint from sqlStorage.GetCurrentConfig().Endpoint.Url instead of using a hardcoded value.

Suggested change
rpcEndpoint := "http://localhost:8545"
rpcEndpoint := m.sqlStorage.GetCurrentConfig().Endpoint.Url

Copilot uses AI. Check for mistakes.
walletService = svc
}

rpcEndpoint := "http://localhost:8545"
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded RPC endpoint violates the documented pattern in CLAUDE.md (lines 1448-1463). Should retrieve the endpoint from sqlStorage.GetCurrentConfig().Endpoint.Url instead of using a hardcoded value.

Suggested change
rpcEndpoint := "http://localhost:8545"
rpcEndpoint := m.sqlStorage.GetCurrentConfig().Endpoint.Url

Copilot uses AI. Check for mistakes.
walletService = svc
}

rpcEndpoint := "http://localhost:8545"
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded RPC endpoint violates the documented pattern in CLAUDE.md (lines 1448-1463). Should retrieve the endpoint from sqlStorage.GetCurrentConfig().Endpoint.Url instead of using a hardcoded value.

Suggested change
rpcEndpoint := "http://localhost:8545"
rpcEndpoint := sqlStorage.GetCurrentConfig().Endpoint.Url

Copilot uses AI. Check for mistakes.
}

// Load with balance
rpcEndpoint := "http://localhost:8545"
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiple hardcoded RPC endpoints violate the documented pattern in CLAUDE.md (lines 1448-1463). All instances should retrieve the endpoint from sqlStorage.GetCurrentConfig().Endpoint.Url instead.

Suggested change
rpcEndpoint := "http://localhost:8545"
rpcEndpoint := storage.GetCurrentConfig().Endpoint.Url

Copilot uses AI. Check for mistakes.
}

// Load with balance
rpcEndpoint := "http://localhost:8545"
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiple hardcoded RPC endpoints violate the documented pattern in CLAUDE.md (lines 1448-1463). All instances should retrieve the endpoint from sqlStorage.GetCurrentConfig().Endpoint.Url instead.

Suggested change
rpcEndpoint := "http://localhost:8545"
rpcEndpoint := storage.GetCurrentConfig().Endpoint.Url

Copilot uses AI. Check for mistakes.
}

// Load with balance
rpcEndpoint := "http://localhost:8545"
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiple hardcoded RPC endpoints violate the documented pattern in CLAUDE.md (lines 1448-1463). All instances should retrieve the endpoint from sqlStorage.GetCurrentConfig().Endpoint.Url instead.

Suggested change
rpcEndpoint := "http://localhost:8545"
rpcEndpoint := storage.GetCurrentConfig().Endpoint.Url

Copilot uses AI. Check for mistakes.
@sirily11 sirily11 force-pushed the wallet-ui branch 2 times, most recently from 8497907 to fb31d33 Compare November 3, 2025 06:34
Copilot AI review requested due to automatic review settings November 3, 2025 06:34
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 20 out of 24 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +173 to +180
logger.Info("Enter pressed, navigating to wallet actions for wallet ID: %d", walletID)
err := m.router.NavigateTo("/evm/wallet/actions", map[string]string{
"id": strconv.FormatUint(uint64(walletID), 10),
})
if err != nil {
logger.Error("Navigation error: %v", err)
}
return m, nil
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Navigation errors are logged but not handled. If navigation fails, the user will remain on the current page with no feedback. Consider setting an error message on the model or returning a command to display the error to the user.

Copilot uses AI. Check for mistakes.
Comment on lines +136 to +137
selectedWalletID := config.SelectedWalletID
if selectedWalletID == nil {
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This validation occurs after successfully loading the wallet but before returning the result. However, the check is performed on selectedWalletID (from line 136) which shadows the variable and is always non-nil since it's assigned from a dereferenced pointer. The actual check should be on config.SelectedWalletID before dereferencing, or this validation should be removed since line 145 already dereferences config.SelectedWalletID safely.

Suggested change
selectedWalletID := config.SelectedWalletID
if selectedWalletID == nil {
if config.SelectedWalletID == nil {

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings November 3, 2025 07:26
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 22 out of 26 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +61 to +66
// After going back, check if there's a pending command from the new page
if r.pendingCmd != nil {
pendingCmd := r.pendingCmd
r.pendingCmd = nil
return r, pendingCmd
}
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment on line 61 should clarify that this handles pending commands specifically after a Back() navigation. Consider: // After Back() navigation, execute any pending command from the newly active page

Copilot uses AI. Check for mistakes.
navigationOccurred := len(r.navigationStack) != oldStackSize || r.pendingCmd != nil

// If pending command exists, return it immediately
// If pending command exists (from navigation), return it immediately
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment should be more specific about when pendingCmd is set. Consider: // If pending command exists (set during NavigateTo/ReplaceRoute), return it immediately to execute the new view's Init()

Suggested change
// If pending command exists (from navigation), return it immediately
// If pending command exists (set during NavigateTo/ReplaceRoute), return it immediately to execute the new view's Init()

Copilot uses AI. Check for mistakes.
Comment on lines +52 to 69
func (q *ConfigQueries) Create() error {
// Check if config already exists
var count int64
if err := q.db.Model(&models.EVMConfig{}).Count(&count).Error; err != nil {
return customerrors.WrapDatabaseError(err, customerrors.ErrCodeDatabaseOperationFailed, "failed to check config existence")
}

// If config exists, skip creation
if count > 0 {
return nil
}

// Create empty config
config := &models.EVMConfig{}
if err := q.db.Create(config).Error; err != nil {
return customerrors.WrapDatabaseError(err, customerrors.ErrCodeDatabaseOperationFailed, "failed to create config")
}
return nil
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Create method silently skips creation if a config already exists. Consider returning a specific error or a boolean indicating whether creation occurred to make this behavior more explicit for callers.

Suggested change
func (q *ConfigQueries) Create() error {
// Check if config already exists
var count int64
if err := q.db.Model(&models.EVMConfig{}).Count(&count).Error; err != nil {
return customerrors.WrapDatabaseError(err, customerrors.ErrCodeDatabaseOperationFailed, "failed to check config existence")
}
// If config exists, skip creation
if count > 0 {
return nil
}
// Create empty config
config := &models.EVMConfig{}
if err := q.db.Create(config).Error; err != nil {
return customerrors.WrapDatabaseError(err, customerrors.ErrCodeDatabaseOperationFailed, "failed to create config")
}
return nil
func (q *ConfigQueries) Create() (bool, error) {
// Check if config already exists
var count int64
if err := q.db.Model(&models.EVMConfig{}).Count(&count).Error; err != nil {
return false, customerrors.WrapDatabaseError(err, customerrors.ErrCodeDatabaseOperationFailed, "failed to check config existence")
}
// If config exists, skip creation
if count > 0 {
return false, nil
}
// Create empty config
config := &models.EVMConfig{}
if err := q.db.Create(config).Error; err != nil {
return false, customerrors.WrapDatabaseError(err, customerrors.ErrCodeDatabaseOperationFailed, "failed to create config")
}
return true, nil

Copilot uses AI. Check for mistakes.
Comment on lines +281 to +295
// Check if storage client exists in shared memory
storageClient, err := m.sharedMemory.Get(config.StorageClientKey)
if err != nil {
return fmt.Errorf("failed to get storage client from shared memory: %w", err)
}

// If storage client is nil, it means it hasn't been configured yet
// This is valid for first-time users, so we don't treat it as an error
if storageClient == nil {
logger.Info("Storage client not configured yet, skipping config creation")
return nil
}

sqlStorage, err := utils.GetStorageClientFromSharedMemory(m.sharedMemory)
if err != nil {
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines 282-292 manually retrieve and check the storage client. This duplicates logic that exists in utils.GetStorageClientFromSharedMemory (line 294). Consider calling the utility function first and handling ErrNotFound specifically if nil storage is a valid state.

Suggested change
// Check if storage client exists in shared memory
storageClient, err := m.sharedMemory.Get(config.StorageClientKey)
if err != nil {
return fmt.Errorf("failed to get storage client from shared memory: %w", err)
}
// If storage client is nil, it means it hasn't been configured yet
// This is valid for first-time users, so we don't treat it as an error
if storageClient == nil {
logger.Info("Storage client not configured yet, skipping config creation")
return nil
}
sqlStorage, err := utils.GetStorageClientFromSharedMemory(m.sharedMemory)
if err != nil {
sqlStorage, err := utils.GetStorageClientFromSharedMemory(m.sharedMemory)
if err != nil {
if errors.Is(err, errors.ErrNotFound) {
logger.Info("Storage client not configured yet, skipping config creation")
return nil
}

Copilot uses AI. Check for mistakes.
Comment on lines +241 to +246
// Get RPC endpoint from database
sqlStorage, err := utils.GetStorageClientFromSharedMemory(m.sharedMemory)
if err != nil {
logger.Error("Failed to get storage client from shared memory: %v", err)
return walletImportedMsg{err: fmt.Errorf("failed to get storage client from shared memory: %w", err)}
}
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pattern of retrieving storage client and config is duplicated across importPrivateKey (lines 242-258), importMnemonic (lines 328-344), and generateWallet (lines 381-397). Consider extracting this into a helper method to reduce duplication.

Copilot uses AI. Check for mistakes.
feat: implement wallet management page

feat: implement logging infrastructure with file rotation and structured logging

- Introduced a new logging system in `internal/log/` using `zerolog` and `lumberjack` for structured logging and file rotation.
- Added comprehensive usage documentation in `internal/log/USAGE.md`.
- Implemented logging in various application components, including error handling and storage management.
- Created tests for the logging functionality to ensure reliability and correctness.

fix: issues

# Conflicts:
#	.gitignore
#	app/evm/wallet/page.go
#	internal/contract/evm/storage/sql/sqlite.go
#	internal/utils/storage.go

feat: implement EVM wallet page pattern and best practices

- Added comprehensive guidelines for implementing wallet-related pages in the `app/evm/wallet/` directory.
- Emphasized the use of utility functions for accessing storage and secure storage.
- Established best practices for RPC endpoint configuration and selected wallet management.
- Updated the `EVMConfig` model structure to include necessary fields for wallet and endpoint management.
- Included complete examples for wallet list and actions pages to demonstrate the new patterns.
- Enhanced test coverage for wallet management functionalities, ensuring robust error handling and user feedback.
@sirily11 sirily11 merged commit 1d516b1 into main Nov 3, 2025
1 check passed
@sirily11 sirily11 deleted the wallet-ui branch November 3, 2025 07:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants