How To Create An AtomicHub NFT Sniper Bot

Ever wished that you could automatically buy an NFT when it gets listed for sale at a certain price?

In this tutorial, I'm going to show you how to set up a bot to do exactly that.

*This article will be updated if/when I make updates to the bot, so make sure to check back often.

Things You'll Need:

*If you're following the Anchor Wallet Tutorial, just create a new wallet instead of connecting your Ledger. That's how you get a new private key.

What We're Actually Doing

Before we get too deep into the tutorial, let me just give an outline of what we're doing here.

We want to find out if there are any sales for a specific NFT, and then buy that NFT if the price is low enough.

There are 5 steps involved here:

  1. Ask the blockchain if there are any sales for the NFT we want
  2. If there are, we will run some checks to see if that sale meets our criteria (i.e is the price low enough)
  3. If it does meet our criteria, there are 3 actions we need to take:

    1. assertsale (This is us telling the AtomicHub contracts "Hey, we're about to buy this thing!")
    2. deposit (This is us making a deposit to the AtomicHub contract, so our balance is available to make the purchase. You can also do this beforehand, so you don't lose precious time making extra transactions when trying to snipe.)
    3. purchasesale (This is us making the actual purchase.)

If all goes well, we will get our NFT.

The Code

Now let's dive deeper into the actual code.

Before doing anything, let's create a new folder where we will be doing all of our work.

You can do this by opening Terminal on your system (on Mac, just type "CMD + Space" and then type in "Terminal")

Once you have Terminal open, you should see a screen like this:

what the Terminal screen looks like

Type the command "cd" and hit enter

cd

This should bring you to your root directory (the main directory on your computer).

Now let's create that folder. Type the following command into Terminal:

mkdir bot

If you did that correctly, you should see a new folder called "bot" on your computer.

Before you do anything else, use terminal to navigate to that folder. You can do this by entering the following command:

cd bot

Your terminal should now be displaying the bot folder name next to your username.

cd into folder using terminal

Make sure you remain in this folder at all times while following this guide. If you install something in the wrong folder, you will have a hard time trying to piece things together.

Installing Node JS

If you don't know what Node JS is or why you need it, don't worry. Just know that you need it to run these scripts.

Before downloading it, you might as well check if you already have it installed.

Type in the following command, then hit enter:

node --version

In my case, I have it installed already so I get a message like this:

node js version 16.15.0 is installed

As you can see, I've got version 16.15.0 installed already.

If you have it installed already, then you can move on to the next step (initializing a new project).

If you don't have Node JS installed yet, download it by going to nodejs.org.

Make sure to select the version that says "recommended for most users".

That's the version that is fully supported and confirmed to be working properly. The other version is for advanced users who want to test out new features, and possibly deal with problems when trying to use it.

Once you download and install it, use the terminal command above to verify that you have it installed.

Initialize Your New Project

This part is very simple. Make sure you are still in the bot directory in Terminal.

Then just type this in:

npm init -y

You should now see a file called 'package.json' in your bot folder. That means it worked.

Installing EosJS

I won't bore you too much with details here, but if you're wondering what the hell EosJS is, here's a quick summary.

There once was a blockchain called EOS. They built some tools so developers could build dApps on their chain.

WAX came along and made a clone of EOS. Since it's a clone, it works with the same set of tools that EOS built.

The end.

Ok, now you can install EosJS (and the other required packages) by entering the following command into Terminal:

npm install eosjs axios node-fetch util

Troubleshooting

If you get errors with this, it's most likely for 1 of 2 reasons:

  1. You don't have npm installed on your system, or have an outdated version
  2. You don't have the proper permissions to install packages

If it's a permissions issue, put "sudo" in front of the command, like this:

sudo yarn add eosjs

or

sudo npm install eosjs

If you don't have npm installed on your system, the installation will vary depending on what system you are running.

To check if you have it installed, type this in your terminal:

npm -v

This is one of those things where you'll just have to use a little bit of common sense.

Google "how to update npm on windows XYZ" or whatever system you're running.

Downgrading Node-Fetch

Once everything is done being installed, there's one more command you want to run:

That last command is just us downgrading one of the packages to an older version, because recent versions don't support one of the things that we're trying to do.

If you get an error later on that says "require() of ES Module node-fetch is not supported", it's because you need to downgrade node-fetch using the above command.

Docs

Before we move forward, here is the documentation for EosJs.

Refer to that page whenever you are having any issues.

For those of you who are developers and understand this stuff already- you'll notice that there is a browser version of EosJS.

Feel free to explore that in the docs, but we aren't using that in this guide.

Setting Up Your AtomicHub Bot

Inside of the bot folder that we created, we are going to create a file with our text editor.

Name that file "bot.js"

Inside of the bot.js file, paste the following lines of code:

const assetID = '1099600019671'; //The asset you want to buy
const desiredPrice = '110000000'; //The price you want to buy it for, in WAX
const privateKeys = ['YOUR_PRIVATE_KEY_HERE'];
const actor = 'your_wax_address'; //the address that has the authority to make these transactions

const axios = require('axios'); 
const { Api, JsonRpc } = require('eosjs');
const { JsSignatureProvider } = require('eosjs/dist/eosjs-jssig'); 
const fetch = require('node-fetch'); 
const { TextDecoder, TextEncoder } = require('util'); 


const signatureProvider = new JsSignatureProvider(privateKeys);
const rpc = new JsonRpc('https://wax.eosdac.io', { fetch }); 
const api = new Api({ rpc, signatureProvider, textDecoder: new TextDecoder(), textEncoder: new TextEncoder() }); 

Save the file.

The first 4 lines are the ones that you need to worry about.

Line 1 is the asset ID that you want to buy. You can find this by pulling up the NFT on AtomicHub.

How to find the asset ID on AtomicHub

The 2nd line is the highest price that you want to buy the NFT for, in WAX.

That number you see in my code is actually 1.1 WAX.

WAX has 8 decimals, and this is the way that it's formatted. You need to add 8 digits after the whole number.

In other words, if you wanted to pay 42,069 WAX, you would put 4206900000000 as your desired price.

Don't worry, we won't be using this number to make our purchase anyway. We are only using it to find out if the NFT is listed for a good price. The actual purchase is handled with a different number.

The 3rd line of code is your private key. This is needed in order to sign transactions when you want to make a purchase.

The 4th line is your WAX address. Like 'mdcryptonfts' is mine. This is the account that will be executing all the transactions.

The remaining lines of code in the box above are just there to establish a connection with the blockchain.

Let's add some more code to our file.

Put these following lines underneath the previous code:

async function getSales() {
  const url = "https://wax.api.atomicassets.io/atomicmarket/v2/sales?asset_id=" + assetID + "&page=1&limit=100&order=desc&sort=created";
  let response = await axios.get(url);
  return response.data;
}

If you look near the middle of that last piece of code, you'll notice "...+ assetID +..." in there.

That piece of code is going to connect to the atomicassets API, and ask the API for data about your desired asset.

Basically, it's checking to see if there are any sales for that NFT.

After we get that data, we need to process it and see if we found any good deals.

Wait for it... incoming..

getSales().then((data) =>{

Object.keys(data.data).forEach(key => {
console.log('\nI'm working, I'll let you know when I find something.');
const price = data.data[key].listing_price.trim();
console.log("\nPrice: " + price);
const saleID = data.data[key].sale_id;
const symbol = data.data[key].listing_symbol;
const state = data.data[key].state;
const usdPrice = data.data[key].price.amount;
console.log("\nUSD PRICE: " + usdPrice);
const median = data.data[key].price.median;
var usdToPay = usdPrice.slice(0, -8) + "." + usdPrice.slice(-8) + " WAX"; //the amount of wax to deposit for USD sales
console.log("\nUSD TO PAY: " + usdToPay);
let firstUTPChar = usdToPay.charAt(0);
if(firstUTPChar == '.'){usdToPay = '0' + usdToPay};
console.log("\nUSD TO PAY: " + usdToPay);

//Here, if there are any sales, we will check a few things.
//Are they priced in WAX?
//Is that price less than or = what we want to spend?
//Does state = 1? (this means, "is the NFT actually for sale?")

if(symbol == 'WAX' & price <= desiredPrice & state == 1){

var waxToPay = price.slice(0, -8) + "." + price.slice(-8) + " WAX";
let firstChar = waxToPay.charAt(0);
if(firstChar == '.'){waxToPay = '0' + waxToPay};

console.log("\nWAX TO PAY: \n" + waxToPay + "It's a good WAX deal!");

  (async () => {
    try {
      const result = await api.transact({
        actions: [{
            account: 'atomicmarket',
            name: 'assertsale',
            authorization: [{
                actor: actor,
                permission: 'active',
            }],
            data: {
                asset_ids_to_assert: [assetID],
                listing_price_to_assert: waxToPay,
                sale_id: saleID,
                settlement_symbol_to_assert: '8,WAX', 

            },
        }]
      }, {
        blocksBehind: 3,
        expireSeconds: 30,
      });
       console.log('\n\nAssert sale transaction went through!\n\n');


//IN THIS FUNCTION, WE WILL DEPOSIT THE REQUIRED WAX TO ATOMICMARKET
//Feel free to do this beforehand, and remove this function from the script. It's one less transaction that might cost you valuable time.

(async () => {
    try {
      const result = await api.transact({
        actions: [{
            account: 'eosio.token',
            name: 'transfer',
            authorization: [{
                actor: actor,
                permission: 'active',
            }],
            data: {
                 from: actor,
                 memo: 'deposit',
                 quantity: waxToPay,
                 to: 'atomicmarket',

            },
        }]
      }, {
        blocksBehind: 3,
        expireSeconds: 30,
      });
      console.log('\n\nWax deposit transaction went through!\n\n');

//IF DEPOSIT GOES THROUGH, WE WILL MAKE THE ACTUAL PURCHASE


  (async () => {
    try {
      const result = await api.transact({
        actions: [{
            account: 'atomicmarket',
            name: 'purchasesale',
            authorization: [{
                actor: actor,
                permission: 'active',
            }],
            data: {
                 buyer: actor,
                 intended_delphi_median: '0',
                 sale_id: saleID,
                 taker_marketplace: '',

            },
        }]
      }, {
        blocksBehind: 3,
        expireSeconds: 30,
      });
       console.log('\n\nPurchase sale transaction went through!\n\n');
     

    } catch (e) {
      console.log('\nCaught exception: ' + e);


      if (e instanceof JsonRpc)
        console.log('\n\n' + JSON.stringify(e.json, null, 2));
    }
  })();

//END OF PURCHASE FUNCTION






    } catch (e) {
      console.log('\nCaught exception: ' + e);

      if (e instanceof JsonRpc)
        console.log('\n\n' + JSON.stringify(e.json, null, 2));
    }
  })();

//END OF DEPOSIT FUNCTION

       

    } catch (e) {
      console.log('\nCaught exception: ' + e);

      if (e instanceof JsonRpc)
        console.log('\n\n' + JSON.stringify(e.json, null, 2));
    }
  })();



} //end if price <= desiredprice

if(symbol == 'WAX' & price > desiredPrice & state == 1){

console.log("\nits a shitty deal!");


} //end if price > desiredprice for WAX based sales


//IF THE SALE IS PRICED IN USD, WE DO ALL THE SAME STUFF BELOW, BUT FOR USD SALES INSTEAD


if(symbol == 'USD' & usdPrice <= desiredPrice & state == 1){

var priceToAssert = price.slice(0, -2) + "." + price.slice(-2) + " USD"; 

let firstUSDChar = priceToAssert.charAt(0);
if(firstUSDChar == '.'){priceToAssert = '0' + priceToAssert};


  (async () => {
    try {
      const result = await api.transact({
        actions: [{
            account: 'atomicmarket',
            name: 'assertsale',
            authorization: [{
                actor: actor,
                permission: 'active',
            }],
            data: {
                asset_ids_to_assert: [assetID],
                listing_price_to_assert: priceToAssert,
                sale_id: saleID,
                settlement_symbol_to_assert: '8,WAX', 

            },
        }]
      }, {
        blocksBehind: 3,
        expireSeconds: 30,
      });
       console.log('\n\nAssert sale transaction went through!\n\n');


       //IN THIS FUNCTION, WE WILL DEPOSIT THE REQUIRED WAX TO ATOMICMARKET
//Feel free to do this beforehand, and remove this function from the script. It's one less transaction that might cost you valuable time.

(async () => {
    try {
      const result = await api.transact({
        actions: [{
            account: 'eosio.token',
            name: 'transfer',
            authorization: [{
                actor: actor,
                permission: 'active',
            }],
            data: {
                 from: actor,
                 memo: 'deposit',
                 quantity: usdToPay,
                 to: 'atomicmarket',

            },
        }]
      }, {
        blocksBehind: 3,
        expireSeconds: 30,
      });
       console.log('\n\nWax deposit transaction went through!\n\n');


       //BELOW, WE WILL MAKE THE ACTUAL PURCHASE


  (async () => {
    try {
      const result = await api.transact({
        actions: [{
            account: 'atomicmarket',
            name: 'purchasesale',
            authorization: [{
                actor: actor,
                permission: 'active',
            }],
            data: {
                 buyer: actor,
                 intended_delphi_median: median,
                 sale_id: saleID,
                 taker_marketplace: '',

            },
        }]
      }, {
        blocksBehind: 3,
        expireSeconds: 30,
      });
       console.log('\n\nPurchase sale transaction went through!\n\n');
     

    } catch (e) {
      console.log('\nCaught exception: ' + e);


      if (e instanceof JsonRpc)
        console.log('\n\n' + JSON.stringify(e.json, null, 2));
    }
  })();

  //END OF PURCHASE TRANSACTION


    } catch (e) {
      console.log('\nCaught exception: ' + e);

      if (e instanceof JsonRpc)
        console.log('\n\n' + JSON.stringify(e.json, null, 2));
    }
  })();

  //END OF WAX DEPOSIT TRANSACTION


       

    } catch (e) {
      console.log('\nCaught exception: ' + e);

      if (e instanceof JsonRpc)
        console.log('\n\n' + JSON.stringify(e.json, null, 2));
    }
  })();

//END OF ASSERTSALE FUNCTION


} //end if price <= desiredprice

    }); //end object.keys

}); //end .then

That was the rest of the code, so your entire file should now look like this:

const assetID = '1099600019671'; //The asset you want to buy
const desiredPrice = '110000000'; //The price you want to buy it for, in WAX
const privateKeys = ['YOUR_PRIVATE_KEY_HERE'];
const actor = 'your_wax_address'; //the address that has the authority to make these transactions

const axios = require('axios'); 
const { Api, JsonRpc } = require('eosjs');
const { JsSignatureProvider } = require('eosjs/dist/eosjs-jssig'); 
const fetch = require('node-fetch'); 
const { TextDecoder, TextEncoder } = require('util'); 


const signatureProvider = new JsSignatureProvider(privateKeys);
const rpc = new JsonRpc('https://wax.eosdac.io', { fetch }); 
const api = new Api({ rpc, signatureProvider, textDecoder: new TextDecoder(), textEncoder: new TextEncoder() });

async function getSales() {
  const url = "https://wax.api.atomicassets.io/atomicmarket/v2/sales?asset_id=" + assetID + "&page=1&limit=100&order=desc&sort=created";
  let response = await axios.get(url);
  return response.data;
}

getSales().then((data) =>{

Object.keys(data.data).forEach(key => {
console.log('\nI'm working, I'll let you know when I find something.');
const price = data.data[key].listing_price.trim();
console.log("\nPrice: " + price);
const saleID = data.data[key].sale_id;
const symbol = data.data[key].listing_symbol;
const state = data.data[key].state;
const usdPrice = data.data[key].price.amount;
console.log("\nUSD PRICE: " + usdPrice);
const median = data.data[key].price.median;
var usdToPay = usdPrice.slice(0, -8) + "." + usdPrice.slice(-8) + " WAX"; //the amount of wax to deposit for USD sales
console.log("\nUSD TO PAY: " + usdToPay);
let firstUTPChar = usdToPay.charAt(0);
if(firstUTPChar == '.'){usdToPay = '0' + usdToPay};
console.log("\nUSD TO PAY: " + usdToPay);

//Here, if there are any sales, we will check a few things.
//Are they priced in WAX?
//Is that price less than or = what we want to spend?
//Does state = 1? (this means, "is the NFT actually for sale?")

if(symbol == 'WAX' & price <= desiredPrice & state == 1){

var waxToPay = price.slice(0, -8) + "." + price.slice(-8) + " WAX";
let firstChar = waxToPay.charAt(0);
if(firstChar == '.'){waxToPay = '0' + waxToPay};

console.log("\nWAX TO PAY: \n" + waxToPay + "It's a good WAX deal!");

  (async () => {
    try {
      const result = await api.transact({
        actions: [{
            account: 'atomicmarket',
            name: 'assertsale',
            authorization: [{
                actor: actor,
                permission: 'active',
            }],
            data: {
                asset_ids_to_assert: [assetID],
                listing_price_to_assert: waxToPay,
                sale_id: saleID,
                settlement_symbol_to_assert: '8,WAX', 

            },
        }]
      }, {
        blocksBehind: 3,
        expireSeconds: 30,
      });
       console.log('\n\nAssert sale transaction went through!\n\n');


//IN THIS FUNCTION, WE WILL DEPOSIT THE REQUIRED WAX TO ATOMICMARKET
//Feel free to do this beforehand, and remove this function from the script. It's one less transaction that might cost you valuable time.

(async () => {
    try {
      const result = await api.transact({
        actions: [{
            account: 'eosio.token',
            name: 'transfer',
            authorization: [{
                actor: actor,
                permission: 'active',
            }],
            data: {
                 from: actor,
                 memo: 'deposit',
                 quantity: waxToPay,
                 to: 'atomicmarket',

            },
        }]
      }, {
        blocksBehind: 3,
        expireSeconds: 30,
      });
      console.log('\n\nWax deposit transaction went through!\n\n');

//IF DEPOSIT GOES THROUGH, WE WILL MAKE THE ACTUAL PURCHASE


  (async () => {
    try {
      const result = await api.transact({
        actions: [{
            account: 'atomicmarket',
            name: 'purchasesale',
            authorization: [{
                actor: actor,
                permission: 'active',
            }],
            data: {
                 buyer: actor,
                 intended_delphi_median: '0',
                 sale_id: saleID,
                 taker_marketplace: '',

            },
        }]
      }, {
        blocksBehind: 3,
        expireSeconds: 30,
      });
       console.log('\n\nPurchase sale transaction went through!\n\n');
     

    } catch (e) {
      console.log('\nCaught exception: ' + e);


      if (e instanceof JsonRpc)
        console.log('\n\n' + JSON.stringify(e.json, null, 2));
    }
  })();

//END OF PURCHASE FUNCTION






    } catch (e) {
      console.log('\nCaught exception: ' + e);

      if (e instanceof JsonRpc)
        console.log('\n\n' + JSON.stringify(e.json, null, 2));
    }
  })();

//END OF DEPOSIT FUNCTION

       

    } catch (e) {
      console.log('\nCaught exception: ' + e);

      if (e instanceof JsonRpc)
        console.log('\n\n' + JSON.stringify(e.json, null, 2));
    }
  })();



} //end if price <= desiredprice

if(symbol == 'WAX' & price > desiredPrice & state == 1){

console.log("\nits a shitty deal!");


} //end if price > desiredprice for WAX based sales


//IF THE SALE IS PRICED IN USD, WE DO ALL THE SAME STUFF BELOW, BUT FOR USD SALES INSTEAD


if(symbol == 'USD' & usdPrice <= desiredPrice & state == 1){

var priceToAssert = price.slice(0, -2) + "." + price.slice(-2) + " USD"; 

let firstUSDChar = priceToAssert.charAt(0);
if(firstUSDChar == '.'){priceToAssert = '0' + priceToAssert};


  (async () => {
    try {
      const result = await api.transact({
        actions: [{
            account: 'atomicmarket',
            name: 'assertsale',
            authorization: [{
                actor: actor,
                permission: 'active',
            }],
            data: {
                asset_ids_to_assert: [assetID],
                listing_price_to_assert: priceToAssert,
                sale_id: saleID,
                settlement_symbol_to_assert: '8,WAX', 

            },
        }]
      }, {
        blocksBehind: 3,
        expireSeconds: 30,
      });
       console.log('\n\nAssert sale transaction went through!\n\n');


       //IN THIS FUNCTION, WE WILL DEPOSIT THE REQUIRED WAX TO ATOMICMARKET
//Feel free to do this beforehand, and remove this function from the script. It's one less transaction that might cost you valuable time.

(async () => {
    try {
      const result = await api.transact({
        actions: [{
            account: 'eosio.token',
            name: 'transfer',
            authorization: [{
                actor: actor,
                permission: 'active',
            }],
            data: {
                 from: actor,
                 memo: 'deposit',
                 quantity: usdToPay,
                 to: 'atomicmarket',

            },
        }]
      }, {
        blocksBehind: 3,
        expireSeconds: 30,
      });
       console.log('\n\nWax deposit transaction went through!\n\n');


       //BELOW, WE WILL MAKE THE ACTUAL PURCHASE


  (async () => {
    try {
      const result = await api.transact({
        actions: [{
            account: 'atomicmarket',
            name: 'purchasesale',
            authorization: [{
                actor: actor,
                permission: 'active',
            }],
            data: {
                 buyer: actor,
                 intended_delphi_median: median,
                 sale_id: saleID,
                 taker_marketplace: '',

            },
        }]
      }, {
        blocksBehind: 3,
        expireSeconds: 30,
      });
       console.log('\n\nPurchase sale transaction went through!\n\n');
     

    } catch (e) {
      console.log('\nCaught exception: ' + e);


      if (e instanceof JsonRpc)
        console.log('\n\n' + JSON.stringify(e.json, null, 2));
    }
  })();

  //END OF PURCHASE TRANSACTION


    } catch (e) {
      console.log('\nCaught exception: ' + e);

      if (e instanceof JsonRpc)
        console.log('\n\n' + JSON.stringify(e.json, null, 2));
    }
  })();

  //END OF WAX DEPOSIT TRANSACTION


       

    } catch (e) {
      console.log('\nCaught exception: ' + e);

      if (e instanceof JsonRpc)
        console.log('\n\n' + JSON.stringify(e.json, null, 2));
    }
  })();

//END OF ASSERTSALE FUNCTION


} //end if price <= desiredprice

    }); //end object.keys

}); //end .then

Once you fill out the few details at the top, you can test out your script by running the following command in terminal:

node bot

Note that you need to still be inside the bot folder when you run this command.

You also might get some errors about missing packages. Our script requires the following node modules:

If you are missing any of those, your script won't work. The script is set up to show you errors, so you should get a message in terminal if there are any problems.

Again, just use some common sense here. Axios is missing? Google "how to install Axios on Node JS".

Setting Your Bot To Run 24/7

You don't just want to check one time for NFT sales right?

Nah, you want to do that every few seconds.

Create a new file in your bot folder. Call the file "run-bot.sh"

Now open up that run-bot file, and put the following code in it:

while true; do node bot; sleep 5; done

This will run your script, wait 5 seconds, and run it again... over and over.

To use this, all you need to do is enter the following command into terminal (from the correct folder):

sh run-bot.sh

If you did everything properly, the bot should send you a message every 5-10 seconds in Terminal.

You should be aware of a couple of things before using this or messing around with any settings...

  1. I can't stress enough that I'm not a professional developer, and there are no guarantees that everything here is going to function properly. Don't keep a lot of money in the wax wallet that you use with this bot. Experiment with it before doing anything more serious.
  2. I am not liable for anything you do with this bot. Whether you make money, lose money, whatever the case may be- it's on you. So be aware of what you're doing and be aware of the risks involved.
  3. If you don't wait long enough in between making API requests, you will get blocked by the API. So don't be trying to set the bot to run 50 times a second. I set it to sleep for 5 seconds in between requests. You should leave it like that, but feel free to experiment at your own risk.
  4. This isn't the most advanced bot out there. This is for trying to scoop up your favorite NFT without having to sit on the computer 24/7. If you think you're gonna use this bot to buy from drops before a human being or another bot can click the 'buy' button, you're sadly mistaken.

If you found this helpful, share it all over the web. Please, it helps me out. Feel free to grab an NFT while you're at it too.

If you want to see more of this kind of content, let me know... I have no idea whether people like this stuff or not. Drop a comment on my video, subscribe to my channel or send in a contact form and tell me. It means a lot more than you know, and it gives me feedback on what I should be making content about.

Lastly, if you have any questions or need help with the bot, join my Discord and I'll do my best to help you out.

Thanks for stopping by. Peace out.