|
1 | 1 | 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' |
5 | 4 |
|
6 | 5 | const POLYMARKET_API = 'https://gamma-api.polymarket.com' |
7 | 6 |
|
@@ -85,154 +84,117 @@ export async function transformEvent( |
85 | 84 | const newEvent = defaultEvent(event) |
86 | 85 |
|
87 | 86 | // 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') |
91 | 89 |
|
92 | | - if (orderFilledEvents.length === 0) { |
| 90 | + if (orderFilledEvents.length === 0 || !orderMatchedEvent) { |
93 | 91 | return newEvent |
94 | 92 | } |
95 | 93 |
|
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 | | - |
111 | 94 | // 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() |
113 | 97 |
|
114 | 98 | if (!userAddress) { |
115 | 99 | return newEvent |
116 | 100 | } |
117 | 101 |
|
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()), |
123 | 121 | ) |
124 | 122 |
|
| 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 | + |
125 | 133 | if (!primaryEvent?.event?.params) { |
126 | 134 | return newEvent |
127 | 135 | } |
128 | 136 |
|
129 | | - const { maker, taker, makerAssetId, takerAssetId, makerAmountFilled, takerAmountFilled } = primaryEvent.event |
130 | | - .params as { |
| 137 | + const { maker, makerAssetId, makerAmountFilled, takerAmountFilled, takerAssetId } = primaryEvent.event.params as { |
131 | 138 | maker: string |
132 | | - taker: string |
133 | 139 | makerAssetId: string |
134 | 140 | takerAssetId: string |
135 | 141 | makerAmountFilled: number |
136 | 142 | takerAmountFilled: number |
137 | 143 | } |
138 | 144 |
|
139 | 145 | // 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 |
151 | 150 |
|
152 | 151 | const sent = assetsSent(event.transfers, userAddress) |
153 | 152 | const received = assetsReceived(event.transfers, userAddress) |
154 | | - |
155 | 153 | 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 |
159 | 154 |
|
160 | 155 | if (tokenId) { |
161 | 156 | const response = await fetch(`${POLYMARKET_API}/markets?clob_token_ids=${tokenId}`) |
162 | | - |
163 | 157 | if (response.ok) { |
164 | 158 | const data = await response.json() |
165 | | - |
166 | 159 | 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 |
171 | 163 |
|
172 | 164 | 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) => ({ |
178 | 170 | outcome, |
179 | | - tokenId: clobTokenIds[index], |
| 171 | + tokenId: parsedTokenIds[index], |
180 | 172 | })), |
181 | 173 | } |
182 | 174 | } |
183 | 175 | } |
184 | 176 | } |
185 | 177 |
|
| 178 | + const marketData = context.marketData as any |
186 | 179 | const user = { address: userAddress, name: null } |
187 | 180 | 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' |
188 | 187 |
|
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 |
233 | 188 | return { |
234 | 189 | ...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, |
236 | 198 | } |
237 | 199 | } |
238 | 200 |
|
|
0 commit comments