From 5e2ea88dd323f9d267a9a9f971a0d58db981096f Mon Sep 17 00:00:00 2001 From: ponyjackal Date: Fri, 26 Jan 2024 06:02:31 -0800 Subject: [PATCH] feat: update common transformations --- src/transformers/_common/contractABI.ts | 5 +- .../_common/contractERC1155Detection.ts | 4 +- .../_common/contractERC20Detection.ts | 4 +- .../_common/contractERC721Detection.ts | 4 +- .../_common/contractERC777Detection.ts | 4 +- .../_common/contractGnosisSafeDetection.ts | 22 +-- .../_common/contractGovernanceDetection.ts | 22 +-- .../_common/contractSelfDestructed.ts | 28 ++-- src/transformers/_common/contractTimestamp.ts | 4 +- .../_common/transactionAssetTransfers.ts | 90 +++++++----- .../_common/transactionContractsCreated.ts | 24 ++- .../_common/transactionDelegateCalls.ts | 6 +- .../transactionDerivativesNeighbors.ts | 21 ++- src/transformers/_common/transactionErrors.ts | 2 +- .../_common/transactionNetAssetTransfers.ts | 82 +++++++---- src/transformers/ethereum/blockEthPrice.ts | 139 ++++++++++++++---- src/types/abi.ts | 24 +++ src/types/{assetTransfer.ts => asset.ts} | 0 src/types/contract.ts | 9 +- src/types/index.ts | 5 +- src/types/neighbor.ts | 7 + src/types/netAssetTransfer.ts | 16 -- src/types/shared.ts | 2 +- src/types/transaction.ts | 3 +- 24 files changed, 354 insertions(+), 173 deletions(-) create mode 100644 src/types/abi.ts rename src/types/{assetTransfer.ts => asset.ts} (100%) create mode 100644 src/types/neighbor.ts delete mode 100644 src/types/netAssetTransfer.ts diff --git a/src/transformers/_common/contractABI.ts b/src/transformers/_common/contractABI.ts index 2ece9c7..0a35065 100644 --- a/src/transformers/_common/contractABI.ts +++ b/src/transformers/_common/contractABI.ts @@ -27,9 +27,8 @@ export function transform(block: RawBlock): TransactionContract[] { isGnosisSafe: false, whatsAbiSelectors: [], isProxy: false, - whatsAbiAbi: { - type: 'constructor', - }, + whatsAbiAbi: [], + tokenMetadata: { tokenStandard: '' }, }; } // Get just the callable selectors diff --git a/src/transformers/_common/contractERC1155Detection.ts b/src/transformers/_common/contractERC1155Detection.ts index a416c8f..ec37837 100644 --- a/src/transformers/_common/contractERC1155Detection.ts +++ b/src/transformers/_common/contractERC1155Detection.ts @@ -1,5 +1,5 @@ -import type { Contract, RawBlock, TransactionContract } from '../types'; -import { bytecodeIsERC1155 } from '../helpers/utils'; +import type { Contract, RawBlock, TransactionContract } from '../../types'; +import { bytecodeIsERC1155 } from '../../helpers/utils'; export function transform(block: RawBlock): TransactionContract[] { const results: { hash: string; contracts: Contract[] }[] = []; diff --git a/src/transformers/_common/contractERC20Detection.ts b/src/transformers/_common/contractERC20Detection.ts index 86a08d8..2c73239 100644 --- a/src/transformers/_common/contractERC20Detection.ts +++ b/src/transformers/_common/contractERC20Detection.ts @@ -1,5 +1,5 @@ -import type { Contract, RawBlock, TransactionContract } from '../types'; -import { bytecodeIsERC20 } from '../helpers/utils'; +import type { Contract, RawBlock, TransactionContract } from '../../types'; +import { bytecodeIsERC20 } from '../../helpers/utils'; export function transform(block: RawBlock): TransactionContract[] { const results: { hash: string; contracts: Contract[] }[] = []; diff --git a/src/transformers/_common/contractERC721Detection.ts b/src/transformers/_common/contractERC721Detection.ts index 6011301..85ae4fa 100644 --- a/src/transformers/_common/contractERC721Detection.ts +++ b/src/transformers/_common/contractERC721Detection.ts @@ -1,5 +1,5 @@ -import type { Contract, RawBlock, TransactionContract } from '../types'; -import { bytecodeIsERC721 } from '../helpers/utils'; +import type { Contract, RawBlock, TransactionContract } from '../../types'; +import { bytecodeIsERC721 } from '../../helpers/utils'; export function transform(block: RawBlock): TransactionContract[] { const results: { hash: string; contracts: Contract[] }[] = []; diff --git a/src/transformers/_common/contractERC777Detection.ts b/src/transformers/_common/contractERC777Detection.ts index dc4ba9b..c6bf320 100644 --- a/src/transformers/_common/contractERC777Detection.ts +++ b/src/transformers/_common/contractERC777Detection.ts @@ -1,5 +1,5 @@ -import type { Contract, RawBlock, TransactionContract } from '../types'; -import { bytecodeIsERC777 } from '../helpers/utils'; +import type { Contract, RawBlock, TransactionContract } from '../../types'; +import { bytecodeIsERC777 } from '../../helpers/utils'; export function transform(block: RawBlock): TransactionContract[] { const results: { hash: string; contracts: Contract[] }[] = []; diff --git a/src/transformers/_common/contractGnosisSafeDetection.ts b/src/transformers/_common/contractGnosisSafeDetection.ts index baebb22..7a328c0 100644 --- a/src/transformers/_common/contractGnosisSafeDetection.ts +++ b/src/transformers/_common/contractGnosisSafeDetection.ts @@ -1,18 +1,20 @@ -import type { RawBlock, Contract, TransactionContract } from '../types'; -import { bytecodeIsGnosisSafe } from '../helpers/utils'; +import type { RawBlock, Contract, TransactionContract } from '../../types'; +import { bytecodeIsGnosisSafe } from '../../helpers/utils'; export function transform(block: RawBlock): TransactionContract[] { const result: TransactionContract[] = block.transactions - .filter((tx) => tx.contracts?.length > 0) + .filter((tx) => tx.contracts && tx.contracts.length > 0) .map((tx) => { - const contracts: Contract[] = tx.contracts.map((txContract) => { - const contract: Contract = { ...txContract }; - if (bytecodeIsGnosisSafe(txContract.bytecode)) { - contract.metadata.isGnosisSafe = true; - } + const contracts: Contract[] = tx.contracts + ? tx.contracts.map((txContract) => { + const contract: Contract = { ...txContract }; + if (bytecodeIsGnosisSafe(txContract.bytecode)) { + contract.metadata.isGnosisSafe = true; + } - return contract; - }); + return contract; + }) + : []; return { hash: tx.hash, contracts }; }); diff --git a/src/transformers/_common/contractGovernanceDetection.ts b/src/transformers/_common/contractGovernanceDetection.ts index 38d1889..d8ed0c0 100644 --- a/src/transformers/_common/contractGovernanceDetection.ts +++ b/src/transformers/_common/contractGovernanceDetection.ts @@ -1,18 +1,20 @@ -import type { RawBlock, Contract, TransactionContract } from '../types'; -import { bytecodeIsIGovernor } from '../helpers/utils'; +import type { RawBlock, Contract, TransactionContract } from '../../types'; +import { bytecodeIsIGovernor } from '../../helpers/utils'; export function transform(block: RawBlock): TransactionContract[] { const result: TransactionContract[] = block.transactions - .filter((tx) => tx.contracts?.length > 0) + .filter((tx) => tx.contracts && tx.contracts.length > 0) .map((tx) => { - const contracts: Contract[] = tx.contracts.map((txContract) => { - const contract: Contract = { ...txContract }; - if (bytecodeIsIGovernor(txContract.bytecode)) { - contract.metadata.isGenericGovernance = true; - } + const contracts: Contract[] = tx.contracts + ? tx.contracts.map((txContract) => { + const contract: Contract = { ...txContract }; + if (bytecodeIsIGovernor(txContract.bytecode)) { + contract.metadata.isGenericGovernance = true; + } - return contract; - }); + return contract; + }) + : []; return { hash: tx.hash, contracts }; }); diff --git a/src/transformers/_common/contractSelfDestructed.ts b/src/transformers/_common/contractSelfDestructed.ts index fd349b8..48e7130 100644 --- a/src/transformers/_common/contractSelfDestructed.ts +++ b/src/transformers/_common/contractSelfDestructed.ts @@ -1,16 +1,22 @@ -import { toBigNumber } from '../helpers/utils'; -import type { RawBlock } from '../types'; +import type { RawBlock, Contract } from '../../types'; -type ContractSelfDestructed = { address: string; refundAddress: string; balance: string }; +type ContractSelfDestructed = { + address: string; + refundAddress: string; + balance: string; +}; + +type ResultType = { + contractsCreated: Contract[]; + contractSelfDestructed: ContractSelfDestructed[]; + hash: string; +}; export function transform(block: RawBlock) { - const results: { - contractSelfDestructed: ContractSelfDestructed[]; - hash: string; - }[] = []; + const results: ResultType[] = []; for (const tx of block.transactions) { - const result = { + const result: ResultType = { contractsCreated: [], contractSelfDestructed: [], hash: tx.hash, @@ -22,8 +28,10 @@ export function transform(block: RawBlock) { if (trace.type === 'suicide' && trace.action) { result.contractSelfDestructed.push({ address: trace.action.address, - balance: toBigNumber(trace.action.balance).toString(), - refundAddress: trace.action.refundAddress, + balance: trace.action.balance + ? BigInt(trace.action.balance).toString() + : '0', + refundAddress: trace.action.refundAddress ?? '', }); } } diff --git a/src/transformers/_common/contractTimestamp.ts b/src/transformers/_common/contractTimestamp.ts index 0320ad2..d3deb96 100644 --- a/src/transformers/_common/contractTimestamp.ts +++ b/src/transformers/_common/contractTimestamp.ts @@ -1,10 +1,12 @@ -import type { RawBlock, TransactionContract } from '../types'; +import type { RawBlock, TransactionContract } from '../../types'; export function transform(block: RawBlock): TransactionContract[] { const isoTimestamp = new Date(block.timestamp * 1000).toISOString(); const result: TransactionContract[] = []; for (const tx of block.transactions) { + if (!tx.contracts) continue; + const contracts = tx.contracts.map((txContract) => { txContract.timestamp = block.timestamp; txContract.isoTimestamp = isoTimestamp; diff --git a/src/transformers/_common/transactionAssetTransfers.ts b/src/transformers/_common/transactionAssetTransfers.ts index 788007e..f1160f2 100644 --- a/src/transformers/_common/transactionAssetTransfers.ts +++ b/src/transformers/_common/transactionAssetTransfers.ts @@ -1,9 +1,12 @@ -import BigNumber from 'bignumber.js'; -import Web3 from 'web3'; - -import { decodeEVMAddress, toBigNumber } from '../helpers/utils'; -import type { AssetTransfer, RawBlock, RawTransaction } from '../types'; +import { decodeEVMAddress } from '../../helpers/utils'; +import { + AssetType, + type AssetTransfer, + type RawBlock, + type RawTransaction, +} from '../../types'; import { KNOWN_ADDRESSES } from '../../helpers/constants'; +import { decodeAbiParameters, Hex } from 'viem'; // 1. pull out token transfers from logs // 2. pull out ETH transfers from traces (this covers tx.value transfers) @@ -17,22 +20,22 @@ const TRANSFER_SIGNATURES = { ERC721: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value) - ERC1155_SINGLE: '0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62', + ERC1155_SINGLE: + '0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62', // event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values) - ERC1155_BATCH: '0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb', + ERC1155_BATCH: + '0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb', // event Deposit(address indexed dst, uint wad) - WETH_DEPOSIT_ERC20: '0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c', + WETH_DEPOSIT_ERC20: + '0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c', // event Withdrawal(address indexed src, uint wad) - WETH_WITHDRAW_ERC20: '0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65', + WETH_WITHDRAW_ERC20: + '0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65', }; -// @NOTE: we do *not* need access to an RPC here -// we just need an instance of Web3 in order to decode some log params -const web3 = new Web3(); - function getTokenTransfers(tx: RawTransaction) { const txAssetTransfers: AssetTransfer[] = []; @@ -49,29 +52,29 @@ function getTokenTransfers(tx: RawTransaction) { asset: log.address, from: decodeEVMAddress(log.topics[1]), to: decodeEVMAddress(log.topics[2]), - tokenId: toBigNumber(log.topics[3]).toString(), - type: 'erc721', + tokenId: BigInt(log.topics[3]).toString(), + type: AssetType.ERC721, }); } else { txAssetTransfers.push({ asset: log.address, from: decodeEVMAddress(log.topics[1]), to: decodeEVMAddress(log.topics[2]), - value: toBigNumber(log.data).toString(), - type: 'erc20', + value: BigInt(log.data).toString(), + type: AssetType.ERC20, }); } continue; } case TRANSFER_SIGNATURES.ERC1155_SINGLE: { - const { tokenId, value } = web3.eth.abi.decodeParameters( + const [tokenId, value] = decodeAbiParameters( [ { name: 'tokenId', type: 'uint256' }, { name: 'value', type: 'uint256' }, ], - log.data - ) as { tokenId: BigNumber; value: BigNumber }; + log.data as Hex, + ); txAssetTransfers.push({ asset: log.address, @@ -79,19 +82,19 @@ function getTokenTransfers(tx: RawTransaction) { to: decodeEVMAddress(log.topics[3]), tokenId: tokenId.toString(), value: value.toString(), - type: 'erc1155', + type: AssetType.ERC1155, }); continue; } case TRANSFER_SIGNATURES.ERC1155_BATCH: { - const { tokenIds, values } = web3.eth.abi.decodeParameters( + const [tokenIds, values] = decodeAbiParameters( [ { name: 'tokenIds', type: 'uint256[]' }, { name: 'values', type: 'uint256[]' }, ], - log.data - ) as { tokenIds: BigNumber[]; values: BigNumber[] }; + log.data as Hex, + ); for (let tokenIdx = 0; tokenIdx < tokenIds.length; tokenIdx += 1) { txAssetTransfers.push({ @@ -100,7 +103,7 @@ function getTokenTransfers(tx: RawTransaction) { to: decodeEVMAddress(log.topics[3]), tokenId: tokenIds[tokenIdx].toString(), value: values[tokenIdx].toString(), - type: 'erc1155', + type: AssetType.ERC1155, }); } continue; @@ -115,8 +118,8 @@ function getTokenTransfers(tx: RawTransaction) { asset: log.address, from: KNOWN_ADDRESSES.NULL, to: decodeEVMAddress(log.topics[1]), - value: toBigNumber(log.data).toString(), - type: 'erc20', + value: BigInt(log.data).toString(), + type: AssetType.ERC20, }); continue; } @@ -130,8 +133,8 @@ function getTokenTransfers(tx: RawTransaction) { asset: log.address, from: decodeEVMAddress(log.topics[1]), to: KNOWN_ADDRESSES.NULL, - value: toBigNumber(log.data).toString(), - type: 'erc20', + value: BigInt(log.data).toString(), + type: AssetType.ERC20, }); continue; } @@ -159,10 +162,12 @@ export function transform(block: RawBlock) { // then group by contract const tokenTransfersByContract: Record = {}; for (const transfer of tokenTransfers) { - if (!tokenTransfersByContract[transfer.asset]) { - tokenTransfersByContract[transfer.asset] = []; + if (transfer.type !== AssetType.ETH) { + if (!tokenTransfersByContract[transfer.asset]) { + tokenTransfersByContract[transfer.asset] = []; + } + tokenTransfersByContract[transfer.asset].push(transfer); } - tokenTransfersByContract[transfer.asset].push(transfer); } // now prepare a final set of *all* asset transfers (including ETH) @@ -173,21 +178,28 @@ export function transform(block: RawBlock) { // check for ETH transfers if (trace.action.callType !== 'delegatecall') { // track contract suicides - if (trace.type === 'suicide' && trace.action.balance && trace.action.balance !== '0x0') { + if ( + trace.type === 'suicide' && + trace.action.balance && + trace.action.balance !== '0x0' + ) { assetTransfers.push({ from: trace.action.address, - to: trace.action.refundAddress, - type: 'eth', - value: toBigNumber(trace.action.balance).toString(), + to: trace.action.refundAddress ?? '', + type: AssetType.ETH, + value: BigInt(trace.action.balance).toString(), }); } // otherwise track ETH transfers else if (trace.action.value && trace.action.value !== '0x0') { assetTransfers.push({ from: trace.action.from, - to: trace.type === 'create' ? trace.result.address : trace.action.to, - type: 'eth', - value: toBigNumber(trace.action.value).toString(), + to: + trace.type === 'create' + ? trace.result.address ?? '' + : trace.action.to ?? '', + type: AssetType.ETH, + value: BigInt(trace.action.value).toString(), }); } } diff --git a/src/transformers/_common/transactionContractsCreated.ts b/src/transformers/_common/transactionContractsCreated.ts index 8d13ceb..f8f8b9c 100644 --- a/src/transformers/_common/transactionContractsCreated.ts +++ b/src/transformers/_common/transactionContractsCreated.ts @@ -1,14 +1,15 @@ import { keccak256 } from 'web3-utils'; -import type { RawBlock, Contract } from '../types'; +import type { RawBlock, Contract } from '../../types'; +type ResultType = { + contracts: Contract[]; + hash: string; +}; export function transform(block: RawBlock) { - const results: { - contracts: Contract[]; - hash: string; - }[] = []; + const results: ResultType[] = []; for (const tx of block.transactions) { - const result = { + const result: ResultType = { contracts: [], hash: tx.hash, }; @@ -16,16 +17,23 @@ export function transform(block: RawBlock) { for (let i = 0; i < tx.traces.length; i += 1) { const trace = tx.traces[i]; - if ((trace.type === 'create' || trace.type == 'create2') && trace.result) { + if ( + (trace.type === 'create' || trace.type == 'create2') && + trace.result + ) { // Basic Create Contract Details const isoTimestamp = new Date(block.timestamp * 1000).toISOString(); + if (!trace.result.address) { + continue; + } const contract: Contract = { deployer: tx.from, directDeployer: trace.action.from, address: trace.result.address, bytecode: trace.result.code, - fingerprint: trace.result.code !== '0x0' ? keccak256(trace.result.code) : '', // TODO - need a way to avoid validation errors + fingerprint: + trace.result.code !== '0x0' ? keccak256(trace.result.code) : '', // TODO - need a way to avoid validation errors gas: trace.action.gas, // TOOD - do we convert with bignumber here? gasUsed: trace.result.gasUsed, // TODO - do we convert with bignumber here? blockNumber: block.number, diff --git a/src/transformers/_common/transactionDelegateCalls.ts b/src/transformers/_common/transactionDelegateCalls.ts index 1dd4e35..84e99f6 100644 --- a/src/transformers/_common/transactionDelegateCalls.ts +++ b/src/transformers/_common/transactionDelegateCalls.ts @@ -1,10 +1,12 @@ -import type { RawBlock, RawTrace } from '../types'; +import type { RawBlock, RawTrace } from '../../types'; export function transform(block: RawBlock) { const results: { hash: string; delegateCalls: RawTrace[] }[] = []; for (const tx of block.transactions) { - const delegateCalls = tx.traces.filter((t) => t.action.callType === 'delegatecall'); + const delegateCalls = tx.traces.filter( + (t) => t.action.callType === 'delegatecall', + ); if (delegateCalls.length > 0) { results.push({ hash: tx.hash, diff --git a/src/transformers/_common/transactionDerivativesNeighbors.ts b/src/transformers/_common/transactionDerivativesNeighbors.ts index f53cf6a..3d9dc06 100644 --- a/src/transformers/_common/transactionDerivativesNeighbors.ts +++ b/src/transformers/_common/transactionDerivativesNeighbors.ts @@ -1,4 +1,4 @@ -import type { RawBlock, Neighbor } from '../types'; +import type { RawBlock, Neighbor } from '../../types'; export function transform(block: RawBlock): Neighbor[] { const result: Neighbor[] = []; @@ -6,13 +6,24 @@ export function transform(block: RawBlock): Neighbor[] { const tx = block.transactions[i]; if (tx.assetTransfers) { const fromAddresses: Set = new Set( - (tx.assetTransfers as { from: string }[]).map((assetTransfer) => assetTransfer.from) + (tx.assetTransfers as { from: string }[]).map( + (assetTransfer) => assetTransfer.from, + ), ); const toAddresses: Set = new Set( - (tx.assetTransfers as { to: string }[]).map((assetTransfer) => assetTransfer.to) + (tx.assetTransfers as { to: string }[]).map( + (assetTransfer) => assetTransfer.to, + ), ); - if (fromAddresses.size === 1 && fromAddresses.has(tx.from) && toAddresses.size === 1) { - result.push({ hash: tx.hash, neighbor: { address: tx.from, neighbor: [...toAddresses][0] } }); + if ( + fromAddresses.size === 1 && + fromAddresses.has(tx.from) && + toAddresses.size === 1 + ) { + result.push({ + hash: tx.hash, + neighbor: { address: tx.from, neighbor: [...toAddresses][0] }, + }); } } } diff --git a/src/transformers/_common/transactionErrors.ts b/src/transformers/_common/transactionErrors.ts index 93b9791..2f7fcd0 100644 --- a/src/transformers/_common/transactionErrors.ts +++ b/src/transformers/_common/transactionErrors.ts @@ -1,4 +1,4 @@ -import type { RawBlock } from '../types'; +import type { RawBlock } from '../../types'; export function transform(block: RawBlock) { const allTxErrors: { hash: string; errors: string[] }[] = []; diff --git a/src/transformers/_common/transactionNetAssetTransfers.ts b/src/transformers/_common/transactionNetAssetTransfers.ts index fc44ff7..1a1d531 100644 --- a/src/transformers/_common/transactionNetAssetTransfers.ts +++ b/src/transformers/_common/transactionNetAssetTransfers.ts @@ -1,7 +1,11 @@ -import type { - RawBlock, - NetAssetTransfer, - NetAssetTransfers, +import { + type RawBlock, + type NetAssetTransfers, + type Asset, + AssetType, + ERC1155Asset, + ERC20Asset, + ETHAsset, } from '../../types'; export function transform(block: RawBlock) { @@ -13,7 +17,7 @@ export function transform(block: RawBlock) { continue; } - const assetsById: Record = {}; + const assetsById: Record = {}; const netAssetsByAddress: Record> = {}; for (const txfer of assetTransfers) { @@ -21,7 +25,8 @@ export function transform(block: RawBlock) { continue; } - let asset: NetAssetTransfer; + let asset: Asset | undefined = undefined; + let assetValue = BigInt(0); switch (txfer.type) { case 'erc721': asset = { @@ -29,8 +34,8 @@ export function transform(block: RawBlock) { id: `${txfer.asset}-${txfer.tokenId}`, tokenId: txfer.tokenId, type: txfer.type, - value: '1', }; + assetValue = BigInt(1); break; case 'erc1155': asset = { @@ -40,6 +45,7 @@ export function transform(block: RawBlock) { type: txfer.type, value: txfer.value, }; + assetValue = BigInt(txfer.value); break; case 'erc20': asset = { @@ -48,18 +54,19 @@ export function transform(block: RawBlock) { type: txfer.type, value: txfer.value, }; + assetValue = BigInt(txfer.value); break; case 'eth': asset = { - asset: 'eth', id: 'eth', type: txfer.type, value: txfer.value, }; + assetValue = BigInt(txfer.value); break; } - if (!asset.id || !asset.value || asset.value === '0') { + if (!asset?.id) { continue; } @@ -70,40 +77,63 @@ export function transform(block: RawBlock) { netAssetsByAddress[txfer.to] = {}; } if (!netAssetsByAddress[txfer.from][asset.id]) { - netAssetsByAddress[txfer.from][asset.id] = toBigNumber(0); + netAssetsByAddress[txfer.from][asset.id] = BigInt(0); } if (!netAssetsByAddress[txfer.to][asset.id]) { - netAssetsByAddress[txfer.to][asset.id] = toBigNumber(0); + netAssetsByAddress[txfer.to][asset.id] = BigInt(0); } assetsById[asset.id] = asset; - netAssetsByAddress[txfer.from][asset.id] = netAssetsByAddress[txfer.from][ - asset.id - ].minus(asset.value); - netAssetsByAddress[txfer.to][asset.id] = netAssetsByAddress[txfer.to][ - asset.id - ].plus(asset.value); + netAssetsByAddress[txfer.from][asset.id] = + netAssetsByAddress[txfer.from][asset.id] - BigInt(assetValue); + netAssetsByAddress[txfer.to][asset.id] = + netAssetsByAddress[txfer.to][asset.id] + BigInt(assetValue); } const netAssetTransfers: NetAssetTransfers = {}; for (const [address, assets] of Object.entries(netAssetsByAddress)) { for (const [id, value] of Object.entries(assets)) { - if (value.eq(0)) { - continue; - } if (!netAssetTransfers[address]) { netAssetTransfers[address] = { received: [], sent: [] }; } - if (value.lt(0)) { + + const type = assetsById[id].type; + if (type === AssetType.ERC721) { netAssetTransfers[address].sent.push({ ...assetsById[id], - value: value.multipliedBy(-1).toString(), }); } else { - netAssetTransfers[address].received.push({ - ...assetsById[id], - value: value.toString(), - }); + if (value === BigInt(0)) { + continue; + } + + let asset: Asset | undefined = undefined; + switch (assetsById[id].type) { + case AssetType.ERC1155: + asset = assetsById[id] as ERC1155Asset; + asset.value = + value > BigInt(0) + ? value.toString() + : (value * BigInt(-1)).toString(); + netAssetTransfers[address].received.push(asset); + break; + case AssetType.ERC20: + asset = assetsById[id] as ERC20Asset; + asset.value = + value > BigInt(0) + ? value.toString() + : (value * BigInt(-1)).toString(); + netAssetTransfers[address].received.push(asset); + break; + case AssetType.ETH: + asset = assetsById[id] as ETHAsset; + asset.value = + value > BigInt(0) + ? value.toString() + : (value * BigInt(-1)).toString(); + netAssetTransfers[address].received.push(asset); + break; + } } } } diff --git a/src/transformers/ethereum/blockEthPrice.ts b/src/transformers/ethereum/blockEthPrice.ts index f43a303..de93374 100644 --- a/src/transformers/ethereum/blockEthPrice.ts +++ b/src/transformers/ethereum/blockEthPrice.ts @@ -33,15 +33,26 @@ const uniSwapV3ETHPools = new Set([ ]); export function transform(block: RawBlock) { - const result: { number: number; price?: number; priceSource?: 'ETHERSCAN' | 'UNISWAP' } = { number: block.number }; + const result: { + number: number; + price?: number; + priceSource?: 'ETHERSCAN' | 'UNISWAP'; + } = { number: block.number }; const date = new Date(block.timestamp * 1000); const swaps: { price: BigNumber; volume: BigNumber }[] = []; // if we're pre UniSwap usage (~ February 2019), then use Etherscan prices if (block.number < 7207017) { // only track the price for the block right at midnight - if (date.getUTCHours() === 0 && date.getUTCMinutes() === 0 && date.getUTCSeconds() < 30) { - result.price = etherscanPriceByDay[`${date.getUTCFullYear()}-${date.getUTCMonth() + 1}-${date.getUTCDate()}`]; + if ( + date.getUTCHours() === 0 && + date.getUTCMinutes() === 0 && + date.getUTCSeconds() < 30 + ) { + result.price = + etherscanPriceByDay[ + `${date.getUTCFullYear()}-${date.getUTCMonth() + 1}-${date.getUTCDate()}` + ]; result.priceSource = 'ETHERSCAN'; } } @@ -52,7 +63,8 @@ export function transform(block: RawBlock) { for (const log of tx.receipt.logs) { if ( log.address === uniSwapV1USDC && - log.topics[0] === '0x06239653922ac7bea6aa2b19dc486b9361821d37712eb796adfd38d81de278ca' + log.topics[0] === + '0x06239653922ac7bea6aa2b19dc486b9361821d37712eb796adfd38d81de278ca' ) { const usd = toBigNumber(log.topics[3]).div(USD_DECIMALS); if (usd.gte(MIN_USD_AMOUNT_TO_CONSIDER)) { @@ -73,7 +85,8 @@ export function transform(block: RawBlock) { // check V2 swap logs if ( uniSwapV2ETHPools.has(log.address) && - log.topics[0] === '0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822' && + log.topics[0] === + '0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822' && log.topics.length === 3 ) { const params = decodeEvent( @@ -81,19 +94,49 @@ export function transform(block: RawBlock) { { anonymous: false, inputs: [ - { indexed: true, internalType: 'address', name: 'sender', type: 'address' }, - { indexed: false, internalType: 'uint256', name: 'amount0In', type: 'uint256' }, - { indexed: false, internalType: 'uint256', name: 'amount1In', type: 'uint256' }, - { indexed: false, internalType: 'uint256', name: 'amount0Out', type: 'uint256' }, - { indexed: false, internalType: 'uint256', name: 'amount1Out', type: 'uint256' }, - { indexed: true, internalType: 'address', name: 'to', type: 'address' }, + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0In', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1In', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0Out', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1Out', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, ], name: 'Swap', type: 'event', }, ], log.topics, - log.data + log.data, ); // first 4 data params are possible amounts, only 2 are used @@ -103,7 +146,8 @@ export function transform(block: RawBlock) { // and check V3 swap logs else if ( uniSwapV3ETHPools.has(log.address) && - log.topics[0] === '0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67' && + log.topics[0] === + '0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67' && log.topics.length === 3 ) { const params = decodeEvent( @@ -111,20 +155,55 @@ export function transform(block: RawBlock) { { anonymous: false, inputs: [ - { indexed: true, internalType: 'address', name: 'sender', type: 'address' }, - { indexed: true, internalType: 'address', name: 'recipient', type: 'address' }, - { indexed: false, internalType: 'int256', name: 'amount0', type: 'int256' }, - { indexed: false, internalType: 'int256', name: 'amount1', type: 'int256' }, - { indexed: false, internalType: 'uint160', name: 'sqrtPriceX96', type: 'uint160' }, - { indexed: false, internalType: 'uint128', name: 'liquidity', type: 'uint128' }, - { indexed: false, internalType: 'int24', name: 'tick', type: 'int24' }, + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + indexed: false, + internalType: 'int256', + name: 'amount0', + type: 'int256', + }, + { + indexed: false, + internalType: 'int256', + name: 'amount1', + type: 'int256', + }, + { + indexed: false, + internalType: 'uint160', + name: 'sqrtPriceX96', + type: 'uint160', + }, + { + indexed: false, + internalType: 'uint128', + name: 'liquidity', + type: 'uint128', + }, + { + indexed: false, + internalType: 'int24', + name: 'tick', + type: 'int24', + }, ], name: 'Swap', type: 'event', }, ], log.topics, - log.data + log.data, ); // first 2 data params are possible amounts, one is always negative @@ -133,13 +212,18 @@ export function transform(block: RawBlock) { if (amounts.length >= 2) { // because ETH has 3x the decimals, it will always be the larger number prior to normalizing - let [usd, eth] = amounts.filter((a) => a.gt(0)).sort((a, b) => (a.gt(b) ? 1 : -1)); + let [usd, eth] = amounts + .filter((a) => a.gt(0)) + .sort((a, b) => (a.gt(b) ? 1 : -1)); if (!usd || !eth) { continue; } usd = usd.div(USD_DECIMALS); - if (usd.gte(MIN_USD_AMOUNT_TO_CONSIDER) && usd.lte(MAX_USD_AMOUNT_TO_CONSIDER)) { + if ( + usd.gte(MIN_USD_AMOUNT_TO_CONSIDER) && + usd.lte(MAX_USD_AMOUNT_TO_CONSIDER) + ) { eth = eth.div(ETH_DECIMALS); swaps.push({ price: usd.dividedBy(eth), volume: usd }); } @@ -150,8 +234,13 @@ export function transform(block: RawBlock) { // compute a quick Volume Weight Average Price (VWAP) for the block if (swaps.length) { - const totalVolume = swaps.reduce((a, b) => a.plus(b.volume), toBigNumber(0)); - const price = swaps.reduce((a, b) => a.plus(b.price.multipliedBy(b.volume)), toBigNumber(0)).div(totalVolume); + const totalVolume = swaps.reduce( + (a, b) => a.plus(b.volume), + toBigNumber(0), + ); + const price = swaps + .reduce((a, b) => a.plus(b.price.multipliedBy(b.volume)), toBigNumber(0)) + .div(totalVolume); result.price = price.decimalPlaces(2).toNumber(); result.priceSource = 'UNISWAP'; } diff --git a/src/types/abi.ts b/src/types/abi.ts new file mode 100644 index 0000000..4bad9f9 --- /dev/null +++ b/src/types/abi.ts @@ -0,0 +1,24 @@ +export type StateMutability = 'nonpayable' | 'payable' | 'view' | 'pure'; + +export type ABIFunction = { + type: 'function'; // TODO: constructor, receive, fallback + selector: string; + name?: string; + outputs?: { type: string; length?: number; name: string }[]; + inputs?: { type: string; name: string }[]; + sig?: string; + sigAlts?: string[]; + payable?: boolean; + stateMutability?: StateMutability; +}; + +export type ABIEvent = { + type: 'event'; + hash: string; + name?: string; + sig?: string; + sigAlts?: string[]; + // TODO: ... +}; + +export type ABI = (ABIFunction | ABIEvent)[]; diff --git a/src/types/assetTransfer.ts b/src/types/asset.ts similarity index 100% rename from src/types/assetTransfer.ts rename to src/types/asset.ts diff --git a/src/types/contract.ts b/src/types/contract.ts index 3f0dd18..18a75b0 100644 --- a/src/types/contract.ts +++ b/src/types/contract.ts @@ -1,4 +1,5 @@ -import { StdObj, AbiItem } from './shared'; +import { StdObj } from './shared'; +import { ABI } from './abi'; export type Contract = { chainId?: number; @@ -12,7 +13,7 @@ export type Contract = { blockNumber: number; transactionHash: string; type: 'create' | 'create2'; - metadata?: ContractMetadata; + metadata: ContractMetadata; supportedInterfaces?: SupportedInterfaces; sigHash: string; internalSigHashes: string[]; @@ -43,10 +44,10 @@ export type ContractMetadata = { simplehash?: StdObj; tally?: TallyMetadata; whatsAbiSelectors: string[]; - whatsAbiAbi: AbiItem; + whatsAbiAbi: ABI; isProxy: boolean; implementationAddress?: string; - tokenMetadata?: TokenMetadata; + tokenMetadata: TokenMetadata; }; export type SupportedInterfaces = Record; diff --git a/src/types/index.ts b/src/types/index.ts index 5b2df5b..a6a26bf 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,7 +1,8 @@ -export * from './assetTransfer'; +export * from './asset'; export * from './block'; export * from './contract'; export * from './log'; -export * from './netAssetTransfer'; export * from './shared'; export * from './transaction'; +export * from './neighbor'; +export * from './abi'; diff --git a/src/types/neighbor.ts b/src/types/neighbor.ts new file mode 100644 index 0000000..bde2bdf --- /dev/null +++ b/src/types/neighbor.ts @@ -0,0 +1,7 @@ +export type Neighbor = { + hash: string; + neighbor: { + address: string; + neighbor: string; + }; +}; diff --git a/src/types/netAssetTransfer.ts b/src/types/netAssetTransfer.ts deleted file mode 100644 index 9a434af..0000000 --- a/src/types/netAssetTransfer.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { AssetType } from './shared'; - -export type NetAssetTransfer = { - asset: string; - from?: string; - to?: string; - id: string; - tokenId?: string; - type: AssetType; - value: string; -}; - -export type NetAssetTransfers = Record< - string, - { received: NetAssetTransfer[]; sent: NetAssetTransfer[] } ->; diff --git a/src/types/shared.ts b/src/types/shared.ts index 4cfd56e..cd2b3d2 100644 --- a/src/types/shared.ts +++ b/src/types/shared.ts @@ -1,5 +1,5 @@ import { Contract } from './contract'; -import { AssetTransfer } from './assetTransfer'; +import { AssetTransfer } from './asset'; export type StdObj = Record; diff --git a/src/types/transaction.ts b/src/types/transaction.ts index 9f252db..6f18298 100644 --- a/src/types/transaction.ts +++ b/src/types/transaction.ts @@ -1,6 +1,5 @@ import { StdObj, FragmentType, ParamType } from './shared'; -import { AssetTransfer } from './assetTransfer'; -import { NetAssetTransfers } from './netAssetTransfer'; +import { AssetTransfer, NetAssetTransfers } from './asset'; import { RawReceipt } from './log'; import { Contract } from './contract';