Skip to content

Commit dae5896

Browse files
authored
Remove Ponder: read cohort events from the chain (#4)
1 parent 014f27b commit dae5896

10 files changed

Lines changed: 135 additions & 137 deletions

File tree

builderList.ts

Lines changed: 0 additions & 12 deletions
This file was deleted.

packages/hardhat/deploy/00_deploy_your_contract.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
//import { builderList } from "../../../builderList";
2-
//import { ethers } from "ethers";
31
import { DeployFunction } from "hardhat-deploy/types";
42
import { HardhatRuntimeEnvironment } from "hardhat/types";
53

@@ -32,12 +30,6 @@ const deployYourContract: DeployFunction = async function (hre: HardhatRuntimeEn
3230
// automatically mining the contract deployment transaction. There is no effect on live networks.
3331
autoMine: true,
3432
});
35-
36-
////const yourContract = await hre.ethers.getContract("SandGardenStreams", deployer);
37-
38-
//console.log("🫡 adding batch of builders");
39-
//const builderStakes = Array(builderList.length).fill(ethers.utils.parseEther("1.5"));
40-
//await yourContract.addBatch(builderList, builderStakes);
4133
};
4234

4335
export default deployYourContract;
Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
1-
import { gql, useQuery } from "urql";
2-
import contracts from "~~/generated/hardhat_contracts";
3-
4-
const BuildersQuery = gql`
5-
query Builders($cohortAddress: String!) {
6-
cohortBuilders(where: { cohortContractAddress: $cohortAddress }, orderBy: "timestamp", orderDirection: "desc") {
7-
items {
8-
id
9-
}
10-
}
11-
}
12-
`;
1+
import { useEffect, useState } from "react";
2+
import { getCohortEvents } from "~~/services/web3/cohortEvents";
133

144
export const useAddBuilderEvents = () => {
15-
const [{ data: addBuilderEventsData, fetching: isLoading }] = useQuery({
16-
query: BuildersQuery,
17-
variables: {
18-
cohortAddress: contracts[1][0].contracts.SandGardenStreams.address,
19-
},
20-
});
5+
const [data, setData] = useState<string[]>([]);
6+
const [isLoading, setIsLoading] = useState(true);
7+
8+
useEffect(() => {
9+
let isMounted = true;
10+
11+
getCohortEvents()
12+
.then(events => {
13+
if (isMounted) setData(events.builders);
14+
})
15+
.catch(error => console.error("Error getting cohort builder events: ", error))
16+
.finally(() => {
17+
if (isMounted) setIsLoading(false);
18+
});
19+
20+
return () => {
21+
isMounted = false;
22+
};
23+
}, []);
2124

22-
const data = addBuilderEventsData?.cohortBuilders.items || [];
2325
return { data, isLoading };
2426
};
Lines changed: 18 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,26 @@
1-
import { gql, useQuery } from "urql";
2-
import contracts from "~~/generated/hardhat_contracts";
3-
4-
const WithdrawalsQuery = gql`
5-
query Withdrawls($cohortAddress: String!) {
6-
cohortWithdrawals(where: { cohortContractAddress: $cohortAddress }, orderBy: "timestamp", orderDirection: "desc") {
7-
items {
8-
reason
9-
builder
10-
amount
11-
timestamp
12-
id
13-
}
14-
}
15-
}
16-
`;
1+
import { useEffect, useState } from "react";
2+
import { WithdrawEvent, getCohortEvents } from "~~/services/web3/cohortEvents";
173

184
export const useCohortWithdrawEvents = () => {
19-
const [{ data: newWithdrawEventsData, fetching: isLoadingNew }] = useQuery({
20-
query: WithdrawalsQuery,
21-
variables: {
22-
cohortAddress: contracts[1][0].contracts.SandGardenStreams.address,
23-
},
24-
});
25-
26-
// const [{ data: oldWithdrawEventsData, fetching: isLoadingOld }] = useQuery({
27-
// query: WithdrawalsQuery,
28-
// variables: {
29-
// cohortAddress: contracts[10][0].contracts._SandGardenStreamsOld.address,
30-
// },
31-
// });
5+
const [data, setData] = useState<WithdrawEvent[]>([]);
6+
const [isLoading, setIsLoading] = useState(true);
327

33-
const newContractWithdrawEvents = newWithdrawEventsData?.cohortWithdrawals.items || [];
34-
//const oldContractWithdrawEvents = oldWithdrawEventsData?.cohortWithdrawals || [];
8+
useEffect(() => {
9+
let isMounted = true;
3510

36-
const data = [...newContractWithdrawEvents /*, ...oldContractWithdrawEvents*/];
11+
getCohortEvents()
12+
.then(events => {
13+
if (isMounted) setData(events.withdrawals);
14+
})
15+
.catch(error => console.error("Error getting cohort withdraw events: ", error))
16+
.finally(() => {
17+
if (isMounted) setIsLoading(false);
18+
});
3719

38-
const isLoading = isLoadingNew; // || isLoadingOld;
20+
return () => {
21+
isMounted = false;
22+
};
23+
}, []);
3924

4025
return { data, isLoading };
4126
};

packages/nextjs/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
"react-dom": "^18.2.0",
3131
"react-fast-marquee": "^1.3.5",
3232
"react-hot-toast": "^2.4.0",
33-
"urql": "^4.0.6",
3433
"use-debounce": "^8.0.4",
3534
"usehooks-ts": "^2.7.2",
3635
"wagmi": "^0.12.0",

packages/nextjs/pages/_app.tsx

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { RainbowKitProvider } from "@rainbow-me/rainbowkit";
55
import "@rainbow-me/rainbowkit/styles.css";
66
import NextNProgress from "nextjs-progressbar";
77
import { Toaster } from "react-hot-toast";
8-
import { Client, Provider as URQLProvider, cacheExchange, fetchExchange } from "urql";
98
import { WagmiConfig } from "wagmi";
109
import { Footer } from "~~/components/Footer";
1110
import { Header } from "~~/components/Header";
@@ -18,11 +17,6 @@ import "~~/styles/globals.css";
1817

1918
const shareTechMono = Share_Tech_Mono({ subsets: ["latin"], weight: "400" });
2019

21-
const urqlClient = new Client({
22-
url: "https://bg-ponder-indexer-production.up.railway.app/",
23-
exchanges: [cacheExchange, fetchExchange],
24-
});
25-
2620
const ScaffoldEthApp = ({ Component, pageProps }: AppProps) => {
2721
const price = useEthPrice();
2822
const setEthPrice = useAppStore(state => state.setEthPrice);
@@ -36,18 +30,16 @@ const ScaffoldEthApp = ({ Component, pageProps }: AppProps) => {
3630
return (
3731
<WagmiConfig client={wagmiClient}>
3832
<NextNProgress color="#c913ff" />
39-
<URQLProvider value={urqlClient}>
40-
<RainbowKitProvider chains={appChains.chains} avatar={BlockieAvatar}>
41-
<div className={`flex flex-col min-h-screen ${shareTechMono.className}`}>
42-
<Header />
43-
<main className="relative flex flex-col flex-1">
44-
<Component {...pageProps} />
45-
</main>
46-
<Footer />
47-
</div>
48-
<Toaster />
49-
</RainbowKitProvider>
50-
</URQLProvider>
33+
<RainbowKitProvider chains={appChains.chains} avatar={BlockieAvatar}>
34+
<div className={`flex flex-col min-h-screen ${shareTechMono.className}`}>
35+
<Header />
36+
<main className="relative flex flex-col flex-1">
37+
<Component {...pageProps} />
38+
</main>
39+
<Footer />
40+
</div>
41+
<Toaster />
42+
</RainbowKitProvider>
5143
</WagmiConfig>
5244
);
5345
};

packages/nextjs/pages/members.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,8 @@ const Members: NextPage = () => {
3232
const { data: addBuilderEvents, isLoading: isLoadingBuilderEvents } = useAddBuilderEvents();
3333

3434
useEffect(() => {
35-
if (addBuilderEvents && addBuilderEvents.length > 0) {
36-
const fetchedBuilderList = addBuilderEvents.map((event: any) => event.id.split("-")[0]);
37-
setBuilderList(fetchedBuilderList);
35+
if (addBuilderEvents.length > 0) {
36+
setBuilderList(addBuilderEvents);
3837
}
3938
}, [addBuilderEvents]);
4039

packages/nextjs/scaffold.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const scaffoldConfig = {
3030

3131
contracts: {
3232
SandGardenStreams: {
33-
fromBlock: 1,
33+
fromBlock: 20291636,
3434
},
3535
},
3636
} satisfies ScaffoldConfig;
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { ethers } from "ethers";
2+
import { mainnet } from "wagmi/chains";
3+
import contracts from "~~/generated/hardhat_contracts";
4+
import scaffoldConfig from "~~/scaffold.config";
5+
6+
const { address: cohortAddress, abi } = contracts[1][0].contracts.SandGardenStreams;
7+
8+
export type WithdrawEvent = {
9+
id: string;
10+
builder: string;
11+
amount: string;
12+
reason: string;
13+
timestamp: number;
14+
};
15+
16+
export type CohortEvents = {
17+
withdrawals: WithdrawEvent[];
18+
builders: string[];
19+
};
20+
21+
const fetchCohortEvents = async (): Promise<CohortEvents> => {
22+
const provider = new ethers.providers.JsonRpcBatchProvider(
23+
`${mainnet.rpcUrls.alchemy.http[0]}/${scaffoldConfig.alchemyApiKey}`,
24+
);
25+
const contractInterface = new ethers.utils.Interface(abi);
26+
27+
const logs = await provider.getLogs({
28+
address: cohortAddress,
29+
fromBlock: scaffoldConfig.contracts.SandGardenStreams.fromBlock,
30+
toBlock: "latest",
31+
});
32+
33+
const withdrawLogs: { log: ethers.providers.Log; args: ethers.utils.Result }[] = [];
34+
const builders: string[] = [];
35+
36+
logs.forEach(log => {
37+
let parsed;
38+
try {
39+
parsed = contractInterface.parseLog(log);
40+
} catch (e) {
41+
// Event not in the ABI (or malformed). Skip it.
42+
return;
43+
}
44+
45+
if (parsed.name === "Withdraw") {
46+
withdrawLogs.push({ log, args: parsed.args });
47+
} else if (parsed.name === "AddBuilder" && !builders.includes(parsed.args.to)) {
48+
builders.push(parsed.args.to);
49+
}
50+
});
51+
52+
// Withdraw has no timestamp, so we read it from the block.
53+
const blockNumbers = Array.from(new Set(withdrawLogs.map(({ log }) => log.blockNumber)));
54+
const blocks = await Promise.all(blockNumbers.map(blockNumber => provider.getBlock(blockNumber)));
55+
const timestampByBlock = new Map(blocks.map(block => [block.number, block.timestamp]));
56+
57+
const withdrawals = withdrawLogs
58+
.map(({ log, args }) => ({
59+
id: `${log.transactionHash}-${log.logIndex}`,
60+
builder: args.to as string,
61+
amount: ethers.utils.formatEther(args.amount),
62+
reason: args.reason as string,
63+
timestamp: timestampByBlock.get(log.blockNumber) ?? 0,
64+
}))
65+
.sort((a, b) => b.timestamp - a.timestamp);
66+
67+
return { withdrawals, builders };
68+
};
69+
70+
let cohortEventsPromise: Promise<CohortEvents> | null = null;
71+
72+
/**
73+
* Reads every cohort event from the chain once per browser session.
74+
*/
75+
export const getCohortEvents = () => {
76+
if (!cohortEventsPromise) {
77+
cohortEventsPromise = fetchCohortEvents().catch(error => {
78+
cohortEventsPromise = null;
79+
throw error;
80+
});
81+
}
82+
return cohortEventsPromise;
83+
};

yarn.lock

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,6 @@ __metadata:
55
version: 6
66
cacheKey: 8
77

8-
"@0no-co/graphql.web@npm:^1.0.1":
9-
version: 1.0.4
10-
resolution: "@0no-co/graphql.web@npm:1.0.4"
11-
peerDependencies:
12-
graphql: ^14.0.0 || ^15.0.0 || ^16.0.0
13-
peerDependenciesMeta:
14-
graphql:
15-
optional: true
16-
checksum: d415fb2f063a024e2d382e8dc5e66929d0d9bf94f2c22e03cde201dc2fa03e15d21274dbe5c23a26ce016a4cac3db93ad99fb454de76fd94bc1600fd7062eebe
17-
languageName: node
18-
linkType: hard
19-
208
"@babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.18.6":
219
version: 7.18.6
2210
resolution: "@babel/code-frame@npm:7.18.6"
@@ -1771,7 +1759,6 @@ __metadata:
17711759
react-hot-toast: ^2.4.0
17721760
tailwindcss: ^3.1.8
17731761
typescript: ^4.9.5
1774-
urql: ^4.0.6
17751762
use-debounce: ^8.0.4
17761763
usehooks-ts: ^2.7.2
17771764
vercel: ^28.15.1
@@ -2864,16 +2851,6 @@ __metadata:
28642851
languageName: node
28652852
linkType: hard
28662853

2867-
"@urql/core@npm:^4.2.0":
2868-
version: 4.2.2
2869-
resolution: "@urql/core@npm:4.2.2"
2870-
dependencies:
2871-
"@0no-co/graphql.web": ^1.0.1
2872-
wonka: ^6.3.2
2873-
checksum: 5ac5b1101f2db160968c945529d16d640b7761a4edbb320abe2bd31cab83cce424db6f833e368885882da75c5f9bf94f408ff0003662d6c845bbd6d6e660004d
2874-
languageName: node
2875-
linkType: hard
2876-
28772854
"@vanilla-extract/css@npm:1.9.1":
28782855
version: 1.9.1
28792856
resolution: "@vanilla-extract/css@npm:1.9.1"
@@ -12812,18 +12789,6 @@ __metadata:
1281212789
languageName: node
1281312790
linkType: hard
1281412791

12815-
"urql@npm:^4.0.6":
12816-
version: 4.0.6
12817-
resolution: "urql@npm:4.0.6"
12818-
dependencies:
12819-
"@urql/core": ^4.2.0
12820-
wonka: ^6.3.2
12821-
peerDependencies:
12822-
react: ">= 16.8.0"
12823-
checksum: 1af8471991be3ddd897e8268bc359e605f6a65bae8ff0b624764fb9fb2c555f678ad4f119220d84c279e3bb08418b4e7f0b256b73e766824cb55b07e3a8736a2
12824-
languageName: node
12825-
linkType: hard
12826-
1282712792
"use-callback-ref@npm:^1.3.0":
1282812793
version: 1.3.0
1282912794
resolution: "use-callback-ref@npm:1.3.0"
@@ -13137,13 +13102,6 @@ __metadata:
1313713102
languageName: node
1313813103
linkType: hard
1313913104

13140-
"wonka@npm:^6.3.2":
13141-
version: 6.3.4
13142-
resolution: "wonka@npm:6.3.4"
13143-
checksum: 6bb57955cb2982fb469a7824484e6854b436f89a7f10b6a981348789d88fbc944665771adc4cc404f62416417eb47ab2b8657d898e5301ccd4a53eaac6a10508
13144-
languageName: node
13145-
linkType: hard
13146-
1314713105
"word-wrap@npm:^1.2.3, word-wrap@npm:~1.2.3":
1314813106
version: 1.2.3
1314913107
resolution: "word-wrap@npm:1.2.3"

0 commit comments

Comments
 (0)