A Prediction Market on the AT Protocol
1import 'dotenv/config';
2import { ZaCoCiaranCumulusBet, ZaCoCiaranCumulusMarket, ZaCoCiaranCumulusResolution } from '../../generated/typescript';
3import { is, type Did } from '@atcute/lexicons';
4import type { CreateCommit, DeleteCommit } from '@atcute/jetstream';
5import { marketsTable, betsTable, resolutionsTable } from '@/db'
6import { eq } from 'drizzle-orm';
7import { db } from '@/db';
8import { createUri, isBetCollection, isMarketCollection, isResolutionCollection } from './utils';
9
10export async function tryListMarkets() {
11 return await db.query.marketsTable.findMany({
12 columns: {
13 uri: true,
14 did: true,
15 cid: true,
16 question: true,
17 liquidity: true,
18 closesAt: true,
19 createdAt: true,
20 },
21 orderBy: (markets, { desc }) => [desc(markets.createdAt)],
22 with: {
23 bets: {
24 columns: {
25 uri: true,
26 did: true,
27 cid: true,
28 position: true,
29 createdAt: true,
30 }
31 },
32 resolution: {
33 columns: {
34 uri: true,
35 did: true,
36 cid: true,
37 answer: true,
38 createdAt: true,
39 }
40 }
41 },
42 })
43}
44
45export async function tryFindMarket(uri: string) {
46 return await db.query.marketsTable.findFirst({
47 columns: {
48 uri: true,
49 did: true,
50 cid: true,
51 question: true,
52 liquidity: true,
53 closesAt: true,
54 createdAt: true,
55 },
56 where: eq(marketsTable.uri, uri),
57 with: {
58 bets: {
59 columns: {
60 uri: true,
61 did: true,
62 cid: true,
63 position: true,
64 createdAt: true,
65 }
66 },
67 resolution: {
68 columns: {
69 uri: true,
70 did: true,
71 cid: true,
72 answer: true,
73 createdAt: true,
74 }
75 }
76 }
77 })
78}
79
80export async function tryFindMarketBets(uri: string) {
81 return await db.query.betsTable.findMany({
82 columns: {
83 uri: true,
84 did: true,
85 cid: true,
86 position: true,
87 createdAt: true,
88 },
89 where: eq(betsTable.marketUri, uri),
90 orderBy: (bets, { desc }) => [desc(bets.createdAt)],
91 with: {
92 market: {
93 columns: {
94 uri: true,
95 did: true,
96 cid: true,
97 question: true,
98 liquidity: true,
99 closesAt: true,
100 createdAt: true,
101 },
102 with: {
103 resolution: {
104 columns: {
105 uri: true,
106 did: true,
107 cid: true,
108 answer: true,
109 createdAt: true,
110 }
111 }
112 }
113 }
114 }
115 })
116}
117
118export async function tryFindMarketResolutions(uri: string) {
119 return await db.query.resolutionsTable.findFirst({
120 columns: {
121 uri: true,
122 did: true,
123 cid: true,
124 answer: true,
125 createdAt: true,
126 },
127 where: eq(resolutionsTable.marketUri, uri),
128 orderBy: (resolutions, { desc }) => [desc(resolutions.createdAt)],
129 with: {
130 market: {
131 columns: {
132 uri: true,
133 did: true,
134 cid: true,
135 question: true,
136 liquidity: true,
137 closesAt: true,
138 createdAt: true,
139 },
140 with: {
141 bets: {
142 columns: {
143 uri: true,
144 did: true,
145 cid: true,
146 position: true,
147 createdAt: true,
148 }
149 }
150 }
151 }
152 }
153 })
154}
155
156export async function tryCreateMarket(did: Did, { record, rev, rkey, cid }: CreateCommit) {
157 if (is(ZaCoCiaranCumulusMarket.mainSchema, record)) {
158 const uri = createUri(did, record.$type, rkey);
159 console.log("> Creating Market:", uri);
160
161 const { question, liquidity } = record;
162 const [closesAt, createdAt] = [new Date(record.closesAt), new Date(record.createdAt)];
163
164 const existing = await db.query.marketsTable.findFirst({ where: eq(marketsTable.uri, uri) });
165 if (existing) return;
166
167 const marketData: typeof marketsTable.$inferInsert = {
168 uri, did, rev, rkey, cid, question, liquidity, closesAt, record, createdAt,
169 };
170
171 await db.insert(marketsTable).values(marketData);
172 console.log("> Created Market!");
173 }
174}
175
176export async function tryDeleteMarket(did: Did, commit: DeleteCommit) {
177 if (!isMarketCollection(commit.collection)) return;
178 const uri = createUri(did, commit.collection, commit.rkey);
179 console.log("> Deleting Market:", uri);
180 await db.delete(marketsTable).where(eq(marketsTable.uri, uri))
181}
182
183export async function tryCreateBet(did: Did, { record, rev, rkey, cid }: CreateCommit) {
184 if (is(ZaCoCiaranCumulusBet.mainSchema, record)) {
185 const uri = createUri(did, record.$type, rkey);
186 console.log("> Creating Bet:", uri);
187
188 const { position, market } = record;
189 const createdAt = new Date(record.createdAt);
190
191 const existing = await db.query.betsTable.findFirst({ where: eq(betsTable.uri, uri) });
192 if (existing) return;
193
194 const resolution = await db.query.resolutionsTable.findFirst({ where: eq(resolutionsTable.uri, market.uri) });
195 if (resolution?.uri) return;
196
197 const indexedMarket = await db.query.marketsTable.findFirst({ where: eq(marketsTable.uri, market.uri), columns: { closesAt: true } });
198 if (!indexedMarket || (new Date() > indexedMarket.closesAt)) return;
199
200 const betData: typeof betsTable.$inferInsert = {
201 uri, did, rev, rkey, cid, position, marketUri: market.uri, record, createdAt
202 }
203
204 await db.insert(betsTable).values(betData);
205 console.log("> Created Bet!");
206 }
207}
208
209export async function tryDeleteBet(did: Did, commit: DeleteCommit) {
210 if (!isBetCollection(commit.collection)) return;
211 const uri = createUri(did, commit.collection, commit.rkey);
212 console.log("> Deleting Bet:", uri);
213 await db.delete(betsTable).where(eq(betsTable.uri, uri))
214}
215
216export async function tryCreateResolution(did: Did, { record, rev, rkey, cid }: CreateCommit) {
217 if (is(ZaCoCiaranCumulusResolution.mainSchema, record)) {
218 const uri = createUri(did, record.$type, rkey);
219 console.log("> Creating Resolution:", uri);
220
221 const { answer, market } = record;
222 const createdAt = new Date(record.createdAt);
223
224 const existing = await db.query.resolutionsTable.findFirst({ where: eq(resolutionsTable.uri, uri) });
225 if (existing) return;
226
227 const indexedMarket = await db.query.marketsTable.findFirst({ where: eq(marketsTable.uri, market.uri), columns: { did: true } });
228 if (!indexedMarket) return;
229
230 const canUserEdit = did === indexedMarket.did;
231 if (!canUserEdit) return;
232
233 const resolutionData: typeof resolutionsTable.$inferInsert = {
234 uri, did, rev, rkey, cid, answer, marketUri: market.uri, record, createdAt
235 }
236
237 await db.insert(resolutionsTable).values(resolutionData);
238 console.log("> Created Resolution!");
239 }
240}
241
242export async function tryDeleteResolution(did: Did, commit: DeleteCommit) {
243 if (!isResolutionCollection(commit.collection)) return;
244 const uri = createUri(did, commit.collection, commit.rkey);
245 console.log("> Deleting Resolution:", uri);
246 await db.delete(resolutionsTable).where(eq(resolutionsTable.uri, uri))
247}