diff --git a/harmony/harmonydb/sql/20241105-walletnames.sql b/harmony/harmonydb/sql/20241105-walletnames.sql new file mode 100644 index 000000000..1bef61aa1 --- /dev/null +++ b/harmony/harmonydb/sql/20241105-walletnames.sql @@ -0,0 +1,4 @@ +CREATE TABLE wallet_names ( + wallet VARCHAR PRIMARY KEY, + name VARCHAR(60) NOT NULL UNIQUE +); diff --git a/web/api/webrpc/market_filters.go b/web/api/webrpc/market_filters.go index e533f2dbb..164d7ab4e 100644 --- a/web/api/webrpc/market_filters.go +++ b/web/api/webrpc/market_filters.go @@ -13,7 +13,6 @@ import ( "github.com/filecoin-project/go-state-types/builtin" "github.com/filecoin-project/go-state-types/builtin/v15/market" - "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/node/modules/dtypes" ) @@ -112,11 +111,7 @@ func (a *WebRPC) SetClientFilters(ctx context.Context, name string, active bool, if err != nil { return xerrors.Errorf("invalid wallet address: %w", err) } - client, err := a.deps.Chain.StateLookupID(ctx, w, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("wallet not found: %w", err) - } - clients = append(clients, client.String()) + clients = append(clients, w.String()) } } @@ -258,11 +253,7 @@ func (a *WebRPC) AddClientFilters(ctx context.Context, name string, active bool, if err != nil { return xerrors.Errorf("invalid wallet address: %w", err) } - client, err := a.deps.Chain.StateLookupID(ctx, w, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("wallet not found: %w", err) - } - clients = append(clients, client.String()) + clients = append(clients, w.String()) } } @@ -382,12 +373,7 @@ func (a *WebRPC) AddAllowDenyList(ctx context.Context, wallet string, status boo return xerrors.Errorf("invalid wallet address: %w", err) } - client, err := a.deps.Chain.StateLookupID(ctx, w, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("wallet not found: %w", err) - } - - n, err := a.deps.DB.Exec(ctx, "INSERT INTO market_allow_list (wallet, status) VALUES ($1, $2)", client.String(), status) + n, err := a.deps.DB.Exec(ctx, "INSERT INTO market_allow_list (wallet, status) VALUES ($1, $2)", w.String(), status) if err != nil { return err } diff --git a/web/api/webrpc/wallet.go b/web/api/webrpc/wallet.go new file mode 100644 index 000000000..a2d20724a --- /dev/null +++ b/web/api/webrpc/wallet.go @@ -0,0 +1,101 @@ +package webrpc + +import ( + "context" + "errors" + "sync" + + "github.com/filecoin-project/curio/harmony/harmonydb" +) + +var walletOnce sync.Once +var walletFriendlyNames = map[string]string{} +var walletFriendlyNamesLock sync.Mutex + +func (a *WebRPC) WalletName(ctx context.Context, id string) (string, error) { + walletOnce.Do(func() { + populateWalletFriendlyNames(a.deps.DB) + }) + walletFriendlyNamesLock.Lock() + defer walletFriendlyNamesLock.Unlock() + name, ok := walletFriendlyNames[id] + if ok { + return name, nil + } + return id, nil +} + +func (a *WebRPC) WalletNameChange(ctx context.Context, wallet, newName string) error { + if len(newName) == 0 { + return errors.New("name cannot be empty") + } + _, err := a.deps.DB.Exec(ctx, `UPDATE wallet_names SET name = $1 WHERE wallet = $2`, newName, wallet) + if err != nil { + log.Errorf("failed to set wallet name for %s: %s", wallet, err) + return err + } + walletFriendlyNamesLock.Lock() + defer walletFriendlyNamesLock.Unlock() + walletFriendlyNames[wallet] = newName + return nil +} + +func populateWalletFriendlyNames(db *harmonydb.DB) { + // Get all wallet from DB + var idNames []struct { + Wallet string `db:"wallet"` + Name string `db:"name"` + } + + err := db.Select(context.Background(), &idNames, `SELECT wallet, name FROM wallet_names`) + if err != nil { + log.Errorf("failed to get wallet names: %s", err) + return + } + + walletFriendlyNamesLock.Lock() + defer walletFriendlyNamesLock.Unlock() + for _, idName := range idNames { + walletFriendlyNames[idName.Wallet] = idName.Name + } +} + +func (a *WebRPC) WalletNames(ctx context.Context) (map[string]string, error) { + walletOnce.Do(func() { + populateWalletFriendlyNames(a.deps.DB) + }) + walletFriendlyNamesLock.Lock() + defer walletFriendlyNamesLock.Unlock() + return walletFriendlyNames, nil +} + +func (a *WebRPC) WalletAdd(ctx context.Context, wallet, name string) error { + if len(name) == 0 { + return errors.New("name cannot be empty") + } + if len(wallet) == 0 { + return errors.New("wallet cannot be empty") + } + _, err := a.deps.DB.Exec(ctx, `INSERT INTO wallet_names (wallet, name) VALUES ($1, $2)`, wallet, name) + if err != nil { + log.Errorf("failed to add wallet name for %s: %s", wallet, err) + return err + } + + walletFriendlyNamesLock.Lock() + walletFriendlyNames[wallet] = name + defer walletFriendlyNamesLock.Unlock() + return nil +} + +func (a *WebRPC) WalletRemove(ctx context.Context, wallet string) error { + _, err := a.deps.DB.Exec(ctx, `DELETE FROM wallet_names WHERE wallet = $1`, wallet) + if err != nil { + log.Errorf("failed to remove wallet name for %s: %s", wallet, err) + return err + } + walletFriendlyNamesLock.Lock() + defer walletFriendlyNamesLock.Unlock() + delete(walletFriendlyNames, wallet) + return nil +} diff --git a/web/static/lib/cu-wallet.mjs b/web/static/lib/cu-wallet.mjs new file mode 100644 index 000000000..d562f8dc6 --- /dev/null +++ b/web/static/lib/cu-wallet.mjs @@ -0,0 +1,58 @@ +import { LitElement, html } from 'https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js'; +import RPCCall from '/lib/jsonrpc.mjs'; +import '/lib/clipboard-copy.mjs'; + +class CuWallet extends LitElement { + static properties = { + wallet_id: { type: String }, + name: { state: true }, + havername: { state: true } + }; + + constructor() { + super(); + this.wallet_id = ''; + this.name = ''; + this.havename = false; + } + + connectedCallback() { + super.connectedCallback(); + this.loadWallet(); + } + + async loadWallet() { + if (!this.wallet_id) return; + + try { + const result = await RPCCall('WalletName', [this.wallet_id]); + console.log('WalletName result:', result); + this.name = result || this.wallet_id; + this.havename = (this.name !== this.wallet_id); + } catch (err) { + console.error('Error during WalletName operation:', err); + this.name = this.wallet_id; // fallback + } + } + + createRenderRoot() { + // Render in light DOM so the text can be styled normally and picked up by parent CSS + return this; + } + + render() { + if (!this.havename){ + const shortened = `${this.wallet_id.slice(0, 6)}...${this.wallet_id.slice(-6)}`; + return html` + ${shortened} + + + `; + } + return html` + ${this.name} + `; + } +} + +customElements.define('cu-wallet', CuWallet); diff --git a/web/static/pages/actor/actor-detail.mjs b/web/static/pages/actor/actor-detail.mjs index d497aa3a1..63b57692f 100644 --- a/web/static/pages/actor/actor-detail.mjs +++ b/web/static/pages/actor/actor-detail.mjs @@ -1,6 +1,7 @@ import { LitElement, html, css } from 'https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js'; import '/actor-summary.mjs'; // import RPCCall from '/lib/jsonrpc.mjs'; +import '/lib/cu-wallet.mjs'; customElements.define('actor-detail', class Actor extends LitElement { connectedCallback() { @@ -138,19 +139,19 @@ customElements.define('actor-detail', class Actor extends LitElement { Owner Address: - ${actorInfo.OwnerAddress} + Beneficiary: - ${actorInfo.Beneficiary} + Worker Address: - ${actorInfo.WorkerAddress} + Peer ID: - ${actorInfo.PeerID} + @@ -206,7 +207,7 @@ customElements.define('actor-detail', class Actor extends LitElement { ${actorInfo.PendingOwnerAddress ? html` PendingOwnerAddress: - ${actorInfo.PendingOwnerAddress} + ` : null @@ -265,7 +266,7 @@ customElements.define('actor-detail', class Actor extends LitElement { ${actorInfo.Wallets.map(wallet => html` ${wallet.Type} - ${wallet.Address} + ${wallet.Balance} `) diff --git a/web/static/pages/actor/index.html b/web/static/pages/actor/index.html index d9f6d8325..9e079801d 100644 --- a/web/static/pages/actor/index.html +++ b/web/static/pages/actor/index.html @@ -7,6 +7,9 @@ + @@ -25,10 +28,5 @@

Miner Info

- - - - - \ No newline at end of file diff --git a/web/static/pages/ipni/ipni_status.mjs b/web/static/pages/ipni/ipni_status.mjs index 2b264a814..24d29ee5c 100644 --- a/web/static/pages/ipni/ipni_status.mjs +++ b/web/static/pages/ipni/ipni_status.mjs @@ -107,7 +107,7 @@ class IpniStatus extends LitElement { data-bs-parent="#ipniStatusAccordion" >
-

PeerID: ${provider.peer_id}

+

PeerID:${provider.peer_id}>

Head: ${provider.head}

${provider.sync_status && provider.sync_status.length > 0 ? provider.sync_status.map((status) => html`
diff --git a/web/static/pages/market-settings/allow-list.mjs b/web/static/pages/market-settings/allow-list.mjs index 95919f597..08a1b9c13 100644 --- a/web/static/pages/market-settings/allow-list.mjs +++ b/web/static/pages/market-settings/allow-list.mjs @@ -1,5 +1,6 @@ import { LitElement, html, css } from 'https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js'; import RPCCall from '/lib/jsonrpc.mjs'; +import '/lib/cu-wallet.mjs'; class AllowList extends LitElement { static properties = { @@ -89,8 +90,10 @@ class AllowList extends LitElement { +

Allow/Deny List +

` diff --git a/web/static/pages/market-settings/client-filters.mjs b/web/static/pages/market-settings/client-filters.mjs index afd6316f4..c6c39219b 100644 --- a/web/static/pages/market-settings/client-filters.mjs +++ b/web/static/pages/market-settings/client-filters.mjs @@ -1,5 +1,6 @@ import { LitElement, html, css } from 'https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js'; import RPCCall from '/lib/jsonrpc.mjs'; +import '/lib/cu-wallet.mjs'; class ClientFilters extends LitElement { static properties = { @@ -108,6 +109,7 @@ class ClientFilters extends LitElement { rel="stylesheet" crossorigin="anonymous" /> +

Client Filters +

` diff --git a/web/static/pages/market-settings/defaults.mjs b/web/static/pages/market-settings/defaults.mjs index 449863d21..802b0b510 100644 --- a/web/static/pages/market-settings/defaults.mjs +++ b/web/static/pages/market-settings/defaults.mjs @@ -33,6 +33,7 @@ class DefaultMarketFilters extends LitElement { rel="stylesheet" crossorigin="anonymous" /> +

Filter Settings

diff --git a/web/static/pages/market-settings/index.html b/web/static/pages/market-settings/index.html index 01eeac30f..b267cc7e2 100644 --- a/web/static/pages/market-settings/index.html +++ b/web/static/pages/market-settings/index.html @@ -8,6 +8,9 @@ + diff --git a/web/static/pages/market-settings/pricing-filters.mjs b/web/static/pages/market-settings/pricing-filters.mjs index 6144579cf..dc9f719a3 100644 --- a/web/static/pages/market-settings/pricing-filters.mjs +++ b/web/static/pages/market-settings/pricing-filters.mjs @@ -106,6 +106,7 @@ class PricingFilters extends LitElement { rel="stylesheet" crossorigin="anonymous" /> +

Pricing Filters

` diff --git a/web/static/pages/market/index.html b/web/static/pages/market/index.html index a80a5f2e2..659815ace 100644 --- a/web/static/pages/market/index.html +++ b/web/static/pages/market/index.html @@ -9,6 +9,9 @@ + diff --git a/web/static/pages/market/market-balance.mjs b/web/static/pages/market/market-balance.mjs index 640f2d053..46158c7d8 100644 --- a/web/static/pages/market/market-balance.mjs +++ b/web/static/pages/market/market-balance.mjs @@ -1,5 +1,6 @@ import { html, css, LitElement } from 'https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js'; import RPCCall from '/lib/jsonrpc.mjs'; +import '/lib/cu-wallet.mjs'; class MarketBalance extends LitElement { static properties = { @@ -98,7 +99,7 @@ class MarketBalance extends LitElement { ` : ''} - + ` diff --git a/web/static/pages/mk12-deal/deal.mjs b/web/static/pages/mk12-deal/deal.mjs index 35bee2068..5d8d3fb26 100644 --- a/web/static/pages/mk12-deal/deal.mjs +++ b/web/static/pages/mk12-deal/deal.mjs @@ -2,6 +2,7 @@ import { LitElement, html, css } from 'https://cdn.jsdelivr.net/gh/lit/dist@3/al import RPCCall from '/lib/jsonrpc.mjs'; import { formatDate } from '/lib/dateutil.mjs'; import '/ux/epoch.mjs'; +import '/lib/cu-wallet.mjs'; class DealDetails extends LitElement { constructor() { @@ -36,7 +37,7 @@ class DealDetails extends LitElement { {property: 'Is DDO', value: entry.is_ddo}, {property: 'Start Epoch', value: html``}, {property: 'End Epoch', value: html``}, - {property: 'Client Peer ID', value: entry.client_peer_id}, + {property: 'Client Peer ID', value: html``}, {property: 'Chain Deal ID', value: entry.chain_deal_id}, {property: 'Publish CID', value: entry.publish_cid}, {property: 'Piece CID', value: html`${entry.piece_cid}`}, diff --git a/web/static/pages/mk12-deal/index.html b/web/static/pages/mk12-deal/index.html index 66db8115b..5f63a80b5 100644 --- a/web/static/pages/mk12-deal/index.html +++ b/web/static/pages/mk12-deal/index.html @@ -6,6 +6,9 @@ + diff --git a/web/static/pages/wallet/index.html b/web/static/pages/wallet/index.html new file mode 100644 index 000000000..ab032b363 --- /dev/null +++ b/web/static/pages/wallet/index.html @@ -0,0 +1,25 @@ + + + + Wallets + + + + + + +
+
+
+

Friendly Wallet Names

+
+
+
+ +
+
+
+
+
+ + \ No newline at end of file diff --git a/web/static/pages/wallet/wallet.mjs b/web/static/pages/wallet/wallet.mjs new file mode 100644 index 000000000..7f53c9fa4 --- /dev/null +++ b/web/static/pages/wallet/wallet.mjs @@ -0,0 +1,212 @@ +import { LitElement, html, css } from 'https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js'; +import RPCCall from '/lib/jsonrpc.mjs'; + +class WalletNames extends LitElement { + static properties = { + wallets: { type: Array }, + searchQuery: { type: String }, + filteredWallets: { type: Array }, + newWallet: { type: String }, + newName: { type: String }, + editingWallet: { type: String }, + editingName: { type: String } + }; + + constructor() { + super(); + this.wallets = []; + this.searchQuery = ''; + this.filteredWallets = []; + this.newWallet = ''; + this.newName = ''; + this.editingWallet = null; + this.editingName = ''; + this.loadWallets(); + } + + connectedCallback() { + super.connectedCallback(); + // Get the search term from the URL parameter (if available) + const urlParams = new URLSearchParams(window.location.search); + const wallet = urlParams.get('id'); + if (wallet) { + this.searchQuery = wallet.toLowerCase(); + this.handleSearch(); + } + } + + async loadWallets() { + try { + const result = await RPCCall('WalletNames', []); + if (result && typeof result === 'object') { + this.wallets = Object.entries(result).map(([wallet, name]) => ({ wallet, name })); + this.handleSearch(); + } else { + this.wallets = []; + this.filteredWallets = []; + } + } catch (err) { + console.error('Failed to fetch wallet names:', err); + alert('Failed to fetch wallet names: ' + err.message); + this.wallets = []; + this.filteredWallets = []; + } + } + + handleSearch(e) { + if (e) this.searchQuery = e.target.value.toLowerCase(); + this.filteredWallets = this.wallets.filter(({ wallet, name }) => + wallet.toLowerCase().includes(this.searchQuery) || + name.toLowerCase().includes(this.searchQuery) + ); + } + + async handleAddWallet(e) { + e.preventDefault(); + try { + await RPCCall('WalletAdd', [this.newWallet, this.newName]); + this.newWallet = ''; + this.newName = ''; + await this.loadWallets(); + } catch (err) { + console.error('Failed to add wallet:', err); + alert('Failed to add wallet: ' + err.message); + } + } + + async handleDeleteWallet(wallet) { + try { + await RPCCall('WalletRemove', [wallet]); + await this.loadWallets(); + } catch (err) { + console.error('Failed to remove wallet:', err); + alert('Failed to remove wallet: ' + err.message); + } + } + + startEdit(wallet, currentName) { + this.editingWallet = wallet; + this.editingName = currentName; + } + + cancelEdit() { + this.editingWallet = null; + this.editingName = ''; + } + + async saveEdit() { + try { + await RPCCall('WalletNameChange', [this.editingWallet, this.editingName]); + this.cancelEdit(); + await this.loadWallets(); + } catch (err) { + console.error('Failed to update wallet name:', err); + alert('Failed to update wallet name: ' + err.message); + } + } + + render() { + return html` + + + +
+

Wallet Names

+ +
+
+ +
+
+ +
+
+ +
+ + +
+ +
+ + ${this.filteredWallets.length === 0 + ? html`

No wallet names found.

` + : html` +
${this.attoFilToFilPerTiBPerMonth(filter.price)} ${filter.verified ? 'Yes' : 'No'} +
+
${bal.address} ${bal.balance}
+ + + + + + + + + ${this.filteredWallets.map( + ({ wallet, name }) => html` + + + + + ` + )} + +
WalletNameAction
${wallet} + ${this.editingWallet === wallet + ? html`` + : name} + + ${this.editingWallet === wallet + ? html`
+ + +
` + : html`
+ + + + +
+ `} +
+ `; + } + + static styles = css` + :host { + display: block; + } + .me-1 { + margin-right: 0.5rem; + } + `; +} + +customElements.define('wallet-names', WalletNames); diff --git a/web/static/ux/curio-ux.mjs b/web/static/ux/curio-ux.mjs index 2da5ce91d..f0a015c86 100644 --- a/web/static/ux/curio-ux.mjs +++ b/web/static/ux/curio-ux.mjs @@ -226,6 +226,14 @@ class CurioUX extends LitElement { IPNI +
  • + + + + + Wallets + +