Opening a NFT card shop in Splinterlands for $25+

BUYNANONFT.png


Splinterlands is a wonderful card game that reminds me of early Magic the Gathering. If you are new and like those types of games I highly encourage you to check it out.

Our goal in this series is to add to the ecosystem of this game and have fun. We hope to make money too.

This is meant to be good hearted and fun. Coding is not needed but digging in is encouraged.

The code in this tutorial is good faith but this software is MIT licensed and comes with no warranty or guarantee. You are 100% responsible for anything the script does.

Getting inventory

We are opening a shop, hooray! Let's be smart about it.
If you are a player then buying from the wall is fine. But for us we need to have more control over the price we pay. Market prices tend to fluctuate. If a player is buying a single time mage the player probably doesn't care about a quarter. We don't get such luxuries. If we wait to buy time mage at $0.25 we get twice as many as we would if we bought off the wall at $0.5.

Buying $25 of level 1 time mage

We want to define a json file(bids.json) with our limit orders.

This says to buy 100 time mages at $0.25 for a total of $25(or less).

You can determine the card_detail_id either on splintercards.com or by looking at the url in the splinterlands.com market place.

bids.json

[
    { "card_detail_id": 415, "amount": 100, "bid_usd": "0.25", "edition": 7 }
]

The script

bid.py

from beem import Hive
from beem.transactionbuilder import TransactionBuilder
from beembase.operations import Custom_json
from decimal import Decimal
from urllib.request import urlopen
import json
import os
import time
import argparse

VERSION = "1.03"

def get_collection(owner):
    url = "https://api2.splinterlands.com/cards/collection/"+owner
    return json.loads(urlopen(url).read().decode('utf-8'))

def get_sale_by_card(id, edition, gold=False):
    url = "https://api2.splinterlands.com/market/for_sale_by_card?card_detail_id="+str(id)+"&gold="+str(gold).lower()+"&edition="+str(edition)+"&fee=100"
    return json.loads(urlopen(url).read().decode('utf-8'))

def get_settings():
    url = "https://api2.splinterlands.com/settings"
    return json.loads(urlopen(url).read().decode('utf-8'))

def read_file_or_fail(filename, message):
    if not os.path.exists(filename):
        raise FileNotFoundError(message)
    with open(filename, "r") as f:
        return f.read().rstrip()

def hive_engine_execute_custom_json(json, account):
    hive = Hive(keys=[read_file_or_fail("active.key", "active.key missing")])
    hive.custom_json("sm_market_purchase", json_data=json, required_auths=[account])

def execute_buy(sale, owner, dry_run):
    dec_price = get_settings()['dec_price']
    price_usd = sale["buy_price"]
    price_dec = Decimal(price_usd)/Decimal(dec_price)
    buy = {
            "items": [sale["market_id"]],
            "price": ( "%.3f" % price_dec),
            "currency": "DEC",
            "market": owner,
            "app": "buynano "+VERSION
            }
    if dry_run:
        print("Would buy %d at $%s, %s DEC" % (sale["card_detail_id"], price_usd, price_dec))
        return None
    print("Buying %d at $%s, %s DEC" % (sale["card_detail_id"], price_usd, price_dec))
    return hive_engine_execute_custom_json(buy, owner)

def get_collection_count(collection, card_detail_id, gold):
    count = 0
    for card in collection["cards"]:
        if card["card_detail_id"] == card_detail_id and card["gold"] == gold:
            count += 1
    return count

def cards_needed_to_complete_bid(bid, collection):
    return bid["amount"] - get_collection_count(collection, bid["card_detail_id"], (("gold" in bid) and bid["gold"]))

def should_buy(bid, sale, collection):
    return Decimal(bid["bid_usd"]) >= Decimal(sale["buy_price"]) and cards_needed_to_complete_bid(bid, collection) > 0

def get_sorted_sales(card_detail_id, edition, gold):
    sales = []
    sale_data = get_sale_by_card(card_detail_id, edition, gold)
    for sale in sale_data:
        if sale['xp'] == 1: # level 1 only
            sales.append(sale)
    return sorted(sales, key = lambda sale: Decimal(sale["buy_price"]))

def buy_first(bids, owner, dry_run):
    collection = get_collection(owner)
    for bid in bids:
        for sale in get_sorted_sales(bid["card_detail_id"], bid["edition"], (("gold" in bid) and bid["gold"])):
            if should_buy(bid, sale, collection):
                execute_buy(sale, owner, dry_run)
                return True
        if cards_needed_to_complete_bid(bid, collection) <= 0:
            print("Limit order complete for bid %d, %d @ $%s" % (bid["card_detail_id"], bid["amount"], bid["bid_usd"]))
    return False

parser = argparse.ArgumentParser(description='Splinterlands limit order bid')
parser.add_argument('--dry', action='store_true', help='Do a dry run(do not buy)')
parser.add_argument('owner', type=str, help='Your ign(ex: buynano)')
args = parser.parse_args()

bids = json.loads(read_file_or_fail('bids.json', "Nothing to buy. Add a bids.json"))

while(True):
    if(not buy_first(bids, args.owner, args.dry)):
        print("Nothing found, sleeping.")
        time.sleep(60)
    else:
        time.sleep(1)

This python script will hunt for time mages at all times of the day and attempt to buy any it encounters under or at our bid price.

To explain, we check any dictionary in the bids array for orders. You can think of these as limit orders in an exchange. The NFT market is a sell only market. Anytime a time mage is listed for 0.25 or under you pull the trigger stopping when reaching 100.

Running the script

Install beem

If you have trouble refer to https://pypi.org/project/beem/

> pip install beem

Add active.key

Make a new file called active.key and place your hive active key for the wallet you want to use. This is your private key so don't share it with anyone and make sure it's not network accessible. If on linux you may consider chmod 0400.

Adjust bids.json to set your buy orders

Make sure you set your own buys. You can buy time mages if you want.

Running (dry)

> python bid.py [your ign] --dry

Running (live!)

We're ready to go and we tested! Let's pull the trigger.

> python bid.py [your ign]

Areas for improvement

We currently do not do bulk buys and we could improve efficiency. Additionally we could detect if we are out of DEC. We could add support for higher xp levels. This code was purposefully kept simple.

Using peakmonsters

These two links are really useful to see what happened(use your ign):

Liquidity

There is another issue here. Getting money in. The pairings are DEC pairings. DEC is currently unstable. Putting our $100 into DEC and having it drop to $50 would be problematic. For now we can either feed it manually on-demand or sit in credits.

Credits do not scale for larger budgets. For larger budgets what we want is on-demand liquidity.

If you have a larger budget and need on-demand liquidity contact me for consulting. I have something that works very well here.

About the author

buynano is a nickname pronounced 'boy-nano'(e.g. what's up boooyyynano!?!?').

buynano should not be confused with the cryptocurrency Nano(ticker XNO), which is a feeless, instant, and eco-friendly digital cash solution that can scale to the needs of the entire world.

Legal notice

buynano is not responsible for your monetary gains or losses and none of this constitutes financial advice.


Did you like this content? Comment, like, ask questions, report bugs below. If enough people are interested it will motivate me to continue this series.

H2
H3
H4
3 columns
2 columns
1 column
Join the conversation now
Logo
Center