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
38 changes: 0 additions & 38 deletions cmd/rpc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2595,13 +2595,9 @@ $ curl -X POST localhost:50002/v1/query/order \

- **height**: `uint64` – the block height to read data from (optional: use 0 to read from the latest block)
- **committee**: `uint64` – the unique identifier of the committee to filter by (optional: use 0 to get all committees)
- **sellersSendAddress**: `hex-string` – the seller address to filter orders by (optional: use "" to get all seller addresses)
- **buyerSendAddress**: `hex-string` – the buyer address to filter locked orders by (optional: use "" to get all buyer addresses)
- **pageNumber**: `int` – the page number to retrieve (optional: starts at 1)
- **perPage**: `int` – the number of orders per page (optional: defaults to system default)

**Note**: `sellersSendAddress` and `buyerSendAddress` are mutually exclusive filters. You cannot use both in the same request.

**Response**:
- **pageNumber**: `int` - the current page number
- **perPage**: `int` - the number of items per page
Expand Down Expand Up @@ -2670,40 +2666,6 @@ $ curl -X POST localhost:50002/v1/query/orders \
}'
```

**Example 3: Filter by sellersSendAddress with pagination (uses indexed lookup)**
```
$ curl -X POST localhost:50002/v1/query/orders \
-H "Content-Type: application/json" \
-d '{
"sellersSendAddress": "bb43c46244cef15f2451a446cea011fc1a2eddfe",
"pageNumber": 1,
"perPage": 10
}'
```

**Example 4: Filter by both committee and sellersSendAddress with pagination (most efficient)**
```
$ curl -X POST localhost:50002/v1/query/orders \
-H "Content-Type: application/json" \
-d '{
"committee": 1,
"sellersSendAddress": "bb43c46244cef15f2451a446cea011fc1a2eddfe",
"pageNumber": 1,
"perPage": 10
}'
```

**Example 5: Filter by buyerSendAddress with pagination (locked orders only)**
```
$ curl -X POST localhost:50002/v1/query/orders \
-H "Content-Type: application/json" \
-d '{
"buyerSendAddress": "aaac0b3d64c12c6f164545545b2ba2ab4d80deff",
"pageNumber": 1,
"perPage": 10
}'
```

## Dex Batch
**Route:** `/v1/query/dex-batch`
**Description**: view the locked dex batch for a committee or all dex batches
Expand Down
28 changes: 2 additions & 26 deletions cmd/rpc/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,34 +273,10 @@ func (s *Server) Order(w http.ResponseWriter, r *http.Request, _ httprouter.Para
})
}

// Orders retrieves the order book for a committee with optional filters and pagination
// Orders retrieves the order book for a committee with pagination
func (s *Server) Orders(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
// Invoke helper with the HTTP request, response writer and an inline callback
s.ordersParams(w, r, func(s *fsm.StateMachine, req *ordersRequest) (any, lib.ErrorI) {
// validate mutual exclusion: cannot filter by both seller and buyer address
if req.SellersSendAddress != "" && req.BuyerSendAddress != "" {
return nil, lib.NewError(lib.CodeInvalidArgument, lib.RPCModule, "cannot filter by both sellersSendAddress and buyerSendAddress")
}
// convert seller address if provided
var sellerAddr []byte
if req.SellersSendAddress != "" {
var err lib.ErrorI
sellerAddr, err = lib.StringToBytes(req.SellersSendAddress)
if err != nil {
return nil, err
}
}
// convert buyer address if provided
var buyerAddr []byte
if req.BuyerSendAddress != "" {
var err lib.ErrorI
buyerAddr, err = lib.StringToBytes(req.BuyerSendAddress)
if err != nil {
return nil, err
}
}
// use paginated query
return s.GetOrdersPaginated(sellerAddr, buyerAddr, req.Committee, req.PageParams)
return s.GetOrdersPaginated(req.Committee, req.PageParams)
})
}

Expand Down
4 changes: 1 addition & 3 deletions cmd/rpc/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ type orderRequest struct {
}

type ordersRequest struct {
Committee uint64 `json:"committee"`
SellersSendAddress string `json:"sellersSendAddress"`
BuyerSendAddress string `json:"buyerSendAddress"`
Committee uint64 `json:"committee"`
heightRequest
lib.PageParams
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/rpc/web/explorer/src/components/Home/OverviewCards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ const OverviewCards: React.FC = () => {
key={c.type}
title={c.title}
live
viewAllPath="/swaps"
viewAllPath="/token-swaps"
columns={[{ label: 'Action' }, { label: 'Exchange Rate' }, { label: 'Hash' }]}
rows={rows}
/>
Expand Down
6 changes: 3 additions & 3 deletions cmd/rpc/web/explorer/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { motion, AnimatePresence } from 'framer-motion'
import React from 'react'
import menuConfig from '../data/navbar.json'
import Logo from './Logo'
import { useAllBlocksCache } from '../hooks/useApi'
import { useLatestBlock } from '../hooks/useApi'
import NetworkSelector from './NetworkSelector'

const Navbar = () => {
Expand Down Expand Up @@ -48,7 +48,7 @@ const Navbar = () => {
// State for mobile dropdowns (accordion)
const [mobileOpenIndex, setMobileOpenIndex] = React.useState<number | null>(null)
const toggleMobileIndex = (index: number) => setMobileOpenIndex(prev => prev === index ? null : index)
const blocks = useAllBlocksCache()
const latestBlock = useLatestBlock()

// Check whether the current route is inside an item's child routes
const isActiveRoute = (item: MenuItem): boolean => {
Expand Down Expand Up @@ -93,7 +93,7 @@ const Navbar = () => {
<Logo size={180} showText={false} />
<div className="bg-card rounded-full px-2 py-1 flex items-center gap-2 text-base">
<p className='text-gray-500 font-light'>Block:</p>
<p className="font-medium text-primary">#{blocks.data?.[0]?.blockHeader?.height?.toLocaleString() || '0'}</p>
<p className="font-medium text-primary">#{latestBlock.data?.totalCount?.toLocaleString() || '0'}</p>
</div>
</Link>
</div>
Expand Down
95 changes: 49 additions & 46 deletions cmd/rpc/web/explorer/src/components/account/AccountsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useState, useEffect, useMemo } from 'react'
import React, { useState, useEffect } from 'react'
import { motion } from 'framer-motion'
import AccountsTable from './AccountsTable'
import { useAccounts, useAllValidators } from '../../hooks/useApi'
import { getTotalAccountCount } from '../../lib/api'
import { useAccounts } from '../../hooks/useApi'
import { getTotalAccountCount, Account as AccountApi } from '../../lib/api'
import accountsTexts from '../../data/accounts.json'
import AnimatedNumber from '../AnimatedNumber'

Expand All @@ -15,30 +15,8 @@ const AccountsPage: React.FC = () => {
const [isLoadingStats, setIsLoadingStats] = useState(true)

const { data: accountsData, isLoading, error } = useAccounts(currentPage)
const { data: validatorsData } = useAllValidators()

// Create a map of addresses to staking type
const stakingTypeMap = useMemo(() => {
const map = new Map<string, 'validator' | 'delegator' | 'unstaked'>()

if (validatorsData?.results && Array.isArray(validatorsData.results)) {
validatorsData.results.forEach((validator: any) => {
const address = validator.address
if (!address) return

// Check if unstaking
if (validator.unstakingHeight && validator.unstakingHeight > 0) {
map.set(address.toLowerCase(), 'unstaked')
} else if (validator.delegate === true) {
map.set(address.toLowerCase(), 'delegator')
} else {
map.set(address.toLowerCase(), 'validator')
}
})
}

return map
}, [validatorsData])
const [directSearchResult, setDirectSearchResult] = useState<{ address: string; amount: number } | null>(null)
const [isSearchingDirect, setIsSearchingDirect] = useState(false)

// Fetch account statistics
useEffect(() => {
Expand All @@ -57,9 +35,32 @@ const AccountsPage: React.FC = () => {
fetchStats()
}, [])

// Reset to first page when search term changes
// Reset to first page when search term changes and do direct lookup for exact addresses
useEffect(() => {
setCurrentPage(1)
setDirectSearchResult(null)

const trimmed = searchTerm.trim()
const isExactAddress = /^[a-fA-F0-9]{40}$/.test(trimmed)

if (isExactAddress) {
setIsSearchingDirect(true)
AccountApi(0, trimmed)
.then((data) => {
if (data && data.address) {
setDirectSearchResult({
address: data.address,
amount: Number(data.amount || 0) / 1_000_000,
})
}
})
.catch(() => {
setDirectSearchResult(null)
})
.finally(() => {
setIsSearchingDirect(false)
})
}
}, [searchTerm])

const handlePageChange = (page: number) => {
Expand All @@ -68,27 +69,30 @@ const AccountsPage: React.FC = () => {

const handleEntriesPerPageChange = (value: number) => {
setCurrentEntriesPerPage(value)
setCurrentPage(1) // Reset to first page when changing entries per page
setCurrentPage(1)
}

// Filter accounts based on search term
const filteredAccounts = accountsData?.results?.filter(account =>
account.address.toLowerCase().includes(searchTerm.toLowerCase())
) || []

// Calculate pagination for filtered results
const isSearching = searchTerm.trim() !== ''
const isExactAddress = /^[a-fA-F0-9]{40}$/.test(searchTerm.trim())

// For exact address search, use direct API result; for partial, filter current page
const filteredAccounts = isSearching && !isExactAddress
? (accountsData?.results?.filter(account =>
account.address.toLowerCase().includes(searchTerm.toLowerCase())
) || [])
: []

const accountsToShow = isSearching
? (isExactAddress && directSearchResult ? [directSearchResult] : filteredAccounts)
: (accountsData?.results || [])
const totalCount = isSearching
? accountsToShow.length
: (accountsData?.totalCount || 0)

// For search results, implement local pagination
// For normal browsing, use server pagination
const accountsToShow = isSearching ? filteredAccounts : (accountsData?.results || [])
const totalCount = isSearching ? filteredAccounts.length : (accountsData?.totalCount || 0)

// Local pagination for search results only
const startIndex = (currentPage - 1) * currentEntriesPerPage
const endIndex = startIndex + currentEntriesPerPage
const paginatedAccounts = isSearching
? filteredAccounts.slice(startIndex, endIndex)
? accountsToShow.slice(startIndex, endIndex)
: accountsToShow

// Stage card component
Expand Down Expand Up @@ -179,7 +183,7 @@ const AccountsPage: React.FC = () => {
<div className="relative">
<input
type="text"
placeholder="Search by address..."
placeholder="Search by full address (40 hex chars) or filter current page..."
className="w-full px-4 py-3 pl-10 bg-card border border-gray-800/80 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
Expand All @@ -201,14 +205,13 @@ const AccountsPage: React.FC = () => {
{/* Right Column - Accounts Table */}
<div className="lg:col-span-2">
<AccountsTable
accounts={isSearching ? paginatedAccounts : (accountsData?.results || [])}
loading={isLoading}
accounts={paginatedAccounts}
loading={isLoading || isSearchingDirect}
totalCount={totalCount}
currentPage={currentPage}
onPageChange={handlePageChange}
currentEntriesPerPage={currentEntriesPerPage}
onEntriesPerPageChange={handleEntriesPerPageChange}
stakingTypeMap={stakingTypeMap}
/>
</div>
</div>
Expand Down
26 changes: 2 additions & 24 deletions cmd/rpc/web/explorer/src/components/account/AccountsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ interface AccountsTableProps {
onEntriesPerPageChange?: (value: number) => void
showExportButton?: boolean
onExportButtonClick?: () => void
stakingTypeMap?: Map<string, 'validator' | 'delegator' | 'unstaked'>
}

const AccountsTable: React.FC<AccountsTableProps> = ({
Expand All @@ -31,14 +30,12 @@ const AccountsTable: React.FC<AccountsTableProps> = ({
totalCount = 0,
currentPage = 1,
onPageChange,
// Destructure the new props
showEntriesSelector = false,
entriesPerPageOptions = [10, 25, 50, 100],
currentEntriesPerPage = 10,
onEntriesPerPageChange,
showExportButton = false,
onExportButtonClick,
stakingTypeMap
}) => {
const navigate = useNavigate()
const truncateLong = (s: string, start: number = 10, end: number = 8) => {
Expand All @@ -47,17 +44,8 @@ const AccountsTable: React.FC<AccountsTableProps> = ({
}


// Get staking type for an account
const getStakingType = (address: string): 'validator' | 'delegator' | 'unstaked' | null => {
if (!stakingTypeMap) return null
return stakingTypeMap.get(address.toLowerCase()) || null
}

const rows = accounts.length > 0 ? accounts.map((account) => {
const stakingType = getStakingType(account.address)

return [
// Address
<span
className="text-primary cursor-pointer hover:underline font-mono text-sm"
onClick={() => navigate(`/account/${account.address}`)}
Expand All @@ -66,26 +54,16 @@ const AccountsTable: React.FC<AccountsTableProps> = ({
{truncateLong(account.address, 16, 12)}
</span>,

// Amount
<span className="text-white font-medium">
<AnimatedNumber value={account.amount} format={{ maximumFractionDigits: 4 }} className="text-white" />
<span className="text-gray-400 ml-1">CNPY</span>
</span>,

// Staking Type
<span className="text-gray-300 text-sm">
{stakingType === 'validator' && <span className="text-green-400">Validator</span>}
{stakingType === 'delegator' && <span className="text-blue-400">Delegator</span>}
{stakingType === 'unstaked' && <span className="text-orange-400">Unstaked</span>}
{!stakingType && <span className="text-gray-500">—</span>}
</span>
]
}) : []

const columns = [
{ label: accountsTexts.table.headers.address, width: 'w-[30%]' },
{ label: accountsTexts.table.headers.balance, width: 'w-[25%]' },
{ label: 'Staking', width: 'w-[20%]' }
{ label: accountsTexts.table.headers.address, width: 'w-[40%]' },
{ label: accountsTexts.table.headers.balance, width: 'w-[35%]' },
]

// Show message when no data
Expand Down
Loading
Loading