Skip to content

Commit

Permalink
Merge pull request #38 from Once-Upon/benguyen0214/ou-1152-token-airdrop
Browse files Browse the repository at this point in the history
Token airdrop
  • Loading branch information
pcowgill authored Dec 5, 2023
2 parents cdb81fa + 8703f01 commit 3e58764
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 27 deletions.
2 changes: 0 additions & 2 deletions src/helpers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ export const TOKEN_SWAP_CONTRACTS = [
'0xdef1c0ded9bec7f1a1670819833240f027b25eff', // 0x Exchange Proxy. NOTE - This is both an erc20 swap and erc721 swap contract. This address is in both contract lists.
];

export const AIRDROP_THRESHOLD = 10;

export const KNOWN_ADDRESSES = {
NULL: '0x0000000000000000000000000000000000000000',
WETH: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
Expand Down
8 changes: 1 addition & 7 deletions src/heuristics/tokenAirdrop/tokenAirdrop.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Transaction } from '../../types';
import { detectTokenAirdrop } from './tokenAirdrop';
import tokenAirdrop0x9559fbd9 from '../../test/transactions/tokenAirdrop-0x9559fbd9.json';
import tokenAirdrop0xe2a9a20b from '../../test/transactions/tokenAirdrop-0xe2a9a20b.json';
import tokenAirdrop0x4c7af9a2 from '../../test/transactions/tokenAirdrop-0x4c7af9a2.json';
import tokenAirdrop0xb312ecc2 from '../../test/transactions/tokenAirdrop-0xb312ecc2.json';
import catchall0xc35c01ac from '../../test/transactions/catchall-0xc35c01ac.json';

Expand All @@ -19,14 +18,9 @@ describe('Token Airdrop', () => {
expect(tokenAirdrop2).toBe(true);

const tokenAirdrop3 = detectTokenAirdrop(
tokenAirdrop0x4c7af9a2 as Transaction,
);
expect(tokenAirdrop3).toBe(true);

const tokenAirdrop4 = detectTokenAirdrop(
tokenAirdrop0xb312ecc2 as Transaction,
);
expect(tokenAirdrop4).toBe(true);
expect(tokenAirdrop3).toBe(true);
});

it('Should not detect token airdrop transaction', () => {
Expand Down
56 changes: 38 additions & 18 deletions src/heuristics/tokenAirdrop/tokenAirdrop.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Transaction } from '../../types';
import { AIRDROP_THRESHOLD, KNOWN_ADDRESSES } from '../../helpers/constants';
import { KNOWN_ADDRESSES } from '../../helpers/constants';

const AIRDROP_THRESHOLD = 10;

export function tokenAirdropContextualizer(
transaction: Transaction,
Expand All @@ -10,6 +12,13 @@ export function tokenAirdropContextualizer(
return generateTokenAirdropContext(transaction);
}

/**
* Detection criteria
*
* Only 1 address in netAssetTransfers is sending, all other addresses are receiving. It's ok if the only sending address is the null (airdrop via mints)
* All assets sent are the same asset (contract address)
* There are more than AIRDROP_THRESHOLD number of receivers. A receiver can receive more than one airdrop.
*/
export function detectTokenAirdrop(transaction: Transaction): boolean {
/**
* There is a degree of overlap between the 'detect' and 'generateContext' functions,
Expand All @@ -21,28 +30,39 @@ export function detectTokenAirdrop(transaction: Transaction): boolean {
return false;
}

const airdropTracker: Record<
string,
{ amount: number; assetTransfers: Set<any> }
> = {};

for (const assetTransfer of transaction.assetTransfers) {
const transferKey = `${assetTransfer.from}-${assetTransfer.asset}-${assetTransfer.type}`;
if (!airdropTracker[transferKey]) {
airdropTracker[transferKey] = {
amount: 0,
assetTransfers: new Set(),
};
// check if only 1 address is sending
const sendAddresses = Object.keys(transaction.netAssetTransfers).filter(
(address) => transaction.netAssetTransfers[address].sent.length > 0,
);
if (sendAddresses.length > 1) {
return false;
}
// check if all other addresses are receiving
for (const address in transaction.netAssetTransfers) {
if (address === sendAddresses[0]) {
continue;
}
airdropTracker[transferKey].amount++;
airdropTracker[transferKey].assetTransfers.add(assetTransfer);

if (airdropTracker[transferKey].amount > AIRDROP_THRESHOLD) {
return true;
const sent = transaction.netAssetTransfers[address]?.sent;
const received = transaction.netAssetTransfers[address]?.received;
if (sent?.length || !received?.length) {
return false;
}
}
// check if all assets sent are the same contract
const assetsSent = transaction.netAssetTransfers[sendAddresses[0]].sent;
if (!assetsSent.every((ele) => ele.asset === assetsSent[0].asset)) {
return false;
}
// check if there are more than AIRDROP_THRESHOLD number of receivers
if (
Object.keys(transaction.netAssetTransfers).length - 1 <
AIRDROP_THRESHOLD
) {
return false;
}

return false;
return true;
}

function generateTokenAirdropContext(transaction: Transaction): Transaction {
Expand Down

0 comments on commit 3e58764

Please sign in to comment.