Files
aitbc/dev/env/node_modules/micro-eth-signer/net/archive.js
aitbc 816e258d4c refactor: move brother_node development artifact to dev/test-nodes subdirectory
Development Artifact Cleanup:
 BROTHER_NODE REORGANIZATION: Moved development test node to appropriate location
- dev/test-nodes/brother_node/: Moved from root directory for better organization
- Contains development configuration, test logs, and test chain data
- No impact on production systems - purely development/testing artifact

 DEVELOPMENT ARTIFACTS IDENTIFIED:
- Chain ID: aitbc-brother-chain (test/development chain)
- Ports: 8010 (P2P) and 8011 (RPC) - different from production
- Environment: .env file with test configuration
- Logs: rpc.log and node.log from development testing session (March 15, 2026)

 ROOT DIRECTORY CLEANUP: Removed development clutter from production directory
- brother_node/ moved to dev/test-nodes/brother_node/
- Root directory now contains only production-ready components
- Development artifacts properly organized in dev/ subdirectory

DIRECTORY STRUCTURE IMPROVEMENT:
📁 dev/test-nodes/: Development and testing node configurations
🏗️ Root Directory: Clean production structure with only essential components
🧪 Development Isolation: Test environments separated from production

BENEFITS:
 Clean Production Directory: No development artifacts in root
 Better Organization: Development nodes grouped in dev/ subdirectory
 Clear Separation: Production vs development environments clearly distinguished
 Maintainability: Easier to identify and manage development components

RESULT: Successfully moved brother_node development artifact to dev/test-nodes/ subdirectory, cleaning up the root directory while preserving development testing environment for future use.
2026-03-30 17:09:06 +02:00

772 lines
32 KiB
JavaScript
Executable File

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Web3Provider = void 0;
exports.calcTransfersDiff = calcTransfersDiff;
const index_ts_1 = require("../abi/index.js");
const index_ts_2 = require("../index.js");
const tx_ts_1 = require("../tx.js");
const utils_ts_1 = require("../utils.js");
/*
Methods to fetch list of transactions from any ETH node RPC.
It should be easy. However, this is sparta^W ethereum, so, prepare to suffer.
The network is not directly called: `ArchiveNodeProvider#rpc` calls `Web3Provider`.
- There is no simple & fast API inside nodes, all external API create their own namespace for this
- API is different between nodes: erigon uses streaming, other nodes use pagination
- Recently, Erigon have been also adding pagination
- For token transactions: download block headers, look at bloom filter, download affected blocks
- There is a good `getLogs` API for contracts, but nothing for ETH transfers
- `trace_filter` is slow: it not only finds the transaction, but also executes them
- It's good that it allows to get internal transactions
- The whole thing could be 10x simpler if there was an event in logs for ETH transfer
- For most cases, we only need to see last transactions and know blocks of last txs, which is 20x faster
- This creates a lot of requests to node (2 per tx, 1 per block, and some more depends on block range limits)
Recommended software:
- eth-nodes-for-rent are bad, because of their limits and timeouts
- erigon nodes are fast, taking ~15 seconds per batch
- reth has 100-block limit for trace_filter, requiring 190k requests just get transactions
*/
// Utils
const ethNum = (n) => `0x${!n ? '0' : n.toString(16).replace(/^0+/, '')}`;
const ERC_TRANSFER = (0, index_ts_1.events)(index_ts_1.ERC20).Transfer;
const WETH_DEPOSIT = (0, index_ts_1.events)(index_ts_1.WETH).Deposit;
const WETH_WITHDRAW = (0, index_ts_1.events)(index_ts_1.WETH).Withdrawal;
const ERC721_TRANSFER = (0, index_ts_1.events)(index_ts_1.ERC721).Transfer;
const ERC1155_SINGLE = (0, index_ts_1.events)(index_ts_1.ERC1155).TransferSingle;
const ERC1155_BATCH = (0, index_ts_1.events)(index_ts_1.ERC1155).TransferBatch;
const ERC165 = [
// function supportsInterface(bytes4 interfaceID) external view returns (bool);
{
type: 'function',
name: 'supportsInterface',
inputs: [{ name: 'interfaceID', type: 'bytes4' }],
outputs: [{ type: 'bool' }],
},
];
const CONTRACT_CAPABILITIES = {
erc165: '0x01ffc9a7',
erc165_check: '0xffffffff',
erc20: '0x36372b07',
erc721: '0x80ac58cd',
erc721_metadata: '0x5b5e139f',
erc721_enumerable: '0x780e9d63',
erc1155: '0xd9b67a26',
erc1155_tokenreceiver: '0x4e2312e0',
erc1155_metadata: '0x0e89341c',
};
function group(items, s) {
let res = {};
for (let i of items) {
const key = typeof s === 'function' ? s(i) : i[s];
if (!res[key])
res[key] = [];
res[key].push(i);
}
return res;
}
function fixBlock(block) {
block.timestamp = Number(block.timestamp) * 1000;
block.size = Number(block.size);
if (block.number && block.number !== null)
block.number = Number(block.number);
for (const i of [
'baseFeePerGas',
'difficulty',
'gasLimit',
'gasUsed',
'totalDifficulty',
]) {
if (block[i] && block[i] !== null)
block[i] = BigInt(block[i]);
}
}
function fixAction(action, opts = {}) {
action.action.value = BigInt(action.action.value);
action.action.gas = BigInt(action.action.gas);
action.result.gasUsed = BigInt(action.result.gasUsed);
if (opts.txCallback)
opts.txCallback(action.transactionHash);
if (opts.blockCallback)
opts.blockCallback(action.blockNumber);
}
// Fixes type of network response inplace
function fixLog(log, opts = {}) {
log.blockNumber = Number(log.blockNumber);
log.transactionIndex = Number(log.transactionIndex);
log.logIndex = Number(log.logIndex);
if (opts.txCallback)
opts.txCallback(log.transactionHash);
if (opts.blockCallback)
opts.blockCallback(log.blockNumber);
if (opts.contractCallback)
opts.contractCallback(log.address);
return log;
}
function fixTxInfo(info) {
for (const i of ['blockNumber', 'type', 'transactionIndex'])
info[i] = Number(info[i]);
for (const i of [
'nonce',
'r',
's',
'chainId',
'v',
'gas',
'maxPriorityFeePerGas',
'maxFeePerGas',
'value',
'gasPrice',
'maxFeePerBlobGas',
]) {
if (info[i] !== undefined && info[i] !== null)
info[i] = BigInt(info[i]);
}
return info;
}
function fixTxReceipt(receipt) {
for (const i of ['blockNumber', 'type', 'transactionIndex', 'status'])
receipt[i] = Number(receipt[i]);
for (const i of [
'gasUsed',
'cumulativeGasUsed',
'effectiveGasPrice',
'blobGasPrice',
'blobGasUsed',
]) {
if (receipt[i] !== undefined)
receipt[i] = BigInt(receipt[i]);
}
for (const log of receipt.logs)
fixLog(log);
return receipt;
}
function validateCallbacks(opts) {
for (const i of ['txCallback', 'blockCallback', 'contractCallback']) {
if (opts[i] !== undefined && typeof opts[i] !== 'function')
throw new Error(`validateCallbacks: ${i} should be function`);
}
}
function validatePagination(opts) {
for (const i of ['fromBlock', 'toBlock']) {
if (opts[i] === undefined || Number.isSafeInteger(opts[i]))
continue;
throw new Error(`validatePagination: wrong field ${i}=${opts[i]}. Should be integer or undefined`);
}
}
function validateTraceOpts(opts) {
validatePagination(opts);
for (const i of ['perRequest', 'limitTrace']) {
if (opts[i] === undefined || Number.isSafeInteger(opts[i]))
continue;
throw new Error(`validateTraceOpts: wrong field ${i}=${opts[i]}. Should be integer or undefined`);
}
if (opts.limitTrace !== undefined) {
if (opts.fromBlock === undefined || opts.toBlock === undefined)
throw new Error('validateTraceOpts: fromBlock/toBlock required if limitTrace is present');
}
validateCallbacks(opts);
}
function validateLogOpts(opts) {
validatePagination(opts);
for (const i of ['limitLogs']) {
if (opts[i] === undefined || Number.isSafeInteger(opts[i]))
continue;
throw new Error(`validateLogOpts: wrong field ${i}=${opts[i]}. Should be integer or undefined`);
}
if (opts.limitLogs !== undefined) {
if (opts.fromBlock === undefined || opts.toBlock === undefined)
throw new Error('validateLogOpts: fromBlock/toBlock required if limitLogs is present');
}
validateCallbacks(opts);
}
// Promise.all for objects, undefined if error
async function wait(obj) {
const keys = Object.keys(obj);
const p = await Promise.allSettled(Object.values(obj));
const res = p.map((r, i) => [keys[i], r.status === 'fulfilled' ? r.value : undefined]);
return Object.fromEntries(res);
}
const isReverted = (e) => e instanceof Error && e.message.toLowerCase().includes('revert');
/**
* Transaction-related code around Web3Provider.
* High-level methods are `height`, `unspent`, `transfers`, `allowances` and `tokenBalances`.
*
* Low-level methods are `blockInfo`, `internalTransactions`, `ethLogs`, `tokenTransfers`, `wethTransfers`,
* `tokenInfo` and `txInfo`.
*/
class Web3Provider {
constructor(rpc) {
this.rpc = rpc;
}
call(method, ...args) {
return this.rpc.call(method, ...args);
}
ethCall(args, tag = 'latest') {
return this.rpc.call('eth_call', args, tag);
}
async estimateGas(args, tag = 'latest') {
return (0, utils_ts_1.hexToNumber)(await this.rpc.call('eth_estimateGas', args, tag));
}
// Timestamp is available only inside blocks
async blockInfo(block) {
const res = await this.call('eth_getBlockByNumber', ethNum(block), false);
fixBlock(res);
return res;
}
async unspent(address) {
let [balance, nonce] = await Promise.all([
this.call('eth_getBalance', address, 'latest'),
this.call('eth_getTransactionCount', address, 'latest'),
]);
balance = BigInt(balance);
nonce = BigInt(nonce);
return {
symbol: 'ETH',
decimals: utils_ts_1.amounts.ETH_PRECISION,
balance,
nonce,
// Note: account can be active even if nonce!==0!
active: balance > 0 || nonce !== 0,
};
}
async height() {
return Number.parseInt(await this.call('eth_blockNumber'));
}
async traceFilterSingle(address, opts = {}) {
const res = await this.call('trace_filter', {
fromBlock: ethNum(opts.fromBlock),
toBlock: ethNum(opts.toBlock),
toAddress: [address],
fromAddress: [address],
});
for (const action of res)
fixAction(action, opts);
return res;
}
async internalTransactions(address, opts = {}) {
if (typeof address !== 'string')
throw new Error('internalTransactions: wrong address');
validateTraceOpts(opts);
// For reth
if (opts.limitTrace) {
const promises = [];
for (let i = opts.fromBlock; i <= opts.toBlock; i += opts.limitTrace)
promises.push(this.traceFilterSingle(address, { fromBlock: i, toBlock: i + opts.limitTrace }));
const out = [];
for (const i of await Promise.all(promises))
out.push(...i);
return out;
}
let lastBlock = opts.fromBlock || 0;
const perBlock = {};
const out = [];
for (;;) {
const params = {
fromBlock: ethNum(lastBlock),
toAddress: [address],
fromAddress: [address],
after: perBlock[lastBlock] || 0, // we cannot just store after, since fromBlock changes to last block
};
if (opts.toBlock !== undefined)
params.toBlock = ethNum(opts.toBlock);
if (opts.perRequest !== undefined)
params.count = opts.perRequest;
const res = await this.call('trace_filter', params);
if (!res.length)
break;
for (const action of res) {
fixAction(action, opts);
if (perBlock[action.blockNumber] === undefined)
perBlock[action.blockNumber] = 0;
perBlock[action.blockNumber]++;
out.push(action);
lastBlock = Math.max(lastBlock, action.blockNumber);
}
}
return out;
}
async contractCapabilities(address, capabilities = {}) {
const all = { ...CONTRACT_CAPABILITIES, ...capabilities };
let c = (0, index_ts_1.createContract)(ERC165, this, address);
const keys = Object.keys(all);
// TODO: what about revert?
// if reverted -> all capabilities disabled
try {
const promises = await Promise.all(Object.values(all).map((i) => c.supportsInterface.call(utils_ts_1.ethHex.decode(i))));
const res = Object.fromEntries(keys.map((k, i) => [k, promises[i]]));
// if somehow there is same method, but it doesn't support erc165, then it is different method!
// erc165_check if sailsafe when there is method that always returns true
if (!res.erc165 || res.erc165_check)
for (const k in res)
res[k] = false;
return res;
}
catch (e) {
// If execution reverted: contract doesn't support ERC165
if (isReverted(e))
return Object.fromEntries(keys.map((k) => [k, false]));
throw e;
}
}
async ethLogsSingle(topics, opts) {
const req = { topics, fromBlock: ethNum(opts.fromBlock || 0) };
if (opts.toBlock !== undefined)
req.toBlock = ethNum(opts.toBlock);
const res = await this.call('eth_getLogs', req);
return res.map((i) => fixLog(i, opts));
}
async ethLogs(topics, opts = {}) {
validateLogOpts(opts);
const fromBlock = opts.fromBlock || 0;
if (!('limitLogs' in opts))
return this.ethLogsSingle(topics, opts);
const promises = [];
for (let i = fromBlock; i <= opts.toBlock; i += opts.limitLogs)
promises.push(this.ethLogsSingle(topics, { fromBlock: i, toBlock: i + opts.limitLogs }));
const out = [];
for (const i of await Promise.all(promises))
out.push(...i);
return out;
}
// NOTE: this is very low-level methods that return parts used for .transfers method,
// you will need to decode data yourself.
async tokenTransfers(address, opts = {}) {
if (typeof address !== 'string')
throw new Error('tokenTransfers: wrong address');
validateLogOpts(opts);
// If we want incoming and outgoing token transfers we need to call both
return await Promise.all([
this.ethLogs(ERC_TRANSFER.topics({ from: address, to: null, value: null }), opts), // From
this.ethLogs(ERC_TRANSFER.topics({ from: null, to: address, value: null }), opts), // To
]);
}
async wethTransfers(address, opts = {}) {
if (typeof address !== 'string')
throw new Error('tokenTransfers: wrong address');
validateLogOpts(opts);
const depositTopic = WETH_DEPOSIT.topics({ dst: address, wad: null });
const withdrawTopic = WETH_WITHDRAW.topics({ src: address, wad: null });
// OR query
return await Promise.all([
this.ethLogs([[depositTopic[0], withdrawTopic[0]], depositTopic[1]], opts),
]);
}
async erc1155Transfers(address, opts = {}) {
if (typeof address !== 'string')
throw new Error('tokenTransfers: wrong address');
validateLogOpts(opts);
return await Promise.all([
// Single
this.ethLogs(ERC1155_SINGLE.topics({ operator: null, from: address, to: null, id: null, value: null }), opts),
this.ethLogs(ERC1155_SINGLE.topics({ operator: null, from: null, to: address, id: null, value: null }), opts),
// Batch
this.ethLogs(ERC1155_BATCH.topics({ operator: null, from: address, to: null, ids: null, values: null }), opts),
this.ethLogs(ERC1155_BATCH.topics({ operator: null, from: null, to: address, ids: null, values: null }), opts),
]);
}
async txInfo(txHash, opts = {}) {
let [info, receipt] = await Promise.all([
this.call('eth_getTransactionByHash', txHash),
this.call('eth_getTransactionReceipt', txHash),
]);
info = fixTxInfo(info);
receipt = fixTxReceipt(receipt);
const type = Object.keys(tx_ts_1.TxVersions)[info.type];
// This is not strictly neccessary, but allows to store tx info in very compact format and remove unneccessary fields
// Also, there is additional validation that node returned actual with correct hash/sender and not corrupted stuff.
let raw = undefined;
try {
const rawData = {
nonce: info.nonce,
gasLimit: info.gas,
to: info.to === null ? '0x' : info.to,
value: info.value,
data: info.input,
r: info.r,
s: info.s,
yParity: Number(info.v),
chainId: info.chainId,
};
if (info.accessList)
rawData.accessList = info.accessList;
if (info.maxFeePerBlobGas)
rawData.maxFeePerBlobGas = info.maxFeePerBlobGas;
if (info.blobVersionedHashes)
rawData.blobVersionedHashes = info.blobVersionedHashes;
if (info.maxFeePerGas) {
rawData.maxFeePerGas = info.maxFeePerGas;
rawData.maxPriorityFeePerGas = info.maxPriorityFeePerGas;
}
else if (info.gasPrice)
rawData.gasPrice = info.gasPrice;
if (type === 'legacy')
Object.assign(rawData, tx_ts_1.legacySig.encode({ v: info.v, r: info.r, s: info.s }));
const tx = new index_ts_2.Transaction(type, rawData, false, true);
if (tx.recoverSender().address.toLowerCase() !== info.from.toLowerCase())
throw new Error('txInfo: wrong sender');
if (receipt.transactionHash !== `0x${tx.hash}`)
throw new Error('txInfo: wrong hash');
raw = tx.toHex();
}
catch (err) {
// This can crash if something wrong with our parser or limits, so
// we have option to make network code to work even if rebuilding is crashed
if (!opts.ignoreTxRebuildErrors)
throw err;
}
if (opts.blockCallback && info.blockNumber !== null)
opts.blockCallback(info.blockNumber);
return { type, info, receipt, raw };
}
async tokenInfo(contract) {
const c = (0, index_ts_1.createContract)(index_ts_1.ERC20, this, contract);
const t = await wait({
code: this.call('eth_getCode', contract, 'latest'),
capabilities: this.contractCapabilities(contract),
// We call all stuff at same time to reduce latency (should be done in single req if batched)
name: c.name.call(), // ERC-20 (optional), ERC-721 (metada)
symbol: c.symbol.call(), // ERC-20 (optional), ERC-721 (metadata)
decimals: c.decimals.call(), // ERC-20 (optional), ERC-721 (enumarable)
totalSupply: c.totalSupply.call(), // ERC-20 (required), ERC-721
});
// No code, probably self-destructed
if (t.code === '0x')
return { contract, error: 'not contract or destructed' };
if (t.capabilities && t.capabilities.erc1155) {
// All metadata is inside URI per tokenId to outside network stuff (maybe ipfs), so nothing to do here.
return { contract, abi: 'ERC1155' };
}
if (t.capabilities && t.capabilities.erc721) {
const res = { contract, abi: 'ERC721' };
if (t.capabilities.erc721_metadata) {
if (t.name === undefined)
return { contract, error: 'ERC721+Metadata without name' };
if (t.symbol === undefined)
return { contract, error: 'ERC721+Metadata without symbol' };
Object.assign(res, { name: t.name, symbol: t.symbol, metadata: true });
}
if (t.capabilities.erc721_enumerable) {
if (t.totalSupply === undefined)
return { contract, error: 'ERC721+Enumerable without totalSupply' };
Object.assign(res, { totalSupply: t.totalSupply, enumerable: true });
}
return res;
}
if (t.totalSupply === undefined)
return { contract, error: 'not ERC20 token' }; // If there is no totalSupply, it is not ERC20!
return {
contract,
abi: 'ERC20',
name: t.name,
symbol: t.symbol,
totalSupply: t.totalSupply,
decimals: t.decimals ? Number(t.decimals) : undefined,
};
}
async tokenBalanceSingle(address, token, tokenIds) {
if ('error' in token)
return token;
if (token.abi === 'ERC20') {
const balance = await (0, index_ts_1.createContract)(index_ts_1.ERC20, this, token.contract).balanceOf.call(address);
if (tokenIds && (tokenIds.size > 1 || Array.from(tokenIds)[0] !== 1n)) {
return { contract: token.contract, error: 'unexpected tokenIds for ERC20' };
}
return new Map([[1n, balance]]);
}
else if (token.abi === 'ERC721') {
const c = (0, index_ts_1.createContract)(index_ts_1.ERC721, this, token.contract);
const balance = await c.balanceOf.call(address);
if (!token.enumerable) {
if (!tokenIds) {
if (!balance)
return new Map(); // no tokens owned by user
return {
contract: token.contract,
error: 'erc721 contract not enumerable, but owner has ' + balance + ' tokens',
};
}
// if we cannot enumerate, but has tokenIds, we can check if tokenIds still owned by account
const ids = Array.from(tokenIds);
const owners = await Promise.all(ids.map((i) => c.ownerOf.call(i)));
return new Map(ids.map((i, j) => [i, owners[j].toLowerCase() === address.toLowerCase() ? 1n : 0n]));
}
// if we can fetch tokenIds: always do this
const p = [];
for (let i = 0; i < balance; i++)
p.push(c.tokenOfOwnerByIndex.call({ owner: address, index: BigInt(i) }));
tokenIds = new Set(await Promise.all(p));
const ids = Array.from(tokenIds);
return new Map(ids.map((i) => [i, 1n]));
}
else if (token.abi === 'ERC1155') {
// This is pretty bad standard, because it doesn't allow enumeration of tokenIds for owner
if (!tokenIds)
return { contract: token.contract, error: 'cannot fetch erc1155 without tokenIds' };
const c = (0, index_ts_1.createContract)(index_ts_1.ERC1155, this, token.contract);
const ids = Array.from(tokenIds);
const balances = await c.balanceOfBatch.call({ accounts: ids.map((_) => address), ids });
const res = new Map(ids.map((i, j) => [i, balances[j]]));
return res;
}
throw new Error('unknown token type');
}
async tokenURI(token, tokenId) {
if (typeof token === 'string')
token = await this.tokenInfo(token);
if ('error' in token)
return token;
if (token.abi === 'ERC721') {
const c = (0, index_ts_1.createContract)(index_ts_1.ERC721, this, token.contract);
if (!token.metadata)
return { contract: token.contract, error: 'erc721 without metadata' };
return c.tokenURI.call(tokenId);
}
else if (token.abi === 'ERC1155') {
const c = (0, index_ts_1.createContract)(index_ts_1.ERC1155, this, token.contract);
return c.uri.call(tokenId);
}
return { contract: token.contract, error: 'not supported token type' };
}
async tokenBalances(address, tokens, tokenIds) {
// New API requires data from tokenInfo (which is slow and should be cached).
// But for compat with old API, we do tokenInfo call if contract address (as string) presented
const _tokens = await Promise.all(tokens.map((i) => (typeof i === 'string' ? this.tokenInfo(i) : i)));
const balances = await Promise.all(_tokens.map((i) => this.tokenBalanceSingle(address, i, tokenIds && tokenIds[i.contract])));
return Object.fromEntries(_tokens.map((i, j) => [i.contract, balances[j]]));
}
decodeTokenTransfer(token, log) {
if ('error' in token)
return;
if (token.abi === 'ERC20') {
try {
const decoded = ERC_TRANSFER.decode(log.topics, log.data);
return {
...token,
contract: log.address,
to: decoded.to,
from: decoded.from,
tokens: new Map([[1n, decoded.value]]),
};
}
catch (e) { }
// Weth doesn't issue Transfer event on Deposit/Withdrawal
// NOTE: we don't filter for WETH_CONTRACT here in case of other contracts with similar API or different networks
try {
const decoded = WETH_DEPOSIT.decode(log.topics, log.data);
return {
...token,
contract: log.address,
from: log.address,
to: decoded.dst,
tokens: new Map([[1n, decoded.wad]]),
};
}
catch (e) { }
try {
const decoded = WETH_WITHDRAW.decode(log.topics, log.data);
return {
...token,
contract: log.address,
from: decoded.src,
to: log.address,
tokens: new Map([[1n, decoded.wad]]),
};
}
catch (e) { }
}
else if (token.abi === 'ERC721') {
try {
const decoded = ERC721_TRANSFER.decode(log.topics, log.data);
return {
...token,
from: decoded.from,
to: decoded.to,
tokens: new Map([[decoded.tokenId, 1n]]),
};
}
catch (e) { }
}
else if (token.abi === 'ERC1155') {
try {
const decoded = ERC1155_SINGLE.decode(log.topics, log.data);
return {
...token,
from: decoded.from,
to: decoded.to,
tokens: new Map([[decoded.id, decoded.value]]),
};
}
catch (e) { }
try {
const decoded = ERC1155_BATCH.decode(log.topics, log.data);
return {
...token,
from: decoded.from,
to: decoded.to,
tokens: new Map(decoded.ids.map((i, j) => [i, decoded.values[j]])),
};
}
catch (e) { }
}
return; // unknown token type
}
// We want to get all transactions related to address, that means:
// - from or to equals address in tx
// - any internal tx from or to equals address in tx
// - any erc20 token transfer which hash address in src or dst
// - erc721 is exactly same function signature as erc20 (need to detect after getting transactions)
// - erc1155: from/to + single/batch
// trace_filter (web3) returns information only for first two cases, most of explorers returns only first case.
async transfers(address, opts = {}) {
const txCache = {};
const blockCache = {};
const tokenCache = {};
const _opts = {
...opts,
txCallback: (txHash) => {
if (txCache[txHash])
return;
txCache[txHash] = this.txInfo(txHash, opts);
},
blockCallback: (blockNumber) => {
if (blockCache[blockNumber])
return;
blockCache[blockNumber] = this.blockInfo(blockNumber);
},
contractCallback: (address) => {
if (tokenCache[address])
return;
tokenCache[address] = this.tokenInfo(address);
},
};
if (!_opts.fromBlock)
_opts.fromBlock = 0;
// This runs in parallel and executes callbacks
// Note, we ignore logs and weth, but they will call callbacks and fetch related
const [actions, _logs, _weth] = await Promise.all([
this.internalTransactions(address, _opts),
this.tokenTransfers(address, _opts),
this.wethTransfers(address, _opts),
this.erc1155Transfers(address, _opts),
]);
const mapCache = async (cache) => {
const keys = Object.keys(cache);
const values = await Promise.all(Object.values(cache));
return Object.fromEntries(values.map((v, i) => [keys[i], v]));
};
// it is ok to do this sequentially, since promises already started and probably resolved at this point
const blocks = await mapCache(blockCache);
const tx = await mapCache(txCache);
const tokens = await mapCache(tokenCache);
const actionPerTx = group(actions, 'transactionHash');
// Sort transactions by [blockNumber, transactionIndex]
const _txHashes = Object.entries(tx).map(([k, v]) => [k, v.info.blockNumber, v.info.transactionIndex]);
_txHashes.sort((a, b) => (a[1] !== b[1] ? a[1] - b[1] : a[2] - b[2]));
const txHashes = _txHashes.map((i) => i[0]);
return txHashes.map((txHash) => {
const { info, receipt } = tx[txHash];
const actions = actionPerTx[txHash];
const block = info.blockNumber !== null ? blocks[info.blockNumber] : undefined;
const transfers = [];
if (actions) {
for (const a of actions)
transfers.push({ from: a.action.from, to: a.action.to, value: a.action.value });
}
else {
// If we have action, it was call to contract and transfer from tx is already added
transfers.push({ from: info.from, to: info.to, value: info.value });
}
// cumulativeGasUsed includes all transactions before that in block, so useless. gasUsed is correct even for internal transactions
transfers.push({ from: info.from, value: receipt.gasUsed * receipt.effectiveGasPrice });
// Tokens
const tokenTransfers = [];
for (const log of receipt.logs) {
const tokenInfo = tokens[log.address];
if (!tokenInfo)
continue;
const tt = this.decodeTokenTransfer(tokenInfo, log);
if (tt)
tokenTransfers.push(tt);
}
return {
hash: txHash,
timestamp: block.timestamp,
block: info.blockNumber !== null ? info.blockNumber : undefined,
reverted: !receipt.status,
transfers,
tokenTransfers,
info: { ...tx[txHash], block, actions },
};
});
}
async allowances(address, opts = {}) {
const approval = (0, index_ts_1.events)(index_ts_1.ERC20).Approval;
// ERC-721/ERC-1155: +ApprovalForAll
// ERC-1761 Scoped Approval for partial with 1155/721?
const topics = approval.topics({ owner: address, spender: null, value: null });
const logs = await this.ethLogs(topics, opts);
// res[tokenContract][spender] = value
const res = {};
for (const l of logs) {
const decoded = approval.decode(l.topics, l.data);
if (decoded.owner.toLowerCase() !== address.toLowerCase())
continue;
if (!res[l.address])
res[l.address] = {};
res[l.address][decoded.spender] = decoded.value;
}
return res;
}
}
exports.Web3Provider = Web3Provider;
/**
* Calculates balances at specific point in time after tx.
* Also, useful as a sanity check in case we've missed something.
* Info from multiple addresses can be merged (sort everything first).
*/
function calcTransfersDiff(transfers) {
// address -> balance
const balances = {};
// contract -> address -> tokenId -> balance
const tokenBalances = {};
let _0 = BigInt(0);
for (const t of transfers) {
for (const it of t.transfers) {
if (it.from) {
if (balances[it.from] === undefined)
balances[it.from] = _0;
balances[it.from] -= it.value;
}
if (it.to) {
if (balances[it.to] === undefined)
balances[it.to] = _0;
balances[it.to] += it.value;
}
}
for (const tt of t.tokenTransfers) {
if (!tokenBalances[tt.contract])
tokenBalances[tt.contract] = {};
const token = tokenBalances[tt.contract];
for (const [tokenId, value] of tt.tokens) {
if (token[tt.from] === undefined)
token[tt.from] = new Map();
if (token[tt.to] === undefined)
token[tt.to] = new Map();
const fromTokens = token[tt.from];
const toTokens = token[tt.to];
fromTokens.set(tokenId, (fromTokens.get(tokenId) || _0) - value);
toTokens.set(tokenId, (toTokens.get(tokenId) || _0) + value);
}
}
Object.assign(t, {
balances: { ...balances },
// deep copy
tokenBalances: Object.fromEntries(Object.entries(tokenBalances).map(([k, v]) => [k, { ...v }])),
});
}
return transfers;
}
//# sourceMappingURL=archive.js.map