-
Notifications
You must be signed in to change notification settings - Fork 177
/
MonitorProposalsOrderBook.ts
208 lines (179 loc) · 8.45 KB
/
MonitorProposalsOrderBook.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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
import { OptimisticOracleEthers, OptimisticOracleV2Ethers } from "@uma/contracts-node";
import { ProposePriceEvent } from "@uma/contracts-node/dist/packages/contracts-node/typechain/core/ethers/OptimisticOracleV2";
import { Networker } from "@uma/financial-templates-lib";
import { paginatedEventQuery } from "@uma/common";
import {
Logger,
MonitoringParams,
PolymarketWithEventData,
YES_OR_NO_QUERY,
formatPriceEvents,
getContractInstanceWithProvider,
getMarketKeyToStore,
getMarketsAncillary,
getNotifiedProposals,
getOrderFilledEvents,
getPolymarketMarkets,
getPolymarketOrderBooks,
getUnknownProposalKeyData,
storeNotifiedProposals,
PolymarketMarketWithAncillaryData,
FormattedProposePriceEvent,
} from "./common";
import { logProposalHighVolume, logProposalOrderBook, logUnknownMarketProposal } from "./MonitorLogger";
export async function monitorTransactionsProposedOrderBook(
logger: typeof Logger,
params: MonitoringParams
): Promise<void> {
const networker = new Networker(logger);
const currentBlockNumber = await params.provider.getBlockNumber();
const pastNotifiedProposals = await getNotifiedProposals();
const daysToLookup = 30; // This bot only looks back 1 day for proposals.
// These values are hardcoded for the Polygon network as this bot is only intended to run on Polygon.
const maxBlockLookBack = params.maxBlockLookBack;
const blockLookup = 43200 * daysToLookup; // 1 day in blocks on Polygon is 43200 blocks.
const searchConfig = {
fromBlock: currentBlockNumber - blockLookup < 0 ? 0 : currentBlockNumber - blockLookup,
toBlock: currentBlockNumber,
maxBlockLookBack,
};
const oo = await getContractInstanceWithProvider<OptimisticOracleEthers>("OptimisticOracle", params.provider);
const oov2 = await getContractInstanceWithProvider<OptimisticOracleV2Ethers>("OptimisticOracleV2", params.provider);
const eventsOo = await paginatedEventQuery<ProposePriceEvent>(oo, oo.filters.ProposePrice(), searchConfig);
const eventsOov2 = await paginatedEventQuery<ProposePriceEvent>(oov2, oov2.filters.ProposePrice(), searchConfig);
// Merge the events from both OO versions.
const proposalEvents = await formatPriceEvents([...eventsOo, ...eventsOov2]);
// Sort the proposalEvents array by event.timestamp in descending order so that the most recent events are first.
proposalEvents.sort((a, b) => Number(b.timestamp) - Number(a.timestamp));
const markets = await getPolymarketMarkets(params);
const marketsWithAncillary = await getMarketsAncillary(params, markets);
const proposedMarketsWithoutProposal: PolymarketMarketWithAncillaryData[] = [];
const isMatchingMarketAndProposalEvent = (
event: FormattedProposePriceEvent,
market: PolymarketWithEventData | PolymarketMarketWithAncillaryData
) => {
return (
event.requester.toLowerCase() === market.resolvedBy.toLowerCase() &&
event.ancillaryData.toLowerCase() === market.ancillaryData?.toLowerCase() &&
event.identifier === YES_OR_NO_QUERY
);
};
// Filter out markets that do not have a proposal event.
const marketsWithEventData: PolymarketWithEventData[] = marketsWithAncillary
.filter((market) => {
// If the market is resolved we don't need to check it.
if (market.resolved) return false;
// Events are sorted in descending order so we can stop searching once we find a proposal event for a market.
const found = proposalEvents.find((event) => isMatchingMarketAndProposalEvent(event, market));
// If we don't find a proposal event and the market is "proposed" then we need to log it as an unknown proposal.
if (!found && market.umaResolutionStatus && market.umaResolutionStatus.toLowerCase().includes("proposed")) {
proposedMarketsWithoutProposal.push(market);
}
return found;
})
.map((market) => {
const event = proposalEvents.find((event) => isMatchingMarketAndProposalEvent(event, market));
if (!event) throw new Error("Could not find event for market");
return {
...market,
...event,
};
})
.filter((market) => market.expirationTimestamp > Date.now() / 1000)
.filter((market) => !Object.keys(pastNotifiedProposals).includes(getMarketKeyToStore(market)));
// Log the markets that have been proposed but do not have a proposal event.
for (const market of proposedMarketsWithoutProposal) {
const unknownProposalData = getUnknownProposalKeyData(market.question);
const marketKey = getMarketKeyToStore(unknownProposalData);
// The first time we see a market proposal event missig we store it as not notified.
if (!Object.keys(pastNotifiedProposals).includes(marketKey)) {
await storeNotifiedProposals([{ ...unknownProposalData, notified: false }]);
continue;
}
const pastNotifiedProposal = pastNotifiedProposals[marketKey];
const now = Date.now() / 1000;
const timeSinceLastNotification = now - pastNotifiedProposal.notificationTimestamp;
// By default we only log unknown proposals if we don't find the proposal event after some time.
if (timeSinceLastNotification < params.unknownProposalNotificationInterval) continue;
// If we have already notified this market then we skip it.
if (pastNotifiedProposal.notified) continue;
await logUnknownMarketProposal(logger, {
adapterAddress: market.resolvedBy,
question: market.question,
questionID: market.questionID,
umaResolutionStatus: market.umaResolutionStatus,
endDate: market.endDate,
volumeNum: market.volumeNum,
});
await storeNotifiedProposals([{ ...unknownProposalData, notified: true }]);
}
// Get live order books for markets that have a proposal event.
const marketsWithOrderBooks = await getPolymarketOrderBooks(params, marketsWithEventData, networker);
// Get trades that have occurred since the proposal event
const marketsWithOrderBooksAndTrades = await getOrderFilledEvents(params, marketsWithOrderBooks);
const notifiedProposals = [];
console.log(`Checking proposal price for ${marketsWithOrderBooks.length} markets...`);
for (const market of marketsWithOrderBooksAndTrades) {
const proposedOutcome = market.proposedPrice === "1.0" ? 0 : 1;
const complementaryOutcome = proposedOutcome === 0 ? 1 : 0;
const thresholdAsks = Number(process.env["THRESHOLD_ASKS"]) || 1;
const thresholdBids = Number(process.env["THRESHOLD_BIDS"]) || 0;
const thresholdVolume = Number(process.env["THRESHOLD_VOLUME"]) || 500000;
const sellingWinnerSide = market.orderBooks[proposedOutcome].asks.find((ask) => ask.price < thresholdAsks);
const buyingLoserSide = market.orderBooks[complementaryOutcome].bids.find((bid) => bid.price > thresholdBids);
const soldWinnerSide = market.orderFilledEvents[proposedOutcome].filter(
(event) => event.type == "sell" && event.price < thresholdAsks
);
const boughtLoserSide = market.orderFilledEvents[complementaryOutcome].filter(
(event) => event.type == "buy" && event.price > thresholdBids
);
let notified = false;
if (market.volumeNum > thresholdVolume) {
await logProposalHighVolume(
logger,
{
proposedPrice: market.proposedPrice,
proposedOutcome: market.outcomes[proposedOutcome],
proposalTime: market.proposalTimestamp,
question: market.question,
tx: market.txHash,
volumeNum: market.volumeNum,
outcomes: market.outcomes,
expirationTimestamp: market.expirationTimestamp,
eventIndex: market.eventIndex,
},
params
);
if (!notified) {
notified = true;
notifiedProposals.push(market);
}
}
if (sellingWinnerSide || buyingLoserSide || soldWinnerSide.length > 0 || boughtLoserSide.length > 0) {
await logProposalOrderBook(
logger,
{
proposedPrice: market.proposedPrice,
proposedOutcome: market.outcomes[proposedOutcome],
proposalTime: market.proposalTimestamp,
question: market.question,
tx: market.txHash,
sellingWinnerSide,
buyingLoserSide,
soldWinnerSide,
boughtLoserSide,
outcomes: market.outcomes,
expirationTimestamp: market.expirationTimestamp,
eventIndex: market.eventIndex,
},
params
);
if (!notified) {
notified = true;
notifiedProposals.push(market);
}
}
}
await storeNotifiedProposals(notifiedProposals);
console.log("All proposals have been checked!");
}