-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6 from Once-Upon/benguyen0214/ou-1070-port-heuris…
…tic-and-protocol-contextualizers-not-bridge-ones Add heuristics and protocol contextualizers
- Loading branch information
Showing
63 changed files
with
53,426 additions
and
107 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,5 +52,8 @@ | |
], | ||
"coverageDirectory": "../coverage", | ||
"testEnvironment": "node" | ||
}, | ||
"dependencies": { | ||
"ethers": "^5.6.4" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
export const TOKEN_SWAP_CONTRACTS = [ | ||
'0xe592427a0aece92de3edee1f18e0157c05861564', // Uniswap V3 Router | ||
'0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45', // Uniswap V3router2 | ||
'0x7a250d5630b4cf539739df2c5dacb4c659f2488d', // Uniswap V2 Router2 | ||
'0xef1c6e67703c7bd7107eed8303fbe6ec2554bf6b', // Uniswap Universal Router1 | ||
'0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad', // Uniswap Universal Router2 | ||
'0xd9e1ce17f2641f24ae83637ab66a2cca9c378b9f', // Sushiswap Router | ||
'0x1111111254fb6c44bac0bed2854e76f90643097d', // 1inch Router | ||
'0x881d40237659c251811cec9c364ef91dc08d300c', // Metamask Swap Router | ||
'0xe66b31678d6c16e9ebf358268a790b763c133750', // Coinbase Wallet Swapper | ||
'0x00000000009726632680fb29d3f7a9734e3010e2', // Rainbow Router | ||
'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', | ||
}; | ||
|
||
export const WETH_ADDRESSES = [ | ||
'0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // Ethereum | ||
'0x4200000000000000000000000000000000000006', // Optimism | ||
'0xe5d7c2a44ffddf6b295a15c148167daaaf5cf34f', // Linea | ||
'0x0000000000a39bb272e79075ade125fd351887ac', // Blur | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
const VALID_CHARS = | ||
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890.? '; | ||
|
||
export const hexToString = (str: string) => { | ||
const buf = Buffer.from(str, 'hex'); | ||
return buf.toString('utf8'); | ||
}; | ||
|
||
export const countValidChars = (stringToCount: string) => { | ||
let count = 0; | ||
for (let i = 0; i < stringToCount.length; i++) { | ||
if (VALID_CHARS.indexOf(stringToCount[i]) >= 0) { | ||
count++; | ||
} | ||
} | ||
return count; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { Transaction } from '../types'; | ||
|
||
export function cancelPendingTransactionContextualizer( | ||
transaction: Transaction, | ||
): Transaction { | ||
const isCanceledPendingTransaction = | ||
detectCancelPendingTransaction(transaction); | ||
|
||
if (!isCanceledPendingTransaction) return transaction; | ||
|
||
return generateCancelPendingTransactionContext(transaction); | ||
} | ||
|
||
export function detectCancelPendingTransaction( | ||
transaction: Transaction, | ||
): boolean { | ||
// Check if user cancelled pending transaction | ||
if ( | ||
transaction.to === transaction.from && | ||
(transaction.input === '0x' || transaction.input === '0x0') && | ||
transaction.value === '0' | ||
) { | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
export function generateCancelPendingTransactionContext( | ||
transaction: Transaction, | ||
): Transaction { | ||
transaction.context = { | ||
summaries: { | ||
category: 'DEV', | ||
en: { | ||
title: 'cancelPendingTransaction', | ||
default: '', | ||
}, | ||
}, | ||
}; | ||
|
||
return transaction; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { Transaction } from '../types'; | ||
import { detectContractDeployment } from './contractDeployment'; | ||
import contractDeployed0x88e7d866 from '../test/transactions/contractDeployed-0x88e7d866.json'; | ||
|
||
describe('Contract Deployed', () => { | ||
it('Should detect contract deployment transaction', () => { | ||
const contractDeployed1 = detectContractDeployment( | ||
contractDeployed0x88e7d866 as Transaction, | ||
); | ||
expect(contractDeployed1).toBe(true); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { Transaction } from '../types'; | ||
|
||
export function contractDeploymentContextualizer( | ||
transaction: Transaction, | ||
): Transaction { | ||
const isContractDeployment = detectContractDeployment(transaction); | ||
|
||
if (!isContractDeployment) return transaction; | ||
|
||
return generateContractDeploymentContext(transaction); | ||
} | ||
|
||
export function detectContractDeployment(transaction: Transaction): boolean { | ||
if (transaction.to === null && transaction.receipt?.contractAddress) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
function generateContractDeploymentContext( | ||
transaction: Transaction, | ||
): Transaction { | ||
transaction.context = { | ||
variables: { | ||
deployerAddress: { | ||
type: 'address', | ||
value: transaction.from, | ||
}, | ||
contractAddress: { | ||
type: 'address', | ||
value: transaction.receipt?.contractAddress, | ||
}, | ||
}, | ||
summaries: { | ||
category: 'DEV', | ||
en: { | ||
title: 'Contract Deployed', | ||
default: '[[deployerAddress]] [[deployed]] [[contractAddress]]', | ||
variables: { | ||
deployed: { | ||
type: 'contextAction', | ||
value: 'Deployed', | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
|
||
return transaction; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { Transaction } from '../types'; | ||
import { detectERC1155Purchase } from './erc1155Purchase'; | ||
import erc1155Purchase0x16b2334d from '../test/transactions/erc1155Purchase-0x16b2334d.json'; | ||
|
||
describe('ERC1155 Purchase', () => { | ||
it('Should detect ERC1155 Purchase transaction', () => { | ||
const isERC1155Purchase1 = detectERC1155Purchase( | ||
erc1155Purchase0x16b2334d as Transaction, | ||
); | ||
expect(isERC1155Purchase1).toBe(true); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
import { ethers } from 'ethers'; | ||
import { Asset, Transaction } from '../types'; | ||
|
||
export function erc1155PurchaseContextualizer( | ||
transaction: Transaction, | ||
): Transaction { | ||
const isERC1155Purchase = detectERC1155Purchase(transaction); | ||
if (!isERC1155Purchase) return transaction; | ||
|
||
return generateERC1155PurchaseContext(transaction); | ||
} | ||
|
||
export function detectERC1155Purchase(transaction: Transaction): boolean { | ||
/** | ||
* There is a degree of overlap between the 'detect' and 'generateContext' functions, | ||
* and while this might seem redundant, maintaining the 'detect' function aligns with | ||
* established patterns in our other modules. This consistency is beneficial, | ||
* and it also serves to decouple the logic, thereby simplifying the testing process | ||
*/ | ||
|
||
if (!transaction.netAssetTransfers) return false; | ||
|
||
const addresses = transaction.netAssetTransfers | ||
? Object.keys(transaction.netAssetTransfers) | ||
: []; | ||
|
||
for (const address of addresses) { | ||
const transfers = transaction.netAssetTransfers[address]; | ||
const nftsReceived = transfers.received.filter((t) => t.type === 'erc1155'); | ||
const nftsSent = transfers.sent.filter((t) => t.type === 'erc1155'); | ||
|
||
const ethOrErc20Sent = transfers.sent.filter( | ||
(t) => t.type === 'eth' || t.type === 'erc20', | ||
); | ||
const ethOrErc20Received = transfers.received.filter( | ||
(t) => t.type === 'eth' || t.type === 'erc20', | ||
); | ||
|
||
if (nftsReceived.length > 0 && ethOrErc20Sent.length > 0) { | ||
return true; | ||
} | ||
|
||
if (nftsSent.length > 0 && ethOrErc20Received.length > 0) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
function generateERC1155PurchaseContext(transaction: Transaction): Transaction { | ||
const receivingAddresses: string[] = []; | ||
const receivedNfts: Asset[] = []; | ||
const sentPayments: { type: string; asset: string; value: string }[] = []; | ||
|
||
for (const [address, data] of Object.entries(transaction.netAssetTransfers)) { | ||
const nftTransfers = data.received.filter((t) => t.type === 'erc1155'); | ||
const paymentTransfers = data.sent.filter( | ||
(t) => t.type === 'erc20' || t.type === 'eth', | ||
); | ||
if (nftTransfers.length > 0) { | ||
receivingAddresses.push(address); | ||
nftTransfers.forEach((nft) => receivedNfts.push(nft)); | ||
} | ||
if (paymentTransfers.length > 0) { | ||
paymentTransfers.forEach((payment) => | ||
sentPayments.push({ | ||
type: payment.type, | ||
asset: payment.asset, | ||
value: payment.value, | ||
}), | ||
); | ||
} | ||
} | ||
|
||
const receivedNftContracts = Array.from( | ||
new Set(receivedNfts.map((x) => x.asset)), | ||
); | ||
const totalPayments = Object.values( | ||
sentPayments.reduce((acc, next) => { | ||
acc[next.asset] = { | ||
type: next.type, | ||
asset: next.asset, | ||
value: ethers.BigNumber.from(acc[next.asset]?.value || '0') | ||
.add(next.value) | ||
.toString(), | ||
}; | ||
return acc; | ||
}, {}), | ||
) as { type: 'eth' | 'erc20'; asset: string; value: string }[]; | ||
|
||
transaction.context = { | ||
variables: { | ||
userOrUsers: { | ||
type: receivingAddresses.length > 1 ? 'emphasis' : 'address', | ||
value: | ||
receivingAddresses.length > 1 | ||
? `${receivingAddresses.length} Users` | ||
: receivingAddresses[0], | ||
}, | ||
tokenOrTokens: | ||
receivedNfts.length === 1 | ||
? { | ||
type: 'erc1155', | ||
token: receivedNfts[0].asset, | ||
tokenId: receivedNfts[0].tokenId, | ||
value: receivedNfts[0].value, | ||
} | ||
: receivedNftContracts.length === 1 | ||
? { | ||
type: 'address', | ||
value: receivedNftContracts[0], | ||
} | ||
: { | ||
type: 'emphasis', | ||
value: `${receivedNfts.length} NFTs`, | ||
}, | ||
price: | ||
totalPayments.length > 1 | ||
? { | ||
type: 'emphasis', | ||
value: `${totalPayments.length} Assets`, | ||
} | ||
: totalPayments[0].type === 'eth' | ||
? { | ||
type: 'eth', | ||
value: totalPayments[0].value, | ||
} | ||
: { | ||
type: 'erc20', | ||
token: totalPayments[0].asset, | ||
value: totalPayments[0].value, | ||
}, | ||
}, | ||
summaries: { | ||
category: 'NFT', | ||
en: { | ||
title: 'NFT Purchase', | ||
default: '[[userOrUsers]] [[bought]] [[tokenOrTokens]] for [[price]]', | ||
variables: { | ||
bought: { | ||
type: 'contextAction', | ||
value: 'Bought', | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
|
||
return transaction; | ||
} |
Oops, something went wrong.