diff --git a/moralis-scan/README.md b/moralis-scan/README.md
index 07d0b8ccf..caf77706f 100644
--- a/moralis-scan/README.md
+++ b/moralis-scan/README.md
@@ -16,6 +16,8 @@
[Part 7 - I Cloned Etherscan in 5 hours - Pagination Component](https://youtu.be/3x5eQdZo9i0)
+[Part 8 - I Cloned Etherscan in 5 hours - Token Transactions](https://youtu.be/lzjMcjn6XCk)
+
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
diff --git a/moralis-scan/src/App.js b/moralis-scan/src/App.js
index 0f7e22c25..b70be6960 100644
--- a/moralis-scan/src/App.js
+++ b/moralis-scan/src/App.js
@@ -4,7 +4,7 @@ import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import "./App.css";
import Home from "./components/Home";
import Header from "./Header";
-import Transactions from "./components/Transactions";
+import AddressResults from "./components/AddressResults";
Moralis.initialize(process.env.REACT_APP_MORALIS_APPLICATION_ID);
Moralis.serverURL = process.env.REACT_APP_MORALIS_SERVER_URL;
@@ -15,7 +15,7 @@ function App() {
-
+
diff --git a/moralis-scan/src/components/AddressResults.jsx b/moralis-scan/src/components/AddressResults.jsx
new file mode 100644
index 000000000..b276d3b98
--- /dev/null
+++ b/moralis-scan/src/components/AddressResults.jsx
@@ -0,0 +1,52 @@
+import React from 'react'
+import {
+ BrowserRouter as Router,
+ Switch,
+ Route,
+ NavLink,
+ useParams,
+} from "react-router-dom";
+import Transactions from './Transactions';
+
+export default function AddressResults() {
+ const {address} = useParams();
+ return (
+
diff --git a/moralis-scan/src/hooks/useTransType.js b/moralis-scan/src/hooks/useTransType.js
new file mode 100644
index 000000000..03505f14c
--- /dev/null
+++ b/moralis-scan/src/hooks/useTransType.js
@@ -0,0 +1,43 @@
+import { useEffect, useState } from "react"
+import { useParams } from "react-router";
+import { processTokenTransfer } from "../queries/tokenTransfer";
+import { processTransaction } from "../queries/transactions";
+
+const TransTypeOptions = {
+ main: {
+ cols: [
+ { colName: "Txn Hash", key: "hash" },
+ { colName: "Block", key: "block_number" },
+ { colName: "Age", key: "ago" },
+ { colName: "From", key: "from_address" },
+ { colName: "To", key: "to_address" },
+ { colName: "Value", key: "value" },
+ { colName: "Txn Fee", key: "gas_price" },
+ ],
+ methodName: "getTransactions",
+ postProcess: processTransaction,
+ },
+ erc20: {
+ cols: [
+ { colName: "Txn Hash", key: "transaction_hash" },
+ { colName: "Age", key: "ago" },
+ { colName: "From", key: "from_address" },
+ { colName: "To", key: "to_address" },
+ { colName: "Value", key: "value" },
+ { colName: "Token", key: "name" },
+ ],
+ methodName: "getTokenTranfers",
+ postProcess: processTokenTransfer,
+ },
+};
+
+export const useTransType = () => {
+ const [state, setState] = useState({});
+ const { transType } = useParams();
+
+ useEffect(()=>{
+ setState(TransTypeOptions[transType])
+ }, [transType])
+
+ return state;
+}
diff --git a/moralis-scan/src/queries/cloud-queries.js b/moralis-scan/src/queries/cloud-queries.js
new file mode 100644
index 000000000..dced33686
--- /dev/null
+++ b/moralis-scan/src/queries/cloud-queries.js
@@ -0,0 +1,89 @@
+import Moralis from "moralis";
+
+Moralis.Cloud.define("searchEthAddress", async function(request) {
+ const { address } = request.params
+ if (!address) {
+ return null
+ }
+
+ // find out if address is already watched
+ const query = new Moralis.Query("WatchedEthAddress");
+ query.equalTo("address", address)
+ const watchCount = await query.count();
+
+ if (watchCount > 0) {
+ // already watched don't sync again
+ return null;
+ }
+
+ return Moralis.Cloud.run("watchEthAddress", {address});
+});
+
+Moralis.Cloud.define("getTransactions", function(request) {
+ const {userAddress, pageSize, pageNum } = request.params;
+ const offset = (pageNum - 1) * pageSize;
+
+ const query = new Moralis.Query("EthTransactions");
+ query.equalTo("from_address", userAddress);
+ query.descending("block_number");
+ query.withCount();
+ query.skip(offset)
+ query.limit(pageSize);
+
+ return query.find();
+});
+
+Moralis.Cloud.define("getTokenTranfers", async (request) => {
+ const {userAddress, pageSize, pageNum } = request.params;
+ const offset = (pageNum - 1) * pageSize;
+ const output = {
+ results: [],
+ count: 0,
+ };
+
+ // count results
+ const matchPipeline = {
+ match: {
+ $expr: {
+ $or: [
+ { $eq: ["$from_address", userAddress] },
+ { $eq: ["$to_address", userAddress] },
+ ],
+ },
+ },
+ sort: { block_number: -1 },
+ count: "count",
+ };
+ const query = new Moralis.Query("EthTokenTransfers");
+ const countResult = await query.aggregate(matchPipeline);
+ output.count = countResult[0].count;
+
+ // get page results
+ const lookupPipeline = {
+ ...matchPipeline,
+ skip: offset,
+ limit: pageSize,
+ lookup: {
+ from: "EthTokenBalance",
+ let: { tokenAddress: "$token_address", userAddress },
+ pipeline: [
+ {
+ $match: {
+ $expr: {
+ $and: [
+ { $eq: ["$token_address", "$$tokenAddress"] },
+ { $eq: ["$address", "$$userAddress"] },
+ ],
+ },
+ },
+ },
+ ],
+ as: "EthTokenBalance",
+ },
+ unwind: "$EthTokenBalance",
+ };
+ delete lookupPipeline.count;
+
+ output.results = await query.aggregate(lookupPipeline);
+ return output;
+});
diff --git a/moralis-scan/src/queries/tokenTransfer.js b/moralis-scan/src/queries/tokenTransfer.js
new file mode 100644
index 000000000..d18ddb35c
--- /dev/null
+++ b/moralis-scan/src/queries/tokenTransfer.js
@@ -0,0 +1,14 @@
+import { agoTxt, getEllipsisTxt, tokenValueTxt } from "./utils";
+
+export const processTokenTransfer = (r) => ({
+ transaction_hash: getEllipsisTxt(r.transaction_hash),
+ ago: agoTxt(r.block_timestamp.valueOf()),
+ from_address: getEllipsisTxt(r.from_address),
+ to_address: getEllipsisTxt(r.to_address),
+ value: tokenValueTxt(
+ r.value,
+ r.EthTokenBalance?.decimals,
+ r.EthTokenBalance?.symbol
+ ),
+ name: r.EthTokenBalance?.name || "",
+ });