From 0d7425881a9ef3cd88e8a1779f1363a9e627f0e7 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 26 Aug 2023 10:11:05 +0200 Subject: [PATCH] implemented proper mevbot transaction support, including mevbot swapping nftx stuff into their vault --- src/app.module.ts | 3 +- src/erc721sales.service.spec.ts | 45 +++++++++++++++++++ src/erc721sales.service.ts | 21 +++++---- .../statistics.extension.service.ts | 32 +++++++------ 4 files changed, 79 insertions(+), 22 deletions(-) diff --git a/src/app.module.ts b/src/app.module.ts index 91098c8e..94fe7b42 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -29,7 +29,8 @@ import { PhunksAuctionHouseService } from './extensions/phunks.auction.house.ext export class AppModule { constructor(private saleService:Erc721SalesService) { - if (!global.doNotStartAutomatically) + if (!global.doNotStartAutomatically) { this.saleService.startProvider() + } } } diff --git a/src/erc721sales.service.spec.ts b/src/erc721sales.service.spec.ts index a60d83dc..fd030287 100644 --- a/src/erc721sales.service.spec.ts +++ b/src/erc721sales.service.spec.ts @@ -31,6 +31,50 @@ describe('Erc721SalesService', () => { expect(service).toBeDefined() }); + it('0x5464119779617b8b270bd0defa3cc4aa69661afb71d9360b82ae7247d56aa231 - NFTX sale to vault', async () => { + await delay(COOLDOWN_BETWEEN_TESTS) + const provider = service.getWeb3Provider() + config.contract_address = '0xf07468eAd8cf26c752C676E43C814FEe9c8CF402' + const tokenContract = new ethers.Contract('0xf07468eAd8cf26c752C676E43C814FEe9c8CF402', erc721abi, provider); + let filter = tokenContract.filters.Transfer(); + const startingBlock = 17994239 + const events = await tokenContract.queryFilter(filter, + startingBlock, + startingBlock+1) + const results = (await Promise.all(events.map(async (e) => await service.getTransactionDetails(e)))).filter(r => r !== undefined) + + let logs = '' + results.forEach(r => { + logs += `${r.tokenId} sold for ${r.alternateValue} to ${r.to}\n` + expect(r.alternateValue).toBe(0.265) + }) + expect(results.length).toBe(1) + + console.log(logs) + }) + + it('0xa13c09a4b0dc88f5e1914aca92675a2f19498d173d0ea2ada5df4652467b9e5b - nftx transaction involving swap', async () => { + await delay(COOLDOWN_BETWEEN_TESTS) + const provider = service.getWeb3Provider() + config.contract_address = '0xf07468eAd8cf26c752C676E43C814FEe9c8CF402' + const tokenContract = new ethers.Contract('0xf07468eAd8cf26c752C676E43C814FEe9c8CF402', erc721abi, provider); + let filter = tokenContract.filters.Transfer(); + const startingBlock = 17994239 + const events = await tokenContract.queryFilter(filter, + startingBlock, + startingBlock+1) + const results = await Promise.all(events.map(async (e) => await service.getTransactionDetails(e))) + //expect(results[0].alternateValue).toBe(0.31) + let logs = '' + results.filter(r => r !== undefined).forEach(r => { + console.log(r) + logs += `${r.tokenId} sold for ${r.alternateValue}\n` + expect(r.alternateValue).toBe(0.3205) + }) + + console.log(logs) + }) + it('0xa13c09a4b0dc88f5e1914aca92675a2f19498d173d0ea2ada5df4652467b9e5b - nftx transaction involving swap', async () => { await delay(COOLDOWN_BETWEEN_TESTS) const provider = service.getWeb3Provider() @@ -45,6 +89,7 @@ describe('Erc721SalesService', () => { //expect(results[0].alternateValue).toBe(0.31) let logs = '' results.forEach(r => { + console.log(r) logs += `${r.tokenId} sold for ${r.alternateValue}\n` expect(r.alternateValue).toBe(0.3205) }) diff --git a/src/erc721sales.service.ts b/src/erc721sales.service.ts index a97c846a..00e7b5d7 100644 --- a/src/erc721sales.service.ts +++ b/src/erc721sales.service.ts @@ -29,6 +29,8 @@ const blurBiddingContractAddress = '0x0000000000a39bb272e79075ade125fd351887ac'; const cargoTopicIdentifier = '0x5535fa724c02f50c6fb4300412f937dbcdf655b0ebd4ecaca9a0d377d0c0d9cc' const raribleTopicIdentifier = '0x268820db288a211986b26a8fda86b1e0046281b21206936bb0e61c67b5c79ef4' +const botMevAddress = '0x00000000000A6D473a66abe3DBAab9E1388223Bd' +const nftxVaultBeaconProxyAddress = '0xB39185e33E8c28e0BB3DbBCe24DA5dEA6379Ae91' const looksInterface = new ethers.Interface(looksRareABI); const looksInterfaceV2 = new ethers.Interface(looksRareABIv2); @@ -58,7 +60,7 @@ export class Erc721SalesService extends BaseService { // Listen for Transfer event this.provider.on({ address: config.contract_address, topics: [topics] }, (event) => { - this.getTransactionDetails(event).then((res) => { + this.getTransactionDetails(event, false, false, true).then((res) => { if (!res) return // Only tweet transfers with value (Ignore w2w transfers) if (res?.ether || res?.alternateValue) this.dispatch(res); @@ -69,7 +71,7 @@ export class Erc721SalesService extends BaseService { } - async getTransactionDetails(tx: any, ignoreENS:boolean=false, ignoreNftxSwaps:boolean=true, ): Promise { + async getTransactionDetails(tx: any, ignoreENS:boolean=false, ignoreNftxSwaps:boolean=true, ignoreContracts:boolean=true): Promise { // uncomment this to test a specific transaction // if (tx.transactionHash !== '0xcee5c725e2234fd0704e1408cdf7f71d881e67f8bf5d6696a98fdd7c0bcf52f3') return; @@ -93,15 +95,14 @@ export class Erc721SalesService extends BaseService { // ignore internal transfers to contract, another transfer event will handle this // transaction afterward (the one that'll go to the buyer wallet) - /* const code = await this.provider.getCode(to) - we need this for stats - if (code !== '0x') { + // the ignoreContracts flag make the MEV bots like transaction ignored by the twitter + // bot, but not for statistics + if (to !== nftxVaultBeaconProxyAddress && code !== '0x' && ignoreContracts) { logger.info(`contract detected for ${tx.transactionHash} event index ${tx.index}`) return } - */ - + // not an erc721 transfer if (!tx?.topics[3]) return @@ -323,7 +324,11 @@ export class Erc721SalesService extends BaseService { // count the number of tokens transfered tokenCount = receipt.logs .filter(l => l.address.toLowerCase() === config.contract_address.toLowerCase() && - l.topics[0].toLowerCase() === '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef').length + l.topics[0].toLowerCase() === '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef') + .map(l => l.topics[3]) + // take unique value + .filter((value, index, array) => array.indexOf(value) === index) + .length } alternateValue = parseFloat(NFTX[0].toString())/tokenCount/1000; } else if (NLL.length) { diff --git a/src/extensions/statistics.extension.service.ts b/src/extensions/statistics.extension.service.ts index 6904d9b2..e4980d62 100644 --- a/src/extensions/statistics.extension.service.ts +++ b/src/extensions/statistics.extension.service.ts @@ -153,19 +153,22 @@ export class StatisticsService extends BaseService { ) const guildIds = config.discord_guild_ids.split(',') + const commands = [ + status.toJSON(), + userStats.toJSON(), + topTraders.toJSON(), + volumeStats.toJSON(), + graphStats.toJSON(), + checkTransaction.toJSON(), + indexTransaction.toJSON(), + ownedTokens.toJSON() ] + if (process.env.DEBUG_MODE === 'true') { + commands.push(sample.toJSON()) + } guildIds.forEach(async (guildId) => { await rest.put( Routes.applicationGuildCommands(config.discord_client_id, guildId), - { body: [ - // sample.toJSON(), - status.toJSON(), - userStats.toJSON(), - topTraders.toJSON(), - volumeStats.toJSON(), - graphStats.toJSON(), - checkTransaction.toJSON(), - indexTransaction.toJSON(), - ownedTokens.toJSON()] }, + { body: commands }, ); }) @@ -392,13 +395,16 @@ Amount: ${'Ξ'+(Math.floor(r.amount*100)/100).toFixed(2)}`) } getOwnedTokens(wallet:string) { - const sql = `select distinct token_id from + const sql = `select token_id, + ceil(JULIANDAY('now') - + JULIANDAY((select max(tx_date) from events e2 where e2.token_id = a.token_id))) owned_since + from (select distinct token_id from (select distinct token_id, last_value(to_wallet) over ( partition by token_id order by tx_date RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) owner from events) a - where lower(a.owner) = lower(@wallet)` + where lower(a.owner) = lower(@wallet)) a` const result = this.db.prepare(sql).all({wallet}) return result } @@ -577,7 +583,7 @@ getOwnedTokens(wallet:string) { await delay(500) const results = await Promise.all(elements .filter(e => e !== undefined) - .map(async (e) => this.erc721service.getTransactionDetails(e, true, false))) + .map(async (e) => this.erc721service.getTransactionDetails(e, true, false, false))) for (let result of results) { if (!result) continue if (!result.alternateValue && result.ether)