feat: Add transactions by coin type and owner filtering#1500
feat: Add transactions by coin type and owner filtering#1500gregnazario wants to merge 1 commit intomainfrom
Conversation
✅ Deploy Preview for aptos-explorer ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
23db13a to
e0486ea
Compare
- Add useGetCoinActivitiesByOwnerCursor hook using cursor-based pagination filtered by both asset_type and owner_address - Update Coin and FungibleAsset TransactionsTab to support ?owner= search param for filtering transactions by owner address, integrating with the existing cursor-based pagination and activity type filter - Add 'View Transactions' button in Account CoinsTable that links to the coin/FA page transactions tab with owner pre-filled - Supports both Coin (v1) and Fungible Asset (v2) standards
e0486ea to
a5872ea
Compare
There was a problem hiding this comment.
Pull request overview
Adds owner-address filtering for coin and fungible-asset transaction activity views, enabling “show me all transactions for this asset for a specific owner” flows from the account assets table.
Changes:
- Added
useGetCoinActivitiesByOwnerCursorto queryfungible_asset_activitiesfiltered byasset_type+owner_addresswith cursor pagination. - Updated Coin and FungibleAsset
TransactionsTabto support an?owner=search param and render an owner filter input + filtered activity list. - Updated Account coins UI to include a “Txns” action linking into the filtered transactions view for the account owner.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| app/api/hooks/useGetCoinActivities.ts | Adds a new cursor-based hook for activities filtered by owner + asset type. |
| app/pages/Coin/Tabs/TransactionsTab.tsx | Adds ?owner=-driven owner filter UI and switches data source when filtering is enabled. |
| app/pages/FungibleAsset/Tabs/TransactionsTab.tsx | Mirrors Coin transactions tab owner filtering behavior for fungible assets. |
| app/pages/Account/Tabs/CoinsTab.tsx | Passes the account address into the coins table to enable owner-scoped transaction links. |
| app/pages/Account/Components/CoinsTable.tsx | Adds a “Txns” column/icon to deep-link into the asset transactions tab with owner prefilled. |
| function OwnerFilterInput({ | ||
| owner, | ||
| onOwnerChange, | ||
| }: { | ||
| owner: string; | ||
| onOwnerChange: (owner: string) => void; | ||
| }) { | ||
| const [inputValue, setInputValue] = useState(owner); | ||
|
|
||
| const handleSubmit = (e: React.FormEvent) => { | ||
| e.preventDefault(); | ||
| onOwnerChange(inputValue.trim()); | ||
| }; | ||
|
|
||
| const handleClear = () => { | ||
| setInputValue(""); | ||
| onOwnerChange(""); | ||
| }; | ||
|
|
||
| return ( | ||
| <Stack spacing={1} sx={{mb: 2}}> | ||
| <form onSubmit={handleSubmit}> | ||
| <TextField | ||
| size="small" | ||
| fullWidth | ||
| placeholder="Filter by owner address (e.g. 0x1a2b...)" | ||
| value={inputValue} | ||
| onChange={(e) => setInputValue(e.target.value)} | ||
| slotProps={{ | ||
| input: { | ||
| endAdornment: inputValue ? ( | ||
| <InputAdornment position="end"> | ||
| <IconButton | ||
| aria-label="Clear owner filter" | ||
| onClick={handleClear} | ||
| edge="end" | ||
| size="small" | ||
| > | ||
| <ClearIcon fontSize="small" /> | ||
| </IconButton> | ||
| </InputAdornment> | ||
| ) : null, | ||
| sx: {fontFamily: "monospace", fontSize: "0.875rem"}, | ||
| }, | ||
| }} | ||
| /> | ||
| </form> | ||
| {owner && ( | ||
| <Stack direction="row" alignItems="center" spacing={1}> | ||
| <Typography variant="body2" color="text.secondary"> | ||
| Filtered by owner: | ||
| </Typography> | ||
| <Chip | ||
| label={ | ||
| <HashButton hash={owner} type={HashType.ACCOUNT} size="small" /> | ||
| } | ||
| onDelete={handleClear} | ||
| size="small" | ||
| variant="outlined" | ||
| /> | ||
| </Stack> | ||
| )} | ||
| </Stack> | ||
| ); | ||
| } |
There was a problem hiding this comment.
OwnerFilterInput is duplicated (nearly identical) between Coin and FungibleAsset TransactionsTab. Consider extracting this into a shared component to avoid future divergence and to keep validation/sync behavior consistent.
| const [inputValue, setInputValue] = useState(owner); | ||
|
|
||
| const handleSubmit = (e: React.FormEvent) => { | ||
| e.preventDefault(); | ||
| onOwnerChange(inputValue.trim()); | ||
| }; |
There was a problem hiding this comment.
OwnerFilterInput initializes inputValue from the owner prop but never updates it when owner changes (e.g., back/forward navigation or clearing the owner search param externally). This can leave the text field showing a stale value while the chip/list reflects the new owner. Sync inputValue when owner changes (or make the TextField fully controlled by the prop).
| const [prevAsset, setPrevAsset] = useState(asset); | ||
|
|
||
| if (address !== prevAddress) { | ||
| setPrevAddress(address); | ||
| if (asset !== prevAsset) { | ||
| setPrevAsset(asset); | ||
| setCursorStack([]); | ||
| setFilter("all"); | ||
| } |
There was a problem hiding this comment.
AllTransactionsContent updates state during render when asset changes (if (asset !== prevAsset) { ... }). This is not safe in React and may trigger warnings. Use a useEffect on [asset] to reset cursorStack/filter (or remount AllTransactionsContent when asset changes).
| const [searchParams, setSearchParams] = useSearchParams(); | ||
| const ownerParam = searchParams.get("owner") ?? ""; | ||
|
|
||
| const handleOwnerChange = (newOwner: string) => { | ||
| if (newOwner) { | ||
| searchParams.set("owner", newOwner); | ||
| } else { | ||
| searchParams.delete("owner"); | ||
| } | ||
| setSearchParams(searchParams, {replace: true}); | ||
| }; | ||
|
|
||
| if (!data || Array.isArray(data)) { | ||
| return <EmptyTabContent />; | ||
| } | ||
|
|
||
| return ( | ||
| <Box> | ||
| <OwnerFilterInput owner={ownerParam} onOwnerChange={handleOwnerChange} /> | ||
| {ownerParam ? ( | ||
| <FilteredByOwnerContent asset={address} owner={ownerParam} /> | ||
| ) : ( | ||
| <AllTransactionsContent asset={address} /> | ||
| )} | ||
| </Box> |
There was a problem hiding this comment.
When owner is invalid, useGetCoinActivitiesByOwnerCursor disables the query (standardizedOwner becomes undefined), so this tab falls through to the "No transactions" empty state rather than indicating invalid input. Consider validating/standardizing ownerParam before rendering FilteredByOwnerContent and show an explicit "Invalid owner address" message when parsing fails.
| const assetId = data.tokenAddress ?? data.faAddress; | ||
| const isFA = data.tokenStandard === "v2"; | ||
| const linkTo = isFA | ||
| ? `/fungible_asset/${assetId}/transactions?owner=${ownerAddress}` | ||
| : `/coin/${assetId}/transactions?owner=${ownerAddress}`; | ||
|
|
||
| const handleClick = (e: React.MouseEvent) => { | ||
| e.stopPropagation(); | ||
| if (assetId) { | ||
| navigate({to: augmentTo(linkTo)}); | ||
| } |
There was a problem hiding this comment.
CoinTransactionsCell constructs a link even when both tokenAddress and faAddress are null (assetId becomes null), and still renders an enabled IconButton. This yields a non-functional button (and a path containing "null"). Consider hiding/disabled-rendering the button when assetId is falsy, and encode the owner query param when building the URL.
| const [prevAsset, setPrevAsset] = useState(asset); | ||
|
|
||
| if (asset !== prevAsset) { | ||
| setPrevAsset(asset); | ||
| setCursorStack([]); | ||
| setFilter("all"); | ||
| } |
There was a problem hiding this comment.
AllTransactionsContent updates state during render when asset changes (if (asset !== prevAsset) { ... }). This is not safe in React and may trigger warnings. Use a useEffect on [asset] to reset cursorStack/filter (or remount AllTransactionsContent when asset changes).
| const [prevKey, setPrevKey] = useState(`${asset}-${owner}`); | ||
|
|
||
| const key = `${asset}-${owner}`; | ||
| if (key !== prevKey) { | ||
| setPrevKey(key); | ||
| setCursorStack([]); | ||
| setFilter("all"); | ||
| } |
There was a problem hiding this comment.
FilteredByOwnerContent calls setState during render when asset/owner changes (the if (key !== prevKey) block). Updating state during render is unsupported in React and can cause warnings or render loops. Move this reset logic into a useEffect keyed on [asset, owner] (or remount the component via a key prop) instead.
| const COIN_ACTIVITIES_BY_OWNER_CURSOR_QUERY = ` | ||
| query GetFAActivitiesByOwnerCursor($asset: String, $owner: String, $limit: Int, $cursor: bigint) { | ||
| fungible_asset_activities( | ||
| where: { | ||
| asset_type: {_eq: $asset} | ||
| owner_address: {_eq: $owner} | ||
| type: {_neq: "0x1::aptos_coin::GasFeeEvent"} | ||
| transaction_version: {_lt: $cursor} | ||
| } | ||
| limit: $limit | ||
| order_by: [{transaction_version: desc}, {event_index: desc}] | ||
| ) { | ||
| transaction_version | ||
| event_index | ||
| owner_address | ||
| type | ||
| amount | ||
| } | ||
| } |
There was a problem hiding this comment.
The cursor query paginates with order_by: [{transaction_version: desc}, {event_index: desc}] but the cursor filter is only transaction_version: {_lt: $cursor}. If a page ends mid-transaction (multiple rows share the same transaction_version), the remaining rows for that version will be skipped on the next page. Use a composite cursor (transaction_version + event_index) and filter with an _or condition to page correctly.
| const [prevKey, setPrevKey] = useState(`${asset}-${owner}`); | ||
|
|
||
| if (struct !== prevStruct) { | ||
| setPrevStruct(struct); | ||
| const key = `${asset}-${owner}`; | ||
| if (key !== prevKey) { | ||
| setPrevKey(key); | ||
| setCursorStack([]); | ||
| setFilter("all"); | ||
| } |
There was a problem hiding this comment.
FilteredByOwnerContent calls setState during render when asset/owner changes (the if (key !== prevKey) block). Updating state during render is unsupported in React and can cause warnings or render loops. Move this reset logic into a useEffect keyed on [asset, owner] (or remount the component via a key prop) instead.
| const [searchParams, setSearchParams] = useSearchParams(); | ||
| const ownerParam = searchParams.get("owner") ?? ""; | ||
|
|
||
| const handleOwnerChange = (newOwner: string) => { | ||
| if (newOwner) { | ||
| searchParams.set("owner", newOwner); | ||
| } else { | ||
| searchParams.delete("owner"); | ||
| } | ||
| setSearchParams(searchParams, {replace: true}); | ||
| }; | ||
|
|
||
| if (!data || Array.isArray(data)) { | ||
| return <EmptyTabContent />; | ||
| } | ||
|
|
||
| const asset = pairedFa ?? struct; | ||
|
|
||
| return ( | ||
| <Box> | ||
| <OwnerFilterInput owner={ownerParam} onOwnerChange={handleOwnerChange} /> | ||
| {ownerParam ? ( | ||
| <FilteredByOwnerContent asset={asset} owner={ownerParam} /> | ||
| ) : ( | ||
| <AllTransactionsContent asset={asset} /> | ||
| )} |
There was a problem hiding this comment.
When owner is invalid, useGetCoinActivitiesByOwnerCursor disables the query (standardizedOwner becomes undefined), so this tab falls through to the "No transactions" empty state rather than indicating invalid input. Consider validating/standardizing ownerParam before rendering FilteredByOwnerContent and show an explicit "Invalid owner address" message when parsing fails.
Description
Adds the ability to view all transactions for a specific coin/fungible asset filtered by owner address. This enables users to see all coin transactions for a particular account — e.g., "show me all APT transactions for address 0x1".
Changes:
useGetCoinActivitiesByOwnerCursor— cursor-based pagination hook that queries the indexer'sfungible_asset_activitiestable filtered by bothasset_typeandowner_address, matching the existinguseGetCoinActivitiesCursorpattern?owner=search parameter. When set, transactions are filtered to only show activity for that owner. An owner filter input with clear functionality is provided. Integrates with the existing activity type filter (deposit/withdraw/mint/burn) and cursor-based paginationUser flow:
Supports both Coin (v1) and Fungible Asset (v2) standards.
Related Links
Related to PR #1497 — this was requested as a separate PR for "transactions by type and owner" functionality.
Checklist
pnpm fmtappliedpnpm lintpasses (no new warnings)