Skip to content

Commit 6becf38

Browse files
committed
feat: add data architecture
1 parent ab4d53f commit 6becf38

File tree

12 files changed

+2212
-7
lines changed

12 files changed

+2212
-7
lines changed

.prettierrc

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"semi": true,
3+
"trailingComma": "all",
4+
"singleQuote": true,
5+
"printWidth": 120,
6+
"tabWidth": 2,
7+
"bracketSpacing": true,
8+
"parser": "typescript",
9+
"endOfLine": "auto"
10+
}

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
},
1212
"dependencies": {
1313
"@reduxjs/toolkit": "^1.9.7",
14+
"axios": "^1.6.2",
15+
"prettier": "^3.1.0",
1416
"react": "^18.2.0",
1517
"react-dom": "^18.2.0",
1618
"react-hook-form": "^7.48.2",

src/App.tsx

+18-7
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
1-
import "./App.css";
2-
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
3-
import Home from "./views/Home/Home";
4-
import Wallet from "./views/Wallet/Wallet";
5-
import Overview from "./views/Overview/Overview";
6-
import Navbar from "./components/Navbar/Navbar";
7-
import Dialogs from "./components/Dialogs/Dialogs";
1+
import './App.css';
2+
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
3+
import Home from './views/Home/Home';
4+
import Wallet from './views/Wallet/Wallet';
5+
import Overview from './views/Overview/Overview';
6+
import Navbar from './components/Navbar/Navbar';
7+
import Dialogs from './components/Dialogs/Dialogs';
8+
import { useEffect } from 'react';
9+
import { getWallet } from './blockchains';
10+
import { BlockchainType } from './blockchains/types.ts';
811

912
function App() {
13+
useEffect(() => {
14+
const tmp = async () => {
15+
const wallet = await getWallet('0x4d7dD30059ff10330001816c2aFD29554B215B48', BlockchainType.zkSync);
16+
// replace with your address and blockchain
17+
console.log(wallet);
18+
};
19+
tmp();
20+
}, []);
1021
return (
1122
<Router>
1223
<Navbar />

src/blockchains/blockscout/token.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { BLOCKCHAINS, BlockchainType, Token } from '../types.ts';
2+
import { getTokens as getZkSyncToken } from '../zksync/token.ts';
3+
import axios from 'axios';
4+
5+
const blockchains = {
6+
zkSync: getZkSyncToken,
7+
scroll: () => [],
8+
base: () => [],
9+
};
10+
11+
const getTokens = async (address: string, blockchain: BlockchainType): Promise<Token[]> => {
12+
const tokens = (await blockchains[blockchain](address, blockchain)).sort((a) => (a.type === 'ERC-721' ? 1 : -1));
13+
14+
const response = await axios.get(BLOCKCHAINS[blockchain].apiEndpoint, {
15+
params: {
16+
module: 'account',
17+
action: 'balance',
18+
address,
19+
},
20+
});
21+
22+
tokens.unshift({
23+
name: 'Ethereum',
24+
symbol: 'ETH',
25+
link: BLOCKCHAINS[blockchain].explorer.replace('%ADDRESS%', address),
26+
balance: Number(response.data.result) * 10 ** -18,
27+
type: 'NATIVE',
28+
});
29+
30+
return tokens;
31+
};
32+
33+
export { getTokens };
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import axios from 'axios';
2+
import { Transaction } from './types.ts';
3+
import { BLOCKCHAINS, BlockchainType } from '../types.ts';
4+
5+
const getTransactions = async (address: string, blockchain: BlockchainType): Promise<Transaction[]> => {
6+
const transactions: Transaction[] = [];
7+
const offset = 1000;
8+
let lastBlock = 0;
9+
10+
// eslint-disable-next-line no-constant-condition
11+
while (true) {
12+
try {
13+
const response = await axios.get(BLOCKCHAINS[blockchain].apiEndpoint, {
14+
params: {
15+
module: 'account',
16+
action: 'txlist',
17+
address,
18+
startblock: lastBlock,
19+
endblock: 99999999999999,
20+
sort: 'asc',
21+
offset,
22+
},
23+
});
24+
if (response.status === 200) {
25+
transactions.push(...response.data.result);
26+
if (response.data.result.length < offset) {
27+
return transactions;
28+
}
29+
const lastTransaction = response.data.result[response.data.result.length - 1];
30+
lastBlock = lastTransaction.blockNumber;
31+
} else {
32+
console.error('Error occurred while retrieving transactions.');
33+
}
34+
} catch (error) {
35+
console.error('Error occurred while making the request:', error);
36+
break;
37+
}
38+
}
39+
40+
return transactions;
41+
};
42+
43+
export { getTransactions };
+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import axios from 'axios';
2+
import { Transaction, Transfer } from './types.ts';
3+
import { BLOCKCHAINS, BlockchainType } from '../types.ts';
4+
5+
const getTransfers = async (address: string, blockchain: BlockchainType) => {
6+
const transfers: Transfer[] = [];
7+
const offset = 1000;
8+
let page = 1;
9+
10+
// eslint-disable-next-line no-constant-condition
11+
while (true) {
12+
try {
13+
const response = await axios.get(BLOCKCHAINS[blockchain].apiEndpoint, {
14+
params: {
15+
module: 'account',
16+
action: 'tokentx',
17+
address,
18+
sort: 'asc',
19+
offset,
20+
page,
21+
},
22+
});
23+
24+
if (response.status === 200) {
25+
transfers.push(...response.data.result);
26+
if (response.data.result.length < offset) return transfers;
27+
page++;
28+
} else {
29+
console.error('Error occurred while retrieving transactions.');
30+
}
31+
} catch (error) {
32+
console.error('Error occurred while making the request:', error);
33+
break;
34+
}
35+
}
36+
return transfers;
37+
};
38+
39+
const assignTransfersValue = async (transactions: Transaction[], ethPrice: number) => {
40+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
41+
const tokensPrice: any = {
42+
USDC: 1,
43+
USDT: 1,
44+
ZKUSD: 1,
45+
CEBUSD: 1,
46+
LUSD: 1,
47+
ETH: ethPrice,
48+
WETH: ethPrice,
49+
};
50+
for (const transaction of transactions) {
51+
if (!transaction.transfers) transaction.transfers = [];
52+
for (const transfer of transaction.transfers) {
53+
const value = Number(transfer.value) * 10 ** -Number(transfer.tokenDecimal);
54+
const tokenPrice = tokensPrice[transfer.tokenSymbol.toUpperCase()] || 0;
55+
transfer.transferPrice = value * tokenPrice;
56+
}
57+
transaction.transfers = transaction.transfers.filter((transfer: Transfer) => transfer.transferPrice);
58+
transaction.transfers = transaction.transfers.sort((a: Transfer, b: Transfer) => b.transferPrice - a.transferPrice);
59+
}
60+
};
61+
62+
const assignTransfers = async (transactions: Transaction[], address: string, blockchain: BlockchainType) => {
63+
const transfers = await getTransfers(address, blockchain);
64+
65+
for (const transfer of transfers) {
66+
for (const transaction of transactions) {
67+
if (transaction.hash === transfer.hash) {
68+
if (!transaction.transfers) transaction.transfers = [];
69+
transaction.transfers.push(transfer);
70+
}
71+
}
72+
}
73+
};
74+
75+
export { getTransfers, assignTransfersValue, assignTransfers };

src/blockchains/blockscout/types.ts

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
type Transfer = {
2+
blockNumber: string;
3+
timeStamp: string;
4+
hash: string;
5+
nonce: string;
6+
blockHash: string;
7+
transactionIndex: string;
8+
from: string;
9+
to: string;
10+
value: string;
11+
tokenName: string;
12+
tokenSymbol: string;
13+
tokenDecimal: string;
14+
gas: string;
15+
gasPrice: string;
16+
input: string;
17+
contractAddress: string;
18+
cumulativeGasUsed: string;
19+
gasUsed: string;
20+
confirmations: string;
21+
fee: string;
22+
l1BatchNumber: string;
23+
transactionType: string;
24+
transferPrice: number;
25+
};
26+
27+
type Transaction = {
28+
blockNumber: number;
29+
timeStamp: number;
30+
hash: string;
31+
nonce: number;
32+
blockHash: string;
33+
transactionIndex: number;
34+
from: string;
35+
to: string;
36+
value: number;
37+
gas: number;
38+
gasPrice: number;
39+
isError: number;
40+
txreceipt_status: number;
41+
input: string;
42+
contractAddress: string;
43+
cumulativeGasUsed: number;
44+
gasUsed: number;
45+
confirmations: number;
46+
methodId: string;
47+
functionName: string;
48+
transfers: Transfer[];
49+
};
50+
51+
export type { Transfer, Transaction };

src/blockchains/blockscout/wallet.ts

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { BlockchainType, Contract, Fee, Interaction, Volume, Wallet } from '../types.ts';
2+
import { getTransactions } from './transaction.ts';
3+
import { Transaction } from './types.ts';
4+
import { getTokens } from './token.ts';
5+
import { assignTransfers, assignTransfersValue } from './transfer.ts';
6+
import axios from 'axios';
7+
8+
const getInteraction = (address: string, transactions: Transaction[]): Interaction => {
9+
const interaction: Interaction = {
10+
change: 0,
11+
total: 0,
12+
};
13+
14+
for (const transaction of transactions) {
15+
const transactionDate = new Date(transaction.timeStamp * 1000);
16+
if (transaction.from.toLowerCase() === address.toLowerCase()) {
17+
interaction.total++;
18+
if (transactionDate > new Date(Date.now() - 1000 * 60 * 60 * 24 * 7)) {
19+
interaction.change++;
20+
}
21+
}
22+
}
23+
24+
return interaction;
25+
};
26+
27+
const getVolume = async (transactions: Transaction[], ethPrice: number): Promise<Volume> => {
28+
const volume: Volume = {
29+
change: 0,
30+
total: 0,
31+
};
32+
33+
for (const transaction of transactions) {
34+
const value = Number(transaction.value);
35+
volume.total += value * 10 ** -18 * ethPrice;
36+
if (transaction.transfers.length) volume.total += transaction.transfers[0].transferPrice;
37+
const transactionDate = new Date(transaction.timeStamp * 1000);
38+
if (transactionDate > new Date(Date.now() - 1000 * 60 * 60 * 24 * 7)) {
39+
volume.change += value;
40+
if (transaction.transfers.length) volume.total += transaction.transfers[0].transferPrice;
41+
}
42+
}
43+
44+
return volume;
45+
};
46+
47+
const getFee = async (address: string, transactions: Transaction[], ethPrice: number): Promise<Fee> => {
48+
const fee: Fee = {
49+
change: 0,
50+
total: 0,
51+
};
52+
53+
for (const transaction of transactions) {
54+
if (!transaction.gasPrice || !transaction.gasUsed || transaction.from.toLowerCase() !== address.toLowerCase())
55+
continue;
56+
fee.total += transaction.gasPrice * transaction.gasUsed;
57+
58+
const transactionDate = new Date(transaction.timeStamp * 1000);
59+
if (transactionDate > new Date(Date.now() - 1000 * 60 * 60 * 24 * 7)) {
60+
fee.change += transaction.gasPrice * transaction.gasUsed;
61+
}
62+
}
63+
64+
fee.total *= 10 ** -18 * ethPrice;
65+
fee.change *= 10 ** -18 * ethPrice;
66+
67+
return fee;
68+
};
69+
70+
const getContract = (transactions: Transaction[]): Contract => {
71+
const interactedContracts: Set<string> = new Set();
72+
const interactedContractsChange: Set<string> = new Set();
73+
74+
for (const transaction of transactions) {
75+
if (interactedContracts.has(transaction.to)) continue;
76+
interactedContracts.add(transaction.to);
77+
if (new Date(transaction.timeStamp).getTime() >= new Date().getTime() - 86400 * 7 * 1000) {
78+
interactedContractsChange.add(transaction.to);
79+
}
80+
}
81+
82+
return {
83+
change: interactedContractsChange.size,
84+
total: interactedContracts.size,
85+
};
86+
};
87+
88+
const getWallet = async (address: string, blockchain: BlockchainType): Promise<Wallet> => {
89+
const transactions: Transaction[] = await getTransactions(address, blockchain);
90+
const ethPrice =
91+
Number((await axios.get('https://api.etherscan.io/api?module=stats&action=ethprice')).data.result.ethusd) || 1900;
92+
93+
await assignTransfers(transactions, address, blockchain);
94+
await assignTransfersValue(transactions, ethPrice);
95+
96+
return {
97+
address,
98+
interaction: getInteraction(address, transactions),
99+
volume: await getVolume(transactions, ethPrice),
100+
fee: await getFee(address, transactions, ethPrice),
101+
contract: getContract(transactions),
102+
tokens: await getTokens(address, blockchain),
103+
additionalInfos: [],
104+
protocols: [],
105+
};
106+
};
107+
108+
export { getWallet };

src/blockchains/index.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { BlockchainType, Wallet } from './types.ts';
2+
import { getWallet as getBlockScoutWallet } from './blockscout/wallet.ts';
3+
4+
const blockchains = {
5+
zkSync: getBlockScoutWallet,
6+
scroll: getBlockScoutWallet,
7+
base: getBlockScoutWallet,
8+
};
9+
10+
const getWallet = async (address: string, blockchain: BlockchainType): Promise<Wallet> => {
11+
return await blockchains[blockchain](address, blockchain);
12+
};
13+
14+
export { getWallet };

0 commit comments

Comments
 (0)