Skip to content

Commit db57e2e

Browse files
committed
Refactor std in interpreter
1 parent 5f31c9e commit db57e2e

File tree

4 files changed

+161
-103
lines changed

4 files changed

+161
-103
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { categorizedDefaultEvent } from './std.js'
2+
import type { InterpretedTransaction } from '@/types.js'
3+
import type { DecodedTransaction } from '@3loop/transaction-decoder'
4+
5+
export function transformEvent(event: DecodedTransaction): InterpretedTransaction {
6+
const newEvent = categorizedDefaultEvent(event)
7+
8+
return newEvent
9+
}
10+
11+
export const contracts = [
12+
//Exchange Proxy
13+
'1:0xDef1C0ded9bec7F1a1670819833240f027b25EfF',
14+
'10:0xDef1C0ded9bec7F1a1670819833240f027b25EfF',
15+
'42161:0xDef1C0ded9bec7F1a1670819833240f027b25EfF',
16+
'8453:0xDef1C0ded9bec7F1a1670819833240f027b25EfF',
17+
]

packages/transaction-interpreter/interpreters/blur.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { displayPayments, processNftTransfers, defaultEvent } from './std.js'
1+
import { displayAssets, processNftTransfers, defaultEvent } from './std.js'
22
import type { InterpretedTransaction } from '@/types.js'
33
import type { DecodedTransaction } from '@3loop/transaction-decoder'
44

@@ -16,7 +16,7 @@ export function transformEvent(event: DecodedTransaction): InterpretedTransactio
1616

1717
const collection = nftTransfers[0].name ?? ''
1818
const numberOfNfts = nftTransfers.length > 1 ? ` ${nftTransfers.length} ${collection} NFTS` : ` 1 ${collection} NFT`
19-
const payment = displayPayments(erc20Payments, nativePayments)
19+
const payment = displayAssets([...erc20Payments, ...nativePayments])
2020

2121
const sell = ['takeBidSingle', 'takeBid']
2222
const buy = ['takeAskSinglePool', 'takeAskSingle', 'takeAsk', 'takeAskPool', 'batchBuyWithETH', 'batchBuyWithERC20s']

packages/transaction-interpreter/interpreters/opensea.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { processNftTransfers, displayPayments, defaultEvent } from './std.js'
1+
import { processNftTransfers, displayAssets, defaultEvent } from './std.js'
22
import type { InterpretedTransaction } from '@/types.js'
33
import type { DecodedTransaction } from '@3loop/transaction-decoder'
44

@@ -15,7 +15,7 @@ export function transformEvent(event: DecodedTransaction): InterpretedTransactio
1515

1616
const collection = nftTransfers[0].name ?? ''
1717
const numberOfNfts = nftTransfers.length > 1 ? ` ${nftTransfers.length} ${collection} NFTS` : ` 1 ${collection} NFT`
18-
const payment = displayPayments(erc20Payments, nativePayments)
18+
const payment = displayAssets([...erc20Payments, ...nativePayments])
1919

2020
if (sendingAddresses.includes(event.fromAddress.toLowerCase())) {
2121
const from = receivingAddresses.length > 1 ? ` to ${receivingAddresses.length} users` : ''

packages/transaction-interpreter/interpreters/std.ts

Lines changed: 140 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,33 @@ import { Asset, DecodedTransaction } from '@3loop/transaction-decoder'
33

44
export const NULL_ADDRESS = '0x0000000000000000000000000000000000000000'
55

6-
export function filterZeroTransfers(transfers: Asset[]): Asset[] {
7-
return transfers.filter((t) => (t.amount && t.amount !== '0') || !t.amount)
8-
}
6+
//------------------------------------------------------------------------------
7+
//Core helper functions
98

10-
export function filterNullTransfers(transfers: Asset[]): Asset[] {
11-
return transfers.filter((t) => t.from !== NULL_ADDRESS && t.to !== NULL_ADDRESS)
9+
interface FilterOptions {
10+
excludeZero?: boolean
11+
excludeNull?: boolean
12+
excludeDuplicates?: boolean
1213
}
1314

14-
export function filterDuplicateTransfers(transfers: Asset[]): Asset[] {
15-
return transfers.filter(
16-
(t, i, self) => self.findIndex((t2) => t2.address === t.address && t2.amount === t.amount) === i,
17-
)
15+
export const filterTransfers = (transfers: Asset[], filters: FilterOptions = {}): Asset[] => {
16+
let filtered = [...transfers]
17+
18+
if (filters.excludeZero) {
19+
filtered = filtered.filter((t) => (t.amount && t.amount !== '0') || !t.amount)
20+
}
21+
22+
if (filters.excludeNull) {
23+
filtered = filtered.filter((t) => t.from !== NULL_ADDRESS && t.to !== NULL_ADDRESS)
24+
}
25+
26+
if (filters.excludeDuplicates) {
27+
filtered = filtered.filter(
28+
(t, i, self) => self.findIndex((t2) => t2.address === t.address && t2.amount === t.amount) === i,
29+
)
30+
}
31+
32+
return filtered
1833
}
1934

2035
export function toAssetTransfer(transfer: Asset): AssetTransfer {
@@ -33,81 +48,21 @@ export function toAssetTransfer(transfer: Asset): AssetTransfer {
3348
}
3449

3550
export function assetsSent(transfers: Asset[], address: string): AssetTransfer[] {
36-
let filteredTransfers = filterZeroTransfers(transfers)
37-
38-
if (address !== NULL_ADDRESS) {
39-
filteredTransfers = filterNullTransfers(filteredTransfers)
40-
}
51+
const filtered = filterTransfers(transfers, {
52+
excludeZero: true,
53+
excludeNull: address !== NULL_ADDRESS,
54+
})
4155

42-
return filteredTransfers.filter((t) => t.from.toLowerCase() === address.toLowerCase()).map(toAssetTransfer)
56+
return filtered.filter((t) => t.from.toLowerCase() === address.toLowerCase()).map(toAssetTransfer)
4357
}
4458

4559
export function assetsReceived(transfers: Asset[], address: string): AssetTransfer[] {
46-
let filteredTransfers = filterZeroTransfers(transfers)
47-
48-
if (address !== NULL_ADDRESS) {
49-
filteredTransfers = filterNullTransfers(filteredTransfers)
50-
}
51-
52-
return filteredTransfers.filter((t) => t.to.toLowerCase() === address.toLowerCase()).map(toAssetTransfer)
53-
}
54-
55-
export function displayAddress(address: string): string {
56-
return address.slice(0, 6) + '...' + address.slice(-4)
57-
}
58-
59-
export function isSwap(event: DecodedTransaction): boolean {
60-
if (event.transfers.some((t) => t.type !== 'ERC20' && t.type !== 'native')) return false
61-
62-
const transfers = event.transfers.filter((t) => t.from !== NULL_ADDRESS && t.to !== NULL_ADDRESS)
63-
64-
const sent = new Set(
65-
transfers.filter((t) => t.from.toLowerCase() === event.fromAddress.toLowerCase()).map((t) => t.address),
66-
)
67-
const received = new Set(
68-
transfers.filter((t) => t.to.toLowerCase() === event.fromAddress.toLowerCase()).map((t) => t.address),
69-
)
70-
71-
if (sent.size !== 1 || received.size !== 1 || sent.values() === received.values()) return false
72-
73-
return true
74-
}
75-
76-
export function formatNumber(numberString: string, precision?: number): string {
77-
const [integerPart, decimalPart] = numberString.split('.')
78-
const bigIntPart = BigInt(integerPart)
79-
80-
if ((integerPart && integerPart.length < 3 && !decimalPart) || (decimalPart && decimalPart.startsWith('000')))
81-
return numberString
82-
83-
// Format the integer part manually
84-
let formattedIntegerPart = ''
85-
const integerStr = bigIntPart.toString()
86-
for (let i = 0; i < integerStr.length; i++) {
87-
if (i > 0 && (integerStr.length - i) % 3 === 0) {
88-
formattedIntegerPart += ','
89-
}
90-
formattedIntegerPart += integerStr[i]
91-
}
92-
93-
// Format the decimal part
94-
const formattedDecimalPart = decimalPart
95-
? parseFloat('0.' + decimalPart)
96-
.toFixed(precision ?? 3)
97-
.split('.')[1]
98-
: '00'
99-
100-
return formattedIntegerPart + '.' + formattedDecimalPart
101-
}
102-
103-
export function displayAsset(asset: Payment | undefined): string {
104-
if (!asset || !asset.asset) return 'unknown asset'
105-
106-
const symbol = asset.asset.type === 'ERC20' ? asset.asset.symbol : asset.asset.name
107-
108-
if (symbol) return formatNumber(asset.amount) + ' ' + symbol
60+
const filtered = filterTransfers(transfers, {
61+
excludeZero: true,
62+
excludeNull: address !== NULL_ADDRESS,
63+
})
10964

110-
return formatNumber(asset.amount) + ' ' + displayAddress(asset.asset.address)
65+
return filtered.filter((t) => t.to.toLowerCase() === address.toLowerCase()).map(toAssetTransfer)
11166
}
11267

11368
export function getPayments({
@@ -203,27 +158,91 @@ export function processNftTransfers(transfers: Asset[]) {
203158
}
204159
}
205160

206-
export function displayPayments(erc20Payments: Payment[], nativePayments: Payment[]) {
207-
if (erc20Payments.length > 0 && nativePayments.length > 0) {
208-
const amount = (erc20Payments.length + 1).toString()
209-
return amount + ' assets'
210-
} else if (erc20Payments.length > 0) {
211-
return (
212-
erc20Payments[0].amount +
213-
' ' +
214-
(erc20Payments[0].asset?.symbol || erc20Payments[0].asset?.name + ' tokens' || 'ERCC20 tokens')
215-
)
216-
} else if (nativePayments.length > 0) {
217-
return (
218-
nativePayments[0].amount +
219-
' ' +
220-
(nativePayments[0].asset?.symbol || nativePayments[0].asset?.name + ' tokens' || 'native tokens')
221-
)
161+
//------------------------------------------------------------------------------
162+
// Formatting Functions
163+
164+
export function displayAddress(address: string): string {
165+
return address.slice(0, 6) + '...' + address.slice(-4)
166+
}
167+
168+
export const formatNumber = (numberString: string, precision = 3): string => {
169+
const [integerPart, decimalPart] = numberString.split('.')
170+
const bigIntPart = BigInt(integerPart)
171+
172+
if ((integerPart && integerPart.length < 3 && !decimalPart) || (decimalPart && decimalPart.startsWith('000')))
173+
return numberString
174+
175+
// Format the integer part manually
176+
let formattedIntegerPart = ''
177+
const integerStr = bigIntPart.toString()
178+
for (let i = 0; i < integerStr.length; i++) {
179+
if (i > 0 && (integerStr.length - i) % 3 === 0) {
180+
formattedIntegerPart += ','
181+
}
182+
formattedIntegerPart += integerStr[i]
183+
}
184+
185+
// Format the decimal part
186+
const formattedDecimalPart = decimalPart
187+
? parseFloat('0.' + decimalPart)
188+
.toFixed(precision)
189+
.split('.')[1]
190+
: '00'
191+
192+
return formattedIntegerPart + '.' + formattedDecimalPart
193+
}
194+
195+
export const displayAsset = (asset?: Payment): string => {
196+
if (!asset?.asset) return 'unknown asset'
197+
198+
const symbol = asset.asset.type === 'ERC20' ? asset.asset.symbol : asset.asset.name
199+
200+
if (symbol) return formatNumber(asset.amount) + ' ' + symbol
201+
202+
return formatNumber(asset.amount) + ' ' + displayAddress(asset.asset.address)
203+
}
204+
205+
export function displayAssets(assets: Payment[]) {
206+
const erc20 = assets.filter((a) => a.asset.type === 'ERC20')
207+
const native = assets.filter((a) => a.asset.type === 'native')
208+
209+
if (assets.length === 1) {
210+
return displayAsset(assets[0])
211+
} else if (erc20.length > 0 && native.length > 0) {
212+
return (erc20.length + 1).toString() + ' assets'
213+
} else if (erc20.length > 0) {
214+
return erc20[0].amount + ' ' + (erc20[0].asset?.symbol || erc20[0].asset?.name + ' tokens' || 'ERCC20 tokens')
215+
} else if (native.length > 0) {
216+
return native[0].amount + ' ' + (native[0].asset?.symbol || native[0].asset?.name + ' tokens' || 'native tokens')
222217
} else {
223218
return ''
224219
}
225220
}
226221

222+
//------------------------------------------------------------------------------
223+
// Categorization Functions
224+
225+
export function isSwap(event: DecodedTransaction): boolean {
226+
if (event.transfers.some((t) => t.type !== 'ERC20' && t.type !== 'native')) return false
227+
228+
const transfers = filterTransfers(event.transfers, {
229+
excludeZero: true,
230+
excludeNull: true,
231+
})
232+
233+
const uniqueSent = new Set(
234+
transfers.filter((t) => t.from.toLowerCase() === event.fromAddress.toLowerCase()).map((t) => t.address),
235+
)
236+
const uniqueReceived = new Set(
237+
transfers.filter((t) => t.to.toLowerCase() === event.fromAddress.toLowerCase()).map((t) => t.address),
238+
)
239+
240+
if (uniqueSent.size !== 1 || uniqueReceived.size !== 1 || uniqueSent.values() === uniqueReceived.values())
241+
return false
242+
243+
return true
244+
}
245+
227246
export function defaultEvent(event: DecodedTransaction): InterpretedTransaction {
228247
const burned = assetsReceived(event.transfers, NULL_ADDRESS)
229248
const minted = assetsSent(event.transfers, NULL_ADDRESS)
@@ -246,13 +265,17 @@ export function defaultEvent(event: DecodedTransaction): InterpretedTransaction
246265

247266
export function categorizedDefaultEvent(event: DecodedTransaction): InterpretedTransaction {
248267
const newEvent = defaultEvent(event)
249-
const nonDuplicateTransfers = filterDuplicateTransfers(event.transfers)
250-
const nonZeroTransfers = filterZeroTransfers(nonDuplicateTransfers)
268+
const transfers = filterTransfers(event.transfers, {
269+
excludeDuplicates: true,
270+
})
271+
const nonZeroTransfers = filterTransfers(transfers, {
272+
excludeZero: true,
273+
})
251274
const minted = newEvent.assetsMinted || []
252275
const burned = newEvent.assetsBurned || []
253276

254277
// single burn
255-
if (burned.length === 1 && nonDuplicateTransfers.length <= 2) {
278+
if (burned.length === 1 && nonZeroTransfers.length <= 2) {
256279
return {
257280
...newEvent,
258281
type: 'burn',
@@ -261,7 +284,7 @@ export function categorizedDefaultEvent(event: DecodedTransaction): InterpretedT
261284
}
262285

263286
// single mint
264-
if (minted.length === 1 && nonDuplicateTransfers.length <= 2) {
287+
if (minted.length === 1 && transfers.length <= 2) {
265288
const price = newEvent.assetsSent.length === 1 ? newEvent.assetsSent[0] : undefined
266289
return {
267290
...newEvent,
@@ -308,5 +331,23 @@ export function categorizedDefaultEvent(event: DecodedTransaction): InterpretedT
308331
}
309332
}
310333

334+
if (isSwap(event)) {
335+
const netSent = getPayments({
336+
transfers: event.transfers,
337+
fromAddresses: [event.fromAddress],
338+
})
339+
340+
const netReceived = getPayments({
341+
transfers: event.transfers,
342+
toAddresses: [event.fromAddress],
343+
})
344+
345+
return {
346+
...newEvent,
347+
type: 'swap',
348+
action: 'Swapped ' + displayAsset(netSent[0]) + ' for ' + displayAsset(netReceived[0]),
349+
}
350+
}
351+
311352
return newEvent
312353
}

0 commit comments

Comments
 (0)