Skip to content

Commit 33eaccf

Browse files
n3pscursoragentCopilot
authored
feat: use account API v4 transactions (#29536)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until this PR meets the canonical Definition of Ready For Review in `docs/readme/ready-for-review.md`. In short: the template must be materially complete (not just section titles present), all status checks must be currently passing, and the only expected follow-up commits must be reviewer-driven. --> ## **Description** - Replaces confirmed EVM Activity transactions with data from accounts v4 API via React Query - Adds infinite pagination for confirmed EVM history - Keep local pending EVM transactions and existing non-EVM activity unchanged Note: Due to the API requesting a bearer token, there is a current bottleneck in that token retrieval, in particular in `AuthenticationController.getPrimaryEntropySourceId` ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: feat: use accounts API v4 for transactions ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: Accounts API v4 Transactions Scenario: user views EVM activity Given the wallet has EVM confirmed on-chain activity When user visits the Activity tab Then confirmed onchain transactions returned by the Accounts v4 API is displayed on screen. ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** <!-- Every checklist item must be consciously assessed before marking this PR as "Ready for review". A checked box means you deliberately considered that responsibility, not that you literally performed every action listed. Unchecked boxes are ambiguous: they are not an implicit "N/A" and they are not a silent "skip". See `docs/readme/ready-for-review.md` for the full checklist semantics. --> - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** <!-- Reviewer checklist items follow the same semantics as the author checklist: an unchecked box is ambiguous, a checked box means the reviewer consciously assessed that responsibility. See `docs/readme/ready-for-review.md`. --> - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Moderate risk because it rewires the Activity/UnifiedTransactionsView data source and filtering/deduping logic, which can change what transactions appear and when pagination/refresh occurs. > > **Overview** > **Confirmed EVM Activity now comes from the Accounts v4 API** via a new React Query `useTransactionsQuery` hook, while local pending EVM transactions continue to come from controller state and are merged/deduped with the API results. > > Adds a small transformation layer (`helpers/adapters` + `helpers/transformations`) to normalize API responses into `TransactionMeta`-compatible view models, filter out unwanted items (e.g. spam/incoming transfers/zero-value self-sends), and handle bridge-history matching/deduping (including case-insensitive hash matching). > > Updates `UnifiedTransactionsView` to support infinite scrolling pagination (prefetch near the end of confirmed EVM items), show initial/next-page loading indicators, and refresh both local polling and the query. Related selector additions (`selectLocalTransactions`, `selectRequiredTransactionHashes/Ids`) support filtering required child txs and nonce/hash collisions, and tests/smoke mocks were updated accordingly. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit a3e6592. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Copilot <copilot@github.com>
1 parent 8d9bcda commit 33eaccf

21 files changed

Lines changed: 2051 additions & 347 deletions

app/components/UI/MultichainBridgeTransactionListItem/MultichainBridgeTransactionListItem.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import BadgeWrapper from '../../../component-library/components/Badges/BadgeWrap
2727
import Badge, {
2828
BadgeVariant,
2929
} from '../../../component-library/components/Badges/Badge';
30+
import { AvatarSize } from '../../../component-library/components/Avatars/Avatar';
3031
import { getNetworkImageSource } from '../../../util/networks';
3132
import { parseCaipAssetType } from '@metamask/utils';
3233
import { useAnalytics } from '../../hooks/useAnalytics/useAnalytics';
@@ -102,10 +103,13 @@ const MultichainBridgeTransactionListItem = ({
102103
const networkImageSource = getNetworkImageSource({ chainId });
103104
return (
104105
<BadgeWrapper
106+
badgePosition={{ bottom: -4, right: -4 }}
105107
badgeElement={
106108
<Badge
107109
variant={BadgeVariant.Network}
108110
imageSource={networkImageSource}
111+
isScaled={false}
112+
size={AvatarSize.Xs}
109113
/>
110114
}
111115
>

app/components/UI/MultichainTransactionListItem/MultichainTransactionListItem.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import BadgeWrapper from '../../../component-library/components/Badges/BadgeWrap
2121
import Badge, {
2222
BadgeVariant,
2323
} from '../../../component-library/components/Badges/Badge';
24+
import { AvatarSize } from '../../../component-library/components/Avatars/Avatar';
2425
import { getNetworkImageSource } from '../../../util/networks';
2526
import Routes from '../../../constants/navigation/Routes';
2627
import { useAnalytics } from '../../hooks/useAnalytics/useAnalytics';
@@ -91,10 +92,13 @@ const MultichainTransactionListItem = ({
9192

9293
return (
9394
<BadgeWrapper
95+
badgePosition={{ bottom: -4, right: -4 }}
9496
badgeElement={
9597
<Badge
9698
variant={BadgeVariant.Network}
9799
imageSource={networkImageSource}
100+
isScaled={false}
101+
size={AvatarSize.Xs}
98102
/>
99103
}
100104
>

app/components/UI/TransactionElement/index.js

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import BadgeWrapper from '../../../component-library/components/Badges/BadgeWrap
4949
import Badge, {
5050
BadgeVariant,
5151
} from '../../../component-library/components/Badges/Badge';
52+
import { AvatarSize } from '../../../component-library/components/Avatars/Avatar';
5253
import { NetworkBadgeSource } from '../AssetOverview/Balance/Balance';
5354
import {
5455
getFontFamily,
@@ -101,6 +102,10 @@ const createStyles = (colors, typography) =>
101102
width: 32,
102103
height: 32,
103104
},
105+
iconBadgePosition: {
106+
bottom: -4,
107+
right: -4,
108+
},
104109
importText: {
105110
color: colors.text.alternative,
106111
fontSize: 14,
@@ -271,13 +276,13 @@ class TransactionElement extends PureComponent {
271276
mounted = false;
272277

273278
componentDidMount = async () => {
279+
this.mounted = true;
274280
const [transactionElement, transactionDetails] = await decodeTransaction({
275281
...this.props,
276282
swapsTransactions: this.props.swapsTransactions,
277283
assetSymbol: this.props.assetSymbol,
278284
ticker: this.props.ticker,
279285
});
280-
this.mounted = true;
281286

282287
this.mounted && this.setState({ transactionElement, transactionDetails });
283288
};
@@ -469,10 +474,13 @@ class TransactionElement extends PureComponent {
469474

470475
return (
471476
<BadgeWrapper
477+
badgePosition={styles.iconBadgePosition}
472478
badgeElement={
473479
<Badge
474480
variant={BadgeVariant.Network}
475481
imageSource={NetworkBadgeSource(chainId)}
482+
isScaled={false}
483+
size={AvatarSize.Xs}
476484
/>
477485
}
478486
>
@@ -715,14 +723,43 @@ class TransactionElement extends PureComponent {
715723
);
716724
};
717725

726+
renderPendingElement = () => {
727+
const { i, tx } = this.props;
728+
const { colors, typography } = this.context || mockTheme;
729+
const styles = createStyles(colors, typography);
730+
731+
return (
732+
<ListItem>
733+
<ListItem.Date style={styles.listItemDate}>
734+
{this.renderTxTime()}
735+
</ListItem.Date>
736+
<ListItem.Content style={styles.listItemContent}>
737+
<ListItem.Icon>
738+
<View style={styles.icon} />
739+
</ListItem.Icon>
740+
<ListItem.Body>
741+
<ListItem.Title numberOfLines={1} style={styles.listItemTitle}>
742+
...
743+
</ListItem.Title>
744+
<StatusText
745+
testID={`transaction-status-${i}`}
746+
status={tx.status}
747+
style={styles.listItemStatus}
748+
/>
749+
</ListItem.Body>
750+
</ListItem.Content>
751+
</ListItem>
752+
);
753+
};
754+
718755
render() {
719756
const { tx, selectedInternalAccount } = this.props;
720757
const { transactionElement, transactionDetails } = this.state;
721758

722759
const { colors, typography } = this.context || mockTheme;
723760
const styles = createStyles(colors, typography);
724761

725-
if (!transactionElement || !transactionDetails) return null;
762+
const isReady = Boolean(transactionElement && transactionDetails);
726763

727764
const accountImportTime = selectedInternalAccount?.metadata.importTime;
728765
const { time } = tx;
@@ -734,11 +771,13 @@ class TransactionElement extends PureComponent {
734771
style={
735772
this.props.showBottomBorder ? styles.rowWithBorder : styles.row
736773
}
737-
onPress={this.onPressItem}
774+
onPress={isReady ? this.onPressItem : undefined}
738775
underlayColor={colors.background.alternative}
739776
activeOpacity={1}
740777
>
741-
{this.renderTxElement(transactionElement)}
778+
{isReady
779+
? this.renderTxElement(transactionElement)
780+
: this.renderPendingElement()}
742781
</TouchableHighlight>
743782
{accountImportTime <= time && this.renderImportTime()}
744783
</>

0 commit comments

Comments
 (0)