Skip to content

Commit 4136b36

Browse files
Merge pull request #5 from RoboVault/feature/tutorials
Arkiver Tutorials
2 parents 5ecf6cc + ee426df commit 4136b36

23 files changed

+1766
-5
lines changed

.gitignore

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
.env
22
src/packages
3-
scratchpad.ts
3+
scratchpad.ts
4+
.hypothesis/
5+
6+
.DS_Store

cli/start/mod.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const action = async (
3838
Deno.addSignalListener('SIGABRT', cleanup)
3939

4040
const containerId =
41-
await $`docker run --name arkiver_mongodb -d -p 27017:27017 --rm mongodb/mongodb-community-server:6.0-ubi8`
41+
await $`docker run --name arkiver_mongodb -d -p 27017:27017 --env MONGO_INITDB_ROOT_USERNAME=admin --env MONGO_INITDB_ROOT_PASSWORD=password --rm mongo`
4242
.stdout('piped')
4343
await delay(3000) // wait for db to start
4444
}
@@ -85,12 +85,11 @@ export const action = async (
8585
},
8686
},
8787
})
88-
8988
const arkiver = new Arkiver({
9089
manifest,
9190
mongoConnection: options.db
9291
? options.mongoConnection ??
93-
'mongodb://localhost:27017'
92+
'mongodb://admin:password@localhost:27017'
9493
: undefined,
9594
rpcUrls,
9695
arkiveData: {

docs/docs/examples/_category_.json

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"label": "Examples",
3+
"position": 4,
4+
"link": {
5+
"type": "generated-index"
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
---
2+
sidebar_position: 3
3+
layout: example
4+
title: Block Handler - Vault Snapshots
5+
short_title: Vault Snapshots
6+
date: 2023-04-02 00:00:00
7+
lang: en
8+
index: 3
9+
type: example
10+
description: In this example we will look at how to capture snapshots of Yearn Vaults periodically
11+
short_desc: A simple arkiver job to snapshot yearn vaults
12+
---
13+
14+
In this example we will look at how to capture snapshots of Yearn Vaults periodically using the [Arkiver](https://github.com/RoboVault/robo-arkiver)'s Block Handler Data Source.
15+
16+
The Block Handler data source is usefull when there aren't suitable events to index. Share price and TVL is a good example, often you will want a periodic update snapshot.
17+
18+
## Requirements
19+
20+
- [Deno](https://deno.com/manual@v1.33.1/getting_started/installation)
21+
- [Arkiver CLI](https://robo-arkiver-docs.vercel.app/docs/getting-started/prerequisites#install-arkiver-cli)
22+
- [Docker Compose](https://docs.docker.com/get-docker/) to running the index job locally
23+
24+
## Create the Arkive Job
25+
26+
Let's start by creating the arkiver job with the Arkiver CLI. Run `arkiver init` and select the **block-handler-vaults** template.
27+
28+
```bash
29+
$ arkiver init
30+
31+
32+
▄████████ ▄████████ ▄█ ▄█▄ ▄█ ▄█ █▄ ▄████████ ▄████████
33+
███ ███ ███ ███ ███ ▄███▀ ███ ███ ███ ███ ███ ███ ███
34+
███ ███ ███ ███ ███▐██▀ ███▌ ███ ███ ███ █▀ ███ ███
35+
███ ███ ▄███▄▄▄▄██▀ ▄█████▀ ███▌ ███ ███ ▄███▄▄▄ ▄███▄▄▄▄██▀
36+
▀███████████ ▀▀███▀▀▀▀▀ ▀▀█████▄ ███▌ ███ ███ ▀▀███▀▀▀ ▀▀███▀▀▀▀▀
37+
███ ███ ▀███████████ ███▐██▄ ███ ███ ███ ███ █▄ ▀███████████
38+
███ ███ ███ ███ ███ ▀███▄ ███ ███ ███ ███ ███ ███ ███
39+
███ █▀ ███ ███ ███ ▀█▀ █▀ ▀██████▀ ██████████ ███ ███
40+
███ ███ ▀ ███ ███
41+
42+
43+
-----===== Arkiver v0.4.4 - https://arkiver.net =====-----
44+
45+
? Where should we create your arkive? (./cool-new-arkive) › ./vaults-arkive
46+
? Which template would you like to use? (event-wildcard) › block-handler-vaults
47+
? Are you using VSCode? (Yes) › Yes
48+
✔ Initialized arkive
49+
```
50+
51+
This arkive exmaple is prepared with everything we need.
52+
:::tip Let's take a look inside
53+
54+
Infomation overload? Skip to [Running the arkive](#run-indexing-locally)
55+
56+
### Entities
57+
58+
Entities specify how the indexed data is stored in the db and how it is accessed via the graphql interface.
59+
60+
We need only one entity, the VaultSnapshot entity containing:
61+
- Infomation about the vault
62+
- Vault share price
63+
- Block and Timestamp
64+
65+
```ts title="entities.ts"
66+
import { createEntity } from "../deps.ts";
67+
68+
export interface IVaultSnapshot {
69+
vault: string
70+
name: string
71+
symbol: string
72+
block: number
73+
timestamp: number
74+
sharePrice: number
75+
}
76+
77+
export const VaultSnapshot = createEntity<IVaultSnapshot>("VaultSnapshot", {
78+
vault: String,
79+
name: String,
80+
symbol: String,
81+
block: { type: Number, index: true },
82+
timestamp: { type: Number, index: true },
83+
sharePrice: { type: Number, index: true },
84+
});
85+
```
86+
87+
Note the usse of the `IVaultSnapshot`, this is optional but it provides typing of the entity.
88+
89+
### Manifest
90+
91+
The manifest configures the datasources of the index job. We
92+
93+
```ts title="manifest.ts"
94+
import { Manifest } from "https://deno.land/x/robo_arkiver@v0.4.4/mod.ts";
95+
import { VaultSnapshot } from "./entities/vault.ts";
96+
import { snapshotVault } from "./handlers/vault.ts";
97+
98+
const manifest = new Manifest("yearn-vaults");
99+
100+
manifest
101+
.addEntity(VaultSnapshot)
102+
.chain("mainnet")
103+
.addBlockHandler({ blockInterval: 1000, startBlockHeight: 12790000n, handler: snapshotVault })
104+
105+
export default manifest
106+
.build();
107+
108+
```
109+
110+
This manifest file configures the arkive job with one data source, a Block Handler. The block handler will periodically call the handler `snapshotVault` every 1000 blocks, startnig from block 12790000 on Ethereum. As per usual, the VaultSnapshot entity is added to the manifest to make it accessible via the GraphQL endpoint.
111+
112+
### Handler
113+
114+
`vault.ts` contains `snapshotVault()`, which is called every 1000 blocks. Let's break down what it's doing
115+
116+
```ts title="vault.ts"
117+
// Get vault info from cache or onchain
118+
const vaults = await Promise.all(liveVaults.map(async vault => {
119+
const contract = getContract({ address: vault.address, abi: YearnV2Abi, publicClient: client })
120+
return {
121+
address: vault.address,
122+
vault: { address: vault.address, abi: YearnV2Abi } as const,
123+
contract,
124+
name: await store.retrieve(`${vault.address}:name`, async () => await contract.read.name()),
125+
symbol: await store.retrieve(`${vault.address}:symbol`, async () => await contract.read.symbol()),
126+
decimals: await store.retrieve(`${vault.address}:decimals`, async () => await contract.read.decimals())
127+
}
128+
}))
129+
```
130+
131+
To kick things off we grab the name, symbol and decimals for each of the vaults. We use `store` here to cache the results so they're only called on the first call. The name and symbols is stored with the snaphots and decimals is used to format the share prices.
132+
133+
```ts title="vault.ts"
134+
135+
// fetch share price for this block
136+
const sharePrices = (await Promise.all(vaults.map(e => {
137+
return client.readContract({
138+
address: e.address,
139+
abi: YearnV2Abi,
140+
functionName: 'pricePerShare',
141+
blockNumber: block.number,
142+
})
143+
})))
144+
```
145+
146+
Next up is fetching the share price for each of the vaults with the `vault.pricePerShare()`. This returns a bigint and is formatted to a float further down.
147+
148+
```
149+
// Save the vault snapshots
150+
vaults.map((vault, i) => {
151+
return new VaultSnapshot({
152+
id: `${vault.address}-${Number(block.number)}`,
153+
block: Number(block.number),
154+
timestamp: Number(block.timestamp),
155+
vault: vault.address,
156+
sharePrice: parseFloat(formatUnits(sharePrices[i], Number(vault.decimals))),
157+
name: vault.name,
158+
symbol: vault.symbol,
159+
})
160+
}).map(e => e.save())
161+
```
162+
163+
And here we finally create and save the snapshots.
164+
:::
165+
166+
## Run Indexing Locally
167+
168+
Run the index job locally. This will spin up a database and the graphql server with docker for a fully-feature local dev environment
169+
> Optional: run `arkiver start --help` to see the options
170+
171+
```bash
172+
$ arkiver start .
173+
```
174+
175+
Desired output:
176+
```bash
177+
-----===== Arkiver v0.4.4 - https://arkiver.net =====-----
178+
179+
[0:yearn-vaults@v1.0] INFO Running Arkive - yearn-vaults
180+
🚀 Arkiver playground ready at http://0.0.0.0:4000/graphql
181+
[0:yearn-vaults@v1.0] INFO Running handlers for blocks 14399001-14402002 (3000 blocks - 3 items)
182+
[0:yearn-vaults@v1.0] INFO Processed blocks 14399001-14402002 in 3686.594ms (813.759 blocks/s - 0.814 items/s)
183+
[0:yearn-vaults@v1.0] INFO Running handlers for blocks 14402002-14405003 (3000 blocks - 3 items)
184+
[0:yearn-vaults@v1.0] INFO Processed blocks 14402002-14405003 in 3451.422ms (869.207 blocks/s - 0.869 items/s)
185+
...
186+
```
187+
188+
The index job is running, you can now navigate to http://0.0.0.0:4000/graphql to see the graphql explorer to experiment with the indexed data.
189+
190+
Note: Make sure you click the explorer icon on the left menu to see what queery options are available.
191+
192+
![GraphQL Explorer](./img/block-handler-vaults-snapshot-explorer.png)
193+
194+
## Explore the data
195+
196+
Now we can query the historic share price of the yearn vaults
197+
198+
```graphql
199+
query MyQuery {
200+
VaultSnapshots(sort: TIMESTAMP_DESC, filter: {symbol: "yvDAI"}) {
201+
sharePrice
202+
name
203+
symbol
204+
timestamp
205+
}
206+
}
207+
```
208+
209+
```json title="response"
210+
{
211+
"VaultSnapshots": [
212+
{
213+
"sharePrice": 1.0271595721786284,
214+
"name": "DAI yVault",
215+
"symbol": "yvDAI",
216+
"timestamp": 1648837864
217+
},
218+
{
219+
"sharePrice": 1.0271595721786284,
220+
"name": "DAI yVault",
221+
"symbol": "yvDAI",
222+
"timestamp": 1648824336
223+
},
224+
...
225+
]
226+
}
227+
```
228+
229+
## Deploy to Production
230+
231+
To deploy the arkive job to production you must sign in to your arkiver account. Run `arkiver help` for more infomation.
232+
233+
To deploy, simply run:
234+
235+
```bash
236+
arkiver deploy .
237+
```
238+
239+
This will package an deploy the arkive job. The name of the arkive job is specified in the manifiest file, in this example it's "yearn-vaults".
240+
241+
Navigate to `https://data.arkiver.net/$USERNAME/yearn-vaults/graphql`, where $USERNAME is your username, to see your custom, production-ready graphql endpoint.
242+
243+
Here is a deployment we prepared perviously:
244+
> https://data.arkiver.net/robolabs/yearn-vaults/graphql

0 commit comments

Comments
 (0)