Skip to content

Commit 27b87ce

Browse files
fix: spam filter was not triggered on failed transactions
1 parent d159ed6 commit 27b87ce

5 files changed

Lines changed: 308 additions & 44 deletions

File tree

packages/snap/snap.manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/MetaMask/snap-solana-wallet.git"
88
},
99
"source": {
10-
"shasum": "15lDW5ySUYnyrQuOe18pdCylumtDe83o1t3rLuMLWWw=",
10+
"shasum": "Cto8uBIgowP+zig7Ta1iYWJmzVrm2sPCOaQxpUajukc=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",

packages/snap/src/core/services/transactions/utils/mapRpcTransaction.test.ts

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -206,29 +206,9 @@ describe('mapRpcTransaction', () => {
206206
timestamp: 1736500242,
207207
chain: Network.Mainnet,
208208
status: 'failed',
209-
type: 'send',
210-
from: [
211-
{
212-
address: 'BLw3RweJmfbTapJRgnPRvd962YDjFYAnVGd1p5hmZ5tP',
213-
asset: {
214-
fungible: true,
215-
type: Networks[Network.Mainnet].nativeToken.caip19Id,
216-
unit: Networks[Network.Mainnet].nativeToken.symbol,
217-
amount: '0.1',
218-
},
219-
},
220-
],
221-
to: [
222-
{
223-
address: 'FDUGdV6bjhvw5gbirXCvqbTSWK9999kcrZcrHoCQzXJK',
224-
asset: {
225-
fungible: true,
226-
type: Networks[Network.Mainnet].nativeToken.caip19Id,
227-
unit: Networks[Network.Mainnet].nativeToken.symbol,
228-
amount: '0.1',
229-
},
230-
},
231-
],
209+
type: 'unknown',
210+
from: [],
211+
to: [],
232212
fees: [
233213
{
234214
type: 'base',
@@ -1115,11 +1095,10 @@ describe('mapRpcTransaction', () => {
11151095
expect(result).toBeNull();
11161096
});
11171097

1118-
// TODO: This should be enabled when we support the filtering of spam transactions
1119-
it.skip('returns null if the transaction is a spam transaction - #2', () => {
1098+
it('returns null if the transaction is a spam transaction - #2', () => {
11201099
const result = mapRpcTransaction({
11211100
scope: Network.Mainnet,
1122-
address: asAddress('DAXnAudMEqiD1sS1rFn4ds3pdybRYJd9J58PqCncVVqS'),
1101+
address: asAddress('FQT9SSwEZ6UUQxsmTzgt5JzjrS4M5zm13M1QiYF8TEo6'),
11231102
transactionData: EXPECTED_SPAM_TRANSACTION_DATA_2,
11241103
});
11251104

packages/snap/src/core/services/transactions/utils/mapRpcTransaction.ts

Lines changed: 71 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { BigNumber } from 'bignumber.js';
99
import { KnownCaip19Id, type Network } from '../../../constants/solana';
1010
import type { SolanaTransaction } from '../../../types/solana';
1111
import { parseTransactionNativeTransfers } from './parseTransactionNativeTransfers';
12+
import { parseTransactionNativeTransfersV2 } from './parseTransactionNativeTransfersV2';
1213
import { parseTransactionSplTransfers } from './parseTransactionSplTransfers';
1314

1415
/**
@@ -40,14 +41,37 @@ export function mapRpcTransaction({
4041

4142
const id = firstSignature as string;
4243
const timestamp = Number(transactionData.blockTime);
44+
const status = evaluateTransactionStatus(transactionData);
4345

44-
const nativeTransfers = parseTransactionNativeTransfers({
45-
scope,
46-
transactionData,
47-
});
46+
let fees: Transaction['fees'] = [];
47+
let nativeFrom: Transaction['from'] = [];
48+
let nativeTo: Transaction['to'] = [];
4849

49-
let { fees } = nativeTransfers;
50-
const { from: nativeFrom, to: nativeTo } = nativeTransfers;
50+
if (status === TransactionStatus.Failed) {
51+
/**
52+
* If a transaction fails we don't really have access to meaningful `preBalances`
53+
* and `postBalances` values, since nothing really happened. To extract information
54+
* from it we have created this second version of the native transfers parser which
55+
* reads instructions directly, instead of relying on balance differences.
56+
*/
57+
const nativeTransfers = parseTransactionNativeTransfersV2({
58+
scope,
59+
transactionData,
60+
});
61+
62+
fees = nativeTransfers.fees;
63+
nativeFrom = nativeTransfers.from;
64+
nativeTo = nativeTransfers.to;
65+
} else {
66+
const nativeTransfers = parseTransactionNativeTransfers({
67+
scope,
68+
transactionData,
69+
});
70+
71+
fees = nativeTransfers.fees;
72+
nativeFrom = nativeTransfers.from;
73+
nativeTo = nativeTransfers.to;
74+
}
5175

5276
const { from: splFrom, to: splTo } = parseTransactionSplTransfers({
5377
scope,
@@ -59,6 +83,7 @@ export function mapRpcTransaction({
5983

6084
const type = evaluateTransactionType({
6185
address,
86+
status,
6287
from,
6388
to,
6489
});
@@ -84,12 +109,6 @@ export function mapRpcTransaction({
84109
fees = [];
85110
}
86111

87-
const status =
88-
transactionData.meta?.err ||
89-
(transactionData.meta?.status && 'Err' in transactionData.meta.status)
90-
? TransactionStatus.Failed
91-
: TransactionStatus.Confirmed;
92-
93112
const isLegitimate = evaluateTransactionLegitimacy({
94113
address,
95114
from,
@@ -102,6 +121,17 @@ export function mapRpcTransaction({
102121
return null;
103122
}
104123

124+
/**
125+
* We cannot do this filtering earlier because we need to use the incomplete
126+
* mapped `from` and `to` arrays to determine the transaction's legitimacy.
127+
* For failed transactions, we need to first check if they are not spam before
128+
* finally clearing out what we had mapped to `from` and `to`.
129+
*/
130+
if (status === TransactionStatus.Failed) {
131+
from = [];
132+
to = [];
133+
}
134+
105135
return {
106136
id,
107137
account: address,
@@ -121,25 +151,50 @@ export function mapRpcTransaction({
121151
};
122152
}
123153

154+
/**
155+
* Evaluates the status of a transaction based on the transaction data.
156+
* @param transactionData - The transaction data.
157+
* @returns The status of the transaction.
158+
*/
159+
function evaluateTransactionStatus(
160+
transactionData: SolanaTransaction,
161+
): TransactionStatus {
162+
const isError =
163+
transactionData.meta?.err ||
164+
(transactionData.meta?.status && 'Err' in transactionData.meta.status);
165+
166+
const status = isError
167+
? TransactionStatus.Failed
168+
: TransactionStatus.Confirmed;
169+
170+
return status;
171+
}
172+
124173
/**
125174
* Evaluates the type of transaction based on the address and the from and to items.
126175
* @param params - The options object.
127176
* @param params.address - The address of the user.
128177
* @param params.from - The from items.
129178
* @param params.to - The to items.
179+
* @param params.status - The status of the transaction.
130180
* @returns The type of transaction.
131181
*/
132182
function evaluateTransactionType({
133183
address,
184+
status,
134185
from,
135186
to,
136187
}: {
137188
address: Address;
189+
status: TransactionStatus;
138190
from: Transaction['from'];
139191
to: Transaction['to'];
140192
}): TransactionType {
141-
if (from.length === 0 || to.length === 0) {
142-
// if we are unable to determine the type of transaction, we should set it to unknown
193+
if (
194+
from.length === 0 ||
195+
to.length === 0 ||
196+
status === TransactionStatus.Failed
197+
) {
143198
return TransactionType.Unknown;
144199
}
145200

@@ -203,13 +258,13 @@ function passesSOLAmountThresholdCheck({
203258
to,
204259
}: {
205260
address: Address;
206-
to: Transaction['to'];
261+
to: Transaction['from'];
207262
}): boolean {
208263
const { hasReceivedSOL, receivedSOLAmount } = to.reduce(
209264
(acc, toItem) => {
210265
if (
211266
toItem.address === address &&
212-
toItem.asset?.fungible && // Use optional chaining here
267+
toItem.asset?.fungible &&
213268
(toItem.asset.type === KnownCaip19Id.SolMainnet ||
214269
toItem.asset.type === KnownCaip19Id.SolDevnet)
215270
) {

packages/snap/src/core/services/transactions/utils/parseTransactionNativeTransfers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { lamportsToSol } from '../../../utils/conversion';
1313
import { parseTransactionFees } from './parseTransactionFees';
1414

1515
/**
16-
* Parses native SOL token transfers from a transaction.
16+
* Parses native SOL token transfers from a transaction using its balance changes.
1717
* @param options0 - The options object.
1818
* @param options0.scope - The network scope (e.g., Mainnet, Devnet).
1919
* @param options0.transactionData - The raw transaction data containing balance changes.

0 commit comments

Comments
 (0)