-
-
Notifications
You must be signed in to change notification settings - Fork 279
Expand file tree
/
Copy pathfirst-time-interaction.ts
More file actions
149 lines (132 loc) · 4.89 KB
/
first-time-interaction.ts
File metadata and controls
149 lines (132 loc) · 4.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import type { TransactionDescription } from '@ethersproject/abi';
import type { TraceContext, TraceCallback } from '@metamask/controller-utils';
import { hexToNumber } from '@metamask/utils';
import { getAccountAddressRelationship } from '../api/accounts-api';
import type { GetAccountAddressRelationshipRequest } from '../api/accounts-api';
import { projectLogger as log } from '../logger';
import { TransactionType } from '../types';
import type { TransactionMeta } from '../types';
import { decodeTransactionData } from './transaction-type';
import { validateParamTo } from './validation';
const TOKEN_TRANSFER_TYPES = [
TransactionType.tokenMethodTransfer,
TransactionType.tokenMethodTransferFrom,
TransactionType.tokenMethodSafeTransferFrom,
];
/**
* Returns the effective recipient for first-time-interaction checks (decoded from data for token transfers).
* Used when comparing existing transactions so we match by actual recipient, not txParams.to (the token
* contract for ERC20/ERC721/ERC1155 transfer methods).
*
* @param tx - Transaction meta with txParams and type
* @returns Effective recipient address, or undefined
*/
function getEffectiveRecipient(tx: TransactionMeta): string | undefined {
const { data, to } = tx?.txParams ?? {};
if (data && TOKEN_TRANSFER_TYPES.includes(tx?.type as TransactionType)) {
const parsed = decodeTransactionData(data) as TransactionDescription;
return (parsed?.args?._to ?? parsed?.args?.to ?? to) as string | undefined;
}
return to;
}
type UpdateFirstTimeInteractionRequest = {
existingTransactions: TransactionMeta[];
getTransaction: (transactionId: string) => TransactionMeta | undefined;
isFirstTimeInteractionEnabled: () => boolean;
trace: TraceCallback;
traceContext?: TraceContext;
transactionMeta: TransactionMeta;
updateTransaction: (
updateParams: {
transactionId: string;
note: string;
},
updater: (txMeta: TransactionMeta) => void,
) => void;
};
/**
* Updates the first-time interaction status for a transaction.
*
* @param params - The parameters for updating first time interaction.
* @param params.existingTransactions - The existing transactions.
* @param params.getTransaction - Function to get a transaction by ID.
* @param params.isFirstTimeInteractionEnabled - The function to check if first time interaction is enabled.
* @param params.trace - The trace callback.
* @param params.traceContext - The trace context.
* @param params.transactionMeta - The transaction meta object.
* @param params.updateTransaction - Function to update transaction internal state.
* @returns Promise that resolves when the update is complete.
*/
export async function updateFirstTimeInteraction({
existingTransactions,
getTransaction,
isFirstTimeInteractionEnabled,
trace,
traceContext,
transactionMeta,
updateTransaction,
}: UpdateFirstTimeInteractionRequest): Promise<void> {
if (!isFirstTimeInteractionEnabled()) {
return;
}
const {
chainId,
id: transactionId,
txParams: { from },
} = transactionMeta;
const recipient = getEffectiveRecipient(transactionMeta);
const request: GetAccountAddressRelationshipRequest = {
chainId: hexToNumber(chainId),
to: recipient as string,
from,
};
validateParamTo(recipient);
const recipientLower = recipient?.toLowerCase();
const existingTransaction = existingTransactions.find(
(tx) =>
tx.id !== transactionId &&
tx.chainId === chainId &&
tx.txParams?.from?.toLowerCase() === from?.toLowerCase() &&
getEffectiveRecipient(tx)?.toLowerCase() === recipientLower,
);
// Skip API call only if we already have a tx with same from and same effective recipient (e.g. duplicate or pending).
// For token transfers (ERC20/ERC721/ERC1155), effective recipient is decoded from data; using txParams.to
// would wrongly match any send of the same token contract.
if (existingTransaction) {
return;
}
try {
const { count } = await trace(
{ name: 'Account Address Relationship', parentContext: traceContext },
() => getAccountAddressRelationship(request),
);
const isFirstTimeInteraction =
count === undefined ? undefined : count === 0;
const finalTransactionMeta = getTransaction(transactionId);
/* istanbul ignore if */
if (!finalTransactionMeta) {
log(
'Cannot update first time interaction as transaction not found',
transactionId,
);
return;
}
updateTransaction(
{
transactionId,
note: 'TransactionController#updateFirstInteraction - Update first time interaction',
},
(txMeta) => {
txMeta.isFirstTimeInteraction = isFirstTimeInteraction;
},
);
log('Updated first time interaction', transactionId, {
isFirstTimeInteraction,
});
} catch (error) {
log(
'Error fetching account address relationship, skipping first time interaction update',
error,
);
}
}