basicswap/scripts/createoffers.py

929 lines
39 KiB
Python
Raw Normal View History

2023-01-08 22:53:59 +00:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2023-2024 tecnovert
2024-11-15 16:52:19 +00:00
# Copyright (c) 2024 The Basicswap developers
2023-01-08 22:53:59 +00:00
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
"""
Create offers
{
"min_seconds_between_offers": Add a random delay between creating offers between min and max, default 60.
"max_seconds_between_offers": ^, default "min_seconds_between_offers" * 4
"min_seconds_between_bids": Add a random delay between creating bids between min and max, default 60.
"max_seconds_between_bids": ^, default "min_seconds_between_bids" * 4
"wallet_port_override": Used for testing.
"offers": [
{
"name": Offer template name, eg "Offer 0", will be automatically renamed if not unique.
"coin_from": Coin you send.
"coin_to": Coin you receive.
"amount": Amount to create the offer for.
"minrate": Rate below which the offer won't drop.
"ratetweakpercent": modify the offer rate from the fetched value, can be negative.
"amount_variable": bool, bidder can set a different amount
"address": Address offer is sent from, default will generate a new address per offer.
"min_coin_from_amt": Won't generate offers if the wallet would drop below min_coin_from_amt.
"offer_valid_seconds": Seconds that the generated offers will be valid for.
# Optional
"enabled": Set to false to ignore offer template.
"swap_type": Type of swap, defaults to "adaptor_sig"
"min_swap_amount": Sets "amt_bid_min" on the offer, minimum valid bid when offer amount is variable.
"amount_step": If set offers will be created for amount values between "amount" and "min_coin_from_amt" in decrements of "amount_step".
},
...
],
"bids": [
{
"name": Bid template name, must be unique, eg "Bid 0", will be automatically renamed if not unique.
"coin_from": Coin you receive.
"coin_to": Coin you send.
"amount": amount to bid.
"max_rate": Maximum rate for bids.
"min_coin_to_balance": Won't send bids if wallet amount of "coin_to" would drop below.
# Optional
"enabled": Set to false to ignore bid template.
"max_concurrent": Maximum number of bids to have active at once, default 1.
"amount_variable": Can send bids below the set "amount" where possible if true.
"max_coin_from_balance": Won't send bids if wallet amount of "coin_from" would be above.
"address": Address offer is sent from, default will generate a new address per bid.
},
...
]
}
2023-01-08 22:53:59 +00:00
"""
2024-11-15 16:52:19 +00:00
__version__ = "0.2"
2023-01-08 22:53:59 +00:00
import os
import json
2023-02-14 21:34:01 +00:00
import time
import random
import shutil
2023-01-08 22:53:59 +00:00
import signal
import urllib
import logging
import argparse
import threading
from urllib.request import urlopen
delay_event = threading.Event()
2024-11-15 16:52:19 +00:00
DEFAULT_CONFIG_FILE: str = "createoffers.json"
DEFAULT_STATE_FILE: str = "createoffers_state.json"
2023-01-08 22:53:59 +00:00
2023-02-14 21:34:01 +00:00
def post_req(url: str, json_data=None):
2024-11-15 16:52:19 +00:00
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
2023-02-14 21:34:01 +00:00
if json_data:
2024-11-15 16:52:19 +00:00
req.add_header("Content-Type", "application/json; charset=utf-8")
post_bytes = json.dumps(json_data).encode("utf-8")
req.add_header("Content-Length", len(post_bytes))
2023-02-14 21:34:01 +00:00
else:
post_bytes = None
return urlopen(req, data=post_bytes, timeout=300).read()
2023-01-08 22:53:59 +00:00
2023-02-14 21:34:01 +00:00
def make_json_api_func(host: str, port: int):
host = host
port = port
def api_func(path=None, json_data=None, timeout=300):
nonlocal host, port
2024-11-15 16:52:19 +00:00
url = f"http://{host}:{port}/json"
2023-02-14 21:34:01 +00:00
if path is not None:
2024-11-15 16:52:19 +00:00
url += "/" + path
2023-02-14 21:34:01 +00:00
if json_data is not None:
return json.loads(post_req(url, json_data))
response = urlopen(url, timeout=300).read()
return json.loads(response)
2024-11-15 16:52:19 +00:00
2023-02-14 21:34:01 +00:00
return api_func
2023-01-08 22:53:59 +00:00
def signal_handler(sig, frame) -> None:
2024-11-15 16:52:19 +00:00
logging.info("Signal {} detected.".format(sig))
2023-01-08 22:53:59 +00:00
delay_event.set()
def findCoin(coin: str, known_coins) -> str:
for known_coin in known_coins:
2024-11-15 16:52:19 +00:00
if (
known_coin["name"].lower() == coin.lower()
or known_coin["ticker"].lower() == coin.lower()
):
if known_coin["active"] is False:
raise ValueError(f"Inactive coin {coin}")
return known_coin["name"]
raise ValueError(f"Unknown coin {coin}")
2023-01-08 22:53:59 +00:00
2023-02-14 21:34:01 +00:00
def readConfig(args, known_coins):
config_path: str = args.configfile
num_changes: int = 0
with open(config_path) as fs:
config = json.load(fs)
2024-11-15 16:52:19 +00:00
if "offers" not in config:
config["offers"] = []
if "bids" not in config:
config["bids"] = []
if "stealthex" not in config:
config["stealthex"] = []
2023-02-14 21:34:01 +00:00
2024-11-15 16:52:19 +00:00
if "min_seconds_between_offers" not in config:
config["min_seconds_between_offers"] = 60
print("Set min_seconds_between_offers", config["min_seconds_between_offers"])
2023-02-14 21:34:01 +00:00
num_changes += 1
2024-11-15 16:52:19 +00:00
if "max_seconds_between_offers" not in config:
config["max_seconds_between_offers"] = config["min_seconds_between_offers"] * 4
print("Set max_seconds_between_offers", config["max_seconds_between_offers"])
2023-02-14 21:34:01 +00:00
num_changes += 1
2024-11-15 16:52:19 +00:00
if "min_seconds_between_bids" not in config:
config["min_seconds_between_bids"] = 60
print("Set min_seconds_between_bids", config["min_seconds_between_bids"])
2023-02-14 21:34:01 +00:00
num_changes += 1
2024-11-15 16:52:19 +00:00
if "max_seconds_between_bids" not in config:
config["max_seconds_between_bids"] = config["min_seconds_between_bids"] * 4
print("Set max_seconds_between_bids", config["max_seconds_between_bids"])
2023-02-14 21:34:01 +00:00
num_changes += 1
2024-11-15 16:52:19 +00:00
offer_templates = config["offers"]
2023-02-14 21:34:01 +00:00
offer_templates_map = {}
num_enabled = 0
for i, offer_template in enumerate(offer_templates):
2024-11-15 16:52:19 +00:00
num_enabled += 1 if offer_template.get("enabled", True) else 0
if "name" not in offer_template:
print("Naming offer template", i)
offer_template["name"] = f"Offer {i}"
2023-02-14 21:34:01 +00:00
num_changes += 1
2024-11-15 16:52:19 +00:00
if offer_template["name"] in offer_templates_map:
print("Renaming offer template", offer_template["name"])
original_name = offer_template["name"]
2023-02-14 21:34:01 +00:00
offset = 2
2024-11-15 16:52:19 +00:00
while f"{original_name}_{offset}" in offer_templates_map:
2023-02-14 21:34:01 +00:00
offset += 1
2024-11-15 16:52:19 +00:00
offer_template["name"] = f"{original_name}_{offset}"
2023-02-14 21:34:01 +00:00
num_changes += 1
2024-11-15 16:52:19 +00:00
offer_templates_map[offer_template["name"]] = offer_template
2024-11-15 16:52:19 +00:00
if "amount_step" not in offer_template:
if offer_template.get("min_coin_from_amt", 0) < offer_template["amount"]:
print("Setting min_coin_from_amt for", offer_template["name"])
offer_template["min_coin_from_amt"] = offer_template["amount"]
num_changes += 1
else:
2024-11-15 16:52:19 +00:00
if "min_coin_from_amt" not in offer_template:
print("Setting min_coin_from_amt for", offer_template["name"])
offer_template["min_coin_from_amt"] = 0
num_changes += 1
2024-11-15 16:52:19 +00:00
if "address" not in offer_template:
print("Setting address to auto for offer", offer_template["name"])
offer_template["address"] = "auto"
num_changes += 1
2024-11-15 16:52:19 +00:00
if "ratetweakpercent" not in offer_template:
print("Setting ratetweakpercent to 0 for offer", offer_template["name"])
offer_template["ratetweakpercent"] = 0
num_changes += 1
2024-11-15 16:52:19 +00:00
if "amount_variable" not in offer_template:
print("Setting amount_variable to True for offer", offer_template["name"])
offer_template["amount_variable"] = True
num_changes += 1
2024-11-15 16:52:19 +00:00
if offer_template.get("enabled", True) is False:
continue
2024-11-15 16:52:19 +00:00
offer_template["coin_from"] = findCoin(offer_template["coin_from"], known_coins)
offer_template["coin_to"] = findCoin(offer_template["coin_to"], known_coins)
config["num_enabled_offers"] = num_enabled
2023-02-14 21:34:01 +00:00
2024-11-15 16:52:19 +00:00
bid_templates = config["bids"]
2023-02-14 21:34:01 +00:00
bid_templates_map = {}
num_enabled = 0
for i, bid_template in enumerate(bid_templates):
2024-11-15 16:52:19 +00:00
num_enabled += 1 if bid_template.get("enabled", True) else 0
if "name" not in bid_template:
print("Naming bid template", i)
bid_template["name"] = f"Bid {i}"
2023-02-14 21:34:01 +00:00
num_changes += 1
2024-11-15 16:52:19 +00:00
if bid_template["name"] in bid_templates_map:
print("Renaming bid template", bid_template["name"])
original_name = bid_template["name"]
2023-02-14 21:34:01 +00:00
offset = 2
2024-11-15 16:52:19 +00:00
while f"{original_name}_{offset}" in bid_templates_map:
2023-02-14 21:34:01 +00:00
offset += 1
2024-11-15 16:52:19 +00:00
bid_template["name"] = f"{original_name}_{offset}"
2023-02-14 21:34:01 +00:00
num_changes += 1
2024-11-15 16:52:19 +00:00
bid_templates_map[bid_template["name"]] = bid_template
if bid_template.get("min_swap_amount", 0.0) < 0.001:
2024-11-15 16:52:19 +00:00
print("Setting min_swap_amount for bid template", bid_template["name"])
bid_template["min_swap_amount"] = 0.001
2024-11-15 16:52:19 +00:00
if "address" not in bid_template:
print("Setting address to auto for bid", bid_template["name"])
bid_template["address"] = "auto"
num_changes += 1
2024-11-15 16:52:19 +00:00
if bid_template.get("enabled", True) is False:
continue
2024-11-15 16:52:19 +00:00
bid_template["coin_from"] = findCoin(bid_template["coin_from"], known_coins)
bid_template["coin_to"] = findCoin(bid_template["coin_to"], known_coins)
config["num_enabled_bids"] = num_enabled
2023-02-14 21:34:01 +00:00
num_enabled = 0
2024-11-15 16:52:19 +00:00
stealthex_swaps = config["stealthex"]
2023-02-14 21:34:01 +00:00
for i, swap in enumerate(stealthex_swaps):
2024-11-15 16:52:19 +00:00
num_enabled += 1 if swap.get("enabled", True) else 0
if swap.get("enabled", True) is False:
continue
2024-11-15 16:52:19 +00:00
swap["coin_from"] = findCoin(swap["coin_from"], known_coins)
config["num_enabled_swaps"] = num_enabled
2023-02-14 21:34:01 +00:00
if num_changes > 0:
2024-11-15 16:52:19 +00:00
shutil.copyfile(config_path, config_path + ".last")
with open(config_path, "w") as fp:
2023-02-14 21:34:01 +00:00
json.dump(config, fp, indent=4)
return config
2023-01-08 22:53:59 +00:00
2023-02-21 10:39:45 +00:00
def write_state(statefile, script_state):
if os.path.exists(statefile):
2024-11-15 16:52:19 +00:00
shutil.copyfile(statefile, statefile + ".last")
with open(statefile, "w") as fp:
2023-02-21 10:39:45 +00:00
json.dump(script_state, fp, indent=4)
2023-01-08 22:53:59 +00:00
def main():
parser = argparse.ArgumentParser(description=__doc__)
2024-11-15 16:52:19 +00:00
parser.add_argument(
"-v",
"--version",
action="version",
version="%(prog)s {version}".format(version=__version__),
)
parser.add_argument(
"--host",
dest="host",
help="RPC host (default=127.0.0.1)",
type=str,
default="127.0.0.1",
required=False,
)
parser.add_argument(
"--port",
dest="port",
help="RPC port (default=12700)",
type=int,
default=12700,
required=False,
)
parser.add_argument(
"--oneshot",
dest="oneshot",
help="Exit after one iteration (default=false)",
required=False,
action="store_true",
)
parser.add_argument(
"--debug",
dest="debug",
help="Print extra debug messages (default=false)",
required=False,
action="store_true",
)
parser.add_argument(
"--configfile",
dest="configfile",
help=f"config file path (default={DEFAULT_CONFIG_FILE})",
type=str,
default=DEFAULT_CONFIG_FILE,
required=False,
)
parser.add_argument(
"--statefile",
dest="statefile",
help=f"state file path (default={DEFAULT_STATE_FILE})",
type=str,
default=DEFAULT_STATE_FILE,
required=False,
)
2023-01-08 22:53:59 +00:00
args = parser.parse_args()
2023-02-14 21:34:01 +00:00
read_json_api = make_json_api_func(args.host, args.port)
if not os.path.exists(args.configfile):
raise ValueError(f'Config file "{args.configfile}" not found.')
2023-01-08 22:53:59 +00:00
2024-11-15 16:52:19 +00:00
known_coins = read_json_api("coins")
2023-01-08 22:53:59 +00:00
coins_map = {}
for known_coin in known_coins:
2024-11-15 16:52:19 +00:00
coins_map[known_coin["name"]] = known_coin
2023-01-08 22:53:59 +00:00
2023-02-14 21:34:01 +00:00
script_state = {}
if os.path.exists(args.statefile):
with open(args.statefile) as fs:
script_state = json.load(fs)
2023-01-08 22:53:59 +00:00
signal.signal(signal.SIGINT, signal_handler)
while not delay_event.is_set():
2023-02-14 21:34:01 +00:00
# Read config each iteration so they can be modified without restarting
config = readConfig(args, known_coins)
2024-11-15 16:52:19 +00:00
offer_templates = config["offers"]
2023-02-14 21:34:01 +00:00
random.shuffle(offer_templates)
2024-11-15 16:52:19 +00:00
bid_templates = config["bids"]
2023-02-14 21:34:01 +00:00
random.shuffle(bid_templates)
2024-11-15 16:52:19 +00:00
stealthex_swaps = config["stealthex"]
2023-02-14 21:34:01 +00:00
random.shuffle(bid_templates)
# override wallet api calls for testing
2024-11-15 16:52:19 +00:00
if "wallet_port_override" in config:
wallet_api_port = int(config["wallet_port_override"])
print(f"Overriding wallet api port: {wallet_api_port}")
2023-02-14 21:34:01 +00:00
read_json_api_wallet = make_json_api_func(args.host, wallet_api_port)
else:
read_json_api_wallet = read_json_api
2023-01-08 22:53:59 +00:00
try:
2024-11-15 16:52:19 +00:00
sent_offers = read_json_api("sentoffers", {"active": "active"})
2023-01-08 22:53:59 +00:00
2023-02-14 21:34:01 +00:00
if args.debug and len(offer_templates) > 0:
2024-11-15 16:52:19 +00:00
print(
"Processing {} offer template{}".format(
config["num_enabled_offers"],
"s" if config["num_enabled_offers"] != 1 else "",
)
)
2023-01-08 22:53:59 +00:00
for offer_template in offer_templates:
2024-11-15 16:52:19 +00:00
if offer_template.get("enabled", True) is False:
continue
2023-01-08 22:53:59 +00:00
offers_found = 0
2023-02-14 21:34:01 +00:00
2024-11-15 16:52:19 +00:00
coin_from_data = coins_map[offer_template["coin_from"]]
coin_to_data = coins_map[offer_template["coin_to"]]
2023-02-14 21:34:01 +00:00
2024-11-15 16:52:19 +00:00
wallet_from = read_json_api_wallet(
"wallets/{}".format(coin_from_data["ticker"])
)
2023-02-14 21:34:01 +00:00
2023-01-08 22:53:59 +00:00
for offer in sent_offers:
2024-11-15 16:52:19 +00:00
created_offers = script_state.get("offers", {})
prev_template_offers = created_offers.get(
offer_template["name"], {}
)
if next(
(
x
for x in prev_template_offers
if x["offer_id"] == offer["offer_id"]
),
None,
):
2023-01-08 22:53:59 +00:00
offers_found += 1
2024-11-15 16:52:19 +00:00
if float(wallet_from["balance"]) <= float(
offer_template["min_coin_from_amt"]
):
offer_id = offer["offer_id"]
print(
"Revoking offer {}, wallet from balance below minimum".format(
offer_id
)
)
result = read_json_api(f"revokeoffer/{offer_id}")
print("revokeoffer", result)
2023-01-08 22:53:59 +00:00
if offers_found > 0:
continue
2024-11-15 16:52:19 +00:00
max_offer_amount: float = offer_template["amount"]
min_offer_amount: float = offer_template.get(
"amount_step", max_offer_amount
)
wallet_balance: float = float(wallet_from["balance"])
min_wallet_from_amount: float = float(
offer_template["min_coin_from_amt"]
)
if wallet_balance - min_offer_amount <= min_wallet_from_amount:
2024-11-15 16:52:19 +00:00
print(
"Skipping template {}, wallet from balance below minimum".format(
offer_template["name"]
)
)
2023-02-14 21:34:01 +00:00
continue
offer_amount: float = max_offer_amount
if wallet_balance - max_offer_amount <= min_wallet_from_amount:
available_balance: float = wallet_balance - min_wallet_from_amount
min_steps: int = available_balance // min_offer_amount
2024-11-15 16:52:19 +00:00
assert min_steps > 0 # Should not be possible, checked above
offer_amount = min_offer_amount * min_steps
2024-11-15 16:52:19 +00:00
delay_next_offer_before = script_state.get("delay_next_offer_before", 0)
2023-02-14 21:34:01 +00:00
if delay_next_offer_before > int(time.time()):
2024-11-15 16:52:19 +00:00
print("Delaying offers until {}".format(delay_next_offer_before))
2023-02-14 21:34:01 +00:00
break
"""
received_offers = read_json_api(args.port, 'offers', {'active': 'active', 'include_sent': False, 'coin_from': coin_from_data['id'], 'coin_to': coin_to_data['id']})
print('received_offers', received_offers)
2023-02-14 21:34:01 +00:00
2023-11-09 20:28:01 +00:00
TODO - adjust rates based on existing offers
2023-02-14 21:34:01 +00:00
"""
2024-11-15 16:52:19 +00:00
rates = read_json_api(
"rates",
{"coin_from": coin_from_data["id"], "coin_to": coin_to_data["id"]},
)
print("Rates", rates)
coingecko_rate = float(rates["coingecko"]["rate_inferred"])
2023-01-08 22:53:59 +00:00
use_rate = coingecko_rate
2024-11-15 16:52:19 +00:00
if offer_template["ratetweakpercent"] != 0:
print(
"Adjusting rate {} by {}%.".format(
use_rate, offer_template["ratetweakpercent"]
)
)
tweak = offer_template["ratetweakpercent"] / 100.0
2023-01-08 22:53:59 +00:00
use_rate += use_rate * tweak
2024-11-15 16:52:19 +00:00
if use_rate < offer_template["minrate"]:
print("Warning: Clamping rate to minimum.")
use_rate = offer_template["minrate"]
2023-01-08 22:53:59 +00:00
2024-11-15 16:52:19 +00:00
print(
"Creating offer for: {} at rate: {}".format(
offer_template, use_rate
)
)
template_from_addr = offer_template["address"]
2023-01-08 22:53:59 +00:00
offer_data = {
2024-11-15 16:52:19 +00:00
"addr_from": (
-1 if template_from_addr == "auto" else template_from_addr
),
"coin_from": coin_from_data["ticker"],
"coin_to": coin_to_data["ticker"],
"amt_from": offer_amount,
"amt_var": offer_template["amount_variable"],
"valid_for_seconds": offer_template.get(
"offer_valid_seconds", config.get("offer_valid_seconds", 3600)
),
"rate": use_rate,
"swap_type": offer_template.get("swap_type", "adaptor_sig"),
"lockhrs": "24",
"automation_strat_id": 1,
}
if "min_swap_amount" in offer_template:
offer_data["amt_bid_min"] = offer_template["min_swap_amount"]
2023-02-14 21:34:01 +00:00
if args.debug:
2024-11-15 16:52:19 +00:00
print("offer data {}".format(offer_data))
new_offer = read_json_api("offers/new", offer_data)
if "error" in new_offer:
raise ValueError(
"Server failed to create offer: {}".format(new_offer["error"])
)
print("New offer: {}".format(new_offer["offer_id"]))
if "offers" not in script_state:
script_state["offers"] = {}
template_name = offer_template["name"]
if template_name not in script_state["offers"]:
script_state["offers"][template_name] = []
script_state["offers"][template_name].append(
{"offer_id": new_offer["offer_id"], "time": int(time.time())}
)
max_seconds_between_offers = config["max_seconds_between_offers"]
min_seconds_between_offers = config["min_seconds_between_offers"]
time_between_offers = min_seconds_between_offers
2023-02-14 21:34:01 +00:00
if max_seconds_between_offers > min_seconds_between_offers:
2024-11-15 16:52:19 +00:00
time_between_offers = random.randint(
min_seconds_between_offers, max_seconds_between_offers
)
2024-11-15 16:52:19 +00:00
script_state["delay_next_offer_before"] = (
int(time.time()) + time_between_offers
)
2023-02-21 10:39:45 +00:00
write_state(args.statefile, script_state)
2023-02-14 21:34:01 +00:00
if args.debug and len(bid_templates) > 0:
2024-11-15 16:52:19 +00:00
print(
"Processing {} bid template{}".format(
config["num_enabled_bids"],
"s" if config["num_enabled_bids"] != 1 else "",
)
)
2023-02-14 21:34:01 +00:00
for bid_template in bid_templates:
2024-11-15 16:52:19 +00:00
if bid_template.get("enabled", True) is False:
continue
2024-11-15 16:52:19 +00:00
delay_next_bid_before = script_state.get("delay_next_bid_before", 0)
2023-02-14 21:34:01 +00:00
if delay_next_bid_before > int(time.time()):
2024-11-15 16:52:19 +00:00
print("Delaying bids until {}".format(delay_next_bid_before))
2023-02-14 21:34:01 +00:00
break
# Check bids in progress
2024-11-15 16:52:19 +00:00
max_concurrent = bid_template.get("max_concurrent", 1)
if "bids" not in script_state:
script_state["bids"] = {}
template_name = bid_template["name"]
if template_name not in script_state["bids"]:
script_state["bids"][template_name] = []
previous_bids = script_state["bids"][template_name]
2023-02-14 21:34:01 +00:00
bids_in_progress: int = 0
for previous_bid in previous_bids:
2024-11-15 16:52:19 +00:00
if not previous_bid["active"]:
2023-02-14 21:34:01 +00:00
continue
2024-11-15 16:52:19 +00:00
previous_bid_id = previous_bid["bid_id"]
previous_bid_info = read_json_api(f"bids/{previous_bid_id}")
bid_state = previous_bid_info["bid_state"]
if bid_state in (
"Completed",
"Timed-out",
"Abandoned",
"Error",
"Rejected",
):
print(
f"Marking bid inactive {previous_bid_id}, state {bid_state}"
)
previous_bid["active"] = False
2023-02-21 10:39:45 +00:00
write_state(args.statefile, script_state)
2023-02-14 21:34:01 +00:00
continue
2024-11-15 16:52:19 +00:00
if bid_state in ("Sent", "Received") and previous_bid_info[
"expired_at"
] < int(time.time()):
print(f"Marking bid inactive {previous_bid_id}, expired")
previous_bid["active"] = False
2023-02-21 10:39:45 +00:00
write_state(args.statefile, script_state)
2023-02-14 21:34:01 +00:00
continue
bids_in_progress += 1
if bids_in_progress >= max_concurrent:
2024-11-15 16:52:19 +00:00
print("Max concurrent bids reached for template")
2023-02-14 21:34:01 +00:00
continue
# Bidder sends coin_to and receives coin_from
2024-11-15 16:52:19 +00:00
coin_from_data = coins_map[bid_template["coin_from"]]
coin_to_data = coins_map[bid_template["coin_to"]]
2023-02-14 21:34:01 +00:00
page_limit: int = 25
2023-02-14 21:34:01 +00:00
offers_options = {
2024-11-15 16:52:19 +00:00
"active": "active",
"include_sent": False,
"coin_from": coin_from_data["id"],
"coin_to": coin_to_data["id"],
"with_extra_info": True,
"sort_by": "rate",
"sort_dir": "asc",
"offset": 0,
"limit": page_limit,
2023-02-14 21:34:01 +00:00
}
received_offers = []
for i in range(1000000): # for i in itertools.count()
2024-11-15 16:52:19 +00:00
page_offers = read_json_api("offers", offers_options)
if len(page_offers) < 1:
break
received_offers += page_offers
2024-11-15 16:52:19 +00:00
offers_options["offset"] = offers_options["offset"] + page_limit
if i > 100:
2024-11-15 16:52:19 +00:00
print(f"Warning: Broke offers loop at: {i}")
break
if args.debug:
2024-11-15 16:52:19 +00:00
print("Received Offers", received_offers)
2023-02-14 21:34:01 +00:00
for offer in received_offers:
2024-11-15 16:52:19 +00:00
offer_id = offer["offer_id"]
offer_amount = float(offer["amount_from"])
offer_rate = float(offer["rate"])
bid_amount = bid_template["amount"]
min_swap_amount = bid_template.get(
"min_swap_amount", 0.01
) # TODO: Make default vary per coin
can_adjust_offer_amount: bool = offer["amount_negotiable"]
can_adjust_bid_amount: bool = bid_template.get(
"amount_variable", True
)
can_adjust_amount: bool = (
can_adjust_offer_amount and can_adjust_bid_amount
)
if offer_amount < min_swap_amount:
2023-02-14 21:34:01 +00:00
if args.debug:
2024-11-15 16:52:19 +00:00
print(f"Offer amount below min swap amount bid {offer_id}")
2023-02-14 21:34:01 +00:00
continue
if can_adjust_offer_amount is False and offer_amount > bid_amount:
2023-02-14 21:34:01 +00:00
if args.debug:
2024-11-15 16:52:19 +00:00
print(f"Bid amount too low for offer {offer_id}")
2023-02-14 21:34:01 +00:00
continue
if bid_amount > offer_amount:
if can_adjust_bid_amount:
bid_amount = offer_amount
else:
if args.debug:
2024-11-15 16:52:19 +00:00
print(f"Bid amount too high for offer {offer_id}")
continue
2024-11-15 16:52:19 +00:00
if offer_rate > bid_template["maxrate"]:
2023-02-14 21:34:01 +00:00
if args.debug:
2024-11-15 16:52:19 +00:00
print(f"Bid rate too low for offer {offer_id}")
2023-02-14 21:34:01 +00:00
continue
2024-11-15 16:52:19 +00:00
sent_bids = read_json_api(
"sentbids",
{
"offer_id": offer["offer_id"],
"with_available_or_active": True,
},
)
2023-02-14 21:34:01 +00:00
if len(sent_bids) > 0:
if args.debug:
2024-11-15 16:52:19 +00:00
print(f"Already bidding on offer {offer_id}")
2023-02-14 21:34:01 +00:00
continue
2024-11-15 16:52:19 +00:00
offer_identity = read_json_api(
"identities/{}".format(offer["addr_from"])
)
2023-02-14 21:34:01 +00:00
if len(offer_identity) > 0:
2023-02-15 21:51:55 +00:00
id_offer_from = offer_identity[0]
2024-11-15 16:52:19 +00:00
automation_override = id_offer_from["automation_override"]
2023-02-15 21:51:55 +00:00
if automation_override == 2:
2023-02-14 21:34:01 +00:00
if args.debug:
2024-11-15 16:52:19 +00:00
print(
f"Not bidding on offer {offer_id}, automation_override ({automation_override})."
)
2023-02-14 21:34:01 +00:00
continue
2023-02-15 21:51:55 +00:00
if automation_override == 1:
if args.debug:
2024-11-15 16:52:19 +00:00
print(
"Offer address from {}, set to always accept.".format(
offer["addr_from"]
)
)
2023-02-15 21:51:55 +00:00
else:
2024-11-15 16:52:19 +00:00
successful_sent_bids = id_offer_from[
"num_sent_bids_successful"
]
failed_sent_bids = id_offer_from["num_sent_bids_failed"]
if (
failed_sent_bids > 3
and failed_sent_bids > successful_sent_bids
):
2023-02-15 21:51:55 +00:00
if args.debug:
2024-11-15 16:52:19 +00:00
print(
f"Not bidding on offer {offer_id}, too many failed bids ({failed_sent_bids})."
)
2023-02-15 21:51:55 +00:00
continue
2023-02-14 21:34:01 +00:00
2024-02-16 22:01:28 +00:00
validateamount: bool = False
2024-11-15 16:52:19 +00:00
max_coin_from_balance = bid_template.get(
"max_coin_from_balance", -1
)
2023-02-14 21:34:01 +00:00
if max_coin_from_balance > 0:
2024-11-15 16:52:19 +00:00
wallet_from = read_json_api_wallet(
"wallets/{}".format(coin_from_data["ticker"])
)
total_balance_from = float(wallet_from["balance"]) + float(
wallet_from["unconfirmed"]
)
2023-02-14 21:34:01 +00:00
if args.debug:
2024-11-15 16:52:19 +00:00
print(f"Total coin from balance {total_balance_from}")
2023-02-14 21:34:01 +00:00
if total_balance_from + bid_amount > max_coin_from_balance:
2024-11-15 16:52:19 +00:00
if (
can_adjust_amount
and max_coin_from_balance - total_balance_from
> min_swap_amount
):
2023-02-14 21:34:01 +00:00
bid_amount = max_coin_from_balance - total_balance_from
2024-02-16 22:01:28 +00:00
validateamount = True
2024-11-15 16:52:19 +00:00
print(f"Reduced bid amount to {bid_amount}")
2023-02-14 21:34:01 +00:00
else:
if args.debug:
2024-11-15 16:52:19 +00:00
print(
f"Bid amount would exceed maximum wallet total for offer {offer_id}"
)
2023-02-14 21:34:01 +00:00
continue
2024-11-15 16:52:19 +00:00
min_coin_to_balance = bid_template["min_coin_to_balance"]
2023-02-14 21:34:01 +00:00
if min_coin_to_balance > 0:
2024-11-15 16:52:19 +00:00
wallet_to = read_json_api_wallet(
"wallets/{}".format(coin_to_data["ticker"])
)
2023-02-14 21:34:01 +00:00
2024-11-15 16:52:19 +00:00
total_balance_to = float(wallet_to["balance"]) + float(
wallet_to["unconfirmed"]
)
2023-02-14 21:34:01 +00:00
if args.debug:
2024-11-15 16:52:19 +00:00
print(f"Total coin to balance {total_balance_to}")
2023-02-14 21:34:01 +00:00
swap_amount_to = bid_amount * offer_rate
if total_balance_to - swap_amount_to < min_coin_to_balance:
if can_adjust_amount:
2024-11-15 16:52:19 +00:00
adjusted_swap_amount_to = (
total_balance_to - min_coin_to_balance
)
adjusted_bid_amount = (
adjusted_swap_amount_to / offer_rate
)
2023-02-14 21:34:01 +00:00
if adjusted_bid_amount > min_swap_amount:
bid_amount = adjusted_bid_amount
2024-02-16 22:01:28 +00:00
validateamount = True
2024-11-15 16:52:19 +00:00
print(f"Reduced bid amount to {bid_amount}")
2023-02-14 21:34:01 +00:00
swap_amount_to = adjusted_bid_amount * offer_rate
if total_balance_to - swap_amount_to < min_coin_to_balance:
if args.debug:
2024-11-15 16:52:19 +00:00
print(
f"Bid amount would exceed minimum coin to wallet total for offer {offer_id}"
)
2023-02-14 21:34:01 +00:00
continue
2024-02-16 22:01:28 +00:00
if validateamount:
2024-11-15 16:52:19 +00:00
bid_amount = read_json_api(
"validateamount",
{
"coin": coin_from_data["ticker"],
"amount": bid_amount,
"method": "rounddown",
},
)
2023-02-14 21:34:01 +00:00
bid_data = {
2024-11-15 16:52:19 +00:00
"offer_id": offer["offer_id"],
"amount_from": bid_amount,
}
2023-02-14 21:34:01 +00:00
2024-11-15 16:52:19 +00:00
if "address" in bid_template:
addr_from = bid_template["address"]
if addr_from != -1 and addr_from != "auto":
bid_data["addr_from"] = addr_from
2023-02-14 21:34:01 +00:00
2024-11-15 16:52:19 +00:00
if config.get("test_mode", False):
print("Would create bid: {}".format(bid_data))
bid_id = "simulated"
2023-02-14 21:34:01 +00:00
else:
if args.debug:
2024-11-15 16:52:19 +00:00
print("Creating bid: {}".format(bid_data))
new_bid = read_json_api("bids/new", bid_data)
if "error" in new_bid:
raise ValueError(
"Server failed to create bid: {}".format(
new_bid["error"]
)
)
print(
"New bid: {} on offer {}".format(
new_bid["bid_id"], offer["offer_id"]
)
)
bid_id = new_bid["bid_id"]
script_state["bids"][template_name].append(
{"bid_id": bid_id, "time": int(time.time()), "active": True}
)
max_seconds_between_bids = config["max_seconds_between_bids"]
min_seconds_between_bids = config["min_seconds_between_bids"]
2023-02-14 21:34:01 +00:00
if max_seconds_between_bids > min_seconds_between_bids:
2024-11-15 16:52:19 +00:00
time_between_bids = random.randint(
min_seconds_between_bids, max_seconds_between_bids
)
2023-02-14 21:34:01 +00:00
else:
time_between_bids = min_seconds_between_bids
2024-11-15 16:52:19 +00:00
script_state["delay_next_bid_before"] = (
int(time.time()) + time_between_bids
)
2023-02-21 10:39:45 +00:00
write_state(args.statefile, script_state)
2023-02-14 21:34:01 +00:00
break # Create max one bid per iteration
if args.debug and len(stealthex_swaps) > 0:
2024-11-15 16:52:19 +00:00
print(
"Processing {} stealthex template{}".format(
config["num_enabled_swaps"],
"s" if config["num_enabled_swaps"] != 1 else "",
)
)
2023-02-14 21:34:01 +00:00
for stealthex_swap in stealthex_swaps:
2024-11-15 16:52:19 +00:00
if stealthex_swap.get("enabled", True) is False:
2023-02-14 21:34:01 +00:00
continue
2024-11-15 16:52:19 +00:00
coin_from_data = coins_map[stealthex_swap["coin_from"]]
2023-02-14 21:34:01 +00:00
2024-11-15 16:52:19 +00:00
wallet_from = read_json_api_wallet(
"wallets/{}".format(coin_from_data["ticker"])
)
2023-02-14 21:34:01 +00:00
2024-11-15 16:52:19 +00:00
current_balance = float(wallet_from["balance"])
2023-02-14 21:34:01 +00:00
2024-11-15 16:52:19 +00:00
min_balance_from = float(stealthex_swap["min_balance_from"])
min_swap_amount = float(stealthex_swap["min_amount_tx"])
max_swap_amount = float(stealthex_swap["max_amount_tx"])
2023-02-14 21:34:01 +00:00
# TODO: Check range limits
if current_balance >= min_balance_from + min_swap_amount:
swap_amount = max_swap_amount
if current_balance - swap_amount < min_balance_from:
2024-11-15 16:52:19 +00:00
swap_amount = max(
min_swap_amount, current_balance - min_balance_from
)
estimate_url = "https://api.stealthex.io/api/v2/estimate/{}/{}?amount={}&api_key={}&fixed=true".format(
coin_from_data["ticker"].lower(),
stealthex_swap["coin_to"].lower(),
swap_amount,
stealthex_swap["api_key"],
)
2023-02-14 21:34:01 +00:00
if args.debug:
2024-11-15 16:52:19 +00:00
print(f"Estimate URL: {estimate_url}")
2023-02-14 21:34:01 +00:00
estimate_response = json.loads(post_req(estimate_url))
2024-11-15 16:52:19 +00:00
amount_to = float(estimate_response["estimated_amount"])
2023-02-14 21:34:01 +00:00
rate = swap_amount / amount_to
2024-11-15 16:52:19 +00:00
min_rate = float(stealthex_swap["min_rate"])
2023-02-14 21:34:01 +00:00
if rate < min_rate:
if args.debug:
2024-11-15 16:52:19 +00:00
print(
"Stealthex rate {} below minimum {} for {} to {}".format(
rate,
min_rate,
coin_from_data["ticker"],
stealthex_swap["coin_to"],
)
)
2023-02-14 21:34:01 +00:00
continue
2024-11-15 16:52:19 +00:00
exchange_url = (
"https://api.stealthex.io/api/v2/exchange?api_key={}".format(
stealthex_swap["api_key"]
)
)
address_to = stealthex_swap.get("receive_address", "auto")
if address_to == "auto":
address_to = read_json_api(
"wallets/{}/nextdepositaddr".format(
stealthex_swap["coin_to"]
)
)
address_refund = stealthex_swap.get("refund_address", "auto")
if address_refund == "auto":
address_refund = read_json_api(
"wallets/{}/nextdepositaddr".format(
coin_from_data["ticker"]
)
)
2023-02-14 21:34:01 +00:00
exchange_data = {
2024-11-15 16:52:19 +00:00
"currency_from": coin_from_data["ticker"].lower(),
"currency_to": stealthex_swap["coin_to"].lower(),
"address_to": address_to,
"amount_from": swap_amount,
"fixed": True,
2023-02-15 21:51:55 +00:00
# 'extra_id_to':
# 'referral':
2024-11-15 16:52:19 +00:00
"refund_address": address_refund,
2023-02-15 21:51:55 +00:00
# 'refund_extra_id':
2024-11-15 16:52:19 +00:00
"rate_id": estimate_response["rate_id"],
2023-02-14 21:34:01 +00:00
}
if args.debug:
2024-11-15 16:52:19 +00:00
print(f"Exchange URL: {estimate_url}")
print(f"Exchange data: {exchange_data}")
2023-02-14 21:34:01 +00:00
2024-11-15 16:52:19 +00:00
exchange_response = json.loads(
post_req(exchange_url, exchange_data)
)
2023-02-14 21:34:01 +00:00
2024-11-15 16:52:19 +00:00
if "Error" in exchange_response:
raise ValueError("Exchange error " + exchange_response)
2023-02-14 21:34:01 +00:00
2024-11-15 16:52:19 +00:00
raise ValueError("TODO")
2023-02-14 21:34:01 +00:00
2023-01-08 22:53:59 +00:00
except Exception as e:
2024-11-15 16:52:19 +00:00
print(f"Error: {e}.")
2023-01-08 22:53:59 +00:00
2023-02-14 21:34:01 +00:00
if args.oneshot:
break
2024-11-15 16:52:19 +00:00
print("Looping indefinitely, ctrl+c to exit.")
2023-01-08 22:53:59 +00:00
delay_event.wait(60)
2024-11-15 16:52:19 +00:00
print("Done.")
2023-01-08 22:53:59 +00:00
2024-11-15 16:52:19 +00:00
if __name__ == "__main__":
2023-01-08 22:53:59 +00:00
main()