-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.ts
170 lines (142 loc) · 5.46 KB
/
index.ts
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
160
161
162
163
164
165
166
167
168
169
170
import { createDfuseClient } from "@dfuse/client";
import { WebSocketLink } from "apollo-link-ws";
import ApolloClient from "apollo-client/ApolloClient";
import { InMemoryCache } from "apollo-cache-inmemory";
import nodeFetch from "node-fetch";
import ws from "ws";
import { getRexPoolQuery } from "./query";
import { getTypesFromAbi, createInitialTypes, hexToUint8Array, SerialBuffer, Type } from 'eosjs/dist/eosjs-serialize';
import { Api, JsonRpc } from 'eosjs';
import fetch from 'node-fetch'
import { JsSignatureProvider } from 'eosjs/dist/eosjs-jssig';
import { TextEncoder, TextDecoder } from 'util';
import { SubscriptionClient } from "subscriptions-transport-ws";
import debugFactory from "debug"
(global as any).WebSocket = ws;
(global as any).fetch = nodeFetch;
if (process.env.DFUSE_API_KEY == null) {
console.log("Missing DFUSE_API_KEY environment variable")
process.exit(1)
}
const debug = debugFactory("dfuse:example")
async function main() {
const signatureProvider = new JsSignatureProvider([]);
const rpc = new JsonRpc('https://mainnet.eos.dfuse.io', { fetch: fetch as any });
const api = new Api({ rpc, signatureProvider, textDecoder: new TextDecoder() as any, textEncoder: new TextEncoder() });
const dfuseClient = createDfuseClient({
apiKey: process.env.DFUSE_API_KEY!,
network: "mainnet",
});
const subscriptionClient = new SubscriptionClient(dfuseClient.endpoints.graphqlStreamUrl, {
reconnect: true,
lazy: true,
connectionCallback: (error?: any) => {
if (error) {
console.log("Problem initializing re-connection", error)
}
},
connectionParams: async () => {
const { token } = await dfuseClient!.getTokenInfo();
return {
Authorization: `Bearer ${token}`
}
}
}, ws);
subscriptionClient.onConnecting(() => { debug("Connecting") })
subscriptionClient.onConnected(() => { debug("Connected") })
subscriptionClient.onReconnecting(() => { debug("Reconnecting") })
subscriptionClient.onReconnected(() => { debug("Reconnected") })
subscriptionClient.onDisconnected(() => { debug("Disconnected") })
subscriptionClient.onError((error) => { debug("Error", error) })
const apolloClient = new ApolloClient({
cache: new InMemoryCache(),
link: new WebSocketLink(subscriptionClient),
});
// Would need to deal with the fact that this can be updated through
// the lifecycle of the chain, so at various blocks. Best thing probably is
// to first use dfuse SQE `action:setabi account:eosio data.account:eosio`
// to track all changes and load the ABI for all these blocks through dfuse API.
//
// Then use the right ABI for the right block range.
const abi = await api.getAbi("eosio")
const builtinTypes = createInitialTypes()
const types = getTypesFromAbi(builtinTypes, abi)
const rexPoolType = types.get("rex_pool")
if (rexPoolType === undefined) {
console.log("Type 'rex_pool' does not exist on 'eosio' ABI")
return
}
return new Promise((resolve, reject) => {
debug("Subscribing to REX feed")
let activeCursor = ""
let currentBlock = 0
apolloClient.subscribe({
query: getRexPoolQuery,
variables: {
cursor: activeCursor,
lowBlockNum: 61_500_000,
highBlockNum: 0,
}
}).subscribe({
start: (subscription) => { debug("Started", subscription) },
next: (value) => {
debug("Received message")
const message = value.data.searchTransactionsForward;
const trace = message.trace
if (currentBlock < trace.block.num) {
const matchingAction = trace.matchingActions[0]
const rexPoolDbOp = matchingAction.dbOps.find(isRexpoolDbOp)
const rexPoolRow = decodeRexpoolRow(rexPoolDbOp.newData, rexPoolType)
console.log(rexPoolRowToPricePoint(rexPoolRow) + " @ " + trace.block.timestamp)
currentBlock = trace.block.num
}
activeCursor = message.cursor
},
error: (error) => { reject(error) },
complete: () => { resolve() },
});
})
}
function rexPoolRowToPricePoint(rexPoolRow: RexpoolRow) {
if (rexPoolRow.version !== 0) {
throw Error(
`Expecting version 0 of rexpool object, got ${rexPoolRow.version}, code need to be adpated`
)
}
// This might proves problematic because JavaScript has 53 bits precisions
// so it might not be as accurate as possible. Hopefully, only the precision
// is affected and not the overall number. More testing is required to ensure
// we have the correct value.
const totalEos = assetToQuantity(rexPoolRow.total_lendable)
const totalRex = assetToQuantity(rexPoolRow.total_rex)
return totalRex / totalEos
}
function isRexpoolDbOp(dbOp: any) {
return dbOp.key.code === "eosio" && dbOp.key.scope === "eosio" && dbOp.key.table === "rexpool"
}
type RexpoolRow = {
version: number
total_lent: string
total_unlent: string
total_rent: string
total_lendable: string
total_rex: string
namebid_proceeds: string
loan_num: number
}
function decodeRexpoolRow(hexData: string, rexPoolType: Type): RexpoolRow {
const data = hexToUint8Array(hexData);
const buffer = new SerialBuffer({ textDecoder: new TextDecoder() as any, textEncoder: new TextEncoder() });
buffer.pushArray(data);
return rexPoolType.deserialize(buffer);
}
function assetToQuantity(asset: string) {
return parseFloat(asset.split(" ")[0])
}
main().then(() => {
console.log("Completed")
process.exit(0)
}).catch((error) => {
console.log("An error occurred", error)
process.exit(1)
})