Skip to content

feat: Add transactions by coin type and owner filtering#1500

Open
gregnazario wants to merge 1 commit intomainfrom
cursor/transaction-type-and-owner-e68c
Open

feat: Add transactions by coin type and owner filtering#1500
gregnazario wants to merge 1 commit intomainfrom
cursor/transaction-type-and-owner-e68c

Conversation

@gregnazario
Copy link
Contributor

@gregnazario gregnazario commented Mar 20, 2026

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:

  • New hook: useGetCoinActivitiesByOwnerCursor — cursor-based pagination hook that queries the indexer's fungible_asset_activities table filtered by both asset_type and owner_address, matching the existing useGetCoinActivitiesCursor pattern
  • Updated Coin and FungibleAsset TransactionsTab — both now support an ?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 pagination
  • Updated Account CoinsTable — adds a "Txns" column with receipt icon buttons that link to the coin/FA transactions page with the owner address pre-filled

User flow:

  1. Go to any account's Assets tab
  2. Click the receipt icon next to any coin
  3. See all transactions for that coin filtered by the account owner
  4. Optionally clear the filter or enter a different owner address
  5. Use the activity type filter to narrow by deposit/withdraw/mint/burn

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 fmt applied
  • pnpm lint passes (no new warnings)
  • TypeScript compiles cleanly
  • All 168 existing tests pass
  • Rebased on latest main
  • Manually tested end-to-end on mainnet
Open in Web Open in Cursor 

@netlify
Copy link

netlify bot commented Mar 20, 2026

Deploy Preview for aptos-explorer ready!

Name Link
🔨 Latest commit a5872ea
🔍 Latest deploy log https://app.netlify.com/projects/aptos-explorer/deploys/69be21bc85fe450008acaf3f
😎 Deploy Preview https://deploy-preview-1500--aptos-explorer.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@cursor cursor bot force-pushed the cursor/transaction-type-and-owner-e68c branch from 23db13a to e0486ea Compare March 20, 2026 18:48
@gregnazario gregnazario marked this pull request as ready for review March 20, 2026 21:13
- 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
@cursor cursor bot force-pushed the cursor/transaction-type-and-owner-e68c branch from e0486ea to a5872ea Compare March 21, 2026 04:42
@gregnazario gregnazario requested a review from Copilot March 21, 2026 06:35
Copy link
Contributor

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

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 useGetCoinActivitiesByOwnerCursor to query fungible_asset_activities filtered by asset_type + owner_address with cursor pagination.
  • Updated Coin and FungibleAsset TransactionsTab to 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.

Comment on lines +84 to +148
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>
);
}
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +90 to +95
const [inputValue, setInputValue] = useState(owner);

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onOwnerChange(inputValue.trim());
};
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
Comment on lines +283 to 289
const [prevAsset, setPrevAsset] = useState(asset);

if (address !== prevAddress) {
setPrevAddress(address);
if (asset !== prevAsset) {
setPrevAsset(asset);
setCursorStack([]);
setFilter("all");
}
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
Comment on lines +383 to +407
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>
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +164 to +174
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)});
}
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +284 to 290
const [prevAsset, setPrevAsset] = useState(asset);

if (asset !== prevAsset) {
setPrevAsset(asset);
setCursorStack([]);
setFilter("all");
}
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
Comment on lines +158 to +165
const [prevKey, setPrevKey] = useState(`${asset}-${owner}`);

const key = `${asset}-${owner}`;
if (key !== prevKey) {
setPrevKey(key);
setCursorStack([]);
setFilter("all");
}
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +252 to +270
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
}
}
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +159 to +166
const [prevKey, setPrevKey] = useState(`${asset}-${owner}`);

if (struct !== prevStruct) {
setPrevStruct(struct);
const key = `${asset}-${owner}`;
if (key !== prevKey) {
setPrevKey(key);
setCursorStack([]);
setFilter("all");
}
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +388 to +413
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} />
)}
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
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.

3 participants