Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CON-182] Ethereum Address Appearances #64

Merged
merged 4 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions sample-dapps/ethereum-address-appearances/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
VITE_QUICKNODE_ENDPOINT = "YOUR_QUICKNODE_ETHEREUM_ENDPOINT_URL"
VITE_ETHERSCAN_API_KEY = "YOUR_ETHERSCAN_API_KEY"
18 changes: 18 additions & 0 deletions sample-dapps/ethereum-address-appearances/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
26 changes: 26 additions & 0 deletions sample-dapps/ethereum-address-appearances/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

.env
90 changes: 90 additions & 0 deletions sample-dapps/ethereum-address-appearances/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Ethereum Address Appearances Application

## Introduction

This application is designed to fetch and analyze Ethereum transactions associated with a specific address, leveraging the capabilities of QuickNode's [Address Appearances API](https://marketplace.quicknode.com/add-on/address-appearances-api). It provides users with detailed comparisons of transactions found using **Address Appearances API** and Etherscan's API, offering insights into transaction history and differences between these sources.

<!-- TO-DO: ADD GUIDE URL WHEN IT'S READY -->
<!-- For an in-depth guide on how to fetch data and develop further functionalities, refer to [our comprehensive guide on QuickNode](https://www.quicknode.com/guides). -->

### Tech Stack
- Frontend Framework/Library: React
- Language: TypeScript
- Build Tool/Development Server: Vite
- Styling: Tailwind CSS

## Features

- **Transaction Comparison**: Compares transactions found using QuickNode's Address Appearances API with those found using Etherscan API.
- **Duplicate Detection**: Identifies and marks duplicate transactions of Etherscan API.
- **Filtering**: Excludes internal transactions if associated normal transactions are present.

## Getting Started

### Prerequisites

Before you begin, ensure you have the following:
- [Node.js](https://nodejs.org/en/) installed on your system.
- A QuickNode account with the [Address Appearances API](https://marketplace.quicknode.com/add-on/address-appearances-api) enabled.
- A code editor or an IDE (e.g., [VS Code](https://code.visualstudio.com/))
- [TypeScript](https://www.typescriptlang.org/) and [ts-node](https://typestrong.org/ts-node/)

> You can run the commands below to install TypeScript and ts-node globally to have TypeScript available across all projects.

```bash
npm install -g typescript
npm install -g ts-node
```

### Installation Dependencies

1. Clone the repository to your local machine:
```bash
git clone https://github.com/quiknode-labs/qn-guide-examples.git
```

2. Navigate to the project directory:
```bash
cd sample-dapps/ethereum-address-appearances
```

3. Install the necessary dependencies:
```bash
npm install
```

### Setting Environment Variables

Rename `.env.example` to `.env` and replace the `YOUR_QUICKNODE_ETHEREUM_ENDPOINT_URL` and `YOUR_ETHERSCAN_API_KEY` placeholders with your QuickNode Ethereum Node Endpoint and Etherscan API key. Make sure that the [Address Appearances API](https://marketplace.quicknode.com/add-on/address-appearances-api) is enabled.

```env
VITE_QUICKNODE_ENDPOINT = "YOUR_QUICKNODE_ETHEREUM_ENDPOINT_URL"
```

> Please note that while we utilize `dotenv` for environment variable management, sensitive information like endpoints can still be visible on the frontend. This configuration is not recommended for production environments as-is.

### Running the Application

Run the development server:

```bash
npm run dev
```

Open [http://localhost:5173/](http://localhost:5173/) with your browser to see the application.

## Using the App
1. Input an Ethereum address.
2. Press Generate.
3. View the comparison of transactions found by QuickNode's Address Appearances API and Etherscan API.
4. Review the transaction summary and detailed comparison table.

The **Ethereum Address Appearances Application** will query the Ethereum blockchain for the address's transactions, compare the data from QuickNode and Etherscan, and display the results.

![Preview](public/image.png)

## Conclusion

QuickNode's [Address Appearances API](https://marketplace.quicknode.com/add-on/address-appearances-api) excels in identifying more transaction appearances compared to other sources. This enhanced capability provides developers and businesses with more comprehensive and accurate transaction data. By leveraging this API, users can gain deeper insights into blockchain interactions.

Whether for audit purposes, regulatory compliance, or market analysis, QuickNode's Address Appearances API ensures you have the most detailed and accurate transaction data available. To discover more about how QuickNode assists companies and individuals in extracting comprehensive blockchain data, please [contact us](https://www.quicknode.com/contact-us); we're eager to engage with you!
13 changes: 13 additions & 0 deletions sample-dapps/ethereum-address-appearances/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Ethereum Transactions Index Comparison by QuickNode</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
35 changes: 35 additions & 0 deletions sample-dapps/ethereum-address-appearances/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "ethereum-address-appearances",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@quicknode/sdk": "^2.2.2",
"axios": "^1.6.8",
"chart.js": "^4.4.2",
"dotenv": "^16.4.5",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.19",
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.3",
"typescript": "^5.2.2",
"vite": "^5.2.0"
}
}
6 changes: 6 additions & 0 deletions sample-dapps/ethereum-address-appearances/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions sample-dapps/ethereum-address-appearances/public/vite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions sample-dapps/ethereum-address-appearances/src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}

.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}

@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}

.card {
padding: 2em;
}

.read-the-docs {
color: #888;
}
88 changes: 88 additions & 0 deletions sample-dapps/ethereum-address-appearances/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, { useState, useEffect } from "react";
import Header from "./components/Header";
import Footer from "./components/Footer";
import AddressInputForm from "./components/AddressInputForm";
import ComparisonTable from "./components/ComparisonTable";
import TransactionSummary from "./components/TransactionSummary";

import compareData from "./helpers/compareData";
import fetchTransactions from "./helpers/fetchData";
import {
Appearance,
SimplifiedEtherscanTransaction,
CombinedTransactionData,
} from "./interfaces";

const App: React.FC = () => {
const [address, setAddress] = useState<string>("");
const [errorMsg, setErrorMsg] = useState<string>("");
const [customData, setCustomData] = useState<Appearance[]>([]);
const [etherscanData, setEtherscanData] = useState<{
[key: string]: SimplifiedEtherscanTransaction[];
}>({});
const [comparisonTable, setComparisonTable] = useState<
CombinedTransactionData[]
>([]);
const [loading, setLoading] = useState<boolean>(false);
const [customTotal, setCustomTotal] = useState<number>(0);
const [etherscanTotals, setEtherscanTotals] = useState<{
[key: string]: number;
}>({});

const handleFormSubmit = async (address: string) => {
try {
setLoading(true);
setCustomData([]);
setEtherscanData({});
setErrorMsg("");

const { customMethodData, esData, customTotal, etherscanTotals } =
await fetchTransactions(address);

setCustomData(customMethodData);
setEtherscanData(esData);
setCustomTotal(customTotal);
setEtherscanTotals(etherscanTotals);
} catch (error) {
console.error("Error getting data:", error);
setErrorMsg("Error getting data");
} finally {
setLoading(false);
}
};

useEffect(() => {
if (customData.length > 0 || Object.keys(etherscanData).length > 0) {
const comparisonResult = compareData(customData, etherscanData);
setComparisonTable(comparisonResult);
}
}, [customData, etherscanData]);

return (
<div className="flex flex-col min-h-screen">
<Header />
<AddressInputForm
onSubmit={handleFormSubmit}
setAddress={setAddress}
isLoading={loading}
/>
{errorMsg && (
<div className="mt-4 mx-auto text-red-600 bg-red-100 border border-red-400 rounded p-2 w-1/2 max-w-sm text-center justify-center">
{errorMsg}
</div>
)}

{customTotal > 0 && (
<TransactionSummary
address={address}
customTotal={customTotal}
etherscanTotals={etherscanTotals}
/>
)}
{comparisonTable.length > 0 && <ComparisonTable data={comparisonTable} />}
<Footer />
</div>
);
};

export default App;
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React, { useState } from "react";
import { viem } from "@quicknode/sdk";

interface AddressInputFormProps {
onSubmit: (address: string) => void;
setAddress: (address: string) => void;
isLoading: boolean;
}

const AddressInputForm: React.FC<AddressInputFormProps> = ({
onSubmit,
setAddress,
isLoading,
}) => {
const [addressInput, setAddressInput] = useState("");
const [isValidAddress, setIsValidAddress] = useState(false);

/* eslint-disable @typescript-eslint/no-explicit-any */
const handleAddressChange = (e: any) => {
const inputAddress = e.target.value;
setAddressInput(inputAddress);
setIsValidAddress(viem.isAddress(inputAddress));
setAddress(inputAddress);
};

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit(addressInput);
};

return (
<div className="flex justify-center mt-4">
<form
onSubmit={handleSubmit}
className="bg-white p-6 rounded shadow-lg w-full max-w-md"
>
<input
type="text"
value={addressInput}
onChange={handleAddressChange}
placeholder="Enter Ethereum address"
className="border p-2 w-full mb-4 rounded"
/>
<button
type="submit"
disabled={!isValidAddress}
className={`w-full py-2 rounded text-white ${
isValidAddress
? "bg-blue-500 hover:bg-blue-600"
: "bg-gray-200 cursor-not-allowed"
}`}
>
{isLoading ? "Loading..." : "Generate"}
</button>
</form>

</div>
);
};

export default AddressInputForm;
Loading
Loading