-
Notifications
You must be signed in to change notification settings - Fork 5
/
App.tsx
159 lines (149 loc) · 5.54 KB
/
App.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import './App.css';
import {fromJSProcessor, JSProcessor} from 'ethereum-indexer-js-processor';
import {createIndexerState, keepStateOnIndexedDB} from 'ethereum-indexer-browser';
import {connect} from './utils/web3';
import react from 'react';
// we need the contract info
// the abi will be used by the processor to have its type generated, allowing you to get type-safety
// the adress will be given to the indexer, so it index only this contract
// the startBlock field allow to tell the indexer to start indexing from that point only
// here it is the block at which the contract was deployed
const contract = {
abi: [
{
anonymous: false,
inputs: [
{
indexed: true,
name: 'user',
type: 'address',
},
{
indexed: false,
name: 'message',
type: 'string',
},
],
name: 'MessageChanged',
type: 'event',
},
],
address: '0x21d366ee3BbF67AB057c517380D37E54fFd9dfC0',
startBlock: 3040661,
} as const;
// we define the type of the state computed by the processor
// we can also declare it inline in the generic type of JSProcessor
type State = {greetings: {account: `0x${string}`; message: string}[]};
// the processor is given the type of the ABI as Generic type to get generated
// it also specify the type which represent the current state
const processor: JSProcessor<typeof contract.abi, State> = {
// you can set a version, ideally you would generate it so that it changes for each change
// when a version changes, the indexer will detect that and clear the state
// if it has the event stream cached, it will repopulate the state automatically
version: '1.0.1',
// this function set the starting state
// this allow the app to always have access to a state, no undefined needed
construct() {
return {greetings: []};
},
// each event has an associated on<EventName> function which is given both the current state and the typed event
// each event's argument can be accessed via the `args` field
// it then modify the state as it wishes
// behind the scene, the JSProcessor will handle reorg by reverting and applying new events automatically
onMessageChanged(state, event) {
const greetingFound = state.greetings.find((v) => v.account === event.args.user);
if (greetingFound) {
greetingFound.message = event.args.message;
} else {
state.greetings.push({
message: event.args.message,
account: event.args.user,
});
}
},
};
// we setup the indexer via a call to `createIndexerState`
// this setup a set of observable (subscribe pattern)
// including one for the current state (computed by the processor above)
// and one for the syncing
// we then call `.withHooks(react)` to transform these observable in react hooks ready to be used.
const {init, useState, useSyncing, startAutoIndexing} = createIndexerState(fromJSProcessor(processor)(), {
keepState: keepStateOnIndexedDB('basic') as any,
}).withHooks(react);
// we now need to get a handle on a ethereum provider
// for this app we are simply using window.ethereum
const ethereum = (window as any).ethereum;
// but to not trigger a metamask popup right away we wrap that in a function to be called via a click of a button
function start() {
if (ethereum) {
// here we first connect it to the chain of our choice and then initialise the indexer
// see ./utils/web3
connect(ethereum, {
chain: {
chainId: '11155111',
chainName: 'Sepolia',
rpcUrls: ['https://rpc.sepolia.org'],
nativeCurrency: {name: 'Sepolia Ether', symbol: 'SEP', decimals: 18},
blockExplorerUrls: ['https://sepolia.etherscan.io'],
},
}).then(({ethereum}) => {
// we already setup the processor
// now we need to initialise the indexer with
// - an EIP-1193 provider (window.ethereum here)
// - source config which includes the chainId and the list of contracts (abi,address. startBlock)
init({
provider: ethereum,
source: {chainId: '11155111', contracts: [contract]},
}).then(() => {
// this automatically index on a timer
// alternatively you can call `indexMore` or `indexMoreAndCatchupIfNeeded`, both available from the return value of `createIndexerState`
// startAutoIndexing is easier but manually calling `indexMore` or `indexMoreAndCatchupIfNeeded` is better
// this is because you can call them for every `newHeads` eth_subscribe message
startAutoIndexing();
});
});
}
}
function App() {
// we use the hooks to get the latest state and make sure react update the values as they changes
const $state = useState();
const $syncing = useSyncing();
if (!ethereum) {
return (
<div className="App">
<h1>Indexing a basic example</h1>
<p>To test this app, you need to have a ethereum wallet installed</p>
</div>
);
}
// we have various variable to check the status of the indexer
// here we can act on whether the indexer is still waiting to be provided an EIP-1193 provider
if ($syncing.waitingForProvider) {
return (
<div className="App">
<h1>Indexing a basic example</h1>
<button onClick={start} style={{backgroundColor: '#45ffbb', color: 'black'}}>
Start
</button>
</div>
);
}
// here we add a progress bar indicating the progress of the indexer
return (
<div className="App">
<h1>Indexing a basic example</h1>
<p>{$syncing.lastSync?.syncPercentage || 0}</p>
{$syncing.lastSync ? (
<progress value={($syncing.lastSync.syncPercentage || 0) / 100} style={{width: '100%'}} />
) : (
<p>Please wait...</p>
)}
<div>
{$state.greetings.map((greeting) => (
<p key={greeting.account}>{greeting.message}</p>
))}
</div>
</div>
);
}
export default App;