diff --git a/src/contextualizers/heuristics/tokenAirdrop/tokenAirdrop.spec.ts b/src/contextualizers/heuristics/tokenAirdrop/tokenAirdrop.spec.ts index b333e187..bff04132 100644 --- a/src/contextualizers/heuristics/tokenAirdrop/tokenAirdrop.spec.ts +++ b/src/contextualizers/heuristics/tokenAirdrop/tokenAirdrop.spec.ts @@ -1,10 +1,11 @@ import { Transaction } from '../../../types'; -import { detect } from './tokenAirdrop'; +import { detect, generate } from './tokenAirdrop'; import tokenAirdrop0x9559fbd9 from '../../test/transactions/tokenAirdrop-0x9559fbd9.json'; import tokenAirdrop0xe2a9a20b from '../../test/transactions/tokenAirdrop-0xe2a9a20b.json'; import tokenAirdrop0xb312ecc2 from '../../test/transactions/tokenAirdrop-0xb312ecc2.json'; import tokenAirdrop0xcce0327b from '../../test/transactions/tokenAirdrop-0xcce0327b.json'; import catchall0xc35c01ac from '../../test/transactions/catchall-0xc35c01ac.json'; +import { containsBigInt, contextSummary } from '../../../helpers/utils'; describe('Token Airdrop', () => { it('Should detect token airdrop transaction', () => { @@ -29,6 +30,44 @@ describe('Token Airdrop', () => { expect(tokenAirdrop4).toBe(true); }); + it('should generate context for tokenAirdrop', () => { + const transaction1 = generate( + tokenAirdrop0x9559fbd9 as unknown as Transaction, + ); + expect(transaction1.context?.summaries?.en.title).toBe('Token Airdrop'); + expect(contextSummary(transaction1.context)).toBe( + '265 users RECEIVED_AIRDROP 0x1792a96e5668ad7c167ab804a100ce42395ce54d', + ); + expect(containsBigInt(transaction1.context)).toBe(false); + + const transaction2 = generate( + tokenAirdrop0xe2a9a20b as unknown as Transaction, + ); + expect(transaction2.context?.summaries?.en.title).toBe('Token Airdrop'); + expect(contextSummary(transaction2.context)).toBe( + '172 users RECEIVED_AIRDROP 0x1792a96e5668ad7c167ab804a100ce42395ce54d', + ); + expect(containsBigInt(transaction2.context)).toBe(false); + + const transaction3 = generate( + tokenAirdrop0xb312ecc2 as unknown as Transaction, + ); + expect(transaction3.context?.summaries?.en.title).toBe('Token Airdrop'); + expect(contextSummary(transaction3.context)).toBe( + '182 users RECEIVED_AIRDROP 0xc9b82badf090551a9c5ff6010bbdfb39587bd007', + ); + expect(containsBigInt(transaction3.context)).toBe(false); + + const transaction4 = generate( + tokenAirdrop0xcce0327b as unknown as Transaction, + ); + expect(transaction4.context?.summaries?.en.title).toBe('Token Airdrop'); + expect(contextSummary(transaction4.context)).toBe( + '51 users RECEIVED_AIRDROP 150 assets from 0xbcac490d0447b41e77fa7d5ec701659dfc348636', + ); + expect(containsBigInt(transaction4.context)).toBe(false); + }); + it('Should not detect token airdrop transaction', () => { const tokenAirdrop1 = detect(catchall0xc35c01ac as unknown as Transaction); expect(tokenAirdrop1).toBe(false); diff --git a/src/contextualizers/heuristics/tokenAirdrop/tokenAirdrop.ts b/src/contextualizers/heuristics/tokenAirdrop/tokenAirdrop.ts index 948f51c1..dda199f8 100644 --- a/src/contextualizers/heuristics/tokenAirdrop/tokenAirdrop.ts +++ b/src/contextualizers/heuristics/tokenAirdrop/tokenAirdrop.ts @@ -1,15 +1,13 @@ import { Transaction, AssetType, - ERC20Asset, - ERC1155Asset, - ERC721Asset, ERC1155AssetTransfer, ERC20AssetTransfer, ERC721AssetTransfer, HeuristicContextActionEnum, } from '../../../types'; import { KNOWN_ADDRESSES } from '../../../helpers/constants'; +import { zeroAddress } from 'viem'; const AIRDROP_THRESHOLD = 10; @@ -57,27 +55,6 @@ export function detect(transaction: Transaction): boolean { return false; } } - // check if all assets sent are the same contract - // const assetsSent = transaction.netAssetTransfers[ - // sendAddresses[0] - // ].sent.filter((ele) => ele.type !== AssetType.ETH) as ( - // | ERC20Asset - // | ERC1155Asset - // | ERC721Asset - // )[]; - // if ( - // transaction.hash === - // '0xcce0b3924cf1be66ad19fbaa255ab8a3d65cd6ad42e1fde3486f23b30293327b' - // ) { - // console.log( - // 'assetsSent', - // assetsSent[0], - // assetsSent.filter((ele) => ele.contract !== assetsSent[0].contract), - // ); - // } - // if (!assetsSent.every((ele) => ele.contract === assetsSent[0].contract)) { - // return false; - // } // check if there are more than AIRDROP_THRESHOLD number of receivers if ( Object.keys(transaction.netAssetTransfers).length - 1 < @@ -89,7 +66,7 @@ export function detect(transaction: Transaction): boolean { return true; } -function generate(transaction: Transaction): Transaction { +export function generate(transaction: Transaction): Transaction { if (!transaction.assetTransfers) { return transaction; } @@ -101,7 +78,13 @@ function generate(transaction: Transaction): Transaction { x.type === AssetType.ERC20, ) as (ERC1155AssetTransfer | ERC20AssetTransfer | ERC721AssetTransfer)[]; const assets = Array.from(new Set(tokenTransfers.map((x) => x.contract))); - const senders = Array.from(new Set(tokenTransfers.map((x) => x.from))); + const senders = Array.from( + new Set( + tokenTransfers + .map((x) => x.from) + .filter((address) => address != zeroAddress), + ), + ); const recipients = Array.from(new Set(tokenTransfers.map((x) => x.to))); const firstAssetTransfer =