Skip to content

Commit 4826fd7

Browse files
Update polymarket interpreter (#256)
1 parent 25adf22 commit 4826fd7

File tree

2 files changed

+70
-103
lines changed

2 files changed

+70
-103
lines changed

.changeset/bright-drinks-kick.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@3loop/transaction-interpreter': patch
3+
---
4+
5+
Update polymarket interpreter

packages/transaction-interpreter/interpreters/polymarket.ts

Lines changed: 65 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { InterpretedTransaction, InterpreterOptions } from '../src/types.js'
2-
import type { DecodedTransaction } from '@3loop/transaction-decoder'
3-
import { defaultEvent, assetsSent, assetsReceived } from './std.js'
4-
import { AssetTransfer } from '../src/types.js'
2+
import type { DecodedTransaction, Transfer } from '@3loop/transaction-decoder'
3+
import { defaultEvent, assetsSent, assetsReceived, formatNumber } from './std.js'
54

65
const POLYMARKET_API = 'https://gamma-api.polymarket.com'
76

@@ -85,154 +84,117 @@ export async function transformEvent(
8584
const newEvent = defaultEvent(event)
8685

8786
// Find OrderFilled events to determine transaction type
88-
const orderFilledEvents = event.interactions
89-
.sort((a: OrderFilledEvent, b: OrderFilledEvent) => a.event.logIndex - b.event.logIndex)
90-
.filter((i: OrderFilledEvent) => i.event.eventName === 'OrderFilled')
87+
const orderFilledEvents = event.interactions.filter((i: OrderFilledEvent) => i.event.eventName === 'OrderFilled')
88+
const orderMatchedEvent = event.interactions.find((i: any) => i.event.eventName === 'OrdersMatched')
9189

92-
if (orderFilledEvents.length === 0) {
90+
if (orderFilledEvents.length === 0 || !orderMatchedEvent) {
9391
return newEvent
9492
}
9593

96-
// Usually users are trading from the proxy wallets created by the polymarket
97-
// Signer is EOA owned by the user
98-
const signersAndProxies = event.methodCall?.params
99-
?.filter((p: OrderParam) => p?.name === 'takerOrder' || p?.name === 'makerOrders')
100-
.map((p: OrderParam) => ({
101-
signer:
102-
p?.name === 'takerOrder'
103-
? p?.components?.find((c) => c?.name === 'signer')?.value
104-
: p?.components?.[0]?.components?.find((c) => c?.name === 'signer')?.value,
105-
proxy:
106-
p?.name === 'takerOrder'
107-
? p?.components?.find((c) => c?.name === 'maker')?.value
108-
: p?.components?.[0]?.components?.find((c) => c?.name === 'maker')?.value,
109-
}))
110-
11194
// As a user we look at the input address first, otherwise just use the 1st maker address
112-
const userAddress = options?.interpretAsUserAddress || orderFilledEvents[0]?.event?.params?.maker?.toLowerCase()
95+
const userAddress =
96+
options?.interpretAsUserAddress?.toLowerCase() || orderFilledEvents[0]?.event?.params?.maker?.toLowerCase()
11397

11498
if (!userAddress) {
11599
return newEvent
116100
}
117101

118-
// Find the primary event for the user to determine the trade direction
119-
const primaryEvent = orderFilledEvents.find(
120-
(e: OrderFilledEvent) =>
121-
e.event?.params?.maker?.toLowerCase() === userAddress.toLowerCase() ||
122-
e.event?.params?.taker?.toLowerCase() === userAddress.toLowerCase(),
102+
// Usually users are trading from the proxy wallets created by the polymarket
103+
// Signer is EOA owned by the user
104+
const signersAndProxies = event.methodCall?.params
105+
?.filter((p: OrderParam) => p.name === 'takerOrder' || p.name === 'makerOrders')
106+
.map((p: OrderParam) => {
107+
const components = p.name === 'takerOrder' ? p.components : p.components?.[0]?.components
108+
return {
109+
signer: components?.find((c) => c.name === 'signer')?.value,
110+
proxy: components?.find((c) => c.name === 'maker')?.value,
111+
}
112+
})
113+
114+
// To interpret the case like with minting/burning new CT tokens,
115+
// we need to look at the final token transfers instead of the event params
116+
const conditionalTokenTransfers = event.transfers.filter(
117+
(t: Transfer) =>
118+
t.type === 'ERC1155' &&
119+
t.tokenId != null &&
120+
(t.to.toLowerCase() === userAddress.toLowerCase() || t.from.toLowerCase() === userAddress.toLowerCase()),
123121
)
124122

123+
const tokenId = conditionalTokenTransfers.length === 1 ? conditionalTokenTransfers[0].tokenId : undefined
124+
125+
// Find the primary event for the user to determine the trade direction
126+
const primaryEvent = orderFilledEvents.find((e: OrderFilledEvent) => {
127+
const params = e.event?.params
128+
const isUserInvolved = params?.maker?.toLowerCase() === userAddress || params?.taker?.toLowerCase() === userAddress
129+
const isTokenInvolved = params?.makerAssetId === tokenId || params?.takerAssetId === tokenId
130+
return isUserInvolved && isTokenInvolved
131+
})
132+
125133
if (!primaryEvent?.event?.params) {
126134
return newEvent
127135
}
128136

129-
const { maker, taker, makerAssetId, takerAssetId, makerAmountFilled, takerAmountFilled } = primaryEvent.event
130-
.params as {
137+
const { maker, makerAssetId, makerAmountFilled, takerAmountFilled, takerAssetId } = primaryEvent.event.params as {
131138
maker: string
132-
taker: string
133139
makerAssetId: string
134140
takerAssetId: string
135141
makerAmountFilled: number
136142
takerAmountFilled: number
137143
}
138144

139145
// Determine transaction type based on asset IDs
140-
const isMakerBuying = makerAssetId === '0'
141-
const isTakerBuying = takerAssetId === '0'
142-
const userIsMaker = maker.toLowerCase() === userAddress.toLowerCase()
143-
const userIsTaker = taker.toLowerCase() === userAddress.toLowerCase()
144-
145-
if (!userIsMaker && !userIsTaker) {
146-
return newEvent
147-
}
148-
149-
const userIsBuying = (userIsMaker && isMakerBuying) || (userIsTaker && isTakerBuying)
150-
const userIsSelling = (userIsMaker && !isMakerBuying) || (userIsTaker && !isTakerBuying)
146+
const userIsMaker = maker?.toLowerCase() === userAddress
147+
const userIsBuying = userIsMaker ? makerAssetId === '0' : makerAssetId !== '0'
148+
const tokenAmount = (userIsMaker ? takerAmountFilled : makerAmountFilled) / 1e6
149+
const costAmount = (userIsMaker ? makerAmountFilled : takerAmountFilled) / 1e6
151150

152151
const sent = assetsSent(event.transfers, userAddress)
153152
const received = assetsReceived(event.transfers, userAddress)
154-
155153
const context: Record<string, unknown> = {}
156-
const tokenId = [...sent, ...received].find(
157-
(t: AssetTransfer) => t.asset.type === 'ERC1155' && t.asset.tokenId != null,
158-
)?.asset.tokenId
159154

160155
if (tokenId) {
161156
const response = await fetch(`${POLYMARKET_API}/markets?clob_token_ids=${tokenId}`)
162-
163157
if (response.ok) {
164158
const data = await response.json()
165-
166159
if (data.length > 0) {
167-
const marketData = data[0]
168-
const outcomes = typeof marketData.outcomes === 'string' ? JSON.parse(marketData.outcomes) : marketData.outcomes
169-
const clobTokenIds =
170-
typeof marketData.clobTokenIds === 'string' ? JSON.parse(marketData.clobTokenIds) : marketData.clobTokenIds
160+
const { outcomes, clobTokenIds, conditionId, question, slug, negRisk } = data[0]
161+
const parsedOutcomes = typeof outcomes === 'string' ? JSON.parse(outcomes) : outcomes
162+
const parsedTokenIds = typeof clobTokenIds === 'string' ? JSON.parse(clobTokenIds) : clobTokenIds
171163

172164
context.marketData = {
173-
conditionId: marketData?.conditionId,
174-
question: marketData?.question,
175-
slug: marketData?.slug,
176-
negRisk: marketData?.negRisk,
177-
tokens: outcomes.map((outcome: any, index: number) => ({
165+
conditionId,
166+
question,
167+
slug,
168+
negRisk,
169+
tokens: parsedOutcomes.map((outcome: any, index: number) => ({
178170
outcome,
179-
tokenId: clobTokenIds[index],
171+
tokenId: parsedTokenIds[index],
180172
})),
181173
}
182174
}
183175
}
184176
}
185177

178+
const marketData = context.marketData as any
186179
const user = { address: userAddress, name: null }
187180
const baseContext = { proxyWallets: signersAndProxies, ...context }
181+
const lookupTokenId = userIsBuying ? takerAssetId : tokenId
182+
const outcome = marketData?.tokens.find((t: any) => t.tokenId === lookupTokenId)?.outcome
183+
const outcomeText = outcome ? `'${outcome}'` : 'outcome tokens'
184+
const marketText = marketData?.question ? ` in the market '${marketData.question}'` : ''
185+
const action = userIsBuying ? 'Bought' : 'Sold'
186+
const type = userIsBuying ? 'buy-outcome' : 'sell-outcome'
188187

189-
// Buying outcome tokens
190-
if (userIsBuying) {
191-
const [cost, amount] = [takerAmountFilled / 10 ** 6, makerAmountFilled / 10 ** 6]
192-
const outcome = (context.marketData as any)?.tokens.find((t: any) => t.tokenId === tokenId)?.outcome
193-
const question = (context.marketData as any)?.question
194-
195-
const outcomeText = outcome ? `'${outcome}'` : 'outcome tokens'
196-
const marketText = question ? ` in the market '${question}'` : ''
197-
198-
return {
199-
...newEvent,
200-
user,
201-
type: 'buy-outcome',
202-
action: `Bought ${amount} shares of ${outcomeText}${marketText} for ${cost} of USDC`,
203-
assetsSent: sent,
204-
assetsReceived: received,
205-
context: baseContext,
206-
}
207-
}
208-
209-
// Selling outcome tokens
210-
if (userIsSelling) {
211-
const [cost, amount] = [takerAmountFilled / 10 ** 6, makerAmountFilled / 10 ** 6]
212-
const outcome = (context.marketData as any)?.tokens.find((t: any) => t.tokenId === tokenId)?.outcome
213-
const question = (context.marketData as any)?.question
214-
215-
const outcomeText = outcome ? `'${outcome}'` : 'outcome tokens'
216-
const marketText = question ? ` in the market '${question}'` : ''
217-
218-
return {
219-
...newEvent,
220-
user,
221-
type: 'sell-outcome',
222-
action: `Sold ${amount} shares of ${outcomeText}${marketText} for ${cost} of USDC`,
223-
assetsSent: sent,
224-
assetsReceived: received,
225-
context: baseContext,
226-
}
227-
}
228-
229-
// TODO: Handle complex scenarios (minting/burning)
230-
// When both users are buying opposite outcomes, tokens are minted
231-
232-
// Fallback to default interpretation with assets
233188
return {
234189
...newEvent,
235-
action: 'Traded on Polymarket',
190+
user,
191+
type,
192+
action: `${action} ${formatNumber(tokenAmount.toString())} shares of ${outcomeText}${marketText} for ${formatNumber(
193+
costAmount.toString(),
194+
)} of USDC`,
195+
assetsSent: sent,
196+
assetsReceived: received,
197+
context: baseContext,
236198
}
237199
}
238200

0 commit comments

Comments
 (0)