12 min read
Overview
Accurate and comprehensive transaction data is crucial for blockchain professionals, accountants, and enthusiasts. This tutorial will teach you how to build an app that uses the Address Appearances API add-on by TrueBlocks to retrieve historical Ethereum transactions.
An appearance is any instance where a specific address is involved in a transaction. This includes common address fields, data fields, and smart contract calls.
The Address Appearances API is useful for users who need the complete history of an address. Use cases such as accounting will find value in this tool since it is an all-in-one solution, and data does not need to be retrieved from multiple sources.
TrueBlocks Key provides TrueBlocks Core’s unique indexing through a cloud-based Web 2.0 API, making it easier for users to access this comprehensive data. TrueBlocks Core is a fully decentralized, open-source solution that focuses on indexing blockchain addresses, tracking all appearances of an address across different transaction types. While TrueBlocks Key offers a convenient Web 2.0 interface, TrueBlocks Core remains at the heart of TrueBlocks' commitment to decentralization.
For more detailed information about TrueBlocks Key, kindly check here.
What You Will Do
- Clone a ready-to-use React app from our GitHub repository that utilizes the Address Appearances API to fetch Ethereum transaction appearances data
- Set up and run the app locally to visualize transaction appearances
- Optionally, compare the Address Appearances API results with Etherscan API results
What You Will Need
- A QuickNode account with access to the Address Appearances API
- Have Node.js (version 18.16 or higher) installed on your system
- A code editor (e.g., VS Code)
- Typescript and ts-node - installation instructions are indicated in the guide
- Familiarity with JavaScript or TypeScript, and basic knowledge of React
Understanding the Address Appearances API
The Address Appearances API builds an exhaustive index of every appearance on the blockchain. This includes appearances found in regular transactions, internal transactions, logs, token transfers, withdrawals, mining and uncle rewards, and anywhere else an address can appear.
To fully leverage the capabilities of the Address Appearances API, it is important to understand the methods available. Here are the available methods:
tb_getAppearances
: Returns an array of appearances for the given address. This method ensures comprehensive data coverage by listing all instances where the address appears.tb_getBounds
: Provides the latest and earliest appearance for the given address. This method helps establish the full transaction history for an address.tb_status
: Returns the status of the index, including the block number of the last indexed block. This method ensures that the data is up-to-date and accurate.
Setting Up Your Development Environment
To build the app, you need to set up your development environment. This involves installing the necessary tools and dependencies and configuring your QuickNode access.
Installing Necessary Tools and Dependencies
Before you begin, make sure you have Node.js installed on your system. If not, download and install it from the official website. Node.js comes with npm (Node Package Manager), which you will use to install other dependencies.
Next, install TypeScript and ts-node globally if you haven't already:
npm install -g typescript ts-node
Setting Up an Ethereum Endpoint
Before you begin, please note that the Address Appearances API is a paid add-on. Please check the details here and compare plans based on your needs.
Setting up your Ethereum endpoint with the Address Appearances API is quite easy. If you haven't signed up already, you can create an account here.
Once you have logged in, navigate to the Endpoints page and click Create an endpoint. Select Ethereum mainnet, then click Next. Then, you'll be prompted to configure the add-on. Activate Address Appearances API. Afterward, simply click Create Endpoint.
If you already have a Ethereum endpoint without the add-on, go to the Add-ons page within your Ethereum endpoint, select the Address Appearances API, and activate it.
Once your endpoint is ready, copy the HTTP Provider link and keep it handy, as you'll need it in the next section.
Running the Sample App
To get started quickly, we have a ready-to-use solution available on GitHub. Follow these steps to clone and set up the sample app:
- Clone the repository from GitHub.
git clone https://github.com/quiknode-labs/qn-guide-examples.git
- Navigate to the project directory
cd sample-dapps/ethereum-address-appearances
- Install dependencies
npm install
- Rename
.env.example
to.env
and replace the placeholder with your QuickNode Ethereum endpoint URL.
VITE_QUICKNODE_ENDPOINT="YOUR_QUICKNODE_ENDPOINT_URL"
- Start the development server
npm run dev
- Open http://localhost:5173/ in your browser to see the application
This guide will focus on using the Address Appearances API. However, if you provide an Etherscan API key in the .env
file like the one below, the app displays appearance results from both sources for a specified address.
VITE_QUICKNODE_ENDPOINT="YOUR_QUICKNODE_ENDPOINT_URL"
VITE_ETHERSCAN_API_KEY="YOUR_ETHERSCAN_API_KEY"
Working Flow of the App
The app works by fetching transaction data for a specified Ethereum address from the Address Appearances API. If an Etherscan API key is provided in the .env
file, the app will also fetch data from Etherscan's API. It then filters out duplicate transactions from the Etherscan data to ensure accuracy. After filtering, it compares these datasets to highlight any differences in the transaction appearances.
Fetching Data from the Address Appearances API
The app fetches detailed Ethereum transaction data using the tb_getAppearances
method provided by the Address Appearances API. The method returns an array of appearances from the latest block to the earliest.
The fetchCustomMethodData
function handles this:
const fetchCustomMethodData = async (
address: string
): Promise<Appearance[]> => {
const results: Appearance[] = [];
let previousPageId: string | null = null;
do {
const response: ApiRoot = await axios.post(QUICKNODE_ENDPOINT, {
jsonrpc: "2.0",
method: "tb_getAppearances",
params: [{ address, perPage: 1000, pageId: previousPageId }],
id: 1,
});
if (response.data) {
const { data, meta } = response.data.result;
results.push(...data);
previousPageId = meta.previousPageId;
} else {
throw new Error("Failed to fetch transactions");
}
} while (previousPageId);
return results;
};
(Optionally) Fetching Data from Etherscan API
If an Etherscan API key is provided, the app fetches transaction data from Etherscan's API using various endpoints for different transaction types. The fetchEtherscanData
function manages this:
const fetchEtherscanData = async (address: string) => {
const actions = [
"txlist",
"txlistinternal",
"tokentx",
"tokennfttx",
"token1155tx",
];
const results: { [key: string]: SimplifiedEtherscanTransaction[] } = {};
for (const action of actions) {
results[action] = await fetchEtherscanTransactions(address, action);
}
return results;
};
const fetchEtherscanTransactions = async (address: string, action: string) => {
const results: SimplifiedEtherscanTransaction[] = [];
const response = await axios.get("https://api.etherscan.io/api", {
params: {
module: "account",
action,
address,
startblock: 0,
endblock: 99999999,
page: 1,
offset: 10000,
sort: "asc",
apikey: ETHERSCAN_API_KEY,
},
});
const { result } = response.data;
results.push(
...result.map((tx: SimplifiedEtherscanTransaction) => ({
blockNumber: tx.blockNumber,
hash: tx.hash,
transactionIndex: tx.transactionIndex || undefined,
gas: tx.gas,
}))
);
await sleep(RATE_LIMIT_DELAY);
return results;
};
Then, to ensure the data is accurate, the app filters out duplicate transactions from the Etherscan data. The filterDuplicates
function handles this process:
const filterDuplicates = (etherscanData: {
[key: string]: SimplifiedEtherscanTransaction[];
}) => {
const allTransactions: SimplifiedEtherscanTransaction[] = [];
for (const transactions of Object.values(etherscanData)) {
allTransactions.push(...transactions);
}
const uniqueTransactions = new Map<string, SimplifiedEtherscanTransaction>();
allTransactions.forEach((tx) => {
const key = `${tx.blockNumber}-${tx.hash}`;
if (!uniqueTransactions.has(key)) {
uniqueTransactions.set(key, tx);
}
});
// Reconstruct the etherscanData object with unique transactions
const filteredData: { [key: string]: SimplifiedEtherscanTransaction[] } = {};
for (const [action, transactions] of Object.entries(etherscanData)) {
filteredData[action] = transactions.filter((tx) => {
const key = `${tx.blockNumber}-${tx.hash}`;
if (uniqueTransactions.has(key)) {
uniqueTransactions.delete(key);
return true;
}
return false;
});
}
return filteredData;
};
(Optionally) Comparing Results
The app primarily fetches transaction data using the Address Appearances API. If an Etherscan API key is provided, it will also fetch data from Etherscan's API. The compareData
function identifies unique and common transactions between the datasets, highlighting any discrepancies.
Here is how the compareData
function works:
const compareData = (
customData: Appearance[],
etherscanData: { [key: string]: SimplifiedEtherscanTransaction[] }
) => {
const combinedData: CombinedTransactionData[] = [];
// Create a map to group Etherscan data by block number and transaction index
const etherscanMap = new Map<
string,
(SimplifiedEtherscanTransaction & { type: string })[]
>();
for (const [type, transactions] of Object.entries(etherscanData)) {
for (const tx of transactions) {
const key = `${tx.blockNumber}-${tx.transactionIndex ?? "N/A"}`;
if (!etherscanMap.has(key)) {
etherscanMap.set(key, []);
}
etherscanMap.get(key)!.push({ ...tx, type });
}
}
// Iterate over custom data and add corresponding Etherscan data if available
for (const customTx of customData) {
const key = `${customTx.blockNumber}-${customTx.transactionIndex}`;
const etherscanTxs = etherscanMap.get(key) || [];
if (etherscanTxs.length === 0) {
combinedData.push({
customBlockNumber: customTx.blockNumber,
customTxIndex: customTx.transactionIndex,
etherscanBlockNumber: "",
etherscanTxIndex: "",
type: "",
});
} else {
for (const etherscanTx of etherscanTxs) {
combinedData.push({
customBlockNumber: customTx.blockNumber,
customTxIndex: customTx.transactionIndex,
etherscanBlockNumber: etherscanTx.blockNumber,
etherscanTxIndex: etherscanTx.transactionIndex,
type: etherscanTx.type,
});
}
}
}
// Add remaining Etherscan transactions not matched with custom data
for (const [key, transactions] of etherscanMap) {
for (const tx of transactions) {
const [blockNumber, transactionIndex] = key.split("-");
const existsInCustomData = customData.some(
(customTx) =>
customTx.blockNumber === blockNumber &&
customTx.transactionIndex === transactionIndex
);
if (!existsInCustomData) {
combinedData.push({
customBlockNumber: "",
customTxIndex: "",
etherscanBlockNumber: tx.blockNumber,
etherscanTxIndex: tx.transactionIndex,
type: tx.type,
});
}
}
}
// Sort filteredData by block number in descending order
combinedData.sort(
(a, b) =>
Number(b.customBlockNumber || b.etherscanBlockNumber) -
Number(a.customBlockNumber || a.etherscanBlockNumber)
);
return combinedData;
};
Reviewing and Analyzing Results
Once you have entered an Ethereum address and the app fetches the data, you will see the results displayed in a user interface. The app displays the block number and the transaction index in that block for each appearance of the specified address on Ethereum Mainnet. In this guide, we will use the Ethereum address, 0xb5Ab08D153218C1A6a5318B14eeb92DF0Fb168D6
.
To obtain more information about the relevant transaction, you can use the RPC method eth_getTransactionByBlockNumberAndIndex
and input the block number (in hexadecimal format) along with its transaction index as shown below.
curl YOUR_QUICKNODE_ETHEREUM_ENDPOINT \
-X POST \
-H "Content-Type: application/json" \
--data '{"method":"eth_getTransactionByBlockNumberAndIndex","params":["0xc5043f", "0x0"],"id":1,"jsonrpc":"2.0"}'
If you provide an Etherscan API key, the app will also show comparisons between the Address Appearances API and Etherscan, allowing you to analyze any differences.
In this guide, we will analyze the differences in the results of both APIs by walking through a sample result.
After fetching and comparing the data from both APIs, it is observed that the total number of transactions is not the same, and several transactions appear in one dataset but not the other.
Missing Transactions
Let's examine some transactions which are not found in a dataset. Since the tb_getAppearances
method returns the block number and the transaction index for each appearance, you can use this information to get more information about a specific transaction.
1. Using the eth_getTransactionByBlockNumberAndIndex
RPC method
curl YOUR_QUICKNODE_ETHEREUM_ENDPOINT \
-X POST \
-H "Content-Type: application/json" \
--data '{"method":"eth_getTransactionByBlockNumberAndIndex","params":["0xc5043f", "0x0"],"id":1,"jsonrpc":"2.0"}'
2. Checking transaction IDs in a block on Etherscan
Transaction IDs can be seen on each block's page via Etherscan.
Now, let's focus on two transactions that are not in Etherscan's result but in Address Appearances' result:
Block Number | Transaction Index | Transaction Hash |
---|---|---|
19187479 | 59 | 0xb733dd698bc9b0803169b544960d85239210c3b12f93037300b5a2294572c4db |
18955844 | 158 | 0x9335390dc5eab40e26ace32088cf2efa5ab021a884154eb1b005fd468671339c |
1. Transaction 0xb733dd698bc9b0803169b544960d85239210c3b12f93037300b5a2294572c4db
As shown in the image below, this transaction failed. Etherscan may have excluded this transaction from their index due to its failure status, which is often not considered crucial for standard transaction tracking. However, obtaining all associated data, including failed transactions, can be important for comprehensive blockchain analysis and auditing.
2. Transaction: 0x9335390dc5eab40e26ace32088cf2efa5ab021a884154eb1b005fd468671339c
As shown in the image below, this transaction succeeded and includes the searched address in the input data. Despite its success and relevance to the searched address, it was not indexed in Etherscan's results. This finding could be significant, especially for applications requiring comprehensive data, such as detailed auditing or regulatory compliance.
Analyzing the Differences
It appears that Etherscan does not include failed transactions. Additionally, batch commits from Layer 2 projects to the Ethereum Mainnet seem to be unrecognized by Etherscan's API. While a more in-depth analysis would be required to reach a definitive conclusion, these differences can be quite important, particularly for applications requiring comprehensive and accurate transaction data.
Moreover, the results provided by both APIs are quite different. For instance, the Address Appearances API method returns an array of block numbers and transaction IDs for each appearance, whereas Etherscan's API includes extensive information such as token transfers and transaction hash. This difference may be expected since Etherscan's API is not solely focused on appearances.
Conclusion
We hope this guide has provided you with a foundation in retrieving transaction history for an address. Readers are encouraged to dive deeper and apply these concepts to their own use cases. If you need assistance with retrieving blockchain data not described in this guide, please feel free to reach out to us; we would love to talk to you!
Subscribe to our newsletter for more articles and guides on Web3 and blockchain. If you have any questions or need further assistance, feel free to join our Discord server or provide feedback using the form below. Stay up to date with the latest by following us on Twitter (@QuickNode) and our Telegram announcement channel.
We ❤️ Feedback!
Let us know if you have any feedback or requests for new topics. We'd love to hear from you.